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 extends Handler> 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.osgiorg.eclipse.osgi
+ providedjavax.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.osgiorg.eclipse.osgi
+ providedorg.eclipse.osgiorg.eclipse.osgi.services
+ providedorg.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.osgiorg.eclipse.osgi
+ providedorg.eclipse.osgiorg.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.spdyhttp://download.eclipse.org/jetty/orbit/target/distribution
- 2.6.0
- 1.4.0
- 1.5.1
- 4.0.3
+ 3.4.0
+ 1.5.21.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.exampax-exam${exam.version}test
-
- org.apache.geronimo.specs
- geronimo-atinject_1.0_spec
- ${injection.bundle.version}
- test
- org.ops4j.pax.exampax-exam-inject${exam.version}test
-
- org.apache.aries.spifly
- org.apache.aries.spifly.dynamic.bundle
- 1.0.0
- test
-
-
-
-
-
org.ops4j.pax.exampax-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.exampax-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.servletjavax.servlet-api
@@ -167,47 +154,34 @@
1.1.1test
-
-
- 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.asmasm4.1
-
-
+
+ org.ow2.asmasm-commons4.1
-
-
+
+ org.ow2.asmasm-tree4.1
-
+
@@ -386,7 +360,9 @@
${project.version}runtime