Merge branch 'jetty-9.4.x' into jetty-10.0.x

Signed-off-by: Greg Wilkins <gregw@webtide.com>
This commit is contained in:
Greg Wilkins 2018-11-01 20:24:43 +01:00
commit 254dc88743
43 changed files with 1099 additions and 1448 deletions

View File

@ -47,7 +47,6 @@ import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.server.handler.StatisticsHandler;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
@ -209,9 +208,7 @@ public class LikeJettyXml
requestLog.setExtended(true);
requestLog.setLogCookies(false);
requestLog.setLogTimeZone("GMT");
RequestLogHandler requestLogHandler = new RequestLogHandler();
requestLogHandler.setRequestLog(requestLog);
handlers.addHandler(requestLogHandler);
server.setRequestLog(requestLog);
// === jetty-lowresources.xml ===

View File

@ -35,7 +35,6 @@ import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.server.handler.gzip.GzipHandler;
import org.eclipse.jetty.util.ajax.JSON;
@ -115,13 +114,12 @@ public class ManyHandlers
HandlerWrapper wrapper = new WelcomeWrapHandler();
Handler hello = new HelloHandler();
Handler dft = new DefaultHandler();
RequestLogHandler requestLog = new RequestLogHandler();
// configure request logging
File requestLogFile = File.createTempFile("demo", "log");
NCSARequestLog ncsaLog = new NCSARequestLog(
requestLogFile.getAbsolutePath());
requestLog.setRequestLog(ncsaLog);
server.setRequestLog(ncsaLog);
// create the handler collections
HandlerCollection handlers = new HandlerCollection();
@ -129,20 +127,8 @@ public class ManyHandlers
// link them all together
wrapper.setHandler(hello);
list.setHandlers(new Handler[] { param, new GzipHandler(), dft });
handlers.setHandlers(new Handler[] { list, requestLog });
// Handler tree looks like the following
// <pre>
// Server
// + HandlerCollection
// . + HandlerList
// . | + param (ParamHandler)
// . | + wrapper (WelcomeWrapHandler)
// . | | \ hello (HelloHandler)
// . | \ dft (DefaultHandler)
// . \ requestLog (RequestLogHandler)
// </pre>
list.setHandlers(new Handler[] { param, new GzipHandler() });
handlers.setHandlers(new Handler[] { list, dft });
server.setHandler(handlers);

View File

@ -20,7 +20,7 @@
<extensions>true</extensions>
<configuration>
<instructions>
<Import-Package>org.objectweb.asm;version="[5.0,7)",*</Import-Package>
<Import-Package>org.objectweb.asm;version="5",*</Import-Package>
<Require-Capability>osgi.serviceloader; filter:="(osgi.serviceloader=javax.servlet.ServletContainerInitializer)";resolution:=optional;cardinality:=multiple, osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)";resolution:=optional</Require-Capability>
</instructions>
</configuration>

View File

@ -40,7 +40,6 @@ import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.util.Scanner;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.xml.XmlConfiguration;
@ -407,9 +406,8 @@ public class ServerProxyImpl implements ServerProxy
*/
private void configureHandlers()
{
RequestLogHandler requestLogHandler = new RequestLogHandler();
if (requestLog != null)
requestLogHandler.setRequestLog(requestLog);
server.setRequestLog(requestLog);
contexts = (ContextHandlerCollection) server
.getChildHandlerByClass(ContextHandlerCollection.class);
@ -422,8 +420,7 @@ public class ServerProxyImpl implements ServerProxy
{
handlers = new HandlerCollection();
server.setHandler(handlers);
handlers.setHandlers(new Handler[] { contexts, new DefaultHandler(),
requestLogHandler });
handlers.setHandlers(new Handler[] { contexts, new DefaultHandler() });
}
else
{

View File

@ -550,14 +550,14 @@ public abstract class HttpReceiver
// respect to concurrency between request and response.
Result result = exchange.terminateResponse();
terminateResponse(exchange, result);
return true;
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("Concurrent failure: response termination skipped, performed by helpers");
return false;
}
return true;
}
private boolean updateResponseState(ResponseState from, ResponseState to)

View File

@ -76,7 +76,7 @@ public class HttpRequest implements Request
private String query;
private String method = HttpMethod.GET.asString();
private HttpVersion version = HttpVersion.HTTP_1_1;
private long idleTimeout;
private long idleTimeout = -1;
private long timeout;
private long timeoutAt;
private ContentProvider content;
@ -99,7 +99,6 @@ public class HttpRequest implements Request
extractParams(query);
followRedirects(client.isFollowRedirects());
idleTimeout = client.getIdleTimeout();
HttpField acceptEncodingField = client.getAcceptEncodingField();
if (acceptEncodingField != null)
headers.put(acceptEncodingField);

View File

@ -579,14 +579,14 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
// respect to concurrency between request and response.
Result result = exchange.terminateRequest();
terminateRequest(exchange, failure, result);
return true;
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("Concurrent failure: request termination skipped, performed by helpers");
return false;
}
return true;
}
private boolean updateRequestState(RequestState from, RequestState to)

View File

@ -247,7 +247,9 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements Connec
// Save the old idle timeout to restore it.
EndPoint endPoint = getEndPoint();
idleTimeout = endPoint.getIdleTimeout();
endPoint.setIdleTimeout(request.getIdleTimeout());
long requestIdleTimeout = request.getIdleTimeout();
if (requestIdleTimeout >= 0)
endPoint.setIdleTimeout(requestIdleTimeout);
// One channel per connection, just delegate the send.
return send(channel, exchange);

View File

@ -110,7 +110,7 @@ public class NeedWantClientAuthTest
startClient(clientSSL);
ContentResponse response = client.newRequest("https://localhost:" + connector.getLocalPort())
.timeout(5, TimeUnit.SECONDS)
.timeout(10, TimeUnit.SECONDS)
.send();
assertEquals(HttpStatus.OK_200, response.getStatus());
@ -149,11 +149,11 @@ public class NeedWantClientAuthTest
startClient(clientSSL);
ContentResponse response = client.newRequest("https://localhost:" + connector.getLocalPort())
.timeout(5, TimeUnit.SECONDS)
.timeout(10, TimeUnit.SECONDS)
.send();
assertEquals(HttpStatus.OK_200, response.getStatus());
assertTrue(handshakeLatch.await(5, TimeUnit.SECONDS));
assertTrue(handshakeLatch.await(10, TimeUnit.SECONDS));
}
@Test
@ -192,7 +192,7 @@ public class NeedWantClientAuthTest
CountDownLatch latch = new CountDownLatch(1);
client.newRequest("https://localhost:" + connector.getLocalPort())
.timeout(5, TimeUnit.SECONDS)
.timeout(10, TimeUnit.SECONDS)
.send(result ->
{
if (result.isFailed())
@ -203,8 +203,8 @@ public class NeedWantClientAuthTest
}
});
assertTrue(handshakeLatch.await(5, TimeUnit.SECONDS));
assertTrue(latch.await(5, TimeUnit.SECONDS));
assertTrue(handshakeLatch.await(10, TimeUnit.SECONDS));
assertTrue(latch.await(10, TimeUnit.SECONDS));
}
@Test
@ -240,10 +240,10 @@ public class NeedWantClientAuthTest
startClient(clientSSL);
ContentResponse response = client.newRequest("https://localhost:" + connector.getLocalPort())
.timeout(5, TimeUnit.SECONDS)
.timeout(10, TimeUnit.SECONDS)
.send();
assertEquals(HttpStatus.OK_200, response.getStatus());
assertTrue(handshakeLatch.await(5, TimeUnit.SECONDS));
assertTrue(handshakeLatch.await(10, TimeUnit.SECONDS));
}
}

View File

@ -166,7 +166,7 @@ public class HttpChannelOverFCGI extends HttpChannel
{
super(connection.getHttpDestination().getHttpClient().getScheduler());
this.connection = connection;
setIdleTimeout(idleTimeout);
setIdleTimeout(idleTimeout >= 0 ? idleTimeout : connection.getEndPoint().getIdleTimeout());
}
@Override

View File

@ -46,7 +46,6 @@ public abstract class CookieCutter
{
// Parse the header
String name = null;
String value = null;
String cookieName = null;
String cookieValue = null;
@ -61,11 +60,11 @@ public abstract class CookieCutter
boolean escaped=false;
int tokenstart=-1;
int tokenend=-1;
for (int i = 0, length = hdr.length(), last=length-1; i < length; i++)
for (int i = 0, length = hdr.length(); i <= length; i++)
{
char c = hdr.charAt(i);
char c = i==length?0:hdr.charAt(i);
// System.err.printf("i=%d c=%s v=%b q=%b e=%b u=%s s=%d e=%d%n" ,i,""+c,invalue,inQuoted,escaped,unquoted,tokenstart,tokenend);
// System.err.printf("i=%d/%d c=%s v=%b q=%b/%b e=%b u=%s s=%d e=%d \t%s=%s%n" ,i,length,c==0?"|":(""+c),invalue,inQuoted,quoted,escaped,unquoted,tokenstart,tokenend,name,value);
// Handle quoted values for name or value
if (inQuoted)
@ -73,52 +72,39 @@ public abstract class CookieCutter
if (escaped)
{
escaped=false;
unquoted.append(c);
if (c>0)
unquoted.append(c);
else
{
unquoted.setLength(0);
inQuoted = false;
i--;
}
continue;
}
switch (c)
{
case '"':
inQuoted=false;
if (i==last)
{
value = unquoted.toString();
unquoted.setLength(0);
}
else
{
quoted=true;
tokenstart=i;
tokenend=-1;
}
inQuoted = false;
quoted = true;
tokenstart = i;
tokenend = -1;
break;
case '\\':
if (i==last)
{
unquoted.setLength(0);
inQuoted = false;
i--;
}
else
{
escaped=true;
}
escaped = true;
continue;
case 0:
// unterminated quote, let's ignore quotes
unquoted.setLength(0);
inQuoted = false;
i--;
continue;
default:
if (i==last)
{
// unterminated quote, let's ignore quotes
unquoted.setLength(0);
inQuoted = false;
i--;
}
else
{
unquoted.append(c);
}
unquoted.append(c);
continue;
}
}
@ -134,21 +120,90 @@ public abstract class CookieCutter
case '\t':
break;
case ',':
if (_compliance!=CookieCompliance.RFC2965)
{
if (quoted)
{
// must have been a bad internal quote. let's fix as best we can
unquoted.append(hdr,tokenstart,i--);
inQuoted = true;
quoted = false;
continue;
}
if (tokenstart<0)
tokenstart = i;
tokenend=i;
continue;
}
// fall through
case 0:
case ';':
{
String value;
if (quoted)
{
value = unquoted.toString();
unquoted.setLength(0);
quoted = false;
}
else if(tokenstart>=0 && tokenend>=0)
value = hdr.substring(tokenstart, tokenend+1);
else if(tokenstart>=0)
value = tokenend>=tokenstart?hdr.substring(tokenstart, tokenend+1):hdr.substring(tokenstart);
else
value = "";
try
{
if (name.startsWith("$"))
{
if (_compliance==CookieCompliance.RFC2965)
{
String lowercaseName = name.toLowerCase(Locale.ENGLISH);
switch(lowercaseName)
{
case "$path":
cookiePath = value;
break;
case "$domain":
cookieDomain = value;
break;
case "$port":
cookieComment = "$port="+value;
break;
case "$version":
cookieVersion = Integer.parseInt(value);
break;
default:
break;
}
}
}
else
{
// This is a new cookie, so add the completed last cookie if we have one
if (cookieName!=null)
{
addCookie(cookieName, cookieValue, cookieDomain, cookiePath, cookieVersion, cookieComment);
cookieDomain = null;
cookiePath = null;
cookieComment = null;
}
cookieName = name;
cookieValue = value;
}
}
catch (Exception e)
{
LOG.debug(e);
}
name = null;
tokenstart = -1;
invalue=false;
break;
}
case '"':
if (tokenstart<0)
@ -165,20 +220,14 @@ public abstract class CookieCutter
if (quoted)
{
// must have been a bad internal quote. let's fix as best we can
unquoted.append(hdr.substring(tokenstart,i));
unquoted.append(hdr,tokenstart,i--);
inQuoted = true;
quoted = false;
i--;
continue;
}
if (tokenstart<0)
tokenstart=i;
tokenstart = i;
tokenend=i;
if (i==last)
{
value = hdr.substring(tokenstart, tokenend+1);
break;
}
continue;
}
}
@ -191,21 +240,6 @@ public abstract class CookieCutter
case '\t':
continue;
case ';':
if (quoted)
{
name = unquoted.toString();
unquoted.setLength(0);
quoted = false;
}
else if(tokenstart>=0 && tokenend>=0)
{
name = hdr.substring(tokenstart, tokenend+1);
}
tokenstart = -1;
break;
case '=':
if (quoted)
{
@ -213,100 +247,29 @@ public abstract class CookieCutter
unquoted.setLength(0);
quoted = false;
}
else if(tokenstart>=0 && tokenend>=0)
{
name = hdr.substring(tokenstart, tokenend+1);
}
else if(tokenstart>=0)
name = tokenend>=tokenstart?hdr.substring(tokenstart, tokenend+1):hdr.substring(tokenstart);
tokenstart = -1;
invalue=true;
invalue = true;
break;
default:
if (quoted)
{
// must have been a bad internal quote. let's fix as best we can
unquoted.append(hdr.substring(tokenstart,i));
unquoted.append(hdr,tokenstart,i--);
inQuoted = true;
quoted = false;
i--;
continue;
}
if (tokenstart<0)
tokenstart=i;
tokenend=i;
if (i==last)
break;
continue;
}
}
}
if (invalue && i==last && value==null)
{
if (quoted)
{
value = unquoted.toString();
unquoted.setLength(0);
quoted = false;
}
else if(tokenstart>=0 && tokenend>=0)
{
value = hdr.substring(tokenstart, tokenend+1);
}
else
value = "";
}
// If after processing the current character we have a value and a name, then it is a cookie
if (name!=null && value!=null)
{
try
{
if (name.startsWith("$"))
{
String lowercaseName = name.toLowerCase(Locale.ENGLISH);
if (_compliance==CookieCompliance.RFC6265)
{
// Ignore
}
else if ("$path".equals(lowercaseName))
{
cookiePath = value;
}
else if ("$domain".equals(lowercaseName))
{
cookieDomain = value;
}
else if ("$port".equals(lowercaseName))
{
cookieComment = (cookieComment==null?"$port=":", $port=")+value;
}
else if ("$version".equals(lowercaseName))
{
cookieVersion = Integer.parseInt(value);
}
}
else
{
if (cookieName!=null)
{
addCookie(cookieName, cookieValue, cookieDomain, cookiePath, cookieVersion, cookieComment);
cookieDomain = null;
cookiePath = null;
cookieComment = null;
}
cookieName = name;
cookieValue = value;
}
}
catch (Exception e)
{
LOG.debug(e);
}
name = null;
value = null;
}
}
if (cookieName!=null)

View File

@ -145,7 +145,12 @@ public class CookieCutterLenientTest
Arguments.of("GAPS=1:A1aaaAaAA1aaAAAaa1a11a:aAaaAa-aaA1-", "GAPS", "1:A1aaaAaAA1aaAAAaa1a11a:aAaaAa-aaA1-"),
// Strong abuse of cookie spec (lots of tspecials)
Arguments.of("$Version=0; rToken=F_TOKEN''!--\"</a>=&{()}", "rToken", "F_TOKEN''!--\"</a>=&{()}")
Arguments.of("$Version=0; rToken=F_TOKEN''!--\"</a>=&{()}", "rToken", "F_TOKEN''!--\"</a>=&{()}"),
// Commas that were not commas
Arguments.of("name=foo,bar","name","foo,bar"),
Arguments.of("name=foo , bar","name","foo , bar"),
Arguments.of("name=foo , bar, bob","name","foo , bar, bob")
);
}

View File

@ -140,17 +140,22 @@ public class CookieCutterTest
* Example from RFC2965
*/
@Test
@Disabled("comma separation no longer supported by new RFC6265")
public void testRFC2965_CookieSpoofingExample()
{
String rawCookie = "$Version=\"1\"; session_id=\"1234\", " +
"$Version=\"1\"; session_id=\"1111\"; $Domain=\".cracker.edu\"";
Cookie cookies[] = parseCookieHeaders(CookieCompliance.RFC6265,rawCookie);
Cookie cookies[] = parseCookieHeaders(CookieCompliance.RFC2965,rawCookie);
assertThat("Cookies.length", cookies.length, is(2));
assertCookie("Cookies[0]", cookies[0], "session_id", "1234", 1, null);
assertCookie("Cookies[1]", cookies[1], "session_id", "1111", 1, null);
cookies = parseCookieHeaders(CookieCompliance.RFC6265,rawCookie);
assertThat("Cookies.length", cookies.length, is(2));
assertCookie("Cookies[0]", cookies[0], "session_id", "1234\", $Version=\"1", 0, null);
assertCookie("Cookies[1]", cookies[1], "session_id", "1111", 0, null);
}
/**

View File

@ -562,7 +562,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
IStream stream = createLocalStream(streamId);
stream.setListener(listener);
ControlEntry entry = new ControlEntry(frame, stream, new PromiseCallback<>(promise, stream));
ControlEntry entry = new ControlEntry(frame, stream, new StreamPromiseCallback(promise, stream));
queued = flusher.append(entry);
}
// Iterate outside the synchronized block.
@ -606,7 +606,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
IStream pushStream = createLocalStream(streamId);
pushStream.setListener(listener);
ControlEntry entry = new ControlEntry(frame, pushStream, new PromiseCallback<>(promise, pushStream));
ControlEntry entry = new ControlEntry(frame, pushStream, new StreamPromiseCallback(promise, pushStream));
queued = flusher.append(entry);
}
// Iterate outside the synchronized block.
@ -764,7 +764,8 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
int localCount = localStreamCount.get();
int maxCount = getMaxLocalStreams();
if (maxCount >= 0 && localCount >= maxCount)
throw new IllegalStateException("Max local stream count " + maxCount + " exceeded");
// TODO: remove the dump() in the exception message.
throw new IllegalStateException("Max local stream count " + maxCount + " exceeded" + System.lineSeparator() + dump());
if (localStreamCount.compareAndSet(localCount, localCount + 1))
break;
}
@ -780,6 +781,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
}
else
{
localStreamCount.decrementAndGet();
throw new IllegalStateException("Duplicate stream " + streamId);
}
}
@ -816,6 +818,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
}
else
{
remoteStreamCount.addAndGetHi(-1);
onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "duplicate_stream");
return null;
}
@ -1461,21 +1464,21 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
}
}
private static class PromiseCallback<C> implements Callback
private static class StreamPromiseCallback implements Callback
{
private final Promise<C> promise;
private final C value;
private final Promise<Stream> promise;
private final IStream stream;
private PromiseCallback(Promise<C> promise, C value)
private StreamPromiseCallback(Promise<Stream> promise, IStream stream)
{
this.promise = promise;
this.value = value;
this.stream = stream;
}
@Override
public void succeeded()
{
promise.succeeded(value);
promise.succeeded(stream);
}
@Override

View File

@ -138,6 +138,7 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
{
if (writing.compareAndSet(null, callback))
return true;
close();
callback.failed(new WritePendingException());
return false;
}
@ -275,8 +276,6 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
private void onHeaders(HeadersFrame frame, Callback callback)
{
if (updateClose(frame.isEndStream(), CloseState.Event.RECEIVED))
session.removeStream(this);
MetaData metaData = frame.getMetaData();
if (metaData.isRequest() || metaData.isResponse())
{
@ -286,6 +285,10 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
length = fields.getLongField(HttpHeader.CONTENT_LENGTH.asString());
dataLength = length >= 0 ? length : Long.MIN_VALUE;
}
if (updateClose(frame.isEndStream(), CloseState.Event.RECEIVED))
session.removeStream(this);
callback.succeeded();
}
@ -507,6 +510,13 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
}
}
@Override
public void onClose()
{
super.onClose();
notifyClosed(this);
}
private void updateStreamCount(int deltaStream, int deltaClosing)
{
((HTTP2Session)session).updateStreamCount(isLocal(), deltaStream, deltaClosing);
@ -612,6 +622,21 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
}
}
private void notifyClosed(Stream stream)
{
Listener listener = this.listener;
if (listener == null)
return;
try
{
listener.onClosed(stream);
}
catch (Throwable x)
{
LOG.info("Failure while notifying listener " + listener, x);
}
}
@Override
public String dump()
{

View File

@ -163,6 +163,13 @@ public interface Stream
*/
public void onData(Stream stream, DataFrame frame, Callback callback);
/**
* <p>Callback method invoked when a RST_STREAM frame has been received for this stream.</p>
*
* @param stream the stream
* @param frame the RST_FRAME received
* @param callback the callback to complete when the reset has been handled
*/
public default void onReset(Stream stream, ResetFrame frame, Callback callback)
{
try
@ -197,11 +204,28 @@ public interface Stream
*/
public boolean onIdleTimeout(Stream stream, Throwable x);
/**
* <p>Callback method invoked when the stream failed.</p>
*
* @param stream the stream
* @param error the error code
* @param reason the error reason, or null
* @param callback the callback to complete when the failure has been handled
*/
public default void onFailure(Stream stream, int error, String reason, Callback callback)
{
callback.succeeded();
}
/**
* <p>Callback method invoked after the stream has been closed.</p>
*
* @param stream the stream
*/
public default void onClosed(Stream stream)
{
}
/**
* <p>Empty implementation of {@link Listener}</p>
*/

View File

@ -25,6 +25,7 @@ import org.eclipse.jetty.client.HttpReceiver;
import org.eclipse.jetty.client.HttpSender;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.http2.ErrorCode;
import org.eclipse.jetty.http2.IStream;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.frames.ResetFrame;
@ -101,6 +102,11 @@ public class HttpChannelOverHTTP2 extends HttpChannel
connection.release(this);
}
void onStreamClosed(IStream stream)
{
connection.onStreamClosed(stream, this);
}
@Override
public void exchangeTerminated(HttpExchange exchange, Result result)
{

View File

@ -35,12 +35,17 @@ import org.eclipse.jetty.client.HttpRequest;
import org.eclipse.jetty.client.SendFailure;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http2.ErrorCode;
import org.eclipse.jetty.http2.IStream;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.Sweeper;
public class HttpConnectionOverHTTP2 extends HttpConnection implements Sweeper.Sweepable
{
private static final Logger LOG = Log.getLogger(HttpConnection.class);
private final Set<HttpChannel> activeChannels = ConcurrentHashMap.newKeySet();
private final Queue<HttpChannelOverHTTP2> idleChannels = new ConcurrentLinkedQueue<>();
private final AtomicBoolean closed = new AtomicBoolean();
@ -87,16 +92,15 @@ public class HttpConnectionOverHTTP2 extends HttpConnection implements Sweeper.S
protected void release(HttpChannelOverHTTP2 channel)
{
// Only non-push channels are released.
if (LOG.isDebugEnabled())
LOG.debug("Released {}", channel);
if (activeChannels.remove(channel))
{
channel.setStream(null);
// Recycle only non-failed channels.
if (channel.isFailed())
channel.destroy();
else
idleChannels.offer(channel);
getHttpDestination().release(this);
}
else
{
@ -104,6 +108,16 @@ public class HttpConnectionOverHTTP2 extends HttpConnection implements Sweeper.S
}
}
void onStreamClosed(IStream stream, HttpChannelOverHTTP2 channel)
{
if (LOG.isDebugEnabled())
LOG.debug("{} closed for {}", stream, channel);
channel.setStream(null);
// Only non-push channels are released.
if (stream.isLocal())
getHttpDestination().release(this);
}
@Override
public boolean onIdleTimeout(long idleTimeout)
{

View File

@ -38,6 +38,7 @@ import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.ErrorCode;
import org.eclipse.jetty.http2.IStream;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
@ -171,8 +172,10 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listen
@Override
public boolean onIdleTimeout(Stream stream, Throwable x)
{
responseFailure(x);
return true;
HttpExchange exchange = getHttpExchange();
if (exchange == null)
return false;
return !exchange.abort(x);
}
@Override
@ -182,6 +185,12 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listen
callback.succeeded();
}
@Override
public void onClosed(Stream stream)
{
getHttpChannel().onStreamClosed((IStream)stream);
}
private void notifyContent(HttpExchange exchange, DataFrame frame, Callback callback)
{
contentNotifier.offer(new DataInfo(exchange, frame, callback));

View File

@ -67,7 +67,9 @@ public class HttpSenderOverHTTP2 extends HttpSender
{
channel.setStream(stream);
((IStream)stream).setAttachment(channel);
stream.setIdleTimeout(request.getIdleTimeout());
long idleTimeout = request.getIdleTimeout();
if (idleTimeout >= 0)
stream.setIdleTimeout(idleTimeout);
if (content.hasContent() && !expects100Continue(request))
{

View File

@ -18,6 +18,22 @@
package org.eclipse.jetty.http2.client.http;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.AbstractConnectionPool;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpDestination;
@ -43,21 +59,6 @@ import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.jupiter.api.Test;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -370,7 +371,7 @@ public class MaxConcurrentStreamsTest extends AbstractTest
}
@Test
public void testTwoConcurrentStreamsFirstTimesOut() throws Exception
public void testTwoStreamsFirstTimesOut() throws Exception
{
long timeout = 1000;
start(1, new EmptyServerHandler()

View File

@ -0,0 +1,222 @@
//
// ========================================================================
// Copyright (c) 1995-2018 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.requestlog.jmh;
import static java.lang.invoke.MethodHandles.dropArguments;
import static java.lang.invoke.MethodHandles.foldArguments;
import static java.lang.invoke.MethodType.methodType;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.util.TypeUtil;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.profile.GCProfiler;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
@State(Scope.Benchmark)
@Threads(4)
@Warmup(iterations = 7, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 7, time = 500, timeUnit = TimeUnit.MILLISECONDS)
public class RequestLogBenchmark
{
public static void append(String s, StringBuilder b)
{
b.append(s);
}
public static void logURI(StringBuilder b, String request)
{
b.append(request);
}
public static void logLength(StringBuilder b, String request)
{
b.append(request.length());
}
public static void logAddr(StringBuilder b, String request)
{
try
{
TypeUtil.toHex(request.hashCode(), b);
}
catch(Exception e)
{
throw new RuntimeException(e);
}
}
private ThreadLocal<StringBuilder> buffers = new ThreadLocal<StringBuilder>()
{
@Override
protected StringBuilder initialValue()
{
return new StringBuilder(256);
}
};
MethodHandle logHandle;
Object[] iteratedLog;
public RequestLogBenchmark()
{
try
{
MethodType logType = methodType(Void.TYPE, StringBuilder.class, String.class);
MethodHandle append = MethodHandles.lookup()
.findStatic(RequestLogBenchmark.class, "append", methodType(Void.TYPE, String.class, StringBuilder.class));
MethodHandle logURI = MethodHandles.lookup().findStatic(RequestLogBenchmark.class, "logURI", logType);
MethodHandle logAddr = MethodHandles.lookup().findStatic(RequestLogBenchmark.class, "logAddr", logType);
MethodHandle logLength = MethodHandles.lookup().findStatic(RequestLogBenchmark.class, "logLength", logType);
// setup iteration
iteratedLog = new Object[]
{
logURI,
" - ",
logAddr,
" ",
logLength,
"\n"
};
// setup methodHandle
logHandle = dropArguments(append.bindTo("\n"), 1, String.class);
logHandle = foldArguments(logHandle, logLength);
logHandle = foldArguments(logHandle, dropArguments(append.bindTo(" "), 1, String.class));
logHandle = foldArguments(logHandle, logAddr);
logHandle = foldArguments(logHandle, dropArguments(append.bindTo(" - "), 1, String.class));
logHandle = foldArguments(logHandle, logURI);
}
catch (Throwable th)
{
throw new RuntimeException(th);
}
}
public String logFixed(String request)
{
StringBuilder b = buffers.get();
logURI(b,request);
append(" - ",b);
logAddr(b,request);
append(" ",b);
logLength(b,request);
append("\n",b);
String l = b.toString();
b.setLength(0);
return l;
}
public String logIterate(String request)
{
try
{
StringBuilder b = buffers.get();
for (Object o : iteratedLog)
{
if (o instanceof String)
append((String)o, b);
else if (o instanceof MethodHandle)
((MethodHandle)o).invoke(b, request);
}
String l = b.toString();
b.setLength(0);
return l;
}
catch(Throwable th)
{
throw new RuntimeException(th);
}
}
public String logMethodHandle(String request)
{
try
{
StringBuilder b = buffers.get();
logHandle.invoke(buffers.get(), request);
String l = b.toString();
b.setLength(0);
return l;
}
catch (Throwable th)
{
throw new RuntimeException(th);
}
}
@Benchmark
@BenchmarkMode({ Mode.Throughput})
public String testFixed()
{
return logFixed(Long.toString(ThreadLocalRandom.current().nextLong()));
};
@Benchmark
@BenchmarkMode({ Mode.Throughput})
public String testIterate()
{
return logIterate(Long.toString(ThreadLocalRandom.current().nextLong()));
};
@Benchmark
@BenchmarkMode({ Mode.Throughput})
public String testHandle()
{
return logMethodHandle(Long.toString(ThreadLocalRandom.current().nextLong()));
};
public static void main(String[] args) throws RunnerException
{
Options opt = new OptionsBuilder()
.include(RequestLogBenchmark.class.getSimpleName())
.warmupIterations(20)
.measurementIterations(10)
.addProfiler(GCProfiler.class)
.forks(1)
.threads(100)
.build();
new Runner(opt).run();
}
}

View File

@ -33,7 +33,6 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.Configurations;
@ -70,9 +69,8 @@ public class ServerSupport
throw new IllegalArgumentException ("Server is null");
DefaultHandler defaultHandler = new DefaultHandler();
RequestLogHandler requestLogHandler = new RequestLogHandler();
if (requestLog != null)
requestLogHandler.setRequestLog(requestLog);
server.setRequestLog(requestLog);
ContextHandlerCollection contexts = findContextHandlerCollection(server);
if (contexts == null)
@ -83,7 +81,7 @@ public class ServerSupport
{
handlers = new HandlerCollection();
server.setHandler(handlers);
handlers.setHandlers(new Handler[]{contexts, defaultHandler, requestLogHandler});
handlers.setHandlers(new Handler[]{contexts, defaultHandler});
}
else
{

View File

@ -80,7 +80,7 @@
javax.servlet.http;version="[3.1,4.1)",
javax.transaction;version="1.1.0";resolution:=optional,
javax.transaction.xa;version="1.1.0";resolution:=optional,
org.objectweb.asm;version=4;resolution:=optional,
org.objectweb.asm;version="5";resolution:=optional,
org.osgi.framework,
org.osgi.service.cm;version="1.2.0",
org.osgi.service.packageadmin,

View File

@ -15,6 +15,7 @@
<packaging>pom</packaging>
<properties>
<asm.version>6.2</asm.version>
<osgi-version>3.6.0.v20100517</osgi-version>
<osgi-services-version>3.2.100.v20100503</osgi-services-version>
<equinox-http-servlet-version>1.0.0-v20070606</equinox-http-servlet-version>

View File

@ -43,7 +43,6 @@ import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.server.handler.StatisticsHandler;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
@ -88,7 +87,6 @@ public class Runner
protected URLClassLoader _classLoader;
protected Classpath _classpath = new Classpath();
protected ContextHandlerCollection _contexts;
protected RequestLogHandler _logHandler;
protected String _logFile;
protected ArrayList<String> _configFiles;
protected boolean _enableStats=false;
@ -392,14 +390,6 @@ public class Runner
handlers.addHandler(new DefaultHandler());
}
//ensure a log handler is present
_logHandler = (RequestLogHandler) handlers.getChildHandlerByClass(RequestLogHandler.class);
if (_logHandler == null)
{
_logHandler = new RequestLogHandler();
handlers.addHandler(_logHandler);
}
//check a connector is configured to listen on
Connector[] connectors = _server.getConnectors();
@ -509,7 +499,7 @@ public class Runner
{
NCSARequestLog requestLog = new NCSARequestLog(_logFile);
requestLog.setExtended(false);
_logHandler.setRequestLog(requestLog);
_server.setRequestLog(requestLog);
}
}

View File

@ -888,7 +888,6 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
@Override
public void write(ByteBuffer content, boolean complete, Callback callback)
{
_written+=BufferUtil.length(content);
sendResponse(null,content,complete,callback);
}
@ -1226,18 +1225,21 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
private class CommitCallback extends Callback.Nested
{
private final ByteBuffer _content;
private final int _length;
private final boolean _complete;
private CommitCallback(Callback callback, ByteBuffer content, boolean complete)
{
super(callback);
this._content = content == null ? BufferUtil.EMPTY_BUFFER : content.slice();
this._complete = complete;
_content = content == null ? BufferUtil.EMPTY_BUFFER : content.slice();
_length = _content.remaining();
_complete = complete;
}
@Override
public void succeeded()
{
_written += _length;
super.succeeded();
notifyResponseCommit(_request);
if (_content.hasRemaining())
@ -1299,18 +1301,21 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
private class ContentCallback extends Callback.Nested
{
private final ByteBuffer _content;
private final int _length;
private final boolean _complete;
private ContentCallback(Callback callback, ByteBuffer content, boolean complete)
{
super(callback);
this._content = content == null ? BufferUtil.EMPTY_BUFFER : content.slice();
this._complete = complete;
_content = content == null ? BufferUtil.EMPTY_BUFFER : content.slice();
_length = _content.remaining();
_complete = complete;
}
@Override
public void succeeded()
{
_written += _length;
super.succeeded();
if (_content.hasRemaining())
notifyResponseContent(_request, _content);

View File

@ -286,7 +286,12 @@ public class HttpInput extends ServletInputStream implements Runnable
{
long minimum_data = minRequestDataRate * TimeUnit.NANOSECONDS.toMillis(period) / TimeUnit.SECONDS.toMillis(1);
if (_contentArrived < minimum_data)
throw new BadMessageException(HttpStatus.REQUEST_TIMEOUT_408,String.format("Request content data rate < %d B/s",minRequestDataRate));
{
BadMessageException bad = new BadMessageException(HttpStatus.REQUEST_TIMEOUT_408,
String.format("Request content data rate < %d B/s", minRequestDataRate));
_channelState.getHttpChannel().abort(bad);
throw bad;
}
}
}

View File

@ -25,6 +25,7 @@ import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritePendingException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
@ -934,7 +935,11 @@ public class HttpOutput extends ServletOutputStream implements Runnable
if (LOG.isDebugEnabled())
LOG.debug("Flushed bytes min/actual {}/{}", minFlushed, _flushed);
if (_flushed < minFlushed)
throw new IOException(String.format("Response content data rate < %d B/s", minDataRate));
{
IOException ioe = new IOException(String.format("Response content data rate < %d B/s", minDataRate));
_channel.abort(ioe);
throw ioe;
}
}
public void recycle()

View File

@ -761,12 +761,15 @@ public class Request implements HttpServletRequest
}
_cookiesExtracted = true;
for (String c : metadata.getFields().getValuesList(HttpHeader.COOKIE))
for (HttpField field : metadata.getFields())
{
if (_cookies == null)
_cookies = new Cookies(getHttpChannel().getHttpConfiguration().getCookieCompliance());
_cookies.addCookieField(c);
if (field.getHeader()==HttpHeader.COOKIE)
{
if (_cookies==null)
_cookies = new Cookies(getHttpChannel().getHttpConfiguration().getCookieCompliance());
_cookies.addCookieField(field.getValue());
}
}
//Javadoc for Request.getCookies() stipulates null for no cookies

View File

@ -31,11 +31,11 @@ import org.eclipse.jetty.server.Server;
/**
* RequestLogHandler.
* This handler can be used to wrap an individual context for context logging.
* To set a {@link RequestLog} instance for the entire {@link Server}, use
* {@link Server#setRequestLog(RequestLog)} instead of this handler.
*
* <p>This handler provides an alternate way (other than {@link Server#setRequestLog(RequestLog)})
* to log request, that can be applied to a particular handler (eg context).
* This handler can be used to wrap an individual context for context logging, or can be listed
* prior to a handler.
* </p>
* @see Server#setRequestLog(RequestLog)
*/
public class RequestLogHandler extends HandlerWrapper

View File

@ -158,19 +158,12 @@ public class SessionHandler extends ScopedHandler
* has its session completed as the request exits the context.
*/
public class SessionAsyncListener implements AsyncListener
{
private Session _session;
public SessionAsyncListener(Session session)
{
_session = session;
}
{
@Override
public void onComplete(AsyncEvent event) throws IOException
{
//An async request has completed, so we can complete the session
complete(((HttpServletRequest)event.getAsyncContext().getRequest()).getSession(false));
complete(Request.getBaseRequest(event.getAsyncContext().getRequest()).getSession(false));
}
@Override
@ -182,7 +175,7 @@ public class SessionHandler extends ScopedHandler
@Override
public void onError(AsyncEvent event) throws IOException
{
complete(((HttpServletRequest)event.getAsyncContext().getRequest()).getSession(false));
complete(Request.getBaseRequest(event.getAsyncContext().getRequest()).getSession(false));
}
@Override
@ -190,7 +183,6 @@ public class SessionHandler extends ScopedHandler
{
event.getAsyncContext().addListener(this);
}
}
static final HttpSessionContext __nullSessionContext=new HttpSessionContext()
@ -251,6 +243,7 @@ public class SessionHandler extends ScopedHandler
protected Scheduler _scheduler;
protected boolean _ownScheduler = false;
protected final SessionAsyncListener _sessionAsyncListener = new SessionAsyncListener();
@ -381,7 +374,7 @@ public class SessionHandler extends ScopedHandler
{
if (request.isAsyncStarted() && request.getDispatcherType() == DispatcherType.REQUEST)
{
request.getAsyncContext().addListener(new SessionAsyncListener(session));
request.getAsyncContext().addListener(_sessionAsyncListener);
}
else
{

View File

@ -1,194 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2018 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.server.handler;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.RequestLog;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
/**
* Testing oddball request scenarios (like error 400) where the error should
* be logged
*/
@Tag("Unstable")
@Disabled
public class BadRequestLogHandlerTest
{
private static final Logger LOG = Log.getLogger(BadRequestLogHandlerTest.class);
public static class CaptureLog extends AbstractLifeCycle implements RequestLog
{
public List<String> captured = new ArrayList<>();
@Override
public void log(Request request, Response response)
{
int status = response.getCommittedMetaData().getStatus();
captured.add(String.format("%s %s %s %03d",request.getMethod(),request.getHttpURI(),request.getProtocol(),status));
}
}
private static class HelloHandler extends AbstractHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
response.setContentType("text/plain");
response.getWriter().print("Hello World");
baseRequest.setHandled(true);
}
}
public static Stream<Arguments> data()
{
List<Object[]> data = new ArrayList<>();
data.add(new String[]{ "GET /bad VER\r\n"
+ "Host: localhost\r\n"
+ "Connection: close\r\n\r\n" ,
"GET <invalidrequest> HTTP/1.1 400" });
data.add(new String[]{ "GET /%%adsasd HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: close\r\n\r\n" ,
"GET <invalidrequest> HTTP/1.1 400" });
return data.stream().map(Arguments::of);
}
@ParameterizedTest
@MethodSource("data")
public void testLogHandler(String requestHeader, String expectedLog) throws Exception
{
Server server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(0);
server.setConnectors(new Connector[] { connector });
CaptureLog captureLog = new CaptureLog();
RequestLogHandler requestLog = new RequestLogHandler();
requestLog.setRequestLog(captureLog);
requestLog.setHandler(new HelloHandler());
server.setHandler(requestLog);
try
{
server.start();
String host = connector.getHost();
if (host == null)
{
host = "localhost";
}
InetAddress destAddr = InetAddress.getByName(host);
int port = connector.getLocalPort();
SocketAddress endpoint = new InetSocketAddress(destAddr,port);
Socket socket = new Socket();
socket.setSoTimeout(1000);
socket.connect(endpoint);
assertTimeoutPreemptively(Duration.ofSeconds(4), ()-> {
try (OutputStream out = socket.getOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(out, StandardCharsets.UTF_8);
InputStream in = socket.getInputStream();
InputStreamReader reader = new InputStreamReader(in, StandardCharsets.UTF_8))
{
StringReader request = new StringReader(requestHeader);
IO.copy(request, writer);
writer.flush();
StringWriter response = new StringWriter();
IO.copy(reader, response);
LOG.info("Response: {}", response);
}
finally
{
socket.close();
}
});
assertRequestLog(expectedLog, captureLog);
}
finally
{
server.stop();
}
}
private void assertRequestLog(final String expectedLog, CaptureLog captureLog)
{
int captureCount = captureLog.captured.size();
if (captureCount != 1)
{
LOG.warn("Capture Log size is {}, expected to be 1",captureCount);
if (captureCount > 1)
{
for (int i = 0; i < captureCount; i++)
{
LOG.warn("[{}] {}",i,captureLog.captured.get(i));
}
}
assertThat("Capture Log Entry Count",captureLog.captured.size(),is(1));
}
String actual = captureLog.captured.get(0);
assertThat("Capture Log",actual,is(expectedLog));
}
}

View File

@ -1,886 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2018 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.server.handler;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.HttpChannelState;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.RequestLog;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.toolchain.test.IO;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.log.StacklessLogging;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
/**
* Tests for RequestLogHandler behavior.
* <p>
* Tests different request handler behavior against different server+error configurations
*/
@Tag("Unstable")
@Disabled
public class RequestLogHandlerTest
{
private static final Logger LOG = Log.getLogger(RequestLogHandlerTest.class);
public static class CaptureLog extends AbstractLifeCycle implements RequestLog
{
public List<String> captured = new ArrayList<>();
@Override
public void log(Request request, Response response)
{
int status = response.getCommittedMetaData().getStatus();
captured.add(String.format("%s %s %s %03d",request.getMethod(),request.getRequestURI(),request.getProtocol(),status));
}
}
private static abstract class AbstractTestHandler extends AbstractHandler
{
@Override
public String toString()
{
return this.getClass().getSimpleName();
}
}
private static class HelloHandler extends AbstractTestHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
response.setContentType("text/plain");
response.getWriter().print("Hello World");
baseRequest.setHandled(true);
}
}
private static class ResponseSendErrorHandler extends AbstractTestHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
response.sendError(500,"Whoops");
baseRequest.setHandled(true);
}
}
private static class ServletExceptionHandler extends AbstractTestHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
throw new ServletException("Whoops");
}
}
private static class IOExceptionHandler extends AbstractTestHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
throw new IOException("Whoops");
}
}
private static class RuntimeExceptionHandler extends AbstractTestHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
throw new RuntimeException("Whoops");
}
}
private static class AsyncOnTimeoutCompleteHandler extends AbstractTestHandler implements AsyncListener
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
AsyncContext ac = request.startAsync();
ac.setTimeout(1000);
ac.addListener(this);
baseRequest.setHandled(true);
}
@Override
public void onTimeout(AsyncEvent event) throws IOException
{
event.getAsyncContext().complete();
}
@Override
public void onStartAsync(AsyncEvent event) throws IOException
{
}
@Override
public void onError(AsyncEvent event) throws IOException
{
}
@Override
public void onComplete(AsyncEvent event) throws IOException
{
}
}
private static class AsyncOnTimeoutCompleteUnhandledHandler extends AbstractTestHandler implements AsyncListener
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
AsyncContext ac = request.startAsync();
ac.setTimeout(1000);
ac.addListener(this);
}
@Override
public void onTimeout(AsyncEvent event) throws IOException
{
event.getAsyncContext().complete();
}
@Override
public void onStartAsync(AsyncEvent event) throws IOException
{
}
@Override
public void onError(AsyncEvent event) throws IOException
{
}
@Override
public void onComplete(AsyncEvent event) throws IOException
{
}
}
private static class AsyncOnTimeoutDispatchHandler extends AbstractTestHandler implements AsyncListener
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
if (request.getAttribute("deep") == null)
{
AsyncContext ac = request.startAsync();
ac.setTimeout(1000);
ac.addListener(this);
baseRequest.setHandled(true);
request.setAttribute("deep",true);
}
}
@Override
public void onTimeout(AsyncEvent event) throws IOException
{
event.getAsyncContext().dispatch();
}
@Override
public void onStartAsync(AsyncEvent event) throws IOException
{
}
@Override
public void onError(AsyncEvent event) throws IOException
{
}
@Override
public void onComplete(AsyncEvent event) throws IOException
{
}
}
private static class AsyncOnStartIOExceptionHandler extends AbstractTestHandler implements AsyncListener
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
AsyncContext ac = request.startAsync();
ac.setTimeout(1000);
ac.addListener(this);
baseRequest.setHandled(true);
}
@Override
public void onTimeout(AsyncEvent event) throws IOException
{
}
@Override
public void onStartAsync(AsyncEvent event) throws IOException
{
event.getAsyncContext().complete();
throw new IOException("Whoops");
}
@Override
public void onError(AsyncEvent event) throws IOException
{
LOG.warn("onError() -> {}",event);
}
@Override
public void onComplete(AsyncEvent event) throws IOException
{
}
}
public static class OKErrorHandler extends ErrorHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
{
if (baseRequest.isHandled() || response.isCommitted())
{
return;
}
// collect error details
String reason = (response instanceof Response)?((Response)response).getReason():null;
int status = response.getStatus();
// intentionally set response status to OK (this is a test to see what is actually logged)
response.setStatus(200);
response.setContentType("text/plain");
PrintWriter out = response.getWriter();
out.printf("Error %d: %s%n",status,reason);
baseRequest.setHandled(true);
}
}
public static class DispatchErrorHandler extends ErrorHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
{
if (baseRequest.isHandled() || response.isCommitted())
{
return;
}
RequestDispatcher dispatcher = request.getRequestDispatcher("/errorok/");
assertThat("Dispatcher", dispatcher, notNullValue());
try
{
dispatcher.forward(request,response);
}
catch (ServletException e)
{
throw new IOException("Dispatch.forward failed",e);
}
}
}
public static class AltErrorHandler extends ErrorHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
{
if (baseRequest.isHandled() || response.isCommitted())
{
return;
}
// collect error details
String reason = (response instanceof Response)?((Response)response).getReason():null;
int status = response.getStatus();
// intentionally set response status to OK (this is a test to see what is actually logged)
response.setContentType("text/plain");
PrintWriter out = response.getWriter();
out.printf("Error %d: %s%n",status,reason);
baseRequest.setHandled(true);
}
}
public static Stream<Arguments> data()
{
List<Object[]> data = new ArrayList<>();
data.add(new Object[] { new HelloHandler(), "/test/", "GET /test/ HTTP/1.1 200" });
data.add(new Object[] { new AsyncOnTimeoutCompleteHandler(), "/test/", "GET /test/ HTTP/1.1 200" });
data.add(new Object[] { new AsyncOnTimeoutCompleteUnhandledHandler(), "/test/", "GET /test/ HTTP/1.1 200" });
data.add(new Object[] { new AsyncOnTimeoutDispatchHandler(), "/test/", "GET /test/ HTTP/1.1 200" });
data.add(new Object[] { new AsyncOnStartIOExceptionHandler(), "/test/", "GET /test/ HTTP/1.1 500" });
data.add(new Object[] { new ResponseSendErrorHandler(), "/test/", "GET /test/ HTTP/1.1 500" });
data.add(new Object[] { new ServletExceptionHandler(), "/test/", "GET /test/ HTTP/1.1 500" });
data.add(new Object[] { new IOExceptionHandler(), "/test/", "GET /test/ HTTP/1.1 500" });
data.add(new Object[] { new RuntimeExceptionHandler(), "/test/", "GET /test/ HTTP/1.1 500" });
return data.stream().map(Arguments::of);
}
/**
* Test a RequestLogHandler at the end of a HandlerCollection. all other configuration on server at defaults.
* @throws Exception if test failure
*/
@ParameterizedTest
@MethodSource("data")
public void testLogHandlerCollection(Handler testHandler, String requestPath, String expectedLogEntry) throws Exception
{
Server server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(0);
server.setConnectors(new Connector[] { connector });
CaptureLog captureLog = new CaptureLog();
RequestLogHandler requestLog = new RequestLogHandler();
requestLog.setRequestLog(captureLog);
HandlerCollection handlers = new HandlerCollection();
handlers.setHandlers(new Handler[] { testHandler, requestLog });
server.setHandler(handlers);
try
{
server.start();
String host = connector.getHost();
if (host == null)
{
host = "localhost";
}
int port = connector.getLocalPort();
URI serverUri = new URI("http",null,host,port,requestPath,null,null);
// Make call to test handler
HttpURLConnection connection = (HttpURLConnection)serverUri.toURL().openConnection();
try
{
connection.setAllowUserInteraction(false);
// log response status code
int statusCode = connection.getResponseCode();
LOG.debug("Response Status Code: {}",statusCode);
if (statusCode == 200)
{
// collect response message and log it
String content = getResponseContent(connection);
LOG.debug("Response Content: {}",content);
}
}
finally
{
connection.disconnect();
}
assertRequestLog(expectedLogEntry, captureLog);
}
finally
{
server.stop();
}
}
@ParameterizedTest
@MethodSource("data")
public void testMultipleLogHandlers(Handler testHandler, String requestPath, String expectedLogEntry) throws Exception
{
Server server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(0);
server.setConnectors(new Connector[]{connector});
List<CaptureLog> captureLogs = new ArrayList<>();
List<Handler> handlerList = new ArrayList<>();
handlerList.add(testHandler);
for (int i = 0; i < 4; ++i) {
CaptureLog captureLog = new CaptureLog();
captureLogs.add(captureLog);
RequestLogHandler requestLog = new RequestLogHandler();
requestLog.setRequestLog(captureLog);
handlerList.add(requestLog);
}
HandlerCollection handlers = new HandlerCollection();
handlers.setHandlers(handlerList.toArray(new Handler[0]));
server.setHandler(handlers);
try
{
server.start();
String host = connector.getHost();
if (host == null)
{
host = "localhost";
}
int port = connector.getLocalPort();
URI serverUri = new URI("http",null,host,port,requestPath,null,null);
// Make call to test handler
HttpURLConnection connection = (HttpURLConnection)serverUri.toURL().openConnection();
try
{
connection.setAllowUserInteraction(false);
// log response status code
int statusCode = connection.getResponseCode();
LOG.debug("Response Status Code: {}",statusCode);
if (statusCode == 200)
{
// collect response message and log it
String content = getResponseContent(connection);
LOG.debug("Response Content: {}",content);
}
}
finally
{
connection.disconnect();
}
for (CaptureLog captureLog:captureLogs)
assertRequestLog(expectedLogEntry, captureLog);
}
finally
{
server.stop();
}
}
/**
* Test a RequestLogHandler at the end of a HandlerCollection and also with the default ErrorHandler as server bean in place.
* @throws Exception if test failure
*/
@ParameterizedTest
@MethodSource("data")
public void testLogHandlerCollection_ErrorHandler_ServerBean(Handler testHandler, String requestPath, String expectedLogEntry) throws Exception
{
Server server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(0);
server.setConnectors(new Connector[] { connector });
ErrorHandler errorHandler = new ErrorHandler();
server.addBean(errorHandler);
CaptureLog captureLog = new CaptureLog();
RequestLogHandler requestLog = new RequestLogHandler();
requestLog.setRequestLog(captureLog);
HandlerCollection handlers = new HandlerCollection();
handlers.setHandlers(new Handler[] { testHandler, requestLog });
server.setHandler(handlers);
try
{
server.start();
String host = connector.getHost();
if (host == null)
{
host = "localhost";
}
int port = connector.getLocalPort();
URI serverUri = new URI("http",null,host,port,requestPath,null,null);
// Make call to test handler
HttpURLConnection connection = (HttpURLConnection)serverUri.toURL().openConnection();
try
{
connection.setAllowUserInteraction(false);
// log response status code
int statusCode = connection.getResponseCode();
LOG.debug("Response Status Code: {}",statusCode);
if (statusCode == 200)
{
// collect response message and log it
String content = getResponseContent(connection);
LOG.debug("Response Content: {}",content);
}
}
finally
{
connection.disconnect();
}
assertRequestLog(expectedLogEntry, captureLog);
}
finally
{
server.stop();
}
}
/**
* Test a RequestLogHandler at the end of a HandlerCollection and also with the ErrorHandler in place.
* @throws Exception if test failure
*/
@ParameterizedTest
@MethodSource("data")
public void testLogHandlerCollection_AltErrorHandler(Handler testHandler, String requestPath, String expectedLogEntry) throws Exception
{
Server server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(0);
server.setConnectors(new Connector[] { connector });
AltErrorHandler errorDispatcher = new AltErrorHandler();
server.addBean(errorDispatcher);
ContextHandlerCollection contexts = new ContextHandlerCollection();
ContextHandler errorContext = new ContextHandler("/errorpage");
errorContext.setHandler(new AltErrorHandler());
ContextHandler testContext = new ContextHandler("/test");
testContext.setHandler(testHandler);
contexts.addHandler(errorContext);
contexts.addHandler(testContext);
RequestLogHandler requestLog = new RequestLogHandler();
CaptureLog captureLog = new CaptureLog();
requestLog.setRequestLog(captureLog);
HandlerCollection handlers = new HandlerCollection();
handlers.setHandlers(new Handler[] { contexts, requestLog });
server.setHandler(handlers);
try
{
server.start();
String host = connector.getHost();
if (host == null)
{
host = "localhost";
}
int port = connector.getLocalPort();
URI serverUri = new URI("http",null,host,port,requestPath,null,null);
// Make call to test handler
HttpURLConnection connection = (HttpURLConnection)serverUri.toURL().openConnection();
try
{
connection.setAllowUserInteraction(false);
// log response status code
int statusCode = connection.getResponseCode();
LOG.debug("Response Status Code: {}",statusCode);
if (statusCode == 200)
{
// collect response message and log it
String content = getResponseContent(connection);
LOG.debug("Response Content: {}",content);
}
}
finally
{
connection.disconnect();
}
assertRequestLog(expectedLogEntry, captureLog);
}
finally
{
server.stop();
}
}
/**
* Test a RequestLogHandler at the end of a HandlerCollection and also with the ErrorHandler in place.
* @throws Exception if test failure
*/
@ParameterizedTest
@MethodSource("data")
public void testLogHandlerCollection_OKErrorHandler(Handler testHandler, String requestPath, String expectedLogEntry) throws Exception
{
Server server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(0);
server.setConnectors(new Connector[] { connector });
OKErrorHandler errorDispatcher = new OKErrorHandler();
server.addBean(errorDispatcher);
ContextHandlerCollection contexts = new ContextHandlerCollection();
ContextHandler errorContext = new ContextHandler("/errorpage");
errorContext.setHandler(new AltErrorHandler());
ContextHandler testContext = new ContextHandler("/test");
testContext.setHandler(testHandler);
contexts.addHandler(errorContext);
contexts.addHandler(testContext);
RequestLogHandler requestLog = new RequestLogHandler();
CaptureLog captureLog = new CaptureLog();
requestLog.setRequestLog(captureLog);
HandlerCollection handlers = new HandlerCollection();
handlers.setHandlers(new Handler[] { contexts, requestLog });
server.setHandler(handlers);
try
{
server.start();
String host = connector.getHost();
if (host == null)
{
host = "localhost";
}
int port = connector.getLocalPort();
URI serverUri = new URI("http",null,host,port,requestPath,null,null);
// Make call to test handler
HttpURLConnection connection = (HttpURLConnection)serverUri.toURL().openConnection();
try
{
connection.setAllowUserInteraction(false);
// log response status code
int statusCode = connection.getResponseCode();
LOG.debug("Response Status Code: {}",statusCode);
if (statusCode == 200)
{
// collect response message and log it
String content = getResponseContent(connection);
LOG.debug("Response Content: {}",content);
}
}
finally
{
connection.disconnect();
}
assertRequestLog(expectedLogEntry, captureLog);
}
finally
{
server.stop();
}
}
/**
* Test a RequestLogHandler at the end of a HandlerCollection and also with the ErrorHandler in place.
* @throws Exception if test failure
*/
@ParameterizedTest
@MethodSource("data")
public void testLogHandlerCollection_DispatchErrorHandler(Handler testHandler, String requestPath, String expectedLogEntry) throws Exception
{
Server server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(0);
server.setConnectors(new Connector[] { connector });
DispatchErrorHandler errorDispatcher = new DispatchErrorHandler();
server.addBean(errorDispatcher);
ContextHandlerCollection contexts = new ContextHandlerCollection();
ContextHandler errorContext = new ContextHandler("/errorok");
errorContext.setHandler(new OKErrorHandler());
ContextHandler testContext = new ContextHandler("/test");
testContext.setHandler(testHandler);
contexts.addHandler(errorContext);
contexts.addHandler(testContext);
RequestLogHandler requestLog = new RequestLogHandler();
CaptureLog captureLog = new CaptureLog();
requestLog.setRequestLog(captureLog);
HandlerCollection handlers = new HandlerCollection();
handlers.setHandlers(new Handler[] { contexts, requestLog });
server.setHandler(handlers);
try
{
server.start();
String host = connector.getHost();
if (host == null)
{
host = "localhost";
}
int port = connector.getLocalPort();
URI serverUri = new URI("http",null,host,port,requestPath,null,null);
// Make call to test handler
HttpURLConnection connection = (HttpURLConnection)serverUri.toURL().openConnection();
try
{
connection.setAllowUserInteraction(false);
// log response status code
int statusCode = connection.getResponseCode();
LOG.debug("Response Status Code: {}",statusCode);
if (statusCode == 200)
{
// collect response message and log it
String content = getResponseContent(connection);
LOG.debug("Response Content: {}",content);
}
}
finally
{
connection.disconnect();
}
assertRequestLog(expectedLogEntry, captureLog);
}
finally
{
server.stop();
}
}
@ParameterizedTest
@MethodSource("data")
public void testLogHandlerWrapped(Handler testHandler, String requestPath, String expectedLogEntry) throws Exception
{
Server server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(0);
server.setConnectors(new Connector[] { connector });
CaptureLog captureLog = new CaptureLog();
RequestLogHandler requestLog = new RequestLogHandler();
requestLog.setRequestLog(captureLog);
requestLog.setHandler(testHandler);
server.setHandler(requestLog);
try (StacklessLogging ignore = new StacklessLogging(HttpChannel.class,HttpChannelState.class))
{
server.start();
String host = connector.getHost();
if (host == null)
{
host = "localhost";
}
int port = connector.getLocalPort();
URI serverUri = new URI("http",null,host,port,requestPath,null,null);
// Make call to test handler
HttpURLConnection connection = (HttpURLConnection)serverUri.toURL().openConnection();
try
{
connection.setAllowUserInteraction(false);
// log response status code
int statusCode = connection.getResponseCode();
LOG.info("Response Status Code: {}",statusCode);
if (statusCode == 200)
{
// collect response message and log it
String content = getResponseContent(connection);
LOG.info("Response Content: {}",content);
}
}
finally
{
connection.disconnect();
}
assertRequestLog(expectedLogEntry, captureLog);
}
finally
{
server.stop();
}
}
private void assertRequestLog(final String expectedLogEntry, CaptureLog captureLog)
{
int captureCount = captureLog.captured.size();
if (captureCount != 1)
{
LOG.warn("Capture Log size is {}, expected to be 1",captureCount);
if (captureCount > 1)
{
for (int i = 0; i < captureCount; i++)
{
LOG.warn("[{}] {}",i,captureLog.captured.get(i));
}
}
assertThat("Capture Log Entry Count",captureLog.captured.size(),is(1));
}
String actual = captureLog.captured.get(0);
assertThat("Capture Log",actual,is(expectedLogEntry));
}
private String getResponseContent(HttpURLConnection connection) throws IOException
{
try (InputStream in = connection.getInputStream(); InputStreamReader reader = new InputStreamReader(in))
{
StringWriter writer = new StringWriter();
IO.copy(reader,writer);
return writer.toString();
}
}
}

View File

@ -18,31 +18,47 @@
package org.eclipse.jetty.server.handler;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.Exchanger;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.server.AbstractNCSARequestLog;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.RequestLog;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
public class RequestLogTest
{
Exchanger<String> _log;
Log _log;
Server _server;
LocalConnector _connector;
@ -50,19 +66,35 @@ public class RequestLogTest
@BeforeEach
public void before() throws Exception
{
_log = new Exchanger<String>();
_log = new Log();
_server = new Server();
_connector = new LocalConnector(_server);
_server.addConnector(_connector);
_server.setRequestLog(new Log());
}
void testHandlerServerStart() throws Exception
{
_server.setRequestLog(_log);
_server.setHandler(new TestHandler());
_server.start();
}
private void startServer() throws Exception
{
_server.start();
}
private void makeRequest(String requestPath) throws Exception
{
_connector.getResponse("GET "+requestPath+" HTTP/1.0\r\n\r\n");
}
@AfterEach
public void after() throws Exception
{
_server.stop();
}
@ -70,35 +102,41 @@ public class RequestLogTest
@Test
public void testNotHandled() throws Exception
{
testHandlerServerStart();
_connector.getResponse("GET /foo HTTP/1.0\n\n");
String log = _log.exchange(null,5,TimeUnit.SECONDS);
String log = _log.entries.poll(5,TimeUnit.SECONDS);
assertThat(log,containsString("GET /foo HTTP/1.0\" 404 "));
}
@Test
public void testRequestLine() throws Exception
{
testHandlerServerStart();
_connector.getResponse("GET /foo?data=1 HTTP/1.0\nhost: host:80\n\n");
String log = _log.exchange(null,5,TimeUnit.SECONDS);
String log = _log.entries.poll(5,TimeUnit.SECONDS);
assertThat(log,containsString("GET /foo?data=1 HTTP/1.0\" 200 "));
_connector.getResponse("GET //bad/foo?data=1 HTTP/1.0\n\n");
log = _log.exchange(null,5,TimeUnit.SECONDS);
log = _log.entries.poll(5,TimeUnit.SECONDS);
assertThat(log,containsString("GET //bad/foo?data=1 HTTP/1.0\" 200 "));
_connector.getResponse("GET http://host:80/foo?data=1 HTTP/1.0\n\n");
log = _log.exchange(null,5,TimeUnit.SECONDS);
log = _log.entries.poll(5,TimeUnit.SECONDS);
assertThat(log,containsString("GET http://host:80/foo?data=1 HTTP/1.0\" 200 "));
}
@Test
public void testHTTP10Host() throws Exception
{
testHandlerServerStart();
_connector.getResponse(
"GET /foo?name=value HTTP/1.0\n"+
"Host: servername\n"+
"\n");
String log = _log.exchange(null,5,TimeUnit.SECONDS);
String log = _log.entries.poll(5,TimeUnit.SECONDS);
assertThat(log,containsString("GET /foo?name=value"));
assertThat(log,containsString(" 200 "));
}
@ -106,11 +144,13 @@ public class RequestLogTest
@Test
public void testHTTP11() throws Exception
{
testHandlerServerStart();
_connector.getResponse(
"GET /foo?name=value HTTP/1.1\n"+
"Host: servername\n"+
"\n");
String log = _log.exchange(null,5,TimeUnit.SECONDS);
String log = _log.entries.poll(5,TimeUnit.SECONDS);
assertThat(log,containsString("GET /foo?name=value"));
assertThat(log,containsString(" 200 "));
}
@ -118,11 +158,13 @@ public class RequestLogTest
@Test
public void testAbsolute() throws Exception
{
testHandlerServerStart();
_connector.getResponse(
"GET http://hostname:8888/foo?name=value HTTP/1.1\n"+
"Host: servername\n"+
"\n");
String log = _log.exchange(null,5,TimeUnit.SECONDS);
String log = _log.entries.poll(5,TimeUnit.SECONDS);
assertThat(log,containsString("GET http://hostname:8888/foo?name=value"));
assertThat(log,containsString(" 200 "));
}
@ -130,8 +172,10 @@ public class RequestLogTest
@Test
public void testQuery() throws Exception
{
testHandlerServerStart();
_connector.getResponse("GET /foo?name=value HTTP/1.0\n\n");
String log = _log.exchange(null,5,TimeUnit.SECONDS);
String log = _log.entries.poll(5,TimeUnit.SECONDS);
assertThat(log,containsString("GET /foo?name=value"));
assertThat(log,containsString(" 200 "));
}
@ -139,8 +183,10 @@ public class RequestLogTest
@Test
public void testSmallData() throws Exception
{
testHandlerServerStart();
_connector.getResponse("GET /foo?data=42 HTTP/1.0\n\n");
String log = _log.exchange(null,5,TimeUnit.SECONDS);
String log = _log.entries.poll(5,TimeUnit.SECONDS);
assertThat(log,containsString("GET /foo?"));
assertThat(log,containsString(" 200 42 "));
}
@ -148,8 +194,10 @@ public class RequestLogTest
@Test
public void testBigData() throws Exception
{
testHandlerServerStart();
_connector.getResponse("GET /foo?data=102400 HTTP/1.0\n\n");
String log = _log.exchange(null,5,TimeUnit.SECONDS);
String log = _log.entries.poll(5,TimeUnit.SECONDS);
assertThat(log,containsString("GET /foo?"));
assertThat(log,containsString(" 200 102400 "));
}
@ -157,8 +205,10 @@ public class RequestLogTest
@Test
public void testStatus() throws Exception
{
testHandlerServerStart();
_connector.getResponse("GET /foo?status=206 HTTP/1.0\n\n");
String log = _log.exchange(null,5,TimeUnit.SECONDS);
String log = _log.entries.poll(5,TimeUnit.SECONDS);
assertThat(log,containsString("GET /foo?"));
assertThat(log,containsString(" 206 0 "));
}
@ -166,8 +216,10 @@ public class RequestLogTest
@Test
public void testStatusData() throws Exception
{
testHandlerServerStart();
_connector.getResponse("GET /foo?status=206&data=42 HTTP/1.0\n\n");
String log = _log.exchange(null,5,TimeUnit.SECONDS);
String log = _log.entries.poll(5,TimeUnit.SECONDS);
assertThat(log,containsString("GET /foo?"));
assertThat(log,containsString(" 206 42 "));
}
@ -175,83 +227,460 @@ public class RequestLogTest
@Test
public void testBadRequest() throws Exception
{
testHandlerServerStart();
_connector.getResponse("XXXXXXXXXXXX\n\n");
String log = _log.exchange(null,5,TimeUnit.SECONDS);
String log = _log.entries.poll(5,TimeUnit.SECONDS);
assertThat(log,containsString("\"- - -\""));
assertThat(log,containsString(" 400 0 "));
assertThat(log,containsString(" 400 "));
}
@Test
public void testBadCharacter() throws Exception
{
testHandlerServerStart();
_connector.getResponse("METHOD /f\00o HTTP/1.0\n\n");
String log = _log.exchange(null,5,TimeUnit.SECONDS);
String log = _log.entries.poll(5,TimeUnit.SECONDS);
assertThat(log,containsString("\"- - -\""));
assertThat(log,containsString(" 400 0 "));
assertThat(log,containsString(" 400 "));
}
@Test
public void testBadVersion() throws Exception
{
testHandlerServerStart();
_connector.getResponse("METHOD /foo HTTP/9\n\n");
String log = _log.exchange(null,5,TimeUnit.SECONDS);
String log = _log.entries.poll(5,TimeUnit.SECONDS);
assertThat(log,containsString("\"- - -\""));
assertThat(log,containsString(" 400 0 "));
assertThat(log,containsString(" 400 "));
}
@Test
public void testLongURI() throws Exception
{
testHandlerServerStart();
char[] chars = new char[10000];
Arrays.fill(chars,'o');
String ooo = new String(chars);
_connector.getResponse("METHOD /f"+ooo+" HTTP/1.0\n\n");
String log = _log.exchange(null,5,TimeUnit.SECONDS);
String log = _log.entries.poll(5,TimeUnit.SECONDS);
assertThat(log,containsString("\"- - -\""));
assertThat(log,containsString(" 414 0 "));
assertThat(log,containsString(" 414 "));
}
@Test
public void testLongHeader() throws Exception
{
testHandlerServerStart();
char[] chars = new char[10000];
Arrays.fill(chars,'o');
String ooo = new String(chars);
_connector.getResponse("METHOD /foo HTTP/1.0\name: f+"+ooo+"\n\n");
String log = _log.exchange(null,5,TimeUnit.SECONDS);
String log = _log.entries.poll(5,TimeUnit.SECONDS);
assertThat(log,containsString("\"METHOD /foo HTTP/1.0\""));
assertThat(log,containsString(" 431 0 "));
assertThat(log,containsString(" 431 "));
}
@Test
public void testBadRequestNoHost() throws Exception
{
testHandlerServerStart();
_connector.getResponse("GET /foo HTTP/1.1\n\n");
String log = _log.exchange(null,5,TimeUnit.SECONDS);
String log = _log.entries.poll(5,TimeUnit.SECONDS);
assertThat(log,containsString("GET /foo "));
assertThat(log,containsString(" 400 0 "));
assertThat(log,containsString(" 400 "));
}
@Test
public void testUseragentWithout() throws Exception
{
testHandlerServerStart();
_connector.getResponse("GET http://[:1]/foo HTTP/1.1\nReferer: http://other.site\n\n");
String log = _log.exchange(null,5,TimeUnit.SECONDS);
String log = _log.entries.poll(5,TimeUnit.SECONDS);
assertThat(log,containsString("GET http://[:1]/foo "));
assertThat(log,containsString(" 400 0 \"http://other.site\" \"-\" - "));
assertThat(log,containsString(" 400 50 \"http://other.site\" \"-\" - "));
}
@Test
public void testUseragentWith() throws Exception
{
testHandlerServerStart();
_connector.getResponse("GET http://[:1]/foo HTTP/1.1\nReferer: http://other.site\nUser-Agent: Mozilla/5.0 (test)\n\n");
String log = _log.exchange(null,5,TimeUnit.SECONDS);
String log = _log.entries.poll(5,TimeUnit.SECONDS);
assertThat(log,containsString("GET http://[:1]/foo "));
assertThat(log,containsString(" 400 0 \"http://other.site\" \"Mozilla/5.0 (test)\" - "));
assertThat(log,containsString(" 400 50 \"http://other.site\" \"Mozilla/5.0 (test)\" - "));
}
// Tests from here use these parameters
public static Stream<Arguments> data()
{
List<Object[]> data = new ArrayList<>();
data.add(new Object[] { new NoopHandler(), "/noop", "\"GET /noop HTTP/1.0\" 404" });
data.add(new Object[] { new HelloHandler(), "/hello", "\"GET /hello HTTP/1.0\" 200" });
data.add(new Object[] { new ResponseSendErrorHandler(), "/sendError", "\"GET /sendError HTTP/1.0\" 599" });
data.add(new Object[] { new ServletExceptionHandler(), "/sex", "\"GET /sex HTTP/1.0\" 500" });
data.add(new Object[] { new IOExceptionHandler(), "/ioex", "\"GET /ioex HTTP/1.0\" 500" });
data.add(new Object[] { new RuntimeExceptionHandler(), "/rtex", "\"GET /rtex HTTP/1.0\" 500" });
data.add(new Object[] { new BadMessageHandler(), "/bad", "\"GET /bad HTTP/1.0\" 499" });
data.add(new Object[] { new AbortHandler(), "/bad", "\"GET /bad HTTP/1.0\" 488" });
return data.stream().map(Arguments::of);
}
@ParameterizedTest
@MethodSource("data")
public void testServerRequestLog(Handler testHandler, String requestPath, String expectedLogEntry) throws Exception
{
_server.setRequestLog(_log);
_server.setHandler(testHandler);
startServer();
makeRequest(requestPath);
assertRequestLog(expectedLogEntry, _log);
}
@ParameterizedTest
@MethodSource("data")
public void testLogHandlerWrapper(Handler testHandler, String requestPath, String expectedLogEntry) throws Exception
{
RequestLogHandler handler = new RequestLogHandler();
handler.setRequestLog(_log);
handler.setHandler(testHandler);
_server.setHandler(handler);
startServer();
makeRequest(requestPath);
assertRequestLog(expectedLogEntry, _log);
}
@ParameterizedTest
@MethodSource("data")
public void testLogHandlerCollectionFirst(Handler testHandler, String requestPath, String expectedLogEntry) throws Exception
{
RequestLogHandler handler = new RequestLogHandler();
handler.setRequestLog(_log);
HandlerCollection handlers = new HandlerCollection();
handlers.setHandlers(new Handler[] { handler, testHandler });
_server.setHandler(handlers);
startServer();
makeRequest(requestPath);
assertRequestLog(expectedLogEntry, _log);
}
@ParameterizedTest
@MethodSource("data")
public void testLogHandlerCollectionLast(Handler testHandler, String requestPath, String expectedLogEntry) throws Exception
{
RequestLogHandler handler = new RequestLogHandler();
handler.setRequestLog(_log);
// This is the old ordering of request handler and it cannot well handle thrown exception
Assumptions.assumeTrue(
testHandler instanceof NoopHandler ||
testHandler instanceof HelloHandler ||
testHandler instanceof ResponseSendErrorHandler
);
HandlerCollection handlers = new HandlerCollection();
handlers.setHandlers(new Handler[] { testHandler, handler });
_server.setHandler(handlers);
startServer();
makeRequest(requestPath);
assertRequestLog(expectedLogEntry, _log);
}
@ParameterizedTest
@MethodSource("data")
public void testErrorHandler(Handler testHandler, String requestPath, String expectedLogEntry) throws Exception
{
_server.setRequestLog(_log);
AbstractHandler.ErrorDispatchHandler wrapper = new AbstractHandler.ErrorDispatchHandler()
{
@Override
protected void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
testHandler.handle(target,baseRequest,request,response);
}
};
_server.setHandler(wrapper);
List<String> errors = new ArrayList<>();
ErrorHandler errorHandler = new ErrorHandler()
{
@Override
public void doError(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
{
errors.add(baseRequest.getRequestURI());
super.doError(target, baseRequest, request, response);
}
};
_server.addBean(errorHandler);
startServer();
makeRequest(requestPath);
assertRequestLog(expectedLogEntry, _log);
if (!(testHandler instanceof HelloHandler))
assertThat(errors,contains(requestPath));
}
@ParameterizedTest
@MethodSource("data")
public void testOKErrorHandler(Handler testHandler, String requestPath, String expectedLogEntry) throws Exception
{
_server.setRequestLog(_log);
AbstractHandler.ErrorDispatchHandler wrapper = new AbstractHandler.ErrorDispatchHandler()
{
@Override
protected void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
testHandler.handle(target,baseRequest,request,response);
}
};
_server.setHandler(wrapper);
ErrorHandler errorHandler = new OKErrorHandler();
_server.addBean(errorHandler);
startServer();
makeRequest(requestPath);
expectedLogEntry = "\"GET " + requestPath + " HTTP/1.0\" 200";
assertRequestLog(expectedLogEntry, _log);
}
@ParameterizedTest
@MethodSource("data")
public void testAsyncDispatch(Handler testHandler, String requestPath, String expectedLogEntry) throws Exception
{
_server.setRequestLog(_log);
_server.setHandler(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
if (Boolean.TRUE.equals(request.getAttribute("ASYNC")))
testHandler.handle(target,baseRequest,request,response);
else
{
request.setAttribute("ASYNC",Boolean.TRUE);
AsyncContext ac = request.startAsync();
ac.setTimeout(1000);
ac.dispatch();
baseRequest.setHandled(true);
}
}
});
startServer();
makeRequest(requestPath);
assertRequestLog(expectedLogEntry, _log);
}
@ParameterizedTest
@MethodSource("data")
public void testAsyncComplete(Handler testHandler, String requestPath, String expectedLogEntry) throws Exception
{
_server.setRequestLog(_log);
_server.setHandler(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
if (Boolean.TRUE.equals(request.getAttribute("ASYNC")))
testHandler.handle(target,baseRequest,request,response);
else
{
request.setAttribute("ASYNC",Boolean.TRUE);
AsyncContext ac = request.startAsync();
ac.setTimeout(1000);
baseRequest.setHandled(true);
_server.getThreadPool().execute(()->
{
try
{
try
{
baseRequest.setHandled(false);
testHandler.handle(target, baseRequest, request, response);
if (!baseRequest.isHandled())
response.sendError(404);
}
catch (BadMessageException bad)
{
response.sendError(bad.getCode());
}
catch (Exception e)
{
response.sendError(500);
}
}
catch(Throwable th)
{
throw new RuntimeException(th);
}
ac.complete();
});
}
}
});
startServer();
makeRequest(requestPath);
assertRequestLog(expectedLogEntry, _log);
}
private void assertRequestLog(final String expectedLogEntry, Log log) throws Exception
{
String line = log.entries.poll(5, TimeUnit.SECONDS);
Assertions.assertNotNull(line);
assertThat(line,containsString(expectedLogEntry));
Assertions.assertTrue(log.entries.isEmpty());
}
public static class CaptureLog extends AbstractLifeCycle implements RequestLog
{
public BlockingQueue<String> log = new BlockingArrayQueue<>();
@Override
public void log(Request request, Response response)
{
int status = response.getCommittedMetaData().getStatus();
log.add(String.format("%s %s %s %03d",request.getMethod(),request.getRequestURI(),request.getProtocol(),status));
}
}
private static abstract class AbstractTestHandler extends AbstractHandler
{
@Override
public String toString()
{
return this.getClass().getSimpleName();
}
}
private static class NoopHandler extends AbstractTestHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
}
}
private static class HelloHandler extends AbstractTestHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
response.setContentType("text/plain");
response.getWriter().print("Hello World");
if (baseRequest!=null)
baseRequest.setHandled(true);
}
}
private static class ResponseSendErrorHandler extends AbstractTestHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
response.sendError(599,"expected");
if (baseRequest!=null)
baseRequest.setHandled(true);
}
}
private static class ServletExceptionHandler extends AbstractTestHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
throw new ServletException("expected");
}
}
private static class IOExceptionHandler extends AbstractTestHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
throw new IOException("expected");
}
}
private static class RuntimeExceptionHandler extends AbstractTestHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
throw new RuntimeException("expected");
}
}
private static class BadMessageHandler extends AbstractTestHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
throw new BadMessageException(499);
}
}
private static class AbortHandler extends AbstractTestHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
BadMessageException bad = new BadMessageException(488);
baseRequest.getHttpChannel().abort(bad);
throw bad;
}
}
public static class OKErrorHandler extends ErrorHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
{
if (baseRequest.isHandled() || response.isCommitted())
{
return;
}
// collect error details
String reason = (response instanceof Response)?((Response)response).getReason():null;
int status = response.getStatus();
// intentionally set response status to OK (this is a test to see what is actually logged)
response.setStatus(200);
response.setContentType("text/plain");
PrintWriter out = response.getWriter();
out.printf("Error %d: %s%n",status,reason);
baseRequest.setHandled(true);
}
}
private class Log extends AbstractNCSARequestLog
{
public BlockingQueue<String> entries = new BlockingArrayQueue<>();
Log()
{
super.setExtended(true);
super.setLogLatency(true);
@ -269,7 +698,7 @@ public class RequestLogTest
{
try
{
_log.exchange(requestEntry);
entries.add(requestEntry);
}
catch(Exception e)
{
@ -277,7 +706,7 @@ public class RequestLogTest
}
}
}
private class TestHandler extends AbstractHandler
{
@Override
@ -286,7 +715,7 @@ public class RequestLogTest
String q = request.getQueryString();
if (q==null)
return;
baseRequest.setHandled(true);
for (String action : q.split("\\&"))
{
@ -300,12 +729,12 @@ public class RequestLogTest
response.setStatus(Integer.parseInt(value));
break;
}
case "data":
{
int data = Integer.parseInt(value);
PrintWriter out = response.getWriter();
int w=0;
while (w<data)
{
@ -343,7 +772,7 @@ public class RequestLogTest
response.flushBuffer();
break;
}
case "read":
{
InputStream in = request.getInputStream();

View File

@ -53,14 +53,12 @@ import javax.servlet.http.HttpServletResponseWrapper;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.DebugListener;
import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.HttpChannel.Listener;
import org.eclipse.jetty.server.QuietServletException;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.RequestLog;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
@ -99,15 +97,13 @@ public class AsyncServletTest
_log=new ArrayList<>();
RequestLog log=new Log();
RequestLogHandler logHandler = new RequestLogHandler();
logHandler.setRequestLog(log);
_server.setHandler(logHandler);
_server.setRequestLog(log);
_expectedLogs=1;
_expectedCode="200 ";
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
context.setContextPath("/ctx");
logHandler.setHandler(context);
_server.setHandler(context);
context.addEventListener(new DebugListener());
_errorHandler = new ErrorPageErrorHandler();

View File

@ -53,7 +53,6 @@ import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.toolchain.test.IO;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.log.Log;
@ -313,12 +312,9 @@ public class ServletRequestLogTest
// Next the behavior as defined by etc/jetty-requestlog.xml
// the id="RequestLog"
RequestLogHandler requestLog = new RequestLogHandler();
CaptureLog captureLog = new CaptureLog();
requestLog.setRequestLog(captureLog);
server.setRequestLog(captureLog);
handlers.addHandler(requestLog);
// Lastly, the behavior as defined by deployment of a webapp
// Add the Servlet Context
ServletContextHandler app = new ServletContextHandler(ServletContextHandler.SESSIONS);
@ -404,12 +400,9 @@ public class ServletRequestLogTest
// Next the behavior as defined by etc/jetty-requestlog.xml
// the id="RequestLog"
RequestLogHandler requestLog = new RequestLogHandler();
CaptureLog captureLog = new CaptureLog();
requestLog.setRequestLog(captureLog);
server.setRequestLog(captureLog);
handlers.addHandler(requestLog);
// Lastly, the behavior as defined by deployment of a webapp
// Add the Servlet Context
ServletContextHandler app = new ServletContextHandler(ServletContextHandler.SESSIONS);
@ -493,12 +486,9 @@ public class ServletRequestLogTest
// Next the behavior as defined by etc/jetty-requestlog.xml
// the id="RequestLog"
RequestLogHandler requestLog = new RequestLogHandler();
CaptureLog captureLog = new CaptureLog();
requestLog.setRequestLog(captureLog);
server.setRequestLog(captureLog);
handlers.addHandler(requestLog);
// Lastly, the behavior as defined by deployment of a webapp
// Add the Servlet Context
ServletContextHandler app = new ServletContextHandler(ServletContextHandler.SESSIONS);
@ -587,14 +577,9 @@ public class ServletRequestLogTest
// Next the proposed behavioral change to etc/jetty-requestlog.xml
// the id="RequestLog"
RequestLogHandler requestLog = new RequestLogHandler();
CaptureLog captureLog = new CaptureLog();
requestLog.setRequestLog(captureLog);
Handler origServerHandler = server.getHandler();
requestLog.setHandler(origServerHandler);
server.setHandler(requestLog);
server.setRequestLog(captureLog);
// Lastly, the behavior as defined by deployment of a webapp
// Add the Servlet Context
ServletContextHandler app = new ServletContextHandler(ServletContextHandler.SESSIONS);

View File

@ -40,7 +40,7 @@
<!-- default values are unsupported, but required to be defined for reactor sanity reasons -->
<alpn.version>undefined</alpn.version>
<conscrypt.version>1.1.4</conscrypt.version>
<asm.version>7.0-beta</asm.version>
<asm.version>7.0</asm.version>
<jmh.version>1.21</jmh.version>
<jmhjar.name>benchmarks</jmhjar.name>
<tycho-version>1.2.0</tycho-version>

View File

@ -18,15 +18,6 @@
package org.eclipse.jetty.http.client;
import static org.eclipse.jetty.http.client.Transport.FCGI;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@ -65,6 +56,15 @@ import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
import static org.eclipse.jetty.http.client.Transport.FCGI;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
public class HttpClientContinueTest extends AbstractTest<TransportScenario>
{
@Override
@ -344,13 +344,14 @@ public class HttpClientContinueTest extends AbstractTest<TransportScenario>
}
});
scenario.client.setIdleTimeout(idleTimeout);
scenario.client.setIdleTimeout(2 * idleTimeout);
byte[] content = new byte[1024];
final CountDownLatch latch = new CountDownLatch(1);
scenario.client.newRequest(scenario.newURI())
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.content(new BytesContentProvider(content))
.idleTimeout(idleTimeout, TimeUnit.MILLISECONDS)
.send(new BufferingResponseListener()
{
@Override

View File

@ -18,11 +18,8 @@
package org.eclipse.jetty.http.client;
import static org.eclipse.jetty.http.client.Transport.UNIX_SOCKET;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
@ -30,11 +27,11 @@ import java.util.Locale;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.IntStream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -68,6 +65,9 @@ import org.hamcrest.Matchers;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class HttpClientLoadTest extends AbstractTest<HttpClientLoadTest.LoadTransportScenario>
{
private final Logger logger = Log.getLogger(HttpClientLoadTest.class);
@ -186,7 +186,7 @@ public class HttpClientLoadTest extends AbstractTest<HttpClientLoadTest.LoadTran
// Choose a random method
HttpMethod method = random.nextBoolean() ? HttpMethod.GET : HttpMethod.POST;
boolean ssl = scenario.isTransportSecure();
boolean ssl = scenario.transport.isTlsBased();
// Choose randomly whether to close the connection on the client or on the server
boolean clientClose = false;
@ -196,13 +196,17 @@ public class HttpClientLoadTest extends AbstractTest<HttpClientLoadTest.LoadTran
if (!ssl && random.nextInt(100) < 5)
serverClose = true;
long clientTimeout = 0;
// if (!ssl && random.nextInt(100) < 5)
// clientTimeout = random.nextInt(500) + 500;
int maxContentLength = 64 * 1024;
int contentLength = random.nextInt(maxContentLength) + 1;
test(scenario.getScheme(), host, method.asString(), clientClose, serverClose, contentLength, true, latch, failures);
test(scenario.getScheme(), host, method.asString(), clientClose, serverClose, clientTimeout, contentLength, true, latch, failures);
}
private void test(String scheme, String host, String method, boolean clientClose, boolean serverClose, int contentLength, final boolean checkContentLength, final CountDownLatch latch, final List<String> failures)
private void test(String scheme, String host, String method, boolean clientClose, boolean serverClose, long clientTimeout, int contentLength, final boolean checkContentLength, final CountDownLatch latch, final List<String> failures)
{
long requestId = requestCount.incrementAndGet();
Request request = scenario.client.newRequest(host, scenario.getNetworkConnectorLocalPortInt().orElse(0))
@ -215,6 +219,12 @@ public class HttpClientLoadTest extends AbstractTest<HttpClientLoadTest.LoadTran
else if (serverClose)
request.header("X-Close", "true");
if (clientTimeout > 0)
{
request.header("X-Timeout", String.valueOf(clientTimeout));
request.idleTimeout(clientTimeout, TimeUnit.MILLISECONDS);
}
switch (method)
{
case "GET":
@ -254,12 +264,18 @@ public class HttpClientLoadTest extends AbstractTest<HttpClientLoadTest.LoadTran
{
if (result.isFailed())
{
result.getFailure().printStackTrace();
failures.add("Result failed " + result);
Throwable failure = result.getFailure();
if (!(clientTimeout > 0 && failure instanceof TimeoutException))
{
failure.printStackTrace();
failures.add("Result failed " + result);
}
}
else
{
if (checkContentLength && contentLength.get() != 0)
failures.add("Content length mismatch " + contentLength);
}
if (checkContentLength && contentLength.get() != 0)
failures.add("Content length mismatch " + contentLength);
requestLatch.countDown();
latch.countDown();
@ -288,8 +304,14 @@ public class HttpClientLoadTest extends AbstractTest<HttpClientLoadTest.LoadTran
private class LoadHandler extends AbstractHandler
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
{
baseRequest.setHandled(true);
String timeout = request.getHeader("X-Timeout");
if (timeout != null)
sleep(2 * Integer.parseInt(timeout));
String method = request.getMethod().toUpperCase(Locale.ENGLISH);
switch (method)
{
@ -313,8 +335,18 @@ public class HttpClientLoadTest extends AbstractTest<HttpClientLoadTest.LoadTran
if (Boolean.parseBoolean(request.getHeader("X-Close")))
response.setHeader("Connection", "close");
}
baseRequest.setHandled(true);
private void sleep(long time) throws InterruptedIOException
{
try
{
Thread.sleep(time);
}
catch (InterruptedException x)
{
throw new InterruptedIOException();
}
}
}
@ -329,8 +361,9 @@ public class HttpClientLoadTest extends AbstractTest<HttpClientLoadTest.LoadTran
}
@Override
public Connector newServerConnector( Server server) throws Exception {
if (transport == UNIX_SOCKET)
public Connector newServerConnector( Server server)
{
if (transport == Transport.UNIX_SOCKET)
{
UnixSocketConnector
unixSocketConnector = new UnixSocketConnector( server, provideServerConnectionFactory( transport ));

View File

@ -20,9 +20,14 @@ package org.eclipse.jetty.http.client;
import static org.eclipse.jetty.http.client.Transport.FCGI;
import static org.eclipse.jetty.http.client.Transport.UNIX_SOCKET;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
@ -56,10 +61,10 @@ import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.log.StacklessLogging;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
@ -616,6 +621,7 @@ public class ServerTimeoutsTest extends AbstractTest<TransportScenario>
{
init(transport);
int bytesPerSecond = 20;
scenario.requestLog.clear();
scenario.httpConfig.setMinRequestDataRate(bytesPerSecond);
CountDownLatch handlerLatch = new CountDownLatch(1);
scenario.start(new AbstractHandler.ErrorDispatchHandler()
@ -643,13 +649,15 @@ public class ServerTimeoutsTest extends AbstractTest<TransportScenario>
});
DeferredContentProvider contentProvider = new DeferredContentProvider();
CountDownLatch resultLatch = new CountDownLatch(1);
BlockingQueue<Object> results = new BlockingArrayQueue<>();
scenario.client.newRequest(scenario.newURI())
.content(contentProvider)
.send(result ->
{
if (result.getResponse().getStatus() == HttpStatus.REQUEST_TIMEOUT_408)
resultLatch.countDown();
if (result.isFailed())
results.offer(result.getFailure());
else
results.offer(result.getResponse().getStatus());
});
for (int i = 0; i < 3; ++i)
@ -659,9 +667,17 @@ public class ServerTimeoutsTest extends AbstractTest<TransportScenario>
}
contentProvider.close();
assertThat(scenario.requestLog.poll(5,TimeUnit.SECONDS), containsString(" 408"));
// Request should timeout.
assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
assertTrue(resultLatch.await(5, TimeUnit.SECONDS));
Object result = results.poll(5, TimeUnit.SECONDS);
assertNotNull(result);
if (result instanceof Integer)
assertThat((Integer)result,is(408));
else
assertThat(result,instanceOf(Throwable.class));
}
@ParameterizedTest

View File

@ -25,6 +25,7 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.BlockingQueue;
import javax.servlet.http.HttpServlet;
@ -56,6 +57,7 @@ import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.unixsocket.UnixSocketConnector;
import org.eclipse.jetty.unixsocket.client.HttpClientTransportOverUnixSockets;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.SocketAddressResolver;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -75,6 +77,7 @@ public class TransportScenario
protected String servletPath = "/servlet";
protected HttpClient client;
protected Path sockFile;
protected final BlockingQueue<String> requestLog= new BlockingArrayQueue<>();
public TransportScenario(final Transport transport) throws IOException
{
@ -320,7 +323,15 @@ public class TransportScenario
server.addBean(mbeanContainer);
connector = newServerConnector(server);
server.addConnector(connector);
server.setRequestLog((request, response) ->
{
int status = response.getCommittedMetaData().getStatus();
requestLog.offer(String.format("%s %s %s %03d",request.getMethod(),request.getRequestURI(),request.getProtocol(),status));
});
server.setHandler(handler);
try
{
server.start();
@ -375,4 +386,6 @@ public class TransportScenario
}
}
}
}

View File

@ -34,7 +34,6 @@ import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.server.session.DefaultSessionCache;
import org.eclipse.jetty.server.session.FileSessionDataStore;
@ -107,9 +106,8 @@ public class TestServer
// Handlers
HandlerCollection handlers = new HandlerCollection();
ContextHandlerCollection contexts = new ContextHandlerCollection();
RequestLogHandler requestLogHandler = new RequestLogHandler();
handlers.setHandlers(new Handler[]
{ contexts, new DefaultHandler(), requestLogHandler });
{ contexts, new DefaultHandler() });
// Add restart handler to test the ability to save sessions and restart
RestartHandler restart = new RestartHandler();
@ -127,7 +125,7 @@ public class TestServer
File log=File.createTempFile("jetty-yyyy_mm_dd", "log");
NCSARequestLog requestLog = new NCSARequestLog(log.toString());
requestLog.setExtended(false);
requestLogHandler.setRequestLog(requestLog);
server.setRequestLog(requestLog);
server.setStopAtShutdown(true);