/* * JBoss, Home of Professional Open Source. * Copyright 2017, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.wildfly.extension.undertow.deployment; import io.undertow.UndertowLogger; import io.undertow.io.IoCallback; import io.undertow.io.Sender; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.resource.Resource; import io.undertow.util.DateUtils; import io.undertow.util.ETag; import io.undertow.util.MimeMappings; import org.jboss.vfs.VirtualFile; import org.xnio.FileAccess; import org.xnio.IoUtils; import org.xnio.Pooled; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.file.Path; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * @author Stuart Douglas */ public class VirtualFileResource implements Resource { private final File resourceManagerRoot; private final VirtualFile file; private final String path; public VirtualFileResource(File resourceManagerRoot, final VirtualFile file, String path) { this.resourceManagerRoot = resourceManagerRoot; this.file = file; this.path = path; } @Override public String getPath() { return path; } @Override public Date getLastModified() { return new Date(file.getLastModified()); } @Override public String getLastModifiedString() { final Date lastModified = getLastModified(); if (lastModified == null) { return null; } return DateUtils.toDateString(lastModified); } @Override public ETag getETag() { return null; } @Override public String getName() { return file.getName(); } @Override public boolean isDirectory() { return file.isDirectory(); } @Override public List<Resource> list() { final List<Resource> resources = new ArrayList<Resource>(); for (VirtualFile child : file.getChildren()) { resources.add(new VirtualFileResource(resourceManagerRoot, child, path)); } return resources; } @Override public String getContentType(final MimeMappings mimeMappings) { final String fileName = file.getName(); int index = fileName.lastIndexOf('.'); if (index != -1 && index != fileName.length() - 1) { return mimeMappings.getMimeType(fileName.substring(index + 1)); } return null; } @Override public void serve(final Sender sender, final HttpServerExchange exchange, final IoCallback callback) { abstract class BaseFileTask implements Runnable { protected volatile FileChannel fileChannel; protected boolean openFile() { try { fileChannel = exchange.getConnection().getWorker().getXnio().openFile(file.getPhysicalFile(), FileAccess.READ_ONLY); } catch (FileNotFoundException e) { exchange.setResponseCode(404); callback.onException(exchange, sender, e); return false; } catch (IOException e) { exchange.setResponseCode(500); callback.onException(exchange, sender, e); return false; } return true; } } class ServerTask extends BaseFileTask implements IoCallback { private Pooled<ByteBuffer> pooled; @Override public void run() { if (fileChannel == null) { if (!openFile()) { return; } pooled = exchange.getConnection().getBufferPool().allocate(); } if (pooled != null) { ByteBuffer buffer = pooled.getResource(); try { buffer.clear(); int res = fileChannel.read(buffer); if (res == -1) { //we are done pooled.free(); IoUtils.safeClose(fileChannel); callback.onComplete(exchange, sender); return; } buffer.flip(); sender.send(buffer, this); } catch (IOException e) { onException(exchange, sender, e); } } } @Override public void onComplete(final HttpServerExchange exchange, final Sender sender) { if (exchange.isInIoThread()) { exchange.dispatch(this); } else { run(); } } @Override public void onException(final HttpServerExchange exchange, final Sender sender, final IOException exception) { UndertowLogger.REQUEST_IO_LOGGER.ioException(exception); if (pooled != null) { pooled.free(); pooled = null; } IoUtils.safeClose(fileChannel); if (!exchange.isResponseStarted()) { exchange.setResponseCode(500); } callback.onException(exchange, sender, exception); } } class TransferTask extends BaseFileTask { @Override public void run() { if (!openFile()) { return; } sender.transferFrom(fileChannel, new IoCallback() { @Override public void onComplete(HttpServerExchange exchange, Sender sender) { try { IoUtils.safeClose(fileChannel); } finally { callback.onComplete(exchange, sender); } } @Override public void onException(HttpServerExchange exchange, Sender sender, IOException exception) { try { IoUtils.safeClose(fileChannel); } finally { callback.onException(exchange, sender, exception); } } }); } } BaseFileTask task = new TransferTask(); if (exchange.isInIoThread()) { exchange.dispatch(task); } else { task.run(); } } @Override public Long getContentLength() { return file.getSize(); } @Override public String getCacheKey() { return file.toString(); } @Override public File getFile() { try { return file.getPhysicalFile(); } catch (IOException e) { throw new RuntimeException(e); } } @Override public File getResourceManagerRoot() { return resourceManagerRoot; } @Override public URL getUrl() { try { return file.toURL(); } catch (MalformedURLException e) { throw new RuntimeException(e); } } public Path getResourceManagerRootPath() { return getResourceManagerRoot().toPath(); } public Path getFilePath() { if(getFile() == null) { return null; } return getFile().toPath(); } }