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>
<scope>provided</scope>
</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) -->
<dependency>
<groupId>javax.websocket</groupId>

View File

@ -2,7 +2,7 @@
protonego-boot
[files]
http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.0.0/alpn-boot-7.0.0.jar:lib/alpn/alpn-boot-7.0.0.jar
http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.0.0.v20140317/alpn-boot-7.0.0.v20140317.jar:lib/alpn/alpn-boot-7.0.0.v20140317.jar
[exec]
-Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.jar
-Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.v20140317.jar

View File

@ -2,7 +2,7 @@
protonego-boot
[files]
http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.0.0/alpn-boot-7.0.0.jar:lib/alpn/alpn-boot-7.0.0.jar
http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.0.0.v20140317/alpn-boot-7.0.0.v20140317.jar:lib/alpn/alpn-boot-7.0.0.v20140317.jar
[exec]
-Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.jar
-Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.v20140317.jar

View File

@ -2,7 +2,7 @@
protonego-boot
[files]
http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.0.0/alpn-boot-7.0.0.jar:lib/alpn/alpn-boot-7.0.0.jar
http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.0.0.v20140317/alpn-boot-7.0.0.v20140317.jar:lib/alpn/alpn-boot-7.0.0.v20140317.jar
[exec]
-Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.jar
-Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.v20140317.jar

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
[files]
http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.0.0/alpn-boot-8.0.0.jar:lib/alpn/alpn-boot-8.0.0.jar
http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.0.0.v20140317/alpn-boot-8.0.0.v20140317.jar:lib/alpn/alpn-boot-8.0.0.v20140317.jar
[exec]
-Xbootclasspath/p:lib/alpn/alpn-boot-8.0.0.jar
-Xbootclasspath/p:lib/alpn/alpn-boot-8.0.0.v20140317.jar

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>
<instructions>
<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>
</configuration>
</execution>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@ -140,8 +140,11 @@ public abstract class PoolingHttpDestination<C extends Connection> extends HttpD
protected abstract void send(C connection, HttpExchange exchange);
public void release(C connection)
@Override
public void release(Connection c)
{
@SuppressWarnings("unchecked")
C connection = (C)c;
LOG.debug("{} released", connection);
HttpClient client = getHttpClient();
if (client.isRunning())

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.Result;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IteratingNestedCallback;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -106,31 +108,20 @@ public class ResponseNotifier
}
}
public void notifyContent(List<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.
buffer = buffer.slice();
if (!buffer.hasRemaining())
return;
// Optimized to avoid allocations of iterator instances
for (int i = 0; i < listeners.size(); ++i)
{
Response.ResponseListener listener = listeners.get(i);
if (listener instanceof Response.ContentListener)
{
// The buffer was sliced, so we always clear it (position=0, limit=capacity)
// before passing it to the listener that may consume it.
buffer.clear();
notifyContent((Response.ContentListener)listener, response, buffer);
}
}
// Here we use an IteratingNestedCallback not to avoid the stack overflow, but to
// invoke the listeners one after the other. When all of them have invoked the
// callback they got passed, the callback passed to this method is finally invoked.
ContentCallback contentCallback = new ContentCallback(listeners, response, buffer, callback);
contentCallback.iterate();
}
private void notifyContent(Response.ContentListener listener, Response response, ByteBuffer buffer)
private void notifyContent(Response.AsyncContentListener listener, Response response, ByteBuffer buffer, Callback callback)
{
try
{
listener.onContent(response, buffer);
listener.onContent(response, buffer, callback);
}
catch (Throwable x)
{
@ -218,7 +209,8 @@ public class ResponseNotifier
}
notifyHeaders(listeners, response);
if (response instanceof ContentResponse)
notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent()));
// TODO: handle callback
notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent()), new Callback.Adapter());
notifySuccess(listeners, response);
}
@ -239,7 +231,8 @@ public class ResponseNotifier
}
notifyHeaders(listeners, response);
if (response instanceof ContentResponse)
notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent()));
// TODO: handle callback
notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent()), new Callback.Adapter());
notifyFailure(listeners, response, failure);
}
@ -248,4 +241,51 @@ public class ResponseNotifier
forwardFailure(listeners, response, responseFailure);
notifyComplete(listeners, new Result(request, requestFailure, response, responseFailure));
}
private class ContentCallback extends IteratingNestedCallback
{
private final List<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);
/**
* @param listener a listener for response content events
* @param listener a consuming listener for response content events
* @return this request object
*/
Request onResponseContent(Response.ContentListener listener);
/**
* @param listener an asynchronous listener for response content events
* @return this request object
*/
Request onResponseContentAsync(Response.AsyncContentListener listener);
/**
* @param listener a listener for response success event
* @return this request object

View File

@ -26,6 +26,7 @@ import org.eclipse.jetty.client.util.BufferingResponseListener;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.util.Callback;
/**
* <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 interface AsyncContentListener extends ResponseListener
{
public void onContent(Response response, ByteBuffer content, Callback callback);
}
/**
* Listener for the response succeeded event.
*/
@ -204,7 +210,7 @@ public interface Response
/**
* Listener for all response events.
*/
public interface Listener extends BeginListener, HeaderListener, HeadersListener, ContentListener, SuccessListener, FailureListener, CompleteListener
public interface Listener extends BeginListener, HeaderListener, HeadersListener, ContentListener, AsyncContentListener, SuccessListener, FailureListener, CompleteListener
{
/**
* An empty implementation of {@link Listener}
@ -232,6 +238,20 @@ public interface Response
{
}
@Override
public void onContent(Response response, ByteBuffer content, Callback callback)
{
try
{
onContent(response, content);
callback.succeeded();
}
catch (Exception x)
{
callback.failed(x);
}
}
@Override
public void onSuccess(Response response)
{

View File

@ -20,10 +20,12 @@ package org.eclipse.jetty.client.http;
import org.eclipse.jetty.client.HttpChannel;
import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpVersion;
public class HttpChannelOverHTTP extends HttpChannel
{
@ -77,10 +79,23 @@ public class HttpChannelOverHTTP extends HttpChannel
public void exchangeTerminated(Result result)
{
super.exchangeTerminated(result);
HttpFields responseHeaders = result.getResponse().getHeaders();
boolean close = result.isFailed() ||
responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()) ||
receiver.isShutdown();
Response response = result.getResponse();
HttpFields responseHeaders = response.getHeaders();
boolean close = result.isFailed() || receiver.isShutdown();
// Only check HTTP headers if there are no failures.
if (!close)
{
if (response.getVersion().compareTo(HttpVersion.HTTP_1_1) < 0)
{
// HTTP 1.0 must close the connection unless it has an explicit keep alive.
close = !responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString());
}
else
{
// HTTP 1.1 or greater closes only if it has an explicit close.
close = responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString());
}
}
if (close)
connection.close();
else

View File

@ -32,12 +32,13 @@ import org.eclipse.jetty.http.HttpParser;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.CompletableCallback;
public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
{
private final HttpParser parser = new HttpParser(this);
private ByteBuffer buffer;
private boolean shutdown;
public HttpReceiverOverHTTP(HttpChannelOverHTTP channel)
@ -58,68 +59,97 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
public void receive()
{
HttpConnectionOverHTTP connection = getHttpConnection();
EndPoint endPoint = connection.getEndPoint();
HttpClient client = getHttpDestination().getHttpClient();
ByteBufferPool bufferPool = client.getByteBufferPool();
ByteBuffer buffer = bufferPool.acquire(client.getResponseBufferSize(), true);
try
{
while (true)
{
// Connection may be closed in a parser callback
if (connection.isClosed())
{
LOG.debug("{} closed", connection);
break;
}
else
{
int read = endPoint.fill(buffer);
if (LOG.isDebugEnabled()) // Avoid boxing of variable 'read'
LOG.debug("Read {} bytes from {}", read, endPoint);
if (read > 0)
{
parse(buffer);
}
else if (read == 0)
{
fillInterested();
break;
}
else
{
shutdown();
break;
}
}
}
}
catch (EofException x)
{
LOG.ignore(x);
failAndClose(x);
}
catch (Exception x)
{
LOG.debug(x);
failAndClose(x);
}
finally
buffer = bufferPool.acquire(client.getResponseBufferSize(), true);
process();
}
private void process()
{
if (readAndParse())
{
HttpClient client = getHttpDestination().getHttpClient();
ByteBufferPool bufferPool = client.getByteBufferPool();
bufferPool.release(buffer);
// Don't linger the buffer around if we are idle.
buffer = null;
}
}
private void parse(ByteBuffer buffer)
private boolean readAndParse()
{
while (buffer.hasRemaining())
parser.parseNext(buffer);
HttpConnectionOverHTTP connection = getHttpConnection();
EndPoint endPoint = connection.getEndPoint();
ByteBuffer buffer = this.buffer;
while (true)
{
try
{
// Connection may be closed in a parser callback.
if (connection.isClosed())
{
if (LOG.isDebugEnabled())
LOG.debug("{} closed", connection);
return true;
}
if (!parse(buffer))
return false;
int read = endPoint.fill(buffer);
// Avoid boxing of variable 'read'
if (LOG.isDebugEnabled())
LOG.debug("Read {} bytes from {}", read, endPoint);
if (read > 0)
{
if (!parse(buffer))
return false;
}
else if (read == 0)
{
fillInterested();
return true;
}
else
{
shutdown();
return true;
}
}
catch (Throwable x)
{
LOG.debug(x);
failAndClose(x);
return true;
}
}
}
/**
* Parses a HTTP response from the given {@code buffer}.
*
* @param buffer the response bytes
* @return true to indicate that the parsing may proceed (for example with another response),
* false to indicate that the parsing should be interrupted (and will be resumed by another thread).
*/
private boolean parse(ByteBuffer buffer)
{
// Must parse even if the buffer is fully consumed, to allow the
// parser to advance from asynchronous content to response complete.
if (parser.parseNext(buffer))
{
// If the parser returns true, we need to differentiate two cases:
// A) the response is completed, so the parser is in START state;
// B) the content is handled asynchronously, so the parser is in CONTENT state.
return parser.isStart();
}
return true;
}
private void fillInterested()
{
// TODO: do we need to call fillInterested() only if we are not failed (or we have an exchange) ?
getHttpChannel().getHttpConnection().fillInterested();
}
@ -195,8 +225,22 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
if (exchange == null)
return false;
responseContent(exchange, buffer);
return false;
CompletableCallback callback = new CompletableCallback()
{
@Override
public void resume()
{
LOG.debug("Content consumed asynchronously, resuming processing");
process();
}
public void abort(Throwable x)
{
failAndClose(x);
}
};
responseContent(exchange, buffer, callback);
return callback.tryComplete();
}
@Override

View File

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

View File

@ -18,11 +18,14 @@
package org.eclipse.jetty.client.util;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Arrays;
import java.util.Locale;
import org.eclipse.jetty.client.api.Response;
@ -30,6 +33,7 @@ import org.eclipse.jetty.client.api.Response.Listener;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.util.BufferUtil;
/**
* <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
{
private final int maxLength;
private volatile byte[] buffer = new byte[0];
private volatile ByteBuffer buffer;
private volatile String encoding;
/**
@ -58,53 +62,57 @@ public abstract class BufferingResponseListener extends Listener.Adapter
*/
public BufferingResponseListener(int maxLength)
{
this.maxLength = maxLength;
this.maxLength=maxLength;
}
@Override
public void onHeaders(Response response)
{
super.onHeaders(response);
HttpFields headers = response.getHeaders();
long length = headers.getLongField(HttpHeader.CONTENT_LENGTH.asString());
if (length > maxLength)
{
response.abort(new IllegalArgumentException("Buffering capacity exceeded"));
return;
}
else
buffer=BufferUtil.allocate((length > 0)?(int)length:1024);
String contentType = headers.get(HttpHeader.CONTENT_TYPE);
if (contentType != null)
{
String contentType = headers.get(HttpHeader.CONTENT_TYPE);
if (contentType != null)
String charset = "charset=";
int index = contentType.toLowerCase(Locale.ENGLISH).indexOf(charset);
if (index > 0)
{
String charset = "charset=";
int index = contentType.toLowerCase(Locale.ENGLISH).indexOf(charset);
String encoding = contentType.substring(index + charset.length());
// Sometimes charsets arrive with an ending semicolon
index = encoding.indexOf(';');
if (index > 0)
{
String encoding = contentType.substring(index + charset.length());
// Sometimes charsets arrive with an ending semicolon
index = encoding.indexOf(';');
if (index > 0)
encoding = encoding.substring(0, index);
this.encoding = encoding;
}
encoding = encoding.substring(0, index);
this.encoding = encoding;
}
}
}
@Override
public void onContent(Response response, ByteBuffer content)
{
long newLength = buffer.length + content.remaining();
if (newLength > maxLength)
{
int length = content.remaining();
if (length>BufferUtil.space(buffer))
{
response.abort(new IllegalArgumentException("Buffering capacity exceeded"));
}
else
{
byte[] newBuffer = new byte[(int)newLength];
System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
content.get(newBuffer, buffer.length, content.remaining());
buffer = newBuffer;
int requiredCapacity = buffer==null?0:buffer.capacity()+length;
if (requiredCapacity>maxLength)
response.abort(new IllegalArgumentException("Buffering capacity exceeded"));
int newCapacity = Math.min(Integer.highestOneBit(requiredCapacity) << 1, maxLength);
buffer = BufferUtil.ensureCapacity(buffer,newCapacity);
}
BufferUtil.append(buffer, content);
}
@Override
@ -121,7 +129,9 @@ public abstract class BufferingResponseListener extends Listener.Adapter
*/
public byte[] getContent()
{
return buffer;
if (buffer==null)
return new byte[0];
return BufferUtil.toArray(buffer);
}
/**
@ -144,14 +154,9 @@ public abstract class BufferingResponseListener extends Listener.Adapter
*/
public String getContentAsString(String encoding)
{
try
{
return new String(getContent(), encoding);
}
catch (UnsupportedEncodingException x)
{
throw new UnsupportedCharsetException(encoding);
}
if (buffer==null)
return null;
return BufferUtil.toString(buffer, Charset.forName(encoding));
}
/**
@ -161,6 +166,19 @@ public abstract class BufferingResponseListener extends Listener.Adapter
*/
public String getContentAsString(Charset encoding)
{
return new String(getContent(), encoding);
if (buffer==null)
return null;
return BufferUtil.toString(buffer, encoding);
}
/* ------------------------------------------------------------ */
/**
* @return Content as InputStream
*/
public InputStream getContentAsInputStream()
{
if (buffer==null)
return new ByteArrayInputStream(new byte[]{});
return new ByteArrayInputStream(buffer.array(), buffer.arrayOffset(), buffer.remaining());
}
}

View File

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

View File

@ -102,6 +102,16 @@ public class InputStreamContentProvider implements ContentProvider
return ByteBuffer.wrap(buffer, offset, length);
}
/**
* Callback method invoked when an exception is thrown while reading
* from the stream.
*
* @param failure the exception thrown while reading from the stream.
*/
protected void onReadFailure(Throwable failure)
{
}
@Override
public Iterator<ByteBuffer> iterator()
{
@ -166,6 +176,7 @@ public class InputStreamContentProvider implements ContentProvider
if (failure == null)
{
failure = x;
onReadFailure(x);
// Signal we have more content to cause a call to
// next() which will throw NoSuchElementException.
hasNext = Boolean.TRUE;

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

View File

@ -24,7 +24,6 @@ import java.util.Arrays;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -37,6 +36,7 @@ import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
import org.eclipse.jetty.client.util.ByteBufferContentProvider;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.annotation.Slow;
@ -489,4 +489,40 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
Assert.assertEquals(0, idleConnections.size());
Assert.assertEquals(0, activeConnections.size());
}
@Test
public void testConnectionForHTTP10ResponseIsRemoved() throws Exception
{
start(new EmptyServerHandler());
String host = "localhost";
int port = connector.getLocalPort();
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
ConnectionPool connectionPool = destination.getConnectionPool();
final BlockingQueue<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>
<argument>jetty.home=${assembly-directory}</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-startd-ini=jsp,jstl,http,https</argument>
<argument>--add-to-start=server,continuation,deploy,websocket,ext,resources,client,annotations,jndi,servlets</argument>
<argument>--add-to-startd=jsp,jstl,http,https</argument>
</arguments>
</configuration>
<goals>

View File

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

View File

@ -16,5 +16,5 @@ jsp-impl/${jsp-impl}-jsp
# default jetty >= 9.2
jsp-impl=apache
# To use an non-jdk compiler for JSP compilation uncomment next line
# To use a non-jdk compiler for JSP compilation when using glassfish uncomment next line
# -Dorg.apache.jasper.compiler.disablejsr199=true

View File

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

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
{
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
HttpConnectionOverFCGI connection = new HttpConnectionOverFCGI(endPoint, destination);
HttpConnectionOverFCGI connection = new HttpConnectionOverFCGI(endPoint, destination, isMultiplexed());
LOG.debug("Created {}", connection);
@SuppressWarnings("unchecked")
Promise<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.HttpDestination;
import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.client.PoolingHttpDestination;
import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
@ -39,10 +38,14 @@ import org.eclipse.jetty.fcgi.FCGI;
import org.eclipse.jetty.fcgi.generator.Flusher;
import org.eclipse.jetty.fcgi.parser.ClientParser;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.CompletableCallback;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -55,14 +58,17 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
private final AtomicBoolean closed = new AtomicBoolean();
private final Flusher flusher;
private final HttpDestination destination;
private final boolean multiplexed;
private final Delegate delegate;
private final ClientParser parser;
private ByteBuffer buffer;
public HttpConnectionOverFCGI(EndPoint endPoint, HttpDestination destination)
public HttpConnectionOverFCGI(EndPoint endPoint, HttpDestination destination, boolean multiplexed)
{
super(endPoint, destination.getHttpClient().getExecutor(), destination.getHttpClient().isDispatchIO());
this.flusher = new Flusher(endPoint);
this.destination = destination;
this.multiplexed = multiplexed;
this.flusher = new Flusher(endPoint);
this.delegate = new Delegate(destination);
this.parser = new ClientParser(new ResponseListener());
requests.addLast(0);
@ -94,53 +100,76 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
@Override
public void onFillable()
{
EndPoint endPoint = getEndPoint();
HttpClient client = destination.getHttpClient();
ByteBufferPool bufferPool = client.getByteBufferPool();
ByteBuffer buffer = bufferPool.acquire(client.getResponseBufferSize(), true);
try
buffer = bufferPool.acquire(client.getResponseBufferSize(), true);
process();
}
private void process()
{
if (readAndParse())
{
while (true)
HttpClient client = destination.getHttpClient();
ByteBufferPool bufferPool = client.getByteBufferPool();
bufferPool.release(buffer);
// Don't linger the buffer around if we are idle.
buffer = null;
}
}
private boolean readAndParse()
{
EndPoint endPoint = getEndPoint();
ByteBuffer buffer = this.buffer;
while (true)
{
try
{
if (!parse(buffer))
return false;
int read = endPoint.fill(buffer);
if (LOG.isDebugEnabled()) // Avoid boxing of variable 'read'
if (LOG.isDebugEnabled()) // Avoid boxing of variable 'read'.
LOG.debug("Read {} bytes from {}", read, endPoint);
if (read > 0)
{
parse(buffer);
if (!parse(buffer))
return false;
}
else if (read == 0)
{
fillInterested();
break;
return true;
}
else
{
shutdown();
break;
return true;
}
}
}
catch (Exception x)
{
LOG.debug(x);
// TODO: fail and close ?
}
finally
{
bufferPool.release(buffer);
catch (Exception x)
{
LOG.debug(x);
close(x);
return false;
}
}
}
private void parse(ByteBuffer buffer)
private boolean parse(ByteBuffer buffer)
{
while (buffer.hasRemaining())
parser.parse(buffer);
return !parser.parse(buffer);
}
private void shutdown()
{
close(new EOFException());
// Close explicitly only if we are idle, since the request may still
// be in progress, otherwise close only if we can fail the responses.
if (channels.isEmpty())
close();
else
failAndClose(new EOFException());
}
@Override
@ -153,13 +182,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
protected void release(HttpChannelOverFCGI channel)
{
channels.remove(channel.getRequest());
if (destination instanceof PoolingHttpDestination)
{
@SuppressWarnings("unchecked")
PoolingHttpDestination<HttpConnectionOverFCGI> fcgiDestination =
(PoolingHttpDestination<HttpConnectionOverFCGI>)destination;
fcgiDestination.release(this);
}
destination.release(this);
}
@Override
@ -168,7 +191,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
close(new AsynchronousCloseException());
}
private void close(Throwable failure)
protected void close(Throwable failure)
{
if (closed.compareAndSet(false, true))
{
@ -184,6 +207,16 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
}
}
protected boolean closeByHTTP(HttpFields fields)
{
if (multiplexed)
return false;
if (!fields.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()))
return false;
close();
return true;
}
protected void abort(Throwable failure)
{
for (HttpChannelOverFCGI channel : channels.values())
@ -195,6 +228,15 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
channels.clear();
}
private void failAndClose(Throwable failure)
{
boolean result = false;
for (HttpChannelOverFCGI channel : channels.values())
result |= channel.responseFailure(failure);
if (result)
close(failure);
}
private int acquireRequest()
{
synchronized (requests)
@ -291,7 +333,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
}
@Override
public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
{
switch (stream)
{
@ -299,7 +341,25 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
{
HttpChannelOverFCGI channel = channels.get(request);
if (channel != null)
channel.content(buffer);
{
CompletableCallback callback = new CompletableCallback()
{
@Override
public void resume()
{
LOG.debug("Content consumed asynchronously, resuming processing");
process();
}
@Override
public void abort(Throwable x)
{
close(x);
}
};
channel.content(buffer, callback);
return callback.tryComplete();
}
else
noChannel(request);
break;
@ -314,6 +374,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
throw new IllegalArgumentException();
}
}
return false;
}
@Override
@ -322,8 +383,23 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
HttpChannelOverFCGI channel = channels.get(request);
if (channel != null)
{
channel.responseSuccess();
releaseRequest(request);
if (channel.responseSuccess())
releaseRequest(request);
}
else
{
noChannel(request);
}
}
@Override
public void onFailure(int request, Throwable failure)
{
HttpChannelOverFCGI channel = channels.get(request);
if (channel != null)
{
if (channel.responseFailure(failure))
releaseRequest(request);
}
else
{

View File

@ -24,6 +24,7 @@ import org.eclipse.jetty.client.HttpChannel;
import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.client.HttpReceiver;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.util.Callback;
public class HttpReceiverOverFCGI extends HttpReceiver
{
@ -51,9 +52,9 @@ public class HttpReceiverOverFCGI extends HttpReceiver
}
@Override
protected boolean responseContent(HttpExchange exchange, ByteBuffer buffer)
protected boolean responseContent(HttpExchange exchange, ByteBuffer buffer, Callback callback)
{
return super.responseContent(exchange, buffer);
return super.responseContent(exchange, buffer, callback);
}
@Override

View File

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

View File

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

View File

@ -86,9 +86,9 @@ public class ClientParser extends Parser
}
@Override
public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
{
listener.onContent(request, stream, buffer);
return listener.onContent(request, stream, buffer);
}
@Override
@ -98,5 +98,13 @@ public class ClientParser extends Parser
for (StreamContentParser streamParser : streamParsers)
streamParser.end(request);
}
@Override
public void onFailure(int request, Throwable failure)
{
listener.onFailure(request, failure);
for (StreamContentParser streamParser : streamParsers)
streamParser.end(request);
}
}
}

View File

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

View File

@ -35,7 +35,7 @@ public class EndRequestContentParser extends ContentParser
}
@Override
public boolean parse(ByteBuffer buffer)
public Result parse(ByteBuffer buffer)
{
while (buffer.hasRemaining())
{
@ -76,7 +76,7 @@ public class EndRequestContentParser extends ContentParser
buffer.position(buffer.position() + 3);
onEnd();
reset();
return true;
return Result.COMPLETE;
}
else
{
@ -92,7 +92,7 @@ public class EndRequestContentParser extends ContentParser
{
onEnd();
reset();
return true;
return Result.COMPLETE;
}
break;
}
@ -102,13 +102,17 @@ public class EndRequestContentParser extends ContentParser
}
}
}
return false;
return Result.PENDING;
}
private void onEnd()
{
// TODO: if protocol != 0, invoke an error callback
listener.onEnd(getRequest());
if (application != 0)
listener.onFailure(getRequest(), new Exception("FastCGI application returned code " + application));
else if (protocol != 0)
listener.onFailure(getRequest(), new Exception("FastCGI server returned code " + protocol));
else
listener.onEnd(getRequest());
}
private void reset()

View File

@ -32,6 +32,12 @@ public class HeaderParser
private int length;
private int padding;
/**
* Parses the bytes in the given {@code buffer} as FastCGI header bytes
*
* @param buffer the bytes to parse
* @return whether there were enough bytes for a FastCGI header
*/
public boolean parse(ByteBuffer buffer)
{
while (buffer.hasRemaining())

View File

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

View File

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

View File

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

View File

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

View File

@ -158,10 +158,11 @@ public class ClientGeneratorTest
ServerParser parser = new ServerParser(new ServerParser.Listener.Adapter()
{
@Override
public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
{
Assert.assertEquals(id, request);
totalLength.addAndGet(buffer.remaining());
return false;
}
@Override

View File

@ -111,16 +111,17 @@ public class ClientParserTest
ByteBufferPool byteBufferPool = new MappedByteBufferPool();
ServerGenerator generator = new ServerGenerator(byteBufferPool);
Generator.Result result1 = generator.generateResponseHeaders(id, 200, "OK", fields, null);
Generator.Result result2 = generator.generateResponseContent(id, null, true, null);
Generator.Result result2 = generator.generateResponseContent(id, null, true, false, null);
final AtomicInteger verifier = new AtomicInteger();
ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter()
{
@Override
public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
{
Assert.assertEquals(id, request);
verifier.addAndGet(2);
return false;
}
@Override
@ -162,17 +163,18 @@ public class ClientParserTest
ByteBufferPool byteBufferPool = new MappedByteBufferPool();
ServerGenerator generator = new ServerGenerator(byteBufferPool);
Generator.Result result1 = generator.generateResponseHeaders(id, code, "OK", fields, null);
Generator.Result result2 = generator.generateResponseContent(id, content, true, null);
Generator.Result result2 = generator.generateResponseContent(id, content, true, false, null);
final AtomicInteger verifier = new AtomicInteger();
ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter()
{
@Override
public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
{
Assert.assertEquals(id, request);
Assert.assertEquals(contentLength, buffer.remaining());
verifier.addAndGet(2);
return false;
}
@Override
@ -214,17 +216,18 @@ public class ClientParserTest
ByteBufferPool byteBufferPool = new MappedByteBufferPool();
ServerGenerator generator = new ServerGenerator(byteBufferPool);
Generator.Result result1 = generator.generateResponseHeaders(id, code, "OK", fields, null);
Generator.Result result2 = generator.generateResponseContent(id, content, true, null);
Generator.Result result2 = generator.generateResponseContent(id, content, true, false, null);
final AtomicInteger totalLength = new AtomicInteger();
final AtomicBoolean verifier = new AtomicBoolean();
ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter()
{
@Override
public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
{
Assert.assertEquals(id, request);
totalLength.addAndGet(buffer.remaining());
return false;
}
@Override

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

View File

@ -151,7 +151,7 @@ public class ServerFCGIConnection extends AbstractConnection
}
@Override
public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
{
HttpChannelOverFCGI channel = channels.get(request);
if (LOG.isDebugEnabled())
@ -161,6 +161,7 @@ public class ServerFCGIConnection extends AbstractConnection
if (channel.content(buffer))
channel.dispatch();
}
return false;
}
@Override
@ -175,5 +176,17 @@ public class ServerFCGIConnection extends AbstractConnection
channel.dispatch();
}
}
@Override
public void onFailure(int request, Throwable failure)
{
HttpChannelOverFCGI channel = channels.remove(request);
if (LOG.isDebugEnabled())
LOG.debug("Request {} failure on {}: {}", request, channel, failure);
if (channel != null)
{
channel.badMessage(400, failure.toString());
}
}
}
}

View File

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

View File

@ -24,11 +24,13 @@ import java.net.URI;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.GZIPOutputStream;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
@ -40,10 +42,13 @@ import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.BytesContentProvider;
import org.eclipse.jetty.client.util.DeferredContentProvider;
import org.eclipse.jetty.client.util.FutureResponseListener;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.IO;
import org.eclipse.jetty.toolchain.test.annotation.Slow;
import org.eclipse.jetty.util.Callback;
import org.junit.Assert;
import org.junit.Test;
@ -551,4 +556,148 @@ public class HttpClientTest extends AbstractHttpClientServerTest
Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
}
@Test
public void testEarlyEOF() throws Exception
{
start(new AbstractHandler()
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
// Promise some content, then flush the headers, then fail to send the content.
response.setContentLength(16);
response.flushBuffer();
throw new NullPointerException("Explicitly thrown by test");
}
});
try
{
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.fail();
}
catch (ExecutionException x)
{
// Expected.
}
}
@Test
public void testSmallContentDelimitedByEOFWithSlowRequest() throws Exception
{
testContentDelimitedByEOFWithSlowRequest(1024);
}
@Test
public void testBigContentDelimitedByEOFWithSlowRequest() throws Exception
{
testContentDelimitedByEOFWithSlowRequest(128 * 1024);
}
private void testContentDelimitedByEOFWithSlowRequest(int length) throws Exception
{
final byte[] data = new byte[length];
new Random().nextBytes(data);
start(new AbstractHandler()
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
response.setHeader("Connection", "close");
response.getOutputStream().write(data);
}
});
DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.wrap(new byte[]{0}));
Request request = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.content(content);
FutureResponseListener listener = new FutureResponseListener(request);
request.send(listener);
// Wait some time to simulate a slow request.
Thread.sleep(1000);
content.close();
ContentResponse response = listener.get(5, TimeUnit.SECONDS);
Assert.assertEquals(200, response.getStatus());
Assert.assertArrayEquals(data, response.getContent());
}
@Test
public void testSmallAsyncContent() throws Exception
{
start(new AbstractHandler()
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
ServletOutputStream output = response.getOutputStream();
output.write(65);
output.flush();
output.write(66);
}
});
final AtomicInteger contentCount = new AtomicInteger();
final AtomicReference<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>
<module>fcgi-client</module>
<!--<module>fcgi-http-client-transport</module>-->
<module>fcgi-server</module>
<!--<module>fcgi-proxy</module>-->
<!--<module>fcgi-distribution</module>-->
</modules>
<dependencies>

View File

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

View File

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

View File

@ -44,6 +44,7 @@ public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint
return AbstractEndPoint.this.needsFill();
}
};
private final WriteFlusher _writeFlusher = new WriteFlusher(this)
{
@Override
@ -144,10 +145,20 @@ public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint
{
boolean output_shutdown=isOutputShutdown();
boolean input_shutdown=isInputShutdown();
_fillInterest.onFail(timeout);
_writeFlusher.onFail(timeout);
if (isOpen() && output_shutdown || input_shutdown)
boolean fillFailed = _fillInterest.onFail(timeout);
boolean writeFailed = _writeFlusher.onFail(timeout);
// If the endpoint is half closed and there was no onFail handling, the close here
// This handles the situation where the connection has completed its close handling
// and the endpoint is half closed, but the other party does not complete the close.
// This perhaps should not check for half closed, however the servlet spec case allows
// for a dispatched servlet or suspended request to extend beyond the connections idle
// time. So if this test would always close an idle endpoint that is not handled, then
// we would need a mode to ignore timeouts for some HTTP states
if (isOpen() && (output_shutdown || input_shutdown) && !(fillFailed || writeFailed))
close();
else
LOG.debug("Ignored idle endpoint {}",this);
}
@Override

View File

@ -93,12 +93,17 @@ public abstract class FillInterest
/* ------------------------------------------------------------ */
/** Call to signal a failure to a registered interest
* @return true if the cause was passed to a {@link Callback} instance
*/
public void onFail(Throwable cause)
public boolean onFail(Throwable cause)
{
Callback callback=_interested.get();
if (callback!=null && _interested.compareAndSet(callback,null))
{
callback.failed(cause);
return true;
}
return false;
}
/* ------------------------------------------------------------ */

View File

@ -19,11 +19,13 @@
package org.eclipse.jetty.io;
import java.io.IOException;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.List;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.Scheduler;
@ -57,9 +59,11 @@ public class NetworkTrafficSelectChannelEndPoint extends SelectChannelEndPoint
if (b.hasRemaining())
{
int position = b.position();
ByteBuffer view=b.slice();
flushed&=super.flush(b);
int l=b.position()-position;
notifyOutgoing(b, position, l);
view.limit(view.position()+l);
notifyOutgoing(view);
if (!flushed)
break;
}
@ -67,9 +71,12 @@ public class NetworkTrafficSelectChannelEndPoint extends SelectChannelEndPoint
return flushed;
}
public void notifyOpened()
@Override
public void onOpen()
{
super.onOpen();
if (listeners != null && !listeners.isEmpty())
{
for (NetworkTrafficListener listener : listeners)
@ -86,6 +93,27 @@ public class NetworkTrafficSelectChannelEndPoint extends SelectChannelEndPoint
}
}
@Override
public void onClose()
{
super.onClose();
if (listeners != null && !listeners.isEmpty())
{
for (NetworkTrafficListener listener : listeners)
{
try
{
listener.closed(getSocket());
}
catch (Exception x)
{
LOG.warn(x);
}
}
}
}
public void notifyIncoming(ByteBuffer buffer, int read)
{
if (listeners != null && !listeners.isEmpty() && read > 0)
@ -105,18 +133,16 @@ public class NetworkTrafficSelectChannelEndPoint extends SelectChannelEndPoint
}
}
public void notifyOutgoing(ByteBuffer buffer, int position, int written)
public void notifyOutgoing(ByteBuffer view)
{
if (listeners != null && !listeners.isEmpty() && written > 0)
if (listeners != null && !listeners.isEmpty() && view.hasRemaining())
{
Socket socket=getSocket();
for (NetworkTrafficListener listener : listeners)
{
try
{
ByteBuffer view = buffer.slice();
view.position(position);
view.limit(position + written);
listener.outgoing(getSocket(), view);
listener.outgoing(socket, view);
}
catch (Exception x)
{
@ -126,21 +152,4 @@ public class NetworkTrafficSelectChannelEndPoint extends SelectChannelEndPoint
}
}
public void notifyClosed()
{
if (listeners != null && !listeners.isEmpty())
{
for (NetworkTrafficListener listener : listeners)
{
try
{
listener.closed(getSocket());
}
catch (Exception x)
{
LOG.warn(x);
}
}
}
}
}

View File

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

View File

@ -18,8 +18,7 @@
package org.eclipse.jetty.io;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@ -129,6 +128,7 @@ public class ByteArrayEndPointTest
assertEquals(true,endp.flush(BufferUtil.EMPTY_BUFFER,BufferUtil.toBuffer(" and"),BufferUtil.toBuffer(" more")));
assertEquals("some output some more and more",endp.getOutputString());
endp.close();
}
@Test
@ -147,6 +147,7 @@ public class ByteArrayEndPointTest
assertEquals(true,endp.flush(data));
assertEquals("data.",BufferUtil.toString(endp.takeOutput()));
endp.close();
}
@ -234,6 +235,27 @@ public class ByteArrayEndPointTest
assertTrue(fcb.isDone());
assertEquals(null, fcb.get());
assertEquals(" more.", endp.getOutputString());
endp.close();
}
/**
* Simulate AbstractConnection.ReadCallback.failed()
*/
public static class Closer extends FutureCallback
{
private EndPoint endp;
public Closer(EndPoint endp)
{
this.endp = endp;
}
@Override
public void failed(Throwable cause)
{
endp.close();
super.failed(cause);
}
}
@Slow
@ -275,7 +297,7 @@ public class ByteArrayEndPointTest
assertThat(t.getCause(), instanceOf(TimeoutException.class));
}
assertThat(System.currentTimeMillis() - start, greaterThan(idleTimeout / 2));
assertTrue(endp.isOpen());
assertThat("Endpoint open", endp.isOpen(), is(true));
// We need to delay the write timeout test below from the read timeout test above.
// The reason is that the scheduler thread that fails the endPoint WriteFlusher
@ -298,17 +320,19 @@ public class ByteArrayEndPointTest
assertThat(t.getCause(), instanceOf(TimeoutException.class));
}
assertThat(System.currentTimeMillis() - start, greaterThan(idleTimeout / 2));
assertTrue(endp.isOpen());
assertThat("Endpoint open", endp.isOpen(), is(true));
// Still no idle close
Thread.sleep(idleTimeout * 2);
assertTrue(endp.isOpen());
endp.fillInterested(new Closer(endp));
// Still no idle close (wait half the time)
Thread.sleep(idleTimeout / 2);
assertThat("Endpoint open", endp.isOpen(), is(true));
// shutdown out
endp.shutdownOutput();
// idle close
// idle close (wait double the time)
Thread.sleep(idleTimeout * 2);
assertFalse(endp.isOpen());
assertThat("Endpoint open", endp.isOpen(), is(false));
}
}

View File

@ -112,6 +112,7 @@
javax.servlet.http;version="[3.1,3.2)",
javax.transaction;version="1.1.0";resolution:=optional,
javax.transaction.xa;version="1.1.0";resolution:=optional,
org.objectweb.asm;version=4;resolution:=optional,
org.eclipse.jetty.annotations;version="9.0.0";resolution:=optional,
org.osgi.framework,
org.osgi.service.cm;version="1.2.0",

View File

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

View File

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

View File

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

View File

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

View File

@ -14,102 +14,36 @@
<bundle-symbolic-name>${project.groupId}.boot.test.spdy</bundle-symbolic-name>
<jetty-orbit-url>http://download.eclipse.org/jetty/orbit/</jetty-orbit-url>
<assembly-directory>target/distribution</assembly-directory>
<exam.version>2.6.0</exam.version>
<url.version>1.4.0</url.version>
<paxswissbox.version>1.5.1</paxswissbox.version>
<felixversion>4.0.3</felixversion>
<exam.version>3.4.0</exam.version>
<url.version>1.5.2</url.version>
<injection.bundle.version>1.0</injection.bundle.version>
<runner.version>1.7.6</runner.version>
<runner.version>1.8.5</runner.version>
</properties>
<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>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam</artifactId>
<version>${exam.version}</version>
<scope>test</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.ops4j.pax.exam</groupId>
<artifactId>pax-exam-inject</artifactId>
<version>${exam.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>
<!-- Don't use the native container for now. Observed limitations:
- 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
<!-- use the forked container so we can pass it system properties eg for npn/alpn -->
<dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam-container-forked</artifactId>
<version>${exam.version}</version>
<scope>test</scope>
</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>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam-junit4</artifactId>
@ -134,28 +68,81 @@
<version>${url.version}</version>
<scope>test</scope>
</dependency>
<!-- OSGi R4 frameworks -->
<!--
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.framework</artifactId>
<version>${felixversion}</version>
<version>4.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam-testforge</artifactId>
<version>${exam.version}</version>
<scope>test</scope>
</dependency>
<!-- For sane logging -->
<!--
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.1</version>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.enterprise</artifactId>
<version>5.0.0</version>
<scope>test</scope>
</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>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
@ -167,47 +154,34 @@
<version>1.1.1</version>
<scope>test</scope>
</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>
<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>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>4.1</version>
</dependency>
<dependency>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-commons</artifactId>
<version>4.1</version>
</dependency>
<dependency>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-tree</artifactId>
<version>4.1</version>
</dependency>
</dependency>
<!-- Jetty Deps -->
@ -386,7 +360,9 @@
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<!-- Eclipse OSGi Deps -->
<!--
<dependency>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi</artifactId>
@ -398,7 +374,6 @@
<scope>runtime</scope>
<exclusions>
<exclusion>
<!-- we use the servlet jar from orbit -->
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</exclusion>
@ -409,6 +384,9 @@
<artifactId>servlet</artifactId>
<scope>runtime</scope>
</dependency>
-->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<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.osgi.boot.OSGiServerConstants;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.Configuration;
import org.ops4j.pax.exam.junit.JUnit4TestRunner;
import org.ops4j.pax.exam.Configuration;
import org.ops4j.pax.exam.junit.PaxExam;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
@ -55,12 +56,11 @@ import org.osgi.framework.ServiceReference;
* Tests the ServiceContextProvider.
*
*/
@RunWith(JUnit4TestRunner.class)
@RunWith(PaxExam.class)
public class TestJettyOSGiBootContextAsService
{
private static final boolean LOGGING_ENABLED = false;
private static final String LOG_LEVEL = "WARN";
private static final boolean REMOTE_DEBUGGING = false;
@Inject
BundleContext bundleContext = null;
@ -69,7 +69,6 @@ public class TestJettyOSGiBootContextAsService
public static Option[] configure()
{
ArrayList<Option> options = new ArrayList<Option>();
TestOSGiUtil.addMoreOSGiContainers(options);
options.add(CoreOptions.junitBundles());
options.addAll(configureJettyHomeAndPort("jetty-selector.xml"));
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*"));
@ -79,22 +78,10 @@ public class TestJettyOSGiBootContextAsService
// to pick up and deploy
options.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("test-jetty-osgi-context").versionAsInProject().start());
String logLevel = "WARN";
// Enable Logging
if (LOGGING_ENABLED)
logLevel = "INFO";
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))));
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()]);
}
@ -117,6 +104,7 @@ public class TestJettyOSGiBootContextAsService
return options;
}
@Ignore
@Test
public void assertAllBundlesActiveOrResolved()
{
@ -148,14 +136,6 @@ public class TestJettyOSGiBootContextAsService
ServiceReference[] refs = bundleContext.getServiceReferences(ContextHandler.class.getName(), null);
assertNotNull(refs);
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]);
assertEquals("/acme", ch.getContextPath());

View File

@ -19,19 +19,23 @@
package org.eclipse.jetty.osgi.test;
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.Arrays;
import java.util.List;
import javax.inject.Inject;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.MavenUtils;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.Configuration;
import org.ops4j.pax.exam.junit.JUnit4TestRunner;
import org.ops4j.pax.exam.Configuration;
import org.ops4j.pax.exam.junit.PaxExam;
import org.ops4j.pax.exam.options.MavenUrlReference.VersionResolver;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
@ -40,27 +44,30 @@ import org.osgi.framework.BundleContext;
/**
* Default OSGi setup integration test
*/
@RunWith( JUnit4TestRunner.class )
@RunWith( PaxExam.class )
public class TestJettyOSGiBootCore
{
private static final String LOG_LEVEL = "WARN";
public static int DEFAULT_JETTY_HTTP_PORT = 9876;
@Inject
private BundleContext bundleContext;
@Configuration
public Option[] config()
{
VersionResolver resolver = MavenUtils.asInProject();
ArrayList<Option> options = new ArrayList<Option>();
TestOSGiUtil.addMoreOSGiContainers(options);
options.addAll(provisionCoreJetty());
options.add(CoreOptions.junitBundles());
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()]);
}
public static List<Option> provisionCoreJetty()
{
List<Option> res = new ArrayList<Option>();
@ -75,12 +82,21 @@ public class TestJettyOSGiBootCore
public static List<Option> coreJettyDependencies()
{
List<Option> res = new ArrayList<Option>();
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-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.spifly" ).artifactId( "org.apache.aries.spifly.dynamic.bundle" ).version("1.0.0").start());
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-commons" ).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.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.annotation" ).artifactId( "javax.annotation-api" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.apache.geronimo.specs" ).artifactId( "geronimo-jta_1.1_spec" ).version("1.1.1").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-jndi" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-plus" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-annotations" ).versionAsInProject().start());
if (version < 1.8)
{
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-common" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty.websocket" ).artifactId( "websocket-servlet" ).versionAsInProject().noStart());
@ -124,9 +143,12 @@ public class TestJettyOSGiBootCore
return res;
}
@Ignore
@Test
public void assertAllBundlesActiveOrResolved() throws Exception
{
TestOSGiUtil.debugBundles(bundleContext);
TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext);
}

View File

@ -36,18 +36,18 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.Configuration;
import org.ops4j.pax.exam.junit.JUnit4TestRunner;
import org.ops4j.pax.exam.Configuration;
import org.ops4j.pax.exam.junit.PaxExam;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
/**
* SPDY setup.
*/
@RunWith(JUnit4TestRunner.class)
@RunWith(PaxExam.class)
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";
@ -60,31 +60,14 @@ public class TestJettyOSGiBootSpdy
public Option[] config()
{
ArrayList<Option> options = new ArrayList<Option>();
TestOSGiUtil.addMoreOSGiContainers(options);
options.addAll(TestJettyOSGiBootWithJsp.configureJettyHomeAndPort("jetty-spdy.xml"));
options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
options.addAll(spdyJettyDependencies());
options.add(CoreOptions.junitBundles());
options.addAll(TestJettyOSGiBootCore.httpServiceJetty());
String logLevel = "WARN";
// 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))));
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()]);
}
@ -101,7 +84,7 @@ public class TestJettyOSGiBootSpdy
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.spdy").artifactId("spdy-client").versionAsInProject().noStart());
@ -112,7 +95,6 @@ public class TestJettyOSGiBootSpdy
return res;
}
@Ignore
@Test
public void checkALPNBootOnBootstrapClasspath() throws Exception
{
@ -121,13 +103,11 @@ public class TestJettyOSGiBootSpdy
Assert.assertNull(alpn.getClassLoader());
}
@Ignore
@Test
public void assertAllBundlesActiveOrResolved()
{
Bundle b = TestOSGiUtil.getBundle(bundleContext, "org.eclipse.jetty.spdy.client");
TestOSGiUtil.diagnoseNonActiveOrNonResolvedBundle(b);
b = TestOSGiUtil.getBundle(bundleContext, "org.eclipse.jetty.osgi.boot");
TestOSGiUtil.diagnoseNonActiveOrNonResolvedBundle(b);
TestOSGiUtil.debugBundles(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.server.handler.ContextHandler;
import org.eclipse.jetty.webapp.WebAppContext;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.Configuration;
import org.ops4j.pax.exam.junit.JUnit4TestRunner;
import org.ops4j.pax.exam.Configuration;
import org.ops4j.pax.exam.junit.PaxExam;
import org.osgi.framework.BundleContext;
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
* top of this.
*/
@RunWith(JUnit4TestRunner.class)
@RunWith(PaxExam.class)
public class TestJettyOSGiBootWebAppAsService
{
private static final boolean LOGGING_ENABLED = false;
private static final boolean REMOTE_DEBUGGING = false;
private static final String LOG_LEVEL = "WARN";
@Inject
BundleContext bundleContext = null;
@ -72,8 +71,6 @@ public class TestJettyOSGiBootWebAppAsService
public static Option[] configure()
{
ArrayList<Option> options = new ArrayList<Option>();
TestOSGiUtil.addMoreOSGiContainers(options);
options.add(CoreOptions.junitBundles());
options.addAll(configureJettyHomeAndPort("jetty-selector.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"));
options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
String logLevel = "WARN";
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))));
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))));
options.addAll(jspDependencies());
return options.toArray(new Option[options.size()]);
@ -139,6 +126,7 @@ public class TestJettyOSGiBootWebAppAsService
return res;
}
@Ignore
@Test
public void assertAllBundlesActiveOrResolved()
{

View File

@ -38,25 +38,21 @@ import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.Configuration;
import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.Configuration;
import org.ops4j.pax.exam.junit.JUnit4TestRunner;
import org.osgi.framework.Bundle;
import org.ops4j.pax.exam.junit.PaxExam;
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
* httpservice web-bundle. Then make sure we can deploy an OSGi service on the
* top of this.
*/
@RunWith(JUnit4TestRunner.class)
@RunWith(PaxExam.class)
public class TestJettyOSGiBootWithJsp
{
private static final boolean LOGGING_ENABLED = false;
private static final boolean REMOTE_DEBUGGING = false;
private static final String LOG_LEVEL = "WARN";
@Inject
BundleContext bundleContext = null;
@ -66,9 +62,6 @@ public class TestJettyOSGiBootWithJsp
{
ArrayList<Option> options = new ArrayList<Option>();
TestOSGiUtil.addMoreOSGiContainers(options);
options.add(CoreOptions.junitBundles());
options.addAll(configureJettyHomeAndPort("jetty-selector.xml"));
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"));
options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
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))));
String logLevel = "WARN";
// 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());
// 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()]);
}
@ -157,7 +122,7 @@ public class TestJettyOSGiBootWithJsp
return res;
}
@Ignore
@Test
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.util.ssl.SslContextFactory;
import org.junit.Assert;
import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.Option;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
@ -46,22 +45,6 @@ import org.osgi.service.http.HttpService;
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)
{
Map<String,Bundle> _bundles = new HashMap<String, Bundle>();
@ -146,7 +129,7 @@ public class TestOSGiUtil
for (Bundle b : bundleContext.getBundles())
{
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.ConcurrentMap;
import java.util.concurrent.Executor;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@ -432,8 +431,7 @@ public class ConnectHandler extends HandlerWrapper
protected class ConnectManager extends SelectorManager
{
private ConnectManager(Executor executor, Scheduler scheduler, int selectors)
protected ConnectManager(Executor executor, Scheduler scheduler, int selectors)
{
super(executor, scheduler, selectors);
}
@ -455,10 +453,16 @@ public class ConnectHandler extends HandlerWrapper
}
@Override
protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment)
protected void connectionFailed(SocketChannel channel, final Throwable ex, final Object attachment)
{
ConnectContext connectContext = (ConnectContext)attachment;
onConnectFailure(connectContext.request, connectContext.response, connectContext.asyncContext, ex);
getExecutor().execute(new Runnable()
{
public void run()
{
ConnectContext connectContext = (ConnectContext)attachment;
onConnectFailure(connectContext.request, connectContext.response, connectContext.asyncContext, ex);
}
});
}
}
@ -518,8 +522,14 @@ public class ConnectHandler extends HandlerWrapper
public void onOpen()
{
super.onOpen();
onConnectSuccess(connectContext, this);
fillInterested();
getExecutor().execute(new Runnable()
{
public void run()
{
onConnectSuccess(connectContext, UpstreamConnection.this);
fillInterested();
}
});
}
@Override

View File

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

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;
import static java.nio.file.StandardOpenOption.CREATE;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@ -30,6 +28,8 @@ import java.net.HttpCookie;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -39,7 +39,6 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.zip.GZIPOutputStream;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
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.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.AdvancedRunner;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.TestTracker;
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.log.Log;
import org.eclipse.jetty.util.log.StdErrLog;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.hamcrest.Matchers;
import org.junit.After;
@ -78,11 +74,22 @@ import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(AdvancedRunner.class)
@RunWith(Parameterized.class)
public class ProxyServletTest
{
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;
@ -92,15 +99,25 @@ public class ProxyServletTest
private Server server;
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();
proxyConnector = new ServerConnector(proxy);
proxy.addConnector(proxyConnector);
ServletContextHandler proxyCtx = new ServletContextHandler(proxy, "/", true, false);
this.proxyServlet = proxyServlet;
ServletHolder proxyServletHolder = new ServletHolder(proxyServlet);
proxyServletHolder.setInitParameters(initParams);
proxyCtx.addServlet(proxyServletHolder, "/*");
proxy.start();
@ -145,7 +162,7 @@ public class ProxyServletTest
@Test
public void testProxyDown() throws Exception
{
prepareProxy(new ProxyServlet());
prepareProxy();
prepareServer(new EmptyHttpServlet());
// 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
public void testProxyWithoutContent() throws Exception
{
prepareProxy(new ProxyServlet());
prepareProxy();
prepareServer(new HttpServlet()
{
@Override
@ -234,7 +206,7 @@ public class ProxyServletTest
@Test
public void testProxyWithResponseContent() throws Exception
{
prepareProxy(new ProxyServlet());
prepareProxy();
HttpClient result = new HttpClient();
result.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyConnector.getLocalPort()));
@ -259,16 +231,16 @@ 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())
.timeout(5, TimeUnit.SECONDS)
.send();
}
for ( int i = 0; i < 10; ++i )
for (int i = 0; i < 10; ++i)
{
Assert.assertEquals(200, responses[i].getStatus());
Assert.assertTrue(responses[i].getHeaders().containsKey(PROXIED_HEADER));
@ -279,7 +251,7 @@ public class ProxyServletTest
@Test
public void testProxyWithRequestContentAndResponseContent() throws Exception
{
prepareProxy(new ProxyServlet());
prepareProxy();
prepareServer(new HttpServlet()
{
@Override
@ -307,7 +279,7 @@ public class ProxyServletTest
@Test
public void testProxyWithBigRequestContentIgnored() throws Exception
{
prepareProxy(new ProxyServlet());
prepareProxy();
prepareServer(new HttpServlet()
{
@Override
@ -335,7 +307,7 @@ public class ProxyServletTest
final byte[] content = new byte[128 * 1024];
new Random().nextBytes(content);
prepareProxy(new ProxyServlet());
prepareProxy();
prepareServer(new HttpServlet()
{
@Override
@ -359,7 +331,7 @@ public class ProxyServletTest
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.method(HttpMethod.POST)
.content(new BytesContentProvider(content))
.timeout(5, TimeUnit.SECONDS)
.timeout(555, TimeUnit.SECONDS)
.send();
Assert.assertEquals(200, response.getStatus());
@ -370,7 +342,7 @@ public class ProxyServletTest
@Test
public void testProxyWithBigResponseContentWithSlowReader() throws Exception
{
prepareProxy(new ProxyServlet());
prepareProxy();
// Create a 6 MiB file
final int length = 6 * 1024;
@ -379,7 +351,7 @@ public class ProxyServletTest
final Path temp = Files.createTempFile(targetTestsDir, "test_", null);
byte[] kb = new byte[1024];
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)
output.write(kb);
@ -431,7 +403,7 @@ public class ProxyServletTest
@Test
public void testProxyWithQueryString() throws Exception
{
prepareProxy(new ProxyServlet());
prepareProxy();
String query = "a=1&b=%E2%82%AC";
prepareServer(new HttpServlet()
{
@ -453,7 +425,7 @@ public class ProxyServletTest
@Test
public void testProxyLongPoll() throws Exception
{
prepareProxy(new ProxyServlet());
prepareProxy();
final long timeout = 1000;
prepareServer(new HttpServlet()
{
@ -500,73 +472,10 @@ public class ProxyServletTest
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
public void testProxyXForwardedHostHeaderIsPresent() throws Exception
{
prepareProxy(new ProxyServlet());
prepareProxy();
prepareServer(new HttpServlet()
{
@Override
@ -587,7 +496,7 @@ public class ProxyServletTest
@Test
public void testProxyWhiteList() throws Exception
{
prepareProxy(new ProxyServlet());
prepareProxy();
prepareServer(new EmptyHttpServlet());
int port = serverConnector.getLocalPort();
proxyServlet.getWhiteListHosts().add("127.0.0.1:" + port);
@ -608,7 +517,7 @@ public class ProxyServletTest
@Test
public void testProxyBlackList() throws Exception
{
prepareProxy(new ProxyServlet());
prepareProxy();
prepareServer(new EmptyHttpServlet());
int port = serverConnector.getLocalPort();
proxyServlet.getBlackListHosts().add("localhost:" + port);
@ -629,7 +538,7 @@ public class ProxyServletTest
@Test
public void testClientExcludedHosts() throws Exception
{
prepareProxy(new ProxyServlet());
prepareProxy();
prepareServer(new HttpServlet()
{
@Override
@ -684,8 +593,11 @@ public class ProxyServletTest
});
String proxyTo = "http://localhost:" + serverConnector.getLocalPort();
ProxyServlet.Transparent proxyServlet = new ProxyServlet.Transparent(proxyTo, prefix);
prepareProxy(proxyServlet);
proxyServlet = new ProxyServlet.Transparent();
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
ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort())
@ -723,8 +635,11 @@ public class ProxyServletTest
String proxyTo = "http://localhost:" + serverConnector.getLocalPort();
String prefix = "/proxy";
ProxyServlet.Transparent proxyServlet = new ProxyServlet.Transparent(proxyTo, prefix);
prepareProxy(proxyServlet);
proxyServlet = new ProxyServlet.Transparent();
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
ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort())
@ -735,6 +650,36 @@ public class ProxyServletTest
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
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
// subclassed enough to write your own caching servlet
final String cacheHeader = "X-Cached";
ProxyServlet proxyServlet = new ProxyServlet()
proxyServlet = new ProxyServlet()
{
private Map<String, ContentResponse> cache = new HashMap<>();
private Map<String, ByteArrayOutputStream> temp = new HashMap<>();
@ -777,7 +722,7 @@ public class ProxyServletTest
}
@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
ByteArrayOutputStream baos = temp.get(request.getRequestURI());
@ -787,7 +732,7 @@ public class ProxyServletTest
temp.put(request.getRequestURI(), baos);
}
baos.write(buffer, offset, length);
super.onResponseContent(request, response, proxyResponse, buffer, offset, length);
super.onResponseContent(request, response, proxyResponse, buffer, offset, length, callback);
}
@Override
@ -799,7 +744,7 @@ public class ProxyServletTest
super.onResponseSuccess(request, response, proxyResponse);
}
};
prepareProxy(proxyServlet);
prepareProxy();
// First request
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
@ -821,7 +766,7 @@ public class ProxyServletTest
@Test
public void testRedirectsAreProxied() throws Exception
{
prepareProxy(new ProxyServlet());
prepareProxy();
prepareServer(new HttpServlet()
{
@Override
@ -846,7 +791,7 @@ public class ProxyServletTest
public void testGZIPContentIsProxied() throws Exception
{
final byte[] content = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
prepareProxy(new ProxyServlet());
prepareProxy();
prepareServer(new HttpServlet()
{
@Override
@ -871,21 +816,23 @@ public class ProxyServletTest
}
@Test(expected = TimeoutException.class)
public void shouldHandleWrongContentLength() throws Exception
public void testWrongContentLength() throws Exception
{
prepareProxy(new ProxyServlet());
prepareServer(new HttpServlet() {
prepareProxy();
prepareServer(new HttpServlet()
{
@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");
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);
}
});
client.newRequest("localhost", serverConnector.getLocalPort())
.timeout(5, TimeUnit.SECONDS)
.timeout(1, TimeUnit.SECONDS)
.send();
Assert.fail();
@ -895,7 +842,7 @@ public class ProxyServletTest
public void testCookiesFromDifferentClientsAreNotMixed() throws Exception
{
final String name = "biscuit";
prepareProxy(new ProxyServlet());
prepareProxy();
prepareServer(new HttpServlet()
{
@Override
@ -953,17 +900,4 @@ public class ProxyServletTest
}
// 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.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.Response;
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.Logger;
import org.eclipse.jetty.util.security.Constraint;
@ -820,7 +821,9 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
if (paths != null && !paths.isEmpty())
{
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 false;

View File

@ -133,6 +133,7 @@ public class HashLoginService extends MappedLoginService implements UserListener
/**
* @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
*/
@Override
protected void doStart() throws Exception
{
super.doStart();
@ -154,6 +155,7 @@ public class HashLoginService extends MappedLoginService implements UserListener
/**
* @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
*/
@Override
protected void doStop() throws Exception
{
super.doStop();
@ -163,6 +165,7 @@ public class HashLoginService extends MappedLoginService implements UserListener
}
/* ------------------------------------------------------------ */
@Override
public void update(String userName, Credential credential, String[] roleArray)
{
if (LOG.isDebugEnabled())
@ -171,6 +174,7 @@ public class HashLoginService extends MappedLoginService implements UserListener
}
/* ------------------------------------------------------------ */
@Override
public void remove(String userName)
{
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.HandlerWrapper;
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.Logger;
@ -74,8 +75,6 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
private LoginService _loginService;
private IdentityService _identityService;
private boolean _renewSession=true;
private boolean _discoveredIdentityService = false;
private boolean _discoveredLoginService = false;
/* ------------------------------------------------------------ */
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);
LoginService service = null;
String realm=getRealmName();
if (realm!=null)
{
for (LoginService service : list)
if (service.getName()!=null && service.getName().equals(realm))
return service;
for (LoginService s : list)
if (s.getName()!=null && s.getName().equals(realm))
{
service=s;
break;
}
}
else if (list.size()==1)
return list.iterator().next();
return null;
service = list.iterator().next();
return service;
}
/* ------------------------------------------------------------ */
@ -342,9 +345,10 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
if (_loginService==null)
{
setLoginService(findLoginService());
_discoveredLoginService = true;
if (_loginService!=null)
unmanage(_loginService);
}
if (_identityService==null)
{
if (_loginService!=null)
@ -353,10 +357,16 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
if (_identityService==null)
setIdentityService(findIdentityService());
if (_identityService==null && _realmName!=null)
setIdentityService(new DefaultIdentityService());
_discoveredIdentityService = true;
if (_identityService==null)
{
if (_realmName!=null)
{
setIdentityService(new DefaultIdentityService());
manage(_identityService);
}
}
else
unmanage(_identityService);
}
if (_loginService!=null)
@ -387,17 +397,16 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
protected void doStop() throws Exception
{
//if we discovered the services (rather than had them explicitly configured), remove them.
if (_discoveredIdentityService)
if (!isManaged(_identityService))
{
removeBean(_identityService);
_identityService = null;
_identityService = null;
}
if (_discoveredLoginService)
if (!isManaged(_loginService))
{
removeBean(_loginService);
_loginService = null;
_loginService=null;
}
super.doStop();
@ -427,6 +436,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
/**
* @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
*/
@Override
public boolean isSessionRenewedOnAuthentication()
{
return _renewSession;
@ -473,7 +483,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
{
if (!baseRequest.isHandled())
{
response.sendError(Response.SC_FORBIDDEN);
response.sendError(HttpServletResponse.SC_FORBIDDEN);
baseRequest.setHandled(true);
}
return;
@ -488,7 +498,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
LOG.warn("No authenticator for: "+roleInfo);
if (!baseRequest.isHandled())
{
response.sendError(Response.SC_FORBIDDEN);
response.sendError(HttpServletResponse.SC_FORBIDDEN);
baseRequest.setHandled(true);
}
return;
@ -524,7 +534,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
boolean authorized=checkWebResourcePermissions(pathInContext, baseRequest, base_response, roleInfo, userAuth.getUserIdentity());
if (!authorized)
{
response.sendError(Response.SC_FORBIDDEN, "!role");
response.sendError(HttpServletResponse.SC_FORBIDDEN, "!role");
baseRequest.setHandled(true);
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
// from AuthException
response.sendError(Response.SC_INTERNAL_SERVER_ERROR, e.getMessage());
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
}
finally
{
@ -634,6 +644,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
/* ------------------------------------------------------------ */
public class NotChecked implements Principal
{
@Override
public String getName()
{
return null;
@ -656,6 +667,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
/* ------------------------------------------------------------ */
public static final Principal __NO_USER = new Principal()
{
@Override
public String getName()
{
return null;
@ -680,6 +692,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
*/
public static final Principal __NOBODY = new Principal()
{
@Override
public String getName()
{
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 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()
{
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.
*
* <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
{
@ -57,16 +42,22 @@ public class HttpChannelState
private final static long DEFAULT_TIMEOUT=30000L;
/** The dispatched state of the HttpChannel, used to control the overall livecycle
*/
public enum State
{
IDLE, // Idle request
DISPATCHED, // Request dispatched to filter/servlet
ASYNCWAIT, // Suspended and parked
ASYNCIO, // Has been dispatched for async IO
COMPLETING, // Request is completable
COMPLETED // Request is complete
IDLE, // Idle request
DISPATCHED, // Request dispatched to filter/servlet
ASYNC_WAIT, // Suspended and parked
ASYNC_WOKEN, // A thread has been dispatch to handle from ASYNCWAIT
ASYNC_IO, // Has been dispatched for async IO
COMPLETING, // Request is completable
COMPLETED // Request is complete
}
/**
* The actions to take as the channel moves from state to state.
*/
public enum Action
{
REQUEST_DISPATCH, // handle a normal request dispatch
@ -78,6 +69,11 @@ public class HttpChannelState
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
{
STARTED,
@ -188,16 +184,16 @@ public class HttpChannelState
case COMPLETED:
return Action.WAIT;
case ASYNCWAIT:
case ASYNC_WOKEN:
if (_asyncRead)
{
_state=State.ASYNCIO;
_state=State.ASYNC_IO;
_asyncRead=false;
return Action.READ_CALLBACK;
}
if (_asyncWrite)
{
_state=State.ASYNCIO;
_state=State.ASYNC_IO;
_asyncWrite=false;
return Action.WRITE_CALLBACK;
}
@ -221,6 +217,7 @@ public class HttpChannelState
_async=null;
return Action.ASYNC_EXPIRED;
case STARTED:
// TODO
if (DEBUG)
LOG.debug("TODO Fix this double dispatch",new IllegalStateException(this
.getStatusString()));
@ -293,7 +290,7 @@ public class HttpChannelState
switch(_state)
{
case DISPATCHED:
case ASYNCIO:
case ASYNC_IO:
break;
default:
throw new IllegalStateException(this.getStatusString());
@ -301,7 +298,7 @@ public class HttpChannelState
if (_asyncRead)
{
_state=State.ASYNCIO;
_state=State.ASYNC_IO;
_asyncRead=false;
return Action.READ_CALLBACK;
}
@ -309,7 +306,7 @@ public class HttpChannelState
if (_asyncWrite)
{
_asyncWrite=false;
_state=State.ASYNCIO;
_state=State.ASYNC_IO;
return Action.WRITE_CALLBACK;
}
@ -333,7 +330,7 @@ public class HttpChannelState
case EXPIRING:
case STARTED:
scheduleTimeout();
_state=State.ASYNCWAIT;
_state=State.ASYNC_WAIT;
return Action.WAIT;
}
}
@ -360,11 +357,19 @@ public class HttpChannelState
switch(_state)
{
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;
break;
default:
dispatch=true;
LOG.warn("async dispatched when complete {}",this);
dispatch=false;
break;
}
}
@ -411,8 +416,11 @@ public class HttpChannelState
if (_async==Async.EXPIRING)
{
_async=Async.EXPIRED;
if (_state==State.ASYNCWAIT)
if (_state==State.ASYNC_WAIT)
{
_state=State.ASYNC_WOKEN;
dispatch=true;
}
}
}
@ -423,13 +431,17 @@ public class HttpChannelState
public void complete()
{
// just like resume, except don't set _dispatched=true;
boolean handle;
boolean handle=false;
synchronized (this)
{
if (_async!=Async.STARTED && _async!=Async.EXPIRING)
throw new IllegalStateException(this.getStatusString());
_async=Async.COMPLETE;
handle=_state==State.ASYNCWAIT;
if (_state==State.ASYNC_WAIT)
{
handle=true;
_state=State.ASYNC_WOKEN;
}
}
cancelTimeout();
@ -511,7 +523,7 @@ public class HttpChannelState
switch(_state)
{
case DISPATCHED:
case ASYNCIO:
case ASYNC_IO:
throw new IllegalStateException(getStatusString());
default:
break;
@ -571,7 +583,7 @@ public class HttpChannelState
{
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()
{
boolean handle;
boolean handle=false;
synchronized (this)
{
_asyncRead=true;
handle=_state==State.ASYNCWAIT;
if (_state==State.ASYNC_WAIT)
{
_state=State.ASYNC_WOKEN;
handle=true;
}
}
if (handle)
@ -679,12 +695,16 @@ public class HttpChannelState
public void onWritePossible()
{
boolean handle;
boolean handle=false;
synchronized (this)
{
_asyncWrite=true;
handle=_state==State.ASYNCWAIT;
if (_state==State.ASYNC_WAIT)
{
_state=State.ASYNC_WOKEN;
handle=true;
}
}
if (handle)

View File

@ -52,7 +52,7 @@ public class HttpConfiguration
private String _secureScheme = HttpScheme.HTTPS.asString();
private boolean _sendServerVersion = true; //send Server: 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
{

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.server;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.RejectedExecutionException;
@ -62,6 +63,13 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
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()
{
return __currentConnection.get();
@ -210,6 +218,8 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
else
{
// 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();
// 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.
// 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.
// 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();
}
else
{
// 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();
}
}
@ -259,7 +273,136 @@ 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
protected void onFillInterestedFailed(Throwable cause)
@ -303,88 +446,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
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>
{
public HttpChannelOverHttp(Connector connector, HttpConfiguration config, EndPoint endPoint, HttpTransport transport, HttpInput<ByteBuffer> input)
@ -471,6 +533,14 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
{
getEndPoint().shutdownOutput();
}
@Override
public boolean messageComplete()
{
super.messageComplete();
return false;
}
}
private class CommitCallback extends IteratingCallback

View File

@ -20,7 +20,6 @@ package org.eclipse.jetty.server;
import java.io.IOException;
import java.util.Objects;
import javax.servlet.ReadListener;
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.
*/
@ -355,7 +362,7 @@ public abstract class HttpInput<T> extends ServletInputStream implements Runnabl
{
synchronized (lock())
{
if (_onError == null)
if (_onError != null)
LOG.warn(x);
else
_onError = x;
@ -436,6 +443,7 @@ public abstract class HttpInput<T> extends ServletInputStream implements Runnabl
input.blockForContent();
}
@Override
public String toString()
{
return "STREAM";
@ -471,6 +479,7 @@ public abstract class HttpInput<T> extends ServletInputStream implements Runnabl
return true;
}
@Override
public String toString()
{
return "EARLY_EOF";
@ -485,6 +494,7 @@ public abstract class HttpInput<T> extends ServletInputStream implements Runnabl
return true;
}
@Override
public String toString()
{
return "EOF";

View File

@ -86,38 +86,11 @@ public class HttpInputOverHTTP extends HttpInput<ByteBuffer> implements Callback
// No - then we are going to need to parse some more content
_content=null;
ByteBuffer requestBuffer = _httpConnection.getRequestBuffer();
while (!_httpConnection.getParser().isComplete())
{
// 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))
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;
}
}
_httpConnection.parseContent();
// If we have some content available, return it
if (BufferUtil.hasContent(_content))
return _content;
return null;

View File

@ -24,7 +24,6 @@ import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritePendingException;
import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
@ -75,7 +74,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
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);
public HttpOutput(HttpChannel<?> channel)
@ -147,7 +146,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
break loop;
case UNREADY:
throw new WritePendingException(); // TODO ?
if (_state.compareAndSet(state,OutputState.ERROR))
_writeListener.onError(_onError==null?new EofException("Async close"):_onError);
continue;
default:
if (_state.compareAndSet(state,OutputState.CLOSED))
@ -180,7 +181,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
break loop;
case UNREADY:
throw new WritePendingException(); // TODO ?
if (_state.compareAndSet(state,OutputState.ERROR))
_writeListener.onError(_onError==null?new EofException("Async closed"):_onError);
continue;
default:
if (_state.compareAndSet(state,OutputState.CLOSED))
@ -239,6 +242,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
case UNREADY:
throw new WritePendingException();
case ERROR:
throw new EofException(_onError);
case CLOSED:
return;
}
@ -299,6 +305,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
case UNREADY:
throw new WritePendingException();
case ERROR:
throw new EofException(_onError);
case CLOSED:
throw new EofException("Closed");
}
@ -397,6 +406,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
case UNREADY:
throw new WritePendingException();
case ERROR:
throw new EofException(_onError);
case CLOSED:
throw new EofException("Closed");
}
@ -477,6 +489,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
case UNREADY:
throw new WritePendingException();
case ERROR:
throw new EofException(_onError);
case CLOSED:
throw new EofException("Closed");
}
@ -616,6 +631,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable
if (!_state.compareAndSet(OutputState.OPEN, OutputState.PENDING))
continue;
break;
case ERROR:
throw new EofException(_onError);
case CLOSED:
throw new EofException("Closed");
default:
@ -707,6 +724,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
return false;
case UNREADY:
return false;
case ERROR:
return true;
case CLOSED:
return true;
@ -717,45 +737,55 @@ public class HttpOutput extends ServletOutputStream implements Runnable
@Override
public void run()
{
if(_onError!=null)
loop: while (true)
{
Throwable th=_onError;
_onError=null;
_writeListener.onError(new IOException(th));
close();
}
switch(_state.get())
{
case READY:
try
OutputState state = _state.get();
if(_onError!=null)
{
switch(state)
{
_writeListener.onWritePossible();
case CLOSED:
case ERROR:
_onError=null;
break loop;
default:
if (_state.compareAndSet(state, OutputState.ERROR))
{
Throwable th=_onError;
_onError=null;
LOG.debug("onError",th);
_writeListener.onError(th);
close();
break loop;
}
}
catch (Throwable e)
{
_writeListener.onError(e);
close();
}
break;
case CLOSED:
try
{
new Throwable().printStackTrace();
continue loop;
}
switch(_state.get())
{
case READY:
case CLOSED:
// even though a write is not possible, because a close has
// occurred, we need to call onWritePossible to tell async
// producer that the last write completed.
_writeListener.onWritePossible();
}
catch (Throwable e)
{
_writeListener.onError(e);
}
break;
default:
try
{
_writeListener.onWritePossible();
break loop;
}
catch (Throwable e)
{
_onError=e;
}
break;
default:
}
}
}
@ -770,37 +800,29 @@ public class HttpOutput extends ServletOutputStream implements Runnable
@Override
protected void completed()
{
try
while(true)
{
while(true)
OutputState last=_state.get();
switch(last)
{
HttpOutput.OutputState last=_state.get();
switch(last)
{
case PENDING:
if (!_state.compareAndSet(HttpOutput.OutputState.PENDING, HttpOutput.OutputState.ASYNC))
continue;
break;
case PENDING:
if (!_state.compareAndSet(OutputState.PENDING, OutputState.ASYNC))
continue;
break;
case UNREADY:
if (!_state.compareAndSet(HttpOutput.OutputState.UNREADY, HttpOutput.OutputState.READY))
continue;
_channel.getState().onWritePossible();
break;
case UNREADY:
if (!_state.compareAndSet(OutputState.UNREADY, OutputState.READY))
continue;
_channel.getState().onWritePossible();
break;
case CLOSED:
break;
case CLOSED:
break;
default:
throw new IllegalStateException();
}
break;
default:
throw new IllegalStateException();
}
}
catch (Exception e)
{
_onError=e;
_channel.getState().onWritePossible();
break;
}
}
@ -885,7 +907,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
// Can we just aggregate the remainder?
if (!_complete && _len<BufferUtil.space(_aggregate) && _len<_commitSize)
{
int position = BufferUtil.flipToFill(_aggregate);
BufferUtil.put(_buffer,_aggregate);
BufferUtil.flipToFlush(_aggregate, position);
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
{
NetworkTrafficSelectChannelEndPoint endPoint = new NetworkTrafficSelectChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout(), listeners);
endPoint.notifyOpened();
return endPoint;
}
}

View File

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

View File

@ -1214,20 +1214,17 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
_protectedTargets = null;
return;
}
_protectedTargets = new String[targets.length];
System.arraycopy(targets, 0, _protectedTargets, 0, targets.length);
_protectedTargets = Arrays.copyOf(targets, targets.length);
}
/* ------------------------------------------------------------ */
public String[] getProtectedTargets ()
public String[] getProtectedTargets()
{
if (_protectedTargets == null)
return null;
String[] tmp = new String[_protectedTargets.length];
System.arraycopy(_protectedTargets, 0, tmp, 0, _protectedTargets.length);
return tmp;
return Arrays.copyOf(_protectedTargets, _protectedTargets.length);
}
@ -1384,8 +1381,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
/* ------------------------------------------------------------ */
/**
* @param base
* The resourceBase to set.
* Set the base resource for this context.
* @param base The resource used as the base for all static content of this context.
* @see #setResourceBase(String)
*/
public void setBaseResource(Resource base)
{
@ -1393,9 +1391,11 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
}
/* ------------------------------------------------------------ */
/**
* @param resourceBase
* The base resource as a string.
/**
* Set the base resource for this context.
* @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)
{

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