io.undertow.server.handlers.resource.PathResource Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including
all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and
JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
The newest version!
package io.undertow.server.handlers.resource;
import io.undertow.UndertowLogger;
import io.undertow.io.IoCallback;
import io.undertow.io.Sender;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.DateUtils;
import io.undertow.util.ETag;
import io.undertow.util.MimeMappings;
import io.undertow.util.StatusCodes;
import org.xnio.IoUtils;
import io.undertow.connector.PooledByteBuffer;
import java.io.File;
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.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* A path resource
*
* @author Stuart Douglas
*/
public class PathResource implements RangeAwareResource {
private final Path file;
private final String path;
private final ETag eTag;
private final PathResourceManager manager;
public PathResource(final Path file, final PathResourceManager manager, String path, ETag eTag) {
this.file = file;
this.path = path;
this.manager = manager;
this.eTag = eTag;
}
public PathResource(final Path file, final PathResourceManager manager, String path) {
this(file, manager, path, null);
}
@Override
public String getPath() {
return path;
}
@Override
public Date getLastModified() {
try {
if (Files.isSymbolicLink(file) && Files.notExists(file)) {
return null;
}
return new Date(Files.getLastModifiedTime(file).toMillis());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public String getLastModifiedString() {
return DateUtils.toDateString(getLastModified());
}
@Override
public ETag getETag() {
return eTag;
}
@Override
public String getName() {
if( file.getFileName() != null ) {
return file.getFileName().toString();
}
return file.toString();
}
@Override
public boolean isDirectory() {
return Files.isDirectory(file);
}
@Override
public List list() {
final List resources = new ArrayList<>();
try (DirectoryStream stream = Files.newDirectoryStream(file)) {
if(stream != null) {
for (Path child : stream) {
resources.add(new PathResource(child, manager, path + file.getFileSystem().getSeparator() + child.getFileName().toString()));
}
} else {
UndertowLogger.ROOT_LOGGER.failedToListPathsForFile(file);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return resources;
}
@Override
public String getContentType(final MimeMappings mimeMappings) {
final String fileName = file.getFileName().toString();
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) {
serveImpl(sender, exchange, -1, -1, callback, false);
}
@Override
public void serveRange(final Sender sender, final HttpServerExchange exchange, final long start, final long end, final IoCallback callback) {
serveImpl(sender, exchange, start, end, callback, true);
}
private void serveImpl(final Sender sender, final HttpServerExchange exchange, final long start, final long end, final IoCallback callback, final boolean range) {
abstract class BaseFileTask implements Runnable {
protected volatile FileChannel fileChannel;
protected boolean openFile() {
try {
fileChannel = FileChannel.open(file, StandardOpenOption.READ);
if(range) {
fileChannel.position(start);
}
} catch (NoSuchFileException e) {
exchange.setStatusCode(StatusCodes.NOT_FOUND);
callback.onException(exchange, sender, e);
return false;
} catch (IOException e) {
exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR);
callback.onException(exchange, sender, e);
return false;
}
return true;
}
}
class ServerTask extends BaseFileTask implements IoCallback {
private PooledByteBuffer pooled;
long remaining = end - start + 1;
@Override
public void run() {
if(range && remaining == 0) {
//we are done
if (pooled != null) {
pooled.close();
pooled = null;
}
IoUtils.safeClose(fileChannel);
callback.onComplete(exchange, sender);
return;
}
if (fileChannel == null) {
if (!openFile()) {
return;
}
pooled = exchange.getConnection().getByteBufferPool().allocate();
}
if (pooled != null) {
ByteBuffer buffer = pooled.getBuffer();
try {
buffer.clear();
int res = fileChannel.read(buffer);
if (res == -1) {
//we are done
pooled.close();
IoUtils.safeClose(fileChannel);
callback.onComplete(exchange, sender);
return;
}
buffer.flip();
if(range) {
if(buffer.remaining() > remaining) {
buffer.limit((int) (buffer.position() + remaining));
}
remaining -= buffer.remaining();
}
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.close();
pooled = null;
}
IoUtils.safeClose(fileChannel);
if (!exchange.isResponseStarted()) {
exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR);
}
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;
try {
task = manager.getTransferMinSize() > Files.size(file) || range ? new ServerTask() : new TransferTask();
} catch (IOException e) {
throw new RuntimeException(e);
}
if (exchange.isInIoThread()) {
exchange.dispatch(task);
} else {
task.run();
}
}
@Override
public Long getContentLength() {
try {
if (Files.isSymbolicLink(file) && Files.notExists(file)) {
return null;
}
return Files.size(file);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public String getCacheKey() {
return file.toString();
}
@Override
public File getFile() {
return file.toFile();
}
@Override
public Path getFilePath() {
return file;
}
@Override
public File getResourceManagerRoot() {
return manager.getBasePath().toFile();
}
@Override
public Path getResourceManagerRootPath() {
return manager.getBasePath();
}
@Override
public URL getUrl() {
try {
return file.toUri().toURL();
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
@Override
public boolean isRangeSupported() {
return true;
}
}