diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java index 69ae92eac5d..7ef790624f2 100644 --- a/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java +++ b/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java @@ -18,17 +18,6 @@ package org.eclipse.jetty.security; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.isIn; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.startsWith; -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; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; @@ -45,7 +34,6 @@ import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; - import javax.servlet.HttpConstraintElement; import javax.servlet.HttpMethodConstraintElement; import javax.servlet.ServletException; @@ -83,6 +71,17 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.isIn; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.startsWith; +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; + public class ConstraintTest { private static final String TEST_REALM = "TestRealm"; @@ -1526,6 +1525,12 @@ public class ConstraintTest UserIdentity.Scope scope = new UserIdentity.Scope() { + @Override + public ContextHandler getContextHandler() + { + return null; + } + @Override public String getContextPath() { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncRequestLogWriter.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncRequestLogWriter.java index ddc7e0d27fc..8f704fd572c 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncRequestLogWriter.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncRequestLogWriter.java @@ -1,3 +1,21 @@ +// +// ======================================================================== +// 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; import java.io.IOException; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/CustomRequestLog.java b/jetty-server/src/main/java/org/eclipse/jetty/server/CustomRequestLog.java index 7a55d8cb16d..098dc1b4200 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/CustomRequestLog.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/CustomRequestLog.java @@ -34,6 +34,7 @@ import javax.servlet.http.Cookie; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.QuotedCSV; import org.eclipse.jetty.http.pathmap.PathMappings; +import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.DateCache; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.component.ContainerLifeCycle; @@ -172,22 +173,8 @@ import static java.lang.invoke.MethodType.methodType; The time, in the form given by an optional format, parameter (default format [18/Sep/2011:19:18:28 -0400] where the last number indicates the timezone offset from GMT.) -

- The format parameter should be in an extended strftime(3) format (potentially localized). - If the format starts with begin: (default) the time is taken at the beginning of the request processing. - If it starts with end: it is the time when the log entry gets written, close to the end of the request processing. - -

In addition to the formats supported by strftime(3), the following format tokens are supported: - -
- sec         number of seconds since the Epoch
- msec        number of milliseconds since the Epoch
- usec        number of microseconds since the Epoch
- msec_frac   millisecond fraction
- usec_frac   microsecond fraction
- 
- - These tokens can not be combined with each other or strftime(3) formatting in the same format string. You can use multiple %{format}t tokens instead. +
+ The format parameter should be in a format supported by {@link DateCache} @@ -263,14 +250,13 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog { protected static final Logger LOG = Log.getLogger(CustomRequestLog.class); - //TODO previous NCSA format includes "" in the append, so %C would print out "cookies" if cookies exist and - without the "" if they do not public static final String NCSA_FORMAT = "%a - %u %t \"%r\" %s %B \"%{Referer}i\" \"%{User-Agent}i\" \"%C\""; + public static final String DEFAULT_DATE_FORMAT = "dd/MMM/yyyy:HH:mm:ss ZZZ"; private static ThreadLocal _buffers = ThreadLocal.withInitial(() -> new StringBuilder(256)); private String[] _ignorePaths; private transient PathMappings _ignorePathMap; - private final static String DEFAULT_DATE_FORMAT = "dd/MMM/yyyy:HH:mm:ss ZZZ"; private Locale _logLocale = Locale.getDefault(); private String _logTimeZone = "GMT"; @@ -445,7 +431,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog private MethodHandle getLogHandle(String formatString) throws NoSuchMethodException, IllegalAccessException { MethodHandle append = MethodHandles.lookup().findStatic(CustomRequestLog.class, "append", methodType(Void.TYPE, String.class, StringBuilder.class)); - MethodHandle logHandle = dropArguments(dropArguments(append.bindTo("\n"), 1, Request.class), 2, Response.class); + MethodHandle logHandle = MethodHandles.lookup().findStatic(CustomRequestLog.class, "logNothing", methodType(Void.TYPE, StringBuilder.class, Request.class, Response.class)); List tokens = getTokens(formatString); Collections.reverse(tokens); @@ -515,13 +501,12 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog private static class Token { - //todo make final - public String code = null; - public String arg = null; - public List modifiers = null; - public boolean negated = false; + public final String code; + public final String arg; + public final List modifiers; + public final boolean negated; - public String literal = null; + public final String literal; public Token(String code, String arg, List modifiers, boolean negated) { @@ -529,9 +514,16 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog this.arg = arg; this.modifiers = modifiers; this.negated = negated; + + this.literal = null; } public Token(String literal) { + this.code = null; + this.arg = null; + this.modifiers = null; + this.negated = false; + this.literal = literal; } @@ -539,6 +531,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog { return(literal != null); } + public boolean isPercentCode() { return(code != null); @@ -578,7 +571,6 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog { case "%": { - //todo use literal specificHandle = dropArguments(dropArguments(append.bindTo("%"), 1, Request.class), 2, Response.class); break; } @@ -773,7 +765,6 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog case "t": { - //todo is this correctly supporting the right formats DateCache logDateCache; if (arg == null || arg.isEmpty()) logDateCache = new DateCache(DEFAULT_DATE_FORMAT, _logLocale , _logTimeZone); @@ -787,7 +778,6 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog break; } - case "T": { if (arg == null) @@ -910,10 +900,11 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog - - //-----------------------------------------------------------------------------------// + private static void logNothing(StringBuilder b, Request request, Response response) + { + } private static void logClientIP(StringBuilder b, Request request, Response response) { @@ -984,8 +975,16 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog private static void logFilename(StringBuilder b, Request request, Response response) { - //TODO verify - append(b, request.getServletContext().getRealPath(request.getPathInfo())); + UserIdentity.Scope scope = request.getUserIdentityScope(); + if (scope==null || scope.getContextHandler()==null) + b.append('-'); + else + { + ContextHandler context = scope.getContextHandler(); + int lengthToStrip = scope.getContextPath().length()>1 ? scope.getContextPath().length() : 0; + String filename = context.getServletContext().getRealPath(request.getPathInfo().substring(lengthToStrip)); + append(b, filename); + } } private static void logRemoteHostName(StringBuilder b, Request request, Response response) @@ -1053,7 +1052,6 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog private static void logRequestHandler(StringBuilder b, Request request, Response response) { - //todo verify append(b, request.getServletName()); } @@ -1072,7 +1070,6 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog private static void logLatencyMicroseconds(StringBuilder b, Request request, Response response) { - //todo can we use nanotime? long latency = System.currentTimeMillis() - request.getTimeStamp(); b.append(TimeUnit.MILLISECONDS.toMicros(latency)); } @@ -1122,14 +1119,13 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog private static void logBytesSent(StringBuilder b, Request request, Response response) { - //todo redirect to logResponseSize - append(b, "?"); + //todo difference between this and logResponseSize + b.append(response.getHttpOutput().getWritten()); } private static void logBytesTransferred(StringBuilder b, Request request, Response response) { - //todo implement: bytesTransferred = bytesReceived+bytesSent - append(b, "?"); + b.append(request.getHttpInput().getContentConsumed() + response.getHttpOutput().getWritten()); } private static void logRequestTrailer(String arg, StringBuilder b, Request request, Response response) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/RequestLogWriter.java b/jetty-server/src/main/java/org/eclipse/jetty/server/RequestLogWriter.java index 5ce235781f3..7015709837d 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/RequestLogWriter.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/RequestLogWriter.java @@ -1,3 +1,21 @@ +// +// ======================================================================== +// 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; import java.io.IOException; @@ -7,7 +25,6 @@ import java.io.Writer; import java.util.TimeZone; import org.eclipse.jetty.util.RolloverFileOutputStream; -import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -163,7 +180,7 @@ public class RequestLogWriter extends AbstractLifeCycle implements RequestLog.Wr if (_writer==null) return; _writer.write(requestEntry); - _writer.write(StringUtil.__LINE_SEPARATOR); + _writer.write(System.lineSeparator()); _writer.flush(); } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Slf4jRequestLogWriter.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Slf4jRequestLogWriter.java index 2dee670cbca..b496283c653 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Slf4jRequestLogWriter.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Slf4jRequestLogWriter.java @@ -1,3 +1,21 @@ +// +// ======================================================================== +// 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; import java.io.IOException; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/UserIdentity.java b/jetty-server/src/main/java/org/eclipse/jetty/server/UserIdentity.java index 1f63407bec5..daa60a0fcdf 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/UserIdentity.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/UserIdentity.java @@ -20,9 +20,10 @@ package org.eclipse.jetty.server; import java.security.Principal; import java.util.Map; - import javax.security.auth.Subject; +import org.eclipse.jetty.server.handler.ContextHandler; + /** * User object that encapsulates user identity and operations such as run-as-role actions, * checking isUserInRole and getUserPrincipal. @@ -64,6 +65,12 @@ public interface UserIdentity */ interface Scope { + /* ------------------------------------------------------------ */ + /** + * @return The context handler that the identity is being considered within + */ + ContextHandler getContextHandler(); + /* ------------------------------------------------------------ */ /** * @return The context path that the identity is being considered within diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/CustomRequestLogTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/CustomRequestLogTest.java index 78679446e3b..dedf48241f2 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/CustomRequestLogTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/CustomRequestLogTest.java @@ -32,21 +32,26 @@ import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.RequestLog; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.BlockingArrayQueue; +import org.eclipse.jetty.util.DateCache; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.lessThanOrEqualTo; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.fail; public class CustomRequestLogTest { - RequestLog _log; + CustomRequestLog _log; Server _server; LocalConnector _connector; BlockingQueue _entries = new BlockingArrayQueue<>(); + BlockingQueue requestTimes = new BlockingArrayQueue<>(); @BeforeEach @@ -72,8 +77,6 @@ public class CustomRequestLogTest _server.stop(); } - - @Test public void testModifier() throws Exception { @@ -81,24 +84,17 @@ public class CustomRequestLogTest _connector.getResponse("GET /error404 HTTP/1.0\nReferer: testReferer\n\n"); String log = _entries.poll(5,TimeUnit.SECONDS); - assertThat(log, is("404: -\n")); + assertThat(log, is("404: -")); _connector.getResponse("GET /error301 HTTP/1.0\nReferer: testReferer\n\n"); log = _entries.poll(5,TimeUnit.SECONDS); - assertThat(log, is("301: -\n")); + assertThat(log, is("301: -")); _connector.getResponse("GET /success HTTP/1.0\nReferer: testReferer\n\n"); log = _entries.poll(5,TimeUnit.SECONDS); - assertThat(log, is("200: testReferer\n")); + assertThat(log, is("200: testReferer")); } - @Test - public void testInvalidArguments() throws Exception - { - fail(); - } - - @Test public void testDoublePercent() throws Exception { @@ -106,7 +102,7 @@ public class CustomRequestLogTest _connector.getResponse("GET / HTTP/1.0\n\n"); String log = _entries.poll(5,TimeUnit.SECONDS); - assertThat(log, is("%%%a\n")); + assertThat(log, is("%%%a")); } @Test @@ -146,11 +142,11 @@ public class CustomRequestLogTest _connector.getResponse("GET / HTTP/1.0\n\n"); String log = _entries.poll(5,TimeUnit.SECONDS); - assertThat(log, is("ResponseSize: 0\n")); + assertThat(log, is("ResponseSize: 0")); _connector.getResponse("GET / HTTP/1.0\nEcho: hello world\n\n"); log = _entries.poll(5,TimeUnit.SECONDS); - assertThat(log, is("ResponseSize: 11\n")); + assertThat(log, is("ResponseSize: 11")); } @Test @@ -160,11 +156,11 @@ public class CustomRequestLogTest _connector.getResponse("GET / HTTP/1.0\n\n"); String log = _entries.poll(5,TimeUnit.SECONDS); - assertThat(log, is("ResponseSize: -\n")); + assertThat(log, is("ResponseSize: -")); _connector.getResponse("GET / HTTP/1.0\nEcho: hello world\n\n"); log = _entries.poll(5,TimeUnit.SECONDS); - assertThat(log, is("ResponseSize: 11\n")); + assertThat(log, is("ResponseSize: 11")); } @Test @@ -174,7 +170,7 @@ public class CustomRequestLogTest _connector.getResponse("GET / HTTP/1.0\nCookie: cookieName=cookieValue; cookie2=value2\n\n"); String log = _entries.poll(5,TimeUnit.SECONDS); - assertThat(log, is("RequestCookies: cookieValue, value2, -\n")); + assertThat(log, is("RequestCookies: cookieValue, value2, -")); } @Test @@ -184,7 +180,7 @@ public class CustomRequestLogTest _connector.getResponse("GET / HTTP/1.0\nCookie: cookieName=cookieValue; cookie2=value2\n\n"); String log = _entries.poll(5,TimeUnit.SECONDS); - assertThat(log, is("RequestCookies: cookieName=cookieValue;cookie2=value2\n")); + assertThat(log, is("RequestCookies: cookieName=cookieValue;cookie2=value2")); } @Test @@ -194,17 +190,7 @@ public class CustomRequestLogTest _connector.getResponse("GET / HTTP/1.0\n\n"); String log = _entries.poll(5,TimeUnit.SECONDS); - assertThat(log, is("EnvironmentVar: " + System.getenv("JAVA_HOME") + "\n")); - } - - @Test - public void testLogFilename() throws Exception - { - testHandlerServerStart("Filename: %f"); - - _connector.getResponse("GET / HTTP/1.0\n\n"); - String log = _entries.poll(5,TimeUnit.SECONDS); - fail(log); + assertThat(log, is("EnvironmentVar: " + System.getenv("JAVA_HOME") + "")); } @Test @@ -224,7 +210,7 @@ public class CustomRequestLogTest _connector.getResponse("GET / HTTP/1.0\n\n"); String log = _entries.poll(5,TimeUnit.SECONDS); - assertThat(log, is("Protocol: HTTP/1.0\n")); + assertThat(log, is("Protocol: HTTP/1.0")); } @Test @@ -234,7 +220,7 @@ public class CustomRequestLogTest _connector.getResponse("GET / HTTP/1.0\nHeader1: value1\nHeader2: value2\n\n"); String log = _entries.poll(5,TimeUnit.SECONDS); - assertThat(log, is("RequestHeader: value1, value2, -\n")); + assertThat(log, is("RequestHeader: value1, value2, -")); } @Test @@ -242,14 +228,29 @@ public class CustomRequestLogTest { testHandlerServerStart("KeepAliveRequests: %k"); - _connector.getResponse("GET / HTTP/1.0\n\n"); - _connector.getResponse("GET / HTTP/1.0\n\n"); - _connector.getResponse("GET / HTTP/1.0\n\n"); + LocalConnector.LocalEndPoint connect = _connector.connect(); + connect.addInput("GET /a HTTP/1.0\n" + + "Connection: keep-alive\n\n"); + connect.addInput("GET /a HTTP/1.1\n" + + "Host: localhost\n\n"); - _entries.poll(5,TimeUnit.SECONDS); - _entries.poll(5,TimeUnit.SECONDS); - String log = _entries.poll(5,TimeUnit.SECONDS); - fail(log); + assertThat(connect.getResponse(), containsString("200 OK")); + assertThat(connect.getResponse(), containsString("200 OK")); + + connect.addInput("GET /a HTTP/1.0\n\n"); + assertThat(connect.getResponse(), containsString("200 OK")); + + + assertThat(_entries.poll(5,TimeUnit.SECONDS), is("KeepAliveRequests: 1")); + assertThat(_entries.poll(5,TimeUnit.SECONDS), is("KeepAliveRequests: 2")); + assertThat(_entries.poll(5,TimeUnit.SECONDS), is("KeepAliveRequests: 3")); + } + + @Test + public void testLogKeepAliveRequestsHttp2() throws Exception + { + testHandlerServerStart("KeepAliveRequests: %k"); + fail(); } @Test @@ -259,7 +260,7 @@ public class CustomRequestLogTest _connector.getResponse("GET / HTTP/1.0\n\n"); String log = _entries.poll(5,TimeUnit.SECONDS); - assertThat(log, is("RequestMethod: GET\n")); + assertThat(log, is("RequestMethod: GET")); } @Test @@ -269,7 +270,7 @@ public class CustomRequestLogTest _connector.getResponse("GET /responseHeaders HTTP/1.0\n\n"); String log = _entries.poll(5,TimeUnit.SECONDS); - assertThat(log, is("ResponseHeader: value1, value2, -\n")); + assertThat(log, is("ResponseHeader: value1, value2, -")); } @Test @@ -318,7 +319,7 @@ public class CustomRequestLogTest _connector.getResponse("GET /path?queryString HTTP/1.0\n\n"); String log = _entries.poll(5,TimeUnit.SECONDS); - assertThat(log, is("QueryString: ?queryString\n")); + assertThat(log, is("QueryString: ?queryString")); } @Test @@ -328,17 +329,7 @@ public class CustomRequestLogTest _connector.getResponse("GET /path?query HTTP/1.0\nHeader: null\n\n"); String log = _entries.poll(5,TimeUnit.SECONDS); - assertThat(log, is("RequestFirstLin: GET /path?query HTTP/1.0\n")); - } - - @Test - public void testLogRequestHandler() throws Exception - { - testHandlerServerStart("RequestHandler: %R"); - - _connector.getResponse("GET / HTTP/1.0\n\n"); - String log = _entries.poll(5,TimeUnit.SECONDS); - fail(log); + assertThat(log, is("RequestFirstLin: GET /path?query HTTP/1.0")); } @Test @@ -348,15 +339,15 @@ public class CustomRequestLogTest _connector.getResponse("GET /error404 HTTP/1.0\n\n"); String log = _entries.poll(5,TimeUnit.SECONDS); - assertThat(log, is("LogResponseStatus: 404\n")); + assertThat(log, is("LogResponseStatus: 404")); _connector.getResponse("GET /error301 HTTP/1.0\n\n"); log = _entries.poll(5,TimeUnit.SECONDS); - assertThat(log, is("LogResponseStatus: 301\n")); + assertThat(log, is("LogResponseStatus: 301")); _connector.getResponse("GET / HTTP/1.0\n\n"); log = _entries.poll(5,TimeUnit.SECONDS); - assertThat(log, is("LogResponseStatus: 200\n")); + assertThat(log, is("LogResponseStatus: 200")); } @Test @@ -366,63 +357,67 @@ public class CustomRequestLogTest _connector.getResponse("GET / HTTP/1.0\n\n"); String log = _entries.poll(5,TimeUnit.SECONDS); - fail(log); + long requestTime = requestTimes.poll(5,TimeUnit.SECONDS).longValue(); + DateCache dateCache = new DateCache(_log.DEFAULT_DATE_FORMAT, _log.getLogLocale(), _log.getLogTimeZone()); + assertThat(log, is("RequestTime: ["+ dateCache.format(requestTime) +"]")); } @Test public void testLogRequestTimeCustomFormats() throws Exception { - /* - The time, in the form given by format, which should be in an extended strftime(3) format (potentially localized). - If the format starts with begin: (default) the time is taken at the beginning of the request processing. - If it starts with end: it is the time when the log entry gets written, close to the end of the request processing. - - In addition to the formats supported by strftime(3), the following format tokens are supported: - sec number of seconds since the Epoch - msec number of milliseconds since the Epoch - usec number of microseconds since the Epoch - msec_frac millisecond fraction - usec_frac microsecond fraction - - These tokens can not be combined with each other or strftime(3) formatting in the same format string. - You can use multiple %{format}t tokens instead. - */ - - testHandlerServerStart("RequestTime: %{?}t"); + testHandlerServerStart("RequestTime: %{EEE MMM dd HH:mm:ss zzz yyyy}t"); _connector.getResponse("GET / HTTP/1.0\n\n"); String log = _entries.poll(5,TimeUnit.SECONDS); - fail(log); + long requestTime = requestTimes.poll(5,TimeUnit.SECONDS).longValue(); + DateCache dateCache = new DateCache("EEE MMM dd HH:mm:ss zzz yyyy", _log.getLogLocale(), _log.getLogTimeZone()); + assertThat(log, is("RequestTime: ["+ dateCache.format(requestTime) +"]")); } @Test public void testLogLatencyMicroseconds() throws Exception { - testHandlerServerStart("LatencyMicroseconds: %{us}Tus"); + testHandlerServerStart("%{us}T"); + long lowerBound = System.currentTimeMillis(); _connector.getResponse("GET / HTTP/1.0\n\n"); String log = _entries.poll(5,TimeUnit.SECONDS); - fail(log); + long upperBound = requestTimes.poll(5 ,TimeUnit.SECONDS); + + long duration = upperBound-lowerBound; + assertThat(Long.parseLong(log), greaterThan((long)0)); + assertThat(Long.parseLong(log), lessThanOrEqualTo(TimeUnit.MILLISECONDS.toMicros(duration))); } @Test public void testLogLatencyMilliseconds() throws Exception { - testHandlerServerStart("LatencyMilliseconds: %{ms}Tms"); + testHandlerServerStart("%{ms}T"); + long lowerBound = System.currentTimeMillis(); _connector.getResponse("GET / HTTP/1.0\n\n"); String log = _entries.poll(5,TimeUnit.SECONDS); - fail(log); + long upperBound = requestTimes.poll(5 ,TimeUnit.SECONDS); + + long duration = upperBound-lowerBound; + assertThat(Long.parseLong(log), greaterThan((long)0)); + assertThat(Long.parseLong(log), lessThanOrEqualTo(duration)); } @Test public void testLogLatencySeconds() throws Exception { - testHandlerServerStart("LatencySeconds: %{s}Ts"); + testHandlerServerStart("%{s}T"); - _connector.getResponse("GET / HTTP/1.0\n\n"); + long lowerBound = System.currentTimeMillis(); + _connector.getResponse("GET /delay HTTP/1.0\n\n"); String log = _entries.poll(5,TimeUnit.SECONDS); - fail(log); + long upperBound = requestTimes.poll(5 ,TimeUnit.SECONDS); + + + long duration = upperBound-lowerBound; + assertThat(Long.parseLong(log), greaterThan((long)0)); + assertThat(Long.parseLong(log), lessThanOrEqualTo(TimeUnit.MILLISECONDS.toMicros(duration))); } @Test @@ -442,7 +437,7 @@ public class CustomRequestLogTest _connector.getResponse("GET /path?query HTTP/1.0\n\n"); String log = _entries.poll(5,TimeUnit.SECONDS); - assertThat(log, is("UrlRequestPath: /path\n")); + assertThat(log, is("UrlRequestPath: /path")); } @Test @@ -450,9 +445,9 @@ public class CustomRequestLogTest { testHandlerServerStart("ServerName: %v"); - _connector.getResponse("GET / HTTP/1.0\n\n"); + _connector.getResponse("GET / HTTP/1.0\nHost: webtide.com\n\n"); String log = _entries.poll(5,TimeUnit.SECONDS); - fail(log); + assertThat(log, is("ServerName: webtide.com")); } @Test @@ -555,7 +550,19 @@ public class CustomRequestLogTest response.addHeader("Header1", "value1"); response.addHeader("Header2", "value2"); } + else if (request.getRequestURI().contains("delay")) + { + try + { + Thread.sleep(2000); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + } + requestTimes.offer(baseRequest.getTimeStamp()); baseRequest.setHandled(true); } } diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java index 4ddec3bb15b..30bc77031bc 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java @@ -32,7 +32,6 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; - import javax.servlet.MultipartConfigElement; import javax.servlet.Servlet; import javax.servlet.ServletConfig; @@ -756,6 +755,13 @@ public class ServletHolder extends Holder implements UserIdentity.Scope } } + /* ------------------------------------------------------------ */ + @Override + public ContextHandler getContextHandler() + { + return ContextHandler.getContextHandler(_config.getServletContext()); + } + /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.server.UserIdentity.Scope#getContextPath() diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/CustomRequestLogTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/CustomRequestLogTest.java new file mode 100644 index 00000000000..72374e44e17 --- /dev/null +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/CustomRequestLogTest.java @@ -0,0 +1,142 @@ +// +// ======================================================================== +// 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.servlet; + +import java.io.IOException; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.CustomRequestLog; +import org.eclipse.jetty.server.LocalConnector; +import org.eclipse.jetty.server.RequestLog; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.BlockingArrayQueue; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class CustomRequestLogTest +{ + RequestLog _log; + Server _server; + LocalConnector _connector; + BlockingQueue _entries = new BlockingArrayQueue<>(); + String _tmpDir = System.getProperty("java.io.tmpdir"); + + @BeforeEach + public void before() + { + _server = new Server(); + _connector = new LocalConnector(_server); + _server.addConnector(_connector); + } + + void testHandlerServerStart(String formatString) throws Exception + { + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/context"); + context.setResourceBase(_tmpDir); + context.addServlet(TestServlet.class, "/servlet/*"); + + TestRequestLogWriter writer = new TestRequestLogWriter(); + _log = new CustomRequestLog(writer, formatString); + _server.setRequestLog(_log); + _server.setHandler(context); + _server.start(); + } + + @AfterEach + public void after() throws Exception + { + _server.stop(); + } + + @Test + public void testLogFilename() throws Exception + { + testHandlerServerStart("Filename: %f"); + + _connector.getResponse("GET /context/servlet/info HTTP/1.0\n\n"); + String log = _entries.poll(5,TimeUnit.SECONDS); + assertThat(log, is("Filename: " + _tmpDir + "/servlet/info")); + } + + + @Test + public void testLogRequestHandler() throws Exception + { + testHandlerServerStart("RequestHandler: %R"); + + _connector.getResponse("GET / HTTP/1.0\n\n"); + String log = _entries.poll(5,TimeUnit.SECONDS); + assertThat(log, Matchers.containsString("TestServlet")); + } + + + class TestRequestLogWriter implements RequestLog.Writer + { + @Override + public void write(String requestEntry) + { + try + { + _entries.add(requestEntry); + } + catch(Exception e) + { + e.printStackTrace(); + } + } + } + + public static class TestServlet extends HttpServlet + { + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + if (request.getRequestURI().contains("error404")) + { + response.setStatus(404); + } + else if (request.getRequestURI().contains("error301")) + { + response.setStatus(301); + } + else if (request.getHeader("echo") != null) + { + ServletOutputStream outputStream = response.getOutputStream(); + outputStream.print(request.getHeader("echo")); + } + else if (request.getRequestURI().contains("responseHeaders")) + { + response.addHeader("Header1", "value1"); + response.addHeader("Header2", "value2"); + } + } + } +}