diff --git a/aggregates/jetty-all/pom.xml b/aggregates/jetty-all/pom.xml index de451684512..c64b2bc8fa7 100644 --- a/aggregates/jetty-all/pom.xml +++ b/aggregates/jetty-all/pom.xml @@ -191,6 +191,12 @@ ${project.version} provided + + org.eclipse.jetty + jetty-quickstart + ${project.version} + provided + javax.websocket diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_40.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_40.mod index 09cda68e9e6..bee4693e8ac 100644 --- a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_40.mod +++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_40.mod @@ -2,7 +2,7 @@ protonego-boot [files] -http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.0.0/alpn-boot-7.0.0.jar:lib/alpn/alpn-boot-7.0.0.jar +http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.0.0.v20140317/alpn-boot-7.0.0.v20140317.jar:lib/alpn/alpn-boot-7.0.0.v20140317.jar [exec] --Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.jar +-Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.v20140317.jar diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_45.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_45.mod index 09cda68e9e6..bee4693e8ac 100644 --- a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_45.mod +++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_45.mod @@ -2,7 +2,7 @@ protonego-boot [files] -http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.0.0/alpn-boot-7.0.0.jar:lib/alpn/alpn-boot-7.0.0.jar +http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.0.0.v20140317/alpn-boot-7.0.0.v20140317.jar:lib/alpn/alpn-boot-7.0.0.v20140317.jar [exec] --Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.jar +-Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.v20140317.jar diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_51.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_51.mod index 09cda68e9e6..bee4693e8ac 100644 --- a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_51.mod +++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_51.mod @@ -2,7 +2,7 @@ protonego-boot [files] -http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.0.0/alpn-boot-7.0.0.jar:lib/alpn/alpn-boot-7.0.0.jar +http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.0.0.v20140317/alpn-boot-7.0.0.v20140317.jar:lib/alpn/alpn-boot-7.0.0.v20140317.jar [exec] --Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.jar +-Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.v20140317.jar diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_55.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_55.mod new file mode 100644 index 00000000000..bee4693e8ac --- /dev/null +++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_55.mod @@ -0,0 +1,8 @@ +[name] +protonego-boot + +[files] +http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.0.0.v20140317/alpn-boot-7.0.0.v20140317.jar:lib/alpn/alpn-boot-7.0.0.v20140317.jar + +[exec] +-Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.v20140317.jar diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0.mod index b738281cb93..4089153ac14 100644 --- a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0.mod +++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0.mod @@ -2,7 +2,7 @@ protonego-boot [files] -http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.0.0/alpn-boot-8.0.0.jar:lib/alpn/alpn-boot-8.0.0.jar +http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.0.0.v20140317/alpn-boot-8.0.0.v20140317.jar:lib/alpn/alpn-boot-8.0.0.v20140317.jar [exec] --Xbootclasspath/p:lib/alpn/alpn-boot-8.0.0.jar +-Xbootclasspath/p:lib/alpn/alpn-boot-8.0.0.v20140317.jar diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_05.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_05.mod new file mode 100644 index 00000000000..4089153ac14 --- /dev/null +++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_05.mod @@ -0,0 +1,8 @@ +[name] +protonego-boot + +[files] +http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.0.0.v20140317/alpn-boot-8.0.0.v20140317.jar:lib/alpn/alpn-boot-8.0.0.v20140317.jar + +[exec] +-Xbootclasspath/p:lib/alpn/alpn-boot-8.0.0.v20140317.jar diff --git a/jetty-annotations/pom.xml b/jetty-annotations/pom.xml index 0fc2b9f229d..f07812ffce3 100644 --- a/jetty-annotations/pom.xml +++ b/jetty-annotations/pom.xml @@ -44,7 +44,7 @@ javax.servlet.*;version="[2.6.0,3.2)",org.objectweb.asm.*;version=4,* - osgi.serviceloader; filter:="(osgi.serviceloader=javax.servlet.ServletContainerInitializer)";cardinality:=multiple, osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)" + osgi.serviceloader; filter:="(osgi.serviceloader=javax.servlet.ServletContainerInitializer)";resolution:=optional;cardinality:=multiple, osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)" diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java index 5587c2fd6d5..41b1627f6e0 100644 --- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java +++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java @@ -69,6 +69,8 @@ public class AnnotationParser private static final Logger LOG = Log.getLogger(AnnotationParser.class); protected Set _parsedClassNames = new ConcurrentHashSet(); + + protected static int ASM_OPCODE_VERSION = Opcodes.ASM5; //compatibility of api /** @@ -373,7 +375,7 @@ public class AnnotationParser final String signature, final String[] exceptions) { - super(Opcodes.ASM4); + super(ASM_OPCODE_VERSION); _handlers = handlers; _mi = new MethodInfo(classInfo, name, access, methodDesc,signature, exceptions); } @@ -417,7 +419,7 @@ public class AnnotationParser final String signature, final Object value) { - super(Opcodes.ASM4); + super(ASM_OPCODE_VERSION); _handlers = handlers; _fieldInfo = new FieldInfo(classInfo, fieldName, access, fieldType, signature, value); } @@ -456,7 +458,7 @@ public class AnnotationParser public MyClassVisitor(Set handlers, Resource containingResource) { - super(Opcodes.ASM4); + super(ASM_OPCODE_VERSION); _handlers = handlers; _containingResource = containingResource; } @@ -702,6 +704,7 @@ public class AnnotationParser } catch (Exception ex) { + if (LOG.isDebugEnabled()) LOG.debug("Error scanning file "+files[f], ex); me.add(new RuntimeException("Error scanning file "+files[f],ex)); } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/GZIPContentDecoder.java b/jetty-client/src/main/java/org/eclipse/jetty/client/GZIPContentDecoder.java index 8a457a6e89f..ab8f2a40b5f 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/GZIPContentDecoder.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/GZIPContentDecoder.java @@ -246,14 +246,12 @@ public class GZIPContentDecoder implements ContentDecoder if (output == null) { // Save the inflated bytes and loop to see if we have finished - output = new byte[decoded]; - System.arraycopy(bytes, 0, output, 0, decoded); + output = Arrays.copyOf(bytes, decoded); } else { // Accumulate inflated bytes and loop to see if we have finished - byte[] newOutput = new byte[output.length + decoded]; - System.arraycopy(output, 0, newOutput, 0, output.length); + byte[] newOutput = Arrays.copyOf(output, output.length+decoded); System.arraycopy(bytes, 0, newOutput, output.length, decoded); output = newOutput; } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java index 79c59629ba7..8e323bc734a 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java @@ -201,8 +201,8 @@ public class HttpClient extends ContainerLifeCycle scheduler = new ScheduledExecutorScheduler(name + "-scheduler", false); addBean(scheduler); - addBean(transport); transport.setHttpClient(this); + addBean(transport); resolver = new SocketAddressResolver(executor, scheduler, getAddressResolutionTimeout()); @@ -228,7 +228,6 @@ public class HttpClient extends ContainerLifeCycle protected void doStop() throws Exception { cookieStore.removeAll(); - cookieStore = null; decoderFactories.clear(); handlers.clear(); @@ -391,26 +390,29 @@ public class HttpClient extends ContainerLifeCycle .idleTimeout(oldRequest.getIdleTimeout(), TimeUnit.MILLISECONDS) .timeout(oldRequest.getTimeout(), TimeUnit.MILLISECONDS) .followRedirects(oldRequest.isFollowRedirects()); - for (HttpField header : oldRequest.getHeaders()) + for (HttpField field : oldRequest.getHeaders()) { - // We have a new URI, so skip the host header if present - if (HttpHeader.HOST == header.getHeader()) + HttpHeader header = field.getHeader(); + // We have a new URI, so skip the host header if present. + if (HttpHeader.HOST == header) continue; - // Remove expectation headers - if (HttpHeader.EXPECT == header.getHeader()) + // Remove expectation headers. + if (HttpHeader.EXPECT == header) continue; - // Remove cookies - if (HttpHeader.COOKIE == header.getHeader()) + // Remove cookies. + if (HttpHeader.COOKIE == header) continue; - // Remove authorization headers - if (HttpHeader.AUTHORIZATION == header.getHeader() || - HttpHeader.PROXY_AUTHORIZATION == header.getHeader()) + // Remove authorization headers. + if (HttpHeader.AUTHORIZATION == header || + HttpHeader.PROXY_AUTHORIZATION == header) continue; - newRequest.header(header.getName(), header.getValue()); + String value = field.getValue(); + if (!newRequest.getHeaders().contains(header, value)) + newRequest.header(field.getName(), value); } return newRequest; } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java index 5a40a2c4061..cc83372c437 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java @@ -215,6 +215,10 @@ public abstract class HttpDestination implements Destination, Closeable, Dumpabl LOG.debug("Closed {}", this); } + public void release(Connection connection) + { + } + public void close(Connection connection) { } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java index d7a7b6eae3c..becab10b57a 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java @@ -33,6 +33,7 @@ import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -48,8 +49,8 @@ import org.eclipse.jetty.util.log.Logger; * is available *
  • {@link #responseHeader(HttpExchange, HttpField)}, when a HTTP field is available
  • *
  • {@link #responseHeaders(HttpExchange)}, when all HTTP headers are available
  • - *
  • {@link #responseContent(HttpExchange, ByteBuffer)}, when HTTP content is available; this is the only method - * that may be invoked multiple times with different buffers containing different content
  • + *
  • {@link #responseContent(HttpExchange, ByteBuffer, Callback)}, when HTTP content is available; this is the only + * method that may be invoked multiple times with different buffers containing different content
  • *
  • {@link #responseSuccess(HttpExchange)}, when the response is complete
  • * * At any time, subclasses may invoke {@link #responseFailure(Throwable)} to indicate that the response has failed @@ -237,7 +238,7 @@ public abstract class HttpReceiver HttpResponse response = exchange.getResponse(); if (LOG.isDebugEnabled()) - LOG.debug("Response headers {}{}{}", response, System.getProperty("line.separator"), response.getHeaders().toString().trim()); + LOG.debug("Response headers {}{}{}", response, System.lineSeparator(), response.getHeaders().toString().trim()); ResponseNotifier notifier = getHttpDestination().getResponseNotifier(); notifier.notifyHeaders(exchange.getConversation().getResponseListeners(), response); @@ -269,7 +270,7 @@ public abstract class HttpReceiver * @param buffer the response HTTP content buffer * @return whether the processing should continue */ - protected boolean responseContent(HttpExchange exchange, ByteBuffer buffer) + protected boolean responseContent(HttpExchange exchange, ByteBuffer buffer, Callback callback) { out: while (true) { @@ -292,18 +293,18 @@ public abstract class HttpReceiver HttpResponse response = exchange.getResponse(); if (LOG.isDebugEnabled()) - LOG.debug("Response content {}{}{}", response, System.getProperty("line.separator"), BufferUtil.toDetailString(buffer)); + LOG.debug("Response content {}{}{}", response, System.lineSeparator(), BufferUtil.toDetailString(buffer)); ContentDecoder decoder = this.decoder; if (decoder != null) { buffer = decoder.decode(buffer); if (LOG.isDebugEnabled()) - LOG.debug("Response content decoded ({}) {}{}{}", decoder, response, System.getProperty("line.separator"), BufferUtil.toDetailString(buffer)); + LOG.debug("Response content decoded ({}) {}{}{}", decoder, response, System.lineSeparator(), BufferUtil.toDetailString(buffer)); } ResponseNotifier notifier = getHttpDestination().getResponseNotifier(); - notifier.notifyContent(exchange.getConversation().getResponseListeners(), response, buffer); + notifier.notifyContent(exchange.getConversation().getResponseListeners(), response, buffer, callback); return true; } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java index afe214f5297..9b6eec6a57e 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java @@ -50,6 +50,7 @@ import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Fields; public class HttpRequest implements Request @@ -449,12 +450,34 @@ public class HttpRequest implements Request @Override public Request onResponseContent(final Response.ContentListener listener) { - this.responseListeners.add(new Response.ContentListener() + this.responseListeners.add(new Response.AsyncContentListener() { @Override - public void onContent(Response response, ByteBuffer content) + public void onContent(Response response, ByteBuffer content, Callback callback) { - listener.onContent(response, content); + try + { + listener.onContent(response, content); + callback.succeeded(); + } + catch (Exception x) + { + callback.failed(x); + } + } + }); + return this; + } + + @Override + public Request onResponseContentAsync(final Response.AsyncContentListener listener) + { + this.responseListeners.add(new Response.AsyncContentListener() + { + @Override + public void onContent(Response response, ByteBuffer content, Callback callback) + { + listener.onContent(response, content, callback); } }); return this; diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java index 0e39d1e05ce..d2e26bdafd2 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java @@ -62,7 +62,7 @@ public abstract class HttpSender implements AsyncContentProvider.Listener private final AtomicReference requestState = new AtomicReference<>(RequestState.QUEUED); private final AtomicReference senderState = new AtomicReference<>(SenderState.IDLE); private final Callback commitCallback = new CommitCallback(); - private final Callback contentCallback = new ContentCallback(); + private final IteratingCallback contentCallback = new ContentCallback(); private final Callback lastCallback = new LastContentCallback(); private final HttpChannel channel; private volatile HttpContent content; @@ -100,14 +100,7 @@ public abstract class HttpSender implements AsyncContentProvider.Listener if (updateSenderState(current, newSenderState)) { LOG.debug("Deferred content available, {} -> {}", current, newSenderState); - // TODO should just call contentCallback.iterate() here. - HttpContent content = this.content; - if (content.advance()) - sendContent(exchange, content, contentCallback); // TODO old style usage! - else if (content.isConsumed()) - sendContent(exchange, content, lastCallback); - else - throw new IllegalStateException(); + contentCallback.iterate(); return; } break; @@ -456,25 +449,11 @@ public abstract class HttpSender implements AsyncContentProvider.Listener case WAITING: { // We received the 100 Continue, now send the content if any. - HttpContent content = this.content; - // TODO should just call contentCallback.iterate() here. - if (content.advance()) - { - // There is content to send. - if (!updateSenderState(current, SenderState.SENDING)) - throw illegalSenderState(current); - LOG.debug("Proceeding while waiting"); - sendContent(exchange, content, contentCallback); // TODO old style usage! - return; - } - else - { - // No content to send yet - it's deferred. - if (!updateSenderState(current, SenderState.IDLE)) - throw illegalSenderState(current); - LOG.debug("Proceeding deferred"); - return; - } + if (!updateSenderState(current, SenderState.SENDING)) + throw illegalSenderState(current); + LOG.debug("Proceeding while waiting"); + contentCallback.iterate(); + return; } default: { @@ -665,30 +644,8 @@ public abstract class HttpSender implements AsyncContentProvider.Listener { case SENDING: { - // TODO should just call contentCallback.iterate() here. - // We have content to send ? - if (content.advance()) - { - sendContent(exchange, content, contentCallback); // TODO old style usage! - return; - } - else - { - if (content.isConsumed()) - { - sendContent(exchange, content, lastCallback); - return; - } - else - { - if (updateSenderState(current, SenderState.IDLE)) - { - LOG.debug("Waiting for deferred content for {}", request); - return; - } - break; - } - } + contentCallback.iterate(); + return; } case SENDING_WITH_CONTENT: { @@ -745,59 +702,43 @@ public abstract class HttpSender implements AsyncContentProvider.Listener if (exchange == null) return Action.IDLE; - Request request = exchange.getRequest(); HttpContent content = HttpSender.this.content; - - ByteBuffer contentBuffer = content.getContent(); - if (contentBuffer != null) - { - if (!someToContent(request, contentBuffer)) - return Action.IDLE; - } - while (true) { boolean advanced = content.advance(); boolean consumed = content.isConsumed(); + if (LOG.isDebugEnabled()) + LOG.debug("Content {} consumed {} for {}", advanced, consumed, exchange.getRequest()); - SenderState current = senderState.get(); + if (advanced) + { + sendContent(exchange, content, this); + return Action.SCHEDULED; + } + + if (consumed) + { + sendContent(exchange, content, lastCallback); + return Action.IDLE; + } + + SenderState current = HttpSender.this.senderState.get(); switch (current) { case SENDING: { - if (advanced) + if (updateSenderState(current, SenderState.IDLE)) { - // There is more content to send - sendContent(exchange, content, this); - return Action.SCHEDULED; - } - else if (consumed) - { - sendContent(exchange, content, lastCallback); + if (LOG.isDebugEnabled()) + LOG.debug("Content is deferred for {}", exchange.getRequest()); return Action.IDLE; } - else - { - if (updateSenderState(current, SenderState.IDLE)) - { - LOG.debug("Waiting for deferred content for {}", request); - return Action.IDLE; - } - break; - } + break; } case SENDING_WITH_CONTENT: { - if (updateSenderState(current, SenderState.SENDING)) - { - LOG.debug("Deferred content available for {}", request); - if (advanced) - { - sendContent(exchange, content, this); - return Action.SCHEDULED; - } - } - throw illegalSenderState(current); + updateSenderState(current, SenderState.SENDING); + break; } default: { @@ -810,6 +751,8 @@ public abstract class HttpSender implements AsyncContentProvider.Listener @Override public void succeeded() { + ByteBuffer buffer = content.getContent(); + someToContent(getHttpExchange().getRequest(), buffer); content.succeeded(); super.succeeded(); } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/PoolingHttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/PoolingHttpDestination.java index 6bba6a58205..73c13ad45da 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/PoolingHttpDestination.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/PoolingHttpDestination.java @@ -140,8 +140,11 @@ public abstract class PoolingHttpDestination extends HttpD protected abstract void send(C connection, HttpExchange exchange); - public void release(C connection) + @Override + public void release(Connection c) { + @SuppressWarnings("unchecked") + C connection = (C)c; LOG.debug("{} released", connection); HttpClient client = getHttpClient(); if (client.isRunning()) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java index 6fa640cce9d..b100ea81114 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java @@ -27,6 +27,8 @@ import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.IteratingNestedCallback; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -106,31 +108,20 @@ public class ResponseNotifier } } - public void notifyContent(List listeners, Response response, ByteBuffer buffer) + public void notifyContent(List listeners, Response response, ByteBuffer buffer, Callback callback) { - // Slice the buffer to avoid that listeners peek into data they should not look at. - buffer = buffer.slice(); - if (!buffer.hasRemaining()) - return; - // Optimized to avoid allocations of iterator instances - for (int i = 0; i < listeners.size(); ++i) - { - Response.ResponseListener listener = listeners.get(i); - if (listener instanceof Response.ContentListener) - { - // The buffer was sliced, so we always clear it (position=0, limit=capacity) - // before passing it to the listener that may consume it. - buffer.clear(); - notifyContent((Response.ContentListener)listener, response, buffer); - } - } + // Here we use an IteratingNestedCallback not to avoid the stack overflow, but to + // invoke the listeners one after the other. When all of them have invoked the + // callback they got passed, the callback passed to this method is finally invoked. + ContentCallback contentCallback = new ContentCallback(listeners, response, buffer, callback); + contentCallback.iterate(); } - private void notifyContent(Response.ContentListener listener, Response response, ByteBuffer buffer) + private void notifyContent(Response.AsyncContentListener listener, Response response, ByteBuffer buffer, Callback callback) { try { - listener.onContent(response, buffer); + listener.onContent(response, buffer, callback); } catch (Throwable x) { @@ -218,7 +209,8 @@ public class ResponseNotifier } notifyHeaders(listeners, response); if (response instanceof ContentResponse) - notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent())); + // TODO: handle callback + notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent()), new Callback.Adapter()); notifySuccess(listeners, response); } @@ -239,7 +231,8 @@ public class ResponseNotifier } notifyHeaders(listeners, response); if (response instanceof ContentResponse) - notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent())); + // TODO: handle callback + notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent()), new Callback.Adapter()); notifyFailure(listeners, response, failure); } @@ -248,4 +241,51 @@ public class ResponseNotifier forwardFailure(listeners, response, responseFailure); notifyComplete(listeners, new Result(request, requestFailure, response, responseFailure)); } + + private class ContentCallback extends IteratingNestedCallback + { + private final List listeners; + private final Response response; + private final ByteBuffer buffer; + private int index; + + private ContentCallback(List listeners, Response response, ByteBuffer buffer, Callback callback) + { + super(callback); + this.listeners = listeners; + this.response = response; + // Slice the buffer to avoid that listeners peek into data they should not look at. + this.buffer = buffer.slice(); + } + + @Override + protected Action process() throws Exception + { + if (index == listeners.size()) + return Action.SUCCEEDED; + + Response.ResponseListener listener = listeners.get(index); + if (listener instanceof Response.AsyncContentListener) + { + // The buffer was sliced, so we always clear it + // (clear => position=0, limit=capacity) before + // passing it to the listener that may consume it. + buffer.clear(); + ResponseNotifier.this.notifyContent((Response.AsyncContentListener)listener, response, buffer, this); + return Action.SCHEDULED; + } + else + { + succeeded(); + return Action.SCHEDULED; + } + } + + @Override + public void succeeded() + { + ++index; + super.succeeded(); + } + } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java index cc3b5f85a68..b7caf08b8a2 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java @@ -338,11 +338,17 @@ public interface Request Request onResponseHeaders(Response.HeadersListener listener); /** - * @param listener a listener for response content events + * @param listener a consuming listener for response content events * @return this request object */ Request onResponseContent(Response.ContentListener listener); + /** + * @param listener an asynchronous listener for response content events + * @return this request object + */ + Request onResponseContentAsync(Response.AsyncContentListener listener); + /** * @param listener a listener for response success event * @return this request object diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java index 5f60ef80198..f1ef5c5fe23 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java @@ -26,6 +26,7 @@ import org.eclipse.jetty.client.util.BufferingResponseListener; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.util.Callback; /** *

    {@link Response} represents a HTTP response and offers methods to retrieve status code, HTTP version @@ -152,6 +153,11 @@ public interface Response public void onContent(Response response, ByteBuffer content); } + public interface AsyncContentListener extends ResponseListener + { + public void onContent(Response response, ByteBuffer content, Callback callback); + } + /** * Listener for the response succeeded event. */ @@ -204,7 +210,7 @@ public interface Response /** * Listener for all response events. */ - public interface Listener extends BeginListener, HeaderListener, HeadersListener, ContentListener, SuccessListener, FailureListener, CompleteListener + public interface Listener extends BeginListener, HeaderListener, HeadersListener, ContentListener, AsyncContentListener, SuccessListener, FailureListener, CompleteListener { /** * An empty implementation of {@link Listener} @@ -232,6 +238,20 @@ public interface Response { } + @Override + public void onContent(Response response, ByteBuffer content, Callback callback) + { + try + { + onContent(response, content); + callback.succeeded(); + } + catch (Exception x) + { + callback.failed(x); + } + } + @Override public void onSuccess(Response response) { diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpChannelOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpChannelOverHTTP.java index cd5bcf274f6..912c058b202 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpChannelOverHTTP.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpChannelOverHTTP.java @@ -20,10 +20,12 @@ package org.eclipse.jetty.client.http; import org.eclipse.jetty.client.HttpChannel; import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; +import org.eclipse.jetty.http.HttpVersion; public class HttpChannelOverHTTP extends HttpChannel { @@ -77,10 +79,23 @@ public class HttpChannelOverHTTP extends HttpChannel public void exchangeTerminated(Result result) { super.exchangeTerminated(result); - HttpFields responseHeaders = result.getResponse().getHeaders(); - boolean close = result.isFailed() || - responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()) || - receiver.isShutdown(); + Response response = result.getResponse(); + HttpFields responseHeaders = response.getHeaders(); + boolean close = result.isFailed() || receiver.isShutdown(); + // Only check HTTP headers if there are no failures. + if (!close) + { + if (response.getVersion().compareTo(HttpVersion.HTTP_1_1) < 0) + { + // HTTP 1.0 must close the connection unless it has an explicit keep alive. + close = !responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString()); + } + else + { + // HTTP 1.1 or greater closes only if it has an explicit close. + close = responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()); + } + } if (close) connection.close(); else diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java index 1b688e16e50..f98f4828942 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java @@ -32,12 +32,13 @@ import org.eclipse.jetty.http.HttpParser; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.EndPoint; -import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.CompletableCallback; public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.ResponseHandler { private final HttpParser parser = new HttpParser(this); + private ByteBuffer buffer; private boolean shutdown; public HttpReceiverOverHTTP(HttpChannelOverHTTP channel) @@ -58,68 +59,97 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res public void receive() { - HttpConnectionOverHTTP connection = getHttpConnection(); - EndPoint endPoint = connection.getEndPoint(); HttpClient client = getHttpDestination().getHttpClient(); ByteBufferPool bufferPool = client.getByteBufferPool(); - ByteBuffer buffer = bufferPool.acquire(client.getResponseBufferSize(), true); - try - { - while (true) - { - // Connection may be closed in a parser callback - if (connection.isClosed()) - { - LOG.debug("{} closed", connection); - break; - } - else - { - int read = endPoint.fill(buffer); - if (LOG.isDebugEnabled()) // Avoid boxing of variable 'read' - LOG.debug("Read {} bytes from {}", read, endPoint); - if (read > 0) - { - parse(buffer); - } - else if (read == 0) - { - fillInterested(); - break; - } - else - { - shutdown(); - break; - } - } - } - } - catch (EofException x) - { - LOG.ignore(x); - failAndClose(x); - } - catch (Exception x) - { - LOG.debug(x); - failAndClose(x); - } - finally + buffer = bufferPool.acquire(client.getResponseBufferSize(), true); + process(); + } + + private void process() + { + if (readAndParse()) { + HttpClient client = getHttpDestination().getHttpClient(); + ByteBufferPool bufferPool = client.getByteBufferPool(); bufferPool.release(buffer); + // Don't linger the buffer around if we are idle. + buffer = null; } } - private void parse(ByteBuffer buffer) + private boolean readAndParse() { - while (buffer.hasRemaining()) - parser.parseNext(buffer); + HttpConnectionOverHTTP connection = getHttpConnection(); + EndPoint endPoint = connection.getEndPoint(); + ByteBuffer buffer = this.buffer; + while (true) + { + try + { + // Connection may be closed in a parser callback. + if (connection.isClosed()) + { + if (LOG.isDebugEnabled()) + LOG.debug("{} closed", connection); + return true; + } + + if (!parse(buffer)) + return false; + + int read = endPoint.fill(buffer); + // Avoid boxing of variable 'read' + if (LOG.isDebugEnabled()) + LOG.debug("Read {} bytes from {}", read, endPoint); + + if (read > 0) + { + if (!parse(buffer)) + return false; + } + else if (read == 0) + { + fillInterested(); + return true; + } + else + { + shutdown(); + return true; + } + } + catch (Throwable x) + { + LOG.debug(x); + failAndClose(x); + return true; + } + } + } + + /** + * Parses a HTTP response from the given {@code buffer}. + * + * @param buffer the response bytes + * @return true to indicate that the parsing may proceed (for example with another response), + * false to indicate that the parsing should be interrupted (and will be resumed by another thread). + */ + private boolean parse(ByteBuffer buffer) + { + // Must parse even if the buffer is fully consumed, to allow the + // parser to advance from asynchronous content to response complete. + if (parser.parseNext(buffer)) + { + // If the parser returns true, we need to differentiate two cases: + // A) the response is completed, so the parser is in START state; + // B) the content is handled asynchronously, so the parser is in CONTENT state. + return parser.isStart(); + } + return true; } private void fillInterested() { - // TODO: do we need to call fillInterested() only if we are not failed (or we have an exchange) ? getHttpChannel().getHttpConnection().fillInterested(); } @@ -195,8 +225,22 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res if (exchange == null) return false; - responseContent(exchange, buffer); - return false; + CompletableCallback callback = new CompletableCallback() + { + @Override + public void resume() + { + LOG.debug("Content consumed asynchronously, resuming processing"); + process(); + } + + public void abort(Throwable x) + { + failAndClose(x); + } + }; + responseContent(exchange, buffer, callback); + return callback.tryComplete(); } @Override diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpSenderOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpSenderOverHTTP.java index 543bd17c14f..a00be54cac2 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpSenderOverHTTP.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpSenderOverHTTP.java @@ -114,7 +114,7 @@ public class HttpSenderOverHTTP extends HttpSender } } } - catch (Exception x) + catch (Throwable x) { LOG.debug(x); callback.failed(x); diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java index bc5f8146f8e..e30c6b90155 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java @@ -18,11 +18,14 @@ package org.eclipse.jetty.client.util; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.charset.UnsupportedCharsetException; +import java.util.Arrays; import java.util.Locale; import org.eclipse.jetty.client.api.Response; @@ -30,6 +33,7 @@ import org.eclipse.jetty.client.api.Response.Listener; import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.util.BufferUtil; /** *

    Implementation of {@link Listener} that buffers the content up to a maximum length @@ -40,7 +44,7 @@ import org.eclipse.jetty.http.HttpHeader; public abstract class BufferingResponseListener extends Listener.Adapter { private final int maxLength; - private volatile byte[] buffer = new byte[0]; + private volatile ByteBuffer buffer; private volatile String encoding; /** @@ -58,53 +62,57 @@ public abstract class BufferingResponseListener extends Listener.Adapter */ public BufferingResponseListener(int maxLength) { - this.maxLength = maxLength; + this.maxLength=maxLength; } @Override public void onHeaders(Response response) { + super.onHeaders(response); + HttpFields headers = response.getHeaders(); long length = headers.getLongField(HttpHeader.CONTENT_LENGTH.asString()); if (length > maxLength) { response.abort(new IllegalArgumentException("Buffering capacity exceeded")); + return; } - else + + buffer=BufferUtil.allocate((length > 0)?(int)length:1024); + + String contentType = headers.get(HttpHeader.CONTENT_TYPE); + if (contentType != null) { - String contentType = headers.get(HttpHeader.CONTENT_TYPE); - if (contentType != null) + String charset = "charset="; + int index = contentType.toLowerCase(Locale.ENGLISH).indexOf(charset); + if (index > 0) { - String charset = "charset="; - int index = contentType.toLowerCase(Locale.ENGLISH).indexOf(charset); + String encoding = contentType.substring(index + charset.length()); + // Sometimes charsets arrive with an ending semicolon + index = encoding.indexOf(';'); if (index > 0) - { - String encoding = contentType.substring(index + charset.length()); - // Sometimes charsets arrive with an ending semicolon - index = encoding.indexOf(';'); - if (index > 0) - encoding = encoding.substring(0, index); - this.encoding = encoding; - } + encoding = encoding.substring(0, index); + this.encoding = encoding; } } } @Override public void onContent(Response response, ByteBuffer content) - { - long newLength = buffer.length + content.remaining(); - if (newLength > maxLength) + { + int length = content.remaining(); + if (length>BufferUtil.space(buffer)) { - response.abort(new IllegalArgumentException("Buffering capacity exceeded")); - } - else - { - byte[] newBuffer = new byte[(int)newLength]; - System.arraycopy(buffer, 0, newBuffer, 0, buffer.length); - content.get(newBuffer, buffer.length, content.remaining()); - buffer = newBuffer; + int requiredCapacity = buffer==null?0:buffer.capacity()+length; + if (requiredCapacity>maxLength) + response.abort(new IllegalArgumentException("Buffering capacity exceeded")); + + int newCapacity = Math.min(Integer.highestOneBit(requiredCapacity) << 1, maxLength); + buffer = BufferUtil.ensureCapacity(buffer,newCapacity); } + + BufferUtil.append(buffer, content); + } @Override @@ -121,7 +129,9 @@ public abstract class BufferingResponseListener extends Listener.Adapter */ public byte[] getContent() { - return buffer; + if (buffer==null) + return new byte[0]; + return BufferUtil.toArray(buffer); } /** @@ -144,14 +154,9 @@ public abstract class BufferingResponseListener extends Listener.Adapter */ public String getContentAsString(String encoding) { - try - { - return new String(getContent(), encoding); - } - catch (UnsupportedEncodingException x) - { - throw new UnsupportedCharsetException(encoding); - } + if (buffer==null) + return null; + return BufferUtil.toString(buffer, Charset.forName(encoding)); } /** @@ -161,6 +166,19 @@ public abstract class BufferingResponseListener extends Listener.Adapter */ public String getContentAsString(Charset encoding) { - return new String(getContent(), encoding); + if (buffer==null) + return null; + return BufferUtil.toString(buffer, encoding); + } + + /* ------------------------------------------------------------ */ + /** + * @return Content as InputStream + */ + public InputStream getContentAsInputStream() + { + if (buffer==null) + return new ByteArrayInputStream(new byte[]{}); + return new ByteArrayInputStream(buffer.array(), buffer.arrayOffset(), buffer.remaining()); } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java index a0af29571af..55a82f79ce1 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java @@ -24,6 +24,7 @@ import java.io.InterruptedIOException; import java.nio.ByteBuffer; import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.Queue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -33,6 +34,7 @@ import org.eclipse.jetty.client.api.ContentProvider; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.util.ArrayQueue; +import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; /** @@ -83,10 +85,11 @@ import org.eclipse.jetty.util.Callback; */ public class DeferredContentProvider implements AsyncContentProvider, Closeable { - private static final ByteBuffer CLOSE = ByteBuffer.allocate(0); + private static final Callback EMPTY_CALLBACK = new Callback.Adapter(); + private static final AsyncChunk CLOSE = new AsyncChunk(BufferUtil.EMPTY_BUFFER, EMPTY_CALLBACK); private final Object lock = this; - private final Queue chunks = new ArrayQueue<>(4, 64, lock); + private final Queue chunks = new ArrayQueue<>(4, 64, lock); private final AtomicReference listener = new AtomicReference<>(); private final Iterator iterator = new DeferredContentProviderIterator(); private final AtomicBoolean closed = new AtomicBoolean(); @@ -126,12 +129,22 @@ public class DeferredContentProvider implements AsyncContentProvider, Closeable * @return true if the content was added, false otherwise */ public boolean offer(ByteBuffer buffer) + { + return offer(buffer, EMPTY_CALLBACK); + } + + public boolean offer(ByteBuffer buffer, Callback callback) + { + return offer(new AsyncChunk(buffer, callback)); + } + + private boolean offer(AsyncChunk chunk) { boolean result; synchronized (lock) { - result = chunks.offer(buffer); - if (result && buffer != CLOSE) + result = chunks.offer(chunk); + if (result && chunk != CLOSE) ++size; } if (result) @@ -186,7 +199,7 @@ public class DeferredContentProvider implements AsyncContentProvider, Closeable private class DeferredContentProviderIterator implements Iterator, Callback { - private ByteBuffer current; + private AsyncChunk current; @Override public boolean hasNext() @@ -202,10 +215,10 @@ public class DeferredContentProvider implements AsyncContentProvider, Closeable { synchronized (lock) { - ByteBuffer element = current = chunks.poll(); - if (element == CLOSE) + AsyncChunk chunk = current = chunks.poll(); + if (chunk == CLOSE) throw new NoSuchElementException(); - return element; + return chunk == null ? null : chunk.buffer; } } @@ -218,24 +231,44 @@ public class DeferredContentProvider implements AsyncContentProvider, Closeable @Override public void succeeded() { + AsyncChunk chunk; synchronized (lock) { - if (current != null) + chunk = current; + if (chunk != null) { --size; lock.notify(); } } + if (chunk != null) + chunk.callback.succeeded(); } @Override public void failed(Throwable x) { + AsyncChunk chunk; synchronized (lock) { + chunk = current; failure = x; lock.notify(); } + if (chunk != null) + chunk.callback.failed(x); + } + } + + private static class AsyncChunk + { + private final ByteBuffer buffer; + private final Callback callback; + + private AsyncChunk(ByteBuffer buffer, Callback callback) + { + this.buffer = Objects.requireNonNull(buffer); + this.callback = Objects.requireNonNull(callback); } } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java index bd0606530d4..89d3022ae27 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java @@ -102,6 +102,16 @@ public class InputStreamContentProvider implements ContentProvider return ByteBuffer.wrap(buffer, offset, length); } + /** + * Callback method invoked when an exception is thrown while reading + * from the stream. + * + * @param failure the exception thrown while reading from the stream. + */ + protected void onReadFailure(Throwable failure) + { + } + @Override public Iterator iterator() { @@ -166,6 +176,7 @@ public class InputStreamContentProvider implements ContentProvider if (failure == null) { failure = x; + onReadFailure(x); // Signal we have more content to cause a call to // next() which will throw NoSuchElementException. hasNext = Boolean.TRUE; diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAsyncContentTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAsyncContentTest.java new file mode 100644 index 00000000000..4ca0db19ccf --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAsyncContentTest.java @@ -0,0 +1,118 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.client; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.client.api.Response; +import org.eclipse.jetty.client.api.Result; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.junit.Assert; +import org.junit.Test; + +public class HttpClientAsyncContentTest extends AbstractHttpClientServerTest +{ + public HttpClientAsyncContentTest(SslContextFactory sslContextFactory) + { + super(sslContextFactory); + } + + @Test + public void testSmallAsyncContent() throws Exception + { + start(new AbstractHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + ServletOutputStream output = response.getOutputStream(); + output.write(65); + output.flush(); + output.write(66); + } + }); + + final AtomicInteger contentCount = new AtomicInteger(); + final AtomicReference callbackRef = new AtomicReference<>(); + final AtomicReference contentLatch = new AtomicReference<>(new CountDownLatch(1)); + final CountDownLatch completeLatch = new CountDownLatch(1); + client.newRequest("localhost", connector.getLocalPort()) + .scheme(scheme) + .onResponseContentAsync(new Response.AsyncContentListener() + { + @Override + public void onContent(Response response, ByteBuffer content, Callback callback) + { + contentCount.incrementAndGet(); + callbackRef.set(callback); + contentLatch.get().countDown(); + } + }) + .send(new Response.CompleteListener() + { + @Override + public void onComplete(Result result) + { + completeLatch.countDown(); + } + }); + + Assert.assertTrue(contentLatch.get().await(5, TimeUnit.SECONDS)); + Callback callback = callbackRef.get(); + + // Wait a while to be sure that the parsing does not proceed. + TimeUnit.MILLISECONDS.sleep(1000); + + Assert.assertEquals(1, contentCount.get()); + + // Succeed the content callback to proceed with parsing. + callbackRef.set(null); + contentLatch.set(new CountDownLatch(1)); + callback.succeeded(); + + Assert.assertTrue(contentLatch.get().await(5, TimeUnit.SECONDS)); + callback = callbackRef.get(); + + // Wait a while to be sure that the parsing does not proceed. + TimeUnit.MILLISECONDS.sleep(1000); + + Assert.assertEquals(2, contentCount.get()); + Assert.assertEquals(1, completeLatch.getCount()); + + // Succeed the content callback to proceed with parsing. + callbackRef.set(null); + contentLatch.set(new CountDownLatch(1)); + callback.succeeded(); + + Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS)); + Assert.assertEquals(2, contentCount.get()); + } +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java index bedd3482776..9b77675ff08 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java @@ -37,12 +37,14 @@ import java.util.List; import java.util.NoSuchElementException; import java.util.Random; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Exchanger; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.zip.GZIPOutputStream; + import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; @@ -69,6 +71,7 @@ import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.toolchain.test.TestingDir; import org.eclipse.jetty.toolchain.test.annotation.Slow; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FuturePromise; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.ssl.SslContextFactory; @@ -985,6 +988,13 @@ public class HttpClientTest extends AbstractHttpClientServerTest counter.incrementAndGet(); } + @Override + public void onContent(Response response, ByteBuffer content, Callback callback) + { + // Should not be invoked + counter.incrementAndGet(); + } + @Override public void onSuccess(Response response) { @@ -1012,6 +1022,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest .onResponseHeader(listener) .onResponseHeaders(listener) .onResponseContent(listener) + .onResponseContentAsync(listener) .onResponseSuccess(listener) .onResponseFailure(listener) .send(listener); @@ -1038,26 +1049,33 @@ public class HttpClientTest extends AbstractHttpClientServerTest } }); - final AtomicInteger complete = new AtomicInteger(); + final Exchanger ex = new Exchanger(); BufferingResponseListener listener = new BufferingResponseListener() { @Override public void onComplete(Result result) { - complete.incrementAndGet(); + try + { + ex.exchange(result.getResponse()); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } } }; - ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + + client.newRequest("localhost", connector.getLocalPort()) .scheme(scheme) - .onResponseContent(listener) - .onComplete(listener) - .send(); + .send(listener); + + Response response = ex.exchange(null); Assert.assertEquals(200, response.getStatus()); - Assert.assertEquals(1, complete.get()); Assert.assertArrayEquals(content, listener.getContent()); - Assert.assertArrayEquals(content, response.getContent()); + } @Test diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java index 3313e224de0..98c59c85305 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java @@ -24,7 +24,6 @@ import java.util.Arrays; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; - import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -37,6 +36,7 @@ import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.http.HttpDestinationOverHTTP; import org.eclipse.jetty.client.util.ByteBufferContentProvider; import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.toolchain.test.annotation.Slow; @@ -489,4 +489,40 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest Assert.assertEquals(0, idleConnections.size()); Assert.assertEquals(0, activeConnections.size()); } + + @Test + public void testConnectionForHTTP10ResponseIsRemoved() throws Exception + { + start(new EmptyServerHandler()); + + String host = "localhost"; + int port = connector.getLocalPort(); + HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port); + ConnectionPool connectionPool = destination.getConnectionPool(); + + final BlockingQueue idleConnections = connectionPool.getIdleConnections(); + Assert.assertEquals(0, idleConnections.size()); + + final BlockingQueue activeConnections = connectionPool.getActiveConnections(); + Assert.assertEquals(0, activeConnections.size()); + + client.setStrictEventOrdering(false); + ContentResponse response = client.newRequest(host, port) + .scheme(scheme) + .onResponseBegin(new Response.BeginListener() + { + @Override + public void onBegin(Response response) + { + // Simulate a HTTP 1.0 response has been received. + ((HttpResponse)response).version(HttpVersion.HTTP_1_0); + } + }) + .send(); + + Assert.assertEquals(200, response.getStatus()); + + Assert.assertEquals(0, idleConnections.size()); + Assert.assertEquals(0, activeConnections.size()); + } } diff --git a/jetty-distribution/pom.xml b/jetty-distribution/pom.xml index 542484fda82..d81eb575abf 100644 --- a/jetty-distribution/pom.xml +++ b/jetty-distribution/pom.xml @@ -569,8 +569,8 @@ jetty.home=${assembly-directory} jetty.base=${assembly-directory}/demo-base - --add-to-start=server,continuation,deploy,ext,resources,client,annotations,jndi,servlets - --add-to-startd-ini=jsp,jstl,http,https + --add-to-start=server,continuation,deploy,websocket,ext,resources,client,annotations,jndi,servlets + --add-to-startd=jsp,jstl,http,https diff --git a/jetty-distribution/src/main/resources/bin/jetty.sh b/jetty-distribution/src/main/resources/bin/jetty.sh index ce489dbe1d4..3190332432e 100644 --- a/jetty-distribution/src/main/resources/bin/jetty.sh +++ b/jetty-distribution/src/main/resources/bin/jetty.sh @@ -105,8 +105,14 @@ findDirectory() running() { - local PID=$(cat "$1" 2>/dev/null) || return 1 - kill -0 "$PID" 2>/dev/null + if [ -f "$1" ] + then + local PID=$(cat "$1" 2>/dev/null) || return 1 + kill -0 "$PID" 2>/dev/null + return + fi + rm -f "$1" + return 1 } started() @@ -408,16 +414,10 @@ case "$ACTION" in else - if [ -f "$JETTY_PID" ] + if running $JETTY_PID then - if running $JETTY_PID - then - echo "Already Running!" - exit 1 - else - # dead pid file - remove - rm -f "$JETTY_PID" - fi + echo "Already Running $(cat $JETTY_PID)!" + exit 1 fi if [ "$JETTY_USER" ] @@ -519,16 +519,10 @@ case "$ACTION" in run|demo) echo "Running Jetty: " - if [ -f "$JETTY_PID" ] + if running "$JETTY_PID" then - if running "$JETTY_PID" - then - echo "Already Running!" - exit 1 - else - # dead pid file - remove - rm -f "$JETTY_PID" - fi + echo Already Running $(cat "$JETTY_PID")! + exit 1 fi exec "${RUN_CMD[@]}" @@ -550,7 +544,7 @@ case "$ACTION" in echo "RUN_CMD = ${RUN_CMD[*]}" echo - if [ -f "$JETTY_PID" ] + if running "$JETTY_PID" then echo "Jetty running pid=$(< "$JETTY_PID")" exit 0 diff --git a/jetty-distribution/src/main/resources/modules/jsp.mod b/jetty-distribution/src/main/resources/modules/jsp.mod index 4924ed91030..fa5b9fdfa95 100644 --- a/jetty-distribution/src/main/resources/modules/jsp.mod +++ b/jetty-distribution/src/main/resources/modules/jsp.mod @@ -16,5 +16,5 @@ jsp-impl/${jsp-impl}-jsp # default jetty >= 9.2 jsp-impl=apache -# To use an non-jdk compiler for JSP compilation uncomment next line +# To use a non-jdk compiler for JSP compilation when using glassfish uncomment next line # -Dorg.apache.jasper.compiler.disablejsr199=true diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java index a6f7de6ec3b..59775694ebb 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java @@ -28,10 +28,9 @@ import org.eclipse.jetty.fcgi.generator.Flusher; import org.eclipse.jetty.fcgi.generator.Generator; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; -import org.eclipse.jetty.http.HttpHeader; -import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.io.IdleTimeout; +import org.eclipse.jetty.util.Callback; public class HttpChannelOverFCGI extends HttpChannel { @@ -83,42 +82,46 @@ public class HttpChannelOverFCGI extends HttpChannel return receiver.abort(cause); } - protected void responseBegin(int code, String reason) + protected boolean responseBegin(int code, String reason) { HttpExchange exchange = getHttpExchange(); - if (exchange != null) - { - exchange.getResponse().version(version).status(code).reason(reason); - receiver.responseBegin(exchange); - } + if (exchange == null) + return false; + exchange.getResponse().version(version).status(code).reason(reason); + return receiver.responseBegin(exchange); } - protected void responseHeader(HttpField field) + protected boolean responseHeader(HttpField field) { HttpExchange exchange = getHttpExchange(); - if (exchange != null) - receiver.responseHeader(exchange, field); + return exchange != null && receiver.responseHeader(exchange, field); } - protected void responseHeaders() + protected boolean responseHeaders() { HttpExchange exchange = getHttpExchange(); - if (exchange != null) - receiver.responseHeaders(exchange); + return exchange != null && receiver.responseHeaders(exchange); } - protected void content(ByteBuffer buffer) + protected boolean content(ByteBuffer buffer, Callback callback) { HttpExchange exchange = getHttpExchange(); if (exchange != null) - receiver.responseContent(exchange, buffer); + return receiver.responseContent(exchange, buffer, callback); + callback.succeeded(); + return false; } - protected void responseSuccess() + protected boolean responseSuccess() { HttpExchange exchange = getHttpExchange(); - if (exchange != null) - receiver.responseSuccess(exchange); + return exchange != null && receiver.responseSuccess(exchange); + } + + protected boolean responseFailure(Throwable failure) + { + HttpExchange exchange = getHttpExchange(); + return exchange != null && receiver.responseFailure(failure); } @Override @@ -126,12 +129,10 @@ public class HttpChannelOverFCGI extends HttpChannel { super.exchangeTerminated(result); idle.onClose(); - boolean close = result.isFailed(); HttpFields responseHeaders = result.getResponse().getHeaders(); - close |= responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()); - if (close) - connection.close(); - else + if (result.isFailed()) + connection.close(result.getFailure()); + else if (!connection.closeByHTTP(responseHeaders)) connection.release(this); } @@ -154,7 +155,8 @@ public class HttpChannelOverFCGI extends HttpChannel @Override protected void onIdleExpired(TimeoutException timeout) { - LOG.debug("Idle timeout for request {}", request); + if (LOG.isDebugEnabled()) + LOG.debug("Idle timeout for request {}", request); connection.abort(timeout); } diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java index eed8c892a60..00c6778d5e3 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java @@ -69,7 +69,7 @@ public class HttpClientTransportOverFCGI extends AbstractHttpClientTransport public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map context) throws IOException { HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY); - HttpConnectionOverFCGI connection = new HttpConnectionOverFCGI(endPoint, destination); + HttpConnectionOverFCGI connection = new HttpConnectionOverFCGI(endPoint, destination, isMultiplexed()); LOG.debug("Created {}", connection); @SuppressWarnings("unchecked") Promise promise = (Promise)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY); diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java index 262a7c498c9..caafebaff80 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java @@ -31,7 +31,6 @@ import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpConnection; import org.eclipse.jetty.client.HttpDestination; import org.eclipse.jetty.client.HttpExchange; -import org.eclipse.jetty.client.PoolingHttpDestination; import org.eclipse.jetty.client.api.Connection; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; @@ -39,10 +38,14 @@ import org.eclipse.jetty.fcgi.FCGI; import org.eclipse.jetty.fcgi.generator.Flusher; import org.eclipse.jetty.fcgi.parser.ClientParser; import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.CompletableCallback; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -55,14 +58,17 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec private final AtomicBoolean closed = new AtomicBoolean(); private final Flusher flusher; private final HttpDestination destination; + private final boolean multiplexed; private final Delegate delegate; private final ClientParser parser; + private ByteBuffer buffer; - public HttpConnectionOverFCGI(EndPoint endPoint, HttpDestination destination) + public HttpConnectionOverFCGI(EndPoint endPoint, HttpDestination destination, boolean multiplexed) { super(endPoint, destination.getHttpClient().getExecutor(), destination.getHttpClient().isDispatchIO()); - this.flusher = new Flusher(endPoint); this.destination = destination; + this.multiplexed = multiplexed; + this.flusher = new Flusher(endPoint); this.delegate = new Delegate(destination); this.parser = new ClientParser(new ResponseListener()); requests.addLast(0); @@ -94,53 +100,76 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec @Override public void onFillable() { - EndPoint endPoint = getEndPoint(); HttpClient client = destination.getHttpClient(); ByteBufferPool bufferPool = client.getByteBufferPool(); - ByteBuffer buffer = bufferPool.acquire(client.getResponseBufferSize(), true); - try + buffer = bufferPool.acquire(client.getResponseBufferSize(), true); + process(); + } + + private void process() + { + if (readAndParse()) { - while (true) + HttpClient client = destination.getHttpClient(); + ByteBufferPool bufferPool = client.getByteBufferPool(); + bufferPool.release(buffer); + // Don't linger the buffer around if we are idle. + buffer = null; + } + } + + private boolean readAndParse() + { + EndPoint endPoint = getEndPoint(); + ByteBuffer buffer = this.buffer; + while (true) + { + try { + if (!parse(buffer)) + return false; + int read = endPoint.fill(buffer); - if (LOG.isDebugEnabled()) // Avoid boxing of variable 'read' + if (LOG.isDebugEnabled()) // Avoid boxing of variable 'read'. LOG.debug("Read {} bytes from {}", read, endPoint); if (read > 0) { - parse(buffer); + if (!parse(buffer)) + return false; } else if (read == 0) { fillInterested(); - break; + return true; } else { shutdown(); - break; + return true; } } - } - catch (Exception x) - { - LOG.debug(x); - // TODO: fail and close ? - } - finally - { - bufferPool.release(buffer); + catch (Exception x) + { + LOG.debug(x); + close(x); + return false; + } } } - private void parse(ByteBuffer buffer) + private boolean parse(ByteBuffer buffer) { - while (buffer.hasRemaining()) - parser.parse(buffer); + return !parser.parse(buffer); } private void shutdown() { - close(new EOFException()); + // Close explicitly only if we are idle, since the request may still + // be in progress, otherwise close only if we can fail the responses. + if (channels.isEmpty()) + close(); + else + failAndClose(new EOFException()); } @Override @@ -153,13 +182,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec protected void release(HttpChannelOverFCGI channel) { channels.remove(channel.getRequest()); - if (destination instanceof PoolingHttpDestination) - { - @SuppressWarnings("unchecked") - PoolingHttpDestination fcgiDestination = - (PoolingHttpDestination)destination; - fcgiDestination.release(this); - } + destination.release(this); } @Override @@ -168,7 +191,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec close(new AsynchronousCloseException()); } - private void close(Throwable failure) + protected void close(Throwable failure) { if (closed.compareAndSet(false, true)) { @@ -184,6 +207,16 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec } } + protected boolean closeByHTTP(HttpFields fields) + { + if (multiplexed) + return false; + if (!fields.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString())) + return false; + close(); + return true; + } + protected void abort(Throwable failure) { for (HttpChannelOverFCGI channel : channels.values()) @@ -195,6 +228,15 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec channels.clear(); } + private void failAndClose(Throwable failure) + { + boolean result = false; + for (HttpChannelOverFCGI channel : channels.values()) + result |= channel.responseFailure(failure); + if (result) + close(failure); + } + private int acquireRequest() { synchronized (requests) @@ -291,7 +333,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec } @Override - public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) + public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) { switch (stream) { @@ -299,7 +341,25 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec { HttpChannelOverFCGI channel = channels.get(request); if (channel != null) - channel.content(buffer); + { + CompletableCallback callback = new CompletableCallback() + { + @Override + public void resume() + { + LOG.debug("Content consumed asynchronously, resuming processing"); + process(); + } + + @Override + public void abort(Throwable x) + { + close(x); + } + }; + channel.content(buffer, callback); + return callback.tryComplete(); + } else noChannel(request); break; @@ -314,6 +374,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec throw new IllegalArgumentException(); } } + return false; } @Override @@ -322,8 +383,23 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec HttpChannelOverFCGI channel = channels.get(request); if (channel != null) { - channel.responseSuccess(); - releaseRequest(request); + if (channel.responseSuccess()) + releaseRequest(request); + } + else + { + noChannel(request); + } + } + + @Override + public void onFailure(int request, Throwable failure) + { + HttpChannelOverFCGI channel = channels.get(request); + if (channel != null) + { + if (channel.responseFailure(failure)) + releaseRequest(request); } else { diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpReceiverOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpReceiverOverFCGI.java index a222c8c4654..2cdd34d00f6 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpReceiverOverFCGI.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpReceiverOverFCGI.java @@ -24,6 +24,7 @@ import org.eclipse.jetty.client.HttpChannel; import org.eclipse.jetty.client.HttpExchange; import org.eclipse.jetty.client.HttpReceiver; import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.util.Callback; public class HttpReceiverOverFCGI extends HttpReceiver { @@ -51,9 +52,9 @@ public class HttpReceiverOverFCGI extends HttpReceiver } @Override - protected boolean responseContent(HttpExchange exchange, ByteBuffer buffer) + protected boolean responseContent(HttpExchange exchange, ByteBuffer buffer, Callback callback) { - return super.responseContent(exchange, buffer); + return super.responseContent(exchange, buffer, callback); } @Override diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java index 39619268a7b..e113dd29b78 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java @@ -88,21 +88,36 @@ public class ServerGenerator extends Generator return generateContent(request, buffer, true, false, callback, FCGI.FrameType.STDOUT); } - public Result generateResponseContent(int request, ByteBuffer content, boolean lastContent, Callback callback) + public Result generateResponseContent(int request, ByteBuffer content, boolean lastContent, boolean aborted, Callback callback) { - Result result = generateContent(request, content, false, lastContent, callback, FCGI.FrameType.STDOUT); - if (lastContent) + if (aborted) { - // Generate the FCGI_END_REQUEST - request &= 0xFF_FF; - ByteBuffer endRequestBuffer = byteBufferPool.acquire(8, false); - BufferUtil.clearToFill(endRequestBuffer); - endRequestBuffer.putInt(0x01_03_00_00 + request); - endRequestBuffer.putInt(0x00_08_00_00); - endRequestBuffer.putLong(0x00L); - endRequestBuffer.flip(); - result = result.append(endRequestBuffer, true); + Result result = new Result(byteBufferPool, callback); + if (lastContent) + result.append(generateEndRequest(request, true), true); + else + result.append(BufferUtil.EMPTY_BUFFER, false); + return result; } - return result; + else + { + Result result = generateContent(request, content, false, lastContent, callback, FCGI.FrameType.STDOUT); + if (lastContent) + result.append(generateEndRequest(request, false), true); + return result; + } + } + + private ByteBuffer generateEndRequest(int request, boolean aborted) + { + request &= 0xFF_FF; + ByteBuffer endRequestBuffer = byteBufferPool.acquire(8, false); + BufferUtil.clearToFill(endRequestBuffer); + endRequestBuffer.putInt(0x01_03_00_00 + request); + endRequestBuffer.putInt(0x00_08_00_00); + endRequestBuffer.putInt(aborted ? 1 : 0); + endRequestBuffer.putInt(0); + endRequestBuffer.flip(); + return endRequestBuffer; } } diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java index 09c0755c10c..50911bc0d7e 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java @@ -37,7 +37,7 @@ public class BeginRequestContentParser extends ContentParser } @Override - public boolean parse(ByteBuffer buffer) + public Result parse(ByteBuffer buffer) { while (buffer.hasRemaining()) { @@ -78,7 +78,7 @@ public class BeginRequestContentParser extends ContentParser buffer.position(buffer.position() + 5); onStart(); reset(); - return true; + return Result.COMPLETE; } else { @@ -94,7 +94,7 @@ public class BeginRequestContentParser extends ContentParser { onStart(); reset(); - return true; + return Result.COMPLETE; } break; } @@ -104,7 +104,7 @@ public class BeginRequestContentParser extends ContentParser } } } - return false; + return Result.PENDING; } private void onStart() diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java index 4ba70b44e22..88a5e7a2e72 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java @@ -86,9 +86,9 @@ public class ClientParser extends Parser } @Override - public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) + public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) { - listener.onContent(request, stream, buffer); + return listener.onContent(request, stream, buffer); } @Override @@ -98,5 +98,13 @@ public class ClientParser extends Parser for (StreamContentParser streamParser : streamParsers) streamParser.end(request); } + + @Override + public void onFailure(int request, Throwable failure) + { + listener.onFailure(request, failure); + for (StreamContentParser streamParser : streamParsers) + streamParser.end(request); + } } } diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java index 500396ae9ef..55bf6d2c30d 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java @@ -29,7 +29,7 @@ public abstract class ContentParser this.headerParser = headerParser; } - public abstract boolean parse(ByteBuffer buffer); + public abstract Result parse(ByteBuffer buffer); public void noContent() { @@ -45,4 +45,9 @@ public abstract class ContentParser { return headerParser.getContentLength(); } + + public enum Result + { + PENDING, ASYNC, COMPLETE + } } diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java index 419536af77d..b2198ad59d6 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java @@ -35,7 +35,7 @@ public class EndRequestContentParser extends ContentParser } @Override - public boolean parse(ByteBuffer buffer) + public Result parse(ByteBuffer buffer) { while (buffer.hasRemaining()) { @@ -76,7 +76,7 @@ public class EndRequestContentParser extends ContentParser buffer.position(buffer.position() + 3); onEnd(); reset(); - return true; + return Result.COMPLETE; } else { @@ -92,7 +92,7 @@ public class EndRequestContentParser extends ContentParser { onEnd(); reset(); - return true; + return Result.COMPLETE; } break; } @@ -102,13 +102,17 @@ public class EndRequestContentParser extends ContentParser } } } - return false; + return Result.PENDING; } private void onEnd() { - // TODO: if protocol != 0, invoke an error callback - listener.onEnd(getRequest()); + if (application != 0) + listener.onFailure(getRequest(), new Exception("FastCGI application returned code " + application)); + else if (protocol != 0) + listener.onFailure(getRequest(), new Exception("FastCGI server returned code " + protocol)); + else + listener.onEnd(getRequest()); } private void reset() diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java index 496275a49fc..85c34d0c3eb 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java @@ -32,6 +32,12 @@ public class HeaderParser private int length; private int padding; + /** + * Parses the bytes in the given {@code buffer} as FastCGI header bytes + * + * @param buffer the bytes to parse + * @return whether there were enough bytes for a FastCGI header + */ public boolean parse(ByteBuffer buffer) { while (buffer.hasRemaining()) diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java index 7f2805a2b27..42cbee811da 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java @@ -45,7 +45,7 @@ public class ParamsContentParser extends ContentParser } @Override - public boolean parse(ByteBuffer buffer) + public Result parse(ByteBuffer buffer) { while (buffer.hasRemaining() || state == State.PARAM) { @@ -185,7 +185,7 @@ public class ParamsContentParser extends ContentParser if (length == 0) { reset(); - return true; + return Result.COMPLETE; } break; } @@ -195,7 +195,7 @@ public class ParamsContentParser extends ContentParser } } } - return false; + return Result.PENDING; } @Override diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java index 577219c301c..45a348f1396 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java @@ -29,7 +29,7 @@ public abstract class Parser private State state = State.HEADER; private int padding; - public void parse(ByteBuffer buffer) + public boolean parse(ByteBuffer buffer) { while (true) { @@ -38,7 +38,7 @@ public abstract class Parser case HEADER: { if (!headerParser.parse(buffer)) - return; + return false; state = State.CONTENT; break; } @@ -51,8 +51,11 @@ public abstract class Parser } else { - if (!contentParser.parse(buffer)) - return; + ContentParser.Result result = contentParser.parse(buffer); + if (result == ContentParser.Result.PENDING) + return false; + else if (result == ContentParser.Result.ASYNC) + return true; } padding = headerParser.getPaddingLength(); state = State.PADDING; @@ -70,7 +73,7 @@ public abstract class Parser { padding -= buffer.remaining(); buffer.position(buffer.limit()); - return; + return false; } } default: @@ -96,10 +99,12 @@ public abstract class Parser public void onHeaders(int request); - public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer); + public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer); public void onEnd(int request); + public void onFailure(int request, Throwable failure); + public static class Adapter implements Listener { @Override @@ -113,14 +118,21 @@ public abstract class Parser } @Override - public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) + public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) { + return false; } @Override public void onEnd(int request) { } + + @Override + public void onFailure(int request, Throwable failure) + { + + } } } diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java index b130c1374ee..26983fe0e7c 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java @@ -52,7 +52,7 @@ public class ResponseContentParser extends StreamContentParser } @Override - protected void onContent(ByteBuffer buffer) + protected boolean onContent(ByteBuffer buffer) { int request = getRequest(); ResponseParser parser = parsers.get(request); @@ -61,7 +61,7 @@ public class ResponseContentParser extends StreamContentParser parser = new ResponseParser(listener, request); parsers.put(request, parser); } - parser.parse(buffer); + return parser.parse(buffer); } @Override @@ -87,7 +87,7 @@ public class ResponseContentParser extends StreamContentParser this.httpParser = new FCGIHttpParser(this); } - public void parse(ByteBuffer buffer) + public boolean parse(ByteBuffer buffer) { LOG.debug("Response {} {} content {} {}", request, FCGI.StreamType.STD_OUT, state, buffer); @@ -117,7 +117,8 @@ public class ResponseContentParser extends StreamContentParser } case RAW_CONTENT: { - notifyContent(buffer); + if (notifyContent(buffer)) + return true; remaining = 0; break; } @@ -133,6 +134,7 @@ public class ResponseContentParser extends StreamContentParser } } } + return false; } @Override @@ -253,15 +255,16 @@ public class ResponseContentParser extends StreamContentParser return false; } - private void notifyContent(ByteBuffer buffer) + private boolean notifyContent(ByteBuffer buffer) { try { - listener.onContent(request, FCGI.StreamType.STD_OUT, buffer); + return listener.onContent(request, FCGI.StreamType.STD_OUT, buffer); } catch (Throwable x) { logger.debug("Exception while invoking listener " + listener, x); + return false; } } diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java index b59593dd25a..655a4d24bf6 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java @@ -41,7 +41,7 @@ public class StreamContentParser extends ContentParser } @Override - public boolean parse(ByteBuffer buffer) + public Result parse(ByteBuffer buffer) { while (buffer.hasRemaining()) { @@ -59,14 +59,15 @@ public class StreamContentParser extends ContentParser int limit = buffer.limit(); buffer.limit(buffer.position() + length); ByteBuffer slice = buffer.slice(); - onContent(slice); buffer.position(buffer.limit()); buffer.limit(limit); contentLength -= length; + if (onContent(slice)) + return Result.ASYNC; if (contentLength > 0) break; state = State.LENGTH; - return true; + return Result.COMPLETE; } default: { @@ -74,7 +75,7 @@ public class StreamContentParser extends ContentParser } } } - return false; + return Result.PENDING; } @Override @@ -90,15 +91,16 @@ public class StreamContentParser extends ContentParser } } - protected void onContent(ByteBuffer buffer) + protected boolean onContent(ByteBuffer buffer) { try { - listener.onContent(getRequest(), streamType, buffer); + return listener.onContent(getRequest(), streamType, buffer); } catch (Throwable x) { logger.debug("Exception while invoking listener " + listener, x); + return false; } } diff --git a/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java b/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java index 5756c01e512..c1307599f73 100644 --- a/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java +++ b/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java @@ -158,10 +158,11 @@ public class ClientGeneratorTest ServerParser parser = new ServerParser(new ServerParser.Listener.Adapter() { @Override - public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) + public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) { Assert.assertEquals(id, request); totalLength.addAndGet(buffer.remaining()); + return false; } @Override diff --git a/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java b/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java index 5f80ed2827c..fdd2acb2f8b 100644 --- a/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java +++ b/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java @@ -111,16 +111,17 @@ public class ClientParserTest ByteBufferPool byteBufferPool = new MappedByteBufferPool(); ServerGenerator generator = new ServerGenerator(byteBufferPool); Generator.Result result1 = generator.generateResponseHeaders(id, 200, "OK", fields, null); - Generator.Result result2 = generator.generateResponseContent(id, null, true, null); + Generator.Result result2 = generator.generateResponseContent(id, null, true, false, null); final AtomicInteger verifier = new AtomicInteger(); ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter() { @Override - public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) + public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) { Assert.assertEquals(id, request); verifier.addAndGet(2); + return false; } @Override @@ -162,17 +163,18 @@ public class ClientParserTest ByteBufferPool byteBufferPool = new MappedByteBufferPool(); ServerGenerator generator = new ServerGenerator(byteBufferPool); Generator.Result result1 = generator.generateResponseHeaders(id, code, "OK", fields, null); - Generator.Result result2 = generator.generateResponseContent(id, content, true, null); + Generator.Result result2 = generator.generateResponseContent(id, content, true, false, null); final AtomicInteger verifier = new AtomicInteger(); ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter() { @Override - public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) + public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) { Assert.assertEquals(id, request); Assert.assertEquals(contentLength, buffer.remaining()); verifier.addAndGet(2); + return false; } @Override @@ -214,17 +216,18 @@ public class ClientParserTest ByteBufferPool byteBufferPool = new MappedByteBufferPool(); ServerGenerator generator = new ServerGenerator(byteBufferPool); Generator.Result result1 = generator.generateResponseHeaders(id, code, "OK", fields, null); - Generator.Result result2 = generator.generateResponseContent(id, content, true, null); + Generator.Result result2 = generator.generateResponseContent(id, content, true, false, null); final AtomicInteger totalLength = new AtomicInteger(); final AtomicBoolean verifier = new AtomicBoolean(); ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter() { @Override - public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) + public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) { Assert.assertEquals(id, request); totalLength.addAndGet(buffer.remaining()); + return false; } @Override diff --git a/jetty-fcgi/fcgi-distribution/pom.xml b/jetty-fcgi/fcgi-distribution/pom.xml deleted file mode 100644 index 81d03d9e236..00000000000 --- a/jetty-fcgi/fcgi-distribution/pom.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - fcgi-parent - org.eclipse.jetty.fcgi - 9.2.0-SNAPSHOT - - - 4.0.0 - fcgi-distribution - pom - Jetty :: FastCGI :: Distribution - - - ${project.build.directory}/distribution - - - - - - maven-dependency-plugin - - - copy-jars - generate-resources - - copy-dependencies - - - org.eclipse.jetty.fcgi - fcgi-server - jar - ${distribution-directory}/lib/fcgi - - - - - - maven-assembly-plugin - - - assemble - package - - assembly - - - jetty-fcgi-${project.version} - - src/main/assembly/distribution.xml - - gnu - - - - - - - - - - org.eclipse.jetty.fcgi - fcgi-proxy - ${project.version} - - - - diff --git a/jetty-fcgi/fcgi-distribution/src/main/assembly/distribution.xml b/jetty-fcgi/fcgi-distribution/src/main/assembly/distribution.xml deleted file mode 100644 index df1f093682d..00000000000 --- a/jetty-fcgi/fcgi-distribution/src/main/assembly/distribution.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - distribution - - - tar.gz - - - false - - - - ${project.basedir}/src/main/config/modules - /modules - - *.mod - - - - ${distribution-directory} - / - - lib/** - - - - - diff --git a/jetty-fcgi/fcgi-distribution/src/main/config/webapps/wordpress-example.xml b/jetty-fcgi/fcgi-distribution/src/main/config/webapps/wordpress-example.xml deleted file mode 100644 index 9ec42697473..00000000000 --- a/jetty-fcgi/fcgi-distribution/src/main/config/webapps/wordpress-example.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - /var/www/wordpress-3.7.1 - - - /wp - - - index.php - - - - org.eclipse.jetty.fcgi.proxy.TryFilesFilter - /* - - - - - - - files - $path /index.php?p=$path - - - - - - - default - - - org.eclipse.jetty.servlet.DefaultServlet - - - - dirAllowed - false - - - - / - - - - org.eclipse.jetty.fcgi.proxy.FastCGIProxyServlet - *.php - - proxyTo - http://localhost:9000 - - - prefix - / - - - scriptRoot - - - - scriptPattern - (.+?\\.php) - - - - diff --git a/jetty-fcgi/fcgi-http-client-transport/pom.xml b/jetty-fcgi/fcgi-http-client-transport/pom.xml deleted file mode 100644 index d911c12528c..00000000000 --- a/jetty-fcgi/fcgi-http-client-transport/pom.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - org.eclipse.jetty.fcgi - fcgi-parent - 9.2.0-SNAPSHOT - - - 4.0.0 - fcgi-http-client-transport - Jetty :: FastCGI :: HTTP Client Transport - - - ${project.groupId}.client.http - - - - - org.eclipse.jetty.fcgi - fcgi-core - ${project.version} - - - org.eclipse.jetty - jetty-client - ${project.version} - - - - org.eclipse.jetty.fcgi - fcgi-server - ${project.version} - test - - - org.eclipse.jetty.toolchain - jetty-test-helper - - - - diff --git a/jetty-fcgi/fcgi-http-client-transport/src/test/resources/jetty-logging.properties b/jetty-fcgi/fcgi-http-client-transport/src/test/resources/jetty-logging.properties deleted file mode 100644 index b8df62d071d..00000000000 --- a/jetty-fcgi/fcgi-http-client-transport/src/test/resources/jetty-logging.properties +++ /dev/null @@ -1,3 +0,0 @@ -org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog -#org.eclipse.jetty.client.LEVEL=DEBUG -#org.eclipse.jetty.fcgi.LEVEL=DEBUG diff --git a/jetty-fcgi/fcgi-proxy/pom.xml b/jetty-fcgi/fcgi-proxy/pom.xml deleted file mode 100644 index 149d189df73..00000000000 --- a/jetty-fcgi/fcgi-proxy/pom.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - fcgi-parent - org.eclipse.jetty.fcgi - 9.2.0-SNAPSHOT - - - 4.0.0 - fcgi-proxy - Jetty :: FastCGI :: Proxy - - - ${project.groupId}.proxy - - - - - javax.servlet - javax.servlet-api - - - org.eclipse.jetty.fcgi - fcgi-http-client-transport - ${project.version} - - - org.eclipse.jetty - jetty-proxy - ${project.version} - - - - org.eclipse.jetty - jetty-server - ${project.version} - test - - - org.eclipse.jetty - jetty-servlet - ${project.version} - test - - - org.eclipse.jetty.spdy - spdy-http-server - ${project.version} - test - - - - diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java index fd62656164e..91459ed7798 100644 --- a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java +++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java @@ -24,6 +24,8 @@ import org.eclipse.jetty.fcgi.generator.Flusher; import org.eclipse.jetty.fcgi.generator.Generator; import org.eclipse.jetty.fcgi.generator.ServerGenerator; import org.eclipse.jetty.http.HttpGenerator; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.server.HttpTransport; import org.eclipse.jetty.util.BufferUtil; @@ -35,6 +37,8 @@ public class HttpTransportOverFCGI implements HttpTransport private final Flusher flusher; private final int request; private volatile boolean head; + private volatile boolean shutdown; + private volatile boolean aborted; public HttpTransportOverFCGI(ByteBufferPool byteBufferPool, Flusher flusher, int request) { @@ -47,13 +51,15 @@ public class HttpTransportOverFCGI implements HttpTransport public void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback) { boolean head = this.head = info.isHead(); + boolean shutdown = this.shutdown = info.getHttpFields().contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()); + if (head) { if (lastContent) { Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(), info.getHttpFields(), new Callback.Adapter()); - Generator.Result contentResult = generator.generateResponseContent(request, BufferUtil.EMPTY_BUFFER, lastContent, callback); + Generator.Result contentResult = generator.generateResponseContent(request, BufferUtil.EMPTY_BUFFER, lastContent, aborted, callback); flusher.flush(headersResult, contentResult); } else @@ -67,9 +73,12 @@ public class HttpTransportOverFCGI implements HttpTransport { Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(), info.getHttpFields(), new Callback.Adapter()); - Generator.Result contentResult = generator.generateResponseContent(request, content, lastContent, callback); + Generator.Result contentResult = generator.generateResponseContent(request, content, lastContent, aborted, callback); flusher.flush(headersResult, contentResult); } + + if (lastContent && shutdown) + flusher.shutdown(); } @Override @@ -79,7 +88,7 @@ public class HttpTransportOverFCGI implements HttpTransport { if (lastContent) { - Generator.Result result = generator.generateResponseContent(request, BufferUtil.EMPTY_BUFFER, lastContent, callback); + Generator.Result result = generator.generateResponseContent(request, BufferUtil.EMPTY_BUFFER, lastContent, aborted, callback); flusher.flush(result); } else @@ -90,18 +99,22 @@ public class HttpTransportOverFCGI implements HttpTransport } else { - Generator.Result result = generator.generateResponseContent(request, content, lastContent, callback); + Generator.Result result = generator.generateResponseContent(request, content, lastContent, aborted, callback); flusher.flush(result); } + + if (lastContent && shutdown) + flusher.shutdown(); + } + + @Override + public void abort() + { + aborted = true; } @Override public void completed() { } - - @Override - public void abort() - { - } } diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java index c69253e4ded..1aa253a1fb1 100644 --- a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java +++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java @@ -151,7 +151,7 @@ public class ServerFCGIConnection extends AbstractConnection } @Override - public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) + public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) { HttpChannelOverFCGI channel = channels.get(request); if (LOG.isDebugEnabled()) @@ -161,6 +161,7 @@ public class ServerFCGIConnection extends AbstractConnection if (channel.content(buffer)) channel.dispatch(); } + return false; } @Override @@ -175,5 +176,17 @@ public class ServerFCGIConnection extends AbstractConnection channel.dispatch(); } } + + @Override + public void onFailure(int request, Throwable failure) + { + HttpChannelOverFCGI channel = channels.remove(request); + if (LOG.isDebugEnabled()) + LOG.debug("Request {} failure on {}: {}", request, channel, failure); + if (channel != null) + { + channel.badMessage(400, failure.toString()); + } + } } } diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java index f8926e96bb9..d107011752d 100644 --- a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java +++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java @@ -32,6 +32,7 @@ import org.eclipse.jetty.fcgi.FCGI; import org.eclipse.jetty.fcgi.client.http.HttpClientTransportOverFCGI; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpScheme; +import org.eclipse.jetty.proxy.AsyncProxyServlet; import org.eclipse.jetty.proxy.ProxyServlet; /** @@ -53,14 +54,18 @@ import org.eclipse.jetty.proxy.ProxyServlet; *

  • the FastCGI SCRIPT_NAME parameter
  • *
  • the FastCGI PATH_INFO parameter
  • * + *
  • fastCGI.HTTPS, optional, defaults to false, that specifies whether + * to force the FastCGI HTTPS parameter to the value on
  • * * * @see TryFilesFilter */ -public class FastCGIProxyServlet extends ProxyServlet.Transparent +public class FastCGIProxyServlet extends AsyncProxyServlet.Transparent { public static final String SCRIPT_ROOT_INIT_PARAM = "scriptRoot"; public static final String SCRIPT_PATTERN_INIT_PARAM = "scriptPattern"; + public static final String FASTCGI_HTTPS_INIT_PARAM = "fastCGI.HTTPS"; + private static final String REMOTE_ADDR_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".remoteAddr"; private static final String REMOTE_PORT_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".remotePort"; private static final String SERVER_NAME_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".serverName"; @@ -70,6 +75,7 @@ public class FastCGIProxyServlet extends ProxyServlet.Transparent private static final String REQUEST_URI_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".requestURI"; private Pattern scriptPattern; + private boolean fcgiHTTPS; @Override public void init() throws ServletException @@ -80,6 +86,8 @@ public class FastCGIProxyServlet extends ProxyServlet.Transparent if (value == null) value = "(.+?\\.php)"; scriptPattern = Pattern.compile(value); + + fcgiHTTPS = Boolean.parseBoolean(getInitParameter(FASTCGI_HTTPS_INIT_PARAM)); } @Override @@ -130,7 +138,7 @@ public class FastCGIProxyServlet extends ProxyServlet.Transparent fastCGIHeaders.put(FCGI.Headers.SERVER_ADDR, (String)proxyRequest.getAttributes().get(SERVER_ADDR_ATTRIBUTE)); fastCGIHeaders.put(FCGI.Headers.SERVER_PORT, (String)proxyRequest.getAttributes().get(SERVER_PORT_ATTRIBUTE)); - if (HttpScheme.HTTPS.is((String)proxyRequest.getAttributes().get(SCHEME_ATTRIBUTE))) + if (fcgiHTTPS || HttpScheme.HTTPS.is((String)proxyRequest.getAttributes().get(SCHEME_ATTRIBUTE))) fastCGIHeaders.put(FCGI.Headers.HTTPS, "on"); URI proxyRequestURI = proxyRequest.getURI(); diff --git a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java index 103a326eabb..e97750f8b50 100644 --- a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java +++ b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java @@ -24,11 +24,13 @@ import java.net.URI; import java.net.URLEncoder; import java.nio.ByteBuffer; import java.util.Arrays; +import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import java.util.zip.GZIPOutputStream; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; @@ -40,10 +42,13 @@ import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.util.BytesContentProvider; +import org.eclipse.jetty.client.util.DeferredContentProvider; +import org.eclipse.jetty.client.util.FutureResponseListener; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.toolchain.test.IO; import org.eclipse.jetty.toolchain.test.annotation.Slow; +import org.eclipse.jetty.util.Callback; import org.junit.Assert; import org.junit.Test; @@ -551,4 +556,148 @@ public class HttpClientTest extends AbstractHttpClientServerTest Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS)); } + + @Test + public void testEarlyEOF() throws Exception + { + start(new AbstractHandler() + { + @Override + public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + // Promise some content, then flush the headers, then fail to send the content. + response.setContentLength(16); + response.flushBuffer(); + throw new NullPointerException("Explicitly thrown by test"); + } + }); + + try + { + client.newRequest("localhost", connector.getLocalPort()) + .scheme(scheme) + .timeout(5, TimeUnit.SECONDS) + .send(); + Assert.fail(); + } + catch (ExecutionException x) + { + // Expected. + } + } + + @Test + public void testSmallContentDelimitedByEOFWithSlowRequest() throws Exception + { + testContentDelimitedByEOFWithSlowRequest(1024); + } + + @Test + public void testBigContentDelimitedByEOFWithSlowRequest() throws Exception + { + testContentDelimitedByEOFWithSlowRequest(128 * 1024); + } + + private void testContentDelimitedByEOFWithSlowRequest(int length) throws Exception + { + final byte[] data = new byte[length]; + new Random().nextBytes(data); + start(new AbstractHandler() + { + @Override + public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + response.setHeader("Connection", "close"); + response.getOutputStream().write(data); + } + }); + + DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.wrap(new byte[]{0})); + Request request = client.newRequest("localhost", connector.getLocalPort()) + .scheme(scheme) + .content(content); + FutureResponseListener listener = new FutureResponseListener(request); + request.send(listener); + // Wait some time to simulate a slow request. + Thread.sleep(1000); + content.close(); + + ContentResponse response = listener.get(5, TimeUnit.SECONDS); + + Assert.assertEquals(200, response.getStatus()); + Assert.assertArrayEquals(data, response.getContent()); + } + + @Test + public void testSmallAsyncContent() throws Exception + { + start(new AbstractHandler() + { + @Override + public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + ServletOutputStream output = response.getOutputStream(); + output.write(65); + output.flush(); + output.write(66); + } + }); + + final AtomicInteger contentCount = new AtomicInteger(); + final AtomicReference callbackRef = new AtomicReference<>(); + final AtomicReference contentLatch = new AtomicReference<>(new CountDownLatch(1)); + final CountDownLatch completeLatch = new CountDownLatch(1); + client.newRequest("localhost", connector.getLocalPort()) + .scheme(scheme) + .onResponseContentAsync(new Response.AsyncContentListener() + { + @Override + public void onContent(Response response, ByteBuffer content, Callback callback) + { + contentCount.incrementAndGet(); + callbackRef.set(callback); + contentLatch.get().countDown(); + } + }) + .send(new Response.CompleteListener() + { + @Override + public void onComplete(Result result) + { + completeLatch.countDown(); + } + }); + + Assert.assertTrue(contentLatch.get().await(5, TimeUnit.SECONDS)); + Callback callback = callbackRef.get(); + + // Wait a while to be sure that the parsing does not proceed. + TimeUnit.MILLISECONDS.sleep(1000); + + Assert.assertEquals(1, contentCount.get()); + + // Succeed the content callback to proceed with parsing. + callbackRef.set(null); + contentLatch.set(new CountDownLatch(1)); + callback.succeeded(); + + Assert.assertTrue(contentLatch.get().await(5, TimeUnit.SECONDS)); + callback = callbackRef.get(); + + // Wait a while to be sure that the parsing does not proceed. + TimeUnit.MILLISECONDS.sleep(1000); + + Assert.assertEquals(2, contentCount.get()); + Assert.assertEquals(1, completeLatch.getCount()); + + // Succeed the content callback to proceed with parsing. + callbackRef.set(null); + contentLatch.set(new CountDownLatch(1)); + callback.succeeded(); + + Assert.assertTrue(completeLatch.await(555, TimeUnit.SECONDS)); + Assert.assertEquals(2, contentCount.get()); + } } diff --git a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServletTest.java b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServletTest.java new file mode 100644 index 00000000000..b1a85c191fe --- /dev/null +++ b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServletTest.java @@ -0,0 +1,112 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.fcgi.server.proxy; + +import java.io.IOException; +import java.net.URI; +import java.util.Random; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.fcgi.server.ServerFCGIConnectionFactory; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; + +public class FastCGIProxyServletTest +{ + private Server server; + private ServerConnector httpConnector; + private ServerConnector fcgiConnector; + private HttpClient client; + + public void prepare(HttpServlet servlet) throws Exception + { + server = new Server(); + httpConnector = new ServerConnector(server); + server.addConnector(httpConnector); + + fcgiConnector = new ServerConnector(server, new ServerFCGIConnectionFactory(new HttpConfiguration())); + server.addConnector(fcgiConnector); + + final String contextPath = "/"; + ServletContextHandler context = new ServletContextHandler(server, contextPath); + + final String servletPath = "/script"; + FastCGIProxyServlet fcgiServlet = new FastCGIProxyServlet() + { + @Override + protected URI rewriteURI(HttpServletRequest request) + { + return URI.create("http://localhost:" + fcgiConnector.getLocalPort() + servletPath + request.getServletPath()); + } + }; + ServletHolder fcgiServletHolder = new ServletHolder(fcgiServlet); + context.addServlet(fcgiServletHolder, "*.php"); + fcgiServletHolder.setInitParameter(FastCGIProxyServlet.SCRIPT_ROOT_INIT_PARAM, "/scriptRoot"); + fcgiServletHolder.setInitParameter("proxyTo", "http://localhost"); + fcgiServletHolder.setInitParameter(FastCGIProxyServlet.SCRIPT_PATTERN_INIT_PARAM, "(.+?\\.php)"); + + context.addServlet(new ServletHolder(servlet), servletPath + "/*"); + + client = new HttpClient(); + server.addBean(client); + + server.start(); + } + + @After + public void dispose() throws Exception + { + server.stop(); + } + + @Test + public void testGETWithSmallResponseContent() throws Exception + { + final byte[] data = new byte[1024]; + new Random().nextBytes(data); + + final String path = "/foo/index.php"; + prepare(new HttpServlet() + { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + Assert.assertTrue(req.getRequestURI().endsWith(path)); + resp.getOutputStream().write(data); + } + }); + + ContentResponse response = client.newRequest("localhost", httpConnector.getLocalPort()) + .path(path) + .send(); + Assert.assertEquals(200, response.getStatus()); + Assert.assertArrayEquals(data, response.getContent()); + } +} diff --git a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/TryFilesFilterTest.java b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/TryFilesFilterTest.java new file mode 100644 index 00000000000..e1d78c79b44 --- /dev/null +++ b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/TryFilesFilterTest.java @@ -0,0 +1,107 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.fcgi.server.proxy; + +import java.io.IOException; +import java.util.EnumSet; +import javax.servlet.DispatcherType; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.FilterHolder; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; + +public class TryFilesFilterTest +{ + private Server server; + private ServerConnector connector; + private ServerConnector sslConnector; + private HttpClient client; + private String forwardPath; + + public void prepare(HttpServlet servlet) throws Exception + { + server = new Server(); + connector = new ServerConnector(server); + server.addConnector(connector); + + SslContextFactory sslContextFactory = new SslContextFactory(); + sslContextFactory.setEndpointIdentificationAlgorithm(""); + sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks"); + sslContextFactory.setKeyStorePassword("storepwd"); + sslContextFactory.setTrustStorePath("src/test/resources/truststore.jks"); + sslContextFactory.setTrustStorePassword("storepwd"); + sslConnector = new ServerConnector(server, sslContextFactory); + server.addConnector(sslConnector); + + ServletContextHandler context = new ServletContextHandler(server, "/"); + + FilterHolder filterHolder = context.addFilter(TryFilesFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); + forwardPath = "/index.php"; + filterHolder.setInitParameter(TryFilesFilter.FILES_INIT_PARAM, "$path " + forwardPath + "?p=$path"); + + context.addServlet(new ServletHolder(servlet), "/*"); + + client = new HttpClient(sslContextFactory); + server.addBean(client); + + server.start(); + } + + @After + public void dispose() throws Exception + { + server.stop(); + } + + @Test + public void testHTTPSRequestIsForwarded() throws Exception + { + final String path = "/one/"; + prepare(new HttpServlet() + { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + Assert.assertTrue("https".equalsIgnoreCase(req.getScheme())); + Assert.assertTrue(req.isSecure()); + Assert.assertEquals(forwardPath, req.getRequestURI()); + Assert.assertTrue(req.getQueryString().endsWith(path)); + } + }); + + ContentResponse response = client.newRequest("localhost", sslConnector.getLocalPort()) + .scheme("https") + .path(path) + .send(); + + Assert.assertEquals(200, response.getStatus()); + } +} diff --git a/jetty-fcgi/pom.xml b/jetty-fcgi/pom.xml index d309fec8a21..ff2f37caad5 100644 --- a/jetty-fcgi/pom.xml +++ b/jetty-fcgi/pom.xml @@ -14,10 +14,7 @@ fcgi-client - fcgi-server - - diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java index e99d3cc8153..a51e4ba7b85 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import org.eclipse.jetty.http.HttpTokens.EndOfContent; import org.eclipse.jetty.util.BufferUtil; @@ -916,10 +917,8 @@ public class HttpGenerator line[versionLength+6+reason.length()]=HttpTokens.LINE_FEED; __preprepared[i] = new PreparedResponse(); - __preprepared[i]._reason=new byte[line.length-versionLength-7] ; - System.arraycopy(line,versionLength+5,__preprepared[i]._reason,0,line.length-versionLength-7); - __preprepared[i]._schemeCode=new byte[versionLength+5]; - System.arraycopy(line,0,__preprepared[i]._schemeCode,0,versionLength+5); + __preprepared[i]._schemeCode = Arrays.copyOfRange(line, 0,versionLength+5); + __preprepared[i]._reason = Arrays.copyOfRange(line, versionLength+5, line.length-2); __preprepared[i]._responseLine=line; } } @@ -1091,8 +1090,7 @@ public class HttpGenerator { super(header,value); int cbl=header.getBytesColonSpace().length; - _bytes=new byte[cbl+value.length()+2]; - System.arraycopy(header.getBytesColonSpace(),0,_bytes,0,cbl); + _bytes=Arrays.copyOf(header.getBytesColonSpace(), cbl+value.length()+2); System.arraycopy(value.getBytes(StandardCharsets.ISO_8859_1),0,_bytes,cbl,value.length()); _bytes[_bytes.length-2]=(byte)'\r'; _bytes[_bytes.length-1]=(byte)'\n'; diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java index b14574c0d82..79f1c28d90d 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java @@ -58,7 +58,7 @@ import org.eclipse.jetty.util.log.Logger; * For performance, the parse is heavily dependent on the * {@link Trie#getBest(ByteBuffer, int, int)} method to look ahead in a * single pass for both the structure ( : and CRLF ) and semantic (which - * header and value) of a header. Specifically the static {@link HttpField#CACHE} + * header and value) of a header. Specifically the static {@link HttpHeader#CACHE} * is used to lookup common combinations of headers and values * (eg. "Connection: close"), or just header names (eg. "Connection:" ). * For headers who's value is not known statically (eg. Host, COOKIE) then a @@ -186,7 +186,7 @@ public class HttpParser for (String charset : new String[]{"UTF-8","ISO-8859-1"}) { - CACHE.put(field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type+";charset="+charset)); + CACHE.put(new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type+";charset="+charset)); CACHE.put(new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type+"; charset="+charset)); } } @@ -813,7 +813,7 @@ public class HttpParser { if (_valueString.endsWith(HttpHeaderValue.CHUNKED.toString())) _endOfContent=EndOfContent.CHUNKED_CONTENT; - else if (_valueString.indexOf(HttpHeaderValue.CHUNKED.toString()) >= 0) + else if (_valueString.contains(HttpHeaderValue.CHUNKED.toString())) { throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad chunking"); } @@ -872,7 +872,7 @@ public class HttpParser case CONNECTION: // Don't cache if not persistent - if (_valueString!=null && _valueString.indexOf("close")>=0) + if (_valueString!=null && _valueString.contains("close")) { _closed=true; _connectionFields=null; @@ -1223,8 +1223,6 @@ public class HttpParser LOG.debug("parseNext s={} {}",_state,BufferUtil.toDetailString(buffer)); try { - boolean handle=false; - // Start a request/response if (_state==State.START) { @@ -1233,28 +1231,39 @@ public class HttpParser _methodString=null; _endOfContent=EndOfContent.UNKNOWN_CONTENT; _header=null; - handle=quickStart(buffer); + if (quickStart(buffer)) + return true; } // Request/response line - if (!handle && _state.ordinal()>= State.START.ordinal() && _state.ordinal()= State.START.ordinal() && _state.ordinal()= State.HEADER.ordinal() && _state.ordinal()= State.HEADER.ordinal() && _state.ordinal()= State.CONTENT.ordinal() && _state.ordinal()= State.CONTENT.ordinal() && _state.ordinal()0 && _headResponse) { setState(State.END); - handle=_handler.messageComplete(); + if (_handler.messageComplete()) + return true; } else - handle=parseContent(buffer); + { + if (parseContent(buffer)) + return true; + } } // handle end states @@ -1288,8 +1297,8 @@ public class HttpParser break; case START: - _handler.earlyEOF(); setState(State.CLOSED); + _handler.earlyEOF(); break; case END: @@ -1297,29 +1306,28 @@ public class HttpParser break; case EOF_CONTENT: - handle=_handler.messageComplete()||handle; setState(State.CLOSED); - break; + return _handler.messageComplete(); case CONTENT: case CHUNKED_CONTENT: case CHUNK_SIZE: case CHUNK_PARAMS: case CHUNK: - _handler.earlyEOF(); setState(State.CLOSED); + _handler.earlyEOF(); break; default: if (DEBUG) LOG.debug("{} EOF in {}",this,_state); - _handler.badMessage(400,null); setState(State.CLOSED); + _handler.badMessage(400,null); break; } } - return handle; + return false; } catch(BadMessage e) { @@ -1357,24 +1365,36 @@ public class HttpParser protected boolean parseContent(ByteBuffer buffer) { + int remaining=buffer.remaining(); + if (remaining==0 && _state==State.CONTENT) + { + long content=_contentLength - _contentPosition; + if (content == 0) + { + setState(State.END); + if (_handler.messageComplete()) + return true; + } + } + // Handle _content byte ch; - while (_state.ordinal() < State.END.ordinal() && buffer.hasRemaining()) + while (_state.ordinal() < State.END.ordinal() && remaining>0) { switch (_state) { case EOF_CONTENT: _contentChunk=buffer.asReadOnlyBuffer(); - _contentPosition += _contentChunk.remaining(); - buffer.position(buffer.position()+_contentChunk.remaining()); + _contentPosition += remaining; + buffer.position(buffer.position()+remaining); if (_handler.content(_contentChunk)) return true; break; case CONTENT: { - long remaining=_contentLength - _contentPosition; - if (remaining == 0) + long content=_contentLength - _contentPosition; + if (content == 0) { setState(State.END); if (_handler.messageComplete()) @@ -1385,25 +1405,25 @@ public class HttpParser _contentChunk=buffer.asReadOnlyBuffer(); // limit content by expected size - if (_contentChunk.remaining() > remaining) + if (remaining > content) { // We can cast remaining to an int as we know that it is smaller than // or equal to length which is already an int. - _contentChunk.limit(_contentChunk.position()+(int)remaining); + _contentChunk.limit(_contentChunk.position()+(int)content); } _contentPosition += _contentChunk.remaining(); buffer.position(buffer.position()+_contentChunk.remaining()); - boolean handle=_handler.content(_contentChunk); + if (_handler.content(_contentChunk)) + return true; + if(_contentPosition == _contentLength) { setState(State.END); if (_handler.messageComplete()) return true; } - if (handle) - return true; } break; } @@ -1463,8 +1483,8 @@ public class HttpParser case CHUNK: { - int remaining=_chunkLength - _chunkPosition; - if (remaining == 0) + int chunk=_chunkLength - _chunkPosition; + if (chunk == 0) { setState(State.CHUNKED_CONTENT); } @@ -1472,13 +1492,13 @@ public class HttpParser { _contentChunk=buffer.asReadOnlyBuffer(); - if (_contentChunk.remaining() > remaining) - _contentChunk.limit(_contentChunk.position()+remaining); - remaining=_contentChunk.remaining(); + if (remaining > chunk) + _contentChunk.limit(_contentChunk.position()+chunk); + chunk=_contentChunk.remaining(); - _contentPosition += remaining; - _chunkPosition += remaining; - buffer.position(buffer.position()+remaining); + _contentPosition += chunk; + _chunkPosition += chunk; + buffer.position(buffer.position()+chunk); if (_handler.content(_contentChunk)) return true; } @@ -1493,7 +1513,10 @@ public class HttpParser default: break; + } + + remaining=buffer.remaining(); } return false; } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java index 6517dbd3427..8fa2cc86ef9 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java @@ -44,6 +44,7 @@ public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint return AbstractEndPoint.this.needsFill(); } }; + private final WriteFlusher _writeFlusher = new WriteFlusher(this) { @Override @@ -144,10 +145,20 @@ public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint { boolean output_shutdown=isOutputShutdown(); boolean input_shutdown=isInputShutdown(); - _fillInterest.onFail(timeout); - _writeFlusher.onFail(timeout); - if (isOpen() && output_shutdown || input_shutdown) + boolean fillFailed = _fillInterest.onFail(timeout); + boolean writeFailed = _writeFlusher.onFail(timeout); + + // If the endpoint is half closed and there was no onFail handling, the close here + // This handles the situation where the connection has completed its close handling + // and the endpoint is half closed, but the other party does not complete the close. + // This perhaps should not check for half closed, however the servlet spec case allows + // for a dispatched servlet or suspended request to extend beyond the connections idle + // time. So if this test would always close an idle endpoint that is not handled, then + // we would need a mode to ignore timeouts for some HTTP states + if (isOpen() && (output_shutdown || input_shutdown) && !(fillFailed || writeFailed)) close(); + else + LOG.debug("Ignored idle endpoint {}",this); } @Override diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/FillInterest.java b/jetty-io/src/main/java/org/eclipse/jetty/io/FillInterest.java index 0f3c2e55eaf..b2c3f685559 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/FillInterest.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/FillInterest.java @@ -93,12 +93,17 @@ public abstract class FillInterest /* ------------------------------------------------------------ */ /** Call to signal a failure to a registered interest + * @return true if the cause was passed to a {@link Callback} instance */ - public void onFail(Throwable cause) + public boolean onFail(Throwable cause) { Callback callback=_interested.get(); if (callback!=null && _interested.compareAndSet(callback,null)) + { callback.failed(cause); + return true; + } + return false; } /* ------------------------------------------------------------ */ diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java index d45902e36dd..a4b6f7d2a16 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java @@ -19,11 +19,13 @@ package org.eclipse.jetty.io; import java.io.IOException; +import java.net.Socket; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; import java.util.List; +import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.thread.Scheduler; @@ -57,9 +59,11 @@ public class NetworkTrafficSelectChannelEndPoint extends SelectChannelEndPoint if (b.hasRemaining()) { int position = b.position(); + ByteBuffer view=b.slice(); flushed&=super.flush(b); int l=b.position()-position; - notifyOutgoing(b, position, l); + view.limit(view.position()+l); + notifyOutgoing(view); if (!flushed) break; } @@ -67,9 +71,12 @@ public class NetworkTrafficSelectChannelEndPoint extends SelectChannelEndPoint return flushed; } + - public void notifyOpened() + @Override + public void onOpen() { + super.onOpen(); if (listeners != null && !listeners.isEmpty()) { for (NetworkTrafficListener listener : listeners) @@ -86,6 +93,27 @@ public class NetworkTrafficSelectChannelEndPoint extends SelectChannelEndPoint } } + @Override + public void onClose() + { + super.onClose(); + if (listeners != null && !listeners.isEmpty()) + { + for (NetworkTrafficListener listener : listeners) + { + try + { + listener.closed(getSocket()); + } + catch (Exception x) + { + LOG.warn(x); + } + } + } + } + + public void notifyIncoming(ByteBuffer buffer, int read) { if (listeners != null && !listeners.isEmpty() && read > 0) @@ -105,18 +133,16 @@ public class NetworkTrafficSelectChannelEndPoint extends SelectChannelEndPoint } } - public void notifyOutgoing(ByteBuffer buffer, int position, int written) + public void notifyOutgoing(ByteBuffer view) { - if (listeners != null && !listeners.isEmpty() && written > 0) + if (listeners != null && !listeners.isEmpty() && view.hasRemaining()) { + Socket socket=getSocket(); for (NetworkTrafficListener listener : listeners) { try { - ByteBuffer view = buffer.slice(); - view.position(position); - view.limit(position + written); - listener.outgoing(getSocket(), view); + listener.outgoing(socket, view); } catch (Exception x) { @@ -126,21 +152,4 @@ public class NetworkTrafficSelectChannelEndPoint extends SelectChannelEndPoint } } - public void notifyClosed() - { - if (listeners != null && !listeners.isEmpty()) - { - for (NetworkTrafficListener listener : listeners) - { - try - { - listener.closed(getSocket()); - } - catch (Exception x) - { - LOG.warn(x); - } - } - } - } } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java b/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java index 326ef3faff6..fccc6229552 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.WritePendingException; +import java.util.Arrays; import java.util.EnumMap; import java.util.EnumSet; import java.util.Set; @@ -253,10 +254,14 @@ abstract public class WriteFlusher return _buffers; } - protected void fail(Throwable cause) + protected boolean fail(Throwable cause) { if (_callback!=null) + { _callback.failed(cause); + return true; + } + return false; } protected void complete() @@ -297,10 +302,7 @@ abstract public class WriteFlusher if (consumed == length) return EMPTY_BUFFERS; - int newLength = length - consumed; - ByteBuffer[] result = new ByteBuffer[newLength]; - System.arraycopy(buffers, consumed, result, 0, newLength); - return result; + return Arrays.copyOfRange(buffers,consumed,length); } } @@ -430,7 +432,12 @@ abstract public class WriteFlusher } } - public void onFail(Throwable cause) + /* ------------------------------------------------------------ */ + /** Notify the flusher of a failure + * @param cause The cause of the failure + * @return true if the flusher passed the failure to a {@link Callback} instance + */ + public boolean onFail(Throwable cause) { // Keep trying to handle the failure until we get to IDLE or FAILED state while(true) @@ -442,7 +449,7 @@ abstract public class WriteFlusher case FAILED: if (DEBUG) LOG.debug("ignored: {} {}", this, cause); - return; + return false; case PENDING: if (DEBUG) @@ -450,10 +457,7 @@ abstract public class WriteFlusher PendingState pending = (PendingState)current; if (updateState(pending,__IDLE)) - { - pending.fail(cause); - return; - } + return pending.fail(cause); break; default: @@ -461,7 +465,7 @@ abstract public class WriteFlusher LOG.debug("failed: {} {}", this, cause); if (updateState(current,new FailedState(cause))) - return; + return false; break; } } diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java index 1db6908fe78..f6cefa12e9b 100644 --- a/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java +++ b/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java @@ -18,8 +18,7 @@ package org.eclipse.jetty.io; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.instanceOf; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -129,6 +128,7 @@ public class ByteArrayEndPointTest assertEquals(true,endp.flush(BufferUtil.EMPTY_BUFFER,BufferUtil.toBuffer(" and"),BufferUtil.toBuffer(" more"))); assertEquals("some output some more and more",endp.getOutputString()); + endp.close(); } @Test @@ -147,6 +147,7 @@ public class ByteArrayEndPointTest assertEquals(true,endp.flush(data)); assertEquals("data.",BufferUtil.toString(endp.takeOutput())); + endp.close(); } @@ -234,6 +235,27 @@ public class ByteArrayEndPointTest assertTrue(fcb.isDone()); assertEquals(null, fcb.get()); assertEquals(" more.", endp.getOutputString()); + endp.close(); + } + + /** + * Simulate AbstractConnection.ReadCallback.failed() + */ + public static class Closer extends FutureCallback + { + private EndPoint endp; + + public Closer(EndPoint endp) + { + this.endp = endp; + } + + @Override + public void failed(Throwable cause) + { + endp.close(); + super.failed(cause); + } } @Slow @@ -275,7 +297,7 @@ public class ByteArrayEndPointTest assertThat(t.getCause(), instanceOf(TimeoutException.class)); } assertThat(System.currentTimeMillis() - start, greaterThan(idleTimeout / 2)); - assertTrue(endp.isOpen()); + assertThat("Endpoint open", endp.isOpen(), is(true)); // We need to delay the write timeout test below from the read timeout test above. // The reason is that the scheduler thread that fails the endPoint WriteFlusher @@ -298,17 +320,19 @@ public class ByteArrayEndPointTest assertThat(t.getCause(), instanceOf(TimeoutException.class)); } assertThat(System.currentTimeMillis() - start, greaterThan(idleTimeout / 2)); - assertTrue(endp.isOpen()); + assertThat("Endpoint open", endp.isOpen(), is(true)); - // Still no idle close - Thread.sleep(idleTimeout * 2); - assertTrue(endp.isOpen()); + endp.fillInterested(new Closer(endp)); + + // Still no idle close (wait half the time) + Thread.sleep(idleTimeout / 2); + assertThat("Endpoint open", endp.isOpen(), is(true)); // shutdown out endp.shutdownOutput(); - // idle close + // idle close (wait double the time) Thread.sleep(idleTimeout * 2); - assertFalse(endp.isOpen()); + assertThat("Endpoint open", endp.isOpen(), is(false)); } } diff --git a/jetty-osgi/jetty-osgi-boot/pom.xml b/jetty-osgi/jetty-osgi-boot/pom.xml index 5a02af5fdbf..5a074b92430 100644 --- a/jetty-osgi/jetty-osgi-boot/pom.xml +++ b/jetty-osgi/jetty-osgi-boot/pom.xml @@ -112,6 +112,7 @@ javax.servlet.http;version="[3.1,3.2)", javax.transaction;version="1.1.0";resolution:=optional, javax.transaction.xa;version="1.1.0";resolution:=optional, + org.objectweb.asm;version=4;resolution:=optional, org.eclipse.jetty.annotations;version="9.0.0";resolution:=optional, org.osgi.framework, org.osgi.service.cm;version="1.2.0", diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationParser.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationParser.java index b1ccba263a8..8887036f595 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationParser.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationParser.java @@ -35,6 +35,7 @@ import org.eclipse.jetty.annotations.ClassNameResolver; import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper; import org.eclipse.jetty.util.ConcurrentHashSet; import org.eclipse.jetty.util.resource.Resource; +import org.objectweb.asm.Opcodes; import org.osgi.framework.Bundle; import org.osgi.framework.Constants; @@ -50,6 +51,14 @@ public class AnnotationParser extends org.eclipse.jetty.annotations.AnnotationPa private ConcurrentHashMap _resourceToBundle = new ConcurrentHashMap(); private ConcurrentHashMap _bundleToUri = new ConcurrentHashMap(); + static + { + //As of jetty 9.2.0, the impl of asm visitor classes is compatible with both asm4 and asm5. + //We need to use asm4 with osgi, because we need to use aries spifly to support annotations, + //and currently this only supports asm4. Therefore, we set the asm api version to be 4 for osgi. + ASM_OPCODE_VERSION = Opcodes.ASM4; + } + /** * Keep track of a jetty URI Resource and its associated OSGi bundle. * @param uri diff --git a/jetty-osgi/jetty-osgi-httpservice/pom.xml b/jetty-osgi/jetty-osgi-httpservice/pom.xml index 3749cab43e7..91087d7bcca 100644 --- a/jetty-osgi/jetty-osgi-httpservice/pom.xml +++ b/jetty-osgi/jetty-osgi-httpservice/pom.xml @@ -28,6 +28,7 @@ org.eclipse.osgi org.eclipse.osgi + provided javax.servlet diff --git a/jetty-osgi/test-jetty-osgi-context/pom.xml b/jetty-osgi/test-jetty-osgi-context/pom.xml index 4e1c28a342b..941b13bfb3c 100644 --- a/jetty-osgi/test-jetty-osgi-context/pom.xml +++ b/jetty-osgi/test-jetty-osgi-context/pom.xml @@ -21,10 +21,12 @@ org.eclipse.osgi org.eclipse.osgi + provided org.eclipse.osgi org.eclipse.osgi.services + provided org.eclipse.jetty.toolchain diff --git a/jetty-osgi/test-jetty-osgi-webapp/pom.xml b/jetty-osgi/test-jetty-osgi-webapp/pom.xml index 00697afd6dd..1a0f37b6655 100644 --- a/jetty-osgi/test-jetty-osgi-webapp/pom.xml +++ b/jetty-osgi/test-jetty-osgi-webapp/pom.xml @@ -21,10 +21,12 @@ org.eclipse.osgi org.eclipse.osgi + provided org.eclipse.osgi org.eclipse.osgi.services + provided diff --git a/jetty-osgi/test-jetty-osgi/pom.xml b/jetty-osgi/test-jetty-osgi/pom.xml index 5cc1640678c..cf6810a052a 100644 --- a/jetty-osgi/test-jetty-osgi/pom.xml +++ b/jetty-osgi/test-jetty-osgi/pom.xml @@ -14,102 +14,36 @@ ${project.groupId}.boot.test.spdy http://download.eclipse.org/jetty/orbit/ target/distribution - 2.6.0 - 1.4.0 - 1.5.1 - 4.0.3 + 3.4.0 + 1.5.2 1.0 - 1.7.6 + 1.8.5 - - - org.ops4j.pax.swissbox - pax-swissbox-core - ${paxswissbox.version} - test - - - org.ops4j.pax.swissbox - pax-swissbox-extender - ${paxswissbox.version} - test - - - org.ops4j.pax.swissbox - pax-swissbox-lifecycle - ${paxswissbox.version} - test - - - org.ops4j.pax.swissbox - pax-swissbox-framework - ${paxswissbox.version} - test - + org.ops4j.pax.exam pax-exam ${exam.version} test - - org.apache.geronimo.specs - geronimo-atinject_1.0_spec - ${injection.bundle.version} - test - org.ops4j.pax.exam pax-exam-inject ${exam.version} test - - org.apache.aries.spifly - org.apache.aries.spifly.dynamic.bundle - 1.0.0 - test - - - - - org.ops4j.pax.exam pax-exam-container-forked ${exam.version} test - --> - - - org.ops4j.pax.exam - pax-exam-container-paxrunner - ${exam.version} - test - - - - org.ops4j.pax.runner - pax-runner-no-jcl - ${runner.version} - test - - + org.ops4j.pax.exam pax-exam-junit4 @@ -134,28 +68,81 @@ ${url.version} test + + - + + org.eclipse + osgi + 3.9.1-v20140110-1610 + test + + + org.eclipse.osgi + org.eclipse.osgi.services + test + + + + + + org.eclipse.jetty.osgi + jetty-osgi-boot + ${project.version} + test + + + org.eclipse.osgi + org.eclipse.osgi + + + org.eclipse.osgi + org.eclipse.osgi.services + + + + + org.eclipse.jetty.osgi + jetty-osgi-boot-jsp + ${project.version} + test + + + org.eclipse.osgi + org.eclipse.osgi + + + org.eclipse.osgi + org.eclipse.osgi.services + + + + + org.eclipse.jetty.toolchain + jetty-jsp-fragment + 2.3.3 + test + + + org.eclipse.jetty.osgi + jetty-httpservice + ${project.version} + test + + javax.servlet javax.servlet-api @@ -167,47 +154,34 @@ 1.1.1 test - - - org.eclipse.jetty.osgi - jetty-osgi-boot - ${project.version} - provided - - - org.eclipse.jetty.osgi - jetty-osgi-boot-jsp - ${project.version} - provided - - - org.eclipse.jetty.toolchain - jetty-jsp-fragment - 2.3.3 - provided - - - org.eclipse.jetty.osgi - jetty-httpservice - ${project.version} - provided - - + + org.apache.geronimo.specs + geronimo-atinject_1.0_spec + ${injection.bundle.version} + test + + + org.apache.aries.spifly + org.apache.aries.spifly.dynamic.bundle + 1.0.0 + test + + org.ow2.asm asm 4.1 - - + + org.ow2.asm asm-commons 4.1 - - + + org.ow2.asm asm-tree 4.1 - + @@ -386,7 +360,9 @@ ${project.version} runtime
    + + javax.servlet servlet-api @@ -409,6 +384,9 @@ servlet runtime + +--> + org.eclipse.jetty test-jetty-webapp diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java index 94ecd39df48..0dfc1bb0e18 100644 --- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java +++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java @@ -37,12 +37,13 @@ import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.osgi.boot.OSGiServerConstants; import org.eclipse.jetty.server.handler.ContextHandler; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.ops4j.pax.exam.CoreOptions; import org.ops4j.pax.exam.Option; -import org.ops4j.pax.exam.junit.Configuration; -import org.ops4j.pax.exam.junit.JUnit4TestRunner; +import org.ops4j.pax.exam.Configuration; +import org.ops4j.pax.exam.junit.PaxExam; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; @@ -55,12 +56,11 @@ import org.osgi.framework.ServiceReference; * Tests the ServiceContextProvider. * */ -@RunWith(JUnit4TestRunner.class) +@RunWith(PaxExam.class) public class TestJettyOSGiBootContextAsService { - private static final boolean LOGGING_ENABLED = false; + private static final String LOG_LEVEL = "WARN"; - private static final boolean REMOTE_DEBUGGING = false; @Inject BundleContext bundleContext = null; @@ -69,7 +69,6 @@ public class TestJettyOSGiBootContextAsService public static Option[] configure() { ArrayList