Merge branch 'master' into release-9
This commit is contained in:
commit
cdb38c4532
|
@ -191,6 +191,12 @@
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-quickstart</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
<!-- dependencies that jetty-all needs (some optional) -->
|
<!-- dependencies that jetty-all needs (some optional) -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>javax.websocket</groupId>
|
<groupId>javax.websocket</groupId>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
protonego-boot
|
protonego-boot
|
||||||
|
|
||||||
[files]
|
[files]
|
||||||
http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.0.0/alpn-boot-7.0.0.jar:lib/alpn/alpn-boot-7.0.0.jar
|
http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.0.0.v20140317/alpn-boot-7.0.0.v20140317.jar:lib/alpn/alpn-boot-7.0.0.v20140317.jar
|
||||||
|
|
||||||
[exec]
|
[exec]
|
||||||
-Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.jar
|
-Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.v20140317.jar
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
protonego-boot
|
protonego-boot
|
||||||
|
|
||||||
[files]
|
[files]
|
||||||
http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.0.0/alpn-boot-7.0.0.jar:lib/alpn/alpn-boot-7.0.0.jar
|
http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.0.0.v20140317/alpn-boot-7.0.0.v20140317.jar:lib/alpn/alpn-boot-7.0.0.v20140317.jar
|
||||||
|
|
||||||
[exec]
|
[exec]
|
||||||
-Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.jar
|
-Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.v20140317.jar
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
protonego-boot
|
protonego-boot
|
||||||
|
|
||||||
[files]
|
[files]
|
||||||
http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.0.0/alpn-boot-7.0.0.jar:lib/alpn/alpn-boot-7.0.0.jar
|
http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.0.0.v20140317/alpn-boot-7.0.0.v20140317.jar:lib/alpn/alpn-boot-7.0.0.v20140317.jar
|
||||||
|
|
||||||
[exec]
|
[exec]
|
||||||
-Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.jar
|
-Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.v20140317.jar
|
||||||
|
|
|
@ -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
|
|
@ -2,7 +2,7 @@
|
||||||
protonego-boot
|
protonego-boot
|
||||||
|
|
||||||
[files]
|
[files]
|
||||||
http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.0.0/alpn-boot-8.0.0.jar:lib/alpn/alpn-boot-8.0.0.jar
|
http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.0.0.v20140317/alpn-boot-8.0.0.v20140317.jar:lib/alpn/alpn-boot-8.0.0.v20140317.jar
|
||||||
|
|
||||||
[exec]
|
[exec]
|
||||||
-Xbootclasspath/p:lib/alpn/alpn-boot-8.0.0.jar
|
-Xbootclasspath/p:lib/alpn/alpn-boot-8.0.0.v20140317.jar
|
||||||
|
|
|
@ -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
|
|
@ -44,7 +44,7 @@
|
||||||
<configuration>
|
<configuration>
|
||||||
<instructions>
|
<instructions>
|
||||||
<Import-Package>javax.servlet.*;version="[2.6.0,3.2)",org.objectweb.asm.*;version=4,*</Import-Package>
|
<Import-Package>javax.servlet.*;version="[2.6.0,3.2)",org.objectweb.asm.*;version=4,*</Import-Package>
|
||||||
<Require-Capability>osgi.serviceloader; filter:="(osgi.serviceloader=javax.servlet.ServletContainerInitializer)";cardinality:=multiple, osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)"</Require-Capability>
|
<Require-Capability>osgi.serviceloader; filter:="(osgi.serviceloader=javax.servlet.ServletContainerInitializer)";resolution:=optional;cardinality:=multiple, osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)"</Require-Capability>
|
||||||
</instructions>
|
</instructions>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
|
|
|
@ -70,6 +70,8 @@ public class AnnotationParser
|
||||||
|
|
||||||
protected Set<String> _parsedClassNames = new ConcurrentHashSet<String>();
|
protected Set<String> _parsedClassNames = new ConcurrentHashSet<String>();
|
||||||
|
|
||||||
|
protected static int ASM_OPCODE_VERSION = Opcodes.ASM5; //compatibility of api
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert internal name to simple name
|
* Convert internal name to simple name
|
||||||
|
@ -373,7 +375,7 @@ public class AnnotationParser
|
||||||
final String signature,
|
final String signature,
|
||||||
final String[] exceptions)
|
final String[] exceptions)
|
||||||
{
|
{
|
||||||
super(Opcodes.ASM4);
|
super(ASM_OPCODE_VERSION);
|
||||||
_handlers = handlers;
|
_handlers = handlers;
|
||||||
_mi = new MethodInfo(classInfo, name, access, methodDesc,signature, exceptions);
|
_mi = new MethodInfo(classInfo, name, access, methodDesc,signature, exceptions);
|
||||||
}
|
}
|
||||||
|
@ -417,7 +419,7 @@ public class AnnotationParser
|
||||||
final String signature,
|
final String signature,
|
||||||
final Object value)
|
final Object value)
|
||||||
{
|
{
|
||||||
super(Opcodes.ASM4);
|
super(ASM_OPCODE_VERSION);
|
||||||
_handlers = handlers;
|
_handlers = handlers;
|
||||||
_fieldInfo = new FieldInfo(classInfo, fieldName, access, fieldType, signature, value);
|
_fieldInfo = new FieldInfo(classInfo, fieldName, access, fieldType, signature, value);
|
||||||
}
|
}
|
||||||
|
@ -456,7 +458,7 @@ public class AnnotationParser
|
||||||
|
|
||||||
public MyClassVisitor(Set<? extends Handler> handlers, Resource containingResource)
|
public MyClassVisitor(Set<? extends Handler> handlers, Resource containingResource)
|
||||||
{
|
{
|
||||||
super(Opcodes.ASM4);
|
super(ASM_OPCODE_VERSION);
|
||||||
_handlers = handlers;
|
_handlers = handlers;
|
||||||
_containingResource = containingResource;
|
_containingResource = containingResource;
|
||||||
}
|
}
|
||||||
|
@ -702,6 +704,7 @@ public class AnnotationParser
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
if (LOG.isDebugEnabled()) LOG.debug("Error scanning file "+files[f], ex);
|
||||||
me.add(new RuntimeException("Error scanning file "+files[f],ex));
|
me.add(new RuntimeException("Error scanning file "+files[f],ex));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -246,14 +246,12 @@ public class GZIPContentDecoder implements ContentDecoder
|
||||||
if (output == null)
|
if (output == null)
|
||||||
{
|
{
|
||||||
// Save the inflated bytes and loop to see if we have finished
|
// Save the inflated bytes and loop to see if we have finished
|
||||||
output = new byte[decoded];
|
output = Arrays.copyOf(bytes, decoded);
|
||||||
System.arraycopy(bytes, 0, output, 0, decoded);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Accumulate inflated bytes and loop to see if we have finished
|
// Accumulate inflated bytes and loop to see if we have finished
|
||||||
byte[] newOutput = new byte[output.length + decoded];
|
byte[] newOutput = Arrays.copyOf(output, output.length+decoded);
|
||||||
System.arraycopy(output, 0, newOutput, 0, output.length);
|
|
||||||
System.arraycopy(bytes, 0, newOutput, output.length, decoded);
|
System.arraycopy(bytes, 0, newOutput, output.length, decoded);
|
||||||
output = newOutput;
|
output = newOutput;
|
||||||
}
|
}
|
||||||
|
|
|
@ -201,8 +201,8 @@ public class HttpClient extends ContainerLifeCycle
|
||||||
scheduler = new ScheduledExecutorScheduler(name + "-scheduler", false);
|
scheduler = new ScheduledExecutorScheduler(name + "-scheduler", false);
|
||||||
addBean(scheduler);
|
addBean(scheduler);
|
||||||
|
|
||||||
addBean(transport);
|
|
||||||
transport.setHttpClient(this);
|
transport.setHttpClient(this);
|
||||||
|
addBean(transport);
|
||||||
|
|
||||||
resolver = new SocketAddressResolver(executor, scheduler, getAddressResolutionTimeout());
|
resolver = new SocketAddressResolver(executor, scheduler, getAddressResolutionTimeout());
|
||||||
|
|
||||||
|
@ -228,7 +228,6 @@ public class HttpClient extends ContainerLifeCycle
|
||||||
protected void doStop() throws Exception
|
protected void doStop() throws Exception
|
||||||
{
|
{
|
||||||
cookieStore.removeAll();
|
cookieStore.removeAll();
|
||||||
cookieStore = null;
|
|
||||||
decoderFactories.clear();
|
decoderFactories.clear();
|
||||||
handlers.clear();
|
handlers.clear();
|
||||||
|
|
||||||
|
@ -391,26 +390,29 @@ public class HttpClient extends ContainerLifeCycle
|
||||||
.idleTimeout(oldRequest.getIdleTimeout(), TimeUnit.MILLISECONDS)
|
.idleTimeout(oldRequest.getIdleTimeout(), TimeUnit.MILLISECONDS)
|
||||||
.timeout(oldRequest.getTimeout(), TimeUnit.MILLISECONDS)
|
.timeout(oldRequest.getTimeout(), TimeUnit.MILLISECONDS)
|
||||||
.followRedirects(oldRequest.isFollowRedirects());
|
.followRedirects(oldRequest.isFollowRedirects());
|
||||||
for (HttpField header : oldRequest.getHeaders())
|
for (HttpField field : oldRequest.getHeaders())
|
||||||
{
|
{
|
||||||
// We have a new URI, so skip the host header if present
|
HttpHeader header = field.getHeader();
|
||||||
if (HttpHeader.HOST == header.getHeader())
|
// We have a new URI, so skip the host header if present.
|
||||||
|
if (HttpHeader.HOST == header)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Remove expectation headers
|
// Remove expectation headers.
|
||||||
if (HttpHeader.EXPECT == header.getHeader())
|
if (HttpHeader.EXPECT == header)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Remove cookies
|
// Remove cookies.
|
||||||
if (HttpHeader.COOKIE == header.getHeader())
|
if (HttpHeader.COOKIE == header)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Remove authorization headers
|
// Remove authorization headers.
|
||||||
if (HttpHeader.AUTHORIZATION == header.getHeader() ||
|
if (HttpHeader.AUTHORIZATION == header ||
|
||||||
HttpHeader.PROXY_AUTHORIZATION == header.getHeader())
|
HttpHeader.PROXY_AUTHORIZATION == header)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
newRequest.header(header.getName(), header.getValue());
|
String value = field.getValue();
|
||||||
|
if (!newRequest.getHeaders().contains(header, value))
|
||||||
|
newRequest.header(field.getName(), value);
|
||||||
}
|
}
|
||||||
return newRequest;
|
return newRequest;
|
||||||
}
|
}
|
||||||
|
|
|
@ -215,6 +215,10 @@ public abstract class HttpDestination implements Destination, Closeable, Dumpabl
|
||||||
LOG.debug("Closed {}", this);
|
LOG.debug("Closed {}", this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void release(Connection connection)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public void close(Connection connection)
|
public void close(Connection connection)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ import org.eclipse.jetty.client.api.Result;
|
||||||
import org.eclipse.jetty.http.HttpField;
|
import org.eclipse.jetty.http.HttpField;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
import org.eclipse.jetty.util.BufferUtil;
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
import org.eclipse.jetty.util.Callback;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
|
||||||
|
@ -48,8 +49,8 @@ import org.eclipse.jetty.util.log.Logger;
|
||||||
* is available</li>
|
* is available</li>
|
||||||
* <li>{@link #responseHeader(HttpExchange, HttpField)}, when a HTTP field is available</li>
|
* <li>{@link #responseHeader(HttpExchange, HttpField)}, when a HTTP field is available</li>
|
||||||
* <li>{@link #responseHeaders(HttpExchange)}, when all HTTP headers are available</li>
|
* <li>{@link #responseHeaders(HttpExchange)}, when all HTTP headers are available</li>
|
||||||
* <li>{@link #responseContent(HttpExchange, ByteBuffer)}, when HTTP content is available; this is the only method
|
* <li>{@link #responseContent(HttpExchange, ByteBuffer, Callback)}, when HTTP content is available; this is the only
|
||||||
* that may be invoked multiple times with different buffers containing different content</li>
|
* method that may be invoked multiple times with different buffers containing different content</li>
|
||||||
* <li>{@link #responseSuccess(HttpExchange)}, when the response is complete</li>
|
* <li>{@link #responseSuccess(HttpExchange)}, when the response is complete</li>
|
||||||
* </ol>
|
* </ol>
|
||||||
* At any time, subclasses may invoke {@link #responseFailure(Throwable)} to indicate that the response has failed
|
* At any time, subclasses may invoke {@link #responseFailure(Throwable)} to indicate that the response has failed
|
||||||
|
@ -237,7 +238,7 @@ public abstract class HttpReceiver
|
||||||
|
|
||||||
HttpResponse response = exchange.getResponse();
|
HttpResponse response = exchange.getResponse();
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Response headers {}{}{}", response, System.getProperty("line.separator"), response.getHeaders().toString().trim());
|
LOG.debug("Response headers {}{}{}", response, System.lineSeparator(), response.getHeaders().toString().trim());
|
||||||
ResponseNotifier notifier = getHttpDestination().getResponseNotifier();
|
ResponseNotifier notifier = getHttpDestination().getResponseNotifier();
|
||||||
notifier.notifyHeaders(exchange.getConversation().getResponseListeners(), response);
|
notifier.notifyHeaders(exchange.getConversation().getResponseListeners(), response);
|
||||||
|
|
||||||
|
@ -269,7 +270,7 @@ public abstract class HttpReceiver
|
||||||
* @param buffer the response HTTP content buffer
|
* @param buffer the response HTTP content buffer
|
||||||
* @return whether the processing should continue
|
* @return whether the processing should continue
|
||||||
*/
|
*/
|
||||||
protected boolean responseContent(HttpExchange exchange, ByteBuffer buffer)
|
protected boolean responseContent(HttpExchange exchange, ByteBuffer buffer, Callback callback)
|
||||||
{
|
{
|
||||||
out: while (true)
|
out: while (true)
|
||||||
{
|
{
|
||||||
|
@ -292,18 +293,18 @@ public abstract class HttpReceiver
|
||||||
|
|
||||||
HttpResponse response = exchange.getResponse();
|
HttpResponse response = exchange.getResponse();
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Response content {}{}{}", response, System.getProperty("line.separator"), BufferUtil.toDetailString(buffer));
|
LOG.debug("Response content {}{}{}", response, System.lineSeparator(), BufferUtil.toDetailString(buffer));
|
||||||
|
|
||||||
ContentDecoder decoder = this.decoder;
|
ContentDecoder decoder = this.decoder;
|
||||||
if (decoder != null)
|
if (decoder != null)
|
||||||
{
|
{
|
||||||
buffer = decoder.decode(buffer);
|
buffer = decoder.decode(buffer);
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Response content decoded ({}) {}{}{}", decoder, response, System.getProperty("line.separator"), BufferUtil.toDetailString(buffer));
|
LOG.debug("Response content decoded ({}) {}{}{}", decoder, response, System.lineSeparator(), BufferUtil.toDetailString(buffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
ResponseNotifier notifier = getHttpDestination().getResponseNotifier();
|
ResponseNotifier notifier = getHttpDestination().getResponseNotifier();
|
||||||
notifier.notifyContent(exchange.getConversation().getResponseListeners(), response, buffer);
|
notifier.notifyContent(exchange.getConversation().getResponseListeners(), response, buffer, callback);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,7 @@ import org.eclipse.jetty.http.HttpFields;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
import org.eclipse.jetty.http.HttpMethod;
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
import org.eclipse.jetty.http.HttpVersion;
|
import org.eclipse.jetty.http.HttpVersion;
|
||||||
|
import org.eclipse.jetty.util.Callback;
|
||||||
import org.eclipse.jetty.util.Fields;
|
import org.eclipse.jetty.util.Fields;
|
||||||
|
|
||||||
public class HttpRequest implements Request
|
public class HttpRequest implements Request
|
||||||
|
@ -449,12 +450,34 @@ public class HttpRequest implements Request
|
||||||
@Override
|
@Override
|
||||||
public Request onResponseContent(final Response.ContentListener listener)
|
public Request onResponseContent(final Response.ContentListener listener)
|
||||||
{
|
{
|
||||||
this.responseListeners.add(new Response.ContentListener()
|
this.responseListeners.add(new Response.AsyncContentListener()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void onContent(Response response, ByteBuffer content)
|
public void onContent(Response response, ByteBuffer content, Callback callback)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
listener.onContent(response, content);
|
listener.onContent(response, content);
|
||||||
|
callback.succeeded();
|
||||||
|
}
|
||||||
|
catch (Exception x)
|
||||||
|
{
|
||||||
|
callback.failed(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Request onResponseContentAsync(final Response.AsyncContentListener listener)
|
||||||
|
{
|
||||||
|
this.responseListeners.add(new Response.AsyncContentListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onContent(Response response, ByteBuffer content, Callback callback)
|
||||||
|
{
|
||||||
|
listener.onContent(response, content, callback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -62,7 +62,7 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
|
||||||
private final AtomicReference<RequestState> requestState = new AtomicReference<>(RequestState.QUEUED);
|
private final AtomicReference<RequestState> requestState = new AtomicReference<>(RequestState.QUEUED);
|
||||||
private final AtomicReference<SenderState> senderState = new AtomicReference<>(SenderState.IDLE);
|
private final AtomicReference<SenderState> senderState = new AtomicReference<>(SenderState.IDLE);
|
||||||
private final Callback commitCallback = new CommitCallback();
|
private final Callback commitCallback = new CommitCallback();
|
||||||
private final Callback contentCallback = new ContentCallback();
|
private final IteratingCallback contentCallback = new ContentCallback();
|
||||||
private final Callback lastCallback = new LastContentCallback();
|
private final Callback lastCallback = new LastContentCallback();
|
||||||
private final HttpChannel channel;
|
private final HttpChannel channel;
|
||||||
private volatile HttpContent content;
|
private volatile HttpContent content;
|
||||||
|
@ -100,14 +100,7 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
|
||||||
if (updateSenderState(current, newSenderState))
|
if (updateSenderState(current, newSenderState))
|
||||||
{
|
{
|
||||||
LOG.debug("Deferred content available, {} -> {}", current, newSenderState);
|
LOG.debug("Deferred content available, {} -> {}", current, newSenderState);
|
||||||
// TODO should just call contentCallback.iterate() here.
|
contentCallback.iterate();
|
||||||
HttpContent content = this.content;
|
|
||||||
if (content.advance())
|
|
||||||
sendContent(exchange, content, contentCallback); // TODO old style usage!
|
|
||||||
else if (content.isConsumed())
|
|
||||||
sendContent(exchange, content, lastCallback);
|
|
||||||
else
|
|
||||||
throw new IllegalStateException();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -456,26 +449,12 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
|
||||||
case WAITING:
|
case WAITING:
|
||||||
{
|
{
|
||||||
// We received the 100 Continue, now send the content if any.
|
// We received the 100 Continue, now send the content if any.
|
||||||
HttpContent content = this.content;
|
|
||||||
// TODO should just call contentCallback.iterate() here.
|
|
||||||
if (content.advance())
|
|
||||||
{
|
|
||||||
// There is content to send.
|
|
||||||
if (!updateSenderState(current, SenderState.SENDING))
|
if (!updateSenderState(current, SenderState.SENDING))
|
||||||
throw illegalSenderState(current);
|
throw illegalSenderState(current);
|
||||||
LOG.debug("Proceeding while waiting");
|
LOG.debug("Proceeding while waiting");
|
||||||
sendContent(exchange, content, contentCallback); // TODO old style usage!
|
contentCallback.iterate();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// No content to send yet - it's deferred.
|
|
||||||
if (!updateSenderState(current, SenderState.IDLE))
|
|
||||||
throw illegalSenderState(current);
|
|
||||||
LOG.debug("Proceeding deferred");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
throw illegalSenderState(current);
|
throw illegalSenderState(current);
|
||||||
|
@ -665,31 +644,9 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
|
||||||
{
|
{
|
||||||
case SENDING:
|
case SENDING:
|
||||||
{
|
{
|
||||||
// TODO should just call contentCallback.iterate() here.
|
contentCallback.iterate();
|
||||||
// We have content to send ?
|
|
||||||
if (content.advance())
|
|
||||||
{
|
|
||||||
sendContent(exchange, content, contentCallback); // TODO old style usage!
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
if (content.isConsumed())
|
|
||||||
{
|
|
||||||
sendContent(exchange, content, lastCallback);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (updateSenderState(current, SenderState.IDLE))
|
|
||||||
{
|
|
||||||
LOG.debug("Waiting for deferred content for {}", request);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case SENDING_WITH_CONTENT:
|
case SENDING_WITH_CONTENT:
|
||||||
{
|
{
|
||||||
// We have deferred content to send.
|
// We have deferred content to send.
|
||||||
|
@ -745,59 +702,43 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
|
||||||
if (exchange == null)
|
if (exchange == null)
|
||||||
return Action.IDLE;
|
return Action.IDLE;
|
||||||
|
|
||||||
Request request = exchange.getRequest();
|
|
||||||
HttpContent content = HttpSender.this.content;
|
HttpContent content = HttpSender.this.content;
|
||||||
|
|
||||||
ByteBuffer contentBuffer = content.getContent();
|
|
||||||
if (contentBuffer != null)
|
|
||||||
{
|
|
||||||
if (!someToContent(request, contentBuffer))
|
|
||||||
return Action.IDLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
boolean advanced = content.advance();
|
boolean advanced = content.advance();
|
||||||
boolean consumed = content.isConsumed();
|
boolean consumed = content.isConsumed();
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("Content {} consumed {} for {}", advanced, consumed, exchange.getRequest());
|
||||||
|
|
||||||
SenderState current = senderState.get();
|
|
||||||
switch (current)
|
|
||||||
{
|
|
||||||
case SENDING:
|
|
||||||
{
|
|
||||||
if (advanced)
|
if (advanced)
|
||||||
{
|
{
|
||||||
// There is more content to send
|
|
||||||
sendContent(exchange, content, this);
|
sendContent(exchange, content, this);
|
||||||
return Action.SCHEDULED;
|
return Action.SCHEDULED;
|
||||||
}
|
}
|
||||||
else if (consumed)
|
|
||||||
|
if (consumed)
|
||||||
{
|
{
|
||||||
sendContent(exchange, content, lastCallback);
|
sendContent(exchange, content, lastCallback);
|
||||||
return Action.IDLE;
|
return Action.IDLE;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
SenderState current = HttpSender.this.senderState.get();
|
||||||
|
switch (current)
|
||||||
|
{
|
||||||
|
case SENDING:
|
||||||
{
|
{
|
||||||
if (updateSenderState(current, SenderState.IDLE))
|
if (updateSenderState(current, SenderState.IDLE))
|
||||||
{
|
{
|
||||||
LOG.debug("Waiting for deferred content for {}", request);
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("Content is deferred for {}", exchange.getRequest());
|
||||||
return Action.IDLE;
|
return Action.IDLE;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
case SENDING_WITH_CONTENT:
|
case SENDING_WITH_CONTENT:
|
||||||
{
|
{
|
||||||
if (updateSenderState(current, SenderState.SENDING))
|
updateSenderState(current, SenderState.SENDING);
|
||||||
{
|
break;
|
||||||
LOG.debug("Deferred content available for {}", request);
|
|
||||||
if (advanced)
|
|
||||||
{
|
|
||||||
sendContent(exchange, content, this);
|
|
||||||
return Action.SCHEDULED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw illegalSenderState(current);
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
|
@ -810,6 +751,8 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
|
||||||
@Override
|
@Override
|
||||||
public void succeeded()
|
public void succeeded()
|
||||||
{
|
{
|
||||||
|
ByteBuffer buffer = content.getContent();
|
||||||
|
someToContent(getHttpExchange().getRequest(), buffer);
|
||||||
content.succeeded();
|
content.succeeded();
|
||||||
super.succeeded();
|
super.succeeded();
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,8 +140,11 @@ public abstract class PoolingHttpDestination<C extends Connection> extends HttpD
|
||||||
|
|
||||||
protected abstract void send(C connection, HttpExchange exchange);
|
protected abstract void send(C connection, HttpExchange exchange);
|
||||||
|
|
||||||
public void release(C connection)
|
@Override
|
||||||
|
public void release(Connection c)
|
||||||
{
|
{
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
C connection = (C)c;
|
||||||
LOG.debug("{} released", connection);
|
LOG.debug("{} released", connection);
|
||||||
HttpClient client = getHttpClient();
|
HttpClient client = getHttpClient();
|
||||||
if (client.isRunning())
|
if (client.isRunning())
|
||||||
|
|
|
@ -27,6 +27,8 @@ import org.eclipse.jetty.client.api.Request;
|
||||||
import org.eclipse.jetty.client.api.Response;
|
import org.eclipse.jetty.client.api.Response;
|
||||||
import org.eclipse.jetty.client.api.Result;
|
import org.eclipse.jetty.client.api.Result;
|
||||||
import org.eclipse.jetty.http.HttpField;
|
import org.eclipse.jetty.http.HttpField;
|
||||||
|
import org.eclipse.jetty.util.Callback;
|
||||||
|
import org.eclipse.jetty.util.IteratingNestedCallback;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
|
||||||
|
@ -106,31 +108,20 @@ public class ResponseNotifier
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notifyContent(List<Response.ResponseListener> listeners, Response response, ByteBuffer buffer)
|
public void notifyContent(List<Response.ResponseListener> listeners, Response response, ByteBuffer buffer, Callback callback)
|
||||||
{
|
{
|
||||||
// Slice the buffer to avoid that listeners peek into data they should not look at.
|
// Here we use an IteratingNestedCallback not to avoid the stack overflow, but to
|
||||||
buffer = buffer.slice();
|
// invoke the listeners one after the other. When all of them have invoked the
|
||||||
if (!buffer.hasRemaining())
|
// callback they got passed, the callback passed to this method is finally invoked.
|
||||||
return;
|
ContentCallback contentCallback = new ContentCallback(listeners, response, buffer, callback);
|
||||||
// Optimized to avoid allocations of iterator instances
|
contentCallback.iterate();
|
||||||
for (int i = 0; i < listeners.size(); ++i)
|
|
||||||
{
|
|
||||||
Response.ResponseListener listener = listeners.get(i);
|
|
||||||
if (listener instanceof Response.ContentListener)
|
|
||||||
{
|
|
||||||
// The buffer was sliced, so we always clear it (position=0, limit=capacity)
|
|
||||||
// before passing it to the listener that may consume it.
|
|
||||||
buffer.clear();
|
|
||||||
notifyContent((Response.ContentListener)listener, response, buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyContent(Response.ContentListener listener, Response response, ByteBuffer buffer)
|
private void notifyContent(Response.AsyncContentListener listener, Response response, ByteBuffer buffer, Callback callback)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
listener.onContent(response, buffer);
|
listener.onContent(response, buffer, callback);
|
||||||
}
|
}
|
||||||
catch (Throwable x)
|
catch (Throwable x)
|
||||||
{
|
{
|
||||||
|
@ -218,7 +209,8 @@ public class ResponseNotifier
|
||||||
}
|
}
|
||||||
notifyHeaders(listeners, response);
|
notifyHeaders(listeners, response);
|
||||||
if (response instanceof ContentResponse)
|
if (response instanceof ContentResponse)
|
||||||
notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent()));
|
// TODO: handle callback
|
||||||
|
notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent()), new Callback.Adapter());
|
||||||
notifySuccess(listeners, response);
|
notifySuccess(listeners, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,7 +231,8 @@ public class ResponseNotifier
|
||||||
}
|
}
|
||||||
notifyHeaders(listeners, response);
|
notifyHeaders(listeners, response);
|
||||||
if (response instanceof ContentResponse)
|
if (response instanceof ContentResponse)
|
||||||
notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent()));
|
// TODO: handle callback
|
||||||
|
notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent()), new Callback.Adapter());
|
||||||
notifyFailure(listeners, response, failure);
|
notifyFailure(listeners, response, failure);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,4 +241,51 @@ public class ResponseNotifier
|
||||||
forwardFailure(listeners, response, responseFailure);
|
forwardFailure(listeners, response, responseFailure);
|
||||||
notifyComplete(listeners, new Result(request, requestFailure, response, responseFailure));
|
notifyComplete(listeners, new Result(request, requestFailure, response, responseFailure));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ContentCallback extends IteratingNestedCallback
|
||||||
|
{
|
||||||
|
private final List<Response.ResponseListener> listeners;
|
||||||
|
private final Response response;
|
||||||
|
private final ByteBuffer buffer;
|
||||||
|
private int index;
|
||||||
|
|
||||||
|
private ContentCallback(List<Response.ResponseListener> listeners, Response response, ByteBuffer buffer, Callback callback)
|
||||||
|
{
|
||||||
|
super(callback);
|
||||||
|
this.listeners = listeners;
|
||||||
|
this.response = response;
|
||||||
|
// Slice the buffer to avoid that listeners peek into data they should not look at.
|
||||||
|
this.buffer = buffer.slice();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Action process() throws Exception
|
||||||
|
{
|
||||||
|
if (index == listeners.size())
|
||||||
|
return Action.SUCCEEDED;
|
||||||
|
|
||||||
|
Response.ResponseListener listener = listeners.get(index);
|
||||||
|
if (listener instanceof Response.AsyncContentListener)
|
||||||
|
{
|
||||||
|
// The buffer was sliced, so we always clear it
|
||||||
|
// (clear => position=0, limit=capacity) before
|
||||||
|
// passing it to the listener that may consume it.
|
||||||
|
buffer.clear();
|
||||||
|
ResponseNotifier.this.notifyContent((Response.AsyncContentListener)listener, response, buffer, this);
|
||||||
|
return Action.SCHEDULED;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
succeeded();
|
||||||
|
return Action.SCHEDULED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void succeeded()
|
||||||
|
{
|
||||||
|
++index;
|
||||||
|
super.succeeded();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -338,11 +338,17 @@ public interface Request
|
||||||
Request onResponseHeaders(Response.HeadersListener listener);
|
Request onResponseHeaders(Response.HeadersListener listener);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param listener a listener for response content events
|
* @param listener a consuming listener for response content events
|
||||||
* @return this request object
|
* @return this request object
|
||||||
*/
|
*/
|
||||||
Request onResponseContent(Response.ContentListener listener);
|
Request onResponseContent(Response.ContentListener listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param listener an asynchronous listener for response content events
|
||||||
|
* @return this request object
|
||||||
|
*/
|
||||||
|
Request onResponseContentAsync(Response.AsyncContentListener listener);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param listener a listener for response success event
|
* @param listener a listener for response success event
|
||||||
* @return this request object
|
* @return this request object
|
||||||
|
|
|
@ -26,6 +26,7 @@ import org.eclipse.jetty.client.util.BufferingResponseListener;
|
||||||
import org.eclipse.jetty.http.HttpField;
|
import org.eclipse.jetty.http.HttpField;
|
||||||
import org.eclipse.jetty.http.HttpFields;
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
import org.eclipse.jetty.http.HttpVersion;
|
import org.eclipse.jetty.http.HttpVersion;
|
||||||
|
import org.eclipse.jetty.util.Callback;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>{@link Response} represents a HTTP response and offers methods to retrieve status code, HTTP version
|
* <p>{@link Response} represents a HTTP response and offers methods to retrieve status code, HTTP version
|
||||||
|
@ -152,6 +153,11 @@ public interface Response
|
||||||
public void onContent(Response response, ByteBuffer content);
|
public void onContent(Response response, ByteBuffer content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface AsyncContentListener extends ResponseListener
|
||||||
|
{
|
||||||
|
public void onContent(Response response, ByteBuffer content, Callback callback);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listener for the response succeeded event.
|
* Listener for the response succeeded event.
|
||||||
*/
|
*/
|
||||||
|
@ -204,7 +210,7 @@ public interface Response
|
||||||
/**
|
/**
|
||||||
* Listener for all response events.
|
* Listener for all response events.
|
||||||
*/
|
*/
|
||||||
public interface Listener extends BeginListener, HeaderListener, HeadersListener, ContentListener, SuccessListener, FailureListener, CompleteListener
|
public interface Listener extends BeginListener, HeaderListener, HeadersListener, ContentListener, AsyncContentListener, SuccessListener, FailureListener, CompleteListener
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* An empty implementation of {@link Listener}
|
* An empty implementation of {@link Listener}
|
||||||
|
@ -232,6 +238,20 @@ public interface Response
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onContent(Response response, ByteBuffer content, Callback callback)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
onContent(response, content);
|
||||||
|
callback.succeeded();
|
||||||
|
}
|
||||||
|
catch (Exception x)
|
||||||
|
{
|
||||||
|
callback.failed(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Response response)
|
public void onSuccess(Response response)
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,10 +20,12 @@ package org.eclipse.jetty.client.http;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.HttpChannel;
|
import org.eclipse.jetty.client.HttpChannel;
|
||||||
import org.eclipse.jetty.client.HttpExchange;
|
import org.eclipse.jetty.client.HttpExchange;
|
||||||
|
import org.eclipse.jetty.client.api.Response;
|
||||||
import org.eclipse.jetty.client.api.Result;
|
import org.eclipse.jetty.client.api.Result;
|
||||||
import org.eclipse.jetty.http.HttpFields;
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||||
|
import org.eclipse.jetty.http.HttpVersion;
|
||||||
|
|
||||||
public class HttpChannelOverHTTP extends HttpChannel
|
public class HttpChannelOverHTTP extends HttpChannel
|
||||||
{
|
{
|
||||||
|
@ -77,10 +79,23 @@ public class HttpChannelOverHTTP extends HttpChannel
|
||||||
public void exchangeTerminated(Result result)
|
public void exchangeTerminated(Result result)
|
||||||
{
|
{
|
||||||
super.exchangeTerminated(result);
|
super.exchangeTerminated(result);
|
||||||
HttpFields responseHeaders = result.getResponse().getHeaders();
|
Response response = result.getResponse();
|
||||||
boolean close = result.isFailed() ||
|
HttpFields responseHeaders = response.getHeaders();
|
||||||
responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()) ||
|
boolean close = result.isFailed() || receiver.isShutdown();
|
||||||
receiver.isShutdown();
|
// Only check HTTP headers if there are no failures.
|
||||||
|
if (!close)
|
||||||
|
{
|
||||||
|
if (response.getVersion().compareTo(HttpVersion.HTTP_1_1) < 0)
|
||||||
|
{
|
||||||
|
// HTTP 1.0 must close the connection unless it has an explicit keep alive.
|
||||||
|
close = !responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// HTTP 1.1 or greater closes only if it has an explicit close.
|
||||||
|
close = responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString());
|
||||||
|
}
|
||||||
|
}
|
||||||
if (close)
|
if (close)
|
||||||
connection.close();
|
connection.close();
|
||||||
else
|
else
|
||||||
|
|
|
@ -32,12 +32,13 @@ import org.eclipse.jetty.http.HttpParser;
|
||||||
import org.eclipse.jetty.http.HttpVersion;
|
import org.eclipse.jetty.http.HttpVersion;
|
||||||
import org.eclipse.jetty.io.ByteBufferPool;
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.io.EofException;
|
|
||||||
import org.eclipse.jetty.util.BufferUtil;
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
import org.eclipse.jetty.util.CompletableCallback;
|
||||||
|
|
||||||
public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
|
public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
|
||||||
{
|
{
|
||||||
private final HttpParser parser = new HttpParser(this);
|
private final HttpParser parser = new HttpParser(this);
|
||||||
|
private ByteBuffer buffer;
|
||||||
private boolean shutdown;
|
private boolean shutdown;
|
||||||
|
|
||||||
public HttpReceiverOverHTTP(HttpChannelOverHTTP channel)
|
public HttpReceiverOverHTTP(HttpChannelOverHTTP channel)
|
||||||
|
@ -58,68 +59,97 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
||||||
|
|
||||||
public void receive()
|
public void receive()
|
||||||
{
|
{
|
||||||
HttpConnectionOverHTTP connection = getHttpConnection();
|
|
||||||
EndPoint endPoint = connection.getEndPoint();
|
|
||||||
HttpClient client = getHttpDestination().getHttpClient();
|
HttpClient client = getHttpDestination().getHttpClient();
|
||||||
ByteBufferPool bufferPool = client.getByteBufferPool();
|
ByteBufferPool bufferPool = client.getByteBufferPool();
|
||||||
ByteBuffer buffer = bufferPool.acquire(client.getResponseBufferSize(), true);
|
buffer = bufferPool.acquire(client.getResponseBufferSize(), true);
|
||||||
try
|
process();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void process()
|
||||||
{
|
{
|
||||||
|
if (readAndParse())
|
||||||
|
{
|
||||||
|
HttpClient client = getHttpDestination().getHttpClient();
|
||||||
|
ByteBufferPool bufferPool = client.getByteBufferPool();
|
||||||
|
bufferPool.release(buffer);
|
||||||
|
// Don't linger the buffer around if we are idle.
|
||||||
|
buffer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean readAndParse()
|
||||||
|
{
|
||||||
|
HttpConnectionOverHTTP connection = getHttpConnection();
|
||||||
|
EndPoint endPoint = connection.getEndPoint();
|
||||||
|
ByteBuffer buffer = this.buffer;
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
// Connection may be closed in a parser callback
|
try
|
||||||
|
{
|
||||||
|
// Connection may be closed in a parser callback.
|
||||||
if (connection.isClosed())
|
if (connection.isClosed())
|
||||||
{
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("{} closed", connection);
|
LOG.debug("{} closed", connection);
|
||||||
break;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
if (!parse(buffer))
|
||||||
|
return false;
|
||||||
|
|
||||||
int read = endPoint.fill(buffer);
|
int read = endPoint.fill(buffer);
|
||||||
if (LOG.isDebugEnabled()) // Avoid boxing of variable 'read'
|
// Avoid boxing of variable 'read'
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Read {} bytes from {}", read, endPoint);
|
LOG.debug("Read {} bytes from {}", read, endPoint);
|
||||||
|
|
||||||
if (read > 0)
|
if (read > 0)
|
||||||
{
|
{
|
||||||
parse(buffer);
|
if (!parse(buffer))
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
else if (read == 0)
|
else if (read == 0)
|
||||||
{
|
{
|
||||||
fillInterested();
|
fillInterested();
|
||||||
break;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
shutdown();
|
shutdown();
|
||||||
break;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
catch (Throwable x)
|
||||||
}
|
|
||||||
catch (EofException x)
|
|
||||||
{
|
|
||||||
LOG.ignore(x);
|
|
||||||
failAndClose(x);
|
|
||||||
}
|
|
||||||
catch (Exception x)
|
|
||||||
{
|
{
|
||||||
LOG.debug(x);
|
LOG.debug(x);
|
||||||
failAndClose(x);
|
failAndClose(x);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
bufferPool.release(buffer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parse(ByteBuffer buffer)
|
/**
|
||||||
|
* Parses a HTTP response from the given {@code buffer}.
|
||||||
|
*
|
||||||
|
* @param buffer the response bytes
|
||||||
|
* @return true to indicate that the parsing may proceed (for example with another response),
|
||||||
|
* false to indicate that the parsing should be interrupted (and will be resumed by another thread).
|
||||||
|
*/
|
||||||
|
private boolean parse(ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
while (buffer.hasRemaining())
|
// Must parse even if the buffer is fully consumed, to allow the
|
||||||
parser.parseNext(buffer);
|
// parser to advance from asynchronous content to response complete.
|
||||||
|
if (parser.parseNext(buffer))
|
||||||
|
{
|
||||||
|
// If the parser returns true, we need to differentiate two cases:
|
||||||
|
// A) the response is completed, so the parser is in START state;
|
||||||
|
// B) the content is handled asynchronously, so the parser is in CONTENT state.
|
||||||
|
return parser.isStart();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fillInterested()
|
private void fillInterested()
|
||||||
{
|
{
|
||||||
// TODO: do we need to call fillInterested() only if we are not failed (or we have an exchange) ?
|
|
||||||
getHttpChannel().getHttpConnection().fillInterested();
|
getHttpChannel().getHttpConnection().fillInterested();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,8 +225,22 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
||||||
if (exchange == null)
|
if (exchange == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
responseContent(exchange, buffer);
|
CompletableCallback callback = new CompletableCallback()
|
||||||
return false;
|
{
|
||||||
|
@Override
|
||||||
|
public void resume()
|
||||||
|
{
|
||||||
|
LOG.debug("Content consumed asynchronously, resuming processing");
|
||||||
|
process();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void abort(Throwable x)
|
||||||
|
{
|
||||||
|
failAndClose(x);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
responseContent(exchange, buffer, callback);
|
||||||
|
return callback.tryComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -114,7 +114,7 @@ public class HttpSenderOverHTTP extends HttpSender
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception x)
|
catch (Throwable x)
|
||||||
{
|
{
|
||||||
LOG.debug(x);
|
LOG.debug(x);
|
||||||
callback.failed(x);
|
callback.failed(x);
|
||||||
|
|
|
@ -18,11 +18,14 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.client.util;
|
package org.eclipse.jetty.client.util;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.charset.UnsupportedCharsetException;
|
import java.nio.charset.UnsupportedCharsetException;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.api.Response;
|
import org.eclipse.jetty.client.api.Response;
|
||||||
|
@ -30,6 +33,7 @@ import org.eclipse.jetty.client.api.Response.Listener;
|
||||||
import org.eclipse.jetty.client.api.Result;
|
import org.eclipse.jetty.client.api.Result;
|
||||||
import org.eclipse.jetty.http.HttpFields;
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>Implementation of {@link Listener} that buffers the content up to a maximum length
|
* <p>Implementation of {@link Listener} that buffers the content up to a maximum length
|
||||||
|
@ -40,7 +44,7 @@ import org.eclipse.jetty.http.HttpHeader;
|
||||||
public abstract class BufferingResponseListener extends Listener.Adapter
|
public abstract class BufferingResponseListener extends Listener.Adapter
|
||||||
{
|
{
|
||||||
private final int maxLength;
|
private final int maxLength;
|
||||||
private volatile byte[] buffer = new byte[0];
|
private volatile ByteBuffer buffer;
|
||||||
private volatile String encoding;
|
private volatile String encoding;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -58,20 +62,24 @@ public abstract class BufferingResponseListener extends Listener.Adapter
|
||||||
*/
|
*/
|
||||||
public BufferingResponseListener(int maxLength)
|
public BufferingResponseListener(int maxLength)
|
||||||
{
|
{
|
||||||
this.maxLength = maxLength;
|
this.maxLength=maxLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onHeaders(Response response)
|
public void onHeaders(Response response)
|
||||||
{
|
{
|
||||||
|
super.onHeaders(response);
|
||||||
|
|
||||||
HttpFields headers = response.getHeaders();
|
HttpFields headers = response.getHeaders();
|
||||||
long length = headers.getLongField(HttpHeader.CONTENT_LENGTH.asString());
|
long length = headers.getLongField(HttpHeader.CONTENT_LENGTH.asString());
|
||||||
if (length > maxLength)
|
if (length > maxLength)
|
||||||
{
|
{
|
||||||
response.abort(new IllegalArgumentException("Buffering capacity exceeded"));
|
response.abort(new IllegalArgumentException("Buffering capacity exceeded"));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
buffer=BufferUtil.allocate((length > 0)?(int)length:1024);
|
||||||
|
|
||||||
String contentType = headers.get(HttpHeader.CONTENT_TYPE);
|
String contentType = headers.get(HttpHeader.CONTENT_TYPE);
|
||||||
if (contentType != null)
|
if (contentType != null)
|
||||||
{
|
{
|
||||||
|
@ -88,23 +96,23 @@ public abstract class BufferingResponseListener extends Listener.Adapter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onContent(Response response, ByteBuffer content)
|
public void onContent(Response response, ByteBuffer content)
|
||||||
{
|
{
|
||||||
long newLength = buffer.length + content.remaining();
|
int length = content.remaining();
|
||||||
if (newLength > maxLength)
|
if (length>BufferUtil.space(buffer))
|
||||||
{
|
{
|
||||||
|
int requiredCapacity = buffer==null?0:buffer.capacity()+length;
|
||||||
|
if (requiredCapacity>maxLength)
|
||||||
response.abort(new IllegalArgumentException("Buffering capacity exceeded"));
|
response.abort(new IllegalArgumentException("Buffering capacity exceeded"));
|
||||||
|
|
||||||
|
int newCapacity = Math.min(Integer.highestOneBit(requiredCapacity) << 1, maxLength);
|
||||||
|
buffer = BufferUtil.ensureCapacity(buffer,newCapacity);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
BufferUtil.append(buffer, content);
|
||||||
byte[] newBuffer = new byte[(int)newLength];
|
|
||||||
System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
|
|
||||||
content.get(newBuffer, buffer.length, content.remaining());
|
|
||||||
buffer = newBuffer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -121,7 +129,9 @@ public abstract class BufferingResponseListener extends Listener.Adapter
|
||||||
*/
|
*/
|
||||||
public byte[] getContent()
|
public byte[] getContent()
|
||||||
{
|
{
|
||||||
return buffer;
|
if (buffer==null)
|
||||||
|
return new byte[0];
|
||||||
|
return BufferUtil.toArray(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -144,14 +154,9 @@ public abstract class BufferingResponseListener extends Listener.Adapter
|
||||||
*/
|
*/
|
||||||
public String getContentAsString(String encoding)
|
public String getContentAsString(String encoding)
|
||||||
{
|
{
|
||||||
try
|
if (buffer==null)
|
||||||
{
|
return null;
|
||||||
return new String(getContent(), encoding);
|
return BufferUtil.toString(buffer, Charset.forName(encoding));
|
||||||
}
|
|
||||||
catch (UnsupportedEncodingException x)
|
|
||||||
{
|
|
||||||
throw new UnsupportedCharsetException(encoding);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -161,6 +166,19 @@ public abstract class BufferingResponseListener extends Listener.Adapter
|
||||||
*/
|
*/
|
||||||
public String getContentAsString(Charset encoding)
|
public String getContentAsString(Charset encoding)
|
||||||
{
|
{
|
||||||
return new String(getContent(), encoding);
|
if (buffer==null)
|
||||||
|
return null;
|
||||||
|
return BufferUtil.toString(buffer, encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* @return Content as InputStream
|
||||||
|
*/
|
||||||
|
public InputStream getContentAsInputStream()
|
||||||
|
{
|
||||||
|
if (buffer==null)
|
||||||
|
return new ByteArrayInputStream(new byte[]{});
|
||||||
|
return new ByteArrayInputStream(buffer.array(), buffer.arrayOffset(), buffer.remaining());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import java.io.InterruptedIOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
@ -33,6 +34,7 @@ import org.eclipse.jetty.client.api.ContentProvider;
|
||||||
import org.eclipse.jetty.client.api.Request;
|
import org.eclipse.jetty.client.api.Request;
|
||||||
import org.eclipse.jetty.client.api.Response;
|
import org.eclipse.jetty.client.api.Response;
|
||||||
import org.eclipse.jetty.util.ArrayQueue;
|
import org.eclipse.jetty.util.ArrayQueue;
|
||||||
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
import org.eclipse.jetty.util.Callback;
|
import org.eclipse.jetty.util.Callback;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -83,10 +85,11 @@ import org.eclipse.jetty.util.Callback;
|
||||||
*/
|
*/
|
||||||
public class DeferredContentProvider implements AsyncContentProvider, Closeable
|
public class DeferredContentProvider implements AsyncContentProvider, Closeable
|
||||||
{
|
{
|
||||||
private static final ByteBuffer CLOSE = ByteBuffer.allocate(0);
|
private static final Callback EMPTY_CALLBACK = new Callback.Adapter();
|
||||||
|
private static final AsyncChunk CLOSE = new AsyncChunk(BufferUtil.EMPTY_BUFFER, EMPTY_CALLBACK);
|
||||||
|
|
||||||
private final Object lock = this;
|
private final Object lock = this;
|
||||||
private final Queue<ByteBuffer> chunks = new ArrayQueue<>(4, 64, lock);
|
private final Queue<AsyncChunk> chunks = new ArrayQueue<>(4, 64, lock);
|
||||||
private final AtomicReference<Listener> listener = new AtomicReference<>();
|
private final AtomicReference<Listener> listener = new AtomicReference<>();
|
||||||
private final Iterator<ByteBuffer> iterator = new DeferredContentProviderIterator();
|
private final Iterator<ByteBuffer> iterator = new DeferredContentProviderIterator();
|
||||||
private final AtomicBoolean closed = new AtomicBoolean();
|
private final AtomicBoolean closed = new AtomicBoolean();
|
||||||
|
@ -126,12 +129,22 @@ public class DeferredContentProvider implements AsyncContentProvider, Closeable
|
||||||
* @return true if the content was added, false otherwise
|
* @return true if the content was added, false otherwise
|
||||||
*/
|
*/
|
||||||
public boolean offer(ByteBuffer buffer)
|
public boolean offer(ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
return offer(buffer, EMPTY_CALLBACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean offer(ByteBuffer buffer, Callback callback)
|
||||||
|
{
|
||||||
|
return offer(new AsyncChunk(buffer, callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean offer(AsyncChunk chunk)
|
||||||
{
|
{
|
||||||
boolean result;
|
boolean result;
|
||||||
synchronized (lock)
|
synchronized (lock)
|
||||||
{
|
{
|
||||||
result = chunks.offer(buffer);
|
result = chunks.offer(chunk);
|
||||||
if (result && buffer != CLOSE)
|
if (result && chunk != CLOSE)
|
||||||
++size;
|
++size;
|
||||||
}
|
}
|
||||||
if (result)
|
if (result)
|
||||||
|
@ -186,7 +199,7 @@ public class DeferredContentProvider implements AsyncContentProvider, Closeable
|
||||||
|
|
||||||
private class DeferredContentProviderIterator implements Iterator<ByteBuffer>, Callback
|
private class DeferredContentProviderIterator implements Iterator<ByteBuffer>, Callback
|
||||||
{
|
{
|
||||||
private ByteBuffer current;
|
private AsyncChunk current;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasNext()
|
public boolean hasNext()
|
||||||
|
@ -202,10 +215,10 @@ public class DeferredContentProvider implements AsyncContentProvider, Closeable
|
||||||
{
|
{
|
||||||
synchronized (lock)
|
synchronized (lock)
|
||||||
{
|
{
|
||||||
ByteBuffer element = current = chunks.poll();
|
AsyncChunk chunk = current = chunks.poll();
|
||||||
if (element == CLOSE)
|
if (chunk == CLOSE)
|
||||||
throw new NoSuchElementException();
|
throw new NoSuchElementException();
|
||||||
return element;
|
return chunk == null ? null : chunk.buffer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,24 +231,44 @@ public class DeferredContentProvider implements AsyncContentProvider, Closeable
|
||||||
@Override
|
@Override
|
||||||
public void succeeded()
|
public void succeeded()
|
||||||
{
|
{
|
||||||
|
AsyncChunk chunk;
|
||||||
synchronized (lock)
|
synchronized (lock)
|
||||||
{
|
{
|
||||||
if (current != null)
|
chunk = current;
|
||||||
|
if (chunk != null)
|
||||||
{
|
{
|
||||||
--size;
|
--size;
|
||||||
lock.notify();
|
lock.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (chunk != null)
|
||||||
|
chunk.callback.succeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void failed(Throwable x)
|
public void failed(Throwable x)
|
||||||
{
|
{
|
||||||
|
AsyncChunk chunk;
|
||||||
synchronized (lock)
|
synchronized (lock)
|
||||||
{
|
{
|
||||||
|
chunk = current;
|
||||||
failure = x;
|
failure = x;
|
||||||
lock.notify();
|
lock.notify();
|
||||||
}
|
}
|
||||||
|
if (chunk != null)
|
||||||
|
chunk.callback.failed(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class AsyncChunk
|
||||||
|
{
|
||||||
|
private final ByteBuffer buffer;
|
||||||
|
private final Callback callback;
|
||||||
|
|
||||||
|
private AsyncChunk(ByteBuffer buffer, Callback callback)
|
||||||
|
{
|
||||||
|
this.buffer = Objects.requireNonNull(buffer);
|
||||||
|
this.callback = Objects.requireNonNull(callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,6 +102,16 @@ public class InputStreamContentProvider implements ContentProvider
|
||||||
return ByteBuffer.wrap(buffer, offset, length);
|
return ByteBuffer.wrap(buffer, offset, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback method invoked when an exception is thrown while reading
|
||||||
|
* from the stream.
|
||||||
|
*
|
||||||
|
* @param failure the exception thrown while reading from the stream.
|
||||||
|
*/
|
||||||
|
protected void onReadFailure(Throwable failure)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<ByteBuffer> iterator()
|
public Iterator<ByteBuffer> iterator()
|
||||||
{
|
{
|
||||||
|
@ -166,6 +176,7 @@ public class InputStreamContentProvider implements ContentProvider
|
||||||
if (failure == null)
|
if (failure == null)
|
||||||
{
|
{
|
||||||
failure = x;
|
failure = x;
|
||||||
|
onReadFailure(x);
|
||||||
// Signal we have more content to cause a call to
|
// Signal we have more content to cause a call to
|
||||||
// next() which will throw NoSuchElementException.
|
// next() which will throw NoSuchElementException.
|
||||||
hasNext = Boolean.TRUE;
|
hasNext = Boolean.TRUE;
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,12 +37,14 @@ import java.util.List;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.Exchanger;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.zip.GZIPOutputStream;
|
import java.util.zip.GZIPOutputStream;
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.ServletOutputStream;
|
import javax.servlet.ServletOutputStream;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
@ -69,6 +71,7 @@ import org.eclipse.jetty.http.HttpVersion;
|
||||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
import org.eclipse.jetty.toolchain.test.TestingDir;
|
import org.eclipse.jetty.toolchain.test.TestingDir;
|
||||||
import org.eclipse.jetty.toolchain.test.annotation.Slow;
|
import org.eclipse.jetty.toolchain.test.annotation.Slow;
|
||||||
|
import org.eclipse.jetty.util.Callback;
|
||||||
import org.eclipse.jetty.util.FuturePromise;
|
import org.eclipse.jetty.util.FuturePromise;
|
||||||
import org.eclipse.jetty.util.IO;
|
import org.eclipse.jetty.util.IO;
|
||||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
|
@ -985,6 +988,13 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
||||||
counter.incrementAndGet();
|
counter.incrementAndGet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onContent(Response response, ByteBuffer content, Callback callback)
|
||||||
|
{
|
||||||
|
// Should not be invoked
|
||||||
|
counter.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Response response)
|
public void onSuccess(Response response)
|
||||||
{
|
{
|
||||||
|
@ -1012,6 +1022,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
||||||
.onResponseHeader(listener)
|
.onResponseHeader(listener)
|
||||||
.onResponseHeaders(listener)
|
.onResponseHeaders(listener)
|
||||||
.onResponseContent(listener)
|
.onResponseContent(listener)
|
||||||
|
.onResponseContentAsync(listener)
|
||||||
.onResponseSuccess(listener)
|
.onResponseSuccess(listener)
|
||||||
.onResponseFailure(listener)
|
.onResponseFailure(listener)
|
||||||
.send(listener);
|
.send(listener);
|
||||||
|
@ -1038,26 +1049,33 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
final AtomicInteger complete = new AtomicInteger();
|
final Exchanger<Response> ex = new Exchanger<Response>();
|
||||||
BufferingResponseListener listener = new BufferingResponseListener()
|
BufferingResponseListener listener = new BufferingResponseListener()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void onComplete(Result result)
|
public void onComplete(Result result)
|
||||||
{
|
{
|
||||||
complete.incrementAndGet();
|
try
|
||||||
|
{
|
||||||
|
ex.exchange(result.getResponse());
|
||||||
|
}
|
||||||
|
catch (InterruptedException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
|
|
||||||
|
client.newRequest("localhost", connector.getLocalPort())
|
||||||
.scheme(scheme)
|
.scheme(scheme)
|
||||||
.onResponseContent(listener)
|
.send(listener);
|
||||||
.onComplete(listener)
|
|
||||||
.send();
|
Response response = ex.exchange(null);
|
||||||
|
|
||||||
Assert.assertEquals(200, response.getStatus());
|
Assert.assertEquals(200, response.getStatus());
|
||||||
Assert.assertEquals(1, complete.get());
|
|
||||||
Assert.assertArrayEquals(content, listener.getContent());
|
Assert.assertArrayEquals(content, listener.getContent());
|
||||||
Assert.assertArrayEquals(content, response.getContent());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -24,7 +24,6 @@ import java.util.Arrays;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
@ -37,6 +36,7 @@ import org.eclipse.jetty.client.api.Result;
|
||||||
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
|
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
|
||||||
import org.eclipse.jetty.client.util.ByteBufferContentProvider;
|
import org.eclipse.jetty.client.util.ByteBufferContentProvider;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
import org.eclipse.jetty.http.HttpVersion;
|
||||||
import org.eclipse.jetty.server.Handler;
|
import org.eclipse.jetty.server.Handler;
|
||||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
import org.eclipse.jetty.toolchain.test.annotation.Slow;
|
import org.eclipse.jetty.toolchain.test.annotation.Slow;
|
||||||
|
@ -489,4 +489,40 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
||||||
Assert.assertEquals(0, idleConnections.size());
|
Assert.assertEquals(0, idleConnections.size());
|
||||||
Assert.assertEquals(0, activeConnections.size());
|
Assert.assertEquals(0, activeConnections.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConnectionForHTTP10ResponseIsRemoved() throws Exception
|
||||||
|
{
|
||||||
|
start(new EmptyServerHandler());
|
||||||
|
|
||||||
|
String host = "localhost";
|
||||||
|
int port = connector.getLocalPort();
|
||||||
|
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||||
|
ConnectionPool connectionPool = destination.getConnectionPool();
|
||||||
|
|
||||||
|
final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||||
|
Assert.assertEquals(0, idleConnections.size());
|
||||||
|
|
||||||
|
final BlockingQueue<Connection> activeConnections = connectionPool.getActiveConnections();
|
||||||
|
Assert.assertEquals(0, activeConnections.size());
|
||||||
|
|
||||||
|
client.setStrictEventOrdering(false);
|
||||||
|
ContentResponse response = client.newRequest(host, port)
|
||||||
|
.scheme(scheme)
|
||||||
|
.onResponseBegin(new Response.BeginListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onBegin(Response response)
|
||||||
|
{
|
||||||
|
// Simulate a HTTP 1.0 response has been received.
|
||||||
|
((HttpResponse)response).version(HttpVersion.HTTP_1_0);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.send();
|
||||||
|
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
Assert.assertEquals(0, idleConnections.size());
|
||||||
|
Assert.assertEquals(0, activeConnections.size());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -569,8 +569,8 @@
|
||||||
<arguments>
|
<arguments>
|
||||||
<argument>jetty.home=${assembly-directory}</argument>
|
<argument>jetty.home=${assembly-directory}</argument>
|
||||||
<argument>jetty.base=${assembly-directory}/demo-base</argument>
|
<argument>jetty.base=${assembly-directory}/demo-base</argument>
|
||||||
<argument>--add-to-start=server,continuation,deploy,ext,resources,client,annotations,jndi,servlets</argument>
|
<argument>--add-to-start=server,continuation,deploy,websocket,ext,resources,client,annotations,jndi,servlets</argument>
|
||||||
<argument>--add-to-startd-ini=jsp,jstl,http,https</argument>
|
<argument>--add-to-startd=jsp,jstl,http,https</argument>
|
||||||
</arguments>
|
</arguments>
|
||||||
</configuration>
|
</configuration>
|
||||||
<goals>
|
<goals>
|
||||||
|
|
|
@ -105,8 +105,14 @@ findDirectory()
|
||||||
|
|
||||||
running()
|
running()
|
||||||
{
|
{
|
||||||
|
if [ -f "$1" ]
|
||||||
|
then
|
||||||
local PID=$(cat "$1" 2>/dev/null) || return 1
|
local PID=$(cat "$1" 2>/dev/null) || return 1
|
||||||
kill -0 "$PID" 2>/dev/null
|
kill -0 "$PID" 2>/dev/null
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
rm -f "$1"
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
started()
|
started()
|
||||||
|
@ -408,16 +414,10 @@ case "$ACTION" in
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|
||||||
if [ -f "$JETTY_PID" ]
|
|
||||||
then
|
|
||||||
if running $JETTY_PID
|
if running $JETTY_PID
|
||||||
then
|
then
|
||||||
echo "Already Running!"
|
echo "Already Running $(cat $JETTY_PID)!"
|
||||||
exit 1
|
exit 1
|
||||||
else
|
|
||||||
# dead pid file - remove
|
|
||||||
rm -f "$JETTY_PID"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$JETTY_USER" ]
|
if [ "$JETTY_USER" ]
|
||||||
|
@ -519,16 +519,10 @@ case "$ACTION" in
|
||||||
run|demo)
|
run|demo)
|
||||||
echo "Running Jetty: "
|
echo "Running Jetty: "
|
||||||
|
|
||||||
if [ -f "$JETTY_PID" ]
|
|
||||||
then
|
|
||||||
if running "$JETTY_PID"
|
if running "$JETTY_PID"
|
||||||
then
|
then
|
||||||
echo "Already Running!"
|
echo Already Running $(cat "$JETTY_PID")!
|
||||||
exit 1
|
exit 1
|
||||||
else
|
|
||||||
# dead pid file - remove
|
|
||||||
rm -f "$JETTY_PID"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exec "${RUN_CMD[@]}"
|
exec "${RUN_CMD[@]}"
|
||||||
|
@ -550,7 +544,7 @@ case "$ACTION" in
|
||||||
echo "RUN_CMD = ${RUN_CMD[*]}"
|
echo "RUN_CMD = ${RUN_CMD[*]}"
|
||||||
echo
|
echo
|
||||||
|
|
||||||
if [ -f "$JETTY_PID" ]
|
if running "$JETTY_PID"
|
||||||
then
|
then
|
||||||
echo "Jetty running pid=$(< "$JETTY_PID")"
|
echo "Jetty running pid=$(< "$JETTY_PID")"
|
||||||
exit 0
|
exit 0
|
||||||
|
|
|
@ -16,5 +16,5 @@ jsp-impl/${jsp-impl}-jsp
|
||||||
# default jetty >= 9.2
|
# default jetty >= 9.2
|
||||||
jsp-impl=apache
|
jsp-impl=apache
|
||||||
|
|
||||||
# To use an non-jdk compiler for JSP compilation uncomment next line
|
# To use a non-jdk compiler for JSP compilation when using glassfish uncomment next line
|
||||||
# -Dorg.apache.jasper.compiler.disablejsr199=true
|
# -Dorg.apache.jasper.compiler.disablejsr199=true
|
||||||
|
|
|
@ -28,10 +28,9 @@ import org.eclipse.jetty.fcgi.generator.Flusher;
|
||||||
import org.eclipse.jetty.fcgi.generator.Generator;
|
import org.eclipse.jetty.fcgi.generator.Generator;
|
||||||
import org.eclipse.jetty.http.HttpField;
|
import org.eclipse.jetty.http.HttpField;
|
||||||
import org.eclipse.jetty.http.HttpFields;
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
|
||||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
|
||||||
import org.eclipse.jetty.http.HttpVersion;
|
import org.eclipse.jetty.http.HttpVersion;
|
||||||
import org.eclipse.jetty.io.IdleTimeout;
|
import org.eclipse.jetty.io.IdleTimeout;
|
||||||
|
import org.eclipse.jetty.util.Callback;
|
||||||
|
|
||||||
public class HttpChannelOverFCGI extends HttpChannel
|
public class HttpChannelOverFCGI extends HttpChannel
|
||||||
{
|
{
|
||||||
|
@ -83,42 +82,46 @@ public class HttpChannelOverFCGI extends HttpChannel
|
||||||
return receiver.abort(cause);
|
return receiver.abort(cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void responseBegin(int code, String reason)
|
protected boolean responseBegin(int code, String reason)
|
||||||
{
|
{
|
||||||
HttpExchange exchange = getHttpExchange();
|
HttpExchange exchange = getHttpExchange();
|
||||||
if (exchange != null)
|
if (exchange == null)
|
||||||
{
|
return false;
|
||||||
exchange.getResponse().version(version).status(code).reason(reason);
|
exchange.getResponse().version(version).status(code).reason(reason);
|
||||||
receiver.responseBegin(exchange);
|
return receiver.responseBegin(exchange);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void responseHeader(HttpField field)
|
protected boolean responseHeader(HttpField field)
|
||||||
|
{
|
||||||
|
HttpExchange exchange = getHttpExchange();
|
||||||
|
return exchange != null && receiver.responseHeader(exchange, field);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean responseHeaders()
|
||||||
|
{
|
||||||
|
HttpExchange exchange = getHttpExchange();
|
||||||
|
return exchange != null && receiver.responseHeaders(exchange);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean content(ByteBuffer buffer, Callback callback)
|
||||||
{
|
{
|
||||||
HttpExchange exchange = getHttpExchange();
|
HttpExchange exchange = getHttpExchange();
|
||||||
if (exchange != null)
|
if (exchange != null)
|
||||||
receiver.responseHeader(exchange, field);
|
return receiver.responseContent(exchange, buffer, callback);
|
||||||
|
callback.succeeded();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void responseHeaders()
|
protected boolean responseSuccess()
|
||||||
{
|
{
|
||||||
HttpExchange exchange = getHttpExchange();
|
HttpExchange exchange = getHttpExchange();
|
||||||
if (exchange != null)
|
return exchange != null && receiver.responseSuccess(exchange);
|
||||||
receiver.responseHeaders(exchange);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void content(ByteBuffer buffer)
|
protected boolean responseFailure(Throwable failure)
|
||||||
{
|
{
|
||||||
HttpExchange exchange = getHttpExchange();
|
HttpExchange exchange = getHttpExchange();
|
||||||
if (exchange != null)
|
return exchange != null && receiver.responseFailure(failure);
|
||||||
receiver.responseContent(exchange, buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void responseSuccess()
|
|
||||||
{
|
|
||||||
HttpExchange exchange = getHttpExchange();
|
|
||||||
if (exchange != null)
|
|
||||||
receiver.responseSuccess(exchange);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -126,12 +129,10 @@ public class HttpChannelOverFCGI extends HttpChannel
|
||||||
{
|
{
|
||||||
super.exchangeTerminated(result);
|
super.exchangeTerminated(result);
|
||||||
idle.onClose();
|
idle.onClose();
|
||||||
boolean close = result.isFailed();
|
|
||||||
HttpFields responseHeaders = result.getResponse().getHeaders();
|
HttpFields responseHeaders = result.getResponse().getHeaders();
|
||||||
close |= responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString());
|
if (result.isFailed())
|
||||||
if (close)
|
connection.close(result.getFailure());
|
||||||
connection.close();
|
else if (!connection.closeByHTTP(responseHeaders))
|
||||||
else
|
|
||||||
connection.release(this);
|
connection.release(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,6 +155,7 @@ public class HttpChannelOverFCGI extends HttpChannel
|
||||||
@Override
|
@Override
|
||||||
protected void onIdleExpired(TimeoutException timeout)
|
protected void onIdleExpired(TimeoutException timeout)
|
||||||
{
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Idle timeout for request {}", request);
|
LOG.debug("Idle timeout for request {}", request);
|
||||||
connection.abort(timeout);
|
connection.abort(timeout);
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,7 @@ public class HttpClientTransportOverFCGI extends AbstractHttpClientTransport
|
||||||
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
||||||
{
|
{
|
||||||
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
|
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
|
||||||
HttpConnectionOverFCGI connection = new HttpConnectionOverFCGI(endPoint, destination);
|
HttpConnectionOverFCGI connection = new HttpConnectionOverFCGI(endPoint, destination, isMultiplexed());
|
||||||
LOG.debug("Created {}", connection);
|
LOG.debug("Created {}", connection);
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Promise<Connection> promise = (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
Promise<Connection> promise = (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
||||||
|
|
|
@ -31,7 +31,6 @@ import org.eclipse.jetty.client.HttpClient;
|
||||||
import org.eclipse.jetty.client.HttpConnection;
|
import org.eclipse.jetty.client.HttpConnection;
|
||||||
import org.eclipse.jetty.client.HttpDestination;
|
import org.eclipse.jetty.client.HttpDestination;
|
||||||
import org.eclipse.jetty.client.HttpExchange;
|
import org.eclipse.jetty.client.HttpExchange;
|
||||||
import org.eclipse.jetty.client.PoolingHttpDestination;
|
|
||||||
import org.eclipse.jetty.client.api.Connection;
|
import org.eclipse.jetty.client.api.Connection;
|
||||||
import org.eclipse.jetty.client.api.Request;
|
import org.eclipse.jetty.client.api.Request;
|
||||||
import org.eclipse.jetty.client.api.Response;
|
import org.eclipse.jetty.client.api.Response;
|
||||||
|
@ -39,10 +38,14 @@ import org.eclipse.jetty.fcgi.FCGI;
|
||||||
import org.eclipse.jetty.fcgi.generator.Flusher;
|
import org.eclipse.jetty.fcgi.generator.Flusher;
|
||||||
import org.eclipse.jetty.fcgi.parser.ClientParser;
|
import org.eclipse.jetty.fcgi.parser.ClientParser;
|
||||||
import org.eclipse.jetty.http.HttpField;
|
import org.eclipse.jetty.http.HttpField;
|
||||||
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||||
import org.eclipse.jetty.io.AbstractConnection;
|
import org.eclipse.jetty.io.AbstractConnection;
|
||||||
import org.eclipse.jetty.io.ByteBufferPool;
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.util.BufferUtil;
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
import org.eclipse.jetty.util.CompletableCallback;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
|
||||||
|
@ -55,14 +58,17 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
private final AtomicBoolean closed = new AtomicBoolean();
|
private final AtomicBoolean closed = new AtomicBoolean();
|
||||||
private final Flusher flusher;
|
private final Flusher flusher;
|
||||||
private final HttpDestination destination;
|
private final HttpDestination destination;
|
||||||
|
private final boolean multiplexed;
|
||||||
private final Delegate delegate;
|
private final Delegate delegate;
|
||||||
private final ClientParser parser;
|
private final ClientParser parser;
|
||||||
|
private ByteBuffer buffer;
|
||||||
|
|
||||||
public HttpConnectionOverFCGI(EndPoint endPoint, HttpDestination destination)
|
public HttpConnectionOverFCGI(EndPoint endPoint, HttpDestination destination, boolean multiplexed)
|
||||||
{
|
{
|
||||||
super(endPoint, destination.getHttpClient().getExecutor(), destination.getHttpClient().isDispatchIO());
|
super(endPoint, destination.getHttpClient().getExecutor(), destination.getHttpClient().isDispatchIO());
|
||||||
this.flusher = new Flusher(endPoint);
|
|
||||||
this.destination = destination;
|
this.destination = destination;
|
||||||
|
this.multiplexed = multiplexed;
|
||||||
|
this.flusher = new Flusher(endPoint);
|
||||||
this.delegate = new Delegate(destination);
|
this.delegate = new Delegate(destination);
|
||||||
this.parser = new ClientParser(new ResponseListener());
|
this.parser = new ClientParser(new ResponseListener());
|
||||||
requests.addLast(0);
|
requests.addLast(0);
|
||||||
|
@ -94,53 +100,76 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
@Override
|
@Override
|
||||||
public void onFillable()
|
public void onFillable()
|
||||||
{
|
{
|
||||||
EndPoint endPoint = getEndPoint();
|
|
||||||
HttpClient client = destination.getHttpClient();
|
HttpClient client = destination.getHttpClient();
|
||||||
ByteBufferPool bufferPool = client.getByteBufferPool();
|
ByteBufferPool bufferPool = client.getByteBufferPool();
|
||||||
ByteBuffer buffer = bufferPool.acquire(client.getResponseBufferSize(), true);
|
buffer = bufferPool.acquire(client.getResponseBufferSize(), true);
|
||||||
try
|
process();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void process()
|
||||||
{
|
{
|
||||||
|
if (readAndParse())
|
||||||
|
{
|
||||||
|
HttpClient client = destination.getHttpClient();
|
||||||
|
ByteBufferPool bufferPool = client.getByteBufferPool();
|
||||||
|
bufferPool.release(buffer);
|
||||||
|
// Don't linger the buffer around if we are idle.
|
||||||
|
buffer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean readAndParse()
|
||||||
|
{
|
||||||
|
EndPoint endPoint = getEndPoint();
|
||||||
|
ByteBuffer buffer = this.buffer;
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!parse(buffer))
|
||||||
|
return false;
|
||||||
|
|
||||||
int read = endPoint.fill(buffer);
|
int read = endPoint.fill(buffer);
|
||||||
if (LOG.isDebugEnabled()) // Avoid boxing of variable 'read'
|
if (LOG.isDebugEnabled()) // Avoid boxing of variable 'read'.
|
||||||
LOG.debug("Read {} bytes from {}", read, endPoint);
|
LOG.debug("Read {} bytes from {}", read, endPoint);
|
||||||
if (read > 0)
|
if (read > 0)
|
||||||
{
|
{
|
||||||
parse(buffer);
|
if (!parse(buffer))
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
else if (read == 0)
|
else if (read == 0)
|
||||||
{
|
{
|
||||||
fillInterested();
|
fillInterested();
|
||||||
break;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
shutdown();
|
shutdown();
|
||||||
break;
|
return true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception x)
|
catch (Exception x)
|
||||||
{
|
{
|
||||||
LOG.debug(x);
|
LOG.debug(x);
|
||||||
// TODO: fail and close ?
|
close(x);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
bufferPool.release(buffer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parse(ByteBuffer buffer)
|
private boolean parse(ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
while (buffer.hasRemaining())
|
return !parser.parse(buffer);
|
||||||
parser.parse(buffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void shutdown()
|
private void shutdown()
|
||||||
{
|
{
|
||||||
close(new EOFException());
|
// Close explicitly only if we are idle, since the request may still
|
||||||
|
// be in progress, otherwise close only if we can fail the responses.
|
||||||
|
if (channels.isEmpty())
|
||||||
|
close();
|
||||||
|
else
|
||||||
|
failAndClose(new EOFException());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -153,13 +182,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
protected void release(HttpChannelOverFCGI channel)
|
protected void release(HttpChannelOverFCGI channel)
|
||||||
{
|
{
|
||||||
channels.remove(channel.getRequest());
|
channels.remove(channel.getRequest());
|
||||||
if (destination instanceof PoolingHttpDestination)
|
destination.release(this);
|
||||||
{
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
PoolingHttpDestination<HttpConnectionOverFCGI> fcgiDestination =
|
|
||||||
(PoolingHttpDestination<HttpConnectionOverFCGI>)destination;
|
|
||||||
fcgiDestination.release(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -168,7 +191,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
close(new AsynchronousCloseException());
|
close(new AsynchronousCloseException());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void close(Throwable failure)
|
protected void close(Throwable failure)
|
||||||
{
|
{
|
||||||
if (closed.compareAndSet(false, true))
|
if (closed.compareAndSet(false, true))
|
||||||
{
|
{
|
||||||
|
@ -184,6 +207,16 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected boolean closeByHTTP(HttpFields fields)
|
||||||
|
{
|
||||||
|
if (multiplexed)
|
||||||
|
return false;
|
||||||
|
if (!fields.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()))
|
||||||
|
return false;
|
||||||
|
close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
protected void abort(Throwable failure)
|
protected void abort(Throwable failure)
|
||||||
{
|
{
|
||||||
for (HttpChannelOverFCGI channel : channels.values())
|
for (HttpChannelOverFCGI channel : channels.values())
|
||||||
|
@ -195,6 +228,15 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
channels.clear();
|
channels.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void failAndClose(Throwable failure)
|
||||||
|
{
|
||||||
|
boolean result = false;
|
||||||
|
for (HttpChannelOverFCGI channel : channels.values())
|
||||||
|
result |= channel.responseFailure(failure);
|
||||||
|
if (result)
|
||||||
|
close(failure);
|
||||||
|
}
|
||||||
|
|
||||||
private int acquireRequest()
|
private int acquireRequest()
|
||||||
{
|
{
|
||||||
synchronized (requests)
|
synchronized (requests)
|
||||||
|
@ -291,7 +333,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
|
public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
switch (stream)
|
switch (stream)
|
||||||
{
|
{
|
||||||
|
@ -299,7 +341,25 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
{
|
{
|
||||||
HttpChannelOverFCGI channel = channels.get(request);
|
HttpChannelOverFCGI channel = channels.get(request);
|
||||||
if (channel != null)
|
if (channel != null)
|
||||||
channel.content(buffer);
|
{
|
||||||
|
CompletableCallback callback = new CompletableCallback()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void resume()
|
||||||
|
{
|
||||||
|
LOG.debug("Content consumed asynchronously, resuming processing");
|
||||||
|
process();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void abort(Throwable x)
|
||||||
|
{
|
||||||
|
close(x);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
channel.content(buffer, callback);
|
||||||
|
return callback.tryComplete();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
noChannel(request);
|
noChannel(request);
|
||||||
break;
|
break;
|
||||||
|
@ -314,6 +374,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -322,7 +383,22 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
HttpChannelOverFCGI channel = channels.get(request);
|
HttpChannelOverFCGI channel = channels.get(request);
|
||||||
if (channel != null)
|
if (channel != null)
|
||||||
{
|
{
|
||||||
channel.responseSuccess();
|
if (channel.responseSuccess())
|
||||||
|
releaseRequest(request);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
noChannel(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(int request, Throwable failure)
|
||||||
|
{
|
||||||
|
HttpChannelOverFCGI channel = channels.get(request);
|
||||||
|
if (channel != null)
|
||||||
|
{
|
||||||
|
if (channel.responseFailure(failure))
|
||||||
releaseRequest(request);
|
releaseRequest(request);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -24,6 +24,7 @@ import org.eclipse.jetty.client.HttpChannel;
|
||||||
import org.eclipse.jetty.client.HttpExchange;
|
import org.eclipse.jetty.client.HttpExchange;
|
||||||
import org.eclipse.jetty.client.HttpReceiver;
|
import org.eclipse.jetty.client.HttpReceiver;
|
||||||
import org.eclipse.jetty.http.HttpField;
|
import org.eclipse.jetty.http.HttpField;
|
||||||
|
import org.eclipse.jetty.util.Callback;
|
||||||
|
|
||||||
public class HttpReceiverOverFCGI extends HttpReceiver
|
public class HttpReceiverOverFCGI extends HttpReceiver
|
||||||
{
|
{
|
||||||
|
@ -51,9 +52,9 @@ public class HttpReceiverOverFCGI extends HttpReceiver
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean responseContent(HttpExchange exchange, ByteBuffer buffer)
|
protected boolean responseContent(HttpExchange exchange, ByteBuffer buffer, Callback callback)
|
||||||
{
|
{
|
||||||
return super.responseContent(exchange, buffer);
|
return super.responseContent(exchange, buffer, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -88,21 +88,36 @@ public class ServerGenerator extends Generator
|
||||||
return generateContent(request, buffer, true, false, callback, FCGI.FrameType.STDOUT);
|
return generateContent(request, buffer, true, false, callback, FCGI.FrameType.STDOUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result generateResponseContent(int request, ByteBuffer content, boolean lastContent, Callback callback)
|
public Result generateResponseContent(int request, ByteBuffer content, boolean lastContent, boolean aborted, Callback callback)
|
||||||
|
{
|
||||||
|
if (aborted)
|
||||||
|
{
|
||||||
|
Result result = new Result(byteBufferPool, callback);
|
||||||
|
if (lastContent)
|
||||||
|
result.append(generateEndRequest(request, true), true);
|
||||||
|
else
|
||||||
|
result.append(BufferUtil.EMPTY_BUFFER, false);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
Result result = generateContent(request, content, false, lastContent, callback, FCGI.FrameType.STDOUT);
|
Result result = generateContent(request, content, false, lastContent, callback, FCGI.FrameType.STDOUT);
|
||||||
if (lastContent)
|
if (lastContent)
|
||||||
|
result.append(generateEndRequest(request, false), true);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ByteBuffer generateEndRequest(int request, boolean aborted)
|
||||||
{
|
{
|
||||||
// Generate the FCGI_END_REQUEST
|
|
||||||
request &= 0xFF_FF;
|
request &= 0xFF_FF;
|
||||||
ByteBuffer endRequestBuffer = byteBufferPool.acquire(8, false);
|
ByteBuffer endRequestBuffer = byteBufferPool.acquire(8, false);
|
||||||
BufferUtil.clearToFill(endRequestBuffer);
|
BufferUtil.clearToFill(endRequestBuffer);
|
||||||
endRequestBuffer.putInt(0x01_03_00_00 + request);
|
endRequestBuffer.putInt(0x01_03_00_00 + request);
|
||||||
endRequestBuffer.putInt(0x00_08_00_00);
|
endRequestBuffer.putInt(0x00_08_00_00);
|
||||||
endRequestBuffer.putLong(0x00L);
|
endRequestBuffer.putInt(aborted ? 1 : 0);
|
||||||
|
endRequestBuffer.putInt(0);
|
||||||
endRequestBuffer.flip();
|
endRequestBuffer.flip();
|
||||||
result = result.append(endRequestBuffer, true);
|
return endRequestBuffer;
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ public class BeginRequestContentParser extends ContentParser
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean parse(ByteBuffer buffer)
|
public Result parse(ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
while (buffer.hasRemaining())
|
while (buffer.hasRemaining())
|
||||||
{
|
{
|
||||||
|
@ -78,7 +78,7 @@ public class BeginRequestContentParser extends ContentParser
|
||||||
buffer.position(buffer.position() + 5);
|
buffer.position(buffer.position() + 5);
|
||||||
onStart();
|
onStart();
|
||||||
reset();
|
reset();
|
||||||
return true;
|
return Result.COMPLETE;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -94,7 +94,7 @@ public class BeginRequestContentParser extends ContentParser
|
||||||
{
|
{
|
||||||
onStart();
|
onStart();
|
||||||
reset();
|
reset();
|
||||||
return true;
|
return Result.COMPLETE;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -104,7 +104,7 @@ public class BeginRequestContentParser extends ContentParser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return Result.PENDING;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onStart()
|
private void onStart()
|
||||||
|
|
|
@ -86,9 +86,9 @@ public class ClientParser extends Parser
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
|
public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
listener.onContent(request, stream, buffer);
|
return listener.onContent(request, stream, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -98,5 +98,13 @@ public class ClientParser extends Parser
|
||||||
for (StreamContentParser streamParser : streamParsers)
|
for (StreamContentParser streamParser : streamParsers)
|
||||||
streamParser.end(request);
|
streamParser.end(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(int request, Throwable failure)
|
||||||
|
{
|
||||||
|
listener.onFailure(request, failure);
|
||||||
|
for (StreamContentParser streamParser : streamParsers)
|
||||||
|
streamParser.end(request);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ public abstract class ContentParser
|
||||||
this.headerParser = headerParser;
|
this.headerParser = headerParser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract boolean parse(ByteBuffer buffer);
|
public abstract Result parse(ByteBuffer buffer);
|
||||||
|
|
||||||
public void noContent()
|
public void noContent()
|
||||||
{
|
{
|
||||||
|
@ -45,4 +45,9 @@ public abstract class ContentParser
|
||||||
{
|
{
|
||||||
return headerParser.getContentLength();
|
return headerParser.getContentLength();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum Result
|
||||||
|
{
|
||||||
|
PENDING, ASYNC, COMPLETE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ public class EndRequestContentParser extends ContentParser
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean parse(ByteBuffer buffer)
|
public Result parse(ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
while (buffer.hasRemaining())
|
while (buffer.hasRemaining())
|
||||||
{
|
{
|
||||||
|
@ -76,7 +76,7 @@ public class EndRequestContentParser extends ContentParser
|
||||||
buffer.position(buffer.position() + 3);
|
buffer.position(buffer.position() + 3);
|
||||||
onEnd();
|
onEnd();
|
||||||
reset();
|
reset();
|
||||||
return true;
|
return Result.COMPLETE;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -92,7 +92,7 @@ public class EndRequestContentParser extends ContentParser
|
||||||
{
|
{
|
||||||
onEnd();
|
onEnd();
|
||||||
reset();
|
reset();
|
||||||
return true;
|
return Result.COMPLETE;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -102,12 +102,16 @@ public class EndRequestContentParser extends ContentParser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return Result.PENDING;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onEnd()
|
private void onEnd()
|
||||||
{
|
{
|
||||||
// TODO: if protocol != 0, invoke an error callback
|
if (application != 0)
|
||||||
|
listener.onFailure(getRequest(), new Exception("FastCGI application returned code " + application));
|
||||||
|
else if (protocol != 0)
|
||||||
|
listener.onFailure(getRequest(), new Exception("FastCGI server returned code " + protocol));
|
||||||
|
else
|
||||||
listener.onEnd(getRequest());
|
listener.onEnd(getRequest());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,12 @@ public class HeaderParser
|
||||||
private int length;
|
private int length;
|
||||||
private int padding;
|
private int padding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the bytes in the given {@code buffer} as FastCGI header bytes
|
||||||
|
*
|
||||||
|
* @param buffer the bytes to parse
|
||||||
|
* @return whether there were enough bytes for a FastCGI header
|
||||||
|
*/
|
||||||
public boolean parse(ByteBuffer buffer)
|
public boolean parse(ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
while (buffer.hasRemaining())
|
while (buffer.hasRemaining())
|
||||||
|
|
|
@ -45,7 +45,7 @@ public class ParamsContentParser extends ContentParser
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean parse(ByteBuffer buffer)
|
public Result parse(ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
while (buffer.hasRemaining() || state == State.PARAM)
|
while (buffer.hasRemaining() || state == State.PARAM)
|
||||||
{
|
{
|
||||||
|
@ -185,7 +185,7 @@ public class ParamsContentParser extends ContentParser
|
||||||
if (length == 0)
|
if (length == 0)
|
||||||
{
|
{
|
||||||
reset();
|
reset();
|
||||||
return true;
|
return Result.COMPLETE;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -195,7 +195,7 @@ public class ParamsContentParser extends ContentParser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return Result.PENDING;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -29,7 +29,7 @@ public abstract class Parser
|
||||||
private State state = State.HEADER;
|
private State state = State.HEADER;
|
||||||
private int padding;
|
private int padding;
|
||||||
|
|
||||||
public void parse(ByteBuffer buffer)
|
public boolean parse(ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
|
@ -38,7 +38,7 @@ public abstract class Parser
|
||||||
case HEADER:
|
case HEADER:
|
||||||
{
|
{
|
||||||
if (!headerParser.parse(buffer))
|
if (!headerParser.parse(buffer))
|
||||||
return;
|
return false;
|
||||||
state = State.CONTENT;
|
state = State.CONTENT;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -51,8 +51,11 @@ public abstract class Parser
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!contentParser.parse(buffer))
|
ContentParser.Result result = contentParser.parse(buffer);
|
||||||
return;
|
if (result == ContentParser.Result.PENDING)
|
||||||
|
return false;
|
||||||
|
else if (result == ContentParser.Result.ASYNC)
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
padding = headerParser.getPaddingLength();
|
padding = headerParser.getPaddingLength();
|
||||||
state = State.PADDING;
|
state = State.PADDING;
|
||||||
|
@ -70,7 +73,7 @@ public abstract class Parser
|
||||||
{
|
{
|
||||||
padding -= buffer.remaining();
|
padding -= buffer.remaining();
|
||||||
buffer.position(buffer.limit());
|
buffer.position(buffer.limit());
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -96,10 +99,12 @@ public abstract class Parser
|
||||||
|
|
||||||
public void onHeaders(int request);
|
public void onHeaders(int request);
|
||||||
|
|
||||||
public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer);
|
public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer);
|
||||||
|
|
||||||
public void onEnd(int request);
|
public void onEnd(int request);
|
||||||
|
|
||||||
|
public void onFailure(int request, Throwable failure);
|
||||||
|
|
||||||
public static class Adapter implements Listener
|
public static class Adapter implements Listener
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -113,14 +118,21 @@ public abstract class Parser
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
|
public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnd(int request)
|
public void onEnd(int request)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(int request, Throwable failure)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ public class ResponseContentParser extends StreamContentParser
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onContent(ByteBuffer buffer)
|
protected boolean onContent(ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
int request = getRequest();
|
int request = getRequest();
|
||||||
ResponseParser parser = parsers.get(request);
|
ResponseParser parser = parsers.get(request);
|
||||||
|
@ -61,7 +61,7 @@ public class ResponseContentParser extends StreamContentParser
|
||||||
parser = new ResponseParser(listener, request);
|
parser = new ResponseParser(listener, request);
|
||||||
parsers.put(request, parser);
|
parsers.put(request, parser);
|
||||||
}
|
}
|
||||||
parser.parse(buffer);
|
return parser.parse(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -87,7 +87,7 @@ public class ResponseContentParser extends StreamContentParser
|
||||||
this.httpParser = new FCGIHttpParser(this);
|
this.httpParser = new FCGIHttpParser(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void parse(ByteBuffer buffer)
|
public boolean parse(ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
LOG.debug("Response {} {} content {} {}", request, FCGI.StreamType.STD_OUT, state, buffer);
|
LOG.debug("Response {} {} content {} {}", request, FCGI.StreamType.STD_OUT, state, buffer);
|
||||||
|
|
||||||
|
@ -117,7 +117,8 @@ public class ResponseContentParser extends StreamContentParser
|
||||||
}
|
}
|
||||||
case RAW_CONTENT:
|
case RAW_CONTENT:
|
||||||
{
|
{
|
||||||
notifyContent(buffer);
|
if (notifyContent(buffer))
|
||||||
|
return true;
|
||||||
remaining = 0;
|
remaining = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -133,6 +134,7 @@ public class ResponseContentParser extends StreamContentParser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -253,15 +255,16 @@ public class ResponseContentParser extends StreamContentParser
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyContent(ByteBuffer buffer)
|
private boolean notifyContent(ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
listener.onContent(request, FCGI.StreamType.STD_OUT, buffer);
|
return listener.onContent(request, FCGI.StreamType.STD_OUT, buffer);
|
||||||
}
|
}
|
||||||
catch (Throwable x)
|
catch (Throwable x)
|
||||||
{
|
{
|
||||||
logger.debug("Exception while invoking listener " + listener, x);
|
logger.debug("Exception while invoking listener " + listener, x);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ public class StreamContentParser extends ContentParser
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean parse(ByteBuffer buffer)
|
public Result parse(ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
while (buffer.hasRemaining())
|
while (buffer.hasRemaining())
|
||||||
{
|
{
|
||||||
|
@ -59,14 +59,15 @@ public class StreamContentParser extends ContentParser
|
||||||
int limit = buffer.limit();
|
int limit = buffer.limit();
|
||||||
buffer.limit(buffer.position() + length);
|
buffer.limit(buffer.position() + length);
|
||||||
ByteBuffer slice = buffer.slice();
|
ByteBuffer slice = buffer.slice();
|
||||||
onContent(slice);
|
|
||||||
buffer.position(buffer.limit());
|
buffer.position(buffer.limit());
|
||||||
buffer.limit(limit);
|
buffer.limit(limit);
|
||||||
contentLength -= length;
|
contentLength -= length;
|
||||||
|
if (onContent(slice))
|
||||||
|
return Result.ASYNC;
|
||||||
if (contentLength > 0)
|
if (contentLength > 0)
|
||||||
break;
|
break;
|
||||||
state = State.LENGTH;
|
state = State.LENGTH;
|
||||||
return true;
|
return Result.COMPLETE;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
|
@ -74,7 +75,7 @@ public class StreamContentParser extends ContentParser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return Result.PENDING;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -90,15 +91,16 @@ public class StreamContentParser extends ContentParser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onContent(ByteBuffer buffer)
|
protected boolean onContent(ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
listener.onContent(getRequest(), streamType, buffer);
|
return listener.onContent(getRequest(), streamType, buffer);
|
||||||
}
|
}
|
||||||
catch (Throwable x)
|
catch (Throwable x)
|
||||||
{
|
{
|
||||||
logger.debug("Exception while invoking listener " + listener, x);
|
logger.debug("Exception while invoking listener " + listener, x);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -158,10 +158,11 @@ public class ClientGeneratorTest
|
||||||
ServerParser parser = new ServerParser(new ServerParser.Listener.Adapter()
|
ServerParser parser = new ServerParser(new ServerParser.Listener.Adapter()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
|
public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
Assert.assertEquals(id, request);
|
Assert.assertEquals(id, request);
|
||||||
totalLength.addAndGet(buffer.remaining());
|
totalLength.addAndGet(buffer.remaining());
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -111,16 +111,17 @@ public class ClientParserTest
|
||||||
ByteBufferPool byteBufferPool = new MappedByteBufferPool();
|
ByteBufferPool byteBufferPool = new MappedByteBufferPool();
|
||||||
ServerGenerator generator = new ServerGenerator(byteBufferPool);
|
ServerGenerator generator = new ServerGenerator(byteBufferPool);
|
||||||
Generator.Result result1 = generator.generateResponseHeaders(id, 200, "OK", fields, null);
|
Generator.Result result1 = generator.generateResponseHeaders(id, 200, "OK", fields, null);
|
||||||
Generator.Result result2 = generator.generateResponseContent(id, null, true, null);
|
Generator.Result result2 = generator.generateResponseContent(id, null, true, false, null);
|
||||||
|
|
||||||
final AtomicInteger verifier = new AtomicInteger();
|
final AtomicInteger verifier = new AtomicInteger();
|
||||||
ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter()
|
ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
|
public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
Assert.assertEquals(id, request);
|
Assert.assertEquals(id, request);
|
||||||
verifier.addAndGet(2);
|
verifier.addAndGet(2);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -162,17 +163,18 @@ public class ClientParserTest
|
||||||
ByteBufferPool byteBufferPool = new MappedByteBufferPool();
|
ByteBufferPool byteBufferPool = new MappedByteBufferPool();
|
||||||
ServerGenerator generator = new ServerGenerator(byteBufferPool);
|
ServerGenerator generator = new ServerGenerator(byteBufferPool);
|
||||||
Generator.Result result1 = generator.generateResponseHeaders(id, code, "OK", fields, null);
|
Generator.Result result1 = generator.generateResponseHeaders(id, code, "OK", fields, null);
|
||||||
Generator.Result result2 = generator.generateResponseContent(id, content, true, null);
|
Generator.Result result2 = generator.generateResponseContent(id, content, true, false, null);
|
||||||
|
|
||||||
final AtomicInteger verifier = new AtomicInteger();
|
final AtomicInteger verifier = new AtomicInteger();
|
||||||
ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter()
|
ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
|
public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
Assert.assertEquals(id, request);
|
Assert.assertEquals(id, request);
|
||||||
Assert.assertEquals(contentLength, buffer.remaining());
|
Assert.assertEquals(contentLength, buffer.remaining());
|
||||||
verifier.addAndGet(2);
|
verifier.addAndGet(2);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -214,17 +216,18 @@ public class ClientParserTest
|
||||||
ByteBufferPool byteBufferPool = new MappedByteBufferPool();
|
ByteBufferPool byteBufferPool = new MappedByteBufferPool();
|
||||||
ServerGenerator generator = new ServerGenerator(byteBufferPool);
|
ServerGenerator generator = new ServerGenerator(byteBufferPool);
|
||||||
Generator.Result result1 = generator.generateResponseHeaders(id, code, "OK", fields, null);
|
Generator.Result result1 = generator.generateResponseHeaders(id, code, "OK", fields, null);
|
||||||
Generator.Result result2 = generator.generateResponseContent(id, content, true, null);
|
Generator.Result result2 = generator.generateResponseContent(id, content, true, false, null);
|
||||||
|
|
||||||
final AtomicInteger totalLength = new AtomicInteger();
|
final AtomicInteger totalLength = new AtomicInteger();
|
||||||
final AtomicBoolean verifier = new AtomicBoolean();
|
final AtomicBoolean verifier = new AtomicBoolean();
|
||||||
ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter()
|
ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
|
public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
Assert.assertEquals(id, request);
|
Assert.assertEquals(id, request);
|
||||||
totalLength.addAndGet(buffer.remaining());
|
totalLength.addAndGet(buffer.remaining());
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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
|
|
|
@ -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>
|
|
|
@ -24,6 +24,8 @@ import org.eclipse.jetty.fcgi.generator.Flusher;
|
||||||
import org.eclipse.jetty.fcgi.generator.Generator;
|
import org.eclipse.jetty.fcgi.generator.Generator;
|
||||||
import org.eclipse.jetty.fcgi.generator.ServerGenerator;
|
import org.eclipse.jetty.fcgi.generator.ServerGenerator;
|
||||||
import org.eclipse.jetty.http.HttpGenerator;
|
import org.eclipse.jetty.http.HttpGenerator;
|
||||||
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||||
import org.eclipse.jetty.io.ByteBufferPool;
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
import org.eclipse.jetty.server.HttpTransport;
|
import org.eclipse.jetty.server.HttpTransport;
|
||||||
import org.eclipse.jetty.util.BufferUtil;
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
@ -35,6 +37,8 @@ public class HttpTransportOverFCGI implements HttpTransport
|
||||||
private final Flusher flusher;
|
private final Flusher flusher;
|
||||||
private final int request;
|
private final int request;
|
||||||
private volatile boolean head;
|
private volatile boolean head;
|
||||||
|
private volatile boolean shutdown;
|
||||||
|
private volatile boolean aborted;
|
||||||
|
|
||||||
public HttpTransportOverFCGI(ByteBufferPool byteBufferPool, Flusher flusher, int request)
|
public HttpTransportOverFCGI(ByteBufferPool byteBufferPool, Flusher flusher, int request)
|
||||||
{
|
{
|
||||||
|
@ -47,13 +51,15 @@ public class HttpTransportOverFCGI implements HttpTransport
|
||||||
public void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback)
|
public void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback)
|
||||||
{
|
{
|
||||||
boolean head = this.head = info.isHead();
|
boolean head = this.head = info.isHead();
|
||||||
|
boolean shutdown = this.shutdown = info.getHttpFields().contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString());
|
||||||
|
|
||||||
if (head)
|
if (head)
|
||||||
{
|
{
|
||||||
if (lastContent)
|
if (lastContent)
|
||||||
{
|
{
|
||||||
Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(),
|
Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(),
|
||||||
info.getHttpFields(), new Callback.Adapter());
|
info.getHttpFields(), new Callback.Adapter());
|
||||||
Generator.Result contentResult = generator.generateResponseContent(request, BufferUtil.EMPTY_BUFFER, lastContent, callback);
|
Generator.Result contentResult = generator.generateResponseContent(request, BufferUtil.EMPTY_BUFFER, lastContent, aborted, callback);
|
||||||
flusher.flush(headersResult, contentResult);
|
flusher.flush(headersResult, contentResult);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -67,9 +73,12 @@ public class HttpTransportOverFCGI implements HttpTransport
|
||||||
{
|
{
|
||||||
Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(),
|
Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(),
|
||||||
info.getHttpFields(), new Callback.Adapter());
|
info.getHttpFields(), new Callback.Adapter());
|
||||||
Generator.Result contentResult = generator.generateResponseContent(request, content, lastContent, callback);
|
Generator.Result contentResult = generator.generateResponseContent(request, content, lastContent, aborted, callback);
|
||||||
flusher.flush(headersResult, contentResult);
|
flusher.flush(headersResult, contentResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (lastContent && shutdown)
|
||||||
|
flusher.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -79,7 +88,7 @@ public class HttpTransportOverFCGI implements HttpTransport
|
||||||
{
|
{
|
||||||
if (lastContent)
|
if (lastContent)
|
||||||
{
|
{
|
||||||
Generator.Result result = generator.generateResponseContent(request, BufferUtil.EMPTY_BUFFER, lastContent, callback);
|
Generator.Result result = generator.generateResponseContent(request, BufferUtil.EMPTY_BUFFER, lastContent, aborted, callback);
|
||||||
flusher.flush(result);
|
flusher.flush(result);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -90,18 +99,22 @@ public class HttpTransportOverFCGI implements HttpTransport
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Generator.Result result = generator.generateResponseContent(request, content, lastContent, callback);
|
Generator.Result result = generator.generateResponseContent(request, content, lastContent, aborted, callback);
|
||||||
flusher.flush(result);
|
flusher.flush(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (lastContent && shutdown)
|
||||||
|
flusher.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void abort()
|
||||||
|
{
|
||||||
|
aborted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void completed()
|
public void completed()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void abort()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -151,7 +151,7 @@ public class ServerFCGIConnection extends AbstractConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
|
public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
HttpChannelOverFCGI channel = channels.get(request);
|
HttpChannelOverFCGI channel = channels.get(request);
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
|
@ -161,6 +161,7 @@ public class ServerFCGIConnection extends AbstractConnection
|
||||||
if (channel.content(buffer))
|
if (channel.content(buffer))
|
||||||
channel.dispatch();
|
channel.dispatch();
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -175,5 +176,17 @@ public class ServerFCGIConnection extends AbstractConnection
|
||||||
channel.dispatch();
|
channel.dispatch();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(int request, Throwable failure)
|
||||||
|
{
|
||||||
|
HttpChannelOverFCGI channel = channels.remove(request);
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("Request {} failure on {}: {}", request, channel, failure);
|
||||||
|
if (channel != null)
|
||||||
|
{
|
||||||
|
channel.badMessage(400, failure.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ import org.eclipse.jetty.fcgi.FCGI;
|
||||||
import org.eclipse.jetty.fcgi.client.http.HttpClientTransportOverFCGI;
|
import org.eclipse.jetty.fcgi.client.http.HttpClientTransportOverFCGI;
|
||||||
import org.eclipse.jetty.http.HttpFields;
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
import org.eclipse.jetty.http.HttpScheme;
|
import org.eclipse.jetty.http.HttpScheme;
|
||||||
|
import org.eclipse.jetty.proxy.AsyncProxyServlet;
|
||||||
import org.eclipse.jetty.proxy.ProxyServlet;
|
import org.eclipse.jetty.proxy.ProxyServlet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -53,14 +54,18 @@ import org.eclipse.jetty.proxy.ProxyServlet;
|
||||||
* <li>the FastCGI SCRIPT_NAME parameter</li>
|
* <li>the FastCGI SCRIPT_NAME parameter</li>
|
||||||
* <li>the FastCGI PATH_INFO parameter</li>
|
* <li>the FastCGI PATH_INFO parameter</li>
|
||||||
* </ul></li>
|
* </ul></li>
|
||||||
|
* <li><code>fastCGI.HTTPS</code>, optional, defaults to false, that specifies whether
|
||||||
|
* to force the FastCGI <code>HTTPS</code> parameter to the value <code>on</code></li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @see TryFilesFilter
|
* @see TryFilesFilter
|
||||||
*/
|
*/
|
||||||
public class FastCGIProxyServlet extends ProxyServlet.Transparent
|
public class FastCGIProxyServlet extends AsyncProxyServlet.Transparent
|
||||||
{
|
{
|
||||||
public static final String SCRIPT_ROOT_INIT_PARAM = "scriptRoot";
|
public static final String SCRIPT_ROOT_INIT_PARAM = "scriptRoot";
|
||||||
public static final String SCRIPT_PATTERN_INIT_PARAM = "scriptPattern";
|
public static final String SCRIPT_PATTERN_INIT_PARAM = "scriptPattern";
|
||||||
|
public static final String FASTCGI_HTTPS_INIT_PARAM = "fastCGI.HTTPS";
|
||||||
|
|
||||||
private static final String REMOTE_ADDR_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".remoteAddr";
|
private static final String REMOTE_ADDR_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".remoteAddr";
|
||||||
private static final String REMOTE_PORT_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".remotePort";
|
private static final String REMOTE_PORT_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".remotePort";
|
||||||
private static final String SERVER_NAME_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".serverName";
|
private static final String SERVER_NAME_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".serverName";
|
||||||
|
@ -70,6 +75,7 @@ public class FastCGIProxyServlet extends ProxyServlet.Transparent
|
||||||
private static final String REQUEST_URI_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".requestURI";
|
private static final String REQUEST_URI_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".requestURI";
|
||||||
|
|
||||||
private Pattern scriptPattern;
|
private Pattern scriptPattern;
|
||||||
|
private boolean fcgiHTTPS;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init() throws ServletException
|
public void init() throws ServletException
|
||||||
|
@ -80,6 +86,8 @@ public class FastCGIProxyServlet extends ProxyServlet.Transparent
|
||||||
if (value == null)
|
if (value == null)
|
||||||
value = "(.+?\\.php)";
|
value = "(.+?\\.php)";
|
||||||
scriptPattern = Pattern.compile(value);
|
scriptPattern = Pattern.compile(value);
|
||||||
|
|
||||||
|
fcgiHTTPS = Boolean.parseBoolean(getInitParameter(FASTCGI_HTTPS_INIT_PARAM));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -130,7 +138,7 @@ public class FastCGIProxyServlet extends ProxyServlet.Transparent
|
||||||
fastCGIHeaders.put(FCGI.Headers.SERVER_ADDR, (String)proxyRequest.getAttributes().get(SERVER_ADDR_ATTRIBUTE));
|
fastCGIHeaders.put(FCGI.Headers.SERVER_ADDR, (String)proxyRequest.getAttributes().get(SERVER_ADDR_ATTRIBUTE));
|
||||||
fastCGIHeaders.put(FCGI.Headers.SERVER_PORT, (String)proxyRequest.getAttributes().get(SERVER_PORT_ATTRIBUTE));
|
fastCGIHeaders.put(FCGI.Headers.SERVER_PORT, (String)proxyRequest.getAttributes().get(SERVER_PORT_ATTRIBUTE));
|
||||||
|
|
||||||
if (HttpScheme.HTTPS.is((String)proxyRequest.getAttributes().get(SCHEME_ATTRIBUTE)))
|
if (fcgiHTTPS || HttpScheme.HTTPS.is((String)proxyRequest.getAttributes().get(SCHEME_ATTRIBUTE)))
|
||||||
fastCGIHeaders.put(FCGI.Headers.HTTPS, "on");
|
fastCGIHeaders.put(FCGI.Headers.HTTPS, "on");
|
||||||
|
|
||||||
URI proxyRequestURI = proxyRequest.getURI();
|
URI proxyRequestURI = proxyRequest.getURI();
|
||||||
|
|
|
@ -24,11 +24,13 @@ import java.net.URI;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Random;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.zip.GZIPOutputStream;
|
import java.util.zip.GZIPOutputStream;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.ServletOutputStream;
|
import javax.servlet.ServletOutputStream;
|
||||||
|
@ -40,10 +42,13 @@ import org.eclipse.jetty.client.api.Request;
|
||||||
import org.eclipse.jetty.client.api.Response;
|
import org.eclipse.jetty.client.api.Response;
|
||||||
import org.eclipse.jetty.client.api.Result;
|
import org.eclipse.jetty.client.api.Result;
|
||||||
import org.eclipse.jetty.client.util.BytesContentProvider;
|
import org.eclipse.jetty.client.util.BytesContentProvider;
|
||||||
|
import org.eclipse.jetty.client.util.DeferredContentProvider;
|
||||||
|
import org.eclipse.jetty.client.util.FutureResponseListener;
|
||||||
import org.eclipse.jetty.http.HttpMethod;
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
import org.eclipse.jetty.toolchain.test.IO;
|
import org.eclipse.jetty.toolchain.test.IO;
|
||||||
import org.eclipse.jetty.toolchain.test.annotation.Slow;
|
import org.eclipse.jetty.toolchain.test.annotation.Slow;
|
||||||
|
import org.eclipse.jetty.util.Callback;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
@ -551,4 +556,148 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
||||||
|
|
||||||
Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
|
Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEarlyEOF() throws Exception
|
||||||
|
{
|
||||||
|
start(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
// Promise some content, then flush the headers, then fail to send the content.
|
||||||
|
response.setContentLength(16);
|
||||||
|
response.flushBuffer();
|
||||||
|
throw new NullPointerException("Explicitly thrown by test");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
client.newRequest("localhost", connector.getLocalPort())
|
||||||
|
.scheme(scheme)
|
||||||
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
.send();
|
||||||
|
Assert.fail();
|
||||||
|
}
|
||||||
|
catch (ExecutionException x)
|
||||||
|
{
|
||||||
|
// Expected.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSmallContentDelimitedByEOFWithSlowRequest() throws Exception
|
||||||
|
{
|
||||||
|
testContentDelimitedByEOFWithSlowRequest(1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBigContentDelimitedByEOFWithSlowRequest() throws Exception
|
||||||
|
{
|
||||||
|
testContentDelimitedByEOFWithSlowRequest(128 * 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testContentDelimitedByEOFWithSlowRequest(int length) throws Exception
|
||||||
|
{
|
||||||
|
final byte[] data = new byte[length];
|
||||||
|
new Random().nextBytes(data);
|
||||||
|
start(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
response.setHeader("Connection", "close");
|
||||||
|
response.getOutputStream().write(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.wrap(new byte[]{0}));
|
||||||
|
Request request = client.newRequest("localhost", connector.getLocalPort())
|
||||||
|
.scheme(scheme)
|
||||||
|
.content(content);
|
||||||
|
FutureResponseListener listener = new FutureResponseListener(request);
|
||||||
|
request.send(listener);
|
||||||
|
// Wait some time to simulate a slow request.
|
||||||
|
Thread.sleep(1000);
|
||||||
|
content.close();
|
||||||
|
|
||||||
|
ContentResponse response = listener.get(5, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertArrayEquals(data, response.getContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSmallAsyncContent() throws Exception
|
||||||
|
{
|
||||||
|
start(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
ServletOutputStream output = response.getOutputStream();
|
||||||
|
output.write(65);
|
||||||
|
output.flush();
|
||||||
|
output.write(66);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final AtomicInteger contentCount = new AtomicInteger();
|
||||||
|
final AtomicReference<Callback> callbackRef = new AtomicReference<>();
|
||||||
|
final AtomicReference<CountDownLatch> contentLatch = new AtomicReference<>(new CountDownLatch(1));
|
||||||
|
final CountDownLatch completeLatch = new CountDownLatch(1);
|
||||||
|
client.newRequest("localhost", connector.getLocalPort())
|
||||||
|
.scheme(scheme)
|
||||||
|
.onResponseContentAsync(new Response.AsyncContentListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onContent(Response response, ByteBuffer content, Callback callback)
|
||||||
|
{
|
||||||
|
contentCount.incrementAndGet();
|
||||||
|
callbackRef.set(callback);
|
||||||
|
contentLatch.get().countDown();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.send(new Response.CompleteListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onComplete(Result result)
|
||||||
|
{
|
||||||
|
completeLatch.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.assertTrue(contentLatch.get().await(5, TimeUnit.SECONDS));
|
||||||
|
Callback callback = callbackRef.get();
|
||||||
|
|
||||||
|
// Wait a while to be sure that the parsing does not proceed.
|
||||||
|
TimeUnit.MILLISECONDS.sleep(1000);
|
||||||
|
|
||||||
|
Assert.assertEquals(1, contentCount.get());
|
||||||
|
|
||||||
|
// Succeed the content callback to proceed with parsing.
|
||||||
|
callbackRef.set(null);
|
||||||
|
contentLatch.set(new CountDownLatch(1));
|
||||||
|
callback.succeeded();
|
||||||
|
|
||||||
|
Assert.assertTrue(contentLatch.get().await(5, TimeUnit.SECONDS));
|
||||||
|
callback = callbackRef.get();
|
||||||
|
|
||||||
|
// Wait a while to be sure that the parsing does not proceed.
|
||||||
|
TimeUnit.MILLISECONDS.sleep(1000);
|
||||||
|
|
||||||
|
Assert.assertEquals(2, contentCount.get());
|
||||||
|
Assert.assertEquals(1, completeLatch.getCount());
|
||||||
|
|
||||||
|
// Succeed the content callback to proceed with parsing.
|
||||||
|
callbackRef.set(null);
|
||||||
|
contentLatch.set(new CountDownLatch(1));
|
||||||
|
callback.succeeded();
|
||||||
|
|
||||||
|
Assert.assertTrue(completeLatch.await(555, TimeUnit.SECONDS));
|
||||||
|
Assert.assertEquals(2, contentCount.get());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,10 +14,7 @@
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
<module>fcgi-client</module>
|
<module>fcgi-client</module>
|
||||||
<!--<module>fcgi-http-client-transport</module>-->
|
|
||||||
<module>fcgi-server</module>
|
<module>fcgi-server</module>
|
||||||
<!--<module>fcgi-proxy</module>-->
|
|
||||||
<!--<module>fcgi-distribution</module>-->
|
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
|
@ -22,6 +22,7 @@ import java.io.IOException;
|
||||||
import java.nio.BufferOverflowException;
|
import java.nio.BufferOverflowException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
import org.eclipse.jetty.http.HttpTokens.EndOfContent;
|
import org.eclipse.jetty.http.HttpTokens.EndOfContent;
|
||||||
import org.eclipse.jetty.util.BufferUtil;
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
@ -916,10 +917,8 @@ public class HttpGenerator
|
||||||
line[versionLength+6+reason.length()]=HttpTokens.LINE_FEED;
|
line[versionLength+6+reason.length()]=HttpTokens.LINE_FEED;
|
||||||
|
|
||||||
__preprepared[i] = new PreparedResponse();
|
__preprepared[i] = new PreparedResponse();
|
||||||
__preprepared[i]._reason=new byte[line.length-versionLength-7] ;
|
__preprepared[i]._schemeCode = Arrays.copyOfRange(line, 0,versionLength+5);
|
||||||
System.arraycopy(line,versionLength+5,__preprepared[i]._reason,0,line.length-versionLength-7);
|
__preprepared[i]._reason = Arrays.copyOfRange(line, versionLength+5, line.length-2);
|
||||||
__preprepared[i]._schemeCode=new byte[versionLength+5];
|
|
||||||
System.arraycopy(line,0,__preprepared[i]._schemeCode,0,versionLength+5);
|
|
||||||
__preprepared[i]._responseLine=line;
|
__preprepared[i]._responseLine=line;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1091,8 +1090,7 @@ public class HttpGenerator
|
||||||
{
|
{
|
||||||
super(header,value);
|
super(header,value);
|
||||||
int cbl=header.getBytesColonSpace().length;
|
int cbl=header.getBytesColonSpace().length;
|
||||||
_bytes=new byte[cbl+value.length()+2];
|
_bytes=Arrays.copyOf(header.getBytesColonSpace(), cbl+value.length()+2);
|
||||||
System.arraycopy(header.getBytesColonSpace(),0,_bytes,0,cbl);
|
|
||||||
System.arraycopy(value.getBytes(StandardCharsets.ISO_8859_1),0,_bytes,cbl,value.length());
|
System.arraycopy(value.getBytes(StandardCharsets.ISO_8859_1),0,_bytes,cbl,value.length());
|
||||||
_bytes[_bytes.length-2]=(byte)'\r';
|
_bytes[_bytes.length-2]=(byte)'\r';
|
||||||
_bytes[_bytes.length-1]=(byte)'\n';
|
_bytes[_bytes.length-1]=(byte)'\n';
|
||||||
|
|
|
@ -58,7 +58,7 @@ import org.eclipse.jetty.util.log.Logger;
|
||||||
* For performance, the parse is heavily dependent on the
|
* For performance, the parse is heavily dependent on the
|
||||||
* {@link Trie#getBest(ByteBuffer, int, int)} method to look ahead in a
|
* {@link Trie#getBest(ByteBuffer, int, int)} method to look ahead in a
|
||||||
* single pass for both the structure ( : and CRLF ) and semantic (which
|
* single pass for both the structure ( : and CRLF ) and semantic (which
|
||||||
* header and value) of a header. Specifically the static {@link HttpField#CACHE}
|
* header and value) of a header. Specifically the static {@link HttpHeader#CACHE}
|
||||||
* is used to lookup common combinations of headers and values
|
* is used to lookup common combinations of headers and values
|
||||||
* (eg. "Connection: close"), or just header names (eg. "Connection:" ).
|
* (eg. "Connection: close"), or just header names (eg. "Connection:" ).
|
||||||
* For headers who's value is not known statically (eg. Host, COOKIE) then a
|
* For headers who's value is not known statically (eg. Host, COOKIE) then a
|
||||||
|
@ -186,7 +186,7 @@ public class HttpParser
|
||||||
|
|
||||||
for (String charset : new String[]{"UTF-8","ISO-8859-1"})
|
for (String charset : new String[]{"UTF-8","ISO-8859-1"})
|
||||||
{
|
{
|
||||||
CACHE.put(field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type+";charset="+charset));
|
CACHE.put(new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type+";charset="+charset));
|
||||||
CACHE.put(new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type+"; charset="+charset));
|
CACHE.put(new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type+"; charset="+charset));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -813,7 +813,7 @@ public class HttpParser
|
||||||
{
|
{
|
||||||
if (_valueString.endsWith(HttpHeaderValue.CHUNKED.toString()))
|
if (_valueString.endsWith(HttpHeaderValue.CHUNKED.toString()))
|
||||||
_endOfContent=EndOfContent.CHUNKED_CONTENT;
|
_endOfContent=EndOfContent.CHUNKED_CONTENT;
|
||||||
else if (_valueString.indexOf(HttpHeaderValue.CHUNKED.toString()) >= 0)
|
else if (_valueString.contains(HttpHeaderValue.CHUNKED.toString()))
|
||||||
{
|
{
|
||||||
throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad chunking");
|
throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad chunking");
|
||||||
}
|
}
|
||||||
|
@ -872,7 +872,7 @@ public class HttpParser
|
||||||
|
|
||||||
case CONNECTION:
|
case CONNECTION:
|
||||||
// Don't cache if not persistent
|
// Don't cache if not persistent
|
||||||
if (_valueString!=null && _valueString.indexOf("close")>=0)
|
if (_valueString!=null && _valueString.contains("close"))
|
||||||
{
|
{
|
||||||
_closed=true;
|
_closed=true;
|
||||||
_connectionFields=null;
|
_connectionFields=null;
|
||||||
|
@ -1223,8 +1223,6 @@ public class HttpParser
|
||||||
LOG.debug("parseNext s={} {}",_state,BufferUtil.toDetailString(buffer));
|
LOG.debug("parseNext s={} {}",_state,BufferUtil.toDetailString(buffer));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
boolean handle=false;
|
|
||||||
|
|
||||||
// Start a request/response
|
// Start a request/response
|
||||||
if (_state==State.START)
|
if (_state==State.START)
|
||||||
{
|
{
|
||||||
|
@ -1233,28 +1231,39 @@ public class HttpParser
|
||||||
_methodString=null;
|
_methodString=null;
|
||||||
_endOfContent=EndOfContent.UNKNOWN_CONTENT;
|
_endOfContent=EndOfContent.UNKNOWN_CONTENT;
|
||||||
_header=null;
|
_header=null;
|
||||||
handle=quickStart(buffer);
|
if (quickStart(buffer))
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request/response line
|
// Request/response line
|
||||||
if (!handle && _state.ordinal()>= State.START.ordinal() && _state.ordinal()<State.HEADER.ordinal())
|
if (_state.ordinal()>= State.START.ordinal() && _state.ordinal()<State.HEADER.ordinal())
|
||||||
handle=parseLine(buffer);
|
{
|
||||||
|
if (parseLine(buffer))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// parse headers
|
// parse headers
|
||||||
if (!handle && _state.ordinal()>= State.HEADER.ordinal() && _state.ordinal()<State.CONTENT.ordinal())
|
if (_state.ordinal()>= State.HEADER.ordinal() && _state.ordinal()<State.CONTENT.ordinal())
|
||||||
handle=parseHeaders(buffer);
|
{
|
||||||
|
if (parseHeaders(buffer))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// parse content
|
// parse content
|
||||||
if (!handle && _state.ordinal()>= State.CONTENT.ordinal() && _state.ordinal()<State.END.ordinal())
|
if (_state.ordinal()>= State.CONTENT.ordinal() && _state.ordinal()<State.END.ordinal())
|
||||||
{
|
{
|
||||||
// Handle HEAD response
|
// Handle HEAD response
|
||||||
if (_responseStatus>0 && _headResponse)
|
if (_responseStatus>0 && _headResponse)
|
||||||
{
|
{
|
||||||
setState(State.END);
|
setState(State.END);
|
||||||
handle=_handler.messageComplete();
|
if (_handler.messageComplete())
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
handle=parseContent(buffer);
|
{
|
||||||
|
if (parseContent(buffer))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle end states
|
// handle end states
|
||||||
|
@ -1288,8 +1297,8 @@ public class HttpParser
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case START:
|
case START:
|
||||||
_handler.earlyEOF();
|
|
||||||
setState(State.CLOSED);
|
setState(State.CLOSED);
|
||||||
|
_handler.earlyEOF();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case END:
|
case END:
|
||||||
|
@ -1297,29 +1306,28 @@ public class HttpParser
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EOF_CONTENT:
|
case EOF_CONTENT:
|
||||||
handle=_handler.messageComplete()||handle;
|
|
||||||
setState(State.CLOSED);
|
setState(State.CLOSED);
|
||||||
break;
|
return _handler.messageComplete();
|
||||||
|
|
||||||
case CONTENT:
|
case CONTENT:
|
||||||
case CHUNKED_CONTENT:
|
case CHUNKED_CONTENT:
|
||||||
case CHUNK_SIZE:
|
case CHUNK_SIZE:
|
||||||
case CHUNK_PARAMS:
|
case CHUNK_PARAMS:
|
||||||
case CHUNK:
|
case CHUNK:
|
||||||
_handler.earlyEOF();
|
|
||||||
setState(State.CLOSED);
|
setState(State.CLOSED);
|
||||||
|
_handler.earlyEOF();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if (DEBUG)
|
if (DEBUG)
|
||||||
LOG.debug("{} EOF in {}",this,_state);
|
LOG.debug("{} EOF in {}",this,_state);
|
||||||
_handler.badMessage(400,null);
|
|
||||||
setState(State.CLOSED);
|
setState(State.CLOSED);
|
||||||
|
_handler.badMessage(400,null);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return handle;
|
return false;
|
||||||
}
|
}
|
||||||
catch(BadMessage e)
|
catch(BadMessage e)
|
||||||
{
|
{
|
||||||
|
@ -1357,24 +1365,36 @@ public class HttpParser
|
||||||
|
|
||||||
protected boolean parseContent(ByteBuffer buffer)
|
protected boolean parseContent(ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
|
int remaining=buffer.remaining();
|
||||||
|
if (remaining==0 && _state==State.CONTENT)
|
||||||
|
{
|
||||||
|
long content=_contentLength - _contentPosition;
|
||||||
|
if (content == 0)
|
||||||
|
{
|
||||||
|
setState(State.END);
|
||||||
|
if (_handler.messageComplete())
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handle _content
|
// Handle _content
|
||||||
byte ch;
|
byte ch;
|
||||||
while (_state.ordinal() < State.END.ordinal() && buffer.hasRemaining())
|
while (_state.ordinal() < State.END.ordinal() && remaining>0)
|
||||||
{
|
{
|
||||||
switch (_state)
|
switch (_state)
|
||||||
{
|
{
|
||||||
case EOF_CONTENT:
|
case EOF_CONTENT:
|
||||||
_contentChunk=buffer.asReadOnlyBuffer();
|
_contentChunk=buffer.asReadOnlyBuffer();
|
||||||
_contentPosition += _contentChunk.remaining();
|
_contentPosition += remaining;
|
||||||
buffer.position(buffer.position()+_contentChunk.remaining());
|
buffer.position(buffer.position()+remaining);
|
||||||
if (_handler.content(_contentChunk))
|
if (_handler.content(_contentChunk))
|
||||||
return true;
|
return true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CONTENT:
|
case CONTENT:
|
||||||
{
|
{
|
||||||
long remaining=_contentLength - _contentPosition;
|
long content=_contentLength - _contentPosition;
|
||||||
if (remaining == 0)
|
if (content == 0)
|
||||||
{
|
{
|
||||||
setState(State.END);
|
setState(State.END);
|
||||||
if (_handler.messageComplete())
|
if (_handler.messageComplete())
|
||||||
|
@ -1385,25 +1405,25 @@ public class HttpParser
|
||||||
_contentChunk=buffer.asReadOnlyBuffer();
|
_contentChunk=buffer.asReadOnlyBuffer();
|
||||||
|
|
||||||
// limit content by expected size
|
// limit content by expected size
|
||||||
if (_contentChunk.remaining() > remaining)
|
if (remaining > content)
|
||||||
{
|
{
|
||||||
// We can cast remaining to an int as we know that it is smaller than
|
// We can cast remaining to an int as we know that it is smaller than
|
||||||
// or equal to length which is already an int.
|
// or equal to length which is already an int.
|
||||||
_contentChunk.limit(_contentChunk.position()+(int)remaining);
|
_contentChunk.limit(_contentChunk.position()+(int)content);
|
||||||
}
|
}
|
||||||
|
|
||||||
_contentPosition += _contentChunk.remaining();
|
_contentPosition += _contentChunk.remaining();
|
||||||
buffer.position(buffer.position()+_contentChunk.remaining());
|
buffer.position(buffer.position()+_contentChunk.remaining());
|
||||||
|
|
||||||
boolean handle=_handler.content(_contentChunk);
|
if (_handler.content(_contentChunk))
|
||||||
|
return true;
|
||||||
|
|
||||||
if(_contentPosition == _contentLength)
|
if(_contentPosition == _contentLength)
|
||||||
{
|
{
|
||||||
setState(State.END);
|
setState(State.END);
|
||||||
if (_handler.messageComplete())
|
if (_handler.messageComplete())
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (handle)
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1463,8 +1483,8 @@ public class HttpParser
|
||||||
|
|
||||||
case CHUNK:
|
case CHUNK:
|
||||||
{
|
{
|
||||||
int remaining=_chunkLength - _chunkPosition;
|
int chunk=_chunkLength - _chunkPosition;
|
||||||
if (remaining == 0)
|
if (chunk == 0)
|
||||||
{
|
{
|
||||||
setState(State.CHUNKED_CONTENT);
|
setState(State.CHUNKED_CONTENT);
|
||||||
}
|
}
|
||||||
|
@ -1472,13 +1492,13 @@ public class HttpParser
|
||||||
{
|
{
|
||||||
_contentChunk=buffer.asReadOnlyBuffer();
|
_contentChunk=buffer.asReadOnlyBuffer();
|
||||||
|
|
||||||
if (_contentChunk.remaining() > remaining)
|
if (remaining > chunk)
|
||||||
_contentChunk.limit(_contentChunk.position()+remaining);
|
_contentChunk.limit(_contentChunk.position()+chunk);
|
||||||
remaining=_contentChunk.remaining();
|
chunk=_contentChunk.remaining();
|
||||||
|
|
||||||
_contentPosition += remaining;
|
_contentPosition += chunk;
|
||||||
_chunkPosition += remaining;
|
_chunkPosition += chunk;
|
||||||
buffer.position(buffer.position()+remaining);
|
buffer.position(buffer.position()+chunk);
|
||||||
if (_handler.content(_contentChunk))
|
if (_handler.content(_contentChunk))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1493,7 +1513,10 @@ public class HttpParser
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
remaining=buffer.remaining();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint
|
||||||
return AbstractEndPoint.this.needsFill();
|
return AbstractEndPoint.this.needsFill();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private final WriteFlusher _writeFlusher = new WriteFlusher(this)
|
private final WriteFlusher _writeFlusher = new WriteFlusher(this)
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -144,10 +145,20 @@ public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint
|
||||||
{
|
{
|
||||||
boolean output_shutdown=isOutputShutdown();
|
boolean output_shutdown=isOutputShutdown();
|
||||||
boolean input_shutdown=isInputShutdown();
|
boolean input_shutdown=isInputShutdown();
|
||||||
_fillInterest.onFail(timeout);
|
boolean fillFailed = _fillInterest.onFail(timeout);
|
||||||
_writeFlusher.onFail(timeout);
|
boolean writeFailed = _writeFlusher.onFail(timeout);
|
||||||
if (isOpen() && output_shutdown || input_shutdown)
|
|
||||||
|
// If the endpoint is half closed and there was no onFail handling, the close here
|
||||||
|
// This handles the situation where the connection has completed its close handling
|
||||||
|
// and the endpoint is half closed, but the other party does not complete the close.
|
||||||
|
// This perhaps should not check for half closed, however the servlet spec case allows
|
||||||
|
// for a dispatched servlet or suspended request to extend beyond the connections idle
|
||||||
|
// time. So if this test would always close an idle endpoint that is not handled, then
|
||||||
|
// we would need a mode to ignore timeouts for some HTTP states
|
||||||
|
if (isOpen() && (output_shutdown || input_shutdown) && !(fillFailed || writeFailed))
|
||||||
close();
|
close();
|
||||||
|
else
|
||||||
|
LOG.debug("Ignored idle endpoint {}",this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -93,12 +93,17 @@ public abstract class FillInterest
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/** Call to signal a failure to a registered interest
|
/** Call to signal a failure to a registered interest
|
||||||
|
* @return true if the cause was passed to a {@link Callback} instance
|
||||||
*/
|
*/
|
||||||
public void onFail(Throwable cause)
|
public boolean onFail(Throwable cause)
|
||||||
{
|
{
|
||||||
Callback callback=_interested.get();
|
Callback callback=_interested.get();
|
||||||
if (callback!=null && _interested.compareAndSet(callback,null))
|
if (callback!=null && _interested.compareAndSet(callback,null))
|
||||||
|
{
|
||||||
callback.failed(cause);
|
callback.failed(cause);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
|
|
@ -19,11 +19,13 @@
|
||||||
package org.eclipse.jetty.io;
|
package org.eclipse.jetty.io;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.Socket;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.SelectionKey;
|
import java.nio.channels.SelectionKey;
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
import org.eclipse.jetty.util.thread.Scheduler;
|
import org.eclipse.jetty.util.thread.Scheduler;
|
||||||
|
@ -57,9 +59,11 @@ public class NetworkTrafficSelectChannelEndPoint extends SelectChannelEndPoint
|
||||||
if (b.hasRemaining())
|
if (b.hasRemaining())
|
||||||
{
|
{
|
||||||
int position = b.position();
|
int position = b.position();
|
||||||
|
ByteBuffer view=b.slice();
|
||||||
flushed&=super.flush(b);
|
flushed&=super.flush(b);
|
||||||
int l=b.position()-position;
|
int l=b.position()-position;
|
||||||
notifyOutgoing(b, position, l);
|
view.limit(view.position()+l);
|
||||||
|
notifyOutgoing(view);
|
||||||
if (!flushed)
|
if (!flushed)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -68,8 +72,11 @@ public class NetworkTrafficSelectChannelEndPoint extends SelectChannelEndPoint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void notifyOpened()
|
|
||||||
|
@Override
|
||||||
|
public void onOpen()
|
||||||
{
|
{
|
||||||
|
super.onOpen();
|
||||||
if (listeners != null && !listeners.isEmpty())
|
if (listeners != null && !listeners.isEmpty())
|
||||||
{
|
{
|
||||||
for (NetworkTrafficListener listener : listeners)
|
for (NetworkTrafficListener listener : listeners)
|
||||||
|
@ -86,6 +93,27 @@ public class NetworkTrafficSelectChannelEndPoint extends SelectChannelEndPoint
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClose()
|
||||||
|
{
|
||||||
|
super.onClose();
|
||||||
|
if (listeners != null && !listeners.isEmpty())
|
||||||
|
{
|
||||||
|
for (NetworkTrafficListener listener : listeners)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
listener.closed(getSocket());
|
||||||
|
}
|
||||||
|
catch (Exception x)
|
||||||
|
{
|
||||||
|
LOG.warn(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void notifyIncoming(ByteBuffer buffer, int read)
|
public void notifyIncoming(ByteBuffer buffer, int read)
|
||||||
{
|
{
|
||||||
if (listeners != null && !listeners.isEmpty() && read > 0)
|
if (listeners != null && !listeners.isEmpty() && read > 0)
|
||||||
|
@ -105,18 +133,16 @@ public class NetworkTrafficSelectChannelEndPoint extends SelectChannelEndPoint
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notifyOutgoing(ByteBuffer buffer, int position, int written)
|
public void notifyOutgoing(ByteBuffer view)
|
||||||
{
|
{
|
||||||
if (listeners != null && !listeners.isEmpty() && written > 0)
|
if (listeners != null && !listeners.isEmpty() && view.hasRemaining())
|
||||||
{
|
{
|
||||||
|
Socket socket=getSocket();
|
||||||
for (NetworkTrafficListener listener : listeners)
|
for (NetworkTrafficListener listener : listeners)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ByteBuffer view = buffer.slice();
|
listener.outgoing(socket, view);
|
||||||
view.position(position);
|
|
||||||
view.limit(position + written);
|
|
||||||
listener.outgoing(getSocket(), view);
|
|
||||||
}
|
}
|
||||||
catch (Exception x)
|
catch (Exception x)
|
||||||
{
|
{
|
||||||
|
@ -126,21 +152,4 @@ public class NetworkTrafficSelectChannelEndPoint extends SelectChannelEndPoint
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notifyClosed()
|
|
||||||
{
|
|
||||||
if (listeners != null && !listeners.isEmpty())
|
|
||||||
{
|
|
||||||
for (NetworkTrafficListener listener : listeners)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
listener.closed(getSocket());
|
|
||||||
}
|
|
||||||
catch (Exception x)
|
|
||||||
{
|
|
||||||
LOG.warn(x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.ClosedChannelException;
|
import java.nio.channels.ClosedChannelException;
|
||||||
import java.nio.channels.WritePendingException;
|
import java.nio.channels.WritePendingException;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -253,10 +254,14 @@ abstract public class WriteFlusher
|
||||||
return _buffers;
|
return _buffers;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void fail(Throwable cause)
|
protected boolean fail(Throwable cause)
|
||||||
{
|
{
|
||||||
if (_callback!=null)
|
if (_callback!=null)
|
||||||
|
{
|
||||||
_callback.failed(cause);
|
_callback.failed(cause);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void complete()
|
protected void complete()
|
||||||
|
@ -297,10 +302,7 @@ abstract public class WriteFlusher
|
||||||
if (consumed == length)
|
if (consumed == length)
|
||||||
return EMPTY_BUFFERS;
|
return EMPTY_BUFFERS;
|
||||||
|
|
||||||
int newLength = length - consumed;
|
return Arrays.copyOfRange(buffers,consumed,length);
|
||||||
ByteBuffer[] result = new ByteBuffer[newLength];
|
|
||||||
System.arraycopy(buffers, consumed, result, 0, newLength);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -430,7 +432,12 @@ abstract public class WriteFlusher
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onFail(Throwable cause)
|
/* ------------------------------------------------------------ */
|
||||||
|
/** Notify the flusher of a failure
|
||||||
|
* @param cause The cause of the failure
|
||||||
|
* @return true if the flusher passed the failure to a {@link Callback} instance
|
||||||
|
*/
|
||||||
|
public boolean onFail(Throwable cause)
|
||||||
{
|
{
|
||||||
// Keep trying to handle the failure until we get to IDLE or FAILED state
|
// Keep trying to handle the failure until we get to IDLE or FAILED state
|
||||||
while(true)
|
while(true)
|
||||||
|
@ -442,7 +449,7 @@ abstract public class WriteFlusher
|
||||||
case FAILED:
|
case FAILED:
|
||||||
if (DEBUG)
|
if (DEBUG)
|
||||||
LOG.debug("ignored: {} {}", this, cause);
|
LOG.debug("ignored: {} {}", this, cause);
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
case PENDING:
|
case PENDING:
|
||||||
if (DEBUG)
|
if (DEBUG)
|
||||||
|
@ -450,10 +457,7 @@ abstract public class WriteFlusher
|
||||||
|
|
||||||
PendingState pending = (PendingState)current;
|
PendingState pending = (PendingState)current;
|
||||||
if (updateState(pending,__IDLE))
|
if (updateState(pending,__IDLE))
|
||||||
{
|
return pending.fail(cause);
|
||||||
pending.fail(cause);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -461,7 +465,7 @@ abstract public class WriteFlusher
|
||||||
LOG.debug("failed: {} {}", this, cause);
|
LOG.debug("failed: {} {}", this, cause);
|
||||||
|
|
||||||
if (updateState(current,new FailedState(cause)))
|
if (updateState(current,new FailedState(cause)))
|
||||||
return;
|
return false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,7 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.io;
|
package org.eclipse.jetty.io;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.*;
|
||||||
import static org.hamcrest.Matchers.greaterThan;
|
|
||||||
import static org.hamcrest.Matchers.instanceOf;
|
import static org.hamcrest.Matchers.instanceOf;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
@ -129,6 +128,7 @@ public class ByteArrayEndPointTest
|
||||||
|
|
||||||
assertEquals(true,endp.flush(BufferUtil.EMPTY_BUFFER,BufferUtil.toBuffer(" and"),BufferUtil.toBuffer(" more")));
|
assertEquals(true,endp.flush(BufferUtil.EMPTY_BUFFER,BufferUtil.toBuffer(" and"),BufferUtil.toBuffer(" more")));
|
||||||
assertEquals("some output some more and more",endp.getOutputString());
|
assertEquals("some output some more and more",endp.getOutputString());
|
||||||
|
endp.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -147,6 +147,7 @@ public class ByteArrayEndPointTest
|
||||||
|
|
||||||
assertEquals(true,endp.flush(data));
|
assertEquals(true,endp.flush(data));
|
||||||
assertEquals("data.",BufferUtil.toString(endp.takeOutput()));
|
assertEquals("data.",BufferUtil.toString(endp.takeOutput()));
|
||||||
|
endp.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -234,6 +235,27 @@ public class ByteArrayEndPointTest
|
||||||
assertTrue(fcb.isDone());
|
assertTrue(fcb.isDone());
|
||||||
assertEquals(null, fcb.get());
|
assertEquals(null, fcb.get());
|
||||||
assertEquals(" more.", endp.getOutputString());
|
assertEquals(" more.", endp.getOutputString());
|
||||||
|
endp.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simulate AbstractConnection.ReadCallback.failed()
|
||||||
|
*/
|
||||||
|
public static class Closer extends FutureCallback
|
||||||
|
{
|
||||||
|
private EndPoint endp;
|
||||||
|
|
||||||
|
public Closer(EndPoint endp)
|
||||||
|
{
|
||||||
|
this.endp = endp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void failed(Throwable cause)
|
||||||
|
{
|
||||||
|
endp.close();
|
||||||
|
super.failed(cause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Slow
|
@Slow
|
||||||
|
@ -275,7 +297,7 @@ public class ByteArrayEndPointTest
|
||||||
assertThat(t.getCause(), instanceOf(TimeoutException.class));
|
assertThat(t.getCause(), instanceOf(TimeoutException.class));
|
||||||
}
|
}
|
||||||
assertThat(System.currentTimeMillis() - start, greaterThan(idleTimeout / 2));
|
assertThat(System.currentTimeMillis() - start, greaterThan(idleTimeout / 2));
|
||||||
assertTrue(endp.isOpen());
|
assertThat("Endpoint open", endp.isOpen(), is(true));
|
||||||
|
|
||||||
// We need to delay the write timeout test below from the read timeout test above.
|
// We need to delay the write timeout test below from the read timeout test above.
|
||||||
// The reason is that the scheduler thread that fails the endPoint WriteFlusher
|
// The reason is that the scheduler thread that fails the endPoint WriteFlusher
|
||||||
|
@ -298,17 +320,19 @@ public class ByteArrayEndPointTest
|
||||||
assertThat(t.getCause(), instanceOf(TimeoutException.class));
|
assertThat(t.getCause(), instanceOf(TimeoutException.class));
|
||||||
}
|
}
|
||||||
assertThat(System.currentTimeMillis() - start, greaterThan(idleTimeout / 2));
|
assertThat(System.currentTimeMillis() - start, greaterThan(idleTimeout / 2));
|
||||||
assertTrue(endp.isOpen());
|
assertThat("Endpoint open", endp.isOpen(), is(true));
|
||||||
|
|
||||||
// Still no idle close
|
endp.fillInterested(new Closer(endp));
|
||||||
Thread.sleep(idleTimeout * 2);
|
|
||||||
assertTrue(endp.isOpen());
|
// Still no idle close (wait half the time)
|
||||||
|
Thread.sleep(idleTimeout / 2);
|
||||||
|
assertThat("Endpoint open", endp.isOpen(), is(true));
|
||||||
|
|
||||||
// shutdown out
|
// shutdown out
|
||||||
endp.shutdownOutput();
|
endp.shutdownOutput();
|
||||||
|
|
||||||
// idle close
|
// idle close (wait double the time)
|
||||||
Thread.sleep(idleTimeout * 2);
|
Thread.sleep(idleTimeout * 2);
|
||||||
assertFalse(endp.isOpen());
|
assertThat("Endpoint open", endp.isOpen(), is(false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,6 +112,7 @@
|
||||||
javax.servlet.http;version="[3.1,3.2)",
|
javax.servlet.http;version="[3.1,3.2)",
|
||||||
javax.transaction;version="1.1.0";resolution:=optional,
|
javax.transaction;version="1.1.0";resolution:=optional,
|
||||||
javax.transaction.xa;version="1.1.0";resolution:=optional,
|
javax.transaction.xa;version="1.1.0";resolution:=optional,
|
||||||
|
org.objectweb.asm;version=4;resolution:=optional,
|
||||||
org.eclipse.jetty.annotations;version="9.0.0";resolution:=optional,
|
org.eclipse.jetty.annotations;version="9.0.0";resolution:=optional,
|
||||||
org.osgi.framework,
|
org.osgi.framework,
|
||||||
org.osgi.service.cm;version="1.2.0",
|
org.osgi.service.cm;version="1.2.0",
|
||||||
|
|
|
@ -35,6 +35,7 @@ import org.eclipse.jetty.annotations.ClassNameResolver;
|
||||||
import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper;
|
import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper;
|
||||||
import org.eclipse.jetty.util.ConcurrentHashSet;
|
import org.eclipse.jetty.util.ConcurrentHashSet;
|
||||||
import org.eclipse.jetty.util.resource.Resource;
|
import org.eclipse.jetty.util.resource.Resource;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
import org.osgi.framework.Bundle;
|
import org.osgi.framework.Bundle;
|
||||||
import org.osgi.framework.Constants;
|
import org.osgi.framework.Constants;
|
||||||
|
|
||||||
|
@ -50,6 +51,14 @@ public class AnnotationParser extends org.eclipse.jetty.annotations.AnnotationPa
|
||||||
private ConcurrentHashMap<Resource, Bundle> _resourceToBundle = new ConcurrentHashMap<Resource, Bundle>();
|
private ConcurrentHashMap<Resource, Bundle> _resourceToBundle = new ConcurrentHashMap<Resource, Bundle>();
|
||||||
private ConcurrentHashMap<Bundle,URI> _bundleToUri = new ConcurrentHashMap<Bundle, URI>();
|
private ConcurrentHashMap<Bundle,URI> _bundleToUri = new ConcurrentHashMap<Bundle, URI>();
|
||||||
|
|
||||||
|
static
|
||||||
|
{
|
||||||
|
//As of jetty 9.2.0, the impl of asm visitor classes is compatible with both asm4 and asm5.
|
||||||
|
//We need to use asm4 with osgi, because we need to use aries spifly to support annotations,
|
||||||
|
//and currently this only supports asm4. Therefore, we set the asm api version to be 4 for osgi.
|
||||||
|
ASM_OPCODE_VERSION = Opcodes.ASM4;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keep track of a jetty URI Resource and its associated OSGi bundle.
|
* Keep track of a jetty URI Resource and its associated OSGi bundle.
|
||||||
* @param uri
|
* @param uri
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.osgi</groupId>
|
<groupId>org.eclipse.osgi</groupId>
|
||||||
<artifactId>org.eclipse.osgi</artifactId>
|
<artifactId>org.eclipse.osgi</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>javax.servlet</groupId>
|
<groupId>javax.servlet</groupId>
|
||||||
|
|
|
@ -21,10 +21,12 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.osgi</groupId>
|
<groupId>org.eclipse.osgi</groupId>
|
||||||
<artifactId>org.eclipse.osgi</artifactId>
|
<artifactId>org.eclipse.osgi</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.osgi</groupId>
|
<groupId>org.eclipse.osgi</groupId>
|
||||||
<artifactId>org.eclipse.osgi.services</artifactId>
|
<artifactId>org.eclipse.osgi.services</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty.toolchain</groupId>
|
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||||
|
|
|
@ -21,10 +21,12 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.osgi</groupId>
|
<groupId>org.eclipse.osgi</groupId>
|
||||||
<artifactId>org.eclipse.osgi</artifactId>
|
<artifactId>org.eclipse.osgi</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.osgi</groupId>
|
<groupId>org.eclipse.osgi</groupId>
|
||||||
<artifactId>org.eclipse.osgi.services</artifactId>
|
<artifactId>org.eclipse.osgi.services</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|
|
@ -14,101 +14,35 @@
|
||||||
<bundle-symbolic-name>${project.groupId}.boot.test.spdy</bundle-symbolic-name>
|
<bundle-symbolic-name>${project.groupId}.boot.test.spdy</bundle-symbolic-name>
|
||||||
<jetty-orbit-url>http://download.eclipse.org/jetty/orbit/</jetty-orbit-url>
|
<jetty-orbit-url>http://download.eclipse.org/jetty/orbit/</jetty-orbit-url>
|
||||||
<assembly-directory>target/distribution</assembly-directory>
|
<assembly-directory>target/distribution</assembly-directory>
|
||||||
<exam.version>2.6.0</exam.version>
|
<exam.version>3.4.0</exam.version>
|
||||||
<url.version>1.4.0</url.version>
|
<url.version>1.5.2</url.version>
|
||||||
<paxswissbox.version>1.5.1</paxswissbox.version>
|
|
||||||
<felixversion>4.0.3</felixversion>
|
|
||||||
<injection.bundle.version>1.0</injection.bundle.version>
|
<injection.bundle.version>1.0</injection.bundle.version>
|
||||||
<runner.version>1.7.6</runner.version>
|
<runner.version>1.8.5</runner.version>
|
||||||
</properties>
|
</properties>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<!-- Pax Exam Dependencies -->
|
<!-- Pax Exam Dependencies -->
|
||||||
<!-- OPS4J Swissbox Dependencies -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.ops4j.pax.swissbox</groupId>
|
|
||||||
<artifactId>pax-swissbox-core</artifactId>
|
|
||||||
<version>${paxswissbox.version}</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.ops4j.pax.swissbox</groupId>
|
|
||||||
<artifactId>pax-swissbox-extender</artifactId>
|
|
||||||
<version>${paxswissbox.version}</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.ops4j.pax.swissbox</groupId>
|
|
||||||
<artifactId>pax-swissbox-lifecycle</artifactId>
|
|
||||||
<version>${paxswissbox.version}</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.ops4j.pax.swissbox</groupId>
|
|
||||||
<artifactId>pax-swissbox-framework</artifactId>
|
|
||||||
<version>${paxswissbox.version}</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.ops4j.pax.exam</groupId>
|
<groupId>org.ops4j.pax.exam</groupId>
|
||||||
<artifactId>pax-exam</artifactId>
|
<artifactId>pax-exam</artifactId>
|
||||||
<version>${exam.version}</version>
|
<version>${exam.version}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.geronimo.specs</groupId>
|
|
||||||
<artifactId>geronimo-atinject_1.0_spec</artifactId>
|
|
||||||
<version>${injection.bundle.version}</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.ops4j.pax.exam</groupId>
|
<groupId>org.ops4j.pax.exam</groupId>
|
||||||
<artifactId>pax-exam-inject</artifactId>
|
<artifactId>pax-exam-inject</artifactId>
|
||||||
<version>${exam.version}</version>
|
<version>${exam.version}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.aries.spifly</groupId>
|
|
||||||
<artifactId>org.apache.aries.spifly.dynamic.bundle</artifactId>
|
|
||||||
<version>1.0.0</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Don't use the native container for now. Observed limitations:
|
<!-- use the forked container so we can pass it system properties eg for npn/alpn -->
|
||||||
- single test with a single configuration
|
|
||||||
- does not read the versions of the dependencies from the pom.xml
|
|
||||||
and hence hardcode the bundles versions in the source code instead
|
|
||||||
- no support for most configuration options for the OSGi container. -->
|
|
||||||
<!--dependency>
|
|
||||||
<groupId>org.ops4j.pax.exam</groupId>
|
|
||||||
<artifactId>pax-exam-container-native</artifactId>
|
|
||||||
<version>${exam.version}</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency-->
|
|
||||||
|
|
||||||
<!-- container is not bad but not enough config parameters yet
|
|
||||||
can't pass the VMOption for alpn-boot
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.ops4j.pax.exam</groupId>
|
<groupId>org.ops4j.pax.exam</groupId>
|
||||||
<artifactId>pax-exam-container-forked</artifactId>
|
<artifactId>pax-exam-container-forked</artifactId>
|
||||||
<version>${exam.version}</version>
|
<version>${exam.version}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
-->
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.ops4j.pax.exam</groupId>
|
|
||||||
<artifactId>pax-exam-container-paxrunner</artifactId>
|
|
||||||
<version>${exam.version}</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.ops4j.pax.runner</groupId>
|
|
||||||
<artifactId>pax-runner-no-jcl</artifactId>
|
|
||||||
<version>${runner.version}</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.ops4j.pax.exam</groupId>
|
<groupId>org.ops4j.pax.exam</groupId>
|
||||||
|
@ -134,28 +68,81 @@
|
||||||
<version>${url.version}</version>
|
<version>${url.version}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- OSGi R4 frameworks -->
|
<!-- OSGi R4 frameworks -->
|
||||||
|
<!--
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.felix</groupId>
|
<groupId>org.apache.felix</groupId>
|
||||||
<artifactId>org.apache.felix.framework</artifactId>
|
<artifactId>org.apache.felix.framework</artifactId>
|
||||||
<version>${felixversion}</version>
|
<version>4.4.0</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.ops4j.pax.exam</groupId>
|
<groupId>org.osgi</groupId>
|
||||||
<artifactId>pax-exam-testforge</artifactId>
|
<artifactId>org.osgi.enterprise</artifactId>
|
||||||
<version>${exam.version}</version>
|
<version>5.0.0</version>
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<!-- For sane logging -->
|
|
||||||
<!--
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.slf4j</groupId>
|
|
||||||
<artifactId>slf4j-log4j12</artifactId>
|
|
||||||
<version>1.6.1</version>
|
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
-->
|
-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse</groupId>
|
||||||
|
<artifactId>osgi</artifactId>
|
||||||
|
<version>3.9.1-v20140110-1610</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.osgi</groupId>
|
||||||
|
<artifactId>org.eclipse.osgi.services</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Jetty OSGi Deps -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.osgi</groupId>
|
||||||
|
<artifactId>jetty-osgi-boot</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.eclipse.osgi</groupId>
|
||||||
|
<artifactId>org.eclipse.osgi</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.eclipse.osgi</groupId>
|
||||||
|
<artifactId>org.eclipse.osgi.services</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.osgi</groupId>
|
||||||
|
<artifactId>jetty-osgi-boot-jsp</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.eclipse.osgi</groupId>
|
||||||
|
<artifactId>org.eclipse.osgi</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.eclipse.osgi</groupId>
|
||||||
|
<artifactId>org.eclipse.osgi.services</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||||
|
<artifactId>jetty-jsp-fragment</artifactId>
|
||||||
|
<version>2.3.3</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.osgi</groupId>
|
||||||
|
<artifactId>jetty-httpservice</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>javax.servlet</groupId>
|
<groupId>javax.servlet</groupId>
|
||||||
<artifactId>javax.servlet-api</artifactId>
|
<artifactId>javax.servlet-api</artifactId>
|
||||||
|
@ -167,32 +154,19 @@
|
||||||
<version>1.1.1</version>
|
<version>1.1.1</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- OSGi Deps -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.eclipse.jetty.osgi</groupId>
|
|
||||||
<artifactId>jetty-osgi-boot</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.eclipse.jetty.osgi</groupId>
|
|
||||||
<artifactId>jetty-osgi-boot-jsp</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.eclipse.jetty.toolchain</groupId>
|
|
||||||
<artifactId>jetty-jsp-fragment</artifactId>
|
|
||||||
<version>2.3.3</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.eclipse.jetty.osgi</groupId>
|
|
||||||
<artifactId>jetty-httpservice</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.geronimo.specs</groupId>
|
||||||
|
<artifactId>geronimo-atinject_1.0_spec</artifactId>
|
||||||
|
<version>${injection.bundle.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.aries.spifly</groupId>
|
||||||
|
<artifactId>org.apache.aries.spifly.dynamic.bundle</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.ow2.asm</groupId>
|
<groupId>org.ow2.asm</groupId>
|
||||||
<artifactId>asm</artifactId>
|
<artifactId>asm</artifactId>
|
||||||
|
@ -386,7 +360,9 @@
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Eclipse OSGi Deps -->
|
<!-- Eclipse OSGi Deps -->
|
||||||
|
<!--
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.osgi</groupId>
|
<groupId>org.eclipse.osgi</groupId>
|
||||||
<artifactId>org.eclipse.osgi</artifactId>
|
<artifactId>org.eclipse.osgi</artifactId>
|
||||||
|
@ -398,7 +374,6 @@
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
<!-- we use the servlet jar from orbit -->
|
|
||||||
<groupId>javax.servlet</groupId>
|
<groupId>javax.servlet</groupId>
|
||||||
<artifactId>servlet-api</artifactId>
|
<artifactId>servlet-api</artifactId>
|
||||||
</exclusion>
|
</exclusion>
|
||||||
|
@ -409,6 +384,9 @@
|
||||||
<artifactId>servlet</artifactId>
|
<artifactId>servlet</artifactId>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>test-jetty-webapp</artifactId>
|
<artifactId>test-jetty-webapp</artifactId>
|
||||||
|
|
|
@ -37,12 +37,13 @@ import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
import org.eclipse.jetty.http.HttpStatus;
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
|
import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
|
||||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.ops4j.pax.exam.CoreOptions;
|
import org.ops4j.pax.exam.CoreOptions;
|
||||||
import org.ops4j.pax.exam.Option;
|
import org.ops4j.pax.exam.Option;
|
||||||
import org.ops4j.pax.exam.junit.Configuration;
|
import org.ops4j.pax.exam.Configuration;
|
||||||
import org.ops4j.pax.exam.junit.JUnit4TestRunner;
|
import org.ops4j.pax.exam.junit.PaxExam;
|
||||||
import org.osgi.framework.Bundle;
|
import org.osgi.framework.Bundle;
|
||||||
import org.osgi.framework.BundleContext;
|
import org.osgi.framework.BundleContext;
|
||||||
import org.osgi.framework.ServiceReference;
|
import org.osgi.framework.ServiceReference;
|
||||||
|
@ -55,12 +56,11 @@ import org.osgi.framework.ServiceReference;
|
||||||
* Tests the ServiceContextProvider.
|
* Tests the ServiceContextProvider.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@RunWith(JUnit4TestRunner.class)
|
@RunWith(PaxExam.class)
|
||||||
public class TestJettyOSGiBootContextAsService
|
public class TestJettyOSGiBootContextAsService
|
||||||
{
|
{
|
||||||
private static final boolean LOGGING_ENABLED = false;
|
private static final String LOG_LEVEL = "WARN";
|
||||||
|
|
||||||
private static final boolean REMOTE_DEBUGGING = false;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
BundleContext bundleContext = null;
|
BundleContext bundleContext = null;
|
||||||
|
@ -69,7 +69,6 @@ public class TestJettyOSGiBootContextAsService
|
||||||
public static Option[] configure()
|
public static Option[] configure()
|
||||||
{
|
{
|
||||||
ArrayList<Option> options = new ArrayList<Option>();
|
ArrayList<Option> options = new ArrayList<Option>();
|
||||||
TestOSGiUtil.addMoreOSGiContainers(options);
|
|
||||||
options.add(CoreOptions.junitBundles());
|
options.add(CoreOptions.junitBundles());
|
||||||
options.addAll(configureJettyHomeAndPort("jetty-selector.xml"));
|
options.addAll(configureJettyHomeAndPort("jetty-selector.xml"));
|
||||||
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*"));
|
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*"));
|
||||||
|
@ -79,21 +78,9 @@ public class TestJettyOSGiBootContextAsService
|
||||||
// to pick up and deploy
|
// to pick up and deploy
|
||||||
options.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("test-jetty-osgi-context").versionAsInProject().start());
|
options.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("test-jetty-osgi-context").versionAsInProject().start());
|
||||||
|
|
||||||
String logLevel = "WARN";
|
options.addAll(Arrays.asList(options(systemProperty("pax.exam.logging").value("none"))));
|
||||||
// Enable Logging
|
options.addAll(Arrays.asList(options(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL))));
|
||||||
if (LOGGING_ENABLED)
|
options.addAll(Arrays.asList(options(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL))));
|
||||||
logLevel = "INFO";
|
|
||||||
|
|
||||||
|
|
||||||
options.addAll(Arrays.asList(options(
|
|
||||||
// install log service using pax runners profile abstraction (there
|
|
||||||
// are more profiles, like DS)
|
|
||||||
// logProfile(),
|
|
||||||
// this is how you set the default log level when using pax logging
|
|
||||||
// (logProfile)
|
|
||||||
systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(logLevel),
|
|
||||||
systemProperty("org.eclipse.jetty.LEVEL").value(logLevel))));
|
|
||||||
|
|
||||||
|
|
||||||
return options.toArray(new Option[options.size()]);
|
return options.toArray(new Option[options.size()]);
|
||||||
}
|
}
|
||||||
|
@ -117,6 +104,7 @@ public class TestJettyOSGiBootContextAsService
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Ignore
|
||||||
@Test
|
@Test
|
||||||
public void assertAllBundlesActiveOrResolved()
|
public void assertAllBundlesActiveOrResolved()
|
||||||
{
|
{
|
||||||
|
@ -148,14 +136,6 @@ public class TestJettyOSGiBootContextAsService
|
||||||
ServiceReference[] refs = bundleContext.getServiceReferences(ContextHandler.class.getName(), null);
|
ServiceReference[] refs = bundleContext.getServiceReferences(ContextHandler.class.getName(), null);
|
||||||
assertNotNull(refs);
|
assertNotNull(refs);
|
||||||
assertEquals(1, refs.length);
|
assertEquals(1, refs.length);
|
||||||
//uncomment for debugging
|
|
||||||
/*
|
|
||||||
String[] keys = refs[0].getPropertyKeys();
|
|
||||||
if (keys != null)
|
|
||||||
{
|
|
||||||
for (String k : keys)
|
|
||||||
System.err.println("service property: " + k + ", " + refs[0].getProperty(k));
|
|
||||||
}*/
|
|
||||||
ContextHandler ch = (ContextHandler) bundleContext.getService(refs[0]);
|
ContextHandler ch = (ContextHandler) bundleContext.getService(refs[0]);
|
||||||
assertEquals("/acme", ch.getContextPath());
|
assertEquals("/acme", ch.getContextPath());
|
||||||
|
|
||||||
|
|
|
@ -19,19 +19,23 @@
|
||||||
package org.eclipse.jetty.osgi.test;
|
package org.eclipse.jetty.osgi.test;
|
||||||
|
|
||||||
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
|
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
|
||||||
|
import static org.ops4j.pax.exam.CoreOptions.options;
|
||||||
|
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.ops4j.pax.exam.CoreOptions;
|
import org.ops4j.pax.exam.CoreOptions;
|
||||||
import org.ops4j.pax.exam.MavenUtils;
|
import org.ops4j.pax.exam.MavenUtils;
|
||||||
import org.ops4j.pax.exam.Option;
|
import org.ops4j.pax.exam.Option;
|
||||||
import org.ops4j.pax.exam.junit.Configuration;
|
import org.ops4j.pax.exam.Configuration;
|
||||||
import org.ops4j.pax.exam.junit.JUnit4TestRunner;
|
import org.ops4j.pax.exam.junit.PaxExam;
|
||||||
import org.ops4j.pax.exam.options.MavenUrlReference.VersionResolver;
|
import org.ops4j.pax.exam.options.MavenUrlReference.VersionResolver;
|
||||||
import org.osgi.framework.Bundle;
|
import org.osgi.framework.Bundle;
|
||||||
import org.osgi.framework.BundleContext;
|
import org.osgi.framework.BundleContext;
|
||||||
|
@ -40,10 +44,10 @@ import org.osgi.framework.BundleContext;
|
||||||
/**
|
/**
|
||||||
* Default OSGi setup integration test
|
* Default OSGi setup integration test
|
||||||
*/
|
*/
|
||||||
@RunWith( JUnit4TestRunner.class )
|
@RunWith( PaxExam.class )
|
||||||
public class TestJettyOSGiBootCore
|
public class TestJettyOSGiBootCore
|
||||||
{
|
{
|
||||||
|
private static final String LOG_LEVEL = "WARN";
|
||||||
public static int DEFAULT_JETTY_HTTP_PORT = 9876;
|
public static int DEFAULT_JETTY_HTTP_PORT = 9876;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
|
@ -54,10 +58,13 @@ public class TestJettyOSGiBootCore
|
||||||
{
|
{
|
||||||
VersionResolver resolver = MavenUtils.asInProject();
|
VersionResolver resolver = MavenUtils.asInProject();
|
||||||
ArrayList<Option> options = new ArrayList<Option>();
|
ArrayList<Option> options = new ArrayList<Option>();
|
||||||
TestOSGiUtil.addMoreOSGiContainers(options);
|
|
||||||
options.addAll(provisionCoreJetty());
|
options.addAll(provisionCoreJetty());
|
||||||
options.add(CoreOptions.junitBundles());
|
options.add(CoreOptions.junitBundles());
|
||||||
options.addAll(httpServiceJetty());
|
options.addAll(httpServiceJetty());
|
||||||
|
options.addAll(Arrays.asList(options(systemProperty("pax.exam.logging").value("none"))));
|
||||||
|
options.addAll(Arrays.asList(options(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL))));
|
||||||
|
options.addAll(Arrays.asList(options(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL))));
|
||||||
|
|
||||||
return options.toArray(new Option[options.size()]);
|
return options.toArray(new Option[options.size()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,11 +82,20 @@ public class TestJettyOSGiBootCore
|
||||||
public static List<Option> coreJettyDependencies()
|
public static List<Option> coreJettyDependencies()
|
||||||
{
|
{
|
||||||
List<Option> res = new ArrayList<Option>();
|
List<Option> res = new ArrayList<Option>();
|
||||||
|
|
||||||
|
String jdk = System.getProperty("java.version");
|
||||||
|
int firstdot = jdk.indexOf(".");
|
||||||
|
jdk = jdk.substring(0,firstdot+2);
|
||||||
|
double version = Double.parseDouble(jdk);
|
||||||
|
|
||||||
|
if (version < 1.8)
|
||||||
|
{
|
||||||
res.add(mavenBundle().groupId( "org.ow2.asm" ).artifactId( "asm" ).versionAsInProject().start());
|
res.add(mavenBundle().groupId( "org.ow2.asm" ).artifactId( "asm" ).versionAsInProject().start());
|
||||||
res.add(mavenBundle().groupId( "org.ow2.asm" ).artifactId( "asm-commons" ).versionAsInProject().start());
|
res.add(mavenBundle().groupId( "org.ow2.asm" ).artifactId( "asm-commons" ).versionAsInProject().start());
|
||||||
res.add(mavenBundle().groupId( "org.ow2.asm" ).artifactId( "asm-tree" ).versionAsInProject().start());
|
res.add(mavenBundle().groupId( "org.ow2.asm" ).artifactId( "asm-tree" ).versionAsInProject().start());
|
||||||
res.add(mavenBundle().groupId( "org.apache.aries" ).artifactId( "org.apache.aries.util" ).version("1.0.0").start());
|
res.add(mavenBundle().groupId( "org.apache.aries" ).artifactId( "org.apache.aries.util" ).version("1.0.0").start());
|
||||||
res.add(mavenBundle().groupId( "org.apache.aries.spifly" ).artifactId( "org.apache.aries.spifly.dynamic.bundle" ).version("1.0.0").start());
|
res.add(mavenBundle().groupId( "org.apache.aries.spifly" ).artifactId( "org.apache.aries.spifly.dynamic.bundle" ).version("1.0.0").start());
|
||||||
|
}
|
||||||
|
|
||||||
res.add(mavenBundle().groupId( "javax.servlet" ).artifactId( "javax.servlet-api" ).versionAsInProject().noStart());
|
res.add(mavenBundle().groupId( "javax.servlet" ).artifactId( "javax.servlet-api" ).versionAsInProject().noStart());
|
||||||
res.add(mavenBundle().groupId( "javax.annotation" ).artifactId( "javax.annotation-api" ).versionAsInProject().noStart());
|
res.add(mavenBundle().groupId( "javax.annotation" ).artifactId( "javax.annotation-api" ).versionAsInProject().noStart());
|
||||||
|
@ -103,7 +119,10 @@ public class TestJettyOSGiBootCore
|
||||||
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-client" ).versionAsInProject().noStart());
|
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-client" ).versionAsInProject().noStart());
|
||||||
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-jndi" ).versionAsInProject().noStart());
|
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-jndi" ).versionAsInProject().noStart());
|
||||||
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-plus" ).versionAsInProject().noStart());
|
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-plus" ).versionAsInProject().noStart());
|
||||||
|
if (version < 1.8)
|
||||||
|
{
|
||||||
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-annotations" ).versionAsInProject().start());
|
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-annotations" ).versionAsInProject().start());
|
||||||
|
}
|
||||||
res.add(mavenBundle().groupId( "org.eclipse.jetty.websocket" ).artifactId( "websocket-api" ).versionAsInProject().noStart());
|
res.add(mavenBundle().groupId( "org.eclipse.jetty.websocket" ).artifactId( "websocket-api" ).versionAsInProject().noStart());
|
||||||
res.add(mavenBundle().groupId( "org.eclipse.jetty.websocket" ).artifactId( "websocket-common" ).versionAsInProject().noStart());
|
res.add(mavenBundle().groupId( "org.eclipse.jetty.websocket" ).artifactId( "websocket-common" ).versionAsInProject().noStart());
|
||||||
res.add(mavenBundle().groupId( "org.eclipse.jetty.websocket" ).artifactId( "websocket-servlet" ).versionAsInProject().noStart());
|
res.add(mavenBundle().groupId( "org.eclipse.jetty.websocket" ).artifactId( "websocket-servlet" ).versionAsInProject().noStart());
|
||||||
|
@ -124,9 +143,12 @@ public class TestJettyOSGiBootCore
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Ignore
|
||||||
@Test
|
@Test
|
||||||
public void assertAllBundlesActiveOrResolved() throws Exception
|
public void assertAllBundlesActiveOrResolved() throws Exception
|
||||||
{
|
{
|
||||||
|
|
||||||
|
TestOSGiUtil.debugBundles(bundleContext);
|
||||||
TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext);
|
TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,18 +36,18 @@ import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.ops4j.pax.exam.CoreOptions;
|
import org.ops4j.pax.exam.CoreOptions;
|
||||||
import org.ops4j.pax.exam.Option;
|
import org.ops4j.pax.exam.Option;
|
||||||
import org.ops4j.pax.exam.junit.Configuration;
|
import org.ops4j.pax.exam.Configuration;
|
||||||
import org.ops4j.pax.exam.junit.JUnit4TestRunner;
|
import org.ops4j.pax.exam.junit.PaxExam;
|
||||||
import org.osgi.framework.Bundle;
|
import org.osgi.framework.Bundle;
|
||||||
import org.osgi.framework.BundleContext;
|
import org.osgi.framework.BundleContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SPDY setup.
|
* SPDY setup.
|
||||||
*/
|
*/
|
||||||
@RunWith(JUnit4TestRunner.class)
|
@RunWith(PaxExam.class)
|
||||||
public class TestJettyOSGiBootSpdy
|
public class TestJettyOSGiBootSpdy
|
||||||
{
|
{
|
||||||
private static final boolean LOGGING_ENABLED = false;
|
private static final String LOG_LEVEL = "WARN";
|
||||||
|
|
||||||
private static final String JETTY_SPDY_PORT = "jetty.spdy.port";
|
private static final String JETTY_SPDY_PORT = "jetty.spdy.port";
|
||||||
|
|
||||||
|
@ -60,31 +60,14 @@ public class TestJettyOSGiBootSpdy
|
||||||
public Option[] config()
|
public Option[] config()
|
||||||
{
|
{
|
||||||
ArrayList<Option> options = new ArrayList<Option>();
|
ArrayList<Option> options = new ArrayList<Option>();
|
||||||
|
|
||||||
TestOSGiUtil.addMoreOSGiContainers(options);
|
|
||||||
|
|
||||||
|
|
||||||
options.addAll(TestJettyOSGiBootWithJsp.configureJettyHomeAndPort("jetty-spdy.xml"));
|
options.addAll(TestJettyOSGiBootWithJsp.configureJettyHomeAndPort("jetty-spdy.xml"));
|
||||||
options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
|
options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
|
||||||
options.addAll(spdyJettyDependencies());
|
options.addAll(spdyJettyDependencies());
|
||||||
options.add(CoreOptions.junitBundles());
|
options.add(CoreOptions.junitBundles());
|
||||||
options.addAll(TestJettyOSGiBootCore.httpServiceJetty());
|
options.addAll(TestJettyOSGiBootCore.httpServiceJetty());
|
||||||
|
options.addAll(Arrays.asList(options(systemProperty("pax.exam.logging").value("none"))));
|
||||||
String logLevel = "WARN";
|
options.addAll(Arrays.asList(options(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL))));
|
||||||
|
options.addAll(Arrays.asList(options(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL))));
|
||||||
// Enable Logging
|
|
||||||
if (LOGGING_ENABLED)
|
|
||||||
logLevel = "INFO";
|
|
||||||
|
|
||||||
|
|
||||||
options.addAll(Arrays.asList(options(
|
|
||||||
// install log service using pax runners profile abstraction (there
|
|
||||||
// are more profiles, like DS)
|
|
||||||
// logProfile(),
|
|
||||||
// this is how you set the default log level when using pax logging
|
|
||||||
// (logProfile)
|
|
||||||
systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(logLevel),
|
|
||||||
systemProperty("org.eclipse.jetty.LEVEL").value(logLevel))));
|
|
||||||
return options.toArray(new Option[options.size()]);
|
return options.toArray(new Option[options.size()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +84,7 @@ public class TestJettyOSGiBootSpdy
|
||||||
|
|
||||||
res.add(CoreOptions.vmOptions("-Xbootclasspath/p:" + alpnBoot));
|
res.add(CoreOptions.vmOptions("-Xbootclasspath/p:" + alpnBoot));
|
||||||
|
|
||||||
res.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("jetty-osgi-alpn").versionAsInProject().start());
|
res.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("jetty-osgi-alpn").versionAsInProject().noStart());
|
||||||
res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-server").versionAsInProject().start());
|
res.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-server").versionAsInProject().start());
|
||||||
|
|
||||||
res.add(mavenBundle().groupId("org.eclipse.jetty.spdy").artifactId("spdy-client").versionAsInProject().noStart());
|
res.add(mavenBundle().groupId("org.eclipse.jetty.spdy").artifactId("spdy-client").versionAsInProject().noStart());
|
||||||
|
@ -112,7 +95,6 @@ public class TestJettyOSGiBootSpdy
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Ignore
|
|
||||||
@Test
|
@Test
|
||||||
public void checkALPNBootOnBootstrapClasspath() throws Exception
|
public void checkALPNBootOnBootstrapClasspath() throws Exception
|
||||||
{
|
{
|
||||||
|
@ -121,13 +103,11 @@ public class TestJettyOSGiBootSpdy
|
||||||
Assert.assertNull(alpn.getClassLoader());
|
Assert.assertNull(alpn.getClassLoader());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Ignore
|
||||||
@Test
|
@Test
|
||||||
public void assertAllBundlesActiveOrResolved()
|
public void assertAllBundlesActiveOrResolved()
|
||||||
{
|
{
|
||||||
Bundle b = TestOSGiUtil.getBundle(bundleContext, "org.eclipse.jetty.spdy.client");
|
TestOSGiUtil.debugBundles(bundleContext);
|
||||||
TestOSGiUtil.diagnoseNonActiveOrNonResolvedBundle(b);
|
|
||||||
b = TestOSGiUtil.getBundle(bundleContext, "org.eclipse.jetty.osgi.boot");
|
|
||||||
TestOSGiUtil.diagnoseNonActiveOrNonResolvedBundle(b);
|
|
||||||
TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext);
|
TestOSGiUtil.assertAllBundlesActiveOrResolved(bundleContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,12 +38,13 @@ import org.eclipse.jetty.http.HttpStatus;
|
||||||
import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
|
import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
|
||||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||||
import org.eclipse.jetty.webapp.WebAppContext;
|
import org.eclipse.jetty.webapp.WebAppContext;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.ops4j.pax.exam.CoreOptions;
|
import org.ops4j.pax.exam.CoreOptions;
|
||||||
import org.ops4j.pax.exam.Option;
|
import org.ops4j.pax.exam.Option;
|
||||||
import org.ops4j.pax.exam.junit.Configuration;
|
import org.ops4j.pax.exam.Configuration;
|
||||||
import org.ops4j.pax.exam.junit.JUnit4TestRunner;
|
import org.ops4j.pax.exam.junit.PaxExam;
|
||||||
import org.osgi.framework.BundleContext;
|
import org.osgi.framework.BundleContext;
|
||||||
import org.osgi.framework.ServiceReference;
|
import org.osgi.framework.ServiceReference;
|
||||||
|
|
||||||
|
@ -58,12 +59,10 @@ import org.osgi.framework.ServiceReference;
|
||||||
* httpservice web-bundle. Then make sure we can deploy an OSGi service on the
|
* httpservice web-bundle. Then make sure we can deploy an OSGi service on the
|
||||||
* top of this.
|
* top of this.
|
||||||
*/
|
*/
|
||||||
@RunWith(JUnit4TestRunner.class)
|
@RunWith(PaxExam.class)
|
||||||
public class TestJettyOSGiBootWebAppAsService
|
public class TestJettyOSGiBootWebAppAsService
|
||||||
{
|
{
|
||||||
private static final boolean LOGGING_ENABLED = false;
|
private static final String LOG_LEVEL = "WARN";
|
||||||
|
|
||||||
private static final boolean REMOTE_DEBUGGING = false;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
BundleContext bundleContext = null;
|
BundleContext bundleContext = null;
|
||||||
|
@ -72,8 +71,6 @@ public class TestJettyOSGiBootWebAppAsService
|
||||||
public static Option[] configure()
|
public static Option[] configure()
|
||||||
{
|
{
|
||||||
ArrayList<Option> options = new ArrayList<Option>();
|
ArrayList<Option> options = new ArrayList<Option>();
|
||||||
TestOSGiUtil.addMoreOSGiContainers(options);
|
|
||||||
|
|
||||||
options.add(CoreOptions.junitBundles());
|
options.add(CoreOptions.junitBundles());
|
||||||
options.addAll(configureJettyHomeAndPort("jetty-selector.xml"));
|
options.addAll(configureJettyHomeAndPort("jetty-selector.xml"));
|
||||||
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*"));
|
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*"));
|
||||||
|
@ -82,19 +79,9 @@ public class TestJettyOSGiBootWebAppAsService
|
||||||
"com.sun.org.apache.xpath.internal.jaxp", "com.sun.org.apache.xpath.internal.objects"));
|
"com.sun.org.apache.xpath.internal.jaxp", "com.sun.org.apache.xpath.internal.objects"));
|
||||||
|
|
||||||
options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
|
options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
|
||||||
|
options.addAll(Arrays.asList(options(systemProperty("pax.exam.logging").value("none"))));
|
||||||
String logLevel = "WARN";
|
options.addAll(Arrays.asList(options(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL))));
|
||||||
if (LOGGING_ENABLED)
|
options.addAll(Arrays.asList(options(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL))));
|
||||||
logLevel = "INFO";
|
|
||||||
|
|
||||||
options.addAll(Arrays.asList(options(
|
|
||||||
// install log service using pax runners profile abstraction (there
|
|
||||||
// are more profiles, like DS)
|
|
||||||
// logProfile(),
|
|
||||||
// this is how you set the default log level when using pax logging
|
|
||||||
// (logProfile)
|
|
||||||
systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(logLevel),
|
|
||||||
systemProperty("org.eclipse.jetty.LEVEL").value(logLevel))));
|
|
||||||
|
|
||||||
options.addAll(jspDependencies());
|
options.addAll(jspDependencies());
|
||||||
return options.toArray(new Option[options.size()]);
|
return options.toArray(new Option[options.size()]);
|
||||||
|
@ -139,6 +126,7 @@ public class TestJettyOSGiBootWebAppAsService
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Ignore
|
||||||
@Test
|
@Test
|
||||||
public void assertAllBundlesActiveOrResolved()
|
public void assertAllBundlesActiveOrResolved()
|
||||||
{
|
{
|
||||||
|
|
|
@ -38,25 +38,21 @@ import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.ops4j.pax.exam.Configuration;
|
||||||
import org.ops4j.pax.exam.CoreOptions;
|
import org.ops4j.pax.exam.CoreOptions;
|
||||||
import org.ops4j.pax.exam.Option;
|
import org.ops4j.pax.exam.Option;
|
||||||
import org.ops4j.pax.exam.junit.Configuration;
|
import org.ops4j.pax.exam.junit.PaxExam;
|
||||||
import org.ops4j.pax.exam.junit.JUnit4TestRunner;
|
|
||||||
import org.osgi.framework.Bundle;
|
|
||||||
import org.osgi.framework.BundleContext;
|
import org.osgi.framework.BundleContext;
|
||||||
import org.osgi.framework.ServiceReference;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pax-Exam to make sure the jetty-osgi-boot can be started along with the
|
* Pax-Exam to make sure the jetty-osgi-boot can be started along with the
|
||||||
* httpservice web-bundle. Then make sure we can deploy an OSGi service on the
|
* httpservice web-bundle. Then make sure we can deploy an OSGi service on the
|
||||||
* top of this.
|
* top of this.
|
||||||
*/
|
*/
|
||||||
@RunWith(JUnit4TestRunner.class)
|
@RunWith(PaxExam.class)
|
||||||
public class TestJettyOSGiBootWithJsp
|
public class TestJettyOSGiBootWithJsp
|
||||||
{
|
{
|
||||||
private static final boolean LOGGING_ENABLED = false;
|
private static final String LOG_LEVEL = "WARN";
|
||||||
|
|
||||||
private static final boolean REMOTE_DEBUGGING = false;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
BundleContext bundleContext = null;
|
BundleContext bundleContext = null;
|
||||||
|
@ -66,9 +62,6 @@ public class TestJettyOSGiBootWithJsp
|
||||||
{
|
{
|
||||||
|
|
||||||
ArrayList<Option> options = new ArrayList<Option>();
|
ArrayList<Option> options = new ArrayList<Option>();
|
||||||
|
|
||||||
TestOSGiUtil.addMoreOSGiContainers(options);
|
|
||||||
|
|
||||||
options.add(CoreOptions.junitBundles());
|
options.add(CoreOptions.junitBundles());
|
||||||
options.addAll(configureJettyHomeAndPort("jetty-selector.xml"));
|
options.addAll(configureJettyHomeAndPort("jetty-selector.xml"));
|
||||||
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*", "javax.activation.*"));
|
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*", "javax.activation.*"));
|
||||||
|
@ -77,39 +70,11 @@ public class TestJettyOSGiBootWithJsp
|
||||||
"com.sun.org.apache.xpath.internal.jaxp", "com.sun.org.apache.xpath.internal.objects"));
|
"com.sun.org.apache.xpath.internal.jaxp", "com.sun.org.apache.xpath.internal.objects"));
|
||||||
|
|
||||||
options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
|
options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
|
||||||
|
options.addAll(Arrays.asList(options(systemProperty("pax.exam.logging").value("none"))));
|
||||||
String logLevel = "WARN";
|
options.addAll(Arrays.asList(options(systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(LOG_LEVEL))));
|
||||||
|
options.addAll(Arrays.asList(options(systemProperty("org.eclipse.jetty.LEVEL").value(LOG_LEVEL))));
|
||||||
// Enable Logging
|
|
||||||
if (LOGGING_ENABLED)
|
|
||||||
logLevel = "INFO";
|
|
||||||
|
|
||||||
options.addAll(Arrays.asList(options(
|
|
||||||
// install log service using pax runners profile abstraction (there
|
|
||||||
// are more profiles, like DS)
|
|
||||||
// logProfile(),
|
|
||||||
// this is how you set the default log level when using pax logging
|
|
||||||
// (logProfile)
|
|
||||||
systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(logLevel),
|
|
||||||
systemProperty("org.eclipse.jetty.annotations.LEVEL").value(logLevel))));
|
|
||||||
|
|
||||||
options.addAll(jspDependencies());
|
options.addAll(jspDependencies());
|
||||||
|
|
||||||
// Remote JDWP Debugging, this won't work with the forked container.
|
|
||||||
// if(REMOTE_DEBUGGING) {
|
|
||||||
// options.addAll(Arrays.asList(options(
|
|
||||||
// // this just adds all what you write here to java vm argumenents of
|
|
||||||
// the (new) osgi process.
|
|
||||||
// PaxRunnerOptions.vmOption(
|
|
||||||
// "-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5006" )
|
|
||||||
// )));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// bug at the moment: this would make the httpservice catch all
|
|
||||||
// requests and prevent the webapp at the root context to catch any of
|
|
||||||
// them.
|
|
||||||
// options.addAll(TestJettyOSGiBootCore.httpServiceJetty());
|
|
||||||
|
|
||||||
return options.toArray(new Option[options.size()]);
|
return options.toArray(new Option[options.size()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,7 +122,7 @@ public class TestJettyOSGiBootWithJsp
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Ignore
|
||||||
@Test
|
@Test
|
||||||
public void assertAllBundlesActiveOrResolved()
|
public void assertAllBundlesActiveOrResolved()
|
||||||
{
|
{
|
||||||
|
|
|
@ -33,7 +33,6 @@ import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
import org.eclipse.jetty.http.HttpStatus;
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.ops4j.pax.exam.CoreOptions;
|
|
||||||
import org.ops4j.pax.exam.Option;
|
import org.ops4j.pax.exam.Option;
|
||||||
import org.osgi.framework.Bundle;
|
import org.osgi.framework.Bundle;
|
||||||
import org.osgi.framework.BundleContext;
|
import org.osgi.framework.BundleContext;
|
||||||
|
@ -46,22 +45,6 @@ import org.osgi.service.http.HttpService;
|
||||||
public class TestOSGiUtil
|
public class TestOSGiUtil
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
|
||||||
* Note: this will run many more tests. TODO: find a better way to control
|
|
||||||
* this and use non-deprecated methods.
|
|
||||||
*
|
|
||||||
* @param opti
|
|
||||||
*/
|
|
||||||
protected static void addMoreOSGiContainers(List<Option> options)
|
|
||||||
{
|
|
||||||
//Uncomment to run more containers - these have been commented out
|
|
||||||
//to improve speed of builds.
|
|
||||||
//options.add(CoreOptions.equinox().version("3.6.1"));
|
|
||||||
//options.add(CoreOptions.equinox().version("3.7.0"));
|
|
||||||
// options.add(CoreOptions.felix().version("3.2.2"));
|
|
||||||
options.add(CoreOptions.felix().version("4.0.2"));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static Bundle getBundle(BundleContext bundleContext, String symbolicName)
|
protected static Bundle getBundle(BundleContext bundleContext, String symbolicName)
|
||||||
{
|
{
|
||||||
Map<String,Bundle> _bundles = new HashMap<String, Bundle>();
|
Map<String,Bundle> _bundles = new HashMap<String, Bundle>();
|
||||||
|
@ -146,7 +129,7 @@ public class TestOSGiUtil
|
||||||
for (Bundle b : bundleContext.getBundles())
|
for (Bundle b : bundleContext.getBundles())
|
||||||
{
|
{
|
||||||
bundlesIndexedBySymbolicName.put(b.getSymbolicName(), b);
|
bundlesIndexedBySymbolicName.put(b.getSymbolicName(), b);
|
||||||
System.err.println(" " + b.getSymbolicName() + " " + b.getState());
|
System.err.println(" " + b.getSymbolicName() + " " + b.getLocation() + " " + b.getVersion()+ " " + b.getState());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,7 +28,6 @@ import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
import javax.servlet.AsyncContext;
|
import javax.servlet.AsyncContext;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
@ -432,8 +431,7 @@ public class ConnectHandler extends HandlerWrapper
|
||||||
|
|
||||||
protected class ConnectManager extends SelectorManager
|
protected class ConnectManager extends SelectorManager
|
||||||
{
|
{
|
||||||
|
protected ConnectManager(Executor executor, Scheduler scheduler, int selectors)
|
||||||
private ConnectManager(Executor executor, Scheduler scheduler, int selectors)
|
|
||||||
{
|
{
|
||||||
super(executor, scheduler, selectors);
|
super(executor, scheduler, selectors);
|
||||||
}
|
}
|
||||||
|
@ -455,11 +453,17 @@ public class ConnectHandler extends HandlerWrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment)
|
protected void connectionFailed(SocketChannel channel, final Throwable ex, final Object attachment)
|
||||||
|
{
|
||||||
|
getExecutor().execute(new Runnable()
|
||||||
|
{
|
||||||
|
public void run()
|
||||||
{
|
{
|
||||||
ConnectContext connectContext = (ConnectContext)attachment;
|
ConnectContext connectContext = (ConnectContext)attachment;
|
||||||
onConnectFailure(connectContext.request, connectContext.response, connectContext.asyncContext, ex);
|
onConnectFailure(connectContext.request, connectContext.response, connectContext.asyncContext, ex);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static class ConnectContext
|
protected static class ConnectContext
|
||||||
|
@ -518,9 +522,15 @@ public class ConnectHandler extends HandlerWrapper
|
||||||
public void onOpen()
|
public void onOpen()
|
||||||
{
|
{
|
||||||
super.onOpen();
|
super.onOpen();
|
||||||
onConnectSuccess(connectContext, this);
|
getExecutor().execute(new Runnable()
|
||||||
|
{
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
onConnectSuccess(connectContext, UpstreamConnection.this);
|
||||||
fillInterested();
|
fillInterested();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int read(EndPoint endPoint, ByteBuffer buffer) throws IOException
|
protected int read(EndPoint endPoint, ByteBuffer buffer) throws IOException
|
||||||
|
|
|
@ -41,14 +41,16 @@ import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.HttpClient;
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.api.ContentProvider;
|
||||||
import org.eclipse.jetty.client.api.Request;
|
import org.eclipse.jetty.client.api.Request;
|
||||||
import org.eclipse.jetty.client.api.Response;
|
import org.eclipse.jetty.client.api.Response;
|
||||||
import org.eclipse.jetty.client.api.Result;
|
import org.eclipse.jetty.client.api.Result;
|
||||||
import org.eclipse.jetty.client.util.InputStreamContentProvider;
|
import org.eclipse.jetty.client.util.InputStreamContentProvider;
|
||||||
import org.eclipse.jetty.http.HttpField;
|
import org.eclipse.jetty.http.HttpField;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
import org.eclipse.jetty.http.HttpMethod;
|
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||||
import org.eclipse.jetty.http.HttpVersion;
|
import org.eclipse.jetty.http.HttpVersion;
|
||||||
|
import org.eclipse.jetty.util.Callback;
|
||||||
import org.eclipse.jetty.util.HttpCookieStore;
|
import org.eclipse.jetty.util.HttpCookieStore;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
@ -79,7 +81,6 @@ import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||||
*/
|
*/
|
||||||
public class ProxyServlet extends HttpServlet
|
public class ProxyServlet extends HttpServlet
|
||||||
{
|
{
|
||||||
protected static final String ASYNC_CONTEXT = ProxyServlet.class.getName() + ".asyncContext";
|
|
||||||
private static final Set<String> HOP_HEADERS = new HashSet<>();
|
private static final Set<String> HOP_HEADERS = new HashSet<>();
|
||||||
static
|
static
|
||||||
{
|
{
|
||||||
|
@ -174,14 +175,21 @@ public class ProxyServlet extends HttpServlet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected HttpClient getHttpClient()
|
||||||
|
{
|
||||||
|
return _client;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return a logger instance with a name derived from this servlet's name.
|
* @return a logger instance with a name derived from this servlet's name.
|
||||||
*/
|
*/
|
||||||
protected Logger createLogger()
|
protected Logger createLogger()
|
||||||
{
|
{
|
||||||
String name = getServletConfig().getServletName();
|
String servletName = getServletConfig().getServletName();
|
||||||
name = name.replace('-', '.');
|
servletName = servletName.replace('-', '.');
|
||||||
return Log.getLogger(name);
|
if (!servletName.startsWith(getClass().getPackage().getName()))
|
||||||
|
servletName = getClass().getName() + "." + servletName;
|
||||||
|
return Log.getLogger(servletName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void destroy()
|
public void destroy()
|
||||||
|
@ -390,26 +398,25 @@ public class ProxyServlet extends HttpServlet
|
||||||
}
|
}
|
||||||
|
|
||||||
final Request proxyRequest = _client.newRequest(rewrittenURI)
|
final Request proxyRequest = _client.newRequest(rewrittenURI)
|
||||||
.method(HttpMethod.fromString(request.getMethod()))
|
.method(request.getMethod())
|
||||||
.version(HttpVersion.fromString(request.getProtocol()));
|
.version(HttpVersion.fromString(request.getProtocol()));
|
||||||
|
|
||||||
// Copy headers
|
// Copy headers
|
||||||
boolean hasContent = false;
|
boolean hasContent = request.getContentLength() > 0 || request.getContentType() != null;
|
||||||
for (Enumeration<String> headerNames = request.getHeaderNames(); headerNames.hasMoreElements();)
|
for (Enumeration<String> headerNames = request.getHeaderNames(); headerNames.hasMoreElements();)
|
||||||
{
|
{
|
||||||
String headerName = headerNames.nextElement();
|
String headerName = headerNames.nextElement();
|
||||||
String lowerHeaderName = headerName.toLowerCase(Locale.ENGLISH);
|
String lowerHeaderName = headerName.toLowerCase(Locale.ENGLISH);
|
||||||
|
|
||||||
// Remove hop-by-hop headers
|
if (HttpHeader.TRANSFER_ENCODING.is(headerName))
|
||||||
if (HOP_HEADERS.contains(lowerHeaderName))
|
hasContent = true;
|
||||||
continue;
|
|
||||||
|
|
||||||
if (_hostHeader != null && HttpHeader.HOST.is(headerName))
|
if (_hostHeader != null && HttpHeader.HOST.is(headerName))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (request.getContentLength() > 0 || request.getContentType() != null ||
|
// Remove hop-by-hop headers
|
||||||
HttpHeader.TRANSFER_ENCODING.is(headerName))
|
if (HOP_HEADERS.contains(lowerHeaderName))
|
||||||
hasContent = true;
|
continue;
|
||||||
|
|
||||||
for (Enumeration<String> headerValues = request.getHeaders(headerName); headerValues.hasMoreElements();)
|
for (Enumeration<String> headerValues = request.getHeaders(headerName); headerValues.hasMoreElements();)
|
||||||
{
|
{
|
||||||
|
@ -427,29 +434,13 @@ public class ProxyServlet extends HttpServlet
|
||||||
addViaHeader(proxyRequest);
|
addViaHeader(proxyRequest);
|
||||||
addXForwardedHeaders(proxyRequest, request);
|
addXForwardedHeaders(proxyRequest, request);
|
||||||
|
|
||||||
if (hasContent)
|
|
||||||
{
|
|
||||||
proxyRequest.content(new InputStreamContentProvider(request.getInputStream())
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public long getLength()
|
|
||||||
{
|
|
||||||
return request.getContentLength();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ByteBuffer onRead(byte[] buffer, int offset, int length)
|
|
||||||
{
|
|
||||||
_log.debug("{} proxying content to upstream: {} bytes", requestId, length);
|
|
||||||
return super.onRead(buffer, offset, length);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
final AsyncContext asyncContext = request.startAsync();
|
final AsyncContext asyncContext = request.startAsync();
|
||||||
// We do not timeout the continuation, but the proxy request
|
// We do not timeout the continuation, but the proxy request
|
||||||
asyncContext.setTimeout(0);
|
asyncContext.setTimeout(0);
|
||||||
request.setAttribute(ASYNC_CONTEXT, asyncContext);
|
proxyRequest.timeout(getTimeout(), TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
if (hasContent)
|
||||||
|
proxyRequest.content(proxyRequestContent(proxyRequest, request));
|
||||||
|
|
||||||
customizeProxyRequest(proxyRequest, request);
|
customizeProxyRequest(proxyRequest, request);
|
||||||
|
|
||||||
|
@ -486,10 +477,40 @@ public class ProxyServlet extends HttpServlet
|
||||||
proxyRequest.getHeaders().toString().trim());
|
proxyRequest.getHeaders().toString().trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
proxyRequest.timeout(getTimeout(), TimeUnit.MILLISECONDS);
|
|
||||||
proxyRequest.send(new ProxyResponseListener(request, response));
|
proxyRequest.send(new ProxyResponseListener(request, response));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected ContentProvider proxyRequestContent(final Request proxyRequest, final HttpServletRequest request) throws IOException
|
||||||
|
{
|
||||||
|
return new InputStreamContentProvider(request.getInputStream())
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public long getLength()
|
||||||
|
{
|
||||||
|
return request.getContentLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ByteBuffer onRead(byte[] buffer, int offset, int length)
|
||||||
|
{
|
||||||
|
_log.debug("{} proxying content to upstream: {} bytes", getRequestId(request), length);
|
||||||
|
return super.onRead(buffer, offset, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onReadFailure(Throwable failure)
|
||||||
|
{
|
||||||
|
onClientRequestFailure(proxyRequest, request, failure);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onClientRequestFailure(Request proxyRequest, HttpServletRequest request, Throwable failure)
|
||||||
|
{
|
||||||
|
_log.debug(getRequestId(request) + " client request failure", failure);
|
||||||
|
proxyRequest.abort(failure);
|
||||||
|
}
|
||||||
|
|
||||||
protected void onRewriteFailed(HttpServletRequest request, HttpServletResponse response) throws IOException
|
protected void onRewriteFailed(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||||
{
|
{
|
||||||
response.sendError(HttpServletResponse.SC_FORBIDDEN);
|
response.sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||||
|
@ -510,6 +531,10 @@ public class ProxyServlet extends HttpServlet
|
||||||
|
|
||||||
protected void onResponseHeaders(HttpServletRequest request, HttpServletResponse response, Response proxyResponse)
|
protected void onResponseHeaders(HttpServletRequest request, HttpServletResponse response, Response proxyResponse)
|
||||||
{
|
{
|
||||||
|
// Clear the response headers in case it comes with predefined ones.
|
||||||
|
for (String name : response.getHeaderNames())
|
||||||
|
response.setHeader(name, null);
|
||||||
|
|
||||||
for (HttpField field : proxyResponse.getHeaders())
|
for (HttpField field : proxyResponse.getHeaders())
|
||||||
{
|
{
|
||||||
String headerName = field.getName();
|
String headerName = field.getName();
|
||||||
|
@ -525,17 +550,25 @@ public class ProxyServlet extends HttpServlet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length) throws IOException
|
protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length, Callback callback)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
response.getOutputStream().write(buffer, offset, length);
|
|
||||||
_log.debug("{} proxying content to downstream: {} bytes", getRequestId(request), length);
|
_log.debug("{} proxying content to downstream: {} bytes", getRequestId(request), length);
|
||||||
|
response.getOutputStream().write(buffer, offset, length);
|
||||||
|
callback.succeeded();
|
||||||
|
}
|
||||||
|
catch (Throwable x)
|
||||||
|
{
|
||||||
|
callback.failed(x);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onResponseSuccess(HttpServletRequest request, HttpServletResponse response, Response proxyResponse)
|
protected void onResponseSuccess(HttpServletRequest request, HttpServletResponse response, Response proxyResponse)
|
||||||
{
|
{
|
||||||
AsyncContext asyncContext = (AsyncContext)request.getAttribute(ASYNC_CONTEXT);
|
|
||||||
asyncContext.complete();
|
|
||||||
_log.debug("{} proxying successful", getRequestId(request));
|
_log.debug("{} proxying successful", getRequestId(request));
|
||||||
|
AsyncContext asyncContext = request.getAsyncContext();
|
||||||
|
asyncContext.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onResponseFailure(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, Throwable failure)
|
protected void onResponseFailure(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, Throwable failure)
|
||||||
|
@ -547,10 +580,11 @@ public class ProxyServlet extends HttpServlet
|
||||||
response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT);
|
response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT);
|
||||||
else
|
else
|
||||||
response.setStatus(HttpServletResponse.SC_BAD_GATEWAY);
|
response.setStatus(HttpServletResponse.SC_BAD_GATEWAY);
|
||||||
}
|
response.setHeader(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString());
|
||||||
AsyncContext asyncContext = (AsyncContext)request.getAttribute(ASYNC_CONTEXT);
|
AsyncContext asyncContext = request.getAsyncContext();
|
||||||
asyncContext.complete();
|
asyncContext.complete();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected int getRequestId(HttpServletRequest request)
|
protected int getRequestId(HttpServletRequest request)
|
||||||
{
|
{
|
||||||
|
@ -597,59 +631,66 @@ public class ProxyServlet extends HttpServlet
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transparent Proxy.
|
* This convenience extension to {@link ProxyServlet} configures the servlet as a transparent proxy.
|
||||||
* <p/>
|
* This servlet is configured with the following init parameters:
|
||||||
* This convenience extension to ProxyServlet configures the servlet as a transparent proxy.
|
|
||||||
* The servlet is configured with init parameters:
|
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>proxyTo - a URI like http://host:80/context to which the request is proxied.
|
* <li>proxyTo - a mandatory URI like http://host:80/context to which the request is proxied.</li>
|
||||||
* <li>prefix - a URI prefix that is striped from the start of the forwarded URI.
|
* <li>prefix - an optional URI prefix that is stripped from the start of the forwarded URI.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* For example, if a request is received at /foo/bar and the 'proxyTo' parameter is "http://host:80/context"
|
* <p/>
|
||||||
|
* For example, if a request is received at "/foo/bar", the 'proxyTo' parameter is "http://host:80/context"
|
||||||
* and the 'prefix' parameter is "/foo", then the request would be proxied to "http://host:80/context/bar".
|
* and the 'prefix' parameter is "/foo", then the request would be proxied to "http://host:80/context/bar".
|
||||||
*/
|
*/
|
||||||
public static class Transparent extends ProxyServlet
|
public static class Transparent extends ProxyServlet
|
||||||
{
|
{
|
||||||
|
private final TransparentDelegate delegate = new TransparentDelegate(this);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(ServletConfig config) throws ServletException
|
||||||
|
{
|
||||||
|
super.init(config);
|
||||||
|
delegate.init(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected URI rewriteURI(HttpServletRequest request)
|
||||||
|
{
|
||||||
|
return delegate.rewriteURI(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class TransparentDelegate
|
||||||
|
{
|
||||||
|
private final ProxyServlet proxyServlet;
|
||||||
private String _proxyTo;
|
private String _proxyTo;
|
||||||
private String _prefix;
|
private String _prefix;
|
||||||
|
|
||||||
public Transparent()
|
protected TransparentDelegate(ProxyServlet proxyServlet)
|
||||||
{
|
{
|
||||||
|
this.proxyServlet = proxyServlet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Transparent(String proxyTo, String prefix)
|
protected void init(ServletConfig config) throws ServletException
|
||||||
{
|
{
|
||||||
_proxyTo = URI.create(proxyTo).normalize().toString();
|
_proxyTo = config.getInitParameter("proxyTo");
|
||||||
_prefix = URI.create(prefix).normalize().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init() throws ServletException
|
|
||||||
{
|
|
||||||
super.init();
|
|
||||||
|
|
||||||
ServletConfig config = getServletConfig();
|
|
||||||
|
|
||||||
String prefix = config.getInitParameter("prefix");
|
|
||||||
_prefix = prefix == null ? _prefix : prefix;
|
|
||||||
|
|
||||||
// Adjust prefix value to account for context path
|
|
||||||
String contextPath = getServletContext().getContextPath();
|
|
||||||
_prefix = _prefix == null ? contextPath : (contextPath + _prefix);
|
|
||||||
|
|
||||||
String proxyTo = config.getInitParameter("proxyTo");
|
|
||||||
_proxyTo = proxyTo == null ? _proxyTo : proxyTo;
|
|
||||||
|
|
||||||
if (_proxyTo == null)
|
if (_proxyTo == null)
|
||||||
throw new UnavailableException("Init parameter 'proxyTo' is required.");
|
throw new UnavailableException("Init parameter 'proxyTo' is required.");
|
||||||
|
|
||||||
if (!_prefix.startsWith("/"))
|
String prefix = config.getInitParameter("prefix");
|
||||||
throw new UnavailableException("Init parameter 'prefix' parameter must start with a '/'.");
|
if (prefix != null)
|
||||||
|
{
|
||||||
_log.debug(config.getServletName() + " @ " + _prefix + " to " + _proxyTo);
|
if (!prefix.startsWith("/"))
|
||||||
|
throw new UnavailableException("Init parameter 'prefix' must start with a '/'.");
|
||||||
|
_prefix = prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust prefix value to account for context path
|
||||||
|
String contextPath = config.getServletContext().getContextPath();
|
||||||
|
_prefix = _prefix == null ? contextPath : (contextPath + _prefix);
|
||||||
|
|
||||||
|
proxyServlet._log.debug(config.getServletName() + " @ " + _prefix + " to " + _proxyTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected URI rewriteURI(HttpServletRequest request)
|
protected URI rewriteURI(HttpServletRequest request)
|
||||||
{
|
{
|
||||||
String path = request.getRequestURI();
|
String path = request.getRequestURI();
|
||||||
|
@ -668,7 +709,7 @@ public class ProxyServlet extends HttpServlet
|
||||||
uri.append("?").append(query);
|
uri.append("?").append(query);
|
||||||
URI rewrittenURI = URI.create(uri.toString()).normalize();
|
URI rewrittenURI = URI.create(uri.toString()).normalize();
|
||||||
|
|
||||||
if (!validateDestination(rewrittenURI.getHost(), rewrittenURI.getPort()))
|
if (!proxyServlet.validateDestination(rewrittenURI.getHost(), rewrittenURI.getPort()))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return rewrittenURI;
|
return rewrittenURI;
|
||||||
|
@ -726,7 +767,7 @@ public class ProxyServlet extends HttpServlet
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onContent(Response proxyResponse, ByteBuffer content)
|
public void onContent(final Response proxyResponse, ByteBuffer content, final Callback callback)
|
||||||
{
|
{
|
||||||
byte[] buffer;
|
byte[] buffer;
|
||||||
int offset;
|
int offset;
|
||||||
|
@ -743,31 +784,30 @@ public class ProxyServlet extends HttpServlet
|
||||||
offset = 0;
|
offset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
onResponseContent(request, response, proxyResponse, buffer, offset, length, new Callback()
|
||||||
{
|
{
|
||||||
onResponseContent(request, response, proxyResponse, buffer, offset, length);
|
@Override
|
||||||
|
public void succeeded()
|
||||||
|
{
|
||||||
|
callback.succeeded();
|
||||||
}
|
}
|
||||||
catch (IOException x)
|
|
||||||
|
@Override
|
||||||
|
public void failed(Throwable x)
|
||||||
{
|
{
|
||||||
|
callback.failed(x);
|
||||||
proxyResponse.abort(x);
|
proxyResponse.abort(x);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Response proxyResponse)
|
|
||||||
{
|
|
||||||
onResponseSuccess(request, response, proxyResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Response proxyResponse, Throwable failure)
|
|
||||||
{
|
|
||||||
onResponseFailure(request, response, proxyResponse, failure);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onComplete(Result result)
|
public void onComplete(Result result)
|
||||||
{
|
{
|
||||||
|
if (result.isSucceeded())
|
||||||
|
onResponseSuccess(request, response, result.getResponse());
|
||||||
|
else
|
||||||
|
onResponseFailure(request, response, result.getResponse(), result.getFailure());
|
||||||
_log.debug("{} proxying complete", getRequestId(request));
|
_log.debug("{} proxying complete", getRequestId(request));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.proxy;
|
package org.eclipse.jetty.proxy;
|
||||||
|
|
||||||
import static java.nio.file.StandardOpenOption.CREATE;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
@ -30,6 +28,8 @@ import java.net.HttpCookie;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -39,7 +39,6 @@ import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.zip.GZIPOutputStream;
|
import java.util.zip.GZIPOutputStream;
|
||||||
|
|
||||||
import javax.servlet.AsyncContext;
|
import javax.servlet.AsyncContext;
|
||||||
import javax.servlet.AsyncEvent;
|
import javax.servlet.AsyncEvent;
|
||||||
import javax.servlet.AsyncListener;
|
import javax.servlet.AsyncListener;
|
||||||
|
@ -62,15 +61,12 @@ import org.eclipse.jetty.http.HttpMethod;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.ServerConnector;
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||||
import org.eclipse.jetty.servlet.ServletHandler;
|
|
||||||
import org.eclipse.jetty.servlet.ServletHolder;
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
import org.eclipse.jetty.toolchain.test.AdvancedRunner;
|
|
||||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||||
import org.eclipse.jetty.toolchain.test.TestTracker;
|
import org.eclipse.jetty.toolchain.test.TestTracker;
|
||||||
import org.eclipse.jetty.toolchain.test.annotation.Slow;
|
import org.eclipse.jetty.toolchain.test.annotation.Slow;
|
||||||
|
import org.eclipse.jetty.util.Callback;
|
||||||
import org.eclipse.jetty.util.IO;
|
import org.eclipse.jetty.util.IO;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
|
||||||
import org.eclipse.jetty.util.log.StdErrLog;
|
|
||||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
|
@ -78,11 +74,22 @@ import org.junit.Assert;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.Parameterized;
|
||||||
|
|
||||||
@RunWith(AdvancedRunner.class)
|
@RunWith(Parameterized.class)
|
||||||
public class ProxyServletTest
|
public class ProxyServletTest
|
||||||
{
|
{
|
||||||
private static final String PROXIED_HEADER = "X-Proxied";
|
private static final String PROXIED_HEADER = "X-Proxied";
|
||||||
|
|
||||||
|
@Parameterized.Parameters
|
||||||
|
public static Iterable<Object[]> data()
|
||||||
|
{
|
||||||
|
return Arrays.asList(new Object[][]{
|
||||||
|
{ProxyServlet.class},
|
||||||
|
{AsyncProxyServlet.class}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public final TestTracker tracker = new TestTracker();
|
public final TestTracker tracker = new TestTracker();
|
||||||
private HttpClient client;
|
private HttpClient client;
|
||||||
|
@ -92,15 +99,25 @@ public class ProxyServletTest
|
||||||
private Server server;
|
private Server server;
|
||||||
private ServerConnector serverConnector;
|
private ServerConnector serverConnector;
|
||||||
|
|
||||||
private void prepareProxy(ProxyServlet proxyServlet) throws Exception
|
public ProxyServletTest(Class<?> proxyServletClass) throws Exception
|
||||||
|
{
|
||||||
|
this.proxyServlet = (ProxyServlet)proxyServletClass.newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prepareProxy() throws Exception
|
||||||
|
{
|
||||||
|
prepareProxy(new HashMap<String, String>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prepareProxy(Map<String, String> initParams) throws Exception
|
||||||
{
|
{
|
||||||
proxy = new Server();
|
proxy = new Server();
|
||||||
proxyConnector = new ServerConnector(proxy);
|
proxyConnector = new ServerConnector(proxy);
|
||||||
proxy.addConnector(proxyConnector);
|
proxy.addConnector(proxyConnector);
|
||||||
|
|
||||||
ServletContextHandler proxyCtx = new ServletContextHandler(proxy, "/", true, false);
|
ServletContextHandler proxyCtx = new ServletContextHandler(proxy, "/", true, false);
|
||||||
this.proxyServlet = proxyServlet;
|
|
||||||
ServletHolder proxyServletHolder = new ServletHolder(proxyServlet);
|
ServletHolder proxyServletHolder = new ServletHolder(proxyServlet);
|
||||||
|
proxyServletHolder.setInitParameters(initParams);
|
||||||
proxyCtx.addServlet(proxyServletHolder, "/*");
|
proxyCtx.addServlet(proxyServletHolder, "/*");
|
||||||
|
|
||||||
proxy.start();
|
proxy.start();
|
||||||
|
@ -145,7 +162,7 @@ public class ProxyServletTest
|
||||||
@Test
|
@Test
|
||||||
public void testProxyDown() throws Exception
|
public void testProxyDown() throws Exception
|
||||||
{
|
{
|
||||||
prepareProxy(new ProxyServlet());
|
prepareProxy();
|
||||||
prepareServer(new EmptyHttpServlet());
|
prepareServer(new EmptyHttpServlet());
|
||||||
|
|
||||||
// Shutdown the proxy
|
// Shutdown the proxy
|
||||||
|
@ -164,55 +181,10 @@ public class ProxyServletTest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testServerDown() throws Exception
|
|
||||||
{
|
|
||||||
prepareProxy(new ProxyServlet());
|
|
||||||
prepareServer(new EmptyHttpServlet());
|
|
||||||
|
|
||||||
// Shutdown the server
|
|
||||||
int serverPort = serverConnector.getLocalPort();
|
|
||||||
server.stop();
|
|
||||||
|
|
||||||
ContentResponse response = client.newRequest("localhost", serverPort)
|
|
||||||
.timeout(5, TimeUnit.SECONDS)
|
|
||||||
.send();
|
|
||||||
|
|
||||||
Assert.assertEquals(502, response.getStatus());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testServerException() throws Exception
|
|
||||||
{
|
|
||||||
((StdErrLog)Log.getLogger(ServletHandler.class)).setHideStacks(true);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
prepareProxy(new ProxyServlet());
|
|
||||||
prepareServer(new HttpServlet()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
|
||||||
{
|
|
||||||
throw new ServletException("Expected Test Exception");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
|
|
||||||
.timeout(5, TimeUnit.SECONDS)
|
|
||||||
.send();
|
|
||||||
|
|
||||||
Assert.assertEquals(500, response.getStatus());
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
((StdErrLog)Log.getLogger(ServletHandler.class)).setHideStacks(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testProxyWithoutContent() throws Exception
|
public void testProxyWithoutContent() throws Exception
|
||||||
{
|
{
|
||||||
prepareProxy(new ProxyServlet());
|
prepareProxy();
|
||||||
prepareServer(new HttpServlet()
|
prepareServer(new HttpServlet()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -234,7 +206,7 @@ public class ProxyServletTest
|
||||||
@Test
|
@Test
|
||||||
public void testProxyWithResponseContent() throws Exception
|
public void testProxyWithResponseContent() throws Exception
|
||||||
{
|
{
|
||||||
prepareProxy(new ProxyServlet());
|
prepareProxy();
|
||||||
|
|
||||||
HttpClient result = new HttpClient();
|
HttpClient result = new HttpClient();
|
||||||
result.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyConnector.getLocalPort()));
|
result.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyConnector.getLocalPort()));
|
||||||
|
@ -259,7 +231,7 @@ public class ProxyServletTest
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for ( int i = 0; i < 10; ++i )
|
for (int i = 0; i < 10; ++i)
|
||||||
{
|
{
|
||||||
// Request is for the target server
|
// Request is for the target server
|
||||||
responses[i] = result.newRequest("localhost", serverConnector.getLocalPort())
|
responses[i] = result.newRequest("localhost", serverConnector.getLocalPort())
|
||||||
|
@ -268,7 +240,7 @@ public class ProxyServletTest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
for ( int i = 0; i < 10; ++i )
|
for (int i = 0; i < 10; ++i)
|
||||||
{
|
{
|
||||||
Assert.assertEquals(200, responses[i].getStatus());
|
Assert.assertEquals(200, responses[i].getStatus());
|
||||||
Assert.assertTrue(responses[i].getHeaders().containsKey(PROXIED_HEADER));
|
Assert.assertTrue(responses[i].getHeaders().containsKey(PROXIED_HEADER));
|
||||||
|
@ -279,7 +251,7 @@ public class ProxyServletTest
|
||||||
@Test
|
@Test
|
||||||
public void testProxyWithRequestContentAndResponseContent() throws Exception
|
public void testProxyWithRequestContentAndResponseContent() throws Exception
|
||||||
{
|
{
|
||||||
prepareProxy(new ProxyServlet());
|
prepareProxy();
|
||||||
prepareServer(new HttpServlet()
|
prepareServer(new HttpServlet()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -307,7 +279,7 @@ public class ProxyServletTest
|
||||||
@Test
|
@Test
|
||||||
public void testProxyWithBigRequestContentIgnored() throws Exception
|
public void testProxyWithBigRequestContentIgnored() throws Exception
|
||||||
{
|
{
|
||||||
prepareProxy(new ProxyServlet());
|
prepareProxy();
|
||||||
prepareServer(new HttpServlet()
|
prepareServer(new HttpServlet()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -335,7 +307,7 @@ public class ProxyServletTest
|
||||||
final byte[] content = new byte[128 * 1024];
|
final byte[] content = new byte[128 * 1024];
|
||||||
new Random().nextBytes(content);
|
new Random().nextBytes(content);
|
||||||
|
|
||||||
prepareProxy(new ProxyServlet());
|
prepareProxy();
|
||||||
prepareServer(new HttpServlet()
|
prepareServer(new HttpServlet()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -359,7 +331,7 @@ public class ProxyServletTest
|
||||||
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
|
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
|
||||||
.method(HttpMethod.POST)
|
.method(HttpMethod.POST)
|
||||||
.content(new BytesContentProvider(content))
|
.content(new BytesContentProvider(content))
|
||||||
.timeout(5, TimeUnit.SECONDS)
|
.timeout(555, TimeUnit.SECONDS)
|
||||||
.send();
|
.send();
|
||||||
|
|
||||||
Assert.assertEquals(200, response.getStatus());
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
@ -370,7 +342,7 @@ public class ProxyServletTest
|
||||||
@Test
|
@Test
|
||||||
public void testProxyWithBigResponseContentWithSlowReader() throws Exception
|
public void testProxyWithBigResponseContentWithSlowReader() throws Exception
|
||||||
{
|
{
|
||||||
prepareProxy(new ProxyServlet());
|
prepareProxy();
|
||||||
|
|
||||||
// Create a 6 MiB file
|
// Create a 6 MiB file
|
||||||
final int length = 6 * 1024;
|
final int length = 6 * 1024;
|
||||||
|
@ -379,7 +351,7 @@ public class ProxyServletTest
|
||||||
final Path temp = Files.createTempFile(targetTestsDir, "test_", null);
|
final Path temp = Files.createTempFile(targetTestsDir, "test_", null);
|
||||||
byte[] kb = new byte[1024];
|
byte[] kb = new byte[1024];
|
||||||
new Random().nextBytes(kb);
|
new Random().nextBytes(kb);
|
||||||
try (OutputStream output = Files.newOutputStream(temp, CREATE))
|
try (OutputStream output = Files.newOutputStream(temp, StandardOpenOption.CREATE))
|
||||||
{
|
{
|
||||||
for (int i = 0; i < length; ++i)
|
for (int i = 0; i < length; ++i)
|
||||||
output.write(kb);
|
output.write(kb);
|
||||||
|
@ -431,7 +403,7 @@ public class ProxyServletTest
|
||||||
@Test
|
@Test
|
||||||
public void testProxyWithQueryString() throws Exception
|
public void testProxyWithQueryString() throws Exception
|
||||||
{
|
{
|
||||||
prepareProxy(new ProxyServlet());
|
prepareProxy();
|
||||||
String query = "a=1&b=%E2%82%AC";
|
String query = "a=1&b=%E2%82%AC";
|
||||||
prepareServer(new HttpServlet()
|
prepareServer(new HttpServlet()
|
||||||
{
|
{
|
||||||
|
@ -453,7 +425,7 @@ public class ProxyServletTest
|
||||||
@Test
|
@Test
|
||||||
public void testProxyLongPoll() throws Exception
|
public void testProxyLongPoll() throws Exception
|
||||||
{
|
{
|
||||||
prepareProxy(new ProxyServlet());
|
prepareProxy();
|
||||||
final long timeout = 1000;
|
final long timeout = 1000;
|
||||||
prepareServer(new HttpServlet()
|
prepareServer(new HttpServlet()
|
||||||
{
|
{
|
||||||
|
@ -500,73 +472,10 @@ public class ProxyServletTest
|
||||||
Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
|
Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Slow
|
|
||||||
@Test
|
|
||||||
public void testProxyRequestExpired() throws Exception
|
|
||||||
{
|
|
||||||
prepareProxy(new ProxyServlet());
|
|
||||||
final long timeout = 1000;
|
|
||||||
proxyServlet.setTimeout(timeout);
|
|
||||||
prepareServer(new HttpServlet()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
|
|
||||||
{
|
|
||||||
if (request.getHeader("Via") != null)
|
|
||||||
response.addHeader(PROXIED_HEADER, "true");
|
|
||||||
try
|
|
||||||
{
|
|
||||||
TimeUnit.MILLISECONDS.sleep(2 * timeout);
|
|
||||||
}
|
|
||||||
catch (InterruptedException x)
|
|
||||||
{
|
|
||||||
throw new ServletException(x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Response response = client.newRequest("localhost", serverConnector.getLocalPort())
|
|
||||||
.timeout(3 * timeout, TimeUnit.MILLISECONDS)
|
|
||||||
.send();
|
|
||||||
Assert.assertEquals(504, response.getStatus());
|
|
||||||
Assert.assertFalse(response.getHeaders().containsKey(PROXIED_HEADER));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Slow
|
|
||||||
@Test(expected = TimeoutException.class)
|
|
||||||
public void testClientRequestExpired() throws Exception
|
|
||||||
{
|
|
||||||
prepareProxy(new ProxyServlet());
|
|
||||||
final long timeout = 1000;
|
|
||||||
proxyServlet.setTimeout(3 * timeout);
|
|
||||||
prepareServer(new HttpServlet()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
|
|
||||||
{
|
|
||||||
if (request.getHeader("Via") != null)
|
|
||||||
response.addHeader(PROXIED_HEADER, "true");
|
|
||||||
try
|
|
||||||
{
|
|
||||||
TimeUnit.MILLISECONDS.sleep(2 * timeout);
|
|
||||||
}
|
|
||||||
catch (InterruptedException x)
|
|
||||||
{
|
|
||||||
throw new ServletException(x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
client.newRequest("localhost", serverConnector.getLocalPort())
|
|
||||||
.timeout(timeout, TimeUnit.MILLISECONDS)
|
|
||||||
.send();
|
|
||||||
Assert.fail();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testProxyXForwardedHostHeaderIsPresent() throws Exception
|
public void testProxyXForwardedHostHeaderIsPresent() throws Exception
|
||||||
{
|
{
|
||||||
prepareProxy(new ProxyServlet());
|
prepareProxy();
|
||||||
prepareServer(new HttpServlet()
|
prepareServer(new HttpServlet()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -587,7 +496,7 @@ public class ProxyServletTest
|
||||||
@Test
|
@Test
|
||||||
public void testProxyWhiteList() throws Exception
|
public void testProxyWhiteList() throws Exception
|
||||||
{
|
{
|
||||||
prepareProxy(new ProxyServlet());
|
prepareProxy();
|
||||||
prepareServer(new EmptyHttpServlet());
|
prepareServer(new EmptyHttpServlet());
|
||||||
int port = serverConnector.getLocalPort();
|
int port = serverConnector.getLocalPort();
|
||||||
proxyServlet.getWhiteListHosts().add("127.0.0.1:" + port);
|
proxyServlet.getWhiteListHosts().add("127.0.0.1:" + port);
|
||||||
|
@ -608,7 +517,7 @@ public class ProxyServletTest
|
||||||
@Test
|
@Test
|
||||||
public void testProxyBlackList() throws Exception
|
public void testProxyBlackList() throws Exception
|
||||||
{
|
{
|
||||||
prepareProxy(new ProxyServlet());
|
prepareProxy();
|
||||||
prepareServer(new EmptyHttpServlet());
|
prepareServer(new EmptyHttpServlet());
|
||||||
int port = serverConnector.getLocalPort();
|
int port = serverConnector.getLocalPort();
|
||||||
proxyServlet.getBlackListHosts().add("localhost:" + port);
|
proxyServlet.getBlackListHosts().add("localhost:" + port);
|
||||||
|
@ -629,7 +538,7 @@ public class ProxyServletTest
|
||||||
@Test
|
@Test
|
||||||
public void testClientExcludedHosts() throws Exception
|
public void testClientExcludedHosts() throws Exception
|
||||||
{
|
{
|
||||||
prepareProxy(new ProxyServlet());
|
prepareProxy();
|
||||||
prepareServer(new HttpServlet()
|
prepareServer(new HttpServlet()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -684,8 +593,11 @@ public class ProxyServletTest
|
||||||
});
|
});
|
||||||
|
|
||||||
String proxyTo = "http://localhost:" + serverConnector.getLocalPort();
|
String proxyTo = "http://localhost:" + serverConnector.getLocalPort();
|
||||||
ProxyServlet.Transparent proxyServlet = new ProxyServlet.Transparent(proxyTo, prefix);
|
proxyServlet = new ProxyServlet.Transparent();
|
||||||
prepareProxy(proxyServlet);
|
Map<String, String> params = new HashMap<>();
|
||||||
|
params.put("proxyTo", proxyTo);
|
||||||
|
params.put("prefix", prefix);
|
||||||
|
prepareProxy(params);
|
||||||
|
|
||||||
// Make the request to the proxy, it should transparently forward to the server
|
// Make the request to the proxy, it should transparently forward to the server
|
||||||
ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort())
|
ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort())
|
||||||
|
@ -723,8 +635,11 @@ public class ProxyServletTest
|
||||||
|
|
||||||
String proxyTo = "http://localhost:" + serverConnector.getLocalPort();
|
String proxyTo = "http://localhost:" + serverConnector.getLocalPort();
|
||||||
String prefix = "/proxy";
|
String prefix = "/proxy";
|
||||||
ProxyServlet.Transparent proxyServlet = new ProxyServlet.Transparent(proxyTo, prefix);
|
proxyServlet = new ProxyServlet.Transparent();
|
||||||
prepareProxy(proxyServlet);
|
Map<String, String> params = new HashMap<>();
|
||||||
|
params.put("proxyTo", proxyTo);
|
||||||
|
params.put("prefix", prefix);
|
||||||
|
prepareProxy(params);
|
||||||
|
|
||||||
// Make the request to the proxy, it should transparently forward to the server
|
// Make the request to the proxy, it should transparently forward to the server
|
||||||
ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort())
|
ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort())
|
||||||
|
@ -735,6 +650,36 @@ public class ProxyServletTest
|
||||||
Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
|
Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTransparentProxyWithoutPrefix() throws Exception
|
||||||
|
{
|
||||||
|
final String target = "/test";
|
||||||
|
prepareServer(new HttpServlet()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||||
|
{
|
||||||
|
if (req.getHeader("Via") != null)
|
||||||
|
resp.addHeader(PROXIED_HEADER, "true");
|
||||||
|
resp.setStatus(target.equals(req.getRequestURI()) ? 200 : 404);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final String proxyTo = "http://localhost:" + serverConnector.getLocalPort();
|
||||||
|
proxyServlet = new ProxyServlet.Transparent();
|
||||||
|
Map<String, String> initParams = new HashMap<>();
|
||||||
|
initParams.put("proxyTo", proxyTo);
|
||||||
|
prepareProxy(initParams);
|
||||||
|
|
||||||
|
// Make the request to the proxy, it should transparently forward to the server
|
||||||
|
ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort())
|
||||||
|
.path(target)
|
||||||
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
.send();
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCachingProxy() throws Exception
|
public void testCachingProxy() throws Exception
|
||||||
{
|
{
|
||||||
|
@ -754,7 +699,7 @@ public class ProxyServletTest
|
||||||
// it is only used for this test and to verify that ProxyServlet can be
|
// it is only used for this test and to verify that ProxyServlet can be
|
||||||
// subclassed enough to write your own caching servlet
|
// subclassed enough to write your own caching servlet
|
||||||
final String cacheHeader = "X-Cached";
|
final String cacheHeader = "X-Cached";
|
||||||
ProxyServlet proxyServlet = new ProxyServlet()
|
proxyServlet = new ProxyServlet()
|
||||||
{
|
{
|
||||||
private Map<String, ContentResponse> cache = new HashMap<>();
|
private Map<String, ContentResponse> cache = new HashMap<>();
|
||||||
private Map<String, ByteArrayOutputStream> temp = new HashMap<>();
|
private Map<String, ByteArrayOutputStream> temp = new HashMap<>();
|
||||||
|
@ -777,7 +722,7 @@ public class ProxyServletTest
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length) throws IOException
|
protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length, Callback callback)
|
||||||
{
|
{
|
||||||
// Accumulate the response content
|
// Accumulate the response content
|
||||||
ByteArrayOutputStream baos = temp.get(request.getRequestURI());
|
ByteArrayOutputStream baos = temp.get(request.getRequestURI());
|
||||||
|
@ -787,7 +732,7 @@ public class ProxyServletTest
|
||||||
temp.put(request.getRequestURI(), baos);
|
temp.put(request.getRequestURI(), baos);
|
||||||
}
|
}
|
||||||
baos.write(buffer, offset, length);
|
baos.write(buffer, offset, length);
|
||||||
super.onResponseContent(request, response, proxyResponse, buffer, offset, length);
|
super.onResponseContent(request, response, proxyResponse, buffer, offset, length, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -799,7 +744,7 @@ public class ProxyServletTest
|
||||||
super.onResponseSuccess(request, response, proxyResponse);
|
super.onResponseSuccess(request, response, proxyResponse);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
prepareProxy(proxyServlet);
|
prepareProxy();
|
||||||
|
|
||||||
// First request
|
// First request
|
||||||
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
|
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
|
||||||
|
@ -821,7 +766,7 @@ public class ProxyServletTest
|
||||||
@Test
|
@Test
|
||||||
public void testRedirectsAreProxied() throws Exception
|
public void testRedirectsAreProxied() throws Exception
|
||||||
{
|
{
|
||||||
prepareProxy(new ProxyServlet());
|
prepareProxy();
|
||||||
prepareServer(new HttpServlet()
|
prepareServer(new HttpServlet()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -846,7 +791,7 @@ public class ProxyServletTest
|
||||||
public void testGZIPContentIsProxied() throws Exception
|
public void testGZIPContentIsProxied() throws Exception
|
||||||
{
|
{
|
||||||
final byte[] content = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
|
final byte[] content = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
|
||||||
prepareProxy(new ProxyServlet());
|
prepareProxy();
|
||||||
prepareServer(new HttpServlet()
|
prepareServer(new HttpServlet()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -871,21 +816,23 @@ public class ProxyServletTest
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = TimeoutException.class)
|
@Test(expected = TimeoutException.class)
|
||||||
public void shouldHandleWrongContentLength() throws Exception
|
public void testWrongContentLength() throws Exception
|
||||||
|
{
|
||||||
|
prepareProxy();
|
||||||
|
prepareServer(new HttpServlet()
|
||||||
{
|
{
|
||||||
prepareProxy(new ProxyServlet());
|
|
||||||
prepareServer(new HttpServlet() {
|
|
||||||
@Override
|
@Override
|
||||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||||
|
{
|
||||||
byte[] message = "tooshort".getBytes("ascii");
|
byte[] message = "tooshort".getBytes("ascii");
|
||||||
resp.setContentType("text/plain;charset=ascii");
|
resp.setContentType("text/plain;charset=ascii");
|
||||||
resp.setHeader("Content-Length", Long.toString(message.length+1));
|
resp.setHeader("Content-Length", Long.toString(message.length + 1));
|
||||||
resp.getOutputStream().write(message);
|
resp.getOutputStream().write(message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
client.newRequest("localhost", serverConnector.getLocalPort())
|
client.newRequest("localhost", serverConnector.getLocalPort())
|
||||||
.timeout(5, TimeUnit.SECONDS)
|
.timeout(1, TimeUnit.SECONDS)
|
||||||
.send();
|
.send();
|
||||||
|
|
||||||
Assert.fail();
|
Assert.fail();
|
||||||
|
@ -895,7 +842,7 @@ public class ProxyServletTest
|
||||||
public void testCookiesFromDifferentClientsAreNotMixed() throws Exception
|
public void testCookiesFromDifferentClientsAreNotMixed() throws Exception
|
||||||
{
|
{
|
||||||
final String name = "biscuit";
|
final String name = "biscuit";
|
||||||
prepareProxy(new ProxyServlet());
|
prepareProxy();
|
||||||
prepareServer(new HttpServlet()
|
prepareServer(new HttpServlet()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -953,17 +900,4 @@ public class ProxyServletTest
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: test proxy authentication
|
// TODO: test proxy authentication
|
||||||
|
|
||||||
private static class EmptyHttpServlet extends HttpServlet
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||||
#org.eclipse.jetty.LEVEL=DEBUG
|
#org.eclipse.jetty.LEVEL=DEBUG
|
||||||
#org.eclipse.jetty.client.LEVEL=DEBUG
|
#org.eclipse.jetty.client.LEVEL=DEBUG
|
||||||
|
#org.eclipse.jetty.proxy.LEVEL=DEBUG
|
||||||
|
|
|
@ -45,6 +45,7 @@ import org.eclipse.jetty.server.HttpConfiguration;
|
||||||
import org.eclipse.jetty.server.Request;
|
import org.eclipse.jetty.server.Request;
|
||||||
import org.eclipse.jetty.server.Response;
|
import org.eclipse.jetty.server.Response;
|
||||||
import org.eclipse.jetty.server.UserIdentity;
|
import org.eclipse.jetty.server.UserIdentity;
|
||||||
|
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
import org.eclipse.jetty.util.security.Constraint;
|
import org.eclipse.jetty.util.security.Constraint;
|
||||||
|
@ -820,7 +821,9 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
|
||||||
if (paths != null && !paths.isEmpty())
|
if (paths != null && !paths.isEmpty())
|
||||||
{
|
{
|
||||||
for (String p:paths)
|
for (String p:paths)
|
||||||
LOG.warn("Path with uncovered http methods: {}",p);
|
LOG.warn("{} has uncovered http methods for path: {}",ContextHandler.getCurrentContext(), p);
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug(new Throwable());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -133,6 +133,7 @@ public class HashLoginService extends MappedLoginService implements UserListener
|
||||||
/**
|
/**
|
||||||
* @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
|
* @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
protected void doStart() throws Exception
|
protected void doStart() throws Exception
|
||||||
{
|
{
|
||||||
super.doStart();
|
super.doStart();
|
||||||
|
@ -154,6 +155,7 @@ public class HashLoginService extends MappedLoginService implements UserListener
|
||||||
/**
|
/**
|
||||||
* @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
|
* @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
protected void doStop() throws Exception
|
protected void doStop() throws Exception
|
||||||
{
|
{
|
||||||
super.doStop();
|
super.doStop();
|
||||||
|
@ -163,6 +165,7 @@ public class HashLoginService extends MappedLoginService implements UserListener
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
@Override
|
||||||
public void update(String userName, Credential credential, String[] roleArray)
|
public void update(String userName, Credential credential, String[] roleArray)
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
|
@ -171,6 +174,7 @@ public class HashLoginService extends MappedLoginService implements UserListener
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
@Override
|
||||||
public void remove(String userName)
|
public void remove(String userName)
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
|
|
|
@ -43,6 +43,7 @@ import org.eclipse.jetty.server.handler.ContextHandler;
|
||||||
import org.eclipse.jetty.server.handler.ContextHandler.Context;
|
import org.eclipse.jetty.server.handler.ContextHandler.Context;
|
||||||
import org.eclipse.jetty.server.handler.HandlerWrapper;
|
import org.eclipse.jetty.server.handler.HandlerWrapper;
|
||||||
import org.eclipse.jetty.server.session.AbstractSession;
|
import org.eclipse.jetty.server.session.AbstractSession;
|
||||||
|
import org.eclipse.jetty.util.component.LifeCycle;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
|
||||||
|
@ -74,8 +75,6 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
|
||||||
private LoginService _loginService;
|
private LoginService _loginService;
|
||||||
private IdentityService _identityService;
|
private IdentityService _identityService;
|
||||||
private boolean _renewSession=true;
|
private boolean _renewSession=true;
|
||||||
private boolean _discoveredIdentityService = false;
|
|
||||||
private boolean _discoveredLoginService = false;
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
protected SecurityHandler()
|
protected SecurityHandler()
|
||||||
|
@ -266,20 +265,24 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
protected LoginService findLoginService()
|
protected LoginService findLoginService() throws Exception
|
||||||
{
|
{
|
||||||
Collection<LoginService> list = getServer().getBeans(LoginService.class);
|
Collection<LoginService> list = getServer().getBeans(LoginService.class);
|
||||||
|
LoginService service = null;
|
||||||
String realm=getRealmName();
|
String realm=getRealmName();
|
||||||
if (realm!=null)
|
if (realm!=null)
|
||||||
{
|
{
|
||||||
for (LoginService service : list)
|
for (LoginService s : list)
|
||||||
if (service.getName()!=null && service.getName().equals(realm))
|
if (s.getName()!=null && s.getName().equals(realm))
|
||||||
return service;
|
{
|
||||||
|
service=s;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (list.size()==1)
|
else if (list.size()==1)
|
||||||
return list.iterator().next();
|
service = list.iterator().next();
|
||||||
return null;
|
|
||||||
|
return service;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
@ -342,7 +345,8 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
|
||||||
if (_loginService==null)
|
if (_loginService==null)
|
||||||
{
|
{
|
||||||
setLoginService(findLoginService());
|
setLoginService(findLoginService());
|
||||||
_discoveredLoginService = true;
|
if (_loginService!=null)
|
||||||
|
unmanage(_loginService);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_identityService==null)
|
if (_identityService==null)
|
||||||
|
@ -353,10 +357,16 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
|
||||||
if (_identityService==null)
|
if (_identityService==null)
|
||||||
setIdentityService(findIdentityService());
|
setIdentityService(findIdentityService());
|
||||||
|
|
||||||
if (_identityService==null && _realmName!=null)
|
if (_identityService==null)
|
||||||
|
{
|
||||||
|
if (_realmName!=null)
|
||||||
|
{
|
||||||
setIdentityService(new DefaultIdentityService());
|
setIdentityService(new DefaultIdentityService());
|
||||||
|
manage(_identityService);
|
||||||
_discoveredIdentityService = true;
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
unmanage(_identityService);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_loginService!=null)
|
if (_loginService!=null)
|
||||||
|
@ -387,17 +397,16 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
|
||||||
protected void doStop() throws Exception
|
protected void doStop() throws Exception
|
||||||
{
|
{
|
||||||
//if we discovered the services (rather than had them explicitly configured), remove them.
|
//if we discovered the services (rather than had them explicitly configured), remove them.
|
||||||
if (_discoveredIdentityService)
|
if (!isManaged(_identityService))
|
||||||
{
|
{
|
||||||
removeBean(_identityService);
|
removeBean(_identityService);
|
||||||
_identityService = null;
|
_identityService = null;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_discoveredLoginService)
|
if (!isManaged(_loginService))
|
||||||
{
|
{
|
||||||
removeBean(_loginService);
|
removeBean(_loginService);
|
||||||
_loginService = null;
|
_loginService=null;
|
||||||
}
|
}
|
||||||
|
|
||||||
super.doStop();
|
super.doStop();
|
||||||
|
@ -427,6 +436,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
|
||||||
/**
|
/**
|
||||||
* @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
|
* @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public boolean isSessionRenewedOnAuthentication()
|
public boolean isSessionRenewedOnAuthentication()
|
||||||
{
|
{
|
||||||
return _renewSession;
|
return _renewSession;
|
||||||
|
@ -473,7 +483,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
|
||||||
{
|
{
|
||||||
if (!baseRequest.isHandled())
|
if (!baseRequest.isHandled())
|
||||||
{
|
{
|
||||||
response.sendError(Response.SC_FORBIDDEN);
|
response.sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||||
baseRequest.setHandled(true);
|
baseRequest.setHandled(true);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -488,7 +498,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
|
||||||
LOG.warn("No authenticator for: "+roleInfo);
|
LOG.warn("No authenticator for: "+roleInfo);
|
||||||
if (!baseRequest.isHandled())
|
if (!baseRequest.isHandled())
|
||||||
{
|
{
|
||||||
response.sendError(Response.SC_FORBIDDEN);
|
response.sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||||
baseRequest.setHandled(true);
|
baseRequest.setHandled(true);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -524,7 +534,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
|
||||||
boolean authorized=checkWebResourcePermissions(pathInContext, baseRequest, base_response, roleInfo, userAuth.getUserIdentity());
|
boolean authorized=checkWebResourcePermissions(pathInContext, baseRequest, base_response, roleInfo, userAuth.getUserIdentity());
|
||||||
if (!authorized)
|
if (!authorized)
|
||||||
{
|
{
|
||||||
response.sendError(Response.SC_FORBIDDEN, "!role");
|
response.sendError(HttpServletResponse.SC_FORBIDDEN, "!role");
|
||||||
baseRequest.setHandled(true);
|
baseRequest.setHandled(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -574,7 +584,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
|
||||||
{
|
{
|
||||||
// jaspi 3.8.3 send HTTP 500 internal server error, with message
|
// jaspi 3.8.3 send HTTP 500 internal server error, with message
|
||||||
// from AuthException
|
// from AuthException
|
||||||
response.sendError(Response.SC_INTERNAL_SERVER_ERROR, e.getMessage());
|
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
@ -634,6 +644,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
public class NotChecked implements Principal
|
public class NotChecked implements Principal
|
||||||
{
|
{
|
||||||
|
@Override
|
||||||
public String getName()
|
public String getName()
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
|
@ -656,6 +667,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
public static final Principal __NO_USER = new Principal()
|
public static final Principal __NO_USER = new Principal()
|
||||||
{
|
{
|
||||||
|
@Override
|
||||||
public String getName()
|
public String getName()
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
|
@ -680,6 +692,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
|
||||||
*/
|
*/
|
||||||
public static final Principal __NOBODY = new Principal()
|
public static final Principal __NOBODY = new Principal()
|
||||||
{
|
{
|
||||||
|
@Override
|
||||||
public String getName()
|
public String getName()
|
||||||
{
|
{
|
||||||
return "Nobody";
|
return "Nobody";
|
||||||
|
|
|
@ -73,6 +73,11 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable, H
|
||||||
private static final Logger LOG = Log.getLogger(HttpChannel.class);
|
private static final Logger LOG = Log.getLogger(HttpChannel.class);
|
||||||
private static final ThreadLocal<HttpChannel<?>> __currentChannel = new ThreadLocal<>();
|
private static final ThreadLocal<HttpChannel<?>> __currentChannel = new ThreadLocal<>();
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/** Get the current channel that this thread is dispatched to.
|
||||||
|
* @see Request#getAttribute(String) for a more general way to access the HttpChannel
|
||||||
|
* @return the current HttpChannel or null
|
||||||
|
*/
|
||||||
public static HttpChannel<?> getCurrentHttpChannel()
|
public static HttpChannel<?> getCurrentHttpChannel()
|
||||||
{
|
{
|
||||||
return __currentChannel.get();
|
return __currentChannel.get();
|
||||||
|
|
|
@ -35,21 +35,6 @@ import org.eclipse.jetty.util.thread.Scheduler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of AsyncContext interface that holds the state of request-response cycle.
|
* Implementation of AsyncContext interface that holds the state of request-response cycle.
|
||||||
*
|
|
||||||
* <table>
|
|
||||||
* <tr><th>STATE</th><th colspan=6>ACTION</th></tr>
|
|
||||||
* <tr><th></th> <th>handling()</th> <th>startAsync()</th><th>unhandle()</th> <th>dispatch()</th> <th>complete()</th> <th>completed()</th></tr>
|
|
||||||
* <tr><th align=right>IDLE:</th> <td>DISPATCHED</td> <td></td> <td></td> <td></td> <td>COMPLETECALLED??</td><td></td></tr>
|
|
||||||
* <tr><th align=right>DISPATCHED:</th> <td></td> <td>ASYNCSTARTED</td><td>COMPLETING</td> <td></td> <td></td> <td></td></tr>
|
|
||||||
* <tr><th align=right>ASYNCSTARTED:</th> <td></td> <td></td> <td>ASYNCWAIT</td> <td>REDISPATCHING</td><td>COMPLETECALLED</td> <td></td></tr>
|
|
||||||
* <tr><th align=right>REDISPATCHING:</th> <td></td> <td></td> <td>REDISPATCHED</td><td></td> <td></td> <td></td></tr>
|
|
||||||
* <tr><th align=right>ASYNCWAIT:</th> <td></td> <td></td> <td></td> <td>REDISPATCH</td> <td>COMPLETECALLED</td> <td></td></tr>
|
|
||||||
* <tr><th align=right>REDISPATCH:</th> <td>REDISPATCHED</td><td></td> <td></td> <td></td> <td></td> <td></td></tr>
|
|
||||||
* <tr><th align=right>REDISPATCHED:</th> <td></td> <td>ASYNCSTARTED</td><td>COMPLETING</td> <td></td> <td></td> <td></td></tr>
|
|
||||||
* <tr><th align=right>COMPLETECALLED:</th><td>COMPLETING</td> <td></td> <td>COMPLETING</td> <td></td> <td></td> <td></td></tr>
|
|
||||||
* <tr><th align=right>COMPLETING:</th> <td>COMPLETING</td> <td></td> <td></td> <td></td> <td></td> <td>COMPLETED</td></tr>
|
|
||||||
* <tr><th align=right>COMPLETED:</th> <td></td> <td></td> <td></td> <td></td> <td></td> <td></td></tr>
|
|
||||||
* </table>
|
|
||||||
*/
|
*/
|
||||||
public class HttpChannelState
|
public class HttpChannelState
|
||||||
{
|
{
|
||||||
|
@ -57,16 +42,22 @@ public class HttpChannelState
|
||||||
|
|
||||||
private final static long DEFAULT_TIMEOUT=30000L;
|
private final static long DEFAULT_TIMEOUT=30000L;
|
||||||
|
|
||||||
|
/** The dispatched state of the HttpChannel, used to control the overall livecycle
|
||||||
|
*/
|
||||||
public enum State
|
public enum State
|
||||||
{
|
{
|
||||||
IDLE, // Idle request
|
IDLE, // Idle request
|
||||||
DISPATCHED, // Request dispatched to filter/servlet
|
DISPATCHED, // Request dispatched to filter/servlet
|
||||||
ASYNCWAIT, // Suspended and parked
|
ASYNC_WAIT, // Suspended and parked
|
||||||
ASYNCIO, // Has been dispatched for async IO
|
ASYNC_WOKEN, // A thread has been dispatch to handle from ASYNCWAIT
|
||||||
|
ASYNC_IO, // Has been dispatched for async IO
|
||||||
COMPLETING, // Request is completable
|
COMPLETING, // Request is completable
|
||||||
COMPLETED // Request is complete
|
COMPLETED // Request is complete
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The actions to take as the channel moves from state to state.
|
||||||
|
*/
|
||||||
public enum Action
|
public enum Action
|
||||||
{
|
{
|
||||||
REQUEST_DISPATCH, // handle a normal request dispatch
|
REQUEST_DISPATCH, // handle a normal request dispatch
|
||||||
|
@ -78,6 +69,11 @@ public class HttpChannelState
|
||||||
COMPLETE // Complete the channel
|
COMPLETE // Complete the channel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The state of the servlet async API. This can lead or follow the
|
||||||
|
* channel dispatch state and also includes reasons such as expired,
|
||||||
|
* dispatched or completed.
|
||||||
|
*/
|
||||||
public enum Async
|
public enum Async
|
||||||
{
|
{
|
||||||
STARTED,
|
STARTED,
|
||||||
|
@ -188,16 +184,16 @@ public class HttpChannelState
|
||||||
case COMPLETED:
|
case COMPLETED:
|
||||||
return Action.WAIT;
|
return Action.WAIT;
|
||||||
|
|
||||||
case ASYNCWAIT:
|
case ASYNC_WOKEN:
|
||||||
if (_asyncRead)
|
if (_asyncRead)
|
||||||
{
|
{
|
||||||
_state=State.ASYNCIO;
|
_state=State.ASYNC_IO;
|
||||||
_asyncRead=false;
|
_asyncRead=false;
|
||||||
return Action.READ_CALLBACK;
|
return Action.READ_CALLBACK;
|
||||||
}
|
}
|
||||||
if (_asyncWrite)
|
if (_asyncWrite)
|
||||||
{
|
{
|
||||||
_state=State.ASYNCIO;
|
_state=State.ASYNC_IO;
|
||||||
_asyncWrite=false;
|
_asyncWrite=false;
|
||||||
return Action.WRITE_CALLBACK;
|
return Action.WRITE_CALLBACK;
|
||||||
}
|
}
|
||||||
|
@ -221,6 +217,7 @@ public class HttpChannelState
|
||||||
_async=null;
|
_async=null;
|
||||||
return Action.ASYNC_EXPIRED;
|
return Action.ASYNC_EXPIRED;
|
||||||
case STARTED:
|
case STARTED:
|
||||||
|
// TODO
|
||||||
if (DEBUG)
|
if (DEBUG)
|
||||||
LOG.debug("TODO Fix this double dispatch",new IllegalStateException(this
|
LOG.debug("TODO Fix this double dispatch",new IllegalStateException(this
|
||||||
.getStatusString()));
|
.getStatusString()));
|
||||||
|
@ -293,7 +290,7 @@ public class HttpChannelState
|
||||||
switch(_state)
|
switch(_state)
|
||||||
{
|
{
|
||||||
case DISPATCHED:
|
case DISPATCHED:
|
||||||
case ASYNCIO:
|
case ASYNC_IO:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException(this.getStatusString());
|
throw new IllegalStateException(this.getStatusString());
|
||||||
|
@ -301,7 +298,7 @@ public class HttpChannelState
|
||||||
|
|
||||||
if (_asyncRead)
|
if (_asyncRead)
|
||||||
{
|
{
|
||||||
_state=State.ASYNCIO;
|
_state=State.ASYNC_IO;
|
||||||
_asyncRead=false;
|
_asyncRead=false;
|
||||||
return Action.READ_CALLBACK;
|
return Action.READ_CALLBACK;
|
||||||
}
|
}
|
||||||
|
@ -309,7 +306,7 @@ public class HttpChannelState
|
||||||
if (_asyncWrite)
|
if (_asyncWrite)
|
||||||
{
|
{
|
||||||
_asyncWrite=false;
|
_asyncWrite=false;
|
||||||
_state=State.ASYNCIO;
|
_state=State.ASYNC_IO;
|
||||||
return Action.WRITE_CALLBACK;
|
return Action.WRITE_CALLBACK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -333,7 +330,7 @@ public class HttpChannelState
|
||||||
case EXPIRING:
|
case EXPIRING:
|
||||||
case STARTED:
|
case STARTED:
|
||||||
scheduleTimeout();
|
scheduleTimeout();
|
||||||
_state=State.ASYNCWAIT;
|
_state=State.ASYNC_WAIT;
|
||||||
return Action.WAIT;
|
return Action.WAIT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -360,11 +357,19 @@ public class HttpChannelState
|
||||||
switch(_state)
|
switch(_state)
|
||||||
{
|
{
|
||||||
case DISPATCHED:
|
case DISPATCHED:
|
||||||
case ASYNCIO:
|
case ASYNC_IO:
|
||||||
|
dispatch=false;
|
||||||
|
break;
|
||||||
|
case ASYNC_WAIT:
|
||||||
|
_state=State.ASYNC_WOKEN;
|
||||||
|
dispatch=true;
|
||||||
|
break;
|
||||||
|
case ASYNC_WOKEN:
|
||||||
dispatch=false;
|
dispatch=false;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
dispatch=true;
|
LOG.warn("async dispatched when complete {}",this);
|
||||||
|
dispatch=false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -411,10 +416,13 @@ public class HttpChannelState
|
||||||
if (_async==Async.EXPIRING)
|
if (_async==Async.EXPIRING)
|
||||||
{
|
{
|
||||||
_async=Async.EXPIRED;
|
_async=Async.EXPIRED;
|
||||||
if (_state==State.ASYNCWAIT)
|
if (_state==State.ASYNC_WAIT)
|
||||||
|
{
|
||||||
|
_state=State.ASYNC_WOKEN;
|
||||||
dispatch=true;
|
dispatch=true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (dispatch)
|
if (dispatch)
|
||||||
scheduleDispatch();
|
scheduleDispatch();
|
||||||
|
@ -423,13 +431,17 @@ public class HttpChannelState
|
||||||
public void complete()
|
public void complete()
|
||||||
{
|
{
|
||||||
// just like resume, except don't set _dispatched=true;
|
// just like resume, except don't set _dispatched=true;
|
||||||
boolean handle;
|
boolean handle=false;
|
||||||
synchronized (this)
|
synchronized (this)
|
||||||
{
|
{
|
||||||
if (_async!=Async.STARTED && _async!=Async.EXPIRING)
|
if (_async!=Async.STARTED && _async!=Async.EXPIRING)
|
||||||
throw new IllegalStateException(this.getStatusString());
|
throw new IllegalStateException(this.getStatusString());
|
||||||
_async=Async.COMPLETE;
|
_async=Async.COMPLETE;
|
||||||
handle=_state==State.ASYNCWAIT;
|
if (_state==State.ASYNC_WAIT)
|
||||||
|
{
|
||||||
|
handle=true;
|
||||||
|
_state=State.ASYNC_WOKEN;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelTimeout();
|
cancelTimeout();
|
||||||
|
@ -511,7 +523,7 @@ public class HttpChannelState
|
||||||
switch(_state)
|
switch(_state)
|
||||||
{
|
{
|
||||||
case DISPATCHED:
|
case DISPATCHED:
|
||||||
case ASYNCIO:
|
case ASYNC_IO:
|
||||||
throw new IllegalStateException(getStatusString());
|
throw new IllegalStateException(getStatusString());
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -571,7 +583,7 @@ public class HttpChannelState
|
||||||
{
|
{
|
||||||
synchronized(this)
|
synchronized(this)
|
||||||
{
|
{
|
||||||
return _state==State.ASYNCWAIT || _state==State.DISPATCHED && _async==Async.STARTED;
|
return _state==State.ASYNC_WAIT || _state==State.DISPATCHED && _async==Async.STARTED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -665,12 +677,16 @@ public class HttpChannelState
|
||||||
|
|
||||||
public void onReadPossible()
|
public void onReadPossible()
|
||||||
{
|
{
|
||||||
boolean handle;
|
boolean handle=false;
|
||||||
|
|
||||||
synchronized (this)
|
synchronized (this)
|
||||||
{
|
{
|
||||||
_asyncRead=true;
|
_asyncRead=true;
|
||||||
handle=_state==State.ASYNCWAIT;
|
if (_state==State.ASYNC_WAIT)
|
||||||
|
{
|
||||||
|
_state=State.ASYNC_WOKEN;
|
||||||
|
handle=true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handle)
|
if (handle)
|
||||||
|
@ -679,12 +695,16 @@ public class HttpChannelState
|
||||||
|
|
||||||
public void onWritePossible()
|
public void onWritePossible()
|
||||||
{
|
{
|
||||||
boolean handle;
|
boolean handle=false;
|
||||||
|
|
||||||
synchronized (this)
|
synchronized (this)
|
||||||
{
|
{
|
||||||
_asyncWrite=true;
|
_asyncWrite=true;
|
||||||
handle=_state==State.ASYNCWAIT;
|
if (_state==State.ASYNC_WAIT)
|
||||||
|
{
|
||||||
|
_state=State.ASYNC_WOKEN;
|
||||||
|
handle=true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handle)
|
if (handle)
|
||||||
|
|
|
@ -52,7 +52,7 @@ public class HttpConfiguration
|
||||||
private String _secureScheme = HttpScheme.HTTPS.asString();
|
private String _secureScheme = HttpScheme.HTTPS.asString();
|
||||||
private boolean _sendServerVersion = true; //send Server: header
|
private boolean _sendServerVersion = true; //send Server: header
|
||||||
private boolean _sendXPoweredBy = false; //send X-Powered-By: header
|
private boolean _sendXPoweredBy = false; //send X-Powered-By: header
|
||||||
private boolean _sendDateHeader = false; //send Date: header
|
private boolean _sendDateHeader = true; //send Date: header
|
||||||
|
|
||||||
public interface Customizer
|
public interface Customizer
|
||||||
{
|
{
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.server;
|
package org.eclipse.jetty.server;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.concurrent.RejectedExecutionException;
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
|
||||||
|
@ -62,6 +63,13 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
||||||
private volatile ByteBuffer _chunk = null;
|
private volatile ByteBuffer _chunk = null;
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/** Get the current connection that this thread is dispatched to.
|
||||||
|
* Note that a thread may be processing a request asynchronously and
|
||||||
|
* thus not be dispatched to the connection.
|
||||||
|
* @see Request#getAttribute(String) for a more general way to access the HttpConnection
|
||||||
|
* @return the current HttpConnection or null
|
||||||
|
*/
|
||||||
public static HttpConnection getCurrentConnection()
|
public static HttpConnection getCurrentConnection()
|
||||||
{
|
{
|
||||||
return __currentConnection.get();
|
return __currentConnection.get();
|
||||||
|
@ -210,6 +218,8 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Get a buffer
|
// Get a buffer
|
||||||
|
// We are not in a race here for the request buffer as we have not yet received a request,
|
||||||
|
// so there are not an possible legal threads calling #parseContent or #completed.
|
||||||
_requestBuffer = getRequestBuffer();
|
_requestBuffer = getRequestBuffer();
|
||||||
|
|
||||||
// fill
|
// fill
|
||||||
|
@ -229,11 +239,15 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
||||||
// The parser returned true, which indicates the channel is ready to handle a request.
|
// The parser returned true, which indicates the channel is ready to handle a request.
|
||||||
// Call the channel and this will either handle the request/response to completion OR,
|
// Call the channel and this will either handle the request/response to completion OR,
|
||||||
// if the request suspends, the request/response will be incomplete so the outer loop will exit.
|
// if the request suspends, the request/response will be incomplete so the outer loop will exit.
|
||||||
|
// Not that onFillable no longer manipulates the request buffer from this point and that is
|
||||||
|
// left to threads calling #completed or #parseContent (which may be this thread inside handle())
|
||||||
suspended = !_channel.handle();
|
suspended = !_channel.handle();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// We parsed what we could, recycle the request buffer
|
// We parsed what we could, recycle the request buffer
|
||||||
|
// We are not in a race here for the request buffer as we have not yet received a request,
|
||||||
|
// so there are not an possible legal threads calling #parseContent or #completed.
|
||||||
releaseRequestBuffer();
|
releaseRequestBuffer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -260,6 +274,135 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/** Fill and parse data looking for content
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
protected void parseContent() throws IOException
|
||||||
|
{
|
||||||
|
// Not in a race here for the request buffer with #onFillable because an async consumer of
|
||||||
|
// content would only be started after onFillable has given up control.
|
||||||
|
// In a little bit of a race with #completed, but then not sure if it is legal to be doing
|
||||||
|
// async calls to IO and have a completed call at the same time.
|
||||||
|
ByteBuffer requestBuffer = getRequestBuffer();
|
||||||
|
|
||||||
|
while (_parser.inContentState())
|
||||||
|
{
|
||||||
|
// Can the parser progress (even with an empty buffer)
|
||||||
|
boolean parsed = _parser.parseNext(requestBuffer==null?BufferUtil.EMPTY_BUFFER:requestBuffer);
|
||||||
|
|
||||||
|
// No, we can we try reading some content?
|
||||||
|
if (BufferUtil.isEmpty(requestBuffer) && getEndPoint().isInputShutdown())
|
||||||
|
{
|
||||||
|
_parser.atEOF();
|
||||||
|
if (parsed)
|
||||||
|
break;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsed)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// OK lets read some data
|
||||||
|
int filled=getEndPoint().fill(requestBuffer);
|
||||||
|
if (LOG.isDebugEnabled()) // Avoid boxing of variable 'filled'
|
||||||
|
LOG.debug("{} filled {}",this,filled);
|
||||||
|
if (filled<=0)
|
||||||
|
{
|
||||||
|
if (filled<0)
|
||||||
|
{
|
||||||
|
_parser.atEOF();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void completed()
|
||||||
|
{
|
||||||
|
// Handle connection upgrades
|
||||||
|
if (_channel.getResponse().getStatus() == HttpStatus.SWITCHING_PROTOCOLS_101)
|
||||||
|
{
|
||||||
|
Connection connection = (Connection)_channel.getRequest().getAttribute(UPGRADE_CONNECTION_ATTRIBUTE);
|
||||||
|
if (connection != null)
|
||||||
|
{
|
||||||
|
LOG.debug("Upgrade from {} to {}", this, connection);
|
||||||
|
onClose();
|
||||||
|
getEndPoint().setConnection(connection);
|
||||||
|
connection.onOpen();
|
||||||
|
_channel.reset();
|
||||||
|
_parser.reset();
|
||||||
|
_generator.reset();
|
||||||
|
releaseRequestBuffer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finish consuming the request
|
||||||
|
// If we are still expecting
|
||||||
|
if (_channel.isExpecting100Continue())
|
||||||
|
// close to seek EOF
|
||||||
|
_parser.close();
|
||||||
|
else if (_parser.inContentState() && _generator.isPersistent())
|
||||||
|
// Complete reading the request
|
||||||
|
_channel.getRequest().getHttpInput().consumeAll();
|
||||||
|
|
||||||
|
// Reset the channel, parsers and generator
|
||||||
|
_channel.reset();
|
||||||
|
if (_generator.isPersistent() && !_parser.isClosed())
|
||||||
|
_parser.reset();
|
||||||
|
else
|
||||||
|
_parser.close();
|
||||||
|
|
||||||
|
// Not in a race here with onFillable, because it has given up control before calling handle.
|
||||||
|
// in a slight race with #completed, but not sure what to do with that anyway.
|
||||||
|
releaseRequestBuffer();
|
||||||
|
if (_chunk!=null)
|
||||||
|
_bufferPool.release(_chunk);
|
||||||
|
_chunk=null;
|
||||||
|
_generator.reset();
|
||||||
|
|
||||||
|
// if we are not called from the onfillable thread, schedule completion
|
||||||
|
if (getCurrentConnection()!=this)
|
||||||
|
{
|
||||||
|
// If we are looking for the next request
|
||||||
|
if (_parser.isStart())
|
||||||
|
{
|
||||||
|
// if the buffer is empty
|
||||||
|
if (BufferUtil.isEmpty(_requestBuffer))
|
||||||
|
{
|
||||||
|
// look for more data
|
||||||
|
fillInterested();
|
||||||
|
}
|
||||||
|
// else if we are still running
|
||||||
|
else if (getConnector().isRunning())
|
||||||
|
{
|
||||||
|
// Dispatched to handle a pipelined request
|
||||||
|
try
|
||||||
|
{
|
||||||
|
getExecutor().execute(this);
|
||||||
|
}
|
||||||
|
catch (RejectedExecutionException e)
|
||||||
|
{
|
||||||
|
if (getConnector().isRunning())
|
||||||
|
LOG.warn(e);
|
||||||
|
else
|
||||||
|
LOG.ignore(e);
|
||||||
|
getEndPoint().close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
getEndPoint().close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// else the parser must be closed, so seek the EOF if we are still open
|
||||||
|
else if (getEndPoint().isOpen())
|
||||||
|
fillInterested();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onFillInterestedFailed(Throwable cause)
|
protected void onFillInterestedFailed(Throwable cause)
|
||||||
|
@ -303,87 +446,6 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
||||||
new ContentCallback(content,lastContent,callback).iterate();
|
new ContentCallback(content,lastContent,callback).iterate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void completed()
|
|
||||||
{
|
|
||||||
// Handle connection upgrades
|
|
||||||
if (_channel.getResponse().getStatus() == HttpStatus.SWITCHING_PROTOCOLS_101)
|
|
||||||
{
|
|
||||||
Connection connection = (Connection)_channel.getRequest().getAttribute(UPGRADE_CONNECTION_ATTRIBUTE);
|
|
||||||
if (connection != null)
|
|
||||||
{
|
|
||||||
LOG.debug("Upgrade from {} to {}", this, connection);
|
|
||||||
onClose();
|
|
||||||
getEndPoint().setConnection(connection);
|
|
||||||
connection.onOpen();
|
|
||||||
_channel.reset();
|
|
||||||
_parser.reset();
|
|
||||||
_generator.reset();
|
|
||||||
releaseRequestBuffer();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finish consuming the request
|
|
||||||
// If we are still expecting
|
|
||||||
if (_channel.isExpecting100Continue())
|
|
||||||
// close to seek EOF
|
|
||||||
_parser.close();
|
|
||||||
else if (_parser.inContentState() && _generator.isPersistent())
|
|
||||||
// Complete reading the request
|
|
||||||
_channel.getRequest().getHttpInput().consumeAll();
|
|
||||||
|
|
||||||
// Reset the channel, parsers and generator
|
|
||||||
_channel.reset();
|
|
||||||
if (_generator.isPersistent() && !_parser.isClosed())
|
|
||||||
_parser.reset();
|
|
||||||
else
|
|
||||||
_parser.close();
|
|
||||||
releaseRequestBuffer();
|
|
||||||
if (_chunk!=null)
|
|
||||||
_bufferPool.release(_chunk);
|
|
||||||
_chunk=null;
|
|
||||||
_generator.reset();
|
|
||||||
|
|
||||||
// if we are not called from the onfillable thread, schedule completion
|
|
||||||
if (getCurrentConnection()!=this)
|
|
||||||
{
|
|
||||||
// If we are looking for the next request
|
|
||||||
if (_parser.isStart())
|
|
||||||
{
|
|
||||||
// if the buffer is empty
|
|
||||||
if (_requestBuffer == null)
|
|
||||||
{
|
|
||||||
// look for more data
|
|
||||||
fillInterested();
|
|
||||||
}
|
|
||||||
// else if we are still running
|
|
||||||
else if (getConnector().isRunning())
|
|
||||||
{
|
|
||||||
// Dispatched to handle a pipelined request
|
|
||||||
try
|
|
||||||
{
|
|
||||||
getExecutor().execute(this);
|
|
||||||
}
|
|
||||||
catch (RejectedExecutionException e)
|
|
||||||
{
|
|
||||||
if (getConnector().isRunning())
|
|
||||||
LOG.warn(e);
|
|
||||||
else
|
|
||||||
LOG.ignore(e);
|
|
||||||
getEndPoint().close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
getEndPoint().close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// else the parser must be closed, so seek the EOF if we are still open
|
|
||||||
else if (getEndPoint().isOpen())
|
|
||||||
fillInterested();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected class HttpChannelOverHttp extends HttpChannel<ByteBuffer>
|
protected class HttpChannelOverHttp extends HttpChannel<ByteBuffer>
|
||||||
{
|
{
|
||||||
|
@ -471,6 +533,14 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
||||||
{
|
{
|
||||||
getEndPoint().shutdownOutput();
|
getEndPoint().shutdownOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean messageComplete()
|
||||||
|
{
|
||||||
|
super.messageComplete();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class CommitCallback extends IteratingCallback
|
private class CommitCallback extends IteratingCallback
|
||||||
|
|
|
@ -20,7 +20,6 @@ package org.eclipse.jetty.server;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import javax.servlet.ReadListener;
|
import javax.servlet.ReadListener;
|
||||||
import javax.servlet.ServletInputStream;
|
import javax.servlet.ServletInputStream;
|
||||||
|
|
||||||
|
@ -288,6 +287,14 @@ public abstract class HttpInput<T> extends ServletInputStream implements Runnabl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isAsync()
|
||||||
|
{
|
||||||
|
synchronized (lock())
|
||||||
|
{
|
||||||
|
return _contentState==ASYNC;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return whether an EOF has been detected, even though there may be content to consume.
|
* @return whether an EOF has been detected, even though there may be content to consume.
|
||||||
*/
|
*/
|
||||||
|
@ -355,7 +362,7 @@ public abstract class HttpInput<T> extends ServletInputStream implements Runnabl
|
||||||
{
|
{
|
||||||
synchronized (lock())
|
synchronized (lock())
|
||||||
{
|
{
|
||||||
if (_onError == null)
|
if (_onError != null)
|
||||||
LOG.warn(x);
|
LOG.warn(x);
|
||||||
else
|
else
|
||||||
_onError = x;
|
_onError = x;
|
||||||
|
@ -436,6 +443,7 @@ public abstract class HttpInput<T> extends ServletInputStream implements Runnabl
|
||||||
input.blockForContent();
|
input.blockForContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
return "STREAM";
|
return "STREAM";
|
||||||
|
@ -471,6 +479,7 @@ public abstract class HttpInput<T> extends ServletInputStream implements Runnabl
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
return "EARLY_EOF";
|
return "EARLY_EOF";
|
||||||
|
@ -485,6 +494,7 @@ public abstract class HttpInput<T> extends ServletInputStream implements Runnabl
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
return "EOF";
|
return "EOF";
|
||||||
|
|
|
@ -86,39 +86,12 @@ public class HttpInputOverHTTP extends HttpInput<ByteBuffer> implements Callback
|
||||||
|
|
||||||
// No - then we are going to need to parse some more content
|
// No - then we are going to need to parse some more content
|
||||||
_content=null;
|
_content=null;
|
||||||
ByteBuffer requestBuffer = _httpConnection.getRequestBuffer();
|
_httpConnection.parseContent();
|
||||||
|
|
||||||
while (!_httpConnection.getParser().isComplete())
|
// If we have some content available, return it
|
||||||
{
|
|
||||||
// Can the parser progress (even with an empty buffer)
|
|
||||||
_httpConnection.getParser().parseNext(requestBuffer==null?BufferUtil.EMPTY_BUFFER:requestBuffer);
|
|
||||||
|
|
||||||
// If we got some content, that will do for now!
|
|
||||||
if (BufferUtil.hasContent(_content))
|
if (BufferUtil.hasContent(_content))
|
||||||
return _content;
|
return _content;
|
||||||
|
|
||||||
// No, we can we try reading some content?
|
|
||||||
if (BufferUtil.isEmpty(requestBuffer) && _httpConnection.getEndPoint().isInputShutdown())
|
|
||||||
{
|
|
||||||
_httpConnection.getParser().atEOF();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// OK lets read some data
|
|
||||||
int filled=_httpConnection.getEndPoint().fill(requestBuffer);
|
|
||||||
if (LOG.isDebugEnabled()) // Avoid boxing of variable 'filled'
|
|
||||||
LOG.debug("{} filled {}",this,filled);
|
|
||||||
if (filled<=0)
|
|
||||||
{
|
|
||||||
if (filled<0)
|
|
||||||
{
|
|
||||||
_httpConnection.getParser().atEOF();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,6 @@ import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.ReadableByteChannel;
|
import java.nio.channels.ReadableByteChannel;
|
||||||
import java.nio.channels.WritePendingException;
|
import java.nio.channels.WritePendingException;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import javax.servlet.RequestDispatcher;
|
import javax.servlet.RequestDispatcher;
|
||||||
import javax.servlet.ServletOutputStream;
|
import javax.servlet.ServletOutputStream;
|
||||||
import javax.servlet.ServletRequest;
|
import javax.servlet.ServletRequest;
|
||||||
|
@ -75,7 +74,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
||||||
write completed - - - ASYNC READY->owp -
|
write completed - - - ASYNC READY->owp -
|
||||||
|
|
||||||
*/
|
*/
|
||||||
enum OutputState { OPEN, ASYNC, READY, PENDING, UNREADY, CLOSED }
|
enum OutputState { OPEN, ASYNC, READY, PENDING, UNREADY, ERROR, CLOSED }
|
||||||
private final AtomicReference<OutputState> _state=new AtomicReference<>(OutputState.OPEN);
|
private final AtomicReference<OutputState> _state=new AtomicReference<>(OutputState.OPEN);
|
||||||
|
|
||||||
public HttpOutput(HttpChannel<?> channel)
|
public HttpOutput(HttpChannel<?> channel)
|
||||||
|
@ -147,7 +146,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
||||||
break loop;
|
break loop;
|
||||||
|
|
||||||
case UNREADY:
|
case UNREADY:
|
||||||
throw new WritePendingException(); // TODO ?
|
if (_state.compareAndSet(state,OutputState.ERROR))
|
||||||
|
_writeListener.onError(_onError==null?new EofException("Async close"):_onError);
|
||||||
|
continue;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if (_state.compareAndSet(state,OutputState.CLOSED))
|
if (_state.compareAndSet(state,OutputState.CLOSED))
|
||||||
|
@ -180,7 +181,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
||||||
break loop;
|
break loop;
|
||||||
|
|
||||||
case UNREADY:
|
case UNREADY:
|
||||||
throw new WritePendingException(); // TODO ?
|
if (_state.compareAndSet(state,OutputState.ERROR))
|
||||||
|
_writeListener.onError(_onError==null?new EofException("Async closed"):_onError);
|
||||||
|
continue;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if (_state.compareAndSet(state,OutputState.CLOSED))
|
if (_state.compareAndSet(state,OutputState.CLOSED))
|
||||||
|
@ -239,6 +242,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
||||||
case UNREADY:
|
case UNREADY:
|
||||||
throw new WritePendingException();
|
throw new WritePendingException();
|
||||||
|
|
||||||
|
case ERROR:
|
||||||
|
throw new EofException(_onError);
|
||||||
|
|
||||||
case CLOSED:
|
case CLOSED:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -299,6 +305,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
||||||
case UNREADY:
|
case UNREADY:
|
||||||
throw new WritePendingException();
|
throw new WritePendingException();
|
||||||
|
|
||||||
|
case ERROR:
|
||||||
|
throw new EofException(_onError);
|
||||||
|
|
||||||
case CLOSED:
|
case CLOSED:
|
||||||
throw new EofException("Closed");
|
throw new EofException("Closed");
|
||||||
}
|
}
|
||||||
|
@ -397,6 +406,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
||||||
case UNREADY:
|
case UNREADY:
|
||||||
throw new WritePendingException();
|
throw new WritePendingException();
|
||||||
|
|
||||||
|
case ERROR:
|
||||||
|
throw new EofException(_onError);
|
||||||
|
|
||||||
case CLOSED:
|
case CLOSED:
|
||||||
throw new EofException("Closed");
|
throw new EofException("Closed");
|
||||||
}
|
}
|
||||||
|
@ -477,6 +489,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
||||||
case UNREADY:
|
case UNREADY:
|
||||||
throw new WritePendingException();
|
throw new WritePendingException();
|
||||||
|
|
||||||
|
case ERROR:
|
||||||
|
throw new EofException(_onError);
|
||||||
|
|
||||||
case CLOSED:
|
case CLOSED:
|
||||||
throw new EofException("Closed");
|
throw new EofException("Closed");
|
||||||
}
|
}
|
||||||
|
@ -616,6 +631,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
||||||
if (!_state.compareAndSet(OutputState.OPEN, OutputState.PENDING))
|
if (!_state.compareAndSet(OutputState.OPEN, OutputState.PENDING))
|
||||||
continue;
|
continue;
|
||||||
break;
|
break;
|
||||||
|
case ERROR:
|
||||||
|
throw new EofException(_onError);
|
||||||
case CLOSED:
|
case CLOSED:
|
||||||
throw new EofException("Closed");
|
throw new EofException("Closed");
|
||||||
default:
|
default:
|
||||||
|
@ -708,6 +725,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
||||||
case UNREADY:
|
case UNREADY:
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
case ERROR:
|
||||||
|
return true;
|
||||||
|
|
||||||
case CLOSED:
|
case CLOSED:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -717,47 +737,57 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
||||||
@Override
|
@Override
|
||||||
public void run()
|
public void run()
|
||||||
{
|
{
|
||||||
|
loop: while (true)
|
||||||
|
{
|
||||||
|
OutputState state = _state.get();
|
||||||
|
|
||||||
if(_onError!=null)
|
if(_onError!=null)
|
||||||
|
{
|
||||||
|
switch(state)
|
||||||
|
{
|
||||||
|
case CLOSED:
|
||||||
|
case ERROR:
|
||||||
|
_onError=null;
|
||||||
|
break loop;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (_state.compareAndSet(state, OutputState.ERROR))
|
||||||
{
|
{
|
||||||
Throwable th=_onError;
|
Throwable th=_onError;
|
||||||
_onError=null;
|
_onError=null;
|
||||||
_writeListener.onError(new IOException(th));
|
LOG.debug("onError",th);
|
||||||
|
_writeListener.onError(th);
|
||||||
close();
|
close();
|
||||||
|
|
||||||
|
break loop;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
continue loop;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(_state.get())
|
switch(_state.get())
|
||||||
{
|
{
|
||||||
case READY:
|
case READY:
|
||||||
try
|
|
||||||
{
|
|
||||||
_writeListener.onWritePossible();
|
|
||||||
}
|
|
||||||
catch (Throwable e)
|
|
||||||
{
|
|
||||||
_writeListener.onError(e);
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CLOSED:
|
case CLOSED:
|
||||||
try
|
|
||||||
{
|
|
||||||
new Throwable().printStackTrace();
|
|
||||||
// even though a write is not possible, because a close has
|
// even though a write is not possible, because a close has
|
||||||
// occurred, we need to call onWritePossible to tell async
|
// occurred, we need to call onWritePossible to tell async
|
||||||
// producer that the last write completed.
|
// producer that the last write completed.
|
||||||
|
try
|
||||||
|
{
|
||||||
_writeListener.onWritePossible();
|
_writeListener.onWritePossible();
|
||||||
|
break loop;
|
||||||
}
|
}
|
||||||
catch (Throwable e)
|
catch (Throwable e)
|
||||||
{
|
{
|
||||||
_writeListener.onError(e);
|
_onError=e;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
|
@ -769,21 +799,19 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
protected void completed()
|
protected void completed()
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
while(true)
|
while(true)
|
||||||
{
|
{
|
||||||
HttpOutput.OutputState last=_state.get();
|
OutputState last=_state.get();
|
||||||
switch(last)
|
switch(last)
|
||||||
{
|
{
|
||||||
case PENDING:
|
case PENDING:
|
||||||
if (!_state.compareAndSet(HttpOutput.OutputState.PENDING, HttpOutput.OutputState.ASYNC))
|
if (!_state.compareAndSet(OutputState.PENDING, OutputState.ASYNC))
|
||||||
continue;
|
continue;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case UNREADY:
|
case UNREADY:
|
||||||
if (!_state.compareAndSet(HttpOutput.OutputState.UNREADY, HttpOutput.OutputState.READY))
|
if (!_state.compareAndSet(OutputState.UNREADY, OutputState.READY))
|
||||||
continue;
|
continue;
|
||||||
_channel.getState().onWritePossible();
|
_channel.getState().onWritePossible();
|
||||||
break;
|
break;
|
||||||
|
@ -797,12 +825,6 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_onError=e;
|
|
||||||
_channel.getState().onWritePossible();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void failed(Throwable e)
|
public void failed(Throwable e)
|
||||||
|
@ -885,7 +907,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
||||||
// Can we just aggregate the remainder?
|
// Can we just aggregate the remainder?
|
||||||
if (!_complete && _len<BufferUtil.space(_aggregate) && _len<_commitSize)
|
if (!_complete && _len<BufferUtil.space(_aggregate) && _len<_commitSize)
|
||||||
{
|
{
|
||||||
|
int position = BufferUtil.flipToFill(_aggregate);
|
||||||
BufferUtil.put(_buffer,_aggregate);
|
BufferUtil.put(_buffer,_aggregate);
|
||||||
|
BufferUtil.flipToFlush(_aggregate, position);
|
||||||
return Action.SUCCEEDED;
|
return Action.SUCCEEDED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -87,7 +87,6 @@ public class NetworkTrafficServerConnector extends ServerConnector
|
||||||
protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectorManager.ManagedSelector selectSet, SelectionKey key) throws IOException
|
protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectorManager.ManagedSelector selectSet, SelectionKey key) throws IOException
|
||||||
{
|
{
|
||||||
NetworkTrafficSelectChannelEndPoint endPoint = new NetworkTrafficSelectChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout(), listeners);
|
NetworkTrafficSelectChannelEndPoint endPoint = new NetworkTrafficSelectChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout(), listeners);
|
||||||
endPoint.notifyOpened();
|
|
||||||
return endPoint;
|
return endPoint;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -339,6 +339,8 @@ public class Request implements HttpServletRequest
|
||||||
throw new IllegalStateException("Form too large " + content_length + ">" + maxFormContentSize);
|
throw new IllegalStateException("Form too large " + content_length + ">" + maxFormContentSize);
|
||||||
}
|
}
|
||||||
InputStream in = getInputStream();
|
InputStream in = getInputStream();
|
||||||
|
if (_input.isAsync())
|
||||||
|
throw new IllegalStateException("Cannot extract parameters with async IO");
|
||||||
|
|
||||||
// Add form params to query params
|
// Add form params to query params
|
||||||
UrlEncoded.decodeTo(in,_baseParameters,encoding,content_length < 0?maxFormContentSize:-1,maxFormKeys);
|
UrlEncoded.decodeTo(in,_baseParameters,encoding,content_length < 0?maxFormContentSize:-1,maxFormKeys);
|
||||||
|
|
|
@ -1215,19 +1215,16 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_protectedTargets = new String[targets.length];
|
_protectedTargets = Arrays.copyOf(targets, targets.length);
|
||||||
System.arraycopy(targets, 0, _protectedTargets, 0, targets.length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
public String[] getProtectedTargets ()
|
public String[] getProtectedTargets()
|
||||||
{
|
{
|
||||||
if (_protectedTargets == null)
|
if (_protectedTargets == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
String[] tmp = new String[_protectedTargets.length];
|
return Arrays.copyOf(_protectedTargets, _protectedTargets.length);
|
||||||
System.arraycopy(_protectedTargets, 0, tmp, 0, _protectedTargets.length);
|
|
||||||
return tmp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1384,8 +1381,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/**
|
/**
|
||||||
* @param base
|
* Set the base resource for this context.
|
||||||
* The resourceBase to set.
|
* @param base The resource used as the base for all static content of this context.
|
||||||
|
* @see #setResourceBase(String)
|
||||||
*/
|
*/
|
||||||
public void setBaseResource(Resource base)
|
public void setBaseResource(Resource base)
|
||||||
{
|
{
|
||||||
|
@ -1394,8 +1392,10 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/**
|
/**
|
||||||
* @param resourceBase
|
* Set the base resource for this context.
|
||||||
* The base resource as a string.
|
* @param resourceBase A string representing the base resource for the context. Any string accepted
|
||||||
|
* by {@link Resource#newResource(String)} may be passed and the call is equivalent to
|
||||||
|
* <code>setBaseResource(newResource(resourceBase));</code>
|
||||||
*/
|
*/
|
||||||
public void setResourceBase(String resourceBase)
|
public void setResourceBase(String resourceBase)
|
||||||
{
|
{
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue