Merge branch 'master' into release-9
diff --git a/.travis.yml b/.travis.yml
index 80b6f4b..af6acdf 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,4 +1,8 @@
+branches:
+ only:
+ - /^release-.*$/
language: java
+script: mvn install
jdk:
- openjdk7
- oraclejdk7
diff --git a/examples/embedded/src/main/resources/jetty-logging.properties b/examples/embedded/src/main/resources/jetty-logging.properties
index 73456ad..5838fba 100644
--- a/examples/embedded/src/main/resources/jetty-logging.properties
+++ b/examples/embedded/src/main/resources/jetty-logging.properties
@@ -5,6 +5,6 @@
#org.eclipse.jetty.STACKS=false
#org.eclipse.jetty.spdy.LEVEL=DEBUG
#org.eclipse.jetty.server.LEVEL=DEBUG
-org.eclipse.jetty.io.LEVEL=DEBUG
-org.eclipse.jetty.io.ssl.LEVEL=DEBUG
+#org.eclipse.jetty.io.LEVEL=DEBUG
+#org.eclipse.jetty.io.ssl.LEVEL=DEBUG
#org.eclipse.jetty.spdy.server.LEVEL=DEBUG
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 7b66d6f..7adcb80 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
@@ -40,6 +40,7 @@
public static final int DEFAULT_MAX_CONTENT_LENGTH = 4096;
public static final Logger LOG = Log.getLogger(AuthenticationProtocolHandler.class);
private static final Pattern AUTHENTICATE_PATTERN = Pattern.compile("([^\\s]+)\\s+realm=\"([^\"]+)\"(.*)", Pattern.CASE_INSENSITIVE);
+ private static final String AUTHENTICATION_ATTRIBUTE = AuthenticationProtocolHandler.class.getName() + ".authentication";
private final HttpClient client;
private final int maxContentLength;
@@ -90,6 +91,15 @@
return;
}
+ HttpConversation conversation = client.getConversation(request.getConversationID(), false);
+ if (conversation.getAttribute(AUTHENTICATION_ATTRIBUTE) != null)
+ {
+ // We have already tried to authenticate, but we failed again
+ LOG.debug("Bad credentials for {}", request);
+ forwardSuccessComplete(request, response);
+ return;
+ }
+
HttpHeader header = getAuthenticateHeader();
List<Authentication.HeaderInfo> headerInfos = parseAuthenticateHeader(response, header);
if (headerInfos.isEmpty())
@@ -118,7 +128,6 @@
return;
}
- HttpConversation conversation = client.getConversation(request.getConversationID(), false);
final Authentication.Result authnResult = authentication.authenticate(request, response, headerInfo, conversation);
LOG.debug("Authentication result {}", authnResult);
if (authnResult == null)
@@ -127,6 +136,8 @@
return;
}
+ conversation.setAttribute(AUTHENTICATION_ATTRIBUTE, true);
+
Request newRequest = client.copyRequest(request, request.getURI());
authnResult.apply(newRequest);
newRequest.onResponseSuccess(new Response.SuccessListener()
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 dd2c7bc..643d117 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
@@ -63,6 +63,7 @@
import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.SocketAddressResolver;
+import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -362,7 +363,7 @@
*/
public Request newRequest(String host, int port)
{
- return newRequest(URI.create(address("http", host, port)));
+ return newRequest(address("http", host, port));
}
/**
@@ -417,9 +418,11 @@
return newRequest;
}
- private String address(String scheme, String host, int port)
+ protected String address(String scheme, String host, int port)
{
- return scheme + "://" + host + ":" + port;
+ StringBuilder result = new StringBuilder();
+ URIUtil.appendSchemeHostPort(result, scheme, host, port);
+ return result.toString();
}
/**
@@ -900,6 +903,13 @@
return encodingField;
}
+ protected String normalizeHost(String host)
+ {
+ if (host != null && host.matches("\\[.*\\]"))
+ return host.substring(1, host.length() - 1);
+ return host;
+ }
+
protected int normalizePort(String scheme, int port)
{
return port > 0 ? port : HttpScheme.HTTPS.is(scheme) ? 443 : 80;
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 ca61d22..1769ff3 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
@@ -18,14 +18,10 @@
package org.eclipse.jetty.client;
-import java.io.UnsupportedEncodingException;
import java.net.HttpCookie;
import java.net.URI;
-import java.net.URLEncoder;
-import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList;
import java.util.Enumeration;
-import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@@ -35,17 +31,14 @@
import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
-import org.eclipse.jetty.client.util.StringContentProvider;
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.http.MimeTypes;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -166,45 +159,12 @@
path = "/";
request.path(path);
}
- if (destination.isProxied() && HttpMethod.CONNECT != request.getMethod())
+ if (destination.isProxied() && HttpMethod.CONNECT != method)
{
path = request.getURI().toString();
request.path(path);
}
- Fields fields = request.getParams();
- if (!fields.isEmpty())
- {
- StringBuilder params = new StringBuilder();
- for (Iterator<Fields.Field> fieldIterator = fields.iterator(); fieldIterator.hasNext();)
- {
- Fields.Field field = fieldIterator.next();
- String[] values = field.values();
- for (int i = 0; i < values.length; ++i)
- {
- if (i > 0)
- params.append("&");
- params.append(field.name()).append("=");
- params.append(urlEncode(values[i]));
- }
- if (fieldIterator.hasNext())
- params.append("&");
- }
-
- // POST with no content, send parameters as body
- if (method == HttpMethod.POST && request.getContent() == null)
- {
- request.header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.FORM_ENCODED.asString());
- request.content(new StringContentProvider(params.toString()));
- }
- else
- {
- path += "?";
- path += params.toString();
- request.path(path);
- }
- }
-
// If we are HTTP 1.1, add the Host header
if (version.getVersion() > 10)
{
@@ -257,19 +217,6 @@
}
}
- private String urlEncode(String value)
- {
- String encoding = "UTF-8";
- try
- {
- return URLEncoder.encode(value, encoding);
- }
- catch (UnsupportedEncodingException e)
- {
- throw new UnsupportedCharsetException(encoding);
- }
- }
-
public HttpExchange getExchange()
{
return exchange.get();
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 081e4b6..2f33731 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
@@ -374,10 +374,9 @@
}
@Override
- public boolean earlyEOF()
+ public void earlyEOF()
{
failAndClose(new EOFException());
- return false;
}
private void failAndClose(Throwable failure)
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 6841f94..458dca3 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
@@ -22,10 +22,12 @@
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
+import java.net.URLEncoder;
import java.nio.charset.UnsupportedCharsetException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -62,6 +64,7 @@
private URI uri;
private String scheme;
private String path;
+ private String query;
private HttpMethod method;
private HttpVersion version;
private long idleTimeout;
@@ -80,35 +83,15 @@
this.client = client;
this.conversation = conversation;
scheme = uri.getScheme();
- host = uri.getHost();
+ host = client.normalizeHost(uri.getHost());
port = client.normalizePort(scheme, uri.getPort());
path = uri.getRawPath();
- String query = uri.getRawQuery();
- if (query != null)
- {
- for (String nameValue : query.split("&"))
- {
- String[] parts = nameValue.split("=");
- param(parts[0], parts.length < 2 ? "" : urlDecode(parts[1]));
- }
- }
+ query = uri.getRawQuery();
+ extractParams(query);
this.uri = buildURI();
followRedirects(client.isFollowRedirects());
}
- private String urlDecode(String value)
- {
- String charset = "UTF-8";
- try
- {
- return URLDecoder.decode(value, charset);
- }
- catch (UnsupportedEncodingException x)
- {
- throw new UnsupportedCharsetException(charset);
- }
- }
-
@Override
public long getConversationID()
{
@@ -163,12 +146,26 @@
@Override
public Request path(String path)
{
- this.path = path;
+ URI uri = URI.create(path);
+ this.path = uri.getRawPath();
+ String query = uri.getRawQuery();
+ if (query != null)
+ {
+ this.query = query;
+ params.clear();
+ extractParams(query);
+ }
this.uri = buildURI();
return this;
}
@Override
+ public String getQuery()
+ {
+ return query;
+ }
+
+ @Override
public URI getURI()
{
return uri;
@@ -191,13 +188,14 @@
public Request param(String name, String value)
{
params.add(name, value);
+ this.query = buildQuery();
return this;
}
@Override
public Fields getParams()
{
- return params;
+ return new Fields(params, true);
}
@Override
@@ -496,12 +494,73 @@
return aborted;
}
+ private String buildQuery()
+ {
+ StringBuilder result = new StringBuilder();
+ for (Iterator<Fields.Field> iterator = params.iterator(); iterator.hasNext();)
+ {
+ Fields.Field field = iterator.next();
+ String[] values = field.values();
+ for (int i = 0; i < values.length; ++i)
+ {
+ if (i > 0)
+ result.append("&");
+ result.append(field.name()).append("=");
+ result.append(urlEncode(values[i]));
+ }
+ if (iterator.hasNext())
+ result.append("&");
+ }
+ return result.toString();
+ }
+
+ private String urlEncode(String value)
+ {
+ String encoding = "UTF-8";
+ try
+ {
+ return URLEncoder.encode(value, encoding);
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ throw new UnsupportedCharsetException(encoding);
+ }
+ }
+
+ private void extractParams(String query)
+ {
+ if (query != null)
+ {
+ for (String nameValue : query.split("&"))
+ {
+ String[] parts = nameValue.split("=");
+ param(parts[0], parts.length < 2 ? "" : urlDecode(parts[1]));
+ }
+ }
+ }
+
+ private String urlDecode(String value)
+ {
+ String charset = "UTF-8";
+ try
+ {
+ return URLDecoder.decode(value, charset);
+ }
+ catch (UnsupportedEncodingException x)
+ {
+ throw new UnsupportedCharsetException(charset);
+ }
+ }
+
private URI buildURI()
{
String path = getPath();
+ String query = getQuery();
+ if (query != null)
+ path += "?" + query;
URI result = URI.create(path);
if (!result.isAbsolute())
- result = URI.create(getScheme() + "://" + getHost() + ":" + getPort() + path);
+ result = URI.create(client.address(getScheme(), getHost(), getPort()) + path);
return result;
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java
index 17835ba..f6737cc 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java
@@ -172,7 +172,11 @@
{
ContentProvider requestContent = request.getContent();
long contentLength = requestContent == null ? -1 : requestContent.getLength();
- requestInfo = new HttpGenerator.RequestInfo(request.getVersion(), request.getHeaders(), contentLength, request.getMethod().asString(), request.getPath());
+ String path = request.getPath();
+ String query = request.getQuery();
+ if (query != null)
+ path += "?" + query;
+ requestInfo = new HttpGenerator.RequestInfo(request.getVersion(), request.getHeaders(), contentLength, request.getMethod().asString(), path);
break;
}
case NEED_HEADER:
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java
index f78447c..252aff1 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java
@@ -20,6 +20,7 @@
import java.io.IOException;
import java.net.URI;
+import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.util.EventListener;
@@ -86,18 +87,33 @@
Request method(HttpMethod method);
/**
- * @return the path of this request, such as "/"
+ * @return the path of this request, such as "/" or "/path" - without the query
+ * @see #getQuery()
*/
String getPath();
/**
- * @param path the path of this request, such as "/"
+ * Specifies the path - and possibly the query - of this request.
+ * If the query part is specified, parameter values must be properly
+ * {@link URLEncoder#encode(String, String) UTF-8 URL encoded}.
+ * For example, if the parameter value is the euro symbol € then the
+ * query string must be "param=%E2%82%AC".
+ * For transparent encoding of parameter values, use {@link #param(String, String)}.
+ *
+ * @param path the path of this request, such as "/" or "/path?param=1"
* @return this request object
*/
Request path(String path);
/**
- * @return the full URI of this request such as "http://host:port/path"
+ * @return the query string of this request such as "param=1"
+ * @see #getPath()
+ * @see #getParams()
+ */
+ String getQuery();
+
+ /**
+ * @return the full URI of this request such as "http://host:port/path?param=1"
*/
URI getURI();
@@ -118,6 +134,9 @@
Fields getParams();
/**
+ * Adds a query parameter with the given name and value.
+ * The value is {@link URLEncoder#encode(String, String) UTF-8 URL encoded}.
+ *
* @param name the name of the query parameter
* @param value the value of the query parameter
* @return this request object
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ExternalSiteTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ExternalSiteTest.java
index a50bf5b..503ad74 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/ExternalSiteTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ExternalSiteTest.java
@@ -20,7 +20,6 @@
import java.io.IOException;
import java.net.Socket;
-import java.net.URI;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -94,8 +93,6 @@
{
Assert.assertTrue(result.isSucceeded());
Assert.assertEquals(200, result.getResponse().getStatus());
- URI uri = result.getRequest().getURI();
- Assert.assertTrue(uri.getPort() > 0);
latch2.countDown();
}
});
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java
index 80c3d0b..6f6d705 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java
@@ -304,4 +304,20 @@
Assert.assertEquals(401, response.getStatus());
Assert.assertTrue(requests.get().await(5, TimeUnit.SECONDS));
}
+
+ @Test
+ public void test_BasicAuthentication_WithWrongPassword() throws Exception
+ {
+ startBasic(new EmptyServerHandler());
+
+ AuthenticationStore authenticationStore = client.getAuthenticationStore();
+ URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort());
+ BasicAuthentication authentication = new BasicAuthentication(uri, realm, "basic", "wrong");
+ authenticationStore.addAuthentication(authentication);
+
+ Request request = client.newRequest("localhost", connector.getLocalPort()).scheme(scheme).path("/secure");
+ ContentResponse response = request.timeout(555, TimeUnit.SECONDS).send();
+ Assert.assertNotNull(response);
+ Assert.assertEquals(401, response.getStatus());
+ }
}
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
new file mode 100644
index 0000000..0727e3a
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientURITest.java
@@ -0,0 +1,241 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.util.concurrent.TimeUnit;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class HttpClientURITest extends AbstractHttpClientServerTest
+{
+ public HttpClientURITest(SslContextFactory sslContextFactory)
+ {
+ super(sslContextFactory);
+ }
+
+ @Test
+ public void testIPv6Host() throws Exception
+ {
+ start(new EmptyServerHandler());
+
+ String host = "::1";
+ Request request = client.newRequest(host, connector.getLocalPort())
+ .scheme(scheme)
+ .timeout(5, TimeUnit.SECONDS);
+
+ Assert.assertEquals(host, request.getHost());
+ StringBuilder uri = new StringBuilder();
+ URIUtil.appendSchemeHostPort(uri, scheme, host, connector.getLocalPort());
+ Assert.assertEquals(uri.toString(), request.getURI().toString());
+
+ Assert.assertEquals(HttpStatus.OK_200, request.send().getStatus());
+ }
+
+ @Test
+ public void testPath() throws Exception
+ {
+ final String path = "/path";
+ 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(path, request.getRequestURI());
+ }
+ });
+
+ Request request = client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .timeout(5, TimeUnit.SECONDS)
+ .path(path);
+
+ Assert.assertEquals(path, request.getPath());
+ Assert.assertNull(request.getQuery());
+ Fields params = request.getParams();
+ Assert.assertEquals(0, params.size());
+ Assert.assertTrue(request.getURI().toString().endsWith(path));
+
+ ContentResponse response = request.send();
+
+ Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+ }
+
+ @Test
+ public void testPathWithQuery() throws Exception
+ {
+ String name = "a";
+ String value = "1";
+ final String query = name + "=" + value;
+ final String path = "/path";
+ 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(path, request.getRequestURI());
+ Assert.assertEquals(query, request.getQueryString());
+ }
+ });
+
+ Request request = client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .timeout(5, TimeUnit.SECONDS)
+ .path(path + "?" + query);
+
+ Assert.assertEquals(path, request.getPath());
+ Assert.assertEquals(query, request.getQuery());
+ Fields params = request.getParams();
+ Assert.assertEquals(1, params.size());
+ Assert.assertEquals(value, params.get(name).value());
+
+ ContentResponse response = request.send();
+
+ Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+ }
+
+ @Test
+ public void testPathWithParam() throws Exception
+ {
+ String name = "a";
+ String value = "1";
+ final String query = name + "=" + value;
+ final String path = "/path";
+ 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(path, request.getRequestURI());
+ Assert.assertEquals(query, request.getQueryString());
+ }
+ });
+
+ Request request = client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .timeout(5, TimeUnit.SECONDS)
+ .path(path)
+ .param(name, value);
+
+ Assert.assertEquals(path, request.getPath());
+ Assert.assertEquals(query, request.getQuery());
+ Fields params = request.getParams();
+ Assert.assertEquals(1, params.size());
+ Assert.assertEquals(value, params.get(name).value());
+
+ ContentResponse response = request.send();
+
+ Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+ }
+
+ @Test
+ public void testPathWithQueryAndParam() throws Exception
+ {
+ String name1 = "a";
+ String value1 = "1";
+ String name2 = "b";
+ String value2 = "2";
+ final String query = name1 + "=" + value1 + "&" + name2 + "=" + value2;
+ final String path = "/path";
+ 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(path, request.getRequestURI());
+ Assert.assertEquals(query, request.getQueryString());
+ }
+ });
+
+ Request request = client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .timeout(5, TimeUnit.SECONDS)
+ .path(path + "?" + name1 + "=" + value1)
+ .param(name2, value2);
+
+ Assert.assertEquals(path, request.getPath());
+ Assert.assertEquals(query, request.getQuery());
+ Fields params = request.getParams();
+ Assert.assertEquals(2, params.size());
+ Assert.assertEquals(value1, params.get(name1).value());
+ Assert.assertEquals(value2, params.get(name2).value());
+
+ ContentResponse response = request.send();
+
+ Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+ }
+
+ @Test
+ public void testPathWithQueryAndParamValueEncoded() throws Exception
+ {
+ final String name1 = "a";
+ final String value1 = "\u20AC";
+ final String encodedValue1 = URLEncoder.encode(value1, "UTF-8");
+ final String name2 = "b";
+ final String value2 = "\u00A5";
+ String encodedValue2 = URLEncoder.encode(value2, "UTF-8");
+ final String query = name1 + "=" + encodedValue1 + "&" + name2 + "=" + encodedValue2;
+ final String path = "/path";
+ 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(path, request.getRequestURI());
+ Assert.assertEquals(query, request.getQueryString());
+ Assert.assertEquals(value1, request.getParameter(name1));
+ Assert.assertEquals(value2, request.getParameter(name2));
+ }
+ });
+
+ Request request = client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .timeout(5, TimeUnit.SECONDS)
+ .path(path + "?" + name1 + "=" + encodedValue1)
+ .param(name2, value2);
+
+ Assert.assertEquals(path, request.getPath());
+ Assert.assertEquals(query, request.getQuery());
+ Fields params = request.getParams();
+ Assert.assertEquals(2, params.size());
+ Assert.assertEquals(value1, params.get(name1).value());
+ Assert.assertEquals(value2, params.get(name2).value());
+
+ ContentResponse response = request.send();
+
+ Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+ }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java
index 4d2aa2f..b9be5c1 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java
@@ -24,6 +24,8 @@
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -66,7 +68,10 @@
Assert.assertEquals(0, activeConnections.size());
final CountDownLatch headersLatch = new CountDownLatch(1);
+ final CountDownLatch testLatch = new CountDownLatch(1);
final CountDownLatch successLatch = new CountDownLatch(3);
+ final AtomicBoolean failed = new AtomicBoolean(false);
+
client.newRequest(host, port)
.scheme(scheme)
.onRequestSuccess(new Request.SuccessListener()
@@ -82,9 +87,15 @@
@Override
public void onHeaders(Response response)
{
- Assert.assertEquals(0, idleConnections.size());
- Assert.assertEquals(1, activeConnections.size());
headersLatch.countDown();
+ try
+ {
+ testLatch.await();
+ }
+ catch (InterruptedException e)
+ {
+ e.printStackTrace();
+ }
}
})
.send(new Response.Listener.Empty()
@@ -98,16 +109,21 @@
@Override
public void onComplete(Result result)
{
- Assert.assertFalse(result.isFailed());
+ failed.set(result.isFailed());
successLatch.countDown();
}
});
Assert.assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
+ Assert.assertEquals(0, idleConnections.size());
+ Assert.assertEquals(1, activeConnections.size());
+ testLatch.countDown();
+
Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS));
Assert.assertEquals(1, idleConnections.size());
Assert.assertEquals(0, activeConnections.size());
+ Assert.assertFalse(failed.get());
}
@Test
@@ -127,6 +143,8 @@
final CountDownLatch beginLatch = new CountDownLatch(1);
final CountDownLatch failureLatch = new CountDownLatch(2);
+ final AtomicBoolean failed = new AtomicBoolean(false);
+
client.newRequest(host, port).scheme(scheme).listener(new Request.Listener.Empty()
{
@Override
@@ -146,9 +164,7 @@
@Override
public void onComplete(Result result)
{
- Assert.assertTrue(result.isFailed());
- Assert.assertEquals(0, idleConnections.size());
- Assert.assertEquals(0, activeConnections.size());
+ failed.set(result.isFailed());
failureLatch.countDown();
}
});
@@ -158,6 +174,7 @@
Assert.assertEquals(0, idleConnections.size());
Assert.assertEquals(0, activeConnections.size());
+ Assert.assertTrue(failed.get());
}
@Test
@@ -176,6 +193,9 @@
Assert.assertEquals(0, activeConnections.size());
final CountDownLatch successLatch = new CountDownLatch(3);
+ final AtomicBoolean failed = new AtomicBoolean(false);
+ final AtomicBoolean four_hundred = new AtomicBoolean(false);
+
client.newRequest(host, port)
.scheme(scheme)
.listener(new Request.Listener.Empty()
@@ -198,16 +218,16 @@
@Override
public void onSuccess(Response response)
{
- Assert.assertEquals(400, response.getStatus());
// 400 response also come with a Connection: close,
// so the connection is closed and removed
+ four_hundred.set(response.getStatus()==400);
successLatch.countDown();
}
@Override
public void onComplete(Result result)
{
- Assert.assertFalse(result.isFailed());
+ failed.set(result.isFailed());
successLatch.countDown();
}
});
@@ -216,6 +236,8 @@
Assert.assertEquals(0, idleConnections.size());
Assert.assertEquals(0, activeConnections.size());
+ Assert.assertFalse(failed.get());
+ Assert.assertTrue(four_hundred.get());
}
@Slow
@@ -236,6 +258,8 @@
final long delay = 1000;
final CountDownLatch successLatch = new CountDownLatch(3);
+ final AtomicBoolean failed = new AtomicBoolean(false);
+ final AtomicBoolean four_hundred = new AtomicBoolean(false);
client.newRequest(host, port)
.scheme(scheme)
.listener(new Request.Listener.Empty()
@@ -271,16 +295,16 @@
@Override
public void onSuccess(Response response)
{
- Assert.assertEquals(400, response.getStatus());
// 400 response also come with a Connection: close,
// so the connection is closed and removed
+ four_hundred.set(response.getStatus()==400);
successLatch.countDown();
}
@Override
public void onComplete(Result result)
{
- Assert.assertFalse(result.isFailed());
+ failed.set(result.isFailed());
successLatch.countDown();
}
});
@@ -289,6 +313,8 @@
Assert.assertEquals(0, idleConnections.size());
Assert.assertEquals(0, activeConnections.size());
+ Assert.assertFalse(failed.get());
+ Assert.assertTrue(four_hundred.get());
}
@Test
@@ -309,6 +335,7 @@
server.stop();
final CountDownLatch failureLatch = new CountDownLatch(2);
+ final AtomicBoolean failed = new AtomicBoolean(false);
client.newRequest(host, port)
.scheme(scheme)
.onRequestFailure(new Request.FailureListener()
@@ -324,7 +351,7 @@
@Override
public void onComplete(Result result)
{
- Assert.assertTrue(result.isFailed());
+ failed.set(result.isFailed());
failureLatch.countDown();
}
});
@@ -333,6 +360,7 @@
Assert.assertEquals(0, idleConnections.size());
Assert.assertEquals(0, activeConnections.size());
+ Assert.assertTrue(failed.get());
}
@Test
@@ -359,6 +387,7 @@
Assert.assertEquals(0, activeConnections.size());
final CountDownLatch latch = new CountDownLatch(1);
+ final AtomicBoolean failed = new AtomicBoolean(false);
client.newRequest(host, port)
.scheme(scheme)
.send(new Response.Listener.Empty()
@@ -366,9 +395,7 @@
@Override
public void onComplete(Result result)
{
- Assert.assertFalse(result.isFailed());
- Assert.assertEquals(0, idleConnections.size());
- Assert.assertEquals(0, activeConnections.size());
+ failed.set(result.isFailed());
latch.countDown();
}
});
@@ -377,6 +404,7 @@
Assert.assertEquals(0, idleConnections.size());
Assert.assertEquals(0, activeConnections.size());
+ Assert.assertFalse(failed.get());
}
@Test
@@ -410,6 +438,7 @@
Log.getLogger(HttpConnection.class).info("Expecting java.lang.IllegalStateException: HttpParser{s=CLOSED,...");
final CountDownLatch latch = new CountDownLatch(1);
+ final AtomicBoolean failed = new AtomicBoolean(false);
ByteBuffer buffer = ByteBuffer.allocate(16 * 1024 * 1024);
Arrays.fill(buffer.array(),(byte)'x');
client.newRequest(host, port)
@@ -420,8 +449,7 @@
@Override
public void onComplete(Result result)
{
- Assert.assertEquals(0, idleConnections.size());
- Assert.assertEquals(0, activeConnections.size());
+ failed.set(result.isFailed());
latch.countDown();
}
});
@@ -430,6 +458,7 @@
Assert.assertEquals(0, idleConnections.size());
Assert.assertEquals(0, activeConnections.size());
+ Assert.assertTrue(failed.get());
server.stop();
}
finally
diff --git a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/test/XmlConfiguredJetty.java b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/test/XmlConfiguredJetty.java
index 9e94676..fdc8092 100644
--- a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/test/XmlConfiguredJetty.java
+++ b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/test/XmlConfiguredJetty.java
@@ -44,6 +44,7 @@
import org.eclipse.jetty.toolchain.test.PathAssert;
import org.eclipse.jetty.toolchain.test.TestingDir;
import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.xml.XmlConfiguration;
@@ -290,9 +291,7 @@
public URI getServerURI() throws UnknownHostException
{
StringBuilder uri = new StringBuilder();
- uri.append(getScheme()).append("://");
- uri.append(InetAddress.getLocalHost().getHostAddress());
- uri.append(":").append(getServerPort());
+ URIUtil.appendSchemeHostPort(uri, getScheme(), InetAddress.getLocalHost().getHostAddress(), getServerPort());
return URI.create(uri.toString());
}
diff --git a/jetty-distribution/pom.xml b/jetty-distribution/pom.xml
index 2189d3b..e06e1fb 100644
--- a/jetty-distribution/pom.xml
+++ b/jetty-distribution/pom.xml
@@ -100,17 +100,47 @@
<type>war</type>
<overWrite>true</overWrite>
<includes>**</includes>
- <outputDirectory>${assembly-directory}/webapps</outputDirectory>
+ <outputDirectory>${assembly-directory}/webapps.demo</outputDirectory>
<destFileName>test.war</destFileName>
</artifactItem>
<artifactItem>
+ <groupId>org.eclipse.jetty.tests</groupId>
+ <artifactId>test-jaas-webapp</artifactId>
+ <version>${project.version}</version>
+ <type>war</type>
+ <overWrite>true</overWrite>
+ <includes>**</includes>
+ <outputDirectory>${assembly-directory}/webapps.demo</outputDirectory>
+ <destFileName>test-jaas.war</destFileName>
+ </artifactItem>
+ <artifactItem>
+ <groupId>org.eclipse.jetty.tests</groupId>
+ <artifactId>test-jndi-webapp</artifactId>
+ <version>${project.version}</version>
+ <type>war</type>
+ <overWrite>true</overWrite>
+ <includes>**</includes>
+ <outputDirectory>${assembly-directory}/webapps.demo</outputDirectory>
+ <destFileName>test-jndi.war</destFileName>
+ </artifactItem>
+ <artifactItem>
+ <groupId>org.eclipse.jetty.tests</groupId>
+ <artifactId>test-spec-webapp</artifactId>
+ <version>${project.version}</version>
+ <type>war</type>
+ <overWrite>true</overWrite>
+ <includes>**</includes>
+ <outputDirectory>${assembly-directory}/webapps.demo</outputDirectory>
+ <destFileName>test-spec.war</destFileName>
+ </artifactItem>
+ <artifactItem>
<groupId>org.eclipse.jetty</groupId>
<artifactId>test-proxy-webapp</artifactId>
<version>${project.version}</version>
<type>war</type>
<overWrite>true</overWrite>
<includes>**</includes>
- <outputDirectory>${assembly-directory}/webapps</outputDirectory>
+ <outputDirectory>${assembly-directory}/webapps.demo</outputDirectory>
<destFileName>xref-proxy.war</destFileName>
</artifactItem>
<artifactItem>
@@ -120,7 +150,7 @@
<type>war</type>
<overWrite>true</overWrite>
<includes>**</includes>
- <outputDirectory>${assembly-directory}/webapps</outputDirectory>
+ <outputDirectory>${assembly-directory}/webapps.demo</outputDirectory>
<destFileName>async-rest.war</destFileName>
</artifactItem>
<artifactItem>
@@ -173,6 +203,7 @@
</artifactItems>
</configuration>
</execution>
+
<execution>
<id>unpack-setuid-config</id>
<phase>process-resources</phase>
@@ -193,6 +224,70 @@
</artifactItems>
</configuration>
</execution>
+
+ <execution>
+ <id>unpack-test-jaas-config</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>unpack</goal>
+ </goals>
+ <configuration>
+ <artifactItems>
+ <artifactItem>
+ <groupId>org.eclipse.jetty.tests</groupId>
+ <artifactId>test-jaas-webapp</artifactId>
+ <version>${project.version}</version>
+ <classifier>config</classifier>
+ <type>jar</type>
+ <overWrite>true</overWrite>
+ <outputDirectory>${assembly-directory}</outputDirectory>
+ </artifactItem>
+ </artifactItems>
+ </configuration>
+ </execution>
+
+ <execution>
+ <id>unpack-test-jndi-config</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>unpack</goal>
+ </goals>
+ <configuration>
+ <artifactItems>
+ <artifactItem>
+ <groupId>org.eclipse.jetty.tests</groupId>
+ <artifactId>test-jndi-webapp</artifactId>
+ <version>${project.version}</version>
+ <classifier>config</classifier>
+ <type>jar</type>
+ <overWrite>true</overWrite>
+ <outputDirectory>${assembly-directory}</outputDirectory>
+ </artifactItem>
+ </artifactItems>
+ </configuration>
+ </execution>
+
+ <execution>
+ <id>unpack-test-spec-config</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>unpack</goal>
+ </goals>
+ <configuration>
+ <artifactItems>
+ <artifactItem>
+ <groupId>org.eclipse.jetty.tests</groupId>
+ <artifactId>test-spec-webapp</artifactId>
+ <version>${project.version}</version>
+ <classifier>config</classifier>
+ <type>jar</type>
+ <overWrite>true</overWrite>
+ <outputDirectory>${assembly-directory}</outputDirectory>
+ </artifactItem>
+ </artifactItems>
+ </configuration>
+ </execution>
+
<execution>
<id>copy-lib-deps</id>
<phase>generate-resources</phase>
diff --git a/jetty-distribution/src/main/resources/bin/jetty.sh b/jetty-distribution/src/main/resources/bin/jetty.sh
index cc3f771..bfcb1fd 100755
--- a/jetty-distribution/src/main/resources/bin/jetty.sh
+++ b/jetty-distribution/src/main/resources/bin/jetty.sh
@@ -118,9 +118,9 @@
for T in 1 2 3 4 5 6 7 9 10 11 12 13 14 15
do
sleep 4
- [ -z "$(grep STARTED $1)" ] || return 0
- [ -z "$(grep STOPPED $1)" ] || return 1
- [ -z "$(grep FAILED $1)" ] || return 1
+ [ -z "$(grep STARTED $1 2>/dev/null)" ] || return 0
+ [ -z "$(grep STOPPED $1 2>/dev/null)" ] || return 1
+ [ -z "$(grep FAILED $1 2>/dev/null)" ] || return 1
local PID=$(cat "$2" 2>/dev/null) || return 1
kill -0 "$PID" 2>/dev/null || return 1
echo -n ". "
@@ -338,13 +338,17 @@
fi
#####################################################
-# Find a PID for the pid file
+# Find a pid and state file
#####################################################
if [ -z "$JETTY_PID" ]
then
JETTY_PID="$JETTY_RUN/jetty.pid"
fi
-JETTY_STATE=$(dirname $JETTY_PID)/jetty.state
+
+if [ -z "$JETTY_STATE" ]
+then
+ JETTY_STATE=$JETTY_HOME/jetty.state
+fi
JAVA_OPTIONS+=("-Djetty.state=$JETTY_STATE")
rm -f $JETTY_STATE
@@ -415,8 +419,8 @@
then
echo "JETTY_HOME = $JETTY_HOME"
echo "JETTY_CONF = $JETTY_CONF"
- echo "JETTY_RUN = $JETTY_RUN"
echo "JETTY_PID = $JETTY_PID"
+ echo "JETTY_START = $JETTY_START"
echo "JETTY_ARGS = $JETTY_ARGS"
echo "CONFIGS = ${CONFIGS[*]}"
echo "JAVA_OPTIONS = ${JAVA_OPTIONS[*]}"
@@ -566,22 +570,21 @@
fi
exec "${RUN_CMD[@]}"
-
;;
check|status)
echo "Checking arguments to Jetty: "
+ echo "START_INI = $START_INI"
echo "JETTY_HOME = $JETTY_HOME"
echo "JETTY_CONF = $JETTY_CONF"
- echo "JETTY_RUN = $JETTY_RUN"
echo "JETTY_PID = $JETTY_PID"
- echo "JETTY_PORT = $JETTY_PORT"
+ echo "JETTY_START = $JETTY_START"
echo "JETTY_LOGS = $JETTY_LOGS"
- echo "START_INI = $START_INI"
echo "CONFIGS = ${CONFIGS[*]}"
- echo "JAVA_OPTIONS = ${JAVA_OPTIONS[*]}"
- echo "JAVA = $JAVA"
echo "CLASSPATH = $CLASSPATH"
+ echo "JAVA = $JAVA"
+ echo "JAVA_OPTIONS = ${JAVA_OPTIONS[*]}"
+ echo "JETTY_ARGS = $JETTY_ARGS"
echo "RUN_CMD = ${RUN_CMD[*]}"
echo
diff --git a/jetty-distribution/src/main/resources/etc/jetty-demo.xml b/jetty-distribution/src/main/resources/etc/jetty-demo.xml
new file mode 100644
index 0000000..4dc0aae
--- /dev/null
+++ b/jetty-distribution/src/main/resources/etc/jetty-demo.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+
+<!-- =============================================================== -->
+<!-- Configure the demos -->
+<!-- =============================================================== -->
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+
+ <!-- ============================================================= -->
+ <!-- Add webapps.demo to deployment manager scans -->
+ <!-- ============================================================= -->
+ <Ref refid="DeploymentManager">
+ <Call id="webappprovider" name="addAppProvider">
+ <Arg>
+ <New class="org.eclipse.jetty.deploy.providers.WebAppProvider">
+ <Set name="monitoredDirName"><Property name="jetty.home" default="." />/webapps.demo</Set>
+ <Set name="defaultsDescriptor"><Property name="jetty.home" default="." />/etc/webdefault.xml</Set>
+ <Set name="scanInterval">1</Set>
+ <Set name="extractWars">true</Set>
+ <Set name="configurationManager">
+ <New class="org.eclipse.jetty.deploy.PropertiesConfigurationManager"/>
+ </Set>
+ </New>
+ </Arg>
+ </Call>
+ </Ref>
+
+
+ <!-- ============================================================= -->
+ <!-- Add rewrite rules -->
+ <!-- ============================================================= -->
+ <Ref refid="Rewrite">
+ <!-- Add rule to protect against IE ssl bug -->
+ <Call name="addRule">
+ <Arg>
+ <New class="org.eclipse.jetty.rewrite.handler.MsieSslRule"/>
+ </Arg>
+ </Call>
+
+ <!-- protect favicon handling -->
+ <Call name="addRule">
+ <Arg>
+ <New class="org.eclipse.jetty.rewrite.handler.HeaderPatternRule">
+ <Set name="pattern">/favicon.ico</Set>
+ <Set name="name">Cache-Control</Set>
+ <Set name="value">Max-Age=3600,public</Set>
+ <Set name="terminating">true</Set>
+ </New>
+ </Arg>
+ </Call>
+
+ <!-- redirect from the welcome page to a specific page -->
+ <Call name="addRule">
+ <Arg>
+ <New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
+ <Set name="pattern">/test/rewrite/</Set>
+ <Set name="replacement">/test/rewrite/info.html</Set>
+ </New>
+ </Arg>
+ </Call>
+
+ <!-- replace the entire request URI -->
+ <Call name="addRule">
+ <Arg>
+ <New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
+ <Set name="pattern">/test/some/old/context</Set>
+ <Set name="replacement">/test/rewritten/newcontext</Set>
+ </New>
+ </Arg>
+ </Call>
+
+ <!-- replace the beginning of the request URI -->
+ <Call name="addRule">
+ <Arg>
+ <New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
+ <Set name="pattern">/test/rewrite/for/*</Set>
+ <Set name="replacement">/test/rewritten/</Set>
+ </New>
+ </Arg>
+ </Call>
+
+ <!-- reverse the order of the path sections -->
+ <Call name="addRule">
+ <Arg>
+ <New class="org.eclipse.jetty.rewrite.handler.RewriteRegexRule">
+ <Set name="regex">(.*?)/reverse/([^/]*)/(.*)</Set>
+ <Set name="replacement">$1/reverse/$3/$2</Set>
+ </New>
+ </Arg>
+ </Call>
+
+ <!-- add a cookie to each path visited -->
+ <Call name="addRule">
+ <Arg>
+ <New class="org.eclipse.jetty.rewrite.handler.CookiePatternRule">
+ <Set name="pattern">/*</Set>
+ <Set name="name">visited</Set>
+ <Set name="value">yes</Set>
+ </New>
+ </Arg>
+ </Call>
+
+ <!-- actual redirect, instead of internal rewrite -->
+ <Call name="addRule">
+ <Arg>
+ <New class="org.eclipse.jetty.rewrite.handler.RedirectPatternRule">
+ <Set name="pattern">/test/redirect/*</Set>
+ <Set name="location">/test/redirected</Set>
+ </New>
+ </Arg>
+ </Call>
+
+ <!-- add a response rule -->
+ <Call name="addRule">
+ <Arg>
+ <New class="org.eclipse.jetty.rewrite.handler.ResponsePatternRule">
+ <Set name="pattern">/400Error</Set>
+ <Set name="code">400</Set>
+ <Set name="reason">ResponsePatternRule Demo</Set>
+ </New>
+ </Arg>
+ </Call>
+ </Ref>
+</Configure>
diff --git a/jetty-distribution/src/main/resources/start.d/900-demo.ini b/jetty-distribution/src/main/resources/start.d/900-demo.ini
new file mode 100644
index 0000000..fc153ad
--- /dev/null
+++ b/jetty-distribution/src/main/resources/start.d/900-demo.ini
@@ -0,0 +1,53 @@
+
+# ===========================================================
+# Enable the demonstration web applications
+#
+# To disable the demos, either delete this file, move it out of
+# the start.d directory or rename it to not end with ".ini"
+# ===========================================================
+
+# ===========================================================
+# Enable rewrite handler
+# -----------------------------------------------------------
+OPTIONS=rewrite
+etc/jetty-rewrite.xml
+
+# ===========================================================
+# Add a deploy app provider to scan the webapps.demo directory
+# -----------------------------------------------------------
+etc/jetty-demo.xml
+
+# ===========================================================
+# Enable the Jetty HTTP client APIs for use by demo webapps
+# -----------------------------------------------------------
+OPTIONS=client
+
+# ===========================================================
+# Enable the test-realm login service for use by authentication
+# demonstrations
+# -----------------------------------------------------------
+etc/test-realm.xml
+
+# ===========================================================
+# Enable JAAS test webapp
+# -----------------------------------------------------------
+OPTIONS=jaas
+jaas.login.conf=webapps.demo/test-jaas.d/login.conf
+etc/jetty-jaas.xml
+
+# ===========================================================
+# Enable JNDI test webapp
+# -----------------------------------------------------------
+OPTIONS=jndi,jndi.demo
+
+# ===========================================================
+# Enable additional webapp environment configurators
+# -----------------------------------------------------------
+OPTIONS=plus
+etc/jetty-plus.xml
+
+# ===========================================================
+# Enable servlet 3.1 annotations
+# -----------------------------------------------------------
+OPTIONS=annotations
+etc/jetty-annotations.xml
diff --git a/jetty-distribution/src/main/resources/start.ini b/jetty-distribution/src/main/resources/start.ini
index 382010d..fdd0102 100644
--- a/jetty-distribution/src/main/resources/start.ini
+++ b/jetty-distribution/src/main/resources/start.ini
@@ -1,5 +1,6 @@
#===========================================================
# Jetty start.jar arguments
+#
# The contents of this file, together with the start.ini
# fragments found in start.d directory are used to build
# the classpath and command line on a call to
@@ -21,50 +22,34 @@
# + A JVM option like: -Xmx2000m
# + A System Property like: -Dcom.sun.management.jmxremote
#
+#-----------------------------------------------------------
+#
+# NOTE: The lines in this file may be uncommented to activate
+# features. Alternately, the lines may be copied to a ini file
+# in the start.d directory to enabled configuration without
+# editing this file. See start.d/900-demo.ini for an example.
+#
+# Future releases will switch start.d style configuration for
+# all features.
#===========================================================
-#===========================================================
-# The --exec option should be used if any of the JVM options
-# in this file are uncommented (eg -D* or -X*). Because a
-# JVM cannot change it's own options, the --exec flag causes
-# start.jar to fork a new JVM with the requested arguments.
-#
-# Alternately, a command line may be generated by running
-#
-# java -jar start.jar --exec-print
-#
-# and the results executed to start the jetty server.
-# For example --exec can be avoided if jetty is started on unix with
-#
-# eval $(java -jar start.jar --exec-print)
-#
-#-----------------------------------------------------------
-# --exec
-#===========================================================
-
-#===========================================================
-# Configure Properties.
-# The properties defined here may be used by the
-# <Property name="myproperty"/> element in the XML files
-# passed to start.jar.
-# Alternately a file ending with ".properties" can be
-# added that will include multiple properties.
-# Properties, unlike SystemProperties, do not need --exec
-# to be specified.
-#-----------------------------------------------------------
-# jetty.home=.
-# jetty.logs=./logs
-# jetty.host=0.0.0.0
-#===========================================================
#===========================================================
# Configure JVM arguments.
-# Must be used with --exec or --exec-print
+# If JVM args are include in an ini file then --exec is needed
+# to start a new JVM from start.jar with the extra args.
+# If you wish to avoid an extra JVM running, place JVM args
+# on the normal command line and do not use --exec
#-----------------------------------------------------------
-# -Dorg.apache.jasper.compiler.disablejsr199=true
+# --exec
# -Xmx2000m
# -Xmn512m
+# -XX:+UseConcMarkSweepGC
+# -XX:ParallelCMSThreads=2
+# -XX:+CMSClassUnloadingEnabled
+# -XX:+UseCMSCompactAtFullCollection
+# -XX:CMSInitiatingOccupancyFraction=80
# -verbose:gc
# -XX:+PrintGCDateStamps
# -XX:+PrintGCTimeStamps
@@ -72,12 +57,10 @@
# -XX:+PrintTenuringDistribution
# -XX:+PrintCommandLineFlags
# -XX:+DisableExplicitGC
-# -XX:+UseConcMarkSweepGC
-# -XX:ParallelCMSThreads=2
-# -XX:+CMSClassUnloadingEnabled
-# -XX:+UseCMSCompactAtFullCollection
-# -XX:CMSInitiatingOccupancyFraction=80
-#===========================================================
+
+# -Dorg.apache.jasper.compiler.disablejsr199=true
+
+
#===========================================================
# Default Server Options
@@ -87,8 +70,50 @@
# Include the core jetty configuration file
#-----------------------------------------------------------
OPTIONS=Server,websocket,resources,ext
+threads.min=10
+threads.max=200
+threads.timeout=60000
+#jetty.host=myhost.com
+jetty.dump.start=false
+jetty.dump.stop=false
+
etc/jetty.xml
+
#===========================================================
+# JMX Management
+# To enable remote JMX access uncomment jmxremote and
+# enable --exec
+#-----------------------------------------------------------
+OPTIONS=jmx
+# jetty.jmxrmihost=localhost
+# jetty.jmxrmiport=1099
+# -Dcom.sun.management.jmxremote
+etc/jetty-jmx.xml
+
+#===========================================================
+# Java Server Pages
+#-----------------------------------------------------------
+OPTIONS=jsp
+
+#===========================================================
+# Request logger
+# Will add a handler to log all HTTP requests to a standard
+# request log format file.
+#-----------------------------------------------------------
+# requestlog.retain=90
+# requestlog.append=true
+# requestlog.extended=true
+# etc/jetty-requestlog.xml
+
+
+#===========================================================
+# stderr/stdout logging.
+# The following configuration will redirect stderr and stdout
+# to file which is rolled over daily.
+#-----------------------------------------------------------
+# jetty.log.retain=90
+# etc/jetty-logging.xml
+
#===========================================================
# Enable SetUID
@@ -96,59 +121,25 @@
# starting as root you must change the run privledged to true
#-----------------------------------------------------------
# OPTIONS=setuid
-# etc/jetty-setuid.xml
# jetty.startServerAsPrivileged=false
# jetty.username=jetty
# jetty.groupname=jetty
# jetty.umask=002
-#===========================================================
+# etc/jetty-setuid.xml
-#===========================================================
-# Server logging.
-# The following configuration will redirect stderr and stdout
-# to file which is rolled over daily.
-#-----------------------------------------------------------
-# etc/jetty-logging.xml
-#===========================================================
-
-#===========================================================
-# JMX Management
-# To enable remote JMX access uncomment jmxremote and
-# enable --exec or use --exec-print (see above)
-#-----------------------------------------------------------
-OPTIONS=jmx
-# jetty.jmxrmihost=localhost
-# jetty.jmxrmiport=1099
-# -Dcom.sun.management.jmxremote
-etc/jetty-jmx.xml
-#===========================================================
-
-#===========================================================
-# Java Server Pages
-#-----------------------------------------------------------
-OPTIONS=jsp
-#===========================================================
-
-
-#===========================================================
-# Annotations JNDI JAAS processing
-#-----------------------------------------------------------
-# OPTIONS=plus
-# etc/jetty-plus.xml
-# OPTIONS=annotations
-# etc/jetty-annotations.xml
-#===========================================================
#===========================================================
# HTTP Connector
#-----------------------------------------------------------
-# jetty.port=8080
+jetty.port=8080
+http.timeout=30000
etc/jetty-http.xml
-#===========================================================
+
#===========================================================
# SSL Context
-# For use by HTTPS and SPDY
+# Create the keystore and trust store for use by
+# HTTPS and SPDY
#-----------------------------------------------------------
# jetty.keystore=etc/keystore
# jetty.keystore.password=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4
@@ -157,65 +148,90 @@
# jetty.truststore.password=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4
# jetty.secure.port=8443
# etc/jetty-ssl.xml
-#===========================================================
+
#===========================================================
# HTTPS Connector
+# Must be used with 200-ssl.ini
#-----------------------------------------------------------
# jetty.https.port=8443
# etc/jetty-https.xml
+
+
#===========================================================
+# NPN Next Protocol Negotiation
+#
+# The SPDY and HTTP/2.0 connectors require NPN. The jar for
+# NPN cannot be downloaded from eclipse. So the --download
+# option is used to install the NPN jar if it does not already
+# exist
+#
+#-----------------------------------------------------------
+# --exec
+# --download=http://repo1.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.5.v20130313/npn-boot-1.1.5.v20130313.jar:lib/npn/npn-boot-1.1.5.v20130313.jar
+# -Xbootclasspath/p:lib/npn/npn-boot-1.1.5.v20130313.jar
+
#===========================================================
# SPDY Connector
-#
-# SPDY requires the NPN jar which must be separately downloaded:
-#
-# http://repo1.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.5.v20130313/npn-boot-1.1.5.v20130313.jar
-#
-# Which should be saved in lib/npn-boot-1.1.5.v20130313.jar
-#
-# To include the NPN jar on the boot path, you must either:
-#
-# a) enable --exec above and uncomment the -Xbootclass line
-# below
-#
-# b) Add -Xbootclasspath/p:lib/npn-boot-1.1.5.v20130313.jar
-# to the command line when running jetty.
-#
+# Requires SSL Context and NPN from above
#-----------------------------------------------------------
# OPTIONS=spdy
-# -Xbootclasspath/p:lib/npn-boot-1.1.5.v20130313.jar
# jetty.spdy.port=8443
# etc/jetty-spdy.xml
-#===========================================================
+
#===========================================================
# Webapplication Deployer
#-----------------------------------------------------------
etc/jetty-deploy.xml
-#===========================================================
+
+
+# ===========================================================
+# Enable JAAS
+# -----------------------------------------------------------
+# OPTIONS=jaas
+# jaas.login.conf=etc/login.conf
+# etc/jetty-jaas.xml
+
+# ===========================================================
+# Enable JNDI
+# -----------------------------------------------------------
+# OPTIONS=jndi
+
+# ===========================================================
+# Enable additional webapp environment configurators
+# -----------------------------------------------------------
+# OPTIONS=plus
+# etc/jetty-plus.xml
+
+# ===========================================================
+# Enable servlet 3.1 annotations
+# -----------------------------------------------------------
+# OPTIONS=annotations
+# etc/jetty-annotations.xml
#===========================================================
-# Request logger
-# Will add a handler to log all HTTP requests to a standard
-# request log format file.
+# Other server features
#-----------------------------------------------------------
-etc/jetty-requestlog.xml
-#===========================================================
-
-#===========================================================
-# Additional configurations
-# See headers of individual files for explanations
-#-----------------------------------------------------------
-# etc/jetty-stats.xml
# etc/jetty-debug.xml
# etc/jetty-ipaccess.xml
-# etc/jetty-lowresources.xml
-#===========================================================
+# etc/jetty-stats.xml
+
#===========================================================
-# Lookup additional ini files in start.d
+# Low resource managment
#-----------------------------------------------------------
-start.d/
+# lowresources.period=1050
+# lowresources.lowResourcesIdleTimeout=200
+# lowresources.monitorThreads=true
+# lowresources.maxConnections=0
+# lowresources.maxMemory=0
+# lowresources.maxLowResourcesTime=5000
+# etc/jetty-lowresources.xml
+
+
#===========================================================
+# The start.d directory contains the active start.ini fragments
+start.d/
+
diff --git a/jetty-distribution/src/main/resources/webapps.demo/README.TXT b/jetty-distribution/src/main/resources/webapps.demo/README.TXT
new file mode 100644
index 0000000..ec2bea2
--- /dev/null
+++ b/jetty-distribution/src/main/resources/webapps.demo/README.TXT
@@ -0,0 +1,7 @@
+
+This directory is scanned by the demo WebAppDeployer provider
+created in the etc/jetty-demo.xml file and enabled by the
+start.d/900-demo.ini file.
+
+For normal deployment, use the webapps directory.
+
diff --git a/jetty-distribution/src/main/resources/webapps/ROOT/images/jetty-header.jpg b/jetty-distribution/src/main/resources/webapps.demo/ROOT/images/jetty-header.jpg
similarity index 100%
rename from jetty-distribution/src/main/resources/webapps/ROOT/images/jetty-header.jpg
rename to jetty-distribution/src/main/resources/webapps.demo/ROOT/images/jetty-header.jpg
Binary files differ
diff --git a/jetty-distribution/src/main/resources/webapps/ROOT/images/webtide_logo.jpg b/jetty-distribution/src/main/resources/webapps.demo/ROOT/images/webtide_logo.jpg
similarity index 100%
rename from jetty-distribution/src/main/resources/webapps/ROOT/images/webtide_logo.jpg
rename to jetty-distribution/src/main/resources/webapps.demo/ROOT/images/webtide_logo.jpg
Binary files differ
diff --git a/jetty-distribution/src/main/resources/webapps/ROOT/index.html b/jetty-distribution/src/main/resources/webapps.demo/ROOT/index.html
similarity index 87%
rename from jetty-distribution/src/main/resources/webapps/ROOT/index.html
rename to jetty-distribution/src/main/resources/webapps.demo/ROOT/index.html
index 826974f..8f73374 100644
--- a/jetty-distribution/src/main/resources/webapps/ROOT/index.html
+++ b/jetty-distribution/src/main/resources/webapps.demo/ROOT/index.html
@@ -34,8 +34,9 @@
<ul>
<li><a href="/test/">Test Jetty Webapp</a></li>
<li><a href="/async-rest/">Async Rest</a></li>
- <li><a href="/proxy/apidocs/">Transparent Proxy to Javadoc</a></li>
- <li><a href="/proxy/xref/">Transparent Proxy to Xref</a></li>
+ <li><a href="/test-jaas/">JAAS Test</a></li>
+ <li><a href="/test-jndi/">JNDI Test</a></li>
+ <li><a href="/test-spec/">Servlet 3.1 Test</a></li>
<li><a href="/oldContextPath/">Redirected Context</a></li>
</ul>
</td>
@@ -44,6 +45,8 @@
<ul>
<li><a href="http://www.eclipse.org/jetty/">Jetty @ Eclipse Home</a></li>
<li><a href="http://wiki.eclipse.org/Jetty">Jetty @ Eclipse Doco</a></li>
+ <li><a href="/proxy/apidocs/">Javadoc</a> (via transparent proxy)</li>
+ <li><a href="/proxy/xref/">Xref</a> (via transparent proxy)</li>
<li><a
href="http://docs.codehaus.org/display/JETTY/Jetty+Powered">Jetty Powered</a></li>
</ul>
diff --git a/jetty-distribution/src/main/resources/webapps/ROOT/jetty.css b/jetty-distribution/src/main/resources/webapps.demo/ROOT/jetty.css
similarity index 100%
rename from jetty-distribution/src/main/resources/webapps/ROOT/jetty.css
rename to jetty-distribution/src/main/resources/webapps.demo/ROOT/jetty.css
diff --git a/jetty-distribution/src/main/resources/webapps/example-moved.xml b/jetty-distribution/src/main/resources/webapps.demo/example-moved.xml
similarity index 100%
rename from jetty-distribution/src/main/resources/webapps/example-moved.xml
rename to jetty-distribution/src/main/resources/webapps.demo/example-moved.xml
diff --git a/jetty-distribution/src/main/resources/webapps/javadoc.xml b/jetty-distribution/src/main/resources/webapps.demo/javadoc.xml
similarity index 100%
rename from jetty-distribution/src/main/resources/webapps/javadoc.xml
rename to jetty-distribution/src/main/resources/webapps.demo/javadoc.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-distribution/src/main/resources/webapps/.donotdelete
deleted file mode 100644
index e69de29..0000000
--- a/jetty-distribution/src/main/resources/webapps/.donotdelete
+++ /dev/null
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java
index c1a9ef2..c127e75 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java
@@ -33,7 +33,7 @@
*/
public class HttpField
{
- public final static Trie<HttpField> CACHE = new ArrayTrie<>(1024);
+ public final static Trie<HttpField> CACHE = new ArrayTrie<>(2048);
public final static Trie<HttpField> CONTENT_TYPE = new ArrayTrie<>(512);
static
@@ -77,10 +77,7 @@
}
// Add headers with null values so HttpParser can avoid looking up name again for unknown values
- Set<HttpHeader> headers = new HashSet<>();
- for (String key:CACHE.keySet())
- headers.add(CACHE.get(key).getHeader());
- for (HttpHeader h:headers)
+ for (HttpHeader h:HttpHeader.values())
if (!CACHE.put(new HttpField(h,(String)null)))
throw new IllegalStateException("CACHE FULL");
// Add some more common headers
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethod.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethod.java
index 60dc69d..6b27173 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethod.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethod.java
@@ -110,7 +110,10 @@
{
if (buffer.hasArray())
return lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.arrayOffset()+buffer.limit());
- return CACHE.getBest(buffer,0,buffer.remaining());
+
+ // TODO use cache and check for space
+ // return CACHE.getBest(buffer,0,buffer.remaining());
+ return null;
}
/* ------------------------------------------------------------ */
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java
index 239b44f..9b89bfa 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java
@@ -18,7 +18,6 @@
package org.eclipse.jetty.http;
-import java.io.IOException;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http.HttpTokens.EndOfContent;
@@ -26,6 +25,7 @@
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Trie;
+import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -80,13 +80,13 @@
private String _methodString;
private HttpVersion _version;
private ByteBuffer _uri=ByteBuffer.allocate(INITIAL_URI_LENGTH); // Tune?
- private byte _eol;
private EndOfContent _endOfContent;
private long _contentLength;
private long _contentPosition;
private int _chunkLength;
private int _chunkPosition;
private boolean _headResponse;
+ private boolean _cr;
private ByteBuffer _contentChunk;
private Trie<HttpField> _connectionFields;
@@ -205,10 +205,80 @@
}
/* ------------------------------------------------------------------------------- */
+ private byte next(ByteBuffer buffer)
+ {
+ byte ch=buffer.get();
+
+ // If not a special character
+ if (ch>=HttpTokens.SPACE || ch<0)
+ {
+ if (_cr)
+ {
+ badMessage(buffer,400,"Bad EOL");
+ return -1;
+ }
+ /*
+ if (ch>HttpTokens.SPACE)
+ System.err.println("Next "+(char)ch);
+ else
+ System.err.println("Next ["+ch+"]");*/
+ return ch;
+ }
+
+
+ // Only a LF acceptable after CR
+ if (_cr)
+ {
+ _cr=false;
+ if (ch==HttpTokens.LINE_FEED)
+ return ch;
+
+ badMessage(buffer,400,"Bad EOL");
+ return -1;
+ }
+
+ // If it is a CR
+ if (ch==HttpTokens.CARRIAGE_RETURN)
+ {
+ // Skip CR and look for a LF
+ if (buffer.hasRemaining())
+ {
+ if(_maxHeaderBytes>0 && _state.ordinal()<State.END.ordinal())
+ _headerBytes++;
+ ch=buffer.get();
+ if (ch==HttpTokens.LINE_FEED)
+ return ch;
+
+ badMessage(buffer,HttpStatus.BAD_REQUEST_400,null);
+ return -1;
+ }
+
+ // Defer lookup of LF
+ _cr=true;
+ return 0;
+ }
+
+ // Only LF or TAB acceptable special characters
+ if (ch!=HttpTokens.LINE_FEED && ch!=HttpTokens.TAB)
+ {
+ badMessage(buffer,HttpStatus.BAD_REQUEST_400,null);
+ return -1;
+ }
+
+ /*
+ if (ch>HttpTokens.SPACE)
+ System.err.println("Next "+(char)ch);
+ else
+ System.err.println("Next ["+ch+"]");
+ */
+ return ch;
+ }
+
+ /* ------------------------------------------------------------------------------- */
/* Quick lookahead for the start state looking for a request method or a HTTP version,
* otherwise skip white space until something else to parse.
*/
- private void quickStart(ByteBuffer buffer)
+ private boolean quickStart(ByteBuffer buffer)
{
// Quick start look
while (_state==State.START && buffer.hasRemaining())
@@ -221,7 +291,7 @@
_methodString = _method.asString();
buffer.position(buffer.position()+_methodString.length()+1);
setState(State.SPACE1);
- return;
+ return false;
}
}
else if (_responseHandler!=null)
@@ -231,27 +301,23 @@
{
buffer.position(buffer.position()+_version.asString().length()+1);
setState(State.SPACE1);
- return;
+ return false;
}
}
- byte ch=buffer.get();
-
- if (_eol == HttpTokens.CARRIAGE_RETURN && ch == HttpTokens.LINE_FEED)
- {
- _eol=HttpTokens.LINE_FEED;
- continue;
- }
- _eol=0;
-
- if (ch > HttpTokens.SPACE || ch<0)
+ byte ch=next(buffer);
+
+ if (ch > HttpTokens.SPACE)
{
_string.setLength(0);
_string.append((char)ch);
setState(_requestHandler!=null?State.METHOD:State.RESPONSE_VERSION);
- return;
+ return false;
}
+ if (ch==-1)
+ return true;
}
+ return false;
}
private String takeString()
@@ -281,7 +347,11 @@
while (_state.ordinal()<State.HEADER.ordinal() && buffer.hasRemaining() && !return_from_parse)
{
// process each character
- byte ch=buffer.get();
+ byte ch=next(buffer);
+ if (ch==-1)
+ return true;
+ if (ch==0)
+ continue;
if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
{
@@ -301,13 +371,6 @@
return true;
}
- if (_eol == HttpTokens.CARRIAGE_RETURN && ch == HttpTokens.LINE_FEED)
- {
- _eol=HttpTokens.LINE_FEED;
- continue;
- }
- _eol=0;
-
switch (_state)
{
case METHOD:
@@ -413,7 +476,6 @@
else if (ch < HttpTokens.SPACE && ch>=0)
{
return_from_parse|=_responseHandler.startResponse(_version, _responseStatus, null);
- _eol=ch;
setState(State.HEADER);
}
else
@@ -464,28 +526,39 @@
{
setState(State.REQUEST_VERSION);
- // try quick look ahead
+ // try quick look ahead for HTTP Version
if (buffer.position()>0 && buffer.hasArray())
{
- _version=HttpVersion.lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position()-1,buffer.arrayOffset()+buffer.limit());
- if (_version!=null)
+ HttpVersion version=HttpVersion.lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position()-1,buffer.arrayOffset()+buffer.limit());
+ if (version!=null)
{
- _string.setLength(0);
- buffer.position(buffer.position()+_version.asString().length()-1);
- _eol=buffer.get();
- setState(State.HEADER);
- _uri.flip();
- return_from_parse|=_requestHandler.startRequest(_method,_methodString,_uri, _version);
+ int pos = buffer.position()+version.asString().length()-1;
+ if (pos<buffer.limit())
+ {
+ byte n=buffer.get(pos);
+ if (n==HttpTokens.CARRIAGE_RETURN)
+ {
+ _cr=true;
+ _version=version;
+ _string.setLength(0);
+ buffer.position(pos+1);
+ }
+ else if (n==HttpTokens.LINE_FEED)
+ {
+ _version=version;
+ _string.setLength(0);
+ buffer.position(pos);
+ }
+ }
}
}
}
}
- else if (ch < HttpTokens.SPACE)
+ else if (ch == HttpTokens.LINE_FEED)
{
if (_responseHandler!=null)
{
return_from_parse|=_responseHandler.startResponse(_version, _responseStatus, null);
- _eol=ch;
setState(State.HEADER);
}
else
@@ -502,10 +575,10 @@
break;
case REQUEST_VERSION:
- if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
+ if (ch == HttpTokens.LINE_FEED)
{
- String version = takeString();
- _version=HttpVersion.CACHE.get(version);
+ if (_version==null)
+ _version=HttpVersion.CACHE.get(takeString());
if (_version==null)
{
badMessage(buffer,HttpStatus.BAD_REQUEST_400,"Unknown Version");
@@ -513,14 +586,13 @@
}
// Should we try to cache header fields?
- if (_version.getVersion()>=HttpVersion.HTTP_1_1.getVersion())
+ if (_connectionFields==null && _version.getVersion()>=HttpVersion.HTTP_1_1.getVersion())
{
int header_cache = _handler.getHeaderCacheSize();
if (header_cache>0)
_connectionFields=new ArrayTernaryTrie<>(header_cache);
}
- _eol=ch;
setState(State.HEADER);
_uri.flip();
return_from_parse|=_requestHandler.startRequest(_method,_methodString,_uri, _version);
@@ -532,11 +604,10 @@
break;
case REASON:
- if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
+ if (ch == HttpTokens.LINE_FEED)
{
String reason=takeLengthString();
- _eol=ch;
setState(State.HEADER);
return_from_parse|=_responseHandler.startResponse(_version, _responseStatus, reason);
continue;
@@ -609,7 +680,8 @@
return true;
}
- loop: for (int i = host.length(); i-- > 0;)
+ int len=host.length();
+ loop: for (int i = len; i-- > 0;)
{
char c2 = (char)(0xff & host.charAt(i));
switch (c2)
@@ -620,6 +692,7 @@
case ':':
try
{
+ len=i;
port = StringUtil.toInt(host.substring(i+1));
}
catch (NumberFormatException e)
@@ -628,10 +701,21 @@
badMessage(buffer,HttpStatus.BAD_REQUEST_400,"Bad Host header");
return true;
}
- host = host.substring(0,i);
break loop;
}
}
+ if (host.charAt(0)=='[')
+ {
+ if (host.charAt(len-1)!=']')
+ {
+ badMessage(buffer,HttpStatus.BAD_REQUEST_400,"Bad IPv6 Host header");
+ return true;
+ }
+ host = host.substring(1,len-1);
+ }
+ else if (len!=host.length())
+ host = host.substring(0,len);
+
if (_requestHandler!=null)
_requestHandler.parsedHostHeader(host,port);
@@ -652,6 +736,9 @@
case CACHE_CONTROL:
case USER_AGENT:
add_to_connection_trie=_connectionFields!=null && _field==null;
+ break;
+
+ default: break;
}
if (add_to_connection_trie && !_connectionFields.isFull() && _header!=null && _valueString!=null)
@@ -676,7 +763,12 @@
while (_state.ordinal()<State.END.ordinal() && buffer.hasRemaining() && !return_from_parse)
{
// process each character
- byte ch=buffer.get();
+ byte ch=next(buffer);
+ if (ch==-1)
+ return true;
+ if (ch==0)
+ continue;
+
if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
{
LOG.warn("Header is too large >"+_maxHeaderBytes);
@@ -684,13 +776,6 @@
return true;
}
- if (_eol == HttpTokens.CARRIAGE_RETURN && ch == HttpTokens.LINE_FEED)
- {
- _eol=HttpTokens.LINE_FEED;
- continue;
- }
- _eol=0;
-
switch (_state)
{
case HEADER:
@@ -735,10 +820,8 @@
_field=null;
// now handle the ch
- if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
+ if (ch == HttpTokens.LINE_FEED)
{
- consumeCRLF(ch,buffer);
-
_contentPosition=0;
// End of headers!
@@ -796,44 +879,53 @@
}
else
{
- if (buffer.remaining()>6)
+ if (buffer.hasRemaining())
{
// Try a look ahead for the known header name and value.
- _field=_connectionFields==null?null:_connectionFields.getBest(buffer,-1,buffer.remaining());
- if (_field==null)
- _field=HttpField.CACHE.getBest(buffer,-1,buffer.remaining());
+ HttpField field=_connectionFields==null?null:_connectionFields.getBest(buffer,-1,buffer.remaining());
+ if (field==null)
+ field=HttpField.CACHE.getBest(buffer,-1,buffer.remaining());
- if (_field!=null)
+ if (field!=null)
{
- _header=_field.getHeader();
- _headerString=_field.getName();
- _valueString=_field.getValue();
- if (_valueString==null)
+ String n=field.getName();
+ String v=field.getValue();
+
+ if (v==null)
{
+ // Header only
+ _header=field.getHeader();
+ _headerString=n;
setState(State.HEADER_VALUE);
- buffer.position(buffer.position()+_headerString.length()+1);
_string.setLength(0);
_length=0;
- _field=null;
+ buffer.position(buffer.position()+n.length()+1);
+ break;
}
else
{
- setState(State.HEADER_IN_VALUE);
- buffer.position(buffer.position()+_headerString.length()+_valueString.length()+1);
- }
- break;
- }
+ // Header and value
+ int pos=buffer.position()+n.length()+v.length()+1;
+ byte b=buffer.get(pos);
- // Try a look ahead for the known header name.
- _header=HttpHeader.CACHE.getBest(buffer,-1,buffer.remaining());
- //_header=HttpHeader.CACHE.getBest(buffer.array(),buffer.arrayOffset()+buffer.position()-1,buffer.remaining()+1);
- if (_header!=null)
- {
- _headerString=_header.asString();
- _string.setLength(0);
- setState(State.HEADER_IN_NAME);
- buffer.position(buffer.position()+_headerString.length()-1);
- break;
+ if (b==HttpTokens.CARRIAGE_RETURN || b==HttpTokens.LINE_FEED)
+ {
+ _field=field;
+ _header=_field.getHeader();
+ _headerString=n;
+ _valueString=v;
+ setState(State.HEADER_IN_VALUE);
+
+ if (b==HttpTokens.CARRIAGE_RETURN)
+ {
+ _cr=true;
+ buffer.position(pos+1);
+ }
+ else
+ buffer.position(pos);
+ break;
+ }
+ }
}
}
@@ -851,9 +943,7 @@
case HEADER_NAME:
switch(ch)
{
- case HttpTokens.CARRIAGE_RETURN:
case HttpTokens.LINE_FEED:
- consumeCRLF(ch,buffer);
if (_headerString==null)
{
_headerString=takeLengthString();
@@ -888,9 +978,7 @@
case HEADER_IN_NAME:
switch(ch)
{
- case HttpTokens.CARRIAGE_RETURN:
case HttpTokens.LINE_FEED:
- consumeCRLF(ch,buffer);
_headerString=takeString();
_length=-1;
_header=HttpHeader.CACHE.get(_headerString);
@@ -936,9 +1024,7 @@
case HEADER_VALUE:
switch(ch)
{
- case HttpTokens.CARRIAGE_RETURN:
case HttpTokens.LINE_FEED:
- consumeCRLF(ch,buffer);
if (_length > 0)
{
if (_valueString!=null)
@@ -975,9 +1061,7 @@
case HEADER_IN_VALUE:
switch(ch)
{
- case HttpTokens.CARRIAGE_RETURN:
case HttpTokens.LINE_FEED:
- consumeCRLF(ch,buffer);
if (_length > 0)
{
if (HttpHeaderValue.hasKnownValues(_header))
@@ -1031,17 +1115,6 @@
}
/* ------------------------------------------------------------------------------- */
- private void consumeCRLF(byte ch, ByteBuffer buffer)
- {
- _eol=ch;
- if (_eol==HttpTokens.CARRIAGE_RETURN && buffer.hasRemaining() && buffer.get(buffer.position())==HttpTokens.LINE_FEED)
- {
- buffer.get();
- _eol=0;
- }
- }
-
- /* ------------------------------------------------------------------------------- */
/**
* Parse until next Event.
* @return True if an {@link RequestHandler} method was called and it returned true;
@@ -1059,7 +1132,8 @@
_methodString=null;
_endOfContent=EndOfContent.UNKNOWN_CONTENT;
_header=null;
- quickStart(buffer);
+ if(quickStart(buffer))
+ return true;
break;
case CONTENT:
@@ -1077,18 +1151,18 @@
case CLOSED:
if (BufferUtil.hasContent(buffer))
{
- int len=buffer.remaining();
- _headerBytes+=len;
+ // Just ignore data when closed
+ _headerBytes+=buffer.remaining();
+ BufferUtil.clear(buffer);
if (_headerBytes>_maxHeaderBytes)
{
- Thread.sleep(100);
- String chars = BufferUtil.toDetailString(buffer);
- BufferUtil.clear(buffer);
- throw new IllegalStateException(String.format("%s %d/%d>%d data when CLOSED:%s",this,len,_headerBytes,_maxHeaderBytes,chars));
+ // Don't want to waste time reading data of a closed request
+ throw new IllegalStateException("too much data after closed");
}
- BufferUtil.clear(buffer);
}
return false;
+ default: break;
+
}
// Request/response line
@@ -1113,13 +1187,6 @@
byte ch;
while (_state.ordinal() > State.END.ordinal() && buffer.hasRemaining())
{
- if (_eol == HttpTokens.CARRIAGE_RETURN && buffer.get(buffer.position()) == HttpTokens.LINE_FEED)
- {
- _eol=buffer.get();
- continue;
- }
- _eol=0;
-
switch (_state)
{
case EOF_CONTENT:
@@ -1169,31 +1236,24 @@
case CHUNKED_CONTENT:
{
- ch=buffer.get(buffer.position());
- if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
- _eol=buffer.get();
- else if (ch <= HttpTokens.SPACE)
- buffer.get();
- else
+ ch=next(buffer);
+ if (ch>HttpTokens.SPACE)
{
- _chunkLength=0;
+ _chunkLength=TypeUtil.convertHexDigit(ch);
_chunkPosition=0;
setState(State.CHUNK_SIZE);
}
+
break;
}
case CHUNK_SIZE:
{
- ch=buffer.get();
- if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
+ ch=next(buffer);
+ if (ch == HttpTokens.LINE_FEED)
{
- _eol=ch;
-
if (_chunkLength == 0)
{
- if (_eol==HttpTokens.CARRIAGE_RETURN && buffer.hasRemaining() && buffer.get(buffer.position())==HttpTokens.LINE_FEED)
- _eol=buffer.get();
setState(State.END);
if (_handler.messageComplete())
return true;
@@ -1203,27 +1263,18 @@
}
else if (ch <= HttpTokens.SPACE || ch == HttpTokens.SEMI_COLON)
setState(State.CHUNK_PARAMS);
- else if (ch >= '0' && ch <= '9')
- _chunkLength=_chunkLength * 16 + (ch - '0');
- else if (ch >= 'a' && ch <= 'f')
- _chunkLength=_chunkLength * 16 + (10 + ch - 'a');
- else if (ch >= 'A' && ch <= 'F')
- _chunkLength=_chunkLength * 16 + (10 + ch - 'A');
- else
- throw new IOException("bad chunk char: " + ch);
+ else
+ _chunkLength=_chunkLength * 16 + TypeUtil.convertHexDigit(ch);
break;
}
case CHUNK_PARAMS:
{
- ch=buffer.get();
- if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
+ ch=next(buffer);
+ if (ch == HttpTokens.LINE_FEED)
{
- _eol=ch;
if (_chunkLength == 0)
{
- if (_eol==HttpTokens.CARRIAGE_RETURN && buffer.hasRemaining() && buffer.get(buffer.position())==HttpTokens.LINE_FEED)
- _eol=buffer.get();
setState(State.END);
if (_handler.messageComplete())
return true;
@@ -1262,6 +1313,9 @@
BufferUtil.clear(buffer);
return false;
}
+
+ default:
+ break;
}
}
@@ -1340,8 +1394,19 @@
case CLOSED:
case END:
break;
+
+ case EOF_CONTENT:
+ _handler.messageComplete();
+ break;
+
default:
- LOG.warn("Closing {}",this);
+ if (_state.ordinal()>State.END.ordinal())
+ {
+ _handler.earlyEOF();
+ _handler.messageComplete();
+ }
+ else
+ LOG.warn("Closing {}",this);
}
setState(State.CLOSED);
_endOfContent=EndOfContent.UNKNOWN_CONTENT;
@@ -1369,6 +1434,7 @@
/* ------------------------------------------------------------------------------- */
private void setState(State state)
{
+ // LOG.debug("{} --> {}",_state,state);
_state=state;
}
@@ -1405,8 +1471,18 @@
*/
public boolean parsedHeader(HttpField field);
- public boolean earlyEOF();
+ /* ------------------------------------------------------------ */
+ /** Called to signal that an EOF was received unexpectedly
+ * during the parsing of a HTTP message
+ * @return True if the parser should return to its caller
+ */
+ public void earlyEOF();
+ /* ------------------------------------------------------------ */
+ /** Called to signal that a bad HTTP message has been received.
+ * @param status The bad status to send
+ * @param reason The textual reason for badness
+ */
public void badMessage(int status, String reason);
/* ------------------------------------------------------------ */
@@ -1443,5 +1519,9 @@
public abstract boolean startResponse(HttpVersion version, int status, String reason);
}
+ public Trie<HttpField> getFieldCache()
+ {
+ return _connectionFields;
+ }
}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTester.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTester.java
index 24f2e7a..b14221d 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTester.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTester.java
@@ -138,9 +138,8 @@
}
@Override
- public boolean earlyEOF()
+ public void earlyEOF()
{
- return true;
}
@Override
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTokens.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTokens.java
index e3d13d5..d54efff 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTokens.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTokens.java
@@ -25,13 +25,12 @@
{
// Terminal symbols.
static final byte COLON= (byte)':';
- static final byte SPACE= 0x20;
- static final byte CARRIAGE_RETURN= 0x0D;
+ static final byte TAB= 0x09;
static final byte LINE_FEED= 0x0A;
+ static final byte CARRIAGE_RETURN= 0x0D;
+ static final byte SPACE= 0x20;
static final byte[] CRLF = {CARRIAGE_RETURN,LINE_FEED};
static final byte SEMI_COLON= (byte)';';
- static final byte TAB= 0x09;
-
public enum EndOfContent { UNKNOWN_CONTENT,NO_CONTENT,EOF_CONTENT,CONTENT_LENGTH,CHUNKED_CONTENT,SELF_DEFINING_CONTENT }
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java
index f7297f2..dcd00cf 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java
@@ -546,6 +546,8 @@
{
if (_host==_port)
return null;
+ if (_raw[_host]=='[')
+ return new String(_raw,_host+1,_port-_host-2,_charset);
return new String(_raw,_host,_port-_host,_charset);
}
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java
index 7ca440f..262c0ac 100644
--- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java
@@ -51,9 +51,8 @@
}
@Override
- public boolean earlyEOF()
+ public void earlyEOF()
{
- return true;
}
@Override
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java
index 16651a9..b65e10a 100644
--- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java
@@ -23,6 +23,8 @@
import static org.junit.Assert.assertTrue;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
import org.eclipse.jetty.http.HttpParser.State;
import org.eclipse.jetty.util.BufferUtil;
@@ -50,11 +52,13 @@
throw new IllegalStateException("!START");
// continue parsing
- while (!parser.isState(State.END) && buffer.hasRemaining())
+ int remaining=buffer.remaining();
+ while (!parser.isState(State.END) && remaining>0)
{
- int remaining=buffer.remaining();
+ int was_remaining=remaining;
parser.parseNext(buffer);
- if (remaining==buffer.remaining())
+ remaining=buffer.remaining();
+ if (remaining==was_remaining)
break;
}
}
@@ -64,8 +68,8 @@
{
ByteBuffer buffer= BufferUtil.toBuffer("POST /foo HTTP/1.0\015\012" + "\015\012");
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("POST", _methodOrVersion);
assertEquals("/foo", _uriOrStatus);
@@ -79,8 +83,8 @@
ByteBuffer buffer= BufferUtil.toBuffer("GET /999\015\012");
_versionOrReason= null;
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("GET", _methodOrVersion);
assertEquals("/999", _uriOrStatus);
@@ -94,8 +98,8 @@
ByteBuffer buffer= BufferUtil.toBuffer("POST /222 \015\012");
_versionOrReason= null;
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("POST", _methodOrVersion);
assertEquals("/222", _uriOrStatus);
@@ -108,8 +112,8 @@
{
ByteBuffer buffer= BufferUtil.toBuffer("POST /fo\u0690 HTTP/1.0\015\012" + "\015\012",StringUtil.__UTF8_CHARSET);
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("POST", _methodOrVersion);
assertEquals("/fo\u0690", _uriOrStatus);
@@ -122,8 +126,8 @@
{
ByteBuffer buffer= BufferUtil.toBuffer("POST /foo?param=\u0690 HTTP/1.0\015\012" + "\015\012",StringUtil.__UTF8_CHARSET);
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("POST", _methodOrVersion);
assertEquals("/foo?param=\u0690", _uriOrStatus);
@@ -136,8 +140,8 @@
{
ByteBuffer buffer= BufferUtil.toBuffer("POST /123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/ HTTP/1.0\015\012" + "\015\012");
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("POST", _methodOrVersion);
assertEquals("/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/", _uriOrStatus);
@@ -149,10 +153,9 @@
public void testConnect() throws Exception
{
ByteBuffer buffer= BufferUtil.toBuffer("CONNECT 192.168.1.2:80 HTTP/1.1\015\012" + "\015\012");
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
- assertTrue(handler.request);
assertEquals("CONNECT", _methodOrVersion);
assertEquals("192.168.1.2:80", _uriOrStatus);
assertEquals("HTTP/1.1", _versionOrReason);
@@ -182,8 +185,8 @@
BufferUtil.put(b0,buffer);
BufferUtil.flipToFlush(buffer,pos);
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("GET", _methodOrVersion);
@@ -230,8 +233,8 @@
"Accept-Encoding: gzip, deflated\015\012" +
"Accept: unknown\015\012" +
"\015\012");
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("GET", _methodOrVersion);
@@ -260,6 +263,8 @@
assertEquals(9, _h);
}
+
+
@Test
public void testHeaderParseLF() throws Exception
{
@@ -278,8 +283,8 @@
"Accept-Encoding: gzip, deflated\n" +
"Accept: unknown\n" +
"\n");
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("GET", _methodOrVersion);
@@ -328,9 +333,10 @@
for (int i=0;i<buffer.capacity()-4;i++)
{
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
+ // System.err.println(BufferUtil.toDetailString(buffer));
buffer.position(2);
buffer.limit(2+i);
@@ -377,8 +383,8 @@
+ "1a\015\012"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ\015\012"
+ "0\015\012");
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("GET", _methodOrVersion);
@@ -421,8 +427,8 @@
+ "0123456789\015\012");
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("GET", _methodOrVersion);
assertEquals("/mp", _uriOrStatus);
@@ -466,8 +472,8 @@
+ "\015\012"
+ "0123456789\015\012");
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
+ HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("HTTP/1.1", _methodOrVersion);
assertEquals("200", _uriOrStatus);
@@ -485,8 +491,8 @@
+ "Connection: close\015\012"
+ "\015\012");
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
+ HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("HTTP/1.1", _methodOrVersion);
assertEquals("304", _uriOrStatus);
@@ -509,8 +515,8 @@
+ "\015\012"
+ "0123456789\015\012");
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
+ HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("HTTP/1.1", _methodOrVersion);
assertEquals("204", _uriOrStatus);
@@ -542,8 +548,8 @@
+ "\015\012"
+ "0123456789\015\012");
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
+ HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("HTTP/1.1", _methodOrVersion);
assertEquals("200", _uriOrStatus);
@@ -563,8 +569,8 @@
+ "\015\012"
+ "0123456789\015\012");
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
+ HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("HTTP/1.1", _methodOrVersion);
assertEquals("200", _uriOrStatus);
@@ -582,8 +588,8 @@
+ "Content-Length: 10\015\012"
+ "\015\012");
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
+ HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("HTTP/1.1", _methodOrVersion);
assertEquals("304", _uriOrStatus);
@@ -601,8 +607,8 @@
+ "Transfer-Encoding: chunked\015\012"
+ "\015\012");
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
+ HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("HTTP/1.1", _methodOrVersion);
assertEquals("101", _uriOrStatus);
@@ -624,8 +630,8 @@
+ "HTTP/1.1 400 OK\015\012"); // extra data causes close
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
+ HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("HTTP/1.1", _methodOrVersion);
@@ -647,8 +653,8 @@
+ "Connection: close\015\012"
+ "\015\012");
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals(null,_methodOrVersion);
@@ -667,8 +673,8 @@
+ "Connection: close\015\012"
+ "\015\012");
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals(null,_methodOrVersion);
@@ -686,8 +692,8 @@
+ "Connection: close\015\012"
+ "\015\012");
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
+ HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals(null,_methodOrVersion);
@@ -705,8 +711,8 @@
+ "Connection: close\015\012"
+ "\015\012");
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
+ HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals(null,_methodOrVersion);
@@ -724,9 +730,9 @@
+ "Connection: close\015\012"
+ "\015\012");
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
-
+ HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
+
parser.parseNext(buffer);
assertEquals(null,_methodOrVersion);
assertEquals("No Status",_bad);
@@ -743,8 +749,23 @@
+ "Connection: close\015\012"
+ "\015\012");
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
+
+ parser.parseNext(buffer);
+ assertEquals(null,_methodOrVersion);
+ assertEquals("Unknown Version",_bad);
+ assertFalse(buffer.hasRemaining());
+ assertEquals(HttpParser.State.CLOSED,parser.getState());
+
+ buffer= BufferUtil.toBuffer(
+ "GET / HTTP/1.01\015\012"
+ + "Content-Length: 0\015\012"
+ + "Connection: close\015\012"
+ + "\015\012");
+
+ handler = new Handler();handler = new Handler();
+ parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals(null,_methodOrVersion);
@@ -752,6 +773,42 @@
assertFalse(buffer.hasRemaining());
assertEquals(HttpParser.State.CLOSED,parser.getState());
}
+
+ @Test
+ public void testBadCR() throws Exception
+ {
+ ByteBuffer buffer= BufferUtil.toBuffer(
+ "GET / HTTP/1.0\r\n"
+ + "Content-Length: 0\r"
+ + "Connection: close\r"
+ + "\r");
+
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
+
+ parser.parseNext(buffer);
+ assertEquals("Bad EOL",_bad);
+ assertFalse(buffer.hasRemaining());
+ assertEquals(HttpParser.State.CLOSED,parser.getState());
+
+
+ buffer= BufferUtil.toBuffer(
+ "GET / HTTP/1.0\r"
+ + "Content-Length: 0\r"
+ + "Connection: close\r"
+ + "\r");
+
+ handler = new Handler();
+ parser= new HttpParser(handler);
+
+ parser.parseNext(buffer);
+ assertEquals("Bad EOL",_bad);
+ assertFalse(buffer.hasRemaining());
+ assertEquals(HttpParser.State.CLOSED,parser.getState());
+ }
+
+
+
@Test
public void testBadContentLength0() throws Exception
@@ -762,8 +819,8 @@
+ "Connection: close\015\012"
+ "\015\012");
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("GET",_methodOrVersion);
@@ -781,8 +838,8 @@
+ "Connection: close\015\012"
+ "\015\012");
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("GET",_methodOrVersion);
@@ -800,8 +857,8 @@
+ "Connection: close\015\012"
+ "\015\012");
- Handler handler = new Handler();
- HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("GET",_methodOrVersion);
@@ -809,7 +866,154 @@
assertFalse(buffer.hasRemaining());
assertEquals(HttpParser.State.CLOSED,parser.getState());
}
+
+ @Test
+ public void testHost() throws Exception
+ {
+ ByteBuffer buffer= BufferUtil.toBuffer(
+ "GET / HTTP/1.1\015\012"
+ + "Host: host\015\012"
+ + "Connection: close\015\012"
+ + "\015\012");
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
+ parser.parseNext(buffer);
+ assertEquals("host",_host);
+ assertEquals(0,_port);
+ }
+
+ @Test
+ public void testIPHost() throws Exception
+ {
+ ByteBuffer buffer= BufferUtil.toBuffer(
+ "GET / HTTP/1.1\015\012"
+ + "Host: 192.168.0.1\015\012"
+ + "Connection: close\015\012"
+ + "\015\012");
+
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
+ parser.parseNext(buffer);
+ assertEquals("192.168.0.1",_host);
+ assertEquals(0,_port);
+ }
+
+ @Test
+ public void testIPv6Host() throws Exception
+ {
+ ByteBuffer buffer= BufferUtil.toBuffer(
+ "GET / HTTP/1.1\015\012"
+ + "Host: [::1]\015\012"
+ + "Connection: close\015\012"
+ + "\015\012");
+
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
+ parser.parseNext(buffer);
+ assertEquals("::1",_host);
+ assertEquals(0,_port);
+ }
+
+ @Test
+ public void testBadIPv6Host() throws Exception
+ {
+ ByteBuffer buffer= BufferUtil.toBuffer(
+ "GET / HTTP/1.1\015\012"
+ + "Host: [::1\015\012"
+ + "Connection: close\015\012"
+ + "\015\012");
+
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
+ parser.parseNext(buffer);
+ assertEquals("Bad IPv6 Host header",_bad);
+ }
+
+ @Test
+ public void testHostPort() throws Exception
+ {
+ ByteBuffer buffer= BufferUtil.toBuffer(
+ "GET / HTTP/1.1\015\012"
+ + "Host: myhost:8888\015\012"
+ + "Connection: close\015\012"
+ + "\015\012");
+
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
+ parser.parseNext(buffer);
+ assertEquals("myhost",_host);
+ assertEquals(8888,_port);
+ }
+
+ @Test
+ public void testHostBadPort() throws Exception
+ {
+ ByteBuffer buffer= BufferUtil.toBuffer(
+ "GET / HTTP/1.1\015\012"
+ + "Host: myhost:xxx\015\012"
+ + "Connection: close\015\012"
+ + "\015\012");
+
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
+ parser.parseNext(buffer);
+ assertEquals("Bad Host header",_bad);
+ }
+
+ @Test
+ public void testIPHostPort() throws Exception
+ {
+ ByteBuffer buffer= BufferUtil.toBuffer(
+ "GET / HTTP/1.1\015\012"
+ + "Host: 192.168.0.1:8888\015\012"
+ + "Connection: close\015\012"
+ + "\015\012");
+
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
+ parser.parseNext(buffer);
+ assertEquals("192.168.0.1",_host);
+ assertEquals(8888,_port);
+ }
+
+ @Test
+ public void testIPv6HostPort() throws Exception
+ {
+ ByteBuffer buffer= BufferUtil.toBuffer(
+ "GET / HTTP/1.1\015\012"
+ + "Host: [::1]:8888\015\012"
+ + "Connection: close\015\012"
+ + "\015\012");
+
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
+ parser.parseNext(buffer);
+ assertEquals("::1",_host);
+ assertEquals(8888,_port);
+ }
+
+ @Test
+ public void testCachedField() throws Exception
+ {
+ ByteBuffer buffer= BufferUtil.toBuffer(
+ "GET / HTTP/1.1\r\n"+
+ "Host: www.smh.com.au\r\n"+
+ "\r\n");
+
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
+ parseAll(parser,buffer);
+ assertEquals("www.smh.com.au",parser.getFieldCache().get("Host: www.smh.com.au").getValue());
+ HttpField field=_fields.get(0);
+
+ //System.err.println(parser.getFieldCache());
+
+ buffer.position(0);
+ parseAll(parser,buffer);
+ assertTrue(field==_fields.get(0));
+
+ }
@Before
public void init()
@@ -826,11 +1030,14 @@
_messageCompleted=false;
}
+ private String _host;
+ private int _port;
private String _bad;
private String _content;
private String _methodOrVersion;
private String _uriOrStatus;
private String _versionOrReason;
+ private List<HttpField> _fields=new ArrayList<>();
private String[] _hdr;
private String[] _val;
private int _h;
@@ -858,6 +1065,7 @@
@Override
public boolean startRequest(HttpMethod httpMethod, String method, ByteBuffer uri, HttpVersion version)
{
+ _fields.clear();
request=true;
_h= -1;
_hdr= new String[10];
@@ -875,6 +1083,7 @@
@Override
public boolean parsedHeader(HttpField field)
{
+ _fields.add(field);
//System.err.println("header "+name+": "+value);
_hdr[++_h]= field.getName();
_val[_h]= field.getValue();
@@ -884,7 +1093,8 @@
@Override
public boolean parsedHostHeader(String host,int port)
{
- // TODO test this
+ _host=host;
+ _port=port;
return false;
}
@@ -921,6 +1131,7 @@
@Override
public boolean startResponse(HttpVersion version, int status, String reason)
{
+ _fields.clear();
request=false;
_methodOrVersion = version.asString();
_uriOrStatus = Integer.toString(status);
@@ -936,9 +1147,8 @@
}
@Override
- public boolean earlyEOF()
+ public void earlyEOF()
{
- return true;
}
@Override
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpURITest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpURITest.java
index 6262efd..52eb707 100644
--- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpURITest.java
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpURITest.java
@@ -29,39 +29,51 @@
/* ------------------------------------------------------------ */
public class HttpURITest
{
- public static final String __input = "http://example.com:8080/path/to/context?parameter=%22value%22#fragment";
- public static final String __scheme = "http";
- public static final String __host = "example.com";
- public static final int __port = 8080;
- public static final String __path = "/path/to/context";
- public static final String __query = "parameter=%22value%22";
- public static final String __fragment = "fragment";
+ String[][] tests=
+ {
+ {"/path/to/context",null,null,"-1","/path/to/context",null,null,null},
+ {"http://example.com/path/to/context;param?query=%22value%22#fragment","http","example.com","-1","/path/to/context","param","query=%22value%22","fragment"},
+ {"http://[::1]/path/to/context;param?query=%22value%22#fragment","http","::1","-1","/path/to/context","param","query=%22value%22","fragment"},
+ {"http://example.com:8080/path/to/context;param?query=%22value%22#fragment","http","example.com","8080","/path/to/context","param","query=%22value%22","fragment"},
+ {"http://[::1]:8080/path/to/context;param?query=%22value%22#fragment","http","::1","8080","/path/to/context","param","query=%22value%22","fragment"},
+ };
+
+ public static int
+ INPUT=0,SCHEME=1,HOST=2,PORT=3,PATH=4,PARAM=5,QUERY=6,FRAGMENT=7;
/* ------------------------------------------------------------ */
@Test
public void testFromString() throws Exception
{
- HttpURI uri = new HttpURI(__input);
+ for (String[] test:tests)
+ {
+ HttpURI uri = new HttpURI(test[INPUT]);
- assertEquals(__scheme, uri.getScheme());
- assertEquals(__host,uri.getHost());
- assertEquals(__port,uri.getPort());
- assertEquals(__path,uri.getPath());
- assertEquals(__query,uri.getQuery());
- assertEquals(__fragment,uri.getFragment());
+ assertEquals(test[SCHEME], uri.getScheme());
+ assertEquals(test[HOST], uri.getHost());
+ assertEquals(Integer.parseInt(test[PORT]), uri.getPort());
+ assertEquals(test[PATH], uri.getPath());
+ assertEquals(test[PARAM], uri.getParam());
+ assertEquals(test[QUERY], uri.getQuery());
+ assertEquals(test[FRAGMENT], uri.getFragment());
+ }
}
/* ------------------------------------------------------------ */
@Test
public void testFromURI() throws Exception
{
- HttpURI uri = new HttpURI(new URI(__input));
+ for (String[] test:tests)
+ {
+ HttpURI uri = new HttpURI(new URI(test[INPUT]));
- assertEquals(__scheme, uri.getScheme());
- assertEquals(__host,uri.getHost());
- assertEquals(__port,uri.getPort());
- assertEquals(__path,uri.getPath());
- assertEquals(__query,uri.getQuery());
- assertEquals(__fragment,uri.getFragment());
+ assertEquals(test[SCHEME], uri.getScheme());
+ assertEquals(test[HOST], uri.getHost());
+ assertEquals(Integer.parseInt(test[PORT]), uri.getPort());
+ assertEquals(test[PATH], uri.getPath());
+ assertEquals(test[PARAM], uri.getParam());
+ assertEquals(test[QUERY], uri.getQuery());
+ assertEquals(test[FRAGMENT], uri.getFragment());
+ }
}
}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java
index a0b090c..3fc9bef 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java
@@ -24,6 +24,7 @@
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
+import org.eclipse.jetty.util.BlockingCallback;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -112,13 +113,130 @@
if (_state.compareAndSet(State.FILLING,State.FILLING_INTERESTED))
break loop;
break;
+
+ case FILLING_BLOCKED:
+ if (_state.compareAndSet(State.FILLING_BLOCKED,State.FILLING_BLOCKED_INTERESTED))
+ break loop;
+ break;
+
+ case BLOCKED:
+ if (_state.compareAndSet(State.BLOCKED,State.BLOCKED_INTERESTED))
+ break loop;
+ break;
+ case FILLING_BLOCKED_INTERESTED:
+ case FILLING_INTERESTED:
+ case BLOCKED_INTERESTED:
+ case INTERESTED:
+ break loop;
+ }
+ }
+ }
+
+
+ private void unblock()
+ {
+ LOG.debug("unblock {}",this);
+
+ loop:while(true)
+ {
+ switch(_state.get())
+ {
+ case FILLING_BLOCKED:
+ if (_state.compareAndSet(State.FILLING_BLOCKED,State.FILLING))
+ break loop;
+ break;
+
+ case FILLING_BLOCKED_INTERESTED:
+ if (_state.compareAndSet(State.FILLING_BLOCKED_INTERESTED,State.FILLING_INTERESTED))
+ break loop;
+ break;
+
+ case BLOCKED_INTERESTED:
+ if (_state.compareAndSet(State.BLOCKED_INTERESTED,State.INTERESTED))
+ {
+ getEndPoint().fillInterested(_readCallback);
+ break loop;
+ }
+ break;
+
+ case BLOCKED:
+ if (_state.compareAndSet(State.BLOCKED,State.IDLE))
+ break loop;
+ break;
+
+ case FILLING:
+ case IDLE:
case FILLING_INTERESTED:
case INTERESTED:
break loop;
}
}
}
+
+
+ /**
+ */
+ protected void block(final BlockingCallback callback)
+ {
+ LOG.debug("block {}",this);
+
+ final Callback blocked=new Callback()
+ {
+ @Override
+ public void succeeded()
+ {
+ unblock();
+ callback.succeeded();
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ unblock();
+ callback.failed(x);
+ }
+ };
+
+ loop:while(true)
+ {
+ switch(_state.get())
+ {
+ case IDLE:
+ if (_state.compareAndSet(State.IDLE,State.BLOCKED))
+ {
+ getEndPoint().fillInterested(blocked);
+ break loop;
+ }
+ break;
+
+ case FILLING:
+ if (_state.compareAndSet(State.FILLING,State.FILLING_BLOCKED))
+ {
+ getEndPoint().fillInterested(blocked);
+ break loop;
+ }
+ break;
+
+ case FILLING_INTERESTED:
+ if (_state.compareAndSet(State.FILLING_INTERESTED,State.FILLING_BLOCKED_INTERESTED))
+ {
+ getEndPoint().fillInterested(blocked);
+ break loop;
+ }
+ break;
+
+ case BLOCKED:
+ case BLOCKED_INTERESTED:
+ case FILLING_BLOCKED:
+ case FILLING_BLOCKED_INTERESTED:
+ throw new IllegalStateException("Already Blocked");
+
+ case INTERESTED:
+ throw new IllegalStateException();
+ }
+ }
+ }
/**
* <p>Callback method invoked when the endpoint is ready to be read.</p>
@@ -225,7 +343,7 @@
private enum State
{
- IDLE, INTERESTED, FILLING, FILLING_INTERESTED
+ IDLE, INTERESTED, FILLING, FILLING_INTERESTED, FILLING_BLOCKED, BLOCKED, FILLING_BLOCKED_INTERESTED, BLOCKED_INTERESTED
}
private class ReadCallback implements Callback, Runnable
@@ -247,12 +365,25 @@
{
case IDLE:
case INTERESTED:
- throw new IllegalStateException();
+ case BLOCKED:
+ case BLOCKED_INTERESTED:
+ LOG.warn(new IllegalStateException());
+ return;
case FILLING:
if (_state.compareAndSet(State.FILLING,State.IDLE))
break loop;
break;
+
+ case FILLING_BLOCKED:
+ if (_state.compareAndSet(State.FILLING_BLOCKED,State.BLOCKED))
+ break loop;
+ break;
+
+ case FILLING_BLOCKED_INTERESTED:
+ if (_state.compareAndSet(State.FILLING_BLOCKED_INTERESTED,State.BLOCKED_INTERESTED))
+ break loop;
+ break;
case FILLING_INTERESTED:
if (_state.compareAndSet(State.FILLING_INTERESTED,State.INTERESTED))
@@ -266,7 +397,7 @@
}
}
else
- LOG.warn(new Throwable());
+ LOG.warn(new IllegalStateException());
}
@Override
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java
index 317c55e..f8b1327 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java
@@ -35,7 +35,6 @@
import java.util.List;
import java.util.Queue;
import java.util.Set;
-import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
@@ -167,20 +166,6 @@
selector.submit(selector.new Accept(channel));
}
- /**
- * <p>Registers a channel to perform non-blocking read/write operations.</p>
- * <p>This method is called just after a channel has been accepted by {@link ServerSocketChannel#accept()},
- * or just after having performed a blocking connect via {@link Socket#connect(SocketAddress, int)}.</p>
- *
- * @param channel the channel to register
- * @param attachment An attachment to be passed via the selection key to the {@link SelectorManager#newConnection(SocketChannel, EndPoint, Object)} method.
- */
- public void accept(final SocketChannel channel, Object attachment)
- {
- final ManagedSelector selector = chooseSelector();
- selector.submit(selector.new Accept(channel, attachment));
- }
-
@Override
protected void doStart() throws Exception
{
@@ -333,7 +318,7 @@
public class ManagedSelector extends AbstractLifeCycle implements Runnable, Dumpable
{
private final Queue<Runnable> _changes = new ConcurrentArrayQueue<>();
-
+
private final int _id;
private Selector _selector;
private volatile Thread _thread;
@@ -699,18 +684,10 @@
private class Accept implements Runnable
{
private final SocketChannel _channel;
- private final Object _attachment;
public Accept(SocketChannel channel)
{
this._channel = channel;
- this._attachment = null;
- }
-
- public Accept(SocketChannel channel, Object attachment)
- {
- this._channel = channel;
- this._attachment = attachment;
}
@Override
@@ -718,7 +695,7 @@
{
try
{
- SelectionKey key = _channel.register(_selector, 0, _attachment);
+ SelectionKey key = _channel.register(_selector, 0, null);
EndPoint endpoint = createEndPoint(_channel, key);
key.attach(endpoint);
}
diff --git a/jetty-jaas/src/main/config/etc/jetty-jaas.xml b/jetty-jaas/src/main/config/etc/jetty-jaas.xml
index e399478..7494898 100644
--- a/jetty-jaas/src/main/config/etc/jetty-jaas.xml
+++ b/jetty-jaas/src/main/config/etc/jetty-jaas.xml
@@ -11,7 +11,7 @@
<!-- ======================================================== -->
<Call class="java.lang.System" name="setProperty">
<Arg>java.security.auth.login.config</Arg>
- <Arg><SystemProperty name="jetty.home" default="." />/etc/login.conf</Arg>
+ <Arg><Property name="jetty.home" default="." />/<Property name="jaas.login.conf" default="etc/login.conf"/></Arg>
</Call>
</Configure>
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractJettyMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractJettyMojo.java
index 9429512..558899c 100644
--- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractJettyMojo.java
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractJettyMojo.java
@@ -284,7 +284,7 @@
/**
* A wrapper for the Server object
*/
- protected JettyServer server = JettyServer.getInstance();
+ protected JettyServer server = new JettyServer();
/**
@@ -494,6 +494,8 @@
String tmp = System.getProperty(PORT_SYSPROPERTY, MavenServerConnector.DEFAULT_PORT_STR);
httpConnector.setPort(Integer.parseInt(tmp.trim()));
}
+ if (httpConnector.getServer() == null)
+ httpConnector.setServer(this.server);
this.server.addConnector(httpConnector);
}
@@ -504,12 +506,13 @@
//if <httpConnector> not configured in the pom, create one
if (httpConnector == null)
{
- httpConnector = new MavenServerConnector();
+ httpConnector = new MavenServerConnector();
//use any jetty.port settings provided
String tmp = System.getProperty(PORT_SYSPROPERTY, MavenServerConnector.DEFAULT_PORT_STR);
httpConnector.setPort(Integer.parseInt(tmp.trim()));
}
-
+ if (httpConnector.getServer() == null)
+ httpConnector.setServer(this.server);
this.server.setConnectors(new Connector[] {httpConnector});
}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyServer.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyServer.java
index 5be4aaf..8018052 100644
--- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyServer.java
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyServer.java
@@ -38,16 +38,6 @@
{
public static final JettyServer __instance = new JettyServer();
- /**
- * Singleton instance
- * @return
- */
- public static JettyServer getInstance()
- {
- return __instance;
- }
-
-
private RequestLog requestLog;
private ContextHandlerCollection contexts;
@@ -56,7 +46,7 @@
/**
*
*/
- private JettyServer()
+ public JettyServer()
{
super();
setStopAtShutdown(true);
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenServerConnector.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenServerConnector.java
index f7c20c5..54aedb1 100644
--- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenServerConnector.java
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenServerConnector.java
@@ -19,21 +19,259 @@
package org.eclipse.jetty.maven.plugin;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.EndPoint;
+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.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+
+
/**
* MavenServerConnector
*
- *
+ * As the ServerConnector class does not have a no-arg constructor, and moreover requires
+ * the server instance passed in to all its constructors, it cannot
+ * be referenced in the pom.xml. This class wraps a ServerConnector, delaying setting the
+ * server instance. Only a few of the setters from the ServerConnector class are supported.
*/
-public class MavenServerConnector extends ServerConnector
+public class MavenServerConnector extends AbstractLifeCycle implements Connector
{
public static int DEFAULT_PORT = 8080;
public static String DEFAULT_PORT_STR = String.valueOf(DEFAULT_PORT);
public static int DEFAULT_MAX_IDLE_TIME = 30000;
+ private Server server;
+ private ServerConnector delegate;
+ private String host;
+ private String name;
+ private int port;
+ private long idleTimeout;
+ private int lingerTime;
+
+
public MavenServerConnector()
{
- super(JettyServer.getInstance());
+ }
+
+ public void setServer(Server server)
+ {
+ this.server = server;
+ }
+
+ public void setHost(String host)
+ {
+ this.host = host;
+ }
+
+ public String getHost()
+ {
+ return this.host;
+ }
+
+ public void setPort(int port)
+ {
+ this.port = port;
+ }
+
+ public int getPort ()
+ {
+ return this.port;
+ }
+
+ public void setName (String name)
+ {
+ this.name = name;
+ }
+
+ public void setIdleTimeout(long idleTimeout)
+ {
+ this.idleTimeout = idleTimeout;
+ }
+
+ public void setSoLingerTime(int lingerTime)
+ {
+ this.lingerTime = lingerTime;
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+
+ if (this.server == null)
+ throw new IllegalStateException("Server not set for MavenServerConnector");
+
+ this.delegate = new ServerConnector(this.server);
+ this.delegate.setName(this.name);
+ this.delegate.setPort(this.port);
+ this.delegate.setHost(this.host);
+ this.delegate.setIdleTimeout(idleTimeout);
+ this.delegate.setSoLingerTime(lingerTime);
+ this.delegate.start();
+
+ super.doStart();
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ this.delegate.stop();
+ super.doStop();
+ this.delegate = null;
+ }
+
+ /**
+ * @see org.eclipse.jetty.util.component.Graceful#shutdown()
+ */
+ @Override
+ public Future<Void> shutdown()
+ {
+ checkDelegate();
+ return this.delegate.shutdown();
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.Connector#getServer()
+ */
+ @Override
+ public Server getServer()
+ {
+ return this.server;
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.Connector#getExecutor()
+ */
+ @Override
+ public Executor getExecutor()
+ {
+ checkDelegate();
+ return this.delegate.getExecutor();
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.Connector#getScheduler()
+ */
+ @Override
+ public Scheduler getScheduler()
+ {
+ checkDelegate();
+ return this.delegate.getScheduler();
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.Connector#getByteBufferPool()
+ */
+ @Override
+ public ByteBufferPool getByteBufferPool()
+ {
+ checkDelegate();
+ return this.delegate.getByteBufferPool();
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.Connector#getConnectionFactory(java.lang.String)
+ */
+ @Override
+ public ConnectionFactory getConnectionFactory(String nextProtocol)
+ {
+ checkDelegate();
+ return this.delegate.getConnectionFactory(nextProtocol);
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.Connector#getConnectionFactory(java.lang.Class)
+ */
+ @Override
+ public <T> T getConnectionFactory(Class<T> factoryType)
+ {
+ checkDelegate();
+ return this.delegate.getConnectionFactory(factoryType);
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.Connector#getDefaultConnectionFactory()
+ */
+ @Override
+ public ConnectionFactory getDefaultConnectionFactory()
+ {
+ checkDelegate();
+ return getDefaultConnectionFactory();
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.Connector#getConnectionFactories()
+ */
+ @Override
+ public Collection<ConnectionFactory> getConnectionFactories()
+ {
+ checkDelegate();
+ return this.delegate.getConnectionFactories();
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.Connector#getProtocols()
+ */
+ @Override
+ public List<String> getProtocols()
+ {
+ checkDelegate();
+ return this.delegate.getProtocols();
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.Connector#getIdleTimeout()
+ */
+ @Override
+ @ManagedAttribute("maximum time a connection can be idle before being closed (in ms)")
+ public long getIdleTimeout()
+ {
+ checkDelegate();
+ return this.delegate.getIdleTimeout();
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.Connector#getTransport()
+ */
+ @Override
+ public Object getTransport()
+ {
+ checkDelegate();
+ return this.delegate.getTransport();
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.Connector#getConnectedEndPoints()
+ */
+ @Override
+ public Collection<EndPoint> getConnectedEndPoints()
+ {
+ checkDelegate();
+ return this.delegate.getConnectedEndPoints();
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.Connector#getName()
+ */
+ @Override
+ public String getName()
+ {
+ return this.name;
+ }
+
+ private void checkDelegate() throws IllegalStateException
+ {
+ if (this.delegate == null)
+ throw new IllegalStateException ("MavenServerConnector delegate not ready");
}
}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java
index 649b847..4d56d0f 100644
--- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java
@@ -55,7 +55,7 @@
private List<File> jettyXmls; // list of jetty.xml config files to apply - Mandatory
private File contextXml; //name of context xml file to configure the webapp - Mandatory
- private JettyServer server;
+ private JettyServer server = new JettyServer();
private JettyWebAppContext webApp;
@@ -120,8 +120,6 @@
{
LOG.debug("Starting Jetty Server ...");
- this.server = JettyServer.getInstance();
-
//apply any configs from jetty.xml files first
applyJettyXml ();
@@ -132,6 +130,7 @@
{
//if a SystemProperty -Djetty.port=<portnum> has been supplied, use that as the default port
MavenServerConnector httpConnector = new MavenServerConnector();
+ httpConnector.setServer(this.server);
String tmp = System.getProperty(PORT_SYSPROPERTY, MavenServerConnector.DEFAULT_PORT_STR);
httpConnector.setPort(Integer.parseInt(tmp.trim()));
connectors = new Connector[] {httpConnector};
diff --git a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionManager.java b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionManager.java
index ac00de7..b078f08 100644
--- a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionManager.java
+++ b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionManager.java
@@ -149,7 +149,7 @@
}
else
{
- version = new Long(((Long)version).intValue() + 1);
+ version = new Long(((Number)version).longValue() + 1);
update.put("$inc",__version_1);
}
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java
index a89ff5b..f20838b 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java
@@ -236,7 +236,7 @@
// can define their own configuration.
if ((enUrls == null || !enUrls.hasMoreElements()))
{
- String tmp = DEFAULT_JETTYHOME+etcFile;
+ String tmp = DEFAULT_JETTYHOME+(DEFAULT_JETTYHOME.endsWith("/")?"":"/")+etcFile;
enUrls = BundleFileLocatorHelperFactory.getFactory().getHelper().findEntries(configurationBundle, tmp);
LOG.info("Configuring jetty from bundle: "
+ configurationBundle.getSymbolicName()
diff --git a/jetty-osgi/test-jetty-osgi/pom.xml b/jetty-osgi/test-jetty-osgi/pom.xml
index 66af9e4..58184f8 100644
--- a/jetty-osgi/test-jetty-osgi/pom.xml
+++ b/jetty-osgi/test-jetty-osgi/pom.xml
@@ -95,7 +95,7 @@
<version>${exam.version}</version>
<scope>test</scope>
</dependency>
-
+
<dependency>
<groupId>org.ops4j.pax.runner</groupId>
<artifactId>pax-runner-no-jcl</artifactId>
@@ -141,12 +141,14 @@
<scope>test</scope>
</dependency>
<!-- For sane logging -->
+<!--
<dependency>
<groupId>org.slf4j</groupId>
- <artifactId>slf4j-simple</artifactId>
+ <artifactId>slf4j-log4j12</artifactId>
<version>1.6.1</version>
<scope>test</scope>
</dependency>
+-->
<!-- Orbit Servlet Deps -->
<dependency>
<groupId>org.eclipse.jetty.orbit</groupId>
@@ -258,7 +260,7 @@
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
-
+
<dependency>
<groupId>org.eclipse.jetty.spdy</groupId>
<artifactId>spdy-core</artifactId>
@@ -296,7 +298,7 @@
<scope>test</scope>
</dependency>
-
+
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-plus</artifactId>
diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java
index dc850f2..b15e002 100644
--- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java
+++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java
@@ -78,17 +78,21 @@
// to pick up and deploy
options.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("test-jetty-osgi-context").versionAsInProject().start());
+ String logLevel = "WARN";
// Enable Logging
if (LOGGING_ENABLED)
- {
- options.addAll(Arrays.asList(options(
- // install log service using pax runners profile abstraction (there
- // are more profiles, like DS)
- // logProfile(),
- // this is how you set the default log level when using pax logging
- // (logProfile)
- systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value("INFO"))));
- }
+ logLevel = "INFO";
+
+
+ options.addAll(Arrays.asList(options(
+ // install log service using pax runners profile abstraction (there
+ // are more profiles, like DS)
+ // logProfile(),
+ // this is how you set the default log level when using pax logging
+ // (logProfile)
+ systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(logLevel),
+ systemProperty("org.eclipse.jetty.LEVEL").value(logLevel))));
+
return options.toArray(new Option[options.size()]);
}
@@ -142,12 +146,14 @@
ServiceReference[] refs = bundleContext.getServiceReferences(ContextHandler.class.getName(), null);
Assert.assertNotNull(refs);
Assert.assertEquals(1, refs.length);
- String[] keys = refs[0].getPropertyKeys();
+ //uncomment for debugging
+ /*
+ String[] keys = refs[0].getPropertyKeys();
if (keys != null)
{
for (String k : keys)
System.err.println("service property: " + k + ", " + refs[0].getProperty(k));
- }
+ }*/
ContextHandler ch = (ContextHandler) bundleContext.getService(refs[0]);
Assert.assertEquals("/acme", ch.getContextPath());
diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootCore.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootCore.java
index 8ffde85..01eb167 100644
--- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootCore.java
+++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootCore.java
@@ -53,7 +53,6 @@
public Option[] config()
{
VersionResolver resolver = MavenUtils.asInProject();
- System.err.println(resolver.getVersion("org.eclipse.jetty", "jetty-server"));
ArrayList<Option> options = new ArrayList<Option>();
TestOSGiUtil.addMoreOSGiContainers(options);
options.addAll(provisionCoreJetty());
diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootSpdy.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootSpdy.java
index 3d6b211..ee104b7 100644
--- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootSpdy.java
+++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootSpdy.java
@@ -19,9 +19,13 @@
package org.eclipse.jetty.osgi.test;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.systemProperty;
+import static org.ops4j.pax.exam.CoreOptions.options;
+
import java.io.File;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import javax.inject.Inject;
@@ -41,7 +45,8 @@
@RunWith(JUnit4TestRunner.class)
public class TestJettyOSGiBootSpdy
{
-
+ private static final boolean LOGGING_ENABLED = false;
+
private static final String JETTY_SPDY_PORT = "jetty.spdy.port";
private static final int DEFAULT_JETTY_SPDY_PORT = 9877;
@@ -61,6 +66,22 @@
options.add(CoreOptions.junitBundles());
options.addAll(TestJettyOSGiBootCore.httpServiceJetty());
options.addAll(spdyJettyDependencies());
+
+ String logLevel = "WARN";
+
+ // Enable Logging
+ if (LOGGING_ENABLED)
+ logLevel = "INFO";
+
+
+ options.addAll(Arrays.asList(options(
+ // install log service using pax runners profile abstraction (there
+ // are more profiles, like DS)
+ // logProfile(),
+ // this is how you set the default log level when using pax logging
+ // (logProfile)
+ systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(logLevel),
+ systemProperty("org.eclipse.jetty.LEVEL").value(logLevel))));
return options.toArray(new Option[options.size()]);
}
diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWebAppAsService.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWebAppAsService.java
index 4ed58cb..f6e04f5 100644
--- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWebAppAsService.java
+++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWebAppAsService.java
@@ -77,18 +77,20 @@
options.addAll(configureJettyHomeAndPort("jetty-selector.xml"));
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*"));
options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
-
- // Enable Logging
+
+ String logLevel = "WARN";
if (LOGGING_ENABLED)
- {
- options.addAll(Arrays.asList(options(
- // install log service using pax runners profile abstraction (there
- // are more profiles, like DS)
- // logProfile(),
- // this is how you set the default log level when using pax logging
- // (logProfile)
- systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value("INFO"))));
- }
+ logLevel = "INFO";
+
+
+ options.addAll(Arrays.asList(options(
+ // install log service using pax runners profile abstraction (there
+ // are more profiles, like DS)
+ // logProfile(),
+ // this is how you set the default log level when using pax logging
+ // (logProfile)
+ systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(logLevel),
+ systemProperty("org.eclipse.jetty.LEVEL").value(logLevel))));
options.addAll(jspDependencies());
return options.toArray(new Option[options.size()]);
diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithJsp.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithJsp.java
index f8521b9..cd4b5b2 100644
--- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithJsp.java
+++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithJsp.java
@@ -52,7 +52,7 @@
@RunWith(JUnit4TestRunner.class)
public class TestJettyOSGiBootWithJsp
{
- private static final boolean LOGGING_ENABLED = true;
+ private static final boolean LOGGING_ENABLED = false;
private static final boolean REMOTE_DEBUGGING = false;
@@ -72,18 +72,21 @@
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*"));
options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
+ String logLevel = "WARN";
+
// Enable Logging
if (LOGGING_ENABLED)
- {
+ logLevel = "INFO";
+
options.addAll(Arrays.asList(options(
// install log service using pax runners profile abstraction (there
// are more profiles, like DS)
// logProfile(),
// this is how you set the default log level when using pax logging
// (logProfile)
- systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value("INFO"))));
- }
-
+ systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(logLevel),
+ systemProperty("org.eclipse.jetty.LEVEL").value(logLevel))));
+
options.addAll(jspDependencies());
// Remote JDWP Debugging, this won't work with the forked container.
@@ -119,7 +122,6 @@
+ etc
+ "/jetty-testrealm.xml";
options.add(systemProperty(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS).value(xmlConfigs));
- System.err.println(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS+"="+xmlConfigs);
options.add(systemProperty("jetty.port").value(String.valueOf(TestJettyOSGiBootCore.DEFAULT_JETTY_HTTP_PORT)));
options.add(systemProperty("jetty.home").value(etcFolder.getParentFile().getAbsolutePath()));
return options;
@@ -163,10 +165,6 @@
@Test
public void testJspDump() throws Exception
{
-
- // System.err.println("http://127.0.0.1:9876/jsp/dump.jsp sleeping....");
- // Thread.currentThread().sleep(5000000);
- // now test the jsp/dump.jsp
HttpClient client = new HttpClient();
try
{
@@ -175,7 +173,6 @@
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
String content = new String(response.getContent());
- System.err.println("content: " + content);
Assert.assertTrue(content.contains("<tr><th>ServletPath:</th><td>/jsp/dump.jsp</td></tr>"));
}
finally
diff --git a/jetty-osgi/test-jetty-osgi/src/test/resources/log4j.properties b/jetty-osgi/test-jetty-osgi/src/test/resources/log4j.properties
new file mode 100644
index 0000000..f9eff1f
--- /dev/null
+++ b/jetty-osgi/test-jetty-osgi/src/test/resources/log4j.properties
@@ -0,0 +1,13 @@
+# LOG4J levels: OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL
+#
+log4j.rootLogger=ALL,CONSOLE
+
+log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
+#log4j.appender.CONSOLE.threshold=INFO
+log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
+#log4j.appender.CONSOLE.layout.ConversionPattern=%d %t [%5p][%c{1}] %m%n
+log4j.appender.CONSOLE.layout.ConversionPattern=%d [%5p][%c] %m%n
+
+# Level tuning
+log4j.logger.org.eclipse.jetty=INFO
+log4j.logger.org.ops4j=WARN
diff --git a/jetty-rewrite/src/main/config/etc/jetty-rewrite.xml b/jetty-rewrite/src/main/config/etc/jetty-rewrite.xml
index fd0a891..e13a162 100644
--- a/jetty-rewrite/src/main/config/etc/jetty-rewrite.xml
+++ b/jetty-rewrite/src/main/config/etc/jetty-rewrite.xml
@@ -16,101 +16,9 @@
<Set name="handler">
<New id="Rewrite" class="org.eclipse.jetty.rewrite.handler.RewriteHandler">
<Set name="handler"><Ref refid="oldhandler"/></Set>
- <Set name="rewriteRequestURI">true</Set>
- <Set name="rewritePathInfo">false</Set>
- <Set name="originalPathAttribute">requestedPath</Set>
-
- <!-- Add rule to protect against IE ssl bug -->
- <Call name="addRule">
- <Arg>
- <New class="org.eclipse.jetty.rewrite.handler.MsieSslRule"/>
- </Arg>
- </Call>
-
- <!-- protect favicon handling -->
- <Call name="addRule">
- <Arg>
- <New class="org.eclipse.jetty.rewrite.handler.HeaderPatternRule">
- <Set name="pattern">/favicon.ico</Set>
- <Set name="name">Cache-Control</Set>
- <Set name="value">Max-Age=3600,public</Set>
- <Set name="terminating">true</Set>
- </New>
- </Arg>
- </Call>
-
- <!-- redirect from the welcome page to a specific page -->
- <Call name="addRule">
- <Arg>
- <New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
- <Set name="pattern">/test/rewrite/</Set>
- <Set name="replacement">/test/rewrite/info.html</Set>
- </New>
- </Arg>
- </Call>
-
- <!-- replace the entire request URI -->
- <Call name="addRule">
- <Arg>
- <New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
- <Set name="pattern">/test/some/old/context</Set>
- <Set name="replacement">/test/rewritten/newcontext</Set>
- </New>
- </Arg>
- </Call>
-
- <!-- replace the beginning of the request URI -->
- <Call name="addRule">
- <Arg>
- <New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
- <Set name="pattern">/test/rewrite/for/*</Set>
- <Set name="replacement">/test/rewritten/</Set>
- </New>
- </Arg>
- </Call>
-
- <!-- reverse the order of the path sections -->
- <Call name="addRule">
- <Arg>
- <New class="org.eclipse.jetty.rewrite.handler.RewriteRegexRule">
- <Set name="regex">(.*?)/reverse/([^/]*)/(.*)</Set>
- <Set name="replacement">$1/reverse/$3/$2</Set>
- </New>
- </Arg>
- </Call>
-
- <!-- add a cookie to each path visited -->
- <Call name="addRule">
- <Arg>
- <New class="org.eclipse.jetty.rewrite.handler.CookiePatternRule">
- <Set name="pattern">/*</Set>
- <Set name="name">visited</Set>
- <Set name="value">yes</Set>
- </New>
- </Arg>
- </Call>
-
- <!-- actual redirect, instead of internal rewrite -->
- <Call name="addRule">
- <Arg>
- <New class="org.eclipse.jetty.rewrite.handler.RedirectPatternRule">
- <Set name="pattern">/test/redirect/*</Set>
- <Set name="location">/test/redirected</Set>
- </New>
- </Arg>
- </Call>
-
- <!-- add a response rule -->
- <Call name="addRule">
- <Arg>
- <New class="org.eclipse.jetty.rewrite.handler.ResponsePatternRule">
- <Set name="pattern">/400Error</Set>
- <Set name="code">400</Set>
- <Set name="reason">ResponsePatternRule Demo</Set>
- </New>
- </Arg>
- </Call>
-
+ <Set name="rewriteRequestURI"><Property name="rewrite.rewriteRequestURI" default="true"/></Set>
+ <Set name="rewritePathInfo"><Property name="rewrite.rewritePathInfo" default="false"/></Set>
+ <Set name="originalPathAttribute"><Property name="rewrite.originalPathAttribute" default="requestedPath"/></Set>
</New>
</Set>
diff --git a/jetty-runner/pom.xml b/jetty-runner/pom.xml
index 0f6dff6..3ddb4ca 100644
--- a/jetty-runner/pom.xml
+++ b/jetty-runner/pom.xml
@@ -27,9 +27,7 @@
</goals>
<configuration>
<includes>**</includes>
- <excludes>**/MANIFEST.MF</excludes>
- <excludes>**/ECLIPSEF.RSA</excludes>
- <excludes>**/ECLIPSEF.SF</excludes>
+ <excludes>**/MANIFEST.MF,META-INF/*.RSA,META-INF/*.DSA,META-INF/*.SF</excludes>
<outputDirectory>${project.build.directory}/classes</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>true</overWriteSnapshots>
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java
index 7878479..fd9a415 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java
@@ -551,23 +551,20 @@
protected void processConstraintMappingWithMethodOmissions (ConstraintMapping mapping, Map<String, RoleInfo> mappings)
{
String[] omissions = mapping.getMethodOmissions();
-
- for (String omission:omissions)
+ StringBuilder sb = new StringBuilder();
+ for (int i=0; i<omissions.length; i++)
{
- //for each method omission, see if there is already a RoleInfo for it in mappings
- RoleInfo ri = mappings.get(omission+OMISSION_SUFFIX);
- if (ri == null)
- {
- //if not, make one
- ri = new RoleInfo();
- mappings.put(omission+OMISSION_SUFFIX, ri);
- }
-
- //initialize RoleInfo or combine from ConstraintMapping
- configureRoleInfo(ri, mapping);
+ if (i > 0)
+ sb.append(".");
+ sb.append(omissions[i]);
}
+ sb.append(OMISSION_SUFFIX);
+
+ RoleInfo ri = new RoleInfo();
+ mappings.put(sb.toString(), ri);
+ configureRoleInfo(ri, mapping);
}
-
+
/* ------------------------------------------------------------ */
/**
@@ -630,7 +627,7 @@
* <ol>
* <li>A mapping of an exact method name </li>
* <li>A mapping will null key that matches every method name</li>
- * <li>Mappings with keys of the form "method.omission" that indicates it will match every method name EXCEPT that given</li>
+ * <li>Mappings with keys of the form "<method>.<method>.<method>.omission" that indicates it will match every method name EXCEPT those given</li>
* </ol>
*
* @see org.eclipse.jetty.security.SecurityHandler#prepareConstraintInfo(java.lang.String, org.eclipse.jetty.server.Request)
@@ -659,7 +656,7 @@
//(ie matches because target method is not omitted, hence considered covered by the constraint)
for (Entry<String, RoleInfo> entry: mappings.entrySet())
{
- if (entry.getKey() != null && entry.getKey().contains(OMISSION_SUFFIX) && !(httpMethod+OMISSION_SUFFIX).equals(entry.getKey()))
+ if (entry.getKey() != null && entry.getKey().endsWith(OMISSION_SUFFIX) && ! entry.getKey().contains(httpMethod))
applicableConstraints.add(entry.getValue());
}
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java
index 2dcb0d0..0fe3747 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java
@@ -21,11 +21,11 @@
import java.io.IOException;
import java.security.MessageDigest;
import java.security.SecureRandom;
+import java.util.BitSet;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
@@ -53,24 +53,40 @@
* @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
*
* The nonce max age in ms can be set with the {@link SecurityHandler#setInitParameter(String, String)}
- * using the name "maxNonceAge"
+ * using the name "maxNonceAge". The nonce max count can be set with {@link SecurityHandler#setInitParameter(String, String)}
+ * using the name "maxNonceCount". When the age or count is exceeded, the nonce is considered stale.
*/
public class DigestAuthenticator extends LoginAuthenticator
{
private static final Logger LOG = Log.getLogger(DigestAuthenticator.class);
SecureRandom _random = new SecureRandom();
private long _maxNonceAgeMs = 60*1000;
- private ConcurrentMap<String, Nonce> _nonceCount = new ConcurrentHashMap<String, Nonce>();
+ private int _maxNC=1024;
+ private ConcurrentMap<String, Nonce> _nonceMap = new ConcurrentHashMap<String, Nonce>();
private Queue<Nonce> _nonceQueue = new ConcurrentLinkedQueue<Nonce>();
private static class Nonce
{
final String _nonce;
final long _ts;
- AtomicInteger _nc=new AtomicInteger();
- public Nonce(String nonce, long ts)
+ final BitSet _seen;
+
+ public Nonce(String nonce, long ts, int size)
{
_nonce=nonce;
_ts=ts;
+ _seen = new BitSet(size);
+ }
+
+ public boolean seen(int count)
+ {
+ synchronized (this)
+ {
+ if (count>=_seen.size())
+ return true;
+ boolean s=_seen.get(count);
+ _seen.set(count);
+ return s;
+ }
}
}
@@ -92,11 +108,31 @@
String mna=configuration.getInitParameter("maxNonceAge");
if (mna!=null)
{
- synchronized (this)
- {
- _maxNonceAgeMs=Long.valueOf(mna);
- }
+ _maxNonceAgeMs=Long.valueOf(mna);
}
+ String mnc=configuration.getInitParameter("maxNonceCount");
+ if (mnc!=null)
+ {
+ _maxNC=Integer.valueOf(mnc);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getMaxNonceCount()
+ {
+ return _maxNC;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setMaxNonceCount(int maxNC)
+ {
+ _maxNC = maxNC;
+ }
+
+ /* ------------------------------------------------------------ */
+ public long getMaxNonceAge()
+ {
+ return _maxNonceAgeMs;
}
/* ------------------------------------------------------------ */
@@ -238,9 +274,9 @@
byte[] nounce = new byte[24];
_random.nextBytes(nounce);
- nonce = new Nonce(new String(B64Code.encode(nounce)),request.getTimeStamp());
+ nonce = new Nonce(new String(B64Code.encode(nounce)),request.getTimeStamp(),_maxNC);
}
- while (_nonceCount.putIfAbsent(nonce._nonce,nonce)!=null);
+ while (_nonceMap.putIfAbsent(nonce._nonce,nonce)!=null);
_nonceQueue.add(nonce);
return nonce._nonce;
@@ -255,34 +291,27 @@
private int checkNonce(Digest digest, Request request)
{
// firstly let's expire old nonces
- long expired;
- synchronized (this)
- {
- expired = request.getTimeStamp()-_maxNonceAgeMs;
- }
-
+ long expired = request.getTimeStamp()-_maxNonceAgeMs;
Nonce nonce=_nonceQueue.peek();
while (nonce!=null && nonce._ts<expired)
{
_nonceQueue.remove(nonce);
- _nonceCount.remove(nonce._nonce);
+ _nonceMap.remove(nonce._nonce);
nonce=_nonceQueue.peek();
}
-
+ // Now check the requested nonce
try
{
- nonce = _nonceCount.get(digest.nonce);
+ nonce = _nonceMap.get(digest.nonce);
if (nonce==null)
return 0;
long count = Long.parseLong(digest.nc,16);
- if (count>Integer.MAX_VALUE)
+ if (count>=_maxNC)
return 0;
- int old=nonce._nc.get();
- while (!nonce._nc.compareAndSet(old,(int)count))
- old=nonce._nc.get();
- if (count<=old)
+
+ if (nonce.seen((int)count))
return -1;
return 1;
@@ -383,6 +412,7 @@
return false;
}
+ @Override
public String toString()
{
return username + "," + response;
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 abbfe73..a1480b4 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
@@ -25,6 +25,7 @@
import static org.junit.matchers.JUnitMatchers.containsString;
import java.io.IOException;
+import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -33,12 +34,15 @@
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.security.authentication.BasicAuthenticator;
+import org.eclipse.jetty.security.authentication.DigestAuthenticator;
import org.eclipse.jetty.security.authentication.FormAuthenticator;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
@@ -52,7 +56,10 @@
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.security.Constraint;
+import org.eclipse.jetty.util.security.Credential;
import org.eclipse.jetty.util.security.Password;
import org.junit.After;
import org.junit.Before;
@@ -205,7 +212,6 @@
@Test
public void testBasic() throws Exception
{
-
List<ConstraintMapping> list = new ArrayList<ConstraintMapping>(_security.getConstraintMappings());
Constraint constraint6 = new Constraint();
@@ -250,14 +256,11 @@
_server.start();
String response;
- /*
response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n\r\n");
assertThat(response,startsWith("HTTP/1.1 200 OK"));
-*/
response = _connector.getResponses("GET /ctx/forbid/info HTTP/1.0\r\n\r\n");
assertThat(response,startsWith("HTTP/1.1 403 Forbidden"));
- /*
response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n");
assertThat(response,startsWith("HTTP/1.1 401 Unauthorized"));
assertThat(response,containsString("WWW-Authenticate: basic realm=\"TestRealm\""));
@@ -272,8 +275,7 @@
"Authorization: Basic " + B64Code.encode("user:password") + "\r\n" +
"\r\n");
assertThat(response,startsWith("HTTP/1.1 200 OK"));
-*/
-/*
+
// test admin
response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n\r\n");
assertThat(response,startsWith("HTTP/1.1 401 Unauthorized"));
@@ -304,28 +306,159 @@
response = _connector.getResponses("GET /ctx/omit/x HTTP/1.0\r\n" +
"Authorization: Basic " + B64Code.encode("admin:password") + "\r\n" +
"\r\n");
- assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+ assertThat(response,startsWith("HTTP/1.1 200 OK"));
//check POST is in role user
response = _connector.getResponses("POST /ctx/omit/x HTTP/1.0\r\n" +
"Authorization: Basic " + B64Code.encode("user2:password") + "\r\n" +
"\r\n");
- assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+ assertThat(response,startsWith("HTTP/1.1 200 OK"));
//check POST can be in role foo too
response = _connector.getResponses("POST /ctx/omit/x HTTP/1.0\r\n" +
"Authorization: Basic " + B64Code.encode("user3:password") + "\r\n" +
"\r\n");
- assertTrue(response.startsWith("HTTP/1.1 200 OK"));
+ assertThat(response,startsWith("HTTP/1.1 200 OK"));
//check HEAD cannot be in role user
response = _connector.getResponses("HEAD /ctx/omit/x HTTP/1.0\r\n" +
"Authorization: Basic " + B64Code.encode("user2:password") + "\r\n" +
"\r\n");
- assertTrue(response.startsWith("HTTP/1.1 200 OK"));*/
+ assertThat(response,startsWith("HTTP/1.1 403 "));
+ }
+
+
+ private static String CNONCE="1234567890";
+ private String digest(String nonce, String username,String password,String uri,String nc) throws Exception
+ {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ byte[] ha1;
+ // calc A1 digest
+ md.update(username.getBytes(StringUtil.__ISO_8859_1));
+ md.update((byte) ':');
+ md.update("TestRealm".getBytes(StringUtil.__ISO_8859_1));
+ md.update((byte) ':');
+ md.update(password.getBytes(StringUtil.__ISO_8859_1));
+ ha1 = md.digest();
+ // calc A2 digest
+ md.reset();
+ md.update("GET".getBytes(StringUtil.__ISO_8859_1));
+ md.update((byte) ':');
+ md.update(uri.getBytes(StringUtil.__ISO_8859_1));
+ byte[] ha2 = md.digest();
+
+ // calc digest
+ // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":"
+ // nc-value ":" unq(cnonce-value) ":" unq(qop-value) ":" H(A2) )
+ // <">
+ // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2)
+ // ) > <">
+
+ md.update(TypeUtil.toString(ha1, 16).getBytes(StringUtil.__ISO_8859_1));
+ md.update((byte) ':');
+ md.update(nonce.getBytes(StringUtil.__ISO_8859_1));
+ md.update((byte) ':');
+ md.update(nc.getBytes(StringUtil.__ISO_8859_1));
+ md.update((byte) ':');
+ md.update(CNONCE.getBytes(StringUtil.__ISO_8859_1));
+ md.update((byte) ':');
+ md.update("auth".getBytes(StringUtil.__ISO_8859_1));
+ md.update((byte) ':');
+ md.update(TypeUtil.toString(ha2, 16).getBytes(StringUtil.__ISO_8859_1));
+ byte[] digest = md.digest();
+
+ // check digest
+ return TypeUtil.toString(digest, 16);
}
-
+ @Test
+ public void testDigest() throws Exception
+ {
+ DigestAuthenticator authenticator = new DigestAuthenticator();
+ authenticator.setMaxNonceCount(5);
+ _security.setAuthenticator(authenticator);
+ _security.setStrict(false);
+ _server.start();
+
+ String response;
+ response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n\r\n");
+ assertThat(response,startsWith("HTTP/1.1 200 OK"));
+
+ response = _connector.getResponses("GET /ctx/forbid/info HTTP/1.0\r\n\r\n");
+ assertThat(response,startsWith("HTTP/1.1 403 Forbidden"));
+ response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n");
+ assertThat(response,startsWith("HTTP/1.1 401 Unauthorized"));
+ assertThat(response,containsString("WWW-Authenticate: Digest realm=\"TestRealm\""));
+
+ Pattern nonceP = Pattern.compile("nonce=\"([^\"]*)\",");
+ Matcher matcher = nonceP.matcher(response);
+ assertTrue(matcher.find());
+ String nonce=matcher.group(1);
+
+
+ //wrong password
+ String digest= digest(nonce,"user","WRONG","/ctx/auth/info","1");
+ response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
+ "Authorization: Digest username=\"user\", qop=auth, cnonce=\"1234567890\", uri=\"/ctx/auth/info\", realm=\"TestRealm\", "+
+ "nc=1, "+
+ "nonce=\""+nonce+"\", "+
+ "response=\""+digest+"\"\r\n"+
+ "\r\n");
+ assertThat(response,startsWith("HTTP/1.1 401 Unauthorized"));
+
+ // right password
+ digest= digest(nonce,"user","password","/ctx/auth/info","2");
+ response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
+ "Authorization: Digest username=\"user\", qop=auth, cnonce=\"1234567890\", uri=\"/ctx/auth/info\", realm=\"TestRealm\", "+
+ "nc=2, "+
+ "nonce=\""+nonce+"\", "+
+ "response=\""+digest+"\"\r\n"+
+ "\r\n");
+ assertThat(response,startsWith("HTTP/1.1 200 OK"));
+
+
+ // once only
+ digest= digest(nonce,"user","password","/ctx/auth/info","2");
+ response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
+ "Authorization: Digest username=\"user\", qop=auth, cnonce=\"1234567890\", uri=\"/ctx/auth/info\", realm=\"TestRealm\", "+
+ "nc=2, "+
+ "nonce=\""+nonce+"\", "+
+ "response=\""+digest+"\"\r\n"+
+ "\r\n");
+ assertThat(response,startsWith("HTTP/1.1 401 Unauthorized"));
+
+ // increasing
+ digest= digest(nonce,"user","password","/ctx/auth/info","4");
+ response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
+ "Authorization: Digest username=\"user\", qop=auth, cnonce=\"1234567890\", uri=\"/ctx/auth/info\", realm=\"TestRealm\", "+
+ "nc=4, "+
+ "nonce=\""+nonce+"\", "+
+ "response=\""+digest+"\"\r\n"+
+ "\r\n");
+ assertThat(response,startsWith("HTTP/1.1 200 OK"));
+
+ // out of order
+ digest= digest(nonce,"user","password","/ctx/auth/info","3");
+ response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
+ "Authorization: Digest username=\"user\", qop=auth, cnonce=\"1234567890\", uri=\"/ctx/auth/info\", realm=\"TestRealm\", "+
+ "nc=3, "+
+ "nonce=\""+nonce+"\", "+
+ "response=\""+digest+"\"\r\n"+
+ "\r\n");
+ assertThat(response,startsWith("HTTP/1.1 200 OK"));
+
+ // stale
+ digest= digest(nonce,"user","password","/ctx/auth/info","5");
+ response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
+ "Authorization: Digest username=\"user\", qop=auth, cnonce=\"1234567890\", uri=\"/ctx/auth/info\", realm=\"TestRealm\", "+
+ "nc=5, "+
+ "nonce=\""+nonce+"\", "+
+ "response=\""+digest+"\"\r\n"+
+ "\r\n");
+ assertThat(response,startsWith("HTTP/1.1 401 Unauthorized"));
+ assertThat(response,containsString("stale=true"));
+ }
+
@Test
public void testFormDispatch() throws Exception
@@ -863,32 +996,32 @@
String response;
response = _connector.getResponses("GET /ctx/data/info HTTP/1.0\r\n\r\n");
- assertTrue(response.startsWith("HTTP/1.1 403"));
+ assertThat(response,startsWith("HTTP/1.1 403"));
_config.setSecurePort(8443);
_config.setSecureScheme("https");
response = _connector.getResponses("GET /ctx/data/info HTTP/1.0\r\n\r\n");
- assertTrue(response.startsWith("HTTP/1.1 302 Found"));
+ assertThat(response,startsWith("HTTP/1.1 302 Found"));
assertTrue(response.indexOf("Location") > 0);
assertTrue(response.indexOf(":8443/ctx/data/info") > 0);
_config.setSecurePort(443);
response = _connector.getResponses("GET /ctx/data/info HTTP/1.0\r\n\r\n");
- assertTrue(response.startsWith("HTTP/1.1 302 Found"));
+ assertThat(response,startsWith("HTTP/1.1 302 Found"));
assertTrue(response.indexOf("Location") > 0);
assertTrue(response.indexOf(":443/ctx/data/info") < 0);
_config.setSecurePort(8443);
response = _connector.getResponses("GET /ctx/data/info HTTP/1.0\r\nHost: wobble.com\r\n\r\n");
- assertTrue(response.startsWith("HTTP/1.1 302 Found"));
+ assertThat(response,startsWith("HTTP/1.1 302 Found"));
assertTrue(response.indexOf("Location") > 0);
assertTrue(response.indexOf("https://wobble.com:8443/ctx/data/info") > 0);
_config.setSecurePort(443);
response = _connector.getResponses("GET /ctx/data/info HTTP/1.0\r\nHost: wobble.com\r\n\r\n");
System.err.println(response);
- assertTrue(response.startsWith("HTTP/1.1 302 Found"));
+ assertThat(response,startsWith("HTTP/1.1 302 Found"));
assertTrue(response.indexOf("Location") > 0);
assertTrue(response.indexOf(":443") < 0);
assertTrue(response.indexOf("https://wobble.com/ctx/data/info") > 0);
diff --git a/jetty-server/src/main/config/etc/jetty-http.xml b/jetty-server/src/main/config/etc/jetty-http.xml
index 731304e..7ae3064 100644
--- a/jetty-server/src/main/config/etc/jetty-http.xml
+++ b/jetty-server/src/main/config/etc/jetty-http.xml
@@ -33,7 +33,7 @@
</Arg>
<Set name="host"><Property name="jetty.host" /></Set>
<Set name="port"><Property name="jetty.port" default="8080" /></Set>
- <Set name="idleTimeout">30000</Set>
+ <Set name="idleTimeout"><Property name="http.timeout" default="30000"/></Set>
</New>
</Arg>
</Call>
diff --git a/jetty-server/src/main/config/etc/jetty-lowresources.xml b/jetty-server/src/main/config/etc/jetty-lowresources.xml
index 5b264f1..060919a 100644
--- a/jetty-server/src/main/config/etc/jetty-lowresources.xml
+++ b/jetty-server/src/main/config/etc/jetty-lowresources.xml
@@ -10,12 +10,12 @@
<Arg>
<New class="org.eclipse.jetty.server.LowResourceMonitor">
<Arg name="server"><Ref refid='Server'/></Arg>
- <Set name="period">1000</Set>
- <Set name="lowResourcesIdleTimeout">200</Set>
- <Set name="monitorThreads">true</Set>
- <Set name="maxConnections">0</Set>
- <Set name="maxMemory">0</Set>
- <Set name="maxLowResourcesTime">5000</Set>
+ <Set name="period"><Property name="lowresources.period" default="1000"/></Set>
+ <Set name="lowResourcesIdleTimeout"><Property name="lowresources.lowResourcesIdleTimeout" default="200"/></Set>
+ <Set name="monitorThreads"><Property name="lowresources.monitorThreads" default="true"/></Set>
+ <Set name="maxConnections"><Property name="lowresources.maxConnections" default="0"/></Set>
+ <Set name="maxMemory"><Property name="lowresources.maxMemory" default="0"/></Set>
+ <Set name="maxLowResourcesTime"><Property name="lowresources.maxLowResourcesTime" default="5000"/></Set>
</New>
</Arg>
</Call>
diff --git a/jetty-server/src/main/config/etc/jetty-requestlog.xml b/jetty-server/src/main/config/etc/jetty-requestlog.xml
index 6e6eb05..2131777 100644
--- a/jetty-server/src/main/config/etc/jetty-requestlog.xml
+++ b/jetty-server/src/main/config/etc/jetty-requestlog.xml
@@ -17,9 +17,9 @@
<New id="RequestLogImpl" class="org.eclipse.jetty.server.AsyncNCSARequestLog">
<Set name="filename"><Property name="jetty.logs" default="./logs" />/yyyy_mm_dd.request.log</Set>
<Set name="filenameDateFormat">yyyy_MM_dd</Set>
- <Set name="retainDays">90</Set>
- <Set name="append">true</Set>
- <Set name="extended">true</Set>
+ <Set name="retainDays"><Property name="requestlog.retain" default="90"/></Set>
+ <Set name="append"><Property name="requestlog.append" default="false"/></Set>
+ <Set name="extended"><Property name="requestlog.extended" default="false"/></Set>
<Set name="logCookies">false</Set>
<Set name="LogTimeZone">GMT</Set>
</New>
diff --git a/jetty-server/src/main/config/etc/jetty-stats.xml b/jetty-server/src/main/config/etc/jetty-stats.xml
index dbef0e2..2e7a57c 100644
--- a/jetty-server/src/main/config/etc/jetty-stats.xml
+++ b/jetty-server/src/main/config/etc/jetty-stats.xml
@@ -12,4 +12,7 @@
<Set name="handler"><Ref refid="oldhandler" /></Set>
</New>
</Set>
+ <Call class="org.eclipse.jetty.server.ConnectorStatistics" name="addToAllConnectors">
+ <Arg><Ref refid="Server"/></Arg>
+ </Call>
</Configure>
diff --git a/jetty-server/src/main/config/etc/jetty.xml b/jetty-server/src/main/config/etc/jetty.xml
index 668980e..a551326 100644
--- a/jetty-server/src/main/config/etc/jetty.xml
+++ b/jetty-server/src/main/config/etc/jetty.xml
@@ -44,9 +44,9 @@
<!-- =========================================================== -->
<Arg name="threadpool">
<New id="threadpool" class="org.eclipse.jetty.util.thread.QueuedThreadPool">
- <Arg name="minThreads" type="int">10</Arg>
- <Arg name="maxThreads" type="int">200</Arg>
- <Arg name="idleTimeout" type="int">60000</Arg>
+ <Arg name="minThreads" type="int"><Property name="threads.min" default="10"/></Arg>
+ <Arg name="maxThreads" type="int"><Property name="threads.max" default="200"/></Arg>
+ <Arg name="idleTimeout" type="int"><Property name="threads.timeout" default="60000"/></Arg>
<Set name="detailedDump">false</Set>
</New>
</Arg>
@@ -124,7 +124,7 @@
<!-- =========================================================== -->
<Set name="stopAtShutdown">true</Set>
<Set name="stopTimeout">5000</Set>
- <Set name="dumpAfterStart">false</Set>
- <Set name="dumpBeforeStop">false</Set>
+ <Set name="dumpAfterStart"><Property name="jetty.dump.start" default="false"/></Set>
+ <Set name="dumpBeforeStop"><Property name="jetty.dump.stop" default="false"/></Set>
</Configure>
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextEvent.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextEvent.java
new file mode 100644
index 0000000..11d334d
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextEvent.java
@@ -0,0 +1,153 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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 javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+public class AsyncContextEvent extends AsyncEvent
+{
+ final private Context _context;
+ final private AsyncContextState _asyncContext;
+ volatile HttpChannelState _state;
+ private ServletContext _dispatchContext;
+ private String _pathInContext;
+ private Scheduler.Task _timeoutTask;
+ private Throwable _throwable;
+
+ public AsyncContextEvent(Context context,AsyncContextState asyncContext, HttpChannelState state, Request baseRequest, ServletRequest request, ServletResponse response)
+ {
+ super(null,request,response,null);
+ _context=context;
+ _asyncContext=asyncContext;
+ _state=state;
+
+ // If we haven't been async dispatched before
+ if (baseRequest.getAttribute(AsyncContext.ASYNC_REQUEST_URI)==null)
+ {
+ // We are setting these attributes during startAsync, when the spec implies that
+ // they are only available after a call to AsyncContext.dispatch(...);
+
+ // have we been forwarded before?
+ String uri=(String)baseRequest.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI);
+ if (uri!=null)
+ {
+ baseRequest.setAttribute(AsyncContext.ASYNC_REQUEST_URI,uri);
+ baseRequest.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,baseRequest.getAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH));
+ baseRequest.setAttribute(AsyncContext.ASYNC_SERVLET_PATH,baseRequest.getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH));
+ baseRequest.setAttribute(AsyncContext.ASYNC_PATH_INFO,baseRequest.getAttribute(RequestDispatcher.FORWARD_PATH_INFO));
+ baseRequest.setAttribute(AsyncContext.ASYNC_QUERY_STRING,baseRequest.getAttribute(RequestDispatcher.FORWARD_QUERY_STRING));
+ }
+ else
+ {
+ baseRequest.setAttribute(AsyncContext.ASYNC_REQUEST_URI,baseRequest.getRequestURI());
+ baseRequest.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,baseRequest.getContextPath());
+ baseRequest.setAttribute(AsyncContext.ASYNC_SERVLET_PATH,baseRequest.getServletPath());
+ baseRequest.setAttribute(AsyncContext.ASYNC_PATH_INFO,baseRequest.getPathInfo());
+ baseRequest.setAttribute(AsyncContext.ASYNC_QUERY_STRING,baseRequest.getQueryString());
+ }
+ }
+ }
+
+ public ServletContext getSuspendedContext()
+ {
+ return _context;
+ }
+
+ public Context getContext()
+ {
+ return _context;
+ }
+
+ public ServletContext getDispatchContext()
+ {
+ return _dispatchContext;
+ }
+
+ public ServletContext getServletContext()
+ {
+ return _dispatchContext==null?_context:_dispatchContext;
+ }
+
+ /**
+ * @return The path in the context
+ */
+ public String getPath()
+ {
+ return _pathInContext;
+ }
+
+ public void setTimeoutTask(Scheduler.Task task)
+ {
+ _timeoutTask = task;
+ }
+
+ public void cancelTimeoutTask()
+ {
+ Scheduler.Task task=_timeoutTask;
+ _timeoutTask=null;
+ if (task!=null)
+ task.cancel();
+ }
+
+ @Override
+ public AsyncContext getAsyncContext()
+ {
+ return _asyncContext;
+ }
+
+ @Override
+ public Throwable getThrowable()
+ {
+ return _throwable;
+ }
+
+ public void setThrowable(Throwable throwable)
+ {
+ _throwable=throwable;
+ }
+
+ public void setDispatchTarget(ServletContext context, String path)
+ {
+ if (context!=null)
+ _dispatchContext=context;
+ if (path!=null)
+ _pathInContext=path;
+ }
+
+
+ public void completed()
+ {
+ _asyncContext.reset();
+ }
+
+ public HttpChannelState getHttpChannelState()
+ {
+ return _state;
+ }
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextState.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextState.java
new file mode 100644
index 0000000..1688ed6
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextState.java
@@ -0,0 +1,180 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+
+public class AsyncContextState implements AsyncContext
+{
+ volatile HttpChannelState _state;
+
+ public AsyncContextState(HttpChannelState state)
+ {
+ _state=state;
+ }
+
+ private HttpChannelState state()
+ {
+ HttpChannelState state=_state;
+ if (state==null)
+ throw new IllegalStateException("AsyncContext completed");
+ return state;
+ }
+
+ @Override
+ public void addListener(final AsyncListener listener, final ServletRequest request, final ServletResponse response)
+ {
+ AsyncListener wrap = new AsyncListener()
+ {
+ @Override
+ public void onTimeout(AsyncEvent event) throws IOException
+ {
+ listener.onTimeout(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
+ }
+
+ @Override
+ public void onStartAsync(AsyncEvent event) throws IOException
+ {
+ listener.onStartAsync(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException
+ {
+ listener.onComplete(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
+ }
+
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException
+ {
+ listener.onComplete(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
+ }
+ };
+ state().addListener(wrap);
+ }
+
+ @Override
+ public void addListener(AsyncListener listener)
+ {
+ state().addListener(listener);
+ }
+
+ @Override
+ public void complete()
+ {
+ state().complete();
+ }
+
+ @Override
+ public <T extends AsyncListener> T createListener(Class<T> clazz) throws ServletException
+ {
+ try
+ {
+ return clazz.newInstance();
+ }
+ catch(Exception e)
+ {
+ throw new ServletException(e);
+ }
+ }
+
+ @Override
+ public void dispatch()
+ {
+ state().dispatch(null,null);
+ }
+
+ @Override
+ public void dispatch(String path)
+ {
+ state().dispatch(null,path);
+ }
+
+ @Override
+ public void dispatch(ServletContext context, String path)
+ {
+ state().dispatch(context,path);
+ }
+
+ @Override
+ public ServletRequest getRequest()
+ {
+ return state().getAsyncContextEvent().getSuppliedRequest();
+ }
+
+ @Override
+ public ServletResponse getResponse()
+ {
+ return state().getAsyncContextEvent().getSuppliedResponse();
+ }
+
+ @Override
+ public long getTimeout()
+ {
+ return state().getTimeout();
+ }
+
+ @Override
+ public boolean hasOriginalRequestAndResponse()
+ {
+ HttpChannel<?> channel=state().getHttpChannel();
+ return channel.getRequest()==getRequest() && channel.getResponse()==getResponse();
+ }
+
+ @Override
+ public void setTimeout(long arg0)
+ {
+ state().setTimeout(arg0);
+ }
+
+ @Override
+ public void start(final Runnable task)
+ {
+ state().getHttpChannel().execute(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ state().getAsyncContextEvent().getContext().getContextHandler().handle(task);
+ }
+ });
+ }
+
+ public void reset()
+ {
+ _state=null;
+ }
+
+ public HttpChannelState getHttpChannelState()
+ {
+ return state();
+ }
+
+
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ConnectorStatistics.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ConnectorStatistics.java
index 5fb942e..56bb66d 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ConnectorStatistics.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ConnectorStatistics.java
@@ -23,14 +23,22 @@
import java.util.concurrent.atomic.AtomicLong;
import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.annotation.ManagedOperation;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.Container;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.statistic.CounterStatistic;
import org.eclipse.jetty.util.statistic.SampleStatistic;
+
+/* ------------------------------------------------------------ */
+/** A Connector.Listener that gathers Connector and Connections Statistics.
+ * Adding an instance of this class as with {@link AbstractConnector#addBean(Object)}
+ * will register the listener with all connections accepted by that connector.
+ */
@ManagedObject("Connector Statistics")
public class ConnectorStatistics extends AbstractLifeCycle implements Dumpable, Connection.Listener
{
@@ -52,78 +60,93 @@
connectionClosed(System.currentTimeMillis()-connection.getCreatedTimeStamp(),connection.getMessagesIn(),connection.getMessagesOut());
}
+ @ManagedAttribute("Total number of bytes received by this connector")
public int getBytesIn()
{
// TODO
return -1;
}
+ @ManagedAttribute("Total number of bytes sent by this connector")
public int getBytesOut()
{
// TODO
return -1;
}
+ @ManagedAttribute("Total number of connections seen by this connector")
public int getConnections()
{
return (int)_connectionStats.getTotal();
}
+ @ManagedAttribute("Connection duraton maximum in ms")
public long getConnectionsDurationMax()
{
return _connectionDurationStats.getMax();
}
+ @ManagedAttribute("Connection duraton mean in ms")
public double getConnectionsDurationMean()
{
return _connectionDurationStats.getMean();
}
+ @ManagedAttribute("Connection duraton standard deviation")
public double getConnectionsDurationStdDev()
{
return _connectionDurationStats.getStdDev();
}
+ @ManagedAttribute("Connection duraton total of all connections in ms")
public long getConnectionsDurationTotal()
{
return _connectionDurationStats.getTotal();
}
- public int getConnectionsMessagesInMax()
- {
- return (int)_messagesIn.getMax();
- }
-
- public double getConnectionsMessagesInMean()
- {
- return _messagesIn.getMean();
- }
-
- public double getConnectionsMessagesInStdDev()
- {
- return _messagesIn.getStdDev();
- }
-
- public int getConnectionsOpen()
- {
- return (int)_connectionStats.getCurrent();
- }
-
- public int getConnectionsOpenMax()
- {
- return (int)_connectionStats.getMax();
- }
-
+ @ManagedAttribute("Messages In for all connections")
public int getMessagesIn()
{
return (int)_messagesIn.getTotal();
}
+ @ManagedAttribute("Messages In per connection maximum")
+ public int getConnectionsMessagesInMax()
+ {
+ return (int)_messagesIn.getMax();
+ }
+
+ @ManagedAttribute("Messages In per connection mean")
+ public double getConnectionsMessagesInMean()
+ {
+ return _messagesIn.getMean();
+ }
+
+ @ManagedAttribute("Messages In per connection standard deviation")
+ public double getConnectionsMessagesInStdDev()
+ {
+ return _messagesIn.getStdDev();
+ }
+
+ @ManagedAttribute("Connections open")
+ public int getConnectionsOpen()
+ {
+ return (int)_connectionStats.getCurrent();
+ }
+
+ @ManagedAttribute("Connections open maximum")
+ public int getConnectionsOpenMax()
+ {
+ return (int)_connectionStats.getMax();
+ }
+
+ @ManagedAttribute("Messages Out for all connections")
public int getMessagesOut()
{
return (int)_messagesIn.getTotal();
}
+ @ManagedAttribute("Connection statistics started ms since epoch")
public long getStartedMillis()
{
long start = _startMillis.get();
@@ -141,6 +164,7 @@
{
}
+ @ManagedOperation("Reset the statistics")
public void reset()
{
_startMillis.set(System.currentTimeMillis());
@@ -178,7 +202,6 @@
}
}
-
@Override
@ManagedOperation("dump thread state")
public String dump()
@@ -192,4 +215,13 @@
ContainerLifeCycle.dumpObject(out,this);
ContainerLifeCycle.dump(out,indent,Arrays.asList(new String[]{"connections="+_connectionStats,"duration="+_connectionDurationStats,"in="+_messagesIn,"out="+_messagesOut}));
}
+
+ public static void addToAllConnectors(Server server)
+ {
+ for (Connector connector : server.getConnectors())
+ {
+ if (connector instanceof Container)
+ ((Container)connector).addBean(new ConnectorStatistics());
+ }
+ }
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java
index 9fbbde7..5d82b78 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java
@@ -40,8 +40,10 @@
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.ChannelEndPoint;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.server.HttpChannelState.Next;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
@@ -216,6 +218,15 @@
@Override
public void run()
{
+ handle();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if the channel is ready to continue handling (ie it is not suspended)
+ */
+ public boolean handle()
+ {
LOG.debug("{} handle enter", this);
setCurrentHttpChannel(this);
@@ -227,104 +238,104 @@
Thread.currentThread().setName(threadName + " - " + _uri);
}
- try
+ // Loop here to handle async request redispatches.
+ // The loop is controlled by the call to async.unhandle in the
+ // finally block below. Unhandle will return false only if an async dispatch has
+ // already happened when unhandle is called.
+ HttpChannelState.Next next = _state.handling();
+ while (next==Next.CONTINUE && getServer().isRunning())
{
- // Loop here to handle async request redispatches.
- // The loop is controlled by the call to async.unhandle in the
- // finally block below. Unhandle will return false only if an async dispatch has
- // already happened when unhandle is called.
- boolean handling = _state.handling();
-
- while (handling && getServer().isRunning())
+ try
{
- try
- {
- _request.setHandled(false);
- _response.getHttpOutput().reopen();
+ _request.setHandled(false);
+ _response.getHttpOutput().reopen();
- if (_state.isInitial())
+ if (_state.isInitial())
+ {
+ _request.setTimeStamp(System.currentTimeMillis());
+ _request.setDispatcherType(DispatcherType.REQUEST);
+
+ for (HttpConfiguration.Customizer customizer : _configuration.getCustomizers())
+ customizer.customize(getConnector(),_configuration,_request);
+ getServer().handle(this);
+ }
+ else
+ {
+ if (_request.getHttpChannelState().isExpired())
{
- _request.setTimeStamp(System.currentTimeMillis());
- _request.setDispatcherType(DispatcherType.REQUEST);
-
- for (HttpConfiguration.Customizer customizer : _configuration.getCustomizers())
- customizer.customize(getConnector(),_configuration,_request);
- getServer().handle(this);
+ _request.setDispatcherType(DispatcherType.ERROR);
+ _request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,new Integer(500));
+ _request.setAttribute(RequestDispatcher.ERROR_MESSAGE,"Async Timeout");
+ _request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI,_request.getRequestURI());
+ _response.setStatusWithReason(500,"Async Timeout");
}
else
- {
- if (_request.getHttpChannelState().isExpired())
- {
- _request.setDispatcherType(DispatcherType.ERROR);
- _request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,new Integer(500));
- _request.setAttribute(RequestDispatcher.ERROR_MESSAGE,"Async Timeout");
- _request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI,_request.getRequestURI());
- _response.setStatusWithReason(500,"Async Timeout");
- }
- else
- _request.setDispatcherType(DispatcherType.ASYNC);
- getServer().handleAsync(this);
- }
- }
- catch (Error e)
- {
- if ("ContinuationThrowable".equals(e.getClass().getSimpleName()))
- LOG.ignore(e);
- else
- throw e;
- }
- catch (Exception e)
- {
- if (e instanceof EofException)
- LOG.debug(e);
- else
- LOG.warn(String.valueOf(_uri), e);
- _state.error(e);
- _request.setHandled(true);
- handleException(e);
- }
- finally
- {
- handling = !_state.unhandle();
+ _request.setDispatcherType(DispatcherType.ASYNC);
+ getServer().handleAsync(this);
}
}
- }
- finally
- {
- if (threadName != null && LOG.isDebugEnabled())
- Thread.currentThread().setName(threadName);
- setCurrentHttpChannel(null);
-
- if (_state.isCompleting())
+ catch (Error e)
{
- try
- {
- _state.completed();
-
- if (!_response.isCommitted() && !_request.isHandled())
- _response.sendError(404);
-
- // Complete generating the response
- _response.complete();
-
- }
- catch(EofException e)
- {
+ if ("ContinuationThrowable".equals(e.getClass().getSimpleName()))
+ LOG.ignore(e);
+ else
+ throw e;
+ }
+ catch (Exception e)
+ {
+ if (e instanceof EofException)
LOG.debug(e);
- }
- catch(Exception e)
- {
- LOG.warn(e);
- }
- finally
- {
- _request.setHandled(true);
- _transport.completed();
- }
+ else
+ LOG.warn(String.valueOf(_uri), e);
+ _state.error(e);
+ _request.setHandled(true);
+ handleException(e);
}
-
- LOG.debug("{} handle exit", this);
+ finally
+ {
+ next = _state.unhandle();
+ }
}
+
+ if (threadName != null && LOG.isDebugEnabled())
+ Thread.currentThread().setName(threadName);
+ setCurrentHttpChannel(null);
+
+ if (next==Next.COMPLETE)
+ {
+ try
+ {
+ _state.completed();
+
+ if (!_response.isCommitted() && !_request.isHandled())
+ _response.sendError(404);
+
+ // Complete generating the response
+ _response.complete();
+ }
+ catch(EofException e)
+ {
+ LOG.debug(e);
+ }
+ catch(Exception e)
+ {
+ LOG.warn(e);
+ }
+ finally
+ {
+ next=Next.RECYCLE;
+ }
+ }
+
+ if (next==Next.RECYCLE)
+ {
+ _request.setHandled(true);
+ _transport.completed();
+ }
+
+ LOG.debug("{} handle exit", this);
+
+ return next!=Next.WAIT;
}
/**
@@ -380,11 +391,13 @@
@Override
public String toString()
{
- return String.format("%s@%x{r=%s,a=%s}",
+ return String.format("%s@%x{r=%s,a=%s,uri=%s}",
getClass().getSimpleName(),
hashCode(),
_requests,
- _state.getState());
+ _state.getState(),
+ _state.getState()==HttpChannelState.State.IDLE?"-":_request.getRequestURI()
+ );
}
@Override
@@ -555,10 +568,9 @@
}
@Override
- public boolean earlyEOF()
+ public void earlyEOF()
{
_request.getHttpInput().earlyEOF();
- return false;
}
@Override
@@ -569,10 +581,9 @@
try
{
- if (_state.handling())
+ if (_state.handling()==Next.CONTINUE)
{
commitResponse(new ResponseInfo(HttpVersion.HTTP_1_1,new HttpFields(),0,status,reason,false),null,true);
- _state.unhandle();
}
}
catch (IOException e)
@@ -580,8 +591,9 @@
LOG.warn(e);
}
finally
- {
- _state.completed();
+ {
+ if (_state.unhandle()==Next.COMPLETE)
+ _state.completed();
}
}
@@ -664,4 +676,13 @@
{
return _connector.getScheduler();
}
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if the HttpChannel can efficiently use direct buffer (typically this means it is not over SSL or a multiplexed protocol)
+ */
+ public boolean useDirectBuffers()
+ {
+ return getEndPoint() instanceof ChannelEndPoint;
+ }
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java
index e50f77a..b61f1cb 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java
@@ -21,19 +21,15 @@
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
-import javax.servlet.AsyncContext;
+
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandler.Context;
-import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.Scheduler;
@@ -56,7 +52,7 @@
* <tr><th align=right>COMPLETED:</th> <td></td> <td></td> <td></td> <td></td> <td></td> <td></td></tr>
* </table>
*/
-public class HttpChannelState implements AsyncContext
+public class HttpChannelState
{
private static final Logger LOG = Log.getLogger(HttpChannelState.class);
@@ -75,6 +71,14 @@
COMPLETING, // Request is completable
COMPLETED // Request is complete
}
+
+ public enum Next
+ {
+ CONTINUE, // Continue handling the channel
+ WAIT, // Wait for further events
+ COMPLETE, // Complete the channel
+ RECYCLE, // Channel is completed
+ }
private final HttpChannel<?> _channel;
private List<AsyncListener> _lastAsyncListeners;
@@ -86,7 +90,7 @@
private boolean _expired;
private volatile boolean _responseWrapped;
private long _timeoutMs=DEFAULT_TIMEOUT;
- private AsyncEventState _event;
+ private AsyncContextEvent _event;
protected HttpChannelState(HttpChannel<?> channel)
{
@@ -103,7 +107,6 @@
}
}
- @Override
public void addListener(AsyncListener listener)
{
synchronized(this)
@@ -114,19 +117,6 @@
}
}
- @Override
- public void addListener(AsyncListener listener,ServletRequest request, ServletResponse response)
- {
- synchronized(this)
- {
- if (_asyncListeners==null)
- _asyncListeners=new ArrayList<>();
- _asyncListeners.add(listener);
- }
- }
-
-
- @Override
public void setTimeout(long ms)
{
synchronized(this)
@@ -135,7 +125,6 @@
}
}
- @Override
public long getTimeout()
{
synchronized(this)
@@ -144,7 +133,7 @@
}
}
- public AsyncEventState getAsyncEventState()
+ public AsyncContextEvent getAsyncContextEvent()
{
synchronized(this)
{
@@ -173,9 +162,9 @@
}
/**
- * @return true if the handling of the request should proceed
+ * @return Next handling of the request should proceed
*/
- protected boolean handling()
+ protected Next handling()
{
synchronized (this)
{
@@ -197,12 +186,16 @@
case COMPLETECALLED:
_state=State.COMPLETING;
- return false;
+ return Next.COMPLETE;
- case ASYNCWAIT:
case COMPLETING:
+ return Next.COMPLETE;
+
+ case ASYNCWAIT:
+ return Next.WAIT;
+
case COMPLETED:
- return false;
+ return Next.RECYCLE;
case REDISPATCH:
_state=State.REDISPATCHED;
@@ -213,12 +206,13 @@
}
_responseWrapped=false;
- return true;
+ return Next.CONTINUE;
}
}
- public void startAsync()
+
+ public void startAsync(AsyncContextEvent event)
{
synchronized (this)
{
@@ -228,56 +222,15 @@
case REDISPATCHED:
_dispatched=false;
_expired=false;
+ _responseWrapped=event.getSuppliedResponse()!=_channel.getResponse();
_responseWrapped=false;
- _event=new AsyncEventState(_channel.getRequest().getServletContext(),_channel.getRequest(),_channel.getResponse());
+ _event=event;
_state=State.ASYNCSTARTED;
List<AsyncListener> listeners=_lastAsyncListeners;
_lastAsyncListeners=_asyncListeners;
+ if (listeners!=null)
+ listeners.clear();
_asyncListeners=listeners;
- if (_asyncListeners!=null)
- _asyncListeners.clear();
- break;
-
- default:
- throw new IllegalStateException(this.getStatusString());
- }
- }
-
- if (_lastAsyncListeners!=null)
- {
- for (AsyncListener listener : _lastAsyncListeners)
- {
- try
- {
- listener.onStartAsync(_event);
- }
- catch(Exception e)
- {
- LOG.warn(e);
- }
- }
- }
- }
-
- public void startAsync(final ServletContext context,final ServletRequest request,final ServletResponse response)
- {
- synchronized (this)
- {
- switch(_state)
- {
- case DISPATCHED:
- case REDISPATCHED:
- _dispatched=false;
- _expired=false;
- _responseWrapped=response!=_channel.getResponse();
- _event=new AsyncEventState(context,request,response);
- _event._pathInContext = (request instanceof HttpServletRequest)?URIUtil.addPaths(((HttpServletRequest)request).getServletPath(),((HttpServletRequest)request).getPathInfo()):null;
- _state=State.ASYNCSTARTED;
- List<AsyncListener> listeners=_lastAsyncListeners;
- _lastAsyncListeners=_asyncListeners;
- _asyncListeners=listeners;
- if (_asyncListeners!=null)
- _asyncListeners.clear();
break;
default:
@@ -306,7 +259,7 @@
synchronized (this)
{
if (_event!=null)
- _event._cause=th;
+ _event.setThrowable(th);
}
}
@@ -314,10 +267,10 @@
* Signal that the HttpConnection has finished handling the request.
* For blocking connectors, this call may block if the request has
* been suspended (startAsync called).
- * @return true if handling is complete, false if the request should
+ * @return next actions
* be handled again (eg because of a resume that happened before unhandle was called)
*/
- protected boolean unhandle()
+ protected Next unhandle()
{
synchronized (this)
{
@@ -326,7 +279,7 @@
case REDISPATCHED:
case DISPATCHED:
_state=State.COMPLETING;
- return true;
+ return Next.COMPLETE;
case IDLE:
throw new IllegalStateException(this.getStatusString());
@@ -335,26 +288,17 @@
_initial=false;
_state=State.ASYNCWAIT;
scheduleTimeout();
- if (_state==State.ASYNCWAIT)
- return true;
- else if (_state==State.COMPLETECALLED)
- {
- _state=State.COMPLETING;
- return true;
- }
- _initial=false;
- _state=State.REDISPATCHED;
- return false;
+ return Next.WAIT;
case REDISPATCHING:
_initial=false;
_state=State.REDISPATCHED;
- return false;
+ return Next.CONTINUE;
case COMPLETECALLED:
_initial=false;
_state=State.COMPLETING;
- return true;
+ return Next.COMPLETE;
default:
throw new IllegalStateException(this.getStatusString());
@@ -362,26 +306,29 @@
}
}
- @Override
- public void dispatch()
+ public void dispatch(ServletContext context, String path)
{
boolean dispatch;
synchronized (this)
{
+
switch(_state)
{
case ASYNCSTARTED:
_state=State.REDISPATCHING;
+ _event.setDispatchTarget(context,path);
_dispatched=true;
return;
case ASYNCWAIT:
dispatch=!_expired;
_state=State.REDISPATCH;
+ _event.setDispatchTarget(context,path);
_dispatched=true;
break;
case REDISPATCH:
+ _event.setDispatchTarget(context,path);
return;
default:
@@ -407,6 +354,7 @@
protected void expired()
{
final List<AsyncListener> aListeners;
+ AsyncEvent event;
synchronized (this)
{
switch(_state)
@@ -414,6 +362,7 @@
case ASYNCSTARTED:
case ASYNCWAIT:
_expired=true;
+ event=_event;
aListeners=_asyncListeners;
break;
default:
@@ -427,7 +376,7 @@
{
try
{
- listener.onTimeout(_event);
+ listener.onTimeout(event);
}
catch(Exception e)
{
@@ -453,11 +402,10 @@
scheduleDispatch();
}
- @Override
public void complete()
{
// just like resume, except don't set _dispatched=true;
- boolean dispatch;
+ boolean handle;
synchronized (this)
{
switch(_state)
@@ -473,7 +421,7 @@
case ASYNCWAIT:
_state=State.COMPLETECALLED;
- dispatch=!_expired;
+ handle=!_expired;
break;
default:
@@ -481,29 +429,21 @@
}
}
- if (dispatch)
+ if (handle)
{
cancelTimeout();
- scheduleDispatch();
- }
- }
-
- @Override
- public <T extends AsyncListener> T createListener(Class<T> clazz) throws ServletException
- {
- try
- {
- return clazz.newInstance();
- }
- catch(Exception e)
- {
- throw new ServletException(e);
+ ContextHandler handler=getContextHandler();
+ if (handler!=null)
+ handler.handle(_channel);
+ else
+ _channel.handle();
}
}
protected void completed()
{
final List<AsyncListener> aListeners;
+ final AsyncContextEvent event;
synchronized (this)
{
switch(_state)
@@ -511,6 +451,8 @@
case COMPLETING:
_state=State.COMPLETED;
aListeners=_asyncListeners;
+ event=_event;
+ _event=null;
break;
default:
@@ -524,14 +466,14 @@
{
try
{
- if (_event!=null && _event._cause!=null)
+ if (event!=null && event.getThrowable()!=null)
{
- _event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,_event._cause);
- _event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_MESSAGE,_event._cause.getMessage());
- listener.onError(_event);
+ event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,event.getThrowable());
+ event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_MESSAGE,event.getThrowable().getMessage());
+ listener.onError(event);
}
else
- listener.onComplete(_event);
+ listener.onComplete(event);
}
catch(Exception e)
{
@@ -539,6 +481,9 @@
}
}
}
+
+ if (event!=null)
+ event.completed();
}
protected void recycle()
@@ -563,14 +508,6 @@
}
}
- public void cancel()
- {
- synchronized (this)
- {
- cancelTimeout();
- }
- }
-
protected void scheduleDispatch()
{
_channel.execute(_channel);
@@ -580,18 +517,14 @@
{
Scheduler scheduler = _channel.getScheduler();
if (scheduler!=null && _timeoutMs>0)
- _event._timeout=scheduler.schedule(new AsyncTimeout(),_timeoutMs,TimeUnit.MILLISECONDS);
+ _event.setTimeoutTask(scheduler.schedule(new AsyncTimeout(),_timeoutMs,TimeUnit.MILLISECONDS));
}
protected void cancelTimeout()
{
- AsyncEventState event=_event;
+ AsyncContextEvent event=_event;
if (event!=null)
- {
- Scheduler.Task task=event._timeout;
- if (task!=null)
- task.cancel();
- }
+ event.cancelTimeoutTask();
}
public boolean isExpired()
@@ -656,73 +589,25 @@
}
}
- @Override
- public void dispatch(ServletContext context, String path)
- {
- _event._dispatchContext=context;
- _event._pathInContext=path;
- dispatch();
- }
-
- @Override
- public void dispatch(String path)
- {
- _event._pathInContext=path;
- dispatch();
- }
-
public Request getBaseRequest()
{
return _channel.getRequest();
}
- @Override
- public ServletRequest getRequest()
+ public HttpChannel<?> getHttpChannel()
{
- if (_event!=null)
- return _event.getSuppliedRequest();
- return _channel.getRequest();
- }
-
- @Override
- public ServletResponse getResponse()
- {
- if (_responseWrapped && _event!=null && _event.getSuppliedResponse()!=null)
- return _event.getSuppliedResponse();
- return _channel.getResponse();
- }
-
- @Override
- public void start(final Runnable run)
- {
- final AsyncEventState event=_event;
- if (event!=null)
- {
- _channel.execute(new Runnable()
- {
- @Override
- public void run()
- {
- ((Context)event.getServletContext()).getContextHandler().handle(run);
- }
- });
- }
- }
-
- @Override
- public boolean hasOriginalRequestAndResponse()
- {
- synchronized (this)
- {
- return (_event!=null && _event.getSuppliedRequest()==_channel.getRequest() && _event.getSuppliedResponse()==_channel.getResponse());
- }
+ return _channel;
}
public ContextHandler getContextHandler()
{
- final AsyncEventState event=_event;
+ final AsyncContextEvent event=_event;
if (event!=null)
- return ((Context)event.getServletContext()).getContextHandler();
+ {
+ Context context=((Context)event.getServletContext());
+ if (context!=null)
+ return context.getContextHandler();
+ }
return null;
}
@@ -757,70 +642,4 @@
}
}
- public class AsyncEventState extends AsyncEvent
- {
- final private ServletContext _suspendedContext;
- private String _pathInContext;
- private Scheduler.Task _timeout;
- private ServletContext _dispatchContext;
- private Throwable _cause;
-
- public AsyncEventState(ServletContext context, ServletRequest request, ServletResponse response)
- {
- super(HttpChannelState.this, request,response);
- _suspendedContext=context;
-
- // Get the base request So we can remember the initial paths
- Request r=_channel.getRequest();
-
- // If we haven't been async dispatched before
- if (r.getAttribute(AsyncContext.ASYNC_REQUEST_URI)==null)
- {
- // We are setting these attributes during startAsync, when the spec implies that
- // they are only available after a call to AsyncContext.dispatch(...);
-
- // have we been forwarded before?
- String uri=(String)r.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI);
- if (uri!=null)
- {
- r.setAttribute(AsyncContext.ASYNC_REQUEST_URI,uri);
- r.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,r.getAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH));
- r.setAttribute(AsyncContext.ASYNC_SERVLET_PATH,r.getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH));
- r.setAttribute(AsyncContext.ASYNC_PATH_INFO,r.getAttribute(RequestDispatcher.FORWARD_PATH_INFO));
- r.setAttribute(AsyncContext.ASYNC_QUERY_STRING,r.getAttribute(RequestDispatcher.FORWARD_QUERY_STRING));
- }
- else
- {
- r.setAttribute(AsyncContext.ASYNC_REQUEST_URI,r.getRequestURI());
- r.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,r.getContextPath());
- r.setAttribute(AsyncContext.ASYNC_SERVLET_PATH,r.getServletPath());
- r.setAttribute(AsyncContext.ASYNC_PATH_INFO,r.getPathInfo());
- r.setAttribute(AsyncContext.ASYNC_QUERY_STRING,r.getQueryString());
- }
- }
- }
-
- public ServletContext getSuspendedContext()
- {
- return _suspendedContext;
- }
-
- public ServletContext getDispatchContext()
- {
- return _dispatchContext;
- }
-
- public ServletContext getServletContext()
- {
- return _dispatchContext==null?_suspendedContext:_dispatchContext;
- }
-
- /**
- * @return The path in the context
- */
- public String getPath()
- {
- return _pathInContext;
- }
- }
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
index 69433f5..7029fd4 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
@@ -207,8 +207,28 @@
// Can the parser progress (even with an empty buffer)
boolean call_channel=_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer);
- // If there is a request buffer, we are re-entering here
- if (!call_channel && BufferUtil.isEmpty(_requestBuffer))
+ // Parse the buffer
+ if (call_channel)
+ {
+ // Parse as much content as there is available before calling the channel
+ // this is both efficient (may queue many chunks), will correctly set available for 100 continues
+ // and will drive the parser to completion if all content is available.
+ while (_parser.inContentState())
+ {
+ if (!_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer))
+ break;
+ }
+
+ // The parser returned true, which indicates the channel is ready to handle a request.
+ // Call the channel and this will either handle the request/response to completion OR,
+ // if the request suspends, the request/response will be incomplete so the outer loop will exit.
+ boolean handle=_channel.handle();
+
+ // Return if suspended or upgraded
+ if (!handle || getEndPoint().getConnection()!=this)
+ return;
+ }
+ else if (BufferUtil.isEmpty(_requestBuffer))
{
if (_requestBuffer == null)
_requestBuffer = _bufferPool.acquire(getInputBufferSize(), REQUEST_BUFFER_DIRECT);
@@ -242,32 +262,14 @@
releaseRequestBuffer();
return;
}
-
- // Parse what we have read
- call_channel=_parser.parseNext(_requestBuffer);
}
-
- // Parse the buffer
- if (call_channel)
+ else
{
- // Parse as much content as there is available before calling the channel
- // this is both efficient (may queue many chunks), will correctly set available for 100 continues
- // and will drive the parser to completion if all content is available.
- while (_parser.inContentState())
- {
- if (!_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer))
- break;
- }
-
- // The parser returned true, which indicates the channel is ready to handle a request.
- // Call the channel and this will either handle the request/response to completion OR,
- // if the request suspends, the request/response will be incomplete so the outer loop will exit.
-
- _channel.run();
-
- // Return if suspended or upgraded
- if (_channel.getState().isSuspended() || getEndPoint().getConnection()!=this)
- return;
+ // TODO work out how we can get here and a better way to handle it
+ LOG.warn("Unexpected state: "+this+ " "+_channel+" "+_channel.getRequest());
+ if (!_channel.getState().isSuspended())
+ getEndPoint().close();
+ return;
}
}
}
@@ -275,7 +277,7 @@
{
LOG.debug(e);
}
- catch (IOException e)
+ catch (Exception e)
{
if (_parser.isIdle())
LOG.debug(e);
@@ -283,11 +285,6 @@
LOG.warn(this.toString(), e);
close();
}
- catch (Exception e)
- {
- LOG.warn(this.toString(), e);
- close();
- }
finally
{
setCurrentConnection(null);
@@ -511,13 +508,6 @@
getEndPoint().close();
}
}
-
- // make sure that an oshut connection is driven towards close
- // TODO this is a little ugly
- if (getEndPoint().isOpen() && getEndPoint().isOutputShutdown())
- {
- fillInterested();
- }
}
}
@@ -559,11 +549,12 @@
if (getEndPoint().isInputShutdown())
{
_parser.shutdownInput();
+ shutdown();
return;
}
// Wait until we can read
- getEndPoint().fillInterested(_readBlocker);
+ block(_readBlocker);
LOG.debug("{} block readable on {}",this,_readBlocker);
_readBlocker.block();
@@ -618,7 +609,6 @@
*/
}
-
@Override
protected void onAllContentConsumed()
{
@@ -628,6 +618,12 @@
*/
releaseRequestBuffer();
}
+
+ @Override
+ public String toString()
+ {
+ return super.toString()+"{"+_channel+","+HttpConnection.this+"}";
+ }
}
private class HttpChannelOverHttp extends HttpChannel<ByteBuffer>
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java
index d5db837..f166e82 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java
@@ -96,32 +96,53 @@
T item = null;
synchronized (lock())
{
- while (item == null)
+ // Get the current head of the input Q
+ item = _inputQ.peekUnsafe();
+
+ // Skip empty items at the head of the queue
+ while (item != null && remaining(item) == 0)
{
+ _inputQ.pollUnsafe();
+ onContentConsumed(item);
+ LOG.debug("{} consumed {}", this, item);
item = _inputQ.peekUnsafe();
- while (item != null && remaining(item) == 0)
+
+ // If that was the last item then notify
+ if (item==null)
+ onAllContentConsumed();
+ }
+
+ // If we have no item
+ if (item == null)
+ {
+ // Was it unexpectedly EOF'd?
+ if (isEarlyEOF())
+ throw new EofException();
+
+ // check for EOF
+ if (isShutdown())
{
- _inputQ.pollUnsafe();
- onContentConsumed(item);
- LOG.debug("{} consumed {}", this, item);
- item = _inputQ.peekUnsafe();
+ onEOF();
+ return -1;
}
- if (item == null)
+ // OK then block for some more content
+ blockForContent();
+
+ // If still not content, we must be closed
+ item = _inputQ.peekUnsafe();
+ if (item==null)
{
- onAllContentConsumed();
-
if (isEarlyEOF())
throw new EofException();
+
+ // blockForContent will only return with no
+ // content if it is closed.
+ if (!isShutdown())
+ LOG.warn("Unexpected !EOF: "+this);
- // check for EOF
- if (isShutdown())
- {
- onEOF();
- return -1;
- }
-
- blockForContent();
+ onEOF();
+ return -1;
}
}
}
@@ -152,20 +173,34 @@
}
}
+ /* ------------------------------------------------------------ */
+ /** Called by this HttpInput to signal new content has been queued
+ * @param item
+ */
protected void onContentQueued(T item)
{
lock().notify();
}
+ /* ------------------------------------------------------------ */
+ /** Called by this HttpInput to signal all available content has been consumed
+ */
protected void onAllContentConsumed()
{
}
+ /* ------------------------------------------------------------ */
+ /** Called by this HttpInput to signal it has reached EOF
+ */
protected void onEOF()
{
}
- public boolean content(T item)
+ /* ------------------------------------------------------------ */
+ /** Add some content to the input stream
+ * @param item
+ */
+ public void content(T item)
{
synchronized (lock())
{
@@ -176,19 +211,26 @@
onContentQueued(item);
LOG.debug("{} queued {}", this, item);
}
- return true;
}
+ /* ------------------------------------------------------------ */
+ /** This method should be called to signal to the HttpInput
+ * that an EOF has arrived before all the expected content.
+ * Typically this will result in an EOFException being thrown
+ * from a subsequent read rather than a -1 return.
+ */
public void earlyEOF()
{
synchronized (lock())
{
_earlyEOF = true;
+ _inputEOF = true;
lock().notify();
LOG.debug("{} early EOF", this);
}
}
+ /* ------------------------------------------------------------ */
public boolean isEarlyEOF()
{
synchronized (lock())
@@ -197,6 +239,7 @@
}
}
+ /* ------------------------------------------------------------ */
public void shutdown()
{
synchronized (lock())
@@ -207,6 +250,7 @@
}
}
+ /* ------------------------------------------------------------ */
public boolean isShutdown()
{
synchronized (lock())
@@ -215,13 +259,14 @@
}
}
+ /* ------------------------------------------------------------ */
public void consumeAll()
{
synchronized (lock())
{
+ T item = _inputQ.peekUnsafe();
while (!isShutdown() && !isEarlyEOF())
{
- T item = _inputQ.peekUnsafe();
while (item != null)
{
_inputQ.pollUnsafe();
@@ -235,6 +280,9 @@
try
{
blockForContent();
+ item = _inputQ.peekUnsafe();
+ if (item==null)
+ break;
}
catch (IOException e)
{
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java
index fbe6597..56bc29d 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java
@@ -47,9 +47,6 @@
*/
public class HttpOutput extends ServletOutputStream
{
- private static final boolean OUTPUT_BUFFER_DIRECT=false;
- private static final boolean CHANNEL_BUFFER_DIRECT=true;
- private static final boolean STREAM_BUFFER_DIRECT=false;
private static Logger LOG = Log.getLogger(HttpOutput.class);
private final HttpChannel<?> _channel;
private boolean _closed;
@@ -165,8 +162,9 @@
return;
}
- // Allocate an aggregate buffer
- _aggregate = _channel.getByteBufferPool().acquire(size, OUTPUT_BUFFER_DIRECT);
+ // Allocate an aggregate buffer.
+ // Never direct as it is slow to do little writes to a direct buffer.
+ _aggregate = _channel.getByteBufferPool().acquire(size, false);
}
// Do we have space to aggregate ?
@@ -206,8 +204,10 @@
if (isClosed())
throw new EOFException("Closed");
+ // Allocate an aggregate buffer.
+ // Never direct as it is slow to do little writes to a direct buffer.
if (_aggregate == null)
- _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), OUTPUT_BUFFER_DIRECT);
+ _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), false);
BufferUtil.append(_aggregate, (byte)b);
_written++;
@@ -256,7 +256,7 @@
if (etag!=null)
response.getHttpFields().put(HttpHeader.ETAG,etag);
- content = httpContent.getDirectBuffer();
+ content = _channel.useDirectBuffers()?httpContent.getDirectBuffer():null;
if (content == null)
content = httpContent.getIndirectBuffer();
if (content == null)
@@ -268,7 +268,7 @@
{
Resource resource = (Resource)content;
_channel.getResponse().getHttpFields().putDateField(HttpHeader.LAST_MODIFIED, resource.lastModified());
- content = resource.getInputStream();
+ content=resource.getInputStream(); // Closed below
}
// Process content.
@@ -279,9 +279,8 @@
}
else if (content instanceof ReadableByteChannel)
{
- ReadableByteChannel channel = (ReadableByteChannel)content;
- ByteBuffer buffer = _channel.getByteBufferPool().acquire(getBufferSize(), CHANNEL_BUFFER_DIRECT);
- try
+ ByteBuffer buffer = _channel.getByteBufferPool().acquire(getBufferSize(), _channel.useDirectBuffers());
+ try (ReadableByteChannel channel = (ReadableByteChannel)content;)
{
while(channel.isOpen())
{
@@ -301,12 +300,12 @@
}
else if (content instanceof InputStream)
{
- InputStream in = (InputStream)content;
- ByteBuffer buffer = _channel.getByteBufferPool().acquire(getBufferSize(), STREAM_BUFFER_DIRECT);
+ // allocate non direct buffer so array may be directly accessed.
+ ByteBuffer buffer = _channel.getByteBufferPool().acquire(getBufferSize(), false);
byte[] array = buffer.array();
int offset=buffer.arrayOffset();
int size=array.length-offset;
- try
+ try (InputStream in = (InputStream)content;)
{
while(true)
{
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/InclusiveByteRange.java b/jetty-server/src/main/java/org/eclipse/jetty/server/InclusiveByteRange.java
index 7910c6e..ebf0025 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/InclusiveByteRange.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/InclusiveByteRange.java
@@ -81,7 +81,7 @@
* @param size Size of the resource.
* @return LazyList of satisfiable ranges
*/
- public static List satisfiableRanges(Enumeration headers, long size)
+ public static List<InclusiveByteRange> satisfiableRanges(Enumeration headers, long size)
{
Object satRanges=null;
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
index f2518ff..6831dbc 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
@@ -62,6 +62,7 @@
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersion;
@@ -114,9 +115,9 @@
*/
public class Request implements HttpServletRequest
{
- public static final String __MULTIPART_CONFIG_ELEMENT = "org.eclipse.multipartConfig";
- public static final String __MULTIPART_INPUT_STREAM = "org.eclipse.multiPartInputStream";
- public static final String __MULTIPART_CONTEXT = "org.eclipse.multiPartContext";
+ public static final String __MULTIPART_CONFIG_ELEMENT = "org.eclipse.jetty.multipartConfig";
+ public static final String __MULTIPART_INPUT_STREAM = "org.eclipse.jetty.multiPartInputStream";
+ public static final String __MULTIPART_CONTEXT = "org.eclipse.jetty.multiPartContext";
private static final Logger LOG = Log.getLogger(Request.class);
private static final Collection<Locale> __defaultLocale = Collections.singleton(Locale.getDefault());
@@ -203,7 +204,8 @@
private long _dispatchTime;
private HttpURI _uri;
private MultiPartInputStreamParser _multiPartInputStream; //if the request is a multi-part mime
-
+ private AsyncContextState _async;
+
/* ------------------------------------------------------------ */
public Request(HttpChannel<?> channel, HttpInput<?> input)
{
@@ -369,10 +371,11 @@
@Override
public AsyncContext getAsyncContext()
{
- HttpChannelState continuation = getHttpChannelState();
- if (continuation.isInitial() && !continuation.isAsync())
- throw new IllegalStateException(continuation.getStatusString());
- return continuation;
+ HttpChannelState state = getHttpChannelState();
+ if (_async==null || state.isInitial() && !state.isAsync())
+ throw new IllegalStateException(state.getStatusString());
+
+ return _async;
}
/* ------------------------------------------------------------ */
@@ -1027,19 +1030,8 @@
@Override
public StringBuffer getRequestURL()
{
- final StringBuffer url = new StringBuffer(48);
- String scheme = getScheme();
- int port = getServerPort();
-
- url.append(scheme);
- url.append("://");
- url.append(getServerName());
- if (_port > 0 && ((scheme.equalsIgnoreCase(URIUtil.HTTP) && port != 80) || (scheme.equalsIgnoreCase(URIUtil.HTTPS) && port != 443)))
- {
- url.append(':');
- url.append(_port);
- }
-
+ final StringBuffer url = new StringBuffer(128);
+ URIUtil.appendSchemeHostPort(url,getScheme(),getServerName(),getServerPort());
url.append(getRequestURI());
return url;
}
@@ -1063,19 +1055,8 @@
*/
public StringBuilder getRootURL()
{
- StringBuilder url = new StringBuilder(48);
- String scheme = getScheme();
- int port = getServerPort();
-
- url.append(scheme);
- url.append("://");
- url.append(getServerName());
-
- if (port > 0 && ((scheme.equalsIgnoreCase("http") && port != 80) || (scheme.equalsIgnoreCase("https") && port != 443)))
- {
- url.append(':');
- url.append(port);
- }
+ StringBuilder url = new StringBuilder(128);
+ URIUtil.appendSchemeHostPort(url,getScheme(),getServerName(),getServerPort());
return url;
}
@@ -1105,41 +1086,58 @@
// Return host from absolute URI
_serverName = _uri.getHost();
- _port = _uri.getPort();
if (_serverName != null)
+ {
+ _port = _uri.getPort();
return _serverName;
+ }
// Return host from header field
String hostPort = _fields.getStringField(HttpHeader.HOST);
+
+ _port=0;
if (hostPort != null)
{
- loop: for (int i = hostPort.length(); i-- > 0;)
+ int len=hostPort.length();
+ loop: for (int i = len; i-- > 0;)
{
- char ch = (char)(0xff & hostPort.charAt(i));
- switch (ch)
+ char c2 = (char)(0xff & hostPort.charAt(i));
+ switch (c2)
{
case ']':
break loop;
case ':':
- _serverName = hostPort.substring(0,i);
try
{
+ len=i;
_port = StringUtil.toInt(hostPort.substring(i+1));
}
catch (NumberFormatException e)
{
LOG.warn(e);
+ _serverName=hostPort;
+ _port=0;
+ return _serverName;
}
- return _serverName;
+ break loop;
}
}
-
- if (_serverName == null || _port < 0)
+ if (hostPort.charAt(0)=='[')
{
- _serverName = hostPort;
- _port = 0;
+ if (hostPort.charAt(len-1)!=']')
+ {
+ LOG.warn("Bad IPv6 "+hostPort);
+ _serverName=hostPort;
+ _port=0;
+ return _serverName;
+ }
+ _serverName = hostPort.substring(1,len-1);
}
+ else if (len==hostPort.length())
+ _serverName=hostPort;
+ else
+ _serverName = hostPort.substring(0,len);
return _serverName;
}
@@ -1506,6 +1504,9 @@
setAuthentication(Authentication.NOT_CHECKED);
getHttpChannelState().recycle();
+ if (_async!=null)
+ _async.reset();
+ _async=null;
_asyncSupported = true;
_handled = false;
if (_context != null)
@@ -1989,8 +1990,11 @@
if (!_asyncSupported)
throw new IllegalStateException("!asyncSupported");
HttpChannelState state = getHttpChannelState();
- state.startAsync();
- return state;
+ if (_async==null)
+ _async=new AsyncContextState(state);
+ AsyncContextEvent event = new AsyncContextEvent(_context,_async,state,this,this,getResponse());
+ state.startAsync(event);
+ return _async;
}
/* ------------------------------------------------------------ */
@@ -2000,8 +2004,12 @@
if (!_asyncSupported)
throw new IllegalStateException("!asyncSupported");
HttpChannelState state = getHttpChannelState();
- state.startAsync(_context, servletRequest, servletResponse);
- return state;
+ if (_async==null)
+ _async=new AsyncContextState(state);
+ AsyncContextEvent event = new AsyncContextEvent(_context,_async,state,this,servletRequest,servletResponse);
+ event.setDispatchTarget(getServletContext(),URIUtil.addPaths(getServletPath(),getPathInfo()));
+ state.startAsync(event);
+ return _async;
}
/* ------------------------------------------------------------ */
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java
index d800970..6dc7640 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java
@@ -57,8 +57,8 @@
private final ResourceCache _parent;
private final MimeTypes _mimeTypes;
private final boolean _etagSupported;
-
- private boolean _useFileMappedBuffer=true;
+ private final boolean _useFileMappedBuffer;
+
private int _maxCachedFileSize =4*1024*1024;
private int _maxCachedFiles=2048;
private int _maxCacheSize =32*1024*1024;
@@ -143,12 +143,6 @@
}
/* ------------------------------------------------------------ */
- public void setUseFileMappedBuffer(boolean useFileMappedBuffer)
- {
- _useFileMappedBuffer = useFileMappedBuffer;
- }
-
- /* ------------------------------------------------------------ */
public void flushCache()
{
if (_cache!=null)
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java
index d1e90a2..d18366a 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java
@@ -753,6 +753,8 @@
break;
case STREAM:
getOutputStream().close();
+ break;
+ default:
}
return true;
}
@@ -926,6 +928,7 @@
case TE:
_fields.put(HttpHeader.CONNECTION, HttpHeaderValue.TE.toString());
break;
+ default:
}
}
}
@@ -965,6 +968,8 @@
case STREAM:
case WRITER:
_out.reset();
+ break;
+ default:
}
_out.resetBuffer();
@@ -1023,7 +1028,7 @@
return _reason;
}
- public void complete() throws IOException
+ public void complete()
{
_out.close();
}
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 e8e0311..28e6de3 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
@@ -19,11 +19,9 @@
package org.eclipse.jetty.server;
import java.io.IOException;
-import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
-import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -34,22 +32,18 @@
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
-
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import javax.xml.validation.Schema;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpGenerator;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
-import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpURI;
-import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.util.Attributes;
@@ -282,9 +276,9 @@
{
if (getStopAtShutdown())
{
- ShutdownThread.register(this);
+ ShutdownThread.register(this);
}
-
+
ShutdownMonitor.getInstance().start(); // initialize
LOG.info("jetty-"+getVersion());
@@ -318,7 +312,7 @@
if (isDumpAfterStart())
dumpStdErr();
-
+
// use DateCache timer for Date field reformat
final HttpFields.DateGenerator date = new HttpFields.DateGenerator();
long now=System.currentTimeMillis();
@@ -335,8 +329,8 @@
this.cancel();
}
},tick,1000);
-
-
+
+
mex.ifExceptionThrow();
}
@@ -374,7 +368,7 @@
if (stopTimeout>0)
{
long stop_by=System.currentTimeMillis()+stopTimeout;
- LOG.info("Graceful shutdown {} by ",this,new Date(stop_by));
+ LOG.debug("Graceful shutdown {} by ",this,new Date(stop_by));
// Wait for shutdowns
for (Future<Void> future: futures)
@@ -476,16 +470,16 @@
*/
public void handleAsync(HttpChannel<?> connection) throws IOException, ServletException
{
- final HttpChannelState async = connection.getRequest().getHttpChannelState();
- final HttpChannelState.AsyncEventState state = async.getAsyncEventState();
+ final HttpChannelState state = connection.getRequest().getHttpChannelState();
+ final AsyncContextEvent event = state.getAsyncContextEvent();
final Request baseRequest=connection.getRequest();
- final String path=state.getPath();
+ final String path=event.getPath();
if (path!=null)
{
// this is a dispatch with a path
- ServletContext context=state.getServletContext();
+ ServletContext context=event.getServletContext();
HttpURI uri = new HttpURI(context==null?path:URIUtil.addPaths(context.getContextPath(),path));
baseRequest.setUri(uri);
baseRequest.setRequestURI(null);
@@ -495,8 +489,8 @@
}
final String target=baseRequest.getPathInfo();
- final HttpServletRequest request=(HttpServletRequest)async.getRequest();
- final HttpServletResponse response=(HttpServletResponse)async.getResponse();
+ final HttpServletRequest request=(HttpServletRequest)event.getSuppliedRequest();
+ final HttpServletResponse response=(HttpServletResponse)event.getSuppliedResponse();
if (LOG.isDebugEnabled())
{
@@ -509,7 +503,6 @@
}
-
/* ------------------------------------------------------------ */
public void join() throws InterruptedException
{
@@ -607,10 +600,10 @@
break;
}
}
-
+
if (connector==null)
return null;
-
+
ContextHandler context = getChildHandlerByClass(ContextHandler.class);
try
@@ -622,7 +615,7 @@
host=context.getVirtualHosts()[0];
if (host==null)
host=InetAddress.getLocalHost().getHostAddress();
-
+
String path=context==null?null:context.getContextPath();
if (path==null)
path="/";
@@ -634,7 +627,7 @@
return null;
}
}
-
+
/* ------------------------------------------------------------ */
@Override
public String toString()
@@ -647,7 +640,7 @@
{
dumpBeans(out,indent,Collections.singleton(new ClassLoaderDump(this.getClass().getClassLoader())));
}
-
+
/* ------------------------------------------------------------ */
public static void main(String...args) throws Exception
{
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
index 95d307c..709476c 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
@@ -40,7 +40,6 @@
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Future;
-
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterRegistration;
@@ -104,11 +103,11 @@
{
public static int SERVLET_MAJOR_VERSION=3;
public static int SERVLET_MINOR_VERSION=0;
-
+
final private static String __unimplmented="Unimplemented - use org.eclipse.jetty.servlet.ServletContextHandler";
-
-
+
+
private static final Logger LOG = Log.getLogger(ContextHandler.class);
private static final ThreadLocal<Context> __context = new ThreadLocal<Context>();
@@ -141,8 +140,8 @@
return c.getContextHandler();
return null;
}
-
-
+
+
protected Context _scontext;
private final AttributesMap _attributes;
private final Map<String, String> _initParams;
@@ -566,7 +565,7 @@
public void addEventListener(EventListener listener)
{
_eventListeners.add(listener);
-
+
if (listener instanceof ServletContextListener)
_contextListeners.add((ServletContextListener)listener);
@@ -579,7 +578,7 @@
if (listener instanceof ServletRequestAttributeListener)
_requestAttributeListeners.add((ServletRequestAttributeListener)listener);
}
-
+
/* ------------------------------------------------------------ */
/**
* Remove a context event listeners.
@@ -592,7 +591,7 @@
public void removeEventListener(EventListener listener)
{
_eventListeners.remove(listener);
-
+
if (listener instanceof ServletContextListener)
_contextListeners.remove(listener);
@@ -616,13 +615,13 @@
{
_programmaticListeners.add(listener);
}
-
+
/* ------------------------------------------------------------ */
protected boolean isProgrammaticListener(EventListener listener)
{
return _programmaticListeners.contains(listener);
}
-
+
/* ------------------------------------------------------------ */
/**
* @return true if this context is accepting new requests
@@ -725,7 +724,7 @@
startContext();
_availability = Availability.AVAILABLE;
- LOG.info("started {}",this);
+ LOG.info("Started {}", this);
}
finally
{
@@ -771,7 +770,7 @@
ServletContextEvent event = new ServletContextEvent(_scontext);
for (ServletContextListener listener:_contextListeners)
callContextInitialized(listener, event);
- }
+ }
}
/* ------------------------------------------------------------ */
@@ -818,7 +817,7 @@
if (!_contextListeners.isEmpty())
{
ServletContextEvent event = new ServletContextEvent(_scontext);
- for (int i = _contextListeners.size(); i-->0;)
+ for (int i = _contextListeners.size(); i-->0;)
callContextDestroyed(_contextListeners.get(i),event);
}
@@ -831,14 +830,14 @@
String name = e.nextElement();
checkManagedAttribute(name,null);
}
-
+
for (EventListener l : _programmaticListeners)
removeEventListener(l);
_programmaticListeners.clear();
}
finally
{
- LOG.info("stopped {}",this);
+ LOG.info("Stopped {}", this);
__context.set(old_context);
// reset the classloader
if (_classLoader != null && current_thread!=null)
@@ -894,10 +893,10 @@
default:
match = contextVhost.equalsIgnoreCase(vhost);
}
-
+
if (match)
break loop;
-
+
}
if (!match)
return false;
@@ -1657,7 +1656,7 @@
return host;
}
-
+
/* ------------------------------------------------------------ */
/**
* Add an AliasCheck instance to possibly permit aliased resources
@@ -1667,7 +1666,7 @@
{
_aliasChecks.add(check);
}
-
+
/* ------------------------------------------------------------ */
/**
* @return Mutable list of Alias checks
@@ -1798,7 +1797,7 @@
return null;
return _mimeTypes.getMimeByExtension(file);
}
-
+
/* ------------------------------------------------------------ */
/*
* @see javax.servlet.ServletContext#getRequestDispatcher(java.lang.String)
@@ -2182,13 +2181,13 @@
return _enabled;
}
}
-
+
public static class NoContext extends AttributesMap implements ServletContext
{
private int _effectiveMajorVersion = SERVLET_MAJOR_VERSION;
private int _effectiveMinorVersion = SERVLET_MINOR_VERSION;
-
+
/* ------------------------------------------------------------ */
public NoContext()
{
@@ -2522,8 +2521,8 @@
LOG.warn(__unimplmented);
}
}
-
-
+
+
/* ------------------------------------------------------------ */
/** Interface to check aliases
*/
@@ -2537,7 +2536,7 @@
*/
boolean check(String path, Resource resource);
}
-
+
/* ------------------------------------------------------------ */
/** Approve all aliases.
@@ -2550,7 +2549,7 @@
return true;
}
}
-
+
/* ------------------------------------------------------------ */
/** Approve Aliases with same suffix.
* Eg. a symbolic link from /foobar.html to /somewhere/wibble.html would be
@@ -2568,8 +2567,8 @@
return resource.getAlias().toString().endsWith(suffix);
}
}
-
-
+
+
/* ------------------------------------------------------------ */
/** Approve Aliases with a path prefix.
* Eg. a symbolic link from /dirA/foobar.html to /dirB/foobar.html would be
@@ -2589,7 +2588,7 @@
}
/* ------------------------------------------------------------ */
/** Approve Aliases of a non existent directory.
- * If a directory "/foobar/" does not exist, then the resource is
+ * If a directory "/foobar/" does not exist, then the resource is
* aliased to "/foobar". Accept such aliases.
*/
public static class ApproveNonExistentDirectoryAliases implements AliasCheck
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DebugHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DebugHandler.java
index 066f6ab..a758c70 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DebugHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DebugHandler.java
@@ -27,6 +27,9 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.server.AbstractConnector;
+import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.DateCache;
@@ -40,7 +43,7 @@
* and the current thread name is updated with information that will link
* to the details in that output.
*/
-public class DebugHandler extends HandlerWrapper
+public class DebugHandler extends HandlerWrapper implements Connection.Listener
{
private DateCache _date=new DateCache("HH:mm:ss", Locale.US);
private OutputStream _out;
@@ -69,14 +72,10 @@
String ex=null;
try
{
- long now=System.currentTimeMillis();
- final String d=_date.format(now);
- final int ms=(int)(now%1000);
-
if (retry)
- _print.println(d+(ms>99?".":(ms>9?".0":".00"))+ms+":"+name+" RETRY");
+ print(name,"RESUME");
else
- _print.println(d+(ms>99?".":(ms>9?".0":".00"))+ms+":"+name+" "+baseRequest.getRemoteAddr()+" "+request.getMethod()+" "+baseRequest.getHeader("Cookie")+"; "+baseRequest.getHeader("User-Agent"));
+ print(name,"REQUEST "+baseRequest.getRemoteAddr()+" "+request.getMethod()+" "+baseRequest.getHeader("Cookie")+"; "+baseRequest.getHeader("User-Agent"));
thread.setName(name);
getHandler().handle(target,baseRequest,request,response);
@@ -104,21 +103,25 @@
finally
{
thread.setName(old_name);
- long now=System.currentTimeMillis();
- final String d=_date.format(now);
- final int ms=(int)(now%1000);
suspend=baseRequest.getHttpChannelState().isSuspended();
if (suspend)
{
request.setAttribute("org.eclipse.jetty.thread.name",name);
- _print.println(d+(ms>99?".":(ms>9?".0":".00"))+ms+":"+name+" SUSPEND");
+ print(name,"SUSPEND");
}
else
- _print.println(d+(ms>99?".":(ms>9?".0":".00"))+ms+":"+name+" "+base_response.getStatus()+
- (ex==null?"":("/"+ex))+
- " "+base_response.getContentType());
+ print(name,"RESPONSE "+base_response.getStatus()+(ex==null?"":("/"+ex))+" "+base_response.getContentType());
}
}
+
+ private void print(String name,String message)
+ {
+ long now=System.currentTimeMillis();
+ final String d=_date.format(now);
+ final int ms=(int)(now%1000);
+
+ _print.println(d+(ms>99?".":(ms>9?".0":".00"))+ms+":"+name+" "+message);
+ }
/* (non-Javadoc)
* @see org.eclipse.jetty.server.handler.HandlerWrapper#doStart()
@@ -129,6 +132,11 @@
if (_out==null)
_out=new RolloverFileOutputStream("./logs/yyyy_mm_dd.debug.log",true);
_print=new PrintStream(_out);
+
+ for (Connector connector : getServer().getConnectors())
+ if (connector instanceof AbstractConnector)
+ ((AbstractConnector)connector).addBean(this,false);
+
super.doStart();
}
@@ -140,6 +148,9 @@
{
super.doStop();
_print.close();
+ for (Connector connector : getServer().getConnectors())
+ if (connector instanceof AbstractConnector)
+ ((AbstractConnector)connector).removeBean(this);
}
/**
@@ -157,4 +168,17 @@
{
_out = out;
}
+
+ @Override
+ public void onOpened(Connection connection)
+ {
+ print(Thread.currentThread().getName(),"OPENED "+connection.toString());
+ }
+
+ @Override
+ public void onClosed(Connection connection)
+ {
+ print(Thread.currentThread().getName(),"CLOSED "+connection.toString());
+ }
+
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java
index 7a2ac70..78e8eb7 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java
@@ -28,6 +28,7 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.eclipse.jetty.server.AsyncContextEvent;
import org.eclipse.jetty.server.HttpChannelState;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
@@ -76,12 +77,13 @@
public void onError(AsyncEvent event) throws IOException
{
}
-
+
@Override
public void onComplete(AsyncEvent event) throws IOException
{
- HttpChannelState state = (HttpChannelState)event.getAsyncContext();
+ HttpChannelState state = ((AsyncContextEvent)event).getHttpChannelState();
+
Request request = state.getBaseRequest();
final long elapsed = System.currentTimeMillis()-request.getTimeStamp();
@@ -93,7 +95,7 @@
if (!state.isDispatched())
_asyncWaitStats.decrement();
}
-
+
};
/**
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java
index afe7ae2..e596188 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java
@@ -323,17 +323,20 @@
super.complete();
try
{
- if (_dirty)
+ if (isValid())
{
- //The session attributes have changed, write to the db, ensuring
- //http passivation/activation listeners called
- willPassivate();
- updateSession(this);
- didActivate();
- }
- else if ((getAccessed() - _lastSaved) >= (getSaveInterval() * 1000L))
- {
- updateSessionAccessTime(this);
+ if (_dirty)
+ {
+ //The session attributes have changed, write to the db, ensuring
+ //http passivation/activation listeners called
+ willPassivate();
+ updateSession(this);
+ didActivate();
+ }
+ else if ((getAccessed() - _lastSaved) >= (getSaveInterval() * 1000L))
+ {
+ updateSessionAccessTime(this);
+ }
}
}
catch (Exception e)
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncRequestReadTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncRequestReadTest.java
index 9162374..a1b673d 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncRequestReadTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncRequestReadTest.java
@@ -18,26 +18,38 @@
package org.eclipse.jetty.server;
+import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
+import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
+import java.io.InputStreamReader;
import java.io.OutputStream;
+import java.io.PrintWriter;
import java.net.Socket;
import java.util.Arrays;
+import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Exchanger;
import java.util.concurrent.TimeUnit;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
+import org.hamcrest.Matchers;
+import org.junit.After;
import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
@@ -46,116 +58,129 @@
{
private static Server server;
private static ServerConnector connector;
- private final static Exchanger<Long> __total=new Exchanger<Long>();
+ private final static BlockingQueue<Long> __total=new BlockingArrayQueue<>();
- @BeforeClass
- public static void startServer() throws Exception
+ @Before
+ public void startServer() throws Exception
{
server = new Server();
connector = new ServerConnector(server);
connector.setIdleTimeout(10000);
server.addConnector(connector);
- server.setHandler(new EmptyHandler());
- server.start();
}
- @AfterClass
- public static void stopServer() throws Exception
+ @After
+ public void stopServer() throws Exception
{
server.stop();
server.join();
}
@Test
- public void test() throws Exception
+ public void testPipelined() throws Exception
{
- final Socket socket = new Socket("localhost",connector.getLocalPort());
+ server.setHandler(new AsyncStreamHandler());
+ server.start();
+
+ try (final Socket socket = new Socket("localhost",connector.getLocalPort()))
+ {
+ socket.setSoTimeout(1000);
+
+ byte[] content = new byte[32*4096];
+ Arrays.fill(content, (byte)120);
- byte[] content = new byte[16*4096];
- Arrays.fill(content, (byte)120);
+ OutputStream out = socket.getOutputStream();
+ String header=
+ "POST / HTTP/1.1\r\n"+
+ "Host: localhost\r\n"+
+ "Content-Length: "+content.length+"\r\n"+
+ "Content-Type: bytes\r\n"+
+ "\r\n";
+ byte[] h=header.getBytes(StringUtil.__ISO_8859_1);
+ out.write(h);
+ out.write(content);
+
+
+ header=
+ "POST / HTTP/1.1\r\n"+
+ "Host: localhost\r\n"+
+ "Content-Length: "+content.length+"\r\n"+
+ "Content-Type: bytes\r\n"+
+ "Connection: close\r\n"+
+ "\r\n";
+ h=header.getBytes(StringUtil.__ISO_8859_1);
+ out.write(h);
+ out.write(content);
+ out.flush();
- OutputStream out = socket.getOutputStream();
- String header=
- "POST / HTTP/1.1\r\n"+
- "Host: localhost\r\n"+
- "Content-Length: "+content.length+"\r\n"+
- "Content-Type: bytes\r\n"+
- "Connection: close\r\n"+
- "\r\n";
- byte[] h=header.getBytes(StringUtil.__ISO_8859_1);
+ InputStream in = socket.getInputStream();
+ String response = IO.toString(in);
+ assertTrue(response.indexOf("200 OK")>0);
- out.write(h);
- out.flush();
-
- out.write(content,0,4*4096);
- Thread.sleep(100);
- out.write(content,8192,4*4096);
- Thread.sleep(100);
- out.write(content,8*4096,content.length-8*4096);
-
- out.flush();
-
- InputStream in = socket.getInputStream();
- String response = IO.toString(in);
- assertTrue(response.indexOf("200 OK")>0);
-
- long total=__total.exchange(0L,30,TimeUnit.SECONDS);
- assertEquals(content.length, total);
+ long total=__total.poll(5,TimeUnit.SECONDS);
+ assertEquals(content.length, total);
+ total=__total.poll(5,TimeUnit.SECONDS);
+ assertEquals(content.length, total);
+ }
}
@Test
- @Ignore
- public void tests() throws Exception
+ public void testAsyncReadsWithDelays() throws Exception
{
- runTest(64,4,4,20);
- runTest(256,16,16,50);
- runTest(256,1,128,10);
- runTest(128*1024,1,64,10);
- runTest(256*1024,5321,10,100);
- runTest(512*1024,32*1024,10,10);
+ server.setHandler(new AsyncStreamHandler());
+ server.start();
+
+ asyncReadTest(64,4,4,20);
+ asyncReadTest(256,16,16,50);
+ asyncReadTest(256,1,128,10);
+ asyncReadTest(128*1024,1,64,10);
+ asyncReadTest(256*1024,5321,10,100);
+ asyncReadTest(512*1024,32*1024,10,10);
}
- public void runTest(int contentSize, int chunkSize, int chunks, int delayMS) throws Exception
+ public void asyncReadTest(int contentSize, int chunkSize, int chunks, int delayMS) throws Exception
{
String tst=contentSize+","+chunkSize+","+chunks+","+delayMS;
//System.err.println(tst);
- final Socket socket = new Socket("localhost",connector.getLocalPort());
-
- byte[] content = new byte[contentSize];
- Arrays.fill(content, (byte)120);
-
- OutputStream out = socket.getOutputStream();
- out.write("POST / HTTP/1.1\r\n".getBytes());
- out.write("Host: localhost\r\n".getBytes());
- out.write(("Content-Length: "+content.length+"\r\n").getBytes());
- out.write("Content-Type: bytes\r\n".getBytes());
- out.write("Connection: close\r\n".getBytes());
- out.write("\r\n".getBytes());
- out.flush();
-
- int offset=0;
- for (int i=0;i<chunks;i++)
+ try(final Socket socket = new Socket("localhost",connector.getLocalPort()))
{
- out.write(content,offset,chunkSize);
- offset+=chunkSize;
- Thread.sleep(delayMS);
+
+ byte[] content = new byte[contentSize];
+ Arrays.fill(content, (byte)120);
+
+ OutputStream out = socket.getOutputStream();
+ out.write("POST / HTTP/1.1\r\n".getBytes());
+ out.write("Host: localhost\r\n".getBytes());
+ out.write(("Content-Length: "+content.length+"\r\n").getBytes());
+ out.write("Content-Type: bytes\r\n".getBytes());
+ out.write("Connection: close\r\n".getBytes());
+ out.write("\r\n".getBytes());
+ out.flush();
+
+ int offset=0;
+ for (int i=0;i<chunks;i++)
+ {
+ out.write(content,offset,chunkSize);
+ offset+=chunkSize;
+ Thread.sleep(delayMS);
+ }
+ out.write(content,offset,content.length-offset);
+
+ out.flush();
+
+ InputStream in = socket.getInputStream();
+ String response = IO.toString(in);
+ assertTrue(tst,response.indexOf("200 OK")>0);
+
+ long total=__total.poll(30,TimeUnit.SECONDS);
+ assertEquals(tst,content.length, total);
}
- out.write(content,offset,content.length-offset);
-
- out.flush();
-
- InputStream in = socket.getInputStream();
- String response = IO.toString(in);
- assertTrue(tst,response.indexOf("200 OK")>0);
-
- long total=__total.exchange(0L,30,TimeUnit.SECONDS);
- assertEquals(tst,content.length, total);
}
- private static class EmptyHandler extends AbstractHandler
+ private static class AsyncStreamHandler extends AbstractHandler
{
@Override
public void handle(String path, final Request request, HttpServletRequest httpRequest, final HttpServletResponse httpResponse) throws IOException, ServletException
@@ -164,6 +189,7 @@
request.setHandled(true);
final AsyncContext async = request.startAsync();
+ // System.err.println("handle "+request.getContentLength());
new Thread()
{
@@ -171,9 +197,10 @@
public void run()
{
long total=0;
- try
+ try(InputStream in = request.getInputStream();)
{
- InputStream in = request.getInputStream();
+ // System.err.println("reading...");
+
byte[] b = new byte[4*4096];
int read;
while((read =in.read(b))>=0)
@@ -188,17 +215,156 @@
{
httpResponse.setStatus(200);
async.complete();
- try
- {
- __total.exchange(total);
- }
- catch (InterruptedException e)
- {
- e.printStackTrace();
- }
+ // System.err.println("read "+total);
+ __total.offer(total);
}
}
}.start();
}
}
+
+
+ @Test
+ public void testPartialRead() throws Exception
+ {
+ server.setHandler(new PartialReaderHandler());
+ server.start();
+
+ try (final Socket socket = new Socket("localhost",connector.getLocalPort()))
+ {
+ socket.setSoTimeout(1000);
+
+ byte[] content = new byte[32*4096];
+ Arrays.fill(content, (byte)88);
+
+ OutputStream out = socket.getOutputStream();
+ String header=
+ "POST /?read=10 HTTP/1.1\r\n"+
+ "Host: localhost\r\n"+
+ "Content-Length: "+content.length+"\r\n"+
+ "Content-Type: bytes\r\n"+
+ "\r\n";
+ byte[] h=header.getBytes(StringUtil.__ISO_8859_1);
+ out.write(h);
+ out.write(content);
+
+ header= "POST /?read=10 HTTP/1.1\r\n"+
+ "Host: localhost\r\n"+
+ "Content-Length: "+content.length+"\r\n"+
+ "Content-Type: bytes\r\n"+
+ "Connection: close\r\n"+
+ "\r\n";
+ h=header.getBytes(StringUtil.__ISO_8859_1);
+ out.write(h);
+ out.write(content);
+ out.flush();
+
+ BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+ assertThat(in.readLine(),containsString("HTTP/1.1 200 OK"));
+ assertThat(in.readLine(),containsString("Content-Length:"));
+ assertThat(in.readLine(),containsString("Server:"));
+ in.readLine();
+ assertThat(in.readLine(),containsString("XXXXXXX"));
+ assertThat(in.readLine(),containsString("HTTP/1.1 200 OK"));
+ assertThat(in.readLine(),containsString("Connection: close"));
+ assertThat(in.readLine(),containsString("Server:"));
+ in.readLine();
+ assertThat(in.readLine(),containsString("XXXXXXX"));
+
+ }
+ }
+
+ @Test
+ public void testPartialReadThenShutdown() throws Exception
+ {
+ server.setHandler(new PartialReaderHandler());
+ server.start();
+
+ try (final Socket socket = new Socket("localhost",connector.getLocalPort()))
+ {
+ socket.setSoTimeout(10000);
+
+ byte[] content = new byte[32*4096];
+ Arrays.fill(content, (byte)88);
+
+ OutputStream out = socket.getOutputStream();
+ String header=
+ "POST /?read=10 HTTP/1.1\r\n"+
+ "Host: localhost\r\n"+
+ "Content-Length: "+content.length+"\r\n"+
+ "Content-Type: bytes\r\n"+
+ "\r\n";
+ byte[] h=header.getBytes(StringUtil.__ISO_8859_1);
+ out.write(h);
+ out.write(content,0,4096);
+ out.flush();
+ socket.shutdownOutput();
+
+ BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+ assertThat(in.readLine(),containsString("HTTP/1.1 200 OK"));
+ assertThat(in.readLine(),containsString("Content-Length:"));
+ assertThat(in.readLine(),containsString("Server:"));
+ in.readLine();
+ assertThat(in.readLine(),containsString("XXXXXXX"));
+ }
+ }
+
+ @Test
+ public void testPartialReadThenClose() throws Exception
+ {
+ server.setHandler(new PartialReaderHandler());
+ server.start();
+
+ try (final Socket socket = new Socket("localhost",connector.getLocalPort()))
+ {
+ socket.setSoTimeout(1000);
+
+ byte[] content = new byte[32*4096];
+ Arrays.fill(content, (byte)88);
+
+ OutputStream out = socket.getOutputStream();
+ String header=
+ "POST /?read=10 HTTP/1.1\r\n"+
+ "Host: localhost\r\n"+
+ "Content-Length: "+content.length+"\r\n"+
+ "Content-Type: bytes\r\n"+
+ "\r\n";
+ byte[] h=header.getBytes(StringUtil.__ISO_8859_1);
+ out.write(h);
+ out.write(content,0,4096);
+ out.flush();
+
+ BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+ assertThat(in.readLine(),containsString("HTTP/1.1 200 OK"));
+ assertThat(in.readLine(),containsString("Content-Length:"));
+ assertThat(in.readLine(),containsString("Server:"));
+ in.readLine();
+ assertThat(in.readLine(),containsString("XXXXXXX"));
+
+ socket.close();
+ }
+ }
+
+ private static class PartialReaderHandler extends AbstractHandler
+ {
+ @Override
+ public void handle(String path, final Request request, HttpServletRequest httpRequest, final HttpServletResponse httpResponse) throws IOException, ServletException
+ {
+ httpResponse.setStatus(200);
+ request.setHandled(true);
+
+ BufferedReader in = request.getReader();
+ PrintWriter out =httpResponse.getWriter();
+ int read=Integer.valueOf(request.getParameter("read"));
+ // System.err.println("read="+read);
+ for (int i=read;i-->0;)
+ {
+ int c=in.read();
+ if (c<0)
+ break;
+ out.write(c);
+ }
+ out.println();
+ }
+ }
}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/CheckReverseProxyHeadersTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/CheckReverseProxyHeadersTest.java
index 58a22b7..f4eec3e 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/CheckReverseProxyHeadersTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/CheckReverseProxyHeadersTest.java
@@ -23,6 +23,7 @@
import static org.junit.Assert.assertTrue;
import java.io.IOException;
+import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@@ -44,6 +45,7 @@
"X-Forwarded-For: 10.20.30.40\n" +
"X-Forwarded-Host: example.com", new RequestValidator()
{
+ @Override
public void validate(HttpServletRequest request)
{
assertEquals("example.com", request.getServerName());
@@ -55,6 +57,42 @@
assertFalse(request.isSecure());
}
});
+
+ // IPv6 ProxyPass from example.com:80 to localhost:8080
+ testRequest("Host: localhost:8080\n" +
+ "X-Forwarded-For: 10.20.30.40\n" +
+ "X-Forwarded-Host: [::1]", new RequestValidator()
+ {
+ @Override
+ public void validate(HttpServletRequest request)
+ {
+ assertEquals("::1", request.getServerName());
+ assertEquals(80, request.getServerPort());
+ assertEquals("10.20.30.40", request.getRemoteAddr());
+ assertEquals("10.20.30.40", request.getRemoteHost());
+ assertEquals("[::1]", request.getHeader("Host"));
+ assertEquals("http",request.getScheme());
+ assertFalse(request.isSecure());
+ }
+ });
+
+ // IPv6 ProxyPass from example.com:80 to localhost:8080
+ testRequest("Host: localhost:8080\n" +
+ "X-Forwarded-For: 10.20.30.40\n" +
+ "X-Forwarded-Host: [::1]:8888", new RequestValidator()
+ {
+ @Override
+ public void validate(HttpServletRequest request)
+ {
+ assertEquals("::1", request.getServerName());
+ assertEquals(8888, request.getServerPort());
+ assertEquals("10.20.30.40", request.getRemoteAddr());
+ assertEquals("10.20.30.40", request.getRemoteHost());
+ assertEquals("[::1]:8888", request.getHeader("Host"));
+ assertEquals("http",request.getScheme());
+ assertFalse(request.isSecure());
+ }
+ });
// ProxyPass from example.com:81 to localhost:8080
testRequest("Host: localhost:8080\n" +
@@ -63,6 +101,7 @@
"X-Forwarded-Server: example.com\n"+
"X-Forwarded-Proto: https", new RequestValidator()
{
+ @Override
public void validate(HttpServletRequest request)
{
assertEquals("example.com", request.getServerName());
@@ -82,6 +121,7 @@
"X-Forwarded-Server: example.com, rp.example.com\n"+
"X-Forwarded-Proto: https, http", new RequestValidator()
{
+ @Override
public void validate(HttpServletRequest request)
{
assertEquals("example.com", request.getServerName());
@@ -111,7 +151,8 @@
try
{
server.start();
- connector.getResponses("GET / HTTP/1.1\r\n" +"Connection: close\r\n" + headers + "\r\n\r\n");
+ connector.getResponses("GET / HTTP/1.1\r\n" +"Connection: close\r\n" + headers + "\r\n\r\n",
+ 1000,TimeUnit.SECONDS);
Error error = validationHandler.getError();
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java
index cc8e4c9..d0c8d3f 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java
@@ -81,7 +81,7 @@
}
@Test
- public void testFragmentedChunk()
+ public void testFragmentedChunk() throws Exception
{
String response=null;
try
@@ -119,10 +119,9 @@
}
catch(Exception e)
{
- e.printStackTrace();
- assertTrue(false);
- if (response!=null)
+ if(response != null)
System.err.println(response);
+ throw e;
}
}
@@ -249,7 +248,7 @@
}
@Test
- public void testCharset()
+ public void testCharset() throws Exception
{
String response=null;
@@ -306,10 +305,9 @@
}
catch(Exception e)
{
- e.printStackTrace();
- assertTrue(false);
- if (response!=null)
+ if(response != null)
System.err.println(response);
+ throw e;
}
}
@@ -391,7 +389,7 @@
}
@Test
- public void testConnection()
+ public void testConnection() throws Exception
{
String response=null;
try
@@ -412,10 +410,9 @@
}
catch (Exception e)
{
- e.printStackTrace();
- assertTrue(false);
- if (response!=null)
- System.err.println(response);
+ if(response != null)
+ System.err.println(response);
+ throw e;
}
}
@@ -525,7 +522,7 @@
}
@Test
- public void testAsterisk()
+ public void testAsterisk() throws Exception
{
String response = null;
@@ -572,10 +569,9 @@
}
catch (Exception e)
{
- e.printStackTrace();
- assertTrue(false);
- if (response!=null)
- System.err.println(response);
+ if(response != null)
+ System.err.println(response);
+ throw e;
}
finally
{
@@ -585,7 +581,7 @@
}
@Test
- public void testCONNECT()
+ public void testCONNECT() throws Exception
{
String response = null;
@@ -601,10 +597,9 @@
}
catch (Exception e)
{
- e.printStackTrace();
- assertTrue(false);
- if (response!=null)
- System.err.println(response);
+ if(response != null)
+ System.err.println(response);
+ throw e;
}
}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java
index fbe4baa..b717edf 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java
@@ -960,7 +960,7 @@
String in = IO.toString(is);
- System.err.println(in);
+ // System.err.println(in);
int index = in.indexOf("123456789");
assertTrue(index > 0);
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpURITest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpURITest.java
index 6cb0a2b..a770f95 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpURITest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpURITest.java
@@ -135,11 +135,11 @@
/*33*/ {"/?abc=test",null, null, null,null,"/", null,"abc=test",null},
/*34*/ {"/#fragment",null, null, null,null,"/", null,null,"fragment"},
/*35*/ {"http://192.0.0.1:8080/","http","//192.0.0.1:8080","192.0.0.1","8080","/",null,null,null},
- /*36*/ {"http://[2001:db8::1]:8080/","http","//[2001:db8::1]:8080","[2001:db8::1]","8080","/",null,null,null},
- /*37*/ {"http://user@[2001:db8::1]:8080/","http","//user@[2001:db8::1]:8080","[2001:db8::1]","8080","/",null,null,null},
- /*38*/ {"http://[2001:db8::1]/","http","//[2001:db8::1]","[2001:db8::1]",null,"/",null,null,null},
+ /*36*/ {"http://[2001:db8::1]:8080/","http","//[2001:db8::1]:8080","2001:db8::1","8080","/",null,null,null},
+ /*37*/ {"http://user@[2001:db8::1]:8080/","http","//user@[2001:db8::1]:8080","2001:db8::1","8080","/",null,null,null},
+ /*38*/ {"http://[2001:db8::1]/","http","//[2001:db8::1]","2001:db8::1",null,"/",null,null,null},
/*39*/ {"//[2001:db8::1]:8080/",null,null,null,null,"//[2001:db8::1]:8080/",null,null,null},
- /*40*/ {"http://user@[2001:db8::1]:8080/","http","//user@[2001:db8::1]:8080","[2001:db8::1]","8080","/",null,null,null},
+ /*40*/ {"http://user@[2001:db8::1]:8080/","http","//user@[2001:db8::1]:8080","2001:db8::1","8080","/",null,null,null},
/*41*/ {"*",null,null,null,null,"*",null, null,null}
};
@@ -366,7 +366,7 @@
{
/* 0*/ {" localhost:8080 ","localhost","8080"},
/* 1*/ {" 127.0.0.1:8080 ","127.0.0.1","8080"},
- /* 2*/ {" [127::0::0::1]:8080 ","[127::0::0::1]","8080"},
+ /* 2*/ {" [127::0::0::1]:8080 ","127::0::0::1","8080"},
/* 3*/ {" error ",null,null},
/* 4*/ {" http://localhost:8080/ ",null,null},
};
@@ -382,7 +382,7 @@
byte[] buf = connect_tests[i][0].getBytes(StringUtil.__UTF8);
uri.parseConnect(buf,2,buf.length-4);
- assertEquals("path"+i,connect_tests[i][1]+":"+connect_tests[i][2],uri.getPath());
+ assertEquals("path"+i,connect_tests[i][0].trim(),uri.getPath());
assertEquals("host"+i,connect_tests[i][1],uri.getHost());
assertEquals("port"+i,Integer.parseInt(connect_tests[i][2]),uri.getPort());
}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java
index 6bb09c1..b1c9bde 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java
@@ -241,7 +241,7 @@
multipart;
String responses=_connector.getResponses(request);
- System.err.println(responses);
+ // System.err.println(responses);
assertTrue(responses.startsWith("HTTP/1.1 200"));
}
@@ -354,6 +354,7 @@
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response)
{
+ results.add(request.getRequestURL().toString());
results.add(request.getRemoteAddr());
results.add(request.getServerName());
results.add(String.valueOf(request.getServerPort()));
@@ -361,71 +362,121 @@
}
};
- String responses=_connector.getResponses(
+ results.clear();
+ _connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: myhost\n"+
- "\n"+
-
+ "Connection: close\n"+
+ "\n");
+ int i=0;
+ assertEquals("http://myhost/",results.get(i++));
+ assertEquals("0.0.0.0",results.get(i++));
+ assertEquals("myhost",results.get(i++));
+ assertEquals("80",results.get(i++));
+
+
+ results.clear();
+ _connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: myhost:8888\n"+
- "\n"+
-
+ "Connection: close\n"+
+ "\n");
+ i=0;
+ assertEquals("http://myhost:8888/",results.get(i++));
+ assertEquals("0.0.0.0",results.get(i++));
+ assertEquals("myhost",results.get(i++));
+ assertEquals("8888",results.get(i++));
+
+
+ results.clear();
+ _connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: 1.2.3.4\n"+
- "\n"+
-
+ "Connection: close\n"+
+ "\n");
+ i=0;
+
+ assertEquals("http://1.2.3.4/",results.get(i++));
+ assertEquals("0.0.0.0",results.get(i++));
+ assertEquals("1.2.3.4",results.get(i++));
+ assertEquals("80",results.get(i++));
+
+
+ results.clear();
+ _connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: 1.2.3.4:8888\n"+
- "\n"+
-
+ "Connection: close\n"+
+ "\n");
+ i=0;
+ assertEquals("http://1.2.3.4:8888/",results.get(i++));
+ assertEquals("0.0.0.0",results.get(i++));
+ assertEquals("1.2.3.4",results.get(i++));
+ assertEquals("8888",results.get(i++));
+
+
+ results.clear();
+ _connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: [::1]\n"+
- "\n"+
-
+ "Connection: close\n"+
+ "\n");
+ i=0;
+ assertEquals("http://[::1]/",results.get(i++));
+ assertEquals("0.0.0.0",results.get(i++));
+ assertEquals("::1",results.get(i++));
+ assertEquals("80",results.get(i++));
+
+
+ results.clear();
+ _connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: [::1]:8888\n"+
- "\n"+
-
+ "Connection: close\n"+
+ "\n");
+ i=0;
+ assertEquals("http://[::1]:8888/",results.get(i++));
+ assertEquals("0.0.0.0",results.get(i++));
+ assertEquals("::1",results.get(i++));
+ assertEquals("8888",results.get(i++));
+
+
+ results.clear();
+ _connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: [::1]\n"+
"x-forwarded-for: remote\n"+
"x-forwarded-proto: https\n"+
- "\n"+
-
+ "Connection: close\n"+
+ "\n");
+ i=0;
+ assertEquals("https://[::1]/",results.get(i++));
+ assertEquals("remote",results.get(i++));
+ assertEquals("::1",results.get(i++));
+ assertEquals("443",results.get(i++));
+
+
+ results.clear();
+ _connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: [::1]:8888\n"+
"Connection: close\n"+
"x-forwarded-for: remote\n"+
"x-forwarded-proto: https\n"+
- "\n",10,TimeUnit.SECONDS);
-
-
- int i=0;
- assertEquals("0.0.0.0",results.get(i++));
- assertEquals("myhost",results.get(i++));
- assertEquals("80",results.get(i++));
- assertEquals("0.0.0.0",results.get(i++));
- assertEquals("myhost",results.get(i++));
- assertEquals("8888",results.get(i++));
- assertEquals("0.0.0.0",results.get(i++));
- assertEquals("1.2.3.4",results.get(i++));
- assertEquals("80",results.get(i++));
- assertEquals("0.0.0.0",results.get(i++));
- assertEquals("1.2.3.4",results.get(i++));
- assertEquals("8888",results.get(i++));
- assertEquals("0.0.0.0",results.get(i++));
- assertEquals("[::1]",results.get(i++));
- assertEquals("80",results.get(i++));
- assertEquals("0.0.0.0",results.get(i++));
- assertEquals("[::1]",results.get(i++));
- assertEquals("8888",results.get(i++));
+ "\n");
+ i=0;
+
+ assertEquals("https://[::1]:8888/",results.get(i++));
assertEquals("remote",results.get(i++));
- assertEquals("[::1]",results.get(i++));
- assertEquals("443",results.get(i++));
- assertEquals("remote",results.get(i++));
- assertEquals("[::1]",results.get(i++));
+ assertEquals("::1",results.get(i++));
assertEquals("8888",results.get(i++));
+
+
+
+
+
+
}
@Test
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java
index afa0107..b4d00a1 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java
@@ -432,43 +432,54 @@
String[][] tests = {
// No cookie
{"http://myhost:8888/other/location;jsessionid=12345?name=value","http://myhost:8888/other/location;jsessionid=12345?name=value"},
- {"/other/location;jsessionid=12345?name=value","http://myhost:8888/other/location;jsessionid=12345?name=value"},
- {"./location;jsessionid=12345?name=value","http://myhost:8888/path/location;jsessionid=12345?name=value"},
+ {"/other/location;jsessionid=12345?name=value","http://@HOST@@PORT@/other/location;jsessionid=12345?name=value"},
+ {"./location;jsessionid=12345?name=value","http://@HOST@@PORT@/path/location;jsessionid=12345?name=value"},
// From cookie
- {"/other/location","http://myhost:8888/other/location"},
- {"/other/l%20cation", "http://myhost:8888/other/l%20cation"},
- {"location", "http://myhost:8888/path/location"},
- {"./location", "http://myhost:8888/path/location"},
- {"../location", "http://myhost:8888/location"},
- {"/other/l%20cation", "http://myhost:8888/other/l%20cation"},
- {"l%20cation", "http://myhost:8888/path/l%20cation"},
- {"./l%20cation", "http://myhost:8888/path/l%20cation"},
- {"../l%20cation","http://myhost:8888/l%20cation"},
- {"../locati%C3%abn", "http://myhost:8888/locati%C3%ABn"},
+ {"/other/location","http://@HOST@@PORT@/other/location"},
+ {"/other/l%20cation", "http://@HOST@@PORT@/other/l%20cation"},
+ {"location", "http://@HOST@@PORT@/path/location"},
+ {"./location", "http://@HOST@@PORT@/path/location"},
+ {"../location", "http://@HOST@@PORT@/location"},
+ {"/other/l%20cation", "http://@HOST@@PORT@/other/l%20cation"},
+ {"l%20cation", "http://@HOST@@PORT@/path/l%20cation"},
+ {"./l%20cation", "http://@HOST@@PORT@/path/l%20cation"},
+ {"../l%20cation","http://@HOST@@PORT@/l%20cation"},
+ {"../locati%C3%abn", "http://@HOST@@PORT@/locati%C3%ABn"},
+ {"http://somehost.com/other/location","http://somehost.com/other/location"},
};
- for (int i=0;i<tests.length;i++)
+ int[] ports=new int[]{8080,80};
+ String[] hosts=new String[]{"myhost","192.168.0.1","0::1"};
+ for (int port : ports)
{
- Response response = newResponse();
- Request request = response.getHttpChannel().getRequest();
+ for (String host : hosts)
+ {
+ for (int i=0;i<tests.length;i++)
+ {
+ Response response = newResponse();
+ Request request = response.getHttpChannel().getRequest();
- request.setServerName("myhost");
- request.setServerPort(8888);
- request.setUri(new HttpURI("/path/info;param;jsessionid=12345?query=0&more=1#target"));
- request.setContextPath("/path");
- request.setRequestedSessionId("12345");
- request.setRequestedSessionIdFromCookie(i>2);
- HashSessionManager manager = new HashSessionManager();
- manager.setSessionIdManager(new HashSessionIdManager());
- request.setSessionManager(manager);
- request.setSession(new TestSession(manager, "12345"));
- manager.setCheckingRemoteSessionIdEncoding(false);
+ request.setServerName(host);
+ request.setServerPort(port);
+ request.setUri(new HttpURI("/path/info;param;jsessionid=12345?query=0&more=1#target"));
+ request.setContextPath("/path");
+ request.setRequestedSessionId("12345");
+ request.setRequestedSessionIdFromCookie(i>2);
+ HashSessionManager manager = new HashSessionManager();
+ manager.setSessionIdManager(new HashSessionIdManager());
+ request.setSessionManager(manager);
+ request.setSession(new TestSession(manager, "12345"));
+ manager.setCheckingRemoteSessionIdEncoding(false);
- response.sendRedirect(tests[i][0]);
+ response.sendRedirect(tests[i][0]);
- String location = response.getHeader("Location");
- assertEquals("test-"+i,tests[i][1],location);
+ String location = response.getHeader("Location");
+
+ String expected=tests[i][1].replace("@HOST@",host.contains(":")?("["+host+"]"):host).replace("@PORT@",port==80?"":(":"+port));
+ assertEquals("test-"+i+" "+host+":"+port,expected,location);
+ }
+ }
}
}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/StressTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/StressTest.java
index 1de5bc7..269fce5 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/StressTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/StressTest.java
@@ -122,46 +122,46 @@
q.clear();
}
+ @Test
+ public void testMinNonPersistent() throws Throwable
+ {
+ assumeTrue(!OS.IS_OSX);
+ doThreads(10,10,false);
+ }
@Test
@Stress("Hey, its called StressTest for a reason")
- public void testMinNonPersistent() throws Throwable
- {
- doThreads(2,2,false);
- }
-
- @Test
public void testNonPersistent() throws Throwable
{
// TODO needs to be further investigated
- assumeTrue(!OS.IS_OSX || PropertyFlag.isEnabled("test.stress"));
+ assumeTrue(!OS.IS_OSX);
- doThreads(10,10,false);
- if (PropertyFlag.isEnabled("test.stress"))
- {
- doThreads(20,20,false);
- Thread.sleep(1000);
- doThreads(200,10,false);
- Thread.sleep(1000);
- doThreads(200,200,false);
- }
+ doThreads(20,20,false);
+ Thread.sleep(1000);
+ doThreads(200,10,false);
+ Thread.sleep(1000);
+ doThreads(200,200,false);
}
@Test
+ public void testMinPersistent() throws Throwable
+ {
+ // TODO needs to be further investigated
+ assumeTrue(!OS.IS_OSX);
+ doThreads(10,10,true);
+ }
+
+ @Test
+ @Stress("Hey, its called StressTest for a reason")
public void testPersistent() throws Throwable
{
// TODO needs to be further investigated
- assumeTrue(!OS.IS_OSX || PropertyFlag.isEnabled("test.stress"));
-
- doThreads(10,10,true);
- if (PropertyFlag.isEnabled("test.stress"))
- {
- doThreads(40,40,true);
- Thread.sleep(1000);
- doThreads(200,10,true);
- Thread.sleep(1000);
- doThreads(200,200,true);
- }
+ assumeTrue(!OS.IS_OSX);
+ doThreads(40,40,true);
+ Thread.sleep(1000);
+ doThreads(200,10,true);
+ Thread.sleep(1000);
+ doThreads(200,200,true);
}
private void doThreads(int threadCount, final int loops, final boolean persistent) throws Throwable
@@ -238,7 +238,6 @@
{
System.err.println("STALLED!!!");
System.err.println(_server.getThreadPool().toString());
- ((ServerConnector)(_server.getConnectors()[0])).dump();
Thread.sleep(5000);
System.exit(1);
}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/StatisticsHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/StatisticsHandlerTest.java
index ef91963..2a59567 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/StatisticsHandlerTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/StatisticsHandlerTest.java
@@ -18,19 +18,11 @@
package org.eclipse.jetty.server.handler;
-import static org.hamcrest.Matchers.greaterThanOrEqualTo;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
-
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
@@ -46,6 +38,12 @@
import org.junit.Before;
import org.junit.Test;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
public class StatisticsHandlerTest
{
private Server _server;
@@ -60,7 +58,7 @@
_server = new Server();
_connector = new LocalConnector(_server);
- _statistics=new ConnectorStatistics();
+ _statistics = new ConnectorStatistics();
_connector.addBean(_statistics);
_server.addConnector(_connector);
@@ -105,8 +103,8 @@
_server.start();
String request = "GET / HTTP/1.1\r\n" +
- "Host: localhost\r\n" +
- "\r\n";
+ "Host: localhost\r\n" +
+ "\r\n";
_connector.executeRequest(request);
barrier[0].await();
@@ -174,8 +172,8 @@
assertEquals(2, _statsHandler.getResponses2xx());
_latchHandler.reset(2);
- barrier[0]=new CyclicBarrier(3);
- barrier[1]=new CyclicBarrier(3);
+ barrier[0] = new CyclicBarrier(3);
+ barrier[1] = new CyclicBarrier(3);
_connector.executeRequest(request);
_connector.executeRequest(request);
@@ -208,37 +206,33 @@
assertEquals(0, _statsHandler.getAsyncDispatches());
assertEquals(0, _statsHandler.getExpires());
assertEquals(4, _statsHandler.getResponses2xx());
-
-
}
@Test
public void testSuspendResume() throws Exception
{
+ final long dispatchTime = 10;
+ final long requestTime = 50;
final AtomicReference<AsyncContext> asyncHolder = new AtomicReference<>();
- final CyclicBarrier barrier[] = { new CyclicBarrier(2), new CyclicBarrier(2), new CyclicBarrier(2)};
+ final CyclicBarrier barrier[] = {new CyclicBarrier(2), new CyclicBarrier(2), new CyclicBarrier(2)};
_statsHandler.setHandler(new AbstractHandler()
{
@Override
public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
{
request.setHandled(true);
-
try
{
barrier[0].await();
- Thread.sleep(10);
-
+ Thread.sleep(dispatchTime);
+
if (asyncHolder.get() == null)
- {
asyncHolder.set(request.startAsync());
- }
}
catch (Exception x)
{
- Thread.currentThread().interrupt();
- throw (IOException)new IOException().initCause(x);
+ throw new ServletException(x);
}
finally
{
@@ -246,50 +240,41 @@
{
barrier[1].await();
}
- catch (Exception x)
+ catch (Exception ignored)
{
- x.printStackTrace();
- Thread.currentThread().interrupt();
- fail();
}
}
-
}
});
_server.start();
String request = "GET / HTTP/1.1\r\n" +
- "Host: localhost\r\n" +
- "\r\n";
+ "Host: localhost\r\n" +
+ "\r\n";
_connector.executeRequest(request);
-
barrier[0].await();
assertEquals(1, _statistics.getConnectionsOpen());
-
assertEquals(1, _statsHandler.getRequests());
assertEquals(1, _statsHandler.getRequestsActive());
assertEquals(1, _statsHandler.getDispatched());
assertEquals(1, _statsHandler.getDispatchedActive());
barrier[1].await();
-
assertTrue(_latchHandler.await());
assertNotNull(asyncHolder.get());
- assertTrue(asyncHolder.get()!=null);
assertEquals(1, _statsHandler.getRequests());
assertEquals(1, _statsHandler.getRequestsActive());
assertEquals(1, _statsHandler.getDispatched());
assertEquals(0, _statsHandler.getDispatchedActive());
- Thread.sleep(10);
_latchHandler.reset();
barrier[0].reset();
barrier[1].reset();
- Thread.sleep(50);
+ Thread.sleep(requestTime);
asyncHolder.get().addListener(new AsyncListener()
{
@@ -297,30 +282,34 @@
public void onTimeout(AsyncEvent event) throws IOException
{
}
-
+
@Override
public void onStartAsync(AsyncEvent event) throws IOException
{
}
-
+
@Override
public void onError(AsyncEvent event) throws IOException
{
}
-
+
@Override
public void onComplete(AsyncEvent event) throws IOException
{
- try { barrier[2].await(); } catch(Exception e) {}
+ try
+ {
+ barrier[2].await();
+ }
+ catch (Exception ignored)
+ {
+ }
}
});
-
asyncHolder.get().dispatch();
barrier[0].await(); // entered app handler
assertEquals(1, _statistics.getConnectionsOpen());
-
assertEquals(1, _statsHandler.getRequests());
assertEquals(1, _statsHandler.getRequestsActive());
assertEquals(2, _statsHandler.getDispatched());
@@ -335,50 +324,49 @@
assertEquals(2, _statsHandler.getDispatched());
assertEquals(0, _statsHandler.getDispatchedActive());
-
assertEquals(1, _statsHandler.getAsyncRequests());
assertEquals(1, _statsHandler.getAsyncDispatches());
assertEquals(0, _statsHandler.getExpires());
assertEquals(1, _statsHandler.getResponses2xx());
+ assertThat(_statsHandler.getRequestTimeTotal(), greaterThanOrEqualTo(requestTime * 3 / 4));
+ assertEquals(_statsHandler.getRequestTimeTotal(), _statsHandler.getRequestTimeMax());
+ assertEquals(_statsHandler.getRequestTimeTotal(), _statsHandler.getRequestTimeMean(), 0.01);
- assertThat(_statsHandler.getRequestTimeTotal(),greaterThanOrEqualTo(50L));
- assertEquals(_statsHandler.getRequestTimeTotal(),_statsHandler.getRequestTimeMax());
- assertEquals(_statsHandler.getRequestTimeTotal(),_statsHandler.getRequestTimeMean(), 0.01);
-
- assertTrue(_statsHandler.getDispatchedTimeTotal()>=20);
- assertTrue(_statsHandler.getDispatchedTimeMean()+10<=_statsHandler.getDispatchedTimeTotal());
- assertTrue(_statsHandler.getDispatchedTimeMax()+10<=_statsHandler.getDispatchedTimeTotal());
-
+ assertThat(_statsHandler.getDispatchedTimeTotal(), greaterThanOrEqualTo(dispatchTime * 2 * 3 / 4));
+ assertTrue(_statsHandler.getDispatchedTimeMean() + dispatchTime <= _statsHandler.getDispatchedTimeTotal());
+ assertTrue(_statsHandler.getDispatchedTimeMax() + dispatchTime <= _statsHandler.getDispatchedTimeTotal());
}
@Test
public void testSuspendExpire() throws Exception
{
+ final long dispatchTime = 10;
+ final long timeout = 100;
final AtomicReference<AsyncContext> asyncHolder = new AtomicReference<>();
- final CyclicBarrier barrier[] = { new CyclicBarrier(2), new CyclicBarrier(2), new CyclicBarrier(2)};
+ final CyclicBarrier barrier[] = {new CyclicBarrier(2), new CyclicBarrier(2), new CyclicBarrier(2)};
_statsHandler.setHandler(new AbstractHandler()
{
@Override
public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
{
request.setHandled(true);
-
try
{
barrier[0].await();
- Thread.sleep(10);
+
+ Thread.sleep(dispatchTime);
+
if (asyncHolder.get() == null)
{
- AsyncContext async=request.startAsync();
- async.setTimeout(100);
+ AsyncContext async = request.startAsync();
asyncHolder.set(async);
+ async.setTimeout(timeout);
}
}
catch (Exception x)
{
- Thread.currentThread().interrupt();
- throw (IOException)new IOException().initCause(x);
+ throw new ServletException(x);
}
finally
{
@@ -386,28 +374,22 @@
{
barrier[1].await();
}
- catch (Exception x)
+ catch (Exception ignored)
{
- x.printStackTrace();
- Thread.currentThread().interrupt();
- fail();
}
}
-
}
});
_server.start();
String request = "GET / HTTP/1.1\r\n" +
- "Host: localhost\r\n" +
- "\r\n";
+ "Host: localhost\r\n" +
+ "\r\n";
_connector.executeRequest(request);
-
barrier[0].await();
assertEquals(1, _statistics.getConnectionsOpen());
-
assertEquals(1, _statsHandler.getRequests());
assertEquals(1, _statsHandler.getRequestsActive());
assertEquals(1, _statsHandler.getDispatched());
@@ -423,31 +405,35 @@
{
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
{
- try { barrier[2].await(); } catch(Exception e) {}
+ try
+ {
+ barrier[2].await();
+ }
+ catch (Exception ignored)
+ {
+ }
}
});
-
assertEquals(1, _statsHandler.getRequests());
assertEquals(1, _statsHandler.getRequestsActive());
assertEquals(1, _statsHandler.getDispatched());
assertEquals(0, _statsHandler.getDispatchedActive());
-
barrier[2].await();
assertEquals(1, _statsHandler.getRequests());
@@ -460,67 +446,42 @@
assertEquals(1, _statsHandler.getExpires());
assertEquals(1, _statsHandler.getResponses2xx());
+ assertTrue(_statsHandler.getRequestTimeTotal() >= (timeout + dispatchTime) * 3 / 4);
+ assertEquals(_statsHandler.getRequestTimeTotal(), _statsHandler.getRequestTimeMax());
+ assertEquals(_statsHandler.getRequestTimeTotal(), _statsHandler.getRequestTimeMean(), 0.01);
- assertTrue(_statsHandler.getRequestTimeTotal()>=30);
- assertEquals(_statsHandler.getRequestTimeTotal(),_statsHandler.getRequestTimeMax());
- assertEquals(_statsHandler.getRequestTimeTotal(),_statsHandler.getRequestTimeMean(), 0.01);
-
- assertThat(_statsHandler.getDispatchedTimeTotal(),greaterThanOrEqualTo(10L));
-
+ assertThat(_statsHandler.getDispatchedTimeTotal(), greaterThanOrEqualTo(dispatchTime * 3 / 4));
}
@Test
public void testSuspendComplete() throws Exception
{
+ final long dispatchTime = 10;
final AtomicReference<AsyncContext> asyncHolder = new AtomicReference<>();
- final CyclicBarrier barrier[] = { new CyclicBarrier(2), new CyclicBarrier(2), new CyclicBarrier(2)};
+ final CyclicBarrier barrier[] = {new CyclicBarrier(2), new CyclicBarrier(2)};
+ final CountDownLatch latch = new CountDownLatch(1);
+
_statsHandler.setHandler(new AbstractHandler()
{
@Override
public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
{
request.setHandled(true);
-
try
{
barrier[0].await();
- Thread.sleep(10);
+
+ Thread.sleep(dispatchTime);
+
if (asyncHolder.get() == null)
{
- AsyncContext async=request.startAsync();
- async.setTimeout(1000);
+ AsyncContext async = request.startAsync();
asyncHolder.set(async);
- asyncHolder.get().addListener(new AsyncListener()
- {
-
- @Override
- public void onTimeout(AsyncEvent event) throws IOException
- {
- }
-
- @Override
- public void onStartAsync(AsyncEvent event) throws IOException
- {
- }
-
- @Override
- public void onError(AsyncEvent event) throws IOException
- {
- }
-
- @Override
- public void onComplete(AsyncEvent event) throws IOException
- {
- try { barrier[2].await(); } catch(Exception e) {}
- }
- });
}
-
}
catch (Exception x)
{
- Thread.currentThread().interrupt();
- throw (IOException)new IOException().initCause(x);
+ throw new ServletException(x);
}
finally
{
@@ -528,11 +489,8 @@
{
barrier[1].await();
}
- catch (Exception x)
+ catch (Exception ignored)
{
- x.printStackTrace();
- Thread.currentThread().interrupt();
- fail();
}
}
@@ -541,21 +499,18 @@
_server.start();
String request = "GET / HTTP/1.1\r\n" +
- "Host: localhost\r\n" +
- "\r\n";
+ "Host: localhost\r\n" +
+ "\r\n";
_connector.executeRequest(request);
-
barrier[0].await();
assertEquals(1, _statistics.getConnectionsOpen());
-
assertEquals(1, _statsHandler.getRequests());
assertEquals(1, _statsHandler.getRequestsActive());
assertEquals(1, _statsHandler.getDispatched());
assertEquals(1, _statsHandler.getDispatchedActive());
-
barrier[1].await();
assertTrue(_latchHandler.await());
assertNotNull(asyncHolder.get());
@@ -565,9 +520,39 @@
assertEquals(1, _statsHandler.getDispatched());
assertEquals(0, _statsHandler.getDispatchedActive());
- Thread.sleep(10);
+ asyncHolder.get().addListener(new AsyncListener()
+ {
+ @Override
+ public void onTimeout(AsyncEvent event) throws IOException
+ {
+ }
+
+ @Override
+ public void onStartAsync(AsyncEvent event) throws IOException
+ {
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException
+ {
+ }
+
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException
+ {
+ try
+ {
+ latch.countDown();
+ }
+ catch (Exception ignored)
+ {
+ }
+ }
+ });
+ long requestTime = 20;
+ Thread.sleep(requestTime);
asyncHolder.get().complete();
- barrier[2].await();
+ latch.await();
assertEquals(1, _statsHandler.getRequests());
assertEquals(0, _statsHandler.getRequestsActive());
@@ -579,17 +564,16 @@
assertEquals(0, _statsHandler.getExpires());
assertEquals(1, _statsHandler.getResponses2xx());
- assertTrue(_statsHandler.getRequestTimeTotal()>=20);
- assertEquals(_statsHandler.getRequestTimeTotal(),_statsHandler.getRequestTimeMax());
- assertEquals(_statsHandler.getRequestTimeTotal(),_statsHandler.getRequestTimeMean(), 0.01);
+ assertTrue(_statsHandler.getRequestTimeTotal() >= (dispatchTime + requestTime) * 3 / 4);
+ assertEquals(_statsHandler.getRequestTimeTotal(), _statsHandler.getRequestTimeMax());
+ assertEquals(_statsHandler.getRequestTimeTotal(), _statsHandler.getRequestTimeMean(), 0.01);
- assertTrue(_statsHandler.getDispatchedTimeTotal()>=10);
- assertTrue(_statsHandler.getDispatchedTimeTotal()<_statsHandler.getRequestTimeTotal());
- assertEquals(_statsHandler.getDispatchedTimeTotal(),_statsHandler.getDispatchedTimeMax());
- assertEquals(_statsHandler.getDispatchedTimeTotal(),_statsHandler.getDispatchedTimeMean(), 0.01);
+ assertTrue(_statsHandler.getDispatchedTimeTotal() >= dispatchTime * 3 / 4);
+ assertTrue(_statsHandler.getDispatchedTimeTotal() < _statsHandler.getRequestTimeTotal());
+ assertEquals(_statsHandler.getDispatchedTimeTotal(), _statsHandler.getDispatchedTimeMax());
+ assertEquals(_statsHandler.getDispatchedTimeTotal(), _statsHandler.getDispatchedTimeMean(), 0.01);
}
-
/**
* This handler is external to the statistics handler and it is used to ensure that statistics handler's
* handle() is fully executed before asserting its values in the tests, to avoid race conditions with the
@@ -602,7 +586,7 @@
@Override
public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
{
- final CountDownLatch latch=_latch;
+ final CountDownLatch latch = _latch;
try
{
super.handle(path, request, httpRequest, httpResponse);
@@ -615,12 +599,12 @@
private void reset()
{
- _latch=new CountDownLatch(1);
+ _latch = new CountDownLatch(1);
}
private void reset(int count)
{
- _latch=new CountDownLatch(count);
+ _latch = new CountDownLatch(count);
}
private boolean await() throws InterruptedException
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java
index 76e373f..03d4e32 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java
@@ -125,7 +125,6 @@
{
server.setHandler(new HelloWorldHandler());
server.start();
- server.dumpStdErr();
SSLContext ctx=SSLContext.getInstance("TLS");
ctx.init(null,SslContextFactory.TRUST_ALL_CERTS,new java.security.SecureRandom());
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java
index f95a58d..ac1dca9 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java
@@ -27,7 +27,6 @@
import java.nio.ByteBuffer;
import java.util.Enumeration;
import java.util.List;
-import java.util.Map;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
@@ -42,9 +41,8 @@
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.http.PathMap.MappedEntry;
import org.eclipse.jetty.io.WriterOutputStream;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.HttpOutput;
import org.eclipse.jetty.server.InclusiveByteRange;
import org.eclipse.jetty.server.ResourceCache;
@@ -160,7 +158,6 @@
private ServletHandler _servletHandler;
private ServletHolder _defaultHolder;
-
/* ------------------------------------------------------------ */
@Override
public void init()
@@ -268,7 +265,7 @@
throw new UnavailableException(e.toString());
}
- _servletHandler= (ServletHandler) _contextHandler.getChildHandlerByClass(ServletHandler.class);
+ _servletHandler= _contextHandler.getChildHandlerByClass(ServletHandler.class);
for (ServletHolder h :_servletHandler.getServlets())
if (h.getServletInstance()==this)
_defaultHolder=h;
@@ -343,6 +340,7 @@
* @param pathInContext The path to find a resource for.
* @return The resource to serve.
*/
+ @Override
public Resource getResource(String pathInContext)
{
Resource r=null;
@@ -632,7 +630,7 @@
if ((_welcomeServlets || _welcomeExactServlets) && welcome_servlet==null)
{
- Map.Entry entry=_servletHandler.getHolderEntry(welcome_in_context);
+ MappedEntry<?> entry=_servletHandler.getHolderEntry(welcome_in_context);
if (entry!=null && entry.getValue()!=_defaultHolder &&
(_welcomeServlets || (_welcomeExactServlets && entry.getKey().equals(welcome_in_context))))
welcome_servlet=welcome_in_context;
@@ -828,24 +826,10 @@
boolean include,
Resource resource,
HttpContent content,
- Enumeration reqRanges)
+ Enumeration<String> reqRanges)
throws IOException
{
- boolean direct;
- long content_length;
- if (content==null)
- {
- direct=false;
- content_length=resource.length();
- }
- else
- {
- // TODO sometimes we should be direct!
- Connector connector = HttpChannel.getCurrentHttpChannel().getConnector();
- direct=false;
- content_length=content.getContentLength();
- }
-
+ final long content_length = (content==null)?resource.length():content.getContentLength();
// Get the output stream (or writer)
OutputStream out =null;
@@ -884,17 +868,8 @@
}
else
{
- ByteBuffer buffer = direct?content.getDirectBuffer():content.getIndirectBuffer();
- if (buffer!=null)
- {
- writeHeaders(response,content,content_length);
- ((HttpOutput)out).sendContent(buffer);
- }
- else
- {
- writeHeaders(response,content,content_length);
- resource.writeTo(out,0,content_length);
- }
+ writeHeaders(response,content,content_length);
+ ((HttpOutput)out).sendContent(content.getResource());
}
}
else
@@ -914,7 +889,7 @@
else
{
// Parse the satisfiable ranges
- List ranges =InclusiveByteRange.satisfiableRanges(reqRanges,content_length);
+ List<InclusiveByteRange> ranges =InclusiveByteRange.satisfiableRanges(reqRanges,content_length);
// if there are no satisfiable ranges, send 416 response
if (ranges==null || ranges.size()==0)
@@ -931,8 +906,7 @@
// since were here now), send that range with a 216 response
if ( ranges.size()== 1)
{
- InclusiveByteRange singleSatisfiableRange =
- (InclusiveByteRange)ranges.get(0);
+ InclusiveByteRange singleSatisfiableRange = ranges.get(0);
long singleLength = singleSatisfiableRange.getSize(content_length);
writeHeaders(response,content,singleLength );
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
@@ -947,7 +921,7 @@
// content-length header
//
writeHeaders(response,content,-1);
- String mimetype=(content.getContentType()==null?null:content.getContentType().toString());
+ String mimetype=(content==null||content.getContentType()==null?null:content.getContentType().toString());
if (mimetype==null)
LOG.warn("Unknown mimetype for "+request.getRequestURI());
MultiPartOutputStream multi = new MultiPartOutputStream(out);
@@ -971,7 +945,7 @@
String[] header = new String[ranges.size()];
for (int i=0;i<ranges.size();i++)
{
- InclusiveByteRange ibr = (InclusiveByteRange) ranges.get(i);
+ InclusiveByteRange ibr = ranges.get(i);
header[i]=ibr.toHeaderRangeString(content_length);
length+=
((i>0)?2:0)+
@@ -986,7 +960,7 @@
for (int i=0;i<ranges.size();i++)
{
- InclusiveByteRange ibr = (InclusiveByteRange) ranges.get(i);
+ InclusiveByteRange ibr = ranges.get(i);
multi.startPart(mimetype,new String[]{HttpHeader.CONTENT_RANGE+": "+header[i]});
long start=ibr.getFirst(content_length);
@@ -1023,7 +997,6 @@
/* ------------------------------------------------------------ */
protected void writeHeaders(HttpServletResponse response,HttpContent content,long count)
- throws IOException
{
if (content.getContentType()!=null && response.getContentType()==null)
response.setContentType(content.getContentType().toString());
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/JspPropertyGroupServlet.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/JspPropertyGroupServlet.java
index cfb87fc..fd8f43e 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/JspPropertyGroupServlet.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/JspPropertyGroupServlet.java
@@ -24,6 +24,7 @@
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.server.Dispatcher;
import org.eclipse.jetty.server.HttpConnection;
@@ -96,7 +97,11 @@
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
{
- Request request=(req instanceof Request)?(Request)req:HttpConnection.getCurrentConnection().getHttpChannel().getRequest();
+ HttpServletRequest request = null;
+ if (req instanceof HttpServletRequest)
+ request = (HttpServletRequest)req;
+ else
+ throw new ServletException("Request not HttpServletRequest");
String servletPath=null;
String pathInfo=null;
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java
index 9baefac..3dcd213 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java
@@ -113,10 +113,10 @@
private MultiMap<FilterMapping> _filterNameMappings;
private final Map<String,ServletHolder> _servletNameMap=new HashMap<>();
- private PathMap _servletPathMap;
+ private PathMap<ServletHolder> _servletPathMap;
- protected final ConcurrentMap _chainCache[] = new ConcurrentMap[FilterMapping.ALL];
- protected final Queue[] _chainLRU = new Queue[FilterMapping.ALL];
+ protected final ConcurrentMap<?, ?> _chainCache[] = new ConcurrentMap[FilterMapping.ALL];
+ protected final Queue<?>[] _chainLRU = new Queue[FilterMapping.ALL];
/* ------------------------------------------------------------ */
@@ -208,6 +208,9 @@
_filterNameMappings=null;
_servletPathMap=null;
+
+ _matchBeforeIndex=-1;
+ _matchAfterIndex=-1;
}
/* ------------------------------------------------------------ */
@@ -250,7 +253,7 @@
* @param pathInContext Path within _context.
* @return PathMap Entries pathspec to ServletHolder
*/
- public PathMap.MappedEntry getHolderEntry(String pathInContext)
+ public PathMap.MappedEntry<ServletHolder> getHolderEntry(String pathInContext)
{
if (_servletPathMap==null)
return null;
@@ -331,10 +334,10 @@
if (target.startsWith("/"))
{
// Look for the servlet by path
- PathMap.MappedEntry entry=getHolderEntry(target);
+ PathMap.MappedEntry<ServletHolder> entry=getHolderEntry(target);
if (entry!=null)
{
- servlet_holder=(ServletHolder)entry.getValue();
+ servlet_holder=entry.getValue();
String servlet_path_spec= entry.getKey();
String servlet_path=entry.getMapped()!=null?entry.getMapped():PathMap.pathMatch(servlet_path_spec,target);
@@ -342,8 +345,8 @@
if (DispatcherType.INCLUDE.equals(type))
{
- baseRequest.setAttribute(Dispatcher.INCLUDE_SERVLET_PATH,servlet_path);
- baseRequest.setAttribute(Dispatcher.INCLUDE_PATH_INFO, path_info);
+ baseRequest.setAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH,servlet_path);
+ baseRequest.setAttribute(RequestDispatcher.INCLUDE_PATH_INFO, path_info);
}
else
{
@@ -615,8 +618,8 @@
if (filters.size() > 0)
chain= new CachedChain(filters, servletHolder);
- final Map<String,FilterChain> cache=_chainCache[dispatch];
- final Queue<String> lru=_chainLRU[dispatch];
+ final Map<String,FilterChain> cache=(Map<String, FilterChain>)_chainCache[dispatch];
+ final Queue<String> lru=(Queue<String>)_chainLRU[dispatch];
// Do we have too many cached chains?
while (_maxFilterChainsCacheSize>0 && cache.size()>=_maxFilterChainsCacheSize)
@@ -718,7 +721,7 @@
{
if (servlet.getClassName() == null && servlet.getForcedPath() != null)
{
- ServletHolder forced_holder = (ServletHolder)_servletPathMap.match(servlet.getForcedPath());
+ ServletHolder forced_holder = _servletPathMap.match(servlet.getForcedPath());
if (forced_holder == null || forced_holder.getClassName() == null)
{
mx.add(new IllegalStateException("No forced path servlet for " + servlet.getForcedPath()));
@@ -1053,7 +1056,7 @@
public void addFilterMapping (FilterMapping mapping)
{
if (mapping != null)
- {
+ {
Source source = (mapping.getFilterHolder()==null?null:mapping.getFilterHolder().getSource());
FilterMapping[] mappings =getFilterMappings();
if (mappings==null || mappings.length==0)
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 1e50dd1..77b3d4b 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
@@ -385,8 +385,8 @@
if (o==null)
return;
Servlet servlet = ((Servlet)o);
- servlet.destroy();
getServletHandler().destroyServlet(servlet);
+ servlet.destroy();
}
/* ------------------------------------------------------------ */
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java
index b66764b..a34d185 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java
@@ -103,6 +103,7 @@
Assert.assertEquals("servlet gets right path", "doGet:getServletPath:/servletPath", br.readLine());
Assert.assertEquals("async context gets right path in get","doGet:async:getServletPath:/servletPath",br.readLine());
Assert.assertEquals("async context gets right path in async","async:run:attr:servletPath:/servletPath",br.readLine());
+
}
@Test
@@ -121,6 +122,16 @@
Assert.assertEquals("query string attr is correct","async:run:attr:queryString:dispatch=true",br.readLine());
Assert.assertEquals("context path attr is correct","async:run:attr:contextPath:",br.readLine());
Assert.assertEquals("request uri attr is correct","async:run:attr:requestURI:/servletPath",br.readLine());
+
+ try
+ {
+ __asyncContext.getRequest();
+ Assert.fail();
+ }
+ catch (IllegalStateException e)
+ {
+
+ }
}
@Test
@@ -193,8 +204,11 @@
@Test
public void testDispatchRequestResponse() throws Exception
{
- String request = "GET /forward?dispatchRequestResponse=true HTTP/1.1\r\n" + "Host: localhost\r\n"
- + "Content-Type: application/x-www-form-urlencoded\r\n" + "Connection: close\r\n" + "\r\n";
+ String request = "GET /forward?dispatchRequestResponse=true HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Content-Type: application/x-www-form-urlencoded\r\n" +
+ "Connection: close\r\n" +
+ "\r\n";
String responseString = _connector.getResponses(request);
@@ -233,10 +247,12 @@
}
}
+ public static volatile AsyncContext __asyncContext;
+
private class AsyncDispatchingServlet extends HttpServlet
{
private static final long serialVersionUID = 1L;
-
+
@Override
protected void doGet(HttpServletRequest req, final HttpServletResponse response) throws ServletException, IOException
{
@@ -253,13 +269,14 @@
{
wrapped = true;
asyncContext = request.startAsync(request, new Wrapper(response));
+ __asyncContext=asyncContext;
}
else
{
asyncContext = request.startAsync();
+ __asyncContext=asyncContext;
}
-
new Thread(new DispatchingRunnable(asyncContext, wrapped)).start();
}
}
@@ -301,12 +318,14 @@
if (request.getParameter("dispatch") != null)
{
AsyncContext asyncContext = request.startAsync(request,response);
+ __asyncContext=asyncContext;
asyncContext.dispatch("/servletPath2");
}
else
{
response.getOutputStream().print("doGet:getServletPath:" + request.getServletPath() + "\n");
AsyncContext asyncContext = request.startAsync(request,response);
+ __asyncContext=asyncContext;
response.getOutputStream().print("doGet:async:getServletPath:" + ((HttpServletRequest)asyncContext.getRequest()).getServletPath() + "\n");
asyncContext.start(new AsyncRunnable(asyncContext));
@@ -323,6 +342,7 @@
{
response.getOutputStream().print("doGet:getServletPath:" + request.getServletPath() + "\n");
AsyncContext asyncContext = request.startAsync(request, response);
+ __asyncContext=asyncContext;
response.getOutputStream().print("doGet:async:getServletPath:" + ((HttpServletRequest)asyncContext.getRequest()).getServletPath() + "\n");
asyncContext.start(new AsyncRunnable(asyncContext));
}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java
index d5ad4ee..c2703fe 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java
@@ -19,7 +19,6 @@
package org.eclipse.jetty.servlet;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.io.InputStream;
@@ -345,6 +344,45 @@
Assert.assertThat(response,Matchers.not(Matchers.containsString(content)));
}
+
+ @Test
+ public void testAsyncRead() throws Exception
+ {
+ String header="GET /ctx/path/info?suspend=2000&resume=1500 HTTP/1.1\r\n"+
+ "Host: localhost\r\n"+
+ "Content-Length: 10\r\n"+
+ "\r\n";
+ String body="12345678\r\n";
+ String close="GET /ctx/path/info HTTP/1.1\r\n"+
+ "Host: localhost\r\n"+
+ "Connection: close\r\n"+
+ "\r\n";
+
+ try (Socket socket = new Socket("localhost",_port);)
+ {
+ socket.setSoTimeout(10000);
+ socket.getOutputStream().write(header.getBytes("ISO-8859-1"));
+ Thread.sleep(500);
+ socket.getOutputStream().write(body.getBytes("ISO-8859-1"),0,2);
+ Thread.sleep(500);
+ socket.getOutputStream().write(body.getBytes("ISO-8859-1"),2,8);
+ socket.getOutputStream().write(close.getBytes("ISO-8859-1"));
+
+ String response = IO.toString(socket.getInputStream());
+ assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
+ assertContains(
+ "history: REQUEST\r\n"+
+ "history: initial\r\n"+
+ "history: suspend\r\n"+
+ "history: async-read=10\r\n"+
+ "history: resume\r\n"+
+ "history: ASYNC\r\n"+
+ "history: !initial\r\n"+
+ "history: onComplete\r\n",response);
+ }
+ }
+
+
public synchronized String process(String query,String content) throws Exception
{
String request = "GET /ctx/path/info";
@@ -364,9 +402,8 @@
int port=_port;
String response=null;
- try
+ try (Socket socket = new Socket("localhost",port);)
{
- Socket socket = new Socket("localhost",port);
socket.setSoTimeout(1000000);
socket.getOutputStream().write(request.getBytes("UTF-8"));
@@ -379,11 +416,10 @@
throw e;
}
- // System.err.println(response);
-
return response;
}
-
+
+
private static class AsyncServlet extends HttpServlet
@@ -429,7 +465,7 @@
if (request.getDispatcherType()==DispatcherType.REQUEST)
{
- ((HttpServletResponse)response).addHeader("history","initial");
+ response.addHeader("history","initial");
if (read_before>0)
{
byte[] buf=new byte[read_before];
@@ -442,6 +478,30 @@
while(b!=-1)
b=in.read();
}
+ else if (request.getContentLength()>0)
+ {
+ new Thread()
+ {
+ @Override
+ public void run()
+ {
+ int c=0;
+ try
+ {
+ InputStream in=request.getInputStream();
+ int b=0;
+ while(b!=-1)
+ if((b=in.read())>=0)
+ c++;
+ response.addHeader("history","async-read="+c);
+ }
+ catch(Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }.start();
+ }
if (suspend_for>=0)
{
@@ -449,7 +509,7 @@
if (suspend_for>0)
async.setTimeout(suspend_for);
async.addListener(__listener);
- ((HttpServletResponse)response).addHeader("history","suspend");
+ response.addHeader("history","suspend");
if (complete_after>0)
{
@@ -527,7 +587,7 @@
}
else
{
- ((HttpServletResponse)response).addHeader("history","!initial");
+ response.addHeader("history","!initial");
if (suspend2_for>=0 && request.getAttribute("2nd")==null)
{
@@ -540,7 +600,7 @@
async.setTimeout(suspend2_for);
}
// continuation.addContinuationListener(__listener);
- ((HttpServletResponse)response).addHeader("history","suspend");
+ response.addHeader("history","suspend");
if (complete2_after>0)
{
@@ -581,7 +641,7 @@
@Override
public void run()
{
- ((HttpServletResponse)response).addHeader("history","resume");
+ response.addHeader("history","resume");
async.dispatch();
}
};
@@ -592,7 +652,7 @@
}
else if (resume2_after==0)
{
- ((HttpServletResponse)response).addHeader("history","dispatch");
+ response.addHeader("history","dispatch");
async.dispatch();
}
}
@@ -633,15 +693,11 @@
@Override
public void onStartAsync(AsyncEvent event) throws IOException
{
- // TODO Auto-generated method stub
-
}
@Override
public void onError(AsyncEvent event) throws IOException
{
- // TODO Auto-generated method stub
-
}
@Override
diff --git a/jetty-spdy/pom.xml b/jetty-spdy/pom.xml
index 8b3fa2d..dd13d3c 100644
--- a/jetty-spdy/pom.xml
+++ b/jetty-spdy/pom.xml
@@ -89,6 +89,18 @@
<npn.version>1.1.5.v20130313</npn.version>
</properties>
</profile>
+ <profile>
+ <id>7u21</id>
+ <activation>
+ <property>
+ <name>java.version</name>
+ <value>1.7.0_21</value>
+ </property>
+ </activation>
+ <properties>
+ <npn.version>1.1.5.v20130313</npn.version>
+ </properties>
+ </profile>
</profiles>
<modules>
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/IStream.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/IStream.java
index fc6c4d9..a8455d1 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/IStream.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/IStream.java
@@ -59,7 +59,9 @@
*/
public void setStreamFrameListener(StreamFrameListener listener);
- //TODO: javadoc thomas
+ /**
+ * @return the stream frame listener associated to this stream
+ */
public StreamFrameListener getStreamFrameListener();
/**
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/PushSynInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/PushSynInfo.java
index 7f50304..de411b4 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/PushSynInfo.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/PushSynInfo.java
@@ -32,7 +32,7 @@
private int associatedStreamId;
public PushSynInfo(int associatedStreamId, PushInfo pushInfo){
- super(pushInfo.getHeaders(), pushInfo.isClose());
+ super(pushInfo.getTimeout(), pushInfo.getUnit(), pushInfo.getHeaders(), pushInfo.isClose(), (byte)0);
this.associatedStreamId = associatedStreamId;
}
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDY.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDY.java
index 1a9d686..30d443f 100644
--- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDY.java
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDY.java
@@ -20,6 +20,7 @@
import java.io.IOException;
import java.nio.ByteBuffer;
+import java.util.Queue;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -46,6 +47,7 @@
import org.eclipse.jetty.util.BlockingCallback;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.ConcurrentArrayQueue;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.log.Log;
@@ -74,11 +76,21 @@
this.requestHeaders = requestHeaders;
}
+ protected Stream getStream()
+ {
+ return stream;
+ }
+
+ protected Fields getRequestHeaders()
+ {
+ return requestHeaders;
+ }
+
@Override
public void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback)
{
if (LOG.isDebugEnabled())
- LOG.debug("send {} {} {} {} last={}", this, stream, info, BufferUtil.toDetailString(content), lastContent);
+ LOG.debug("Sending {} {} {} {} last={}", this, stream, info, BufferUtil.toDetailString(content), lastContent);
if (stream.isClosed() || stream.isReset())
{
@@ -86,7 +98,6 @@
callback.failed(exception);
return;
}
- // new Throwable().printStackTrace();
// info==null content==null lastContent==false should not happen
// info==null content==null lastContent==true signals no more content - complete
@@ -149,7 +160,7 @@
{
// Is the stream still open?
if (stream.isClosed() || stream.isReset())
- // tell the callback about the EOF
+ // tell the callback about the EOF
callback.failed(new EofException("stream closed"));
else
// send the data and let it call the callback
@@ -171,7 +182,6 @@
else
// No data and no close so tell callback we are completed
callback.succeeded();
-
}
@Override
@@ -188,11 +198,10 @@
}
}
-
@Override
public void completed()
{
- LOG.debug("completed");
+ LOG.debug("Completed");
}
private void reply(Stream stream, ReplyInfo replyInfo)
@@ -206,65 +215,160 @@
short version = stream.getSession().getVersion();
if (responseHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().startsWith("200") && !stream.isClosed())
{
- // We have a 200 OK with some content to send, check the push strategy
- Fields.Field scheme = requestHeaders.get(HTTPSPDYHeader.SCHEME.name(version));
- Fields.Field host = requestHeaders.get(HTTPSPDYHeader.HOST.name(version));
- Fields.Field uri = requestHeaders.get(HTTPSPDYHeader.URI.name(version));
Set<String> pushResources = pushStrategy.apply(stream, requestHeaders, responseHeaders);
-
- for (String pushResource : pushResources)
+ if (pushResources.size() > 0)
{
- Fields pushHeaders = createPushHeaders(scheme, host, pushResource);
- final Fields pushRequestHeaders = createRequestHeaders(scheme, host, uri, pushResource);
-
- // TODO: handle the timeout better
- stream.push(new PushInfo(0, TimeUnit.MILLISECONDS, pushHeaders, false), new Promise.Adapter<Stream>()
- {
- @Override
- public void succeeded(Stream pushStream)
- {
- HttpChannelOverSPDY pushChannel = newHttpChannelOverSPDY(pushStream, pushRequestHeaders);
- pushChannel.requestStart(pushRequestHeaders, true);
- }
- });
+ PushResourceCoordinator pushResourceCoordinator = new PushResourceCoordinator(pushResources);
+ pushResourceCoordinator.coordinate();
}
}
}
- private Fields createRequestHeaders(Fields.Field scheme, Fields.Field host, Fields.Field uri, String pushResourcePath)
+ private static class PushHttpTransportOverSPDY extends HttpTransportOverSPDY
{
- final Fields newRequestHeaders = new Fields(requestHeaders, false);
- short version = stream.getSession().getVersion();
- newRequestHeaders.put(HTTPSPDYHeader.METHOD.name(version), "GET");
- newRequestHeaders.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1");
- newRequestHeaders.put(scheme);
- newRequestHeaders.put(host);
- newRequestHeaders.put(HTTPSPDYHeader.URI.name(version), pushResourcePath);
- String referrer = scheme.value() + "://" + host.value() + uri.value();
- newRequestHeaders.put("referer", referrer);
- newRequestHeaders.put("x-spdy-push", "true");
- return newRequestHeaders;
- }
+ private final PushResourceCoordinator coordinator;
- private Fields createPushHeaders(Fields.Field scheme, Fields.Field host, String pushResourcePath)
- {
- final Fields pushHeaders = new Fields();
- short version = stream.getSession().getVersion();
- if (version == SPDY.V2)
- pushHeaders.put(HTTPSPDYHeader.URI.name(version), scheme.value() + "://" + host.value() + pushResourcePath);
- else
+ private PushHttpTransportOverSPDY(Connector connector, HttpConfiguration configuration, EndPoint endPoint,
+ PushStrategy pushStrategy, Stream stream, Fields requestHeaders,
+ PushResourceCoordinator coordinator)
{
- pushHeaders.put(HTTPSPDYHeader.URI.name(version), pushResourcePath);
- pushHeaders.put(scheme);
- pushHeaders.put(host);
+ super(connector, configuration, endPoint, pushStrategy, stream, requestHeaders);
+ this.coordinator = coordinator;
}
- return pushHeaders;
+
+ @Override
+ public void completed()
+ {
+ Stream stream = getStream();
+ LOG.debug("Resource pushed for {} on {}",
+ getRequestHeaders().get(HTTPSPDYHeader.URI.name(stream.getSession().getVersion())), stream);
+ coordinator.complete();
+ }
}
- private HttpChannelOverSPDY newHttpChannelOverSPDY(Stream pushStream, Fields pushRequestHeaders)
+ private class PushResourceCoordinator
{
- HttpTransport transport = new HttpTransportOverSPDY(connector, configuration, endPoint, pushStrategy, pushStream, pushRequestHeaders);
- HttpInputOverSPDY input = new HttpInputOverSPDY();
- return new HttpChannelOverSPDY(connector, configuration, endPoint, transport, input, pushStream);
+ private final Queue<PushResource> queue = new ConcurrentArrayQueue<>();
+ private final Set<String> resources;
+ private boolean active;
+
+ private PushResourceCoordinator(Set<String> resources)
+ {
+ this.resources = resources;
+ }
+
+ private void coordinate()
+ {
+ // Must send all push frames to the client at once before we
+ // return from this method and send the main resource data
+ for (String pushResource : resources)
+ pushResource(pushResource);
+ }
+
+ private void sendNextResourceData()
+ {
+ PushResource resource;
+ synchronized (this)
+ {
+ if (active)
+ return;
+ resource = queue.poll();
+ if (resource == null)
+ return;
+ active = true;
+ }
+ HttpChannelOverSPDY pushChannel = newHttpChannelOverSPDY(resource.getPushStream(), resource.getPushRequestHeaders());
+ pushChannel.requestStart(resource.getPushRequestHeaders(), true);
+ }
+
+ private HttpChannelOverSPDY newHttpChannelOverSPDY(Stream pushStream, Fields pushRequestHeaders)
+ {
+ HttpTransport transport = new PushHttpTransportOverSPDY(connector, configuration, endPoint, pushStrategy,
+ pushStream, pushRequestHeaders, this);
+ HttpInputOverSPDY input = new HttpInputOverSPDY();
+ return new HttpChannelOverSPDY(connector, configuration, endPoint, transport, input, pushStream);
+ }
+
+ private void pushResource(String pushResource)
+ {
+ final short version = stream.getSession().getVersion();
+ Fields.Field scheme = requestHeaders.get(HTTPSPDYHeader.SCHEME.name(version));
+ Fields.Field host = requestHeaders.get(HTTPSPDYHeader.HOST.name(version));
+ Fields.Field uri = requestHeaders.get(HTTPSPDYHeader.URI.name(version));
+ final Fields pushHeaders = createPushHeaders(scheme, host, pushResource);
+ final Fields pushRequestHeaders = createRequestHeaders(scheme, host, uri, pushResource);
+
+ stream.push(new PushInfo(pushHeaders, false), new Promise.Adapter<Stream>()
+ {
+ @Override
+ public void succeeded(Stream pushStream)
+ {
+ LOG.debug("Headers pushed for {} on {}", pushHeaders.get(HTTPSPDYHeader.URI.name(version)), pushStream);
+ queue.offer(new PushResource(pushStream, pushRequestHeaders));
+ sendNextResourceData();
+ }
+ });
+ }
+
+ private Fields createRequestHeaders(Fields.Field scheme, Fields.Field host, Fields.Field uri, String pushResourcePath)
+ {
+ final Fields newRequestHeaders = new Fields(requestHeaders, false);
+ short version = stream.getSession().getVersion();
+ newRequestHeaders.put(HTTPSPDYHeader.METHOD.name(version), "GET");
+ newRequestHeaders.put(HTTPSPDYHeader.VERSION.name(version), "HTTP/1.1");
+ newRequestHeaders.put(scheme);
+ newRequestHeaders.put(host);
+ newRequestHeaders.put(HTTPSPDYHeader.URI.name(version), pushResourcePath);
+ String referrer = scheme.value() + "://" + host.value() + uri.value();
+ newRequestHeaders.put("referer", referrer);
+ newRequestHeaders.put("x-spdy-push", "true");
+ return newRequestHeaders;
+ }
+
+ private Fields createPushHeaders(Fields.Field scheme, Fields.Field host, String pushResourcePath)
+ {
+ final Fields pushHeaders = new Fields();
+ short version = stream.getSession().getVersion();
+ if (version == SPDY.V2)
+ pushHeaders.put(HTTPSPDYHeader.URI.name(version), scheme.value() + "://" + host.value() + pushResourcePath);
+ else
+ {
+ pushHeaders.put(HTTPSPDYHeader.URI.name(version), pushResourcePath);
+ pushHeaders.put(scheme);
+ pushHeaders.put(host);
+ }
+ return pushHeaders;
+ }
+
+ private void complete()
+ {
+ synchronized (this)
+ {
+ active = false;
+ }
+ sendNextResourceData();
+ }
+ }
+
+ private static class PushResource
+ {
+ private final Stream pushStream;
+ private final Fields pushRequestHeaders;
+
+ public PushResource(Stream pushStream, Fields pushRequestHeaders)
+ {
+ this.pushStream = pushStream;
+ this.pushRequestHeaders = pushRequestHeaders;
+ }
+
+ public Stream getPushStream()
+ {
+ return pushStream;
+ }
+
+ public Fields getPushRequestHeaders()
+ {
+ return pushRequestHeaders;
+ }
}
}
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/PushStrategy.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/PushStrategy.java
index 0e905c7..43bb3cc 100644
--- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/PushStrategy.java
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/PushStrategy.java
@@ -34,6 +34,8 @@
/**
* <p>Applies the SPDY push logic for the primary resource.</p>
*
+ *
+ *
* @param stream the primary resource stream
* @param requestHeaders the primary resource request headers
* @param responseHeaders the primary resource response headers
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategy.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategy.java
index be893aa..2178869 100644
--- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategy.java
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategy.java
@@ -27,6 +27,7 @@
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;
@@ -262,7 +263,7 @@
private class MainResource
{
private final String name;
- private final Set<String> resources = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
+ private final CopyOnWriteArraySet<String> resources = new CopyOnWriteArraySet<>();
private final AtomicLong firstResourceAdded = new AtomicLong(-1);
private MainResource(String name)
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPSPDYConnection.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPSPDYConnection.java
index e960d12..2c05ec7 100644
--- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPSPDYConnection.java
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPSPDYConnection.java
@@ -158,10 +158,9 @@
}
@Override
- public boolean earlyEOF()
+ public void earlyEOF()
{
// TODO
- return false;
}
@Override
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyTest.java
index 099f736..c2e1b7d 100644
--- a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyTest.java
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyTest.java
@@ -22,9 +22,13 @@
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.util.Arrays;
+import java.util.Random;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -41,6 +45,8 @@
import org.eclipse.jetty.spdy.api.SPDY;
import org.eclipse.jetty.spdy.api.Session;
import org.eclipse.jetty.spdy.api.SessionFrameListener;
+import org.eclipse.jetty.spdy.api.Settings;
+import org.eclipse.jetty.spdy.api.SettingsInfo;
import org.eclipse.jetty.spdy.api.Stream;
import org.eclipse.jetty.spdy.api.StreamFrameListener;
import org.eclipse.jetty.spdy.api.StreamStatus;
@@ -56,6 +62,7 @@
import org.junit.Test;
import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertThat;
public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest
@@ -159,25 +166,186 @@
public void testMaxConcurrentStreamsToDisablePush() throws Exception
{
final CountDownLatch pushReceivedLatch = new CountDownLatch(1);
- Session session = sendMainRequestAndCSSRequest(new SessionFrameListener.Adapter()
+
+ Session pushCacheBuildSession = startClient(version, serverAddress, null);
+
+ pushCacheBuildSession.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter());
+ pushCacheBuildSession.syn(new SynInfo(associatedCSSRequestHeaders, true), new StreamFrameListener.Adapter());
+
+ Session session = startClient(version, serverAddress, null);
+
+ Settings settings = new Settings();
+ settings.put(new Settings.Setting(Settings.ID.MAX_CONCURRENT_STREAMS, 0));
+ SettingsInfo settingsInfo = new SettingsInfo(settings);
+ session.settings(settingsInfo);
+
+ session.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
{
@Override
- public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+ public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
{
pushReceivedLatch.countDown();
+ return super.onPush(stream, pushInfo);
+ }
+ });
+
+ assertThat("No push stream is received", pushReceivedLatch.await(1, TimeUnit.SECONDS), is(false));
+ }
+
+ @Test
+ public void testPushResourceOrder() throws Exception
+ {
+ final CountDownLatch allExpectedPushesReceivedLatch = new CountDownLatch(4);
+
+ Session pushCacheBuildSession = startClient(version, serverAddress, null);
+
+ sendRequest(pushCacheBuildSession, mainRequestHeaders, null, null);
+ sendRequest(pushCacheBuildSession, associatedCSSRequestHeaders, null, null);
+ sendRequest(pushCacheBuildSession, associatedJSRequestHeaders, null, null);
+ sendRequest(pushCacheBuildSession, createHeaders("/image1.jpg", mainResource), null, null);
+ sendRequest(pushCacheBuildSession, createHeaders("/image2.jpg", mainResource), null, null);
+
+ Session session = startClient(version, serverAddress, null);
+
+ session.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
+ {
+ @Override
+ public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+ {
+ LOG.info("onPush: stream: {}, pushInfo: {}", stream, pushInfo);
+ String uriHeader = pushInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version)).value();
+ switch ((int)allExpectedPushesReceivedLatch.getCount())
+ {
+ case 4:
+ assertThat("1st pushed resource is the css", uriHeader.endsWith("css"), is(true));
+ break;
+ case 3:
+ assertThat("2nd pushed resource is the js", uriHeader.endsWith("js"), is(true));
+ break;
+ case 2:
+ assertThat("3rd pushed resource is image1", uriHeader.endsWith("image1.jpg"),
+ is(true));
+ break;
+ case 1:
+ assertThat("4th pushed resource is image2", uriHeader.endsWith("image2.jpg"),
+ is(true));
+ break;
+ }
+ allExpectedPushesReceivedLatch.countDown();
+ return super.onPush(stream, pushInfo);
+ }
+ });
+
+ assertThat("All expected push resources have been received", allExpectedPushesReceivedLatch.await(5,
+ TimeUnit.SECONDS), is(true));
+ }
+
+ @Test
+ public void testThatPushResourcesAreUnique() throws Exception
+ {
+ final CountDownLatch pushReceivedLatch = new CountDownLatch(2);
+ sendMainRequestAndCSSRequest(null);
+ sendMainRequestAndCSSRequest(null);
+
+ Session session = startClient(version, serverAddress, null);
+
+ session.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
+ {
+ @Override
+ public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+ {
+ pushReceivedLatch.countDown();
+ LOG.info("Push received: {}", pushInfo);
return null;
}
});
-// Settings settings = new Settings();
-// settings.put(new Settings.Setting(Settings.ID.MAX_CONCURRENT_STREAMS, 0));
-// SettingsInfo settingsInfo = new SettingsInfo(settings);
-//
-// session.settings(settingsInfo);
+ assertThat("style.css has been pushed only once", pushReceivedLatch.await(1, TimeUnit.SECONDS), is(false));
+ }
- sendRequest(session, mainRequestHeaders, null, null);
+ @Test
+ public void testPushResourceAreSentNonInterleaved() throws Exception
+ {
+ final CountDownLatch allExpectedPushesReceivedLatch = new CountDownLatch(4);
+ final CountDownLatch allPushDataReceivedLatch = new CountDownLatch(4);
+ final CopyOnWriteArrayList<Integer> dataReceivedOrder = new CopyOnWriteArrayList<>();
- assertThat(pushReceivedLatch.await(1, TimeUnit.SECONDS), is(false));
+ InetSocketAddress bigResponseServerAddress = startHTTPServer(version, new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ byte[] bytes = new byte[32768];
+ new Random().nextBytes(bytes);
+ ServletOutputStream outputStream = response.getOutputStream();
+ outputStream.write(bytes);
+ baseRequest.setHandled(true);
+ }
+ });
+ Session pushCacheBuildSession = startClient(version, bigResponseServerAddress, null);
+
+ Fields mainResourceHeaders = createHeadersWithoutReferrer(mainResource);
+ sendRequest(pushCacheBuildSession, mainResourceHeaders, null, null);
+ sendRequest(pushCacheBuildSession, createHeaders("/style.css", mainResource), null, null);
+ sendRequest(pushCacheBuildSession, createHeaders("/javascript.js", mainResource), null, null);
+ sendRequest(pushCacheBuildSession, createHeaders("/image1.jpg", mainResource), null, null);
+ sendRequest(pushCacheBuildSession, createHeaders("/image2.jpg", mainResource), null, null);
+
+ Session session = startClient(version, bigResponseServerAddress, null);
+
+ session.syn(new SynInfo(mainResourceHeaders, true), new StreamFrameListener.Adapter()
+ {
+ AtomicInteger currentStreamId = new AtomicInteger(2);
+
+ @Override
+ public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
+ {
+ LOG.info("Received push for stream: {} {}", stream.getId(), pushInfo);
+ String uriHeader = pushInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version)).value();
+ switch ((int)allExpectedPushesReceivedLatch.getCount())
+ {
+ case 4:
+ assertThat("1st pushed resource is the css", uriHeader.endsWith("css"), is(true));
+ break;
+ case 3:
+ assertThat("2nd pushed resource is the js", uriHeader.endsWith("js"), is(true));
+ break;
+ case 2:
+ assertThat("3rd pushed resource is image1", uriHeader.endsWith("image1.jpg"),
+ is(true));
+ break;
+ case 1:
+ assertThat("4th pushed resource is image2", uriHeader.endsWith("image2.jpg"),
+ is(true));
+ break;
+ }
+ allExpectedPushesReceivedLatch.countDown();
+ return new Adapter()
+ {
+ @Override
+ public void onData(Stream stream, DataInfo dataInfo)
+ {
+ if (stream.getId() != currentStreamId.get())
+ throw new IllegalStateException("Streams interleaved. Expected StreamId: " +
+ currentStreamId + " but was: " + stream.getId());
+ dataInfo.consume(dataInfo.available());
+ if (dataInfo.isClose())
+ {
+ currentStreamId.compareAndSet(currentStreamId.get(), currentStreamId.get() + 2);
+ dataReceivedOrder.add(stream.getId());
+ allPushDataReceivedLatch.countDown();
+ }
+ LOG.info(stream.getId() + ":" + dataInfo);
+ }
+ };
+ }
+ });
+
+ assertThat("All push resources received", allExpectedPushesReceivedLatch.await(5, TimeUnit.SECONDS), is(true));
+ assertThat("All pushData received", allPushDataReceivedLatch.await(5, TimeUnit.SECONDS), is(true));
+ assertThat("The data for different push streams has not been interleaved",
+ dataReceivedOrder.toString(), equalTo("[2, 4, 6, 8]"));
+ LOG.info(dataReceivedOrder.toString());
}
private InetSocketAddress createServer() throws Exception
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyUnitTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyUnitTest.java
index d11c84a..7d038bc 100644
--- a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyUnitTest.java
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ReferrerPushStrategyUnitTest.java
@@ -18,10 +18,6 @@
package org.eclipse.jetty.spdy.server.http;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
-import static org.mockito.Mockito.when;
-
import java.util.Arrays;
import java.util.Set;
@@ -35,6 +31,10 @@
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.when;
+
@RunWith(MockitoJUnitRunner.class)
public class ReferrerPushStrategyUnitTest
{
diff --git a/jetty-spdy/spdy-http-server/src/test/resources/jetty-logging.properties b/jetty-spdy/spdy-http-server/src/test/resources/jetty-logging.properties
index 4286a47..30da0a8 100644
--- a/jetty-spdy/spdy-http-server/src/test/resources/jetty-logging.properties
+++ b/jetty-spdy/spdy-http-server/src/test/resources/jetty-logging.properties
@@ -4,4 +4,5 @@
#org.eclipse.jetty.io.ssl.LEVEL=DEBUG
#org.eclipse.jetty.spdy.LEVEL=DEBUG
#org.eclipse.jetty.client.LEVEL=DEBUG
+#org.eclipse.jetty.spdy.server.http.ReferrerPushStrategy.LEVEL=DEBUG
#org.mortbay.LEVEL=DEBUG
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java
index 0be66d8..3ea743a 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java
@@ -39,6 +39,7 @@
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
+import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -50,6 +51,8 @@
import java.util.Properties;
import java.util.Set;
+import javax.naming.OperationNotSupportedException;
+
/*-------------------------------------------*/
/**
* <p>
@@ -175,6 +178,12 @@
stop(port,key,timeout);
return null;
}
+
+ if (arg.startsWith("--download="))
+ {
+ download(arg);
+ continue;
+ }
if ("--version".equals(arg) || "-v".equals(arg) || "--info".equals(arg))
{
@@ -302,6 +311,53 @@
return xmls;
}
+ private void download(String arg)
+ {
+ try
+ {
+ String[] split = arg.split(":",3);
+ if (split.length!=3 || "http".equalsIgnoreCase(split[0]) || !split[1].startsWith("//"))
+ throw new IllegalArgumentException("Not --download=<http uri>:<location>");
+
+ String location=split[2];
+ if (File.separatorChar!='/')
+ location.replaceAll("/",File.separator);
+ File file = new File(location);
+
+ if (Config.isDebug())
+ System.err.println("Download to "+file.getAbsolutePath()+(file.exists()?" Exists!":""));
+ if (file.exists())
+ return;
+
+ URL url = new URL(split[0].substring(11)+":"+split[1]);
+
+ System.err.println("DOWNLOAD: "+url+" to "+location);
+
+ if (!file.getParentFile().exists())
+ file.getParentFile().mkdirs();
+
+ byte[] buf=new byte[8192];
+ try (InputStream in = url.openStream(); OutputStream out = new FileOutputStream(file);)
+ {
+ while(true)
+ {
+ int len = in.read(buf);
+
+ if (len>0)
+ out.write(buf,0,len);
+ if (len<0)
+ break;
+ }
+ }
+ }
+ catch(Exception e)
+ {
+ System.err.println("ERROR: processing "+arg+"\n"+e);
+ e.printStackTrace();
+ usageExit(EXIT_USAGE);
+ }
+ }
+
private void usage()
{
String usageResource = "org/eclipse/jetty/start/usage.txt";
diff --git a/jetty-start/src/main/resources/org/eclipse/jetty/start/usage.txt b/jetty-start/src/main/resources/org/eclipse/jetty/start/usage.txt
index 7272c5f..e25a73d 100644
--- a/jetty-start/src/main/resources/org/eclipse/jetty/start/usage.txt
+++ b/jetty-start/src/main/resources/org/eclipse/jetty/start/usage.txt
@@ -45,6 +45,10 @@
and additional .ini files in jetty.home/start.d/
will NOT be read. A --ini option with no file indicates that
start.ini should not be read.
+
+ --download=<http-uri>:location
+ If the file does not exist at the given location, then
+ download it from the given http URI
System Properties:
These are set with a command line like "java -Dname=value ..." and are
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java
index 312bd96..ba5c9f9 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java
@@ -131,8 +131,6 @@
_tree[row]=c;
_tree[last]=(char)t;
last=row+EQ;
- t=0;
- break;
}
int row=ROW_SIZE*t;
@@ -419,4 +417,22 @@
{
return _rows+1==_key.length;
}
+
+
+ public void dump()
+ {
+ for (int r=0;r<=_rows;r++)
+ {
+ char c=_tree[r*ROW_SIZE+0];
+ System.err.printf("%4d [%s,%d,%d,%d] %s:%s%n",
+ r,
+ (c<' '||c>127)?(""+(int)c):"'"+c+"'",
+ (int)_tree[r*ROW_SIZE+LO],
+ (int)_tree[r*ROW_SIZE+EQ],
+ (int)_tree[r*ROW_SIZE+HI],
+ _key[r],
+ _value[r]);
+ }
+
+ }
}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java
index a3e549e..ddae8d0 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java
@@ -657,6 +657,30 @@
return false;
}
+ public static void appendSchemeHostPort(StringBuilder url,String scheme,String server, int port)
+ {
+ if (server.indexOf(':')>=0&&server.charAt(0)!='[')
+ url.append(scheme).append("://").append('[').append(server).append(']');
+ else
+ url.append(scheme).append("://").append(server);
+
+ if (port > 0 && (("http".equalsIgnoreCase(scheme) && port != 80) || ("https".equalsIgnoreCase(scheme) && port != 443)))
+ url.append(':').append(port);
+ }
+
+ public static void appendSchemeHostPort(StringBuffer url,String scheme,String server, int port)
+ {
+ synchronized (url)
+ {
+ if (server.indexOf(':')>=0&&server.charAt(0)!='[')
+ url.append(scheme).append("://").append('[').append(server).append(']');
+ else
+ url.append(scheme).append("://").append(server);
+
+ if (port > 0 && (("http".equalsIgnoreCase(scheme) && port != 80) || ("https".equalsIgnoreCase(scheme) && port != 443)))
+ url.append(':').append(port);
+ }
+ }
}
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeResponse.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeResponse.java
index 7c9e971..2751fc8 100644
--- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeResponse.java
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeResponse.java
@@ -22,8 +22,9 @@
import java.nio.ByteBuffer;
import org.eclipse.jetty.websocket.api.UpgradeResponse;
+import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParseListener;
-public class ClientUpgradeResponse extends UpgradeResponse
+public class ClientUpgradeResponse extends UpgradeResponse implements HttpResponseHeaderParseListener
{
private ByteBuffer remainingBuffer;
@@ -43,6 +44,7 @@
throw new UnsupportedOperationException("Not supported on client implementation");
}
+ @Override
public void setRemainingBuffer(ByteBuffer remainingBuffer)
{
this.remainingBuffer = remainingBuffer;
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeConnection.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeConnection.java
index b37a02a..c24a17b 100644
--- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeConnection.java
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeConnection.java
@@ -39,11 +39,12 @@
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.ClientUpgradeResponse;
-import org.eclipse.jetty.websocket.client.io.HttpResponseHeaderParser.ParseException;
import org.eclipse.jetty.websocket.common.AcceptHash;
import org.eclipse.jetty.websocket.common.WebSocketSession;
import org.eclipse.jetty.websocket.common.events.EventDriver;
import org.eclipse.jetty.websocket.common.extensions.ExtensionStack;
+import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParser;
+import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParser.ParseException;
/**
* This is the initial connection handling that exists immediately after physical connection is established to destination server.
@@ -92,7 +93,7 @@
this.request = connectPromise.getRequest();
// Setup the parser
- this.parser = new HttpResponseHeaderParser();
+ this.parser = new HttpResponseHeaderParser(new ClientUpgradeResponse());
}
public void disconnect(boolean onlyOutput)
@@ -173,7 +174,7 @@
{
LOG.debug("Filled {} bytes - {}",filled,BufferUtil.toDetailString(buffer));
}
- ClientUpgradeResponse resp = parser.parse(buffer);
+ ClientUpgradeResponse resp = (ClientUpgradeResponse)parser.parse(buffer);
if (resp != null)
{
// Got a response!
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TimeoutTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TimeoutTest.java
index f404e6c..ecfd50c 100644
--- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TimeoutTest.java
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TimeoutTest.java
@@ -104,8 +104,8 @@
// Make sure idle timeout takes less than 5 total seconds
Assert.assertThat("Idle Timeout",dur,lessThanOrEqualTo(5000L));
- // Client should see a close event, with status NO_CLOSE
- wsocket.assertCloseCode(StatusCode.NORMAL);
+ // Client should see a close event, with status SHUTDOWN
+ wsocket.assertCloseCode(StatusCode.SHUTDOWN);
}
finally
{
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TrackingSocket.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TrackingSocket.java
index c55c103..eb1c4ae 100644
--- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TrackingSocket.java
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TrackingSocket.java
@@ -20,13 +20,12 @@
import static org.hamcrest.Matchers.*;
-import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Exchanger;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-import org.eclipse.jetty.util.BlockingArrayQueue;
+import org.eclipse.jetty.toolchain.test.EventQueue;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.Session;
@@ -46,8 +45,8 @@
public CountDownLatch openLatch = new CountDownLatch(1);
public CountDownLatch closeLatch = new CountDownLatch(1);
public CountDownLatch dataLatch = new CountDownLatch(1);
- public BlockingQueue<String> messageQueue = new BlockingArrayQueue<String>();
- public BlockingQueue<Throwable> errorQueue = new BlockingArrayQueue<>();
+ public EventQueue<String> messageQueue = new EventQueue<>();
+ public EventQueue<Throwable> errorQueue = new EventQueue<>();
public void assertClose(int expectedStatusCode, String expectedReason) throws InterruptedException
{
@@ -93,29 +92,9 @@
Assert.assertThat("Was Opened",openLatch.await(500,TimeUnit.MILLISECONDS),is(true));
}
- public void awaitMessage(int expectedMessageCount, TimeUnit timeoutUnit, int timeoutDuration) throws TimeoutException
+ public void awaitMessage(int expectedMessageCount, TimeUnit timeoutUnit, int timeoutDuration) throws TimeoutException, InterruptedException
{
- long msDur = TimeUnit.MILLISECONDS.convert(timeoutDuration,timeoutUnit);
- long now = System.currentTimeMillis();
- long expireOn = now + msDur;
- LOG.debug("Await Message.. Now: {} - expireOn: {} ({} ms)",now,expireOn,msDur);
-
- while (messageQueue.size() < expectedMessageCount)
- {
- try
- {
- TimeUnit.MILLISECONDS.sleep(20);
- }
- catch (InterruptedException gnore)
- {
- /* ignore */
- }
- if (!LOG.isDebugEnabled() && (System.currentTimeMillis() > expireOn))
- {
- throw new TimeoutException(String.format("Timeout reading all %d expected messages. (managed to only read %d messages)",expectedMessageCount,
- messageQueue.size()));
- }
- }
+ messageQueue.awaitEventCount(expectedMessageCount,timeoutDuration,timeoutUnit);
}
public void clear()
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/blockhead/BlockheadServer.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/blockhead/BlockheadServer.java
index b4d5ea4..cb19368 100644
--- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/blockhead/BlockheadServer.java
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/blockhead/BlockheadServer.java
@@ -54,6 +54,7 @@
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.api.extensions.Frame.Type;
import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
import org.eclipse.jetty.websocket.common.AcceptHash;
@@ -219,6 +220,12 @@
}
WebSocketFrame copy = new WebSocketFrame(frame);
incomingFrames.incomingFrame(copy);
+
+ if (frame.getType() == Type.CLOSE)
+ {
+ CloseInfo close = new CloseInfo(frame);
+ LOG.debug("Close frame: {}",close);
+ }
}
@Override
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/internal/io/HttpResponseHeaderParserTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/internal/io/HttpResponseHeaderParserTest.java
deleted file mode 100644
index d2f8822..0000000
--- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/internal/io/HttpResponseHeaderParserTest.java
+++ /dev/null
@@ -1,145 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2013 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.websocket.client.internal.io;
-
-import static org.hamcrest.Matchers.*;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.eclipse.jetty.toolchain.test.TestTracker;
-import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.websocket.api.UpgradeResponse;
-import org.eclipse.jetty.websocket.client.io.HttpResponseHeaderParser;
-import org.junit.Assert;
-import org.junit.Rule;
-import org.junit.Test;
-
-public class HttpResponseHeaderParserTest
-{
- @Rule
- public TestTracker tt = new TestTracker();
-
- private void appendUtf8(ByteBuffer buf, String line)
- {
- buf.put(ByteBuffer.wrap(StringUtil.getBytes(line,StringUtil.__UTF8)));
- }
-
- @Test
- public void testParseRealWorldResponse()
- {
- // Arbitrary Http Response Headers seen in the wild.
- // Request URI -> https://ssl.google-analytics.com/__utm.gif
- List<String> expected = new ArrayList<>();
- expected.add("HTTP/1.0 200 OK");
- expected.add("Date: Thu, 09 Aug 2012 16:16:39 GMT");
- expected.add("Content-Length: 35");
- expected.add("X-Content-Type-Options: nosniff");
- expected.add("Pragma: no-cache");
- expected.add("Expires: Wed, 19 Apr 2000 11:43:00 GMT");
- expected.add("Last-Modified: Wed, 21 Jan 2004 19:51:30 GMT");
- expected.add("Content-Type: image/gif");
- expected.add("Cache-Control: private, no-cache, no-cache=Set-Cookie, proxy-revalidate");
- expected.add("Age: 518097");
- expected.add("Server: GFE/2.0");
- expected.add("Connection: Keep-Alive");
- expected.add("");
-
- // Prepare Buffer
- ByteBuffer buf = ByteBuffer.allocate(512);
- for (String line : expected)
- {
- appendUtf8(buf,line + "\r\n");
- }
-
- BufferUtil.flipToFlush(buf,0);
-
- // Parse Buffer
- HttpResponseHeaderParser parser = new HttpResponseHeaderParser();
- UpgradeResponse response = parser.parse(buf);
- Assert.assertThat("Response",response,notNullValue());
-
- Assert.assertThat("Response.statusCode",response.getStatusCode(),is(200));
- Assert.assertThat("Response.statusReason",response.getStatusReason(),is("OK"));
-
- Assert.assertThat("Response.header[age]",response.getHeader("age"),is("518097"));
- }
-
- @Test
- public void testParseRealWorldResponse_SmallBuffers()
- {
- // Arbitrary Http Response Headers seen in the wild.
- // Request URI -> https://ssl.google-analytics.com/__utm.gif
- List<String> expected = new ArrayList<>();
- expected.add("HTTP/1.0 200 OK");
- expected.add("Date: Thu, 09 Aug 2012 16:16:39 GMT");
- expected.add("Content-Length: 35");
- expected.add("X-Content-Type-Options: nosniff");
- expected.add("Pragma: no-cache");
- expected.add("Expires: Wed, 19 Apr 2000 11:43:00 GMT");
- expected.add("Last-Modified: Wed, 21 Jan 2004 19:51:30 GMT");
- expected.add("Content-Type: image/gif");
- expected.add("Cache-Control: private, no-cache, no-cache=Set-Cookie, proxy-revalidate");
- expected.add("Age: 518097");
- expected.add("Server: GFE/2.0");
- expected.add("Connection: Keep-Alive");
- expected.add("");
-
- // Prepare Buffer
- ByteBuffer buf = ByteBuffer.allocate(512);
- for (String line : expected)
- {
- appendUtf8(buf,line + "\r\n");
- }
- BufferUtil.flipToFlush(buf,0);
-
- // Prepare small buffers to simulate a slow read/fill/parse from the network
- ByteBuffer small1 = buf.slice();
- ByteBuffer small2 = buf.slice();
- ByteBuffer small3 = buf.slice();
-
- small1.limit(50);
- small2.position(50);
- small2.limit(70);
- small3.position(70);
-
- // Parse Buffer
- HttpResponseHeaderParser parser = new HttpResponseHeaderParser();
- UpgradeResponse response;
-
- // Parse small 1
- response = parser.parse(small1);
- Assert.assertThat("Small 1",response,nullValue());
-
- // Parse small 2
- response = parser.parse(small2);
- Assert.assertThat("Small 2",response,nullValue());
-
- // Parse small 3
- response = parser.parse(small3);
- Assert.assertThat("Small 3",response,notNullValue());
-
- Assert.assertThat("Response.statusCode",response.getStatusCode(),is(200));
- Assert.assertThat("Response.statusReason",response.getStatusReason(),is("OK"));
-
- Assert.assertThat("Response.header[age]",response.getHeader("age"),is("518097"));
- }
-}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/ConnectionState.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/ConnectionState.java
index d40073f..aefc719 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/ConnectionState.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/ConnectionState.java
@@ -18,13 +18,43 @@
package org.eclipse.jetty.websocket.common;
+import org.eclipse.jetty.websocket.common.io.IOState;
+
/**
* Connection states as outlined in <a href="https://tools.ietf.org/html/rfc6455">RFC6455</a>.
*/
public enum ConnectionState
{
+ /** [RFC] Initial state of a connection, the upgrade request / response is in progress */
CONNECTING,
+ /**
+ * [Impl] Intermediate state between CONNECTING and OPEN, used to indicate that a upgrade request/response is successful, but the end-user provided socket's
+ * onOpen code has yet to run.
+ * <p>
+ * This state is to allow the local socket to initiate messages and frames, but to NOT start reading yet.
+ */
+ CONNECTED,
+ /**
+ * [RFC] The websocket connection is established and open.
+ * <p>
+ * This indicates that the Upgrade has succeed, and the end-user provided socket's onOpen code has completed.
+ * <p>
+ * It is now time to start reading from the remote endpoint.
+ */
OPEN,
+ /**
+ * [RFC] The websocket closing handshake is started.
+ * <p>
+ * This can be considered a half-closed state.
+ * <p>
+ * When receiving this as an event on {@link IOState.ConnectionStateListener#onConnectionStateChange(ConnectionState)} a close frame should be sent using
+ * the {@link CloseInfo} available from {@link IOState#getCloseInfo()}
+ */
CLOSING,
+ /**
+ * [RFC] The websocket connection is closed.
+ * <p>
+ * Connection should be disconnected and no further reads or writes should occur.
+ */
CLOSED;
}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java
index 8abdfff..7ca103d 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java
@@ -48,9 +48,11 @@
import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
import org.eclipse.jetty.websocket.common.events.EventDriver;
+import org.eclipse.jetty.websocket.common.io.IOState;
+import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
-@ManagedObject
-public class WebSocketSession extends ContainerLifeCycle implements Session, IncomingFrames
+@ManagedObject("A Jetty WebSocket Session")
+public class WebSocketSession extends ContainerLifeCycle implements Session, IncomingFrames, ConnectionStateListener
{
private static final Logger LOG = Log.getLogger(WebSocketSession.class);
private final URI requestURI;
@@ -80,6 +82,8 @@
this.outgoingHandler = connection;
this.incomingHandler = websocket;
+ this.connection.getIOState().addListener(this);
+
// Get the parameter map (use the jetty MultiMap to do this right)
MultiMap<String> params = new MultiMap<>();
String query = requestURI.getQuery();
@@ -254,6 +258,11 @@
return remote.getInetSocketAddress();
}
+ public URI getRequestURI()
+ {
+ return requestURI;
+ }
+
@Override
public UpgradeRequest getUpgradeRequest()
{
@@ -281,12 +290,11 @@
@Override
public void incomingError(WebSocketException e)
{
- if (connection.getIOState().isInputClosed())
+ if (connection.getIOState().isInputAvailable())
{
- return; // input is closed
+ // Forward Errors to User WebSocket Object
+ websocket.incomingError(e);
}
- // Forward Errors to User WebSocket Object
- websocket.incomingError(e);
}
/**
@@ -295,13 +303,11 @@
@Override
public void incomingFrame(Frame frame)
{
- if (connection.getIOState().isInputClosed())
+ if (connection.getIOState().isInputAvailable())
{
- return; // input is closed
+ // Forward Frames Through Extension List
+ incomingHandler.incomingFrame(frame);
}
-
- // Forward Frames Through Extension List
- incomingHandler.incomingFrame(frame);
}
@Override
@@ -332,6 +338,24 @@
websocket.onClose(new CloseInfo(statusCode,reason));
}
+ @Override
+ public void onConnectionStateChange(ConnectionState state)
+ {
+ if (state == ConnectionState.CLOSED)
+ {
+ IOState ioState = this.connection.getIOState();
+ // The session only cares about abnormal close, as we need to notify
+ // the endpoint of this close scenario.
+ if (ioState.wasAbnormalClose())
+ {
+ CloseInfo close = ioState.getCloseInfo();
+ LOG.debug("Detected abnormal close: {}",close);
+ // notify local endpoint
+ notifyClose(close.getStatusCode(),close.getReason());
+ }
+ }
+ }
+
/**
* Open/Activate the session
*
@@ -345,12 +369,18 @@
return;
}
+ // Upgrade success
+ connection.getIOState().onConnected();
+
// Connect remote
remote = new WebSocketRemoteEndpoint(connection,outgoingHandler);
// Open WebSocket
websocket.openSession(this);
+ // Open connection
+ connection.getIOState().onOpened();
+
if (LOG.isDebugEnabled())
{
LOG.debug("open -> {}",dump());
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriver.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriver.java
index bf74188..d217937 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriver.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriver.java
@@ -103,16 +103,7 @@
onClose(close);
// process handshake
- if (session.getConnection().getIOState().onCloseHandshake(true))
- {
- // handshake resolved, disconnect.
- session.getConnection().disconnect();
- }
- else
- {
- // respond
- session.close(close.getStatusCode(),close.getReason());
- }
+ session.getConnection().getIOState().onCloseRemote(close);
return;
}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxChannel.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxChannel.java
index b8bd825..c3c42ce 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxChannel.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxChannel.java
@@ -38,11 +38,12 @@
import org.eclipse.jetty.websocket.common.WebSocketSession;
import org.eclipse.jetty.websocket.common.io.FutureWriteCallback;
import org.eclipse.jetty.websocket.common.io.IOState;
+import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
/**
* MuxChannel, acts as WebSocketConnection for specific sub-channel.
*/
-public class MuxChannel implements LogicalConnection, IncomingFrames, SuspendToken
+public class MuxChannel implements LogicalConnection, IncomingFrames, SuspendToken, ConnectionStateListener
{
private static final Logger LOG = Log.getLogger(MuxChannel.class);
@@ -65,7 +66,7 @@
this.suspendToken = new AtomicBoolean(false);
this.ioState = new IOState();
- ioState.setState(ConnectionState.CONNECTING);
+ this.ioState.addListener(this);
this.inputClosed = new AtomicBoolean(false);
this.outputClosed = new AtomicBoolean(false);
@@ -88,7 +89,6 @@
@Override
public void disconnect()
{
- this.ioState.setState(ConnectionState.CLOSED);
// TODO: disconnect the virtual end-point?
}
@@ -173,12 +173,18 @@
public void onClose()
{
- this.ioState.setState(ConnectionState.CLOSED);
+ }
+
+ @Override
+ public void onConnectionStateChange(ConnectionState state)
+ {
+ // TODO Auto-generated method stub
+
}
public void onOpen()
{
- this.ioState.setState(ConnectionState.OPEN);
+ this.ioState.onOpened();
}
/**
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java
index f3dffcb..cf37519 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java
@@ -56,11 +56,12 @@
import org.eclipse.jetty.websocket.common.LogicalConnection;
import org.eclipse.jetty.websocket.common.Parser;
import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
/**
* Provides the implementation of {@link WebSocketConnection} within the framework of the new {@link Connection} framework of jetty-io
*/
-public abstract class AbstractWebSocketConnection extends AbstractConnection implements LogicalConnection
+public abstract class AbstractWebSocketConnection extends AbstractConnection implements LogicalConnection, ConnectionStateListener
{
private class FlushCallback implements Callback
{
@@ -141,21 +142,6 @@
}
}
- private class OnCloseCallback implements WriteCallback
- {
- @Override
- public void writeFailed(Throwable x)
- {
- disconnect();
- }
-
- @Override
- public void writeSuccess()
- {
- onWriteWebSocketClose();
- }
- }
-
public static class Stats
{
private AtomicLong countFillInterestedEvents = new AtomicLong(0);
@@ -211,7 +197,7 @@
this.extensions = new ArrayList<>();
this.suspendToken = new AtomicBoolean(false);
this.ioState = new IOState();
- this.ioState.setState(ConnectionState.CONNECTING);
+ this.ioState.addListener(this);
this.writeBytes = new WriteBytesProvider(generator,new FlushCallback());
this.setInputBufferSize(policy.getInputBufferSize());
}
@@ -247,12 +233,18 @@
@Override
public void disconnect()
{
+ synchronized (writeBytes)
+ {
+ if (!writeBytes.isClosed())
+ {
+ writeBytes.close();
+ }
+ }
disconnect(false);
}
public void disconnect(boolean onlyOutput)
{
- ioState.setState(ConnectionState.CLOSED);
EndPoint endPoint = getEndPoint();
// We need to gently close first, to allow
// SSL close alerts to be sent by Jetty
@@ -276,18 +268,8 @@
*/
private void enqueClose(int statusCode, String reason)
{
- synchronized (writeBytes)
- {
- // It is possible to get close events from many different sources.
- // Make sure we only sent 1 over the network.
- if (writeBytes.isClosed())
- {
- // already sent the close
- return;
- }
- }
CloseInfo close = new CloseInfo(statusCode,reason);
- outgoingFrame(close.asFrame(),new OnCloseCallback());
+ ioState.onCloseLocal(close);
}
protected void execute(Runnable task)
@@ -438,11 +420,32 @@
public void onClose()
{
super.onClose();
- this.getIOState().setState(ConnectionState.CLOSED);
writeBytes.close();
}
@Override
+ public void onConnectionStateChange(ConnectionState state)
+ {
+ LOG.debug("Connection State Change: {}",state);
+ switch (state)
+ {
+ case OPEN:
+ LOG.debug("fillInterested");
+ fillInterested();
+ break;
+ case CLOSED:
+ this.disconnect();
+ break;
+ case CLOSING:
+ CloseInfo close = ioState.getCloseInfo();
+ // append close frame
+ outgoingFrame(close.asFrame(),null);
+ default:
+ break;
+ }
+ }
+
+ @Override
public void onFillable()
{
LOG.debug("{} onFillable()",policy.getBehavior());
@@ -482,18 +485,16 @@
public void onOpen()
{
super.onOpen();
- this.ioState.setState(ConnectionState.OPEN);
- LOG.debug("fillInterested");
- fillInterested();
+ this.ioState.onOpened();
}
@Override
protected boolean onReadTimeout()
{
- LOG.info("Read Timeout");
+ LOG.debug("Read Timeout");
IOState state = getIOState();
- if ((state.getState() == ConnectionState.CLOSING) || (state.getState() == ConnectionState.CLOSED))
+ if ((state.getConnectionState() == ConnectionState.CLOSING) || (state.getConnectionState() == ConnectionState.CLOSED))
{
// close already initiated, extra timeouts not relevant
// allow underlying connection and endpoint to disconnect on its own
@@ -501,24 +502,12 @@
}
// Initiate close - politely send close frame.
- // Note: it is not possible in 100% of cases during read timeout to send this close frame.
session.incomingError(new WebSocketTimeoutException("Timeout on Read"));
- session.close(StatusCode.NORMAL,"Idle Timeout");
-
- // Force closure of writeBytes
- writeBytes.close();
+ close(StatusCode.SHUTDOWN,"Idle Timeout");
return false;
}
- public void onWriteWebSocketClose()
- {
- if (ioState.onCloseHandshake(false))
- {
- disconnect();
- }
- }
-
/**
* Frame from API, User, or Internal implementation destined for network.
*/
@@ -550,6 +539,7 @@
else if (filled < 0)
{
LOG.debug("read - EOF Reached (remote: {})",getRemoteAddress());
+ ioState.onReadEOF();
return -1;
}
else
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/IOState.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/IOState.java
index e90ab7e..3836ae6 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/IOState.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/IOState.java
@@ -19,39 +19,79 @@
package org.eclipse.jetty.websocket.common.io;
import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.ConnectionState;
/**
- * Simple state tracker for Input / Output and {@link ConnectionState}
+ * Simple state tracker for Input / Output and {@link ConnectionState}.
+ * <p>
+ * Use the various known .on*() methods to trigger a state change.
+ * <ul>
+ * <li>{@link #onOpened()} - connection has been opened</li>
+ * </ul>
*/
public class IOState
{
+ /**
+ * The source of a close handshake. (ie: who initiated it).
+ */
+ private static enum CloseHandshakeSource
+ {
+ /** No close handshake initiated (yet) */
+ NONE,
+ /** Local side initiated the close handshake */
+ LOCAL,
+ /** Remote side initiated the close handshake */
+ REMOTE,
+ /** An abnormal close situation (disconnect, timeout, etc...) */
+ ABNORMAL;
+ }
+
+ public static interface ConnectionStateListener
+ {
+ public void onConnectionStateChange(ConnectionState state);
+ }
+
private static final Logger LOG = Log.getLogger(IOState.class);
private ConnectionState state;
- private final AtomicBoolean inputClosed;
- private final AtomicBoolean outputClosed;
+ private final List<ConnectionStateListener> listeners = new CopyOnWriteArrayList<>();
+
+ private final AtomicBoolean inputAvailable;
+ private final AtomicBoolean outputAvailable;
+ private final AtomicReference<CloseHandshakeSource> closeHandshakeSource;
+ private final AtomicReference<CloseInfo> closeInfo;
private final AtomicBoolean cleanClose;
- private final AtomicBoolean remoteCloseInitiated;
- private final AtomicBoolean localCloseInitiated;
+ /**
+ * Create a new IOState, initialized to {@link ConnectionState#CONNECTING}
+ */
public IOState()
{
this.state = ConnectionState.CONNECTING;
- this.inputClosed = new AtomicBoolean(false);
- this.outputClosed = new AtomicBoolean(false);
- this.remoteCloseInitiated = new AtomicBoolean(false);
- this.localCloseInitiated = new AtomicBoolean(false);
+ this.inputAvailable = new AtomicBoolean(false);
+ this.outputAvailable = new AtomicBoolean(false);
+ this.closeHandshakeSource = new AtomicReference<>(CloseHandshakeSource.NONE);
+ this.closeInfo = new AtomicReference<>();
this.cleanClose = new AtomicBoolean(false);
}
+ public void addListener(ConnectionStateListener listener)
+ {
+ listeners.add(listener);
+ }
+
public void assertInputOpen() throws IOException
{
- if (isInputClosed())
+ if (!isInputAvailable())
{
throw new IOException("Connection input is closed");
}
@@ -59,15 +99,15 @@
public void assertOutputOpen() throws IOException
{
- if (isOutputClosed())
+ if (!isOutputAvailable())
{
throw new IOException("Connection output is closed");
}
}
- public boolean awaitClosed(long duration)
+ public CloseInfo getCloseInfo()
{
- return (isInputClosed() && isOutputClosed());
+ return closeInfo.get();
}
public ConnectionState getConnectionState()
@@ -75,90 +115,283 @@
return state;
}
- public ConnectionState getState()
- {
- return state;
- }
-
public boolean isClosed()
{
- return (isInputClosed() && isOutputClosed());
+ synchronized (state)
+ {
+ return (state == ConnectionState.CLOSED);
+ }
}
- public boolean isCloseInitiated()
+ public boolean isInputAvailable()
{
- return remoteCloseInitiated.get() || localCloseInitiated.get();
- }
-
- public boolean isInputClosed()
- {
- return inputClosed.get();
+ return inputAvailable.get();
}
public boolean isOpen()
{
- return (getState() != ConnectionState.CLOSED);
+ return (getConnectionState() != ConnectionState.CLOSED);
}
- public boolean isOutputClosed()
+ public boolean isOutputAvailable()
{
- return outputClosed.get();
+ return outputAvailable.get();
+ }
+
+ private void notifyStateListeners(ConnectionState state)
+ {
+ for (ConnectionStateListener listener : listeners)
+ {
+ listener.onConnectionStateChange(state);
+ }
}
/**
- * Test for if connection should disconnect or response on a close handshake.
- *
- * @param incoming
- * true if incoming close
- * @param close
- * the close details.
- * @return true if connection should be disconnected now, or false if response to close should be issued.
+ * A websocket connection has been disconnected for abnormal close reasons.
+ * <p>
+ * This is the low level disconnect of the socket. It could be the result of a normal close operation, from an IO error, or even from a timeout.
*/
- public boolean onCloseHandshake(boolean incoming)
+ public void onAbnormalClose(CloseInfo close)
{
- boolean in = inputClosed.get();
- boolean out = outputClosed.get();
- if (incoming)
+ ConnectionState event = null;
+ synchronized (this.state)
{
- in = true;
- this.inputClosed.set(true);
-
- if (!localCloseInitiated.get())
+ if (this.state == ConnectionState.CLOSED)
{
- remoteCloseInitiated.set(true);
+ // already closed
+ return;
}
- }
- else
- {
- out = true;
- this.outputClosed.set(true);
- if ( !remoteCloseInitiated.get() )
+ if (this.state == ConnectionState.OPEN)
{
- localCloseInitiated.set(true);
+ this.cleanClose.set(false);
+ }
+
+ this.state = ConnectionState.CLOSED;
+ this.closeInfo.compareAndSet(null,close);
+ this.inputAvailable.set(false);
+ this.outputAvailable.set(false);
+ this.closeHandshakeSource.set(CloseHandshakeSource.ABNORMAL);
+ event = this.state;
+ }
+ notifyStateListeners(event);
+ }
+
+ /**
+ * A close handshake has been issued from the local endpoint
+ */
+ public void onCloseLocal(CloseInfo close)
+ {
+ ConnectionState event = null;
+ ConnectionState initialState = this.state;
+ if (initialState == ConnectionState.CLOSED)
+ {
+ // already closed
+ return;
+ }
+
+ if (initialState == ConnectionState.CONNECTED)
+ {
+ // fast close. a local close request from end-user onConnected() method
+ LOG.debug("FastClose in CONNECTED detected");
+ // Force the state open (to allow read/write to endpoint)
+ onOpened();
+ }
+
+ synchronized (this.state)
+ {
+ closeInfo.compareAndSet(null,close);
+
+ boolean in = inputAvailable.get();
+ boolean out = outputAvailable.get();
+ closeHandshakeSource.compareAndSet(CloseHandshakeSource.NONE,CloseHandshakeSource.LOCAL);
+ out = false;
+ outputAvailable.set(false);
+
+ LOG.debug("onCloseLocal(), input={}, output={}",in,out);
+
+ if (!in && !out)
+ {
+ LOG.debug("Close Handshake satisfied, disconnecting");
+ cleanClose.set(true);
+ this.state = ConnectionState.CLOSED;
+ event = this.state;
+ }
+ else if (this.state == ConnectionState.OPEN)
+ {
+ // We are now entering CLOSING (or half-closed)
+ this.state = ConnectionState.CLOSING;
+ event = this.state;
}
}
- LOG.debug("onCloseHandshake({}), input={}, output={}",incoming,in,out);
-
- if (in && out)
+ // Only notify on state change events
+ if (event != null)
{
- LOG.debug("Close Handshake satisfied, disconnecting");
- cleanClose.set(true);
- return true;
+ notifyStateListeners(event);
+
+ // if SHUTDOWN, we don't expect an answer.
+ if (close.getStatusCode() == StatusCode.SHUTDOWN)
+ {
+ synchronized (this.state)
+ {
+ this.state = ConnectionState.CLOSED;
+ cleanClose.set(false);
+ outputAvailable.set(false);
+ inputAvailable.set(false);
+ this.closeHandshakeSource.set(CloseHandshakeSource.ABNORMAL);
+ event = this.state;
+ }
+ notifyStateListeners(event);
+ return;
+ }
+ }
+ }
+
+ /**
+ * A close handshake has been received from the remote endpoint
+ */
+ public void onCloseRemote(CloseInfo close)
+ {
+ ConnectionState event = null;
+ synchronized (this.state)
+ {
+ if (this.state == ConnectionState.CLOSED)
+ {
+ // already closed
+ return;
+ }
+
+ closeInfo.compareAndSet(null,close);
+
+ boolean in = inputAvailable.get();
+ boolean out = outputAvailable.get();
+ closeHandshakeSource.compareAndSet(CloseHandshakeSource.NONE,CloseHandshakeSource.REMOTE);
+ in = false;
+ inputAvailable.set(false);
+
+ LOG.debug("onCloseRemote(), input={}, output={}",in,out);
+
+ if (!in && !out)
+ {
+ LOG.debug("Close Handshake satisfied, disconnecting");
+ cleanClose.set(true);
+ this.state = ConnectionState.CLOSED;
+ event = this.state;
+ }
+ else if (this.state == ConnectionState.OPEN)
+ {
+ // We are now entering CLOSING (or half-closed)
+ this.state = ConnectionState.CLOSING;
+ event = this.state;
+ }
}
- return false;
+ // Only notify on state change events
+ if (event != null)
+ {
+ notifyStateListeners(event);
+ }
}
- public void setConnectionState(ConnectionState connectionState)
+ /**
+ * WebSocket has successfully upgraded, but the end-user onOpen call hasn't run yet.
+ * <p>
+ * This is an intermediate state between the RFC's {@link ConnectionState#CONNECTING} and {@link ConnectionState#OPEN}
+ */
+ public void onConnected()
{
- this.state = connectionState;
+ if (this.state != ConnectionState.CONNECTING)
+ {
+ LOG.debug("Unable to set to connected, not in CONNECTING state: {}",this.state);
+ return;
+ }
+
+ ConnectionState event = null;
+ synchronized (this.state)
+ {
+ this.state = ConnectionState.CONNECTED;
+ this.inputAvailable.set(false); // cannot read (yet)
+ this.outputAvailable.set(true); // write allowed
+ event = this.state;
+ }
+ notifyStateListeners(event);
}
- public void setState(ConnectionState state)
+ /**
+ * A websocket connection has failed its upgrade handshake, and is now closed.
+ */
+ public void onFailedUpgrade()
{
- this.state = state;
+ assert (this.state == ConnectionState.CONNECTING);
+ ConnectionState event = null;
+ synchronized (this.state)
+ {
+ this.state = ConnectionState.CLOSED;
+ this.cleanClose.set(false);
+ this.inputAvailable.set(false);
+ this.outputAvailable.set(false);
+ event = this.state;
+ }
+ notifyStateListeners(event);
+ }
+
+ /**
+ * A websocket connection has finished its upgrade handshake, and is now open.
+ */
+ public void onOpened()
+ {
+ if (this.state != ConnectionState.CONNECTED)
+ {
+ LOG.debug("Unable to open, not in CONNECTED state: {}",this.state);
+ return;
+ }
+
+ assert (this.state == ConnectionState.CONNECTED);
+
+ ConnectionState event = null;
+ synchronized (this.state)
+ {
+ this.state = ConnectionState.OPEN;
+ this.inputAvailable.set(true);
+ this.outputAvailable.set(true);
+ event = this.state;
+ }
+ notifyStateListeners(event);
+ }
+
+ /**
+ * The local endpoint has reached a read EOF.
+ * <p>
+ * This could be a normal result after a proper close handshake, or even a premature close due to a connection disconnect.
+ */
+ public void onReadEOF()
+ {
+ ConnectionState event = null;
+ synchronized (this.state)
+ {
+ if (this.state == ConnectionState.CLOSED)
+ {
+ // already closed
+ return;
+ }
+
+ CloseInfo close = new CloseInfo(StatusCode.NO_CLOSE,"Read EOF");
+
+ this.cleanClose.set(false);
+ this.state = ConnectionState.CLOSED;
+ this.closeInfo.compareAndSet(null,close);
+ this.inputAvailable.set(false);
+ this.outputAvailable.set(false);
+ this.closeHandshakeSource.set(CloseHandshakeSource.ABNORMAL);
+ event = this.state;
+ }
+ notifyStateListeners(event);
+ }
+
+ public boolean wasAbnormalClose()
+ {
+ return closeHandshakeSource.get() == CloseHandshakeSource.ABNORMAL;
}
public boolean wasCleanClose()
@@ -168,11 +401,11 @@
public boolean wasLocalCloseInitiated()
{
- return localCloseInitiated.get();
+ return closeHandshakeSource.get() == CloseHandshakeSource.LOCAL;
}
public boolean wasRemoteCloseInitiated()
{
- return remoteCloseInitiated.get();
+ return closeHandshakeSource.get() == CloseHandshakeSource.REMOTE;
}
}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/WriteBytesProvider.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/WriteBytesProvider.java
index e34e31b..1cf29b2 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/WriteBytesProvider.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/WriteBytesProvider.java
@@ -258,6 +258,10 @@
private void notifySafeFailure(Callback callback, Throwable t)
{
+ if (callback == null)
+ {
+ return;
+ }
try
{
callback.failed(t);
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParseListener.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParseListener.java
new file mode 100644
index 0000000..6cb2ae9
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParseListener.java
@@ -0,0 +1,32 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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.websocket.common.io.http;
+
+import java.nio.ByteBuffer;
+
+public interface HttpResponseHeaderParseListener
+{
+ void addHeader(String name, String value);
+
+ void setRemainingBuffer(ByteBuffer copy);
+
+ void setStatusCode(int statusCode);
+
+ void setStatusReason(String statusReason);
+}
\ No newline at end of file
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/HttpResponseHeaderParser.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParser.java
similarity index 79%
rename from jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/HttpResponseHeaderParser.java
rename to jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParser.java
index 8b67dfd..b1b01c9 100644
--- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/HttpResponseHeaderParser.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParser.java
@@ -16,7 +16,7 @@
// ========================================================================
//
-package org.eclipse.jetty.websocket.client.io;
+package org.eclipse.jetty.websocket.common.io.http;
import java.nio.ByteBuffer;
import java.util.regex.Matcher;
@@ -25,7 +25,6 @@
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Utf8LineParser;
-import org.eclipse.jetty.websocket.client.ClientUpgradeResponse;
/**
* Responsible for reading UTF8 Response Header lines and parsing them into a provided UpgradeResponse object.
@@ -56,12 +55,13 @@
private static final Pattern PAT_HEADER = Pattern.compile("([^:]+):\\s*(.*)");
private static final Pattern PAT_STATUS_LINE = Pattern.compile("^HTTP/1.[01]\\s+(\\d+)\\s+(.*)",Pattern.CASE_INSENSITIVE);
- private ClientUpgradeResponse response;
- private Utf8LineParser lineParser;
+ private final HttpResponseHeaderParseListener listener;
+ private final Utf8LineParser lineParser;
private State state;
- public HttpResponseHeaderParser()
+ public HttpResponseHeaderParser(HttpResponseHeaderParseListener listener)
{
+ this.listener = listener;
this.lineParser = new Utf8LineParser();
this.state = State.STATUS_LINE;
}
@@ -71,7 +71,7 @@
return (state == State.END);
}
- public ClientUpgradeResponse parse(ByteBuffer buf) throws ParseException
+ public HttpResponseHeaderParseListener parse(ByteBuffer buf) throws ParseException
{
while (!isDone() && (buf.remaining() > 0))
{
@@ -84,8 +84,8 @@
ByteBuffer copy = ByteBuffer.allocate(buf.remaining());
BufferUtil.put(buf,copy);
BufferUtil.flipToFlush(copy,0);
- this.response.setRemainingBuffer(copy);
- return this.response;
+ this.listener.setRemainingBuffer(copy);
+ return listener;
}
}
}
@@ -98,22 +98,21 @@
{
case STATUS_LINE:
{
- this.response = new ClientUpgradeResponse();
Matcher mat = PAT_STATUS_LINE.matcher(line);
if (!mat.matches())
{
- throw new ParseException("Unexpected HTTP upgrade response status line [" + line + "]");
+ throw new ParseException("Unexpected HTTP response status line [" + line + "]");
}
try
{
- response.setStatusCode(Integer.parseInt(mat.group(1)));
+ listener.setStatusCode(Integer.parseInt(mat.group(1)));
}
catch (NumberFormatException e)
{
- throw new ParseException("Unexpected HTTP upgrade response status code",e);
+ throw new ParseException("Unexpected HTTP response status code",e);
}
- response.setStatusReason(mat.group(2));
+ listener.setStatusReason(mat.group(2));
state = State.HEADER;
break;
}
@@ -130,8 +129,8 @@
{
String headerName = header.group(1);
String headerValue = header.group(2);
- // TODO: need to split header/value if comma delimited
- response.addHeader(headerName,headerValue);
+ // do need to split header/value if comma delimited?
+ listener.addHeader(headerName,headerValue);
}
break;
}
diff --git a/jetty-websocket/websocket-common/src/test/java/examples/echo/AdapterEchoSocket.java b/jetty-websocket/websocket-common/src/test/java/examples/echo/AdapterEchoSocket.java
index a0cca6d..ad6b3cf 100644
--- a/jetty-websocket/websocket-common/src/test/java/examples/echo/AdapterEchoSocket.java
+++ b/jetty-websocket/websocket-common/src/test/java/examples/echo/AdapterEchoSocket.java
@@ -20,8 +20,6 @@
import java.io.IOException;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
/**
@@ -29,26 +27,21 @@
*/
public class AdapterEchoSocket extends WebSocketAdapter
{
- private static final Logger LOG = Log.getLogger(AdapterEchoSocket.class);
-
@Override
public void onWebSocketText(String message)
{
- if (isNotConnected())
+ if (isConnected())
{
- LOG.debug("WebSocket Not Connected");
- return;
- }
-
- try
- {
- LOG.debug("Echoing back message [{}]",message);
- // echo the data back
- getRemote().sendString(message);
- }
- catch (IOException e)
- {
- e.printStackTrace();
+ try
+ {
+ System.out.printf("Echoing back message [%s]%n",message);
+ // echo the message back
+ getRemote().sendString(message);
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace(System.err);
+ }
}
}
}
diff --git a/jetty-websocket/websocket-common/src/test/java/examples/echo/AnnotatedEchoSocket.java b/jetty-websocket/websocket-common/src/test/java/examples/echo/AnnotatedEchoSocket.java
index a2af920..0b71bb5 100644
--- a/jetty-websocket/websocket-common/src/test/java/examples/echo/AnnotatedEchoSocket.java
+++ b/jetty-websocket/websocket-common/src/test/java/examples/echo/AnnotatedEchoSocket.java
@@ -33,8 +33,9 @@
{
if (session.isOpen())
{
- return;
+ System.out.printf("Echoing back message [%s]%n",message);
+ // echo the message back
+ session.getRemote().sendStringByFuture(message);
}
- session.getRemote().sendStringByFuture(message);
}
}
diff --git a/jetty-websocket/websocket-common/src/test/java/examples/echo/ListenerEchoSocket.java b/jetty-websocket/websocket-common/src/test/java/examples/echo/ListenerEchoSocket.java
index 04fc6f7..6d76f0e 100644
--- a/jetty-websocket/websocket-common/src/test/java/examples/echo/ListenerEchoSocket.java
+++ b/jetty-websocket/websocket-common/src/test/java/examples/echo/ListenerEchoSocket.java
@@ -18,9 +18,6 @@
package examples.echo;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketListener;
@@ -29,7 +26,6 @@
*/
public class ListenerEchoSocket implements WebSocketListener
{
- private static final Logger LOG = Logger.getLogger(ListenerEchoSocket.class.getName());
private Session outbound;
@Override
@@ -53,17 +49,17 @@
@Override
public void onWebSocketError(Throwable cause)
{
- LOG.log(Level.WARNING,"onWebSocketError",cause);
+ cause.printStackTrace(System.err);
}
@Override
public void onWebSocketText(String message)
{
- if (outbound == null)
+ if ((outbound != null) && (outbound.isOpen()))
{
- return;
+ System.out.printf("Echoing back message [%s]%n",message);
+ // echo the message back
+ outbound.getRemote().sendStringByFuture(message);
}
-
- outbound.getRemote().sendStringByFuture(message);
}
}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventDriverTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventDriverTest.java
index 56bc513..ecb5adb 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventDriverTest.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventDriverTest.java
@@ -116,7 +116,7 @@
driver.incomingFrame(new WebSocketFrame(OpCode.PING).setPayload("PING"));
driver.incomingFrame(WebSocketFrame.text("Text Me"));
driver.incomingFrame(WebSocketFrame.binary().setPayload("Hello Bin"));
- driver.incomingFrame(new CloseInfo(StatusCode.SHUTDOWN).asFrame());
+ driver.incomingFrame(new CloseInfo(StatusCode.SHUTDOWN,"testcase").asFrame());
socket.capture.assertEventCount(6);
socket.capture.assertEventStartsWith(0,"onConnect(");
@@ -148,13 +148,14 @@
}
@Test
- public void testListener_Text() throws IOException
+ public void testListener_Text() throws Exception
{
ListenerBasicSocket socket = new ListenerBasicSocket();
EventDriver driver = wrap(socket);
try (LocalWebSocketSession conn = new LocalWebSocketSession(testname,driver))
{
+ conn.start();
conn.open();
driver.incomingFrame(WebSocketFrame.text("Hello World"));
driver.incomingFrame(new CloseInfo(StatusCode.NORMAL).asFrame());
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/IOStateTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/IOStateTest.java
new file mode 100644
index 0000000..24393b1
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/IOStateTest.java
@@ -0,0 +1,245 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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.websocket.common.io;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.util.LinkedList;
+
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.ConnectionState;
+import org.junit.Test;
+
+public class IOStateTest
+{
+ public static class StateTracker implements IOState.ConnectionStateListener
+ {
+ private LinkedList<ConnectionState> transitions = new LinkedList<>();
+
+ public void assertTransitions(ConnectionState ...states)
+ {
+ assertThat("Transitions.count",transitions.size(),is(states.length));
+ if (states.length > 0)
+ {
+ int len = states.length;
+ for (int i = 0; i < len; i++)
+ {
+ assertThat("Transitions[" + i + "]",transitions.get(i),is(states[i]));
+ }
+ }
+ }
+
+ public LinkedList<ConnectionState> getTransitions()
+ {
+ return transitions;
+ }
+
+ @Override
+ public void onConnectionStateChange(ConnectionState state)
+ {
+ transitions.add(state);
+ }
+ }
+
+ private void assertCleanClose(IOState state, boolean expected)
+ {
+ assertThat("State.cleanClose",state.wasCleanClose(),is(expected));
+ }
+
+ private void assertInputAvailable(IOState state, boolean available)
+ {
+ assertThat("State.inputAvailable",state.isInputAvailable(),is(available));
+ }
+
+ private void assertLocalInitiated(IOState state, boolean expected)
+ {
+ assertThat("State.localCloseInitiated",state.wasLocalCloseInitiated(),is(expected));
+ }
+
+ private void assertOutputAvailable(IOState state, boolean available)
+ {
+ assertThat("State.outputAvailable",state.isOutputAvailable(),is(available));
+ }
+
+ private void assertRemoteInitiated(IOState state, boolean expected)
+ {
+ assertThat("State.remoteCloseInitiated",state.wasRemoteCloseInitiated(),is(expected));
+ }
+
+ private void assertState(IOState state, ConnectionState expectedState)
+ {
+ assertThat("State",state.getConnectionState(),is(expectedState));
+ }
+
+ @Test
+ public void testConnectAbnormalClose()
+ {
+ IOState state = new IOState();
+ StateTracker tracker = new StateTracker();
+ state.addListener(tracker);
+ assertState(state,ConnectionState.CONNECTING);
+
+ // connect
+ state.onConnected();
+ assertInputAvailable(state,false);
+ assertOutputAvailable(state,true);
+
+ // open
+ state.onOpened();
+ assertInputAvailable(state,true);
+ assertOutputAvailable(state,true);
+
+ // disconnect
+ state.onAbnormalClose(new CloseInfo(StatusCode.NO_CLOSE,"Oops"));
+
+ assertInputAvailable(state,false);
+ assertOutputAvailable(state,false);
+ tracker.assertTransitions(ConnectionState.CONNECTED,ConnectionState.OPEN,ConnectionState.CLOSED);
+ assertState(state,ConnectionState.CLOSED);
+
+ // not clean
+ assertCleanClose(state,false);
+ assertLocalInitiated(state,false);
+ assertRemoteInitiated(state,false);
+ }
+
+ @Test
+ public void testConnectCloseLocalInitiated()
+ {
+ IOState state = new IOState();
+ StateTracker tracker = new StateTracker();
+ state.addListener(tracker);
+ assertState(state,ConnectionState.CONNECTING);
+
+ // connect
+ state.onConnected();
+ assertInputAvailable(state,false);
+ assertOutputAvailable(state,true);
+
+ // open
+ state.onOpened();
+ assertInputAvailable(state,true);
+ assertOutputAvailable(state,true);
+
+ // close (local initiated)
+ state.onCloseLocal(new CloseInfo(StatusCode.NORMAL,"Hi"));
+ assertInputAvailable(state,true);
+ assertOutputAvailable(state,false);
+ assertState(state,ConnectionState.CLOSING);
+
+ // close (remote response)
+ state.onCloseRemote(new CloseInfo(StatusCode.NORMAL,"Hi"));
+
+ assertInputAvailable(state,false);
+ assertOutputAvailable(state,false);
+ tracker.assertTransitions(ConnectionState.CONNECTED,ConnectionState.OPEN,ConnectionState.CLOSING,ConnectionState.CLOSED);
+ assertState(state,ConnectionState.CLOSED);
+
+ // not clean
+ assertCleanClose(state,true);
+ assertLocalInitiated(state,true);
+ assertRemoteInitiated(state,false);
+ }
+
+ @Test
+ public void testConnectCloseRemoteInitiated()
+ {
+ IOState state = new IOState();
+ StateTracker tracker = new StateTracker();
+ state.addListener(tracker);
+ assertState(state,ConnectionState.CONNECTING);
+
+ // connect
+ state.onConnected();
+ assertInputAvailable(state,false);
+ assertOutputAvailable(state,true);
+
+ // open
+ state.onOpened();
+ assertInputAvailable(state,true);
+ assertOutputAvailable(state,true);
+
+ // close (remote initiated)
+ state.onCloseRemote(new CloseInfo(StatusCode.NORMAL,"Hi"));
+ assertInputAvailable(state,false);
+ assertOutputAvailable(state,true);
+ assertState(state,ConnectionState.CLOSING);
+
+ // close (local response)
+ state.onCloseLocal(new CloseInfo(StatusCode.NORMAL,"Hi"));
+
+ assertInputAvailable(state,false);
+ assertOutputAvailable(state,false);
+ tracker.assertTransitions(ConnectionState.CONNECTED,ConnectionState.OPEN,ConnectionState.CLOSING,ConnectionState.CLOSED);
+ assertState(state,ConnectionState.CLOSED);
+
+ // not clean
+ assertCleanClose(state,true);
+ assertLocalInitiated(state,false);
+ assertRemoteInitiated(state,true);
+ }
+
+ @Test
+ public void testConnectFailure()
+ {
+ IOState state = new IOState();
+ StateTracker tracker = new StateTracker();
+ state.addListener(tracker);
+ assertState(state,ConnectionState.CONNECTING);
+
+ // fail upgrade
+ state.onFailedUpgrade();
+
+ tracker.assertTransitions(ConnectionState.CLOSED);
+ assertState(state,ConnectionState.CLOSED);
+
+ assertInputAvailable(state,false);
+ assertOutputAvailable(state,false);
+
+ // not clean
+ assertCleanClose(state,false);
+ assertLocalInitiated(state,false);
+ assertRemoteInitiated(state,false);
+ }
+
+ @Test
+ public void testInit()
+ {
+ IOState state = new IOState();
+ StateTracker tracker = new StateTracker();
+ state.addListener(tracker);
+ assertState(state,ConnectionState.CONNECTING);
+
+ // do nothing
+
+ tracker.assertTransitions();
+ assertState(state,ConnectionState.CONNECTING);
+
+ // not connected yet
+ assertInputAvailable(state,false);
+ assertOutputAvailable(state,false);
+
+ // no close yet
+ assertCleanClose(state,false);
+ assertLocalInitiated(state,false);
+ assertRemoteInitiated(state,false);
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/LocalWebSocketConnection.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/LocalWebSocketConnection.java
index 5387af3..92e5e77 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/LocalWebSocketConnection.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/LocalWebSocketConnection.java
@@ -22,22 +22,25 @@
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.SuspendToken;
import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
+import org.eclipse.jetty.websocket.common.CloseInfo;
+import org.eclipse.jetty.websocket.common.ConnectionState;
import org.eclipse.jetty.websocket.common.LogicalConnection;
import org.eclipse.jetty.websocket.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
import org.junit.rules.TestName;
-public class LocalWebSocketConnection implements LogicalConnection, IncomingFrames
+public class LocalWebSocketConnection implements LogicalConnection, IncomingFrames, ConnectionStateListener
{
private static final Logger LOG = Log.getLogger(LocalWebSocketConnection.class);
private final String id;
private WebSocketPolicy policy = WebSocketPolicy.newServerPolicy();
- private boolean open = false;
private IncomingFrames incoming;
private IOState ioState = new IOState();
@@ -49,29 +52,33 @@
public LocalWebSocketConnection(String id)
{
this.id = id;
+ this.ioState.addListener(this);
}
public LocalWebSocketConnection(TestName testname)
{
this.id = testname.getMethodName();
+ this.ioState.addListener(this);
}
@Override
public void close()
{
- open = false;
+ close(StatusCode.NORMAL,null);
}
@Override
public void close(int statusCode, String reason)
{
- open = false;
+ LOG.debug("close({}, {})",statusCode,reason);
+ CloseInfo close = new CloseInfo(statusCode,reason);
+ ioState.onCloseLocal(close);
}
@Override
public void disconnect()
{
- open = false;
+ LOG.debug("disconnect()");
}
public IncomingFrames getIncoming()
@@ -131,7 +138,7 @@
@Override
public boolean isOpen()
{
- return open;
+ return getIOState().isOpen();
}
@Override
@@ -140,9 +147,31 @@
return false;
}
+ @Override
+ public void onConnectionStateChange(ConnectionState state)
+ {
+ LOG.debug("Connection State Change: {}",state);
+ switch (state)
+ {
+ case CLOSED:
+ this.disconnect();
+ break;
+ case CLOSING:
+ if (ioState.wasRemoteCloseInitiated())
+ {
+ // send response close frame
+ CloseInfo close = ioState.getCloseInfo();
+ LOG.debug("write close frame: {}",close);
+ ioState.onCloseLocal(close);
+ }
+ default:
+ break;
+ }
+ }
+
public void onOpen() {
LOG.debug("onOpen()");
- open = true;
+ ioState.onOpened();
}
@Override
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParserTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParserTest.java
new file mode 100644
index 0000000..837cf10
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParserTest.java
@@ -0,0 +1,193 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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.websocket.common.io.http;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class HttpResponseHeaderParserTest
+{
+ @Rule
+ public TestTracker tt = new TestTracker();
+
+ private void appendUtf8(ByteBuffer buf, String line)
+ {
+ buf.put(ByteBuffer.wrap(StringUtil.getBytes(line,StringUtil.__UTF8)));
+ }
+
+ @Test
+ public void testParseNotFound()
+ {
+ StringBuilder resp = new StringBuilder();
+ resp.append("HTTP/1.1 404 Not Found\r\n");
+ resp.append("Date: Fri, 26 Apr 2013 21:43:08 GMT\r\n");
+ resp.append("Content-Type: text/html; charset=ISO-8859-1\r\n");
+ resp.append("Cache-Control: must-revalidate,no-cache,no-store\r\n");
+ resp.append("Content-Length: 38\r\n");
+ resp.append("Server: Jetty(9.0.0.v20130308)\r\n");
+ resp.append("\r\n");
+ // and some body content
+ resp.append("What you are looking for is not here\r\n");
+
+ ByteBuffer buf = BufferUtil.toBuffer(resp.toString(),StringUtil.__UTF8_CHARSET);
+
+ HttpResponseParseCapture capture = new HttpResponseParseCapture();
+ HttpResponseHeaderParser parser = new HttpResponseHeaderParser(capture);
+ assertThat("Parser.parse",parser.parse(buf),notNullValue());
+ assertThat("Response.statusCode",capture.getStatusCode(),is(404));
+ assertThat("Response.statusReason",capture.getStatusReason(),is("Not Found"));
+ assertThat("Response.headers[Content-Length]",capture.getHeader("Content-Length"),is("38"));
+
+ assertThat("Response.remainingBuffer",capture.getRemainingBuffer().remaining(),is(38));
+ }
+
+ @Test
+ public void testParseRealWorldResponse()
+ {
+ // Arbitrary Http Response Headers seen in the wild.
+ // Request URI -> https://ssl.google-analytics.com/__utm.gif
+ List<String> expected = new ArrayList<>();
+ expected.add("HTTP/1.0 200 OK");
+ expected.add("Date: Thu, 09 Aug 2012 16:16:39 GMT");
+ expected.add("Content-Length: 35");
+ expected.add("X-Content-Type-Options: nosniff");
+ expected.add("Pragma: no-cache");
+ expected.add("Expires: Wed, 19 Apr 2000 11:43:00 GMT");
+ expected.add("Last-Modified: Wed, 21 Jan 2004 19:51:30 GMT");
+ expected.add("Content-Type: image/gif");
+ expected.add("Cache-Control: private, no-cache, no-cache=Set-Cookie, proxy-revalidate");
+ expected.add("Age: 518097");
+ expected.add("Server: GFE/2.0");
+ expected.add("Connection: Keep-Alive");
+ expected.add("");
+
+ // Prepare Buffer
+ ByteBuffer buf = ByteBuffer.allocate(512);
+ for (String line : expected)
+ {
+ appendUtf8(buf,line + "\r\n");
+ }
+
+ BufferUtil.flipToFlush(buf,0);
+
+ // Parse Buffer
+ HttpResponseParseCapture capture = new HttpResponseParseCapture();
+ HttpResponseHeaderParser parser = new HttpResponseHeaderParser(capture);
+ assertThat("Parser.parse",parser.parse(buf),notNullValue());
+
+ Assert.assertThat("Response.statusCode",capture.getStatusCode(),is(200));
+ Assert.assertThat("Response.statusReason",capture.getStatusReason(),is("OK"));
+
+ Assert.assertThat("Response.header[age]",capture.getHeader("age"),is("518097"));
+ }
+
+ @Test
+ public void testParseRealWorldResponse_SmallBuffers()
+ {
+ // Arbitrary Http Response Headers seen in the wild.
+ // Request URI -> https://ssl.google-analytics.com/__utm.gif
+ List<String> expected = new ArrayList<>();
+ expected.add("HTTP/1.0 200 OK");
+ expected.add("Date: Thu, 09 Aug 2012 16:16:39 GMT");
+ expected.add("Content-Length: 35");
+ expected.add("X-Content-Type-Options: nosniff");
+ expected.add("Pragma: no-cache");
+ expected.add("Expires: Wed, 19 Apr 2000 11:43:00 GMT");
+ expected.add("Last-Modified: Wed, 21 Jan 2004 19:51:30 GMT");
+ expected.add("Content-Type: image/gif");
+ expected.add("Cache-Control: private, no-cache, no-cache=Set-Cookie, proxy-revalidate");
+ expected.add("Age: 518097");
+ expected.add("Server: GFE/2.0");
+ expected.add("Connection: Keep-Alive");
+ expected.add("");
+
+ // Prepare Buffer
+ ByteBuffer buf = ByteBuffer.allocate(512);
+ for (String line : expected)
+ {
+ appendUtf8(buf,line + "\r\n");
+ }
+ BufferUtil.flipToFlush(buf,0);
+
+ // Prepare small buffers to simulate a slow read/fill/parse from the network
+ ByteBuffer small1 = buf.slice();
+ ByteBuffer small2 = buf.slice();
+ ByteBuffer small3 = buf.slice();
+
+ small1.limit(50);
+ small2.position(50);
+ small2.limit(70);
+ small3.position(70);
+
+ // Parse Buffer
+ HttpResponseParseCapture capture = new HttpResponseParseCapture();
+ HttpResponseHeaderParser parser = new HttpResponseHeaderParser(capture);
+ assertThat("Parser.parse",parser.parse(buf),notNullValue());
+
+ // Parse small 1
+ Assert.assertThat("Small 1",parser.parse(small1),nullValue());
+
+ // Parse small 2
+ Assert.assertThat("Small 2",parser.parse(small2),nullValue());
+
+ // Parse small 3
+ Assert.assertThat("Small 3",parser.parse(small3),notNullValue());
+
+ Assert.assertThat("Response.statusCode",capture.getStatusCode(),is(200));
+ Assert.assertThat("Response.statusReason",capture.getStatusReason(),is("OK"));
+
+ Assert.assertThat("Response.header[age]",capture.getHeader("age"),is("518097"));
+ }
+
+ @Test
+ public void testParseUpgrade()
+ {
+ // Example from RFC6455 - Section 1.2 (Protocol Overview)
+ StringBuilder resp = new StringBuilder();
+ resp.append("HTTP/1.1 101 Switching Protocols\r\n");
+ resp.append("Upgrade: websocket\r\n");
+ resp.append("Connection: Upgrade\r\n");
+ resp.append("Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n");
+ resp.append("Sec-WebSocket-Protocol: chat\r\n");
+ resp.append("\r\n");
+
+ ByteBuffer buf = BufferUtil.toBuffer(resp.toString(),StringUtil.__UTF8_CHARSET);
+
+ HttpResponseParseCapture capture = new HttpResponseParseCapture();
+ HttpResponseHeaderParser parser = new HttpResponseHeaderParser(capture);
+ assertThat("Parser.parse",parser.parse(buf),notNullValue());
+ assertThat("Response.statusCode",capture.getStatusCode(),is(101));
+ assertThat("Response.statusReason",capture.getStatusReason(),is("Switching Protocols"));
+ assertThat("Response.headers[Upgrade]",capture.getHeader("Upgrade"),is("websocket"));
+ assertThat("Response.headers[Connection]",capture.getHeader("Connection"),is("Upgrade"));
+
+ assertThat("Buffer.remaining",buf.remaining(),is(0));
+ }
+}
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseParseCapture.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseParseCapture.java
new file mode 100644
index 0000000..a7973e6
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseParseCapture.java
@@ -0,0 +1,76 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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.websocket.common.io.http;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+public class HttpResponseParseCapture implements HttpResponseHeaderParseListener
+{
+ private int statusCode;
+ private String statusReason;
+ private Map<String, String> headers = new HashMap<>();
+ private ByteBuffer remainingBuffer;
+
+ @Override
+ public void addHeader(String name, String value)
+ {
+ headers.put(name.toLowerCase(Locale.ENGLISH),value);
+ }
+
+ public String getHeader(String name)
+ {
+ return headers.get(name.toLowerCase(Locale.ENGLISH));
+ }
+
+ public ByteBuffer getRemainingBuffer()
+ {
+ return remainingBuffer;
+ }
+
+ public int getStatusCode()
+ {
+ return statusCode;
+ }
+
+ public String getStatusReason()
+ {
+ return statusReason;
+ }
+
+ @Override
+ public void setRemainingBuffer(ByteBuffer copy)
+ {
+ this.remainingBuffer = copy;
+ }
+
+ @Override
+ public void setStatusCode(int code)
+ {
+ this.statusCode = code;
+ }
+
+ @Override
+ public void setStatusReason(String reason)
+ {
+ this.statusReason = reason;
+ }
+}
\ No newline at end of file
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerConnection.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerConnection.java
index afe2e78..752972d 100644
--- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerConnection.java
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerConnection.java
@@ -76,13 +76,6 @@
}
@Override
- public void onWriteWebSocketClose()
- {
- // as server, always disconnect if writing close
- disconnect();
- }
-
- @Override
public void setNextIncomingFrames(IncomingFrames incoming)
{
getParser().setIncomingFramesHandler(incoming);
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/AnnotatedMaxMessageSizeTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/AnnotatedMaxMessageSizeTest.java
index 09d06a5..2db4a48 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/AnnotatedMaxMessageSizeTest.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/AnnotatedMaxMessageSizeTest.java
@@ -103,7 +103,7 @@
// Read frame (hopefully text frame)
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500);
- WebSocketFrame tf = capture.getFrames().get(0);
+ WebSocketFrame tf = capture.getFrames().poll();
Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg));
}
finally
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java
index fd16ef7..68bc1b1 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java
@@ -24,6 +24,7 @@
import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
+import org.eclipse.jetty.websocket.server.blockhead.HttpResponse;
import org.eclipse.jetty.websocket.server.examples.MyEchoServlet;
import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture;
import org.junit.AfterClass;
@@ -60,8 +61,8 @@
client.setProtocols("chat");
client.connect();
client.sendStandardRequest();
- String response = client.expectUpgradeResponse();
- Assert.assertThat("Response",response,containsString("x-webkit-deflate-frame"));
+ HttpResponse response = client.expectUpgradeResponse();
+ Assert.assertThat("Response",response.getExtensionsHeader(),containsString("x-webkit-deflate-frame"));
// Generate text frame
String msg = "this is an echo ... cho ... ho ... o";
@@ -69,7 +70,7 @@
// Read frame (hopefully text frame)
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500);
- WebSocketFrame tf = capture.getFrames().get(0);
+ WebSocketFrame tf = capture.getFrames().poll();
Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg));
}
finally
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FragmentExtensionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FragmentExtensionTest.java
index cab75e1..b84ee24 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FragmentExtensionTest.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FragmentExtensionTest.java
@@ -24,6 +24,7 @@
import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
+import org.eclipse.jetty.websocket.server.blockhead.HttpResponse;
import org.eclipse.jetty.websocket.server.helper.EchoServlet;
import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture;
import org.junit.AfterClass;
@@ -80,9 +81,9 @@
client.setTimeout(TimeUnit.SECONDS,1);
client.connect();
client.sendStandardRequest();
- String resp = client.expectUpgradeResponse();
+ HttpResponse resp = client.expectUpgradeResponse();
- Assert.assertThat("Response",resp,containsString("fragment"));
+ Assert.assertThat("Response",resp.getExtensionsHeader(),containsString("fragment"));
String msg = "Sent as a long message that should be split";
client.write(WebSocketFrame.text(msg));
@@ -91,7 +92,7 @@
IncomingFramesCapture capture = client.readFrames(parts.length,TimeUnit.MILLISECONDS,1000);
for (int i = 0; i < parts.length; i++)
{
- WebSocketFrame frame = capture.getFrames().get(i);
+ WebSocketFrame frame = capture.getFrames().poll();
Assert.assertThat("text[" + i + "].payload",frame.getPayloadAsUTF8(),is(parts[i]));
}
}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FrameCompressionExtensionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FrameCompressionExtensionTest.java
index 9e0dfeb..78a6be2 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FrameCompressionExtensionTest.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FrameCompressionExtensionTest.java
@@ -24,6 +24,7 @@
import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
+import org.eclipse.jetty.websocket.server.blockhead.HttpResponse;
import org.eclipse.jetty.websocket.server.helper.EchoServlet;
import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture;
import org.junit.AfterClass;
@@ -64,9 +65,9 @@
client.setTimeout(TimeUnit.SECONDS,1);
client.connect();
client.sendStandardRequest();
- String resp = client.expectUpgradeResponse();
+ HttpResponse resp = client.expectUpgradeResponse();
- Assert.assertThat("Response",resp,containsString("x-webkit-deflate-frame"));
+ Assert.assertThat("Response",resp.getExtensionsHeader(),containsString("x-webkit-deflate-frame"));
String msg = "Hello";
@@ -74,7 +75,7 @@
client.write(WebSocketFrame.text(msg));
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,1000);
- WebSocketFrame frame = capture.getFrames().get(0);
+ WebSocketFrame frame = capture.getFrames().poll();
Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is(msg.toString()));
// Client sends second message
@@ -83,7 +84,7 @@
client.write(WebSocketFrame.text(msg));
capture = client.readFrames(1,TimeUnit.SECONDS,1);
- frame = capture.getFrames().get(0);
+ frame = capture.getFrames().poll();
Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is(msg.toString()));
}
finally
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdentityExtensionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdentityExtensionTest.java
index e56dc95..47fa7ca 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdentityExtensionTest.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdentityExtensionTest.java
@@ -24,6 +24,7 @@
import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
+import org.eclipse.jetty.websocket.server.blockhead.HttpResponse;
import org.eclipse.jetty.websocket.server.helper.EchoServlet;
import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture;
import org.junit.AfterClass;
@@ -65,14 +66,14 @@
client.setTimeout(TimeUnit.SECONDS,1);
client.connect();
client.sendStandardRequest();
- String resp = client.expectUpgradeResponse();
+ HttpResponse resp = client.expectUpgradeResponse();
- Assert.assertThat("Response",resp,containsString("identity"));
+ Assert.assertThat("Response",resp.getExtensionsHeader(),containsString("identity"));
client.write(WebSocketFrame.text("Hello"));
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,1000);
- WebSocketFrame frame = capture.getFrames().get(0);
+ WebSocketFrame frame = capture.getFrames().poll();
Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is("Hello"));
}
finally
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java
index b7e80d3..00c001b 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java
@@ -18,6 +18,8 @@
package org.eclipse.jetty.websocket.server;
+import static org.hamcrest.Matchers.*;
+
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -43,15 +45,11 @@
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
-import org.junit.Ignore;
import org.junit.Test;
-import static org.hamcrest.Matchers.is;
-
/**
* Tests various close scenarios
*/
-@Ignore
public class WebSocketCloseTest
{
@SuppressWarnings("serial")
@@ -145,7 +143,7 @@
client.expectUpgradeResponse();
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1);
- WebSocketFrame frame = capture.getFrames().get(0);
+ WebSocketFrame frame = capture.getFrames().poll();
Assert.assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE));
CloseInfo close = new CloseInfo(frame);
Assert.assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.NORMAL));
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketInvalidVersionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketInvalidVersionTest.java
index 5d47449..410fa82 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketInvalidVersionTest.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketInvalidVersionTest.java
@@ -21,6 +21,7 @@
import static org.hamcrest.Matchers.*;
import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
+import org.eclipse.jetty.websocket.server.blockhead.HttpResponse;
import org.eclipse.jetty.websocket.server.examples.MyEchoServlet;
import org.junit.AfterClass;
import org.junit.Assert;
@@ -56,9 +57,10 @@
{
client.connect();
client.sendStandardRequest();
- String respHeader = client.readResponseHeader();
- Assert.assertThat("Response Code",respHeader,startsWith("HTTP/1.1 400 Unsupported websocket version specification"));
- Assert.assertThat("Response Header Versions",respHeader,containsString("Sec-WebSocket-Version: 13\r\n"));
+ HttpResponse response = client.readResponseHeader();
+ Assert.assertThat("Response Status Code",response.getStatusCode(),is(400));
+ Assert.assertThat("Response Status Reason",response.getStatusReason(),containsString("Unsupported websocket version specification"));
+ Assert.assertThat("Response Versions",response.getHeader("Sec-WebSocket-Version"),is("13"));
}
finally
{
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServerSessionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServerSessionTest.java
index f2a6dc5..6194090 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServerSessionTest.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServerSessionTest.java
@@ -21,6 +21,7 @@
import static org.hamcrest.Matchers.*;
import java.net.URI;
+import java.util.Queue;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.toolchain.test.AdvancedRunner;
@@ -96,13 +97,14 @@
// Read frame (hopefully text frame)
IncomingFramesCapture capture = client.readFrames(4,TimeUnit.MILLISECONDS,500);
- WebSocketFrame tf = capture.getFrames().pop();
+ Queue<WebSocketFrame> frames = capture.getFrames();
+ WebSocketFrame tf = frames.poll();
Assert.assertThat("Parameter Map[snack]",tf.getPayloadAsUTF8(),is("[cashews]"));
- tf = capture.getFrames().pop();
+ tf = frames.poll();
Assert.assertThat("Parameter Map[amount]",tf.getPayloadAsUTF8(),is("[handful]"));
- tf = capture.getFrames().pop();
+ tf = frames.poll();
Assert.assertThat("Parameter Map[brand]",tf.getPayloadAsUTF8(),is("[off]"));
- tf = capture.getFrames().pop();
+ tf = frames.poll();
Assert.assertThat("Parameter Map[cost]",tf.getPayloadAsUTF8(),is("<null>"));
}
finally
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java
index 294ee65..8ca6db3 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java
@@ -115,7 +115,7 @@
// Read frame echo'd back (hopefully a single binary frame)
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,1000);
- Frame binmsg = capture.getFrames().get(0);
+ Frame binmsg = capture.getFrames().poll();
int expectedSize = buf1.length + buf2.length + buf3.length;
Assert.assertThat("BinaryFrame.payloadLength",binmsg.getPayloadLength(),is(expectedSize));
@@ -181,7 +181,7 @@
// Read frame (hopefully text frame)
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500);
- WebSocketFrame tf = capture.getFrames().get(0);
+ WebSocketFrame tf = capture.getFrames().poll();
Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg));
}
finally
@@ -209,7 +209,7 @@
// Read frame (hopefully close frame)
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500);
- Frame cf = capture.getFrames().get(0);
+ Frame cf = capture.getFrames().poll();
CloseInfo close = new CloseInfo(cf);
Assert.assertThat("Close Frame.status code",close.getStatusCode(),is(StatusCode.SERVER_ERROR));
}
@@ -252,7 +252,7 @@
// Read frame (hopefully text frame)
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500);
- WebSocketFrame tf = capture.getFrames().get(0);
+ WebSocketFrame tf = capture.getFrames().poll();
Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg));
}
finally
@@ -292,7 +292,7 @@
}
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1);
- WebSocketFrame frame = capture.getFrames().get(0);
+ WebSocketFrame frame = capture.getFrames().poll();
Assert.assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE));
CloseInfo close = new CloseInfo(frame);
Assert.assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.MESSAGE_TOO_LARGE));
@@ -334,7 +334,7 @@
}
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1);
- WebSocketFrame frame = capture.getFrames().get(0);
+ WebSocketFrame frame = capture.getFrames().poll();
Assert.assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE));
CloseInfo close = new CloseInfo(frame);
Assert.assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.MESSAGE_TOO_LARGE));
@@ -367,7 +367,7 @@
client.writeRaw(bb);
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1);
- WebSocketFrame frame = capture.getFrames().get(0);
+ WebSocketFrame frame = capture.getFrames().poll();
Assert.assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE));
CloseInfo close = new CloseInfo(frame);
Assert.assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.BAD_PAYLOAD));
@@ -413,7 +413,7 @@
// Read frame (hopefully text frame)
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500);
- WebSocketFrame tf = capture.getFrames().get(0);
+ WebSocketFrame tf = capture.getFrames().poll();
Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg));
}
finally
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/Fuzzer.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/Fuzzer.java
index 09e9f3e..5c9d07c 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/Fuzzer.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/Fuzzer.java
@@ -151,7 +151,7 @@
for (int i = 0; i < expectedCount; i++)
{
WebSocketFrame expected = expect.get(i);
- WebSocketFrame actual = capture.getFrames().pop();
+ WebSocketFrame actual = capture.getFrames().poll();
prefix = "Frame[" + i + "]";
@@ -188,14 +188,16 @@
// we expect that the close handshake to have occurred and the server should have closed the connection
try
{
- @SuppressWarnings("unused")
- int val = client.read();
+ ByteBuffer buf = ByteBuffer.wrap(new byte[]
+ { 0x00 });
+ BufferUtil.flipToFill(buf);
+ int len = client.read(buf);
- Assert.fail("Server has not closed socket");
+ Assert.assertThat("Server has not closed socket",len,lessThanOrEqualTo(0));
}
- catch (SocketException e)
+ catch (IOException e)
{
-
+ // valid path
}
IOState ios = client.getIOState();
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java
index 561f9a8..fcb40ff 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java
@@ -19,13 +19,10 @@
package org.eclipse.jetty.websocket.server.blockhead;
import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-import java.io.BufferedReader;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
-import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetAddress;
@@ -41,8 +38,6 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
import javax.net.ssl.HttpsURLConnection;
@@ -51,7 +46,6 @@
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.Utf8StringBuilder;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.WebSocketException;
@@ -71,6 +65,8 @@
import org.eclipse.jetty.websocket.common.extensions.ExtensionStack;
import org.eclipse.jetty.websocket.common.extensions.WebSocketExtensionFactory;
import org.eclipse.jetty.websocket.common.io.IOState;
+import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
+import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParser;
import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture;
import org.junit.Assert;
@@ -86,7 +82,7 @@
* with regards to basic IO behavior, a write should work as expected, a read should work as expected, but <u>what</u> byte it sends or reads is not within its
* scope.
*/
-public class BlockheadClient implements IncomingFrames, OutgoingFrames
+public class BlockheadClient implements IncomingFrames, OutgoingFrames, ConnectionStateListener
{
private static final String REQUEST_HASH_KEY = "dGhlIHNhbXBsZSBub25jZQ==";
private static final int BUFFER_SIZE = 8192;
@@ -117,6 +113,7 @@
private ExtensionStack extensionStack;
private IOState ioState;
private CountDownLatch disconnectedLatch = new CountDownLatch(1);
+ private ByteBuffer remainingBuffer;
public BlockheadClient(URI destWebsocketURI) throws URISyntaxException
{
@@ -144,6 +141,7 @@
this.extensionFactory = new WebSocketExtensionFactory(policy,bufferPool);
this.ioState = new IOState();
+ this.ioState.addListener(this);
}
public void addExtensions(String xtension)
@@ -179,24 +177,22 @@
public void close(int statusCode, String message)
{
- try
- {
- CloseInfo close = new CloseInfo(statusCode,message);
+ CloseInfo close = new CloseInfo(statusCode,message);
- if (ioState.onCloseHandshake(false))
+ ioState.onCloseLocal(close);
+
+ if (!ioState.isClosed())
+ {
+ WebSocketFrame frame = close.asFrame();
+ LOG.debug("Issuing: {}",frame);
+ try
{
- this.disconnect();
- }
- else
- {
- WebSocketFrame frame = close.asFrame();
- LOG.debug("Issuing: {}",frame);
write(frame);
}
- }
- catch (IOException e)
- {
- LOG.debug(e);
+ catch (IOException e)
+ {
+ LOG.debug(e);
+ }
}
}
@@ -234,32 +230,31 @@
}
}
- public String expectUpgradeResponse() throws IOException
+ public HttpResponse expectUpgradeResponse() throws IOException
{
- String respHeader = readResponseHeader();
+ HttpResponse response = readResponseHeader();
if (LOG.isDebugEnabled())
{
- LOG.debug("Response Header: {}{}",'\n',respHeader);
+ LOG.debug("Response Header: {}{}",'\n',response);
}
- Assert.assertThat("Response Code",respHeader,startsWith("HTTP/1.1 101 Switching Protocols"));
- Assert.assertThat("Response Header Upgrade",respHeader,containsString("Upgrade: WebSocket\r\n"));
- Assert.assertThat("Response Header Connection",respHeader,containsString("Connection: Upgrade\r\n"));
+ Assert.assertThat("Response Status Code",response.getStatusCode(),is(101));
+ Assert.assertThat("Response Status Reason",response.getStatusReason(),is("Switching Protocols"));
+ Assert.assertThat("Response Header[Upgrade]",response.getHeader("Upgrade"),is("WebSocket"));
+ Assert.assertThat("Response Header[Connection]",response.getHeader("Connection"),is("Upgrade"));
// Validate the Sec-WebSocket-Accept
- Pattern patAcceptHeader = Pattern.compile("Sec-WebSocket-Accept: (.*=)",Pattern.CASE_INSENSITIVE);
- Matcher matAcceptHeader = patAcceptHeader.matcher(respHeader);
- Assert.assertThat("Response Header Sec-WebSocket-Accept Exists?",matAcceptHeader.find(),is(true));
+ String acceptKey = response.getHeader("Sec-WebSocket-Accept");
+ Assert.assertThat("Response Header[Sec-WebSocket-Accept Exists]",acceptKey,notNullValue());
String reqKey = REQUEST_HASH_KEY;
String expectedHash = AcceptHash.hashKey(reqKey);
- String acceptKey = matAcceptHeader.group(1);
Assert.assertThat("Valid Sec-WebSocket-Accept Hash?",acceptKey,is(expectedHash));
// collect extensions configured in response header
- List<ExtensionConfig> configs = getExtensionConfigs(respHeader);
+ List<ExtensionConfig> configs = getExtensionConfigs(response);
extensionStack = new ExtensionStack(this.extensionFactory);
extensionStack.negotiate(configs);
@@ -283,12 +278,12 @@
// configure parser
parser.setIncomingFramesHandler(extensionStack);
- ioState.setState(ConnectionState.OPEN);
+ ioState.onOpened();
LOG.debug("outgoing = {}",outgoing);
LOG.debug("incoming = {}",extensionStack);
- return respHeader;
+ return response;
}
public void flush() throws IOException
@@ -296,22 +291,16 @@
out.flush();
}
- private List<ExtensionConfig> getExtensionConfigs(String respHeader)
+ private List<ExtensionConfig> getExtensionConfigs(HttpResponse response)
{
List<ExtensionConfig> configs = new ArrayList<>();
- Pattern expat = Pattern.compile("Sec-WebSocket-Extensions: (.*)\r",Pattern.CASE_INSENSITIVE);
- Matcher mat = expat.matcher(respHeader);
- int offset = 0;
- while (mat.find(offset))
+ String econf = response.getHeader("Sec-WebSocket-Extensions");
+ if (econf != null)
{
- String econf = mat.group(1);
LOG.debug("Found Extension Response: {}",econf);
-
ExtensionConfig config = ExtensionConfig.parse(econf);
configs.add(config);
-
- offset = mat.end(1);
}
return configs;
}
@@ -404,14 +393,7 @@
if (frame.getType() == Frame.Type.CLOSE)
{
CloseInfo close = new CloseInfo(frame);
- if (ioState.onCloseHandshake(true))
- {
- this.disconnect();
- }
- else
- {
- close(close.getStatusCode(),close.getReason());
- }
+ ioState.onCloseRemote(close);
}
WebSocketFrame copy = new WebSocketFrame(frame);
@@ -423,32 +405,24 @@
return (socket != null) && (socket.isConnected());
}
- public void lookFor(String string) throws IOException
+ @Override
+ public void onConnectionStateChange(ConnectionState state)
{
- String orig = string;
- Utf8StringBuilder scanned = new Utf8StringBuilder();
- try
+ switch (state)
{
- while (true)
- {
- int b = in.read();
- if (b < 0)
+ case CLOSED:
+ this.disconnect();
+ break;
+ case CLOSING:
+ if (ioState.wasRemoteCloseInitiated())
{
- throw new EOFException();
+ CloseInfo close = ioState.getCloseInfo();
+ close(close.getStatusCode(),close.getReason());
}
- scanned.append((byte)b);
- assertEquals("looking for\"" + orig + "\" in '" + scanned + "'",string.charAt(0),b);
- if (string.length() == 1)
- {
- break;
- }
- string = string.substring(1);
- }
- }
- catch (IOException e)
- {
- System.err.println("IOE while looking for \"" + orig + "\" in '" + scanned + "'");
- throw e;
+ break;
+ default:
+ /* do nothing */
+ break;
}
}
@@ -487,17 +461,18 @@
}
}
- public int read() throws IOException
- {
- return in.read();
- }
-
public int read(ByteBuffer buf) throws IOException
{
if (eof)
{
throw new EOFException("Hit EOF");
}
+
+ if ((remainingBuffer != null) && (remainingBuffer.remaining() > 0))
+ {
+ return BufferUtil.put(remainingBuffer,buf);
+ }
+
int len = 0;
int b;
while ((in.available() > 0) && (buf.remaining() > 0))
@@ -572,25 +547,24 @@
return incomingFrames;
}
- public String readResponseHeader() throws IOException
+ public HttpResponse readResponseHeader() throws IOException
{
- InputStreamReader isr = new InputStreamReader(in);
- BufferedReader reader = new BufferedReader(isr);
- StringBuilder header = new StringBuilder();
- // Read the response header
- String line = reader.readLine();
- Assert.assertNotNull(line);
- Assert.assertThat(line,startsWith("HTTP/1.1 "));
- header.append(line).append("\r\n");
- while ((line = reader.readLine()) != null)
+ HttpResponse response = new HttpResponse();
+ HttpResponseHeaderParser parser = new HttpResponseHeaderParser(response);
+
+ ByteBuffer buf = BufferUtil.allocate(512);
+
+ do
{
- if (line.trim().length() == 0)
- {
- break;
- }
- header.append(line).append("\r\n");
+ BufferUtil.flipToFill(buf);
+ read(buf);
+ BufferUtil.flipToFlush(buf,0);
}
- return header.toString();
+ while (parser.parse(buf) == null);
+
+ remainingBuffer = response.getRemainingBuffer();
+
+ return response;
}
public void sendStandardRequest() throws IOException
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/HttpResponse.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/HttpResponse.java
new file mode 100644
index 0000000..59d44eb
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/HttpResponse.java
@@ -0,0 +1,95 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2013 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.websocket.server.blockhead;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParseListener;
+
+public class HttpResponse implements HttpResponseHeaderParseListener
+{
+ private int statusCode;
+ private String statusReason;
+ private Map<String, String> headers = new HashMap<>();
+ private ByteBuffer remainingBuffer;
+
+ @Override
+ public void addHeader(String name, String value)
+ {
+ headers.put(name.toLowerCase(Locale.ENGLISH),value);
+ }
+
+ public String getExtensionsHeader()
+ {
+ return getHeader("Sec-WebSocket-Extensions");
+ }
+
+ public String getHeader(String name)
+ {
+ return headers.get(name.toLowerCase(Locale.ENGLISH));
+ }
+
+ public ByteBuffer getRemainingBuffer()
+ {
+ return remainingBuffer;
+ }
+
+ public int getStatusCode()
+ {
+ return statusCode;
+ }
+
+ public String getStatusReason()
+ {
+ return statusReason;
+ }
+
+ @Override
+ public void setRemainingBuffer(ByteBuffer copy)
+ {
+ this.remainingBuffer = copy;
+ }
+
+ @Override
+ public void setStatusCode(int code)
+ {
+ this.statusCode = code;
+ }
+
+ @Override
+ public void setStatusReason(String reason)
+ {
+ this.statusReason = reason;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder str = new StringBuilder();
+ str.append("HTTP/1.1 ").append(statusCode).append(' ').append(statusReason);
+ for (Map.Entry<String, String> entry : headers.entrySet())
+ {
+ str.append('\n').append(entry.getKey()).append(": ").append(entry.getValue());
+ }
+ return str.toString();
+ }
+}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java
index a00bc71..d618681 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java
@@ -116,6 +116,9 @@
// Setup the desired Socket to use for all incoming upgrade requests
factory.setCreator(BrowserDebugTool.this);
+
+ // Set the timeout
+ factory.getPolicy().setIdleTimeout(2000);
}
};
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/ExampleEchoServer.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/ExampleEchoServer.java
index e9ce087..f54f12e 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/ExampleEchoServer.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/ExampleEchoServer.java
@@ -31,6 +31,15 @@
*/
public class ExampleEchoServer
{
+ public final class EchoSocketHandler extends WebSocketHandler
+ {
+ @Override
+ public void configure(WebSocketServletFactory factory)
+ {
+ factory.setCreator(new EchoCreator());
+ }
+ }
+
private static final Logger LOG = Log.getLogger(ExampleEchoServer.class);
public static void main(String... args)
@@ -96,14 +105,7 @@
connector.setPort(port);
server.addConnector(connector);
- wsHandler = new WebSocketHandler()
- {
- @Override
- public void configure(WebSocketServletFactory factory)
- {
- factory.setCreator(new EchoCreator());
- }
- };
+ wsHandler = new EchoSocketHandler();
server.setHandler(wsHandler);
@@ -126,6 +128,14 @@
public void runForever() throws Exception
{
server.start();
+ String host = connector.getHost();
+ if (host == null)
+ {
+ host = "localhost";
+ }
+ int port = connector.getLocalPort();
+ System.err.printf("Echo Server started on ws://%s:%d/%n",host,port);
+ System.err.println(server.dump());
server.join();
}
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/IncomingFramesCapture.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/IncomingFramesCapture.java
index a0e2889..82a4723 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/IncomingFramesCapture.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/IncomingFramesCapture.java
@@ -20,8 +20,9 @@
import static org.hamcrest.Matchers.*;
-import java.util.LinkedList;
+import java.util.Queue;
+import org.eclipse.jetty.toolchain.test.EventQueue;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -35,8 +36,8 @@
public class IncomingFramesCapture implements IncomingFrames
{
private static final Logger LOG = Log.getLogger(IncomingFramesCapture.class);
- private LinkedList<WebSocketFrame> frames = new LinkedList<>();
- private LinkedList<WebSocketException> errors = new LinkedList<>();
+ private EventQueue<WebSocketFrame> frames = new EventQueue<>();
+ private EventQueue<WebSocketException> errors = new EventQueue<>();
public void assertErrorCount(int expectedCount)
{
@@ -81,10 +82,10 @@
public void dump()
{
System.err.printf("Captured %d incoming frames%n",frames.size());
- for (int i = 0; i < frames.size(); i++)
+ int i = 0;
+ for (Frame frame : frames)
{
- Frame frame = frames.get(i);
- System.err.printf("[%3d] %s%n",i,frame);
+ System.err.printf("[%3d] %s%n",i++,frame);
System.err.printf(" %s%n",BufferUtil.toDetailString(frame.getPayload()));
}
}
@@ -102,7 +103,7 @@
return count;
}
- public LinkedList<WebSocketException> getErrors()
+ public Queue<WebSocketException> getErrors()
{
return errors;
}
@@ -120,7 +121,7 @@
return count;
}
- public LinkedList<WebSocketFrame> getFrames()
+ public Queue<WebSocketFrame> getFrames()
{
return frames;
}
diff --git a/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties b/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties
index c27be61..e7122e6 100644
--- a/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties
+++ b/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties
@@ -5,7 +5,6 @@
# org.eclipse.jetty.websocket.LEVEL=WARN
# org.eclipse.jetty.websocket.common.io.LEVEL=DEBUG
# org.eclipse.jetty.websocket.server.ab.LEVEL=DEBUG
-# org.eclipse.jetty.websocket.common.io.LEVEL=DEBUG
# org.eclipse.jetty.websocket.common.Parser.LEVEL=DEBUG
# org.eclipse.jetty.websocket.common.Generator.LEVEL=DEBUG
# org.eclipse.jetty.websocket.server.ab.Fuzzer.LEVEL=DEBUG
diff --git a/pom.xml b/pom.xml
index 7b480d9..417706f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -542,7 +542,7 @@
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>
- <version>2.0</version>
+ <version>2.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
diff --git a/tests/test-webapps/test-jaas-webapp/pom.xml b/tests/test-webapps/test-jaas-webapp/pom.xml
index a743267..c7f27b1 100644
--- a/tests/test-webapps/test-jaas-webapp/pom.xml
+++ b/tests/test-webapps/test-jaas-webapp/pom.xml
@@ -34,7 +34,7 @@
<!-- Mandatory. This system property tells JAAS where to find the login module configuration file -->
<systemProperty>
<name>java.security.auth.login.config</name>
- <value>${basedir}/src/main/config/etc/login.conf</value>
+ <value>${basedir}/src/main/config/webapps.demo/test-jaas.d/login.conf</value>
</systemProperty>
</systemProperties>
<webAppConfig>
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/assembly/config.xml b/tests/test-webapps/test-jaas-webapp/src/main/assembly/config.xml
index 04effea..3fca8c6 100644
--- a/tests/test-webapps/test-jaas-webapp/src/main/assembly/config.xml
+++ b/tests/test-webapps/test-jaas-webapp/src/main/assembly/config.xml
@@ -11,7 +11,7 @@
<outputDirectory></outputDirectory>
<includes>
<include>webapps/test-jaas.xml</include>
- <include>etc/**</include>
+ <include>**</include>
</includes>
</fileSet>
</fileSets>
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/config/etc/login.conf b/tests/test-webapps/test-jaas-webapp/src/main/config/etc/login.conf
deleted file mode 100644
index c7a2103..0000000
--- a/tests/test-webapps/test-jaas-webapp/src/main/config/etc/login.conf
+++ /dev/null
@@ -1,5 +0,0 @@
-xyz {
-org.eclipse.jetty.jaas.spi.PropertyFileLoginModule required
-debug="true"
-file="${jetty.home}/etc/login.properties";
-};
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/config/webapps.demo/test-jaas.d/login.conf b/tests/test-webapps/test-jaas-webapp/src/main/config/webapps.demo/test-jaas.d/login.conf
new file mode 100644
index 0000000..0978de6
--- /dev/null
+++ b/tests/test-webapps/test-jaas-webapp/src/main/config/webapps.demo/test-jaas.d/login.conf
@@ -0,0 +1,5 @@
+xyz {
+org.eclipse.jetty.jaas.spi.PropertyFileLoginModule required
+debug="true"
+file="${jetty.home}/webapps.demo/test-jaas.d/login.properties";
+};
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/config/etc/login.properties b/tests/test-webapps/test-jaas-webapp/src/main/config/webapps.demo/test-jaas.d/login.properties
similarity index 100%
rename from tests/test-webapps/test-jaas-webapp/src/main/config/etc/login.properties
rename to tests/test-webapps/test-jaas-webapp/src/main/config/webapps.demo/test-jaas.d/login.properties
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/config/webapps/test-jaas.xml b/tests/test-webapps/test-jaas-webapp/src/main/config/webapps.demo/test-jaas.xml
similarity index 89%
rename from tests/test-webapps/test-jaas-webapp/src/main/config/webapps/test-jaas.xml
rename to tests/test-webapps/test-jaas-webapp/src/main/config/webapps.demo/test-jaas.xml
index 1720ba9..f3b0a18 100644
--- a/tests/test-webapps/test-jaas-webapp/src/main/config/webapps/test-jaas.xml
+++ b/tests/test-webapps/test-jaas-webapp/src/main/config/webapps.demo/test-jaas.xml
@@ -7,7 +7,7 @@
<Configure id='wac' class="org.eclipse.jetty.webapp.WebAppContext">
<Set name="contextPath">/test-jaas</Set>
- <Set name="war"><SystemProperty name="jetty.home" default="."/>/webapps/test-jaas.war</Set>
+ <Set name="war"><Property name="jetty.webapps" default="."/>/test-jaas.war</Set>
<Set name="extractWAR">true</Set>
<Set name="securityHandler">
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/webapp/auth.html b/tests/test-webapps/test-jaas-webapp/src/main/webapp/auth.html
index 829c925..249f958 100644
--- a/tests/test-webapps/test-jaas-webapp/src/main/webapp/auth.html
+++ b/tests/test-webapps/test-jaas-webapp/src/main/webapp/auth.html
@@ -2,11 +2,12 @@
<HEAD>
<META http-equiv="Pragma" content="no-cache">
<META http-equiv="Cache-Control" content="no-cache,no-store">
+ <link rel="stylesheet" type="text/css" href="stylesheet.css"/>
</HEAD>
<BODY>
- <H1>Congratulations, you are AUTHENTICATED and web AUTHORIZED</H1>
- Well done. In order to see this page, you must have been JAAS authentictated using the
+ <H1>SUCCESS! You are AUTHENTICATED and AUTHORIZED</H1>
+ In order to see this page, you must have been JAAS authentictated using the
configured Login Module. You have also been AUTHORIZED according to this webapp's role-based web security constraints.
<P>
To logout click:
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/webapp/authfail.html b/tests/test-webapps/test-jaas-webapp/src/main/webapp/authfail.html
index a390511..f57687b 100644
--- a/tests/test-webapps/test-jaas-webapp/src/main/webapp/authfail.html
+++ b/tests/test-webapps/test-jaas-webapp/src/main/webapp/authfail.html
@@ -1,6 +1,7 @@
<html>
<head>
<title>Authentication Failure</title>
+ <link rel="stylesheet" type="text/css" href="stylesheet.css"/>
</head>
<body>
<h1>Authentication Failure</h1>
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/webapp/index.html b/tests/test-webapps/test-jaas-webapp/src/main/webapp/index.html
index 62b744f..d9f637b 100644
--- a/tests/test-webapps/test-jaas-webapp/src/main/webapp/index.html
+++ b/tests/test-webapps/test-jaas-webapp/src/main/webapp/index.html
@@ -3,12 +3,7 @@
<TITLE>JAAS Authentication and Authorization Test</TITLE>
<META http-equiv="Pragma" content="no-cache">
<META http-equiv="Cache-Control" content="no-cache,no-store">
- <style>
- body {color: #2E2E2E; font-family:sans-serif; font-size:90%;}
- h1 {font-variant: small-caps; font-size:130%; letter-spacing: 0.1em;}
- h2 {font-variant: small-caps; font-size:100%; letter-spacing: 0.1em;}
- </style>
-
+ <link rel="stylesheet" type="text/css" href="stylesheet.css"/>
</HEAD>
<BODY>
<A HREF="http://www.eclipse.org/jetty"><IMG SRC="images/jetty_banner.gif"></A>
@@ -22,26 +17,17 @@
<H1>JAAS Authentication and Authorization Demo </H1>
<h2>Preparation</h2>
- <ol>
- <li>You will need to edit your $JETTY_HOME/start.ini file and add the following lines:
+ <p>To enable JAAS, edit your start.ini or start.d/*.ini files and add the following lines:
<pre>
OPTIONS=jaas
+ jaas.login.conf=etc/login.conf
etc/jetty-jaas.xml
</pre>
- </li>
+ </p>
+ <p>For the jetty distribution demos, jaas is already enabled in the start.d/900-demo.ini file and sets the jaas.login.conf property to webapps.demo/test-jaas.d/login.conf for use with the webapps.demo/test-jaas.war web application. </p>
- <li>Unjar the test-jaas-webapp-<version>-config.jar inside $JETTY_HOME. The following files will be added:
- <pre>
- etc/login.conf
- etc/login.properties
- contexts/test-jaas.xml
- </pre>
- </li>
- </ol>
-
- <p>Now start jetty as usual.</p>
-
-
+<p>The full source of this demonstration is available <a
+href="http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/tests/test-webapps/test-jaas-webapp">here</a>.</p>
<h2>Using the Demo</h2>
<P>
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/webapp/login.html b/tests/test-webapps/test-jaas-webapp/src/main/webapp/login.html
index 4de6491..608ea5e 100644
--- a/tests/test-webapps/test-jaas-webapp/src/main/webapp/login.html
+++ b/tests/test-webapps/test-jaas-webapp/src/main/webapp/login.html
@@ -1,5 +1,8 @@
-<HTML><HEAD><TITLE>JAAS Authentication and Authorization Test</TITLE></HEAD>
+<HTML>
+ <HEAD><TITLE>JAAS Authentication and Authorization Test</TITLE>
+ <link rel="stylesheet" type="text/css" href="stylesheet.css"/>
+</HEAD>
<BODY>
<H1> Enter your username and password to login </H1>
<I> Enter login=me and password=me in order to authenticate successfully</I>
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/webapp/stylesheet.css b/tests/test-webapps/test-jaas-webapp/src/main/webapp/stylesheet.css
new file mode 100644
index 0000000..4ecc2cb
--- /dev/null
+++ b/tests/test-webapps/test-jaas-webapp/src/main/webapp/stylesheet.css
@@ -0,0 +1,7 @@
+body {color: #2E2E2E; font-family:sans-serif; font-size:90%;}
+h1 {font-variant: small-caps; font-size:130%; letter-spacing: 0.1em;}
+h2 {font-variant: small-caps; font-size:100%; letter-spacing: 0.1em;}
+h3 {font-size:100%; letter-spacing: 0.1em;}
+
+span.pass { color: green; }
+span.fail { color:red; }
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/config/start.d/test-webapp.ini b/tests/test-webapps/test-jetty-webapp/src/main/config/start.d/test-webapp.ini
deleted file mode 100644
index 2c4006b..0000000
--- a/tests/test-webapps/test-jetty-webapp/src/main/config/start.d/test-webapp.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-# Enabled services and configuration needed by the test webapp
-OPTIONS=client
-etc/test-realm.xml
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/config/webapps/test.d/override-web.xml b/tests/test-webapps/test-jetty-webapp/src/main/config/webapps.demo/test.d/override-web.xml
similarity index 100%
rename from tests/test-webapps/test-jetty-webapp/src/main/config/webapps/test.d/override-web.xml
rename to tests/test-webapps/test-jetty-webapp/src/main/config/webapps.demo/test.d/override-web.xml
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/config/webapps/test.xml b/tests/test-webapps/test-jetty-webapp/src/main/config/webapps.demo/test.xml
similarity index 92%
rename from tests/test-webapps/test-jetty-webapp/src/main/config/webapps/test.xml
rename to tests/test-webapps/test-jetty-webapp/src/main/config/webapps.demo/test.xml
index ff7eb6b..44cbce5 100644
--- a/tests/test-webapps/test-jetty-webapp/src/main/config/webapps/test.xml
+++ b/tests/test-webapps/test-jetty-webapp/src/main/config/webapps.demo/test.xml
@@ -19,15 +19,15 @@
<!-- + war OR resourceBase -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<Set name="contextPath">/test</Set>
- <Set name="war"><SystemProperty name="jetty.home" default="."/>/webapps/test.war</Set>
+ <Set name="war"><Property name="jetty.webapps" default="."/>/test.war</Set>
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- Optional context configuration -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<Set name="extractWAR">true</Set>
<Set name="copyWebDir">false</Set>
- <Set name="defaultsDescriptor"><SystemProperty name="jetty.home" default="."/>/etc/webdefault.xml</Set>
- <Set name="overrideDescriptor"><SystemProperty name="jetty.home" default="."/>/webapps/test.d/override-web.xml</Set>
+ <Set name="defaultsDescriptor"><Property name="jetty.home" default="."/>/etc/webdefault.xml</Set>
+ <Set name="overrideDescriptor"><Property name="jetty.webapps" default="."/>/test.d/override-web.xml</Set>
<!-- Allow directory symbolic links -->
<Call name="addAliasCheck">
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/Dump.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/Dump.java
index 93a04e2..326015b 100644
--- a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/Dump.java
+++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/Dump.java
@@ -498,6 +498,10 @@
pout.write("<td>"+request.isSecure()+"</td>");
pout.write("</tr><tr>\n");
+ pout.write("<th align=\"right\">encodeRedirectURL(/foo?bar): </th>");
+ pout.write("<td>"+response.encodeRedirectURL("/foo?bar")+"</td>");
+
+ pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">isUserInRole(admin): </th>");
pout.write("<td>"+request.isUserInRole("admin")+"</td>");
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/webapp/index.html b/tests/test-webapps/test-jetty-webapp/src/main/webapp/index.html
index c088e0e..c81fcb9 100644
--- a/tests/test-webapps/test-jetty-webapp/src/main/webapp/index.html
+++ b/tests/test-webapps/test-jetty-webapp/src/main/webapp/index.html
@@ -13,49 +13,37 @@
<A HREF="http://jetty.eclipse.org"><IMG SRC="jetty_banner.gif"></A>
<h1>Welcome to Jetty 9</h1>
<p>
-This is the Test webapp for the Jetty 9 HTTP Server and Servlet Container.
-For more information about Jetty, please visit our
-<a href="http://www.eclipse.org/jetty">website</a>
-or <a href="http://www.eclipse.org/jetty/documentation/current/">documentation</a> hub.<br/>
-Commercial support for Jetty is available via <a href="http://www.webtide.com">Webtide</a> and <a href="http://www.intalio.com">Intalio</a>.
-</p>
-<p>
-This is a test context that serves:
-</p>
+This is the Test webapp for the Jetty 9 HTTP Server and Servlet Container. It is
+deployed in $JETTY_HOME/webapp.demo and configured by $JETTY_HOME/start.d/900-demo.ini
<ul>
+<li>Servet: <a href="hello/">Hello World</a></li>
+<li>Dump: <a href="dump/info">Request</a>, <a href="session/">Session</a>, <a href="cookie/">Cookie</a></li>
+<li>JSP: <a href="jsp/">examples</a></li>
+<li>Comet Chat: <a href="chat/">Long Polling</a>, <a href="ws">WebSocket</a></li>
+<li><a href="auth.html">Authentication</a></li>
+<li><a href="dispatch">Dispatcher Servlet</a></li>
+<li><a href="rewrite/">Request Rewrite Servlet</a></li>
<li>static content:
<a href="d.txt">tiny</a>,
<a href="da.txt">small</a>,
<a href="dat.txt">medium</a>,
<a href="data.txt">large</a>,
<a href="data.txt.gz">large gziped</a></li>
-<li><a href="hello/">Hello World Servlet</a></li>
-<li>Dump: <a href="dump/info">Request</a>, <a href="session/">Session</a>, <a href="cookie/">Cookie</a></li>
-<li><a href="dispatch">Dispatcher Servlet</a></li>
-<li><a href="rewrite/">Request Rewrite Servlet</a></li>
-<li><a href="jsp/">JSP examples</a></li>
-<li>Comet Chat: <a href="chat/">Long Polling</a>, <a href="ws">WebSocket</a></li>
-<li><a href="auth.html">Authentication</a></li>
</ul>
-<p/>
-<!--
<p>
-Other demonstration contexts, some of which may need manual deployment
-(check the README.txt file for details):
-</p>
-<ul>
-<li>a <a href="chat/demo">AJAX Comet Chat with com.acme.ChatServlet demo</a></li>
-<li>a <a href="/cometd/">AJAX Cometd Chat with cometd Bayeux</a></li>
-<li> the <a href="/javadoc/">javadoc</a> </li>
-<li> a demo of the <a href="/test-jndi">JNDI features</a></li>
-<li> a demo of the <a href="/test-annotations">Annotation features</a></li>
-<li> a demo of the <a href="/test-jaas">JAAS features</a></li>
+Useful links are:<ul>
+<li><a
+href="http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/tests/test-webapps/test-jetty-webapp">Source
+tree of this webapp</a></li>
+<li><a href="http://www.eclipse.org/jetty">Jetty project home</a></li>
+<li><a href="http://www.eclipse.org/jetty/documentation/current/">Documentation</a></li>
+<li><a href="http://www.webtide.com">Commercial Support</a></li>
</ul>
-<p/>
--->
+</p>
-This webapp is deployed in $JETTY_HOME/webapp/test and configured by $JETTY_HOME/contexts/test.xml
-
+<p>
+<a href="/">MAIN MENU</a>
+</p>
</BODY>
</HTML>
diff --git a/tests/test-webapps/test-jndi-webapp/src/main/assembly/config.xml b/tests/test-webapps/test-jndi-webapp/src/main/assembly/config.xml
index e307ad4..f4b5d58 100644
--- a/tests/test-webapps/test-jndi-webapp/src/main/assembly/config.xml
+++ b/tests/test-webapps/test-jndi-webapp/src/main/assembly/config.xml
@@ -18,16 +18,16 @@
</fileSet>
<fileSet>
<directory>target</directory>
- <outputDirectory>webapps</outputDirectory>
+ <outputDirectory>webapps.demo</outputDirectory>
<includes>
<include>test-jndi.xml</include>
</includes>
</fileSet>
<fileSet>
- <directory>target</directory>
- <outputDirectory></outputDirectory>
+ <directory>target/lib/jndi</directory>
+ <outputDirectory>lib/jndi.demo</outputDirectory>
<includes>
- <include>lib/jndi/**</include>
+ <include>*.jar</include>
</includes>
</fileSet>
</fileSets>
diff --git a/tests/test-webapps/test-jndi-webapp/src/main/java/com/acme/JNDITest.java b/tests/test-webapps/test-jndi-webapp/src/main/java/com/acme/JNDITest.java
index fbd3626..12bc848 100644
--- a/tests/test-webapps/test-jndi-webapp/src/main/java/com/acme/JNDITest.java
+++ b/tests/test-webapps/test-jndi-webapp/src/main/java/com/acme/JNDITest.java
@@ -55,8 +55,8 @@
private String resourceNameMappingInjectionResult;
private String envEntryOverrideResult;
- private String postConstructResult = "PostConstruct method called: FALSE";
- private String preDestroyResult = "PreDestroy method called: NOT YET";
+ private String postConstructResult = "PostConstruct method called: <span class=\"fail\">FALSE</span>";
+ private String preDestroyResult = "PreDestroy method called: <span class=\"pass\">NOT YET</span>";
private String envEntryGlobalScopeResult;
private String envEntryWebAppScopeResult;
private String userTransactionResult;
@@ -71,15 +71,14 @@
private void postConstruct ()
{
- String tmp = (myDS == null?"":myDS.toString());
- resourceNameMappingInjectionResult= "Injection of resource to locally mapped name (java:comp/env/mydatasource as java:comp/env/mydatasource1): "+String.valueOf(myDS);
- envEntryOverrideResult = "Override of EnvEntry in jetty-env.xml (java:comp/env/wiggle): "+(wiggle==55.0?"PASS":"FAIL(expected 55.0, got "+wiggle+")");
- postConstructResult = "PostConstruct method called: PASS";
+ resourceNameMappingInjectionResult= "Injection of resource to locally mapped name (java:comp/env/mydatasource as java:comp/env/mydatasource1): "+(myDS!=null?"<span class=\"pass\">PASS</span>":"<span class=\"fail\">FAIL</span>");
+ envEntryOverrideResult = "Override of EnvEntry in jetty-env.xml (java:comp/env/wiggle): "+(wiggle==55.0?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL(expected 55.0, got "+wiggle+")")+"</span>";
+ postConstructResult = "PostConstruct method called: <span class=\"pass\">PASS</span>";
}
private void preDestroy()
{
- preDestroyResult = "PreDestroy method called: PASS";
+ preDestroyResult = "PreDestroy method called: <span class=\"pass\">PASS</span>";
}
@@ -90,13 +89,13 @@
{
InitialContext ic = new InitialContext();
woggle = (Integer)ic.lookup("java:comp/env/woggle");
- envEntryGlobalScopeResult = "EnvEntry defined in context xml lookup result (java:comp/env/woggle): "+(woggle==4000?"PASS":"FAIL(expected 4000, got "+woggle+")");
+ envEntryGlobalScopeResult = "EnvEntry defined in context xml lookup result (java:comp/env/woggle): "+(woggle==4000?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL(expected 4000, got "+woggle+")")+"</span>";
gargle = (Double)ic.lookup("java:comp/env/gargle");
- envEntryWebAppScopeResult = "EnvEntry defined in jetty-env.xml lookup result (java:comp/env/gargle): "+(gargle==100.0?"PASS":"FAIL(expected 100, got "+gargle+")");
+ envEntryWebAppScopeResult = "EnvEntry defined in jetty-env.xml lookup result (java:comp/env/gargle): "+(gargle==100.0?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL(expected 100, got "+gargle+")")+"</span>";
UserTransaction utx = (UserTransaction)ic.lookup("java:comp/UserTransaction");
- userTransactionResult = "UserTransaction lookup result (java:comp/UserTransaction): "+(utx!=null?"PASS":"FAIL");
+ userTransactionResult = "UserTransaction lookup result (java:comp/UserTransaction): "+(utx!=null?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL")+"</span>";
myMailSession = (Session)ic.lookup("java:comp/env/mail/Session");
- mailSessionResult = "Mail Session lookup result (java:comp/env/mail/Session): "+(myMailSession!=null?"PASS": "FAIL");
+ mailSessionResult = "Mail Session lookup result (java:comp/env/mail/Session): "+(myMailSession!=null?"<span class=\"pass\">PASS": "<span class=\"fail\">FAIL")+"</span>";
}
catch (Exception e)
{
@@ -129,6 +128,7 @@
response.setContentType("text/html");
ServletOutputStream out = response.getOutputStream();
out.println("<html>");
+ out.println("<head><link rel=\"stylesheet\" type=\"text/css\" href=\"stylesheet.css\"/></head>");
out.println("<h1>Jetty JNDI Tests</h1>");
out.println("<body>");
diff --git a/tests/test-webapps/test-jndi-webapp/src/main/templates/jetty-test-jndi-header.xml b/tests/test-webapps/test-jndi-webapp/src/main/templates/jetty-test-jndi-header.xml
index de012e7..4f4ded0 100644
--- a/tests/test-webapps/test-jndi-webapp/src/main/templates/jetty-test-jndi-header.xml
+++ b/tests/test-webapps/test-jndi-webapp/src/main/templates/jetty-test-jndi-header.xml
@@ -35,7 +35,7 @@
-->
<Set name="contextPath">/test-jndi</Set>
- <Set name="war"><SystemProperty name="jetty.home" default="."/>/webapps/test-jndi.war</Set>
+ <Set name="war"><Property name="jetty.webapps" default="."/>/test-jndi.war</Set>
<Set name="extractWAR">true</Set>
<Set name="copyWebDir">false</Set>
<Set name="configurationDiscovered">true</Set>
diff --git a/tests/test-webapps/test-jndi-webapp/src/main/templates/plugin-context-header.xml b/tests/test-webapps/test-jndi-webapp/src/main/templates/plugin-context-header.xml
index 1fe7679..3e877a5 100644
--- a/tests/test-webapps/test-jndi-webapp/src/main/templates/plugin-context-header.xml
+++ b/tests/test-webapps/test-jndi-webapp/src/main/templates/plugin-context-header.xml
@@ -7,14 +7,9 @@
<Configure id='wac' class="org.eclipse.jetty.webapp.WebAppContext">
<New id="tx" class="org.eclipse.jetty.plus.jndi.Transaction">
- <Arg>
- <New class="com.acme.MockUserTransaction"/>
- </Arg>
- </New>
-
-
- <!-- only necessary for mvn jetty:run -->
-
-
+ <Arg>
+ <New class="com.acme.MockUserTransaction"/>
+ </Arg>
+ </New>
diff --git a/tests/test-webapps/test-jndi-webapp/src/main/webapp/index.html b/tests/test-webapps/test-jndi-webapp/src/main/webapp/index.html
index 80bb1c7..5382876 100644
--- a/tests/test-webapps/test-jndi-webapp/src/main/webapp/index.html
+++ b/tests/test-webapps/test-jndi-webapp/src/main/webapp/index.html
@@ -3,12 +3,7 @@
<TITLE>JNDI Test WebApp</TITLE>
<META http-equiv="Pragma" content="no-cache">
<META http-equiv="Cache-Control" content="no-cache,no-store">
- <style>
- body {color: #2E2E2E; font-family:sans-serif; font-size:90%;}
- h1 {font-variant: small-caps; font-size:130%; letter-spacing: 0.1em;}
- h2 {font-variant: small-caps; font-size:100%; letter-spacing: 0.1em;}
- h3 {font-size:100%; letter-spacing: 0.1em;}
- </style>
+ <link rel="stylesheet" type="text/css" href="stylesheet.css"/>
</HEAD>
<BODY>
<A HREF="http://www.eclipse.org/jetty"><IMG SRC="images/jetty_banner.gif"></A>
@@ -29,21 +24,15 @@
<h2>Preparation</h2>
<p>
- <ol>
- <li>Ensure that you have downloaded and unpacked the test-jndi-webapp-<version>-config.jar, where <version> is replaced by the desired version number. The following files will be created:
- <pre>
- lib/jndi/test-mock-resources-<version>.jar (where <version> is replaced by the particular version number)
- webapps/test-jndi.xml
- </pre>
- </li>
- <li>Edit your $JETTY_HOME/start.ini file and uncomment the following lines:
- <pre>
- OPTIONS=plus
- etc/jetty-plus.xml
- </pre>
- </li>
- </ol>
+To enable JNDI edit the start.ini or start.d/*.ini files to include "OPTIONS=jndi".
</p>
+<p>
+For the jetty distribution demos, jndi is already enabled in the start.d/900-demo.ini file and also enables the
+jndi.demo option which includes some mock resources used by the test.
+</p>
+<p>The full source of this demonstration is available <a href="http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/tests/test-webapps/test-jndi-webapp">here</a>.</p>
+
+
<h2>Execution</h2>
<p>
diff --git a/tests/test-webapps/test-jndi-webapp/src/main/webapp/stylesheet.css b/tests/test-webapps/test-jndi-webapp/src/main/webapp/stylesheet.css
new file mode 100644
index 0000000..4ecc2cb
--- /dev/null
+++ b/tests/test-webapps/test-jndi-webapp/src/main/webapp/stylesheet.css
@@ -0,0 +1,7 @@
+body {color: #2E2E2E; font-family:sans-serif; font-size:90%;}
+h1 {font-variant: small-caps; font-size:130%; letter-spacing: 0.1em;}
+h2 {font-variant: small-caps; font-size:100%; letter-spacing: 0.1em;}
+h3 {font-size:100%; letter-spacing: 0.1em;}
+
+span.pass { color: green; }
+span.fail { color:red; }
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/assembly/config.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/assembly/config.xml
index d1c38d2..2fc52ba 100644
--- a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/assembly/config.xml
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/assembly/config.xml
@@ -17,17 +17,17 @@
</excludes>
</fileSet>
<fileSet>
- <directory>target</directory>
- <outputDirectory>webapps</outputDirectory>
+ <directory>target</directory>
+ <outputDirectory>webapps.demo</outputDirectory>
<includes>
<include>test-spec.xml</include>
</includes>
</fileSet>
<fileSet>
- <directory>target</directory>
- <outputDirectory></outputDirectory>
+ <directory>target/lib/jndi</directory>
+ <outputDirectory>lib/jndi.demo</outputDirectory>
<includes>
- <include>lib/jndi/**</include>
+ <include>*.jar</include>
</includes>
</fileSet>
</fileSets>
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/AnnotationTest.java b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/AnnotationTest.java
index b3eecea..b90f356 100644
--- a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/AnnotationTest.java
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/AnnotationTest.java
@@ -94,18 +94,18 @@
@PostConstruct
private void myPostConstructMethod ()
{
- postConstructResult = "Called";
+ postConstructResult = "<span class=\"pass\">PASS</span>";
try
{
- dsResult = (myDS==null?"FAIL":"myDS="+myDS.toString());
+ dsResult = (myDS==null?"<span class=\"fail\">FAIL</span>":"<span class=\"pass\">myDS="+myDS.toString()+"</span>");
}
catch (Exception e)
{
- dsResult = "FAIL: "+e;
+ dsResult = "<span class=\"fail\">FAIL:</span> "+e;
}
- envResult = (maxAmount==null?"FAIL":"maxAmount="+maxAmount.toString());
+ envResult = (maxAmount==null?"FAIL</span>":"<span class=\"pass\">maxAmount="+maxAmount.toString()+"</span>");
try
{
@@ -114,10 +114,10 @@
}
catch (Exception e)
{
- envLookupResult = "FAIL: "+e;
+ envLookupResult = "<span class=\"fail\">FAIL:</span> "+e;
}
- envResult2 = (minAmount==null?"FAIL":"minAmount="+minAmount.toString());
+ envResult2 = (minAmount==null?"<span class=\"fail\">FAIL</span>":"<span class=\"pass\">minAmount="+minAmount.toString()+"</span>");
try
{
InitialContext ic = new InitialContext();
@@ -125,9 +125,9 @@
}
catch (Exception e)
{
- envLookupResult2 = "FAIL: "+e;
+ envLookupResult2 = "<span class=\"fail\">FAIL:</span> "+e;
}
- envResult3 = (minAmount==null?"FAIL":"avgAmount="+avgAmount.toString());
+ envResult3 = (minAmount==null?"<span class=\"fail\">FAIL</span>":"<span class=\"pass\">avgAmount="+avgAmount.toString()+"</span>");
try
{
InitialContext ic = new InitialContext();
@@ -135,7 +135,7 @@
}
catch (Exception e)
{
- envLookupResult3 = "FAIL: "+e;
+ envLookupResult3 = "<span class=\"fail\">FAIL:</span> "+e;
}
@@ -147,10 +147,10 @@
}
catch (Exception e)
{
- dsLookupResult = "FAIL: "+e;
+ dsLookupResult = "<span class=\"fail\">FAIL:</span> "+e;
}
- txResult = (myUserTransaction==null?"FAIL":"myUserTransaction="+myUserTransaction);
+ txResult = (myUserTransaction==null?"<span class=\"fail\">FAIL</span>":"<span class=\"pass\">myUserTransaction="+myUserTransaction+"</span>");
try
{
InitialContext ic = new InitialContext();
@@ -158,7 +158,7 @@
}
catch (Exception e)
{
- txLookupResult = "FAIL: "+e;
+ txLookupResult = "<span class=\"fail\">FAIL:</span> "+e;
}
}
@@ -189,6 +189,7 @@
response.setContentType("text/html");
ServletOutputStream out = response.getOutputStream();
out.println("<html>");
+ out.println("<HEAD><link rel=\"stylesheet\" type=\"text/css\" href=\"stylesheet.css\"/></HEAD>");
out.println("<body>");
out.println("<h1>Results</h1>");
@@ -196,14 +197,14 @@
out.println("<pre>");
out.println("initParams={@WebInitParam(name=\"fromAnnotation\", value=\"xyz\")}");
out.println("</pre>");
- out.println("<br/><b>Result: "+("xyz".equals(config.getInitParameter("fromAnnotation"))? "PASS": "FAIL"));
+ out.println("<br/><b>Result: "+("xyz".equals(config.getInitParameter("fromAnnotation"))? "<span class=\"pass\">PASS": "<span class=\"fail\">FAIL")+"</span>");
out.println("<h2>Init Params from web-fragment</h2>");
out.println("<pre>");
out.println("extra1=123, extra2=345");
out.println("</pre>");
boolean fragInitParamResult = "123".equals(config.getInitParameter("extra1")) && "345".equals(config.getInitParameter("extra2"));
- out.println("<br/><b>Result: "+(fragInitParamResult? "PASS": "FAIL"));
+ out.println("<br/><b>Result: "+(fragInitParamResult? "<span class=\"pass\">PASS": "<span class=\"fail\">FAIL")+"</span>");
__HandlesTypes = Arrays.asList( "javax.servlet.GenericServlet",
@@ -231,27 +232,27 @@
}
if (classNames.size() != __HandlesTypes.size())
- out.println("<br/>FAIL");
+ out.println("<br/><span class=\"fail\">FAIL</span>");
else if (!classNames.containsAll(__HandlesTypes))
- out.println("<br/>FAIL");
+ out.println("<br/><span class=\"fail\">FAIL</span>");
else
- out.println("<br/>PASS");
+ out.println("<br/><span class=\"pass\">PASS</span>");
}
else
- out.print("<br/>FAIL (No such attribute com.acme.Foo)");
+ out.print("<br/><span class=\"fail\">FAIL</span> (No such attribute com.acme.Foo)");
out.println("</b>");
out.println("<h2>Complete Servlet Registration</h2>");
Boolean complete = (Boolean)config.getServletContext().getAttribute("com.acme.AnnotationTest.complete");
- out.println("<br/><b>Result: "+(complete.booleanValue()?"PASS":"FAIL")+"</b>");
+ out.println("<br/><b>Result: "+(complete.booleanValue()?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL")+"</span></b>");
out.println("<h2>ServletContextListener Programmatic Registration from ServletContainerInitializer</h2>");
Boolean programmaticListener = (Boolean)config.getServletContext().getAttribute("com.acme.AnnotationTest.listenerTest");
- out.println("<br/><b>Result: "+(programmaticListener.booleanValue()?"PASS":"FAIL")+"</b>");
+ out.println("<br/><b>Result: "+(programmaticListener.booleanValue()?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL")+"</span></b>");
out.println("<h2>ServletContextListener Programmatic Registration Prevented from ServletContextListener</h2>");
Boolean programmaticListenerPrevention = (Boolean)config.getServletContext().getAttribute("com.acme.AnnotationTest.listenerRegoTest");
- out.println("<br/><b>Result: "+(programmaticListenerPrevention.booleanValue()?"PASS":"FAIL")+"</b>");
+ out.println("<br/><b>Result: "+(programmaticListenerPrevention.booleanValue()?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL")+"</span></b>");
out.println("<h2>@PostConstruct Callback</h2>");
out.println("<pre>");
@@ -281,11 +282,11 @@
out.println("@Resource(name=\"minAmount\")");
out.println("private Double minAmount;");
out.println("</pre>");
- out.println("<br/><b>Result: "+envResult+": "+(maxAmount.compareTo(new Double(55))==0?" PASS":" FAIL")+"</b>");
+ out.println("<br/><b>Result: "+envResult+": "+(maxAmount.compareTo(new Double(55))==0?" <span class=\"pass\">PASS":" <span class=\"fail\">FAIL")+"</span></b>");
out.println("<br/><b>JNDI Lookup Result: "+envLookupResult+"</b>");
- out.println("<br/><b>Result: "+envResult2+": "+(minAmount.compareTo(new Double("0.99"))==0?" PASS":" FAIL")+"</b>");
+ out.println("<br/><b>Result: "+envResult2+": "+(minAmount.compareTo(new Double("0.99"))==0?" <span class=\"pass\">PASS":" <span class=\"fail\">FAIL")+"</span></b>");
out.println("<br/><b>JNDI Lookup Result: "+envLookupResult2+"</b>");
- out.println("<br/><b>Result: "+envResult3+": "+(avgAmount.compareTo(new Double("1.25"))==0?" PASS":" FAIL")+"</b>");
+ out.println("<br/><b>Result: "+envResult3+": "+(avgAmount.compareTo(new Double("1.25"))==0?" <span class=\"pass\">PASS":" <span class=\"fail\">FAIL")+"</span></b>");
out.println("<br/><b>JNDI Lookup Result: "+envLookupResult3+"</b>");
out.println("<h2>@Resource Injection for UserTransaction </h2>");
out.println("<pre>");
@@ -294,22 +295,6 @@
out.println("</pre>");
out.println("<br/><b>Result: "+txResult+"</b>");
out.println("<br/><b>JNDI Lookup Result: "+txLookupResult+"</b>");
- out.println("<h2>DeclaresRoles</h2>");
- out.println("<p>Login as user \"admin\" with password \"admin\" when prompted after clicking the button below to test @DeclareRoles annotation</p>");
- String context = request.getContextPath();
- if (!context.endsWith("/"))
- context += "/";
- context += "role/";
- out.println("<form action="+context+" method=\"post\"><button type=\"submit\">Test Role Annotations</button></form>");
-
- out.println("<h2>ServletSecurity</h2>");
- out.println("<p>Login as user \"admin\" with password \"admin\" when prompted after clicking the button below to test @ServletSecurity annotation</p>");
- context = request.getContextPath();
- if (!context.endsWith("/"))
- context += "/";
- context += "sec/foo";
- out.println("<form action="+context+" method=\"post\"><button type=\"submit\">Test ServletSecurity Annotation</button></form>");
-
out.println("</body>");
out.println("</html>");
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/MultiPartTest.java b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/MultiPartTest.java
index 9498e06..8732297 100644
--- a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/MultiPartTest.java
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/MultiPartTest.java
@@ -62,8 +62,9 @@
response.setContentType("text/html");
ServletOutputStream out = response.getOutputStream();
out.println("<html>");
- out.println("<h1>Results</h1>");
+ out.println("<HEAD><link rel=\"stylesheet\" type=\"text/css\" href=\"stylesheet.css\"/></HEAD>");
out.println("<body>");
+ out.println("<h1>Results</h1>");
out.println("<p>");
Collection<Part> parts = request.getParts();
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/RoleAnnotationTest.java b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/RoleAnnotationTest.java
index 191c153..303f35c 100644
--- a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/RoleAnnotationTest.java
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/RoleAnnotationTest.java
@@ -64,17 +64,18 @@
response.setContentType("text/html");
ServletOutputStream out = response.getOutputStream();
out.println("<html>");
+ out.println("<HEAD><link rel=\"stylesheet\" type=\"text/css\" href=\"stylesheet.css\"/></HEAD>");
out.println("<h1>Jetty DeclareRoles Annotation Results</h1>");
out.println("<body>");
out.println("<h2>Roles</h2>");
boolean result = request.isUserInRole("other");
- out.println("<br/><b>Result: isUserInRole(\"other\")="+result+":"+ (result==false?" PASS":" FAIL")+"</b>");
+ out.println("<br/><b>Result: isUserInRole(\"other\")="+result+":"+ (result==false?" <span class=\"pass\">PASS":" <span class=\"fail\">FAIL")+"</span></b>");
result = request.isUserInRole("manager");
- out.println("<br/><b>Result: isUserInRole(\"manager\")="+result+":"+ (result?" PASS":" FAIL")+"</b>");
+ out.println("<br/><b>Result: isUserInRole(\"manager\")="+result+":"+ (result?" <span class=\"pass\">PASS":" <span class=\"fail\">FAIL")+"</span></b>");
result = request.isUserInRole("user");
- out.println("<br/><b>Result: isUserInRole(\"user\")="+result+":"+ (result==false?" PASS":" FAIL")+"</b>");
+ out.println("<br/><b>Result: isUserInRole(\"user\")="+result+":"+ (result==false?" <span class=\"pass\">PASS":" <span class=\"fail\">FAIL")+"</span></b>");
String context = _config.getServletContext().getContextPath();
if (!context.endsWith("/"))
context += "/";
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/SecuredServlet.java b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/SecuredServlet.java
index 0c5989a..017f374 100644
--- a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/SecuredServlet.java
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/SecuredServlet.java
@@ -40,13 +40,14 @@
{
PrintWriter writer = resp.getWriter();
writer.println( "<html>");
- writer.println( "<body>");
- writer.println("<h1>@ServletSecurity</h2>");
+ writer.println("<HEAD><link rel=\"stylesheet\" type=\"text/css\" href=\"../stylesheet.css\"/></HEAD>");
+ writer.println("<h1>@ServletSecurity</h1>");
+ writer.println("<body>");
writer.println("<pre>");
writer.println("@ServletSecurity");
writer.println("public class SecuredServlet");
writer.println("</pre>");
- writer.println("<br/><b>Result: "+true+"</b>");
+ writer.println("<p><b>Result: <span class=\"pass\">PASS</span></b></p>");
String context = getServletConfig().getServletContext().getContextPath();
if (!context.endsWith("/"))
context += "/";
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/annotations-context-header.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/annotations-context-header.xml
index 1ec49c7..aa5c782 100644
--- a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/annotations-context-header.xml
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/annotations-context-header.xml
@@ -9,10 +9,10 @@
<Configure id='wac' class="org.eclipse.jetty.webapp.WebAppContext">
<New id="tx" class="org.eclipse.jetty.plus.jndi.Transaction">
- <Arg>
- <New class="com.acme.MockUserTransaction"/>
- </Arg>
- </New>
+ <Arg>
+ <New class="com.acme.MockUserTransaction"/>
+ </Arg>
+ </New>
<!-- =============================================================== -->
@@ -39,7 +39,7 @@
-->
<Set name="contextPath">/test-spec</Set>
- <Set name="war"><SystemProperty name="jetty.home"/>/webapps/test-spec.war</Set>
+ <Set name="war"><Property name="jetty.webapps"/>/test-spec.war</Set>
<Set name="configurationDiscovered">true</Set>
<Get name="securityHandler">
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/plugin-context-header.xml b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/plugin-context-header.xml
index 88202d3..450edda 100644
--- a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/plugin-context-header.xml
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/templates/plugin-context-header.xml
@@ -8,13 +8,10 @@
<Configure id='wac' class="org.eclipse.jetty.webapp.WebAppContext">
-
<!-- Configure the tx mgr (only needed for mvn jetty:run -->
<New id="tx" class="org.eclipse.jetty.plus.jndi.Transaction">
- <Arg>
- <New class="com.acme.MockUserTransaction"/>
- </Arg>
- </New>
-
-
+ <Arg>
+ <New class="com.acme.MockUserTransaction"/>
+ </Arg>
+ </New>
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/index.html b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/index.html
index 6f63eaf..854082f 100644
--- a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/index.html
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/index.html
@@ -3,13 +3,7 @@
<TITLE>Test Specification WebApp</TITLE>
<META http-equiv="Pragma" content="no-cache">
<META http-equiv="Cache-Control" content="no-cache,no-store">
- <style>
- body {color: #2E2E2E; font-family:sans-serif; font-size:90%;}
- h1 {font-variant: small-caps; font-size:130%; letter-spacing: 0.1em;}
- h2 {font-variant: small-caps; font-size:100%; letter-spacing: 0.1em;}
- h3 {font-size:100%; letter-spacing: 0.1em;}
- </style>
-
+ <link rel="stylesheet" type="text/css" href="stylesheet.css"/>
</HEAD>
<BODY >
<A HREF="http://www.eclipse.org/jetty"><IMG SRC="images/jetty_banner.gif"></A>
@@ -21,47 +15,45 @@
<span style="color:red; font-variant:small-caps; font-weight:bold">Test Web Application Only - Do NOT Deploy in Production</span>
</center>
<P>
-<h1>Advanced Servlet Specification Test WebApp</h1>
+<h1>Servlet 3.1 Test WebApp</h1>
<p>
-This example uses and tests newer aspects of the servlet specification such as annotations, web-fragments and servlet container initializers.
+This example tests some aspects of the servlet 3.1 specification:<ul>
+<li>servlet annotations
+<li>web-fragments
+<li>servlet container initializers.
+<li>multi-part upload support.
+</ul>
+The source repository for this test is available <a href="http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/tests/test-webapps/test-servlet-spec">here</a>.
</p>
-<h2>Preparation</h2>
-<p>
- <ol>
- <li>Ensure that you have downloaded and unpacked the test-spec-webapp-<version>-config.jar, where <version> is replaced by the desired version number. The following files will be created:
- <pre>
- lib/jndi/test-mock-resources-<version>.jar (where <version> is replaced by the particular version number)
- webapps/test-spec.xml
- </pre>
- </li>
- <li>Edit your $JETTY_HOME/start.ini file and uncomment the following lines:
- <pre>
- OPTIONS=plus
- etc/jetty-plus.xml
- OPTIONS=annotations
- etc/jetty-annotations.xml
- </pre>
- </li>
- </ol>
-
-</p>
-
-<h2>The Tests</h2>
-
<h3>Test Servlet 2.5/3.0 Annotations, Fragments and Initializers</h3>
<form action="test" method="post">
<button type="submit">Test</button>
</form>
+<h3>DeclaresRoles</h3>
+<p>Login as user <code>admin</code> with password <code>admin</code> when prompted after clicking the button below to test @DeclareRoles annotation</p>
+<form action="role" method="post">
+ <button type="submit">Test Role Annotations</button>
+</form>
+
+<h3>ServletSecurity</h3>
+<p>Login as user <code>admin</code> with password <code>admin</code> when prompted after clicking the button below to test @ServletSecurity annotation</p>
+<form action="sec/foo" method="post">
+ <button type="submit">Test ServletSecurity Annotation</button>
+</form>
+
<h3>Test Servlet 3.0 Multipart Mime</h3>
+Test of the annotation:
+<pre>
+@MultipartConfig(location="foo/bar", maxFileSize=10240, maxRequestSize=-1, fileSizeThreshold=2048)
+</pre>
<form ENCTYPE="multipart/form-data" ACTION="multi" METHOD=POST>
File to upload: <INPUT NAME="userfile1" TYPE="file">
<input TYPE="submit" VALUE="Test Upload">
</form>
-
<center>
<hr/>
<a href="http://www.eclipse.org/jetty"><img style="border:0" src="images/small_powered_by.gif"/></a>
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/login.html b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/login.html
index 1b44dea..c2f6ade 100644
--- a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/login.html
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/login.html
@@ -1,5 +1,9 @@
-<HTML><HEAD><TITLE>Annotation Test</TITLE></HEAD>
+<HTML>
+<HEAD>
+ <TITLE>Annotation Test</TITLE>
+ <link rel="stylesheet" type="text/css" href="stylesheet.css"/>
+</HEAD>
<BODY>
<H1> Enter your username and password to login </H1>
<I> Enter login=admin and password=admin in order to authenticate successfully</I>
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/stylesheet.css b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/stylesheet.css
new file mode 100644
index 0000000..4ecc2cb
--- /dev/null
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/webapp/stylesheet.css
@@ -0,0 +1,7 @@
+body {color: #2E2E2E; font-family:sans-serif; font-size:90%;}
+h1 {font-variant: small-caps; font-size:130%; letter-spacing: 0.1em;}
+h2 {font-variant: small-caps; font-size:100%; letter-spacing: 0.1em;}
+h3 {font-size:100%; letter-spacing: 0.1em;}
+
+span.pass { color: green; }
+span.fail { color:red; }