Merge branch 'master' into release-9

This commit is contained in:
Jesse McConnell 2014-05-08 11:12:36 -05:00
commit cdb38c4532
265 changed files with 10052 additions and 5187 deletions

View File

@ -191,6 +191,12 @@
<version>${project.version}</version> <version>${project.version}</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-quickstart</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<!-- dependencies that jetty-all needs (some optional) --> <!-- dependencies that jetty-all needs (some optional) -->
<dependency> <dependency>
<groupId>javax.websocket</groupId> <groupId>javax.websocket</groupId>

View File

@ -2,7 +2,7 @@
protonego-boot protonego-boot
[files] [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] [exec]
-Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.jar -Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.v20140317.jar

View File

@ -2,7 +2,7 @@
protonego-boot protonego-boot
[files] [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] [exec]
-Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.jar -Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.v20140317.jar

View File

@ -2,7 +2,7 @@
protonego-boot protonego-boot
[files] [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] [exec]
-Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.jar -Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.v20140317.jar

View File

@ -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

View File

@ -2,7 +2,7 @@
protonego-boot protonego-boot
[files] [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] [exec]
-Xbootclasspath/p:lib/alpn/alpn-boot-8.0.0.jar -Xbootclasspath/p:lib/alpn/alpn-boot-8.0.0.v20140317.jar

View File

@ -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

View File

@ -44,7 +44,7 @@
<configuration> <configuration>
<instructions> <instructions>
<Import-Package>javax.servlet.*;version="[2.6.0,3.2)",org.objectweb.asm.*;version=4,*</Import-Package> <Import-Package>javax.servlet.*;version="[2.6.0,3.2)",org.objectweb.asm.*;version=4,*</Import-Package>
<Require-Capability>osgi.serviceloader; filter:="(osgi.serviceloader=javax.servlet.ServletContainerInitializer)";cardinality:=multiple, osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)"</Require-Capability> <Require-Capability>osgi.serviceloader; filter:="(osgi.serviceloader=javax.servlet.ServletContainerInitializer)";resolution:=optional;cardinality:=multiple, osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)"</Require-Capability>
</instructions> </instructions>
</configuration> </configuration>
</execution> </execution>

View File

@ -70,6 +70,8 @@ public class AnnotationParser
protected Set<String> _parsedClassNames = new ConcurrentHashSet<String>(); protected Set<String> _parsedClassNames = new ConcurrentHashSet<String>();
protected static int ASM_OPCODE_VERSION = Opcodes.ASM5; //compatibility of api
/** /**
* Convert internal name to simple name * Convert internal name to simple name
@ -373,7 +375,7 @@ public class AnnotationParser
final String signature, final String signature,
final String[] exceptions) final String[] exceptions)
{ {
super(Opcodes.ASM4); super(ASM_OPCODE_VERSION);
_handlers = handlers; _handlers = handlers;
_mi = new MethodInfo(classInfo, name, access, methodDesc,signature, exceptions); _mi = new MethodInfo(classInfo, name, access, methodDesc,signature, exceptions);
} }
@ -417,7 +419,7 @@ public class AnnotationParser
final String signature, final String signature,
final Object value) final Object value)
{ {
super(Opcodes.ASM4); super(ASM_OPCODE_VERSION);
_handlers = handlers; _handlers = handlers;
_fieldInfo = new FieldInfo(classInfo, fieldName, access, fieldType, signature, value); _fieldInfo = new FieldInfo(classInfo, fieldName, access, fieldType, signature, value);
} }
@ -456,7 +458,7 @@ public class AnnotationParser
public MyClassVisitor(Set<? extends Handler> handlers, Resource containingResource) public MyClassVisitor(Set<? extends Handler> handlers, Resource containingResource)
{ {
super(Opcodes.ASM4); super(ASM_OPCODE_VERSION);
_handlers = handlers; _handlers = handlers;
_containingResource = containingResource; _containingResource = containingResource;
} }
@ -702,6 +704,7 @@ public class AnnotationParser
} }
catch (Exception ex) catch (Exception ex)
{ {
if (LOG.isDebugEnabled()) LOG.debug("Error scanning file "+files[f], ex);
me.add(new RuntimeException("Error scanning file "+files[f],ex)); me.add(new RuntimeException("Error scanning file "+files[f],ex));
} }
} }

View File

@ -246,14 +246,12 @@ public class GZIPContentDecoder implements ContentDecoder
if (output == null) if (output == null)
{ {
// Save the inflated bytes and loop to see if we have finished // Save the inflated bytes and loop to see if we have finished
output = new byte[decoded]; output = Arrays.copyOf(bytes, decoded);
System.arraycopy(bytes, 0, output, 0, decoded);
} }
else else
{ {
// Accumulate inflated bytes and loop to see if we have finished // Accumulate inflated bytes and loop to see if we have finished
byte[] newOutput = new byte[output.length + decoded]; byte[] newOutput = Arrays.copyOf(output, output.length+decoded);
System.arraycopy(output, 0, newOutput, 0, output.length);
System.arraycopy(bytes, 0, newOutput, output.length, decoded); System.arraycopy(bytes, 0, newOutput, output.length, decoded);
output = newOutput; output = newOutput;
} }

View File

@ -201,8 +201,8 @@ public class HttpClient extends ContainerLifeCycle
scheduler = new ScheduledExecutorScheduler(name + "-scheduler", false); scheduler = new ScheduledExecutorScheduler(name + "-scheduler", false);
addBean(scheduler); addBean(scheduler);
addBean(transport);
transport.setHttpClient(this); transport.setHttpClient(this);
addBean(transport);
resolver = new SocketAddressResolver(executor, scheduler, getAddressResolutionTimeout()); resolver = new SocketAddressResolver(executor, scheduler, getAddressResolutionTimeout());
@ -228,7 +228,6 @@ public class HttpClient extends ContainerLifeCycle
protected void doStop() throws Exception protected void doStop() throws Exception
{ {
cookieStore.removeAll(); cookieStore.removeAll();
cookieStore = null;
decoderFactories.clear(); decoderFactories.clear();
handlers.clear(); handlers.clear();
@ -391,26 +390,29 @@ public class HttpClient extends ContainerLifeCycle
.idleTimeout(oldRequest.getIdleTimeout(), TimeUnit.MILLISECONDS) .idleTimeout(oldRequest.getIdleTimeout(), TimeUnit.MILLISECONDS)
.timeout(oldRequest.getTimeout(), TimeUnit.MILLISECONDS) .timeout(oldRequest.getTimeout(), TimeUnit.MILLISECONDS)
.followRedirects(oldRequest.isFollowRedirects()); .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 HttpHeader header = field.getHeader();
if (HttpHeader.HOST == header.getHeader()) // We have a new URI, so skip the host header if present.
if (HttpHeader.HOST == header)
continue; continue;
// Remove expectation headers // Remove expectation headers.
if (HttpHeader.EXPECT == header.getHeader()) if (HttpHeader.EXPECT == header)
continue; continue;
// Remove cookies // Remove cookies.
if (HttpHeader.COOKIE == header.getHeader()) if (HttpHeader.COOKIE == header)
continue; continue;
// Remove authorization headers // Remove authorization headers.
if (HttpHeader.AUTHORIZATION == header.getHeader() || if (HttpHeader.AUTHORIZATION == header ||
HttpHeader.PROXY_AUTHORIZATION == header.getHeader()) HttpHeader.PROXY_AUTHORIZATION == header)
continue; continue;
newRequest.header(header.getName(), header.getValue()); String value = field.getValue();
if (!newRequest.getHeaders().contains(header, value))
newRequest.header(field.getName(), value);
} }
return newRequest; return newRequest;
} }

View File

@ -215,6 +215,10 @@ public abstract class HttpDestination implements Destination, Closeable, Dumpabl
LOG.debug("Closed {}", this); LOG.debug("Closed {}", this);
} }
public void release(Connection connection)
{
}
public void close(Connection connection) public void close(Connection connection)
{ {
} }

View File

@ -33,6 +33,7 @@ import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.util.BufferUtil; 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.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
@ -48,8 +49,8 @@ import org.eclipse.jetty.util.log.Logger;
* is available</li> * is available</li>
* <li>{@link #responseHeader(HttpExchange, HttpField)}, when a HTTP field is available</li> * <li>{@link #responseHeader(HttpExchange, HttpField)}, when a HTTP field is available</li>
* <li>{@link #responseHeaders(HttpExchange)}, when all HTTP headers are available</li> * <li>{@link #responseHeaders(HttpExchange)}, when all HTTP headers are available</li>
* <li>{@link #responseContent(HttpExchange, ByteBuffer)}, when HTTP content is available; this is the only method * <li>{@link #responseContent(HttpExchange, ByteBuffer, Callback)}, when HTTP content is available; this is the only
* that may be invoked multiple times with different buffers containing different content</li> * method that may be invoked multiple times with different buffers containing different content</li>
* <li>{@link #responseSuccess(HttpExchange)}, when the response is complete</li> * <li>{@link #responseSuccess(HttpExchange)}, when the response is complete</li>
* </ol> * </ol>
* At any time, subclasses may invoke {@link #responseFailure(Throwable)} to indicate that the response has failed * 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(); HttpResponse response = exchange.getResponse();
if (LOG.isDebugEnabled()) 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(); ResponseNotifier notifier = getHttpDestination().getResponseNotifier();
notifier.notifyHeaders(exchange.getConversation().getResponseListeners(), response); notifier.notifyHeaders(exchange.getConversation().getResponseListeners(), response);
@ -269,7 +270,7 @@ public abstract class HttpReceiver
* @param buffer the response HTTP content buffer * @param buffer the response HTTP content buffer
* @return whether the processing should continue * @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) out: while (true)
{ {
@ -292,18 +293,18 @@ public abstract class HttpReceiver
HttpResponse response = exchange.getResponse(); HttpResponse response = exchange.getResponse();
if (LOG.isDebugEnabled()) 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; ContentDecoder decoder = this.decoder;
if (decoder != null) if (decoder != null)
{ {
buffer = decoder.decode(buffer); buffer = decoder.decode(buffer);
if (LOG.isDebugEnabled()) 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(); ResponseNotifier notifier = getHttpDestination().getResponseNotifier();
notifier.notifyContent(exchange.getConversation().getResponseListeners(), response, buffer); notifier.notifyContent(exchange.getConversation().getResponseListeners(), response, buffer, callback);
return true; return true;
} }

View File

@ -50,6 +50,7 @@ import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Fields; import org.eclipse.jetty.util.Fields;
public class HttpRequest implements Request public class HttpRequest implements Request
@ -449,12 +450,34 @@ public class HttpRequest implements Request
@Override @Override
public Request onResponseContent(final Response.ContentListener listener) public Request onResponseContent(final Response.ContentListener listener)
{ {
this.responseListeners.add(new Response.ContentListener() this.responseListeners.add(new Response.AsyncContentListener()
{ {
@Override @Override
public void onContent(Response response, ByteBuffer content) public void onContent(Response response, ByteBuffer content, Callback callback)
{
try
{ {
listener.onContent(response, content); 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; return this;

View File

@ -62,7 +62,7 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
private final AtomicReference<RequestState> requestState = new AtomicReference<>(RequestState.QUEUED); private final AtomicReference<RequestState> requestState = new AtomicReference<>(RequestState.QUEUED);
private final AtomicReference<SenderState> senderState = new AtomicReference<>(SenderState.IDLE); private final AtomicReference<SenderState> senderState = new AtomicReference<>(SenderState.IDLE);
private final Callback commitCallback = new CommitCallback(); 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 Callback lastCallback = new LastContentCallback();
private final HttpChannel channel; private final HttpChannel channel;
private volatile HttpContent content; private volatile HttpContent content;
@ -100,14 +100,7 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
if (updateSenderState(current, newSenderState)) if (updateSenderState(current, newSenderState))
{ {
LOG.debug("Deferred content available, {} -> {}", current, newSenderState); LOG.debug("Deferred content available, {} -> {}", current, newSenderState);
// TODO should just call contentCallback.iterate() here. contentCallback.iterate();
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();
return; return;
} }
break; break;
@ -456,26 +449,12 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
case WAITING: case WAITING:
{ {
// We received the 100 Continue, now send the content if any. // 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)) if (!updateSenderState(current, SenderState.SENDING))
throw illegalSenderState(current); throw illegalSenderState(current);
LOG.debug("Proceeding while waiting"); LOG.debug("Proceeding while waiting");
sendContent(exchange, content, contentCallback); // TODO old style usage! contentCallback.iterate();
return; return;
} }
else
{
// No content to send yet - it's deferred.
if (!updateSenderState(current, SenderState.IDLE))
throw illegalSenderState(current);
LOG.debug("Proceeding deferred");
return;
}
}
default: default:
{ {
throw illegalSenderState(current); throw illegalSenderState(current);
@ -665,31 +644,9 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
{ {
case SENDING: case SENDING:
{ {
// TODO should just call contentCallback.iterate() here. contentCallback.iterate();
// We have content to send ?
if (content.advance())
{
sendContent(exchange, content, contentCallback); // TODO old style usage!
return; 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;
}
}
}
case SENDING_WITH_CONTENT: case SENDING_WITH_CONTENT:
{ {
// We have deferred content to send. // We have deferred content to send.
@ -745,59 +702,43 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
if (exchange == null) if (exchange == null)
return Action.IDLE; return Action.IDLE;
Request request = exchange.getRequest();
HttpContent content = HttpSender.this.content; HttpContent content = HttpSender.this.content;
ByteBuffer contentBuffer = content.getContent();
if (contentBuffer != null)
{
if (!someToContent(request, contentBuffer))
return Action.IDLE;
}
while (true) while (true)
{ {
boolean advanced = content.advance(); boolean advanced = content.advance();
boolean consumed = content.isConsumed(); boolean consumed = content.isConsumed();
if (LOG.isDebugEnabled())
LOG.debug("Content {} consumed {} for {}", advanced, consumed, exchange.getRequest());
SenderState current = senderState.get();
switch (current)
{
case SENDING:
{
if (advanced) if (advanced)
{ {
// There is more content to send
sendContent(exchange, content, this); sendContent(exchange, content, this);
return Action.SCHEDULED; return Action.SCHEDULED;
} }
else if (consumed)
if (consumed)
{ {
sendContent(exchange, content, lastCallback); sendContent(exchange, content, lastCallback);
return Action.IDLE; return Action.IDLE;
} }
else
SenderState current = HttpSender.this.senderState.get();
switch (current)
{
case SENDING:
{ {
if (updateSenderState(current, SenderState.IDLE)) if (updateSenderState(current, SenderState.IDLE))
{ {
LOG.debug("Waiting for deferred content for {}", request); if (LOG.isDebugEnabled())
LOG.debug("Content is deferred for {}", exchange.getRequest());
return Action.IDLE; return Action.IDLE;
} }
break; break;
} }
}
case SENDING_WITH_CONTENT: case SENDING_WITH_CONTENT:
{ {
if (updateSenderState(current, SenderState.SENDING)) updateSenderState(current, SenderState.SENDING);
{ break;
LOG.debug("Deferred content available for {}", request);
if (advanced)
{
sendContent(exchange, content, this);
return Action.SCHEDULED;
}
}
throw illegalSenderState(current);
} }
default: default:
{ {
@ -810,6 +751,8 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
@Override @Override
public void succeeded() public void succeeded()
{ {
ByteBuffer buffer = content.getContent();
someToContent(getHttpExchange().getRequest(), buffer);
content.succeeded(); content.succeeded();
super.succeeded(); super.succeeded();
} }

View File

@ -140,8 +140,11 @@ public abstract class PoolingHttpDestination<C extends Connection> extends HttpD
protected abstract void send(C connection, HttpExchange exchange); 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); LOG.debug("{} released", connection);
HttpClient client = getHttpClient(); HttpClient client = getHttpClient();
if (client.isRunning()) if (client.isRunning())

View File

@ -27,6 +27,8 @@ import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.http.HttpField; 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.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
@ -106,31 +108,20 @@ public class ResponseNotifier
} }
} }
public void notifyContent(List<Response.ResponseListener> listeners, Response response, ByteBuffer buffer) public void notifyContent(List<Response.ResponseListener> listeners, Response response, ByteBuffer buffer, Callback callback)
{ {
// Slice the buffer to avoid that listeners peek into data they should not look at. // Here we use an IteratingNestedCallback not to avoid the stack overflow, but to
buffer = buffer.slice(); // invoke the listeners one after the other. When all of them have invoked the
if (!buffer.hasRemaining()) // callback they got passed, the callback passed to this method is finally invoked.
return; ContentCallback contentCallback = new ContentCallback(listeners, response, buffer, callback);
// Optimized to avoid allocations of iterator instances contentCallback.iterate();
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);
}
}
} }
private void notifyContent(Response.ContentListener listener, Response response, ByteBuffer buffer) private void notifyContent(Response.AsyncContentListener listener, Response response, ByteBuffer buffer, Callback callback)
{ {
try try
{ {
listener.onContent(response, buffer); listener.onContent(response, buffer, callback);
} }
catch (Throwable x) catch (Throwable x)
{ {
@ -218,7 +209,8 @@ public class ResponseNotifier
} }
notifyHeaders(listeners, response); notifyHeaders(listeners, response);
if (response instanceof ContentResponse) 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); notifySuccess(listeners, response);
} }
@ -239,7 +231,8 @@ public class ResponseNotifier
} }
notifyHeaders(listeners, response); notifyHeaders(listeners, response);
if (response instanceof ContentResponse) 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); notifyFailure(listeners, response, failure);
} }
@ -248,4 +241,51 @@ public class ResponseNotifier
forwardFailure(listeners, response, responseFailure); forwardFailure(listeners, response, responseFailure);
notifyComplete(listeners, new Result(request, requestFailure, response, responseFailure)); notifyComplete(listeners, new Result(request, requestFailure, response, responseFailure));
} }
private class ContentCallback extends IteratingNestedCallback
{
private final List<Response.ResponseListener> listeners;
private final Response response;
private final ByteBuffer buffer;
private int index;
private ContentCallback(List<Response.ResponseListener> 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();
}
}
} }

View File

@ -338,11 +338,17 @@ public interface Request
Request onResponseHeaders(Response.HeadersListener listener); 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 * @return this request object
*/ */
Request onResponseContent(Response.ContentListener listener); 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 * @param listener a listener for response success event
* @return this request object * @return this request object

View File

@ -26,6 +26,7 @@ import org.eclipse.jetty.client.util.BufferingResponseListener;
import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.util.Callback;
/** /**
* <p>{@link Response} represents a HTTP response and offers methods to retrieve status code, HTTP version * <p>{@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 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. * Listener for the response succeeded event.
*/ */
@ -204,7 +210,7 @@ public interface Response
/** /**
* Listener for all response events. * 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} * 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 @Override
public void onSuccess(Response response) public void onSuccess(Response response)
{ {

View File

@ -20,10 +20,12 @@ package org.eclipse.jetty.client.http;
import org.eclipse.jetty.client.HttpChannel; import org.eclipse.jetty.client.HttpChannel;
import org.eclipse.jetty.client.HttpExchange; import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpVersion;
public class HttpChannelOverHTTP extends HttpChannel public class HttpChannelOverHTTP extends HttpChannel
{ {
@ -77,10 +79,23 @@ public class HttpChannelOverHTTP extends HttpChannel
public void exchangeTerminated(Result result) public void exchangeTerminated(Result result)
{ {
super.exchangeTerminated(result); super.exchangeTerminated(result);
HttpFields responseHeaders = result.getResponse().getHeaders(); Response response = result.getResponse();
boolean close = result.isFailed() || HttpFields responseHeaders = response.getHeaders();
responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()) || boolean close = result.isFailed() || receiver.isShutdown();
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) if (close)
connection.close(); connection.close();
else else

View File

@ -32,12 +32,13 @@ import org.eclipse.jetty.http.HttpParser;
import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.CompletableCallback;
public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer> public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
{ {
private final HttpParser parser = new HttpParser(this); private final HttpParser parser = new HttpParser(this);
private ByteBuffer buffer;
private boolean shutdown; private boolean shutdown;
public HttpReceiverOverHTTP(HttpChannelOverHTTP channel) public HttpReceiverOverHTTP(HttpChannelOverHTTP channel)
@ -58,68 +59,97 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
public void receive() public void receive()
{ {
HttpConnectionOverHTTP connection = getHttpConnection();
EndPoint endPoint = connection.getEndPoint();
HttpClient client = getHttpDestination().getHttpClient(); HttpClient client = getHttpDestination().getHttpClient();
ByteBufferPool bufferPool = client.getByteBufferPool(); ByteBufferPool bufferPool = client.getByteBufferPool();
ByteBuffer buffer = bufferPool.acquire(client.getResponseBufferSize(), true); buffer = bufferPool.acquire(client.getResponseBufferSize(), true);
try 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 boolean readAndParse()
{
HttpConnectionOverHTTP connection = getHttpConnection();
EndPoint endPoint = connection.getEndPoint();
ByteBuffer buffer = this.buffer;
while (true) while (true)
{ {
// Connection may be closed in a parser callback try
{
// Connection may be closed in a parser callback.
if (connection.isClosed()) if (connection.isClosed())
{ {
if (LOG.isDebugEnabled())
LOG.debug("{} closed", connection); LOG.debug("{} closed", connection);
break; return true;
} }
else
{ if (!parse(buffer))
return false;
int read = endPoint.fill(buffer); int read = endPoint.fill(buffer);
if (LOG.isDebugEnabled()) // Avoid boxing of variable 'read' // Avoid boxing of variable 'read'
if (LOG.isDebugEnabled())
LOG.debug("Read {} bytes from {}", read, endPoint); LOG.debug("Read {} bytes from {}", read, endPoint);
if (read > 0) if (read > 0)
{ {
parse(buffer); if (!parse(buffer))
return false;
} }
else if (read == 0) else if (read == 0)
{ {
fillInterested(); fillInterested();
break; return true;
} }
else else
{ {
shutdown(); shutdown();
break; return true;
} }
} }
} catch (Throwable x)
}
catch (EofException x)
{
LOG.ignore(x);
failAndClose(x);
}
catch (Exception x)
{ {
LOG.debug(x); LOG.debug(x);
failAndClose(x); failAndClose(x);
return true;
} }
finally
{
bufferPool.release(buffer);
} }
} }
private void parse(ByteBuffer buffer) /**
* 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)
{ {
while (buffer.hasRemaining()) // Must parse even if the buffer is fully consumed, to allow the
parser.parseNext(buffer); // 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() private void fillInterested()
{ {
// TODO: do we need to call fillInterested() only if we are not failed (or we have an exchange) ?
getHttpChannel().getHttpConnection().fillInterested(); getHttpChannel().getHttpConnection().fillInterested();
} }
@ -195,8 +225,22 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
if (exchange == null) if (exchange == null)
return false; return false;
responseContent(exchange, buffer); CompletableCallback callback = new CompletableCallback()
return false; {
@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 @Override

View File

@ -114,7 +114,7 @@ public class HttpSenderOverHTTP extends HttpSender
} }
} }
} }
catch (Exception x) catch (Throwable x)
{ {
LOG.debug(x); LOG.debug(x);
callback.failed(x); callback.failed(x);

View File

@ -18,11 +18,14 @@
package org.eclipse.jetty.client.util; package org.eclipse.jetty.client.util;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException; import java.nio.charset.UnsupportedCharsetException;
import java.util.Arrays;
import java.util.Locale; import java.util.Locale;
import org.eclipse.jetty.client.api.Response; 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.client.api.Result;
import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.util.BufferUtil;
/** /**
* <p>Implementation of {@link Listener} that buffers the content up to a maximum length * <p>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 public abstract class BufferingResponseListener extends Listener.Adapter
{ {
private final int maxLength; private final int maxLength;
private volatile byte[] buffer = new byte[0]; private volatile ByteBuffer buffer;
private volatile String encoding; private volatile String encoding;
/** /**
@ -58,20 +62,24 @@ public abstract class BufferingResponseListener extends Listener.Adapter
*/ */
public BufferingResponseListener(int maxLength) public BufferingResponseListener(int maxLength)
{ {
this.maxLength = maxLength; this.maxLength=maxLength;
} }
@Override @Override
public void onHeaders(Response response) public void onHeaders(Response response)
{ {
super.onHeaders(response);
HttpFields headers = response.getHeaders(); HttpFields headers = response.getHeaders();
long length = headers.getLongField(HttpHeader.CONTENT_LENGTH.asString()); long length = headers.getLongField(HttpHeader.CONTENT_LENGTH.asString());
if (length > maxLength) if (length > maxLength)
{ {
response.abort(new IllegalArgumentException("Buffering capacity exceeded")); response.abort(new IllegalArgumentException("Buffering capacity exceeded"));
return;
} }
else
{ buffer=BufferUtil.allocate((length > 0)?(int)length:1024);
String contentType = headers.get(HttpHeader.CONTENT_TYPE); String contentType = headers.get(HttpHeader.CONTENT_TYPE);
if (contentType != null) if (contentType != null)
{ {
@ -88,23 +96,23 @@ public abstract class BufferingResponseListener extends Listener.Adapter
} }
} }
} }
}
@Override @Override
public void onContent(Response response, ByteBuffer content) public void onContent(Response response, ByteBuffer content)
{ {
long newLength = buffer.length + content.remaining(); int length = content.remaining();
if (newLength > maxLength) if (length>BufferUtil.space(buffer))
{ {
int requiredCapacity = buffer==null?0:buffer.capacity()+length;
if (requiredCapacity>maxLength)
response.abort(new IllegalArgumentException("Buffering capacity exceeded")); response.abort(new IllegalArgumentException("Buffering capacity exceeded"));
int newCapacity = Math.min(Integer.highestOneBit(requiredCapacity) << 1, maxLength);
buffer = BufferUtil.ensureCapacity(buffer,newCapacity);
} }
else
{ BufferUtil.append(buffer, content);
byte[] newBuffer = new byte[(int)newLength];
System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
content.get(newBuffer, buffer.length, content.remaining());
buffer = newBuffer;
}
} }
@Override @Override
@ -121,7 +129,9 @@ public abstract class BufferingResponseListener extends Listener.Adapter
*/ */
public byte[] getContent() 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) public String getContentAsString(String encoding)
{ {
try if (buffer==null)
{ return null;
return new String(getContent(), encoding); return BufferUtil.toString(buffer, Charset.forName(encoding));
}
catch (UnsupportedEncodingException x)
{
throw new UnsupportedCharsetException(encoding);
}
} }
/** /**
@ -161,6 +166,19 @@ public abstract class BufferingResponseListener extends Listener.Adapter
*/ */
public String getContentAsString(Charset encoding) 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());
} }
} }

View File

@ -24,6 +24,7 @@ import java.io.InterruptedIOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Iterator; import java.util.Iterator;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference; 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.Request;
import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.util.ArrayQueue; import org.eclipse.jetty.util.ArrayQueue;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
/** /**
@ -83,10 +85,11 @@ import org.eclipse.jetty.util.Callback;
*/ */
public class DeferredContentProvider implements AsyncContentProvider, Closeable 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 Object lock = this;
private final Queue<ByteBuffer> chunks = new ArrayQueue<>(4, 64, lock); private final Queue<AsyncChunk> chunks = new ArrayQueue<>(4, 64, lock);
private final AtomicReference<Listener> listener = new AtomicReference<>(); private final AtomicReference<Listener> listener = new AtomicReference<>();
private final Iterator<ByteBuffer> iterator = new DeferredContentProviderIterator(); private final Iterator<ByteBuffer> iterator = new DeferredContentProviderIterator();
private final AtomicBoolean closed = new AtomicBoolean(); 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 * @return true if the content was added, false otherwise
*/ */
public boolean offer(ByteBuffer buffer) 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; boolean result;
synchronized (lock) synchronized (lock)
{ {
result = chunks.offer(buffer); result = chunks.offer(chunk);
if (result && buffer != CLOSE) if (result && chunk != CLOSE)
++size; ++size;
} }
if (result) if (result)
@ -186,7 +199,7 @@ public class DeferredContentProvider implements AsyncContentProvider, Closeable
private class DeferredContentProviderIterator implements Iterator<ByteBuffer>, Callback private class DeferredContentProviderIterator implements Iterator<ByteBuffer>, Callback
{ {
private ByteBuffer current; private AsyncChunk current;
@Override @Override
public boolean hasNext() public boolean hasNext()
@ -202,10 +215,10 @@ public class DeferredContentProvider implements AsyncContentProvider, Closeable
{ {
synchronized (lock) synchronized (lock)
{ {
ByteBuffer element = current = chunks.poll(); AsyncChunk chunk = current = chunks.poll();
if (element == CLOSE) if (chunk == CLOSE)
throw new NoSuchElementException(); throw new NoSuchElementException();
return element; return chunk == null ? null : chunk.buffer;
} }
} }
@ -218,24 +231,44 @@ public class DeferredContentProvider implements AsyncContentProvider, Closeable
@Override @Override
public void succeeded() public void succeeded()
{ {
AsyncChunk chunk;
synchronized (lock) synchronized (lock)
{ {
if (current != null) chunk = current;
if (chunk != null)
{ {
--size; --size;
lock.notify(); lock.notify();
} }
} }
if (chunk != null)
chunk.callback.succeeded();
} }
@Override @Override
public void failed(Throwable x) public void failed(Throwable x)
{ {
AsyncChunk chunk;
synchronized (lock) synchronized (lock)
{ {
chunk = current;
failure = x; failure = x;
lock.notify(); 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);
} }
} }
} }

View File

@ -102,6 +102,16 @@ public class InputStreamContentProvider implements ContentProvider
return ByteBuffer.wrap(buffer, offset, length); 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 @Override
public Iterator<ByteBuffer> iterator() public Iterator<ByteBuffer> iterator()
{ {
@ -166,6 +176,7 @@ public class InputStreamContentProvider implements ContentProvider
if (failure == null) if (failure == null)
{ {
failure = x; failure = x;
onReadFailure(x);
// Signal we have more content to cause a call to // Signal we have more content to cause a call to
// next() which will throw NoSuchElementException. // next() which will throw NoSuchElementException.
hasNext = Boolean.TRUE; hasNext = Boolean.TRUE;

View File

@ -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<Callback> callbackRef = new AtomicReference<>();
final AtomicReference<CountDownLatch> 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());
}
}

View File

@ -37,12 +37,14 @@ import java.util.List;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Random; import java.util.Random;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import java.util.zip.GZIPOutputStream; import java.util.zip.GZIPOutputStream;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream; import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest; 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.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.TestingDir; import org.eclipse.jetty.toolchain.test.TestingDir;
import org.eclipse.jetty.toolchain.test.annotation.Slow; 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.FuturePromise;
import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.ssl.SslContextFactory;
@ -985,6 +988,13 @@ public class HttpClientTest extends AbstractHttpClientServerTest
counter.incrementAndGet(); counter.incrementAndGet();
} }
@Override
public void onContent(Response response, ByteBuffer content, Callback callback)
{
// Should not be invoked
counter.incrementAndGet();
}
@Override @Override
public void onSuccess(Response response) public void onSuccess(Response response)
{ {
@ -1012,6 +1022,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
.onResponseHeader(listener) .onResponseHeader(listener)
.onResponseHeaders(listener) .onResponseHeaders(listener)
.onResponseContent(listener) .onResponseContent(listener)
.onResponseContentAsync(listener)
.onResponseSuccess(listener) .onResponseSuccess(listener)
.onResponseFailure(listener) .onResponseFailure(listener)
.send(listener); .send(listener);
@ -1038,26 +1049,33 @@ public class HttpClientTest extends AbstractHttpClientServerTest
} }
}); });
final AtomicInteger complete = new AtomicInteger(); final Exchanger<Response> ex = new Exchanger<Response>();
BufferingResponseListener listener = new BufferingResponseListener() BufferingResponseListener listener = new BufferingResponseListener()
{ {
@Override @Override
public void onComplete(Result result) 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) .scheme(scheme)
.onResponseContent(listener) .send(listener);
.onComplete(listener)
.send(); Response response = ex.exchange(null);
Assert.assertEquals(200, response.getStatus()); Assert.assertEquals(200, response.getStatus());
Assert.assertEquals(1, complete.get());
Assert.assertArrayEquals(content, listener.getContent()); Assert.assertArrayEquals(content, listener.getContent());
Assert.assertArrayEquals(content, response.getContent());
} }
@Test @Test

View File

@ -24,7 +24,6 @@ import java.util.Arrays;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; 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.http.HttpDestinationOverHTTP;
import org.eclipse.jetty.client.util.ByteBufferContentProvider; import org.eclipse.jetty.client.util.ByteBufferContentProvider;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.annotation.Slow; 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, idleConnections.size());
Assert.assertEquals(0, activeConnections.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<Connection> idleConnections = connectionPool.getIdleConnections();
Assert.assertEquals(0, idleConnections.size());
final BlockingQueue<Connection> 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());
}
} }

View File

@ -569,8 +569,8 @@
<arguments> <arguments>
<argument>jetty.home=${assembly-directory}</argument> <argument>jetty.home=${assembly-directory}</argument>
<argument>jetty.base=${assembly-directory}/demo-base</argument> <argument>jetty.base=${assembly-directory}/demo-base</argument>
<argument>--add-to-start=server,continuation,deploy,ext,resources,client,annotations,jndi,servlets</argument> <argument>--add-to-start=server,continuation,deploy,websocket,ext,resources,client,annotations,jndi,servlets</argument>
<argument>--add-to-startd-ini=jsp,jstl,http,https</argument> <argument>--add-to-startd=jsp,jstl,http,https</argument>
</arguments> </arguments>
</configuration> </configuration>
<goals> <goals>

View File

@ -105,8 +105,14 @@ findDirectory()
running() running()
{ {
if [ -f "$1" ]
then
local PID=$(cat "$1" 2>/dev/null) || return 1 local PID=$(cat "$1" 2>/dev/null) || return 1
kill -0 "$PID" 2>/dev/null kill -0 "$PID" 2>/dev/null
return
fi
rm -f "$1"
return 1
} }
started() started()
@ -408,16 +414,10 @@ case "$ACTION" in
else else
if [ -f "$JETTY_PID" ]
then
if running $JETTY_PID if running $JETTY_PID
then then
echo "Already Running!" echo "Already Running $(cat $JETTY_PID)!"
exit 1 exit 1
else
# dead pid file - remove
rm -f "$JETTY_PID"
fi
fi fi
if [ "$JETTY_USER" ] if [ "$JETTY_USER" ]
@ -519,16 +519,10 @@ case "$ACTION" in
run|demo) run|demo)
echo "Running Jetty: " echo "Running Jetty: "
if [ -f "$JETTY_PID" ]
then
if running "$JETTY_PID" if running "$JETTY_PID"
then then
echo "Already Running!" echo Already Running $(cat "$JETTY_PID")!
exit 1 exit 1
else
# dead pid file - remove
rm -f "$JETTY_PID"
fi
fi fi
exec "${RUN_CMD[@]}" exec "${RUN_CMD[@]}"
@ -550,7 +544,7 @@ case "$ACTION" in
echo "RUN_CMD = ${RUN_CMD[*]}" echo "RUN_CMD = ${RUN_CMD[*]}"
echo echo
if [ -f "$JETTY_PID" ] if running "$JETTY_PID"
then then
echo "Jetty running pid=$(< "$JETTY_PID")" echo "Jetty running pid=$(< "$JETTY_PID")"
exit 0 exit 0

View File

@ -16,5 +16,5 @@ jsp-impl/${jsp-impl}-jsp
# default jetty >= 9.2 # default jetty >= 9.2
jsp-impl=apache 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 # -Dorg.apache.jasper.compiler.disablejsr199=true

View File

@ -28,10 +28,9 @@ import org.eclipse.jetty.fcgi.generator.Flusher;
import org.eclipse.jetty.fcgi.generator.Generator; import org.eclipse.jetty.fcgi.generator.Generator;
import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields; 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.http.HttpVersion;
import org.eclipse.jetty.io.IdleTimeout; import org.eclipse.jetty.io.IdleTimeout;
import org.eclipse.jetty.util.Callback;
public class HttpChannelOverFCGI extends HttpChannel public class HttpChannelOverFCGI extends HttpChannel
{ {
@ -83,42 +82,46 @@ public class HttpChannelOverFCGI extends HttpChannel
return receiver.abort(cause); return receiver.abort(cause);
} }
protected void responseBegin(int code, String reason) protected boolean responseBegin(int code, String reason)
{ {
HttpExchange exchange = getHttpExchange(); HttpExchange exchange = getHttpExchange();
if (exchange != null) if (exchange == null)
{ return false;
exchange.getResponse().version(version).status(code).reason(reason); exchange.getResponse().version(version).status(code).reason(reason);
receiver.responseBegin(exchange); return receiver.responseBegin(exchange);
}
} }
protected void responseHeader(HttpField field) protected boolean responseHeader(HttpField field)
{
HttpExchange exchange = getHttpExchange();
return exchange != null && receiver.responseHeader(exchange, field);
}
protected boolean responseHeaders()
{
HttpExchange exchange = getHttpExchange();
return exchange != null && receiver.responseHeaders(exchange);
}
protected boolean content(ByteBuffer buffer, Callback callback)
{ {
HttpExchange exchange = getHttpExchange(); HttpExchange exchange = getHttpExchange();
if (exchange != null) if (exchange != null)
receiver.responseHeader(exchange, field); return receiver.responseContent(exchange, buffer, callback);
callback.succeeded();
return false;
} }
protected void responseHeaders() protected boolean responseSuccess()
{ {
HttpExchange exchange = getHttpExchange(); HttpExchange exchange = getHttpExchange();
if (exchange != null) return exchange != null && receiver.responseSuccess(exchange);
receiver.responseHeaders(exchange);
} }
protected void content(ByteBuffer buffer) protected boolean responseFailure(Throwable failure)
{ {
HttpExchange exchange = getHttpExchange(); HttpExchange exchange = getHttpExchange();
if (exchange != null) return exchange != null && receiver.responseFailure(failure);
receiver.responseContent(exchange, buffer);
}
protected void responseSuccess()
{
HttpExchange exchange = getHttpExchange();
if (exchange != null)
receiver.responseSuccess(exchange);
} }
@Override @Override
@ -126,12 +129,10 @@ public class HttpChannelOverFCGI extends HttpChannel
{ {
super.exchangeTerminated(result); super.exchangeTerminated(result);
idle.onClose(); idle.onClose();
boolean close = result.isFailed();
HttpFields responseHeaders = result.getResponse().getHeaders(); HttpFields responseHeaders = result.getResponse().getHeaders();
close |= responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()); if (result.isFailed())
if (close) connection.close(result.getFailure());
connection.close(); else if (!connection.closeByHTTP(responseHeaders))
else
connection.release(this); connection.release(this);
} }
@ -154,6 +155,7 @@ public class HttpChannelOverFCGI extends HttpChannel
@Override @Override
protected void onIdleExpired(TimeoutException timeout) protected void onIdleExpired(TimeoutException timeout)
{ {
if (LOG.isDebugEnabled())
LOG.debug("Idle timeout for request {}", request); LOG.debug("Idle timeout for request {}", request);
connection.abort(timeout); connection.abort(timeout);
} }

View File

@ -69,7 +69,7 @@ public class HttpClientTransportOverFCGI extends AbstractHttpClientTransport
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
{ {
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY); 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); LOG.debug("Created {}", connection);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Promise<Connection> promise = (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY); Promise<Connection> promise = (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);

View File

@ -31,7 +31,6 @@ import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpConnection; import org.eclipse.jetty.client.HttpConnection;
import org.eclipse.jetty.client.HttpDestination; import org.eclipse.jetty.client.HttpDestination;
import org.eclipse.jetty.client.HttpExchange; 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.Connection;
import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response; 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.generator.Flusher;
import org.eclipse.jetty.fcgi.parser.ClientParser; import org.eclipse.jetty.fcgi.parser.ClientParser;
import org.eclipse.jetty.http.HttpField; 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.AbstractConnection;
import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.BufferUtil; 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.Log;
import org.eclipse.jetty.util.log.Logger; 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 AtomicBoolean closed = new AtomicBoolean();
private final Flusher flusher; private final Flusher flusher;
private final HttpDestination destination; private final HttpDestination destination;
private final boolean multiplexed;
private final Delegate delegate; private final Delegate delegate;
private final ClientParser parser; 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()); super(endPoint, destination.getHttpClient().getExecutor(), destination.getHttpClient().isDispatchIO());
this.flusher = new Flusher(endPoint);
this.destination = destination; this.destination = destination;
this.multiplexed = multiplexed;
this.flusher = new Flusher(endPoint);
this.delegate = new Delegate(destination); this.delegate = new Delegate(destination);
this.parser = new ClientParser(new ResponseListener()); this.parser = new ClientParser(new ResponseListener());
requests.addLast(0); requests.addLast(0);
@ -94,53 +100,76 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
@Override @Override
public void onFillable() public void onFillable()
{ {
EndPoint endPoint = getEndPoint();
HttpClient client = destination.getHttpClient(); HttpClient client = destination.getHttpClient();
ByteBufferPool bufferPool = client.getByteBufferPool(); ByteBufferPool bufferPool = client.getByteBufferPool();
ByteBuffer buffer = bufferPool.acquire(client.getResponseBufferSize(), true); buffer = bufferPool.acquire(client.getResponseBufferSize(), true);
try process();
}
private void process()
{ {
if (readAndParse())
{
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) while (true)
{ {
try
{
if (!parse(buffer))
return false;
int read = endPoint.fill(buffer); 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); LOG.debug("Read {} bytes from {}", read, endPoint);
if (read > 0) if (read > 0)
{ {
parse(buffer); if (!parse(buffer))
return false;
} }
else if (read == 0) else if (read == 0)
{ {
fillInterested(); fillInterested();
break; return true;
} }
else else
{ {
shutdown(); shutdown();
break; return true;
}
} }
} }
catch (Exception x) catch (Exception x)
{ {
LOG.debug(x); LOG.debug(x);
// TODO: fail and close ? close(x);
return false;
} }
finally
{
bufferPool.release(buffer);
} }
} }
private void parse(ByteBuffer buffer) private boolean parse(ByteBuffer buffer)
{ {
while (buffer.hasRemaining()) return !parser.parse(buffer);
parser.parse(buffer);
} }
private void shutdown() 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 @Override
@ -153,13 +182,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
protected void release(HttpChannelOverFCGI channel) protected void release(HttpChannelOverFCGI channel)
{ {
channels.remove(channel.getRequest()); channels.remove(channel.getRequest());
if (destination instanceof PoolingHttpDestination) destination.release(this);
{
@SuppressWarnings("unchecked")
PoolingHttpDestination<HttpConnectionOverFCGI> fcgiDestination =
(PoolingHttpDestination<HttpConnectionOverFCGI>)destination;
fcgiDestination.release(this);
}
} }
@Override @Override
@ -168,7 +191,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
close(new AsynchronousCloseException()); close(new AsynchronousCloseException());
} }
private void close(Throwable failure) protected void close(Throwable failure)
{ {
if (closed.compareAndSet(false, true)) 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) protected void abort(Throwable failure)
{ {
for (HttpChannelOverFCGI channel : channels.values()) for (HttpChannelOverFCGI channel : channels.values())
@ -195,6 +228,15 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
channels.clear(); 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() private int acquireRequest()
{ {
synchronized (requests) synchronized (requests)
@ -291,7 +333,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
} }
@Override @Override
public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
{ {
switch (stream) switch (stream)
{ {
@ -299,7 +341,25 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
{ {
HttpChannelOverFCGI channel = channels.get(request); HttpChannelOverFCGI channel = channels.get(request);
if (channel != null) 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 else
noChannel(request); noChannel(request);
break; break;
@ -314,6 +374,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
} }
return false;
} }
@Override @Override
@ -322,7 +383,22 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
HttpChannelOverFCGI channel = channels.get(request); HttpChannelOverFCGI channel = channels.get(request);
if (channel != null) if (channel != null)
{ {
channel.responseSuccess(); 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); releaseRequest(request);
} }
else else

View File

@ -24,6 +24,7 @@ import org.eclipse.jetty.client.HttpChannel;
import org.eclipse.jetty.client.HttpExchange; import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.client.HttpReceiver; import org.eclipse.jetty.client.HttpReceiver;
import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.util.Callback;
public class HttpReceiverOverFCGI extends HttpReceiver public class HttpReceiverOverFCGI extends HttpReceiver
{ {
@ -51,9 +52,9 @@ public class HttpReceiverOverFCGI extends HttpReceiver
} }
@Override @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 @Override

View File

@ -88,21 +88,36 @@ public class ServerGenerator extends Generator
return generateContent(request, buffer, true, false, callback, FCGI.FrameType.STDOUT); 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)
{
if (aborted)
{
Result result = new Result(byteBufferPool, callback);
if (lastContent)
result.append(generateEndRequest(request, true), true);
else
result.append(BufferUtil.EMPTY_BUFFER, false);
return result;
}
else
{ {
Result result = generateContent(request, content, false, lastContent, callback, FCGI.FrameType.STDOUT); Result result = generateContent(request, content, false, lastContent, callback, FCGI.FrameType.STDOUT);
if (lastContent) if (lastContent)
result.append(generateEndRequest(request, false), true);
return result;
}
}
private ByteBuffer generateEndRequest(int request, boolean aborted)
{ {
// Generate the FCGI_END_REQUEST
request &= 0xFF_FF; request &= 0xFF_FF;
ByteBuffer endRequestBuffer = byteBufferPool.acquire(8, false); ByteBuffer endRequestBuffer = byteBufferPool.acquire(8, false);
BufferUtil.clearToFill(endRequestBuffer); BufferUtil.clearToFill(endRequestBuffer);
endRequestBuffer.putInt(0x01_03_00_00 + request); endRequestBuffer.putInt(0x01_03_00_00 + request);
endRequestBuffer.putInt(0x00_08_00_00); endRequestBuffer.putInt(0x00_08_00_00);
endRequestBuffer.putLong(0x00L); endRequestBuffer.putInt(aborted ? 1 : 0);
endRequestBuffer.putInt(0);
endRequestBuffer.flip(); endRequestBuffer.flip();
result = result.append(endRequestBuffer, true); return endRequestBuffer;
}
return result;
} }
} }

View File

@ -37,7 +37,7 @@ public class BeginRequestContentParser extends ContentParser
} }
@Override @Override
public boolean parse(ByteBuffer buffer) public Result parse(ByteBuffer buffer)
{ {
while (buffer.hasRemaining()) while (buffer.hasRemaining())
{ {
@ -78,7 +78,7 @@ public class BeginRequestContentParser extends ContentParser
buffer.position(buffer.position() + 5); buffer.position(buffer.position() + 5);
onStart(); onStart();
reset(); reset();
return true; return Result.COMPLETE;
} }
else else
{ {
@ -94,7 +94,7 @@ public class BeginRequestContentParser extends ContentParser
{ {
onStart(); onStart();
reset(); reset();
return true; return Result.COMPLETE;
} }
break; break;
} }
@ -104,7 +104,7 @@ public class BeginRequestContentParser extends ContentParser
} }
} }
} }
return false; return Result.PENDING;
} }
private void onStart() private void onStart()

View File

@ -86,9 +86,9 @@ public class ClientParser extends Parser
} }
@Override @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 @Override
@ -98,5 +98,13 @@ public class ClientParser extends Parser
for (StreamContentParser streamParser : streamParsers) for (StreamContentParser streamParser : streamParsers)
streamParser.end(request); streamParser.end(request);
} }
@Override
public void onFailure(int request, Throwable failure)
{
listener.onFailure(request, failure);
for (StreamContentParser streamParser : streamParsers)
streamParser.end(request);
}
} }
} }

View File

@ -29,7 +29,7 @@ public abstract class ContentParser
this.headerParser = headerParser; this.headerParser = headerParser;
} }
public abstract boolean parse(ByteBuffer buffer); public abstract Result parse(ByteBuffer buffer);
public void noContent() public void noContent()
{ {
@ -45,4 +45,9 @@ public abstract class ContentParser
{ {
return headerParser.getContentLength(); return headerParser.getContentLength();
} }
public enum Result
{
PENDING, ASYNC, COMPLETE
}
} }

View File

@ -35,7 +35,7 @@ public class EndRequestContentParser extends ContentParser
} }
@Override @Override
public boolean parse(ByteBuffer buffer) public Result parse(ByteBuffer buffer)
{ {
while (buffer.hasRemaining()) while (buffer.hasRemaining())
{ {
@ -76,7 +76,7 @@ public class EndRequestContentParser extends ContentParser
buffer.position(buffer.position() + 3); buffer.position(buffer.position() + 3);
onEnd(); onEnd();
reset(); reset();
return true; return Result.COMPLETE;
} }
else else
{ {
@ -92,7 +92,7 @@ public class EndRequestContentParser extends ContentParser
{ {
onEnd(); onEnd();
reset(); reset();
return true; return Result.COMPLETE;
} }
break; break;
} }
@ -102,12 +102,16 @@ public class EndRequestContentParser extends ContentParser
} }
} }
} }
return false; return Result.PENDING;
} }
private void onEnd() private void onEnd()
{ {
// TODO: if protocol != 0, invoke an error callback 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()); listener.onEnd(getRequest());
} }

View File

@ -32,6 +32,12 @@ public class HeaderParser
private int length; private int length;
private int padding; 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) public boolean parse(ByteBuffer buffer)
{ {
while (buffer.hasRemaining()) while (buffer.hasRemaining())

View File

@ -45,7 +45,7 @@ public class ParamsContentParser extends ContentParser
} }
@Override @Override
public boolean parse(ByteBuffer buffer) public Result parse(ByteBuffer buffer)
{ {
while (buffer.hasRemaining() || state == State.PARAM) while (buffer.hasRemaining() || state == State.PARAM)
{ {
@ -185,7 +185,7 @@ public class ParamsContentParser extends ContentParser
if (length == 0) if (length == 0)
{ {
reset(); reset();
return true; return Result.COMPLETE;
} }
break; break;
} }
@ -195,7 +195,7 @@ public class ParamsContentParser extends ContentParser
} }
} }
} }
return false; return Result.PENDING;
} }
@Override @Override

View File

@ -29,7 +29,7 @@ public abstract class Parser
private State state = State.HEADER; private State state = State.HEADER;
private int padding; private int padding;
public void parse(ByteBuffer buffer) public boolean parse(ByteBuffer buffer)
{ {
while (true) while (true)
{ {
@ -38,7 +38,7 @@ public abstract class Parser
case HEADER: case HEADER:
{ {
if (!headerParser.parse(buffer)) if (!headerParser.parse(buffer))
return; return false;
state = State.CONTENT; state = State.CONTENT;
break; break;
} }
@ -51,8 +51,11 @@ public abstract class Parser
} }
else else
{ {
if (!contentParser.parse(buffer)) ContentParser.Result result = contentParser.parse(buffer);
return; if (result == ContentParser.Result.PENDING)
return false;
else if (result == ContentParser.Result.ASYNC)
return true;
} }
padding = headerParser.getPaddingLength(); padding = headerParser.getPaddingLength();
state = State.PADDING; state = State.PADDING;
@ -70,7 +73,7 @@ public abstract class Parser
{ {
padding -= buffer.remaining(); padding -= buffer.remaining();
buffer.position(buffer.limit()); buffer.position(buffer.limit());
return; return false;
} }
} }
default: default:
@ -96,10 +99,12 @@ public abstract class Parser
public void onHeaders(int request); 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 onEnd(int request);
public void onFailure(int request, Throwable failure);
public static class Adapter implements Listener public static class Adapter implements Listener
{ {
@Override @Override
@ -113,14 +118,21 @@ public abstract class Parser
} }
@Override @Override
public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
{ {
return false;
} }
@Override @Override
public void onEnd(int request) public void onEnd(int request)
{ {
} }
@Override
public void onFailure(int request, Throwable failure)
{
}
} }
} }

View File

@ -52,7 +52,7 @@ public class ResponseContentParser extends StreamContentParser
} }
@Override @Override
protected void onContent(ByteBuffer buffer) protected boolean onContent(ByteBuffer buffer)
{ {
int request = getRequest(); int request = getRequest();
ResponseParser parser = parsers.get(request); ResponseParser parser = parsers.get(request);
@ -61,7 +61,7 @@ public class ResponseContentParser extends StreamContentParser
parser = new ResponseParser(listener, request); parser = new ResponseParser(listener, request);
parsers.put(request, parser); parsers.put(request, parser);
} }
parser.parse(buffer); return parser.parse(buffer);
} }
@Override @Override
@ -87,7 +87,7 @@ public class ResponseContentParser extends StreamContentParser
this.httpParser = new FCGIHttpParser(this); 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); LOG.debug("Response {} {} content {} {}", request, FCGI.StreamType.STD_OUT, state, buffer);
@ -117,7 +117,8 @@ public class ResponseContentParser extends StreamContentParser
} }
case RAW_CONTENT: case RAW_CONTENT:
{ {
notifyContent(buffer); if (notifyContent(buffer))
return true;
remaining = 0; remaining = 0;
break; break;
} }
@ -133,6 +134,7 @@ public class ResponseContentParser extends StreamContentParser
} }
} }
} }
return false;
} }
@Override @Override
@ -253,15 +255,16 @@ public class ResponseContentParser extends StreamContentParser
return false; return false;
} }
private void notifyContent(ByteBuffer buffer) private boolean notifyContent(ByteBuffer buffer)
{ {
try try
{ {
listener.onContent(request, FCGI.StreamType.STD_OUT, buffer); return listener.onContent(request, FCGI.StreamType.STD_OUT, buffer);
} }
catch (Throwable x) catch (Throwable x)
{ {
logger.debug("Exception while invoking listener " + listener, x); logger.debug("Exception while invoking listener " + listener, x);
return false;
} }
} }

View File

@ -41,7 +41,7 @@ public class StreamContentParser extends ContentParser
} }
@Override @Override
public boolean parse(ByteBuffer buffer) public Result parse(ByteBuffer buffer)
{ {
while (buffer.hasRemaining()) while (buffer.hasRemaining())
{ {
@ -59,14 +59,15 @@ public class StreamContentParser extends ContentParser
int limit = buffer.limit(); int limit = buffer.limit();
buffer.limit(buffer.position() + length); buffer.limit(buffer.position() + length);
ByteBuffer slice = buffer.slice(); ByteBuffer slice = buffer.slice();
onContent(slice);
buffer.position(buffer.limit()); buffer.position(buffer.limit());
buffer.limit(limit); buffer.limit(limit);
contentLength -= length; contentLength -= length;
if (onContent(slice))
return Result.ASYNC;
if (contentLength > 0) if (contentLength > 0)
break; break;
state = State.LENGTH; state = State.LENGTH;
return true; return Result.COMPLETE;
} }
default: default:
{ {
@ -74,7 +75,7 @@ public class StreamContentParser extends ContentParser
} }
} }
} }
return false; return Result.PENDING;
} }
@Override @Override
@ -90,15 +91,16 @@ public class StreamContentParser extends ContentParser
} }
} }
protected void onContent(ByteBuffer buffer) protected boolean onContent(ByteBuffer buffer)
{ {
try try
{ {
listener.onContent(getRequest(), streamType, buffer); return listener.onContent(getRequest(), streamType, buffer);
} }
catch (Throwable x) catch (Throwable x)
{ {
logger.debug("Exception while invoking listener " + listener, x); logger.debug("Exception while invoking listener " + listener, x);
return false;
} }
} }

View File

@ -158,10 +158,11 @@ public class ClientGeneratorTest
ServerParser parser = new ServerParser(new ServerParser.Listener.Adapter() ServerParser parser = new ServerParser(new ServerParser.Listener.Adapter()
{ {
@Override @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(id, request);
totalLength.addAndGet(buffer.remaining()); totalLength.addAndGet(buffer.remaining());
return false;
} }
@Override @Override

View File

@ -111,16 +111,17 @@ public class ClientParserTest
ByteBufferPool byteBufferPool = new MappedByteBufferPool(); ByteBufferPool byteBufferPool = new MappedByteBufferPool();
ServerGenerator generator = new ServerGenerator(byteBufferPool); ServerGenerator generator = new ServerGenerator(byteBufferPool);
Generator.Result result1 = generator.generateResponseHeaders(id, 200, "OK", fields, null); 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(); final AtomicInteger verifier = new AtomicInteger();
ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter() ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter()
{ {
@Override @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(id, request);
verifier.addAndGet(2); verifier.addAndGet(2);
return false;
} }
@Override @Override
@ -162,17 +163,18 @@ public class ClientParserTest
ByteBufferPool byteBufferPool = new MappedByteBufferPool(); ByteBufferPool byteBufferPool = new MappedByteBufferPool();
ServerGenerator generator = new ServerGenerator(byteBufferPool); ServerGenerator generator = new ServerGenerator(byteBufferPool);
Generator.Result result1 = generator.generateResponseHeaders(id, code, "OK", fields, null); 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(); final AtomicInteger verifier = new AtomicInteger();
ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter() ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter()
{ {
@Override @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(id, request);
Assert.assertEquals(contentLength, buffer.remaining()); Assert.assertEquals(contentLength, buffer.remaining());
verifier.addAndGet(2); verifier.addAndGet(2);
return false;
} }
@Override @Override
@ -214,17 +216,18 @@ public class ClientParserTest
ByteBufferPool byteBufferPool = new MappedByteBufferPool(); ByteBufferPool byteBufferPool = new MappedByteBufferPool();
ServerGenerator generator = new ServerGenerator(byteBufferPool); ServerGenerator generator = new ServerGenerator(byteBufferPool);
Generator.Result result1 = generator.generateResponseHeaders(id, code, "OK", fields, null); 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 AtomicInteger totalLength = new AtomicInteger();
final AtomicBoolean verifier = new AtomicBoolean(); final AtomicBoolean verifier = new AtomicBoolean();
ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter() ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter()
{ {
@Override @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(id, request);
totalLength.addAndGet(buffer.remaining()); totalLength.addAndGet(buffer.remaining());
return false;
} }
@Override @Override

View File

@ -1,70 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>fcgi-parent</artifactId>
<groupId>org.eclipse.jetty.fcgi</groupId>
<version>9.2.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>fcgi-distribution</artifactId>
<packaging>pom</packaging>
<name>Jetty :: FastCGI :: Distribution</name>
<properties>
<distribution-directory>${project.build.directory}/distribution</distribution-directory>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-jars</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<includeGroupIds>org.eclipse.jetty.fcgi</includeGroupIds>
<excludeArtifactIds>fcgi-server</excludeArtifactIds>
<includeTypes>jar</includeTypes>
<outputDirectory>${distribution-directory}/lib/fcgi</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>assemble</id>
<phase>package</phase>
<goals>
<goal>assembly</goal>
</goals>
<configuration>
<finalName>jetty-fcgi-${project.version}</finalName>
<descriptors>
<descriptor>src/main/assembly/distribution.xml</descriptor>
</descriptors>
<tarLongFileMode>gnu</tarLongFileMode>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty.fcgi</groupId>
<artifactId>fcgi-proxy</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
<id>distribution</id>
<formats>
<format>tar.gz</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>${project.basedir}/src/main/config/modules</directory>
<outputDirectory>/modules</outputDirectory>
<includes>
<include>*.mod</include>
</includes>
</fileSet>
<fileSet>
<directory>${distribution-directory}</directory>
<outputDirectory>/</outputDirectory>
<includes>
<include>lib/**</include>
</includes>
</fileSet>
</fileSets>
</assembly>

View File

@ -1,68 +0,0 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
<Configure class="org.eclipse.jetty.servlet.ServletContextHandler">
<New id="root" class="java.lang.String">
<Arg>/var/www/wordpress-3.7.1</Arg>
</New>
<Set name="contextPath">/wp</Set>
<Set name="resourceBase"><Ref refid="root" /></Set>
<Set name="welcomeFiles">
<Array type="String"><Item>index.php</Item></Array>
</Set>
<Call name="addFilter">
<Arg>org.eclipse.jetty.fcgi.proxy.TryFilesFilter</Arg>
<Arg>/*</Arg>
<Arg>
<Call name="of" class="java.util.EnumSet">
<Arg><Get name="REQUEST" class="javax.servlet.DispatcherType" /></Arg>
</Call>
</Arg>
<Call name="setInitParameter">
<Arg>files</Arg>
<Arg>$path /index.php?p=$path</Arg>
</Call>
</Call>
<Call name="addServlet">
<Arg>
<New class="org.eclipse.jetty.servlet.ServletHolder">
<Arg>default</Arg>
<Arg>
<Call name="forName" class="java.lang.Class">
<Arg>org.eclipse.jetty.servlet.DefaultServlet</Arg>
</Call>
</Arg>
<Call name="setInitParameter">
<Arg>dirAllowed</Arg>
<Arg>false</Arg>
</Call>
</New>
</Arg>
<Arg>/</Arg>
</Call>
<Call name="addServlet">
<Arg>org.eclipse.jetty.fcgi.proxy.FastCGIProxyServlet</Arg>
<Arg>*.php</Arg>
<Call name="setInitParameter">
<Arg>proxyTo</Arg>
<Arg>http://localhost:9000</Arg>
</Call>
<Call name="setInitParameter">
<Arg>prefix</Arg>
<Arg>/</Arg>
</Call>
<Call name="setInitParameter">
<Arg>scriptRoot</Arg>
<Arg><Ref refid="root" /></Arg>
</Call>
<Call name="setInitParameter">
<Arg>scriptPattern</Arg>
<Arg>(.+?\\.php)</Arg>
</Call>
</Call>
</Configure>

View File

@ -1,43 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.eclipse.jetty.fcgi</groupId>
<artifactId>fcgi-parent</artifactId>
<version>9.2.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>fcgi-http-client-transport</artifactId>
<name>Jetty :: FastCGI :: HTTP Client Transport</name>
<properties>
<bundle-symbolic-name>${project.groupId}.client.http</bundle-symbolic-name>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty.fcgi</groupId>
<artifactId>fcgi-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.fcgi</groupId>
<artifactId>fcgi-server</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -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

View File

@ -1,55 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>fcgi-parent</artifactId>
<groupId>org.eclipse.jetty.fcgi</groupId>
<version>9.2.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>fcgi-proxy</artifactId>
<name>Jetty :: FastCGI :: Proxy</name>
<properties>
<bundle-symbolic-name>${project.groupId}.proxy</bundle-symbolic-name>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.fcgi</groupId>
<artifactId>fcgi-http-client-transport</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-proxy</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.spdy</groupId>
<artifactId>spdy-http-server</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -24,6 +24,8 @@ import org.eclipse.jetty.fcgi.generator.Flusher;
import org.eclipse.jetty.fcgi.generator.Generator; import org.eclipse.jetty.fcgi.generator.Generator;
import org.eclipse.jetty.fcgi.generator.ServerGenerator; import org.eclipse.jetty.fcgi.generator.ServerGenerator;
import org.eclipse.jetty.http.HttpGenerator; 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.io.ByteBufferPool;
import org.eclipse.jetty.server.HttpTransport; import org.eclipse.jetty.server.HttpTransport;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
@ -35,6 +37,8 @@ public class HttpTransportOverFCGI implements HttpTransport
private final Flusher flusher; private final Flusher flusher;
private final int request; private final int request;
private volatile boolean head; private volatile boolean head;
private volatile boolean shutdown;
private volatile boolean aborted;
public HttpTransportOverFCGI(ByteBufferPool byteBufferPool, Flusher flusher, int request) 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) public void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback)
{ {
boolean head = this.head = info.isHead(); boolean head = this.head = info.isHead();
boolean shutdown = this.shutdown = info.getHttpFields().contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString());
if (head) if (head)
{ {
if (lastContent) if (lastContent)
{ {
Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(), Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(),
info.getHttpFields(), new Callback.Adapter()); 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); flusher.flush(headersResult, contentResult);
} }
else else
@ -67,9 +73,12 @@ public class HttpTransportOverFCGI implements HttpTransport
{ {
Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(), Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(),
info.getHttpFields(), new Callback.Adapter()); 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); flusher.flush(headersResult, contentResult);
} }
if (lastContent && shutdown)
flusher.shutdown();
} }
@Override @Override
@ -79,7 +88,7 @@ public class HttpTransportOverFCGI implements HttpTransport
{ {
if (lastContent) 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); flusher.flush(result);
} }
else else
@ -90,18 +99,22 @@ public class HttpTransportOverFCGI implements HttpTransport
} }
else else
{ {
Generator.Result result = generator.generateResponseContent(request, content, lastContent, callback); Generator.Result result = generator.generateResponseContent(request, content, lastContent, aborted, callback);
flusher.flush(result); flusher.flush(result);
} }
if (lastContent && shutdown)
flusher.shutdown();
}
@Override
public void abort()
{
aborted = true;
} }
@Override @Override
public void completed() public void completed()
{ {
} }
@Override
public void abort()
{
}
} }

View File

@ -151,7 +151,7 @@ public class ServerFCGIConnection extends AbstractConnection
} }
@Override @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); HttpChannelOverFCGI channel = channels.get(request);
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
@ -161,6 +161,7 @@ public class ServerFCGIConnection extends AbstractConnection
if (channel.content(buffer)) if (channel.content(buffer))
channel.dispatch(); channel.dispatch();
} }
return false;
} }
@Override @Override
@ -175,5 +176,17 @@ public class ServerFCGIConnection extends AbstractConnection
channel.dispatch(); 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());
}
}
} }
} }

View File

@ -32,6 +32,7 @@ import org.eclipse.jetty.fcgi.FCGI;
import org.eclipse.jetty.fcgi.client.http.HttpClientTransportOverFCGI; import org.eclipse.jetty.fcgi.client.http.HttpClientTransportOverFCGI;
import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.proxy.AsyncProxyServlet;
import org.eclipse.jetty.proxy.ProxyServlet; import org.eclipse.jetty.proxy.ProxyServlet;
/** /**
@ -53,14 +54,18 @@ import org.eclipse.jetty.proxy.ProxyServlet;
* <li>the FastCGI SCRIPT_NAME parameter</li> * <li>the FastCGI SCRIPT_NAME parameter</li>
* <li>the FastCGI PATH_INFO parameter</li> * <li>the FastCGI PATH_INFO parameter</li>
* </ul></li> * </ul></li>
* <li><code>fastCGI.HTTPS</code>, optional, defaults to false, that specifies whether
* to force the FastCGI <code>HTTPS</code> parameter to the value <code>on</code></li>
* </ul> * </ul>
* *
* @see TryFilesFilter * @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_ROOT_INIT_PARAM = "scriptRoot";
public static final String SCRIPT_PATTERN_INIT_PARAM = "scriptPattern"; 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_ADDR_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".remoteAddr";
private static final String REMOTE_PORT_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".remotePort"; private static final String REMOTE_PORT_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".remotePort";
private static final String SERVER_NAME_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".serverName"; 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 static final String REQUEST_URI_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".requestURI";
private Pattern scriptPattern; private Pattern scriptPattern;
private boolean fcgiHTTPS;
@Override @Override
public void init() throws ServletException public void init() throws ServletException
@ -80,6 +86,8 @@ public class FastCGIProxyServlet extends ProxyServlet.Transparent
if (value == null) if (value == null)
value = "(.+?\\.php)"; value = "(.+?\\.php)";
scriptPattern = Pattern.compile(value); scriptPattern = Pattern.compile(value);
fcgiHTTPS = Boolean.parseBoolean(getInitParameter(FASTCGI_HTTPS_INIT_PARAM));
} }
@Override @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_ADDR, (String)proxyRequest.getAttributes().get(SERVER_ADDR_ATTRIBUTE));
fastCGIHeaders.put(FCGI.Headers.SERVER_PORT, (String)proxyRequest.getAttributes().get(SERVER_PORT_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"); fastCGIHeaders.put(FCGI.Headers.HTTPS, "on");
URI proxyRequestURI = proxyRequest.getURI(); URI proxyRequestURI = proxyRequest.getURI();

View File

@ -24,11 +24,13 @@ import java.net.URI;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Arrays; import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.GZIPOutputStream; import java.util.zip.GZIPOutputStream;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream; 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.Response;
import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.BytesContentProvider; 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.http.HttpMethod;
import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.IO; import org.eclipse.jetty.toolchain.test.IO;
import org.eclipse.jetty.toolchain.test.annotation.Slow; import org.eclipse.jetty.toolchain.test.annotation.Slow;
import org.eclipse.jetty.util.Callback;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
@ -551,4 +556,148 @@ public class HttpClientTest extends AbstractHttpClientServerTest
Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS)); 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<Callback> callbackRef = new AtomicReference<>();
final AtomicReference<CountDownLatch> 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());
}
} }

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -14,10 +14,7 @@
<modules> <modules>
<module>fcgi-client</module> <module>fcgi-client</module>
<!--<module>fcgi-http-client-transport</module>-->
<module>fcgi-server</module> <module>fcgi-server</module>
<!--<module>fcgi-proxy</module>-->
<!--<module>fcgi-distribution</module>-->
</modules> </modules>
<dependencies> <dependencies>

View File

@ -22,6 +22,7 @@ import java.io.IOException;
import java.nio.BufferOverflowException; import java.nio.BufferOverflowException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import org.eclipse.jetty.http.HttpTokens.EndOfContent; import org.eclipse.jetty.http.HttpTokens.EndOfContent;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
@ -916,10 +917,8 @@ public class HttpGenerator
line[versionLength+6+reason.length()]=HttpTokens.LINE_FEED; line[versionLength+6+reason.length()]=HttpTokens.LINE_FEED;
__preprepared[i] = new PreparedResponse(); __preprepared[i] = new PreparedResponse();
__preprepared[i]._reason=new byte[line.length-versionLength-7] ; __preprepared[i]._schemeCode = Arrays.copyOfRange(line, 0,versionLength+5);
System.arraycopy(line,versionLength+5,__preprepared[i]._reason,0,line.length-versionLength-7); __preprepared[i]._reason = Arrays.copyOfRange(line, versionLength+5, line.length-2);
__preprepared[i]._schemeCode=new byte[versionLength+5];
System.arraycopy(line,0,__preprepared[i]._schemeCode,0,versionLength+5);
__preprepared[i]._responseLine=line; __preprepared[i]._responseLine=line;
} }
} }
@ -1091,8 +1090,7 @@ public class HttpGenerator
{ {
super(header,value); super(header,value);
int cbl=header.getBytesColonSpace().length; int cbl=header.getBytesColonSpace().length;
_bytes=new byte[cbl+value.length()+2]; _bytes=Arrays.copyOf(header.getBytesColonSpace(), cbl+value.length()+2);
System.arraycopy(header.getBytesColonSpace(),0,_bytes,0,cbl);
System.arraycopy(value.getBytes(StandardCharsets.ISO_8859_1),0,_bytes,cbl,value.length()); System.arraycopy(value.getBytes(StandardCharsets.ISO_8859_1),0,_bytes,cbl,value.length());
_bytes[_bytes.length-2]=(byte)'\r'; _bytes[_bytes.length-2]=(byte)'\r';
_bytes[_bytes.length-1]=(byte)'\n'; _bytes[_bytes.length-1]=(byte)'\n';

View File

@ -58,7 +58,7 @@ import org.eclipse.jetty.util.log.Logger;
* For performance, the parse is heavily dependent on the * For performance, the parse is heavily dependent on the
* {@link Trie#getBest(ByteBuffer, int, int)} method to look ahead in a * {@link Trie#getBest(ByteBuffer, int, int)} method to look ahead in a
* single pass for both the structure ( : and CRLF ) and semantic (which * 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 * is used to lookup common combinations of headers and values
* (eg. "Connection: close"), or just header names (eg. "Connection:" ). * (eg. "Connection: close"), or just header names (eg. "Connection:" ).
* For headers who's value is not known statically (eg. Host, COOKIE) then a * 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"}) 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)); CACHE.put(new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type+"; charset="+charset));
} }
} }
@ -813,7 +813,7 @@ public class HttpParser
{ {
if (_valueString.endsWith(HttpHeaderValue.CHUNKED.toString())) if (_valueString.endsWith(HttpHeaderValue.CHUNKED.toString()))
_endOfContent=EndOfContent.CHUNKED_CONTENT; _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"); throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad chunking");
} }
@ -872,7 +872,7 @@ public class HttpParser
case CONNECTION: case CONNECTION:
// Don't cache if not persistent // Don't cache if not persistent
if (_valueString!=null && _valueString.indexOf("close")>=0) if (_valueString!=null && _valueString.contains("close"))
{ {
_closed=true; _closed=true;
_connectionFields=null; _connectionFields=null;
@ -1223,8 +1223,6 @@ public class HttpParser
LOG.debug("parseNext s={} {}",_state,BufferUtil.toDetailString(buffer)); LOG.debug("parseNext s={} {}",_state,BufferUtil.toDetailString(buffer));
try try
{ {
boolean handle=false;
// Start a request/response // Start a request/response
if (_state==State.START) if (_state==State.START)
{ {
@ -1233,28 +1231,39 @@ public class HttpParser
_methodString=null; _methodString=null;
_endOfContent=EndOfContent.UNKNOWN_CONTENT; _endOfContent=EndOfContent.UNKNOWN_CONTENT;
_header=null; _header=null;
handle=quickStart(buffer); if (quickStart(buffer))
return true;
} }
// Request/response line // Request/response line
if (!handle && _state.ordinal()>= State.START.ordinal() && _state.ordinal()<State.HEADER.ordinal()) if (_state.ordinal()>= State.START.ordinal() && _state.ordinal()<State.HEADER.ordinal())
handle=parseLine(buffer); {
if (parseLine(buffer))
return true;
}
// parse headers // parse headers
if (!handle && _state.ordinal()>= State.HEADER.ordinal() && _state.ordinal()<State.CONTENT.ordinal()) if (_state.ordinal()>= State.HEADER.ordinal() && _state.ordinal()<State.CONTENT.ordinal())
handle=parseHeaders(buffer); {
if (parseHeaders(buffer))
return true;
}
// parse content // parse content
if (!handle && _state.ordinal()>= State.CONTENT.ordinal() && _state.ordinal()<State.END.ordinal()) if (_state.ordinal()>= State.CONTENT.ordinal() && _state.ordinal()<State.END.ordinal())
{ {
// Handle HEAD response // Handle HEAD response
if (_responseStatus>0 && _headResponse) if (_responseStatus>0 && _headResponse)
{ {
setState(State.END); setState(State.END);
handle=_handler.messageComplete(); if (_handler.messageComplete())
return true;
} }
else else
handle=parseContent(buffer); {
if (parseContent(buffer))
return true;
}
} }
// handle end states // handle end states
@ -1288,8 +1297,8 @@ public class HttpParser
break; break;
case START: case START:
_handler.earlyEOF();
setState(State.CLOSED); setState(State.CLOSED);
_handler.earlyEOF();
break; break;
case END: case END:
@ -1297,29 +1306,28 @@ public class HttpParser
break; break;
case EOF_CONTENT: case EOF_CONTENT:
handle=_handler.messageComplete()||handle;
setState(State.CLOSED); setState(State.CLOSED);
break; return _handler.messageComplete();
case CONTENT: case CONTENT:
case CHUNKED_CONTENT: case CHUNKED_CONTENT:
case CHUNK_SIZE: case CHUNK_SIZE:
case CHUNK_PARAMS: case CHUNK_PARAMS:
case CHUNK: case CHUNK:
_handler.earlyEOF();
setState(State.CLOSED); setState(State.CLOSED);
_handler.earlyEOF();
break; break;
default: default:
if (DEBUG) if (DEBUG)
LOG.debug("{} EOF in {}",this,_state); LOG.debug("{} EOF in {}",this,_state);
_handler.badMessage(400,null);
setState(State.CLOSED); setState(State.CLOSED);
_handler.badMessage(400,null);
break; break;
} }
} }
return handle; return false;
} }
catch(BadMessage e) catch(BadMessage e)
{ {
@ -1357,24 +1365,36 @@ public class HttpParser
protected boolean parseContent(ByteBuffer buffer) 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 // Handle _content
byte ch; byte ch;
while (_state.ordinal() < State.END.ordinal() && buffer.hasRemaining()) while (_state.ordinal() < State.END.ordinal() && remaining>0)
{ {
switch (_state) switch (_state)
{ {
case EOF_CONTENT: case EOF_CONTENT:
_contentChunk=buffer.asReadOnlyBuffer(); _contentChunk=buffer.asReadOnlyBuffer();
_contentPosition += _contentChunk.remaining(); _contentPosition += remaining;
buffer.position(buffer.position()+_contentChunk.remaining()); buffer.position(buffer.position()+remaining);
if (_handler.content(_contentChunk)) if (_handler.content(_contentChunk))
return true; return true;
break; break;
case CONTENT: case CONTENT:
{ {
long remaining=_contentLength - _contentPosition; long content=_contentLength - _contentPosition;
if (remaining == 0) if (content == 0)
{ {
setState(State.END); setState(State.END);
if (_handler.messageComplete()) if (_handler.messageComplete())
@ -1385,25 +1405,25 @@ public class HttpParser
_contentChunk=buffer.asReadOnlyBuffer(); _contentChunk=buffer.asReadOnlyBuffer();
// limit content by expected size // 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 // We can cast remaining to an int as we know that it is smaller than
// or equal to length which is already an int. // or equal to length which is already an int.
_contentChunk.limit(_contentChunk.position()+(int)remaining); _contentChunk.limit(_contentChunk.position()+(int)content);
} }
_contentPosition += _contentChunk.remaining(); _contentPosition += _contentChunk.remaining();
buffer.position(buffer.position()+_contentChunk.remaining()); buffer.position(buffer.position()+_contentChunk.remaining());
boolean handle=_handler.content(_contentChunk); if (_handler.content(_contentChunk))
return true;
if(_contentPosition == _contentLength) if(_contentPosition == _contentLength)
{ {
setState(State.END); setState(State.END);
if (_handler.messageComplete()) if (_handler.messageComplete())
return true; return true;
} }
if (handle)
return true;
} }
break; break;
} }
@ -1463,8 +1483,8 @@ public class HttpParser
case CHUNK: case CHUNK:
{ {
int remaining=_chunkLength - _chunkPosition; int chunk=_chunkLength - _chunkPosition;
if (remaining == 0) if (chunk == 0)
{ {
setState(State.CHUNKED_CONTENT); setState(State.CHUNKED_CONTENT);
} }
@ -1472,13 +1492,13 @@ public class HttpParser
{ {
_contentChunk=buffer.asReadOnlyBuffer(); _contentChunk=buffer.asReadOnlyBuffer();
if (_contentChunk.remaining() > remaining) if (remaining > chunk)
_contentChunk.limit(_contentChunk.position()+remaining); _contentChunk.limit(_contentChunk.position()+chunk);
remaining=_contentChunk.remaining(); chunk=_contentChunk.remaining();
_contentPosition += remaining; _contentPosition += chunk;
_chunkPosition += remaining; _chunkPosition += chunk;
buffer.position(buffer.position()+remaining); buffer.position(buffer.position()+chunk);
if (_handler.content(_contentChunk)) if (_handler.content(_contentChunk))
return true; return true;
} }
@ -1493,7 +1513,10 @@ public class HttpParser
default: default:
break; break;
} }
remaining=buffer.remaining();
} }
return false; return false;
} }

View File

@ -44,6 +44,7 @@ public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint
return AbstractEndPoint.this.needsFill(); return AbstractEndPoint.this.needsFill();
} }
}; };
private final WriteFlusher _writeFlusher = new WriteFlusher(this) private final WriteFlusher _writeFlusher = new WriteFlusher(this)
{ {
@Override @Override
@ -144,10 +145,20 @@ public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint
{ {
boolean output_shutdown=isOutputShutdown(); boolean output_shutdown=isOutputShutdown();
boolean input_shutdown=isInputShutdown(); boolean input_shutdown=isInputShutdown();
_fillInterest.onFail(timeout); boolean fillFailed = _fillInterest.onFail(timeout);
_writeFlusher.onFail(timeout); boolean writeFailed = _writeFlusher.onFail(timeout);
if (isOpen() && output_shutdown || input_shutdown)
// 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(); close();
else
LOG.debug("Ignored idle endpoint {}",this);
} }
@Override @Override

View File

@ -93,12 +93,17 @@ public abstract class FillInterest
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** Call to signal a failure to a registered interest /** 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(); Callback callback=_interested.get();
if (callback!=null && _interested.compareAndSet(callback,null)) if (callback!=null && _interested.compareAndSet(callback,null))
{
callback.failed(cause); callback.failed(cause);
return true;
}
return false;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */

View File

@ -19,11 +19,13 @@
package org.eclipse.jetty.io; package org.eclipse.jetty.io;
import java.io.IOException; import java.io.IOException;
import java.net.Socket;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey; import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel; import java.nio.channels.SocketChannel;
import java.util.List; import java.util.List;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.Scheduler; import org.eclipse.jetty.util.thread.Scheduler;
@ -57,9 +59,11 @@ public class NetworkTrafficSelectChannelEndPoint extends SelectChannelEndPoint
if (b.hasRemaining()) if (b.hasRemaining())
{ {
int position = b.position(); int position = b.position();
ByteBuffer view=b.slice();
flushed&=super.flush(b); flushed&=super.flush(b);
int l=b.position()-position; int l=b.position()-position;
notifyOutgoing(b, position, l); view.limit(view.position()+l);
notifyOutgoing(view);
if (!flushed) if (!flushed)
break; break;
} }
@ -68,8 +72,11 @@ public class NetworkTrafficSelectChannelEndPoint extends SelectChannelEndPoint
} }
public void notifyOpened()
@Override
public void onOpen()
{ {
super.onOpen();
if (listeners != null && !listeners.isEmpty()) if (listeners != null && !listeners.isEmpty())
{ {
for (NetworkTrafficListener listener : listeners) 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) public void notifyIncoming(ByteBuffer buffer, int read)
{ {
if (listeners != null && !listeners.isEmpty() && read > 0) 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) for (NetworkTrafficListener listener : listeners)
{ {
try try
{ {
ByteBuffer view = buffer.slice(); listener.outgoing(socket, view);
view.position(position);
view.limit(position + written);
listener.outgoing(getSocket(), view);
} }
catch (Exception x) 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);
}
}
}
}
} }

View File

@ -22,6 +22,7 @@ import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException; import java.nio.channels.ClosedChannelException;
import java.nio.channels.WritePendingException; import java.nio.channels.WritePendingException;
import java.util.Arrays;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.Set; import java.util.Set;
@ -253,10 +254,14 @@ abstract public class WriteFlusher
return _buffers; return _buffers;
} }
protected void fail(Throwable cause) protected boolean fail(Throwable cause)
{ {
if (_callback!=null) if (_callback!=null)
{
_callback.failed(cause); _callback.failed(cause);
return true;
}
return false;
} }
protected void complete() protected void complete()
@ -297,10 +302,7 @@ abstract public class WriteFlusher
if (consumed == length) if (consumed == length)
return EMPTY_BUFFERS; return EMPTY_BUFFERS;
int newLength = length - consumed; return Arrays.copyOfRange(buffers,consumed,length);
ByteBuffer[] result = new ByteBuffer[newLength];
System.arraycopy(buffers, consumed, result, 0, newLength);
return result;
} }
} }
@ -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 // Keep trying to handle the failure until we get to IDLE or FAILED state
while(true) while(true)
@ -442,7 +449,7 @@ abstract public class WriteFlusher
case FAILED: case FAILED:
if (DEBUG) if (DEBUG)
LOG.debug("ignored: {} {}", this, cause); LOG.debug("ignored: {} {}", this, cause);
return; return false;
case PENDING: case PENDING:
if (DEBUG) if (DEBUG)
@ -450,10 +457,7 @@ abstract public class WriteFlusher
PendingState pending = (PendingState)current; PendingState pending = (PendingState)current;
if (updateState(pending,__IDLE)) if (updateState(pending,__IDLE))
{ return pending.fail(cause);
pending.fail(cause);
return;
}
break; break;
default: default:
@ -461,7 +465,7 @@ abstract public class WriteFlusher
LOG.debug("failed: {} {}", this, cause); LOG.debug("failed: {} {}", this, cause);
if (updateState(current,new FailedState(cause))) if (updateState(current,new FailedState(cause)))
return; return false;
break; break;
} }
} }

View File

@ -18,8 +18,7 @@
package org.eclipse.jetty.io; package org.eclipse.jetty.io;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; 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(true,endp.flush(BufferUtil.EMPTY_BUFFER,BufferUtil.toBuffer(" and"),BufferUtil.toBuffer(" more")));
assertEquals("some output some more and more",endp.getOutputString()); assertEquals("some output some more and more",endp.getOutputString());
endp.close();
} }
@Test @Test
@ -147,6 +147,7 @@ public class ByteArrayEndPointTest
assertEquals(true,endp.flush(data)); assertEquals(true,endp.flush(data));
assertEquals("data.",BufferUtil.toString(endp.takeOutput())); assertEquals("data.",BufferUtil.toString(endp.takeOutput()));
endp.close();
} }
@ -234,6 +235,27 @@ public class ByteArrayEndPointTest
assertTrue(fcb.isDone()); assertTrue(fcb.isDone());
assertEquals(null, fcb.get()); assertEquals(null, fcb.get());
assertEquals(" more.", endp.getOutputString()); 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 @Slow
@ -275,7 +297,7 @@ public class ByteArrayEndPointTest
assertThat(t.getCause(), instanceOf(TimeoutException.class)); assertThat(t.getCause(), instanceOf(TimeoutException.class));
} }
assertThat(System.currentTimeMillis() - start, greaterThan(idleTimeout / 2)); 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. // 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 // 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(t.getCause(), instanceOf(TimeoutException.class));
} }
assertThat(System.currentTimeMillis() - start, greaterThan(idleTimeout / 2)); assertThat(System.currentTimeMillis() - start, greaterThan(idleTimeout / 2));
assertTrue(endp.isOpen()); assertThat("Endpoint open", endp.isOpen(), is(true));
// Still no idle close endp.fillInterested(new Closer(endp));
Thread.sleep(idleTimeout * 2);
assertTrue(endp.isOpen()); // Still no idle close (wait half the time)
Thread.sleep(idleTimeout / 2);
assertThat("Endpoint open", endp.isOpen(), is(true));
// shutdown out // shutdown out
endp.shutdownOutput(); endp.shutdownOutput();
// idle close // idle close (wait double the time)
Thread.sleep(idleTimeout * 2); Thread.sleep(idleTimeout * 2);
assertFalse(endp.isOpen()); assertThat("Endpoint open", endp.isOpen(), is(false));
} }
} }

View File

@ -112,6 +112,7 @@
javax.servlet.http;version="[3.1,3.2)", javax.servlet.http;version="[3.1,3.2)",
javax.transaction;version="1.1.0";resolution:=optional, javax.transaction;version="1.1.0";resolution:=optional,
javax.transaction.xa;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.eclipse.jetty.annotations;version="9.0.0";resolution:=optional,
org.osgi.framework, org.osgi.framework,
org.osgi.service.cm;version="1.2.0", org.osgi.service.cm;version="1.2.0",

View File

@ -35,6 +35,7 @@ import org.eclipse.jetty.annotations.ClassNameResolver;
import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper; import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper;
import org.eclipse.jetty.util.ConcurrentHashSet; import org.eclipse.jetty.util.ConcurrentHashSet;
import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.Resource;
import org.objectweb.asm.Opcodes;
import org.osgi.framework.Bundle; import org.osgi.framework.Bundle;
import org.osgi.framework.Constants; import org.osgi.framework.Constants;
@ -50,6 +51,14 @@ public class AnnotationParser extends org.eclipse.jetty.annotations.AnnotationPa
private ConcurrentHashMap<Resource, Bundle> _resourceToBundle = new ConcurrentHashMap<Resource, Bundle>(); private ConcurrentHashMap<Resource, Bundle> _resourceToBundle = new ConcurrentHashMap<Resource, Bundle>();
private ConcurrentHashMap<Bundle,URI> _bundleToUri = new ConcurrentHashMap<Bundle, URI>(); private ConcurrentHashMap<Bundle,URI> _bundleToUri = new ConcurrentHashMap<Bundle, URI>();
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. * Keep track of a jetty URI Resource and its associated OSGi bundle.
* @param uri * @param uri

View File

@ -28,6 +28,7 @@
<dependency> <dependency>
<groupId>org.eclipse.osgi</groupId> <groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi</artifactId> <artifactId>org.eclipse.osgi</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>javax.servlet</groupId> <groupId>javax.servlet</groupId>

View File

@ -21,10 +21,12 @@
<dependency> <dependency>
<groupId>org.eclipse.osgi</groupId> <groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi</artifactId> <artifactId>org.eclipse.osgi</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.eclipse.osgi</groupId> <groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi.services</artifactId> <artifactId>org.eclipse.osgi.services</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.eclipse.jetty.toolchain</groupId> <groupId>org.eclipse.jetty.toolchain</groupId>

View File

@ -21,10 +21,12 @@
<dependency> <dependency>
<groupId>org.eclipse.osgi</groupId> <groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi</artifactId> <artifactId>org.eclipse.osgi</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.eclipse.osgi</groupId> <groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi.services</artifactId> <artifactId>org.eclipse.osgi.services</artifactId>
<scope>provided</scope>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -14,101 +14,35 @@
<bundle-symbolic-name>${project.groupId}.boot.test.spdy</bundle-symbolic-name> <bundle-symbolic-name>${project.groupId}.boot.test.spdy</bundle-symbolic-name>
<jetty-orbit-url>http://download.eclipse.org/jetty/orbit/</jetty-orbit-url> <jetty-orbit-url>http://download.eclipse.org/jetty/orbit/</jetty-orbit-url>
<assembly-directory>target/distribution</assembly-directory> <assembly-directory>target/distribution</assembly-directory>
<exam.version>2.6.0</exam.version> <exam.version>3.4.0</exam.version>
<url.version>1.4.0</url.version> <url.version>1.5.2</url.version>
<paxswissbox.version>1.5.1</paxswissbox.version>
<felixversion>4.0.3</felixversion>
<injection.bundle.version>1.0</injection.bundle.version> <injection.bundle.version>1.0</injection.bundle.version>
<runner.version>1.7.6</runner.version> <runner.version>1.8.5</runner.version>
</properties> </properties>
<dependencies> <dependencies>
<!-- Pax Exam Dependencies --> <!-- Pax Exam Dependencies -->
<!-- OPS4J Swissbox Dependencies -->
<dependency>
<groupId>org.ops4j.pax.swissbox</groupId>
<artifactId>pax-swissbox-core</artifactId>
<version>${paxswissbox.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.swissbox</groupId>
<artifactId>pax-swissbox-extender</artifactId>
<version>${paxswissbox.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.swissbox</groupId>
<artifactId>pax-swissbox-lifecycle</artifactId>
<version>${paxswissbox.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.swissbox</groupId>
<artifactId>pax-swissbox-framework</artifactId>
<version>${paxswissbox.version}</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.ops4j.pax.exam</groupId> <groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam</artifactId> <artifactId>pax-exam</artifactId>
<version>${exam.version}</version> <version>${exam.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-atinject_1.0_spec</artifactId>
<version>${injection.bundle.version}</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.ops4j.pax.exam</groupId> <groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam-inject</artifactId> <artifactId>pax-exam-inject</artifactId>
<version>${exam.version}</version> <version>${exam.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.apache.aries.spifly</groupId>
<artifactId>org.apache.aries.spifly.dynamic.bundle</artifactId>
<version>1.0.0</version>
<scope>test</scope>
</dependency>
<!-- Don't use the native container for now. Observed limitations: <!-- use the forked container so we can pass it system properties eg for npn/alpn -->
- single test with a single configuration
- does not read the versions of the dependencies from the pom.xml
and hence hardcode the bundles versions in the source code instead
- no support for most configuration options for the OSGi container. -->
<!--dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam-container-native</artifactId>
<version>${exam.version}</version>
<scope>test</scope>
</dependency-->
<!-- container is not bad but not enough config parameters yet
can't pass the VMOption for alpn-boot
<dependency> <dependency>
<groupId>org.ops4j.pax.exam</groupId> <groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam-container-forked</artifactId> <artifactId>pax-exam-container-forked</artifactId>
<version>${exam.version}</version> <version>${exam.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
-->
<dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam-container-paxrunner</artifactId>
<version>${exam.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.runner</groupId>
<artifactId>pax-runner-no-jcl</artifactId>
<version>${runner.version}</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.ops4j.pax.exam</groupId> <groupId>org.ops4j.pax.exam</groupId>
@ -134,28 +68,81 @@
<version>${url.version}</version> <version>${url.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- OSGi R4 frameworks --> <!-- OSGi R4 frameworks -->
<!--
<dependency> <dependency>
<groupId>org.apache.felix</groupId> <groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.framework</artifactId> <artifactId>org.apache.felix.framework</artifactId>
<version>${felixversion}</version> <version>4.4.0</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.ops4j.pax.exam</groupId> <groupId>org.osgi</groupId>
<artifactId>pax-exam-testforge</artifactId> <artifactId>org.osgi.enterprise</artifactId>
<version>${exam.version}</version> <version>5.0.0</version>
<scope>test</scope>
</dependency>
<!-- For sane logging -->
<!--
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.1</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
--> -->
<dependency>
<groupId>org.eclipse</groupId>
<artifactId>osgi</artifactId>
<version>3.9.1-v20140110-1610</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi.services</artifactId>
<scope>test</scope>
</dependency>
<!-- Jetty OSGi Deps -->
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-boot</artifactId>
<version>${project.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi</artifactId>
</exclusion>
<exclusion>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi.services</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-boot-jsp</artifactId>
<version>${project.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi</artifactId>
</exclusion>
<exclusion>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi.services</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-jsp-fragment</artifactId>
<version>2.3.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-httpservice</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>javax.servlet</groupId> <groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId> <artifactId>javax.servlet-api</artifactId>
@ -167,32 +154,19 @@
<version>1.1.1</version> <version>1.1.1</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- OSGi Deps -->
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-boot</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-boot-jsp</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-jsp-fragment</artifactId>
<version>2.3.3</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-httpservice</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-atinject_1.0_spec</artifactId>
<version>${injection.bundle.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.aries.spifly</groupId>
<artifactId>org.apache.aries.spifly.dynamic.bundle</artifactId>
<version>1.0.0</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.ow2.asm</groupId> <groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId> <artifactId>asm</artifactId>
@ -386,7 +360,9 @@
<version>${project.version}</version> <version>${project.version}</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<!-- Eclipse OSGi Deps --> <!-- Eclipse OSGi Deps -->
<!--
<dependency> <dependency>
<groupId>org.eclipse.osgi</groupId> <groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi</artifactId> <artifactId>org.eclipse.osgi</artifactId>
@ -398,7 +374,6 @@
<scope>runtime</scope> <scope>runtime</scope>
<exclusions> <exclusions>
<exclusion> <exclusion>
<!-- we use the servlet jar from orbit -->
<groupId>javax.servlet</groupId> <groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId> <artifactId>servlet-api</artifactId>
</exclusion> </exclusion>
@ -409,6 +384,9 @@
<artifactId>servlet</artifactId> <artifactId>servlet</artifactId>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
-->
<dependency> <dependency>
<groupId>org.eclipse.jetty</groupId> <groupId>org.eclipse.jetty</groupId>
<artifactId>test-jetty-webapp</artifactId> <artifactId>test-jetty-webapp</artifactId>

View File

@ -37,12 +37,13 @@ import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.osgi.boot.OSGiServerConstants; import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandler;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.ops4j.pax.exam.CoreOptions; import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.Option; import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.Configuration; import org.ops4j.pax.exam.Configuration;
import org.ops4j.pax.exam.junit.JUnit4TestRunner; import org.ops4j.pax.exam.junit.PaxExam;
import org.osgi.framework.Bundle; import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext; import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference; import org.osgi.framework.ServiceReference;
@ -55,12 +56,11 @@ import org.osgi.framework.ServiceReference;
* Tests the ServiceContextProvider. * Tests the ServiceContextProvider.
* *
*/ */
@RunWith(JUnit4TestRunner.class) @RunWith(PaxExam.class)
public class TestJettyOSGiBootContextAsService public class TestJettyOSGiBootContextAsService
{ {
private static final boolean LOGGING_ENABLED = false; private static final String LOG_LEVEL = "WARN";
private static final boolean REMOTE_DEBUGGING = false;
@Inject @Inject
BundleContext bundleContext = null; BundleContext bundleContext = null;
@ -69,7 +69,6 @@ public class TestJettyOSGiBootContextAsService
public static Option[] configure() public static Option[] configure()
{ {
ArrayList<Option> options = new ArrayList<Option>(); ArrayList<Option> options = new ArrayList<Option>();
TestOSGiUtil.addMoreOSGiContainers(options);
options.add(CoreOptions.junitBundles()); options.add(CoreOptions.junitBundles());
options.addAll(configureJettyHomeAndPort("jetty-selector.xml")); options.addAll(configureJettyHomeAndPort("jetty-selector.xml"));
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*")); options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*"));
@ -79,21 +78,9 @@ public class TestJettyOSGiBootContextAsService
// to pick up and deploy // to pick up and deploy
options.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("test-jetty-osgi-context").versionAsInProject().start()); options.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("test-jetty-osgi-context").versionAsInProject().start());
String logLevel = "WARN"; options.addAll(Arrays.asList(options(systemProperty("pax.exam.logging").value("none"))));
// Enable Logging options.addAll(Arrays.asList(options(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL))));
if (LOGGING_ENABLED) options.addAll(Arrays.asList(options(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL))));
logLevel = "INFO";
options.addAll(Arrays.asList(options(
// install log service using pax runners profile abstraction (there
// are more profiles, like DS)
// logProfile(),
// this is how you set the default log level when using pax logging
// (logProfile)
systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(logLevel),
systemProperty("org.eclipse.jetty.LEVEL").value(logLevel))));
return options.toArray(new Option[options.size()]); return options.toArray(new Option[options.size()]);
} }
@ -117,6 +104,7 @@ public class TestJettyOSGiBootContextAsService
return options; return options;
} }
@Ignore
@Test @Test
public void assertAllBundlesActiveOrResolved() public void assertAllBundlesActiveOrResolved()
{ {
@ -148,14 +136,6 @@ public class TestJettyOSGiBootContextAsService
ServiceReference[] refs = bundleContext.getServiceReferences(ContextHandler.class.getName(), null); ServiceReference[] refs = bundleContext.getServiceReferences(ContextHandler.class.getName(), null);
assertNotNull(refs); assertNotNull(refs);
assertEquals(1, refs.length); assertEquals(1, refs.length);
//uncomment for debugging
/*
String[] keys = refs[0].getPropertyKeys();
if (keys != null)
{
for (String k : keys)
System.err.println("service property: " + k + ", " + refs[0].getProperty(k));
}*/
ContextHandler ch = (ContextHandler) bundleContext.getService(refs[0]); ContextHandler ch = (ContextHandler) bundleContext.getService(refs[0]);
assertEquals("/acme", ch.getContextPath()); assertEquals("/acme", ch.getContextPath());

View File

@ -19,19 +19,23 @@
package org.eclipse.jetty.osgi.test; package org.eclipse.jetty.osgi.test;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle; import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.options;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.ops4j.pax.exam.CoreOptions; import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.MavenUtils; import org.ops4j.pax.exam.MavenUtils;
import org.ops4j.pax.exam.Option; import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.Configuration; import org.ops4j.pax.exam.Configuration;
import org.ops4j.pax.exam.junit.JUnit4TestRunner; import org.ops4j.pax.exam.junit.PaxExam;
import org.ops4j.pax.exam.options.MavenUrlReference.VersionResolver; import org.ops4j.pax.exam.options.MavenUrlReference.VersionResolver;
import org.osgi.framework.Bundle; import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext; import org.osgi.framework.BundleContext;
@ -40,10 +44,10 @@ import org.osgi.framework.BundleContext;
/** /**
* Default OSGi setup integration test * Default OSGi setup integration test
*/ */
@RunWith( JUnit4TestRunner.class ) @RunWith( PaxExam.class )
public class TestJettyOSGiBootCore public class TestJettyOSGiBootCore
{ {
private static final String LOG_LEVEL = "WARN";
public static int DEFAULT_JETTY_HTTP_PORT = 9876; public static int DEFAULT_JETTY_HTTP_PORT = 9876;
@Inject @Inject
@ -54,10 +58,13 @@ public class TestJettyOSGiBootCore
{ {
VersionResolver resolver = MavenUtils.asInProject(); VersionResolver resolver = MavenUtils.asInProject();
ArrayList<Option> options = new ArrayList<Option>(); ArrayList<Option> options = new ArrayList<Option>();
TestOSGiUtil.addMoreOSGiContainers(options);
options.addAll(provisionCoreJetty()); options.addAll(provisionCoreJetty());
options.add(CoreOptions.junitBundles()); options.add(CoreOptions.junitBundles());
options.addAll(httpServiceJetty()); options.addAll(httpServiceJetty());
options.addAll(Arrays.asList(options(systemProperty("pax.exam.logging").value("none"))));
options.addAll(Arrays.asList(options(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL))));
options.addAll(Arrays.asList(options(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL))));
return options.toArray(new Option[options.size()]); return options.toArray(new Option[options.size()]);
} }
@ -75,11 +82,20 @@ public class TestJettyOSGiBootCore
public static List<Option> coreJettyDependencies() public static List<Option> coreJettyDependencies()
{ {
List<Option> res = new ArrayList<Option>(); List<Option> res = new ArrayList<Option>();
String jdk = System.getProperty("java.version");
int firstdot = jdk.indexOf(".");
jdk = jdk.substring(0,firstdot+2);
double version = Double.parseDouble(jdk);
if (version < 1.8)
{
res.add(mavenBundle().groupId( "org.ow2.asm" ).artifactId( "asm" ).versionAsInProject().start()); res.add(mavenBundle().groupId( "org.ow2.asm" ).artifactId( "asm" ).versionAsInProject().start());
res.add(mavenBundle().groupId( "org.ow2.asm" ).artifactId( "asm-commons" ).versionAsInProject().start()); res.add(mavenBundle().groupId( "org.ow2.asm" ).artifactId( "asm-commons" ).versionAsInProject().start());
res.add(mavenBundle().groupId( "org.ow2.asm" ).artifactId( "asm-tree" ).versionAsInProject().start()); res.add(mavenBundle().groupId( "org.ow2.asm" ).artifactId( "asm-tree" ).versionAsInProject().start());
res.add(mavenBundle().groupId( "org.apache.aries" ).artifactId( "org.apache.aries.util" ).version("1.0.0").start()); res.add(mavenBundle().groupId( "org.apache.aries" ).artifactId( "org.apache.aries.util" ).version("1.0.0").start());
res.add(mavenBundle().groupId( "org.apache.aries.spifly" ).artifactId( "org.apache.aries.spifly.dynamic.bundle" ).version("1.0.0").start()); res.add(mavenBundle().groupId( "org.apache.aries.spifly" ).artifactId( "org.apache.aries.spifly.dynamic.bundle" ).version("1.0.0").start());
}
res.add(mavenBundle().groupId( "javax.servlet" ).artifactId( "javax.servlet-api" ).versionAsInProject().noStart()); res.add(mavenBundle().groupId( "javax.servlet" ).artifactId( "javax.servlet-api" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "javax.annotation" ).artifactId( "javax.annotation-api" ).versionAsInProject().noStart()); res.add(mavenBundle().groupId( "javax.annotation" ).artifactId( "javax.annotation-api" ).versionAsInProject().noStart());
@ -103,7 +119,10 @@ public class TestJettyOSGiBootCore
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-client" ).versionAsInProject().noStart()); res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-client" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-jndi" ).versionAsInProject().noStart()); res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-jndi" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-plus" ).versionAsInProject().noStart()); res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-plus" ).versionAsInProject().noStart());
if (version < 1.8)
{
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-annotations" ).versionAsInProject().start()); res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-annotations" ).versionAsInProject().start());
}
res.add(mavenBundle().groupId( "org.eclipse.jetty.websocket" ).artifactId( "websocket-api" ).versionAsInProject().noStart()); res.add(mavenBundle().groupId( "org.eclipse.jetty.websocket" ).artifactId( "websocket-api" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty.websocket" ).artifactId( "websocket-common" ).versionAsInProject().noStart()); res.add(mavenBundle().groupId( "org.eclipse.jetty.websocket" ).artifactId( "websocket-common" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty.websocket" ).artifactId( "websocket-servlet" ).versionAsInProject().noStart()); res.add(mavenBundle().groupId( "org.eclipse.jetty.websocket" ).artifactId( "websocket-servlet" ).versionAsInProject().noStart());
@ -124,9 +143,12 @@ public class TestJettyOSGiBootCore
return res; return res;
} }
@Ignore
@Test @Test
public void assertAllBundlesActiveOrResolved() throws Exception public void assertAllBundlesActiveOrResolved() throws Exception
{ {
TestOSGiUtil.debugBundles(bundleContext);
TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext); TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext);
} }

View File

@ -36,18 +36,18 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.ops4j.pax.exam.CoreOptions; import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.Option; import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.Configuration; import org.ops4j.pax.exam.Configuration;
import org.ops4j.pax.exam.junit.JUnit4TestRunner; import org.ops4j.pax.exam.junit.PaxExam;
import org.osgi.framework.Bundle; import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext; import org.osgi.framework.BundleContext;
/** /**
* SPDY setup. * SPDY setup.
*/ */
@RunWith(JUnit4TestRunner.class) @RunWith(PaxExam.class)
public class TestJettyOSGiBootSpdy public class TestJettyOSGiBootSpdy
{ {
private static final boolean LOGGING_ENABLED = false; private static final String LOG_LEVEL = "WARN";
private static final String JETTY_SPDY_PORT = "jetty.spdy.port"; private static final String JETTY_SPDY_PORT = "jetty.spdy.port";
@ -60,31 +60,14 @@ public class TestJettyOSGiBootSpdy
public Option[] config() public Option[] config()
{ {
ArrayList<Option> options = new ArrayList<Option>(); ArrayList<Option> options = new ArrayList<Option>();
TestOSGiUtil.addMoreOSGiContainers(options);
options.addAll(TestJettyOSGiBootWithJsp.configureJettyHomeAndPort("jetty-spdy.xml")); options.addAll(TestJettyOSGiBootWithJsp.configureJettyHomeAndPort("jetty-spdy.xml"));
options.addAll(TestJettyOSGiBootCore.coreJettyDependencies()); options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
options.addAll(spdyJettyDependencies()); options.addAll(spdyJettyDependencies());
options.add(CoreOptions.junitBundles()); options.add(CoreOptions.junitBundles());
options.addAll(TestJettyOSGiBootCore.httpServiceJetty()); options.addAll(TestJettyOSGiBootCore.httpServiceJetty());
options.addAll(Arrays.asList(options(systemProperty("pax.exam.logging").value("none"))));
String logLevel = "WARN"; options.addAll(Arrays.asList(options(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL))));
options.addAll(Arrays.asList(options(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL))));
// Enable Logging
if (LOGGING_ENABLED)
logLevel = "INFO";
options.addAll(Arrays.asList(options(
// install log service using pax runners profile abstraction (there
// are more profiles, like DS)
// logProfile(),
// this is how you set the default log level when using pax logging
// (logProfile)
systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(logLevel),
systemProperty("org.eclipse.jetty.LEVEL").value(logLevel))));
return options.toArray(new Option[options.size()]); return options.toArray(new Option[options.size()]);
} }
@ -101,7 +84,7 @@ public class TestJettyOSGiBootSpdy
res.add(CoreOptions.vmOptions("-Xbootclasspath/p:" + alpnBoot)); res.add(CoreOptions.vmOptions("-Xbootclasspath/p:" + alpnBoot));
res.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("jetty-osgi-alpn").versionAsInProject().start()); res.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("jetty-osgi-alpn").versionAsInProject().noStart());
res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-server").versionAsInProject().start()); res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-server").versionAsInProject().start());
res.add(mavenBundle().groupId("org.eclipse.jetty.spdy").artifactId("spdy-client").versionAsInProject().noStart()); res.add(mavenBundle().groupId("org.eclipse.jetty.spdy").artifactId("spdy-client").versionAsInProject().noStart());
@ -112,7 +95,6 @@ public class TestJettyOSGiBootSpdy
return res; return res;
} }
@Ignore
@Test @Test
public void checkALPNBootOnBootstrapClasspath() throws Exception public void checkALPNBootOnBootstrapClasspath() throws Exception
{ {
@ -121,13 +103,11 @@ public class TestJettyOSGiBootSpdy
Assert.assertNull(alpn.getClassLoader()); Assert.assertNull(alpn.getClassLoader());
} }
@Ignore
@Test @Test
public void assertAllBundlesActiveOrResolved() public void assertAllBundlesActiveOrResolved()
{ {
Bundle b = TestOSGiUtil.getBundle(bundleContext, "org.eclipse.jetty.spdy.client"); TestOSGiUtil.debugBundles(bundleContext);
TestOSGiUtil.diagnoseNonActiveOrNonResolvedBundle(b);
b = TestOSGiUtil.getBundle(bundleContext, "org.eclipse.jetty.osgi.boot");
TestOSGiUtil.diagnoseNonActiveOrNonResolvedBundle(b);
TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext); TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext);
} }

View File

@ -38,12 +38,13 @@ import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.osgi.boot.OSGiServerConstants; import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.webapp.WebAppContext;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.ops4j.pax.exam.CoreOptions; import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.Option; import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.Configuration; import org.ops4j.pax.exam.Configuration;
import org.ops4j.pax.exam.junit.JUnit4TestRunner; import org.ops4j.pax.exam.junit.PaxExam;
import org.osgi.framework.BundleContext; import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference; import org.osgi.framework.ServiceReference;
@ -58,12 +59,10 @@ import org.osgi.framework.ServiceReference;
* httpservice web-bundle. Then make sure we can deploy an OSGi service on the * httpservice web-bundle. Then make sure we can deploy an OSGi service on the
* top of this. * top of this.
*/ */
@RunWith(JUnit4TestRunner.class) @RunWith(PaxExam.class)
public class TestJettyOSGiBootWebAppAsService public class TestJettyOSGiBootWebAppAsService
{ {
private static final boolean LOGGING_ENABLED = false; private static final String LOG_LEVEL = "WARN";
private static final boolean REMOTE_DEBUGGING = false;
@Inject @Inject
BundleContext bundleContext = null; BundleContext bundleContext = null;
@ -72,8 +71,6 @@ public class TestJettyOSGiBootWebAppAsService
public static Option[] configure() public static Option[] configure()
{ {
ArrayList<Option> options = new ArrayList<Option>(); ArrayList<Option> options = new ArrayList<Option>();
TestOSGiUtil.addMoreOSGiContainers(options);
options.add(CoreOptions.junitBundles()); options.add(CoreOptions.junitBundles());
options.addAll(configureJettyHomeAndPort("jetty-selector.xml")); options.addAll(configureJettyHomeAndPort("jetty-selector.xml"));
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*")); options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*"));
@ -82,19 +79,9 @@ public class TestJettyOSGiBootWebAppAsService
"com.sun.org.apache.xpath.internal.jaxp", "com.sun.org.apache.xpath.internal.objects")); "com.sun.org.apache.xpath.internal.jaxp", "com.sun.org.apache.xpath.internal.objects"));
options.addAll(TestJettyOSGiBootCore.coreJettyDependencies()); options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
options.addAll(Arrays.asList(options(systemProperty("pax.exam.logging").value("none"))));
String logLevel = "WARN"; options.addAll(Arrays.asList(options(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL))));
if (LOGGING_ENABLED) options.addAll(Arrays.asList(options(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL))));
logLevel = "INFO";
options.addAll(Arrays.asList(options(
// install log service using pax runners profile abstraction (there
// are more profiles, like DS)
// logProfile(),
// this is how you set the default log level when using pax logging
// (logProfile)
systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(logLevel),
systemProperty("org.eclipse.jetty.LEVEL").value(logLevel))));
options.addAll(jspDependencies()); options.addAll(jspDependencies());
return options.toArray(new Option[options.size()]); return options.toArray(new Option[options.size()]);
@ -139,6 +126,7 @@ public class TestJettyOSGiBootWebAppAsService
return res; return res;
} }
@Ignore
@Test @Test
public void assertAllBundlesActiveOrResolved() public void assertAllBundlesActiveOrResolved()
{ {

View File

@ -38,25 +38,21 @@ import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.ops4j.pax.exam.Configuration;
import org.ops4j.pax.exam.CoreOptions; import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.Option; import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.Configuration; import org.ops4j.pax.exam.junit.PaxExam;
import org.ops4j.pax.exam.junit.JUnit4TestRunner;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext; import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
/** /**
* Pax-Exam to make sure the jetty-osgi-boot can be started along with the * Pax-Exam to make sure the jetty-osgi-boot can be started along with the
* httpservice web-bundle. Then make sure we can deploy an OSGi service on the * httpservice web-bundle. Then make sure we can deploy an OSGi service on the
* top of this. * top of this.
*/ */
@RunWith(JUnit4TestRunner.class) @RunWith(PaxExam.class)
public class TestJettyOSGiBootWithJsp public class TestJettyOSGiBootWithJsp
{ {
private static final boolean LOGGING_ENABLED = false; private static final String LOG_LEVEL = "WARN";
private static final boolean REMOTE_DEBUGGING = false;
@Inject @Inject
BundleContext bundleContext = null; BundleContext bundleContext = null;
@ -66,9 +62,6 @@ public class TestJettyOSGiBootWithJsp
{ {
ArrayList<Option> options = new ArrayList<Option>(); ArrayList<Option> options = new ArrayList<Option>();
TestOSGiUtil.addMoreOSGiContainers(options);
options.add(CoreOptions.junitBundles()); options.add(CoreOptions.junitBundles());
options.addAll(configureJettyHomeAndPort("jetty-selector.xml")); options.addAll(configureJettyHomeAndPort("jetty-selector.xml"));
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*", "javax.activation.*")); options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*", "javax.activation.*"));
@ -77,39 +70,11 @@ public class TestJettyOSGiBootWithJsp
"com.sun.org.apache.xpath.internal.jaxp", "com.sun.org.apache.xpath.internal.objects")); "com.sun.org.apache.xpath.internal.jaxp", "com.sun.org.apache.xpath.internal.objects"));
options.addAll(TestJettyOSGiBootCore.coreJettyDependencies()); options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
options.addAll(Arrays.asList(options(systemProperty("pax.exam.logging").value("none"))));
String logLevel = "WARN"; options.addAll(Arrays.asList(options(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL))));
options.addAll(Arrays.asList(options(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL))));
// Enable Logging
if (LOGGING_ENABLED)
logLevel = "INFO";
options.addAll(Arrays.asList(options(
// install log service using pax runners profile abstraction (there
// are more profiles, like DS)
// logProfile(),
// this is how you set the default log level when using pax logging
// (logProfile)
systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(logLevel),
systemProperty("org.eclipse.jetty.annotations.LEVEL").value(logLevel))));
options.addAll(jspDependencies()); options.addAll(jspDependencies());
// Remote JDWP Debugging, this won't work with the forked container.
// if(REMOTE_DEBUGGING) {
// options.addAll(Arrays.asList(options(
// // this just adds all what you write here to java vm argumenents of
// the (new) osgi process.
// PaxRunnerOptions.vmOption(
// "-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5006" )
// )));
// }
// bug at the moment: this would make the httpservice catch all
// requests and prevent the webapp at the root context to catch any of
// them.
// options.addAll(TestJettyOSGiBootCore.httpServiceJetty());
return options.toArray(new Option[options.size()]); return options.toArray(new Option[options.size()]);
} }
@ -157,7 +122,7 @@ public class TestJettyOSGiBootWithJsp
return res; return res;
} }
@Ignore
@Test @Test
public void assertAllBundlesActiveOrResolved() public void assertAllBundlesActiveOrResolved()
{ {

View File

@ -33,7 +33,6 @@ import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.Assert; import org.junit.Assert;
import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.Option; import org.ops4j.pax.exam.Option;
import org.osgi.framework.Bundle; import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext; import org.osgi.framework.BundleContext;
@ -46,22 +45,6 @@ import org.osgi.service.http.HttpService;
public class TestOSGiUtil public class TestOSGiUtil
{ {
/**
* Note: this will run many more tests. TODO: find a better way to control
* this and use non-deprecated methods.
*
* @param opti
*/
protected static void addMoreOSGiContainers(List<Option> options)
{
//Uncomment to run more containers - these have been commented out
//to improve speed of builds.
//options.add(CoreOptions.equinox().version("3.6.1"));
//options.add(CoreOptions.equinox().version("3.7.0"));
// options.add(CoreOptions.felix().version("3.2.2"));
options.add(CoreOptions.felix().version("4.0.2"));
}
protected static Bundle getBundle(BundleContext bundleContext, String symbolicName) protected static Bundle getBundle(BundleContext bundleContext, String symbolicName)
{ {
Map<String,Bundle> _bundles = new HashMap<String, Bundle>(); Map<String,Bundle> _bundles = new HashMap<String, Bundle>();
@ -146,7 +129,7 @@ public class TestOSGiUtil
for (Bundle b : bundleContext.getBundles()) for (Bundle b : bundleContext.getBundles())
{ {
bundlesIndexedBySymbolicName.put(b.getSymbolicName(), b); bundlesIndexedBySymbolicName.put(b.getSymbolicName(), b);
System.err.println(" " + b.getSymbolicName() + " " + b.getState()); System.err.println(" " + b.getSymbolicName() + " " + b.getLocation() + " " + b.getVersion()+ " " + b.getState());
} }
} }

View File

@ -0,0 +1,264 @@
//
// ========================================================================
// 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.proxy;
import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.WritePendingException;
import javax.servlet.ReadListener;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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.client.util.DeferredContentProvider;
import org.eclipse.jetty.util.Callback;
public class AsyncProxyServlet extends ProxyServlet
{
private static final String WRITE_LISTENER_ATTRIBUTE = AsyncProxyServlet.class.getName() + ".writeListener";
@Override
protected ContentProvider proxyRequestContent(Request proxyRequest, HttpServletRequest request) throws IOException
{
ServletInputStream input = request.getInputStream();
DeferredContentProvider provider = new DeferredContentProvider();
input.setReadListener(new StreamReader(proxyRequest, request, provider));
return provider;
}
@Override
protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length, Callback callback)
{
try
{
_log.debug("{} proxying content to downstream: {} bytes", getRequestId(request), length);
StreamWriter writeListener = (StreamWriter)request.getAttribute(WRITE_LISTENER_ATTRIBUTE);
if (writeListener == null)
{
writeListener = new StreamWriter(request, proxyResponse);
request.setAttribute(WRITE_LISTENER_ATTRIBUTE, writeListener);
// Set the data to write before calling setWriteListener(), because
// setWriteListener() may trigger the call to onWritePossible() on
// a different thread and we would have a race.
writeListener.data(buffer, offset, length, callback);
// Setting the WriteListener triggers an invocation to onWritePossible().
response.getOutputStream().setWriteListener(writeListener);
}
else
{
writeListener.data(buffer, offset, length, callback);
writeListener.onWritePossible();
}
}
catch (Throwable x)
{
callback.failed(x);
onResponseFailure(request, response, proxyResponse, x);
}
}
public static class Transparent extends AsyncProxyServlet
{
private final TransparentDelegate delegate = new TransparentDelegate(this);
@Override
public void init(ServletConfig config) throws ServletException
{
super.init(config);
delegate.init(config);
}
@Override
protected URI rewriteURI(HttpServletRequest request)
{
return delegate.rewriteURI(request);
}
}
private class StreamReader implements ReadListener, Callback
{
private final byte[] buffer = new byte[getHttpClient().getRequestBufferSize()];
private final Request proxyRequest;
private final HttpServletRequest request;
private final DeferredContentProvider provider;
public StreamReader(Request proxyRequest, HttpServletRequest request, DeferredContentProvider provider)
{
this.proxyRequest = proxyRequest;
this.request = request;
this.provider = provider;
}
@Override
public void onDataAvailable() throws IOException
{
int requestId = getRequestId(request);
ServletInputStream input = request.getInputStream();
_log.debug("{} asynchronous read start on {}", requestId, input);
// First check for isReady() because it has
// side effects, and then for isFinished().
while (input.isReady() && !input.isFinished())
{
int read = input.read(buffer);
_log.debug("{} asynchronous read {} bytes on {}", requestId, read, input);
if (read > 0)
{
_log.debug("{} proxying content to upstream: {} bytes", requestId, read);
provider.offer(ByteBuffer.wrap(buffer, 0, read), this);
// Do not call isReady() so that we can apply backpressure.
break;
}
}
if (!input.isFinished())
_log.debug("{} asynchronous read pending on {}", requestId, input);
}
@Override
public void onAllDataRead() throws IOException
{
_log.debug("{} proxying content to upstream completed", getRequestId(request));
provider.close();
}
@Override
public void onError(Throwable x)
{
failed(x);
}
@Override
public void succeeded()
{
try
{
if (request.getInputStream().isReady())
onDataAvailable();
}
catch (Throwable x)
{
failed(x);
}
}
@Override
public void failed(Throwable x)
{
onClientRequestFailure(proxyRequest, request, x);
}
}
private class StreamWriter implements WriteListener
{
private final HttpServletRequest request;
private final Response proxyResponse;
private WriteState state;
private byte[] buffer;
private int offset;
private int length;
private Callback callback;
private StreamWriter(HttpServletRequest request, Response proxyResponse)
{
this.request = request;
this.proxyResponse = proxyResponse;
this.state = WriteState.IDLE;
}
private void data(byte[] bytes, int offset, int length, Callback callback)
{
if (state != WriteState.IDLE)
throw new WritePendingException();
this.state = WriteState.READY;
this.buffer = bytes;
this.offset = offset;
this.length = length;
this.callback = callback;
}
@Override
public void onWritePossible() throws IOException
{
int requestId = getRequestId(request);
ServletOutputStream output = request.getAsyncContext().getResponse().getOutputStream();
if (state == WriteState.READY)
{
// There is data to write.
_log.debug("{} asynchronous write start of {} bytes on {}", requestId, length, output);
output.write(buffer, offset, length);
state = WriteState.PENDING;
if (output.isReady())
{
_log.debug("{} asynchronous write of {} bytes completed on {}", requestId, length, output);
complete();
}
else
{
_log.debug("{} asynchronous write of {} bytes pending on {}", requestId, length, output);
}
}
else if (state == WriteState.PENDING)
{
// The write blocked but is now complete.
_log.debug("{} asynchronous write of {} bytes completing on {}", requestId, length, output);
complete();
}
else
{
throw new IllegalStateException();
}
}
private void complete()
{
buffer = null;
offset = 0;
length = 0;
Callback c = callback;
callback = null;
state = WriteState.IDLE;
// Call the callback only after the whole state has been reset,
// because the callback may trigger a reentrant call and
// the state must already be the new one that we reset here.
c.succeeded();
}
@Override
public void onError(Throwable failure)
{
HttpServletResponse response = (HttpServletResponse)request.getAsyncContext().getResponse();
onResponseFailure(request, response, proxyResponse, failure);
}
}
private enum WriteState
{
READY, PENDING, IDLE
}
}

View File

@ -28,7 +28,6 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import javax.servlet.AsyncContext; import javax.servlet.AsyncContext;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -432,8 +431,7 @@ public class ConnectHandler extends HandlerWrapper
protected class ConnectManager extends SelectorManager protected class ConnectManager extends SelectorManager
{ {
protected ConnectManager(Executor executor, Scheduler scheduler, int selectors)
private ConnectManager(Executor executor, Scheduler scheduler, int selectors)
{ {
super(executor, scheduler, selectors); super(executor, scheduler, selectors);
} }
@ -455,11 +453,17 @@ public class ConnectHandler extends HandlerWrapper
} }
@Override @Override
protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment) protected void connectionFailed(SocketChannel channel, final Throwable ex, final Object attachment)
{
getExecutor().execute(new Runnable()
{
public void run()
{ {
ConnectContext connectContext = (ConnectContext)attachment; ConnectContext connectContext = (ConnectContext)attachment;
onConnectFailure(connectContext.request, connectContext.response, connectContext.asyncContext, ex); onConnectFailure(connectContext.request, connectContext.response, connectContext.asyncContext, ex);
} }
});
}
} }
protected static class ConnectContext protected static class ConnectContext
@ -518,9 +522,15 @@ public class ConnectHandler extends HandlerWrapper
public void onOpen() public void onOpen()
{ {
super.onOpen(); super.onOpen();
onConnectSuccess(connectContext, this); getExecutor().execute(new Runnable()
{
public void run()
{
onConnectSuccess(connectContext, UpstreamConnection.this);
fillInterested(); fillInterested();
} }
});
}
@Override @Override
protected int read(EndPoint endPoint, ByteBuffer buffer) throws IOException protected int read(EndPoint endPoint, ByteBuffer buffer) throws IOException

View File

@ -41,14 +41,16 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.InputStreamContentProvider; import org.eclipse.jetty.client.util.InputStreamContentProvider;
import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.HttpCookieStore; import org.eclipse.jetty.util.HttpCookieStore;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
@ -79,7 +81,6 @@ import org.eclipse.jetty.util.thread.QueuedThreadPool;
*/ */
public class ProxyServlet extends HttpServlet public class ProxyServlet extends HttpServlet
{ {
protected static final String ASYNC_CONTEXT = ProxyServlet.class.getName() + ".asyncContext";
private static final Set<String> HOP_HEADERS = new HashSet<>(); private static final Set<String> HOP_HEADERS = new HashSet<>();
static static
{ {
@ -174,14 +175,21 @@ public class ProxyServlet extends HttpServlet
} }
} }
protected HttpClient getHttpClient()
{
return _client;
}
/** /**
* @return a logger instance with a name derived from this servlet's name. * @return a logger instance with a name derived from this servlet's name.
*/ */
protected Logger createLogger() protected Logger createLogger()
{ {
String name = getServletConfig().getServletName(); String servletName = getServletConfig().getServletName();
name = name.replace('-', '.'); servletName = servletName.replace('-', '.');
return Log.getLogger(name); if (!servletName.startsWith(getClass().getPackage().getName()))
servletName = getClass().getName() + "." + servletName;
return Log.getLogger(servletName);
} }
public void destroy() public void destroy()
@ -390,26 +398,25 @@ public class ProxyServlet extends HttpServlet
} }
final Request proxyRequest = _client.newRequest(rewrittenURI) final Request proxyRequest = _client.newRequest(rewrittenURI)
.method(HttpMethod.fromString(request.getMethod())) .method(request.getMethod())
.version(HttpVersion.fromString(request.getProtocol())); .version(HttpVersion.fromString(request.getProtocol()));
// Copy headers // Copy headers
boolean hasContent = false; boolean hasContent = request.getContentLength() > 0 || request.getContentType() != null;
for (Enumeration<String> headerNames = request.getHeaderNames(); headerNames.hasMoreElements();) for (Enumeration<String> headerNames = request.getHeaderNames(); headerNames.hasMoreElements();)
{ {
String headerName = headerNames.nextElement(); String headerName = headerNames.nextElement();
String lowerHeaderName = headerName.toLowerCase(Locale.ENGLISH); String lowerHeaderName = headerName.toLowerCase(Locale.ENGLISH);
// Remove hop-by-hop headers if (HttpHeader.TRANSFER_ENCODING.is(headerName))
if (HOP_HEADERS.contains(lowerHeaderName)) hasContent = true;
continue;
if (_hostHeader != null && HttpHeader.HOST.is(headerName)) if (_hostHeader != null && HttpHeader.HOST.is(headerName))
continue; continue;
if (request.getContentLength() > 0 || request.getContentType() != null || // Remove hop-by-hop headers
HttpHeader.TRANSFER_ENCODING.is(headerName)) if (HOP_HEADERS.contains(lowerHeaderName))
hasContent = true; continue;
for (Enumeration<String> headerValues = request.getHeaders(headerName); headerValues.hasMoreElements();) for (Enumeration<String> headerValues = request.getHeaders(headerName); headerValues.hasMoreElements();)
{ {
@ -427,29 +434,13 @@ public class ProxyServlet extends HttpServlet
addViaHeader(proxyRequest); addViaHeader(proxyRequest);
addXForwardedHeaders(proxyRequest, request); addXForwardedHeaders(proxyRequest, request);
if (hasContent)
{
proxyRequest.content(new InputStreamContentProvider(request.getInputStream())
{
@Override
public long getLength()
{
return request.getContentLength();
}
@Override
protected ByteBuffer onRead(byte[] buffer, int offset, int length)
{
_log.debug("{} proxying content to upstream: {} bytes", requestId, length);
return super.onRead(buffer, offset, length);
}
});
}
final AsyncContext asyncContext = request.startAsync(); final AsyncContext asyncContext = request.startAsync();
// We do not timeout the continuation, but the proxy request // We do not timeout the continuation, but the proxy request
asyncContext.setTimeout(0); asyncContext.setTimeout(0);
request.setAttribute(ASYNC_CONTEXT, asyncContext); proxyRequest.timeout(getTimeout(), TimeUnit.MILLISECONDS);
if (hasContent)
proxyRequest.content(proxyRequestContent(proxyRequest, request));
customizeProxyRequest(proxyRequest, request); customizeProxyRequest(proxyRequest, request);
@ -486,10 +477,40 @@ public class ProxyServlet extends HttpServlet
proxyRequest.getHeaders().toString().trim()); proxyRequest.getHeaders().toString().trim());
} }
proxyRequest.timeout(getTimeout(), TimeUnit.MILLISECONDS);
proxyRequest.send(new ProxyResponseListener(request, response)); proxyRequest.send(new ProxyResponseListener(request, response));
} }
protected ContentProvider proxyRequestContent(final Request proxyRequest, final HttpServletRequest request) throws IOException
{
return new InputStreamContentProvider(request.getInputStream())
{
@Override
public long getLength()
{
return request.getContentLength();
}
@Override
protected ByteBuffer onRead(byte[] buffer, int offset, int length)
{
_log.debug("{} proxying content to upstream: {} bytes", getRequestId(request), length);
return super.onRead(buffer, offset, length);
}
@Override
protected void onReadFailure(Throwable failure)
{
onClientRequestFailure(proxyRequest, request, failure);
}
};
}
protected void onClientRequestFailure(Request proxyRequest, HttpServletRequest request, Throwable failure)
{
_log.debug(getRequestId(request) + " client request failure", failure);
proxyRequest.abort(failure);
}
protected void onRewriteFailed(HttpServletRequest request, HttpServletResponse response) throws IOException protected void onRewriteFailed(HttpServletRequest request, HttpServletResponse response) throws IOException
{ {
response.sendError(HttpServletResponse.SC_FORBIDDEN); response.sendError(HttpServletResponse.SC_FORBIDDEN);
@ -510,6 +531,10 @@ public class ProxyServlet extends HttpServlet
protected void onResponseHeaders(HttpServletRequest request, HttpServletResponse response, Response proxyResponse) protected void onResponseHeaders(HttpServletRequest request, HttpServletResponse response, Response proxyResponse)
{ {
// Clear the response headers in case it comes with predefined ones.
for (String name : response.getHeaderNames())
response.setHeader(name, null);
for (HttpField field : proxyResponse.getHeaders()) for (HttpField field : proxyResponse.getHeaders())
{ {
String headerName = field.getName(); String headerName = field.getName();
@ -525,17 +550,25 @@ public class ProxyServlet extends HttpServlet
} }
} }
protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length) throws IOException protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length, Callback callback)
{
try
{ {
response.getOutputStream().write(buffer, offset, length);
_log.debug("{} proxying content to downstream: {} bytes", getRequestId(request), length); _log.debug("{} proxying content to downstream: {} bytes", getRequestId(request), length);
response.getOutputStream().write(buffer, offset, length);
callback.succeeded();
}
catch (Throwable x)
{
callback.failed(x);
}
} }
protected void onResponseSuccess(HttpServletRequest request, HttpServletResponse response, Response proxyResponse) protected void onResponseSuccess(HttpServletRequest request, HttpServletResponse response, Response proxyResponse)
{ {
AsyncContext asyncContext = (AsyncContext)request.getAttribute(ASYNC_CONTEXT);
asyncContext.complete();
_log.debug("{} proxying successful", getRequestId(request)); _log.debug("{} proxying successful", getRequestId(request));
AsyncContext asyncContext = request.getAsyncContext();
asyncContext.complete();
} }
protected void onResponseFailure(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, Throwable failure) protected void onResponseFailure(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, Throwable failure)
@ -547,10 +580,11 @@ public class ProxyServlet extends HttpServlet
response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT); response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT);
else else
response.setStatus(HttpServletResponse.SC_BAD_GATEWAY); response.setStatus(HttpServletResponse.SC_BAD_GATEWAY);
} response.setHeader(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString());
AsyncContext asyncContext = (AsyncContext)request.getAttribute(ASYNC_CONTEXT); AsyncContext asyncContext = request.getAsyncContext();
asyncContext.complete(); asyncContext.complete();
} }
}
protected int getRequestId(HttpServletRequest request) protected int getRequestId(HttpServletRequest request)
{ {
@ -597,59 +631,66 @@ public class ProxyServlet extends HttpServlet
} }
/** /**
* Transparent Proxy. * This convenience extension to {@link ProxyServlet} configures the servlet as a transparent proxy.
* <p/> * This servlet is configured with the following init parameters:
* This convenience extension to ProxyServlet configures the servlet as a transparent proxy.
* The servlet is configured with init parameters:
* <ul> * <ul>
* <li>proxyTo - a URI like http://host:80/context to which the request is proxied. * <li>proxyTo - a mandatory URI like http://host:80/context to which the request is proxied.</li>
* <li>prefix - a URI prefix that is striped from the start of the forwarded URI. * <li>prefix - an optional URI prefix that is stripped from the start of the forwarded URI.</li>
* </ul> * </ul>
* For example, if a request is received at /foo/bar and the 'proxyTo' parameter is "http://host:80/context" * <p/>
* For example, if a request is received at "/foo/bar", the 'proxyTo' parameter is "http://host:80/context"
* and the 'prefix' parameter is "/foo", then the request would be proxied to "http://host:80/context/bar". * and the 'prefix' parameter is "/foo", then the request would be proxied to "http://host:80/context/bar".
*/ */
public static class Transparent extends ProxyServlet public static class Transparent extends ProxyServlet
{ {
private final TransparentDelegate delegate = new TransparentDelegate(this);
@Override
public void init(ServletConfig config) throws ServletException
{
super.init(config);
delegate.init(config);
}
@Override
protected URI rewriteURI(HttpServletRequest request)
{
return delegate.rewriteURI(request);
}
}
protected static class TransparentDelegate
{
private final ProxyServlet proxyServlet;
private String _proxyTo; private String _proxyTo;
private String _prefix; private String _prefix;
public Transparent() protected TransparentDelegate(ProxyServlet proxyServlet)
{ {
this.proxyServlet = proxyServlet;
} }
public Transparent(String proxyTo, String prefix) protected void init(ServletConfig config) throws ServletException
{ {
_proxyTo = URI.create(proxyTo).normalize().toString(); _proxyTo = config.getInitParameter("proxyTo");
_prefix = URI.create(prefix).normalize().toString();
}
@Override
public void init() throws ServletException
{
super.init();
ServletConfig config = getServletConfig();
String prefix = config.getInitParameter("prefix");
_prefix = prefix == null ? _prefix : prefix;
// Adjust prefix value to account for context path
String contextPath = getServletContext().getContextPath();
_prefix = _prefix == null ? contextPath : (contextPath + _prefix);
String proxyTo = config.getInitParameter("proxyTo");
_proxyTo = proxyTo == null ? _proxyTo : proxyTo;
if (_proxyTo == null) if (_proxyTo == null)
throw new UnavailableException("Init parameter 'proxyTo' is required."); throw new UnavailableException("Init parameter 'proxyTo' is required.");
if (!_prefix.startsWith("/")) String prefix = config.getInitParameter("prefix");
throw new UnavailableException("Init parameter 'prefix' parameter must start with a '/'."); if (prefix != null)
{
_log.debug(config.getServletName() + " @ " + _prefix + " to " + _proxyTo); if (!prefix.startsWith("/"))
throw new UnavailableException("Init parameter 'prefix' must start with a '/'.");
_prefix = prefix;
}
// Adjust prefix value to account for context path
String contextPath = config.getServletContext().getContextPath();
_prefix = _prefix == null ? contextPath : (contextPath + _prefix);
proxyServlet._log.debug(config.getServletName() + " @ " + _prefix + " to " + _proxyTo);
} }
@Override
protected URI rewriteURI(HttpServletRequest request) protected URI rewriteURI(HttpServletRequest request)
{ {
String path = request.getRequestURI(); String path = request.getRequestURI();
@ -668,7 +709,7 @@ public class ProxyServlet extends HttpServlet
uri.append("?").append(query); uri.append("?").append(query);
URI rewrittenURI = URI.create(uri.toString()).normalize(); URI rewrittenURI = URI.create(uri.toString()).normalize();
if (!validateDestination(rewrittenURI.getHost(), rewrittenURI.getPort())) if (!proxyServlet.validateDestination(rewrittenURI.getHost(), rewrittenURI.getPort()))
return null; return null;
return rewrittenURI; return rewrittenURI;
@ -726,7 +767,7 @@ public class ProxyServlet extends HttpServlet
} }
@Override @Override
public void onContent(Response proxyResponse, ByteBuffer content) public void onContent(final Response proxyResponse, ByteBuffer content, final Callback callback)
{ {
byte[] buffer; byte[] buffer;
int offset; int offset;
@ -743,31 +784,30 @@ public class ProxyServlet extends HttpServlet
offset = 0; offset = 0;
} }
try onResponseContent(request, response, proxyResponse, buffer, offset, length, new Callback()
{ {
onResponseContent(request, response, proxyResponse, buffer, offset, length); @Override
public void succeeded()
{
callback.succeeded();
} }
catch (IOException x)
@Override
public void failed(Throwable x)
{ {
callback.failed(x);
proxyResponse.abort(x); proxyResponse.abort(x);
} }
} });
@Override
public void onSuccess(Response proxyResponse)
{
onResponseSuccess(request, response, proxyResponse);
}
@Override
public void onFailure(Response proxyResponse, Throwable failure)
{
onResponseFailure(request, response, proxyResponse, failure);
} }
@Override @Override
public void onComplete(Result result) public void onComplete(Result result)
{ {
if (result.isSucceeded())
onResponseSuccess(request, response, result.getResponse());
else
onResponseFailure(request, response, result.getResponse(), result.getFailure());
_log.debug("{} proxying complete", getRequestId(request)); _log.debug("{} proxying complete", getRequestId(request));
} }
} }

View File

@ -0,0 +1,36 @@
//
// ========================================================================
// 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.proxy;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.util.IO;
public class EchoHttpServlet extends HttpServlet
{
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
IO.copy(request.getInputStream(), response.getOutputStream());
}
}

View File

@ -0,0 +1,33 @@
//
// ========================================================================
// 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.proxy;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class EmptyHttpServlet extends HttpServlet
{
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
}
}

View File

@ -0,0 +1,381 @@
//
// ========================================================================
// 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.proxy;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
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.HttpProxy;
import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.util.BytesContentProvider;
import org.eclipse.jetty.client.util.DeferredContentProvider;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.toolchain.test.TestTracker;
import org.eclipse.jetty.toolchain.test.http.SimpleHttpParser;
import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.StdErrLog;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.After;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class ProxyServletFailureTest
{
private static final String PROXIED_HEADER = "X-Proxied";
@Parameterized.Parameters
public static Iterable<Object[]> data()
{
return Arrays.asList(new Object[][]{
{ProxyServlet.class},
{AsyncProxyServlet.class}
});
}
@Rule
public final TestTracker tracker = new TestTracker();
private HttpClient client;
private Server proxy;
private ServerConnector proxyConnector;
private ProxyServlet proxyServlet;
private Server server;
private ServerConnector serverConnector;
public ProxyServletFailureTest(Class<?> proxyServletClass) throws Exception
{
this.proxyServlet = (ProxyServlet)proxyServletClass.newInstance();
}
private void prepareProxy() throws Exception
{
prepareProxy(new HashMap<String, String>());
}
private void prepareProxy(Map<String, String> initParams) throws Exception
{
QueuedThreadPool executor = new QueuedThreadPool();
executor.setName("proxy");
proxy = new Server(executor);
proxyConnector = new ServerConnector(proxy);
proxy.addConnector(proxyConnector);
ServletContextHandler proxyCtx = new ServletContextHandler(proxy, "/", true, false);
ServletHolder proxyServletHolder = new ServletHolder(proxyServlet);
proxyServletHolder.setInitParameters(initParams);
proxyCtx.addServlet(proxyServletHolder, "/*");
proxy.start();
client = prepareClient();
}
private HttpClient prepareClient() throws Exception
{
HttpClient result = new HttpClient();
QueuedThreadPool executor = new QueuedThreadPool();
executor.setName("client");
result.setExecutor(executor);
result.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyConnector.getLocalPort()));
result.start();
return result;
}
private void prepareServer(HttpServlet servlet) throws Exception
{
QueuedThreadPool executor = new QueuedThreadPool();
executor.setName("server");
server = new Server(executor);
serverConnector = new ServerConnector(server);
server.addConnector(serverConnector);
ServletContextHandler appCtx = new ServletContextHandler(server, "/", true, false);
ServletHolder appServletHolder = new ServletHolder(servlet);
appCtx.addServlet(appServletHolder, "/*");
server.start();
}
@After
public void disposeProxy() throws Exception
{
client.stop();
proxy.stop();
}
@After
public void disposeServer() throws Exception
{
server.stop();
}
@Test
public void testClientRequestStallsHeadersProxyIdlesTimeout() throws Exception
{
prepareProxy();
int idleTimeout = 2000;
proxyConnector.setIdleTimeout(idleTimeout);
prepareServer(new EchoHttpServlet());
try (Socket socket = new Socket("localhost", proxyConnector.getLocalPort()))
{
String serverHostPort = "localhost:" + serverConnector.getLocalPort();
String request = "" +
"GET http://" + serverHostPort + " HTTP/1.1\r\n" +
"Host: " + serverHostPort + "\r\n";
// Don't sent the \r\n that would signal the end of the headers.
OutputStream output = socket.getOutputStream();
output.write(request.getBytes("UTF-8"));
output.flush();
// Wait for idle timeout to fire.
socket.setSoTimeout(2 * idleTimeout);
InputStream input = socket.getInputStream();
Assert.assertEquals(-1, input.read());
}
}
@Test
public void testClientRequestStallsContentProxyIdlesTimeout() throws Exception
{
prepareProxy();
int idleTimeout = 2000;
proxyConnector.setIdleTimeout(idleTimeout);
prepareServer(new EchoHttpServlet());
try (Socket socket = new Socket("localhost", proxyConnector.getLocalPort()))
{
String serverHostPort = "localhost:" + serverConnector.getLocalPort();
String request = "" +
"GET http://" + serverHostPort + " HTTP/1.1\r\n" +
"Host: " + serverHostPort + "\r\n" +
"Content-Length: 1\r\n" +
"\r\n";
OutputStream output = socket.getOutputStream();
output.write(request.getBytes("UTF-8"));
output.flush();
// Do not send the promised content, wait to idle timeout.
socket.setSoTimeout(2 * idleTimeout);
SimpleHttpParser parser = new SimpleHttpParser();
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
SimpleHttpResponse response = parser.readResponse(reader);
Assert.assertTrue(Integer.parseInt(response.getCode()) >= 500);
String connectionHeader = response.getHeaders().get("connection");
Assert.assertNotNull(connectionHeader);
Assert.assertTrue(connectionHeader.contains("close"));
Assert.assertEquals(-1, reader.read());
}
}
@Test
public void testProxyRequestStallsContentServerIdlesTimeout() throws Exception
{
final byte[] content = new byte[]{'C', '0', 'F', 'F', 'E', 'E'};
if (proxyServlet instanceof AsyncProxyServlet)
{
proxyServlet = new AsyncProxyServlet()
{
@Override
protected ContentProvider proxyRequestContent(Request proxyRequest, HttpServletRequest request) throws IOException
{
return new DeferredContentProvider()
{
@Override
public boolean offer(ByteBuffer buffer, Callback callback)
{
// Ignore all content to trigger the test condition.
return true;
}
};
}
};
}
else
{
proxyServlet = new ProxyServlet()
{
@Override
protected ContentProvider proxyRequestContent(Request proxyRequest, HttpServletRequest request) throws IOException
{
return new BytesContentProvider(content)
{
@Override
public long getLength()
{
// Increase the content length to trigger the test condition.
return content.length + 1;
}
};
}
};
}
prepareProxy();
prepareServer(new EchoHttpServlet());
long idleTimeout = 1000;
serverConnector.setIdleTimeout(idleTimeout);
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.content(new BytesContentProvider(content))
.send();
Assert.assertEquals(500, response.getStatus());
}
@Test(expected = TimeoutException.class)
public void testClientRequestExpires() throws Exception
{
prepareProxy();
final long timeout = 1000;
proxyServlet.setTimeout(3 * timeout);
prepareServer(new HttpServlet()
{
@Override
protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
{
try
{
TimeUnit.MILLISECONDS.sleep(2 * timeout);
}
catch (InterruptedException x)
{
throw new ServletException(x);
}
}
});
client.newRequest("localhost", serverConnector.getLocalPort())
.timeout(timeout, TimeUnit.MILLISECONDS)
.send();
Assert.fail();
}
@Test
public void testProxyRequestExpired() throws Exception
{
prepareProxy();
final long timeout = 1000;
proxyServlet.setTimeout(timeout);
prepareServer(new HttpServlet()
{
@Override
protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
{
if (request.getHeader("Via") != null)
response.addHeader(PROXIED_HEADER, "true");
try
{
TimeUnit.MILLISECONDS.sleep(2 * timeout);
}
catch (InterruptedException x)
{
throw new ServletException(x);
}
}
});
Response response = client.newRequest("localhost", serverConnector.getLocalPort())
.timeout(3 * timeout, TimeUnit.MILLISECONDS)
.send();
Assert.assertEquals(504, response.getStatus());
Assert.assertFalse(response.getHeaders().containsKey(PROXIED_HEADER));
}
@Test
public void testServerDown() throws Exception
{
prepareProxy();
prepareServer(new EmptyHttpServlet());
// Shutdown the server
int serverPort = serverConnector.getLocalPort();
server.stop();
ContentResponse response = client.newRequest("localhost", serverPort)
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(502, response.getStatus());
}
@Test
public void testServerException() throws Exception
{
((StdErrLog)Log.getLogger(ServletHandler.class)).setHideStacks(true);
try
{
prepareProxy();
prepareServer(new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
throw new ServletException("Expected Test Exception");
}
});
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(500, response.getStatus());
}
finally
{
((StdErrLog)Log.getLogger(ServletHandler.class)).setHideStacks(false);
}
}
private interface Function<T, R>
{
public R apply(T arg);
}
}

View File

@ -18,8 +18,6 @@
package org.eclipse.jetty.proxy; package org.eclipse.jetty.proxy;
import static java.nio.file.StandardOpenOption.CREATE;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -30,6 +28,8 @@ import java.net.HttpCookie;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -39,7 +39,6 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.zip.GZIPOutputStream; import java.util.zip.GZIPOutputStream;
import javax.servlet.AsyncContext; import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent; import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener; import javax.servlet.AsyncListener;
@ -62,15 +61,12 @@ import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.toolchain.test.AdvancedRunner;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.TestTracker; import org.eclipse.jetty.toolchain.test.TestTracker;
import org.eclipse.jetty.toolchain.test.annotation.Slow; import org.eclipse.jetty.toolchain.test.annotation.Slow;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.StdErrLog;
import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.After; import org.junit.After;
@ -78,11 +74,22 @@ import org.junit.Assert;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(AdvancedRunner.class) @RunWith(Parameterized.class)
public class ProxyServletTest public class ProxyServletTest
{ {
private static final String PROXIED_HEADER = "X-Proxied"; private static final String PROXIED_HEADER = "X-Proxied";
@Parameterized.Parameters
public static Iterable<Object[]> data()
{
return Arrays.asList(new Object[][]{
{ProxyServlet.class},
{AsyncProxyServlet.class}
});
}
@Rule @Rule
public final TestTracker tracker = new TestTracker(); public final TestTracker tracker = new TestTracker();
private HttpClient client; private HttpClient client;
@ -92,15 +99,25 @@ public class ProxyServletTest
private Server server; private Server server;
private ServerConnector serverConnector; private ServerConnector serverConnector;
private void prepareProxy(ProxyServlet proxyServlet) throws Exception public ProxyServletTest(Class<?> proxyServletClass) throws Exception
{
this.proxyServlet = (ProxyServlet)proxyServletClass.newInstance();
}
private void prepareProxy() throws Exception
{
prepareProxy(new HashMap<String, String>());
}
private void prepareProxy(Map<String, String> initParams) throws Exception
{ {
proxy = new Server(); proxy = new Server();
proxyConnector = new ServerConnector(proxy); proxyConnector = new ServerConnector(proxy);
proxy.addConnector(proxyConnector); proxy.addConnector(proxyConnector);
ServletContextHandler proxyCtx = new ServletContextHandler(proxy, "/", true, false); ServletContextHandler proxyCtx = new ServletContextHandler(proxy, "/", true, false);
this.proxyServlet = proxyServlet;
ServletHolder proxyServletHolder = new ServletHolder(proxyServlet); ServletHolder proxyServletHolder = new ServletHolder(proxyServlet);
proxyServletHolder.setInitParameters(initParams);
proxyCtx.addServlet(proxyServletHolder, "/*"); proxyCtx.addServlet(proxyServletHolder, "/*");
proxy.start(); proxy.start();
@ -145,7 +162,7 @@ public class ProxyServletTest
@Test @Test
public void testProxyDown() throws Exception public void testProxyDown() throws Exception
{ {
prepareProxy(new ProxyServlet()); prepareProxy();
prepareServer(new EmptyHttpServlet()); prepareServer(new EmptyHttpServlet());
// Shutdown the proxy // Shutdown the proxy
@ -164,55 +181,10 @@ public class ProxyServletTest
} }
} }
@Test
public void testServerDown() throws Exception
{
prepareProxy(new ProxyServlet());
prepareServer(new EmptyHttpServlet());
// Shutdown the server
int serverPort = serverConnector.getLocalPort();
server.stop();
ContentResponse response = client.newRequest("localhost", serverPort)
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(502, response.getStatus());
}
@Test
public void testServerException() throws Exception
{
((StdErrLog)Log.getLogger(ServletHandler.class)).setHideStacks(true);
try
{
prepareProxy(new ProxyServlet());
prepareServer(new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
throw new ServletException("Expected Test Exception");
}
});
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(500, response.getStatus());
}
finally
{
((StdErrLog)Log.getLogger(ServletHandler.class)).setHideStacks(false);
}
}
@Test @Test
public void testProxyWithoutContent() throws Exception public void testProxyWithoutContent() throws Exception
{ {
prepareProxy(new ProxyServlet()); prepareProxy();
prepareServer(new HttpServlet() prepareServer(new HttpServlet()
{ {
@Override @Override
@ -234,7 +206,7 @@ public class ProxyServletTest
@Test @Test
public void testProxyWithResponseContent() throws Exception public void testProxyWithResponseContent() throws Exception
{ {
prepareProxy(new ProxyServlet()); prepareProxy();
HttpClient result = new HttpClient(); HttpClient result = new HttpClient();
result.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyConnector.getLocalPort())); result.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyConnector.getLocalPort()));
@ -259,7 +231,7 @@ public class ProxyServletTest
} }
}); });
for ( int i = 0; i < 10; ++i ) for (int i = 0; i < 10; ++i)
{ {
// Request is for the target server // Request is for the target server
responses[i] = result.newRequest("localhost", serverConnector.getLocalPort()) responses[i] = result.newRequest("localhost", serverConnector.getLocalPort())
@ -268,7 +240,7 @@ public class ProxyServletTest
} }
for ( int i = 0; i < 10; ++i ) for (int i = 0; i < 10; ++i)
{ {
Assert.assertEquals(200, responses[i].getStatus()); Assert.assertEquals(200, responses[i].getStatus());
Assert.assertTrue(responses[i].getHeaders().containsKey(PROXIED_HEADER)); Assert.assertTrue(responses[i].getHeaders().containsKey(PROXIED_HEADER));
@ -279,7 +251,7 @@ public class ProxyServletTest
@Test @Test
public void testProxyWithRequestContentAndResponseContent() throws Exception public void testProxyWithRequestContentAndResponseContent() throws Exception
{ {
prepareProxy(new ProxyServlet()); prepareProxy();
prepareServer(new HttpServlet() prepareServer(new HttpServlet()
{ {
@Override @Override
@ -307,7 +279,7 @@ public class ProxyServletTest
@Test @Test
public void testProxyWithBigRequestContentIgnored() throws Exception public void testProxyWithBigRequestContentIgnored() throws Exception
{ {
prepareProxy(new ProxyServlet()); prepareProxy();
prepareServer(new HttpServlet() prepareServer(new HttpServlet()
{ {
@Override @Override
@ -335,7 +307,7 @@ public class ProxyServletTest
final byte[] content = new byte[128 * 1024]; final byte[] content = new byte[128 * 1024];
new Random().nextBytes(content); new Random().nextBytes(content);
prepareProxy(new ProxyServlet()); prepareProxy();
prepareServer(new HttpServlet() prepareServer(new HttpServlet()
{ {
@Override @Override
@ -359,7 +331,7 @@ public class ProxyServletTest
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort()) ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.method(HttpMethod.POST) .method(HttpMethod.POST)
.content(new BytesContentProvider(content)) .content(new BytesContentProvider(content))
.timeout(5, TimeUnit.SECONDS) .timeout(555, TimeUnit.SECONDS)
.send(); .send();
Assert.assertEquals(200, response.getStatus()); Assert.assertEquals(200, response.getStatus());
@ -370,7 +342,7 @@ public class ProxyServletTest
@Test @Test
public void testProxyWithBigResponseContentWithSlowReader() throws Exception public void testProxyWithBigResponseContentWithSlowReader() throws Exception
{ {
prepareProxy(new ProxyServlet()); prepareProxy();
// Create a 6 MiB file // Create a 6 MiB file
final int length = 6 * 1024; final int length = 6 * 1024;
@ -379,7 +351,7 @@ public class ProxyServletTest
final Path temp = Files.createTempFile(targetTestsDir, "test_", null); final Path temp = Files.createTempFile(targetTestsDir, "test_", null);
byte[] kb = new byte[1024]; byte[] kb = new byte[1024];
new Random().nextBytes(kb); new Random().nextBytes(kb);
try (OutputStream output = Files.newOutputStream(temp, CREATE)) try (OutputStream output = Files.newOutputStream(temp, StandardOpenOption.CREATE))
{ {
for (int i = 0; i < length; ++i) for (int i = 0; i < length; ++i)
output.write(kb); output.write(kb);
@ -431,7 +403,7 @@ public class ProxyServletTest
@Test @Test
public void testProxyWithQueryString() throws Exception public void testProxyWithQueryString() throws Exception
{ {
prepareProxy(new ProxyServlet()); prepareProxy();
String query = "a=1&b=%E2%82%AC"; String query = "a=1&b=%E2%82%AC";
prepareServer(new HttpServlet() prepareServer(new HttpServlet()
{ {
@ -453,7 +425,7 @@ public class ProxyServletTest
@Test @Test
public void testProxyLongPoll() throws Exception public void testProxyLongPoll() throws Exception
{ {
prepareProxy(new ProxyServlet()); prepareProxy();
final long timeout = 1000; final long timeout = 1000;
prepareServer(new HttpServlet() prepareServer(new HttpServlet()
{ {
@ -500,73 +472,10 @@ public class ProxyServletTest
Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER)); Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
} }
@Slow
@Test
public void testProxyRequestExpired() throws Exception
{
prepareProxy(new ProxyServlet());
final long timeout = 1000;
proxyServlet.setTimeout(timeout);
prepareServer(new HttpServlet()
{
@Override
protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
{
if (request.getHeader("Via") != null)
response.addHeader(PROXIED_HEADER, "true");
try
{
TimeUnit.MILLISECONDS.sleep(2 * timeout);
}
catch (InterruptedException x)
{
throw new ServletException(x);
}
}
});
Response response = client.newRequest("localhost", serverConnector.getLocalPort())
.timeout(3 * timeout, TimeUnit.MILLISECONDS)
.send();
Assert.assertEquals(504, response.getStatus());
Assert.assertFalse(response.getHeaders().containsKey(PROXIED_HEADER));
}
@Slow
@Test(expected = TimeoutException.class)
public void testClientRequestExpired() throws Exception
{
prepareProxy(new ProxyServlet());
final long timeout = 1000;
proxyServlet.setTimeout(3 * timeout);
prepareServer(new HttpServlet()
{
@Override
protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
{
if (request.getHeader("Via") != null)
response.addHeader(PROXIED_HEADER, "true");
try
{
TimeUnit.MILLISECONDS.sleep(2 * timeout);
}
catch (InterruptedException x)
{
throw new ServletException(x);
}
}
});
client.newRequest("localhost", serverConnector.getLocalPort())
.timeout(timeout, TimeUnit.MILLISECONDS)
.send();
Assert.fail();
}
@Test @Test
public void testProxyXForwardedHostHeaderIsPresent() throws Exception public void testProxyXForwardedHostHeaderIsPresent() throws Exception
{ {
prepareProxy(new ProxyServlet()); prepareProxy();
prepareServer(new HttpServlet() prepareServer(new HttpServlet()
{ {
@Override @Override
@ -587,7 +496,7 @@ public class ProxyServletTest
@Test @Test
public void testProxyWhiteList() throws Exception public void testProxyWhiteList() throws Exception
{ {
prepareProxy(new ProxyServlet()); prepareProxy();
prepareServer(new EmptyHttpServlet()); prepareServer(new EmptyHttpServlet());
int port = serverConnector.getLocalPort(); int port = serverConnector.getLocalPort();
proxyServlet.getWhiteListHosts().add("127.0.0.1:" + port); proxyServlet.getWhiteListHosts().add("127.0.0.1:" + port);
@ -608,7 +517,7 @@ public class ProxyServletTest
@Test @Test
public void testProxyBlackList() throws Exception public void testProxyBlackList() throws Exception
{ {
prepareProxy(new ProxyServlet()); prepareProxy();
prepareServer(new EmptyHttpServlet()); prepareServer(new EmptyHttpServlet());
int port = serverConnector.getLocalPort(); int port = serverConnector.getLocalPort();
proxyServlet.getBlackListHosts().add("localhost:" + port); proxyServlet.getBlackListHosts().add("localhost:" + port);
@ -629,7 +538,7 @@ public class ProxyServletTest
@Test @Test
public void testClientExcludedHosts() throws Exception public void testClientExcludedHosts() throws Exception
{ {
prepareProxy(new ProxyServlet()); prepareProxy();
prepareServer(new HttpServlet() prepareServer(new HttpServlet()
{ {
@Override @Override
@ -684,8 +593,11 @@ public class ProxyServletTest
}); });
String proxyTo = "http://localhost:" + serverConnector.getLocalPort(); String proxyTo = "http://localhost:" + serverConnector.getLocalPort();
ProxyServlet.Transparent proxyServlet = new ProxyServlet.Transparent(proxyTo, prefix); proxyServlet = new ProxyServlet.Transparent();
prepareProxy(proxyServlet); Map<String, String> params = new HashMap<>();
params.put("proxyTo", proxyTo);
params.put("prefix", prefix);
prepareProxy(params);
// Make the request to the proxy, it should transparently forward to the server // Make the request to the proxy, it should transparently forward to the server
ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort()) ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort())
@ -723,8 +635,11 @@ public class ProxyServletTest
String proxyTo = "http://localhost:" + serverConnector.getLocalPort(); String proxyTo = "http://localhost:" + serverConnector.getLocalPort();
String prefix = "/proxy"; String prefix = "/proxy";
ProxyServlet.Transparent proxyServlet = new ProxyServlet.Transparent(proxyTo, prefix); proxyServlet = new ProxyServlet.Transparent();
prepareProxy(proxyServlet); Map<String, String> params = new HashMap<>();
params.put("proxyTo", proxyTo);
params.put("prefix", prefix);
prepareProxy(params);
// Make the request to the proxy, it should transparently forward to the server // Make the request to the proxy, it should transparently forward to the server
ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort()) ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort())
@ -735,6 +650,36 @@ public class ProxyServletTest
Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER)); Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
} }
@Test
public void testTransparentProxyWithoutPrefix() throws Exception
{
final String target = "/test";
prepareServer(new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
if (req.getHeader("Via") != null)
resp.addHeader(PROXIED_HEADER, "true");
resp.setStatus(target.equals(req.getRequestURI()) ? 200 : 404);
}
});
final String proxyTo = "http://localhost:" + serverConnector.getLocalPort();
proxyServlet = new ProxyServlet.Transparent();
Map<String, String> initParams = new HashMap<>();
initParams.put("proxyTo", proxyTo);
prepareProxy(initParams);
// Make the request to the proxy, it should transparently forward to the server
ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort())
.path(target)
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(200, response.getStatus());
Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
}
@Test @Test
public void testCachingProxy() throws Exception public void testCachingProxy() throws Exception
{ {
@ -754,7 +699,7 @@ public class ProxyServletTest
// it is only used for this test and to verify that ProxyServlet can be // it is only used for this test and to verify that ProxyServlet can be
// subclassed enough to write your own caching servlet // subclassed enough to write your own caching servlet
final String cacheHeader = "X-Cached"; final String cacheHeader = "X-Cached";
ProxyServlet proxyServlet = new ProxyServlet() proxyServlet = new ProxyServlet()
{ {
private Map<String, ContentResponse> cache = new HashMap<>(); private Map<String, ContentResponse> cache = new HashMap<>();
private Map<String, ByteArrayOutputStream> temp = new HashMap<>(); private Map<String, ByteArrayOutputStream> temp = new HashMap<>();
@ -777,7 +722,7 @@ public class ProxyServletTest
} }
@Override @Override
protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length) throws IOException protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length, Callback callback)
{ {
// Accumulate the response content // Accumulate the response content
ByteArrayOutputStream baos = temp.get(request.getRequestURI()); ByteArrayOutputStream baos = temp.get(request.getRequestURI());
@ -787,7 +732,7 @@ public class ProxyServletTest
temp.put(request.getRequestURI(), baos); temp.put(request.getRequestURI(), baos);
} }
baos.write(buffer, offset, length); baos.write(buffer, offset, length);
super.onResponseContent(request, response, proxyResponse, buffer, offset, length); super.onResponseContent(request, response, proxyResponse, buffer, offset, length, callback);
} }
@Override @Override
@ -799,7 +744,7 @@ public class ProxyServletTest
super.onResponseSuccess(request, response, proxyResponse); super.onResponseSuccess(request, response, proxyResponse);
} }
}; };
prepareProxy(proxyServlet); prepareProxy();
// First request // First request
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort()) ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
@ -821,7 +766,7 @@ public class ProxyServletTest
@Test @Test
public void testRedirectsAreProxied() throws Exception public void testRedirectsAreProxied() throws Exception
{ {
prepareProxy(new ProxyServlet()); prepareProxy();
prepareServer(new HttpServlet() prepareServer(new HttpServlet()
{ {
@Override @Override
@ -846,7 +791,7 @@ public class ProxyServletTest
public void testGZIPContentIsProxied() throws Exception public void testGZIPContentIsProxied() throws Exception
{ {
final byte[] content = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; final byte[] content = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
prepareProxy(new ProxyServlet()); prepareProxy();
prepareServer(new HttpServlet() prepareServer(new HttpServlet()
{ {
@Override @Override
@ -871,21 +816,23 @@ public class ProxyServletTest
} }
@Test(expected = TimeoutException.class) @Test(expected = TimeoutException.class)
public void shouldHandleWrongContentLength() throws Exception public void testWrongContentLength() throws Exception
{
prepareProxy();
prepareServer(new HttpServlet()
{ {
prepareProxy(new ProxyServlet());
prepareServer(new HttpServlet() {
@Override @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
byte[] message = "tooshort".getBytes("ascii"); byte[] message = "tooshort".getBytes("ascii");
resp.setContentType("text/plain;charset=ascii"); resp.setContentType("text/plain;charset=ascii");
resp.setHeader("Content-Length", Long.toString(message.length+1)); resp.setHeader("Content-Length", Long.toString(message.length + 1));
resp.getOutputStream().write(message); resp.getOutputStream().write(message);
} }
}); });
client.newRequest("localhost", serverConnector.getLocalPort()) client.newRequest("localhost", serverConnector.getLocalPort())
.timeout(5, TimeUnit.SECONDS) .timeout(1, TimeUnit.SECONDS)
.send(); .send();
Assert.fail(); Assert.fail();
@ -895,7 +842,7 @@ public class ProxyServletTest
public void testCookiesFromDifferentClientsAreNotMixed() throws Exception public void testCookiesFromDifferentClientsAreNotMixed() throws Exception
{ {
final String name = "biscuit"; final String name = "biscuit";
prepareProxy(new ProxyServlet()); prepareProxy();
prepareServer(new HttpServlet() prepareServer(new HttpServlet()
{ {
@Override @Override
@ -953,17 +900,4 @@ public class ProxyServletTest
} }
// TODO: test proxy authentication // TODO: test proxy authentication
private static class EmptyHttpServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
}
}
} }

View File

@ -1,3 +1,4 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
#org.eclipse.jetty.LEVEL=DEBUG #org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.client.LEVEL=DEBUG #org.eclipse.jetty.client.LEVEL=DEBUG
#org.eclipse.jetty.proxy.LEVEL=DEBUG

View File

@ -45,6 +45,7 @@ import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.security.Constraint; import org.eclipse.jetty.util.security.Constraint;
@ -820,7 +821,9 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
if (paths != null && !paths.isEmpty()) if (paths != null && !paths.isEmpty())
{ {
for (String p:paths) for (String p:paths)
LOG.warn("Path with uncovered http methods: {}",p); LOG.warn("{} has uncovered http methods for path: {}",ContextHandler.getCurrentContext(), p);
if (LOG.isDebugEnabled())
LOG.debug(new Throwable());
return true; return true;
} }
return false; return false;

View File

@ -133,6 +133,7 @@ public class HashLoginService extends MappedLoginService implements UserListener
/** /**
* @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart() * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
*/ */
@Override
protected void doStart() throws Exception protected void doStart() throws Exception
{ {
super.doStart(); super.doStart();
@ -154,6 +155,7 @@ public class HashLoginService extends MappedLoginService implements UserListener
/** /**
* @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop() * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
*/ */
@Override
protected void doStop() throws Exception protected void doStop() throws Exception
{ {
super.doStop(); super.doStop();
@ -163,6 +165,7 @@ public class HashLoginService extends MappedLoginService implements UserListener
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@Override
public void update(String userName, Credential credential, String[] roleArray) public void update(String userName, Credential credential, String[] roleArray)
{ {
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
@ -171,6 +174,7 @@ public class HashLoginService extends MappedLoginService implements UserListener
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@Override
public void remove(String userName) public void remove(String userName)
{ {
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())

View File

@ -43,6 +43,7 @@ import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandler.Context; import org.eclipse.jetty.server.handler.ContextHandler.Context;
import org.eclipse.jetty.server.handler.HandlerWrapper; import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.server.session.AbstractSession; import org.eclipse.jetty.server.session.AbstractSession;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
@ -74,8 +75,6 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
private LoginService _loginService; private LoginService _loginService;
private IdentityService _identityService; private IdentityService _identityService;
private boolean _renewSession=true; private boolean _renewSession=true;
private boolean _discoveredIdentityService = false;
private boolean _discoveredLoginService = false;
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
protected SecurityHandler() protected SecurityHandler()
@ -266,20 +265,24 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
protected LoginService findLoginService() protected LoginService findLoginService() throws Exception
{ {
Collection<LoginService> list = getServer().getBeans(LoginService.class); Collection<LoginService> list = getServer().getBeans(LoginService.class);
LoginService service = null;
String realm=getRealmName(); String realm=getRealmName();
if (realm!=null) if (realm!=null)
{ {
for (LoginService service : list) for (LoginService s : list)
if (service.getName()!=null && service.getName().equals(realm)) if (s.getName()!=null && s.getName().equals(realm))
return service; {
service=s;
break;
}
} }
else if (list.size()==1) else if (list.size()==1)
return list.iterator().next(); service = list.iterator().next();
return null;
return service;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -342,7 +345,8 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
if (_loginService==null) if (_loginService==null)
{ {
setLoginService(findLoginService()); setLoginService(findLoginService());
_discoveredLoginService = true; if (_loginService!=null)
unmanage(_loginService);
} }
if (_identityService==null) if (_identityService==null)
@ -353,10 +357,16 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
if (_identityService==null) if (_identityService==null)
setIdentityService(findIdentityService()); setIdentityService(findIdentityService());
if (_identityService==null && _realmName!=null) if (_identityService==null)
{
if (_realmName!=null)
{
setIdentityService(new DefaultIdentityService()); setIdentityService(new DefaultIdentityService());
manage(_identityService);
_discoveredIdentityService = true; }
}
else
unmanage(_identityService);
} }
if (_loginService!=null) if (_loginService!=null)
@ -387,17 +397,16 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
protected void doStop() throws Exception protected void doStop() throws Exception
{ {
//if we discovered the services (rather than had them explicitly configured), remove them. //if we discovered the services (rather than had them explicitly configured), remove them.
if (_discoveredIdentityService) if (!isManaged(_identityService))
{ {
removeBean(_identityService); removeBean(_identityService);
_identityService = null; _identityService = null;
} }
if (_discoveredLoginService) if (!isManaged(_loginService))
{ {
removeBean(_loginService); removeBean(_loginService);
_loginService = null; _loginService=null;
} }
super.doStop(); super.doStop();
@ -427,6 +436,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
/** /**
* @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication() * @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
*/ */
@Override
public boolean isSessionRenewedOnAuthentication() public boolean isSessionRenewedOnAuthentication()
{ {
return _renewSession; return _renewSession;
@ -473,7 +483,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
{ {
if (!baseRequest.isHandled()) if (!baseRequest.isHandled())
{ {
response.sendError(Response.SC_FORBIDDEN); response.sendError(HttpServletResponse.SC_FORBIDDEN);
baseRequest.setHandled(true); baseRequest.setHandled(true);
} }
return; return;
@ -488,7 +498,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
LOG.warn("No authenticator for: "+roleInfo); LOG.warn("No authenticator for: "+roleInfo);
if (!baseRequest.isHandled()) if (!baseRequest.isHandled())
{ {
response.sendError(Response.SC_FORBIDDEN); response.sendError(HttpServletResponse.SC_FORBIDDEN);
baseRequest.setHandled(true); baseRequest.setHandled(true);
} }
return; return;
@ -524,7 +534,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
boolean authorized=checkWebResourcePermissions(pathInContext, baseRequest, base_response, roleInfo, userAuth.getUserIdentity()); boolean authorized=checkWebResourcePermissions(pathInContext, baseRequest, base_response, roleInfo, userAuth.getUserIdentity());
if (!authorized) if (!authorized)
{ {
response.sendError(Response.SC_FORBIDDEN, "!role"); response.sendError(HttpServletResponse.SC_FORBIDDEN, "!role");
baseRequest.setHandled(true); baseRequest.setHandled(true);
return; return;
} }
@ -574,7 +584,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
{ {
// jaspi 3.8.3 send HTTP 500 internal server error, with message // jaspi 3.8.3 send HTTP 500 internal server error, with message
// from AuthException // from AuthException
response.sendError(Response.SC_INTERNAL_SERVER_ERROR, e.getMessage()); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
} }
finally finally
{ {
@ -634,6 +644,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public class NotChecked implements Principal public class NotChecked implements Principal
{ {
@Override
public String getName() public String getName()
{ {
return null; return null;
@ -656,6 +667,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public static final Principal __NO_USER = new Principal() public static final Principal __NO_USER = new Principal()
{ {
@Override
public String getName() public String getName()
{ {
return null; return null;
@ -680,6 +692,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
*/ */
public static final Principal __NOBODY = new Principal() public static final Principal __NOBODY = new Principal()
{ {
@Override
public String getName() public String getName()
{ {
return "Nobody"; return "Nobody";

View File

@ -73,6 +73,11 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable, H
private static final Logger LOG = Log.getLogger(HttpChannel.class); private static final Logger LOG = Log.getLogger(HttpChannel.class);
private static final ThreadLocal<HttpChannel<?>> __currentChannel = new ThreadLocal<>(); private static final ThreadLocal<HttpChannel<?>> __currentChannel = new ThreadLocal<>();
/* ------------------------------------------------------------ */
/** Get the current channel that this thread is dispatched to.
* @see Request#getAttribute(String) for a more general way to access the HttpChannel
* @return the current HttpChannel or null
*/
public static HttpChannel<?> getCurrentHttpChannel() public static HttpChannel<?> getCurrentHttpChannel()
{ {
return __currentChannel.get(); return __currentChannel.get();

View File

@ -35,21 +35,6 @@ import org.eclipse.jetty.util.thread.Scheduler;
/** /**
* Implementation of AsyncContext interface that holds the state of request-response cycle. * Implementation of AsyncContext interface that holds the state of request-response cycle.
*
* <table>
* <tr><th>STATE</th><th colspan=6>ACTION</th></tr>
* <tr><th></th> <th>handling()</th> <th>startAsync()</th><th>unhandle()</th> <th>dispatch()</th> <th>complete()</th> <th>completed()</th></tr>
* <tr><th align=right>IDLE:</th> <td>DISPATCHED</td> <td></td> <td></td> <td></td> <td>COMPLETECALLED??</td><td></td></tr>
* <tr><th align=right>DISPATCHED:</th> <td></td> <td>ASYNCSTARTED</td><td>COMPLETING</td> <td></td> <td></td> <td></td></tr>
* <tr><th align=right>ASYNCSTARTED:</th> <td></td> <td></td> <td>ASYNCWAIT</td> <td>REDISPATCHING</td><td>COMPLETECALLED</td> <td></td></tr>
* <tr><th align=right>REDISPATCHING:</th> <td></td> <td></td> <td>REDISPATCHED</td><td></td> <td></td> <td></td></tr>
* <tr><th align=right>ASYNCWAIT:</th> <td></td> <td></td> <td></td> <td>REDISPATCH</td> <td>COMPLETECALLED</td> <td></td></tr>
* <tr><th align=right>REDISPATCH:</th> <td>REDISPATCHED</td><td></td> <td></td> <td></td> <td></td> <td></td></tr>
* <tr><th align=right>REDISPATCHED:</th> <td></td> <td>ASYNCSTARTED</td><td>COMPLETING</td> <td></td> <td></td> <td></td></tr>
* <tr><th align=right>COMPLETECALLED:</th><td>COMPLETING</td> <td></td> <td>COMPLETING</td> <td></td> <td></td> <td></td></tr>
* <tr><th align=right>COMPLETING:</th> <td>COMPLETING</td> <td></td> <td></td> <td></td> <td></td> <td>COMPLETED</td></tr>
* <tr><th align=right>COMPLETED:</th> <td></td> <td></td> <td></td> <td></td> <td></td> <td></td></tr>
* </table>
*/ */
public class HttpChannelState public class HttpChannelState
{ {
@ -57,16 +42,22 @@ public class HttpChannelState
private final static long DEFAULT_TIMEOUT=30000L; private final static long DEFAULT_TIMEOUT=30000L;
/** The dispatched state of the HttpChannel, used to control the overall livecycle
*/
public enum State public enum State
{ {
IDLE, // Idle request IDLE, // Idle request
DISPATCHED, // Request dispatched to filter/servlet DISPATCHED, // Request dispatched to filter/servlet
ASYNCWAIT, // Suspended and parked ASYNC_WAIT, // Suspended and parked
ASYNCIO, // Has been dispatched for async IO ASYNC_WOKEN, // A thread has been dispatch to handle from ASYNCWAIT
ASYNC_IO, // Has been dispatched for async IO
COMPLETING, // Request is completable COMPLETING, // Request is completable
COMPLETED // Request is complete COMPLETED // Request is complete
} }
/**
* The actions to take as the channel moves from state to state.
*/
public enum Action public enum Action
{ {
REQUEST_DISPATCH, // handle a normal request dispatch REQUEST_DISPATCH, // handle a normal request dispatch
@ -78,6 +69,11 @@ public class HttpChannelState
COMPLETE // Complete the channel COMPLETE // Complete the channel
} }
/**
* The state of the servlet async API. This can lead or follow the
* channel dispatch state and also includes reasons such as expired,
* dispatched or completed.
*/
public enum Async public enum Async
{ {
STARTED, STARTED,
@ -188,16 +184,16 @@ public class HttpChannelState
case COMPLETED: case COMPLETED:
return Action.WAIT; return Action.WAIT;
case ASYNCWAIT: case ASYNC_WOKEN:
if (_asyncRead) if (_asyncRead)
{ {
_state=State.ASYNCIO; _state=State.ASYNC_IO;
_asyncRead=false; _asyncRead=false;
return Action.READ_CALLBACK; return Action.READ_CALLBACK;
} }
if (_asyncWrite) if (_asyncWrite)
{ {
_state=State.ASYNCIO; _state=State.ASYNC_IO;
_asyncWrite=false; _asyncWrite=false;
return Action.WRITE_CALLBACK; return Action.WRITE_CALLBACK;
} }
@ -221,6 +217,7 @@ public class HttpChannelState
_async=null; _async=null;
return Action.ASYNC_EXPIRED; return Action.ASYNC_EXPIRED;
case STARTED: case STARTED:
// TODO
if (DEBUG) if (DEBUG)
LOG.debug("TODO Fix this double dispatch",new IllegalStateException(this LOG.debug("TODO Fix this double dispatch",new IllegalStateException(this
.getStatusString())); .getStatusString()));
@ -293,7 +290,7 @@ public class HttpChannelState
switch(_state) switch(_state)
{ {
case DISPATCHED: case DISPATCHED:
case ASYNCIO: case ASYNC_IO:
break; break;
default: default:
throw new IllegalStateException(this.getStatusString()); throw new IllegalStateException(this.getStatusString());
@ -301,7 +298,7 @@ public class HttpChannelState
if (_asyncRead) if (_asyncRead)
{ {
_state=State.ASYNCIO; _state=State.ASYNC_IO;
_asyncRead=false; _asyncRead=false;
return Action.READ_CALLBACK; return Action.READ_CALLBACK;
} }
@ -309,7 +306,7 @@ public class HttpChannelState
if (_asyncWrite) if (_asyncWrite)
{ {
_asyncWrite=false; _asyncWrite=false;
_state=State.ASYNCIO; _state=State.ASYNC_IO;
return Action.WRITE_CALLBACK; return Action.WRITE_CALLBACK;
} }
@ -333,7 +330,7 @@ public class HttpChannelState
case EXPIRING: case EXPIRING:
case STARTED: case STARTED:
scheduleTimeout(); scheduleTimeout();
_state=State.ASYNCWAIT; _state=State.ASYNC_WAIT;
return Action.WAIT; return Action.WAIT;
} }
} }
@ -360,11 +357,19 @@ public class HttpChannelState
switch(_state) switch(_state)
{ {
case DISPATCHED: case DISPATCHED:
case ASYNCIO: case ASYNC_IO:
dispatch=false;
break;
case ASYNC_WAIT:
_state=State.ASYNC_WOKEN;
dispatch=true;
break;
case ASYNC_WOKEN:
dispatch=false; dispatch=false;
break; break;
default: default:
dispatch=true; LOG.warn("async dispatched when complete {}",this);
dispatch=false;
break; break;
} }
} }
@ -411,10 +416,13 @@ public class HttpChannelState
if (_async==Async.EXPIRING) if (_async==Async.EXPIRING)
{ {
_async=Async.EXPIRED; _async=Async.EXPIRED;
if (_state==State.ASYNCWAIT) if (_state==State.ASYNC_WAIT)
{
_state=State.ASYNC_WOKEN;
dispatch=true; dispatch=true;
} }
} }
}
if (dispatch) if (dispatch)
scheduleDispatch(); scheduleDispatch();
@ -423,13 +431,17 @@ public class HttpChannelState
public void complete() public void complete()
{ {
// just like resume, except don't set _dispatched=true; // just like resume, except don't set _dispatched=true;
boolean handle; boolean handle=false;
synchronized (this) synchronized (this)
{ {
if (_async!=Async.STARTED && _async!=Async.EXPIRING) if (_async!=Async.STARTED && _async!=Async.EXPIRING)
throw new IllegalStateException(this.getStatusString()); throw new IllegalStateException(this.getStatusString());
_async=Async.COMPLETE; _async=Async.COMPLETE;
handle=_state==State.ASYNCWAIT; if (_state==State.ASYNC_WAIT)
{
handle=true;
_state=State.ASYNC_WOKEN;
}
} }
cancelTimeout(); cancelTimeout();
@ -511,7 +523,7 @@ public class HttpChannelState
switch(_state) switch(_state)
{ {
case DISPATCHED: case DISPATCHED:
case ASYNCIO: case ASYNC_IO:
throw new IllegalStateException(getStatusString()); throw new IllegalStateException(getStatusString());
default: default:
break; break;
@ -571,7 +583,7 @@ public class HttpChannelState
{ {
synchronized(this) synchronized(this)
{ {
return _state==State.ASYNCWAIT || _state==State.DISPATCHED && _async==Async.STARTED; return _state==State.ASYNC_WAIT || _state==State.DISPATCHED && _async==Async.STARTED;
} }
} }
@ -665,12 +677,16 @@ public class HttpChannelState
public void onReadPossible() public void onReadPossible()
{ {
boolean handle; boolean handle=false;
synchronized (this) synchronized (this)
{ {
_asyncRead=true; _asyncRead=true;
handle=_state==State.ASYNCWAIT; if (_state==State.ASYNC_WAIT)
{
_state=State.ASYNC_WOKEN;
handle=true;
}
} }
if (handle) if (handle)
@ -679,12 +695,16 @@ public class HttpChannelState
public void onWritePossible() public void onWritePossible()
{ {
boolean handle; boolean handle=false;
synchronized (this) synchronized (this)
{ {
_asyncWrite=true; _asyncWrite=true;
handle=_state==State.ASYNCWAIT; if (_state==State.ASYNC_WAIT)
{
_state=State.ASYNC_WOKEN;
handle=true;
}
} }
if (handle) if (handle)

View File

@ -52,7 +52,7 @@ public class HttpConfiguration
private String _secureScheme = HttpScheme.HTTPS.asString(); private String _secureScheme = HttpScheme.HTTPS.asString();
private boolean _sendServerVersion = true; //send Server: header private boolean _sendServerVersion = true; //send Server: header
private boolean _sendXPoweredBy = false; //send X-Powered-By: header private boolean _sendXPoweredBy = false; //send X-Powered-By: header
private boolean _sendDateHeader = false; //send Date: header private boolean _sendDateHeader = true; //send Date: header
public interface Customizer public interface Customizer
{ {

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.server; package org.eclipse.jetty.server;
import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionException;
@ -62,6 +63,13 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
private volatile ByteBuffer _chunk = null; private volatile ByteBuffer _chunk = null;
/* ------------------------------------------------------------ */
/** Get the current connection that this thread is dispatched to.
* Note that a thread may be processing a request asynchronously and
* thus not be dispatched to the connection.
* @see Request#getAttribute(String) for a more general way to access the HttpConnection
* @return the current HttpConnection or null
*/
public static HttpConnection getCurrentConnection() public static HttpConnection getCurrentConnection()
{ {
return __currentConnection.get(); return __currentConnection.get();
@ -210,6 +218,8 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
else else
{ {
// Get a buffer // Get a buffer
// We are not in a race here for the request buffer as we have not yet received a request,
// so there are not an possible legal threads calling #parseContent or #completed.
_requestBuffer = getRequestBuffer(); _requestBuffer = getRequestBuffer();
// fill // fill
@ -229,11 +239,15 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
// The parser returned true, which indicates the channel is ready to handle a request. // The parser returned true, which indicates the channel is ready to handle a request.
// Call the channel and this will either handle the request/response to completion OR, // Call the channel and this will either handle the request/response to completion OR,
// if the request suspends, the request/response will be incomplete so the outer loop will exit. // if the request suspends, the request/response will be incomplete so the outer loop will exit.
// Not that onFillable no longer manipulates the request buffer from this point and that is
// left to threads calling #completed or #parseContent (which may be this thread inside handle())
suspended = !_channel.handle(); suspended = !_channel.handle();
} }
else else
{ {
// We parsed what we could, recycle the request buffer // We parsed what we could, recycle the request buffer
// We are not in a race here for the request buffer as we have not yet received a request,
// so there are not an possible legal threads calling #parseContent or #completed.
releaseRequestBuffer(); releaseRequestBuffer();
} }
} }
@ -260,6 +274,135 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
} }
} }
/* ------------------------------------------------------------ */
/** Fill and parse data looking for content
* @throws IOException
*/
protected void parseContent() throws IOException
{
// Not in a race here for the request buffer with #onFillable because an async consumer of
// content would only be started after onFillable has given up control.
// In a little bit of a race with #completed, but then not sure if it is legal to be doing
// async calls to IO and have a completed call at the same time.
ByteBuffer requestBuffer = getRequestBuffer();
while (_parser.inContentState())
{
// Can the parser progress (even with an empty buffer)
boolean parsed = _parser.parseNext(requestBuffer==null?BufferUtil.EMPTY_BUFFER:requestBuffer);
// No, we can we try reading some content?
if (BufferUtil.isEmpty(requestBuffer) && getEndPoint().isInputShutdown())
{
_parser.atEOF();
if (parsed)
break;
continue;
}
if (parsed)
break;
// OK lets read some data
int filled=getEndPoint().fill(requestBuffer);
if (LOG.isDebugEnabled()) // Avoid boxing of variable 'filled'
LOG.debug("{} filled {}",this,filled);
if (filled<=0)
{
if (filled<0)
{
_parser.atEOF();
continue;
}
break;
}
}
}
@Override
public void completed()
{
// Handle connection upgrades
if (_channel.getResponse().getStatus() == HttpStatus.SWITCHING_PROTOCOLS_101)
{
Connection connection = (Connection)_channel.getRequest().getAttribute(UPGRADE_CONNECTION_ATTRIBUTE);
if (connection != null)
{
LOG.debug("Upgrade from {} to {}", this, connection);
onClose();
getEndPoint().setConnection(connection);
connection.onOpen();
_channel.reset();
_parser.reset();
_generator.reset();
releaseRequestBuffer();
return;
}
}
// Finish consuming the request
// If we are still expecting
if (_channel.isExpecting100Continue())
// close to seek EOF
_parser.close();
else if (_parser.inContentState() && _generator.isPersistent())
// Complete reading the request
_channel.getRequest().getHttpInput().consumeAll();
// Reset the channel, parsers and generator
_channel.reset();
if (_generator.isPersistent() && !_parser.isClosed())
_parser.reset();
else
_parser.close();
// Not in a race here with onFillable, because it has given up control before calling handle.
// in a slight race with #completed, but not sure what to do with that anyway.
releaseRequestBuffer();
if (_chunk!=null)
_bufferPool.release(_chunk);
_chunk=null;
_generator.reset();
// if we are not called from the onfillable thread, schedule completion
if (getCurrentConnection()!=this)
{
// If we are looking for the next request
if (_parser.isStart())
{
// if the buffer is empty
if (BufferUtil.isEmpty(_requestBuffer))
{
// look for more data
fillInterested();
}
// else if we are still running
else if (getConnector().isRunning())
{
// Dispatched to handle a pipelined request
try
{
getExecutor().execute(this);
}
catch (RejectedExecutionException e)
{
if (getConnector().isRunning())
LOG.warn(e);
else
LOG.ignore(e);
getEndPoint().close();
}
}
else
{
getEndPoint().close();
}
}
// else the parser must be closed, so seek the EOF if we are still open
else if (getEndPoint().isOpen())
fillInterested();
}
}
@Override @Override
protected void onFillInterestedFailed(Throwable cause) protected void onFillInterestedFailed(Throwable cause)
@ -303,87 +446,6 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
new ContentCallback(content,lastContent,callback).iterate(); new ContentCallback(content,lastContent,callback).iterate();
} }
@Override
public void completed()
{
// Handle connection upgrades
if (_channel.getResponse().getStatus() == HttpStatus.SWITCHING_PROTOCOLS_101)
{
Connection connection = (Connection)_channel.getRequest().getAttribute(UPGRADE_CONNECTION_ATTRIBUTE);
if (connection != null)
{
LOG.debug("Upgrade from {} to {}", this, connection);
onClose();
getEndPoint().setConnection(connection);
connection.onOpen();
_channel.reset();
_parser.reset();
_generator.reset();
releaseRequestBuffer();
return;
}
}
// Finish consuming the request
// If we are still expecting
if (_channel.isExpecting100Continue())
// close to seek EOF
_parser.close();
else if (_parser.inContentState() && _generator.isPersistent())
// Complete reading the request
_channel.getRequest().getHttpInput().consumeAll();
// Reset the channel, parsers and generator
_channel.reset();
if (_generator.isPersistent() && !_parser.isClosed())
_parser.reset();
else
_parser.close();
releaseRequestBuffer();
if (_chunk!=null)
_bufferPool.release(_chunk);
_chunk=null;
_generator.reset();
// if we are not called from the onfillable thread, schedule completion
if (getCurrentConnection()!=this)
{
// If we are looking for the next request
if (_parser.isStart())
{
// if the buffer is empty
if (_requestBuffer == null)
{
// look for more data
fillInterested();
}
// else if we are still running
else if (getConnector().isRunning())
{
// Dispatched to handle a pipelined request
try
{
getExecutor().execute(this);
}
catch (RejectedExecutionException e)
{
if (getConnector().isRunning())
LOG.warn(e);
else
LOG.ignore(e);
getEndPoint().close();
}
}
else
{
getEndPoint().close();
}
}
// else the parser must be closed, so seek the EOF if we are still open
else if (getEndPoint().isOpen())
fillInterested();
}
}
protected class HttpChannelOverHttp extends HttpChannel<ByteBuffer> protected class HttpChannelOverHttp extends HttpChannel<ByteBuffer>
{ {
@ -471,6 +533,14 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
{ {
getEndPoint().shutdownOutput(); getEndPoint().shutdownOutput();
} }
@Override
public boolean messageComplete()
{
super.messageComplete();
return false;
}
} }
private class CommitCallback extends IteratingCallback private class CommitCallback extends IteratingCallback

View File

@ -20,7 +20,6 @@ package org.eclipse.jetty.server;
import java.io.IOException; import java.io.IOException;
import java.util.Objects; import java.util.Objects;
import javax.servlet.ReadListener; import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream; import javax.servlet.ServletInputStream;
@ -288,6 +287,14 @@ public abstract class HttpInput<T> extends ServletInputStream implements Runnabl
} }
} }
public boolean isAsync()
{
synchronized (lock())
{
return _contentState==ASYNC;
}
}
/** /**
* @return whether an EOF has been detected, even though there may be content to consume. * @return whether an EOF has been detected, even though there may be content to consume.
*/ */
@ -355,7 +362,7 @@ public abstract class HttpInput<T> extends ServletInputStream implements Runnabl
{ {
synchronized (lock()) synchronized (lock())
{ {
if (_onError == null) if (_onError != null)
LOG.warn(x); LOG.warn(x);
else else
_onError = x; _onError = x;
@ -436,6 +443,7 @@ public abstract class HttpInput<T> extends ServletInputStream implements Runnabl
input.blockForContent(); input.blockForContent();
} }
@Override
public String toString() public String toString()
{ {
return "STREAM"; return "STREAM";
@ -471,6 +479,7 @@ public abstract class HttpInput<T> extends ServletInputStream implements Runnabl
return true; return true;
} }
@Override
public String toString() public String toString()
{ {
return "EARLY_EOF"; return "EARLY_EOF";
@ -485,6 +494,7 @@ public abstract class HttpInput<T> extends ServletInputStream implements Runnabl
return true; return true;
} }
@Override
public String toString() public String toString()
{ {
return "EOF"; return "EOF";

View File

@ -86,39 +86,12 @@ public class HttpInputOverHTTP extends HttpInput<ByteBuffer> implements Callback
// No - then we are going to need to parse some more content // No - then we are going to need to parse some more content
_content=null; _content=null;
ByteBuffer requestBuffer = _httpConnection.getRequestBuffer(); _httpConnection.parseContent();
while (!_httpConnection.getParser().isComplete()) // If we have some content available, return it
{
// Can the parser progress (even with an empty buffer)
_httpConnection.getParser().parseNext(requestBuffer==null?BufferUtil.EMPTY_BUFFER:requestBuffer);
// If we got some content, that will do for now!
if (BufferUtil.hasContent(_content)) if (BufferUtil.hasContent(_content))
return _content; return _content;
// No, we can we try reading some content?
if (BufferUtil.isEmpty(requestBuffer) && _httpConnection.getEndPoint().isInputShutdown())
{
_httpConnection.getParser().atEOF();
continue;
}
// OK lets read some data
int filled=_httpConnection.getEndPoint().fill(requestBuffer);
if (LOG.isDebugEnabled()) // Avoid boxing of variable 'filled'
LOG.debug("{} filled {}",this,filled);
if (filled<=0)
{
if (filled<0)
{
_httpConnection.getParser().atEOF();
continue;
}
return null;
}
}
return null; return null;
} }

View File

@ -24,7 +24,6 @@ import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel; import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritePendingException; import java.nio.channels.WritePendingException;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.RequestDispatcher; import javax.servlet.RequestDispatcher;
import javax.servlet.ServletOutputStream; import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest; import javax.servlet.ServletRequest;
@ -75,7 +74,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
write completed - - - ASYNC READY->owp - write completed - - - ASYNC READY->owp -
*/ */
enum OutputState { OPEN, ASYNC, READY, PENDING, UNREADY, CLOSED } enum OutputState { OPEN, ASYNC, READY, PENDING, UNREADY, ERROR, CLOSED }
private final AtomicReference<OutputState> _state=new AtomicReference<>(OutputState.OPEN); private final AtomicReference<OutputState> _state=new AtomicReference<>(OutputState.OPEN);
public HttpOutput(HttpChannel<?> channel) public HttpOutput(HttpChannel<?> channel)
@ -147,7 +146,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
break loop; break loop;
case UNREADY: case UNREADY:
throw new WritePendingException(); // TODO ? if (_state.compareAndSet(state,OutputState.ERROR))
_writeListener.onError(_onError==null?new EofException("Async close"):_onError);
continue;
default: default:
if (_state.compareAndSet(state,OutputState.CLOSED)) if (_state.compareAndSet(state,OutputState.CLOSED))
@ -180,7 +181,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
break loop; break loop;
case UNREADY: case UNREADY:
throw new WritePendingException(); // TODO ? if (_state.compareAndSet(state,OutputState.ERROR))
_writeListener.onError(_onError==null?new EofException("Async closed"):_onError);
continue;
default: default:
if (_state.compareAndSet(state,OutputState.CLOSED)) if (_state.compareAndSet(state,OutputState.CLOSED))
@ -239,6 +242,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
case UNREADY: case UNREADY:
throw new WritePendingException(); throw new WritePendingException();
case ERROR:
throw new EofException(_onError);
case CLOSED: case CLOSED:
return; return;
} }
@ -299,6 +305,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
case UNREADY: case UNREADY:
throw new WritePendingException(); throw new WritePendingException();
case ERROR:
throw new EofException(_onError);
case CLOSED: case CLOSED:
throw new EofException("Closed"); throw new EofException("Closed");
} }
@ -397,6 +406,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
case UNREADY: case UNREADY:
throw new WritePendingException(); throw new WritePendingException();
case ERROR:
throw new EofException(_onError);
case CLOSED: case CLOSED:
throw new EofException("Closed"); throw new EofException("Closed");
} }
@ -477,6 +489,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
case UNREADY: case UNREADY:
throw new WritePendingException(); throw new WritePendingException();
case ERROR:
throw new EofException(_onError);
case CLOSED: case CLOSED:
throw new EofException("Closed"); throw new EofException("Closed");
} }
@ -616,6 +631,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable
if (!_state.compareAndSet(OutputState.OPEN, OutputState.PENDING)) if (!_state.compareAndSet(OutputState.OPEN, OutputState.PENDING))
continue; continue;
break; break;
case ERROR:
throw new EofException(_onError);
case CLOSED: case CLOSED:
throw new EofException("Closed"); throw new EofException("Closed");
default: default:
@ -708,6 +725,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
case UNREADY: case UNREADY:
return false; return false;
case ERROR:
return true;
case CLOSED: case CLOSED:
return true; return true;
} }
@ -717,47 +737,57 @@ public class HttpOutput extends ServletOutputStream implements Runnable
@Override @Override
public void run() public void run()
{ {
loop: while (true)
{
OutputState state = _state.get();
if(_onError!=null) if(_onError!=null)
{
switch(state)
{
case CLOSED:
case ERROR:
_onError=null;
break loop;
default:
if (_state.compareAndSet(state, OutputState.ERROR))
{ {
Throwable th=_onError; Throwable th=_onError;
_onError=null; _onError=null;
_writeListener.onError(new IOException(th)); LOG.debug("onError",th);
_writeListener.onError(th);
close(); close();
break loop;
}
}
continue loop;
} }
switch(_state.get()) switch(_state.get())
{ {
case READY: case READY:
try
{
_writeListener.onWritePossible();
}
catch (Throwable e)
{
_writeListener.onError(e);
close();
}
break;
case CLOSED: case CLOSED:
try
{
new Throwable().printStackTrace();
// even though a write is not possible, because a close has // even though a write is not possible, because a close has
// occurred, we need to call onWritePossible to tell async // occurred, we need to call onWritePossible to tell async
// producer that the last write completed. // producer that the last write completed.
try
{
_writeListener.onWritePossible(); _writeListener.onWritePossible();
break loop;
} }
catch (Throwable e) catch (Throwable e)
{ {
_writeListener.onError(e); _onError=e;
} }
break; break;
default: default:
} }
} }
}
@Override @Override
public String toString() public String toString()
@ -769,21 +799,19 @@ public class HttpOutput extends ServletOutputStream implements Runnable
{ {
@Override @Override
protected void completed() protected void completed()
{
try
{ {
while(true) while(true)
{ {
HttpOutput.OutputState last=_state.get(); OutputState last=_state.get();
switch(last) switch(last)
{ {
case PENDING: case PENDING:
if (!_state.compareAndSet(HttpOutput.OutputState.PENDING, HttpOutput.OutputState.ASYNC)) if (!_state.compareAndSet(OutputState.PENDING, OutputState.ASYNC))
continue; continue;
break; break;
case UNREADY: case UNREADY:
if (!_state.compareAndSet(HttpOutput.OutputState.UNREADY, HttpOutput.OutputState.READY)) if (!_state.compareAndSet(OutputState.UNREADY, OutputState.READY))
continue; continue;
_channel.getState().onWritePossible(); _channel.getState().onWritePossible();
break; break;
@ -797,12 +825,6 @@ public class HttpOutput extends ServletOutputStream implements Runnable
break; break;
} }
} }
catch (Exception e)
{
_onError=e;
_channel.getState().onWritePossible();
}
}
@Override @Override
public void failed(Throwable e) public void failed(Throwable e)
@ -885,7 +907,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
// Can we just aggregate the remainder? // Can we just aggregate the remainder?
if (!_complete && _len<BufferUtil.space(_aggregate) && _len<_commitSize) if (!_complete && _len<BufferUtil.space(_aggregate) && _len<_commitSize)
{ {
int position = BufferUtil.flipToFill(_aggregate);
BufferUtil.put(_buffer,_aggregate); BufferUtil.put(_buffer,_aggregate);
BufferUtil.flipToFlush(_aggregate, position);
return Action.SUCCEEDED; return Action.SUCCEEDED;
} }

View File

@ -87,7 +87,6 @@ public class NetworkTrafficServerConnector extends ServerConnector
protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectorManager.ManagedSelector selectSet, SelectionKey key) throws IOException protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectorManager.ManagedSelector selectSet, SelectionKey key) throws IOException
{ {
NetworkTrafficSelectChannelEndPoint endPoint = new NetworkTrafficSelectChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout(), listeners); NetworkTrafficSelectChannelEndPoint endPoint = new NetworkTrafficSelectChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout(), listeners);
endPoint.notifyOpened();
return endPoint; return endPoint;
} }
} }

View File

@ -339,6 +339,8 @@ public class Request implements HttpServletRequest
throw new IllegalStateException("Form too large " + content_length + ">" + maxFormContentSize); throw new IllegalStateException("Form too large " + content_length + ">" + maxFormContentSize);
} }
InputStream in = getInputStream(); InputStream in = getInputStream();
if (_input.isAsync())
throw new IllegalStateException("Cannot extract parameters with async IO");
// Add form params to query params // Add form params to query params
UrlEncoded.decodeTo(in,_baseParameters,encoding,content_length < 0?maxFormContentSize:-1,maxFormKeys); UrlEncoded.decodeTo(in,_baseParameters,encoding,content_length < 0?maxFormContentSize:-1,maxFormKeys);

View File

@ -1215,19 +1215,16 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
return; return;
} }
_protectedTargets = new String[targets.length]; _protectedTargets = Arrays.copyOf(targets, targets.length);
System.arraycopy(targets, 0, _protectedTargets, 0, targets.length);
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public String[] getProtectedTargets () public String[] getProtectedTargets()
{ {
if (_protectedTargets == null) if (_protectedTargets == null)
return null; return null;
String[] tmp = new String[_protectedTargets.length]; return Arrays.copyOf(_protectedTargets, _protectedTargets.length);
System.arraycopy(_protectedTargets, 0, tmp, 0, _protectedTargets.length);
return tmp;
} }
@ -1384,8 +1381,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* @param base * Set the base resource for this context.
* The resourceBase to set. * @param base The resource used as the base for all static content of this context.
* @see #setResourceBase(String)
*/ */
public void setBaseResource(Resource base) public void setBaseResource(Resource base)
{ {
@ -1394,8 +1392,10 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* @param resourceBase * Set the base resource for this context.
* The base resource as a string. * @param resourceBase A string representing the base resource for the context. Any string accepted
* by {@link Resource#newResource(String)} may be passed and the call is equivalent to
* <code>setBaseResource(newResource(resourceBase));</code>
*/ */
public void setResourceBase(String resourceBase) public void setResourceBase(String resourceBase)
{ {

Some files were not shown because too many files have changed in this diff Show More