diff options
author | Simone Bordet | 2016-02-05 17:14:56 +0000 |
---|---|---|
committer | Simone Bordet | 2016-02-05 17:14:56 +0000 |
commit | cb79379b79e01fcd33b74c61edd2dc0a9a9686dd (patch) | |
tree | 188aec81ba0d950f0ce85a621da88ccca689145d | |
parent | acde7a7d56f526339ef3976db40ea8e63a194877 (diff) | |
parent | 4a7fae30fbecfedf49200f2c2b6c644aea2df7b0 (diff) | |
download | org.eclipse.jetty.project-cb79379b79e01fcd33b74c61edd2dc0a9a9686dd.tar.gz org.eclipse.jetty.project-cb79379b79e01fcd33b74c61edd2dc0a9a9686dd.tar.xz org.eclipse.jetty.project-cb79379b79e01fcd33b74c61edd2dc0a9a9686dd.zip |
Merged branch 'jetty-9.3.x' into 'master'.
16 files changed, 411 insertions, 353 deletions
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/ErrorCode.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/ErrorCode.java index e5a69b15c4..7022c37d7c 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/ErrorCode.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/ErrorCode.java @@ -21,21 +21,66 @@ package org.eclipse.jetty.http2; import java.util.HashMap; import java.util.Map; +/** + * Standard HTTP/2 error codes. + */ public enum ErrorCode { + /** + * Indicates no errors. + */ NO_ERROR(0), + /** + * Indicates a generic HTTP/2 protocol violation. + */ PROTOCOL_ERROR(1), + /** + * Indicates an internal error. + */ INTERNAL_ERROR(2), + /** + * Indicates a HTTP/2 flow control violation. + */ FLOW_CONTROL_ERROR(3), + /** + * Indicates that a SETTINGS frame did not receive a reply in a timely manner. + */ SETTINGS_TIMEOUT_ERROR(4), + /** + * Indicates that a stream frame has been received after the stream was closed. + */ STREAM_CLOSED_ERROR(5), + /** + * Indicates that a frame has an invalid length. + */ FRAME_SIZE_ERROR(6), + /** + * Indicates that a stream was rejected before application processing. + */ REFUSED_STREAM_ERROR(7), + /** + * Indicates that a stream is no longer needed. + */ CANCEL_STREAM_ERROR(8), + /** + * Indicates inability to maintain the HPACK compression context. + */ COMPRESSION_ERROR(9), + /** + * Indicates that the connection established by a HTTP CONNECT was abnormally closed. + */ HTTP_CONNECT_ERROR(10), + /** + * Indicates that the other peer might be generating excessive load. + */ ENHANCE_YOUR_CALM_ERROR(11), + /** + * Indicates that the transport properties do not meet minimum security requirements. + */ INADEQUATE_SECURITY_ERROR(12), + /** + * Indicates that HTTP/1.1 must be used rather than HTTP/2. + */ HTTP_1_1_REQUIRED_ERROR(13); public final int code; diff --git a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpChannelOverHTTP2.java b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpChannelOverHTTP2.java index 92e1158e9d..df999131b7 100644 --- a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpChannelOverHTTP2.java +++ b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpChannelOverHTTP2.java @@ -24,8 +24,11 @@ import org.eclipse.jetty.client.HttpExchange; import org.eclipse.jetty.client.HttpReceiver; import org.eclipse.jetty.client.HttpSender; import org.eclipse.jetty.client.api.Result; +import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; +import org.eclipse.jetty.http2.frames.ResetFrame; +import org.eclipse.jetty.util.Callback; public class HttpChannelOverHTTP2 extends HttpChannel { @@ -33,6 +36,7 @@ public class HttpChannelOverHTTP2 extends HttpChannel private final Session session; private final HttpSenderOverHTTP2 sender; private final HttpReceiverOverHTTP2 receiver; + private Stream stream; public HttpChannelOverHTTP2(HttpDestination destination, HttpConnectionOverHTTP2 connection, Session session) { @@ -65,6 +69,16 @@ public class HttpChannelOverHTTP2 extends HttpChannel return receiver; } + public Stream getStream() + { + return stream; + } + + public void setStream(Stream stream) + { + this.stream = stream; + } + @Override public void send() { @@ -80,6 +94,19 @@ public class HttpChannelOverHTTP2 extends HttpChannel } @Override + public boolean abort(HttpExchange exchange, Throwable requestFailure, Throwable responseFailure) + { + boolean aborted = super.abort(exchange, requestFailure, responseFailure); + if (aborted) + { + Stream stream = getStream(); + if (stream != null) + stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); + } + return aborted; + } + + @Override public void exchangeTerminated(HttpExchange exchange, Result result) { super.exchangeTerminated(exchange, result); diff --git a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpReceiverOverHTTP2.java b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpReceiverOverHTTP2.java index 827a8740cc..51453e822a 100644 --- a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpReceiverOverHTTP2.java +++ b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpReceiverOverHTTP2.java @@ -89,7 +89,10 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listen { HttpExchange exchange = getHttpExchange(); if (exchange == null) + { + callback.failed(new IOException("terminated")); return; + } if (responseContent(exchange, frame.getData(), callback)) { diff --git a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpSenderOverHTTP2.java b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpSenderOverHTTP2.java index db01d33c17..b13e68e097 100644 --- a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpSenderOverHTTP2.java +++ b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpSenderOverHTTP2.java @@ -33,8 +33,6 @@ import org.eclipse.jetty.util.Promise; public class HttpSenderOverHTTP2 extends HttpSender { - private Stream stream; - public HttpSenderOverHTTP2(HttpChannelOverHTTP2 channel) { super(channel); @@ -59,7 +57,7 @@ public class HttpSenderOverHTTP2 extends HttpSender @Override public void succeeded(Stream stream) { - HttpSenderOverHTTP2.this.stream = stream; + getHttpChannel().setStream(stream); stream.setIdleTimeout(request.getIdleTimeout()); if (content.hasContent() && !expects100Continue(request)) @@ -95,15 +93,9 @@ public class HttpSenderOverHTTP2 extends HttpSender } else { + Stream stream = getHttpChannel().getStream(); DataFrame frame = new DataFrame(stream.getId(), content.getByteBuffer(), content.isLast()); stream.data(frame, callback); } } - - @Override - protected void reset() - { - super.reset(); - stream = null; - } } diff --git a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/AbstractTest.java b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/AbstractTest.java index d4214dd29f..812253992b 100644 --- a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/AbstractTest.java +++ b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/AbstractTest.java @@ -19,8 +19,11 @@ package org.eclipse.jetty.http2.client.http; import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.http2.api.server.ServerSessionListener; import org.eclipse.jetty.http2.client.HTTP2Client; import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; +import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory; +import org.eclipse.jetty.server.ConnectionFactory; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.Server; @@ -38,25 +41,38 @@ public class AbstractTest protected ServerConnector connector; protected HttpClient client; - protected void start(int maxConcurrentStreams, Handler handler) throws Exception + protected void start(ServerSessionListener listener) throws Exception + { + prepareServer(new RawHTTP2ServerConnectionFactory(new HttpConfiguration(), listener)); + server.start(); + prepareClient(); + client.start(); + } + + protected void start(Handler handler) throws Exception + { + prepareServer(new HTTP2ServerConnectionFactory(new HttpConfiguration())); + server.setHandler(handler); + server.start(); + prepareClient(); + client.start(); + } + + protected void prepareServer(ConnectionFactory connectionFactory) { QueuedThreadPool serverExecutor = new QueuedThreadPool(); serverExecutor.setName("server"); server = new Server(serverExecutor); - - HTTP2ServerConnectionFactory http2 = new HTTP2ServerConnectionFactory(new HttpConfiguration()); - http2.setMaxConcurrentStreams(maxConcurrentStreams); - connector = new ServerConnector(server, 1, 1, http2); + connector = new ServerConnector(server, 1, 1, connectionFactory); server.addConnector(connector); + } - server.setHandler(handler); - server.start(); - + protected void prepareClient() throws Exception + { client = new HttpClient(new HttpClientTransportOverHTTP2(new HTTP2Client()), null); QueuedThreadPool clientExecutor = new QueuedThreadPool(); clientExecutor.setName("client"); client.setExecutor(clientExecutor); - client.start(); } @After diff --git a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2Test.java b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2Test.java index c2334eafac..1dcd6066d0 100644 --- a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2Test.java +++ b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2Test.java @@ -18,19 +18,32 @@ package org.eclipse.jetty.http2.client.http; +import java.nio.ByteBuffer; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http2.api.Stream; +import org.eclipse.jetty.http2.api.server.ServerSessionListener; import org.eclipse.jetty.http2.client.HTTP2Client; +import org.eclipse.jetty.http2.frames.DataFrame; +import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.http2.frames.ResetFrame; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; -public class HttpClientTransportOverHTTP2Test +public class HttpClientTransportOverHTTP2Test extends AbstractTest { @Test public void testPropertiesAreForwarded() throws Exception @@ -56,6 +69,83 @@ public class HttpClientTransportOverHTTP2Test Assert.assertTrue(http2Client.isStopped()); } + @Test + public void testRequestAbortSendsResetFrame() throws Exception + { + CountDownLatch resetLatch = new CountDownLatch(1); + start(new ServerSessionListener.Adapter() + { + @Override + public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) + { + return new Stream.Listener.Adapter() + { + @Override + public void onReset(Stream stream, ResetFrame frame) + { + resetLatch.countDown(); + } + }; + } + }); + + try + { + client.newRequest("localhost", connector.getLocalPort()) + .onRequestCommit(request -> request.abort(new Exception("explicitly_aborted_by_test"))) + .send(); + Assert.fail(); + } + catch (ExecutionException x) + { + Assert.assertTrue(resetLatch.await(5, TimeUnit.SECONDS)); + } + } + + @Test + public void testResponseAbortSendsResetFrame() throws Exception + { + CountDownLatch resetLatch = new CountDownLatch(1); + start(new ServerSessionListener.Adapter() + { + @Override + public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) + { + MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, new HttpFields()); + stream.headers(new HeadersFrame(stream.getId(), metaData, null, false), new Callback() + { + @Override + public void succeeded() + { + ByteBuffer data = ByteBuffer.allocate(1024); + stream.data(new DataFrame(stream.getId(), data, false), NOOP); + } + }); + + return new Stream.Listener.Adapter() + { + @Override + public void onReset(Stream stream, ResetFrame frame) + { + resetLatch.countDown(); + } + }; + } + }); + + try + { + client.newRequest("localhost", connector.getLocalPort()) + .onResponseContent((response, buffer) -> response.abort(new Exception("explicitly_aborted_by_test"))) + .send(); + Assert.fail(); + } + catch (ExecutionException x) + { + Assert.assertTrue(resetLatch.await(5, TimeUnit.SECONDS)); + } + } + @Ignore @Test public void testExternalServer() throws Exception diff --git a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/MaxConcurrentStreamsTest.java b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/MaxConcurrentStreamsTest.java index 78b421969c..c456d0261a 100644 --- a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/MaxConcurrentStreamsTest.java +++ b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/MaxConcurrentStreamsTest.java @@ -28,6 +28,9 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.AbstractHandler; import org.junit.Assert; @@ -35,6 +38,17 @@ import org.junit.Test; public class MaxConcurrentStreamsTest extends AbstractTest { + private void start(int maxConcurrentStreams, Handler handler) throws Exception + { + HTTP2ServerConnectionFactory http2 = new HTTP2ServerConnectionFactory(new HttpConfiguration()); + http2.setMaxConcurrentStreams(maxConcurrentStreams); + prepareServer(http2); + server.setHandler(handler); + server.start(); + prepareClient(); + client.start(); + } + @Test public void testOneConcurrentStream() throws Exception { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java index 1b6c4a8339..1f8c8b4857 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.io.InterruptedIOException; import java.nio.ByteBuffer; import java.util.ArrayDeque; +import java.util.Deque; import java.util.Objects; import java.util.Queue; import java.util.concurrent.TimeoutException; @@ -51,7 +52,7 @@ public class HttpInput extends ServletInputStream implements Runnable private final static Content EARLY_EOF_CONTENT = new EofContent("EARLY_EOF"); private final byte[] _oneByteBuffer = new byte[1]; - private final Queue<Content> _inputQ = new ArrayDeque<>(); + private final Deque<Content> _inputQ = new ArrayDeque<>(); private final HttpChannelState _channelState; private ReadListener _listener; private State _state = STREAM; @@ -370,6 +371,33 @@ public class HttpInput extends ServletInputStream implements Runnable } /** + * Adds some content to the start of this input stream. + * <p>Typically used to push back content that has + * been read, perhaps mutated. The bytes prepended are + * deducted for the contentConsumed total</p> + * @param item the content to add + * @return true if content channel woken for read + */ + public boolean prependContent(Content item) + { + boolean woken=false; + synchronized (_inputQ) + { + _inputQ.push(item); + _contentConsumed-=item.remaining(); + if (LOG.isDebugEnabled()) + LOG.debug("{} prependContent {}", this, item); + + if (_listener==null) + _inputQ.notify(); + else + woken=_channelState.onReadPossible(); + } + + return woken; + } + + /** * Adds some content to this input stream. * * @param item the content to add diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java index 07c316c3c4..c594d245f8 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java @@ -768,6 +768,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu old_context = __context.get(); __context.set(_scontext); + enterScope(null, getState()); + // defers the calling of super.doStart() startContext(); @@ -855,7 +857,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu ClassLoader old_classloader = null; ClassLoader old_webapploader = null; Thread current_thread = null; - + exitScope(null); Context old_context = __context.get(); __context.set(_scontext); try diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpInputTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpInputTest.java index 5e7454c984..5a404f3076 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpInputTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpInputTest.java @@ -221,6 +221,67 @@ public class HttpInputTest assertThat(_history.poll(),nullValue()); } + + @Test + public void testReRead() throws Exception + { + _in.addContent(new TContent("AB")); + _in.addContent(new TContent("CD")); + _fillAndParseSimulate.offer("EF"); + _fillAndParseSimulate.offer("GH"); + assertThat(_in.available(),equalTo(2)); + assertThat(_in.isFinished(),equalTo(false)); + assertThat(_in.isReady(),equalTo(true)); + + assertThat(_in.getContentConsumed(),equalTo(0L)); + assertThat(_in.read(),equalTo((int)'A')); + assertThat(_in.getContentConsumed(),equalTo(1L)); + assertThat(_in.read(),equalTo((int)'B')); + assertThat(_in.getContentConsumed(),equalTo(2L)); + + assertThat(_history.poll(),equalTo("Content succeeded AB")); + assertThat(_history.poll(),nullValue()); + assertThat(_in.read(),equalTo((int)'C')); + assertThat(_in.read(),equalTo((int)'D')); + + assertThat(_history.poll(),equalTo("Content succeeded CD")); + assertThat(_history.poll(),nullValue()); + assertThat(_in.read(),equalTo((int)'E')); + + _in.prependContent(new HttpInput.Content(BufferUtil.toBuffer("abcde"))); + + assertThat(_in.available(),equalTo(5)); + assertThat(_in.isFinished(),equalTo(false)); + assertThat(_in.isReady(),equalTo(true)); + + assertThat(_in.getContentConsumed(),equalTo(0L)); + assertThat(_in.read(),equalTo((int)'a')); + assertThat(_in.getContentConsumed(),equalTo(1L)); + assertThat(_in.read(),equalTo((int)'b')); + assertThat(_in.getContentConsumed(),equalTo(2L)); + assertThat(_in.read(),equalTo((int)'c')); + assertThat(_in.read(),equalTo((int)'d')); + assertThat(_in.read(),equalTo((int)'e')); + + + + assertThat(_in.read(),equalTo((int)'F')); + + assertThat(_history.poll(),equalTo("produceContent 2")); + assertThat(_history.poll(),equalTo("Content succeeded EF")); + assertThat(_history.poll(),nullValue()); + + assertThat(_in.read(),equalTo((int)'G')); + assertThat(_in.read(),equalTo((int)'H')); + + assertThat(_history.poll(),equalTo("Content succeeded GH")); + assertThat(_history.poll(),nullValue()); + + assertThat(_in.getContentConsumed(),equalTo(8L)); + + assertThat(_history.poll(),nullValue()); + } + @Test public void testBlockingRead() throws Exception { diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/RequestGetPartsTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/RequestGetPartsTest.java deleted file mode 100644 index 55de8d851f..0000000000 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/RequestGetPartsTest.java +++ /dev/null @@ -1,146 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd. -// ------------------------------------------------------------------------ -// All rights reserved. This program and the accompanying materials -// are made available under the terms of the Eclipse Public License v1.0 -// and Apache License v2.0 which accompanies this distribution. -// -// The Eclipse Public License is available at -// http://www.eclipse.org/legal/epl-v10.html -// -// The Apache License v2.0 is available at -// http://www.opensource.org/licenses/apache2.0.php -// -// You may elect to redistribute this code under either of these licenses. -// ======================================================================== -// - -package org.eclipse.jetty.servlet; - -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; - -import java.io.File; -import java.io.IOException; -import java.io.PrintWriter; -import java.nio.file.Path; - -import javax.servlet.MultipartConfigElement; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.Part; - -import org.eclipse.jetty.http.HttpTester; -import org.eclipse.jetty.server.LocalConnector; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.toolchain.test.FS; -import org.eclipse.jetty.toolchain.test.MavenTestingUtils; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; - -public class RequestGetPartsTest -{ - @SuppressWarnings("serial") - public static class DumpPartInfoServlet extends HttpServlet - { - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException - { - resp.setContentType("text/plain"); - PrintWriter out = resp.getWriter(); - - for(Part part: req.getParts()) - { - out.printf("Got part: name=%s, size=%,d, filename=%s%n",part.getName(), part.getSize(), part.getSubmittedFileName()); - } - } - } - - private static Server server; - private static LocalConnector connector; - private static File locationDir; - - @BeforeClass - public static void startServer() throws Exception - { - Path tmpDir = MavenTestingUtils.getTargetTestingPath("testrequest_getparts"); - FS.ensureEmpty(tmpDir); - - locationDir = tmpDir.toFile(); - - server = new Server(); - connector = new LocalConnector(server); - server.addConnector(connector); - - ServletContextHandler context = new ServletContextHandler(); - context.setContextPath("/"); - server.setHandler(context); - - ServletHolder holder = context.addServlet(DumpPartInfoServlet.class,"/dump/*"); - String location = locationDir.getAbsolutePath(); - long maxFileSize = 1024*1024*5; - long maxRequestSize = 1024*1024*10; - int fileSizeThreshold = 1; - MultipartConfigElement multipartConfig = new MultipartConfigElement(location,maxFileSize,maxRequestSize,fileSizeThreshold); - ((ServletHolder.Registration) holder.getRegistration()).setMultipartConfig(multipartConfig); - - server.start(); - } - - @AfterClass - public static void stopServer() throws Exception - { - server.stop(); - } - - @Test - public void testMultiFileUpload_SameName() throws Exception - { - // generated and parsed test - HttpTester.Request request = HttpTester.newRequest(); - HttpTester.Response response; - - // test GET - request.setMethod("POST"); - request.setURI("/dump/"); - request.setVersion("HTTP/1.1"); - request.setHeader("Host","tester"); - request.setHeader("Connection","close"); - - String boundary="XyXyXy"; - request.setHeader("Content-Type","multipart/form-data; boundary=" + boundary); - - String crocMsg = "See ya later, aligator."; - String aligMsg = "In a while, crocodile."; - - StringBuilder content = new StringBuilder(); - content.append("--").append(boundary).append("\r\n"); - content.append("Content-Disposition: form-data; name=\"same\"; filename=\"crocodile.dat\"\r\n"); - content.append("Content-Type: application/octet-stream\r\n"); - content.append("\r\n"); - content.append(crocMsg).append("\r\n"); - content.append("--").append(boundary).append("\r\n"); - content.append("Content-Disposition: form-data; name=\"same\"; filename=\"aligator.dat\"\r\n"); - content.append("Content-Type: application/octet-stream\r\n"); - content.append("\r\n"); - content.append(aligMsg).append("\r\n"); - content.append("--").append(boundary).append("--\r\n"); - content.append("\r\n"); - - request.setContent(content.toString()); - - response = HttpTester.parseResponse(connector.getResponses(request.generate())); - assertThat("Response status", response.getStatus(), is(HttpServletResponse.SC_OK)); - assertEquals(HttpServletResponse.SC_OK,response.getStatus()); - - String responseContents = response.getContent(); - assertThat("response.contents", responseContents, containsString(String.format("Got part: name=same, size=%d, filename=crocodile.dat",crocMsg.length()))); - assertThat("response.contents", responseContents, containsString(String.format("Got part: name=same, size=%d, filename=aligator.dat",aligMsg.length()))); - } -} diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java index c2ce682774..c3b43e4750 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java @@ -18,10 +18,9 @@ package org.eclipse.jetty.servlets; + import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; -import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -31,15 +30,10 @@ import static org.junit.Assert.assertTrue; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; -import java.io.Reader; import java.nio.charset.StandardCharsets; -import java.nio.file.Path; import java.util.EnumSet; -import java.util.Enumeration; import java.util.Map; import javax.servlet.DispatcherType; @@ -51,12 +45,9 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpTester; import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.ServletTester; -import org.eclipse.jetty.toolchain.test.FS; -import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.IO; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; public class MultipartFilterTest @@ -65,45 +56,16 @@ public class MultipartFilterTest private ServletTester tester; FilterHolder multipartFilter; - @SuppressWarnings("serial") public static class FilenameServlet extends TestServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - assertThat(req.getAttribute("fileup"), notNullValue()); + assertNotNull(req.getAttribute("fileup")); super.doPost(req, resp); } } - - @SuppressWarnings("serial") - public static class ParameterListServlet extends TestServlet - { - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException - { - resp.setContentType("text/plain"); - PrintWriter out = resp.getWriter(); - - Enumeration<String> pnames = req.getParameterNames(); - while (pnames.hasMoreElements()) - { - String pname = pnames.nextElement(); - Object param = req.getParameter(pname); - out.printf("Parameter[%s] = ",pname); - if (param == null) - { - out.println(" <null>"); - } - else - { - out.printf("(%s) %s%n",param.getClass().getName(),param); - } - } - } - } - @SuppressWarnings("serial") public static class BoundaryServlet extends TestServlet { @Override @@ -111,11 +73,9 @@ public class MultipartFilterTest { //we have configured the multipart filter to always store to disk (file size threshold == 1) //but fileName has no filename param, so only the attribute should be set - assertThat("getParameter('fileName')", req.getParameter("fileName"), nullValue()); - assertThat("getAttribute('fileName')", req.getAttribute("fileName"), notNullValue()); - + assertNull(req.getParameter("fileName")); + assertNotNull(req.getAttribute("fileName")); File f = (File)req.getAttribute("fileName"); - assertThat("File exists", f.exists(), is(true)); ByteArrayOutputStream baos = new ByteArrayOutputStream(); IO.copy(new FileInputStream(f), baos); assertEquals(getServletContext().getAttribute("fileName"), baos.toString()); @@ -133,36 +93,34 @@ public class MultipartFilterTest } } - @SuppressWarnings("serial") public static class TestServlet extends DumpServlet { - @SuppressWarnings("deprecation") @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - String fileup = req.getParameter("fileup"); - assertThat("getParameter('fileup')",fileup,notNullValue()); - - System.err.println("Fileup=" + req.getParameter("fileup")); - - String fileupType = req.getParameter("fileup" + MultiPartFilter.CONTENT_TYPE_SUFFIX); - assertThat("req.getParameter('fileup'+CONTENT_TYPE_SUFFIX)",fileupType,is("application/octet-stream")); + assertNotNull(req.getParameter("fileup")); + System.err.println("Fileup="+req.getParameter("fileup")); + assertNotNull(req.getParameter("fileup"+MultiPartFilter.CONTENT_TYPE_SUFFIX)); + assertEquals(req.getParameter("fileup"+MultiPartFilter.CONTENT_TYPE_SUFFIX), "application/octet-stream"); super.doPost(req, resp); } + } - @SuppressWarnings("deprecation") + + @Before public void setUp() throws Exception { - Path tmpDir = MavenTestingUtils.getTargetTestingPath("testmultupart"); - FS.ensureEmpty(tmpDir); - - _dir = tmpDir.toFile(); + _dir = File.createTempFile("testmultupart",null); + assertTrue(_dir.delete()); + assertTrue(_dir.mkdir()); + _dir.deleteOnExit(); + assertTrue(_dir.isDirectory()); tester=new ServletTester("/context"); - tester.getContext().setResourceBase(tmpDir.toString()); + tester.getContext().setResourceBase(_dir.getCanonicalPath()); tester.getContext().addServlet(TestServlet.class, "/"); tester.getContext().setAttribute("javax.servlet.context.tempdir", _dir); multipartFilter = tester.getContext().addFilter(MultiPartFilter.class,"/*", EnumSet.of(DispatcherType.REQUEST)); @@ -832,7 +790,6 @@ public class MultipartFilterTest assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, response.getStatus()); } - @SuppressWarnings("serial") public static class TestServletParameterMap extends DumpServlet { @Override @@ -843,7 +800,7 @@ public class MultipartFilterTest super.doPost(req, resp); } } - + /** * Validate that the getParameterMap() call is correctly unencoding the parameters in the * map that it returns. @@ -873,7 +830,7 @@ public class MultipartFilterTest "Content-Type: application/octet-stream\r\n\r\n"+ "How now brown cow."+ "\r\n--" + boundary + "\r\n"+ - "Content-Disposition: form-data; name=\"strup\""+ // FIXME: this is missing a "\r\n"?? + "Content-Disposition: form-data; name=\"strup\""+ "Content-Type: application/octet-stream\r\n\r\n"+ "How now brown cow."+ "\r\n--" + boundary + "--\r\n\r\n"; @@ -884,119 +841,6 @@ public class MultipartFilterTest assertEquals(HttpServletResponse.SC_OK,response.getStatus()); assertTrue(response.getContent().indexOf("brown cow")>=0); } - - /** - * Validate that the uploaded file can be accessed on the name it was given - * @throws Exception on test failure - */ - @Test - @Ignore("Fails for Bug #486394") - public void testFileUpload_AccessViaFilename() throws Exception - { - tester.addServlet(ParameterListServlet.class,"/paramlist"); - - multipartFilter.setInitParameter("fileOutputBuffer", "1"); - multipartFilter.setInitParameter("deleteFiles", "false"); - multipartFilter.setInitParameter("writeFilesWithFilenames", "true"); - - // generated and parsed test - HttpTester.Request request = HttpTester.newRequest(); - HttpTester.Response response; - - // test GET - request.setMethod("POST"); - request.setURI("/context/paramlist"); - request.setVersion("HTTP/1.1"); - request.setHeader("Host","tester"); - request.setHeader("Connection","close"); - String boundary="XyXyXy"; - request.setHeader("Content-Type","multipart/form-data; boundary=" + boundary); - - StringBuilder content = new StringBuilder(); - content.append("--").append(boundary).append("\r\n"); - content.append("Content-Disposition: form-data; name=\"file\"; filename=\"tiny.dat\"\r\n"); - content.append("Content-Type: application/octet-stream\r\n"); - content.append("\r\n"); - content.append("How now brown cow.\r\n"); - content.append("--").append(boundary).append("--\r\n"); - content.append("\r\n"); - - request.setContent(content.toString()); - - response = HttpTester.parseResponse(tester.getResponses(request.generate())); - assertThat("Response status", response.getStatus(), is(HttpServletResponse.SC_OK)); - assertEquals(HttpServletResponse.SC_OK,response.getStatus()); - - String contents = assertUploadedFileExists("tiny.dat"); - assertThat("contents", contents, containsString("How now brown cow.")); - } - - /** - * Validate that the two upload files, with the same name="" on two different parts, - * can be accessed with the filename="" portions. - * @throws Exception on test failure - */ - @Test - @Ignore("Fails for Bug #486394") - public void testTwoFileUploads_AccessViaFilename() throws Exception - { - tester.addServlet(ParameterListServlet.class,"/paramlist"); - - multipartFilter.setInitParameter("fileOutputBuffer", "1"); - multipartFilter.setInitParameter("deleteFiles", "false"); - multipartFilter.setInitParameter("writeFilesWithFilenames", "true"); - - // generated and parsed test - HttpTester.Request request = HttpTester.newRequest(); - HttpTester.Response response; - - // test GET - request.setMethod("POST"); - request.setURI("/context/paramlist"); - request.setVersion("HTTP/1.1"); - request.setHeader("Host","tester"); - request.setHeader("Connection","close"); - - String boundary="XyXyXy"; - request.setHeader("Content-Type","multipart/form-data; boundary=" + boundary); - - StringBuilder content = new StringBuilder(); - content.append("--").append(boundary).append("\r\n"); - content.append("Content-Disposition: form-data; name=\"same\"; filename=\"crocodile.dat\"\r\n"); - content.append("Content-Type: application/octet-stream\r\n"); - content.append("\r\n"); - content.append("See ya later, aligator.\r\n"); - content.append("--").append(boundary).append("\r\n"); - content.append("Content-Disposition: form-data; name=\"same\"; filename=\"aligator.dat\"\r\n"); - content.append("Content-Type: application/octet-stream\r\n"); - content.append("\r\n"); - content.append("In a while, crocodile.\r\n"); - content.append("--").append(boundary).append("--\r\n"); - content.append("\r\n"); - - request.setContent(content.toString()); - - response = HttpTester.parseResponse(tester.getResponses(request.generate())); - assertThat("Response status", response.getStatus(), is(HttpServletResponse.SC_OK)); - assertEquals(HttpServletResponse.SC_OK,response.getStatus()); - - String contents = assertUploadedFileExists("crocodile.dat"); - assertThat("contents", contents, containsString("See ya later, aligator.")); - - contents = assertUploadedFileExists("aligator.dat"); - assertThat("contents", contents, containsString("In a while, crocodile.")); - } - - private String assertUploadedFileExists(String filename) throws IOException - { - File uploadedFile = new File(_dir,filename); - assertThat("Uploaded File[" + uploadedFile + "].exists",uploadedFile.exists(),is(true)); - - try (Reader reader = new FileReader(uploadedFile)) - { - return IO.toString(reader); - } - } public static class TestServletCharSet extends HttpServlet { @@ -1103,9 +947,13 @@ public class MultipartFilterTest assertTrue(response.getContent().indexOf("000")>=0); } - @SuppressWarnings("serial") + + public static class DumpServlet extends HttpServlet { + private static final long serialVersionUID = 201012011130L; + + /* ------------------------------------------------------------ */ /** * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/AbstractExtension.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/AbstractExtension.java index 58e8c90db6..f1abf1e49f 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/AbstractExtension.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/AbstractExtension.java @@ -23,7 +23,9 @@ import java.io.IOException; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; +import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.eclipse.jetty.util.component.ContainerLifeCycle; +import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.websocket.api.BatchMode; @@ -38,7 +40,7 @@ import org.eclipse.jetty.websocket.common.LogicalConnection; import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope; @ManagedObject("Abstract Extension") -public abstract class AbstractExtension extends ContainerLifeCycle implements Extension +public abstract class AbstractExtension extends AbstractLifeCycle implements Dumpable, Extension { private final Logger log; private WebSocketPolicy policy; @@ -52,11 +54,15 @@ public abstract class AbstractExtension extends ContainerLifeCycle implements Ex { log = Log.getLogger(this.getClass()); } - + @Override + public String dump() + { + return ContainerLifeCycle.dump(this); + } + public void dump(Appendable out, String indent) throws IOException { - super.dump(out, indent); // incoming dumpWithHeading(out, indent, "incoming", this.nextIncoming); dumpWithHeading(out, indent, "outgoing", this.nextOutgoing); diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStack.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStack.java index 720e98146b..2cde3a5ab3 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStack.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStack.java @@ -29,6 +29,7 @@ import org.eclipse.jetty.util.IteratingCallback; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.component.ContainerLifeCycle; +import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.websocket.api.BatchMode; @@ -89,6 +90,11 @@ public class ExtensionStack extends ContainerLifeCycle implements IncomingFrames Extension ext = exts.next(); ext.setNextOutgoingFrames(nextOutgoing); nextOutgoing = ext; + + if (ext instanceof LifeCycle) + { + addBean(ext,true); + } } // Connect incomings diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/CompressExtension.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/CompressExtension.java index ac32ad6d72..517f714203 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/CompressExtension.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/CompressExtension.java @@ -74,28 +74,34 @@ public abstract class CompressExtension extends AbstractExtension private final Queue<FrameEntry> entries = new ConcurrentArrayQueue<>(); private final IteratingCallback flusher = new Flusher(); - private final Deflater deflater; - private final Inflater inflater; + private Deflater deflaterImpl; + private Inflater inflaterImpl; protected AtomicInteger decompressCount = new AtomicInteger(0); private int tailDrop = TAIL_DROP_NEVER; private int rsvUse = RSV_USE_ALWAYS; protected CompressExtension() { - deflater = new Deflater(Deflater.DEFAULT_COMPRESSION,NOWRAP); - inflater = new Inflater(NOWRAP); tailDrop = getTailDropMode(); rsvUse = getRsvUseMode(); } public Deflater getDeflater() { - return deflater; + if (deflaterImpl == null) + { + deflaterImpl = new Deflater(Deflater.DEFAULT_COMPRESSION,NOWRAP); + } + return deflaterImpl; } public Inflater getInflater() { - return inflater; + if (inflaterImpl == null) + { + inflaterImpl = new Inflater(NOWRAP); + } + return inflaterImpl; } /** @@ -155,6 +161,8 @@ public abstract class CompressExtension extends AbstractExtension } byte[] output = new byte[DECOMPRESS_BUF_SIZE]; + Inflater inflater = getInflater(); + while(buf.hasRemaining() && inflater.needsInput()) { if (!supplyInput(inflater,buf)) @@ -346,6 +354,16 @@ public abstract class CompressExtension extends AbstractExtension } return true; } + + @Override + protected void doStop() throws Exception + { + if(deflaterImpl != null) + deflaterImpl.end(); + if(inflaterImpl != null) + inflaterImpl.end(); + super.doStop(); + } @Override public String toString() @@ -429,6 +447,8 @@ public abstract class CompressExtension extends AbstractExtension LOG.debug("Compressing {}: {} bytes in {} bytes chunk",entry,remaining,outputLength); boolean needsCompress = true; + + Deflater deflater = getDeflater(); if (deflater.needsInput() && !supplyInput(deflater,data)) { diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java index c9d3a03015..415bbe3700 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.http.client; import java.io.IOException; import java.io.InterruptedIOException; import java.util.Random; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.stream.IntStream; @@ -35,6 +36,7 @@ import org.eclipse.jetty.client.util.BytesContentProvider; import org.eclipse.jetty.client.util.FutureResponseListener; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http2.FlowControlStrategy; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.AbstractHandler; import org.junit.Assert; @@ -239,6 +241,50 @@ public class HttpClientTest extends AbstractTest Assert.assertEquals(chunks * chunkSize, Integer.parseInt(response.getContentAsString())); } + @Test + public void testRequestAfterFailedRequest() throws Exception + { + int length = FlowControlStrategy.DEFAULT_WINDOW_SIZE; + start(new AbstractHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + response.getOutputStream().write(new byte[length]); + } + }); + + // Make a request with a large enough response buffer. + org.eclipse.jetty.client.api.Request request = client.newRequest(newURI()); + FutureResponseListener listener = new FutureResponseListener(request, length); + request.send(listener); + ContentResponse response = listener.get(5, TimeUnit.SECONDS); + Assert.assertEquals(response.getStatus(), 200); + + // Make a request with a small response buffer, should fail. + try + { + request = client.newRequest(newURI()); + listener = new FutureResponseListener(request, length / 10); + request.send(listener); + listener.get(5, TimeUnit.SECONDS); + Assert.fail(); + } + catch (ExecutionException x) + { + // Buffering capacity exceeded. + x.printStackTrace(); + } + + // Verify that we can make another request. + request = client.newRequest(newURI()); + listener = new FutureResponseListener(request, length); + request.send(listener); + response = listener.get(5, TimeUnit.SECONDS); + Assert.assertEquals(response.getStatus(), 200); + } + private void sleep(long time) throws IOException { try |