diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java index b87186ec61d..e39888d1e11 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java @@ -26,6 +26,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.jetty.client.api.Authentication; +import org.eclipse.jetty.client.api.Connection; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; @@ -117,12 +118,12 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler Authentication authentication = null; Authentication.HeaderInfo headerInfo = null; - URI uri = getAuthenticationURI(request); - if (uri != null) + URI authURI = getAuthenticationURI(request); + if (authURI != null) { for (Authentication.HeaderInfo element : headerInfos) { - authentication = client.getAuthenticationStore().findAuthentication(element.getType(), uri, element.getRealm()); + authentication = client.getAuthenticationStore().findAuthentication(element.getType(), authURI, element.getRealm()); if (authentication != null) { headerInfo = element; @@ -151,14 +152,33 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler conversation.setAttribute(authenticationAttribute, true); - Request newRequest = client.copyRequest(request, request.getURI()); + URI requestURI = request.getURI(); + String path = null; + if (requestURI == null) + { + String uri = request.getScheme() + "://" + request.getHost(); + int port = request.getPort(); + if (port > 0) + uri += ":" + port; + requestURI = URI.create(uri); + path = request.getPath(); + } + Request newRequest = client.copyRequest(request, requestURI); + if (path != null) + newRequest.path(path); + authnResult.apply(newRequest); // Copy existing, explicitly set, authorization headers. copyIfAbsent(request, newRequest, HttpHeader.AUTHORIZATION); copyIfAbsent(request, newRequest, HttpHeader.PROXY_AUTHORIZATION); - newRequest.onResponseSuccess(r -> client.getAuthenticationStore().addAuthenticationResult(authnResult)) - .send(null); + newRequest.onResponseSuccess(r -> client.getAuthenticationStore().addAuthenticationResult(authnResult)); + + Connection connection = (Connection)request.getAttributes().get(HttpRequest.CONNECTION_ATTRIBUTE); + if (connection != null) + connection.send(newRequest, null); + else + newRequest.send(null); } catch (Throwable x) { diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java index 5af0cd1c391..46360b09bdc 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java @@ -408,9 +408,9 @@ public class HttpClient extends ContainerLifeCycle } /** - * Creates a new request with the specified URI. + * Creates a new request with the specified absolute URI in string format. * - * @param uri the URI to request + * @param uri the request absolute URI * @return the request just created */ public Request newRequest(String uri) @@ -419,9 +419,9 @@ public class HttpClient extends ContainerLifeCycle } /** - * Creates a new request with the specified URI. + * Creates a new request with the specified absolute URI. * - * @param uri the URI to request + * @param uri the request absolute URI * @return the request just created */ public Request newRequest(URI uri) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java index 4c9d4ed3590..31a2930edf8 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java @@ -35,7 +35,6 @@ import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; -import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -89,7 +88,6 @@ public abstract class HttpConnection implements Connection protected void normalizeRequest(Request request) { - String method = request.getMethod(); HttpVersion version = request.getVersion(); HttpFields headers = request.getHeaders(); ContentProvider content = request.getContent(); @@ -102,9 +100,12 @@ public abstract class HttpConnection implements Connection path = "/"; request.path(path); } - if (proxy != null && !HttpMethod.CONNECT.is(method)) + + URI uri = request.getURI(); + + if (proxy != null && !HttpClient.isSchemeSecure(request.getScheme()) && uri != null) { - path = request.getURI().toString(); + path = uri.toString(); request.path(path); } @@ -144,7 +145,6 @@ public abstract class HttpConnection implements Connection CookieStore cookieStore = getHttpClient().getCookieStore(); if (cookieStore != null) { - URI uri = request.getURI(); StringBuilder cookies = null; if (uri != null) cookies = convertCookies(cookieStore.get(uri), null); @@ -155,7 +155,7 @@ public abstract class HttpConnection implements Connection // Authentication applyAuthentication(request, proxy != null ? proxy.getURI() : null); - applyAuthentication(request, request.getURI()); + applyAuthentication(request, uri); } private StringBuilder convertCookies(List cookies, StringBuilder builder) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java index 8a1ea7cc1f7..e8e305c0a24 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java @@ -77,6 +77,8 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest if (proxy != null) { connectionFactory = proxy.newClientConnectionFactory(connectionFactory); + if (proxy.isSecure()) + connectionFactory = newSslClientConnectionFactory(connectionFactory); } else { diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java index acf9a9b0e59..d98af8fd638 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java @@ -63,7 +63,7 @@ public class HttpProxy extends ProxyConfiguration.Proxy return URI.create(new Origin(scheme, getAddress()).asString()); } - public static class HttpProxyClientConnectionFactory implements ClientConnectionFactory + private static class HttpProxyClientConnectionFactory implements ClientConnectionFactory { private static final Logger LOG = Log.getLogger(HttpProxyClientConnectionFactory.class); private final ClientConnectionFactory connectionFactory; @@ -140,13 +140,19 @@ public class HttpProxy extends ProxyConfiguration.Proxy HttpClient httpClient = destination.getHttpClient(); long connectTimeout = httpClient.getConnectTimeout(); Request connect = httpClient.newRequest(proxyAddress.getHost(), proxyAddress.getPort()) - .scheme(HttpScheme.HTTP.asString()) .method(HttpMethod.CONNECT) .path(target) .header(HttpHeader.HOST, target) .idleTimeout(2 * connectTimeout, TimeUnit.MILLISECONDS) .timeout(connectTimeout, TimeUnit.MILLISECONDS); + // In case the proxy replies with a 407, we want + // to use the same connection for resending the + // request (this time with the Proxy-Authorization + // header), so we save it as an attribute to be + // used to send the next request. + connect.attribute(HttpRequest.CONNECTION_ATTRIBUTE, connection); + connection.send(connect, result -> { if (result.isFailed()) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java index a1267713337..db3836639e0 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java @@ -184,7 +184,9 @@ public abstract class HttpReceiver case SET_COOKIE: case SET_COOKIE2: { - storeCookie(exchange.getRequest().getURI(), field); + URI uri = exchange.getRequest().getURI(); + if (uri != null) + storeCookie(uri, field); break; } default: diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRedirector.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRedirector.java index 4927228f586..26eb5633e74 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRedirector.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRedirector.java @@ -229,7 +229,18 @@ public class HttpRedirector private Request redirect(Request request, Response response, Response.CompleteListener listener, URI newURI) { if (!newURI.isAbsolute()) - newURI = request.getURI().resolve(newURI); + { + URI requestURI = request.getURI(); + if (requestURI == null) + { + String uri = request.getScheme() + "://" + request.getHost(); + int port = request.getPort(); + if (port > 0) + uri += ":" + port; + requestURI = URI.create(uri); + } + newURI = requestURI.resolve(newURI); + } int status = response.getStatus(); switch (status) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java index 746763a1ab7..00827e872cf 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java @@ -59,6 +59,7 @@ import org.eclipse.jetty.util.Fields; public class HttpRequest implements Request { private static final URI NULL_URI = URI.create("null:0"); + static final String CONNECTION_ATTRIBUTE = HttpRequest.class.getName() + ".connection"; private final HttpFields headers = new HttpFields(); private final Fields params = new Fields(true); @@ -92,6 +93,7 @@ public class HttpRequest implements Request path = uri.getRawPath(); query = uri.getRawQuery(); extractParams(query); + followRedirects(client.isFollowRedirects()); idleTimeout = client.getIdleTimeout(); HttpField acceptEncodingField = client.getAcceptEncodingField(); @@ -170,8 +172,6 @@ public class HttpRequest implements Request else { String rawPath = uri.getRawPath(); - if (uri.isOpaque()) - rawPath = path; if (rawPath == null) rawPath = ""; this.path = rawPath; @@ -788,7 +788,7 @@ public class HttpRequest implements Request URI result = newURI(path); if (result == null) return NULL_URI; - if (!result.isAbsolute() && !result.isOpaque()) + if (!result.isAbsolute()) result = URI.create(new Origin(getScheme(), getHost(), getPort()).asString() + path); return result; } @@ -797,12 +797,16 @@ public class HttpRequest implements Request { try { - return new URI(uri); + // Handle specially the "OPTIONS *" case, since it is possible to create a URI from "*" (!). + if ("*".equals(uri)) + return null; + URI result = new URI(uri); + return result.isOpaque() ? null : result; } catch (URISyntaxException x) { // The "path" of a HTTP request may not be a URI, - // for example for CONNECT 127.0.0.1:8080 or OPTIONS *. + // for example for CONNECT 127.0.0.1:8080. return null; } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DigestAuthentication.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DigestAuthentication.java index 6e4d9871289..ab8916a4cc6 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DigestAuthentication.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DigestAuthentication.java @@ -208,7 +208,8 @@ public class DigestAuthentication extends AbstractAuthentication String A1 = user + ":" + realm + ":" + password; String hashA1 = toHexString(digester.digest(A1.getBytes(StandardCharsets.ISO_8859_1))); - String A2 = request.getMethod() + ":" + request.getURI(); + URI uri = request.getURI(); + String A2 = request.getMethod() + ":" + uri; if ("auth-int".equals(qop)) A2 += ":" + toHexString(digester.digest(content)); String hashA2 = toHexString(digester.digest(A2.getBytes(StandardCharsets.ISO_8859_1))); @@ -237,7 +238,7 @@ public class DigestAuthentication extends AbstractAuthentication if (opaque != null) value.append(", opaque=\"").append(opaque).append("\""); value.append(", algorithm=\"").append(algorithm).append("\""); - value.append(", uri=\"").append(request.getURI()).append("\""); + value.append(", uri=\"").append(uri).append("\""); if (qop != null) { value.append(", qop=\"").append(qop).append("\""); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientURITest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientURITest.java index fee0a67bbd9..691d7840dd9 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientURITest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientURITest.java @@ -29,6 +29,7 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.util.Fields; @@ -472,4 +473,35 @@ public class HttpClientURITest extends AbstractHttpClientServerTest Assert.assertEquals(HttpStatus.OK_200, response.getStatus()); } + + @Test + public void testAsteriskFormTarget() throws Exception + { + start(new AbstractHandler() + { + @Override + public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + Assert.assertEquals("*", target); + Assert.assertEquals("*", request.getPathInfo()); + } + }); + + Request request = client.newRequest("localhost", connector.getLocalPort()) + .method(HttpMethod.OPTIONS) + .scheme(scheme) + .path("*") + .timeout(5, TimeUnit.SECONDS); + + Assert.assertEquals("*", request.getPath()); + Assert.assertNull(request.getQuery()); + Fields params = request.getParams(); + Assert.assertEquals(0, params.getSize()); + Assert.assertNull(request.getURI()); + + ContentResponse response = request.send(); + + Assert.assertEquals(HttpStatus.OK_200, response.getStatus()); + } } diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java index eaa7423055b..eb664daca1e 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java @@ -64,9 +64,9 @@ public class HttpSenderOverFCGI extends HttpSender // FastCGI headers based on the URI URI uri = request.getURI(); - String path = uri.getRawPath(); + String path = uri == null ? request.getPath() : uri.getRawPath(); fcgiHeaders.put(FCGI.Headers.DOCUMENT_URI, path); - String query = uri.getRawQuery(); + String query = uri == null ? null : uri.getRawQuery(); fcgiHeaders.put(FCGI.Headers.QUERY_STRING, query == null ? "" : query); // FastCGI headers based on HTTP headers diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java index 9fb85f9e64f..43f17e3e11f 100644 --- a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java +++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java @@ -188,8 +188,8 @@ public class FastCGIProxyServlet extends AsyncProxyServlet.Transparent fastCGIHeaders.put(FCGI.Headers.HTTPS, "on"); URI proxyRequestURI = proxyRequest.getURI(); - String rawPath = proxyRequestURI.getRawPath(); - String rawQuery = proxyRequestURI.getRawQuery(); + String rawPath = proxyRequestURI == null ? proxyRequest.getPath() : proxyRequestURI.getRawPath(); + String rawQuery = proxyRequestURI == null ? null : proxyRequestURI.getRawQuery(); String requestURI = (String)proxyRequest.getAttributes().get(REQUEST_URI_ATTRIBUTE); if (requestURI == null) diff --git a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionDataStore.java b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionDataStore.java index f43e4f463a5..fbd7aca7538 100644 --- a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionDataStore.java +++ b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionDataStore.java @@ -423,11 +423,15 @@ public class MongoSessionDataStore extends NoSqlSessionDataStore DBObject o = _dbSessions.findOne(new BasicDBObject("id", id), fields); if (o != null) { - Long currentMaxIdle = (Long)o.get(__MAX_IDLE); - Long currentExpiry = (Long)o.get(__EXPIRY); - if (currentMaxIdle != null && nsqd.getMaxInactiveMs() > 0 && nsqd.getMaxInactiveMs() < currentMaxIdle) + Long tmpLong = (Long)o.get(__MAX_IDLE); + long currentMaxIdle = (tmpLong == null? 0:tmpLong.longValue()); + tmpLong = (Long)o.get(__EXPIRY); + long currentExpiry = (tmpLong == null? 0 : tmpLong.longValue()); + + if (currentMaxIdle != nsqd.getMaxInactiveMs()) sets.put(__MAX_IDLE, nsqd.getMaxInactiveMs()); - if (currentExpiry != null && nsqd.getExpiry() > 0 && nsqd.getExpiry() != currentExpiry) + + if (currentExpiry != nsqd.getExpiry()) sets.put(__EXPIRY, nsqd.getExpiry()); } else diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ForwardProxyServerTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ForwardProxyServerTest.java new file mode 100644 index 00000000000..f114b342e0f --- /dev/null +++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ForwardProxyServerTest.java @@ -0,0 +1,221 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.proxy; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpProxy; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.io.AbstractConnection; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.server.AbstractConnectionFactory; +import org.eclipse.jetty.server.ConnectionFactory; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.toolchain.test.TestTracker; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.Utf8StringBuilder; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.hamcrest.Matchers; +import org.junit.After; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class ForwardProxyServerTest +{ + @Parameterized.Parameters + public static Object[] parameters() + { + return new Object[]{null, newSslContextFactory()}; + } + + @Rule + public final TestTracker tracker = new TestTracker(); + private final SslContextFactory serverSslContextFactory; + private Server server; + private ServerConnector serverConnector; + private Server proxy; + private ServerConnector proxyConnector; + + public ForwardProxyServerTest(SslContextFactory serverSslContextFactory) + { + this.serverSslContextFactory = serverSslContextFactory; + } + + protected void startServer(ConnectionFactory connectionFactory) throws Exception + { + QueuedThreadPool serverThreads = new QueuedThreadPool(); + serverThreads.setName("server"); + server = new Server(serverThreads); + serverConnector = new ServerConnector(server, serverSslContextFactory, connectionFactory); + server.addConnector(serverConnector); + server.start(); + } + + protected void startProxy() throws Exception + { + QueuedThreadPool proxyThreads = new QueuedThreadPool(); + proxyThreads.setName("proxy"); + proxy = new Server(proxyThreads); + proxyConnector = new ServerConnector(proxy); + proxy.addConnector(proxyConnector); + // Under Windows, it takes a while to detect that a connection + // attempt fails, so use an explicit timeout + ConnectHandler connectHandler = new ConnectHandler(); + connectHandler.setConnectTimeout(1000); + proxy.setHandler(connectHandler); + + ServletContextHandler proxyHandler = new ServletContextHandler(connectHandler, "/"); + proxyHandler.addServlet(ProxyServlet.class, "/*"); + + proxy.start(); + } + + protected HttpProxy newHttpProxy() + { + return new HttpProxy("localhost", proxyConnector.getLocalPort()); + } + + private static SslContextFactory newSslContextFactory() + { + SslContextFactory sslContextFactory = new SslContextFactory(); + String keyStorePath = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath(); + sslContextFactory.setKeyStorePath(keyStorePath); + sslContextFactory.setKeyStorePassword("storepwd"); + sslContextFactory.setKeyManagerPassword("keypwd"); + return sslContextFactory; + } + + @After + public void stop() throws Exception + { + stopProxy(); + stopServer(); + } + + protected void stopServer() throws Exception + { + if (server != null) + { + server.stop(); + server.join(); + } + } + + protected void stopProxy() throws Exception + { + if (proxy != null) + { + proxy.stop(); + proxy.join(); + } + } + + @Test + public void testRequestTarget() throws Exception + { + startServer(new AbstractConnectionFactory("http/1.1") + { + @Override + public Connection newConnection(Connector connector, EndPoint endPoint) + { + return new AbstractConnection(endPoint, connector.getExecutor()) + { + @Override + public void onOpen() + { + super.onOpen(); + fillInterested(); + } + + @Override + public void onFillable() + { + try + { + // When using TLS, multiple reads are required. + ByteBuffer buffer = BufferUtil.allocate(1024); + int filled = 0; + while (filled == 0) + filled = getEndPoint().fill(buffer); + Utf8StringBuilder builder = new Utf8StringBuilder(); + builder.append(buffer); + String request = builder.toString(); + + // ProxyServlet will receive an absolute URI from + // the client, and convert it to a relative URI. + // The ConnectHandler won't modify what the client + // sent, which must be a relative URI. + Assert.assertThat(request.length(), Matchers.greaterThan(0)); + if (serverSslContextFactory == null) + Assert.assertFalse(request.contains("http://")); + else + Assert.assertFalse(request.contains("https://")); + + String response = "" + + "HTTP/1.1 200 OK\r\n" + + "Content-Length: 0\r\n" + + "\r\n"; + getEndPoint().write(Callback.NOOP, ByteBuffer.wrap(response.getBytes(StandardCharsets.UTF_8))); + } + catch (Throwable x) + { + x.printStackTrace(); + close(); + } + } + }; + } + }); + startProxy(); + + HttpClient httpClient = new HttpClient(newSslContextFactory()); + httpClient.getProxyConfiguration().getProxies().add(newHttpProxy()); + httpClient.start(); + + try + { + ContentResponse response = httpClient.newRequest("localhost", serverConnector.getLocalPort()) + .scheme(serverSslContextFactory == null ? "http" : "https") + .method(HttpMethod.GET) + .path("/test") + .send(); + + Assert.assertEquals(HttpStatus.OK_200, response.getStatus()); + } + finally + { + httpClient.stop(); + } + } +} diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyTunnellingTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ForwardProxyTLSServerTest.java similarity index 67% rename from jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyTunnellingTest.java rename to jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ForwardProxyTLSServerTest.java index 8fa61e24ff8..18bc3a8a276 100644 --- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyTunnellingTest.java +++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ForwardProxyTLSServerTest.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.proxy; import java.io.IOException; import java.net.ConnectException; import java.net.Socket; +import java.net.URI; import java.net.URLEncoder; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; @@ -34,9 +35,11 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpProxy; +import org.eclipse.jetty.client.Origin; import org.eclipse.jetty.client.api.Connection; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Destination; +import org.eclipse.jetty.client.util.BasicAuthentication; import org.eclipse.jetty.client.util.FutureResponseListener; import org.eclipse.jetty.client.util.StringContentProvider; import org.eclipse.jetty.http.HttpHeader; @@ -62,37 +65,37 @@ import org.junit.Assume; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; -import static org.junit.Assert.assertEquals; - -public class ProxyTunnellingTest +@RunWith(Parameterized.class) +public class ForwardProxyTLSServerTest { - @Rule - public TestTracker tracker = new TestTracker(); + @Parameterized.Parameters + public static Object[] parameters() + { + return new Object[]{null, newSslContextFactory()}; + } - private SslContextFactory sslContextFactory; + @Rule + public final TestTracker tracker = new TestTracker(); + private final SslContextFactory proxySslContextFactory; private Server server; private ServerConnector serverConnector; private Server proxy; private ServerConnector proxyConnector; - protected int proxyPort() + public ForwardProxyTLSServerTest(SslContextFactory proxySslContextFactory) { - return proxyConnector.getLocalPort(); + this.proxySslContextFactory = proxySslContextFactory; } - protected void startSSLServer(Handler handler) throws Exception + protected void startTLSServer(Handler handler) throws Exception { - sslContextFactory = new SslContextFactory(); - String keyStorePath = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath(); - sslContextFactory.setKeyStorePath(keyStorePath); - sslContextFactory.setKeyStorePassword("storepwd"); - sslContextFactory.setKeyManagerPassword("keypwd"); - QueuedThreadPool serverThreads = new QueuedThreadPool(); serverThreads.setName("server"); server = new Server(serverThreads); - serverConnector = new ServerConnector(server, sslContextFactory); + serverConnector = new ServerConnector(server, newSslContextFactory()); server.addConnector(serverConnector); server.setHandler(handler); server.start(); @@ -108,7 +111,7 @@ public class ProxyTunnellingTest QueuedThreadPool proxyThreads = new QueuedThreadPool(); proxyThreads.setName("proxy"); proxy = new Server(proxyThreads); - proxyConnector = new ServerConnector(proxy); + proxyConnector = new ServerConnector(proxy, proxySslContextFactory); proxy.addConnector(proxyConnector); // Under Windows, it takes a while to detect that a connection // attempt fails, so use an explicit timeout @@ -117,6 +120,21 @@ public class ProxyTunnellingTest proxy.start(); } + protected HttpProxy newHttpProxy() + { + return new HttpProxy(new Origin.Address("localhost", proxyConnector.getLocalPort()), proxySslContextFactory != null); + } + + private static SslContextFactory newSslContextFactory() + { + SslContextFactory sslContextFactory = new SslContextFactory(); + String keyStorePath = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath(); + sslContextFactory.setKeyStorePath(keyStorePath); + sslContextFactory.setKeyStorePassword("storepwd"); + sslContextFactory.setKeyManagerPassword("keypwd"); + return sslContextFactory; + } + @After public void stop() throws Exception { @@ -142,19 +160,21 @@ public class ProxyTunnellingTest } } - @Test(timeout=60000) - public void testOneExchangeViaSSL() throws Exception + @Test + public void testOneExchange() throws Exception { - startSSLServer(new ServerHandler()); + startTLSServer(new ServerHandler()); startProxy(); - HttpClient httpClient = new HttpClient(sslContextFactory); - httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort())); + HttpClient httpClient = new HttpClient(newSslContextFactory()); + httpClient.getProxyConfiguration().getProxies().add(newHttpProxy()); httpClient.start(); try { // Use a numeric host to test the URI of the CONNECT request. + // URIs such as host:80 may interpret "host" as the scheme, + // but when the host is numeric it is not a valid URI. String host = "127.0.0.1"; String body = "BODY"; ContentResponse response = httpClient.newRequest(host, serverConnector.getLocalPort()) @@ -163,9 +183,9 @@ public class ProxyTunnellingTest .path("/echo?body=" + URLEncoder.encode(body, "UTF-8")) .send(); - assertEquals(HttpStatus.OK_200, response.getStatus()); + Assert.assertEquals(HttpStatus.OK_200, response.getStatus()); String content = response.getContentAsString(); - assertEquals(body, content); + Assert.assertEquals(body, content); } finally { @@ -173,14 +193,14 @@ public class ProxyTunnellingTest } } - @Test(timeout=60000) - public void testTwoExchangesViaSSL() throws Exception + @Test + public void testTwoExchanges() throws Exception { - startSSLServer(new ServerHandler()); + startTLSServer(new ServerHandler()); startProxy(); - HttpClient httpClient = new HttpClient(sslContextFactory); - httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort())); + HttpClient httpClient = new HttpClient(newSslContextFactory()); + httpClient.getProxyConfiguration().getProxies().add(newHttpProxy()); httpClient.start(); try @@ -192,9 +212,9 @@ public class ProxyTunnellingTest .path("/echo?body=" + URLEncoder.encode(body, "UTF-8")) .send(); - assertEquals(HttpStatus.OK_200, response1.getStatus()); + Assert.assertEquals(HttpStatus.OK_200, response1.getStatus()); String content = response1.getContentAsString(); - assertEquals(body, content); + Assert.assertEquals(body, content); content = "body=" + body; ContentResponse response2 = httpClient.newRequest("localhost", serverConnector.getLocalPort()) @@ -206,9 +226,9 @@ public class ProxyTunnellingTest .content(new StringContentProvider(content)) .send(); - assertEquals(HttpStatus.OK_200, response2.getStatus()); + Assert.assertEquals(HttpStatus.OK_200, response2.getStatus()); content = response2.getContentAsString(); - assertEquals(body, content); + Assert.assertEquals(body, content); } finally { @@ -216,14 +236,14 @@ public class ProxyTunnellingTest } } - @Test(timeout=60000) - public void testTwoConcurrentExchangesViaSSL() throws Exception + @Test + public void testTwoConcurrentExchanges() throws Exception { - startSSLServer(new ServerHandler()); + startTLSServer(new ServerHandler()); startProxy(); - final HttpClient httpClient = new HttpClient(sslContextFactory); - httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort())); + final HttpClient httpClient = new HttpClient(newSslContextFactory()); + httpClient.getProxyConfiguration().getProxies().add(newHttpProxy()); httpClient.start(); try @@ -235,28 +255,24 @@ public class ProxyTunnellingTest .scheme(HttpScheme.HTTPS.asString()) .method(HttpMethod.GET) .path("/echo?body=" + URLEncoder.encode(body1, "UTF-8")) - .onRequestCommit(new org.eclipse.jetty.client.api.Request.CommitListener() + .onRequestCommit(request -> { - @Override - public void onCommit(org.eclipse.jetty.client.api.Request request) + Destination destination = httpClient.getDestination(HttpScheme.HTTPS.asString(), "localhost", serverConnector.getLocalPort()); + destination.newConnection(new Promise.Adapter() { - Destination destination = httpClient.getDestination(HttpScheme.HTTPS.asString(), "localhost", serverConnector.getLocalPort()); - destination.newConnection(new Promise.Adapter() + @Override + public void succeeded(Connection result) { - @Override - public void succeeded(Connection result) - { - connection.set(result); - connectionLatch.countDown(); - } - }); - } + connection.set(result); + connectionLatch.countDown(); + } + }); }) .send(); - assertEquals(HttpStatus.OK_200, response1.getStatus()); + Assert.assertEquals(HttpStatus.OK_200, response1.getStatus()); String content = response1.getContentAsString(); - assertEquals(body1, content); + Assert.assertEquals(body1, content); Assert.assertTrue(connectionLatch.await(5, TimeUnit.SECONDS)); @@ -274,9 +290,9 @@ public class ProxyTunnellingTest connection.get().send(request2, listener2); ContentResponse response2 = listener2.get(5, TimeUnit.SECONDS); - assertEquals(HttpStatus.OK_200, response2.getStatus()); + Assert.assertEquals(HttpStatus.OK_200, response2.getStatus()); String content2 = response1.getContentAsString(); - assertEquals(body1, content2); + Assert.assertEquals(body1, content2); } finally { @@ -284,13 +300,13 @@ public class ProxyTunnellingTest } } - @Test(timeout=60000) + @Test public void testShortIdleTimeoutOverriddenByRequest() throws Exception { // Short idle timeout for HttpClient. long idleTimeout = 500; - startSSLServer(new ServerHandler()); + startTLSServer(new ServerHandler()); startProxy(new ConnectHandler() { @Override @@ -309,8 +325,8 @@ public class ProxyTunnellingTest } }); - HttpClient httpClient = new HttpClient(sslContextFactory); - httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort())); + HttpClient httpClient = new HttpClient(newSslContextFactory()); + httpClient.getProxyConfiguration().getProxies().add(newHttpProxy()); // Short idle timeout for HttpClient. httpClient.setIdleTimeout(idleTimeout); httpClient.start(); @@ -327,9 +343,9 @@ public class ProxyTunnellingTest .idleTimeout(10 * idleTimeout, TimeUnit.MILLISECONDS) .send(); - assertEquals(HttpStatus.OK_200, response.getStatus()); + Assert.assertEquals(HttpStatus.OK_200, response.getStatus()); String content = response.getContentAsString(); - assertEquals(body, content); + Assert.assertEquals(body, content); } finally { @@ -337,16 +353,16 @@ public class ProxyTunnellingTest } } - @Test(timeout=60000) + @Test public void testProxyDown() throws Exception { - startSSLServer(new ServerHandler()); + startTLSServer(new ServerHandler()); startProxy(); - int proxyPort = proxyPort(); + int proxyPort = proxyConnector.getLocalPort(); stopProxy(); - HttpClient httpClient = new HttpClient(sslContextFactory); - httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort)); + HttpClient httpClient = new HttpClient(newSslContextFactory()); + httpClient.getProxyConfiguration().getProxies().add(new HttpProxy(new Origin.Address("localhost", proxyPort), proxySslContextFactory != null)); httpClient.start(); try @@ -369,16 +385,16 @@ public class ProxyTunnellingTest } } - @Test(timeout=60000) + @Test public void testServerDown() throws Exception { - startSSLServer(new ServerHandler()); + startTLSServer(new ServerHandler()); int serverPort = serverConnector.getLocalPort(); stopServer(); startProxy(); - HttpClient httpClient = new HttpClient(sslContextFactory); - httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort())); + HttpClient httpClient = new HttpClient(newSslContextFactory()); + httpClient.getProxyConfiguration().getProxies().add(newHttpProxy()); httpClient.start(); try @@ -401,10 +417,10 @@ public class ProxyTunnellingTest } } - @Test(timeout=60000) + @Test public void testProxyClosesConnection() throws Exception { - startSSLServer(new ServerHandler()); + startTLSServer(new ServerHandler()); startProxy(new ConnectHandler() { @Override @@ -414,8 +430,8 @@ public class ProxyTunnellingTest } }); - HttpClient httpClient = new HttpClient(sslContextFactory); - httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort())); + HttpClient httpClient = new HttpClient(newSslContextFactory()); + httpClient.getProxyConfiguration().getProxies().add(newHttpProxy()); httpClient.start(); try @@ -435,7 +451,55 @@ public class ProxyTunnellingTest } } - @Test(timeout=60000) + @Test + public void testProxyAuthentication() throws Exception + { + startTLSServer(new ServerHandler()); + String proxyRealm = "ProxyRealm"; + startProxy(new ConnectHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + String proxyAuth = request.getHeader(HttpHeader.PROXY_AUTHORIZATION.asString()); + if (proxyAuth == null) + { + baseRequest.setHandled(true); + response.setStatus(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407); + response.setHeader(HttpHeader.PROXY_AUTHENTICATE.asString(), "Basic realm=\"" + proxyRealm + "\""); + return; + } + super.handle(target, baseRequest, request, response); + } + }); + + HttpClient httpClient = new HttpClient(newSslContextFactory()); + httpClient.getProxyConfiguration().getProxies().add(newHttpProxy()); + URI proxyURI = URI.create("https://localhost:" + proxyConnector.getLocalPort()); + httpClient.getAuthenticationStore().addAuthentication(new BasicAuthentication(proxyURI, proxyRealm, "proxyUser", "proxyPassword")); + httpClient.start(); + + try + { + String body = "BODY"; + ContentResponse response = httpClient.newRequest("localhost", serverConnector.getLocalPort()) + .scheme(HttpScheme.HTTPS.asString()) + .method(HttpMethod.GET) + .path("/echo") + .param("body", body) + .send(); + + Assert.assertEquals(HttpStatus.OK_200, response.getStatus()); + String content = response.getContentAsString(); + Assert.assertEquals(body, content); + } + finally + { + httpClient.stop(); + } + } + + @Test @Ignore("External Proxy Server no longer stable enough for testing") public void testExternalProxy() throws Exception { @@ -454,7 +518,7 @@ public class ProxyTunnellingTest SslContextFactory sslContextFactory = new SslContextFactory(); sslContextFactory.start(); - HttpClient httpClient = new HttpClient(sslContextFactory); + HttpClient httpClient = new HttpClient(newSslContextFactory()); httpClient.getProxyConfiguration().getProxies().add(new HttpProxy(proxyHost, proxyPort)); httpClient.start(); @@ -464,7 +528,7 @@ public class ProxyTunnellingTest // Use a longer timeout, sometimes the proxy takes a while to answer .timeout(20, TimeUnit.SECONDS) .send(); - assertEquals(HttpStatus.OK_200, response.getStatus()); + Assert.assertEquals(HttpStatus.OK_200, response.getStatus()); } finally { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java index 948fd60a8aa..8b7f7e34fe0 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java @@ -440,7 +440,7 @@ public class Server extends HandlerWrapper implements Attributes if (LOG.isDebugEnabled()) LOG.debug("doStop {}",this); - + MultiException mex=new MultiException(); // list if graceful futures @@ -529,7 +529,8 @@ public class Server extends HandlerWrapper implements Attributes final Response response=connection.getResponse(); if (LOG.isDebugEnabled()) - LOG.debug("{} on {}{}",request.getDispatcherType(),connection,"\n"+request.getMethod()+" "+request.getHttpURI()+"\n"+request.getHttpFields()); + LOG.debug("{} on {}{}{} {} {}{}{}", request.getDispatcherType(), connection, System.lineSeparator(), + request.getMethod(), target, request.getProtocol(), System.lineSeparator(), request.getHttpFields()); if (HttpMethod.OPTIONS.is(request.getMethod()) || "*".equals(target)) { @@ -543,7 +544,8 @@ public class Server extends HandlerWrapper implements Attributes handle(target, request, request, response); if (LOG.isDebugEnabled()) - LOG.debug("RESPONSE for {} h={}{}",target,request.isHandled(),"\n"+response.getStatus()+" "+response.getReason()+"\n"+response.getHttpFields()); + LOG.debug("RESPONSE for {} h={}{}{} {}{}{}", target, request.isHandled(), System.lineSeparator(), + response.getStatus(), response.getReason(), System.lineSeparator(), response.getHttpFields()); } /* ------------------------------------------------------------ */ diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/Session.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/Session.java index b46df8efb9f..32b99b2340e 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/Session.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/Session.java @@ -404,8 +404,10 @@ public class Session implements SessionManager.SessionIf try (Lock lock = _lock.lockIfNotHeld()) { _sessionData.setMaxInactiveMs((long)secs*1000L); - _sessionData.setExpiry(_sessionData.getMaxInactiveMs() <= 0 ? 0 : (System.currentTimeMillis() + _sessionData.getMaxInactiveMs()*1000L)); + _sessionData.setExpiry(_sessionData.getMaxInactiveMs() <= 0 ? 0 : (System.currentTimeMillis() + _sessionData.getMaxInactiveMs())); _sessionData.setDirty(true); + if (secs <= 0) + LOG.warn("Session {} is now immortal (maxInactiveInterval={})", _sessionData.getId(), secs); } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionManager.java index e0210616498..77acd0b110b 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionManager.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionManager.java @@ -76,6 +76,13 @@ public class SessionManager extends ContainerLifeCycle implements org.eclipse.je /* ------------------------------------------------------------ */ public final static int __distantFuture=60*60*24*7*52*20; + /** + * Web.xml session-timeout is set in minutes, but is stored as an int in seconds by HttpSession and + * the sessionmanager. Thus MAX_INT is the max number of seconds that can be set, and MAX_INT/60 is the + * max number of minutes that you can set. + */ + public final static java.math.BigDecimal MAX_INACTIVE_MINUTES = new java.math.BigDecimal(Integer.MAX_VALUE/60); + static final HttpSessionContext __nullSessionContext=new HttpSessionContext() { @Override @@ -656,6 +663,9 @@ public class SessionManager extends ContainerLifeCycle implements org.eclipse.je public void setMaxInactiveInterval(int seconds) { _dftMaxIdleSecs=seconds; + if (_dftMaxIdleSecs < 0) + LOG.warn("Sessions created by this manager are immortal (default maxInactiveInterval={})"+_dftMaxIdleSecs); + } /* ------------------------------------------------------------ */ diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java index 37de8240c2f..d938847e6f3 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java @@ -649,9 +649,12 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor { XmlParser.Node tNode = node.get("session-timeout"); if (tNode != null) - { - int timeout = Integer.parseInt(tNode.toString(false, true)); - context.getSessionHandler().getSessionManager().setMaxInactiveInterval(timeout * 60); + { + java.math.BigDecimal asDecimal = new java.math.BigDecimal(tNode.toString(false, true)); + if (asDecimal.compareTo(org.eclipse.jetty.server.session.SessionManager.MAX_INACTIVE_MINUTES) > 0) + throw new IllegalStateException ("Max session-timeout in minutes is "+org.eclipse.jetty.server.session.SessionManager.MAX_INACTIVE_MINUTES); + + context.getSessionHandler().getSessionManager().setMaxInactiveInterval(asDecimal.intValueExact() * 60); } //Servlet Spec 3.0 diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AbstractTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AbstractTest.java index 799fd4e1932..32b52e3c44b 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AbstractTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AbstractTest.java @@ -199,20 +199,14 @@ public abstract class AbstractTest return http2Client; } + protected String getScheme() + { + return isTransportSecure() ? "https" : "http"; + } + protected String newURI() { - switch (transport) - { - case HTTP: - case H2C: - case FCGI: - return "http://localhost:" + connector.getLocalPort(); - case HTTPS: - case H2: - return "https://localhost:" + connector.getLocalPort(); - default: - throw new IllegalArgumentException(); - } + return getScheme() + "://localhost:" + connector.getLocalPort(); } protected boolean isTransportSecure() diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientLoadTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientLoadTest.java index 6a16b533b09..0e50606a21a 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientLoadTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientLoadTest.java @@ -263,7 +263,7 @@ public class HttpClientLoadTest extends AbstractTest int maxContentLength = 64 * 1024; int contentLength = random.nextInt(maxContentLength) + 1; - test(ssl ? "https" : "http", host, method.asString(), clientClose, serverClose, contentLength, true, latch, failures); + test(getScheme(), host, method.asString(), clientClose, serverClose, 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 failures) diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java index b0f3d177351..86a40bf7d70 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java @@ -316,6 +316,60 @@ public class HttpClientTest extends AbstractTest .send(); } + @Test + public void testOPTIONS() throws Exception + { + start(new AbstractHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + Assert.assertTrue(HttpMethod.OPTIONS.is(request.getMethod())); + Assert.assertEquals("*", target); + Assert.assertEquals("*", request.getPathInfo()); + } + }); + + ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + .scheme(getScheme()) + .method(HttpMethod.OPTIONS) + .path("*") + .timeout(5, TimeUnit.SECONDS) + .send(); + + Assert.assertEquals(HttpStatus.OK_200, response.getStatus()); + } + + @Test + public void testOPTIONSWithRelativeRedirect() throws Exception + { + start(new AbstractHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + if ("*".equals(target)) + { + // Be nasty and send a relative redirect. + // Code 303 will change the method to GET. + response.setStatus(HttpStatus.SEE_OTHER_303); + response.setHeader("Location", "/"); + } + } + }); + + ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + .scheme(getScheme()) + .method(HttpMethod.OPTIONS) + .path("*") + .timeout(5, TimeUnit.SECONDS) + .send(); + + Assert.assertEquals(HttpStatus.OK_200, response.getStatus()); + } + private void sleep(long time) throws IOException { try diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionExpiryTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionExpiryTest.java index 8c9c71fa2ab..8777c11aab0 100644 --- a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionExpiryTest.java +++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionExpiryTest.java @@ -18,12 +18,39 @@ package org.eclipse.jetty.nosql.mongodb; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.server.session.AbstractSessionExpiryTest; import org.eclipse.jetty.server.session.AbstractTestServer; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.StringUtil; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; +import com.mongodb.BasicDBObject; +import com.mongodb.DBCollection; +import com.mongodb.DBObject; + +import static org.junit.Assert.assertTrue; + +import java.io.IOException; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + + + + public class SessionExpiryTest extends AbstractSessionExpiryTest { @@ -58,4 +85,219 @@ public class SessionExpiryTest extends AbstractSessionExpiryTest { super.testSessionExpiry(); } + + @Test + public void testBigSessionExpiry() throws Exception + { + String contextPath = ""; + String servletMapping = "/server"; + int inactivePeriod = Integer.MAX_VALUE * 60; //integer overflow + int scavengePeriod = 10; + int inspectPeriod = 1; + int idlePassivatePeriod = 0; + AbstractTestServer server1 = createServer(0, inactivePeriod, scavengePeriod, inspectPeriod, idlePassivatePeriod); + ChangeTimeoutServlet servlet = new ChangeTimeoutServlet(); + ServletHolder holder = new ServletHolder(servlet); + ServletContextHandler context = server1.addContext(contextPath); + context.addServlet(holder, servletMapping); + TestHttpSessionListener listener = new TestHttpSessionListener(); + + context.getSessionHandler().addEventListener(listener); + + server1.start(); + int port1 = server1.getPort(); + + try + { + HttpClient client = new HttpClient(); + client.start(); + String url = "http://localhost:" + port1 + contextPath + servletMapping; + + //make a request to set up a session on the server + ContentResponse response1 = client.GET(url + "?action=init"); + assertEquals(HttpServletResponse.SC_OK,response1.getStatus()); + String sessionCookie = response1.getHeaders().get("Set-Cookie"); + assertTrue(sessionCookie != null); + // Mangle the cookie, replacing Path with $Path, etc. + sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path="); + + String sessionId = AbstractTestServer.extractSessionId(sessionCookie); + + DBCollection sessions = ((MongoSessionIdManager)((MongoTestServer)server1).getServer().getSessionIdManager()).getSessions(); + verifySessionCreated(listener,sessionId); + //verify that the session timeout is set in mongo + verifySessionTimeout(sessions, sessionId, -1); //SessionManager sets -1 if maxInactive < 0 + + //get the session expiry time from mongo + long expiry = getSessionExpiry(sessions, sessionId); + assertEquals(0, expiry); + + } + finally + { + server1.stop(); + } + } + + @Test + public void changeSessionTimeout() throws Exception + { + String contextPath = ""; + String servletMapping = "/server"; + int inactivePeriod = 10; + int scavengePeriod = 1; + int inspectPeriod = 1; + int idlePassivatePeriod = 0; + AbstractTestServer server1 = createServer(0, inactivePeriod, scavengePeriod, inspectPeriod, idlePassivatePeriod); + ChangeTimeoutServlet servlet = new ChangeTimeoutServlet(); + ServletHolder holder = new ServletHolder(servlet); + ServletContextHandler context = server1.addContext(contextPath); + context.addServlet(holder, servletMapping); + TestHttpSessionListener listener = new TestHttpSessionListener(); + + context.getSessionHandler().addEventListener(listener); + + server1.start(); + int port1 = server1.getPort(); + + try + { + HttpClient client = new HttpClient(); + client.start(); + String url = "http://localhost:" + port1 + contextPath + servletMapping; + + //make a request to set up a session on the server + ContentResponse response1 = client.GET(url + "?action=init"); + assertEquals(HttpServletResponse.SC_OK,response1.getStatus()); + String sessionCookie = response1.getHeaders().get("Set-Cookie"); + assertTrue(sessionCookie != null); + // Mangle the cookie, replacing Path with $Path, etc. + sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path="); + + String sessionId = AbstractTestServer.extractSessionId(sessionCookie); + + DBCollection sessions = ((MongoSessionIdManager)((MongoTestServer)server1).getServer().getSessionIdManager()).getSessions(); + verifySessionCreated(listener,sessionId); + //verify that the session timeout is set in mongo + verifySessionTimeout(sessions, sessionId, inactivePeriod); + + //get the session expiry time from mongo + long expiry = getSessionExpiry(sessions, sessionId); + + //make another request to change the session timeout to a smaller value + inactivePeriod = 5; + Request request = client.newRequest(url + "?action=change&val="+inactivePeriod); + request.getHeaders().add("Cookie", sessionCookie); + ContentResponse response2 = request.send(); + assertEquals(HttpServletResponse.SC_OK,response2.getStatus()); + + + //check the timeout in mongo + verifySessionTimeout(sessions, sessionId, inactivePeriod); + //check the session expiry time has decreased from previous value + assertTrue(getSessionExpiry(sessions, sessionId) 0) + val = sec*1000L; + else + val = sec; + + assertNotNull(sessions); + assertNotNull(id); + + DBObject o = sessions.findOne(new BasicDBObject(MongoSessionDataStore.__ID,id)); + assertNotNull(o); + Long maxIdle = (Long)o.get(MongoSessionDataStore.__MAX_IDLE); + assertNotNull(maxIdle); + assertEquals(val, maxIdle.longValue()); + } + + public long getSessionExpiry (DBCollection sessions, String id) throws Exception + { + assertNotNull(sessions); + assertNotNull(id); + + DBObject o = sessions.findOne(new BasicDBObject(MongoSessionDataStore.__ID,id)); + assertNotNull(o); + Long expiry = (Long)o.get(MongoSessionDataStore.__EXPIRY); + return (expiry == null? null : expiry.longValue()); + } + + public long getSessionMaxInactiveInterval (DBCollection sessions, String id) throws Exception + { + assertNotNull(sessions); + assertNotNull(id); + + DBObject o = sessions.findOne(new BasicDBObject(MongoSessionDataStore.__ID,id)); + assertNotNull(o); + Long inactiveInterval = (Long)o.get(MongoSessionDataStore.__MAX_IDLE); + return (inactiveInterval == null? null : inactiveInterval.longValue()); + } + + public long getSessionAccessed (DBCollection sessions, String id) throws Exception + { + assertNotNull(sessions); + assertNotNull(id); + + DBObject o = sessions.findOne(new BasicDBObject(MongoSessionDataStore.__ID,id)); + assertNotNull(o); + Long accessed = (Long)o.get(MongoSessionDataStore.__ACCESSED); + return (accessed == null? null : accessed.longValue()); + } + + public void debugPrint (DBCollection sessions, String id) throws Exception + { + assertNotNull(sessions); + assertNotNull(id); + + DBObject o = sessions.findOne(new BasicDBObject(MongoSessionDataStore.__ID,id)); + assertNotNull(o); + System.err.println(o); + } + + public static class ChangeTimeoutServlet extends HttpServlet + { + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse httpServletResponse) throws ServletException, IOException + { + String action = request.getParameter("action"); + if ("init".equals(action)) + { + HttpSession session = request.getSession(true); + session.setAttribute("test", "test"); + } + else if ("change".equals(action)) + { + String tmp = request.getParameter("val"); + int val = (StringUtil.isBlank(tmp)?0:Integer.valueOf(tmp.trim())); + HttpSession session = request.getSession(false); + assertNotNull(session); + session.setMaxInactiveInterval(val); + } + } + } + }