apply an idle timeout to chunked put requests (as finder doesn't terminate chunked transfers properly) [ci skip]

This commit is contained in:
Sebastian Stenzel
2016-01-11 18:43:57 +01:00
parent 9c9e9769ee
commit 58b4905c91
2 changed files with 131 additions and 1 deletions
@@ -0,0 +1,128 @@
package org.cryptomator.webdav.filters;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
/**
* Wrapps the {@link ServletInputStream} into a timeout-aware stream, that returns EOF after a certain timeout.
* Wrapping is done only for chunked PUT requests, as some WebDAV clients are too stupid to send a EOF-chunk (0-byte-chunk).
*/
public class PutIdleTimeoutFilter implements HttpFilter {
private static final long TIMEOUT = 100;
private static final TimeUnit TIMEOUT_UNIT = TimeUnit.MILLISECONDS;
private static final String METHOD_PUT = "PUT";
private static final String HEADER_TRANSFER_ENCODING = "Transfer-Encoding";
private static final String HEADER_TRANSFER_ENCODING_CHUNKED = "chunked";
private final ExecutorService executor = Executors.newSingleThreadExecutor();
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// no-op
}
@Override
public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
if (METHOD_PUT.equalsIgnoreCase(request.getMethod()) && HEADER_TRANSFER_ENCODING_CHUNKED.equalsIgnoreCase(request.getHeader(HEADER_TRANSFER_ENCODING))) {
chain.doFilter(new PutRequestWithIdleTimeout(request), response);
} else {
chain.doFilter(request, response);
}
}
@Override
public void destroy() {
executor.shutdownNow();
}
private class PutRequestWithIdleTimeout extends HttpServletRequestWrapper {
public PutRequestWithIdleTimeout(HttpServletRequest request) {
super(request);
}
@Override
public ServletInputStream getInputStream() throws IOException {
return new IdleTimeoutServletInputStream(super.getInputStream());
}
}
private class IdleTimeoutServletInputStream extends ServletInputStream {
private final ServletInputStream delegate;
private boolean timedOut = false;
public IdleTimeoutServletInputStream(ServletInputStream delegate) {
this.delegate = delegate;
}
@Override
public boolean isFinished() {
return timedOut || delegate.isFinished();
}
@Override
public boolean isReady() {
return !timedOut && delegate.isReady();
}
@Override
public void setReadListener(ReadListener readListener) {
delegate.setReadListener(readListener);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
try {
Future<Integer> readTask = executor.submit(() -> {
return delegate.read(b, off, len);
});
return readTask.get(TIMEOUT, TIMEOUT_UNIT);
} catch (InterruptedException e) {
throw new InterruptedIOException();
} catch (ExecutionException e) {
throw new IOException("Exception during read", e);
} catch (TimeoutException e) {
timedOut = true;
return -1;
}
}
@Override
public int read() throws IOException {
try {
Future<Integer> readTask = executor.submit(() -> {
return delegate.read();
});
return readTask.get(TIMEOUT, TIMEOUT_UNIT);
} catch (InterruptedException e) {
throw new InterruptedIOException();
} catch (ExecutionException e) {
throw new IOException("Exception during read", e);
} catch (TimeoutException e) {
// throw new InterruptedByTimeoutException();
timedOut = true;
return -1;
}
}
}
}
@@ -19,6 +19,7 @@ import javax.servlet.DispatcherType;
import org.cryptomator.filesystem.FileSystem;
import org.cryptomator.webdav.filters.AcceptRangeFilter;
import org.cryptomator.webdav.filters.LoggingHttpFilter;
import org.cryptomator.webdav.filters.PutIdleTimeoutFilter;
import org.cryptomator.webdav.filters.UriNormalizationFilter;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
@@ -55,10 +56,11 @@ class FileSystemBasedWebDavServer {
servletContext.addServlet(servletHolder, "/*");
servletContext.addFilter(AcceptRangeFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
servletContext.addFilter(UriNormalizationFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
servletContext.addFilter(PutIdleTimeoutFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
servletContext.addFilter(LoggingHttpFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
servletCollection.mapContexts();
server.setConnectors(new Connector[] { localConnector });
server.setConnectors(new Connector[] {localConnector});
server.setHandler(servletCollection);
}