Merge branch 'master' into release-9
diff --git a/VERSION.txt b/VERSION.txt
index f0c380b..2073456 100644
--- a/VERSION.txt
+++ b/VERSION.txt
@@ -108,7 +108,6 @@
+ 410405 Avoid NPE for requestDispatcher(../)
+ 410469 UpgradeRequest is sent twice when using SSL, one fails warning about
WritePendingException
- + 410498 ignore type of exception in GoAwayTest.testDataNotProcessedAfterGoAway
+ 410522 jetty start broken for command line options
+ 410537 Exceptions during @OnWebSocketConnect not reported to
@OnWebSocketError
@@ -301,6 +300,75 @@
+ 402757 WebSocket client module can't be used with WebSocket server module in
the same WAR
+jetty-8.1.12.v20130726 - 26 July 2013
+ + 396706 CGI support parameters
+ + 397193 MongoSessionManager refresh updates last access time
+ + 407342 ReloadedSessionMissingClassTest uses class compiled with jdk7
+ + 408529 Etags set in 304 response
+ + 408600 set correct jetty.url in all pom files
+ + 408642 setContentType from addHeader
+ + 408662 In pax-web servlet services requests even if init() has not finished
+ running
+ + 408806 getParameter returns null on Multipart request if called before
+ request.getPart()/getParts()
+ + 408909 GzipFilter setting of headers when reset and/or not compressed
+ + 409028 Jetty HttpClient does not work with proxy CONNECT method.
+ + 409133 Empty <welcome-file> causes StackOverflowError
+ + 409436 NPE on context restart using dynamic servlet registration
+ + 409449 Ensure servlets, filters and listeners added via dynamic
+ registration, annotations or descriptors are cleaned on context restarts
+ + 409556 FileInputStream not closed in DirectNIOBuffer
+ + 410405 Avoid NPE for requestDispatcher(../)
+ + 410630 MongoSessionManager conflicting session update op
+ + 410750 NoSQLSessions: implement session context data persistence across
+ server restarts
+ + 410893 async support defaults to false for spec created servlets and filters
+ + 411135 HttpClient may send proxied https requests to the proxy instead of
+ the target server.
+ + 411216 RequestLogHandler handles async completion
+ + 411458 MultiPartFilter getParameterMap doesn't preserve multivalued
+ parameters 411459 MultiPartFilter.Wrapper getParameter should use charset
+ encoding of part
+ + 411755 MultiPartInputStreamParser fails on base64 encoded content
+ + 411909 GzipFilter flushbuffer() results in erroneous finish() call
+ + 412712 HttpClient does not send the terminal chunk after partial writes.
+ + 412750 HttpClient close expired connections fix
+ + 413371 Default JSON.Converters for List and Set.
+ + 413372 JSON Enum uses name rather than toString()
+ + 413684 Trailing slash shows JSP source
+ + 413812 Make RateTracker serializable
+
+jetty-7.6.12.v20130726 - 26 July 2013
+ + 396706 CGI support parameters
+ + 397193 MongoSessionManager refresh updates last access time
+ + 407342 ReloadedSessionMissingClassTest uses class compiled with jdk7
+ + 408529 Etags set in 304 response
+ + 408600 set correct jetty.url in all pom files
+ + 408642 setContentType from addHeader
+ + 408662 In pax-web servlet services requests even if init() has not finished
+ running
+ + 408909 GzipFilter setting of headers when reset and/or not compressed
+ + 409028 Jetty HttpClient does not work with proxy CONNECT method.
+ + 409133 Empty <welcome-file> causes StackOverflowError
+ + 409556 FileInputStream not closed in DirectNIOBuffer
+ + 410630 MongoSessionManager conflicting session update op
+ + 410750 NoSQLSessions: implement session context data persistence across
+ server restarts
+ + 411135 HttpClient may send proxied https requests to the proxy instead of
+ the target server.
+ + 411216 RequestLogHandler handles async completion
+ + 411458 MultiPartFilter getParameterMap doesn't preserve multivalued
+ parameters 411459 MultiPartFilter.Wrapper getParameter should use charset
+ encoding of part
+ + 411755 MultiPartInputStreamParser fails on base64 encoded content
+ + 411909 GzipFilter flushbuffer() results in erroneous finish() call
+ + 412712 HttpClient does not send the terminal chunk after partial writes.
+ + 412750 HttpClient close expired connections fix
+ + 413371 Default JSON.Converters for List and Set.
+ + 413372 JSON Enum uses name rather than toString()
+ + 413684 Trailing slash shows JSP source
+ + 413812 Make RateTracker serializable
+
jetty-8.1.11.v20130520 - 20 May 2013
+ 402844 STOP.PORT & STOP.KEY behaviour has changed
+ 403281 jetty.sh waits for started or failure before returning
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FileServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FileServer.java
index c77ac42..15d947a 100644
--- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FileServer.java
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FileServer.java
@@ -23,8 +23,6 @@
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.handler.ResourceHandler;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */
/** Simple Jetty FileServer.
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SpdyServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SpdyServer.java
index c2aa948..302f434 100644
--- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SpdyServer.java
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/SpdyServer.java
@@ -55,8 +55,7 @@
System.setProperty("jetty.home",jetty_home);
// Setup Threadpool
- QueuedThreadPool threadPool = new QueuedThreadPool();
- threadPool.setMaxThreads(500);
+ QueuedThreadPool threadPool = new QueuedThreadPool(512);
Server server = new Server(threadPool);
server.manage(threadPool);
diff --git a/jetty-annotations/pom.xml b/jetty-annotations/pom.xml
index 122641b..0d696db 100644
--- a/jetty-annotations/pom.xml
+++ b/jetty-annotations/pom.xml
@@ -77,6 +77,11 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>org.eclipse.jetty.toolchain</groupId>
+ <artifactId>jetty-test-helper</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jndi</artifactId>
<version>${project.version}</version>
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java
index daab409..c8048f4 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java
@@ -753,7 +753,8 @@
public void parseDir (Resource dir, ClassNameResolver resolver)
throws Exception
{
- if (!dir.isDirectory() || !dir.exists())
+ //skip dirs whose name start with . (ie hidden)
+ if (!dir.isDirectory() || !dir.exists() || dir.getName().startsWith("."))
return;
if (LOG.isDebugEnabled()) {LOG.debug("Scanning dir {}", dir);};
@@ -766,16 +767,21 @@
Resource res = dir.addPath(files[f]);
if (res.isDirectory())
parseDir(res, resolver);
- String name = res.getName();
- if (name.endsWith(".class"))
+ else
{
- if ((resolver == null)|| (!resolver.isExcluded(name) && (!isParsed(name) || resolver.shouldOverride(name))))
+ //we've already verified the directories, so just verify the class file name
+ String filename = res.getFile().getName();
+ if (isValidClassFileName(filename))
{
- Resource r = Resource.newResource(res.getURL());
- if (LOG.isDebugEnabled()) {LOG.debug("Scanning class {}", r);};
- scanClass(r.getInputStream());
- }
+ String name = res.getName();
+ if ((resolver == null)|| (!resolver.isExcluded(name) && (!isParsed(name) || resolver.shouldOverride(name))))
+ {
+ Resource r = Resource.newResource(res.getURL());
+ if (LOG.isDebugEnabled()) {LOG.debug("Scanning class {}", r);};
+ scanClass(r.getInputStream());
+ }
+ }
}
}
catch (Exception ex)
@@ -812,23 +818,11 @@
{
try
{
- String name = entry.getName();
- if (name.toLowerCase(Locale.ENGLISH).endsWith(".class"))
- {
- String shortName = name.replace('/', '.').substring(0,name.length()-6);
- if ((resolver == null)
- ||
- (!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName))))
- {
-
- Resource clazz = Resource.newResource("jar:"+jarUri+"!/"+name);
- scanClass(clazz.getInputStream());
- }
- }
+ parseJarEntry(jarUri, entry, resolver);
}
catch (Exception e)
{
- LOG.warn("Problem processing jar entry "+entry, e);
+ LOG.warn("Problem parsing jar entry: {}", entry.getName());
}
}
@@ -896,6 +890,8 @@
scanClass(r.getInputStream());
return;
}
+
+ if (LOG.isDebugEnabled()) LOG.warn("Resource not scannable for classes: {}", uri);
}
@@ -929,29 +925,8 @@
{
JarEntry entry = jar_in.getNextJarEntry();
while (entry!=null)
- {
- try
- {
- String name = entry.getName();
- if (name.toLowerCase(Locale.ENGLISH).endsWith(".class"))
- {
- String shortName = name.replace('/', '.').substring(0,name.length()-6);
-
- if ((resolver == null)
- ||
- (!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName))))
- {
- Resource clazz = Resource.newResource("jar:"+uri+"!/"+name);
- if (LOG.isDebugEnabled()) {LOG.debug("Scanning class from jar {}", clazz);};
- scanClass(clazz.getInputStream());
- }
- }
- }
- catch (Exception e)
- {
- LOG.warn("Problem processing jar entry "+entry, e);
- }
-
+ {
+ parseJarEntry(uri, entry, resolver);
entry = jar_in.getNextJarEntry();
}
}
@@ -961,8 +936,44 @@
}
}
}
+
+ /**
+ * Parse a single entry in a jar file
+ * @param jar
+ * @param entry
+ * @param resolver
+ * @throws Exception
+ */
+ protected void parseJarEntry (URI jar, JarEntry entry, final ClassNameResolver resolver)
+ throws Exception
+ {
+ if (jar == null || entry == null)
+ return;
+
+ //skip directories
+ if (entry.isDirectory())
+ return;
+
+ String name = entry.getName();
+
+ //check file is a valid class file name
+ if (isValidClassFileName(name) && isValidClassFilePath(name))
+ {
+ String shortName = name.replace('/', '.').substring(0,name.length()-6);
+
+ if ((resolver == null)
+ ||
+ (!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName))))
+ {
+ Resource clazz = Resource.newResource("jar:"+jar+"!/"+name);
+ if (LOG.isDebugEnabled()) {LOG.debug("Scanning class from jar {}", clazz);};
+ scanClass(clazz.getInputStream());
+ }
+ }
+ }
+
/**
* Use ASM on a class
*
@@ -975,5 +986,66 @@
ClassReader reader = new ClassReader(is);
reader.accept(new MyClassVisitor(), ClassReader.SKIP_CODE|ClassReader.SKIP_DEBUG|ClassReader.SKIP_FRAMES);
}
+
+ /**
+ * Check that the given path represents a valid class file name.
+ * The check is fairly cursory, checking that:
+ * <ul>
+ * <li> the name ends with .class</li>
+ * <li> it isn't a dot file or in a hidden directory </li>
+ * <li> the name of the class at least begins with a valid identifier for a class name </li>
+ * </ul>
+ * @param name
+ * @return
+ */
+ private boolean isValidClassFileName (String name)
+ {
+ //no name cannot be valid
+ if (name == null || name.length()==0)
+ return false;
+
+ //skip anything that is not a class file
+ if (!name.toLowerCase(Locale.ENGLISH).endsWith(".class"))
+ {
+ if (LOG.isDebugEnabled()) LOG.debug("Not a class: {}",name);
+ return false;
+ }
+
+ //skip any classfiles that are not a valid java identifier
+ int c0 = 0;
+ int ldir = name.lastIndexOf('/', name.length()-6);
+ c0 = (ldir > -1 ? ldir+1 : c0);
+ if (!Character.isJavaIdentifierStart(name.charAt(c0)))
+ {
+ if (LOG.isDebugEnabled()) LOG.debug("Not a java identifier: {}"+name);
+ return false;
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Check that the given path does not contain hidden directories
+ *
+ * @param path
+ * @return
+ */
+ private boolean isValidClassFilePath (String path)
+ {
+ //no path is not valid
+ if (path == null || path.length()==0)
+ return false;
+
+
+ //skip any classfiles that are in a hidden directory
+ if (path.startsWith(".") || path.contains("/."))
+ {
+ if (LOG.isDebugEnabled()) LOG.debug("Contains hidden dirs: {}"+path);
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationParser.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationParser.java
index 4e95a02..5fb97a3 100644
--- a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationParser.java
+++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationParser.java
@@ -18,71 +18,109 @@
package org.eclipse.jetty.annotations;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler;
import org.eclipse.jetty.annotations.AnnotationParser.Value;
+import org.eclipse.jetty.toolchain.test.FS;
+import org.eclipse.jetty.toolchain.test.IO;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.junit.Assert;
+import org.junit.Rule;
import org.junit.Test;
public class TestAnnotationParser
{
+ public static class TrackingAnnotationHandler implements DiscoverableAnnotationHandler
+ {
+ private final String annotationName;
+ public final Set<String> foundClasses;
+
+ public TrackingAnnotationHandler(String annotationName)
+ {
+ this.annotationName = annotationName;
+ this.foundClasses = new HashSet<>();
+ }
+
+ @Override
+ public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotation,
+ List<Value> values)
+ {
+ foundClasses.add(className);
+ }
+
+ @Override
+ public void handleMethod(String className, String methodName, int access, String desc, String signature, String[] exceptions, String annotation,
+ List<Value> values)
+ {
+ /* ignore */
+ }
+
+ @Override
+ public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
+ List<Value> values)
+ {
+ /* ignore */
+ }
+
+ @Override
+ public String getAnnotationName()
+ {
+ return this.annotationName;
+ }
+ }
+
+ @Rule
+ public TestingDir testdir = new TestingDir();
+
@Test
public void testSampleAnnotation() throws Exception
{
- String[] classNames = new String[]{"org.eclipse.jetty.annotations.ClassA"};
+ String[] classNames = new String[]
+ { "org.eclipse.jetty.annotations.ClassA" };
AnnotationParser parser = new AnnotationParser();
class SampleAnnotationHandler implements DiscoverableAnnotationHandler
{
- private List<String> methods = Arrays.asList("a", "b", "c", "d", "l");
+ private List<String> methods = Arrays.asList("a","b","c","d","l");
-
-
-
public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotation,
- List<Value> values)
+ List<Value> values)
{
- assertEquals ("org.eclipse.jetty.annotations.ClassA", className);
+ assertEquals("org.eclipse.jetty.annotations.ClassA",className);
}
public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
- List<Value> values)
+ List<Value> values)
{
- assertEquals ("m", fieldName);
- assertEquals (org.objectweb.asm.Type.OBJECT, org.objectweb.asm.Type.getType(fieldType).getSort());
- assertEquals (1, values.size());
- Value anv1 = values.get(0);
- assertEquals ("value", anv1.getName());
- assertEquals (7, anv1.getValue());
+ assertEquals("m",fieldName);
+ assertEquals(org.objectweb.asm.Type.OBJECT,org.objectweb.asm.Type.getType(fieldType).getSort());
+ assertEquals(1,values.size());
+ Value anv1 = values.get(0);
+ assertEquals("value",anv1.getName());
+ assertEquals(7,anv1.getValue());
}
public void handleMethod(String className, String methodName, int access, String desc, String signature, String[] exceptions, String annotation,
- List<Value> values)
+ List<Value> values)
{
- System.err.println("Sample annotated method : classname="+className+" methodName="+methodName+" access="+access+" desc="+desc+" signature="+signature);
-
- org.objectweb.asm.Type retType = org.objectweb.asm.Type.getReturnType(desc);
- System.err.println("REturn type = "+retType);
- org.objectweb.asm.Type[] params = org.objectweb.asm.Type.getArgumentTypes(desc);
- if (params == null)
- System.err.println("No params");
- else
- System.err.println(params.length+" params");
-
- if (exceptions == null)
- System.err.println("No exceptions");
- else
- System.err.println(exceptions.length+" exceptions");
-
- assertEquals("org.eclipse.jetty.annotations.ClassA", className);
+ assertEquals("org.eclipse.jetty.annotations.ClassA",className);
assertTrue(methods.contains(methodName));
- assertEquals("org.eclipse.jetty.annotations.Sample", annotation);
+ assertEquals("org.eclipse.jetty.annotations.Sample",annotation);
}
@Override
@@ -95,7 +133,7 @@
parser.registerHandler(new SampleAnnotationHandler());
long start = System.currentTimeMillis();
- parser.parse(classNames, new ClassNameResolver ()
+ parser.parse(classNames,new ClassNameResolver()
{
public boolean isExcluded(String name)
{
@@ -110,44 +148,36 @@
});
long end = System.currentTimeMillis();
- System.err.println("Time to parse class: "+((end-start)));
+ //System.err.println("Time to parse class: " + ((end - start)));
}
@Test
public void testMultiAnnotation() throws Exception
{
- String[] classNames = new String[]{"org.eclipse.jetty.annotations.ClassB"};
+ String[] classNames = new String[]
+ { "org.eclipse.jetty.annotations.ClassB" };
AnnotationParser parser = new AnnotationParser();
class MultiAnnotationHandler implements DiscoverableAnnotationHandler
{
public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotation,
- List<Value> values)
+ List<Value> values)
{
assertTrue("org.eclipse.jetty.annotations.ClassB".equals(className));
-
- for (Value anv: values)
- {
- System.err.println(anv.toString());
- }
}
public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
- List<Value> values)
+ List<Value> values)
{
- //there should not be any
+ // there should not be any
fail();
}
public void handleMethod(String className, String methodName, int access, String params, String signature, String[] exceptions, String annotation,
- List<Value> values)
+ List<Value> values)
{
assertTrue("org.eclipse.jetty.annotations.ClassB".equals(className));
assertTrue("a".equals(methodName));
- for (Value anv: values)
- {
- System.err.println(anv.toString());
- }
}
@Override
@@ -155,11 +185,71 @@
{
return "org.eclipse.jetty.annotations.Multi";
}
-
-
+
}
parser.registerHandler(new MultiAnnotationHandler());
- parser.parse(classNames, null);
+ parser.parse(classNames,null);
+ }
+
+ @Test
+ public void testHiddenFilesInJar() throws Exception
+ {
+ File badClassesJar = MavenTestingUtils.getTestResourceFile("bad-classes.jar");
+ AnnotationParser parser = new AnnotationParser();
+ parser.parse(badClassesJar.toURI(),null);
+ // only the valid classes inside bad-classes.jar should be parsed. If any invalid classes are parsed and exception would be thrown here
+ }
+
+ @Test
+ public void testBasedirExclusion() throws Exception
+ {
+ // Build up basedir, which itself has a path segment that violates java package and classnaming.
+ // The basedir should have no effect on annotation scanning.
+ // Intentionally using a base director name that starts with a "."
+ // This mimics what you see in jenkins, hudson, hadoop, solr, camel, and selenium for their
+ // installed and/or managed webapps
+ File basedir = testdir.getFile(".base/workspace/classes");
+ FS.ensureEmpty(basedir);
+
+ // Copy in class that is known to have annotations.
+ copyClass(ClassA.class,basedir);
+
+ // Setup Tracker
+ TrackingAnnotationHandler tracker = new TrackingAnnotationHandler(Sample.class.getName());
+
+ // Setup annotation scanning
+ AnnotationParser parser = new AnnotationParser();
+ parser.registerHandler(tracker);
+
+ // Parse
+ parser.parse(basedir.toURI(),null);
+
+ // Validate
+ Assert.assertThat("Found Class", tracker.foundClasses, contains(ClassA.class.getName()));
+ }
+
+ private void copyClass(Class<?> clazz, File basedir) throws IOException
+ {
+ String classname = clazz.getName().replace('.',File.separatorChar) + ".class";
+ URL url = this.getClass().getResource('/'+classname);
+ Assert.assertThat("URL for: " + classname,url,notNullValue());
+
+ String classpath = classname.substring(0,classname.lastIndexOf(File.separatorChar));
+ FS.ensureDirExists(new File(basedir,classpath));
+
+ InputStream in = null;
+ OutputStream out = null;
+ try
+ {
+ in = url.openStream();
+ out = new FileOutputStream(new File(basedir,classname));
+ IO.copy(in,out);
+ }
+ finally
+ {
+ IO.close(out);
+ IO.close(in);
+ }
}
}
diff --git a/jetty-annotations/src/test/resources/bad-classes.jar b/jetty-annotations/src/test/resources/bad-classes.jar
new file mode 100644
index 0000000..5538c18
--- /dev/null
+++ b/jetty-annotations/src/test/resources/bad-classes.jar
Binary files differ
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 e2d7f10..3fe4028 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
@@ -176,11 +176,7 @@
protected void doStart() throws Exception
{
if (sslContextFactory != null)
- {
addBean(sslContextFactory);
- // Avoid to double dispatch when using SSL
- setDispatchIO(false);
- }
String name = HttpClient.class.getSimpleName() + "@" + hashCode();
@@ -391,7 +387,7 @@
protected Request copyRequest(Request oldRequest, URI newURI)
{
Request newRequest = new HttpRequest(this, oldRequest.getConversationID(), newURI);
- newRequest.method(oldRequest.getMethod())
+ newRequest.method(oldRequest.method())
.version(oldRequest.getVersion())
.content(oldRequest.getContent());
for (HttpField header : oldRequest.getHeaders())
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 9685c20..dfd11c2 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
@@ -53,7 +53,7 @@
private final HttpSender sender;
private final HttpReceiver receiver;
private long idleTimeout;
- private volatile boolean closed;
+ private boolean closed;
public HttpConnection(HttpClient client, EndPoint endPoint, HttpDestination destination)
{
@@ -88,15 +88,9 @@
super.onClose();
}
- @Override
- public void fillInterested()
+ protected boolean isClosed()
{
- // This is necessary when "upgrading" the connection for example after proxied
- // CONNECT requests, because the old connection will read the CONNECT response
- // and then set the read interest, while the new connection attached to the same
- // EndPoint also will set the read interest, causing a ReadPendingException.
- if (!closed)
- super.fillInterested();
+ return closed;
}
@Override
@@ -154,7 +148,7 @@
private void normalizeRequest(Request request)
{
- if (request.getMethod() == null)
+ if (request.method() == null)
request.method(HttpMethod.GET);
if (request.getVersion() == null)
@@ -163,7 +157,7 @@
if (request.getIdleTimeout() <= 0)
request.idleTimeout(client.getIdleTimeout(), TimeUnit.MILLISECONDS);
- HttpMethod method = request.getMethod();
+ String method = request.method();
HttpVersion version = request.getVersion();
HttpFields headers = request.getHeaders();
ContentProvider content = request.getContent();
@@ -178,7 +172,7 @@
path = "/";
request.path(path);
}
- if (destination.isProxied() && HttpMethod.CONNECT != method)
+ if (destination.isProxied() && !HttpMethod.CONNECT.is(method))
{
path = request.getURI().toString();
request.path(path);
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java
index 94744d0..9b2caf8 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java
@@ -428,6 +428,19 @@
getResponseNotifier().notifyComplete(listeners, new Result(request, cause, response, cause));
}
+ protected void tunnelSucceeded(Connection connection, Promise<Connection> promise)
+ {
+ // Wrap the connection with TLS
+ Connection tunnel = client.tunnel(connection);
+ promise.succeeded(tunnel);
+ }
+
+ protected void tunnelFailed(Connection connection, Promise<Connection> promise, Throwable failure)
+ {
+ promise.failed(failure);
+ connection.close();
+ }
+
@Override
public String dump()
{
@@ -516,22 +529,18 @@
{
if (result.isFailed())
{
- failed(result.getFailure());
- connection.close();
+ tunnelFailed(connection, delegate, result.getFailure());
}
else
{
Response response = result.getResponse();
if (response.getStatus() == 200)
{
- // Wrap the connection with TLS
- Connection tunnel = client.tunnel(connection);
- delegate.succeeded(tunnel);
+ tunnelSucceeded(connection, delegate);
}
else
{
- failed(new HttpResponseException("Received " + response + " for " + result.getRequest(), response));
- connection.close();
+ tunnelFailed(connection, delegate, new HttpResponseException("Received " + response + " for " + result.getRequest(), response));
}
}
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java
index 6155421..de04377 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java
@@ -43,6 +43,7 @@
private volatile HttpConnection connection;
private volatile Throwable requestFailure;
private volatile Throwable responseFailure;
+
public HttpExchange(HttpConversation conversation, HttpDestination destination, Request request, List<Response.ResponseListener> listeners)
{
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 2f33731..8bb6c6d 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
@@ -68,21 +68,30 @@
{
while (true)
{
- int read = endPoint.fill(buffer);
- LOG.debug("Read {} bytes from {}", read, connection);
- if (read > 0)
+ // Connection may be closed in a parser callback
+ if (connection.isClosed())
{
- parse(buffer);
- }
- else if (read == 0)
- {
- fillInterested();
+ LOG.debug("{} closed", connection);
break;
}
else
{
- shutdown();
- break;
+ int read = endPoint.fill(buffer);
+ LOG.debug("Read {} bytes from {}", read, connection);
+ if (read > 0)
+ {
+ parse(buffer);
+ }
+ else if (read == 0)
+ {
+ fillInterested();
+ break;
+ }
+ else
+ {
+ shutdown();
+ break;
+ }
}
}
}
@@ -147,7 +156,8 @@
HttpConversation conversation = exchange.getConversation();
HttpResponse response = exchange.getResponse();
- parser.setHeadResponse(exchange.getRequest().getMethod() == HttpMethod.HEAD);
+ String method = exchange.getRequest().method();
+ parser.setHeadResponse(HttpMethod.HEAD.is(method) || HttpMethod.CONNECT.is(method));
response.version(version).status(status).reason(reason);
// Probe the protocol handlers
@@ -414,6 +424,12 @@
return updated;
}
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x on %s", getClass().getSimpleName(), hashCode(), connection);
+ }
+
private enum State
{
IDLE, RECEIVE, 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 ca750b7..2e4cbec 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
@@ -29,6 +29,7 @@
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
@@ -65,7 +66,7 @@
private String scheme;
private String path;
private String query;
- private HttpMethod method;
+ private String method;
private HttpVersion version;
private long idleTimeout;
private long timeout;
@@ -127,13 +128,26 @@
@Override
public HttpMethod getMethod()
{
+ return HttpMethod.fromString(method);
+ }
+
+ @Override
+ public String method()
+ {
return method;
}
@Override
public Request method(HttpMethod method)
{
- this.method = method;
+ this.method = method.asString();
+ return this;
+ }
+
+ @Override
+ public Request method(String method)
+ {
+ this.method = Objects.requireNonNull(method).toUpperCase(Locale.ENGLISH);
return this;
}
@@ -196,6 +210,7 @@
{
params.add(name, value);
this.query = buildQuery();
+ this.uri = buildURI(true);
return this;
}
@@ -541,7 +556,13 @@
for (String nameValue : query.split("&"))
{
String[] parts = nameValue.split("=");
- param(parts[0], parts.length < 2 ? "" : urlDecode(parts[1]));
+ if (parts.length > 0)
+ {
+ String name = parts[0];
+ if (name.trim().length() == 0)
+ continue;
+ param(name, parts.length < 2 ? "" : urlDecode(parts[1]));
+ }
}
}
}
@@ -574,6 +595,6 @@
@Override
public String toString()
{
- return String.format("%s[%s %s %s]@%x", HttpRequest.class.getSimpleName(), getMethod(), getPath(), getVersion(), hashCode());
+ return String.format("%s[%s %s %s]@%x", HttpRequest.class.getSimpleName(), method(), getPath(), getVersion(), hashCode());
}
}
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 f6737cc..eff0576 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
@@ -176,7 +176,7 @@
String query = request.getQuery();
if (query != null)
path += "?" + query;
- requestInfo = new HttpGenerator.RequestInfo(request.getVersion(), request.getHeaders(), contentLength, request.getMethod().asString(), path);
+ requestInfo = new HttpGenerator.RequestInfo(request.getVersion(), request.getHeaders(), contentLength, request.method(), path);
break;
}
case NEED_HEADER:
@@ -540,9 +540,13 @@
boolean notCommitted = isBeforeCommit(current);
if (result == null && notCommitted && request.getAbortCause() == null)
{
- result = exchange.responseComplete(failure).getReference();
- exchange.terminateResponse();
- LOG.debug("Failed on behalf {}", exchange);
+ completion = exchange.responseComplete(failure);
+ if (completion.isMarked())
+ {
+ result = completion.getReference();
+ exchange.terminateResponse();
+ LOG.debug("Failed on behalf {}", exchange);
+ }
}
if (result != null)
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectProtocolHandler.java
index ee177dc..7b9c42a 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectProtocolHandler.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectProtocolHandler.java
@@ -95,8 +95,9 @@
{
case 301:
{
- if (request.getMethod() == HttpMethod.GET || request.getMethod() == HttpMethod.HEAD)
- redirect(result, request.getMethod(), newURI);
+ String method = request.method();
+ if (HttpMethod.GET.is(method) || HttpMethod.HEAD.is(method))
+ redirect(result, method, newURI);
else
fail(result, new HttpResponseException("HTTP protocol violation: received 301 for non GET or HEAD request", response));
break;
@@ -105,13 +106,13 @@
case 303:
{
// Redirect must be done using GET
- redirect(result, HttpMethod.GET, newURI);
+ redirect(result, HttpMethod.GET.asString(), newURI);
break;
}
case 307:
{
// Keep same method
- redirect(result, request.getMethod(), newURI);
+ redirect(result, request.method(), newURI);
break;
}
default:
@@ -174,7 +175,7 @@
}
}
- private void redirect(Result result, HttpMethod method, URI location)
+ private void redirect(Result result, String method, URI location)
{
final Request request = result.getRequest();
HttpConversation conversation = client.getConversation(request.getConversationID(), false);
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 d582028..225137a 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
@@ -76,17 +76,30 @@
int getPort();
/**
- * @return the method of this request, such as GET or POST
+ * @return the method of this request, such as GET or POST, or null if the method is not a standard HTTP method
+ * @deprecated use {@link #method()} instead
*/
+ @Deprecated
HttpMethod getMethod();
/**
+ * @return the method of this request, such as GET or POST, as a String
+ */
+ String method();
+
+ /**
* @param method the method of this request, such as GET or POST
* @return this request object
*/
Request method(HttpMethod method);
/**
+ * @param method the method of this request, such as GET or POST
+ * @return this request object
+ */
+ Request method(String method);
+
+ /**
* @return the path of this request, such as "/" or "/path" - without the query
* @see #getQuery()
*/
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DigestAuthentication.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DigestAuthentication.java
index b6a54cb..55ac0ec 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DigestAuthentication.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DigestAuthentication.java
@@ -221,7 +221,7 @@
String A1 = user + ":" + realm + ":" + password;
String hashA1 = toHexString(digester.digest(A1.getBytes(charset)));
- String A2 = request.getMethod().asString() + ":" + request.getURI();
+ String A2 = request.method() + ":" + request.getURI();
if ("auth-int".equals(qop))
A2 += ":" + toHexString(digester.digest(content));
String hashA2 = toHexString(digester.digest(A2.getBytes(charset)));
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientURITest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientURITest.java
index 0727e3a..610f891 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientURITest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientURITest.java
@@ -108,13 +108,15 @@
}
});
+ String pathQuery = path + "?" + query;
Request request = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.timeout(5, TimeUnit.SECONDS)
- .path(path + "?" + query);
+ .path(pathQuery);
Assert.assertEquals(path, request.getPath());
Assert.assertEquals(query, request.getQuery());
+ Assert.assertTrue(request.getURI().toString().endsWith(pathQuery));
Fields params = request.getParams();
Assert.assertEquals(1, params.size());
Assert.assertEquals(value, params.get(name).value());
@@ -131,6 +133,7 @@
String value = "1";
final String query = name + "=" + value;
final String path = "/path";
+ String pathQuery = path + "?" + query;
start(new AbstractHandler()
{
@Override
@@ -150,6 +153,7 @@
Assert.assertEquals(path, request.getPath());
Assert.assertEquals(query, request.getQuery());
+ Assert.assertTrue(request.getURI().toString().endsWith(pathQuery));
Fields params = request.getParams();
Assert.assertEquals(1, params.size());
Assert.assertEquals(value, params.get(name).value());
@@ -168,6 +172,7 @@
String value2 = "2";
final String query = name1 + "=" + value1 + "&" + name2 + "=" + value2;
final String path = "/path";
+ String pathQuery = path + "?" + query;
start(new AbstractHandler()
{
@Override
@@ -187,6 +192,7 @@
Assert.assertEquals(path, request.getPath());
Assert.assertEquals(query, request.getQuery());
+ Assert.assertTrue(request.getURI().toString().endsWith(pathQuery));
Fields params = request.getParams();
Assert.assertEquals(2, params.size());
Assert.assertEquals(value1, params.get(name1).value());
@@ -208,6 +214,7 @@
String encodedValue2 = URLEncoder.encode(value2, "UTF-8");
final String query = name1 + "=" + encodedValue1 + "&" + name2 + "=" + encodedValue2;
final String path = "/path";
+ String pathQuery = path + "?" + query;
start(new AbstractHandler()
{
@Override
@@ -229,6 +236,7 @@
Assert.assertEquals(path, request.getPath());
Assert.assertEquals(query, request.getQuery());
+ Assert.assertTrue(request.getURI().toString().endsWith(pathQuery));
Fields params = request.getParams();
Assert.assertEquals(2, params.size());
Assert.assertEquals(value1, params.get(name1).value());
@@ -238,4 +246,70 @@
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
}
+
+ @Test
+ public void testNoParameterNameNoParameterValue() throws Exception
+ {
+ final String path = "/path";
+ final String query = "="; // Bogus query
+ String pathQuery = path + "?" + query;
+ 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(pathQuery);
+
+ Assert.assertEquals(path, request.getPath());
+ Assert.assertEquals(query, request.getQuery());
+ Assert.assertTrue(request.getURI().toString().endsWith(pathQuery));
+ Fields params = request.getParams();
+ Assert.assertEquals(0, params.size());
+
+ ContentResponse response = request.send();
+
+ Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+ }
+
+ @Test
+ public void testNoParameterNameWithParameterValue() throws Exception
+ {
+ final String path = "/path";
+ final String query = "=1"; // Bogus query
+ String pathQuery = path + "?" + query;
+ 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(pathQuery);
+
+ Assert.assertEquals(path, request.getPath());
+ Assert.assertEquals(query, request.getQuery());
+ Assert.assertTrue(request.getURI().toString().endsWith(pathQuery));
+ Fields params = request.getParams();
+ Assert.assertEquals(0, params.size());
+
+ ContentResponse response = request.send();
+
+ Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+ }
}
diff --git a/jetty-distribution/src/main/resources/bin/jetty.sh b/jetty-distribution/src/main/resources/bin/jetty.sh
index cbd9f24..c7ab396 100755
--- a/jetty-distribution/src/main/resources/bin/jetty.sh
+++ b/jetty-distribution/src/main/resources/bin/jetty.sh
@@ -188,13 +188,15 @@
then
JETTY_SH=$0
case "$JETTY_SH" in
- /*) ;;
- ./*) ;;
- *) JETTY_SH=./$JETTY_SH ;;
+ /*) JETTY_HOME=${JETTY_SH%/*/*} ;;
+ ./*/*) JETTY_HOME=${JETTY_SH%/*/*} ;;
+ ./*) JETTY_HOME=.. ;;
+ */*/*) JETTY_HOME=./${JETTY_SH%/*/*} ;;
+ */*) JETTY_HOME=. ;;
+ *) JETTY_HOME=.. ;;
esac
- JETTY_HOME=${JETTY_SH%/*/*}
- if [ ! -f "${JETTY_SH%/*/*}/$JETTY_INSTALL_TRACE_FILE" ]
+ if [ ! -f "$JETTY_HOME/$JETTY_INSTALL_TRACE_FILE" ]
then
JETTY_HOME=
fi
diff --git a/jetty-http-spi/pom.xml b/jetty-http-spi/pom.xml
index 4243975..263c91f 100644
--- a/jetty-http-spi/pom.xml
+++ b/jetty-http-spi/pom.xml
@@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
- <version>9.0.0-SNAPSHOT</version>
+ <version>9.0.5-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-http-spi</artifactId>
diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/DelegatingThreadPool.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/DelegatingThreadPool.java
new file mode 100644
index 0000000..1a1ce7b
--- /dev/null
+++ b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/DelegatingThreadPool.java
@@ -0,0 +1,160 @@
+//
+// ========================================================================
+// 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.http.spi;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ThreadPool;
+
+public class DelegatingThreadPool extends ContainerLifeCycle implements ThreadPool
+{
+ private static final Logger LOG = Log.getLogger(DelegatingThreadPool.class);
+
+ private Executor _executor; // memory barrier provided by start/stop semantics
+
+ public DelegatingThreadPool(Executor executor)
+ {
+ _executor=executor;
+ addBean(_executor);
+ }
+
+ /* ------------------------------------------------------------ */
+ public Executor getExecutor()
+ {
+ return _executor;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setExecutor(Executor executor)
+ {
+ if (isRunning())
+ throw new IllegalStateException(getState());
+ updateBean(_executor,executor);
+ _executor=executor;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void execute(Runnable job)
+ {
+ _executor.execute(job);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean dispatch(Runnable job)
+ {
+ final Executor executor=_executor;
+ if (executor instanceof ThreadPool)
+ return ((ThreadPool)executor).dispatch(job);
+
+ try
+ {
+ _executor.execute(job);
+ return true;
+ }
+ catch(RejectedExecutionException e)
+ {
+ LOG.warn(e);
+ return false;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public int getIdleThreads()
+ {
+ final Executor executor=_executor;
+ if (executor instanceof ThreadPool)
+ return ((ThreadPool)executor).getIdleThreads();
+
+ if (executor instanceof ThreadPoolExecutor)
+ {
+ final ThreadPoolExecutor tpe = (ThreadPoolExecutor)executor;
+ return tpe.getPoolSize() - tpe.getActiveCount();
+ }
+ return -1;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public int getThreads()
+ {
+ final Executor executor=_executor;
+ if (executor instanceof ThreadPool)
+ return ((ThreadPool)executor).getThreads();
+
+ if (executor instanceof ThreadPoolExecutor)
+ {
+ final ThreadPoolExecutor tpe = (ThreadPoolExecutor)executor;
+ return tpe.getPoolSize();
+ }
+ return -1;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean isLowOnThreads()
+ {
+ final Executor executor=_executor;
+ if (executor instanceof ThreadPool)
+ return ((ThreadPool)executor).isLowOnThreads();
+
+ if (executor instanceof ThreadPoolExecutor)
+ {
+ final ThreadPoolExecutor tpe = (ThreadPoolExecutor)executor;
+ // getActiveCount() locks the thread pool, so execute it last
+ return tpe.getPoolSize() == tpe.getMaximumPoolSize() &&
+ tpe.getQueue().size() >= tpe.getPoolSize() - tpe.getActiveCount();
+ }
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void join() throws InterruptedException
+ {
+ final Executor executor=_executor;
+ if (executor instanceof ThreadPool)
+ ((ThreadPool)executor).join();
+ else if (executor instanceof ExecutorService)
+ ((ExecutorService)executor).awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
+ else
+ throw new IllegalStateException();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ if (!(_executor instanceof LifeCycle) && (_executor instanceof ExecutorService))
+ ((ExecutorService)_executor).shutdownNow();
+ }
+
+}
diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServer.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServer.java
index f5af972..22e6b3d 100644
--- a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServer.java
+++ b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServer.java
Binary files differ
diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServerProvider.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServerProvider.java
index b96d9a2..410ef2a 100644
--- a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServerProvider.java
+++ b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServerProvider.java
@@ -26,6 +26,9 @@
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.util.thread.ExecutorThreadPool;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.util.thread.ThreadPool;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpsServer;
@@ -53,11 +56,12 @@
if (server == null)
{
- server = new Server();
-
- HandlerCollection handlerCollection = new HandlerCollection();
- handlerCollection.setHandlers(new Handler[] {new ContextHandlerCollection(), new DefaultHandler()});
- server.setHandler(handlerCollection);
+ ThreadPool threadPool = new DelegatingThreadPool(new QueuedThreadPool());
+ server = new Server(threadPool);
+
+ HandlerCollection handlerCollection = new HandlerCollection();
+ handlerCollection.setHandlers(new Handler[] {new ContextHandlerCollection(), new DefaultHandler()});
+ server.setHandler(handlerCollection);
shared = false;
}
diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/ThreadPoolExecutorAdapter.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/ThreadPoolExecutorAdapter.java
deleted file mode 100644
index aaffe8e..0000000
--- a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/ThreadPoolExecutorAdapter.java
+++ /dev/null
Binary files differ
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 aedfd2a..6d835dc 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
@@ -68,6 +68,7 @@
CACHE.put(new CachedHttpField(HttpHeader.CONTENT_LENGTH,"0"));
CACHE.put(new CachedHttpField(HttpHeader.CONTENT_ENCODING,"gzip"));
CACHE.put(new CachedHttpField(HttpHeader.CONTENT_ENCODING,"deflate"));
+ CACHE.put(new CachedHttpField(HttpHeader.TRANSFER_ENCODING,"chunked"));
CACHE.put(new CachedHttpField(HttpHeader.EXPIRES,"Fri, 01 Jan 1990 00:00:00 GMT"));
// Content types
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java
index 98e3e59..0df2964 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java
@@ -18,6 +18,9 @@
package org.eclipse.jetty.http;
+import static org.eclipse.jetty.util.QuotedStringTokenizer.isQuoted;
+import static org.eclipse.jetty.util.QuotedStringTokenizer.quoteOnly;
+
import java.io.IOException;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
@@ -54,23 +57,27 @@
/**
* HTTP Fields. A collection of HTTP header and or Trailer fields.
*
- * <p>This class is not synchronized as it is expected that modifications will only be performed by a
+ * <p>This class is not synchronised as it is expected that modifications will only be performed by a
* single thread.
+ *
+ * <p>The cookie handling provided by this class is guided by the Servlet specification and RFC6265.
*
*/
public class HttpFields implements Iterable<HttpField>
{
private static final Logger LOG = Log.getLogger(HttpFields.class);
- public static final String __COOKIE_DELIM="\"\\\n\r\t\f\b%+ ;=";
public static final TimeZone __GMT = TimeZone.getTimeZone("GMT");
public static final DateCache __dateCache = new DateCache("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
+ public static final String __COOKIE_DELIM_PATH="\"\\\t%+ :;,@?=()<>{}[]";
+ public static final String __COOKIE_DELIM=__COOKIE_DELIM_PATH+"/";
+
static
{
__GMT.setID("GMT");
__dateCache.setTimeZone(__GMT);
}
-
+
public final static String __separators = ", \t";
private static final String[] DAYS =
@@ -808,69 +815,87 @@
final boolean isHttpOnly,
int version)
{
- String delim=__COOKIE_DELIM;
-
// Check arguments
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Bad cookie name");
// Format value and params
StringBuilder buf = new StringBuilder(128);
- String name_value_params;
- QuotedStringTokenizer.quoteIfNeeded(buf, name, delim);
- buf.append('=');
- String start=buf.toString();
- boolean hasDomain = false;
- boolean hasPath = false;
- if (value != null && value.length() > 0)
- QuotedStringTokenizer.quoteIfNeeded(buf, value, delim);
+ // Name is checked by servlet spec, but can also be passed directly so check again
+ boolean quote_name=isQuoteNeededForCookie(name);
+ quoteOnlyOrAppend(buf,name,quote_name);
+
+ buf.append('=');
+
+ // Remember name= part to look for other matching set-cookie
+ String name_equals=buf.toString();
+ // Append the value
+ boolean quote_value=isQuoteNeededForCookie(value);
+ quoteOnlyOrAppend(buf,value,quote_value);
- if (path != null && path.length() > 0)
+ // Look for domain and path fields and check if they need to be quoted
+ boolean has_domain = domain!=null && domain.length()>0;
+ boolean quote_domain = has_domain && isQuoteNeededForCookie(domain);
+ boolean has_path = path!=null && path.length()>0;
+ boolean quote_path = has_path && isQuoteNeededForCookiePath(path);
+
+ // Upgrade the version if we have a comment or we need to quote value/path/domain or if they were already quoted
+ if (version==0 && ( comment!=null || quote_name || quote_value || quote_domain || quote_path || isQuoted(name) || isQuoted(value) || isQuoted(path) || isQuoted(domain)))
+ version=1;
+
+ // Append version
+ if (version==1)
+ buf.append (";Version=1");
+ else if (version>1)
+ buf.append (";Version=").append(version);
+
+ // Append path
+ if (has_path)
{
- hasPath = true;
buf.append(";Path=");
- if (path.trim().startsWith("\""))
- buf.append(path);
- else
- QuotedStringTokenizer.quoteIfNeeded(buf,path,delim);
+ quoteOnlyOrAppend(buf,path,quote_path);
}
- if (domain != null && domain.length() > 0)
+
+ // Append domain
+ if (has_domain)
{
- hasDomain = true;
buf.append(";Domain=");
- QuotedStringTokenizer.quoteIfNeeded(buf,domain.toLowerCase(Locale.ENGLISH),delim);
+ quoteOnlyOrAppend(buf,domain,quote_domain);
}
+ // Handle max-age and/or expires
if (maxAge >= 0)
{
- // Always add the expires param as some browsers still don't handle max-age
+ // Always use expires
+ // This is required as some browser (M$ this means you!) don't handle max-age even with v1 cookies
buf.append(";Expires=");
if (maxAge == 0)
buf.append(__01Jan1970_COOKIE);
else
formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge);
-
- buf.append(";Max-Age=");
- buf.append(maxAge);
+
+ // for v1 cookies, also send max-age
+ if (version>=1)
+ {
+ buf.append(";Max-Age=");
+ buf.append(maxAge);
+ }
}
+ // add the other fields
if (isSecure)
buf.append(";Secure");
if (isHttpOnly)
buf.append(";HttpOnly");
-
- if (comment != null && comment.length() > 0)
+ if (comment != null)
{
buf.append(";Comment=");
- QuotedStringTokenizer.quoteIfNeeded(buf, comment, delim);
+ quoteOnlyOrAppend(buf,comment,isQuoteNeededForCookie(comment));
}
- name_value_params = buf.toString();
-
- // remove existing set-cookie of same name
-
+ // remove any existing set-cookie fields of same name
Iterator<HttpField> i=_fields.iterator();
while (i.hasNext())
{
@@ -878,26 +903,26 @@
if (field.getHeader()==HttpHeader.SET_COOKIE)
{
String val = (field.getValue() == null ? null : field.getValue().toString());
- if (val!=null && val.startsWith(start))
+ if (val!=null && val.startsWith(name_equals))
{
//existing cookie has same name, does it also match domain and path?
- if (((!hasDomain && !val.contains("Domain")) || (hasDomain && val.contains("Domain="+domain))) &&
- ((!hasPath && !val.contains("Path")) || (hasPath && val.contains("Path="+path))))
+ if (((!has_domain && !val.contains("Domain")) || (has_domain && val.contains(domain))) &&
+ ((!has_path && !val.contains("Path")) || (has_path && val.contains(path))))
{
i.remove();
}
}
-
}
}
- add(HttpHeader.SET_COOKIE.toString(), name_value_params);
+ // add the set cookie
+ add(HttpHeader.SET_COOKIE.toString(), buf.toString());
// Expire responses with set-cookie headers so they do not get cached.
put(HttpHeader.EXPIRES.toString(), __01Jan1970);
}
- public void putTo(ByteBuffer bufferInFillMode) throws IOException
+ public void putTo(ByteBuffer bufferInFillMode)
{
for (HttpField field : _fields)
{
@@ -1095,19 +1120,20 @@
}
}
- List vl = LazyList.getList(list, false);
- if (vl.size() < 2) return vl;
+ List<String> vl = LazyList.getList(list, false);
+ if (vl.size() < 2)
+ return vl;
- List ql = LazyList.getList(qual, false);
+ List<Float> ql = LazyList.getList(qual, false);
// sort list with swaps
Float last = __zero;
for (int i = vl.size(); i-- > 0;)
{
- Float q = (Float) ql.get(i);
+ Float q = ql.get(i);
if (last.compareTo(q) > 0)
{
- Object tmp = vl.get(i);
+ String tmp = vl.get(i);
vl.set(i, vl.get(i + 1));
vl.set(i + 1, tmp);
ql.set(i, ql.get(i + 1));
@@ -1123,4 +1149,66 @@
}
+
+ /* ------------------------------------------------------------ */
+ /** Does a cookie value need to be quoted?
+ * @param s value string
+ * @return true if quoted;
+ * @throws IllegalArgumentException If there a control characters in the string
+ */
+ public static boolean isQuoteNeededForCookie(String s)
+ {
+ if (s==null || s.length()==0)
+ return true;
+
+ if (QuotedStringTokenizer.isQuoted(s))
+ return false;
+
+ for (int i=0;i<s.length();i++)
+ {
+ char c = s.charAt(i);
+ if (__COOKIE_DELIM.indexOf(c)>=0)
+ return true;
+
+ if (c<0x20 || c>=0x7f)
+ throw new IllegalArgumentException("Illegal character in cookie value");
+ }
+
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Does a cookie path need to be quoted?
+ * @param s value string
+ * @return true if quoted;
+ * @throws IllegalArgumentException If there a control characters in the string
+ */
+ public static boolean isQuoteNeededForCookiePath(String s)
+ {
+ if (s==null || s.length()==0)
+ return true;
+
+ if (QuotedStringTokenizer.isQuoted(s))
+ return false;
+
+ for (int i=0;i<s.length();i++)
+ {
+ char c = s.charAt(i);
+ if (__COOKIE_DELIM_PATH.indexOf(c)>=0)
+ return true;
+
+ if (c<0x20 || c>=0x7f)
+ throw new IllegalArgumentException("Illegal character in cookie value");
+ }
+
+ return false;
+ }
+
+ private static void quoteOnlyOrAppend(StringBuilder buf, String s, boolean quote)
+ {
+ if (quote)
+ QuotedStringTokenizer.quoteOnly(buf,s);
+ else
+ buf.append(s);
+ }
}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java
index 54ecd02..af09866 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java
@@ -55,13 +55,19 @@
private long _contentPrepared = 0;
private boolean _noContent = false;
private Boolean _persistent = null;
- private boolean _sendServerVersion;
+
+ private final int _send;
+ private final static int SEND_SERVER = 0x01;
+ private final static int SEND_XPOWEREDBY = 0x02;
/* ------------------------------------------------------------------------------- */
- public static void setServerVersion(String version)
+ public static void setJettyVersion(String serverVersion)
{
- SERVER=StringUtil.getBytes("Server: Jetty("+version+")\015\012");
+ SEND[SEND_SERVER] = StringUtil.getBytes("Server: " + serverVersion + "\015\012");
+ SEND[SEND_XPOWEREDBY] = StringUtil.getBytes("X-Powered-By: " + serverVersion + "\015\012");
+ SEND[SEND_SERVER | SEND_XPOWEREDBY] = StringUtil.getBytes("Server: " + serverVersion + "\015\012X-Powered-By: " +
+ serverVersion + "\015\012");
}
/* ------------------------------------------------------------------------------- */
@@ -71,6 +77,13 @@
/* ------------------------------------------------------------------------------- */
public HttpGenerator()
{
+ this(false,false);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpGenerator(boolean sendServerVersion,boolean sendXPoweredBy)
+ {
+ _send=(sendServerVersion?SEND_SERVER:0) | (sendXPoweredBy?SEND_XPOWEREDBY:0);
}
/* ------------------------------------------------------------------------------- */
@@ -86,15 +99,17 @@
}
/* ------------------------------------------------------------ */
+ @Deprecated
public boolean getSendServerVersion ()
{
- return _sendServerVersion;
+ return (_send&SEND_SERVER)!=0;
}
/* ------------------------------------------------------------ */
+ @Deprecated
public void setSendServerVersion (boolean sendServerVersion)
{
- _sendServerVersion = sendServerVersion;
+ throw new UnsupportedOperationException();
}
/* ------------------------------------------------------------ */
@@ -537,11 +552,11 @@
/* ------------------------------------------------------------ */
private void generateHeaders(Info _info,ByteBuffer header,ByteBuffer content,boolean last)
{
- final RequestInfo _request=(_info instanceof RequestInfo)?(RequestInfo)_info:null;
- final ResponseInfo _response=(_info instanceof ResponseInfo)?(ResponseInfo)_info:null;
+ final RequestInfo request=(_info instanceof RequestInfo)?(RequestInfo)_info:null;
+ final ResponseInfo response=(_info instanceof ResponseInfo)?(ResponseInfo)_info:null;
// default field values
- boolean has_server = false;
+ int send=_send;
HttpField transfer_encoding=null;
boolean keep_alive=false;
boolean close=false;
@@ -584,7 +599,7 @@
case CONNECTION:
{
- if (_request!=null)
+ if (request!=null)
field.putTo(header);
// Lookup and/or split connection value field
@@ -619,7 +634,7 @@
case CLOSE:
{
close=true;
- if (_response!=null)
+ if (response!=null)
{
_persistent=false;
if (_endOfContent == EndOfContent.UNKNOWN_CONTENT)
@@ -633,7 +648,7 @@
if (_info.getHttpVersion() == HttpVersion.HTTP_1_0)
{
keep_alive = true;
- if (_response!=null)
+ if (response!=null)
_persistent=true;
}
break;
@@ -656,11 +671,8 @@
case SERVER:
{
- if (getSendServerVersion())
- {
- has_server=true;
- field.putTo(header);
- }
+ send=send&~SEND_SERVER;
+ field.putTo(header);
break;
}
@@ -680,7 +692,7 @@
// 4. Content-Length
// 5. multipart/byteranges
// 6. close
- int status=_response!=null?_response.getStatus():-1;
+ int status=response!=null?response.getStatus():-1;
switch (_endOfContent)
{
case UNKNOWN_CONTENT:
@@ -688,14 +700,14 @@
// written yet?
// Response known not to have a body
- if (_contentPrepared == 0 && _response!=null && (status < 200 || status == 204 || status == 304))
+ if (_contentPrepared == 0 && response!=null && (status < 200 || status == 204 || status == 304))
_endOfContent=EndOfContent.NO_CONTENT;
else if (_info.getContentLength()>0)
{
// we have been given a content length
_endOfContent=EndOfContent.CONTENT_LENGTH;
long content_length = _info.getContentLength();
- if ((_response!=null || content_length>0 || content_type ) && !_noContent)
+ if ((response!=null || content_length>0 || content_type ) && !_noContent)
{
// known length but not actually set.
header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
@@ -710,7 +722,7 @@
long content_length = _contentPrepared+BufferUtil.length(content);
// Do we need to tell the headers about it
- if ((_response!=null || content_length>0 || content_type ) && !_noContent)
+ if ((response!=null || content_length>0 || content_type ) && !_noContent)
{
header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
BufferUtil.putDecLong(header, content_length);
@@ -721,7 +733,7 @@
{
// No idea, so we must assume that a body is coming
_endOfContent = (!isPersistent() || _info.getHttpVersion().ordinal() < HttpVersion.HTTP_1_1.ordinal() ) ? EndOfContent.EOF_CONTENT : EndOfContent.CHUNKED_CONTENT;
- if (_response!=null && _endOfContent==EndOfContent.EOF_CONTENT)
+ if (response!=null && _endOfContent==EndOfContent.EOF_CONTENT)
{
_endOfContent=EndOfContent.NO_CONTENT;
_noContent=true;
@@ -731,7 +743,7 @@
case CONTENT_LENGTH:
long content_length = _info.getContentLength();
- if ((_response!=null || content_length>0 || content_type ) && !_noContent)
+ if ((response!=null || content_length>0 || content_type ) && !_noContent)
{
// known length but not actually set.
header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
@@ -741,12 +753,12 @@
break;
case NO_CONTENT:
- if (_response!=null && status >= 200 && status != 204 && status != 304)
+ if (response!=null && status >= 200 && status != 204 && status != 304)
header.put(CONTENT_LENGTH_0);
break;
case EOF_CONTENT:
- _persistent = _request!=null;
+ _persistent = request!=null;
break;
case CHUNKED_CONTENT:
@@ -780,7 +792,7 @@
}
// If this is a response, work out persistence
- if (_response!=null)
+ if (response!=null)
{
if (!isPersistent() && (close || _info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal()))
{
@@ -814,8 +826,8 @@
}
}
- if (!has_server && status>199 && getSendServerVersion())
- header.put(SERVER);
+ if (status>199)
+ header.put(SEND[send]);
// end the header.
header.put(HttpTokens.CRLF);
@@ -851,7 +863,12 @@
private static final byte[] HTTP_1_1_SPACE = StringUtil.getBytes(HttpVersion.HTTP_1_1+" ");
private static final byte[] CRLF = StringUtil.getBytes("\015\012");
private static final byte[] TRANSFER_ENCODING_CHUNKED = StringUtil.getBytes("Transfer-Encoding: chunked\015\012");
- private static byte[] SERVER = StringUtil.getBytes("Server: Jetty(7.0.x)\015\012");
+ private static final byte[][] SEND = new byte[][]{
+ new byte[0],
+ StringUtil.getBytes("Server: Jetty(9.x.x)\015\012"),
+ StringUtil.getBytes("X-Powered-By: Jetty(9.x.x)\015\012"),
+ StringUtil.getBytes("Server: Jetty(9.x.x)\015\012X-Powered-By: Jetty(9.x.x)\015\012")
+ };
/* ------------------------------------------------------------------------------- */
/* ------------------------------------------------------------------------------- */
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeader.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeader.java
index 202eae1..8bba922 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeader.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeader.java
@@ -108,6 +108,8 @@
SET_COOKIE2("Set-Cookie2"),
MIME_VERSION("MIME-Version"),
IDENTITY("identity"),
+
+ X_POWERED_BY("X-Powered-By"),
UNKNOWN("::UNKNOWN::");
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 6b27173..15b2011 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
@@ -41,7 +41,7 @@
MOVE;
/* ------------------------------------------------------------ */
- /**
+ /**
* Optimised lookup to find a method name and trailing space in a byte array.
* @param bytes Array containing ISO-8859-1 characters
* @param position The first valid index
@@ -70,22 +70,22 @@
return HEAD;
break;
case 'O':
- if (bytes[position+1]=='O' && bytes[position+2]=='T' && bytes[position+3]=='I' && length>=8 &&
+ if (bytes[position+1]=='O' && bytes[position+2]=='T' && bytes[position+3]=='I' && length>=8 &&
bytes[position+4]=='O' && bytes[position+5]=='N' && bytes[position+6]=='S' && bytes[position+7]==' ' )
return OPTIONS;
break;
case 'D':
- if (bytes[position+1]=='E' && bytes[position+2]=='L' && bytes[position+3]=='E' && length>=7 &&
+ if (bytes[position+1]=='E' && bytes[position+2]=='L' && bytes[position+3]=='E' && length>=7 &&
bytes[position+4]=='T' && bytes[position+5]=='E' && bytes[position+6]==' ' )
return DELETE;
break;
case 'T':
- if (bytes[position+1]=='R' && bytes[position+2]=='A' && bytes[position+3]=='C' && length>=6 &&
+ if (bytes[position+1]=='R' && bytes[position+2]=='A' && bytes[position+3]=='C' && length>=6 &&
bytes[position+4]=='E' && bytes[position+5]==' ' )
return TRACE;
break;
case 'C':
- if (bytes[position+1]=='O' && bytes[position+2]=='N' && bytes[position+3]=='N' && length>=8 &&
+ if (bytes[position+1]=='O' && bytes[position+2]=='N' && bytes[position+3]=='N' && length>=8 &&
bytes[position+4]=='E' && bytes[position+5]=='C' && bytes[position+6]=='T' && bytes[position+7]==' ' )
return CONNECT;
break;
@@ -93,7 +93,7 @@
if (bytes[position+1]=='O' && bytes[position+2]=='V' && bytes[position+3]=='E' && bytes[position+4]==' ')
return MOVE;
break;
-
+
default:
break;
}
@@ -101,7 +101,7 @@
}
/* ------------------------------------------------------------ */
- /**
+ /**
* Optimised lookup to find a method name and trailing space in a byte array.
* @param buffer buffer containing ISO-8859-1 characters
* @return A HttpMethod if a match or null if no easy match.
@@ -110,14 +110,14 @@
{
if (buffer.hasArray())
return lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.arrayOffset()+buffer.limit());
-
+
// TODO use cache and check for space
// return CACHE.getBest(buffer,0,buffer.remaining());
return null;
}
-
+
/* ------------------------------------------------------------ */
- public final static Trie<HttpMethod> CACHE= new ArrayTrie<HttpMethod>();
+ public final static Trie<HttpMethod> CACHE= new ArrayTrie<>();
static
{
for (HttpMethod method : HttpMethod.values())
@@ -144,15 +144,15 @@
/* ------------------------------------------------------------ */
public boolean is(String s)
{
- return toString().equalsIgnoreCase(s);
+ return toString().equalsIgnoreCase(s);
}
-
+
/* ------------------------------------------------------------ */
public ByteBuffer asBuffer()
{
return _buffer.asReadOnlyBuffer();
}
-
+
/* ------------------------------------------------------------ */
public String asString()
{
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 b0e7909..db46429 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
@@ -63,11 +63,18 @@
* per parser dynamic Trie of {@link HttpFields} from previous parsed messages
* is used to help the parsing of subsequent messages.
* </p>
+ * <p>
+ * If the system property "org.eclipse.jetty.http.HttpParser.STRICT" is set to true,
+ * then the parser will strictly pass on the exact strings received for methods and header
+ * fields. Otherwise a fast case insensitive string lookup is used that may alter the
+ * case of the method and/or headers
+ * </p>
*/
public class HttpParser
{
public static final Logger LOG = Log.getLogger(HttpParser.class);
- static final int INITIAL_URI_LENGTH=256;
+ public final static boolean __STRICT=Boolean.getBoolean("org.eclipse.jetty.http.HttpParser.STRICT");
+ public final static int INITIAL_URI_LENGTH=256;
// States
public enum State
@@ -82,7 +89,6 @@
REQUEST_VERSION,
REASON,
HEADER,
- HEADER_NAME,
HEADER_IN_NAME,
HEADER_VALUE,
HEADER_IN_VALUE,
@@ -96,10 +102,12 @@
CLOSED
};
+ private final boolean DEBUG=LOG.isDebugEnabled();
private final HttpHandler<ByteBuffer> _handler;
private final RequestHandler<ByteBuffer> _requestHandler;
private final ResponseHandler<ByteBuffer> _responseHandler;
private final int _maxHeaderBytes;
+ private final boolean _strict;
private HttpField _field;
private HttpHeader _header;
private String _headerString;
@@ -131,31 +139,45 @@
/* ------------------------------------------------------------------------------- */
public HttpParser(RequestHandler<ByteBuffer> handler)
{
- this(handler,-1);
+ this(handler,-1,__STRICT);
}
/* ------------------------------------------------------------------------------- */
public HttpParser(ResponseHandler<ByteBuffer> handler)
{
- this(handler,-1);
+ this(handler,-1,__STRICT);
}
/* ------------------------------------------------------------------------------- */
public HttpParser(RequestHandler<ByteBuffer> handler,int maxHeaderBytes)
{
- _handler=handler;
- _requestHandler=handler;
- _responseHandler=null;
- _maxHeaderBytes=maxHeaderBytes;
+ this(handler,maxHeaderBytes,__STRICT);
}
/* ------------------------------------------------------------------------------- */
public HttpParser(ResponseHandler<ByteBuffer> handler,int maxHeaderBytes)
{
+ this(handler,maxHeaderBytes,__STRICT);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(RequestHandler<ByteBuffer> handler,int maxHeaderBytes,boolean strict)
+ {
+ _handler=handler;
+ _requestHandler=handler;
+ _responseHandler=null;
+ _maxHeaderBytes=maxHeaderBytes;
+ _strict=strict;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(ResponseHandler<ByteBuffer> handler,int maxHeaderBytes,boolean strict)
+ {
_handler=handler;
_requestHandler=null;
_responseHandler=handler;
_maxHeaderBytes=maxHeaderBytes;
+ _strict=strict;
}
/* ------------------------------------------------------------------------------- */
@@ -239,6 +261,7 @@
return _state == state;
}
+ /* ------------------------------------------------------------------------------- */
private static class BadMessage extends Error
{
private final int _code;
@@ -309,7 +332,7 @@
if (ch==HttpTokens.LINE_FEED)
return ch;
- throw new BadMessage();
+ throw new BadMessage("Bad EOL");
}
// Defer lookup of LF
@@ -319,7 +342,7 @@
// Only LF or TAB acceptable special characters
if (ch!=HttpTokens.LINE_FEED && ch!=HttpTokens.TAB)
- throw new BadMessage();
+ throw new BadMessage("Illegal character");
/*
if (ch>HttpTokens.SPACE)
@@ -374,14 +397,16 @@
return false;
}
- private String takeString()
+ /* ------------------------------------------------------------------------------- */
+ private void setString(String s)
{
- String s =_string.toString();
_string.setLength(0);
- return s;
+ _string.append(s);
+ _length=s.length();
}
-
- private String takeLengthString()
+
+ /* ------------------------------------------------------------------------------- */
+ private String takeString()
{
_string.setLength(_length);
String s =_string.toString();
@@ -402,8 +427,6 @@
{
// process each character
byte ch=next(buffer);
- if (ch==-1)
- return true;
if (ch==0)
continue;
@@ -429,16 +452,15 @@
case METHOD:
if (ch == HttpTokens.SPACE)
{
+ _length=_string.length();
_methodString=takeString();
HttpMethod method=HttpMethod.CACHE.get(_methodString);
- if (method!=null)
+ if (method!=null && !_strict)
_methodString=method.asString();
setState(State.SPACE1);
}
- else if (ch < HttpTokens.SPACE && ch>=0)
- {
- throw new BadMessage(HttpStatus.BAD_REQUEST_400,"No URI");
- }
+ else if (ch<HttpTokens.SPACE)
+ throw new BadMessage(ch<0?"Illegal character":"No URI");
else
_string.append((char)ch);
break;
@@ -446,6 +468,7 @@
case RESPONSE_VERSION:
if (ch == HttpTokens.SPACE)
{
+ _length=_string.length();
String version=takeString();
_version=HttpVersion.CACHE.get(version);
if (_version==null)
@@ -454,16 +477,14 @@
}
setState(State.SPACE1);
}
- else if (ch < HttpTokens.SPACE && ch>=0)
- {
- throw new BadMessage(HttpStatus.BAD_REQUEST_400,"No Status");
- }
+ else if (ch < HttpTokens.SPACE)
+ throw new BadMessage(ch<0?"Illegal character":"No Status");
else
_string.append((char)ch);
break;
case SPACE1:
- if (ch > HttpTokens.SPACE || ch<0)
+ if (ch > HttpTokens.SPACE)
{
if (_responseHandler!=null)
{
@@ -507,9 +528,7 @@
}
}
else if (ch < HttpTokens.SPACE)
- {
throw new BadMessage(HttpStatus.BAD_REQUEST_400,_requestHandler!=null?"No URI":"No Status");
- }
break;
case STATUS:
@@ -528,7 +547,7 @@
}
else
{
- throw new IllegalStateException();
+ throw new BadMessage();
}
break;
@@ -561,7 +580,7 @@
break;
case SPACE2:
- if (ch > HttpTokens.SPACE || ch<0)
+ if (ch > HttpTokens.SPACE)
{
_string.setLength(0);
_string.append((char)ch);
@@ -620,13 +639,18 @@
return_from_parse=_handler.messageComplete()||return_from_parse;
}
}
+ else if (ch<HttpTokens.SPACE)
+ throw new BadMessage();
break;
case REQUEST_VERSION:
if (ch == HttpTokens.LINE_FEED)
{
if (_version==null)
+ {
+ _length=_string.length();
_version=HttpVersion.CACHE.get(takeString());
+ }
if (_version==null)
{
throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Unknown Version");
@@ -645,26 +669,30 @@
return_from_parse=_requestHandler.startRequest(_method,_methodString,_uri, _version)||return_from_parse;
continue;
}
- else
+ else if (ch>HttpTokens.SPACE)
_string.append((char)ch);
+ else
+ throw new BadMessage();
break;
case REASON:
if (ch == HttpTokens.LINE_FEED)
{
- String reason=takeLengthString();
+ String reason=takeString();
setState(State.HEADER);
return_from_parse=_responseHandler.startResponse(_version, _responseStatus, reason)||return_from_parse;
continue;
}
- else
+ else if (ch>=HttpTokens.SPACE)
{
_string.append((char)ch);
if (ch!=' '&&ch!='\t')
_length=_string.length();
}
+ else
+ throw new BadMessage();
break;
default:
@@ -741,7 +769,8 @@
}
catch (NumberFormatException e)
{
- LOG.debug(e);
+ if (DEBUG)
+ LOG.debug(e);
throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad Host header");
}
break loop;
@@ -806,8 +835,6 @@
{
// process each character
byte ch=next(buffer);
- if (ch==-1)
- return true;
if (ch==0)
continue;
@@ -827,14 +854,18 @@
case HttpTokens.TAB:
{
// header value without name - continuation?
- _string.setLength(0);
- if (_valueString!=null)
+ if (_valueString==null)
{
- _string.append(_valueString);
- _string.append(' ');
+ _string.setLength(0);
+ _length=0;
}
- _length=_string.length();
- _valueString=null;
+ else
+ {
+ setString(_valueString);
+ _string.append(' ');
+ _length++;
+ _valueString=null;
+ }
setState(State.HEADER_VALUE);
break;
}
@@ -917,6 +948,8 @@
break;
}
}
+ else if (ch<=HttpTokens.SPACE)
+ throw new BadMessage();
else
{
if (buffer.hasRemaining())
@@ -928,14 +961,35 @@
if (field!=null)
{
- String n=field.getName();
- String v=field.getValue();
+ final String n;
+ final String v;
+
+ if (_strict)
+ {
+ // Have to get the fields exactly from the buffer to match case
+ String fn=field.getName();
+ String fv=field.getValue();
+ n=BufferUtil.toString(buffer,buffer.position()-1,fn.length(),StringUtil.__US_ASCII_CHARSET);
+ if (fv==null)
+ v=null;
+ else
+ {
+ v=BufferUtil.toString(buffer,buffer.position()+fn.length()+1,fv.length(),StringUtil.__ISO_8859_1_CHARSET);
+ field=new HttpField(field.getHeader(),n,v);
+ }
+ }
+ else
+ {
+ n=field.getName();
+ v=field.getValue();
+ }
+
+ _header=field.getHeader();
+ _headerString=n;
if (v==null)
{
// Header only
- _header=field.getHeader();
- _headerString=n;
setState(State.HEADER_VALUE);
_string.setLength(0);
_length=0;
@@ -951,8 +1005,6 @@
if (b==HttpTokens.CARRIAGE_RETURN || b==HttpTokens.LINE_FEED)
{
_field=field;
- _header=_field.getHeader();
- _headerString=n;
_valueString=v;
setState(State.HEADER_IN_VALUE);
@@ -965,186 +1017,111 @@
buffer.position(pos);
break;
}
+ else
+ {
+ setState(State.HEADER_IN_VALUE);
+ setString(v);
+ buffer.position(pos);
+ break;
+ }
}
}
}
// New header
- setState(State.HEADER_NAME);
+ setState(State.HEADER_IN_NAME);
_string.setLength(0);
_string.append((char)ch);
_length=1;
}
}
}
-
- break;
-
- case HEADER_NAME:
- switch(ch)
- {
- case HttpTokens.LINE_FEED:
- if (_headerString==null)
- {
- _headerString=takeLengthString();
- _header=HttpHeader.CACHE.get(_headerString);
- }
- setState(State.HEADER);
-
- break;
-
- case HttpTokens.COLON:
- if (_headerString==null)
- {
- _headerString=takeLengthString();
- _header=HttpHeader.CACHE.get(_headerString);
- }
- setState(State.HEADER_VALUE);
- break;
- case HttpTokens.SPACE:
- case HttpTokens.TAB:
- _string.append((char)ch);
- break;
- default:
- {
- _string.append((char)ch);
- _length=_string.length();
- setState(State.HEADER_IN_NAME);
- }
- }
-
break;
case HEADER_IN_NAME:
- switch(ch)
+ if (ch==HttpTokens.COLON || ch==HttpTokens.LINE_FEED)
{
- case HttpTokens.LINE_FEED:
+ if (_headerString==null)
+ {
_headerString=takeString();
- _length=-1;
_header=HttpHeader.CACHE.get(_headerString);
- setState(State.HEADER);
- break;
+ }
+ _length=-1;
- case HttpTokens.COLON:
- if (_headerString==null)
- {
- _headerString=takeString();
- _header=HttpHeader.CACHE.get(_headerString);
- }
- _length=-1;
- setState(State.HEADER_VALUE);
- break;
- case HttpTokens.SPACE:
- case HttpTokens.TAB:
- if (_header!=null)
- {
- _string.setLength(0);
- _string.append(_header.asString());
- _length=_string.length();
- _header=null;
- _headerString=null;
- }
- setState(State.HEADER_NAME);
- _string.append((char)ch);
- break;
- default:
- if (_header!=null)
- {
- _string.setLength(0);
- _string.append(_header.asString());
- _length=_string.length();
- _header=null;
- _headerString=null;
- }
- _string.append((char)ch);
- _length++;
+ setState(ch==HttpTokens.LINE_FEED?State.HEADER:State.HEADER_VALUE);
+ break;
}
- break;
+
+ if (ch>=HttpTokens.SPACE || ch==HttpTokens.TAB)
+ {
+ if (_header!=null)
+ {
+ setString(_header.asString());
+ _header=null;
+ _headerString=null;
+ }
+
+ _string.append((char)ch);
+ if (ch>HttpTokens.SPACE)
+ _length=_string.length();
+ break;
+ }
+
+ throw new BadMessage("Illegal character");
case HEADER_VALUE:
- switch(ch)
+ if (ch>HttpTokens.SPACE || ch<0)
{
- case HttpTokens.LINE_FEED:
- if (_length > 0)
- {
- if (_valueString!=null)
- {
- // multi line value!
- _value=null;
- _valueString+=" "+takeLengthString();
- }
- else if (HttpHeaderValue.hasKnownValues(_header))
- {
- _valueString=takeLengthString();
- _value=HttpHeaderValue.CACHE.get(_valueString);
- }
- else
- {
- _value=null;
- _valueString=takeLengthString();
- }
- }
- setState(State.HEADER);
- break;
- case HttpTokens.SPACE:
- case HttpTokens.TAB:
- break;
- default:
- {
- _string.append((char)ch);
- _length=_string.length();
- setState(State.HEADER_IN_VALUE);
- }
+ _string.append((char)(0xff&ch));
+ _length=_string.length();
+ setState(State.HEADER_IN_VALUE);
+ break;
}
- break;
+
+ if (ch==HttpTokens.SPACE || ch==HttpTokens.TAB)
+ break;
+
+ if (ch==HttpTokens.LINE_FEED)
+ {
+ if (_length > 0)
+ {
+ _value=null;
+ _valueString=(_valueString==null)?takeString():(_valueString+" "+takeString());
+ }
+ setState(State.HEADER);
+ break;
+ }
+
+ throw new BadMessage("Illegal character");
case HEADER_IN_VALUE:
- switch(ch)
+ if (ch>=HttpTokens.SPACE || ch<0)
{
- case HttpTokens.LINE_FEED:
- if (_length > 0)
- {
- if (HttpHeaderValue.hasKnownValues(_header))
- {
- _valueString=takeString();
- _value=HttpHeaderValue.CACHE.get(_valueString);
- }
- else
- {
- _value=null;
- _valueString=takeString();
- }
- _length=-1;
- }
- setState(State.HEADER);
- break;
- case HttpTokens.SPACE:
- case HttpTokens.TAB:
- if (_valueString!=null)
- {
- _string.setLength(0);
- _string.append(_valueString);
- _length=_valueString.length();
- _valueString=null;
- _field=null;
- }
- _string.append((char)ch);
- setState(State.HEADER_VALUE);
- break;
- default:
- if (_valueString!=null)
- {
- _string.setLength(0);
- _string.append(_valueString);
- _length=_valueString.length();
- _valueString=null;
- _field=null;
- }
- _string.append((char)ch);
- _length++;
+ if (_valueString!=null)
+ {
+ setString(_valueString);
+ _valueString=null;
+ _field=null;
+ }
+ _string.append((char)(0xff&ch));
+ if (ch>HttpTokens.SPACE || ch<0)
+ _length=_string.length();
+ break;
}
- break;
-
+
+ if (ch==HttpTokens.LINE_FEED)
+ {
+ if (_length > 0)
+ {
+ _value=null;
+ _valueString=takeString();
+ _length=-1;
+ }
+ setState(State.HEADER);
+ break;
+ }
+ throw new BadMessage("Illegal character");
+
default:
throw new IllegalStateException(_state.toString());
@@ -1368,8 +1345,9 @@
{
BufferUtil.clear(buffer);
- LOG.warn("badMessage: "+e._code+(e._message!=null?" "+e._message:"")+" for "+_handler);
- LOG.debug(e);
+ LOG.warn("BadMessage: "+e._code+(e._message!=null?" "+e._message:"")+" for "+_handler);
+ if (DEBUG)
+ LOG.debug(e);
setState(State.CLOSED);
_handler.badMessage(e._code, e._message);
return false;
@@ -1378,8 +1356,9 @@
{
BufferUtil.clear(buffer);
- LOG.warn("badMessage: "+e.toString()+" for "+_handler);
- LOG.debug(e);
+ LOG.warn("Parsing Exception: "+e.toString()+" for "+_handler);
+ if (DEBUG)
+ LOG.debug(e);
if (_state.ordinal()<=State.END.ordinal())
{
@@ -1408,7 +1387,8 @@
*/
public boolean shutdownInput()
{
- LOG.debug("shutdownInput {}", this);
+ if (DEBUG)
+ LOG.debug("shutdownInput {}", this);
// was this unexpected?
switch(_state)
@@ -1437,6 +1417,8 @@
/* ------------------------------------------------------------------------------- */
public void close()
{
+ if (DEBUG)
+ LOG.debug("close {}", this);
switch(_state)
{
case START:
@@ -1469,6 +1451,8 @@
/* ------------------------------------------------------------------------------- */
public void reset()
{
+ if (DEBUG)
+ LOG.debug("reset {}", this);
// reset state
setState(State.START);
_endOfContent=EndOfContent.UNKNOWN_CONTENT;
@@ -1483,7 +1467,8 @@
/* ------------------------------------------------------------------------------- */
private void setState(State state)
{
- // LOG.debug("{} --> {}",_state,state);
+ if (DEBUG)
+ LOG.debug("{} --> {}",_state,state);
_state=state;
}
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 b14221d..0170d3e 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
@@ -92,6 +92,19 @@
_version=version;
}
+ public void setContent(byte[] bytes)
+ {
+ try
+ {
+ _content=new ByteArrayOutputStream();
+ _content.write(bytes);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
public void setContent(String content)
{
try
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java
index 3f8a91d..d1674aa 100644
--- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java
@@ -279,10 +279,10 @@
//test cookies with same name, domain and path, only 1 allowed
fields.addSetCookie("everything","wrong","domain","path",0,"to be replaced",true,true,0);
fields.addSetCookie("everything","value","domain","path",0,"comment",true,true,0);
- assertEquals("everything=value;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",fields.getStringField("Set-Cookie"));
+ assertEquals("everything=value;Version=1;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",fields.getStringField("Set-Cookie"));
Enumeration<String> e =fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements());
- assertEquals("everything=value;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
+ assertEquals("everything=value;Version=1;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
assertFalse(e.hasMoreElements());
assertEquals("Thu, 01 Jan 1970 00:00:00 GMT",fields.getStringField("Expires"));
assertFalse(e.hasMoreElements());
@@ -293,9 +293,9 @@
fields.addSetCookie("everything","value","domain2","path",0,"comment",true,true,0);
e =fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements());
- assertEquals("everything=other;Path=path;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
+ assertEquals("everything=other;Version=1;Path=path;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
assertTrue(e.hasMoreElements());
- assertEquals("everything=value;Path=path;Domain=domain2;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
+ assertEquals("everything=value;Version=1;Path=path;Domain=domain2;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
assertFalse(e.hasMoreElements());
//test cookies with same name, same path, one with domain, one without
@@ -304,9 +304,9 @@
fields.addSetCookie("everything","value","","path",0,"comment",true,true,0);
e =fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements());
- assertEquals("everything=other;Path=path;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
+ assertEquals("everything=other;Version=1;Path=path;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
assertTrue(e.hasMoreElements());
- assertEquals("everything=value;Path=path;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
+ assertEquals("everything=value;Version=1;Path=path;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
assertFalse(e.hasMoreElements());
@@ -316,9 +316,9 @@
fields.addSetCookie("everything","value","domain1","path2",0,"comment",true,true,0);
e =fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements());
- assertEquals("everything=other;Path=path1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
+ assertEquals("everything=other;Version=1;Path=path1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
assertTrue(e.hasMoreElements());
- assertEquals("everything=value;Path=path2;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
+ assertEquals("everything=value;Version=1;Path=path2;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
assertFalse(e.hasMoreElements());
//test cookies with same name, same domain, one with path, one without
@@ -327,9 +327,9 @@
fields.addSetCookie("everything","value","domain1","",0,"comment",true,true,0);
e =fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements());
- assertEquals("everything=other;Path=path1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
+ assertEquals("everything=other;Version=1;Path=path1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
assertTrue(e.hasMoreElements());
- assertEquals("everything=value;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
+ assertEquals("everything=value;Version=1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
assertFalse(e.hasMoreElements());
//test cookies same name only, no path, no domain
@@ -338,13 +338,13 @@
fields.addSetCookie("everything","value","","",0,"comment",true,true,0);
e =fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements());
- assertEquals("everything=value;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
+ assertEquals("everything=value;Version=1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
assertFalse(e.hasMoreElements());
fields.clear();
- fields.addSetCookie("ev erything","va lue","do main","pa th",1,"co mment",true,true,2);
+ fields.addSetCookie("ev erything","va lue","do main","pa th",1,"co mment",true,true,1);
String setCookie=fields.getStringField("Set-Cookie");
- assertThat(setCookie,Matchers.startsWith("\"ev erything\"=\"va lue\";Path=\"pa th\";Domain=\"do main\";Expires="));
+ assertThat(setCookie,Matchers.startsWith("\"ev erything\"=\"va lue\";Version=1;Path=\"pa th\";Domain=\"do main\";Expires="));
assertThat(setCookie,Matchers.endsWith(" GMT;Max-Age=1;Secure;HttpOnly;Comment=\"co mment\""));
fields.clear();
@@ -376,7 +376,7 @@
fields=new HttpFields();
fields.addSetCookie("name","value==",null,null,-1,null,false,false,0);
setCookie=fields.getStringField("Set-Cookie");
- assertEquals("name=\"value==\"",setCookie);
+ assertEquals("name=\"value==\";Version=1",setCookie);
}
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 262c0ac..026fd2c 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
@@ -34,6 +34,7 @@
import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
import org.eclipse.jetty.util.BufferUtil;
+import org.hamcrest.Matchers;
import org.junit.Test;
public class HttpGeneratorServerTest
@@ -309,6 +310,54 @@
}
}
}
+
+ @Test
+ public void testSendServerXPoweredBy() throws Exception
+ {
+ ByteBuffer header = BufferUtil.allocate(8096);
+ ResponseInfo info = new ResponseInfo(HttpVersion.HTTP_1_1, new HttpFields(), -1, 200, null, false);
+ HttpFields fields = new HttpFields();
+ fields.add(HttpHeader.SERVER,"SomeServer");
+ fields.add(HttpHeader.X_POWERED_BY,"SomePower");
+ ResponseInfo infoF = new ResponseInfo(HttpVersion.HTTP_1_1, fields, -1, 200, null, false);
+ String head;
+
+ HttpGenerator gen = new HttpGenerator(true,true);
+ gen.generateResponse(info, header, null, null, true);
+ head = BufferUtil.toString(header);
+ BufferUtil.clear(header);
+ assertThat(head, containsString("HTTP/1.1 200 OK"));
+ assertThat(head, containsString("Server: Jetty(9.x.x)"));
+ assertThat(head, containsString("X-Powered-By: Jetty(9.x.x)"));
+ gen.reset();
+ gen.generateResponse(infoF, header, null, null, true);
+ head = BufferUtil.toString(header);
+ BufferUtil.clear(header);
+ assertThat(head, containsString("HTTP/1.1 200 OK"));
+ assertThat(head, not(containsString("Server: Jetty(9.x.x)")));
+ assertThat(head, containsString("Server: SomeServer"));
+ assertThat(head, containsString("X-Powered-By: Jetty(9.x.x)"));
+ assertThat(head, containsString("X-Powered-By: SomePower"));
+ gen.reset();
+
+ gen = new HttpGenerator(false,false);
+ gen.generateResponse(info, header, null, null, true);
+ head = BufferUtil.toString(header);
+ BufferUtil.clear(header);
+ assertThat(head, containsString("HTTP/1.1 200 OK"));
+ assertThat(head, not(containsString("Server: Jetty(9.x.x)")));
+ assertThat(head, not(containsString("X-Powered-By: Jetty(9.x.x)")));
+ gen.reset();
+ gen.generateResponse(infoF, header, null, null, true);
+ head = BufferUtil.toString(header);
+ BufferUtil.clear(header);
+ assertThat(head, containsString("HTTP/1.1 200 OK"));
+ assertThat(head, not(containsString("Server: Jetty(9.x.x)")));
+ assertThat(head, containsString("Server: SomeServer"));
+ assertThat(head, not(containsString("X-Powered-By: Jetty(9.x.x)")));
+ assertThat(head, containsString("X-Powered-By: SomePower"));
+ gen.reset();
+ }
@Test
public void testResponseNoContent() throws Exception
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 b65e10a..654474d 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
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.nio.ByteBuffer;
@@ -29,6 +30,8 @@
import org.eclipse.jetty.http.HttpParser.State;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.StringUtil;
+import org.hamcrest.Matchers;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -314,6 +317,115 @@
}
@Test
+ public void testEncodedHeader() throws Exception
+ {
+ ByteBuffer buffer=BufferUtil.allocate(4096);
+ BufferUtil.flipToFill(buffer);
+ BufferUtil.put(BufferUtil.toBuffer("GET "),buffer);
+ buffer.put("/foo/\u0690/".getBytes(StringUtil.__UTF8_CHARSET));
+ BufferUtil.put(BufferUtil.toBuffer(" HTTP/1.0\r\n"),buffer);
+ BufferUtil.put(BufferUtil.toBuffer("Header1: "),buffer);
+ buffer.put("\u00e6 \u00e6".getBytes(StringUtil.__ISO_8859_1_CHARSET));
+ BufferUtil.put(BufferUtil.toBuffer(" \r\n\r\n"),buffer);
+ BufferUtil.flipToFlush(buffer,0);
+
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
+ parseAll(parser,buffer);
+
+ assertEquals("GET", _methodOrVersion);
+ assertEquals("/foo/\u0690/", _uriOrStatus);
+ assertEquals("HTTP/1.0", _versionOrReason);
+ assertEquals("Header1", _hdr[0]);
+ assertEquals("\u00e6 \u00e6", _val[0]);
+ assertEquals(0, _h);
+ assertEquals(null,_bad);
+ }
+
+
+
+ @Test
+ public void testBadMethodEncoding() throws Exception
+ {
+ ByteBuffer buffer= BufferUtil.toBuffer(
+ "G\u00e6T / HTTP/1.0\r\nHeader0: value0\r\n\n\n");
+
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
+ parseAll(parser,buffer);
+ assertThat(_bad,Matchers.notNullValue());
+ }
+
+ @Test
+ public void testBadVersionEncoding() throws Exception
+ {
+ ByteBuffer buffer= BufferUtil.toBuffer(
+ "GET / H\u00e6P/1.0\r\nHeader0: value0\r\n\n\n");
+
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
+ parseAll(parser,buffer);
+ assertThat(_bad,Matchers.notNullValue());
+ }
+
+
+ @Test
+ public void testBadHeaderEncoding() throws Exception
+ {
+ ByteBuffer buffer= BufferUtil.toBuffer(
+ "GET / HTTP/1.0\r\nH\u00e6der0: value0\r\n\n\n");
+
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler);
+ parseAll(parser,buffer);
+ assertThat(_bad,Matchers.notNullValue());
+ }
+
+ @Test
+ public void testNonStrict() throws Exception
+ {
+ ByteBuffer buffer= BufferUtil.toBuffer(
+ "get / http/1.0\015\012" +
+ "HOST: localhost\015\012" +
+ "cOnNeCtIoN: ClOsE\015\012"+
+ "\015\012");
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler,-1,false);
+ parseAll(parser,buffer);
+
+ assertEquals("GET", _methodOrVersion);
+ assertEquals("/", _uriOrStatus);
+ assertEquals("HTTP/1.0", _versionOrReason);
+ assertEquals("Host", _hdr[0]);
+ assertEquals("localhost", _val[0]);
+ assertEquals("Connection", _hdr[1]);
+ assertEquals("close", _val[1]);
+ assertEquals(1, _h);
+ }
+
+ @Test
+ public void testStrict() throws Exception
+ {
+ ByteBuffer buffer= BufferUtil.toBuffer(
+ "gEt / http/1.0\015\012" +
+ "HOST: localhost\015\012" +
+ "cOnNeCtIoN: ClOsE\015\012"+
+ "\015\012");
+ HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
+ HttpParser parser= new HttpParser(handler,-1,true);
+ parseAll(parser,buffer);
+
+ assertEquals("gEt", _methodOrVersion);
+ assertEquals("/", _uriOrStatus);
+ assertEquals("HTTP/1.0", _versionOrReason);
+ assertEquals("HOST", _hdr[0]);
+ assertEquals("localhost", _val[0]);
+ assertEquals("cOnNeCtIoN", _hdr[1]);
+ assertEquals("ClOsE", _val[1]);
+ assertEquals(1, _h);
+ }
+
+ @Test
public void testSplitHeaderParse() throws Exception
{
ByteBuffer buffer= BufferUtil.toBuffer(
@@ -426,7 +538,6 @@
+ "\015\012"
+ "0123456789\015\012");
-
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
@@ -1125,7 +1236,7 @@
@Override
public void badMessage(int status, String reason)
{
- _bad=reason;
+ _bad=reason==null?(""+status):reason;
}
@Override
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java
index 5193f07..65878ce 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java
@@ -57,7 +57,7 @@
if (b.hasRemaining())
{
int position = b.position();
- flushed|=super.flush(b);
+ flushed&=super.flush(b);
int l=b.position()-position;
notifyOutgoing(b, position, l);
if (!flushed)
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java
index 7342c91..b972ce9 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java
@@ -19,7 +19,6 @@
package org.eclipse.jetty.io.ssl;
import java.io.IOException;
-import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.util.Arrays;
@@ -39,7 +38,6 @@
import org.eclipse.jetty.io.FillInterest;
import org.eclipse.jetty.io.RuntimeIOException;
import org.eclipse.jetty.io.SelectChannelEndPoint;
-import org.eclipse.jetty.io.SocketBased;
import org.eclipse.jetty.io.WriteFlusher;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
@@ -110,17 +108,20 @@
this._sslEngine = sslEngine;
this._decryptedEndPoint = newDecryptedEndPoint();
- if (endPoint instanceof SocketBased)
- {
- try
- {
- ((SocketBased)endPoint).getSocket().setSoLinger(true, 30000);
- }
- catch (SocketException e)
- {
- throw new RuntimeIOException(e);
- }
- }
+ // commented out for now as it might cause native code being stuck in preClose0.
+ // See: https://java.net/jira/browse/GRIZZLY-547
+
+// if (endPoint instanceof SocketBased)
+// {
+// try
+// {
+// ((SocketBased)endPoint).getSocket().setSoLinger(true, 30000);
+// }
+// catch (SocketException e)
+// {
+// throw new RuntimeIOException(e);
+// }
+// }
}
protected DecryptedEndPoint newDecryptedEndPoint()
diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/PropertyFileLoginModule.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/PropertyFileLoginModule.java
index 45561d4..028e4f2 100644
--- a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/PropertyFileLoginModule.java
+++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/PropertyFileLoginModule.java
@@ -20,10 +20,10 @@
import java.security.Principal;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
@@ -45,7 +45,7 @@
private static final Logger LOG = Log.getLogger(PropertyFileLoginModule.class);
- private static Map<String, PropertyUserStore> _propertyUserStores = new HashMap<String, PropertyUserStore>();
+ private static ConcurrentHashMap<String, PropertyUserStore> _propertyUserStores = new ConcurrentHashMap<String, PropertyUserStore>();
private int _refreshInterval = 0;
private String _filename = DEFAULT_FILENAME;
@@ -68,33 +68,37 @@
private void setupPropertyUserStore(Map<String, ?> options)
{
+ parseConfig(options);
+
if (_propertyUserStores.get(_filename) == null)
{
- parseConfig(options);
+ PropertyUserStore propertyUserStore = new PropertyUserStore();
+ propertyUserStore.setConfig(_filename);
+ propertyUserStore.setRefreshInterval(_refreshInterval);
- PropertyUserStore _propertyUserStore = new PropertyUserStore();
- _propertyUserStore.setConfig(_filename);
- _propertyUserStore.setRefreshInterval(_refreshInterval);
- LOG.debug("setupPropertyUserStore: Starting new PropertyUserStore. PropertiesFile: " + _filename + " refreshInterval: " + _refreshInterval);
-
- try
+ PropertyUserStore prev = _propertyUserStores.putIfAbsent(_filename, propertyUserStore);
+ if (prev == null)
{
- _propertyUserStore.start();
- }
- catch (Exception e)
- {
- LOG.warn("Exception while starting propertyUserStore: ",e);
- }
+ LOG.debug("setupPropertyUserStore: Starting new PropertyUserStore. PropertiesFile: " + _filename + " refreshInterval: " + _refreshInterval);
- _propertyUserStores.put(_filename,_propertyUserStore);
+ try
+ {
+ propertyUserStore.start();
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Exception while starting propertyUserStore: ",e);
+ }
+ }
}
}
private void parseConfig(Map<String, ?> options)
{
- _filename = (String)options.get("file") != null?(String)options.get("file"):DEFAULT_FILENAME;
- String refreshIntervalString = (String)options.get("refreshInterval");
- _refreshInterval = refreshIntervalString == null?_refreshInterval:Integer.parseInt(refreshIntervalString);
+ String tmp = (String)options.get("file");
+ _filename = (tmp == null? DEFAULT_FILENAME : tmp);
+ tmp = (String)options.get("refreshInterval");
+ _refreshInterval = (tmp == null?_refreshInterval:Integer.parseInt(tmp));
}
/**
diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/ContextFactory.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/ContextFactory.java
index b3c6907..c56b694 100644
--- a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/ContextFactory.java
+++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/ContextFactory.java
@@ -75,7 +75,14 @@
* Threadlocal for injecting a context to use
* instead of looking up the map.
*/
- private static final ThreadLocal __threadContext = new ThreadLocal();
+ private static final ThreadLocal<Context> __threadContext = new ThreadLocal<Context>();
+
+ /**
+ * Threadlocal for setting a classloader which must be used
+ * when finding the comp context.
+ */
+ private static final ThreadLocal<ClassLoader> __threadClassLoader = new ThreadLocal<ClassLoader>();
+
/**
@@ -107,10 +114,25 @@
return ctx;
}
+ //See if there is a classloader to use for finding the comp context
+ //Don't use its parent hierarchy if set.
+ ClassLoader loader = (ClassLoader)__threadClassLoader.get();
+ if (loader != null)
+ {
+ if (__log.isDebugEnabled() && loader != null) __log.debug("Using threadlocal classloader");
+ ctx = getContextForClassLoader(loader);
+ if (ctx == null)
+ {
+ ctx = newNamingContext(obj, loader, env, name, nameCtx);
+ __contextMap.put (loader, ctx);
+ if(__log.isDebugEnabled())__log.debug("Made context "+name.get(0)+" for classloader: "+loader);
+ }
+ return ctx;
+ }
- ClassLoader tccl = Thread.currentThread().getContextClassLoader();
- ClassLoader loader = tccl;
//If the thread context classloader is set, then try its hierarchy to find a matching context
+ ClassLoader tccl = Thread.currentThread().getContextClassLoader();
+ loader = tccl;
if (loader != null)
{
if (__log.isDebugEnabled() && loader != null) __log.debug("Trying thread context classloader");
@@ -194,25 +216,34 @@
/**
* Associate the given Context with the current thread.
- * resetComponentContext method should be called to reset the context.
+ * disassociate method should be called to reset the context.
* @param ctx the context to associate to the current thread.
* @return the previous context associated on the thread (can be null)
*/
- public static Context setComponentContext(final Context ctx)
+ public static Context associateContext(final Context ctx)
{
Context previous = (Context)__threadContext.get();
__threadContext.set(ctx);
return previous;
}
- /**
- * Set back the context with the given value.
- * Don't return the previous context, use setComponentContext() method for this.
- * @param ctx the context to associate to the current thread.
- */
- public static void resetComponentContext(final Context ctx)
+ public static void disassociateContext(final Context ctx)
{
- __threadContext.set(ctx);
+ __threadContext.remove();
+ }
+
+
+ public static ClassLoader associateClassLoader(final ClassLoader loader)
+ {
+ ClassLoader prev = (ClassLoader)__threadClassLoader.get();
+ __threadClassLoader.set(loader);
+ return prev;
+ }
+
+
+ public static void disassociateClassLoader ()
+ {
+ __threadClassLoader.remove();
}
public static void dump(Appendable out, String indent) throws IOException
diff --git a/jetty-maven-plugin/pom.xml b/jetty-maven-plugin/pom.xml
index 55a68c0..8b5f991 100644
--- a/jetty-maven-plugin/pom.xml
+++ b/jetty-maven-plugin/pom.xml
@@ -120,6 +120,11 @@
<artifactId>jetty-jsp</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-continuation</artifactId>
+ <version>${project.version}</version>
+ </dependency>
</dependencies>
<reporting>
<plugins>
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 4e976f4..50643d5 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
@@ -203,6 +203,12 @@
*/
protected String stopKey;
+ /**
+ * Use the dump() facility of jetty to print out the server configuration to logging
+ *
+ * @parameter expression"${dumponStart}" default-value="false"
+ */
+ protected boolean dumpOnStart;
/**
* <p>
@@ -272,6 +278,7 @@
*/
protected List pluginArtifacts;
+
/**
* A ServerConnector to use.
@@ -558,6 +565,11 @@
getLog().info("Started Jetty Server");
+ if ( dumpOnStart )
+ {
+ getLog().info(this.server.dump());
+ }
+
// start the scanner thread (if necessary) on the main webapp
configureScanner ();
startScanner();
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyStopMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyStopMojo.java
index 9985446..2270d7a 100644
--- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyStopMojo.java
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyStopMojo.java
@@ -18,6 +18,8 @@
package org.eclipse.jetty.maven.plugin;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.InetAddress;
@@ -54,6 +56,13 @@
* @required
*/
protected String stopKey;
+
+ /**
+ * Max time in seconds that the plugin will wait for confirmation that jetty has stopped.
+ * @parameter
+ */
+ protected int stopWait;
+
public void execute() throws MojoExecutionException, MojoFailureException
{
@@ -66,10 +75,29 @@
{
Socket s=new Socket(InetAddress.getByName("127.0.0.1"),stopPort);
s.setSoLinger(false, 0);
-
+
OutputStream out=s.getOutputStream();
out.write((stopKey+"\r\nstop\r\n").getBytes());
out.flush();
+
+ if (stopWait > 0)
+ {
+ s.setSoTimeout(stopWait * 1000);
+ s.getInputStream();
+
+ System.err.printf("Waiting %d seconds for jetty to stop%n",stopWait);
+ LineNumberReader lin = new LineNumberReader(new InputStreamReader(s.getInputStream()));
+ String response;
+ boolean stopped = false;
+ while (!stopped && ((response = lin.readLine()) != null))
+ {
+ if ("Stopped".equals(response))
+ {
+ stopped = true;
+ System.err.println("Server reports itself as Stopped");
+ }
+ }
+ }
s.close();
}
catch (ConnectException e)
diff --git a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionIdManager.java b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionIdManager.java
index 1c0bae8..e25c80f 100644
--- a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionIdManager.java
+++ b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionIdManager.java
@@ -450,6 +450,7 @@
/**
* is the session id known to mongo, and is it valid
*/
+ @Override
public boolean idInUse(String sessionId)
{
/*
@@ -473,6 +474,7 @@
}
/* ------------------------------------------------------------ */
+ @Override
public void addSession(HttpSession session)
{
if (session == null)
@@ -494,6 +496,7 @@
}
/* ------------------------------------------------------------ */
+ @Override
public void removeSession(HttpSession session)
{
if (session == null)
@@ -508,6 +511,7 @@
}
/* ------------------------------------------------------------ */
+ @Override
public void invalidateAll(String sessionId)
{
synchronized (_sessionsIds)
@@ -520,7 +524,7 @@
Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
for (int i=0; contexts!=null && i<contexts.length; i++)
{
- SessionHandler sessionHandler = (SessionHandler)((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
+ SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
if (sessionHandler != null)
{
SessionManager manager = sessionHandler.getSessionManager();
@@ -532,26 +536,7 @@
}
}
}
- }
-
- /* ------------------------------------------------------------ */
- // TODO not sure if this is correct
- public String getClusterId(String nodeId)
- {
- int dot=nodeId.lastIndexOf('.');
- return (dot>0)?nodeId.substring(0,dot):nodeId;
- }
-
- /* ------------------------------------------------------------ */
- // TODO not sure if this is correct
- public String getNodeId(String clusterId, HttpServletRequest request)
- {
- if (_workerName!=null)
- return clusterId+'.'+_workerName;
-
- return clusterId;
- }
-
+ }
/* ------------------------------------------------------------ */
@Override
@@ -569,7 +554,7 @@
Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
for (int i=0; contexts!=null && i<contexts.length; i++)
{
- SessionHandler sessionHandler = (SessionHandler)((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
+ SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
if (sessionHandler != null)
{
SessionManager manager = sessionHandler.getSessionManager();
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/servlet/ServletHandler.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/servlet/ServletHandler.java
deleted file mode 100644
index ab2e127..0000000
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/servlet/ServletHandler.java
+++ /dev/null
@@ -1,76 +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.plus.servlet;
-
-import org.eclipse.jetty.plus.annotation.InjectionCollection;
-import org.eclipse.jetty.plus.annotation.LifeCycleCallbackCollection;
-
-/**
- * ServletHandler
- *
- *
- */
-public class ServletHandler extends org.eclipse.jetty.servlet.ServletHandler
-{
-
- private InjectionCollection _injections = null;
- private LifeCycleCallbackCollection _callbacks = null;
-
-
-
- /**
- * @return the callbacks
- */
- public LifeCycleCallbackCollection getCallbacks()
- {
- return _callbacks;
- }
-
-
-
- /**
- * @param callbacks the callbacks to set
- */
- public void setCallbacks(LifeCycleCallbackCollection callbacks)
- {
- this._callbacks = callbacks;
- }
-
-
-
- /**
- * @return the injections
- */
- public InjectionCollection getInjections()
- {
- return _injections;
- }
-
-
-
- /**
- * @param injections the injections to set
- */
- public void setInjections(InjectionCollection injections)
- {
- this._injections = injections;
- }
-
-
-}
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/servlet/package-info.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/servlet/package-info.java
deleted file mode 100644
index f83c607..0000000
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/servlet/package-info.java
+++ /dev/null
@@ -1,23 +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.
-// ========================================================================
-//
-
-/**
- * Jetty Plus : Servlet Handler for Limited Additional JEE support
- */
-package org.eclipse.jetty.plus.servlet;
-
diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/EnvConfiguration.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/EnvConfiguration.java
index 660231d..26227f1 100644
--- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/EnvConfiguration.java
+++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/EnvConfiguration.java
@@ -31,6 +31,7 @@
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;
+import org.eclipse.jetty.jndi.ContextFactory;
import org.eclipse.jetty.jndi.NamingContext;
import org.eclipse.jetty.jndi.NamingUtil;
import org.eclipse.jetty.jndi.local.localContextRoot;
@@ -147,6 +148,7 @@
//get rid of any bindings for comp/env for webapp
ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(context.getClassLoader());
+ ContextFactory.associateClassLoader(context.getClassLoader());
try
{
Context ic = new InitialContext();
@@ -170,6 +172,7 @@
}
finally
{
+ ContextFactory.disassociateClassLoader();
Thread.currentThread().setContextClassLoader(oldLoader);
}
}
@@ -251,6 +254,7 @@
{
ClassLoader old_loader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(wac.getClassLoader());
+ ContextFactory.associateClassLoader(wac.getClassLoader());
try
{
Context context = new InitialContext();
@@ -259,8 +263,9 @@
}
finally
{
- Thread.currentThread().setContextClassLoader(old_loader);
- }
+ ContextFactory.disassociateClassLoader();
+ Thread.currentThread().setContextClassLoader(old_loader);
+ }
}
private static class Bound
diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/BalancerServletTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/BalancerServletTest.java
index 6c3cc5c..ee3c4c6 100644
--- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/BalancerServletTest.java
+++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/BalancerServletTest.java
@@ -35,6 +35,7 @@
import org.eclipse.jetty.server.NetworkConnector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.session.AbstractSessionIdManager;
import org.eclipse.jetty.server.session.HashSessionIdManager;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
@@ -99,7 +100,7 @@
if (nodeName != null)
{
- HashSessionIdManager sessionIdManager = new HashSessionIdManager();
+ AbstractSessionIdManager sessionIdManager = new HashSessionIdManager();
sessionIdManager.setWorkerName(nodeName);
server.setSessionIdManager(sessionIdManager);
}
diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyTunnellingTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyTunnellingTest.java
index 9db2204..e4de3f3 100644
--- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyTunnellingTest.java
+++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyTunnellingTest.java
@@ -20,11 +20,13 @@
import java.io.IOException;
import java.net.ConnectException;
+import java.net.Socket;
import java.net.URLEncoder;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
+
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
@@ -55,6 +57,8 @@
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
@@ -116,14 +120,20 @@
protected void stopServer() throws Exception
{
- server.stop();
- server.join();
+ if (server != null)
+ {
+ server.stop();
+ server.join();
+ }
}
protected void stopProxy() throws Exception
{
- proxy.stop();
- proxy.join();
+ if (proxy != null)
+ {
+ proxy.stop();
+ proxy.join();
+ }
}
@Test
@@ -364,6 +374,43 @@
}
}
+ @Test
+ @Ignore // to delicate to rely on external proxy.
+ public void testExternalProxy() throws Exception
+ {
+ // Free proxy server obtained from http://hidemyass.com/proxy-list/
+ String proxyHost = "81.208.25.53";
+ int proxyPort = 3128;
+ try
+ {
+ new Socket(proxyHost, proxyPort).close();
+ }
+ catch (IOException x)
+ {
+ Assume.assumeNoException(x);
+ }
+
+ SslContextFactory sslContextFactory = new SslContextFactory();
+ sslContextFactory.start();
+
+ HttpClient httpClient = new HttpClient(sslContextFactory);
+ httpClient.setProxyConfiguration(new ProxyConfiguration(proxyHost, proxyPort));
+ httpClient.start();
+
+ try
+ {
+ ContentResponse response = httpClient.newRequest("https://www.google.com")
+ // Use a longer timeout, sometimes the proxy takes a while to answer
+ .timeout(20, TimeUnit.SECONDS)
+ .send();
+ assertEquals(HttpStatus.OK_200, response.getStatus());
+ }
+ finally
+ {
+ httpClient.stop();
+ }
+ }
+
private static class ServerHandler extends AbstractHandler
{
public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewritePatternRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewritePatternRule.java
index 48047b8..d6bd65d 100644
--- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewritePatternRule.java
+++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewritePatternRule.java
@@ -19,20 +19,21 @@
package org.eclipse.jetty.rewrite.handler;
import java.io.IOException;
-
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.PathMap;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.URIUtil;
/**
- * Rewrite the URI by replacing the matched {@link PathMap} path with a fixed string.
+ * Rewrite the URI by replacing the matched {@link PathMap} path with a fixed string.
*/
public class RewritePatternRule extends PatternRule implements Rule.ApplyURI
{
private String _replacement;
+ private String _query;
/* ------------------------------------------------------------ */
public RewritePatternRule()
@@ -44,32 +45,63 @@
/* ------------------------------------------------------------ */
/**
* Whenever a match is found, it replaces with this value.
- *
- * @param value the replacement string.
+ *
+ * @param replacement the replacement string.
*/
- public void setReplacement(String value)
+ public void setReplacement(String replacement)
{
- _replacement = value;
+ String[] split = replacement.split("\\?", 2);
+ _replacement = split[0];
+ _query = split.length == 2 ? split[1] : null;
}
/* ------------------------------------------------------------ */
/*
* (non-Javadoc)
- * @see org.eclipse.jetty.server.handler.rules.RuleBase#apply(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+ *
+ * @see org.eclipse.jetty.server.handler.rules.RuleBase#apply(javax.servlet.http.HttpServletRequest,
+ * javax.servlet.http.HttpServletResponse)
*/
@Override
public String apply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException
{
- target = URIUtil.addPaths(_replacement, PathMap.pathInfo(_pattern,target));
+ target = URIUtil.addPaths(_replacement, PathMap.pathInfo(_pattern, target));
return target;
}
/* ------------------------------------------------------------ */
+
+ /**
+ * This method will add _query to the requests's queryString and also combine it with existing queryStrings in
+ * the request. However it won't take care for duplicate. E.g. if request.getQueryString contains a parameter
+ * "param1 = true" and _query will contain "param1=false" the result will be param1=true¶m1=false.
+ * To cover this use case some more complex pattern matching is necessary. We can implement this if there's use
+ * cases.
+ *
+ * @param request
+ * @param oldTarget
+ * @param newTarget
+ * @throws IOException
+ */
@Override
- public void applyURI(Request request, String oldTarget, String newTarget) throws IOException
+ public void applyURI(Request request, String oldTarget, String newTarget) throws IOException
{
- String uri = URIUtil.addPaths(_replacement, PathMap.pathInfo(_pattern,request.getRequestURI()));
- request.setRequestURI(uri);
+ if (_query == null)
+ {
+ request.setRequestURI(newTarget);
+ }
+ else
+ {
+ String queryString = request.getQueryString();
+ if (queryString != null)
+ queryString = queryString + "&" + _query;
+ else
+ queryString = _query;
+ HttpURI uri = new HttpURI(newTarget + "?" + queryString);
+ request.setUri(uri);
+ request.setRequestURI(newTarget);
+ request.setQueryString(queryString);
+ }
}
/* ------------------------------------------------------------ */
diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewritePatternRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewritePatternRuleTest.java
index 51ed119..20d52fb 100644
--- a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewritePatternRuleTest.java
+++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewritePatternRuleTest.java
@@ -18,23 +18,25 @@
package org.eclipse.jetty.rewrite.handler;
-import static org.junit.Assert.assertEquals;
-
import java.io.IOException;
+import org.eclipse.jetty.http.HttpURI;
import org.junit.Before;
import org.junit.Test;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
public class RewritePatternRuleTest extends AbstractRuleTestCase
{
- private String[][] _tests=
- {
- {"/foo/bar","/","/replace"},
- {"/foo/bar","/*","/replace/foo/bar"},
- {"/foo/bar","/foo/*","/replace/bar"},
- {"/foo/bar","/foo/bar","/replace"},
- {"/foo/bar.txt","*.txt","/replace"},
- };
+ private String[][] _tests =
+ {
+ {"/foo/bar", "/", "/replace"},
+ {"/foo/bar", "/*", "/replace/foo/bar"},
+ {"/foo/bar", "/foo/*", "/replace/bar"},
+ {"/foo/bar", "/foo/bar", "/replace"},
+ {"/foo/bar.txt", "*.txt", "/replace"},
+ };
private RewritePatternRule _rule;
@Before
@@ -46,13 +48,82 @@
}
@Test
- public void testRequestUriEnabled() throws IOException
+ public void testMatchAndApplyAndApplyURI() throws IOException
{
for (String[] test : _tests)
{
_rule.setPattern(test[1]);
String result = _rule.matchAndApply(test[0], _request, _response);
- assertEquals(test[1], test[2], result);
+ assertThat(test[1], test[2], is(result));
+
+ _rule.applyURI(_request, null, result);
+ assertThat(_request.getRequestURI(), is(test[2]));
}
}
+
+ @Test
+ public void testReplacementWithQueryString() throws IOException
+ {
+ String replacement = "/replace?given=param";
+ String[] split = replacement.split("\\?", 2);
+ String path = split[0];
+ String queryString = split[1];
+
+ RewritePatternRule rewritePatternRule = new RewritePatternRule();
+ rewritePatternRule.setPattern("/old/context");
+ rewritePatternRule.setReplacement(replacement);
+
+ String result = rewritePatternRule.matchAndApply("/old/context", _request, _response);
+ assertThat(result, is(path));
+
+ rewritePatternRule.applyURI(_request, null, result);
+ assertThat("queryString matches expected", _request.getQueryString(), is(queryString));
+ assertThat("request URI matches expected", _request.getRequestURI(), is(path));
+
+ }
+
+ @Test
+ public void testRequestWithQueryString() throws IOException
+ {
+ String replacement = "/replace";
+ String queryString = "request=parameter";
+ _request.setUri(new HttpURI("/old/context"));
+ _request.setQueryString(queryString);
+
+ RewritePatternRule rewritePatternRule = new RewritePatternRule();
+ rewritePatternRule.setPattern("/old/context");
+ rewritePatternRule.setReplacement(replacement);
+
+ String result = rewritePatternRule.matchAndApply("/old/context", _request, _response);
+ assertThat("result matches expected", result, is(replacement));
+
+ rewritePatternRule.applyURI(_request, null, result);
+ assertThat("queryString matches expected", _request.getQueryString(), is(queryString));
+ assertThat("request URI matches expected", _request.getRequestURI(), is(replacement));
+ }
+
+ @Test
+ public void testRequestAndReplacementWithQueryString() throws IOException
+ {
+ String requestQueryString = "request=parameter";
+ String replacement = "/replace?given=param";
+ String[] split = replacement.split("\\?", 2);
+ String path = split[0];
+ String queryString = split[1];
+ _request.setUri(new HttpURI("/old/context"));
+ _request.setQueryString(requestQueryString);
+
+ RewritePatternRule rewritePatternRule = new RewritePatternRule();
+ rewritePatternRule.setPattern("/old/context");
+ rewritePatternRule.setReplacement(replacement);
+
+ String result = rewritePatternRule.matchAndApply("/old/context", _request, _response);
+ assertThat(result, is(path));
+
+ rewritePatternRule.applyURI(_request, null, result);
+ assertThat("queryString matches expected", _request.getQueryString(),
+ is(requestQueryString + "&" + queryString));
+ assertThat("request URI matches expected", _request.getRequestURI(), is(path));
+ }
+
}
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultUserIdentity.java b/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultUserIdentity.java
index 28b0cb3..dd12b1d 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultUserIdentity.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultUserIdentity.java
@@ -54,13 +54,19 @@
}
public boolean isUserInRole(String role, Scope scope)
- {
+ {
if (scope!=null && scope.getRoleRefMap()!=null)
- role=scope.getRoleRefMap().get(role);
-
+ {
+ String mappedRole = scope.getRoleRefMap().get(role);
+ if (mappedRole != null)
+ role = mappedRole;
+ }
+
for (String r :_roles)
+ {
if (r.equals(role))
return true;
+ }
return false;
}
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/MappedLoginService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/MappedLoginService.java
index 41648d6..37c72c3 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/MappedLoginService.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/MappedLoginService.java
@@ -212,6 +212,9 @@
*/
public UserIdentity login(String username, Object credentials)
{
+ if (username == null)
+ return null;
+
UserIdentity user = _users.get(username);
if (user==null)
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DeferredAuthentication.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DeferredAuthentication.java
index d47a65d..b1a2c60 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DeferredAuthentication.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DeferredAuthentication.java
@@ -116,6 +116,9 @@
@Override
public Authentication login(String username, Object password, ServletRequest request)
{
+ if (username == null)
+ return null;
+
UserIdentity identity = _authenticator.login(username, password, request);
if (identity != null)
{
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNCSARequestLog.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNCSARequestLog.java
index 5223e97..d00f67c 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNCSARequestLog.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNCSARequestLog.java
@@ -25,6 +25,7 @@
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.server.handler.StatisticsHandler;
import org.eclipse.jetty.util.DateCache;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
@@ -59,7 +60,6 @@
private boolean _logLatency = false;
private boolean _logCookies = false;
private boolean _logServer = false;
- private boolean _logDispatch = false;
private boolean _preferProxiedForAddress;
private transient DateCache _logDateCache;
private String _logDateFormat = "dd/MMM/yyyy:HH:mm:ss Z";
@@ -193,17 +193,10 @@
}
}
- if (_logDispatch || _logLatency)
+ if (_logLatency)
{
long now = System.currentTimeMillis();
- if (_logDispatch)
- {
- long d = request.getDispatchTime();
- buf.append(' ');
- buf.append(now - (d==0 ? request.getTimeStamp():d));
- }
-
if (_logLatency)
{
buf.append(' ');
@@ -340,24 +333,18 @@
}
/**
- * Controls logging of the request dispatch time
- *
- * @param value true - request dispatch time will be logged
- * false - request dispatch time will not be logged
+ * @deprecated use {@link StatisticsHandler}
*/
public void setLogDispatch(boolean value)
{
- _logDispatch = value;
}
/**
- * Retrieve request dispatch time logging flag
- *
- * @return value of the flag
+ * @deprecated use {@link StatisticsHandler}
*/
public boolean isLogDispatch()
{
- return _logDispatch;
+ return false;
}
/**
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
index 1688ed6..876e7ae 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextState.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextState.java
@@ -38,7 +38,7 @@
_state=state;
}
- private HttpChannelState state()
+ HttpChannelState state()
{
HttpChannelState state=_state;
if (state==null)
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 e9f61ac..7d54230 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
@@ -23,6 +23,7 @@
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+
import javax.servlet.DispatcherType;
import javax.servlet.RequestDispatcher;
@@ -330,16 +331,11 @@
}
finally
{
- next=Next.RECYCLE;
+ _request.setHandled(true);
+ _transport.completed();
}
}
- if (next==Next.RECYCLE)
- {
- _request.setHandled(true);
- _transport.completed();
- }
-
LOG.debug("{} handle exit, result {}", this, next);
return next!=Next.WAIT;
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 d070aee..127bafa 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
@@ -71,13 +71,12 @@
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
+ COMPLETE // Complete the channel
}
private final HttpChannel<?> _channel;
@@ -190,12 +189,12 @@
case COMPLETING:
return Next.COMPLETE;
-
+
case ASYNCWAIT:
return Next.WAIT;
-
+
case COMPLETED:
- return Next.RECYCLE;
+ return Next.WAIT;
case REDISPATCH:
_state=State.REDISPATCHED;
@@ -325,7 +324,7 @@
_event.setDispatchTarget(context,path);
_dispatched=true;
break;
-
+
default:
throw new IllegalStateException(this.getStatusString());
}
@@ -393,7 +392,7 @@
break;
}
}
-
+
scheduleDispatch();
}
@@ -528,7 +527,7 @@
return _expired;
}
}
-
+
public boolean isInitial()
{
synchronized(this)
@@ -563,13 +562,23 @@
}
}
+ boolean isCompleted()
+ {
+ synchronized (this)
+ {
+ return _state == State.COMPLETED;
+ }
+ }
+
public boolean isAsyncStarted()
{
synchronized (this)
{
switch(_state)
{
- case ASYNCSTARTED:
+ case ASYNCSTARTED: // Suspend called, but not yet returned to container
+ case REDISPATCHING: // resumed while dispatched
+ case COMPLETECALLED: // complete called
case ASYNCWAIT:
return true;
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConfiguration.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConfiguration.java
index e6ed10b..2feb032 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConfiguration.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConfiguration.java
@@ -22,6 +22,7 @@
import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
@@ -40,6 +41,8 @@
@ManagedObject("HTTP Configuration")
public class HttpConfiguration
{
+ public static final String SERVER_VERSION = "Jetty(" + Jetty.VERSION + ")";
+
private List<Customizer> _customizers=new CopyOnWriteArrayList<>();
private int _outputBufferSize=32*1024;
private int _requestHeaderSize=8*1024;
@@ -48,8 +51,8 @@
private int _securePort;
private String _secureScheme = HttpScheme.HTTPS.asString();
private boolean _sendServerVersion = true; //send Server: header
+ private boolean _sendXPoweredBy = false; //send X-Powered-By: header
private boolean _sendDateHeader = false; //send Date: header
-
public interface Customizer
{
@@ -150,11 +153,22 @@
_sendServerVersion = sendServerVersion;
}
- @ManagedAttribute("if true, include the server version in HTTP headers")
+ @ManagedAttribute("if true, send the Server header in responses")
public boolean getSendServerVersion()
{
return _sendServerVersion;
}
+
+ public void setSendXPoweredBy (boolean sendXPoweredBy)
+ {
+ _sendXPoweredBy=sendXPoweredBy;
+ }
+
+ @ManagedAttribute("if true, send the X-Powered-By header in responses")
+ public boolean getSendXPoweredBy()
+ {
+ return _sendXPoweredBy;
+ }
public void setSendDateHeader(boolean sendDateHeader)
{
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 616dc0d..d56fdd3 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
@@ -91,8 +91,7 @@
_config = config;
_connector = connector;
_bufferPool = _connector.getByteBufferPool();
- _generator = new HttpGenerator();
- _generator.setSendServerVersion(_config.getSendServerVersion());
+ _generator = new HttpGenerator(_config.getSendServerVersion(),_config.getSendXPoweredBy());
_channel = new HttpChannelOverHttp(connector, config, endPoint, this, new Input());
_parser = newHttpParser();
@@ -133,14 +132,20 @@
_parser.reset();
// close to seek EOF
_parser.close();
+ if (getEndPoint().isOpen())
+ fillInterested();
}
// else if we are persistent
else if (_generator.isPersistent())
// reset to seek next request
_parser.reset();
else
+ {
// else seek EOF
_parser.close();
+ if (getEndPoint().isOpen())
+ fillInterested();
+ }
_generator.reset();
_channel.reset();
@@ -235,7 +240,7 @@
int filled = getEndPoint().fill(_requestBuffer);
if (filled==0) // Do a retry on fill 0 (optimisation for SSL connections)
filled = getEndPoint().fill(_requestBuffer);
-
+
LOG.debug("{} filled {}", this, filled);
// If we failed to fill
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 79f442f..147eca4 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
@@ -373,7 +373,8 @@
/* ------------------------------------------------------------ */
/** Asynchronous send of content.
- * @param in The content to send
+ * @param in The content to send as a stream. The stream will be closed
+ * after reading all content.
* @param callback The callback to use to notify success or failure
*/
public void sendContent(InputStream in, Callback callback)
@@ -383,7 +384,8 @@
/* ------------------------------------------------------------ */
/** Asynchronous send of content.
- * @param in The content to send
+ * @param in The content to send as a channel. The channel will be closed
+ * after reading all content.
* @param callback The callback to use to notify success or failure
*/
public void sendContent(ReadableByteChannel in, Callback callback)
@@ -472,28 +474,38 @@
@Override
protected boolean process() throws Exception
{
- int len=_in.read(_buffer.array(),0,_buffer.capacity());
- if (len==-1)
- {
- closed();
- _channel.getByteBufferPool().release(_buffer);
- return true;
- }
boolean eof=false;
-
- // if we read less than a buffer, are we at EOF?
- if (len<_buffer.capacity())
+ int len=_in.read(_buffer.array(),0,_buffer.capacity());
+
+ if (len<0)
{
+ eof=true;
+ len=0;
+ _in.close();
+ }
+ else if (len<_buffer.capacity())
+ {
+ // read ahead for EOF to try for single commit
int len2=_in.read(_buffer.array(),len,_buffer.capacity()-len);
if (len2<0)
eof=true;
else
len+=len2;
}
-
+
+ // write what we have
_buffer.position(0);
_buffer.limit(len);
_channel.write(_buffer,eof,this);
+
+ // Handle EOF
+ if (eof)
+ {
+ closed();
+ _channel.getByteBufferPool().release(_buffer);
+ return true;
+ }
+
return false;
}
@@ -502,6 +514,14 @@
{
super.failed(x);
_channel.getByteBufferPool().release(_buffer);
+ try
+ {
+ _in.close();
+ }
+ catch (IOException e)
+ {
+ LOG.ignore(e);
+ }
}
}
@@ -531,30 +551,38 @@
protected boolean process() throws Exception
{
_buffer.clear();
- int len=_in.read(_buffer);
- if (len==-1)
- {
- closed();
- _channel.getByteBufferPool().release(_buffer);
- return true;
- }
-
boolean eof=false;
-
- // if we read less than a buffer, are we at EOF?
- if (len<_buffer.capacity())
+ int len=_in.read(_buffer);
+
+ if (len<0)
{
+ eof=true;
+ len=0;
+ _in.close();
+ }
+ else if (len<_buffer.capacity())
+ {
+ // read ahead for EOF to try for single commit
int len2=_in.read(_buffer);
if (len2<0)
eof=true;
else
len+=len2;
}
-
+
+ // write what we have
_buffer.flip();
_channel.write(_buffer,eof,this);
+
+ // Handle EOF
+ if (eof)
+ {
+ closed();
+ _channel.getByteBufferPool().release(_buffer);
+ return true;
+ }
+
return false;
-
}
@Override
@@ -562,6 +590,14 @@
{
super.failed(x);
_channel.getByteBufferPool().release(_buffer);
+ try
+ {
+ _in.close();
+ }
+ catch (IOException e)
+ {
+ LOG.ignore(e);
+ }
}
}
}
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 267a199..c6db08c 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
@@ -28,6 +28,7 @@
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
+import java.nio.charset.UnsupportedCharsetException;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collection;
@@ -38,7 +39,6 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
-
import javax.servlet.AsyncContext;
import javax.servlet.AsyncListener;
import javax.servlet.DispatcherType;
@@ -63,7 +63,6 @@
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;
@@ -203,7 +202,6 @@
private HttpSession _session;
private SessionManager _sessionManager;
private long _timeStamp;
- private long _dispatchTime;
private HttpURI _uri;
private MultiPartInputStreamParser _multiPartInputStream; //if the request is a multi-part mime
private AsyncContextState _async;
@@ -1395,16 +1393,6 @@
return null;
}
- /* ------------------------------------------------------------ */
- /**
- * Get timestamp of the request dispatch
- *
- * @return timestamp
- */
- public long getDispatchTime()
- {
- return _dispatchTime;
- }
/* ------------------------------------------------------------ */
public boolean isHandled()
@@ -1714,7 +1702,16 @@
// check encoding is supported
if (!StringUtil.isUTF8(encoding))
- Charset.forName(encoding);
+ {
+ try
+ {
+ Charset.forName(encoding);
+ }
+ catch (UnsupportedCharsetException e)
+ {
+ throw new UnsupportedEncodingException(e.getMessage());
+ }
+ }
}
/* ------------------------------------------------------------ */
@@ -1997,18 +1994,6 @@
}
/* ------------------------------------------------------------ */
- /**
- * Set timetstamp of request dispatch
- *
- * @param value
- * timestamp
- */
- public void setDispatchTime(long value)
- {
- _dispatchTime = value;
- }
-
- /* ------------------------------------------------------------ */
@Override
public AsyncContext startAsync() throws IllegalStateException
{
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 cc377f0..31a67de 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
@@ -281,8 +281,8 @@
ShutdownMonitor.getInstance().start(); // initialize
- LOG.info("jetty-"+getVersion());
- HttpGenerator.setServerVersion(getVersion());
+ LOG.info("jetty-" + getVersion());
+ HttpGenerator.setJettyVersion(HttpConfiguration.SERVER_VERSION);
MultiException mex=new MultiException();
try
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java
index eadb2f4..8e815d9 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java
@@ -98,6 +98,23 @@
{
this(server,null,null,null,0,0,new HttpConnectionFactory());
}
+
+ /* ------------------------------------------------------------ */
+ /** HTTP Server Connection.
+ * <p>Construct a ServerConnector with a private instance of {@link HttpConnectionFactory} as the only factory.</p>
+ * @param server The {@link Server} this connector will accept connection for.
+ * @param acceptors
+ * the number of acceptor threads to use, or 0 for a default value. Acceptors accept new TCP/IP connections.
+ * @param selectors
+ * the number of selector threads, or 0 for a default value. Selectors notice and schedule established connection that can make IO progress.
+ */
+ public ServerConnector(
+ @Name("server") Server server,
+ @Name("acceptors") int acceptors,
+ @Name("selectors") int selectors)
+ {
+ this(server,null,null,null,acceptors,selectors,new HttpConnectionFactory());
+ }
/* ------------------------------------------------------------ */
/** Generic Server Connection with default configuration.
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java
index e789d38..ded5bdc 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java
@@ -102,6 +102,13 @@
// Graceful Shutdown
debug("Issuing graceful shutdown..");
ShutdownThread.getInstance().run();
+
+ //Stop accepting any more
+ close(serverSocket);
+ serverSocket = null;
+
+ //Shutdown input from client
+ shutdownInput(socket);
// Reply to client
debug("Informing client that we are stopped.");
@@ -109,11 +116,10 @@
out.flush();
// Shutdown Monitor
- debug("Shutting down monitor");
+ socket.shutdownOutput();
close(socket);
- socket = null;
- close(serverSocket);
- serverSocket = null;
+ socket = null;
+ debug("Shutting down monitor");
if (exitVm)
{
@@ -248,7 +254,7 @@
}
catch (IOException ignore)
{
- /* ignore */
+ debug(ignore);
}
}
@@ -265,10 +271,27 @@
}
catch (IOException ignore)
{
- /* ignore */
+ debug(ignore);
}
}
+
+ private void shutdownInput(Socket socket)
+ {
+ if (socket == null)
+ return;
+
+ try
+ {
+ socket.shutdownInput();
+ }
+ catch (IOException ignore)
+ {
+ debug(ignore);
+ }
+ }
+
+
private void debug(String format, Object... args)
{
if (DEBUG)
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 f1f2d6f..94c2be1 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
@@ -101,8 +101,8 @@
@ManagedObject("URI Context")
public class ContextHandler extends ScopedHandler implements Attributes, Graceful
{
- public static int SERVLET_MAJOR_VERSION=3;
- public static int SERVLET_MINOR_VERSION=0;
+ public final static int SERVLET_MAJOR_VERSION=3;
+ public final static int SERVLET_MINOR_VERSION=0;
final private static String __unimplmented="Unimplemented - use org.eclipse.jetty.servlet.ServletContextHandler";
@@ -884,8 +884,10 @@
String vhost = normalizeHostname(baseRequest.getServerName());
boolean match = false;
+ boolean connectorName = false;
+ boolean connectorMatch = false;
- loop: for (String contextVhost:_vhosts)
+ for (String contextVhost:_vhosts)
{
if (contextVhost == null || contextVhost.length()==0)
continue;
@@ -895,21 +897,21 @@
case '*':
if (contextVhost.startsWith("*."))
// wildcard only at the beginning, and only for one additional subdomain level
- match = contextVhost.regionMatches(true,2,vhost,vhost.indexOf(".") + 1,contextVhost.length() - 2);
+ match = match || contextVhost.regionMatches(true,2,vhost,vhost.indexOf(".") + 1,contextVhost.length() - 2);
break;
case '@':
+ connectorName=true;
String name=baseRequest.getHttpChannel().getConnector().getName();
- match=name!=null && contextVhost.length()==name.length()+1 && contextVhost.endsWith(name);
+ boolean m=name!=null && contextVhost.length()==name.length()+1 && contextVhost.endsWith(name);
+ match = match || m;
+ connectorMatch = connectorMatch || m;
break;
default:
- match = contextVhost.equalsIgnoreCase(vhost);
+ match = match || contextVhost.equalsIgnoreCase(vhost);
}
- if (match)
- break loop;
-
}
- if (!match)
+ if (!match || connectorName && !connectorMatch)
return false;
}
@@ -1287,8 +1289,26 @@
*/
public void setContextPath(String contextPath)
{
- if (contextPath != null && contextPath.length() > 1 && contextPath.endsWith("/"))
- throw new IllegalArgumentException("ends with /");
+ if (contextPath == null)
+ throw new IllegalArgumentException("null contextPath");
+
+ if (contextPath.endsWith("/*"))
+ {
+ LOG.warn(this+" contextPath ends with /*");
+ contextPath=contextPath.substring(0,contextPath.length()-2);
+ }
+ else if (contextPath.endsWith("/"))
+ {
+ LOG.warn(this+" contextPath ends with /");
+ contextPath=contextPath.substring(0,contextPath.length()-1);
+ }
+
+ if (contextPath.length()==0)
+ {
+ LOG.warn("Empty contextPath");
+ contextPath="/";
+ }
+
_contextPath = contextPath;
if (getServer() != null && (getServer().isStarting() || getServer().isStarted()))
@@ -2576,7 +2596,7 @@
if (dot<0)
return false;
String suffix=path.substring(dot);
- return resource.getAlias().toString().endsWith(suffix);
+ return resource.toString().endsWith(suffix);
}
}
@@ -2592,10 +2612,10 @@
public boolean check(String path, Resource resource)
{
int slash = path.lastIndexOf('/');
- if (slash<0)
+ if (slash<0 || slash==path.length()-1)
return false;
String suffix=path.substring(slash);
- return resource.getAlias().toString().endsWith(suffix);
+ return resource.toString().endsWith(suffix);
}
}
/* ------------------------------------------------------------ */
@@ -2609,7 +2629,7 @@
public boolean check(String path, Resource resource)
{
int slash = path.lastIndexOf('/');
- if (slash<0)
+ if (slash<0 || resource.exists())
return false;
String suffix=path.substring(slash);
return resource.getAlias().toString().endsWith(suffix);
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java
index b43ce52..6bed059 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java
@@ -19,19 +19,19 @@
package org.eclipse.jetty.server.handler;
import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.Arrays;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import org.eclipse.jetty.http.PathMap;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HandlerContainer;
import org.eclipse.jetty.server.HttpChannelState;
import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.ArrayTernaryTrie;
+import org.eclipse.jetty.util.ArrayUtil;
+import org.eclipse.jetty.util.Trie;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.annotation.ManagedOperation;
import org.eclipse.jetty.util.log.Log;
@@ -53,7 +53,7 @@
{
private static final Logger LOG = Log.getLogger(ContextHandlerCollection.class);
- private volatile PathMap<Object> _contextMap;
+ private volatile Trie<ContextHandler[]> _contexts;
private Class<? extends ContextHandler> _contextClass = ContextHandler.class;
/* ------------------------------------------------------------ */
@@ -70,90 +70,71 @@
@ManagedOperation("update the mapping of context path to context")
public void mapContexts()
{
- PathMap<Object> contextMap = new PathMap<Object>();
- Handler[] branches = getHandlers();
-
-
- for (int b=0;branches!=null && b<branches.length;b++)
+ int capacity=512;
+
+ // Loop until we have a big enough trie to hold all the context paths
+ Trie<ContextHandler[]> trie;
+ loop: while(true)
{
- Handler[] handlers=null;
+ trie=new ArrayTernaryTrie<>(false,capacity);
- if (branches[b] instanceof ContextHandler)
+ Handler[] branches = getHandlers();
+
+ // loop over each group of contexts
+ for (int b=0;branches!=null && b<branches.length;b++)
{
- handlers = new Handler[]{ branches[b] };
- }
- else if (branches[b] instanceof HandlerContainer)
- {
- handlers = ((HandlerContainer)branches[b]).getChildHandlersByClass(ContextHandler.class);
- }
- else
- continue;
+ Handler[] handlers=null;
- for (int i=0;i<handlers.length;i++)
- {
- ContextHandler handler=(ContextHandler)handlers[i];
-
- String contextPath=handler.getContextPath();
-
- if (contextPath==null || contextPath.indexOf(',')>=0 || contextPath.startsWith("*"))
- throw new IllegalArgumentException ("Illegal context spec:"+contextPath);
-
- if(!contextPath.startsWith("/"))
- contextPath='/'+contextPath;
-
- if (contextPath.length()>1)
+ if (branches[b] instanceof ContextHandler)
{
- if (contextPath.endsWith("/"))
- contextPath+="*";
- else if (!contextPath.endsWith("/*"))
- contextPath+="/*";
+ handlers = new Handler[]{ branches[b] };
}
-
- Object contexts=contextMap.get(contextPath);
- String[] vhosts=handler.getVirtualHosts();
-
-
- if (vhosts!=null && vhosts.length>0)
+ else if (branches[b] instanceof HandlerContainer)
{
- Map<String, Object> hosts;
-
- if (contexts instanceof Map)
- hosts=(Map<String, Object>)contexts;
- else
- {
- hosts=new HashMap<String, Object>();
- hosts.put("*",contexts);
- contextMap.put(contextPath, hosts);
- }
-
- for (int j=0;j<vhosts.length;j++)
- {
- String vhost=vhosts[j];
- contexts=hosts.get(vhost);
- contexts=LazyList.add(contexts,branches[b]);
- hosts.put(vhost,contexts);
- }
- }
- else if (contexts instanceof Map)
- {
- Map<String, Object> hosts=(Map<String, Object>)contexts;
- contexts=hosts.get("*");
- contexts= LazyList.add(contexts, branches[b]);
- hosts.put("*",contexts);
+ handlers = ((HandlerContainer)branches[b]).getChildHandlersByClass(ContextHandler.class);
}
else
+ continue;
+
+ // for each context handler in a group
+ for (int i=0;handlers!=null && i<handlers.length;i++)
{
- contexts= LazyList.add(contexts, branches[b]);
- contextMap.put(contextPath, contexts);
+ ContextHandler handler=(ContextHandler)handlers[i];
+ String contextPath=handler.getContextPath().substring(1);
+ ContextHandler[] contexts=trie.get(contextPath);
+
+ if (!trie.put(contextPath,ArrayUtil.addToArray(contexts,handler,ContextHandler.class)))
+ {
+ capacity+=512;
+ continue loop;
+ }
}
}
+
+ break;
}
- _contextMap=contextMap;
+
+ // Sort the contexts so those with virtual hosts are considered before those without
+ for (String ctx : trie.keySet())
+ {
+ ContextHandler[] contexts=trie.get(ctx);
+ ContextHandler[] sorted=new ContextHandler[contexts.length];
+ int i=0;
+ for (ContextHandler handler:contexts)
+ if (handler.getVirtualHosts()!=null && handler.getVirtualHosts().length>0)
+ sorted[i++]=handler;
+ for (ContextHandler handler:contexts)
+ if (handler.getVirtualHosts()==null || handler.getVirtualHosts().length==0)
+ sorted[i++]=handler;
+ trie.put(ctx,sorted);
+ }
+ //for (String ctx : trie.keySet())
+ // System.err.printf("'%s'->%s%n",ctx,Arrays.asList(trie.get(ctx)));
+ _contexts=trie;
}
-
/* ------------------------------------------------------------ */
/*
* @see org.eclipse.jetty.server.server.handler.HandlerCollection#setHandlers(org.eclipse.jetty.server.server.Handler[])
@@ -161,7 +142,7 @@
@Override
public void setHandlers(Handler[] handlers)
{
- _contextMap=null;
+ _contexts=null;
super.setHandlers(handlers);
if (isStarted())
mapContexts();
@@ -199,67 +180,31 @@
}
// data structure which maps a request to a context; first-best match wins
- // { context path =>
- // { virtual host => context }
+ // { context path => [ context ] }
// }
- PathMap<Object> map = _contextMap;
- if (map!=null && target!=null && target.startsWith("/"))
+ if (target.startsWith("/"))
{
- // first, get all contexts matched by context path
- Object contexts = map.getLazyMatches(target);
+ int limit = target.length()-1;
- for (int i=0; i<LazyList.size(contexts); i++)
- {
- // then, match against the virtualhost of each context
- Map.Entry entry = (Map.Entry)LazyList.get(contexts, i);
- Object list = entry.getValue();
+ while (limit>=0)
+ {
+ // Get best match
+ ContextHandler[] contexts = _contexts.getBest(target,1,limit);
+ if (contexts==null)
+ break;
- if (list instanceof Map)
- {
- Map hosts = (Map)list;
- String host = normalizeHostname(request.getServerName());
-
- // explicitly-defined virtual hosts, most specific
- list=hosts.get(host);
- for (int j=0; j<LazyList.size(list); j++)
- {
- Handler handler = (Handler)LazyList.get(list,j);
- handler.handle(target,baseRequest, request, response);
- if (baseRequest.isHandled())
- return;
- }
-
- // wildcard for one level of names
- list=hosts.get("*."+host.substring(host.indexOf(".")+1));
- for (int j=0; j<LazyList.size(list); j++)
- {
- Handler handler = (Handler)LazyList.get(list,j);
- handler.handle(target,baseRequest, request, response);
- if (baseRequest.isHandled())
- return;
- }
-
- // no virtualhosts defined for the context, least specific
- // will handle any request that does not match to a specific virtual host above
- list=hosts.get("*");
- for (int j=0; j<LazyList.size(list); j++)
- {
- Handler handler = (Handler)LazyList.get(list,j);
- handler.handle(target,baseRequest, request, response);
- if (baseRequest.isHandled())
- return;
- }
- }
- else
- {
- for (int j=0; j<LazyList.size(list); j++)
- {
- Handler handler = (Handler)LazyList.get(list,j);
- handler.handle(target,baseRequest, request, response);
- if (baseRequest.isHandled())
- return;
- }
- }
+ int l=contexts[0].getContextPath().length();
+ if (l==1 || target.length()==l || target.charAt(l)=='/')
+ {
+ for (ContextHandler handler : contexts)
+ {
+ handler.handle(target,baseRequest, request, response);
+ if (baseRequest.isHandled())
+ return;
+ }
+ }
+
+ limit=l-2;
}
}
else
@@ -320,16 +265,5 @@
_contextClass = contextClass;
}
- /* ------------------------------------------------------------ */
- private String normalizeHostname( String host )
- {
- if ( host == null )
- return null;
-
- if ( host.endsWith( "." ) )
- return host.substring( 0, host.length() -1);
-
- return host;
- }
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/RequestLogHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/RequestLogHandler.java
index 899a8bc..bad2be3 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/RequestLogHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/RequestLogHandler.java
@@ -20,15 +20,18 @@
import java.io.IOException;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
import javax.servlet.DispatcherType;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import org.eclipse.jetty.server.HttpChannelState;
+import org.eclipse.jetty.server.AsyncContextState;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.RequestLog;
import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -44,8 +47,37 @@
public class RequestLogHandler extends HandlerWrapper
{
private static final Logger LOG = Log.getLogger(RequestLogHandler.class);
-
private RequestLog _requestLog;
+ private final AsyncListener _listener = new AsyncListener()
+ {
+
+ @Override
+ public void onTimeout(AsyncEvent event) throws IOException
+ {
+
+ }
+
+ @Override
+ public void onStartAsync(AsyncEvent event) throws IOException
+ {
+ event.getAsyncContext().addListener(this);
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException
+ {
+
+ }
+
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException
+ {
+ AsyncContextState context = (AsyncContextState)event.getAsyncContext();
+ Request request=context.getHttpChannelState().getBaseRequest();
+ Response response=request.getResponse();
+ _requestLog.log(request,response);
+ }
+ };
/* ------------------------------------------------------------ */
/*
@@ -55,21 +87,21 @@
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
- HttpChannelState continuation = baseRequest.getHttpChannelState();
- if (!continuation.isInitial())
- {
- baseRequest.setDispatchTime(System.currentTimeMillis());
- }
-
try
{
super.handle(target, baseRequest, request, response);
}
finally
{
- if (_requestLog != null && DispatcherType.REQUEST.equals(baseRequest.getDispatcherType()))
+ if (_requestLog != null && baseRequest.getDispatcherType().equals(DispatcherType.REQUEST))
{
- _requestLog.log(baseRequest, (Response)response);
+ if (baseRequest.getHttpChannelState().isAsync())
+ {
+ if (baseRequest.getHttpChannelState().isInitial())
+ baseRequest.getAsyncContext().addListener(_listener);
+ }
+ else
+ _requestLog.log(baseRequest, (Response)response);
}
}
}
@@ -86,5 +118,36 @@
{
return _requestLog;
}
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void doStart() throws Exception
+ {
+ if (_requestLog==null)
+ {
+ LOG.warn("!RequestLog");
+ _requestLog=new NullRequestLog();
+ }
+ super.doStart();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ if (_requestLog instanceof NullRequestLog)
+ _requestLog=null;
+ }
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ private static class NullRequestLog extends AbstractLifeCycle implements RequestLog
+ {
+ @Override
+ public void log(Request request, Response response)
+ {
+ }
+ }
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionIdManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionIdManager.java
index e3df34f..3ddc0bd 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionIdManager.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionIdManager.java
@@ -37,6 +37,7 @@
protected Random _random;
protected boolean _weakRandom;
protected String _workerName;
+ protected String _workerAttr;
protected long _reseed=100000L;
/* ------------------------------------------------------------ */
@@ -58,6 +59,7 @@
*
* @return String or null
*/
+ @Override
public String getWorkerName()
{
return _workerName;
@@ -67,11 +69,16 @@
/**
* Set the workname. If set, the workername is dot appended to the session
* ID and can be used to assist session affinity in a load balancer.
+ * A worker name starting with $ is used as a request attribute name to
+ * lookup the worker name that can be dynamically set by a request
+ * customiser.
*
* @param workerName
*/
public void setWorkerName(String workerName)
{
+ if (isRunning())
+ throw new IllegalStateException(getState());
if (workerName.contains("."))
throw new IllegalArgumentException("Name cannot contain '.'");
_workerName=workerName;
@@ -114,27 +121,28 @@
*
* @see org.eclipse.jetty.server.SessionIdManager#newSessionId(javax.servlet.http.HttpServletRequest, long)
*/
+ @Override
public String newSessionId(HttpServletRequest request, long created)
{
synchronized (this)
{
- if (request!=null)
- {
- // A requested session ID can only be used if it is in use already.
- String requested_id=request.getRequestedSessionId();
- if (requested_id!=null)
- {
- String cluster_id=getClusterId(requested_id);
- if (idInUse(cluster_id))
- return cluster_id;
- }
+ if (request==null)
+ return newSessionId(created);
- // Else reuse any new session ID already defined for this request.
- String new_id=(String)request.getAttribute(__NEW_SESSION_ID);
- if (new_id!=null&&idInUse(new_id))
- return new_id;
+ // A requested session ID can only be used if it is in use already.
+ String requested_id=request.getRequestedSessionId();
+ if (requested_id!=null)
+ {
+ String cluster_id=getClusterId(requested_id);
+ if (idInUse(cluster_id))
+ return cluster_id;
}
+ // Else reuse any new session ID already defined for this request.
+ String new_id=(String)request.getAttribute(__NEW_SESSION_ID);
+ if (new_id!=null&&idInUse(new_id))
+ return new_id;
+
// pick a new unique ID!
String id = newSessionId(request.hashCode());
@@ -190,15 +198,16 @@
/* ------------------------------------------------------------ */
+ @Override
public abstract void renewSessionId(String oldClusterId, String oldNodeId, HttpServletRequest request);
-
/* ------------------------------------------------------------ */
@Override
protected void doStart() throws Exception
{
initRandom();
+ _workerAttr=(_workerName!=null && _workerName.startsWith("$"))?_workerName.substring(1):null;
}
/* ------------------------------------------------------------ */
@@ -232,5 +241,39 @@
_random.setSeed(_random.nextLong()^System.currentTimeMillis()^hashCode()^Runtime.getRuntime().freeMemory());
}
+ /** Get the session ID with any worker ID.
+ *
+ * @param clusterId
+ * @param request
+ * @return sessionId plus any worker ID.
+ */
+ @Override
+ public String getNodeId(String clusterId, HttpServletRequest request)
+ {
+ if (_workerName!=null)
+ {
+ if (_workerAttr==null)
+ return clusterId+'.'+_workerName;
+
+ String worker=(String)request.getAttribute(_workerAttr);
+ if (worker!=null)
+ return clusterId+'.'+worker;
+ }
+
+ return clusterId;
+ }
+
+ /** Get the session ID without any worker ID.
+ *
+ * @param nodeId the node id
+ * @return sessionId without any worker ID.
+ */
+ @Override
+ public String getClusterId(String nodeId)
+ {
+ int dot=nodeId.lastIndexOf('.');
+ return (dot>0)?nodeId.substring(0,dot):nodeId;
+ }
+
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java
index a81453e..4801d16 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java
@@ -459,16 +459,16 @@
{
if (isUsingCookies())
{
- String sessionPath = (_sessionPath==null) ? contextPath : _sessionPath;
+ String sessionPath = (_cookieConfig.getPath()==null) ? contextPath : _cookieConfig.getPath();
sessionPath = (sessionPath==null||sessionPath.length()==0) ? "/" : sessionPath;
String id = getNodeId(session);
HttpCookie cookie = null;
if (_sessionComment == null)
{
cookie = new HttpCookie(
- _sessionCookie,
+ _cookieConfig.getName(),
id,
- _sessionDomain,
+ _cookieConfig.getDomain(),
sessionPath,
_cookieConfig.getMaxAge(),
_cookieConfig.isHttpOnly(),
@@ -477,9 +477,9 @@
else
{
cookie = new HttpCookie(
- _sessionCookie,
+ _cookieConfig.getName(),
id,
- _sessionDomain,
+ _cookieConfig.getDomain(),
sessionPath,
_cookieConfig.getMaxAge(),
_cookieConfig.isHttpOnly(),
@@ -796,9 +796,11 @@
if (invalidate && _sessionListeners!=null)
{
- HttpSessionEvent event=new HttpSessionEvent(session);
- for (HttpSessionListener listener : _sessionListeners)
- listener.sessionDestroyed(event);
+ HttpSessionEvent event=new HttpSessionEvent(session);
+ for (int i = _sessionListeners.size()-1; i>=0; i--)
+ {
+ _sessionListeners.get(i).sessionDestroyed(event);
+ }
}
}
}
@@ -899,43 +901,57 @@
@Override
public void setComment(String comment)
- {
+ {
+ if (_context != null && _context.getContextHandler().isAvailable())
+ throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
_sessionComment = comment;
}
@Override
public void setDomain(String domain)
{
+ if (_context != null && _context.getContextHandler().isAvailable())
+ throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
_sessionDomain=domain;
}
@Override
public void setHttpOnly(boolean httpOnly)
- {
+ {
+ if (_context != null && _context.getContextHandler().isAvailable())
+ throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
_httpOnly=httpOnly;
}
@Override
public void setMaxAge(int maxAge)
- {
+ {
+ if (_context != null && _context.getContextHandler().isAvailable())
+ throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
_maxCookieAge=maxAge;
}
@Override
public void setName(String name)
- {
+ {
+ if (_context != null && _context.getContextHandler().isAvailable())
+ throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
_sessionCookie=name;
}
@Override
public void setPath(String path)
{
+ if (_context != null && _context.getContextHandler().isAvailable())
+ throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
_sessionPath=path;
}
@Override
public void setSecure(boolean secure)
{
+ if (_context != null && _context.getContextHandler().isAvailable())
+ throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
_secureCookies=secure;
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionIdManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionIdManager.java
index 218fe3d..0ebd29b 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionIdManager.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionIdManager.java
@@ -81,38 +81,7 @@
}
return sessions;
}
- /* ------------------------------------------------------------ */
- /** Get the session ID with any worker ID.
- *
- * @param clusterId
- * @param request
- * @return sessionId plus any worker ID.
- */
- public String getNodeId(String clusterId,HttpServletRequest request)
- {
- // used in Ajp13Parser
- String worker=request==null?null:(String)request.getAttribute("org.eclipse.jetty.ajp.JVMRoute");
- if (worker!=null)
- return clusterId+'.'+worker;
-
- if (_workerName!=null)
- return clusterId+'.'+_workerName;
-
- return clusterId;
- }
-
- /* ------------------------------------------------------------ */
- /** Get the session ID without any worker ID.
- *
- * @param nodeId the node id
- * @return sessionId without any worker ID.
- */
- public String getClusterId(String nodeId)
- {
- int dot=nodeId.lastIndexOf('.');
- return (dot>0)?nodeId.substring(0,dot):nodeId;
- }
-
+
/* ------------------------------------------------------------ */
@Override
protected void doStart() throws Exception
@@ -132,6 +101,7 @@
/**
* @see SessionIdManager#idInUse(String)
*/
+ @Override
public boolean idInUse(String id)
{
synchronized (this)
@@ -144,6 +114,7 @@
/**
* @see SessionIdManager#addSession(HttpSession)
*/
+ @Override
public void addSession(HttpSession session)
{
String id = getClusterId(session.getId());
@@ -165,6 +136,7 @@
/**
* @see SessionIdManager#removeSession(HttpSession)
*/
+ @Override
public void removeSession(HttpSession session)
{
String id = getClusterId(session.getId());
@@ -199,6 +171,7 @@
/**
* @see SessionIdManager#invalidateAll(String)
*/
+ @Override
public void invalidateAll(String id)
{
Collection<WeakReference<HttpSession>> sessions;
@@ -221,6 +194,7 @@
/* ------------------------------------------------------------ */
+ @Override
public void renewSessionId (String oldClusterId, String oldNodeId, HttpServletRequest request)
{
//generate a new id
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java
index 15deaef..6590e24 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java
@@ -29,11 +29,7 @@
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
-import java.util.ArrayList;
-import java.util.Collection;
import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
import java.util.Locale;
import java.util.Random;
import java.util.Set;
@@ -380,6 +376,7 @@
}
+ @Override
public void addSession(HttpSession session)
{
if (session == null)
@@ -422,6 +419,7 @@
+ @Override
public void removeSession(HttpSession session)
{
if (session == null)
@@ -456,32 +454,7 @@
}
- /**
- * Get the session id without any node identifier suffix.
- *
- * @see org.eclipse.jetty.server.SessionIdManager#getClusterId(java.lang.String)
- */
- public String getClusterId(String nodeId)
- {
- int dot=nodeId.lastIndexOf('.');
- return (dot>0)?nodeId.substring(0,dot):nodeId;
- }
-
-
- /**
- * Get the session id, including this node's id as a suffix.
- *
- * @see org.eclipse.jetty.server.SessionIdManager#getNodeId(java.lang.String, javax.servlet.http.HttpServletRequest)
- */
- public String getNodeId(String clusterId, HttpServletRequest request)
- {
- if (_workerName!=null)
- return clusterId+'.'+_workerName;
-
- return clusterId;
- }
-
-
+ @Override
public boolean idInUse(String id)
{
if (id == null)
@@ -515,6 +488,7 @@
*
* @see org.eclipse.jetty.server.SessionIdManager#invalidateAll(java.lang.String)
*/
+ @Override
public void invalidateAll(String id)
{
//take the id out of the list of known sessionids for this node
@@ -527,7 +501,7 @@
Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
for (int i=0; contexts!=null && i<contexts.length; i++)
{
- SessionHandler sessionHandler = (SessionHandler)((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
+ SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
if (sessionHandler != null)
{
SessionManager manager = sessionHandler.getSessionManager();
@@ -542,6 +516,7 @@
}
+ @Override
public void renewSessionId (String oldClusterId, String oldNodeId, HttpServletRequest request)
{
//generate a new id
@@ -556,7 +531,7 @@
Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
for (int i=0; contexts!=null && i<contexts.length; i++)
{
- SessionHandler sessionHandler = (SessionHandler)((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
+ SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
if (sessionHandler != null)
{
SessionManager manager = sessionHandler.getSessionManager();
@@ -971,7 +946,7 @@
Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
for (int i=0; contexts!=null && i<contexts.length; i++)
{
- SessionHandler sessionHandler = (SessionHandler)((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
+ SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
if (sessionHandler != null)
{
SessionManager manager = sessionHandler.getSessionManager();
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java
new file mode 100644
index 0000000..c64387b
--- /dev/null
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java
@@ -0,0 +1,226 @@
+//
+// ========================================================================
+// 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 static org.hamcrest.Matchers.containsString;
+import static org.junit.Assert.assertThat;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.resource.Resource;
+import org.hamcrest.Matchers;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class HttpOutputTest
+{
+ private Server _server;
+ private LocalConnector _connector;
+ private ContentHandler _handler;
+
+ @Before
+ public void init() throws Exception
+ {
+ _server = new Server();
+
+ HttpConnectionFactory http = new HttpConnectionFactory();
+ http.getHttpConfiguration().setRequestHeaderSize(1024);
+ http.getHttpConfiguration().setResponseHeaderSize(1024);
+ http.getHttpConfiguration().setOutputBufferSize(4096);
+
+ _connector = new LocalConnector(_server,http,null);
+ _server.addConnector(_connector);
+ _handler=new ContentHandler();
+ _server.setHandler(_handler);
+ _server.start();
+ }
+
+ @After
+ public void destroy() throws Exception
+ {
+ _server.stop();
+ _server.join();
+ }
+
+ @Test
+ public void testSimple() throws Exception
+ {
+ String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+ assertThat(response,containsString("HTTP/1.1 200 OK"));
+ }
+
+ @Test
+ public void testSendInputStreamSimple() throws Exception
+ {
+ Resource simple = Resource.newClassPathResource("simple/simple.txt");
+ _handler._contentInputStream=simple.getInputStream();
+ String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+ assertThat(response,containsString("HTTP/1.1 200 OK"));
+ assertThat(response,containsString("Content-Length: 11"));
+ }
+
+ @Test
+ public void testSendInputStreamBig() throws Exception
+ {
+ Resource big = Resource.newClassPathResource("simple/big.txt");
+ _handler._contentInputStream=big.getInputStream();
+ String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+ assertThat(response,containsString("HTTP/1.1 200 OK"));
+ assertThat(response,Matchers.not(containsString("Content-Length")));
+ }
+
+ @Test
+ public void testSendInputStreamBigChunked() throws Exception
+ {
+ Resource big = Resource.newClassPathResource("simple/big.txt");
+ _handler._contentInputStream= new FilterInputStream(big.getInputStream())
+ {
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException
+ {
+ int filled= super.read(b,off,len>2000?2000:len);
+ return filled;
+ }
+ };
+ String response=_connector.getResponses(
+ "GET / HTTP/1.1\nHost: localhost:80\n\n"+
+ "GET / HTTP/1.1\nHost: localhost:80\nConnection: close\n\n"
+ );
+ response=response.substring(0,response.lastIndexOf("HTTP/1.1 200 OK"));
+
+ assertThat(response,containsString("HTTP/1.1 200 OK"));
+ assertThat(response,containsString("Transfer-Encoding: chunked"));
+ assertThat(response,containsString("\r\n0\r\n"));
+ }
+
+
+ @Test
+ public void testSendChannelSimple() throws Exception
+ {
+ Resource simple = Resource.newClassPathResource("simple/simple.txt");
+ _handler._contentChannel=simple.getReadableByteChannel();
+ String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+ assertThat(response,containsString("HTTP/1.1 200 OK"));
+ assertThat(response,containsString("Content-Length: 11"));
+ }
+
+ @Test
+ public void testSendChannelBig() throws Exception
+ {
+ Resource big = Resource.newClassPathResource("simple/big.txt");
+ _handler._contentChannel=big.getReadableByteChannel();
+ String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
+ assertThat(response,containsString("HTTP/1.1 200 OK"));
+ assertThat(response,Matchers.not(containsString("Content-Length")));
+ }
+
+ @Test
+ public void testSendChannelBigChunked() throws Exception
+ {
+ Resource big = Resource.newClassPathResource("simple/big.txt");
+ final ReadableByteChannel channel = big.getReadableByteChannel();
+ _handler._contentChannel=new ReadableByteChannel()
+ {
+
+ @Override
+ public boolean isOpen()
+ {
+ return channel.isOpen();
+ }
+
+ @Override
+ public void close() throws IOException
+ {
+ channel.close();
+ }
+
+ @Override
+ public int read(ByteBuffer dst) throws IOException
+ {
+ int filled=0;
+ if (dst.position()==0 && dst.limit()>2000)
+ {
+ int limit=dst.limit();
+ dst.limit(2000);
+ filled=channel.read(dst);
+ dst.limit(limit);
+ }
+ else
+ filled=channel.read(dst);
+ return filled;
+ }
+ };
+
+ String response=_connector.getResponses(
+ "GET / HTTP/1.1\nHost: localhost:80\n\n"+
+ "GET / HTTP/1.1\nHost: localhost:80\nConnection: close\n\n"
+ );
+ response=response.substring(0,response.lastIndexOf("HTTP/1.1 200 OK"));
+
+ assertThat(response,containsString("HTTP/1.1 200 OK"));
+ assertThat(response,containsString("Transfer-Encoding: chunked"));
+ assertThat(response,containsString("\r\n0\r\n"));
+ }
+
+ static class ContentHandler extends AbstractHandler
+ {
+ InputStream _contentInputStream;
+ ReadableByteChannel _contentChannel;
+
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ response.setContentType("text/plain");
+
+ HttpOutput out = (HttpOutput) response.getOutputStream();
+
+ if (_contentInputStream!=null)
+ {
+ out.sendContent(_contentInputStream);
+ _contentInputStream=null;
+ return;
+ }
+
+ if (_contentChannel!=null)
+ {
+ out.sendContent(_contentChannel);
+ _contentChannel=null;
+ return;
+ }
+
+ }
+
+ }
+}
+
+
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 5a5f594..d98e654 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
@@ -48,6 +48,7 @@
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
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 a770f95..05958cc 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
@@ -33,6 +33,7 @@
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.Utf8Appendable;
import org.junit.Assert;
import org.junit.Test;
@@ -322,29 +323,15 @@
{
}
- try
- {
- HttpURI huri=new HttpURI(uri);
- MultiMap<String> params = new MultiMap<>();
- huri.decodeQueryTo(params);
- System.err.println(params);
- Assert.assertTrue(false);
- }
- catch (IllegalArgumentException e)
- {
- }
+ HttpURI huri=new HttpURI(uri);
+ MultiMap<String> params = new MultiMap<>();
+ huri.decodeQueryTo(params);
+ assertEquals("data"+Utf8Appendable.REPLACEMENT+"here"+Utf8Appendable.REPLACEMENT,params.getValue("invalid",0));
- try
- {
- HttpURI huri=new HttpURI(uri);
- MultiMap<String> params = new MultiMap<>();
- huri.decodeQueryTo(params,"UTF-8");
- System.err.println(params);
- Assert.assertTrue(false);
- }
- catch (IllegalArgumentException e)
- {
- }
+ huri=new HttpURI(uri);
+ params = new MultiMap<>();
+ huri.decodeQueryTo(params,"UTF-8");
+ assertEquals("data"+Utf8Appendable.REPLACEMENT+"here"+Utf8Appendable.REPLACEMENT,params.getValue("invalid",0));
}
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 3249d96..e2d94bf 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
@@ -18,26 +18,17 @@
package org.eclipse.jetty.server;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
+import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Enumeration;
import java.util.Map;
import java.util.concurrent.TimeUnit;
-
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletException;
import javax.servlet.ServletRequestEvent;
@@ -52,15 +43,23 @@
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.MultiPartInputStreamParser;
import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Utf8Appendable;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.log.StdErrLog;
import org.hamcrest.Matchers;
import org.junit.After;
-import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
public class RequestTest
{
private static final Logger LOG = Log.getLogger(RequestTest.class);
@@ -100,24 +99,11 @@
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response)
{
- Map map = null;
- try
- {
- //do the parse
- request.getParameterMap();
- Assert.fail("Expected parsing failure");
- return false;
- }
- catch (Exception e)
- {
- //catch the error and check the param map is not null
- map = request.getParameterMap();
- assertFalse(map == null);
- assertTrue(map.isEmpty());
-
- Enumeration names = request.getParameterNames();
- assertFalse(names.hasMoreElements());
- }
+ Map<String,String[]> map = null;
+ //do the parse
+ map = request.getParameterMap();
+ assertEquals("aaa"+Utf8Appendable.REPLACEMENT+"bbb",map.get("param")[0]);
+ assertEquals("value",map.get("other")[0]);
return true;
}
@@ -125,7 +111,7 @@
//Send a request with query string with illegal hex code to cause
//an exception parsing the params
- String request="GET /?param=%ZZaaa HTTP/1.1\r\n"+
+ String request="GET /?param=aaa%ZZbbb&other=value HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Content-Type: text/html;charset=utf8\n"+
"Connection: close\n"+
@@ -237,6 +223,7 @@
"Host: whatever\r\n"+
"Content-Type: multipart/form-data; boundary=\"AaB03x\"\r\n"+
"Content-Length: "+multipart.getBytes().length+"\r\n"+
+ "Connection: close\r\n"+
"\r\n"+
multipart;
@@ -363,12 +350,13 @@
};
results.clear();
- _connector.getResponses(
+ String response=_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: myhost\n"+
"Connection: close\n"+
"\n");
int i=0;
+ assertThat(response,Matchers.containsString("200 OK"));
assertEquals("http://myhost/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("myhost",results.get(i++));
@@ -376,12 +364,13 @@
results.clear();
- _connector.getResponses(
+ response=_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: myhost:8888\n"+
"Connection: close\n"+
"\n");
i=0;
+ assertThat(response,Matchers.containsString("200 OK"));
assertEquals("http://myhost:8888/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("myhost",results.get(i++));
@@ -389,13 +378,14 @@
results.clear();
- _connector.getResponses(
+ response=_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: 1.2.3.4\n"+
"Connection: close\n"+
"\n");
i=0;
-
+
+ assertThat(response,Matchers.containsString("200 OK"));
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++));
@@ -403,12 +393,13 @@
results.clear();
- _connector.getResponses(
+ response=_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: 1.2.3.4:8888\n"+
"Connection: close\n"+
"\n");
i=0;
+ assertThat(response,Matchers.containsString("200 OK"));
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++));
@@ -416,12 +407,13 @@
results.clear();
- _connector.getResponses(
+ response=_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: [::1]\n"+
"Connection: close\n"+
"\n");
i=0;
+ assertThat(response,Matchers.containsString("200 OK"));
assertEquals("http://[::1]/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("::1",results.get(i++));
@@ -429,12 +421,13 @@
results.clear();
- _connector.getResponses(
+ response=_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: [::1]:8888\n"+
"Connection: close\n"+
"\n");
i=0;
+ assertThat(response,Matchers.containsString("200 OK"));
assertEquals("http://[::1]:8888/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("::1",results.get(i++));
@@ -442,7 +435,7 @@
results.clear();
- _connector.getResponses(
+ response=_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: [::1]\n"+
"x-forwarded-for: remote\n"+
@@ -450,6 +443,7 @@
"Connection: close\n"+
"\n");
i=0;
+ assertThat(response,Matchers.containsString("200 OK"));
assertEquals("https://[::1]/",results.get(i++));
assertEquals("remote",results.get(i++));
assertEquals("::1",results.get(i++));
@@ -457,7 +451,7 @@
results.clear();
- _connector.getResponses(
+ response=_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: [::1]:8888\n"+
"Connection: close\n"+
@@ -465,18 +459,11 @@
"x-forwarded-proto: https\n"+
"\n");
i=0;
-
+ assertThat(response,Matchers.containsString("200 OK"));
assertEquals("https://[::1]:8888/",results.get(i++));
assertEquals("remote",results.get(i++));
assertEquals("::1",results.get(i++));
assertEquals("8888",results.get(i++));
-
-
-
-
-
-
-
}
@Test
@@ -1046,6 +1033,12 @@
}
}
+ @Test(expected = UnsupportedEncodingException.class)
+ public void testNotSupportedCharacterEncoding() throws UnsupportedEncodingException
+ {
+ Request request = new Request(null, null);
+ request.setCharacterEncoding("doesNotExist");
+ }
interface RequestTester
{
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 84967af..245fb5c 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
@@ -601,7 +601,7 @@
String set = response.getHttpFields().getStringField("Set-Cookie");
- assertEquals("name=value;Path=/path;Domain=domain;Secure;HttpOnly;Comment=comment", set);
+ assertEquals("name=value;Version=1;Path=/path;Domain=domain;Secure;HttpOnly;Comment=comment", set);
}
@@ -630,7 +630,7 @@
assertNotNull(set);
ArrayList<String> list = Collections.list(set);
assertEquals(2, list.size());
- assertTrue(list.contains("name=value;Path=/path;Domain=domain;Secure;HttpOnly;Comment=comment"));
+ assertTrue(list.contains("name=value;Version=1;Path=/path;Domain=domain;Secure;HttpOnly;Comment=comment"));
assertTrue(list.contains("name2=value2;Path=/path;Domain=domain"));
//get rid of the cookies
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerCollectionTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerCollectionTest.java
index 9dab6b4..a287dcd 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerCollectionTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerCollectionTest.java
@@ -23,6 +23,8 @@
import static org.junit.Assert.assertTrue;
import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@@ -32,60 +34,121 @@
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.ArrayUtil;
+import org.hamcrest.Matchers;
+import org.junit.Assert;
import org.junit.Test;
public class ContextHandlerCollectionTest
{
@Test
- public void testVirtualHostNormalization() throws Exception
+ public void testVirtualHosts() throws Exception
{
Server server = new Server();
- LocalConnector connector = new LocalConnector(server);
+ LocalConnector connector0 = new LocalConnector(server);
+ LocalConnector connector1 = new LocalConnector(server);
+ connector1.setName("connector1");
+
server.setConnectors(new Connector[]
- { connector });
+ { connector0,connector1});
- ContextHandler contextA = new ContextHandler("/");
+ ContextHandler contextA = new ContextHandler("/ctx");
contextA.setVirtualHosts(new String[]
- { "www.example.com" });
- IsHandledHandler handlerA = new IsHandledHandler();
+ { "www.example.com", "alias.example.com" });
+ IsHandledHandler handlerA = new IsHandledHandler("A");
contextA.setHandler(handlerA);
+ contextA.setAllowNullPathInfo(true);
- ContextHandler contextB = new ContextHandler("/");
- IsHandledHandler handlerB = new IsHandledHandler();
+ ContextHandler contextB = new ContextHandler("/ctx");
+ IsHandledHandler handlerB = new IsHandledHandler("B");
contextB.setHandler(handlerB);
contextB.setVirtualHosts(new String[]
- { "www.example2.com." });
+ { "*.other.com" , "@connector1"});
- ContextHandler contextC = new ContextHandler("/");
- IsHandledHandler handlerC = new IsHandledHandler();
+ ContextHandler contextC = new ContextHandler("/ctx");
+ IsHandledHandler handlerC = new IsHandledHandler("C");
contextC.setHandler(handlerC);
- ContextHandlerCollection c = new ContextHandlerCollection();
+ ContextHandler contextD = new ContextHandler("/");
+ IsHandledHandler handlerD = new IsHandledHandler("D");
+ contextD.setHandler(handlerD);
+
+ ContextHandler contextE = new ContextHandler("/ctx/foo");
+ IsHandledHandler handlerE = new IsHandledHandler("E");
+ contextE.setHandler(handlerE);
+
+ ContextHandler contextF = new ContextHandler("/ctxlong");
+ IsHandledHandler handlerF = new IsHandledHandler("F");
+ contextF.setHandler(handlerF);
+ ContextHandlerCollection c = new ContextHandlerCollection();
c.addHandler(contextA);
c.addHandler(contextB);
c.addHandler(contextC);
-
+
+ HandlerList list = new HandlerList();
+ list.addHandler(contextD);
+ list.addHandler(contextE);
+ list.addHandler(contextF);
+ c.addHandler(list);
+
server.setHandler(c);
try
{
server.start();
- connector.getResponses("GET / HTTP/1.0\n" + "Host: www.example.com.\n\n");
+
+ Object[][] tests = new Object[][] {
+ {connector0,"www.example.com.", "/ctx", handlerA},
+ {connector0,"www.example.com.", "/ctx/", handlerA},
+ {connector0,"www.example.com.", "/ctx/info", handlerA},
+ {connector0,"www.example.com", "/ctx/info", handlerA},
+ {connector0,"alias.example.com", "/ctx/info", handlerA},
+ {connector1,"www.example.com.", "/ctx/info", handlerA},
+ {connector1,"www.example.com", "/ctx/info", handlerA},
+ {connector1,"alias.example.com", "/ctx/info", handlerA},
- assertTrue(handlerA.isHandled());
- assertFalse(handlerB.isHandled());
- assertFalse(handlerC.isHandled());
+ {connector1,"www.other.com", "/ctx", null},
+ {connector1,"www.other.com", "/ctx/", handlerB},
+ {connector1,"www.other.com", "/ctx/info", handlerB},
+ {connector0,"www.other.com", "/ctx/info", handlerC},
+
+ {connector0,"www.example.com", "/ctxinfo", handlerD},
+ {connector1,"unknown.com", "/unknown", handlerD},
+
+ {connector0,"alias.example.com", "/ctx/foo/info", handlerE},
+ {connector0,"alias.example.com", "/ctxlong/info", handlerF},
+ };
+
+ for (int i=0;i<tests.length;i++)
+ {
+ Object[] test=tests[i];
+ LocalConnector connector = (LocalConnector)test[0];
+ String host=(String)test[1];
+ String uri=(String)test[2];
+ IsHandledHandler handler = (IsHandledHandler)test[3];
- handlerA.reset();
- handlerB.reset();
- handlerC.reset();
+ handlerA.reset();
+ handlerB.reset();
+ handlerC.reset();
+ handlerD.reset();
+ handlerE.reset();
+ handlerF.reset();
- connector.getResponses("GET / HTTP/1.0\n" + "Host: www.example2.com\n\n");
-
- assertFalse(handlerA.isHandled());
- assertTrue(handlerB.isHandled());
- assertFalse(handlerC.isHandled());
+ // System.err.printf("test %d %s@%s --> %s | %s%n",i,uri,host,connector.getName(),handler);
+ String response = connector.getResponses("GET "+uri+" HTTP/1.0\nHost: "+host+"\n\n");
+
+ if (handler==null)
+ {
+ Assert.assertThat(response,Matchers.containsString(" 302 "));
+ }
+ else if (!handler.isHandled())
+ {
+ System.err.printf("FAILED %d %s@%s --> %s | %s%n",i,uri,host,connector.getName(),handler);
+ System.err.println(response);
+ Assert.fail();
+ }
+ }
}
finally
@@ -103,7 +166,7 @@
ContextHandler context = new ContextHandler("/");
- IsHandledHandler handler = new IsHandledHandler();
+ IsHandledHandler handler = new IsHandledHandler("H");
context.setHandler(handler);
ContextHandlerCollection c = new ContextHandlerCollection();
@@ -149,7 +212,10 @@
for(String host : requestHosts)
{
- connector.getResponses("GET / HTTP/1.0\n" + "Host: "+host+"\nConnection:close\n\n");
+ // System.err.printf("host=%s in %s%n",host,contextHosts==null?Collections.emptyList():Arrays.asList(contextHosts));
+
+ String response=connector.getResponses("GET / HTTP/1.0\n" + "Host: "+host+"\nConnection:close\n\n");
+ // System.err.println(response);
if(succeed)
assertTrue("'"+host+"' should have been handled.",handler.isHandled());
else
@@ -166,17 +232,17 @@
Server server = new Server();
ContextHandler contextA = new ContextHandler("/a");
- IsHandledHandler handlerA = new IsHandledHandler();
+ IsHandledHandler handlerA = new IsHandledHandler("A");
contextA.setHandler(handlerA);
ContextHandler contextB = new ContextHandler("/b");
- IsHandledHandler handlerB = new IsHandledHandler();
+ IsHandledHandler handlerB = new IsHandledHandler("B");
HandlerWrapper wrapperB = new HandlerWrapper();
wrapperB.setHandler(handlerB);
contextB.setHandler(wrapperB);
ContextHandler contextC = new ContextHandler("/c");
- IsHandledHandler handlerC = new IsHandledHandler();
+ IsHandledHandler handlerC = new IsHandledHandler("C");
contextC.setHandler(handlerC);
ContextHandlerCollection collection = new ContextHandlerCollection();
@@ -202,22 +268,36 @@
private static final class IsHandledHandler extends AbstractHandler
{
private boolean handled;
+ private final String name;
+
+ public IsHandledHandler(String string)
+ {
+ name=string;
+ }
public boolean isHandled()
{
return handled;
}
+ @Override
public void handle(String s, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
this.handled = true;
+ response.getWriter().print(name);
}
public void reset()
{
handled = false;
}
+
+ @Override
+ public String toString()
+ {
+ return name;
+ }
}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/session/HashSessionManagerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/session/HashSessionManagerTest.java
index 11ad5a9..960fa62 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/session/HashSessionManagerTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/session/HashSessionManagerTest.java
@@ -102,7 +102,7 @@
Assert.assertTrue(testDir.exists());
Assert.assertTrue(testDir.canWrite());
- HashSessionIdManager idManager = new HashSessionIdManager();
+ AbstractSessionIdManager idManager = new HashSessionIdManager();
idManager.setWorkerName("foo");
manager.setSessionIdManager(idManager);
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/session/SessionCookieTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/session/SessionCookieTest.java
index f5b58e2..0c52028 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/session/SessionCookieTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/session/SessionCookieTest.java
@@ -58,6 +58,7 @@
/**
* @see org.eclipse.jetty.server.SessionIdManager#idInUse(java.lang.String)
*/
+ @Override
public boolean idInUse(String id)
{
return false;
@@ -66,6 +67,7 @@
/**
* @see org.eclipse.jetty.server.SessionIdManager#addSession(javax.servlet.http.HttpSession)
*/
+ @Override
public void addSession(HttpSession session)
{
@@ -74,6 +76,7 @@
/**
* @see org.eclipse.jetty.server.SessionIdManager#removeSession(javax.servlet.http.HttpSession)
*/
+ @Override
public void removeSession(HttpSession session)
{
@@ -82,28 +85,12 @@
/**
* @see org.eclipse.jetty.server.SessionIdManager#invalidateAll(java.lang.String)
*/
+ @Override
public void invalidateAll(String id)
{
}
- /**
- * @see org.eclipse.jetty.server.SessionIdManager#getClusterId(java.lang.String)
- */
- public String getClusterId(String nodeId)
- {
- int dot=nodeId.lastIndexOf('.');
- return (dot>0)?nodeId.substring(0,dot):nodeId;
- }
-
- /**
- * @see org.eclipse.jetty.server.SessionIdManager#getNodeId(java.lang.String, javax.servlet.http.HttpServletRequest)
- */
- public String getNodeId(String clusterId, HttpServletRequest request)
- {
- return clusterId+'.'+_workerName;
- }
-
@Override
public void renewSessionId(String oldClusterId, String oldNodeId, HttpServletRequest request)
{
@@ -119,6 +106,7 @@
/**
* @see org.eclipse.jetty.server.session.AbstractSessionManager#addSession(org.eclipse.jetty.server.session.AbstractSession)
*/
+ @Override
protected void addSession(AbstractSession session)
{
@@ -127,6 +115,7 @@
/**
* @see org.eclipse.jetty.server.session.AbstractSessionManager#getSession(java.lang.String)
*/
+ @Override
public AbstractSession getSession(String idInCluster)
{
return null;
@@ -135,6 +124,7 @@
/**
* @see org.eclipse.jetty.server.session.AbstractSessionManager#invalidateSessions()
*/
+ @Override
protected void invalidateSessions() throws Exception
{
@@ -143,6 +133,7 @@
/**
* @see org.eclipse.jetty.server.session.AbstractSessionManager#newSession(javax.servlet.http.HttpServletRequest)
*/
+ @Override
protected AbstractSession newSession(HttpServletRequest request)
{
return null;
@@ -151,6 +142,7 @@
/**
* @see org.eclipse.jetty.server.session.AbstractSessionManager#removeSession(java.lang.String)
*/
+ @Override
protected boolean removeSession(String idInCluster)
{
return false;
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 42da56a..8d2e453 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
@@ -21,6 +21,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import javax.servlet.AsyncContext;
@@ -33,9 +35,14 @@
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.RequestLog;
+import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
@@ -53,15 +60,28 @@
protected Server _server = new Server();
protected ServletHandler _servletHandler;
protected ServerConnector _connector;
+ protected List<String> _log;
+ protected int _expectedLogs;
+ protected String _expectedCode;
@Before
public void setUp() throws Exception
{
_connector = new ServerConnector(_server);
_server.setConnectors(new Connector[]{ _connector });
+
+ _log=new ArrayList<>();
+ RequestLog log=new Log();
+ RequestLogHandler logHandler = new RequestLogHandler();
+ logHandler.setRequestLog(log);
+ _server.setHandler(logHandler);
+ _expectedLogs=1;
+ _expectedCode="200 ";
+
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
context.setContextPath("/ctx");
- _server.setHandler(context);
+ logHandler.setHandler(context);
+
_servletHandler=context.getServletHandler();
ServletHolder holder=new ServletHolder(_servlet);
holder.setAsyncSupported(true);
@@ -73,6 +93,8 @@
@After
public void tearDown() throws Exception
{
+ assertEquals(_expectedLogs,_log.size());
+ Assert.assertThat(_log.get(0),Matchers.containsString(_expectedCode));
_server.stop();
}
@@ -105,6 +127,7 @@
@Test
public void testSuspend() throws Exception
{
+ _expectedCode="500 ";
String response=process("suspend=200",null);
assertEquals("HTTP/1.1 500 Async Timeout",response.substring(0,26));
assertContains(
@@ -258,6 +281,7 @@
@Test
public void testSuspendWaitResumeSuspend() throws Exception
{
+ _expectedCode="500 ";
String response=process("suspend=1000&resume=10&suspend2=10",null);
assertEquals("HTTP/1.1 500 Async Timeout",response.substring(0,26));
assertContains(
@@ -316,6 +340,7 @@
@Test
public void testSuspendTimeoutSuspend() throws Exception
{
+ _expectedCode="500 ";
String response=process("suspend=10&suspend2=10",null);
assertContains(
"history: REQUEST\r\n"+
@@ -335,6 +360,7 @@
@Test
public void testAsyncRead() throws Exception
{
+ _expectedLogs=2;
String header="GET /ctx/path/info?suspend=2000&resume=1500 HTTP/1.1\r\n"+
"Host: localhost\r\n"+
"Content-Length: 10\r\n"+
@@ -691,4 +717,13 @@
((HttpServletResponse)event.getSuppliedResponse()).addHeader("history","onComplete");
}
};
+
+ class Log extends AbstractLifeCycle implements RequestLog
+ {
+ @Override
+ public void log(Request request, Response response)
+ {
+ _log.add(response.getStatus()+" "+response.getContentCount()+" "+request.getRequestURI());
+ }
+ }
}
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java
index 392d4e5..047f599 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java
@@ -19,6 +19,7 @@
package org.eclipse.jetty.servlets;
import java.io.IOException;
+import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@@ -1066,8 +1067,10 @@
* A RateTracker is associated with a connection, and stores request rate
* data.
*/
- class RateTracker extends Timeout.Task implements HttpSessionBindingListener, HttpSessionActivationListener
+ class RateTracker extends Timeout.Task implements HttpSessionBindingListener, HttpSessionActivationListener, Serializable
{
+ private static final long serialVersionUID = 3534663738034577872L;
+
transient protected final String _id;
transient protected final int _type;
transient protected final long[] _timestamps;
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java
index 8fcb8b2..253d6d5 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java
@@ -45,10 +45,12 @@
import javax.servlet.http.Part;
import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.util.LazyList;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.MultiPartInputStreamParser;
import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -259,11 +261,11 @@
{
try
{
- return new String((byte[])o,_encoding);
+ return getParameterBytesAsString(name, (byte[])o);
}
catch(Exception e)
{
- e.printStackTrace();
+ LOG.warn(e);
}
}
else if (o!=null)
@@ -282,9 +284,7 @@
for ( Object key : _params.keySet() )
{
- String[] a = LazyList.toStringArray(getParameter((String)key));
- cmap.put((String)key,a);
-
+ cmap.put((String)key,getParameterValues((String)key));
}
return Collections.unmodifiableMap(cmap);
@@ -318,7 +318,7 @@
{
try
{
- v[i]=new String((byte[])o,_encoding);
+ v[i]=getParameterBytesAsString(name, (byte[])o);
}
catch(Exception e)
{
@@ -341,5 +341,23 @@
{
_encoding=enc;
}
+
+
+ /* ------------------------------------------------------------------------------- */
+ private String getParameterBytesAsString (String name, byte[] bytes)
+ throws UnsupportedEncodingException
+ {
+ //check if there is a specific encoding for the parameter
+ Object ct = _params.getValue(name+CONTENT_TYPE_SUFFIX,0);
+ //use default if not
+ String contentType = _encoding;
+ if (ct != null)
+ {
+ String tmp = MimeTypes.getCharsetFromContentType((String)ct);
+ contentType = (tmp == null?_encoding:tmp);
+ }
+
+ return new String(bytes,contentType);
+ }
}
}
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/CompressedResponseWrapper.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/CompressedResponseWrapper.java
index 6930536..59324b0 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/CompressedResponseWrapper.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/CompressedResponseWrapper.java
@@ -243,7 +243,7 @@
if (_writer!=null)
_writer.flush();
if (_compressedStream!=null)
- _compressedStream.finish();
+ _compressedStream.flush();
else
getResponse().flushBuffer();
}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterContentLengthTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterContentLengthTest.java
index cc89b1c..101eaaf 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterContentLengthTest.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterContentLengthTest.java
@@ -32,6 +32,7 @@
import org.eclipse.jetty.servlets.gzip.TestServletLengthStreamTypeWrite;
import org.eclipse.jetty.servlets.gzip.TestServletLengthTypeStreamWrite;
import org.eclipse.jetty.servlets.gzip.TestServletStreamLengthTypeWrite;
+import org.eclipse.jetty.servlets.gzip.TestServletStreamLengthTypeWriteWithFlush;
import org.eclipse.jetty.servlets.gzip.TestServletStreamTypeLengthWrite;
import org.eclipse.jetty.servlets.gzip.TestServletTypeLengthStreamWrite;
import org.eclipse.jetty.servlets.gzip.TestServletTypeStreamLengthWrite;
@@ -73,12 +74,14 @@
{ TestServletLengthStreamTypeWrite.class, GzipFilter.GZIP },
{ TestServletLengthTypeStreamWrite.class, GzipFilter.GZIP },
{ TestServletStreamLengthTypeWrite.class, GzipFilter.GZIP },
+ { TestServletStreamLengthTypeWriteWithFlush.class, GzipFilter.GZIP },
{ TestServletStreamTypeLengthWrite.class, GzipFilter.GZIP },
{ TestServletTypeLengthStreamWrite.class, GzipFilter.GZIP },
{ TestServletTypeStreamLengthWrite.class, GzipFilter.GZIP },
{ TestServletLengthStreamTypeWrite.class, GzipFilter.DEFLATE },
{ TestServletLengthTypeStreamWrite.class, GzipFilter.DEFLATE },
{ TestServletStreamLengthTypeWrite.class, GzipFilter.DEFLATE },
+ { TestServletStreamLengthTypeWriteWithFlush.class, GzipFilter.DEFLATE },
{ TestServletStreamTypeLengthWrite.class, GzipFilter.DEFLATE },
{ TestServletTypeLengthStreamWrite.class, GzipFilter.DEFLATE },
{ TestServletTypeStreamLengthWrite.class, GzipFilter.DEFLATE }
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java
index de08023..9098ea3 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java
@@ -26,11 +26,13 @@
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.EnumSet;
+import java.util.Map;
import javax.servlet.DispatcherType;
import javax.servlet.ServletException;
@@ -38,10 +40,12 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletTester;
import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.StringUtil;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -105,6 +109,7 @@
public void tearDown() throws Exception
{
tester.stop();
+ tester=null;
}
@Test
@@ -698,8 +703,6 @@
assertTrue(response.getContent().contains("aaaa,bbbbb"));
}
-
- @Test
public void testContentTypeWithCharSet() throws Exception
{
// generated and parsed test
@@ -730,6 +733,39 @@
}
+ @Test
+ public void testBufferOverflowNoCRLF () throws Exception
+ {
+ String boundary="XyXyXy";
+ // generated and parsed test
+ HttpTester.Request request = HttpTester.newRequest();
+ HttpTester.Response response;
+ tester.addServlet(BoundaryServlet.class,"/testb");
+ tester.setAttribute("fileName", "abc");
+ tester.setAttribute("desc", "123");
+ tester.setAttribute("title", "ttt");
+ request.setMethod("POST");
+ request.setVersion("HTTP/1.0");
+ request.setHeader("Host","tester");
+ request.setURI("/context/testb");
+ request.setHeader("Content-Type","multipart/form-data; boundary="+boundary);
+
+ String content = "--XyXyXy";
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ baos.write(content.getBytes());
+
+ for (int i=0; i< 8500; i++) //create content that will overrun default buffer size of BufferedInputStream
+ {
+ baos.write('a');
+ }
+ request.setContent(baos.toString());
+
+ response = HttpTester.parseResponse(tester.getResponses(request.generate()));
+ assertTrue(response.getContent().contains("Buffer size exceeded"));
+ assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, response.getStatus());
+ }
+
/*
* see the testParameterMap test
*
@@ -786,6 +822,59 @@
assertTrue(response.getContent().indexOf("brown cow")>=0);
}
+ public static class TestServletCharSet extends HttpServlet
+ {
+
+ @Override
+ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ //test that the multipart content bytes were converted correctly from their charset to unicode
+ String content = (String)req.getParameter("ttt");
+ assertNotNull(content);
+ assertEquals("ttt\u01FCzzz",content);
+ assertEquals("application/octet-stream; charset=UTF-8",req.getParameter("ttt"+MultiPartFilter.CONTENT_TYPE_SUFFIX));
+
+
+ //test that the parameter map retrieves values as String[]
+ Map map = req.getParameterMap();
+ Object o = map.get("ttt");
+ assertTrue(o.getClass().isArray());
+ super.doPost(req, resp);
+ }
+ }
+
+
+ @Test
+ public void testWithCharSet()
+ throws Exception
+ {
+ // generated and parsed test
+ HttpTester.Request request = HttpTester.newRequest();
+ HttpTester.Response response;
+ tester.addServlet(TestServletCharSet.class,"/test3");
+
+ // test GET
+ request.setMethod("POST");
+ request.setVersion("HTTP/1.0");
+ request.setHeader("Host","tester");
+ request.setURI("/context/test3");
+
+ String boundary="XyXyXy";
+ request.setHeader("Content-Type","multipart/form-data; boundary="+boundary);
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ baos.write(("--" + boundary + "\r\n"+
+ "Content-Disposition: form-data; name=\"ttt\"\r\n"+
+ "Content-Type: application/octet-stream; charset=UTF-8\r\n\r\n").getBytes());
+ baos.write("ttt\u01FCzzz".getBytes(StringUtil.__UTF8));
+ baos.write(("\r\n--" + boundary + "--\r\n\r\n").getBytes());
+
+
+ request.setContent(baos.toByteArray());
+ response = HttpTester.parseResponse(tester.getResponses(request.generate()));
+ }
+
public static class DumpServlet extends HttpServlet
{
private static final long serialVersionUID = 201012011130L;
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipTester.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipTester.java
index 3cf181a..1cf6f10 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipTester.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipTester.java
@@ -18,13 +18,8 @@
package org.eclipse.jetty.servlets.gzip;
-import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.not;
-import static org.hamcrest.Matchers.notNullValue;
-import static org.hamcrest.Matchers.nullValue;
-import static org.junit.Assert.assertThat;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -113,7 +108,20 @@
// Assert the response headers
// Assert.assertThat("Response.status",response.getStatus(),is(HttpServletResponse.SC_OK));
- Assert.assertThat("Response.header[Content-Length]",response.get("Content-Length"),notNullValue());
+
+ // Response headers should have either a Transfer-Encoding indicating chunked OR a Content-Length
+ String contentLength = response.get("Content-Length");
+ String transferEncoding = response.get("Transfer-Encoding");
+
+ /* TODO need to check for the 3rd option of EOF content. To do this properly you might need to look at both HTTP/1.1 and HTTP/1.0 requests
+ boolean chunked = (transferEncoding != null) && (transferEncoding.indexOf("chunk") >= 0);
+ if(!chunked) {
+ Assert.assertThat("Response.header[Content-Length]",contentLength,notNullValue());
+ } else {
+ Assert.assertThat("Response.header[Transfer-Encoding]",transferEncoding,notNullValue());
+ }
+ */
+
int qindex = compressionType.indexOf(";");
if (qindex < 0)
Assert.assertThat("Response.header[Content-Encoding]",response.get("Content-Encoding"),containsString(compressionType));
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletStreamLengthTypeWriteWithFlush.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletStreamLengthTypeWriteWithFlush.java
new file mode 100644
index 0000000..ff8841c
--- /dev/null
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/TestServletStreamLengthTypeWriteWithFlush.java
@@ -0,0 +1,72 @@
+//
+// ========================================================================
+// 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.servlets.gzip;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.servlets.GzipFilter;
+
+/**
+ * A sample servlet to serve static content, using a order of construction that has caused problems for
+ * {@link GzipFilter} in the past.
+ *
+ * Using a real-world pattern of:
+ *
+ * <pre>
+ * 1) get stream
+ * 2) set content length
+ * 3) set content type
+ * 4) write and flush
+ * </pre>
+ *
+ * @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
+ */
+@SuppressWarnings("serial")
+public class TestServletStreamLengthTypeWriteWithFlush extends TestDirContentServlet
+{
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ {
+ String fileName = request.getServletPath();
+ byte[] dataBytes = loadContentFileBytes(fileName);
+
+ ServletOutputStream out = response.getOutputStream();
+
+ // set content-length of uncompressed content (GzipFilter should handle this)
+ response.setContentLength(dataBytes.length);
+
+ if (fileName.endsWith("txt"))
+ response.setContentType("text/plain");
+ else if (fileName.endsWith("mp3"))
+ response.setContentType("audio/mpeg");
+ response.setHeader("ETag","W/etag-"+fileName);
+
+ for ( int i = 0 ; i < dataBytes.length ; i++)
+ {
+ out.write(dataBytes[i]);
+ // flush using response object (not the stream itself)
+ response.flushBuffer();
+ }
+ }
+}
\ No newline at end of file
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardSession.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardSession.java
index 067f7f7..e50f98e 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardSession.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardSession.java
@@ -218,11 +218,22 @@
if (stream != null)
{
stream.process(frame);
+ removeFrameBytesFromQueue(stream);
removeStream(stream);
}
}
}
+ private void removeFrameBytesFromQueue(Stream stream)
+ {
+ synchronized (queue)
+ {
+ for (FrameBytes frameBytes : queue)
+ if (frameBytes.getStream() == stream)
+ queue.remove(frameBytes);
+ }
+ }
+
@Override
public void settings(SettingsInfo settingsInfo) throws ExecutionException, InterruptedException, TimeoutException
{
@@ -492,7 +503,8 @@
private void onSyn(final SynStreamFrame frame)
{
- IStream stream = createStream(frame, null, false, new Promise.Adapter<Stream>(){
+ IStream stream = createStream(frame, null, false, new Promise.Adapter<Stream>()
+ {
@Override
public void failed(Throwable x)
{
@@ -1054,7 +1066,7 @@
}
}
- private void append(FrameBytes frameBytes)
+ void append(FrameBytes frameBytes)
{
Throwable failure;
synchronized (queue)
@@ -1215,7 +1227,7 @@
public abstract void fail(Throwable throwable);
}
- private abstract class AbstractFrameBytes implements FrameBytes, Runnable
+ abstract class AbstractFrameBytes implements FrameBytes, Runnable
{
private final IStream stream;
private final Callback callback;
diff --git a/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/generator/DataFrameGeneratorTest.java b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/generator/DataFrameGeneratorTest.java
new file mode 100644
index 0000000..42e3d5d
--- /dev/null
+++ b/jetty-spdy/spdy-core/src/test/java/org/eclipse/jetty/spdy/generator/DataFrameGeneratorTest.java
@@ -0,0 +1,110 @@
+//
+// ========================================================================
+// 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.spdy.generator;
+
+import java.nio.ByteBuffer;
+import java.util.Random;
+
+import org.eclipse.jetty.io.ArrayByteBufferPool;
+import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
+import org.eclipse.jetty.spdy.api.DataInfo;
+import org.eclipse.jetty.spdy.frames.DataFrame;
+import org.eclipse.jetty.util.BufferUtil;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+@RunWith(JUnit4.class)
+public class DataFrameGeneratorTest
+{
+ private int increment = 1024;
+ private int streamId = 1;
+ private ArrayByteBufferPool bufferPool;
+ private DataFrameGenerator dataFrameGenerator;
+ private ByteBuffer headerBuffer = ByteBuffer.allocate(DataFrame.HEADER_LENGTH);
+
+ @Before
+ public void setUp()
+ {
+ bufferPool = new ArrayByteBufferPool(64, increment, 8192);
+ dataFrameGenerator = new DataFrameGenerator(bufferPool);
+ headerBuffer.putInt(0, streamId & 0x7F_FF_FF_FF);
+
+ }
+
+ @Test
+ public void testGenerateSmallFrame()
+ {
+ int bufferSize = 256;
+ generateFrame(bufferSize);
+ }
+
+ @Test
+ public void testGenerateFrameWithBufferThatEqualsBucketSize()
+ {
+ int bufferSize = increment;
+ generateFrame(bufferSize);
+ }
+
+ @Test
+ public void testGenerateFrameWithBufferThatEqualsBucketSizeMinusHeaderLength()
+ {
+ int bufferSize = increment - DataFrame.HEADER_LENGTH;
+ generateFrame(bufferSize);
+ }
+
+ private void generateFrame(int bufferSize)
+ {
+ ByteBuffer byteBuffer = createByteBuffer(bufferSize);
+ ByteBufferDataInfo dataInfo = new ByteBufferDataInfo(byteBuffer, true);
+ fillHeaderBuffer(bufferSize);
+ ByteBuffer dataFrameBuffer = dataFrameGenerator.generate(streamId, bufferSize, dataInfo);
+
+ assertThat("The content size in dataFrameBuffer matches the buffersize + header length",
+ dataFrameBuffer.limit(),
+ is(bufferSize + DataFrame.HEADER_LENGTH));
+
+ byte[] headerBytes = new byte[DataFrame.HEADER_LENGTH];
+ dataFrameBuffer.get(headerBytes, 0, DataFrame.HEADER_LENGTH);
+
+ assertThat("Header bytes are prepended", headerBytes, is(headerBuffer.array()));
+ }
+
+ private ByteBuffer createByteBuffer(int bufferSize)
+ {
+ byte[] bytes = new byte[bufferSize];
+ new Random().nextBytes(bytes);
+ ByteBuffer byteBuffer = bufferPool.acquire(bufferSize, false);
+ BufferUtil.flipToFill(byteBuffer);
+ byteBuffer.put(bytes);
+ BufferUtil.flipToFlush(byteBuffer, 0);
+ return byteBuffer;
+ }
+
+ private void fillHeaderBuffer(int bufferSize)
+ {
+ headerBuffer.putInt(4, bufferSize & 0x00_FF_FF_FF);
+ headerBuffer.put(4, DataInfo.FLAG_CLOSE);
+ }
+
+}
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HTTPSPDYServerConnectionFactory.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HTTPSPDYServerConnectionFactory.java
index ca71cb3..45d867a 100644
--- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HTTPSPDYServerConnectionFactory.java
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HTTPSPDYServerConnectionFactory.java
@@ -105,7 +105,8 @@
if (!(headers.get("accept-encoding") != null && headers.get("accept-encoding").value().contains
("gzip")))
headers.add("accept-encoding", "gzip");
- HttpTransportOverSPDY transport = new HttpTransportOverSPDY(connector, httpConfiguration, endPoint, pushStrategy, stream, headers);
+ HttpTransportOverSPDY transport = new HttpTransportOverSPDY(connector, httpConfiguration, endPoint,
+ pushStrategy, stream, headers);
HttpInputOverSPDY input = new HttpInputOverSPDY();
HttpChannelOverSPDY channel = new HttpChannelOverSPDY(connector, httpConfiguration, endPoint, transport, input, stream);
stream.setAttribute(CHANNEL_ATTRIBUTE, channel);
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 4705525..0c352e8 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
@@ -28,6 +28,8 @@
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.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.EndPoint;
@@ -41,6 +43,7 @@
import org.eclipse.jetty.spdy.api.PushInfo;
import org.eclipse.jetty.spdy.api.ReplyInfo;
import org.eclipse.jetty.spdy.api.SPDY;
+import org.eclipse.jetty.spdy.api.Session;
import org.eclipse.jetty.spdy.api.Stream;
import org.eclipse.jetty.spdy.api.StreamStatus;
import org.eclipse.jetty.util.BlockingCallback;
@@ -61,6 +64,7 @@
private final EndPoint endPoint;
private final PushStrategy pushStrategy;
private final Stream stream;
+ private final short version;
private final Fields requestHeaders;
private final BlockingCallback streamBlocker = new BlockingCallback();
private final AtomicBoolean committed = new AtomicBoolean();
@@ -73,6 +77,8 @@
this.pushStrategy = pushStrategy == null ? new PushStrategy.None() : pushStrategy;
this.stream = stream;
this.requestHeaders = requestHeaders;
+ Session session = stream.getSession();
+ this.version = session.getVersion();
}
protected Stream getStream()
@@ -94,7 +100,7 @@
}
@Override
- public void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback)
+ public void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent, final Callback callback)
{
if (LOG.isDebugEnabled())
LOG.debug("Sending {} {} {} {} last={}", this, stream, info, BufferUtil.toDetailString(content), lastContent);
@@ -115,7 +121,9 @@
// info!=null content!=null lastContent==false reply, commit with content
// info!=null content!=null lastContent==true reply, commit with content and complete
- boolean hasContent = BufferUtil.hasContent(content);
+ boolean isHeadRequest = HttpMethod.HEAD.name().equalsIgnoreCase(requestHeaders.get(HTTPSPDYHeader.METHOD.name(version)).value());
+ boolean hasContent = BufferUtil.hasContent(content) && !isHeadRequest;
+ boolean close = !hasContent && lastContent;
if (info != null)
{
@@ -127,68 +135,77 @@
LOG.warn("Committed response twice.", exception);
return;
}
- short version = stream.getSession().getVersion();
- Fields headers = new Fields();
-
- HttpVersion httpVersion = HttpVersion.HTTP_1_1;
- headers.put(HTTPSPDYHeader.VERSION.name(version), httpVersion.asString());
-
- int status = info.getStatus();
- StringBuilder httpStatus = new StringBuilder().append(status);
- String reason = info.getReason();
- if (reason == null)
- reason = HttpStatus.getMessage(status);
- if (reason != null)
- httpStatus.append(" ").append(reason);
- headers.put(HTTPSPDYHeader.STATUS.name(version), httpStatus.toString());
- LOG.debug("HTTP < {} {}", httpVersion, httpStatus);
-
- // TODO merge the two Field classes into one
- HttpFields fields = info.getHttpFields();
- if (fields != null)
+ sendReply(info, !hasContent ? callback : new Callback.Adapter()
{
- for (int i = 0; i < fields.size(); ++i)
+ @Override
+ public void failed(Throwable x)
{
- HttpField field = fields.getField(i);
- String name = field.getName();
- String value = field.getValue();
- headers.add(name, value);
- LOG.debug("HTTP < {}: {}", name, value);
+ callback.failed(x);
}
- }
-
- boolean close = !hasContent && lastContent;
- ReplyInfo reply = new ReplyInfo(headers, close);
- reply(stream, reply);
+ }, close);
}
// Do we have some content to send as well
if (hasContent)
{
- // Is the stream still open?
- if (stream.isClosed() || stream.isReset())
- // tell the callback about the EOF
- callback.failed(new EofException("stream closed"));
- else
- // send the data and let it call the callback
- stream.data(new ByteBufferDataInfo(endPoint.getIdleTimeout(), TimeUnit.MILLISECONDS, content, lastContent
- ), callback);
+ // send the data and let it call the callback
+ LOG.debug("Send content: {} on stream: {} lastContent={}", BufferUtil.toDetailString(content), stream,
+ lastContent);
+ stream.data(new ByteBufferDataInfo(endPoint.getIdleTimeout(), TimeUnit.MILLISECONDS, content, lastContent
+ ), callback);
}
// else do we need to close
- else if (lastContent)
+ else if (lastContent && info == null)
{
- // Are we closed ?
- if (stream.isClosed() || stream.isReset())
- // already closed by reply, so just tell callback we are complete
- callback.succeeded();
- else
- // send empty data to close and let the send call the callback
- stream.data(new ByteBufferDataInfo(endPoint.getIdleTimeout(), TimeUnit.MILLISECONDS,
- BufferUtil.EMPTY_BUFFER, lastContent), callback);
+ // send empty data to close and let the send call the callback
+ LOG.debug("No content and lastContent=true. Sending empty ByteBuffer to close stream: {}", stream);
+ stream.data(new ByteBufferDataInfo(endPoint.getIdleTimeout(), TimeUnit.MILLISECONDS,
+ BufferUtil.EMPTY_BUFFER, lastContent), callback);
}
- else
- // No data and no close so tell callback we are completed
- callback.succeeded();
+ else if (!lastContent && !hasContent && info == null)
+ throw new IllegalStateException("not lastContent, no content and no responseInfo!");
+
+ }
+
+ private void sendReply(HttpGenerator.ResponseInfo info, Callback callback, boolean close)
+ {
+ Fields headers = new Fields();
+
+ HttpVersion httpVersion = HttpVersion.HTTP_1_1;
+ headers.put(HTTPSPDYHeader.VERSION.name(version), httpVersion.asString());
+
+ int status = info.getStatus();
+ StringBuilder httpStatus = new StringBuilder().append(status);
+ String reason = info.getReason();
+ if (reason == null)
+ reason = HttpStatus.getMessage(status);
+ if (reason != null)
+ httpStatus.append(" ").append(reason);
+ headers.put(HTTPSPDYHeader.STATUS.name(version), httpStatus.toString());
+ LOG.debug("HTTP < {} {}", httpVersion, httpStatus);
+
+ // TODO merge the two Field classes into one
+ HttpFields fields = info.getHttpFields();
+ if (fields != null)
+ {
+ for (int i = 0; i < fields.size(); ++i)
+ {
+ HttpField field = fields.getField(i);
+ String name = field.getName();
+ String value = field.getValue();
+ headers.add(name, value);
+ LOG.debug("HTTP < {}: {}", name, value);
+ }
+ }
+
+ if (configuration.getSendServerVersion())
+ headers.add(HttpHeader.SERVER.asString(), HttpConfiguration.SERVER_VERSION);
+ if (configuration.getSendXPoweredBy())
+ headers.add(HttpHeader.X_POWERED_BY.asString(), HttpConfiguration.SERVER_VERSION);
+
+ ReplyInfo reply = new ReplyInfo(headers, close);
+ LOG.debug("Sending reply: {} on stream: {}", reply, stream);
+ reply(stream, reply, callback);
}
@Override
@@ -208,18 +225,17 @@
@Override
public void completed()
{
- LOG.debug("Completed");
+ LOG.debug("Completed {}", this);
}
- private void reply(Stream stream, ReplyInfo replyInfo)
+ private void reply(Stream stream, ReplyInfo replyInfo, Callback callback)
{
if (!stream.isUnidirectional())
- stream.reply(replyInfo, new Callback.Adapter());
+ stream.reply(replyInfo, callback);
else
- stream.headers(new HeadersInfo(replyInfo.getHeaders(), replyInfo.isClose()), new Callback.Adapter());
+ stream.headers(new HeadersInfo(replyInfo.getHeaders(), replyInfo.isClose()), callback);
Fields responseHeaders = replyInfo.getHeaders();
- short version = stream.getSession().getVersion();
if (responseHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().startsWith("200") && !stream.isClosed())
{
Set<String> pushResources = pushStrategy.apply(stream, requestHeaders, responseHeaders);
@@ -234,13 +250,15 @@
private static class PushHttpTransportOverSPDY extends HttpTransportOverSPDY
{
private final PushResourceCoordinator coordinator;
+ private final short version;
private PushHttpTransportOverSPDY(Connector connector, HttpConfiguration configuration, EndPoint endPoint,
PushStrategy pushStrategy, Stream stream, Fields requestHeaders,
- PushResourceCoordinator coordinator)
+ PushResourceCoordinator coordinator, short version)
{
super(connector, configuration, endPoint, pushStrategy, stream, requestHeaders);
this.coordinator = coordinator;
+ this.version = version;
}
@Override
@@ -248,7 +266,7 @@
{
Stream stream = getStream();
LOG.debug("Resource pushed for {} on {}",
- getRequestHeaders().get(HTTPSPDYHeader.URI.name(stream.getSession().getVersion())), stream);
+ getRequestHeaders().get(HTTPSPDYHeader.URI.name(version)), stream);
coordinator.complete();
}
}
@@ -257,7 +275,7 @@
{
private final Queue<PushResource> queue = new ConcurrentArrayQueue<>();
private final Set<String> resources;
- private boolean active;
+ private AtomicBoolean active = new AtomicBoolean(false);
private PushResourceCoordinator(Set<String> resources)
{
@@ -266,6 +284,7 @@
private void coordinate()
{
+ LOG.debug("Pushing resources: {}", resources);
// 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)
@@ -274,31 +293,40 @@
private void sendNextResourceData()
{
- PushResource resource;
- synchronized (this)
+ LOG.debug("{} sendNextResourceData active: {}", hashCode(), active.get());
+ if (active.compareAndSet(false, true))
{
- if (active)
+ PushResource resource = queue.poll();
+ if (resource != null)
+ {
+ LOG.debug("Opening new push channel for: {}", resource);
+ HttpChannelOverSPDY pushChannel = newHttpChannelOverSPDY(resource.getPushStream(), resource.getPushRequestHeaders());
+ pushChannel.requestStart(resource.getPushRequestHeaders(), true);
return;
- resource = queue.poll();
- if (resource == null)
- return;
- active = true;
+ }
+
+ if (active.compareAndSet(true, false))
+ {
+ if (queue.peek() != null)
+ sendNextResourceData();
+ }
+ else
+ {
+ throw new IllegalStateException("active must not be false here! Concurrency bug!");
+ }
}
- 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);
+ pushStream, pushRequestHeaders, this, version);
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));
@@ -319,14 +347,21 @@
public void failed(Throwable x)
{
LOG.debug("Creating push stream failed.", x);
+ sendNextResourceData();
}
});
}
+ private void complete()
+ {
+ if (!active.compareAndSet(true, false))
+ throw new IllegalStateException();
+ 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);
@@ -341,7 +376,6 @@
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
@@ -352,15 +386,6 @@
}
return pushHeaders;
}
-
- private void complete()
- {
- synchronized (this)
- {
- active = false;
- }
- sendNextResourceData();
- }
}
private static class PushResource
@@ -383,5 +408,14 @@
{
return pushRequestHeaders;
}
+
+ @Override
+ public String toString()
+ {
+ return "PushResource{" +
+ "pushStream=" + pushStream +
+ ", pushRequestHeaders=" + pushRequestHeaders +
+ '}';
+ }
}
}
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/HTTPProxyEngine.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/HTTPProxyEngine.java
index b425a58..b46d11c 100644
--- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/HTTPProxyEngine.java
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/HTTPProxyEngine.java
@@ -141,6 +141,7 @@
{
LOG.debug("onHeaders called with response: {}. Sending replyInfo to client.", response);
Fields responseHeaders = createResponseHeaders(clientStream, response);
+ removeHopHeaders(responseHeaders);
ReplyInfo replyInfo = new ReplyInfo(responseHeaders, false);
clientStream.reply(replyInfo, new Callback.Adapter()
{
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyEngine.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyEngine.java
index c5a5cff..0b980b5 100644
--- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyEngine.java
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyEngine.java
@@ -87,6 +87,8 @@
protected void removeHopHeaders(Fields headers)
{
+ // Header names are case-insensitive (RFC2616) and oej.util.Fields.add converts the names to lowercase. So we
+ // need to compare with the lowercase values only
for (String hopHeader : HOP_HEADERS)
headers.remove(hopHeader);
}
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/AbstractHTTPSPDYTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/AbstractHTTPSPDYTest.java
index fec3936..de96a4c 100644
--- a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/AbstractHTTPSPDYTest.java
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/AbstractHTTPSPDYTest.java
@@ -99,7 +99,10 @@
protected HTTPSPDYServerConnector newHTTPSPDYServerConnector(short version)
{
// For these tests, we need the connector to speak HTTP over SPDY even in non-SSL
- HTTPSPDYServerConnector connector = new HTTPSPDYServerConnector(server,version,new HttpConfiguration(), new PushStrategy.None());
+ HttpConfiguration httpConfiguration = new HttpConfiguration();
+ httpConfiguration.setSendServerVersion(true);
+ httpConfiguration.setSendXPoweredBy(true);
+ HTTPSPDYServerConnector connector = new HTTPSPDYServerConnector(server,version, httpConfiguration, new PushStrategy.None());
return connector;
}
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDYTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDYTest.java
index e8d76c7..9689a2d 100644
--- a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDYTest.java
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDYTest.java
@@ -18,13 +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.Matchers.any;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Random;
@@ -52,6 +45,13 @@
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.Matchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
@RunWith(MockitoJUnitRunner.class)
public class HttpTransportOverSPDYTest
{
@@ -72,19 +72,22 @@
@Mock
HttpGenerator.ResponseInfo responseInfo;
- private Random random = new Random();
+ Random random = new Random();
+ short version = SPDY.V3;
HttpTransportOverSPDY httpTransportOverSPDY;
@Before
public void setUp() throws Exception
{
- httpTransportOverSPDY = new HttpTransportOverSPDY(connector, httpConfiguration, endPoint, pushStrategy,
- stream, new Fields());
+ Fields requestHeaders = new Fields();
+ requestHeaders.add(HTTPSPDYHeader.METHOD.name(version), "GET");
when(responseInfo.getStatus()).thenReturn(HttpStatus.OK_200);
when(stream.getSession()).thenReturn(session);
when(session.getVersion()).thenReturn(SPDY.V3);
when(stream.isClosed()).thenReturn(false);
+ httpTransportOverSPDY = new HttpTransportOverSPDY(connector, httpConfiguration, endPoint, pushStrategy,
+ stream, requestHeaders);
}
@Test
@@ -95,7 +98,10 @@
httpTransportOverSPDY.send(null, content, lastContent, callback);
ArgumentCaptor<ByteBufferDataInfo> dataInfoCaptor = ArgumentCaptor.forClass(ByteBufferDataInfo.class);
- verify(stream, times(1)).data(dataInfoCaptor.capture(), any(Callback.class));
+ ArgumentCaptor<Callback> callbackCaptor = ArgumentCaptor.forClass(Callback.class);
+ verify(stream, times(1)).data(dataInfoCaptor.capture(), callbackCaptor.capture());
+ callbackCaptor.getValue().succeeded();
+ verify(callback, times(1)).succeeded();
assertThat("lastContent is true", dataInfoCaptor.getValue().isClose(), is(true));
assertThat("ByteBuffer is empty", dataInfoCaptor.getValue().length(), is(0));
}
@@ -109,7 +115,10 @@
httpTransportOverSPDY.send(null, content, lastContent, callback);
ArgumentCaptor<ByteBufferDataInfo> dataInfoCaptor = ArgumentCaptor.forClass(ByteBufferDataInfo.class);
- verify(stream, times(1)).data(dataInfoCaptor.capture(), any(Callback.class));
+ ArgumentCaptor<Callback> callbackCaptor = ArgumentCaptor.forClass(Callback.class);
+ verify(stream, times(1)).data(dataInfoCaptor.capture(), callbackCaptor.capture());
+ callbackCaptor.getValue().succeeded();
+ verify(callback, times(1)).succeeded();
assertThat("lastContent is true", dataInfoCaptor.getValue().isClose(), is(true));
assertThat("ByteBuffer length is 4096", dataInfoCaptor.getValue().length(), is(4096));
}
@@ -119,48 +128,62 @@
{
ByteBuffer content = BufferUtil.EMPTY_BUFFER;
boolean lastContent = true;
-
httpTransportOverSPDY.send(null, content, lastContent, callback);
ArgumentCaptor<ByteBufferDataInfo> dataInfoCaptor = ArgumentCaptor.forClass(ByteBufferDataInfo.class);
- verify(stream, times(1)).data(dataInfoCaptor.capture(), any(Callback.class));
+ ArgumentCaptor<Callback> callbackCaptor = ArgumentCaptor.forClass(Callback.class);
+ verify(stream, times(1)).data(dataInfoCaptor.capture(), callbackCaptor.capture());
+ callbackCaptor.getValue().succeeded();
+ verify(callback, times(1)).succeeded();
assertThat("lastContent is true", dataInfoCaptor.getValue().isClose(), is(true));
assertThat("ByteBuffer is empty", dataInfoCaptor.getValue().length(), is(0));
}
@Test
- public void testSendWithResponseInfoNullAndContentNullAndLastContentFalse() throws Exception
- {
- ByteBuffer content = null;
- boolean lastContent = false;
-
-
- httpTransportOverSPDY.send(null, content, lastContent, callback);
- verify(stream, times(0)).data(any(ByteBufferDataInfo.class), any(Callback.class));
- }
-
- @Test
public void testSendWithResponseInfoNullAndContentAndLastContentFalse() throws Exception
{
ByteBuffer content = createRandomByteBuffer();
boolean lastContent = false;
-
httpTransportOverSPDY.send(null, content, lastContent, callback);
ArgumentCaptor<ByteBufferDataInfo> dataInfoCaptor = ArgumentCaptor.forClass(ByteBufferDataInfo.class);
- verify(stream, times(1)).data(dataInfoCaptor.capture(), any(Callback.class));
+ ArgumentCaptor<Callback> callbackCaptor = ArgumentCaptor.forClass(Callback.class);
+ verify(stream, times(1)).data(dataInfoCaptor.capture(), callbackCaptor.capture());
+ callbackCaptor.getValue().succeeded();
+ verify(callback, times(1)).succeeded();
assertThat("lastContent is false", dataInfoCaptor.getValue().isClose(), is(false));
assertThat("ByteBuffer is empty", dataInfoCaptor.getValue().length(), is(4096));
}
- @Test
+ @Test(expected = IllegalStateException.class)
+ public void testSendWithResponseInfoNullAndContentNullAndLastContentFalse() throws Exception
+ {
+ ByteBuffer content = null;
+ boolean lastContent = false;
+ httpTransportOverSPDY.send(null, content, lastContent, callback);
+ }
+
+ @Test(expected = IllegalStateException.class)
public void testSendWithResponseInfoNullAndEmptyContentAndLastContentFalse() throws Exception
{
ByteBuffer content = BufferUtil.EMPTY_BUFFER;
boolean lastContent = false;
-
-
httpTransportOverSPDY.send(null, content, lastContent, callback);
+ }
+
+ @Test
+ public void testSendWithResponseInfoAndContentNullAndLastContentFalse() throws Exception
+ {
+ ByteBuffer content = null;
+ boolean lastContent = false;
+
+ httpTransportOverSPDY.send(responseInfo, content, lastContent, callback);
+ ArgumentCaptor<ReplyInfo> replyInfoCaptor = ArgumentCaptor.forClass(ReplyInfo.class);
+ ArgumentCaptor<Callback> callbackCaptor = ArgumentCaptor.forClass(Callback.class);
+ verify(stream, times(1)).reply(replyInfoCaptor.capture(), callbackCaptor.capture());
+ callbackCaptor.getValue().succeeded();
+ assertThat("ReplyInfo close is true", replyInfoCaptor.getValue().isClose(), is(false));
+
verify(stream, times(0)).data(any(ByteBufferDataInfo.class), any(Callback.class));
verify(callback, times(1)).succeeded();
}
@@ -170,13 +193,15 @@
{
ByteBuffer content = null;
boolean lastContent = true;
-
+
// when stream.isClosed() is called a 2nd time, the reply has closed the stream already
when(stream.isClosed()).thenReturn(false).thenReturn(true);
httpTransportOverSPDY.send(responseInfo, content, lastContent, callback);
ArgumentCaptor<ReplyInfo> replyInfoCaptor = ArgumentCaptor.forClass(ReplyInfo.class);
- verify(stream, times(1)).reply(replyInfoCaptor.capture(), any(Callback.class));
+ ArgumentCaptor<Callback> callbackCaptor = ArgumentCaptor.forClass(Callback.class);
+ verify(stream, times(1)).reply(replyInfoCaptor.capture(), callbackCaptor.capture());
+ callbackCaptor.getValue().succeeded();
assertThat("ReplyInfo close is true", replyInfoCaptor.getValue().isClose(), is(true));
verify(callback, times(1)).succeeded();
@@ -186,54 +211,42 @@
public void testSendWithResponseInfoAndContentAndLastContentTrue() throws Exception
{
ByteBuffer content = createRandomByteBuffer();
-
boolean lastContent = true;
-
httpTransportOverSPDY.send(responseInfo, content, lastContent, callback);
-
ArgumentCaptor<ReplyInfo> replyInfoCaptor = ArgumentCaptor.forClass(ReplyInfo.class);
- verify(stream, times(1)).reply(replyInfoCaptor.capture(), any(Callback.class));
+ ArgumentCaptor<Callback> callbackCaptor = ArgumentCaptor.forClass(Callback.class);
+ verify(stream, times(1)).reply(replyInfoCaptor.capture(), callbackCaptor.capture());
+ callbackCaptor.getValue().succeeded();
assertThat("ReplyInfo close is false", replyInfoCaptor.getValue().isClose(), is(false));
-
ArgumentCaptor<ByteBufferDataInfo> dataInfoCaptor = ArgumentCaptor.forClass(ByteBufferDataInfo.class);
- verify(stream, times(1)).data(dataInfoCaptor.capture(), any(Callback.class));
+ verify(stream, times(1)).data(dataInfoCaptor.capture(), callbackCaptor.capture());
+ callbackCaptor.getValue().succeeded();
assertThat("lastContent is true", dataInfoCaptor.getValue().isClose(), is(true));
assertThat("ByteBuffer length is 4096", dataInfoCaptor.getValue().length(), is(4096));
- }
-
- @Test
- public void testSendWithResponseInfoAndContentNullAndLastContentFalse() throws Exception
- {
- ByteBuffer content = null;
- boolean lastContent = false;
-
-
- httpTransportOverSPDY.send(responseInfo, content, lastContent, callback);
- ArgumentCaptor<ReplyInfo> replyInfoCaptor = ArgumentCaptor.forClass(ReplyInfo.class);
- verify(stream, times(1)).reply(replyInfoCaptor.capture(), any(Callback.class));
- assertThat("ReplyInfo close is true", replyInfoCaptor.getValue().isClose(), is(false));
-
- verify(stream, times(0)).data(any(ByteBufferDataInfo.class), any(Callback.class));
+ verify(callback, times(1)).succeeded();
}
@Test
public void testSendWithResponseInfoAndContentAndLastContentFalse() throws Exception
{
ByteBuffer content = createRandomByteBuffer();
-
boolean lastContent = false;
-
httpTransportOverSPDY.send(responseInfo, content, lastContent, callback);
ArgumentCaptor<ReplyInfo> replyInfoCaptor = ArgumentCaptor.forClass(ReplyInfo.class);
- verify(stream, times(1)).reply(replyInfoCaptor.capture(), any(Callback.class));
+ ArgumentCaptor<Callback> callbackCaptor = ArgumentCaptor.forClass(Callback.class);
+ verify(stream, times(1)).reply(replyInfoCaptor.capture(), callbackCaptor.capture());
+ callbackCaptor.getValue().succeeded();
assertThat("ReplyInfo close is false", replyInfoCaptor.getValue().isClose(), is(false));
ArgumentCaptor<ByteBufferDataInfo> dataInfoCaptor = ArgumentCaptor.forClass(ByteBufferDataInfo.class);
- verify(stream, times(1)).data(dataInfoCaptor.capture(), any(Callback.class));
+ verify(stream, times(1)).data(dataInfoCaptor.capture(), callbackCaptor.capture());
+ callbackCaptor.getValue().succeeded();
assertThat("lastContent is false", dataInfoCaptor.getValue().isClose(), is(false));
assertThat("ByteBuffer length is 4096", dataInfoCaptor.getValue().length(), is(4096));
+
+ verify(callback, times(1)).succeeded();
}
@Test
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 5b501fe..d120166 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
@@ -60,7 +60,6 @@
import org.eclipse.jetty.util.log.StdErrLog;
import org.junit.Assert;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.is;
@@ -338,7 +337,6 @@
}
@Test
- @Ignore
public void testPushResourceAreSentNonInterleaved() throws Exception
{
final CountDownLatch allExpectedPushesReceivedLatch = new CountDownLatch(4);
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ServerHTTPSPDYTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ServerHTTPSPDYTest.java
index 1c09046..9851995 100644
--- a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ServerHTTPSPDYTest.java
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ServerHTTPSPDYTest.java
@@ -29,7 +29,6 @@
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.Cookie;
@@ -38,6 +37,7 @@
import org.eclipse.jetty.continuation.Continuation;
import org.eclipse.jetty.continuation.ContinuationSupport;
+import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
@@ -57,10 +57,12 @@
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
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;
public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
{
@@ -100,7 +102,9 @@
{
assertTrue(replyInfo.isClose());
Fields replyHeaders = replyInfo.getHeaders();
- assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"));
+ assertThat(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"), is(true));
+ assertThat(replyHeaders.get(HttpHeader.SERVER.asString()), is(notNullValue()));
+ assertThat(replyHeaders.get(HttpHeader.X_POWERED_BY.asString()), is(notNullValue()));
replyLatch.countDown();
}
});
@@ -185,9 +189,9 @@
assertThat("response code is 200 OK", replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value()
.contains("200"), is(true));
assertThat(replyInfo.getHeaders().get("Set-Cookie").values()[0], is(cookie1 + "=\"" + cookie1Value +
- "\""));
+ "\";Version=1"));
assertThat(replyInfo.getHeaders().get("Set-Cookie").values()[1], is(cookie2 + "=\"" + cookie2Value +
- "\""));
+ "\";Version=1"));
replyLatch.countDown();
}
});
@@ -210,6 +214,7 @@
assertEquals("HEAD", httpRequest.getMethod());
assertEquals(path, target);
assertEquals(path, httpRequest.getRequestURI());
+ httpResponse.getWriter().write("body that shouldn't be sent on a HEAD request");
handlerLatch.countDown();
}
}), null);
@@ -226,12 +231,56 @@
assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"));
replyLatch.countDown();
}
+
+ @Override
+ public void onData(Stream stream, DataInfo dataInfo)
+ {
+ fail("HEAD request shouldn't send any data");
+ }
});
assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
}
@Test
+ public void testPOSTWithDelayedContentBody() throws Exception
+ {
+ final String path = "/foo";
+ final CountDownLatch handlerLatch = new CountDownLatch(1);
+ Session session = startClient(version, startHTTPServer(version, new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+ throws IOException, ServletException
+ {
+ // don't read the request body, reply immediately
+ request.setHandled(true);
+ handlerLatch.countDown();
+ }
+ }), null);
+
+ Fields headers = SPDYTestUtils.createHeaders("localhost", connector.getPort(), version, "POST", path);
+ headers.put("content-type", "application/x-www-form-urlencoded");
+ final CountDownLatch replyLatch = new CountDownLatch(1);
+ Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, headers, false, (byte)0),
+ new StreamFrameListener.Adapter()
+ {
+ @Override
+ public void onReply(Stream stream, ReplyInfo replyInfo)
+ {
+ assertTrue(replyInfo.isClose());
+ Fields replyHeaders = replyInfo.getHeaders();
+ assertTrue(replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().contains("200"));
+ replyLatch.countDown();
+ }
+ });
+ stream.data(new StringDataInfo("a", false));
+ assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
+ assertTrue(replyLatch.await(5, TimeUnit.SECONDS));
+ stream.data(new StringDataInfo("b", true));
+ }
+
+ @Test
public void testPOSTWithParameters() throws Exception
{
final String path = "/foo";
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToHTTPTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToHTTPTest.java
index 658c386..556c148 100644
--- a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToHTTPTest.java
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxySPDYToHTTPTest.java
@@ -219,6 +219,8 @@
public void onReply(Stream stream, ReplyInfo replyInfo)
{
Fields headers = replyInfo.getHeaders();
+ assertThat("Trailer header has been filtered by proxy", headers.get("trailer"),
+ is(nullValue()));
assertThat("custom header exists in response", headers.get(header), is(notNullValue()));
replyLatch.countDown();
}
@@ -545,6 +547,8 @@
while ((read = bufferedReader.read()) != -1)
response.getOutputStream().write(read);
+ // add some hop header to be removed on the proxy
+ response.addHeader("Trailer", "bla");
if (responseHeader != null)
response.addHeader(responseHeader, "bar");
if (responseData != null)
diff --git a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/MaxConcurrentStreamTest.java b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/MaxConcurrentStreamTest.java
index 0b41c90..a3fe141 100644
--- a/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/MaxConcurrentStreamTest.java
+++ b/jetty-spdy/spdy-server/src/test/java/org/eclipse/jetty/spdy/server/MaxConcurrentStreamTest.java
@@ -36,13 +36,14 @@
import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Fields;
-import org.junit.Ignore;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
-@Ignore("Fails the build too often. Runs fine always when run alone.")
+@RunWith(JUnit4.class)
public class MaxConcurrentStreamTest extends AbstractTest
{
@Test
@@ -116,7 +117,5 @@
stream.data(new ByteBufferDataInfo(BufferUtil.EMPTY_BUFFER, true));
assertThat("Data has been received on first stream.", dataReceivedLatch.await(5, TimeUnit.SECONDS), is(true));
-
- session.syn(synInfo, null);
}
}
diff --git a/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSON.java b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSON.java
index d405677..894a17f 100644
--- a/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSON.java
+++ b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSON.java
@@ -39,8 +39,7 @@
/**
* JSON Parser and Generator.
- *
- * <p>
+ * <p />
* This class provides some static methods to convert POJOs to and from JSON
* notation. The mapping from JSON to java is:
*
@@ -52,9 +51,7 @@
* null ==> null
* bool ==> Boolean
* </pre>
- *
- * </p>
- * <p>
+
* The java to JSON mapping is:
*
* <pre>
@@ -68,30 +65,27 @@
* Object --> string (dubious!)
* </pre>
*
- * </p>
- * <p>
* The interface {@link JSON.Convertible} may be implemented by classes that
* wish to externalize and initialize specific fields to and from JSON objects.
* Only directed acyclic graphs of objects are supported.
- * </p>
- * <p>
+ * <p />
* The interface {@link JSON.Generator} may be implemented by classes that know
* how to render themselves as JSON and the {@link #toString(Object)} method
* will use {@link JSON.Generator#addJSON(Appendable)} to generate the JSON.
* The class {@link JSON.Literal} may be used to hold pre-generated JSON object.
- * <p>
+ * <p />
* The interface {@link JSON.Convertor} may be implemented to provide static
- * convertors for objects that may be registered with
- * {@link #registerConvertor(Class, org.eclipse.jetty.util.ajax.JSON.Convertor)}
- * . These convertors are looked up by class, interface and super class by
+ * converters for objects that may be registered with
+ * {@link #registerConvertor(Class, Convertor)}.
+ * These converters are looked up by class, interface and super class by
* {@link #getConvertor(Class)}.
- * </p>
- * <p>If a JSON object has a "class" field, then a java class for that name is
- * looked up and the method {@link convertTo(Class,Map)} is used to find a
- * Convertor for that class. If a JSON object has a "x-class" field then a
- * direct lookup for a Convertor for that named x-class is done, so that none
- * java classes may be converted.
- * </p>
+ * <p />
+ * If a JSON object has a "class" field, then a java class for that name is
+ * loaded and the method {@link #convertTo(Class,Map)} is used to find a
+ * {@link JSON.Convertor} for that class.
+ * <p />
+ * If a JSON object has a "x-class" field then a direct lookup for a
+ * {@link JSON.Convertor} for that class name is done (without loading the class).
*/
public class JSON
{
@@ -105,7 +99,6 @@
{
}
- /* ------------------------------------------------------------ */
/**
* @return the initial stringBuffer size to use when creating JSON strings
* (default 1024)
@@ -115,7 +108,6 @@
return _stringBufferSize;
}
- /* ------------------------------------------------------------ */
/**
* @param stringBufferSize
* the initial stringBuffer size to use when creating JSON
@@ -126,7 +118,6 @@
_stringBufferSize = stringBufferSize;
}
- /* ------------------------------------------------------------ */
/**
* Register a {@link Convertor} for a class or interface.
*
@@ -140,19 +131,16 @@
DEFAULT.addConvertor(forClass,convertor);
}
- /* ------------------------------------------------------------ */
public static JSON getDefault()
{
return DEFAULT;
}
- /* ------------------------------------------------------------ */
@Deprecated
public static void setDefault(JSON json)
{
}
- /* ------------------------------------------------------------ */
public static String toString(Object object)
{
StringBuilder buffer = new StringBuilder(DEFAULT.getStringBufferSize());
@@ -160,7 +148,6 @@
return buffer.toString();
}
- /* ------------------------------------------------------------ */
public static String toString(Map object)
{
StringBuilder buffer = new StringBuilder(DEFAULT.getStringBufferSize());
@@ -168,7 +155,6 @@
return buffer.toString();
}
- /* ------------------------------------------------------------ */
public static String toString(Object[] array)
{
StringBuilder buffer = new StringBuilder(DEFAULT.getStringBufferSize());
@@ -176,7 +162,6 @@
return buffer.toString();
}
- /* ------------------------------------------------------------ */
/**
* @param s
* String containing JSON object or array.
@@ -187,7 +172,6 @@
return DEFAULT.parse(new StringSource(s),false);
}
- /* ------------------------------------------------------------ */
/**
* @param s
* String containing JSON object or array.
@@ -200,7 +184,6 @@
return DEFAULT.parse(new StringSource(s),stripOuterComment);
}
- /* ------------------------------------------------------------ */
/**
* @param in
* Reader containing JSON object or array.
@@ -211,7 +194,6 @@
return DEFAULT.parse(new ReaderSource(in),false);
}
- /* ------------------------------------------------------------ */
/**
* @param in
* Reader containing JSON object or array.
@@ -224,7 +206,6 @@
return DEFAULT.parse(new ReaderSource(in),stripOuterComment);
}
- /* ------------------------------------------------------------ */
/**
* @deprecated use {@link #parse(Reader)}
* @param in
@@ -237,7 +218,6 @@
return DEFAULT.parse(new StringSource(IO.toString(in)),false);
}
- /* ------------------------------------------------------------ */
/**
* @deprecated use {@link #parse(Reader, boolean)}
* @param in
@@ -252,7 +232,6 @@
return DEFAULT.parse(new StringSource(IO.toString(in)),stripOuterComment);
}
- /* ------------------------------------------------------------ */
/**
* Convert Object to JSON
*
@@ -267,7 +246,6 @@
return buffer.toString();
}
- /* ------------------------------------------------------------ */
/**
* Convert JSON to Object
*
@@ -287,7 +265,6 @@
append((Appendable)buffer,object);
}
- /* ------------------------------------------------------------ */
/**
* Append object as JSON to string buffer.
*
@@ -301,32 +278,58 @@
try
{
if (object == null)
+ {
buffer.append("null");
- else if (object instanceof Convertible)
- appendJSON(buffer,(Convertible)object);
- else if (object instanceof Generator)
- appendJSON(buffer,(Generator)object);
+ }
+ // Most likely first
else if (object instanceof Map)
+ {
appendMap(buffer,(Map)object);
- else if (object instanceof Collection)
- appendArray(buffer,(Collection)object);
- else if (object.getClass().isArray())
- appendArray(buffer,object);
- else if (object instanceof Number)
- appendNumber(buffer,(Number)object);
- else if (object instanceof Boolean)
- appendBoolean(buffer,(Boolean)object);
- else if (object instanceof Character)
- appendString(buffer,object.toString());
+ }
else if (object instanceof String)
+ {
appendString(buffer,(String)object);
+ }
+ else if (object instanceof Number)
+ {
+ appendNumber(buffer,(Number)object);
+ }
+ else if (object instanceof Boolean)
+ {
+ appendBoolean(buffer,(Boolean)object);
+ }
+ else if (object.getClass().isArray())
+ {
+ appendArray(buffer,object);
+ }
+ else if (object instanceof Character)
+ {
+ appendString(buffer,object.toString());
+ }
+ else if (object instanceof Convertible)
+ {
+ appendJSON(buffer,(Convertible)object);
+ }
+ else if (object instanceof Generator)
+ {
+ appendJSON(buffer,(Generator)object);
+ }
else
{
+ // Check Convertor before Collection to support JSONCollectionConvertor
Convertor convertor = getConvertor(object.getClass());
if (convertor != null)
+ {
appendJSON(buffer,convertor,object);
+ }
+ else if (object instanceof Collection)
+ {
+ appendArray(buffer,(Collection)object);
+ }
else
+ {
appendString(buffer,object.toString());
+ }
}
}
catch (IOException e)
@@ -335,14 +338,12 @@
}
}
- /* ------------------------------------------------------------ */
@Deprecated
public void appendNull(StringBuffer buffer)
{
appendNull((Appendable)buffer);
}
- /* ------------------------------------------------------------ */
public void appendNull(Appendable buffer)
{
try
@@ -355,14 +356,12 @@
}
}
- /* ------------------------------------------------------------ */
@Deprecated
public void appendJSON(final StringBuffer buffer, final Convertor convertor, final Object object)
{
appendJSON((Appendable)buffer,convertor,object);
}
- /* ------------------------------------------------------------ */
public void appendJSON(final Appendable buffer, final Convertor convertor, final Object object)
{
appendJSON(buffer,new Convertible()
@@ -378,14 +377,12 @@
});
}
- /* ------------------------------------------------------------ */
@Deprecated
public void appendJSON(final StringBuffer buffer, Convertible converter)
{
appendJSON((Appendable)buffer,converter);
}
- /* ------------------------------------------------------------ */
public void appendJSON(final Appendable buffer, Convertible converter)
{
ConvertableOutput out=new ConvertableOutput(buffer);
@@ -393,27 +390,23 @@
out.complete();
}
- /* ------------------------------------------------------------ */
@Deprecated
public void appendJSON(StringBuffer buffer, Generator generator)
{
generator.addJSON(buffer);
}
- /* ------------------------------------------------------------ */
public void appendJSON(Appendable buffer, Generator generator)
{
generator.addJSON(buffer);
}
- /* ------------------------------------------------------------ */
@Deprecated
public void appendMap(StringBuffer buffer, Map<?,?> map)
{
appendMap((Appendable)buffer,map);
}
- /* ------------------------------------------------------------ */
public void appendMap(Appendable buffer, Map<?,?> map)
{
try
@@ -444,14 +437,12 @@
}
}
- /* ------------------------------------------------------------ */
@Deprecated
public void appendArray(StringBuffer buffer, Collection collection)
{
- appendArray((Appendable)buffer,collection);
+ appendArray((Appendable)buffer,collection);
}
- /* ------------------------------------------------------------ */
public void appendArray(Appendable buffer, Collection collection)
{
try
@@ -482,14 +473,12 @@
}
}
- /* ------------------------------------------------------------ */
@Deprecated
public void appendArray(StringBuffer buffer, Object array)
{
- appendArray((Appendable)buffer,array);
+ appendArray((Appendable)buffer,array);
}
- /* ------------------------------------------------------------ */
public void appendArray(Appendable buffer, Object array)
{
try
@@ -518,14 +507,12 @@
}
}
- /* ------------------------------------------------------------ */
@Deprecated
public void appendBoolean(StringBuffer buffer, Boolean b)
{
appendBoolean((Appendable)buffer,b);
}
- /* ------------------------------------------------------------ */
public void appendBoolean(Appendable buffer, Boolean b)
{
try
@@ -535,7 +522,7 @@
appendNull(buffer);
return;
}
- buffer.append(b.booleanValue()?"true":"false");
+ buffer.append(b?"true":"false");
}
catch (IOException e)
{
@@ -543,14 +530,12 @@
}
}
- /* ------------------------------------------------------------ */
@Deprecated
public void appendNumber(StringBuffer buffer, Number number)
{
- appendNumber((Appendable)buffer,number);
+ appendNumber((Appendable)buffer,number);
}
- /* ------------------------------------------------------------ */
public void appendNumber(Appendable buffer, Number number)
{
try
@@ -568,14 +553,12 @@
}
}
- /* ------------------------------------------------------------ */
@Deprecated
public void appendString(StringBuffer buffer, String string)
{
- appendString((Appendable)buffer,string);
+ appendString((Appendable)buffer,string);
}
- /* ------------------------------------------------------------ */
public void appendString(Appendable buffer, String string)
{
if (string == null)
@@ -589,37 +572,31 @@
// Parsing utilities
- /* ------------------------------------------------------------ */
protected String toString(char[] buffer, int offset, int length)
{
return new String(buffer,offset,length);
}
- /* ------------------------------------------------------------ */
protected Map<String, Object> newMap()
{
return new HashMap<String, Object>();
}
- /* ------------------------------------------------------------ */
protected Object[] newArray(int size)
{
return new Object[size];
}
- /* ------------------------------------------------------------ */
protected JSON contextForArray()
{
return this;
}
- /* ------------------------------------------------------------ */
protected JSON contextFor(String field)
{
return this;
}
- /* ------------------------------------------------------------ */
protected Object convertTo(Class type, Map map)
{
if (type != null && Convertible.class.isAssignableFrom(type))
@@ -644,7 +621,6 @@
return map;
}
- /* ------------------------------------------------------------ */
/**
* Register a {@link Convertor} for a class or interface.
*
@@ -658,7 +634,6 @@
_convertors.put(forClass.getName(),convertor);
}
- /* ------------------------------------------------------------ */
/**
* Lookup a convertor for a class.
* <p>
@@ -677,7 +652,7 @@
if (convertor == null && this != DEFAULT)
convertor = DEFAULT.getConvertor(cls);
- while (convertor == null && cls != null && cls != Object.class)
+ while (convertor == null && cls != Object.class)
{
Class[] ifs = cls.getInterfaces();
int i = 0;
@@ -692,7 +667,6 @@
return convertor;
}
- /* ------------------------------------------------------------ */
/**
* Register a {@link JSON.Convertor} for a named class or interface.
*
@@ -706,7 +680,6 @@
_convertors.put(name,convertor);
}
- /* ------------------------------------------------------------ */
/**
* Lookup a convertor for a named class.
*
@@ -716,14 +689,12 @@
*/
public Convertor getConvertorFor(String name)
{
- String clsName = name;
- Convertor convertor = _convertors.get(clsName);
+ Convertor convertor = _convertors.get(name);
if (convertor == null && this != DEFAULT)
- convertor = DEFAULT.getConvertorFor(clsName);
+ convertor = DEFAULT.getConvertorFor(name);
return convertor;
}
- /* ------------------------------------------------------------ */
public Object parse(Source source, boolean stripOuterComment)
{
int comment_state = 0; // 0=no comment, 1="/", 2="/*", 3="/* *" -1="//"
@@ -811,7 +782,6 @@
return o;
}
- /* ------------------------------------------------------------ */
public Object parse(Source source)
{
int comment_state = 0; // 0=no comment, 1="/", 2="/*", 3="/* *" -1="//"
@@ -911,13 +881,11 @@
return null;
}
- /* ------------------------------------------------------------ */
protected Object handleUnknown(Source source, char c)
{
throw new IllegalStateException("unknown char '" + c + "'(" + (int)c + ") in " + source);
}
- /* ------------------------------------------------------------ */
protected Object parseObject(Source source)
{
if (source.next() != '{')
@@ -952,10 +920,10 @@
String xclassname = (String)map.get("x-class");
if (xclassname != null)
{
- Convertor c = getConvertorFor(xclassname);
- if (c != null)
- return c.fromJSON(map);
- LOG.warn("no Convertor for xclassname '%s'", xclassname);
+ Convertor c = getConvertorFor(xclassname);
+ if (c != null)
+ return c.fromJSON(map);
+ LOG.warn("No Convertor for x-class '{}'", xclassname);
}
String classname = (String)map.get("class");
@@ -968,14 +936,13 @@
}
catch (ClassNotFoundException e)
{
- LOG.warn("no Class for classname '%s'", classname);
+ LOG.warn("No Class for '{}'", classname);
}
}
-
+
return map;
}
- /* ------------------------------------------------------------ */
protected Object parseArray(Source source)
{
if (source.next() != '[')
@@ -1042,7 +1009,6 @@
throw new IllegalStateException("unexpected end of array");
}
- /* ------------------------------------------------------------ */
protected String parseString(Source source)
{
if (source.next() != '"')
@@ -1110,7 +1076,6 @@
else if (c == '\\')
{
escape = true;
- continue;
}
else if (c == '\"')
{
@@ -1118,7 +1083,9 @@
return toString(scratch,0,i);
}
else
+ {
scratch[i++] = c;
+ }
}
// Missing end quote, but return string anyway ?
@@ -1175,17 +1142,19 @@
else if (c == '\\')
{
escape = true;
- continue;
}
else if (c == '\"')
+ {
break;
+ }
else
+ {
builder.append(c);
+ }
}
return builder.toString();
}
- /* ------------------------------------------------------------ */
public Number parseNumber(Source source)
{
boolean minus = false;
@@ -1270,7 +1239,6 @@
}
- /* ------------------------------------------------------------ */
protected void seekTo(char seek, Source source)
{
while (source.hasNext())
@@ -1287,7 +1255,6 @@
throw new IllegalStateException("Expected '" + seek + "'");
}
- /* ------------------------------------------------------------ */
protected char seekTo(String seek, Source source)
{
while (source.hasNext())
@@ -1306,7 +1273,6 @@
throw new IllegalStateException("Expected one of '" + seek + "'");
}
- /* ------------------------------------------------------------ */
protected static void complete(String seek, Source source)
{
int i = 0;
@@ -1398,7 +1364,7 @@
_buffer.append(c);
QuotedStringTokenizer.quote(_buffer,name);
_buffer.append(':');
- appendNumber(_buffer,new Double(value));
+ appendNumber(_buffer, value);
c = ',';
}
catch (IOException e)
@@ -1444,7 +1410,6 @@
}
}
- /* ------------------------------------------------------------ */
public interface Source
{
boolean hasNext();
@@ -1456,7 +1421,6 @@
char[] scratchBuffer();
}
- /* ------------------------------------------------------------ */
public static class StringSource implements Source
{
private final String string;
@@ -1500,7 +1464,6 @@
}
}
- /* ------------------------------------------------------------ */
public static class ReaderSource implements Source
{
private Reader _reader;
@@ -1567,7 +1530,6 @@
}
- /* ------------------------------------------------------------ */
/**
* JSON Output class for use by {@link Convertible}.
*/
@@ -1587,7 +1549,6 @@
}
/* ------------------------------------------------------------ */
- /* ------------------------------------------------------------ */
/**
* JSON Convertible object. Object can implement this interface in a similar
* way to the {@link Externalizable} interface is used to allow classes to
@@ -1607,7 +1568,6 @@
public void fromJSON(Map object);
}
- /* ------------------------------------------------------------ */
/**
* Static JSON Convertor.
* <p>
@@ -1626,7 +1586,6 @@
public Object fromJSON(Map object);
}
- /* ------------------------------------------------------------ */
/**
* JSON Generator. A class that can add it's JSON representation directly to
* a StringBuffer. This is useful for object instances that are frequently
@@ -1637,7 +1596,6 @@
public void addJSON(Appendable buffer);
}
- /* ------------------------------------------------------------ */
/**
* A Literal JSON generator A utility instance of {@link JSON.Generator}
* that holds a pre-generated string on JSON text.
@@ -1646,7 +1604,6 @@
{
private String _json;
- /* ------------------------------------------------------------ */
/**
* Construct a literal JSON instance for use by
* {@link JSON#toString(Object)}. If {@link Log#isDebugEnabled()} is
diff --git a/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONCollectionConvertor.java b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONCollectionConvertor.java
new file mode 100644
index 0000000..9629e3c
--- /dev/null
+++ b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONCollectionConvertor.java
@@ -0,0 +1,50 @@
+//
+// ========================================================================
+// 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.util.ajax;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import org.eclipse.jetty.util.Loader;
+
+public class JSONCollectionConvertor implements JSON.Convertor
+{
+ public void toJSON(Object obj, JSON.Output out)
+ {
+ out.addClass(obj.getClass());
+ out.add("list", ((Collection)obj).toArray());
+ }
+
+ public Object fromJSON(Map object)
+ {
+ try
+ {
+ Collection result = (Collection)Loader.loadClass(getClass(), (String)object.get("class")).newInstance();
+ Collections.addAll(result, (Object[])object.get("list"));
+ return result;
+ }
+ catch (Exception x)
+ {
+ if (x instanceof RuntimeException)
+ throw (RuntimeException)x;
+ throw new RuntimeException(x);
+ }
+ }
+}
diff --git a/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONDateConvertor.java b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONDateConvertor.java
index a244b1f..e50206b 100644
--- a/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONDateConvertor.java
+++ b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONDateConvertor.java
@@ -40,9 +40,10 @@
public class JSONDateConvertor implements JSON.Convertor
{
private static final Logger LOG = Log.getLogger(JSONDateConvertor.class);
- private boolean _fromJSON;
- DateCache _dateCache;
- SimpleDateFormat _format;
+
+ private final boolean _fromJSON;
+ private final DateCache _dateCache;
+ private final SimpleDateFormat _format;
public JSONDateConvertor()
{
@@ -53,7 +54,7 @@
{
this(DateCache.DEFAULT_FORMAT,TimeZone.getTimeZone("GMT"),fromJSON);
}
-
+
public JSONDateConvertor(String format,TimeZone zone,boolean fromJSON)
{
_dateCache=new DateCache(format);
@@ -62,7 +63,7 @@
_format=new SimpleDateFormat(format);
_format.setTimeZone(zone);
}
-
+
public JSONDateConvertor(String format, TimeZone zone, boolean fromJSON, Locale locale)
{
_dateCache = new DateCache(format, locale);
@@ -71,7 +72,7 @@
_format = new SimpleDateFormat(format, new DateFormatSymbols(locale));
_format.setTimeZone(zone);
}
-
+
public Object fromJSON(Map map)
{
if (!_fromJSON)
@@ -85,7 +86,7 @@
}
catch(Exception e)
{
- LOG.warn(e);
+ LOG.warn(e);
}
return null;
}
diff --git a/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONEnumConvertor.java b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONEnumConvertor.java
index 3d90eaa..54c10c6 100644
--- a/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONEnumConvertor.java
+++ b/jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONEnumConvertor.java
@@ -32,7 +32,7 @@
* If fromJSON is true in the constructor, the JSON generated will
* be of the form {class="com.acme.TrafficLight",value="Green"}
* If fromJSON is false, then only the string value of the enum is generated.
- *
+ *
*
*/
public class JSONEnumConvertor implements JSON.Convertor
@@ -43,8 +43,8 @@
{
try
{
- Class e = Loader.loadClass(getClass(),"java.lang.Enum");
- _valueOf=e.getMethod("valueOf",new Class[]{Class.class,String.class});
+ Class<?> e = Loader.loadClass(getClass(),"java.lang.Enum");
+ _valueOf=e.getMethod("valueOf",Class.class,String.class);
}
catch(Exception e)
{
@@ -56,12 +56,12 @@
{
this(false);
}
-
+
public JSONEnumConvertor(boolean fromJSON)
{
_fromJSON=fromJSON;
}
-
+
public Object fromJSON(Map map)
{
if (!_fromJSON)
@@ -69,11 +69,11 @@
try
{
Class c=Loader.loadClass(getClass(),(String)map.get("class"));
- return _valueOf.invoke(null,new Object[]{c,map.get("value")});
+ return _valueOf.invoke(null,c,map.get("value"));
}
catch(Exception e)
{
- LOG.warn(e);
+ LOG.warn(e);
}
return null;
}
@@ -83,12 +83,11 @@
if (_fromJSON)
{
out.addClass(obj.getClass());
- out.add("value",obj.toString());
+ out.add("value",((Enum)obj).name());
}
else
{
- out.add(obj.toString());
+ out.add(((Enum)obj).name());
}
}
-
}
diff --git a/jetty-util-ajax/src/test/java/org/eclipse/jetty/util/ajax/JSONCollectionConvertorTest.java b/jetty-util-ajax/src/test/java/org/eclipse/jetty/util/ajax/JSONCollectionConvertorTest.java
new file mode 100644
index 0000000..6534483
--- /dev/null
+++ b/jetty-util-ajax/src/test/java/org/eclipse/jetty/util/ajax/JSONCollectionConvertorTest.java
@@ -0,0 +1,112 @@
+//
+// ========================================================================
+// 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.util.ajax;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class JSONCollectionConvertorTest
+{
+ @Test
+ public void testArrayList() throws Exception
+ {
+ List<String> list = new ArrayList<String>();
+ Collections.addAll(list, "one", "two");
+ testList(list);
+ }
+
+ @Test
+ public void testLinkedList() throws Exception
+ {
+ List<String> list = new LinkedList<String>();
+ Collections.addAll(list, "one", "two");
+ testList(list);
+ }
+
+ @Test
+ public void testCopyOnWriteArrayList() throws Exception
+ {
+ List<String> list = new CopyOnWriteArrayList<String>();
+ Collections.addAll(list, "one", "two");
+ testList(list);
+ }
+
+ private void testList(List<String> list1) throws Exception
+ {
+ JSON json = new JSON();
+ json.addConvertor(List.class, new JSONCollectionConvertor());
+
+ Map<String, Object> object1 = new HashMap<String, Object>();
+ String field = "field";
+ object1.put(field, list1);
+
+ String string = json.toJSON(object1);
+ Assert.assertTrue(string.contains(list1.getClass().getName()));
+
+ @SuppressWarnings("unchecked")
+ Map<String, Object> object2 = (Map<String, Object>)json.parse(new JSON.StringSource(string));
+ @SuppressWarnings("unchecked")
+ List<String> list2 = (List<String>)object2.get(field);
+
+ Assert.assertSame(list1.getClass(), list2.getClass());
+ Assert.assertEquals(list1, list2);
+ }
+
+ @Test
+ public void testHashSet() throws Exception
+ {
+ Set<String> set = new HashSet<String>();
+ Collections.addAll(set, "one", "two", "three");
+ testSet(set);
+ }
+
+ @Test
+ public void testTreeSet() throws Exception
+ {
+ Set<String> set = new TreeSet<String>();
+ Collections.addAll(set, "one", "two", "three");
+ testSet(set);
+ }
+
+ private void testSet(Set<String> set1)
+ {
+ JSON json = new JSON();
+ json.addConvertor(Set.class, new JSONCollectionConvertor());
+
+ String string = json.toJSON(set1);
+ Assert.assertTrue(string.contains(set1.getClass().getName()));
+
+ @SuppressWarnings("unchecked")
+ Set<String> set2 = (Set<String>)json.parse(new JSON.StringSource(string));
+
+ Assert.assertSame(set1.getClass(), set2.getClass());
+ Assert.assertEquals(set1, set2);
+ }
+}
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 646f183..05d8380 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
@@ -118,12 +118,10 @@
@Override
public boolean put(String s, V v)
{
- int last=EQ;
- int t=_tree[last];
- int k;
+ int t=0;
int limit = s.length();
- int node=0;
- for(k=0; k < limit; k++)
+ int last=0;
+ for(int k=0; k < limit; k++)
{
char c=s.charAt(k);
if(isCaseInsensitive() && c<128)
@@ -131,49 +129,66 @@
while (true)
{
- if (t==0)
+ int row=ROW_SIZE*t;
+
+ // Do we need to create the new row?
+ if (t==_rows)
{
- node=t=++_rows;
+ _rows++;
if (_rows>=_key.length)
{
_rows--;
return false;
}
- int row=ROW_SIZE*t;
_tree[row]=c;
- _tree[last]=(char)t;
- last=row+EQ;
}
- int row=ROW_SIZE*t;
char n=_tree[row];
int diff=n-c;
if (diff==0)
- {
- node=t;
t=_tree[last=(row+EQ)];
- break;
- }
- if (diff<0)
+ else if (diff<0)
t=_tree[last=(row+LO)];
else
t=_tree[last=(row+HI)];
+
+ // do we need a new row?
+ if (t==0)
+ {
+ t=_rows;
+ _tree[last]=(char)t;
+ }
+
+ if (diff==0)
+ break;
}
}
- _key[node]=v==null?null:s;
- _value[node] = v;
-
+
+ // Do we need to create the new row?
+ if (t==_rows)
+ {
+ _rows++;
+ if (_rows>=_key.length)
+ {
+ _rows--;
+ return false;
+ }
+ }
+
+ // Put the key and value
+ _key[t]=v==null?null:s;
+ _value[t] = v;
+
return true;
}
+
/* ------------------------------------------------------------ */
@Override
- public V get(String s,int offset, int length)
+ public V get(String s,int offset, int len)
{
- int t = _tree[EQ];
- int len = length;
- int i=0;
- while(i<len)
+ int t = 0;
+ for(int i=0; i < len;)
{
char c=s.charAt(offset+i++);
if(isCaseInsensitive() && c<128)
@@ -187,8 +202,6 @@
if (diff==0)
{
- if (i==len)
- return (V)_value[t];
t=_tree[row+EQ];
if (t==0)
return null;
@@ -201,19 +214,17 @@
}
}
- return null;
+ return (V)_value[t];
}
@Override
- public V get(ByteBuffer b, int offset, int length)
+ public V get(ByteBuffer b, int offset, int len)
{
- int t = _tree[EQ];
- int len = length;
- int i=0;
+ int t = 0;
offset+=b.position();
- while(i<len)
+ for(int i=0; i < len;)
{
byte c=(byte)(b.get(offset+i++)&0x7f);
if(isCaseInsensitive())
@@ -227,8 +238,6 @@
if (diff==0)
{
- if (i==len)
- return (V)_value[t];
t=_tree[row+EQ];
if (t==0)
return null;
@@ -240,35 +249,35 @@
return null;
}
}
-
- return null;
+
+ return (V)_value[t];
}
/* ------------------------------------------------------------ */
@Override
public V getBest(String s)
{
- return getBest(_tree[EQ],s,0,s.length());
+ return getBest(0,s,0,s.length());
}
/* ------------------------------------------------------------ */
@Override
public V getBest(String s, int offset, int length)
{
- return getBest(_tree[EQ],s,offset,length);
+ return getBest(0,s,offset,length);
}
/* ------------------------------------------------------------ */
private V getBest(int t,String s,int offset,int len)
{
- int node=0;
- for(int i=0; t!=0 && i<len; i++)
+ int node=t;
+ loop: for(int i=0; i<len; i++)
{
char c=s.charAt(offset+i);
if(isCaseInsensitive() && c<128)
c=StringUtil.lowercases[c];
- while (t!=0)
+ while (true)
{
int row = ROW_SIZE*t;
char n=_tree[row];
@@ -276,25 +285,27 @@
if (diff==0)
{
- node=t;
t=_tree[row+EQ];
+ if (t==0)
+ break loop;
// if this node is a match, recurse to remember
- if (_key[node]!=null)
+ if (_key[t]!=null)
{
+ node=t;
V best=getBest(t,s,offset+i+1,len-i-1);
if (best!=null)
return best;
- return (V)_value[node];
}
-
break;
}
t=_tree[row+hilo(diff)];
+ if (t==0)
+ break loop;
}
}
- return null;
+ return (V)_value[node];
}
@@ -303,21 +314,21 @@
public V getBest(ByteBuffer b, int offset, int len)
{
if (b.hasArray())
- return getBest(_tree[EQ],b.array(),b.arrayOffset()+b.position()+offset,len);
- return getBest(_tree[EQ],b,offset,len);
+ return getBest(0,b.array(),b.arrayOffset()+b.position()+offset,len);
+ return getBest(0,b,offset,len);
}
/* ------------------------------------------------------------ */
private V getBest(int t,byte[] b, int offset, int len)
{
- int node=0;
- for(int i=0; t!=0 && i<len; i++)
+ int node=t;
+ loop: for(int i=0; i<len; i++)
{
byte c=(byte)(b[offset+i]&0x7f);
if(isCaseInsensitive())
c=(byte)StringUtil.lowercases[c];
- while (t!=0)
+ while (true)
{
int row = ROW_SIZE*t;
char n=_tree[row];
@@ -325,40 +336,42 @@
if (diff==0)
{
- node=t;
t=_tree[row+EQ];
+ if (t==0)
+ break loop;
// if this node is a match, recurse to remember
- if (_key[node]!=null)
+ if (_key[t]!=null)
{
+ node=t;
V best=getBest(t,b,offset+i+1,len-i-1);
if (best!=null)
return best;
- return (V)_value[node];
}
-
break;
}
t=_tree[row+hilo(diff)];
+ if (t==0)
+ break loop;
}
}
- return null;
+ return (V)_value[node];
}
/* ------------------------------------------------------------ */
private V getBest(int t,ByteBuffer b, int offset, int len)
{
- int node=0;
+ int node=t;
int o= offset+b.position();
- for(int i=0; t!=0 && i<len; i++)
+ loop: for(int i=0; i<len; i++)
{
byte c=(byte)(b.get(o+i)&0x7f);
if(isCaseInsensitive())
c=(byte)StringUtil.lowercases[c];
- while (t!=0)
+ while (true)
{
int row = ROW_SIZE*t;
char n=_tree[row];
@@ -366,32 +379,34 @@
if (diff==0)
{
- node=t;
t=_tree[row+EQ];
+ if (t==0)
+ break loop;
// if this node is a match, recurse to remember
- if (_key[node]!=null)
+ if (_key[t]!=null)
{
+ node=t;
V best=getBest(t,b,offset+i+1,len-i-1);
if (best!=null)
return best;
- return (V)_value[node];
}
-
break;
}
t=_tree[row+hilo(diff)];
+ if (t==0)
+ break loop;
}
}
- return null;
+ return (V)_value[node];
}
@Override
public String toString()
{
StringBuilder buf = new StringBuilder();
- for (int r=1;r<=_rows;r++)
+ for (int r=0;r<=_rows;r++)
{
if (_key[r]!=null && _value[r]!=null)
{
@@ -416,7 +431,7 @@
{
Set<String> keys = new HashSet<>();
- for (int r=1;r<=_rows;r++)
+ for (int r=0;r<=_rows;r++)
{
if (_key[r]!=null && _value[r]!=null)
keys.add(_key[r]);
@@ -439,10 +454,10 @@
public void dump()
{
- for (int r=0;r<=_rows;r++)
+ 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",
+ System.err.printf("%4d [%s,%d,%d,%d] '%s':%s%n",
r,
(c<' '||c>127)?(""+(int)c):"'"+c+"'",
(int)_tree[r*ROW_SIZE+LO],
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTrie.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTrie.java
index df1571f..99a3443 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTrie.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTrie.java
@@ -249,18 +249,20 @@
if (index>=0)
{
int idx=t*ROW_SIZE+index;
- t=_rowIndex[idx];
- if (t==0)
- return null;
+ int nt=_rowIndex[idx];
+ if (nt==0)
+ break;
+ t=nt;
}
else
{
char[] big = _bigIndex==null?null:_bigIndex[t];
if (big==null)
return null;
- t=big[c];
- if (t==0)
- return null;
+ int nt=big[c];
+ if (nt==0)
+ break;
+ t=nt;
}
// Is the next Trie is a match
@@ -286,18 +288,20 @@
if (index>=0)
{
int idx=t*ROW_SIZE+index;
- t=_rowIndex[idx];
- if (t==0)
- return null;
+ int nt=_rowIndex[idx];
+ if (nt==0)
+ break;
+ t=nt;
}
else
{
char[] big = _bigIndex==null?null:_bigIndex[t];
if (big==null)
return null;
- t=big[c];
- if (t==0)
- return null;
+ int nt=big[c];
+ if (nt==0)
+ break;
+ t=nt;
}
// Is the next Trie is a match
@@ -307,7 +311,7 @@
V best=getBest(t,b,offset+i+1,len-i-1);
if (best!=null)
return best;
- return (V)_value[t];
+ break;
}
}
return (V)_value[t];
@@ -323,18 +327,20 @@
if (index>=0)
{
int idx=t*ROW_SIZE+index;
- t=_rowIndex[idx];
- if (t==0)
- return null;
+ int nt=_rowIndex[idx];
+ if (nt==0)
+ break;
+ t=nt;
}
else
{
char[] big = _bigIndex==null?null:_bigIndex[t];
if (big==null)
return null;
- t=big[c];
- if (t==0)
- return null;
+ int nt=big[c];
+ if (nt==0)
+ break;
+ t=nt;
}
// Is the next Trie is a match
@@ -344,7 +350,7 @@
V best=getBest(t,b,offset+i+1,len-i-1);
if (best!=null)
return best;
- return (V)_value[t];
+ break;
}
}
return (V)_value[t];
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/B64Code.java b/jetty-util/src/main/java/org/eclipse/jetty/util/B64Code.java
index 5af71bc..936984c 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/B64Code.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/B64Code.java
@@ -336,11 +336,33 @@
if (encoded==null)
return null;
+ ByteArrayOutputStream bout = new ByteArrayOutputStream(4*encoded.length()/3);
+ decode(encoded, bout);
+ return bout.toByteArray();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Base 64 decode as described in RFC 2045.
+ * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
+ * @param encoded String to decode.
+ * @param output stream for decoded bytes
+ * @return byte array containing the decoded form of the input.
+ * @throws IllegalArgumentException if the input is not a valid
+ * B64 encoding.
+ */
+ static public void decode (String encoded, ByteArrayOutputStream bout)
+ {
+ if (encoded==null)
+ return;
+
+ if (bout == null)
+ throw new IllegalArgumentException("No outputstream for decoded bytes");
+
int ci=0;
byte nibbles[] = new byte[4];
int s=0;
- ByteArrayOutputStream bout = new ByteArrayOutputStream(4*encoded.length()/3);
-
+
while (ci<encoded.length())
{
char c=encoded.charAt(ci++);
@@ -375,8 +397,9 @@
}
- return bout.toByteArray();
+ return;
}
+
public static void encode(int value,Appendable buf) throws IOException
{
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStreamParser.java b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStreamParser.java
index 1429d08..f47a197 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStreamParser.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStreamParser.java
@@ -20,15 +20,14 @@
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
-import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
@@ -479,7 +478,16 @@
byte[] byteBoundary=(boundary+"--").getBytes(StringUtil.__ISO_8859_1);
// Get first boundary
- String line=((ReadLineInputStream)_in).readLine();
+ String line = null;
+ try
+ {
+ line=((ReadLineInputStream)_in).readLine();
+ }
+ catch (IOException e)
+ {
+ LOG.warn("Badly formatted multipart request");
+ throw e;
+ }
if (line == null)
throw new IOException("Missing content for multipart request");
@@ -502,11 +510,13 @@
// Read each part
boolean lastPart=false;
- String contentDisposition=null;
- String contentType=null;
- String contentTransferEncoding=null;
+
outer:while(!lastPart)
{
+ String contentDisposition=null;
+ String contentType=null;
+ String contentTransferEncoding=null;
+
MultiMap headers = new MultiMap();
while(true)
{
@@ -577,13 +587,21 @@
continue;
}
+ //Have a new Part
+ MultiPart part = new MultiPart(name, filename);
+ part.setHeaders(headers);
+ part.setContentType(contentType);
+ _parts.add(name, part);
+ part.open();
+
+ InputStream partInput = null;
if ("base64".equalsIgnoreCase(contentTransferEncoding))
{
- _in = new Base64InputStream(_in);
+ partInput = new Base64InputStream((ReadLineInputStream)_in);
}
else if ("quoted-printable".equalsIgnoreCase(contentTransferEncoding))
{
- _in = new FilterInputStream(_in)
+ partInput = new FilterInputStream(_in)
{
@Override
public int read() throws IOException
@@ -604,17 +622,10 @@
}
};
}
+ else
+ partInput = _in;
-
-
- //Have a new Part
- MultiPart part = new MultiPart(name, filename);
- part.setHeaders(headers);
- part.setContentType(contentType);
- _parts.add(name, part);
-
- part.open();
-
+
try
{
int state=-2;
@@ -626,33 +637,38 @@
while(true)
{
int b=0;
- while((c=(state!=-2)?state:_in.read())!=-1)
+ while((c=(state!=-2)?state:partInput.read())!=-1)
{
total ++;
if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize())
throw new IllegalStateException("Request exceeds maxRequestSize ("+_config.getMaxRequestSize()+")");
state=-2;
+
// look for CR and/or LF
if(c==13||c==10)
{
if(c==13)
{
- _in.mark(1);
- int tmp=_in.read();
+ partInput.mark(1);
+ int tmp=partInput.read();
if (tmp!=10)
- _in.reset();
+ partInput.reset();
else
state=tmp;
}
break;
}
- // look for boundary
+
+ // Look for boundary
if(b>=0&&b<byteBoundary.length&&c==byteBoundary[b])
+ {
b++;
+ }
else
{
- // this is not a boundary
+ // Got a character not part of the boundary, so we don't have the boundary marker.
+ // Write out as many chars as we matched, then the char we're looking at.
if(cr)
part.write(13);
@@ -667,7 +683,8 @@
part.write(c);
}
}
- // check partial boundary
+
+ // Check for incomplete boundary match, writing out the chars we matched along the way
if((b>0&&b<byteBoundary.length-2)||(b==byteBoundary.length-1))
{
if(cr)
@@ -680,15 +697,18 @@
part.write(byteBoundary,0,b);
b=-1;
}
- // boundary match
+
+ // Boundary match. If we've run out of input or we matched the entire final boundary marker, then this is the last part.
if(b>0||c==-1)
{
+
if(b==byteBoundary.length)
lastPart=true;
if(state==10)
state=-2;
break;
}
+
// handle CR LF
if(cr)
part.write(13);
@@ -764,14 +784,15 @@
private static class Base64InputStream extends InputStream
{
- BufferedReader _in;
+ ReadLineInputStream _in;
String _line;
byte[] _buffer;
int _pos;
- public Base64InputStream (InputStream in)
+
+ public Base64InputStream(ReadLineInputStream rlis)
{
- _in = new BufferedReader(new InputStreamReader(in));
+ _in = rlis;
}
@Override
@@ -779,18 +800,29 @@
{
if (_buffer==null || _pos>= _buffer.length)
{
- _line = _in.readLine();
+ //Any CR and LF will be consumed by the readLine() call.
+ //We need to put them back into the bytes returned from this
+ //method because the parsing of the multipart content uses them
+ //as markers to determine when we've reached the end of a part.
+ _line = _in.readLine();
if (_line==null)
- return -1;
+ return -1; //nothing left
if (_line.startsWith("--"))
- _buffer=(_line+"\r\n").getBytes();
+ _buffer=(_line+"\r\n").getBytes(); //boundary marking end of part
else if (_line.length()==0)
- _buffer="\r\n".getBytes();
+ _buffer="\r\n".getBytes(); //blank line
else
- _buffer=B64Code.decode(_line);
+ {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream((4*_line.length()/3)+2);
+ B64Code.decode(_line, baos);
+ baos.write(13);
+ baos.write(10);
+ _buffer = baos.toByteArray();
+ }
_pos=0;
}
+
return _buffer[_pos++];
}
}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/QuotedStringTokenizer.java b/jetty-util/src/main/java/org/eclipse/jetty/util/QuotedStringTokenizer.java
index 561c674..0931f32 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/QuotedStringTokenizer.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/QuotedStringTokenizer.java
@@ -334,6 +334,32 @@
/* ------------------------------------------------------------ */
/** Quote a string into an Appendable.
+ * Only quotes and backslash are escaped.
+ * @param buffer The Appendable
+ * @param input The String to quote.
+ */
+ public static void quoteOnly(Appendable buffer, String input)
+ {
+ try
+ {
+ buffer.append('"');
+ for (int i = 0; i < input.length(); ++i)
+ {
+ char c = input.charAt(i);
+ if (c == '"' || c == '\\')
+ buffer.append('\\');
+ buffer.append(c);
+ }
+ buffer.append('"');
+ }
+ catch (IOException x)
+ {
+ throw new RuntimeException(x);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Quote a string into an Appendable.
* The characters ", \, \n, \r, \t, \f and \b are escaped
* @param buffer The Appendable
* @param input The String to quote.
@@ -377,38 +403,6 @@
}
}
- /* ------------------------------------------------------------ */
- /** Quote a string into a StringBuffer only if needed.
- * Quotes are forced if any delim characters are present.
- *
- * @param buf The StringBuffer
- * @param s The String to quote.
- * @param delim String of characters that must be quoted.
- * @return true if quoted;
- */
- public static boolean quoteIfNeeded(Appendable buf, String s,String delim)
- {
- for (int i=0;i<s.length();i++)
- {
- char c = s.charAt(i);
- if (delim.indexOf(c)>=0)
- {
- quote(buf,s);
- return true;
- }
- }
-
- try
- {
- buf.append(s);
- return false;
- }
- catch(IOException e)
- {
- throw new RuntimeException(e);
- }
- }
-
/* ------------------------------------------------------------ */
public static String unquoteOnly(String s)
@@ -566,6 +560,12 @@
}
/* ------------------------------------------------------------ */
+ public static boolean isQuoted(String s)
+ {
+ return s!=null && s.length()>0 && s.charAt(0)=='"' && s.charAt(s.length()-1)=='"';
+ }
+
+ /* ------------------------------------------------------------ */
/**
* @return handle double quotes if true
*/
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ReadLineInputStream.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ReadLineInputStream.java
index bb4c9fb..1aeec5f 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/ReadLineInputStream.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ReadLineInputStream.java
@@ -49,6 +49,10 @@
while (true)
{
int b=super.read();
+
+ if (markpos < 0)
+ throw new IOException("Buffer size exceeded: no line terminator");
+
if (b==-1)
{
int m=markpos;
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java
index 29db211..58ede8b 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java
@@ -53,12 +53,14 @@
public final static Charset __UTF8_CHARSET;
public final static Charset __ISO_8859_1_CHARSET;
public final static Charset __UTF16_CHARSET;
+ public final static Charset __US_ASCII_CHARSET;
static
{
__UTF8_CHARSET=Charset.forName(__UTF8);
__ISO_8859_1_CHARSET=Charset.forName(__ISO_8859_1);
__UTF16_CHARSET=Charset.forName(__UTF16);
+ __US_ASCII_CHARSET=Charset.forName("US-ASCII");
CHARSETS.put("UTF-8",__UTF8);
CHARSETS.put("UTF8",__UTF8);
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/TreeTrie.java b/jetty-util/src/main/java/org/eclipse/jetty/util/TreeTrie.java
index 484a642..b82b6f4 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/TreeTrie.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/TreeTrie.java
@@ -70,9 +70,8 @@
public boolean put(String s, V v)
{
TreeTrie<V> t = this;
- int k;
int limit = s.length();
- for(k=0; k < limit; k++)
+ for(int k=0; k < limit; k++)
{
char c=s.charAt(k);
@@ -102,7 +101,6 @@
}
}
t._key=v==null?null:s;
- V old=t._value;
t._value = v;
return true;
}
@@ -182,7 +180,7 @@
if (index>=0)
{
if (t._nextIndex[index] == null)
- return null;
+ break;
t = t._nextIndex[index];
}
else
@@ -196,7 +194,7 @@
n=null;
}
if (n==null)
- return null;
+ break;
t=n;
}
@@ -207,7 +205,7 @@
V best=t.getBest(b,offset+i+1,len-i-1);
if (best!=null)
return best;
- return t._value;
+ break;
}
}
return t._value;
@@ -240,7 +238,7 @@
if (index>=0)
{
if (t._nextIndex[index] == null)
- return null;
+ break;
t = t._nextIndex[index];
}
else
@@ -254,7 +252,7 @@
n=null;
}
if (n==null)
- return null;
+ break;
t=n;
}
@@ -265,7 +263,7 @@
V best=t.getBest(b,offset+i+1,len-i-1);
if (best!=null)
return best;
- return t._value;
+ break;
}
}
return t._value;
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java
index 68de404..d63fe37 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java
@@ -353,7 +353,7 @@
{
byte b = (byte)((c & 0x1f) + ((c >> 6) * 0x19) - 0x10);
if (b<0 || b>15)
- throw new IllegalArgumentException("!hex "+c);
+ throw new NumberFormatException("!hex "+c);
return b;
}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java b/jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java
index fed74d5..dfe0203 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java
@@ -323,7 +323,13 @@
{
i++;
if (i+4<end)
- buffer.getStringBuilder().append(Character.toChars((convertHexDigit(raw[++i])<<12) +(convertHexDigit(raw[++i])<<8) + (convertHexDigit(raw[++i])<<4) +convertHexDigit(raw[++i])));
+ {
+ byte top=raw[++i];
+ byte hi=raw[++i];
+ byte lo=raw[++i];
+ byte bot=raw[++i];
+ buffer.getStringBuilder().append(Character.toChars((convertHexDigit(top)<<12) +(convertHexDigit(hi)<<8) + (convertHexDigit(lo)<<4) +convertHexDigit(bot)));
+ }
else
{
buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT);
@@ -331,7 +337,11 @@
}
}
else
- buffer.append((byte)((convertHexDigit(raw[++i])<<4) + convertHexDigit(raw[++i])));
+ {
+ byte hi=raw[++i];
+ byte lo=raw[++i];
+ buffer.append((byte)((convertHexDigit(hi)<<4) + convertHexDigit(lo)));
+ }
}
else
{
@@ -350,6 +360,12 @@
LOG.warn(e.toString());
LOG.debug(e);
}
+ catch(NumberFormatException e)
+ {
+ buffer.append(Utf8Appendable.REPLACEMENT_UTF8,0,3);
+ LOG.warn(e.toString());
+ LOG.debug(e);
+ }
}
if (key != null)
@@ -552,6 +568,12 @@
LOG.warn(e.toString());
LOG.debug(e);
}
+ catch(NumberFormatException e)
+ {
+ buffer.append(Utf8Appendable.REPLACEMENT_UTF8,0,3);
+ LOG.warn(e.toString());
+ LOG.debug(e);
+ }
if (maxLength>=0 && (++totalLength > maxLength))
throw new IllegalStateException("Form too large");
}
@@ -798,9 +820,10 @@
LOG.warn(e.toString());
LOG.debug(e);
}
- catch(NumberFormatException nfe)
+ catch(NumberFormatException e)
{
- LOG.debug(nfe);
+ LOG.warn(e.toString());
+ LOG.debug(e);
buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT);
}
}
@@ -870,32 +893,33 @@
{
if ('u'==encoded.charAt(offset+i+1))
{
- if (i+6<length)
- {
- int o=offset+i+2;
- i+=6;
- String unicode = new String(Character.toChars(TypeUtil.parseInt(encoded,o,4,16)));
- byte[] reencoded = unicode.getBytes(charset);
- System.arraycopy(reencoded,0,ba,n,reencoded.length);
- n+=reencoded.length;
- }
- else
- {
- ba[n++] = (byte)'?';
- i=length;
- }
+ if (i+6<length)
+ {
+ int o=offset+i+2;
+ i+=6;
+ String unicode = new String(Character.toChars(TypeUtil.parseInt(encoded,o,4,16)));
+ byte[] reencoded = unicode.getBytes(charset);
+ System.arraycopy(reencoded,0,ba,n,reencoded.length);
+ n+=reencoded.length;
}
else
{
+ ba[n++] = (byte)'?';
+ i=length;
+ }
+ }
+ else
+ {
int o=offset+i+1;
i+=3;
ba[n]=(byte)TypeUtil.parseInt(encoded,o,2,16);
n++;
}
}
- catch(NumberFormatException nfe)
+ catch(Exception e)
{
- LOG.ignore(nfe);
+ LOG.warn(e.toString());
+ LOG.debug(e);
ba[n++] = (byte)'?';
}
}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8Appendable.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8Appendable.java
index bce0746..616fa76 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8Appendable.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8Appendable.java
@@ -52,6 +52,7 @@
{
protected static final Logger LOG = Log.getLogger(Utf8Appendable.class);
public static final char REPLACEMENT = '\ufffd';
+ public static final byte[] REPLACEMENT_UTF8 = new byte[] {(byte)0xEF,(byte)0xBF,(byte)0xBD };
private static final int UTF8_ACCEPT = 0;
private static final int UTF8_REJECT = 12;
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/StdErrLog.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/StdErrLog.java
index 2bbabfb..1439b8a 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/log/StdErrLog.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/StdErrLog.java
@@ -82,6 +82,11 @@
* long form, fully qualified class names. when false, use abbreviated package names<br/>
* Default: false
* </dd>
+ * <dt>org.eclipse.jetty.util.log.stderr.ESCAPE=(true|false)</dt>
+ * <dd>Global Configuration, when true output logging events to STDERR are always
+ * escaped so that control characters are replaced with '?"; '\r' with '<' and '\n' replaced '|'<br/>
+ * Default: true
+ * </dd>
* </dl>
*/
@ManagedObject("Jetty StdErr Logging Implementation")
@@ -94,6 +99,7 @@
private final static boolean __source = Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.SOURCE",
Log.__props.getProperty("org.eclipse.jetty.util.log.stderr.SOURCE","false")));
private final static boolean __long = Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.stderr.LONG","false"));
+ private final static boolean __escape = Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.stderr.ESCAPE","true"));
static
{
@@ -642,29 +648,34 @@
private void escape(StringBuilder builder, String string)
{
- for (int i = 0; i < string.length(); ++i)
+ if (__escape)
{
- char c = string.charAt(i);
- if (Character.isISOControl(c))
+ for (int i = 0; i < string.length(); ++i)
{
- if (c == '\n')
+ char c = string.charAt(i);
+ if (Character.isISOControl(c))
{
- builder.append('|');
- }
- else if (c == '\r')
- {
- builder.append('<');
+ if (c == '\n')
+ {
+ builder.append('|');
+ }
+ else if (c == '\r')
+ {
+ builder.append('<');
+ }
+ else
+ {
+ builder.append('?');
+ }
}
else
{
- builder.append('?');
+ builder.append(c);
}
}
- else
- {
- builder.append(c);
- }
}
+ else
+ builder.append(string);
}
private void format(StringBuilder buffer, Throwable thrown)
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java
index 6831566..3523105 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java
@@ -114,6 +114,7 @@
}
catch(Exception e)
{
+ LOG.warn(e.toString());
LOG.debug(Log.EXCEPTION,e);
return new BadResource(url,e.toString());
}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java
index 0febc24..7c8b5e6 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java
@@ -80,7 +80,7 @@
public QueuedThreadPool(@Name("maxThreads") int maxThreads, @Name("minThreads") int minThreads, @Name("idleTimeout")int idleTimeout)
{
- this(maxThreads, minThreads, 60000,null);
+ this(maxThreads, minThreads, idleTimeout, null);
}
public QueuedThreadPool(@Name("maxThreads") int maxThreads, @Name("minThreads") int minThreads, @Name("idleTimeout") int idleTimeout, @Name("queue") BlockingQueue<Runnable> queue)
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java
index a706e73..a352d12 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java
@@ -18,17 +18,8 @@
package org.eclipse.jetty.util;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.not;
-import static org.hamcrest.Matchers.notNullValue;
-import static org.hamcrest.Matchers.nullValue;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -530,6 +521,34 @@
}
@Test
+ public void testBufferOverflowNoCRLF () throws Exception
+ {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ baos.write("--AaB03x".getBytes());
+ for (int i=0; i< 8500; i++) //create content that will overrun default buffer size of BufferedInputStream
+ {
+ baos.write('a');
+ }
+
+ MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
+ MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(baos.toByteArray()),
+ _contentType,
+ config,
+ _tmpDir);
+ mpis.setDeleteOnExit(true);
+ try
+ {
+ mpis.getParts();
+ fail ("Multipart buffer overrun");
+ }
+ catch (IOException e)
+ {
+ assertTrue(e.getMessage().startsWith("Buffer size exceeded"));
+ }
+
+ }
+
+
public void testCharsetEncoding () throws Exception
{
String contentType = "multipart/form-data; boundary=TheBoundary; charset=ISO-8859-1";
@@ -724,6 +743,98 @@
assertEquals(5, p.getSize());
}
+ @Test
+ public void testBase64EncodedContent () throws Exception
+ {
+ String contentWithEncodedPart =
+ "--AaB03x\r\n"+
+ "Content-disposition: form-data; name=\"other\"\r\n"+
+ "Content-Type: text/plain\r\n"+
+ "\r\n"+
+ "other" + "\r\n"+
+ "--AaB03x\r\n"+
+ "Content-disposition: form-data; name=\"stuff\"; filename=\"stuff.txt\"\r\n"+
+ "Content-Transfer-Encoding: base64\r\n"+
+ "Content-Type: application/octet-stream\r\n"+
+ "\r\n"+
+ B64Code.encode("hello jetty") + "\r\n"+
+ "--AaB03x\r\n"+
+ "Content-disposition: form-data; name=\"final\"\r\n"+
+ "Content-Type: text/plain\r\n"+
+ "\r\n"+
+ "the end" + "\r\n"+
+ "--AaB03x--\r\n";
+
+ MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
+ MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(contentWithEncodedPart.getBytes()),
+ _contentType,
+ config,
+ _tmpDir);
+ mpis.setDeleteOnExit(true);
+ Collection<Part> parts = mpis.getParts();
+ assertEquals(3, parts.size());
+
+ Part p1 = mpis.getPart("other");
+ assertNotNull(p1);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ IO.copy(p1.getInputStream(), baos);
+ assertEquals("other", baos.toString("US-ASCII"));
+
+ Part p2 = mpis.getPart("stuff");
+ assertNotNull(p2);
+ baos = new ByteArrayOutputStream();
+ IO.copy(p2.getInputStream(), baos);
+ assertEquals("hello jetty", baos.toString("US-ASCII"));
+
+ Part p3 = mpis.getPart("final");
+ assertNotNull(p3);
+ baos = new ByteArrayOutputStream();
+ IO.copy(p3.getInputStream(), baos);
+ assertEquals("the end", baos.toString("US-ASCII"));
+ }
+
+ @Test
+ public void testQuotedPrintableEncoding () throws Exception
+ {
+ String contentWithEncodedPart =
+ "--AaB03x\r\n"+
+ "Content-disposition: form-data; name=\"other\"\r\n"+
+ "Content-Type: text/plain\r\n"+
+ "\r\n"+
+ "other" + "\r\n"+
+ "--AaB03x\r\n"+
+ "Content-disposition: form-data; name=\"stuff\"; filename=\"stuff.txt\"\r\n"+
+ "Content-Transfer-Encoding: quoted-printable\r\n"+
+ "Content-Type: text/plain\r\n"+
+ "\r\n"+
+ "truth=3Dbeauty" + "\r\n"+
+ "--AaB03x--\r\n";
+ MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
+ MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(contentWithEncodedPart.getBytes()),
+ _contentType,
+ config,
+ _tmpDir);
+ mpis.setDeleteOnExit(true);
+ Collection<Part> parts = mpis.getParts();
+ assertEquals(2, parts.size());
+
+ Part p1 = mpis.getPart("other");
+ assertNotNull(p1);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ IO.copy(p1.getInputStream(), baos);
+ assertEquals("other", baos.toString("US-ASCII"));
+
+ Part p2 = mpis.getPart("stuff");
+ assertNotNull(p2);
+ baos = new ByteArrayOutputStream();
+ IO.copy(p2.getInputStream(), baos);
+ assertEquals("truth=beauty", baos.toString("US-ASCII"));
+ }
+
+
+
+
+
private String createMultipartRequestString(String filename)
{
int length = filename.length();
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/QuotedStringTokenizerTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/QuotedStringTokenizerTest.java
index a4290e4..692f61b 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/QuotedStringTokenizerTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/QuotedStringTokenizerTest.java
@@ -112,14 +112,6 @@
QuotedStringTokenizer.quote(buf,"abcefg\"");
assertEquals("\"abcefg\\\"\"",buf.toString());
- buf.setLength(0);
- QuotedStringTokenizer.quoteIfNeeded(buf,"abc \n efg","\"\\\n\r\t\f\b%+ ;=");
- assertEquals("\"abc \\n efg\"",buf.toString());
-
- buf.setLength(0);
- QuotedStringTokenizer.quoteIfNeeded(buf,"abcefg","\"\\\n\r\t\f\b%+ ;=");
- assertEquals("abcefg",buf.toString());
-
}
/*
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/TrieTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/TrieTest.java
index 8fe5f6e..d1ff2fb 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/TrieTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/TrieTest.java
@@ -23,6 +23,7 @@
import java.util.Arrays;
import java.util.Collection;
+import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -62,6 +63,21 @@
trie.put("foo-bar",6);
trie.put("foo+bar",7);
trie.put("HELL4",8);
+ trie.put("",9);
+ }
+
+ @Test
+ public void testKeySet() throws Exception
+ {
+ Assert.assertTrue(trie.keySet().contains("hello"));
+ Assert.assertTrue(trie.keySet().contains("He"));
+ Assert.assertTrue(trie.keySet().contains("HELL"));
+ Assert.assertTrue(trie.keySet().contains("wibble"));
+ Assert.assertTrue(trie.keySet().contains("Wobble"));
+ Assert.assertTrue(trie.keySet().contains("foo-bar"));
+ Assert.assertTrue(trie.keySet().contains("foo+bar"));
+ Assert.assertTrue(trie.keySet().contains("HELL4"));
+ Assert.assertTrue(trie.keySet().contains(""));
}
@Test
@@ -82,6 +98,8 @@
Assert.assertEquals(5,trie.get("wobble").intValue());
Assert.assertEquals(6,trie.get("Foo-bar").intValue());
Assert.assertEquals(7,trie.get("FOO+bar").intValue());
+ Assert.assertEquals(8,trie.get("HELL4").intValue());
+ Assert.assertEquals(9,trie.get("").intValue());
Assert.assertEquals(null,trie.get("helloworld"));
Assert.assertEquals(null,trie.get("Help"));
@@ -151,6 +169,7 @@
Assert.assertEquals(3,trie.getBest(StringUtil.getUtf8Bytes("xHELLxxxxx"),1,8).intValue());
Assert.assertEquals(6,trie.getBest(StringUtil.getUtf8Bytes("xfoo-BARxx"),1,8).intValue());
Assert.assertEquals(8,trie.getBest(StringUtil.getUtf8Bytes("xHELL4xxxx"),1,8).intValue());
+ Assert.assertEquals(9,trie.getBest(StringUtil.getUtf8Bytes("xZZZZZxxxx"),1,8).intValue());
}
@Test
@@ -167,6 +186,7 @@
Assert.assertEquals(3,trie.getBest(BufferUtil.toBuffer("xHELLxxxxx"),1,8).intValue());
Assert.assertEquals(6,trie.getBest(BufferUtil.toBuffer("xfoo-BARxx"),1,8).intValue());
Assert.assertEquals(8,trie.getBest(BufferUtil.toBuffer("xHELL4xxxx"),1,8).intValue());
+ Assert.assertEquals(9,trie.getBest(BufferUtil.toBuffer("xZZZZZxxxx"),1,8).intValue());
ByteBuffer buffer = (ByteBuffer)BufferUtil.toBuffer("xhelloxxxxxxx").position(2);
Assert.assertEquals(1,trie.getBest(buffer,-1,10).intValue());
@@ -186,6 +206,7 @@
Assert.assertEquals(3,trie.getBest(BufferUtil.toDirectBuffer("xHELLxxxxx"),1,8).intValue());
Assert.assertEquals(6,trie.getBest(BufferUtil.toDirectBuffer("xfoo-BARxx"),1,8).intValue());
Assert.assertEquals(8,trie.getBest(BufferUtil.toDirectBuffer("xHELL4xxxx"),1,8).intValue());
+ Assert.assertEquals(9,trie.getBest(BufferUtil.toDirectBuffer("xZZZZZxxxx"),1,8).intValue());
ByteBuffer buffer = (ByteBuffer)BufferUtil.toDirectBuffer("xhelloxxxxxxx").position(2);
Assert.assertEquals(1,trie.getBest(buffer,-1,10).intValue());
diff --git a/pom.xml b/pom.xml
index aebaa96..a8b9d44 100644
--- a/pom.xml
+++ b/pom.xml
@@ -169,7 +169,7 @@
</configuration>
</execution>
<execution>
- <id>ban-junit.jar</id>
+ <id>ban-junit-dep.jar</id>
<goals>
<goal>enforce</goal>
</goals>
@@ -177,10 +177,10 @@
<rules>
<bannedDependencies>
<excludes>
- <exclude>junit:junit:*:jar</exclude>
+ <exclude>junit:junit-dep:*:jar</exclude>
</excludes>
<searchTransitive>true</searchTransitive>
- <message>We use junit-dep.jar, not junit.jar (as the standard junit.jar aggregates too many 3rd party libs inside of it)</message>
+ <message>We use junit.jar, not junit-dep.jar (as of junit 4.11, hamcrest is no longer embedded)</message>
</bannedDependencies>
</rules>
</configuration>
@@ -451,10 +451,10 @@
<module>jetty-distribution</module>
<module>jetty-runner</module>
<module>jetty-monitor</module>
+ <module>jetty-http-spi</module>
<!-- modules that need fixed and added back, or simply dropped and not maintained
<module>jetty-rhttp</module>
- <module>jetty-http-spi</module>
-->
<module>jetty-overlay-deployer</module>
</modules>
@@ -543,7 +543,7 @@
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>
- <version>2.3</version>
+ <version>2.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
@@ -565,7 +565,7 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
- <version>4.8.1</version>
+ <version>4.11</version>
</dependency>
-->
<dependency>
@@ -576,17 +576,17 @@
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
- <version>1.2.1</version>
+ <version>1.3</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
- <version>1.2.1</version>
+ <version>1.3</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
- <version>1.8.5</version>
+ <version>1.9.5</version>
<exclusions>
<exclusion>
<groupId>junit</groupId>
diff --git a/tests/test-continuation/src/test/java/org/eclipse/jetty/continuation/ContinuationTest.java b/tests/test-continuation/src/test/java/org/eclipse/jetty/continuation/ContinuationTest.java
index d0df8d5..2ca10a0 100644
--- a/tests/test-continuation/src/test/java/org/eclipse/jetty/continuation/ContinuationTest.java
+++ b/tests/test-continuation/src/test/java/org/eclipse/jetty/continuation/ContinuationTest.java
@@ -20,15 +20,24 @@
import java.io.IOException;
import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.Assert;
import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.RequestLog;
+import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -41,19 +50,27 @@
protected ServletHandler _servletHandler;
protected ServerConnector _connector;
FilterHolder _filter;
+ protected List<String> _log = new ArrayList<String>();
@Before
public void setUp() throws Exception
{
_connector = new ServerConnector(_server);
_server.setConnectors(new Connector[]{ _connector });
+
+ _log.clear();
+ RequestLogHandler requestLogHandler = new RequestLogHandler();
+ requestLogHandler.setRequestLog(new Log());
+ _server.setHandler(requestLogHandler);
+
ServletContextHandler servletContext = new ServletContextHandler(ServletContextHandler.NO_SECURITY|ServletContextHandler.NO_SESSIONS);
- _server.setHandler(servletContext);
+ requestLogHandler.setHandler(servletContext);
+
_servletHandler=servletContext.getServletHandler();
ServletHolder holder=new ServletHolder(_servlet);
holder.setAsyncSupported(true);
_servletHandler.addServletWithMapping(holder,"/");
-
+
_server.start();
_port=_connector.getLocalPort();
}
@@ -61,6 +78,9 @@
@After
public void tearDown() throws Exception
{
+ Assert.assertEquals(1,_log.size());
+ Assert.assertTrue(_log.get(0).startsWith("200 "));
+ Assert.assertTrue(_log.get(0).endsWith(" /"));
_server.stop();
}
@@ -172,4 +192,12 @@
return IO.toString(in);
}
+ class Log extends AbstractLifeCycle implements RequestLog
+ {
+ public void log(Request request, Response response)
+ {
+ _log.add(response.getStatus()+" "+response.getContentCount()+" "+request.getRequestURI());
+ }
+
+ }
}
diff --git a/tests/test-integration/pom.xml b/tests/test-integration/pom.xml
index fb5784a..d3232a3 100644
--- a/tests/test-integration/pom.xml
+++ b/tests/test-integration/pom.xml
@@ -20,7 +20,7 @@
<parent>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>tests-parent</artifactId>
- <version>9.0.0-SNAPSHOT</version>
+ <version>9.0.5-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>test-integration</artifactId>
@@ -111,8 +111,6 @@
<version>${project.version}</version>
</dependency>
<dependency>
- </dependency>
- <dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>
<scope>test</scope>
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/DefaultHandlerTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/DefaultHandlerTest.java
index 96d0556..f6baafa 100644
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/DefaultHandlerTest.java
+++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/DefaultHandlerTest.java
@@ -18,6 +18,8 @@
package org.eclipse.jetty.test;
+import static org.junit.Assert.*;
+
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.InetAddress;
@@ -27,9 +29,9 @@
import java.util.List;
import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.test.support.TestableJettyServer;
-import org.eclipse.jetty.test.support.rawhttp.HttpRequestTester;
-import org.eclipse.jetty.test.support.rawhttp.HttpResponseTester;
import org.eclipse.jetty.test.support.rawhttp.HttpSocketImpl;
import org.eclipse.jetty.test.support.rawhttp.HttpTesting;
import org.eclipse.jetty.util.IO;
@@ -55,6 +57,7 @@
server = new TestableJettyServer();
server.setScheme(HttpScheme.HTTP.asString());
server.addConfiguration("DefaultHandler.xml");
+ server.addConfiguration("NIOHttp.xml");
server.load();
server.start();
@@ -107,14 +110,15 @@
// Collect response
String rawResponse = IO.toString(sock.getInputStream());
DEBUG("--raw-response--\n" + rawResponse);
- HttpResponseTester response = new HttpResponseTester();
- response.parse(rawResponse);
+
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
- response.assertStatusOK();
+ assertEquals(HttpStatus.OK_200, response.getStatus());
- response.assertBody("ABCDEFGHIJKLMNOPQRSTUVWXYZ\n");
+ assertTrue(response.getContent().contains("ABCDEFGHIJKLMNOPQRSTUVWXYZ\n"));
}
+ /*
@Test
public void testMultiGET_Raw() throws Exception
{
@@ -131,38 +135,43 @@
rawRequests.append("\r\n");
HttpTesting http = new HttpTesting(new HttpSocketImpl(),serverPort);
+
- List<HttpResponseTester> responses = http.requests(rawRequests);
+ List<HttpTester.Response> responses = http.requests(rawRequests);
- HttpResponseTester response = responses.get(0);
- response.assertStatusOK();
- response.assertBody("ABCDEFGHIJKLMNOPQRSTUVWXYZ\n");
+ HttpTester.Response response = responses.get(0);
+ assertEquals(HttpStatus.OK_200, response.getStatus());
+ assertTrue(response.getContent().contains("ABCDEFGHIJKLMNOPQRSTUVWXYZ\n"));
response = responses.get(1);
- response.assertStatusOK();
- response.assertBody("Host=Default\nResource=R1\n");
+ assertEquals(HttpStatus.OK_200, response.getStatus());
+ assertTrue(response.getContent().contains("Host=Default\nResource=R1\n"));
response = responses.get(2);
- response.assertStatusOK();
- response.assertBody("Host=Default\nResource=R1\n");
+ assertEquals(HttpStatus.OK_200, response.getStatus());
+ assertTrue(response.getContent().contains("Host=Default\nResource=R1\n"));
}
+ */
+
+
+
@Test
public void testGET_HttpTesting() throws Exception
{
- HttpRequestTester request = new HttpRequestTester();
+ HttpTester.Request request = HttpTester.newRequest();
request.setMethod("GET");
request.setURI("/tests/alpha.txt");
- request.addHeader("Host","localhost");
- request.addHeader("Connection","close");
+ request.put("Host","localhost");
+ request.put("Connection","close");
// request.setContent(null);
HttpTesting testing = new HttpTesting(new HttpSocketImpl(),serverPort);
- HttpResponseTester response = testing.request(request);
+ HttpTester.Response response = testing.request(request);
- response.assertStatusOK();
- response.assertContentType("text/plain");
- response.assertBody("ABCDEFGHIJKLMNOPQRSTUVWXYZ\n");
+ assertEquals(HttpStatus.OK_200, response.getStatus());
+ assertEquals("text/plain", response.get("Content-Type"));
+ assertTrue(response.getContent().contains("ABCDEFGHIJKLMNOPQRSTUVWXYZ\n"));
}
private void DEBUG(String msg)
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/DigestPostTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/DigestPostTest.java
index 2432ae9..4911cd2 100644
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/DigestPostTest.java
+++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/DigestPostTest.java
@@ -21,6 +21,7 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.net.Socket;
+import java.net.URI;
import java.security.MessageDigest;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
@@ -230,14 +231,15 @@
try
{
AuthenticationStore authStore = client.getAuthenticationStore();
- authStore.addAuthentication(new DigestAuthentication(srvUrl, "test", "testuser", "password"));
+ authStore.addAuthentication(new DigestAuthentication(new URI(srvUrl), "test", "testuser", "password"));
client.start();
Request request = client.newRequest(srvUrl);
request.method(HttpMethod.POST);
request.content(new BytesContentProvider(__message.getBytes("UTF8")));
_received=null;
- ContentResponse response = request.send().get(5, TimeUnit.SECONDS);
+ request = request.timeout(5, TimeUnit.SECONDS);
+ ContentResponse response = request.send();
Assert.assertEquals(__message,_received);
Assert.assertEquals(200,response.getStatus());
}
@@ -255,7 +257,7 @@
try
{
AuthenticationStore authStore = client.getAuthenticationStore();
- authStore.addAuthentication(new DigestAuthentication(srvUrl, "test", "testuser", "password"));
+ authStore.addAuthentication(new DigestAuthentication(new URI(srvUrl), "test", "testuser", "password"));
client.start();
String sent = IO.toString(new FileInputStream("src/test/resources/message.txt"));
@@ -264,7 +266,8 @@
request.method(HttpMethod.POST);
request.content(new StringContentProvider(sent));
_received=null;
- ContentResponse response = request.send().get(5, TimeUnit.SECONDS);
+ request = request.timeout(5, TimeUnit.SECONDS);
+ ContentResponse response = request.send();
Assert.assertEquals(200,response.getStatus());
Assert.assertEquals(sent,_received);
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/jsp/JspAndDefaultWithAliasesTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/jsp/JspAndDefaultWithAliasesTest.java
index 82ffc33..1cf3100 100644
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/jsp/JspAndDefaultWithAliasesTest.java
+++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/jsp/JspAndDefaultWithAliasesTest.java
@@ -30,8 +30,8 @@
import java.util.List;
import org.eclipse.jetty.security.HashLoginService;
+import org.eclipse.jetty.server.NetworkConnector;
import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
@@ -67,6 +67,7 @@
// @formatter:off
data.add(new String[] { "false","/dump.jsp" });
+ data.add(new String[] { "false","/dump.jsp/" });
data.add(new String[] { "true", "/dump.jsp%00" });
data.add(new String[] { "false","/dump.jsp%00/" });
data.add(new String[] { "false","/dump.jsp%00x/dump.jsp" });
@@ -86,10 +87,7 @@
@BeforeClass
public static void startServer() throws Exception
{
- server = new Server();
- SelectChannelConnector connector = new SelectChannelConnector();
- connector.setPort(0);
- server.addConnector(connector);
+ server = new Server(0);
// Configure LoginService
HashLoginService login = new HashLoginService();
@@ -118,8 +116,10 @@
server.setHandler(context);
server.start();
+
+ int port = ((NetworkConnector)server.getConnectors()[0]).getLocalPort();
- serverURI = new URI("http://localhost:" + connector.getLocalPort() + "/");
+ serverURI = new URI("http://localhost:" + port + "/");
}
@AfterClass
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/jsp/JspAndDefaultWithoutAliasesTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/jsp/JspAndDefaultWithoutAliasesTest.java
index ab9f484..4cb6b58 100644
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/jsp/JspAndDefaultWithoutAliasesTest.java
+++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/jsp/JspAndDefaultWithoutAliasesTest.java
@@ -30,8 +30,8 @@
import java.util.List;
import org.eclipse.jetty.security.HashLoginService;
+import org.eclipse.jetty.server.NetworkConnector;
import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
@@ -68,6 +68,7 @@
// @formatter:off
data.add(new Object[] { "/dump.jsp" });
+ data.add(new Object[] { "/dump.jsp/" });
data.add(new Object[] { "/dump.jsp%00" });
data.add(new Object[] { "/dump.jsp%00x" });
data.add(new Object[] { "/dump.jsp%00x/dump.jsp" });
@@ -87,10 +88,7 @@
@BeforeClass
public static void startServer() throws Exception
{
- server = new Server();
- SelectChannelConnector connector = new SelectChannelConnector();
- connector.setPort(0);
- server.addConnector(connector);
+ server = new Server(0);
// Configure LoginService
HashLoginService login = new HashLoginService();
@@ -119,8 +117,9 @@
server.setHandler(context);
server.start();
-
- serverURI = new URI("http://localhost:" + connector.getLocalPort() + "/");
+
+ int port = ((NetworkConnector)server.getConnectors()[0]).getLocalPort();
+ serverURI = new URI("http://localhost:" + port + "/");
}
@AfterClass
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616BaseTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616BaseTest.java
index afabaf2..f9b5754 100644
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616BaseTest.java
+++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/rfcs/RFC2616BaseTest.java
@@ -33,9 +33,9 @@
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.test.support.StringUtil;
import org.eclipse.jetty.test.support.TestableJettyServer;
-import org.eclipse.jetty.test.support.rawhttp.HttpResponseTester;
import org.eclipse.jetty.test.support.rawhttp.HttpSocket;
import org.eclipse.jetty.test.support.rawhttp.HttpTesting;
import org.eclipse.jetty.toolchain.test.FS;
@@ -56,8 +56,6 @@
/** STRICT RFC TESTS */
private static final boolean STRICT = false;
private static TestableJettyServer server;
- private List<HttpResponseTester> responses;
- private HttpResponseTester response;
private HttpTesting http;
class TestFile
@@ -167,8 +165,9 @@
req1.append("123\r\n\r\n");
req1.append("0;\r\n\r\n");
- response = http.request(req1);
- response.assertStatus("3.6 Transfer Coding / Bad 400",HttpStatus.BAD_REQUEST_400);
+ HttpTester.Response response = http.request(req1);
+
+ assertEquals("3.6 Transfer Coding / Bad 400",HttpStatus.BAD_REQUEST_400,response.getStatus());
}
/**
@@ -208,20 +207,20 @@
req2.append("Connection: close\n");
req2.append("\n");
- responses = http.requests(req2);
+ List<HttpTester.Response> responses = http.requests(req2);
Assert.assertEquals("Response Count",3,responses.size());
- response = responses.get(0); // Response 1
- response.assertStatusOK("3.6.1 Transfer Codings / Response 1 Code");
- response.assertBody("3.6.1 Transfer Codings / Chunked String","12345\n");
+ HttpTester.Response response = responses.get(0); // Response 1
+ assertEquals("3.6.1 Transfer Codings / Response 1 Code", HttpStatus.OK_200, response.getStatus());
+ assertTrue("3.6.1 Transfer Codings / Chunked String", response.getContent().contains("12345\n"));
response = responses.get(1); // Response 2
- response.assertStatusOK("3.6.1 Transfer Codings / Response 2 Code");
- response.assertBody("3.6.1 Transfer Codings / Chunked String","6789abcde\n");
+ assertEquals("3.6.1 Transfer Codings / Response 2 Code", HttpStatus.OK_200, response.getStatus());
+ assertTrue("3.6.1 Transfer Codings / Chunked String",response.getContent().contains("6789abcde\n"));
response = responses.get(2); // Response 3
- response.assertStatusOK("3.6.1 Transfer Codings / Response 3 Code");
- response.assertNoBody("3.6.1 Transfer Codings / No Body");
+ assertEquals("3.6.1 Transfer Codings / Response 3 Code", HttpStatus.OK_200, response.getStatus());
+ assertTrue("3.6.1 Transfer Codings / No Body",response.getContent() == null);
}
/**
@@ -261,20 +260,20 @@
req3.append("Connection: close\n");
req3.append("\n");
- responses = http.requests(req3);
+ List<HttpTester.Response> responses = http.requests(req3);
Assert.assertEquals("Response Count",3,responses.size());
- response = responses.get(0); // Response 1
- response.assertStatusOK("3.6.1 Transfer Codings / Response 1 Code");
- response.assertBody("3.6.1 Transfer Codings / Chunked String","fghIjk\n"); // Complete R1 string
+ HttpTester.Response response = responses.get(0); // Response 1
+ assertEquals("3.6.1 Transfer Codings / Response 1 Code", HttpStatus.OK_200, response.getStatus());
+ assertTrue("3.6.1 Transfer Codings / Chunked String", response.getContent().contains("fghIjk\n")); // Complete R1 string
response = responses.get(1); // Response 2
- response.assertStatusOK("3.6.1 Transfer Codings / Response 2 Code");
- response.assertBody("3.6.1 Transfer Codings / Chunked String","lmnoPqrst\n"); // Complete R2 string
+ assertEquals("3.6.1 Transfer Codings / Response 2 Code", HttpStatus.OK_200, response.getStatus());
+ assertTrue("3.6.1 Transfer Codings / Chunked String", response.getContent().contains("lmnoPqrst\n")); // Complete R2 string
response = responses.get(2); // Response 3
- response.assertStatusOK("3.6.1 Transfer Codings / Response 3 Code");
- response.assertNoBody("3.6.1 Transfer Codings / No Body");
+ assertEquals("3.6.1 Transfer Codings / Response 3 Code", HttpStatus.OK_200, response.getStatus());
+ assertTrue("3.6.1 Transfer Codings / No Body", response.getContent() == null);
}
@@ -305,16 +304,16 @@
req4.append("Connection: close\n"); // close
req4.append("\n");
- responses = http.requests(req4);
+ List<HttpTester.Response> responses = http.requests(req4);
Assert.assertEquals("Response Count",2,responses.size());
- response = responses.get(0); // Response 1
- response.assertStatusOK("3.6.1 Transfer Codings / Response 1 Code");
- response.assertBody("3.6.1 Transfer Codings / Chunked String","123456\n"); // Complete R1 string
+ HttpTester.Response response = responses.get(0); // Response 1
+ assertEquals("3.6.1 Transfer Codings / Response 1 Code", HttpStatus.OK_200, response.getStatus());
+ assertTrue("3.6.1 Transfer Codings / Chunked String", response.getContent().contains("123456\n")); // Complete R1 string
response = responses.get(1); // Response 2
- response.assertStatusOK("3.6.1 Transfer Codings / Response 2 Code");
- response.assertNoBody("3.6.1 Transfer Codings / No Body");
+ assertEquals("3.6.1 Transfer Codings / Response 2 Code", HttpStatus.OK_200, response.getStatus());
+ assertTrue("3.6.1 Transfer Codings / No Body", response.getContent() == null);
}
/**
@@ -364,15 +363,15 @@
req1.append("Connection: close\n");
req1.append("\n");
- responses = http.requests(req1);
+ List<HttpTester.Response> responses = http.requests(req1);
Assert.assertEquals("Response Count",2,responses.size());
- response = responses.get(0);
- response.assertStatusOK("4.4.2 Message Length / Response Code");
- response.assertBody("4.4.2 Message Length / Body","123\n");
+ HttpTester.Response response = responses.get(0);
+ assertEquals("4.4.2 Message Length / Response Code", HttpStatus.OK_200, response.getStatus());
+ assertTrue("4.4.2 Message Length / Body",response.getContent().contains("123\n"));
response = responses.get(1);
- response.assertStatusOK("4.4.2 Message Length / Response Code");
- response.assertNoBody("4.4.2 Message Length / No Body");
+ assertEquals("4.4.2 Message Length / Response Code", HttpStatus.OK_200, response.getStatus());
+ assertTrue("4.4.2 Message Length / No Body", response.getContent() == null);
// 4.4.3 -
// Client - do not send 'Content-Length' if entity-length
@@ -405,11 +404,11 @@
Assert.assertEquals("Response Count",2,responses.size());
response = responses.get(0); // response 1
- response.assertStatusOK("4.4.3 Ignore Content-Length / Response Code");
- response.assertBody("4.4.3 Ignore Content-Length / Body","123456\n");
+ assertEquals("4.4.3 Ignore Content-Length / Response Code", HttpStatus.OK_200, response.getStatus());
+ assertTrue("4.4.3 Ignore Content-Length / Body", response.getContent().contains("123456\n"));
response = responses.get(1); // response 2
- response.assertStatusOK("4.4.3 Ignore Content-Length / Response Code");
- response.assertBody("4.4.3 Ignore Content-Length / Body","7890AB\n");
+ assertEquals("4.4.3 Ignore Content-Length / Response Code", HttpStatus.OK_200, response.getStatus());
+ assertTrue("4.4.3 Ignore Content-Length / Body", response.getContent().contains("7890AB\n"));
// 4.4 - Server can request valid Content-Length from client if client
// fails to provide a Content-Length.
@@ -430,8 +429,8 @@
response = http.request(req3);
- response.assertStatus("4.4 Valid Content-Length Required",HttpStatus.LENGTH_REQUIRED_411);
- response.assertNoBody("4.4 Valid Content-Length Required");
+ assertEquals("4.4 Valid Content-Length Required",HttpStatus.LENGTH_REQUIRED_411, response.getStatus());
+ assertTrue("4.4 Valid Content-Length Required", response.getContent() == null);
StringBuffer req4 = new StringBuffer();
req4.append("GET /echo/R2 HTTP/1.0\n");
@@ -441,8 +440,8 @@
response = http.request(req4);
- response.assertStatus("4.4 Valid Content-Length Required",HttpStatus.LENGTH_REQUIRED_411);
- response.assertNoBody("4.4 Valid Content-Length Required");
+ assertEquals("4.4 Valid Content-Length Required",HttpStatus.LENGTH_REQUIRED_411, response.getStatus());
+ assertTrue("4.4 Valid Content-Length Required", response.getContent() == null);
}
}
@@ -462,10 +461,10 @@
req1.append("Connection: close\n");
req1.append("\r\n");
- response = http.request(req1);
+ HttpTester.Response response = http.request(req1);
- response.assertStatusOK("5.2 Default Host");
- response.assertBodyContains("5.2 Default Host","Default DOCRoot");
+ assertEquals("5.2 Default Host", HttpStatus.OK_200, response.getStatus());
+ assertTrue("5.2 Default Host",response.getContent().contains("Default DOCRoot"));
}
/**
@@ -484,10 +483,10 @@
req2.append("Connection: close\n");
req2.append("\r\n");
- response = http.request(req2);
+ HttpTester.Response response = http.request(req2);
- response.assertStatusOK("5.2 Virtual Host");
- response.assertBodyContains("5.2 Virtual Host","VirtualHost DOCRoot");
+ assertEquals("5.2 Virtual Host", HttpStatus.OK_200, response.getStatus());
+ assertTrue("5.2 Virtual Host",response.getContent().contains("VirtualHost DOCRoot"));
}
/**
@@ -506,10 +505,10 @@
req3.append("Connection: close\n");
req3.append("\n");
- response = http.request(req3);
+ HttpTester.Response response = http.request(req3);
- response.assertStatusOK("5.2 Virtual Host (mixed case)");
- response.assertBodyContains("5.2 Virtual Host (mixed case)","VirtualHost DOCRoot");
+ assertEquals("5.2 Virtual Host (mixed case)", HttpStatus.OK_200, response.getStatus());
+ assertTrue("5.2 Virtual Host (mixed case)",response.getContent().contains("VirtualHost DOCRoot"));
}
/**
@@ -527,10 +526,10 @@
req4.append("Connection: close\n");
req4.append("\n"); // no virtual host
- response = http.request(req4);
+ HttpTester.Response response = http.request(req4);
- response.assertStatus("5.2 No Host",HttpStatus.BAD_REQUEST_400);
- response.assertNoBody("5.2 No Host");
+ assertEquals("5.2 No Host",HttpStatus.BAD_REQUEST_400,response.getStatus());
+ assertEquals("5.2 No Host","", response.getContent());
}
/**
@@ -549,10 +548,10 @@
req5.append("Connection: close\n");
req5.append("\n");
- response = http.request(req5);
+ HttpTester.Response response = http.request(req5);
- response.assertStatusOK("5.2 Bad Host");
- response.assertBodyContains("5.2 Bad Host","Default DOCRoot"); // served by default context
+ assertEquals("5.2 Bad Host",HttpStatus.OK_200, response.getStatus());
+ assertTrue("5.2 Bad Host",response.getContent().contains("Default DOCRoot")); // served by default context
}
/**
@@ -570,10 +569,10 @@
req6.append("Connection: close\n");
req6.append("\n");
- response = http.request(req6);
+ HttpTester.Response response = http.request(req6);
// No host header should always return a 400 Bad Request.
- response.assertStatus("5.2 Virtual Host as AbsoluteURI (No Host Header / HTTP 1.1)",HttpStatus.BAD_REQUEST_400);
+ assertEquals("5.2 Virtual Host as AbsoluteURI (No Host Header / HTTP 1.1)",HttpStatus.BAD_REQUEST_400,response.getStatus());
}
/**
@@ -591,10 +590,10 @@
req6.append("Connection: close\n");
req6.append("\n");
- response = http.request(req6);
+ HttpTester.Response response = http.request(req6);
- response.assertStatusOK("5.2 Virtual Host as AbsoluteURI (No Host Header / HTTP 1.0)");
- response.assertBodyContains("5.2 Virtual Host as AbsoluteURI (No Host Header / HTTP 1.1)","VirtualHost DOCRoot");
+ assertEquals("5.2 Virtual Host as AbsoluteURI (No Host Header / HTTP 1.0)",HttpStatus.OK_200, response.getStatus());
+ assertTrue("5.2 Virtual Host as AbsoluteURI (No Host Header / HTTP 1.1)",response.getContent().contains("VirtualHost DOCRoot"));
}
/**
@@ -613,10 +612,11 @@
req7.append("Connection: close\n");
req7.append("\n");
- response = http.request(req7);
+ HttpTester.Response response = http.request(req7);
- response.assertStatusOK("5.2 Virtual Host as AbsoluteURI (and Host header)");
- response.assertBodyContains("5.2 Virtual Host as AbsoluteURI (and Host header)","VirtualHost DOCRoot");
+ assertEquals("5.2 Virtual Host as AbsoluteURI (and Host header)", HttpStatus.OK_200, response.getStatus());
+ System.err.println(response.getContent());
+ assertTrue("5.2 Virtual Host as AbsoluteURI (and Host header)",response.getContent().contains("VirtualHost DOCRoot"));
}
/**
@@ -633,11 +633,11 @@
req1.append("Connection: close\n");
req1.append("\n");
- response = http.request(req1);
+ HttpTester.Response response = http.request(req1);
- response.assertStatusOK("8.1 Persistent Connections");
- response.assertHeaderExists("8.1 Persistent Connections","Content-Length");
- response.assertBodyContains("8.1 Persistent Connections","Resource=R1");
+ assertEquals("8.1 Persistent Connections", HttpStatus.OK_200, response.getStatus());
+ assertTrue("8.1 Persistent Connections", response.get("Content-Length") != null);
+ assertTrue("8.1 Persistent Connections",response.getContent().contains("Resource=R1"));
StringBuffer req2 = new StringBuffer();
req2.append("GET /tests/R1.txt HTTP/1.1\n");
@@ -654,19 +654,19 @@
req2.append("Connection: close\n");
req2.append("\n");
- responses = http.requests(req2);
+ List<HttpTester.Response> responses = http.requests(req2);
Assert.assertEquals("Response Count",2,responses.size()); // Should not have a R3 response.
response = responses.get(0); // response 1
- response.assertStatusOK("8.1 Persistent Connections");
- response.assertHeaderExists("8.1 Persistent Connections","Content-Length");
- response.assertBodyContains("8.1 Peristent Connections","Resource=R1");
+ assertEquals("8.1 Persistent Connections", HttpStatus.OK_200, response.getStatus());
+ assertTrue("8.1 Persistent Connections",response.get("Content-Length") != null);
+ assertTrue("8.1 Peristent Connections", response.getContent().contains("Resource=R1"));
response = responses.get(1); // response 2
- response.assertStatusOK("8.1.2.2 Persistent Connections / Pipeline");
- response.assertHeaderExists("8.1.2.2 Persistent Connections / Pipeline","Content-Length");
- response.assertHeader("8.1.2.2 Persistent Connections / Pipeline","Connection","close");
- response.assertBodyContains("8.1.2.2 Peristent Connections / Pipeline","Resource=R2");
+ assertEquals("8.1.2.2 Persistent Connections / Pipeline", HttpStatus.OK_200, response.getStatus());
+ assertTrue("8.1.2.2 Persistent Connections / Pipeline", response.get("Content-Length") != null);
+ assertEquals("8.1.2.2 Persistent Connections / Pipeline","close", response.get("Connection"));
+ assertTrue("8.1.2.2 Peristent Connections / Pipeline", response.getContent().contains("Resource=R2"));
}
/**
@@ -688,9 +688,9 @@
req2.append("\n");
req2.append("12345678\n");
- response = http.request(req2);
+ HttpTester.Response response = http.request(req2);
- response.assertStatus("8.2.3 expect failure",HttpStatus.EXPECTATION_FAILED_417);
+ assertEquals("8.2.3 expect failure",HttpStatus.EXPECTATION_FAILED_417, response.getStatus());
}
/**
@@ -715,9 +715,9 @@
// Should only expect 1 response.
// The existence of 2 responses usually means a bad "HTTP/1.1 100" was received.
- response = http.request(req3);
+ HttpTester.Response response = http.request(req3);
- response.assertStatusOK("8.2.3 expect 100");
+ assertEquals("8.2.3 expect 100", HttpStatus.OK_200, response.getStatus());
}
@@ -748,19 +748,19 @@
req3.append("\n");
req3.append("87654321"); // Body
- List<HttpResponseTester> responses = http.requests(req3);
+ List<HttpTester.Response> responses = http.requests(req3);
// System.err.println(responses);
- response=responses.get(0);
+ HttpTester.Response response=responses.get(0);
// System.err.println(response.getRawResponse());
- response.assertStatus("8.2.3 ignored no 100",302);
+ assertEquals("8.2.3 ignored no 100",302, response.getStatus());
response=responses.get(1);
// System.err.println(response.getRawResponse());
- response.assertStatus("8.2.3 ignored no 100",200);
- response.assertBody("87654321\n");
+ assertEquals("8.2.3 ignored no 100",200, response.getStatus());
+ assertTrue(response.getContent().contains("87654321\n"));
}
/**
@@ -788,14 +788,14 @@
http.send(sock,req4);
http.setTimeoutMillis(2000);
- response = http.readAvailable(sock);
- response.assertStatus("8.2.3 expect 100",HttpStatus.CONTINUE_100);
+ HttpTester.Response response = http.readAvailable(sock);
+ assertEquals("8.2.3 expect 100",HttpStatus.CONTINUE_100,response.getStatus());
http.send(sock,"654321\n"); // Now send the data
response = http.read(sock);
- response.assertStatusOK("8.2.3 expect 100");
- response.assertBody("8.2.3 expect 100","654321\n");
+ assertEquals("8.2.3 expect 100", HttpStatus.OK_200, response.getStatus());
+ assertTrue("8.2.3 expect 100",response.getContent().contains("654321\n"));
}
finally
{
@@ -825,20 +825,20 @@
req1.append("Host: localhost\n");
req1.append("\n");
- response = http.request(req1);
+ HttpTester.Response response = http.request(req1);
- response.assertStatusOK("9.2 OPTIONS");
- response.assertHeaderExists("9.2 OPTIONS","Allow");
+ assertEquals("9.2 OPTIONS", HttpStatus.OK_200, response.getStatus());
+ assertTrue("9.2 OPTIONS",response.get("Allow") != null);
// Header expected ...
// Allow: GET, HEAD, POST, PUT, DELETE, MOVE, OPTIONS, TRACE
- String allow = response.getHeader("Allow");
+ String allow = response.get("Allow");
String expectedMethods[] =
{ "GET", "HEAD", "POST", "PUT", "DELETE", "MOVE", "OPTIONS", "TRACE" };
for (String expectedMethod : expectedMethods)
{
assertThat(allow,containsString(expectedMethod));
}
- response.assertHeader("9.2 OPTIONS","Content-Length","0"); // Required if no response body.
+ assertEquals("9.2 OPTIONS","0", response.get("Content-Length")); // Required if no response body.
}
}
@@ -870,15 +870,15 @@
req2.append("Connection: close\n"); // Close this second request
req2.append("\n");
- responses = http.requests(req2);
+ List<HttpTester.Response> responses = http.requests(req2);
Assert.assertEquals("Response Count",2,responses.size()); // Should have 2 responses
- response = responses.get(0); // Only interested in first response
- response.assertHeaderExists("9.2 OPTIONS","Allow");
+ HttpTester.Response response = responses.get(0); // Only interested in first response
+ assertTrue("9.2 OPTIONS", response.get("Allow") != null);
// Header expected ...
// Allow: GET, HEAD, POST, TRACE, OPTIONS
- String allow = response.getHeader("Allow");
+ String allow = response.get("Allow");
String expectedMethods[] =
{ "GET", "HEAD", "POST", "OPTIONS", "TRACE" };
for (String expectedMethod : expectedMethods)
@@ -886,7 +886,7 @@
assertThat(allow,containsString(expectedMethod));
}
- response.assertHeader("9.2 OPTIONS","Content-Length","0"); // Required if no response body.
+ assertEquals("9.2 OPTIONS","0", response.get("Content-Length")); // Required if no response body.
}
/**
@@ -905,12 +905,12 @@
req1.append("Connection: close\n");
req1.append("\n");
- response = http.request(req1);
+ HttpTester.Response response = http.request(req1);
- response.assertStatusOK("9.4 GET / Response Code");
- response.assertHeader("9.4 GET / Content Type","Content-Type","text/plain");
- response.assertHeader("9.4 HEAD / Content Type","Content-Length","25");
- response.assertBody("9.4 GET / Body","Host=Default\nResource=R1\n");
+ assertEquals("9.4 GET / Response Code", HttpStatus.OK_200, response.getStatus());
+ assertEquals("9.4 GET / Content Type","text/plain", response.get("Content-Type"));
+ assertEquals("9.4 HEAD / Content Type","25", response.get("Content-Length"));
+ assertTrue("9.4 GET / Body", response.getContent().contains("Host=Default\nResource=R1\n"));
/* Test HEAD next. (should have no body) */
@@ -930,7 +930,7 @@
String rawHeadResponse = http.readRaw(sock);
int headResponseLength = rawHeadResponse.length();
// Only interested in the response header from the GET request above.
- String rawGetResponse = response.getRawResponse().toString().substring(0,headResponseLength);
+ String rawGetResponse = response.toString().substring(0,headResponseLength);
// As there is a possibility that the time between GET and HEAD requests
// can cross the second mark. (eg: GET at 11:00:00.999 and HEAD at 11:00:01.001)
@@ -968,12 +968,12 @@
req1.append("Connection: close\n");
req1.append("\n");
- HttpResponseTester response = http.request(req1);
+ HttpTester.Response response = http.request(req1);
- response.assertStatusOK("9.8 TRACE / Response Code");
- response.assertHeader("9.8 TRACE / Content Type","Content-Type","message/http");
- response.assertBodyContains("9.8 TRACE / echo","TRACE /rfc2616-webapp/httpmethods HTTP/1.1");
- response.assertBodyContains("9.8 TRACE / echo","Host: localhost");
+ assertEquals("9.8 TRACE / Response Code", HttpStatus.OK_200, response.getStatus());
+ assertEquals("9.8 TRACE / Content Type", "message/http", response.get("Content-Type"));
+ assertTrue("9.8 TRACE / echo", response.getContent().contains("TRACE /rfc2616-webapp/httpmethods HTTP/1.1"));
+ assertTrue("9.8 TRACE / echo", response.getContent().contains("Host: localhost"));
}
/**
@@ -996,10 +996,10 @@
req1.append("Connection: close\n");
req1.append("\n");
- response = http.request(req1);
+ HttpTester.Response response = http.request(req1);
- boolean noRangeHasContentLocation = response.hasHeader("Content-Location");
- boolean noRangeHasETag = response.hasHeader("ETag");
+ boolean noRangeHasContentLocation = (response.get("Content-Location") != null);
+ boolean noRangeHasETag = (response.get("ETag") != null);
// now try again for the same resource but this time WITH range header
@@ -1012,7 +1012,7 @@
response = http.request(req2);
- response.assertStatus("10.2.7 Partial Content",HttpStatus.PARTIAL_CONTENT_206);
+ assertEquals("10.2.7 Partial Content",HttpStatus.PARTIAL_CONTENT_206, response.getStatus());
// (point 1) A 206 response MUST contain either a Content-Range header
// field (section 14.16) indicating the range included with this
@@ -1021,28 +1021,28 @@
// in the response, its value MUST match the actual number of OCTETs
// transmitted in the message-body.
- if (response.hasHeader("Content-Range"))
+ if (response.get("Content-Range") != null)
{
- response.assertHeader("10.2.7 Partial Content / Response / Content Range","Content-Range","bytes 1-3/27");
+ assertEquals("10.2.7 Partial Content / Response / Content Range","bytes 1-3/27",response.get("Content-Range"));
}
- if (response.hasHeader("Content-Length"))
+ if (response.get("Content-Length") != null)
{
- response.assertHeader("10.2.7 Patial Content / Response / Content Length","Content-Length","3");
+ assertEquals("10.2.7 Patial Content / Response / Content Length","3", response.get("Content-Length"));
}
// (point 2) A 206 response MUST contain a Date header
- response.assertHeaderExists("10.2.7 Partial Content / Response / Date","Date");
+ assertTrue("10.2.7 Partial Content / Response / Date", response.get("Date") != null);
// (point 3) A 206 response MUST contain ETag and/or Content-Location,
// if the header would have been sent in a 200 response to the same request
if (noRangeHasContentLocation)
{
- response.assertHeaderExists("10.2.7 Partial Content / Content-Location","Content-Location");
+ assertTrue("10.2.7 Partial Content / Content-Location", response.get("Content-Location") != null);
}
if (noRangeHasETag)
{
- response.assertHeaderExists("10.2.7 Partial Content / Content-Location","ETag");
+ assertTrue("10.2.7 Partial Content / Content-Location", response.get("ETag") != null);
}
// (point 4) A 206 response MUST contain Expires, Cache-Control, and/or Vary,
@@ -1052,7 +1052,7 @@
// TODO: Not sure how to test this condition.
// Test the body sent
- response.assertBody("10.2.7 Partial Content","BCD"); // should only have bytes 1-3
+ assertTrue("10.2.7 Partial Content",response.getContent().contains("BCD")); // should only have bytes 1-3
}
/**
@@ -1074,11 +1074,11 @@
req1.append("Connection: Close\n");
req1.append("\n");
- response = http.request(req1);
+ HttpTester.Response response = http.request(req1);
specId = "10.3 Redirection HTTP/1.0 - basic";
- response.assertStatus(specId,HttpStatus.FOUND_302);
- response.assertHeader(specId,"Location",serverURI + "/tests/");
+ assertEquals(specId,HttpStatus.FOUND_302, response.getStatus());
+ assertEquals(specId,serverURI + "/tests/", response.get("Location"));
}
/**
@@ -1101,20 +1101,19 @@
req2.append("Connection: close\n");
req2.append("\n");
- responses = http.requests(req2);
+ List<HttpTester.Response> responses = http.requests(req2);
Assert.assertEquals("Response Count",2,responses.size());
- response = responses.get(0);
+ HttpTester.Response response = responses.get(0);
String specId = "10.3 Redirection HTTP/1.1 - basic (response 1)";
- response.assertStatus(specId,HttpStatus.FOUND_302);
- response.assertHeader(specId,"Location",server.getScheme() + "://localhost/tests/");
+ assertEquals(specId,HttpStatus.FOUND_302, response.getStatus());
+ assertEquals(specId,server.getScheme() + "://localhost/tests/", response.get("Location"));
response = responses.get(1);
specId = "10.3 Redirection HTTP/1.1 - basic (response 2)";
- response.assertStatus(specId,HttpStatus.FOUND_302);
- response.assertHeader(specId,"Location",server.getScheme() + "://localhost/tests/");
- response.assertHeader(specId,"Connection","close");
-
+ assertEquals(specId,HttpStatus.FOUND_302, response.getStatus());
+ assertEquals(specId,server.getScheme() + "://localhost/tests/", response.get("Location"));
+ assertEquals(specId,"close", response.get("Connection"));
}
/**
@@ -1133,11 +1132,11 @@
req3.append("Connection: close\n");
req3.append("\n");
- response = http.request(req3);
+ HttpTester.Response response = http.request(req3);
String specId = "10.3 Redirection HTTP/1.0 w/content";
- response.assertStatus(specId,HttpStatus.FOUND_302);
- response.assertHeader(specId,"Location",server.getScheme() + "://localhost/tests/R1.txt");
+ assertEquals(specId,HttpStatus.FOUND_302, response.getStatus());
+ assertEquals(specId,server.getScheme() + "://localhost/tests/R1.txt", response.get("Location"));
}
/**
@@ -1156,13 +1155,13 @@
req4.append("Connection: close\n");
req4.append("\n");
- response = http.request(req4);
+ HttpTester.Response response = http.request(req4);
String specId = "10.3 Redirection HTTP/1.1 w/content";
- response.assertStatus(specId,HttpStatus.FOUND_302);
- response.assertHeader(specId,"Location",server.getScheme() + "://localhost/tests/R2.txt");
- response.assertHeader(specId,"Connection","close");
- response.assertHeaderNotPresent(specId,"Content-Length");
+ assertEquals(specId,HttpStatus.FOUND_302, response.getStatus());
+ assertEquals(specId,server.getScheme() + "://localhost/tests/R2.txt", response.get("Location"));
+ assertEquals(specId,"close", response.get("Connection"));
+ assertTrue(specId,response.get("Content-Length") == null);
}
/**
@@ -1184,11 +1183,11 @@
req1.append("Connection: close\n");
req1.append("\n");
- response = http.request(req1);
+ HttpTester.Response response = http.request(req1);
specId = "14.3 Accept-Encoding Header";
- response.assertStatusOK(specId);
- response.assertHeader(specId,"Content-Encoding","gzip");
- response.assertHeader(specId,"Content-Type","text/html");
+ assertEquals(specId, HttpStatus.OK_200, response.getStatus());
+ assertEquals(specId,"gzip", response.get("Content-Encoding"));
+ assertEquals(specId,"text/html", response.get("Content-Type"));
}
/**
@@ -1210,10 +1209,10 @@
req1.append("Connection: close\n");
req1.append("\n");
- response = http.request(req1);
+ HttpTester.Response response = http.request(req1);
- response.assertStatusOK();
- response.assertBody(ALPHA);
+ assertEquals(HttpStatus.OK_200, response.getStatus());
+ assertTrue(response.getContent().contains(ALPHA));
}
@@ -1229,12 +1228,12 @@
req1.append("Connection: close\n");
req1.append("\n");
- response = http.request(req1);
+ HttpTester.Response response = http.request(req1);
String msg = "Partial Range: '" + rangedef + "'";
- response.assertStatus(msg,HttpStatus.PARTIAL_CONTENT_206);
- response.assertHeader(msg,"Content-Range","bytes " + expectedRange);
- response.assertBody(msg,expectedBody);
+ assertEquals(msg,HttpStatus.PARTIAL_CONTENT_206, response.getStatus());
+ assertEquals(msg,"bytes " + expectedRange, response.get("Content-Range"));
+ assertTrue(msg,response.getContent().contains(expectedBody));
}
/**
@@ -1288,12 +1287,12 @@
req1.append("\n");
http.setTimeoutMillis(60000);
- response = http.request(req1);
+ HttpTester.Response response = http.request(req1);
String msg = "Partial Range (Mixed): 'bytes=a-b,5-8'";
- response.assertStatus(msg,HttpStatus.PARTIAL_CONTENT_206);
- response.assertHeader(msg,"Content-Range","bytes 5-8/27");
- response.assertBody(msg,alpha.substring(5,8 + 1));
+ assertEquals(msg,HttpStatus.PARTIAL_CONTENT_206, response.getStatus());
+ assertEquals(msg,"bytes 5-8/27", response.get("Content-Range"));
+ assertTrue(msg,response.getContent().contains(alpha.substring(5,8 + 1)));
}
/**
@@ -1328,12 +1327,12 @@
req1.append("Connection: close\n");
req1.append("\n");
- response = http.request(req1);
+ HttpTester.Response response = http.request(req1);
String msg = "Partial Range (Mixed): 'bytes=a-b,bytes=5-8'";
- response.assertStatus(msg,HttpStatus.PARTIAL_CONTENT_206);
- response.assertHeader(msg,"Content-Range","bytes 5-8/27");
- response.assertBody(msg,alpha.substring(5,8 + 1));
+ assertEquals(msg,HttpStatus.PARTIAL_CONTENT_206, response.getStatus());
+ assertEquals(msg,"bytes 5-8/27", response.get("Content-Range"));
+ assertTrue(msg,response.getContent().contains(alpha.substring(5,8 + 1)));
}
/**
@@ -1369,12 +1368,12 @@
req1.append("Connection: close\n");
req1.append("\n");
- response = http.request(req1);
+ HttpTester.Response response = http.request(req1);
String msg = "Partial Range (Mixed): 'bytes=a-b' 'bytes=5-8'";
- response.assertStatus(msg,HttpStatus.PARTIAL_CONTENT_206);
- response.assertHeader(msg,"Content-Range","bytes 5-8/27");
- response.assertBody(msg,alpha.substring(5,8 + 1));
+ assertEquals(msg,HttpStatus.PARTIAL_CONTENT_206, response.getStatus());
+ assertEquals(msg,"bytes 5-8/27", response.get("Content-Range"));
+ assertTrue(msg,response.getContent().contains(alpha.substring(5,8 + 1)));
}
/**
@@ -1385,8 +1384,6 @@
@Test
public void test14_23_Http10_NoHostHeader() throws Exception
{
- HttpResponseTester response;
-
// HTTP/1.0 OK with no host
StringBuffer req1 = new StringBuffer();
@@ -1394,8 +1391,8 @@
req1.append("Connection: close\n");
req1.append("\n");
- response = http.request(req1);
- response.assertStatusOK("14.23 HTTP/1.0 - No Host");
+ HttpTester.Response response = http.request(req1);
+ assertEquals("14.23 HTTP/1.0 - No Host", HttpStatus.OK_200, response.getStatus());
}
/**
@@ -1413,8 +1410,8 @@
req2.append("Connection: close\n");
req2.append("\n");
- response = http.request(req2);
- response.assertStatus("14.23 HTTP/1.1 - No Host",HttpStatus.BAD_REQUEST_400);
+ HttpTester.Response response = http.request(req2);
+ assertEquals("14.23 HTTP/1.1 - No Host",HttpStatus.BAD_REQUEST_400, response.getStatus());
}
/**
@@ -1433,8 +1430,8 @@
req3.append("Connection: close\n");
req3.append("\n");
- response = http.request(req3);
- response.assertStatusOK("14.23 HTTP/1.1 - Valid Host");
+ HttpTester.Response response = http.request(req3);
+ assertEquals("14.23 HTTP/1.1 - Valid Host", HttpStatus.OK_200, response.getStatus());
}
/**
@@ -1453,8 +1450,8 @@
req4.append("Connection: close\n");
req4.append("\n");
- response = http.request(req4);
- response.assertStatusOK("14.23 HTTP/1.1 - Empty Host");
+ HttpTester.Response response = http.request(req4);
+ assertEquals("14.23 HTTP/1.1 - Empty Host", HttpStatus.OK_200, response.getStatus());
}
/**
@@ -1476,14 +1473,14 @@
req1.append("Connection: close\n");
req1.append("\n");
- response = http.request(req1);
+ HttpTester.Response response = http.request(req1);
String msg = "Partial (Byte) Range: '" + rangedef + "'";
- response.assertStatus(msg,HttpStatus.PARTIAL_CONTENT_206);
+ assertEquals(msg,HttpStatus.PARTIAL_CONTENT_206, response.getStatus());
// It might be strange to see a "Content-Range' response header to a 'Range' request,
// but this is appropriate per the RFC2616 spec.
- response.assertHeader(msg,"Content-Range","bytes " + expectedRange);
- response.assertBody(msg,expectedBody);
+ assertEquals(msg,"bytes " + expectedRange, response.get("Content-Range"));
+ assertTrue(msg,response.getContent().contains(expectedBody));
}
/**
@@ -1532,12 +1529,12 @@
req1.append("Connection: close\n");
req1.append("\n");
- response = http.request(req1);
+ HttpTester.Response response = http.request(req1);
String msg = "Partial (Byte) Range: '" + rangedef + "'";
- response.assertStatus(msg,HttpStatus.PARTIAL_CONTENT_206);
+ assertEquals(msg,HttpStatus.PARTIAL_CONTENT_206, response.getStatus());
- String contentType = response.getHeader("Content-Type");
+ String contentType = response.get("Content-Type");
// RFC states that multiple parts should result in multipart/byteranges Content type.
StringAssert.assertContains(msg + " Content-Type",contentType,"multipart/byteranges");
@@ -1557,20 +1554,20 @@
Assert.assertNotNull(msg + " Should have found boundary in Content-Type header",boundary);
// Find boundary offsets within body
- List<HttpResponseTester> multiparts = response.findBodyMultiparts(boundary);
+ List<HttpTester.Response> multiparts = HttpTesting.getParts(boundary, response);
Assert.assertEquals(msg + " multiparts in body (count)",2,multiparts.size());
// Validate multipart #1
- HttpResponseTester multipart1 = multiparts.get(0);
- multipart1.assertHeader(msg + " Multipart 1","Content-Type","text/plain");
- multipart1.assertHeader(msg + " Multipart 1","Content-Range","bytes 23-23/27");
- multipart1.assertBody(msg + " Multipart 1","X");
+ HttpTester.Response multipart1 = multiparts.get(0);
+ assertEquals(msg + " Multipart 1","text/plain", multipart1.get("Content-Type"));
+ assertEquals(msg + " Multipart 1","bytes 23-23/27", multipart1.get("Content-Range"));
+ assertTrue(msg + " Multipart 1", multipart1.getContent().contains("X"));
// Validate multipart #2
- HttpResponseTester multipart2 = multiparts.get(1);
- multipart2.assertHeader(msg + " Multipart 2","Content-Type","text/plain");
- multipart2.assertHeader(msg + " Multipart 2","Content-Range","bytes 25-26/27");
- multipart2.assertBody(msg + " Multipart 2","Z\n");
+ HttpTester.Response multipart2 = multiparts.get(1);
+ assertEquals(msg + " Multipart 2","text/plain", multipart2.get("Content-Type"));
+ assertEquals(msg + " Multipart 2","bytes 25-26/27", multipart2.get("Content-Range"));
+ assertTrue(msg + " Multipart 2", multipart2.getContent().contains("Z\n"));
}
/**
@@ -1593,12 +1590,12 @@
req1.append("Connection: close\n");
req1.append("\n");
- response = http.request(req1);
+ HttpTester.Response response = http.request(req1);
String msg = "Partial (Byte) Range: '" + rangedef + "'";
- response.assertStatus(msg,HttpStatus.PARTIAL_CONTENT_206);
+ assertEquals(msg,HttpStatus.PARTIAL_CONTENT_206,response.getStatus());
- String contentType = response.getHeader("Content-Type");
+ String contentType = response.get("Content-Type");
// RFC states that multiple parts should result in multipart/byteranges Content type.
StringAssert.assertContains(msg + " Content-Type",contentType,"multipart/byteranges");
@@ -1618,26 +1615,26 @@
Assert.assertNotNull(msg + " Should have found boundary in Content-Type header",boundary);
// Find boundary offsets within body
- List<HttpResponseTester> multiparts = response.findBodyMultiparts(boundary);
+ List<HttpTester.Response> multiparts = HttpTesting.getParts(boundary, response);
Assert.assertEquals(msg + " multiparts in body (count)",3,multiparts.size());
// Validate multipart #1
- HttpResponseTester multipart1 = multiparts.get(0);
- multipart1.assertHeader(msg + " Multipart 1","Content-Type","text/plain");
- multipart1.assertHeader(msg + " Multipart 1","Content-Range","bytes 26-26/27");
- multipart1.assertBody(msg + " Multipart 1","\n");
+ HttpTester.Response multipart1 = multiparts.get(0);
+ assertEquals(msg + " Multipart 1", "text/plain", multipart1.get("Content-Type"));
+ assertEquals(msg + " Multipart 1","bytes 26-26/27", multipart1.get("Content-Range"));
+ assertTrue(msg + " Multipart 1",multipart1.getContent().contains("\n"));
// Validate multipart #2
- HttpResponseTester multipart2 = multiparts.get(1);
- multipart2.assertHeader(msg + " Multipart 2","Content-Type","text/plain");
- multipart2.assertHeader(msg + " Multipart 2","Content-Range","bytes 25-26/27");
- multipart2.assertBody(msg + " Multipart 2","Z\n");
+ HttpTester.Response multipart2 = multiparts.get(1);
+ assertEquals(msg + " Multipart 2","text/plain", multipart2.get("Content-Type"));
+ assertEquals(msg + " Multipart 2","bytes 25-26/27", multipart2.get("Content-Range"));
+ assertTrue(msg + " Multipart 2", multipart2.getContent().contains("Z\n"));
// Validate multipart #3
- HttpResponseTester multipart3 = multiparts.get(2);
- multipart3.assertHeader(msg + " Multipart 3","Content-Type","text/plain");
- multipart3.assertHeader(msg + " Multipart 3","Content-Range","bytes 24-26/27");
- multipart3.assertBody(msg + " Multipart 3","YZ\n");
+ HttpTester.Response multipart3 = multiparts.get(2);
+ assertEquals(msg + " Multipart 3","text/plain", multipart3.get("Content-Type"));
+ assertEquals(msg + " Multipart 3","bytes 24-26/27", multipart3.get("Content-Range"));
+ assertTrue(msg + " Multipart 3", multipart3.getContent().contains("YZ\n"));
}
/**
@@ -1671,9 +1668,9 @@
req1.append("Connection: close\n");
req1.append("\n");
- response = http.request(req1);
+ HttpTester.Response response = http.request(req1);
- response.assertStatus("BadByteRange: '" + rangedef + "'",HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE_416);
+ assertEquals("BadByteRange: '" + rangedef + "'",HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE_416, response.getStatus());
}
/**
@@ -1714,10 +1711,10 @@
req1.append("Connection: close\n");
req1.append("\n");
- response = http.request(req1);
+ HttpTester.Response response = http.request(req1);
specId = "14.39 TE Header";
- response.assertStatusOK(specId);
- response.assertHeader(specId,"Transfer-Encoding","gzip");
+ assertEquals(specId, HttpStatus.OK_200, response.getStatus());
+ assertEquals(specId,"gzip", response.get("Transfer-Encoding"));
}
}
@@ -1741,9 +1738,9 @@
req2.append("Connection: close\n");
req2.append("\n");
- response = http.request(req2);
+ HttpTester.Response response = http.request(req2);
specId = "14.39 TE Header";
- response.assertStatus(specId,HttpStatus.NOT_IMPLEMENTED_501); // Error on TE (deflate not supported)
+ assertEquals(specId,HttpStatus.NOT_IMPLEMENTED_501, response.getStatus()); // Error on TE (deflate not supported)
}
}
@@ -1755,8 +1752,7 @@
@Test
public void test19_6() throws Exception
{
- List<HttpResponseTester> responses;
- HttpResponseTester response;
+
String specId;
/* Compatibility with HTTP/1.0 */
@@ -1765,10 +1761,10 @@
req1.append("GET /tests/R1.txt HTTP/1.0\n");
req1.append("\n");
- response = http.request(req1);
+ HttpTester.Response response = http.request(req1);
specId = "19.6 Compatibility with HTTP/1.0 - simple request";
- response.assertStatusOK(specId);
- response.assertHeaderNotPresent(specId + " - connection closed not assumed","Connection");
+ assertEquals(specId, HttpStatus.OK_200, response.getStatus());
+ assertTrue(specId + " - connection closed not assumed",response.get("Connection") == null);
/* Compatibility with HTTP/1.0 */
@@ -1783,26 +1779,25 @@
req2.append("Connection: close\n"); // Connection closed here
req2.append("\n");
- req2.append("GET /tests/R3.txt HTTP/1.0\n"); // This request should not
- // be handled
+ req2.append("GET /tests/R3.txt HTTP/1.0\n"); // This request should not be handled
req2.append("Host: localhost\n");
req2.append("Connection: close\n");
req2.append("\n");
- responses = http.requests(req2);
+ List<HttpTester.Response> responses = http.requests(req2);
// Since R2 closes the connection, should only get 2 responses (R1 &
// R2), not (R3)
Assert.assertEquals("Response Count",2,responses.size());
response = responses.get(0); // response 1
specId = "19.6.2 Compatibility with previous HTTP - Keep-alive";
- response.assertStatusOK(specId);
- response.assertHeader(specId,"Connection","keep-alive");
- response.assertBodyContains(specId,"Resource=R1");
+ assertEquals(specId, HttpStatus.OK_200, response.getStatus());
+ assertEquals(specId,"keep-alive", response.get("Connection"));
+ assertTrue(specId,response.getContent().contains("Resource=R1"));
response = responses.get(1); // response 2
- response.assertStatusOK(specId);
- response.assertBodyContains(specId,"Resource=R2");
+ assertEquals(specId, HttpStatus.OK_200, response.getStatus());
+ assertTrue(specId,response.getContent().contains("Resource=R2"));
/* Compatibility with HTTP/1.0 */
@@ -1836,18 +1831,18 @@
specId = "19.6.2 Compatibility with HTTP/1.0- Keep-alive";
response = responses.get(0);
- response.assertStatusOK(specId);
- response.assertHeader(specId,"Connection","keep-alive");
- response.assertBody(specId,"1234567890\n");
+ assertEquals(specId, HttpStatus.OK_200, response.getStatus());
+ assertEquals(specId,"keep-alive", response.get("Connection"));
+ assertTrue(specId, response.getContent().contains("1234567890\n"));
response = responses.get(1);
- response.assertStatusOK(specId);
- response.assertHeader(specId,"Connection","keep-alive");
- response.assertBody(specId,"ABCDEFGHIJ\n");
+ assertEquals(specId, HttpStatus.OK_200, response.getStatus());
+ assertEquals(specId, "keep-alive", response.get("Connection"));
+ assertTrue(specId,response.getContent().contains("ABCDEFGHIJ\n"));
response = responses.get(2);
- response.assertStatusOK(specId);
- response.assertBody(specId,"Host=Default\nResource=R2\n");
+ assertEquals(specId, HttpStatus.OK_200, response.getStatus());
+ assertTrue(specId,response.getContent().contains("Host=Default\nResource=R2\n"));
}
protected void assertDate(String msg, Calendar expectedTime, long actualTime)
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/JettyDistro.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/JettyDistro.java
new file mode 100644
index 0000000..99ce5ff
--- /dev/null
+++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/JettyDistro.java
@@ -0,0 +1,874 @@
+//
+// ========================================================================
+// 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.test.support;
+//
+//========================================================================
+//------------------------------------------------------------------------
+//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.
+//========================================================================
+//
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.toolchain.test.FS;
+import org.eclipse.jetty.toolchain.test.IO;
+import org.eclipse.jetty.toolchain.test.JAR;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.OS;
+import org.eclipse.jetty.toolchain.test.PathAssert;
+import org.eclipse.jetty.toolchain.test.TestingDir;
+import org.junit.Assert;
+
+/**
+ * Basic process based executor for using the Jetty Distribution along with custom configurations to perform basic
+ * <p>
+ * Allows for a test specific directory, that is a copied jetty-distribution, and then modified for the test specific testing required.
+ * <p>
+ * Requires that you setup the maven-dependency-plugin appropriately for the base distribution you want to use, along with any other dependencies (wars, libs,
+ * etc..) that you may need from other maven projects.
+ * <p>
+ * Maven Dependency Plugin Setup:
+ *
+ * <pre>
+ * <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ * xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ *
+ * <!-- Common Destination Directories -->
+ *
+ * <properties>
+ * <test-wars-dir>${project.build.directory}/test-wars</test-wars-dir>
+ * <test-libs-dir>${project.build.directory}/test-libs</test-libs-dir>
+ * <test-distro-dir>${project.build.directory}/test-dist</test-distro-dir>
+ * </properties>
+ *
+ * <build>
+ * <plugins>
+ * <plugin>
+ * <groupId>org.apache.maven.plugins</groupId>
+ * <artifactId>maven-dependency-plugin</artifactId>
+ * <version>2.1</version>
+ * <executions>
+ *
+ * <!-- Copy LIB and WAR dependencies into place that JettyDistro can use them -->
+ *
+ * <execution>
+ * <id>test-lib-war-copy</id>
+ * <phase>process-test-resources</phase>
+ * <goals>
+ * <goal>copy</goal>
+ * </goals>
+ * <configuration>
+ * <artifactItems>
+ * <artifactItem>
+ * <groupId>org.mortbay.jetty.testwars</groupId>
+ * <artifactId>test-war-java_util_logging</artifactId>
+ * <version>7.3.0</version>
+ * <type>war</type>
+ * <outputDirectory>${test-wars-dir}</outputDirectory>
+ * </artifactItem>
+ * <artifactItem>
+ * <groupId>org.mortbay.jetty</groupId>
+ * <artifactId>jetty-aspect-servlet-api-2.5</artifactId>
+ * <version>7.3.0</version>
+ * <type>jar</type>
+ * <outputDirectory>${test-libs-dir}</outputDirectory>
+ * </artifactItem>
+ * </artifactItems>
+ * <overWriteIfNewer>true</overWriteIfNewer>
+ * <overWrite>true</overWrite>
+ * <stripVersion>true</stripVersion>
+ * </configuration>
+ * </execution>
+ *
+ * <!-- Extract Jetty DISTRIBUTION into place that JettyDistro can use it -->
+ *
+ * <execution>
+ * <id>unpack-test-dist</id>
+ * <phase>process-test-resources</phase>
+ * <goals>
+ * <goal>unpack</goal>
+ * </goals>
+ * <configuration>
+ * <artifactItems>
+ * <artifactItem>
+ * <groupId>org.eclipse.jetty</groupId>
+ * <artifactId>jetty-distribution</artifactId>
+ * <version>7.3.0</version>
+ * <type>zip</type>
+ * <overWrite>true</overWrite>
+ * </artifactItem>
+ * </artifactItems>
+ * <outputAbsoluteArtifactFilename>true</outputAbsoluteArtifactFilename>
+ * <outputDirectory>${test-distro-dir}</outputDirectory>
+ * <overWriteSnapshots>true</overWriteSnapshots>
+ * <overWriteIfNewer>true</overWriteIfNewer>
+ * </configuration>
+ * </execution>
+ * </executions>
+ * </plugin>
+ * </plugins>
+ * </build>
+ *
+ * </project>
+ * </pre>
+ * <p>
+ * If you have a specific configuration you want to setup, you'll want to prepare this configuration in an overlay directory underneath the
+ * <code>src/test/resources/</code> directory. <br>
+ * Notes:
+ * <ol>
+ * <li>The {@link JettyDistro} sets up a unique test directory (based on the constructor {@link #JettyDistro(Class)} or {@link #JettyDistro(TestingDir)}), by
+ * ensuring the directory is empty, then copying the <code>target/test-dist</code> directory into this new testing directory prior to the test specific changes
+ * to the configuration.<br>
+ * Note: this testing directory is a complete jetty distribution, suitable for executing via the command line for additional testing needs.</li>
+ * <li>The directory name you choose in <code>src/test/resources</code> will be the name you use in the {@link #overlayConfig(String)} method to provide
+ * replacement configurations for the Jetty Distribution.</li>
+ * <li>You'll want to {@link #delete(String)} any files and/or directories from the standard distribution prior to using the {@link #overlayConfig(String)}
+ * method.</li>
+ * <li>Use the {@link #copyLib(String, String)} method to copy JAR files from the <code>target/test-libs</code> directory (created and managed above using the
+ * <code>maven-dependency-plugin</code>) to copy the lib into the test specific.</li>
+ * <li>Use the {@link #copyTestWar(String)} method to copy WAR files from the <code>target/test-wars</code> directory (created and managed above using the
+ * <code>maven-dependency-plugin</code>) to copy the WAR into the test specific directory.</li>
+ * </ol>
+ * <p>
+ * Next you'll want to use Junit 4.8+ and the <code>@BeforeClass</code> and <code>@AfterClass</code> annotations to setup the <code>JettyDistro</code>
+ * class for setting up your testing configuration.
+ * <p>
+ * Example Test Case using {@link JettyDistro} class
+ *
+ * <pre>
+ * public class MySampleTest
+ * {
+ * private static JettyDistro jetty;
+ *
+ * @BeforeClass
+ * public static void initJetty() throws Exception
+ * {
+ * jetty = new JettyDistro(MySampleTest.class);
+ *
+ * jetty.copyTestWar("test-war-java_util_logging.war");
+ * jetty.copyTestWar("test-war-policy.war");
+ *
+ * jetty.delete("webapps/test.war");
+ * jetty.delete("contexts/test.d");
+ * jetty.delete("contexts/javadoc.xml");
+ * jetty.delete("contexts/test.xml");
+ *
+ * jetty.overlayConfig("no_security");
+ *
+ * jetty.setDebug(true);
+ *
+ * jetty.start();
+ * }
+ *
+ * @AfterClass
+ * public static void shutdownJetty() throws Exception
+ * {
+ * if (jetty != null)
+ * {
+ * jetty.stop();
+ * }
+ * }
+ *
+ * @Test
+ * public void testRequest() throws Exception
+ * {
+ * SimpleRequest request = new SimpleRequest(jetty.getBaseUri());
+ * String path = "/test-war-policy/security/PRACTICAL/testFilsystem");
+ * String response = request.getString(path);
+ * Assert.assertEquals("Success", response);
+ * }
+ * }
+ * </pre>
+ */
+public class JettyDistro
+{
+ private String artifactName = "jetty-distribution";
+ private long startTime = 60;
+ private TimeUnit timeUnit = TimeUnit.SECONDS;
+
+ private File jettyHomeDir;
+ private Process pid;
+ private URI baseUri;
+
+ private String jmxUrl;
+
+ private boolean _debug = false;
+
+ /**
+ * Setup the JettyHome as belonging in a testing directory associated with a testing clazz.
+ *
+ * @param clazz
+ * the testing class using this JettyDistro
+ * @throws IOException
+ * if unable to copy unpacked distribution into place for the provided testing directory
+ */
+ public JettyDistro(Class<?> clazz) throws IOException
+ {
+ this(clazz,null);
+ }
+
+ /**
+ * Setup the JettyHome as belonging in a testing directory associated with a testing clazz.
+ *
+ * @param clazz
+ * the testing class using this JettyDistro
+ * @param artifact
+ * name of jetty distribution artifact
+ * @throws IOException
+ * if unable to copy unpacked distribution into place for the provided testing directory
+ */
+ public JettyDistro(Class<?> clazz, String artifact) throws IOException
+ {
+ this.jettyHomeDir = MavenTestingUtils.getTargetTestingDir(clazz,"jettyHome");
+ if (artifact != null)
+ {
+ this.artifactName = artifact;
+ }
+
+ copyBaseDistro();
+ }
+
+ /**
+ * Setup the JettyHome as belonging to a specific testing method directory
+ *
+ * @param testdir
+ * the testing directory to use as the JettyHome for this JettyDistro
+ * @throws IOException
+ * if unable to copy unpacked distribution into place for the provided testing directory
+ */
+ public JettyDistro(TestingDir testdir) throws IOException
+ {
+ this.jettyHomeDir = testdir.getDir();
+ copyBaseDistro();
+ }
+
+ /**
+ * Setup the JettyHome as belonging to a specific testing method directory
+ *
+ * @param testdir
+ * the testing directory to use as the JettyHome for this JettyDistro
+ * @param artifact
+ * name of jetty distribution artifact
+ * @throws IOException
+ * if unable to copy unpacked distribution into place for the provided testing directory
+ */
+ public JettyDistro(TestingDir testdir, String artifact) throws IOException
+ {
+ this.jettyHomeDir = testdir.getDir();
+ if (artifact != null)
+ {
+ this.artifactName = artifact;
+ }
+
+ copyBaseDistro();
+ }
+
+ /**
+ *
+ * @throws IOException
+ * if unable to copy unpacked distribution into place for the provided testing directory
+ */
+ private void copyBaseDistro() throws IOException
+ {
+ // The outputDirectory for the maven side dependency:unpack goal.
+ File distroUnpackDir = MavenTestingUtils.getTargetFile("test-dist");
+ PathAssert.assertDirExists(artifactName + " dependency:unpack",distroUnpackDir);
+
+ // The actual jetty-distribution-${version} directory is under this directory.
+ // Lets find it.
+ File subdirs[] = distroUnpackDir.listFiles(new FileFilter()
+ {
+ public boolean accept(File path)
+ {
+ if (!path.isDirectory())
+ {
+ return false;
+ }
+
+ return path.getName().startsWith(artifactName + "-");
+ }
+ });
+
+ if (subdirs.length == 0)
+ {
+ // No jetty-distribution found.
+ StringBuilder err = new StringBuilder();
+ err.append("No target/test-dist/");
+ err.append(artifactName);
+ err.append("-${version} directory found.");
+ err.append("\n To fix this, run 'mvn process-test-resources' to create the directory.");
+ throw new IOException(err.toString());
+ }
+
+ if (subdirs.length != 1)
+ {
+ // Too many jetty-distributions found.
+ StringBuilder err = new StringBuilder();
+ err.append("Too many target/test-dist/");
+ err.append(artifactName);
+ err.append("-${version} directories found.");
+ for (File dir : subdirs)
+ {
+ err.append("\n ").append(dir.getAbsolutePath());
+ }
+ err.append("\n To fix this, run 'mvn clean process-test-resources' to recreate the target/test-dist directory.");
+ throw new IOException(err.toString());
+ }
+
+ File distroSrcDir = subdirs[0];
+ FS.ensureEmpty(jettyHomeDir);
+ System.out.printf("Copying Jetty Distribution: %s%n",distroSrcDir.getAbsolutePath());
+ System.out.printf(" To Testing Dir: %s%n",jettyHomeDir.getAbsolutePath());
+ IO.copyDir(distroSrcDir,jettyHomeDir);
+ }
+
+ /**
+ * Return the $(jetty.home) directory being used for this JettyDistro
+ *
+ * @return the jetty.home directory being used
+ */
+ public File getJettyHomeDir()
+ {
+ return this.jettyHomeDir;
+ }
+
+ /**
+ * Copy a war file from ${project.basedir}/target/test-wars/${testWarFilename} into the ${jetty.home}/webapps/ directory
+ *
+ * @param testWarFilename
+ * the war file to copy (must exist)
+ * @throws IOException
+ * if unable to copy the war file.
+ */
+ public void copyTestWar(String testWarFilename) throws IOException
+ {
+ File srcWar = MavenTestingUtils.getTargetFile("test-wars/" + testWarFilename);
+ File destWar = new File(jettyHomeDir,OS.separators("webapps/" + testWarFilename));
+ FS.ensureDirExists(destWar.getParentFile());
+ IO.copyFile(srcWar,destWar);
+ }
+
+ /**
+ * Copy an arbitrary file from <code>src/test/resources/${resourcePath}</code> to the testing directory.
+ *
+ * @param resourcePath
+ * the relative path for file content within the <code>src/test/resources</code> directory.
+ * @param outputPath
+ * the testing directory relative output path for the file output (will result in a file with the outputPath name being created)
+ * @throws IOException
+ * if unable to copy resource file
+ */
+ public void copyResource(String resourcePath, String outputPath) throws IOException
+ {
+ File srcFile = MavenTestingUtils.getTestResourceFile(resourcePath);
+ File destFile = new File(jettyHomeDir,OS.separators(outputPath));
+ FS.ensureDirExists(destFile.getParentFile());
+ IO.copyFile(srcFile,destFile);
+ }
+
+ /**
+ * Copy an arbitrary file from <code>target/test-libs/${libFilename}</code> to the testing directory.
+ *
+ * @param libFilename
+ * the <code>target/test-libs/${libFilename}</code> to copy
+ * @param outputPath
+ * the destination testing directory relative output path for the lib. (will result in a file with the outputPath name being created)
+ * @throws IOException
+ * if unable to copy lib
+ */
+ public void copyLib(String libFilename, String outputPath) throws IOException
+ {
+ File srcLib = MavenTestingUtils.getTargetFile("test-libs/" + libFilename);
+ File destLib = new File(jettyHomeDir,OS.separators(outputPath));
+ FS.ensureDirExists(destLib.getParentFile());
+ IO.copyFile(srcLib,destLib);
+ }
+
+ /**
+ * Copy the <code>${project.basedir}/src/main/config/</code> tree into the testing directory.
+ *
+ * @throws IOException
+ * if unable to copy the directory tree
+ */
+ public void copyProjectMainConfig() throws IOException
+ {
+ File srcDir = MavenTestingUtils.getProjectDir("src/main/config");
+ IO.copyDir(srcDir,jettyHomeDir);
+ }
+
+ /**
+ * Create a <code>${jetty.home}/lib/self/${jarFilename}</code> jar file from the content in the <code>${project.basedir}/target/classes/</code> directory.
+ *
+ * @throws IOException
+ * if unable to copy the directory tree
+ */
+ public void createProjectLib(String jarFilename) throws IOException
+ {
+ File srcDir = MavenTestingUtils.getTargetFile("classes");
+ File libSelfDir = new File(jettyHomeDir,OS.separators("lib/self"));
+ FS.ensureDirExists(libSelfDir);
+ File jarFile = new File(libSelfDir,jarFilename);
+ JAR.create(srcDir,jarFile);
+ }
+
+ /**
+ * Unpack an arbitrary config from <code>target/test-configs/${configFilename}</code> to the testing directory.
+ *
+ * @param configFilename
+ * the <code>target/test-configs/${configFilename}</code> to copy
+ * @throws IOException
+ * if unable to unpack config file
+ */
+ public void unpackConfig(String configFilename) throws IOException
+ {
+ File srcConfig = MavenTestingUtils.getTargetFile("test-configs/" + configFilename);
+ JAR.unpack(srcConfig,jettyHomeDir);
+ }
+
+ /**
+ * Delete a File or Directory found in the ${jetty.home} directory.
+ *
+ * @param path
+ * the path to delete. (can be a file or directory)
+ */
+ public void delete(String path)
+ {
+ File jettyPath = new File(jettyHomeDir,OS.separators(path));
+ FS.delete(jettyPath);
+ }
+
+ /**
+ * Return the baseUri being used for this Jetty Process Instance.
+ *
+ * @return the base URI for this Jetty Process Instance.
+ */
+ public URI getBaseUri()
+ {
+ return this.baseUri;
+ }
+
+ /**
+ * Return the JMX URL being used for this Jetty Process Instance.
+ *
+ * @return the JMX URL for this Jetty Process Instance.
+ */
+ public String getJmxUrl()
+ {
+ return this.jmxUrl;
+ }
+
+ /**
+ * Take the directory contents from ${project.basedir}/src/test/resources/${testConfigName}/ and copy it over whatever happens to be at ${jetty.home}
+ *
+ * @param testConfigName
+ * the src/test/resources/ directory name to use as the source diretory for the configuration we are interested in.
+ * @throws IOException
+ * if unable to copy directory.
+ */
+ public void overlayConfig(String testConfigName) throws IOException
+ {
+ File srcDir = MavenTestingUtils.getTestResourceDir(testConfigName);
+ IO.copyDir(srcDir,jettyHomeDir);
+ }
+
+ /**
+ * Start the jetty server
+ *
+ * @throws IOException
+ * if unable to start the server.
+ */
+ public void start() throws IOException
+ {
+ List<String> commands = new ArrayList<String>();
+ commands.add(getJavaBin());
+
+ commands.add("-Djetty.home=" + jettyHomeDir.getAbsolutePath());
+
+ // Do a dry run first to get the exact command line for Jetty process
+ commands.add("-jar");
+ commands.add("start.jar");
+ commands.add("jetty.port=0");
+ if (_debug)
+ {
+ commands.add("-D.DEBUG=true");
+ }
+ commands.add("--dry-run");
+
+ ProcessBuilder pbCmd = new ProcessBuilder(commands);
+ pbCmd.directory(jettyHomeDir);
+
+ String cmdLine = null;
+ Process pidCmd = pbCmd.start();
+ try
+ {
+ cmdLine = readOutputLine(pidCmd);
+ }
+ finally
+ {
+ pidCmd.destroy();
+ }
+
+ if (cmdLine == null || !cmdLine.contains("XmlConfiguration"))
+ {
+ Assert.fail("Unable to get Jetty command line");
+ }
+
+ // Need to breakdown commandline into parts, as spaces in command line will cause failures.
+ List<String> execCommands = splitAndUnescapeCommandLine(cmdLine);
+
+ System.out.printf("Executing: %s%n",cmdLine);
+ System.out.printf("Working Dir: %s%n",jettyHomeDir.getAbsolutePath());
+
+ pbCmd = new ProcessBuilder(execCommands);
+ pid = pbCmd.start();
+
+ ConsoleParser parser = new ConsoleParser();
+ List<String[]> jmxList = parser.newPattern("JMX Remote URL: (.*)",0);
+ List<String[]> connList = parser.newPattern("Started [A-Za-z]*Connector@([0-9]*\\.[0-9]*\\.[0-9]*\\.[0-9]*):([0-9]*)",1);
+ // DISABLED: This is what exists in Jetty 9+
+ // List<String[]> connList = parser.newPattern("Started [A-Za-z]*Connector@.*[\\({]([0-9]*\\.[0-9]*\\.[0-9]*\\.[0-9]*):([0-9]*)[\\)}].*",1);
+
+ startPump("STDOUT",parser,this.pid.getInputStream());
+ startPump("STDERR",parser,this.pid.getErrorStream());
+
+ try
+ {
+ parser.waitForDone(this.startTime,this.timeUnit);
+
+ if (!jmxList.isEmpty())
+ {
+ this.jmxUrl = jmxList.get(0)[0];
+ System.out.printf("## Found JMX connector at %s%n",this.jmxUrl);
+ }
+
+ if (!connList.isEmpty())
+ {
+ String[] params = connList.get(0);
+ if (params.length == 2)
+ {
+ this.baseUri = URI.create("http://localhost:" + params[1] + "/");
+ }
+ System.out.printf("## Found Jetty connector at host: %s port: %s%n",(Object[])params);
+ }
+
+ }
+ catch (InterruptedException e)
+ {
+ pid.destroy();
+ Assert.fail("Unable to get required information within time limit");
+ }
+ }
+
+ public static List<String> splitAndUnescapeCommandLine(CharSequence rawCmdLine)
+ {
+ List<String> cmds = new ArrayList<String>();
+
+ int len = rawCmdLine.length();
+ StringBuilder arg = new StringBuilder();
+ boolean escaped = false;
+ boolean inQuote = false;
+ char c;
+ for (int i = 0; i < len; i++)
+ {
+ c = rawCmdLine.charAt(i);
+ if (escaped)
+ {
+ switch (c)
+ {
+ case 'r':
+ arg.append('\r');
+ break;
+ case 'f':
+ arg.append('\f');
+ break;
+ case 't':
+ arg.append('\t');
+ break;
+ case 'n':
+ arg.append('\n');
+ break;
+ case 'b':
+ arg.append('\b');
+ break;
+ default:
+ arg.append(c);
+ break;
+ }
+ escaped = false;
+ continue;
+ }
+
+ if (c == '\\')
+ {
+ escaped = true;
+ }
+ else
+ {
+ if ((c == ' ') && (!inQuote))
+ {
+ // the delim!
+ cmds.add(String.valueOf(arg.toString()));
+ arg.setLength(0);
+ }
+ else if (c == '"')
+ {
+ inQuote = !inQuote;
+ }
+ else
+ {
+ arg.append(c);
+ }
+ }
+ }
+ cmds.add(String.valueOf(arg.toString()));
+
+ return cmds;
+ }
+
+ private String readOutputLine(Process pidCmd) throws IOException
+ {
+ InputStream in = null;
+ InputStreamReader reader = null;
+ BufferedReader buf = null;
+ try
+ {
+ in = pidCmd.getInputStream();
+ reader = new InputStreamReader(in);
+ buf = new BufferedReader(reader);
+ return buf.readLine();
+ }
+ finally
+ {
+ IO.close(buf);
+ IO.close(reader);
+ IO.close(in);
+ }
+ }
+
+ private static class ConsoleParser
+ {
+ private List<ConsolePattern> patterns = new ArrayList<ConsolePattern>();
+ private CountDownLatch latch;
+ private int count;
+
+ public List<String[]> newPattern(String exp, int cnt)
+ {
+ ConsolePattern pat = new ConsolePattern(exp,cnt);
+ patterns.add(pat);
+ count += cnt;
+
+ return pat.getMatches();
+ }
+
+ public void parse(String line)
+ {
+ for (ConsolePattern pat : patterns)
+ {
+ Matcher mat = pat.getMatcher(line);
+ if (mat.find())
+ {
+ int num = 0, count = mat.groupCount();
+ String[] match = new String[count];
+ while (num++ < count)
+ {
+ match[num - 1] = mat.group(num);
+ }
+ pat.getMatches().add(match);
+
+ if (pat.getCount() > 0)
+ {
+ getLatch().countDown();
+ }
+ }
+ }
+ }
+
+ public void waitForDone(long timeout, TimeUnit unit) throws InterruptedException
+ {
+ getLatch().await(timeout,unit);
+ }
+
+ private CountDownLatch getLatch()
+ {
+ synchronized (this)
+ {
+ if (latch == null)
+ {
+ latch = new CountDownLatch(count);
+ }
+ }
+
+ return latch;
+ }
+ }
+
+ private static class ConsolePattern
+ {
+ private Pattern pattern;
+ private List<String[]> matches;
+ private int count;
+
+ ConsolePattern(String exp, int cnt)
+ {
+ pattern = Pattern.compile(exp);
+ matches = new ArrayList<String[]>();
+ count = cnt;
+ }
+
+ public Matcher getMatcher(String line)
+ {
+ return pattern.matcher(line);
+ }
+
+ public List<String[]> getMatches()
+ {
+ return matches;
+ }
+
+ public int getCount()
+ {
+ return count;
+ }
+ }
+
+ private void startPump(String mode, ConsoleParser parser, InputStream inputStream)
+ {
+ ConsoleStreamer pump = new ConsoleStreamer(mode,inputStream);
+ pump.setParser(parser);
+ Thread thread = new Thread(pump,"ConsoleStreamer/" + mode);
+ thread.start();
+ }
+
+ /**
+ * enable debug on the jetty process
+ *
+ * @param debug
+ */
+ public void setDebug(boolean debug)
+ {
+ _debug = debug;
+ }
+
+ private String getJavaBin()
+ {
+ String javaexes[] = new String[]
+ { "java", "java.exe" };
+
+ File javaHomeDir = new File(System.getProperty("java.home"));
+ for (String javaexe : javaexes)
+ {
+ File javabin = new File(javaHomeDir,OS.separators("bin/" + javaexe));
+ if (javabin.exists() && javabin.isFile())
+ {
+ return javabin.getAbsolutePath();
+ }
+ }
+
+ Assert.fail("Unable to find java bin");
+ return "java";
+ }
+
+ /**
+ * Stop the jetty server
+ */
+ public void stop()
+ {
+ System.out.println("Stopping JettyDistro ...");
+ if (pid != null)
+ {
+ // TODO: maybe issue a STOP instead?
+ pid.destroy();
+ }
+ }
+
+ /**
+ * Simple streamer for the console output from a Process
+ */
+ private static class ConsoleStreamer implements Runnable
+ {
+ private String mode;
+ private BufferedReader reader;
+ private ConsoleParser parser;
+
+ public ConsoleStreamer(String mode, InputStream is)
+ {
+ this.mode = mode;
+ this.reader = new BufferedReader(new InputStreamReader(is));
+ }
+
+ public void setParser(ConsoleParser connector)
+ {
+ this.parser = connector;
+ }
+
+ public void run()
+ {
+ String line;
+ // System.out.printf("ConsoleStreamer/%s initiated%n",mode);
+ try
+ {
+ while ((line = reader.readLine()) != (null))
+ {
+ if (parser != null)
+ {
+ parser.parse(line);
+ }
+ System.out.println("[" + mode + "] " + line);
+ }
+ }
+ catch (IOException ignore)
+ {
+ /* ignore */
+ }
+ finally
+ {
+ IO.close(reader);
+ }
+ // System.out.printf("ConsoleStreamer/%s finished%n",mode);
+ }
+ }
+
+ public void setStartTime(long startTime, TimeUnit timeUnit)
+ {
+ this.startTime = startTime;
+ this.timeUnit = timeUnit;
+ }
+}
\ No newline at end of file
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/TestableJettyServer.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/TestableJettyServer.java
index cf3b035..7b1e911 100644
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/TestableJettyServer.java
+++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/TestableJettyServer.java
@@ -120,6 +120,7 @@
for (int i = 0; i < this._xmlConfigurations.size(); i++)
{
URL configURL = this._xmlConfigurations.get(i);
+ System.err.println("configuring: "+configURL);
XmlConfiguration configuration = new XmlConfiguration(configURL);
if (last != null)
{
@@ -176,18 +177,8 @@
_server.start();
// Find the active server port.
- this._serverPort = (-1);
- Connector connectors[] = _server.getConnectors();
- for (int i = 0; i < connectors.length; i++)
- {
- NetworkConnector connector = (NetworkConnector)connectors[i];
- if (connector.getLocalPort() > 0)
- {
- this._serverPort = connector.getLocalPort();
- break;
- }
- }
-
+ this._serverPort = ((NetworkConnector)_server.getConnectors()[0]).getPort();
+ System.err.println("Server Port="+_serverPort);
Assert.assertTrue("Server Port is between 1 and 65535. Actually <" + _serverPort + ">",(1 <= this._serverPort) && (this._serverPort <= 65535));
}
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpRequestTester.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpRequestTester.java
deleted file mode 100644
index 0145e20..0000000
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpRequestTester.java
+++ /dev/null
@@ -1,235 +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.test.support.rawhttp;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-import javax.servlet.http.Cookie;
-
-import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.http.HttpGenerator;
-import org.eclipse.jetty.http.HttpVersion;
-import org.eclipse.jetty.http.MimeTypes;
-import org.eclipse.jetty.util.BufferUtil;
-
-
-/**
- * Assist in Generating Proper Raw HTTP Requests. If you want ultimate control
- * over the Raw HTTP Request, to test non-standard behavior, or partial HTTP
- * Requests, do not use this class.
- *
- * <pre>
- * HttpRequestTester request = new HttpRequestTester();
- *
- * request.setMethod("GET");
- * request.setURI("/uri");
- * request.setHost("fakehost");
- * request.setConnectionClosed();
- *
- * String rawRequest = request.generate();
- *
- * System.out.println("--raw-request--\n" + rawRequest);
- * </pre>
- *
- * <pre>
- * --raw-request--
- * GET /uri HTTP/1.1
- * Host: fakehost
- * Connection: close
- * </pre>
- */
-public class HttpRequestTester
-{
- private HttpFields fields = new HttpFields();
- private String method;
- private String uri;
- private String version;
- private byte[] content;
- private String charset;
- private String defaultCharset;
- private String contentType;
-
- public HttpRequestTester()
- {
- this("UTF-8");
- }
-
- public HttpRequestTester(String defCharset)
- {
- this.defaultCharset = defCharset;
- }
-
- public String getMethod()
- {
- return method;
- }
-
- public void setHost(String host)
- {
- addHeader("Host",host);
- }
-
- public void setMethod(String method)
- {
- this.method = method;
- }
-
- public String getURI()
- {
- return uri;
- }
-
- public void setURI(String uri)
- {
- this.uri = uri;
- }
-
- public String getVersion()
- {
- return version;
- }
-
- public void setVersion(String version)
- {
- this.version = version;
- }
-
- public String getCharset()
- {
- return charset;
- }
-
- public void setCharset(String charset)
- {
- this.charset = charset;
- }
-
- public String getContentType()
- {
- return contentType;
- }
-
- public void setContentType(String contentType)
- {
- this.contentType = contentType;
- }
-
- public void setConnectionClosed()
- {
- fields.add("Connection","close");
- }
-
- /**
- * @param name
- * @param value
- * @throws IllegalArgumentException
- * @see org.eclipse.jetty.http.HttpFields#add(java.lang.String,
- * java.lang.String)
- */
- public void addHeader(String name, String value) throws IllegalArgumentException
- {
- fields.add(name,value);
- }
-
- /**
- * @param name
- * @param date
- * @see org.eclipse.jetty.http.HttpFields#addDateField(java.lang.String,
- * long)
- */
- public void addDateHeader(String name, long date)
- {
- fields.addDateField(name,date);
- }
-
-
- /**
- * @param cookie
- * @see org.eclipse.jetty.http.HttpFields#addSetCookie(org.eclipse.jetty.http.HttpCookie)
- */
- public void addSetCookie(Cookie cookie)
- {
- fields.addSetCookie(cookie.getName(),cookie.getValue(),cookie.getDomain(),cookie.getPath(),cookie.getMaxAge(),cookie.getComment(),cookie.getSecure(),
- false,cookie.getVersion());
- }
-
- public String generate() throws IOException
- {
-
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- ByteBuffer header = null;
- ByteBuffer chunk = null;
- ByteBuffer content = null;
- HttpVersion httpVersion = null;
- if (version == null)
- {
- httpVersion = HttpVersion.HTTP_1_1;
- }
- else
- {
- httpVersion = httpVersion.fromString(version);
- }
-
- HttpGenerator.RequestInfo info = new HttpGenerator.RequestInfo(httpVersion,fields,0,method,uri);
-
- HttpGenerator generator = new HttpGenerator();
- loop: while(!generator.isEnd())
- {
- HttpGenerator.Result result = generator.generateRequest(info, header, chunk, content, true);
- switch(result)
- {
- case NEED_HEADER:
- header=BufferUtil.allocate(8192);
- continue;
-
- case NEED_CHUNK:
- chunk=BufferUtil.allocate(HttpGenerator.CHUNK_SIZE);
- continue;
-
- case NEED_INFO:
- throw new IllegalStateException();
-
- case FLUSH:
- if (BufferUtil.hasContent(header))
- {
- out.write(BufferUtil.toArray(header));
- BufferUtil.clear(header);
- }
- if (BufferUtil.hasContent(chunk))
- {
- out.write(BufferUtil.toArray(chunk));
- BufferUtil.clear(chunk);
- }
- if (BufferUtil.hasContent(content))
- {
- out.write(BufferUtil.toArray(content));
- BufferUtil.clear(content);
- }
- break;
-
- case SHUTDOWN_OUT:
- break loop;
- }
- }
-
- return out.toString();
- }
-}
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpRequestTesterTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpRequestTesterTest.java
index e8c9ef2..5d29bc4 100644
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpRequestTesterTest.java
+++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpRequestTesterTest.java
@@ -19,7 +19,11 @@
package org.eclipse.jetty.test.support.rawhttp;
import java.io.IOException;
+import java.nio.ByteBuffer;
+import org.eclipse.jetty.http.HttpTester;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.util.BufferUtil;
import org.junit.Assert;
import org.junit.Test;
@@ -28,42 +32,45 @@
@Test
public void testBasicHttp10Request() throws IOException
{
- HttpRequestTester request = new HttpRequestTester();
+ HttpTester.Request request = HttpTester.newRequest();
request.setMethod("GET");
request.setURI("/uri");
request.setVersion("HTTP/1.0");
- request.setHost("fakehost");
+ request.put("Host","fakehost");
- String rawRequest = request.generate();
+ ByteBuffer bBuff = request.generate();
StringBuffer expectedRequest = new StringBuffer();
expectedRequest.append("GET /uri HTTP/1.0\r\n");
expectedRequest.append("Host: fakehost\r\n");
expectedRequest.append("\r\n");
- Assert.assertEquals("Basic Request",expectedRequest.toString(),rawRequest);
+ Assert.assertEquals("Basic Request",expectedRequest.toString(),BufferUtil.toString(bBuff));
}
@Test
public void testBasicHttp11Request() throws IOException
{
- HttpRequestTester request = new HttpRequestTester();
+ HttpTester.Request request = HttpTester.newRequest();
request.setMethod("GET");
+ request.setVersion(HttpVersion.HTTP_1_1);
request.setURI("/uri");
- request.setHost("fakehost");
- request.setConnectionClosed();
+ request.put("Host","fakehost");
+ request.put("Connection", "close");
+ request.setContent("aaa");
+
- String rawRequest = request.generate();
+ ByteBuffer bBuff = request.generate();
StringBuffer expectedRequest = new StringBuffer();
expectedRequest.append("GET /uri HTTP/1.1\r\n");
expectedRequest.append("Host: fakehost\r\n");
expectedRequest.append("Connection: close\r\n");
- expectedRequest.append("Transfer-Encoding: chunked\r\n");
+ expectedRequest.append("Content-Length: 3\r\n");
expectedRequest.append("\r\n");
- expectedRequest.append("0\r\n");
- expectedRequest.append("\r\n");
+ expectedRequest.append("aaa");
+
- Assert.assertEquals("Basic Request",expectedRequest.toString(),rawRequest);
+ Assert.assertEquals("Basic Request",expectedRequest.toString(),BufferUtil.toString(bBuff));
}
}
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpResponseTester.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpResponseTester.java
deleted file mode 100644
index 4a4f6dd..0000000
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpResponseTester.java
+++ /dev/null
@@ -1,461 +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.test.support.rawhttp;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.StringReader;
-import java.util.ArrayList;
-import java.util.List;
-
-import junit.framework.Assert;
-
-import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.http.HttpHeaders;
-import org.eclipse.jetty.http.HttpParser;
-import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.http.MimeTypes;
-import org.eclipse.jetty.io.Buffer;
-import org.eclipse.jetty.io.ByteArrayBuffer;
-import org.eclipse.jetty.io.View;
-import org.eclipse.jetty.test.support.StringUtil;
-import org.eclipse.jetty.toolchain.test.StringAssert;
-import org.eclipse.jetty.util.ByteArrayOutputStream2;
-
-/**
- * Assists in testing of HTTP Responses.
- */
-public class HttpResponseTester
-{
- private class PH extends HttpParser.EventHandler
- {
- @Override
- public void content(Buffer ref) throws IOException
- {
- if (content == null)
- content = new ByteArrayOutputStream2();
- content.write(ref.asArray());
- }
-
- @Override
- public void headerComplete() throws IOException
- {
- contentType = fields.get(HttpHeaders.CONTENT_TYPE_BUFFER);
- if (contentType != null)
- {
- String calcCharset = MimeTypes.getCharsetFromContentType(contentType);
- if (calcCharset != null)
- {
- charset = calcCharset;
- }
- }
- }
-
- @Override
- public void messageComplete(long contextLength) throws IOException
- {
- }
-
- @Override
- public void parsedHeader(Buffer name, Buffer value) throws IOException
- {
- fields.add(name,value);
- }
-
- @Override
- public void startRequest(Buffer method, Buffer url, Buffer version) throws IOException
- {
- reset();
- HttpResponseTester.this.method = getString(method);
- HttpResponseTester.this.uri = getString(url);
- HttpResponseTester.this.version = getString(version);
- }
-
- @Override
- public void startResponse(Buffer version, int status, Buffer reason) throws IOException
- {
- reset();
- HttpResponseTester.this.version = getString(version);
- HttpResponseTester.this.status = status;
- HttpResponseTester.this.reason = getString(reason);
- }
- }
-
- public static List<HttpResponseTester> parseMulti(CharSequence rawHTTP) throws IOException
- {
- List<HttpResponseTester> responses = new ArrayList<HttpResponseTester>();
- String parse = rawHTTP.toString();
- while (StringUtil.isNotBlank(parse))
- {
- HttpResponseTester response = new HttpResponseTester();
- parse = response.parse(parse);
- responses.add(response);
- }
-
- return responses;
- }
-
- private HttpFields fields = new HttpFields();
- private CharSequence rawResponse;
- private String method;
- private String uri;
- private String version;
- private int status;
- private String reason;
- private Buffer contentType;
- private ByteArrayOutputStream2 content;
- private String charset;
- private String defaultCharset;
-
- public HttpResponseTester()
- {
- this("UTF-8");
- }
-
- public HttpResponseTester(String defCharset)
- {
- this.defaultCharset = defCharset;
- }
-
- public String getMethod()
- {
- return method;
- }
-
- public String getURI()
- {
- return uri;
- }
-
- public String getVersion()
- {
- return version;
- }
-
- public int getStatus()
- {
- return status;
- }
-
- public CharSequence getRawResponse()
- {
- return rawResponse;
- }
-
- public String getReason()
- {
- return reason;
- }
-
- public String getContentType()
- {
- if (contentType == null)
- {
- return null;
- }
- return contentType.toString();
- }
-
- public ByteArrayOutputStream2 getContentBytes()
- {
- return content;
- }
-
- public String getContent()
- {
- return content.toString();
- }
-
- public String getBody()
- {
- return content.toString();
- }
-
- private byte[] getByteArray(CharSequence str)
- {
- if (charset == null)
- {
- return str.toString().getBytes();
- }
-
- try
- {
- return str.toString().getBytes(charset);
- }
- catch (Exception e)
- {
- return str.toString().getBytes();
- }
- }
-
- private String getString(Buffer buffer)
- {
- return getString(buffer.asArray());
- }
-
- private String getString(byte[] b)
- {
- if (charset == null)
- {
- return new String(b);
- }
-
- try
- {
- return new String(b,charset);
- }
- catch (Exception e)
- {
- return new String(b);
- }
- }
-
- /**
- * @param name
- * @return the header value as a date
- * @see org.eclipse.jetty.http.HttpFields#getDateField(java.lang.String)
- */
- public long getDateHeader(String name)
- {
- return fields.getDateField(name);
- }
-
- /**
- * @param name
- * @return the header value as a long
- * @throws NumberFormatException
- * @see org.eclipse.jetty.http.HttpFields#getLongField(java.lang.String)
- */
- public long getLongHeader(String name) throws NumberFormatException
- {
- return fields.getLongField(name);
- }
-
- /**
- * @param name
- * @return the header value
- * @see org.eclipse.jetty.http.HttpFields#getStringField(java.lang.String)
- */
- public String getHeader(String name)
- {
- return fields.getStringField(name);
- }
-
- public boolean hasHeader(String headerKey)
- {
- return fields.containsKey(headerKey);
- }
-
- /**
- * Parse on HTTP Response
- *
- * @param rawHTTP
- * Raw HTTP to parse
- * @return Any unparsed data in the rawHTTP (eg pipelined requests)
- * @throws IOException
- */
- public String parse(CharSequence rawHTTP) throws IOException
- {
- this.charset = defaultCharset;
- this.rawResponse = rawHTTP;
- ByteArrayBuffer buf = new ByteArrayBuffer(getByteArray(rawHTTP));
- View view = new View(buf);
- HttpParser parser = new HttpParser(view,new PH());
- parser.parse();
- return getString(view.asArray());
- }
-
- public void reset()
- {
- fields.clear();
- method = null;
- uri = null;
- version = null;
- status = 0;
- reason = null;
- content = null;
- }
-
- /**
- * Make sure that status code is "OK"
- */
- public void assertStatusOK()
- {
- assertStatus(HttpStatus.OK_200,"OK");
- }
-
- public void assertStatusOK(String msg)
- {
- assertStatus(msg,HttpStatus.OK_200,"OK");
- }
-
- public void assertStatus(int expectedStatus, String expectedReason)
- {
- Assert.assertEquals("Response.status",expectedStatus,this.status);
- Assert.assertEquals("Response.reason",expectedReason,this.reason);
- }
-
- public void assertStatus(String msg, int expectedStatus, String expectedReason)
- {
- Assert.assertEquals(msg + ": Response.status",expectedStatus,this.status);
- Assert.assertEquals(msg + ": Response.reason",expectedReason,this.reason);
- }
-
- public void assertStatus(String msg, int expectedStatus)
- {
- assertStatus(msg,expectedStatus,HttpStatus.getMessage(expectedStatus));
- }
-
- public void assertContentType(String expectedType)
- {
- assertHeader("Content-Type",expectedType);
- }
-
- private void assertHeader(String headerKey, String expectedValue)
- {
- String actual = fields.getStringField(headerKey);
- Assert.assertNotNull("Response[" + headerKey + "] should not be null",actual);
- Assert.assertEquals("Response[" + headerKey + "]",expectedValue,actual);
- }
-
- public void assertHeader(String msg, String headerKey, String expectedValue)
- {
- String actual = fields.getStringField(headerKey);
- Assert.assertNotNull(msg + ": Response[" + headerKey + "] should not be null, expecting <" + expectedValue + ">",actual);
- Assert.assertEquals(msg + ": Response[" + headerKey + "]",expectedValue,actual);
- }
-
- public void assertBody(String expected)
- {
- Assert.assertNotNull("Response.content should not be null",this.content);
- String actual = this.content.toString();
- Assert.assertEquals("Response.content",expected,actual);
- }
-
- public void assertBody(String msg, String expected)
- {
- Assert.assertNotNull(msg + ": Response.content should not be null",this.content);
- String actual = this.content.toString();
- Assert.assertEquals(msg + ": Response.content",expected,actual);
- }
-
- public void assertNoBody(String msg)
- {
- Assert.assertNull(msg + ": Response.content should be null",this.content);
- }
-
- public void assertBodyContains(String msg, String expectedNeedle)
- {
- StringAssert.assertContains(msg + ": Response Content",this.content.toString(),expectedNeedle);
- }
-
- public void assertHeaderExists(String msg, String expectedHeaderKey)
- {
- Assert.assertTrue(msg + ": header <" + expectedHeaderKey + "> should exist",fields.containsKey(expectedHeaderKey));
- }
-
- public void assertHeaderNotPresent(String msg, String headerKey)
- {
- Assert.assertFalse(msg + ": header <" + headerKey + "> should NOT exist",fields.containsKey(headerKey));
- }
-
- public List<HttpResponseTester> findBodyMultiparts(String boundary) throws IOException
- {
- List<HttpResponseTester> multiparts = new ArrayList<HttpResponseTester>();
-
- BufferedReader buf = new BufferedReader(new StringReader(getBody()));
- String line;
- String startBoundary = "--" + boundary;
- String endBoundary = "--" + boundary + "--";
- HttpResponseTester resp = null;
- boolean parsingHeader = true;
- boolean previousBodyLine = false;
-
- while ((line = buf.readLine()) != null)
- {
- if (line.equals(startBoundary))
- {
- // end of multipart, start a new one.
- if (resp != null)
- {
- multiparts.add(resp);
- }
- resp = new HttpResponseTester();
- parsingHeader = true;
- previousBodyLine = false;
- continue;
- }
-
- if (line.equals(endBoundary))
- {
- if (resp != null)
- {
- multiparts.add(resp);
- }
- break;
- }
-
- if (parsingHeader)
- {
- if (line.equals(""))
- {
- parsingHeader = false;
- continue;
- }
-
- resp.parseHeader(line);
- }
- else
- {
- if (previousBodyLine)
- {
- resp.appendBody("\n");
- }
- resp.appendBody(line);
- previousBodyLine = true;
- }
- }
-
- return multiparts;
- }
-
- public void parseHeader(String line)
- {
- int idx = line.indexOf(":");
- String key = line.substring(0,idx).trim();
- String val = line.substring(idx + 1).trim();
-
- fields.add(key,val);
- }
-
- public void appendBody(String s) throws IOException
- {
- appendBody(s.getBytes());
- }
-
- public void appendBody(byte buf[]) throws IOException
- {
- if (content == null)
- {
- content = new ByteArrayOutputStream2();
- }
-
- content.write(buf);
- }
-}
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpResponseTesterTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpResponseTesterTest.java
index 59c97d6..bac0b4a 100644
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpResponseTesterTest.java
+++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpResponseTesterTest.java
@@ -25,6 +25,9 @@
import java.io.IOException;
import java.util.List;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpTester;
import org.junit.Assert;
import org.junit.Test;
@@ -45,16 +48,15 @@
rawResponse.append("ABCDEFGHIJKLMNOPQRSTTUVWXYZ\n");
rawResponse.append("\n");
- HttpResponseTester response = new HttpResponseTester();
- response.parse(rawResponse);
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse.toString());
- Assert.assertEquals("Response.version","HTTP/1.1",response.getVersion());
+ Assert.assertEquals("Response.version","HTTP/1.1",response.getVersion().asString());
Assert.assertEquals("Response.status",200,response.getStatus());
Assert.assertEquals("Response.reason","OK",response.getReason());
- Assert.assertEquals("Response[Content-Type]","text/plain",response.getContentType());
- Assert.assertEquals("Response[Content-Length]",28,response.getLongHeader("Content-Length"));
- Assert.assertEquals("Response[Connection]","close",response.getHeader("Connection"));
+ Assert.assertEquals("Response[Content-Type]","text/plain",response.get(HttpHeader.CONTENT_TYPE));
+ Assert.assertEquals("Response[Content-Length]",28,response.getLongField("Content-Length"));
+ Assert.assertEquals("Response[Connection]","close",response.get("Connection"));
String expected = "ABCDEFGHIJKLMNOPQRSTTUVWXYZ\n";
@@ -73,6 +75,7 @@
rawResponse.append("Server: Jetty(7.0.y.z-SNAPSHOT)\n");
rawResponse.append("\n");
rawResponse.append("ABCDEFGHIJKLMNOPQRSTTUVWXYZ\n");
+
rawResponse.append("HTTP/1.1 200 OK\n");
rawResponse.append("Date: Mon, 08 Jun 2009 23:05:26 GMT\n");
rawResponse.append("Content-Type: text/plain\n");
@@ -82,6 +85,7 @@
rawResponse.append("\n");
rawResponse.append("Host=Default\n");
rawResponse.append("Resource=R1\n");
+
rawResponse.append("HTTP/1.1 200 OK\n");
rawResponse.append("Date: Mon, 08 Jun 2009 23:05:26 GMT\n");
rawResponse.append("Content-Type: text/plain\n");
@@ -93,27 +97,32 @@
rawResponse.append("Host=Default\n");
rawResponse.append("Resource=R2\n");
rawResponse.append("\n");
+
+
+ List<HttpTester.Response> responses = HttpTesting.readResponses(rawResponse.toString());
- List<HttpResponseTester> responses = HttpResponseTester.parseMulti(rawResponse);
Assert.assertNotNull("Responses should not be null",responses);
Assert.assertEquals("Responses.size",3,responses.size());
- HttpResponseTester resp1 = responses.get(0);
- resp1.assertStatusOK();
- resp1.assertContentType("text/plain");
- resp1.assertBody("ABCDEFGHIJKLMNOPQRSTTUVWXYZ\n");
- assertThat(resp1.getHeader("Connection"),is(not("close")));
+ HttpTester.Response resp1 = responses.get(0);
+ System.err.println(resp1.toString());
+ Assert.assertEquals(HttpStatus.OK_200, resp1.getStatus());
+ Assert.assertEquals("text/plain", resp1.get("Content-Type"));
+ Assert.assertTrue(resp1.getContent().contains("ABCDEFGHIJKLMNOPQRSTTUVWXYZ\n"));
+ assertThat(resp1.get("Connection"),is(not("close")));
- HttpResponseTester resp2 = responses.get(1);
- resp2.assertStatusOK();
- resp2.assertContentType("text/plain");
- resp2.assertBody("Host=Default\nResource=R1\n");
- assertThat(resp2.getHeader("Connection"),is(not("close")));
+ HttpTester.Response resp2 = responses.get(1);
+ System.err.println(resp2.toString());
+ Assert.assertEquals(HttpStatus.OK_200, resp2.getStatus());
+ Assert.assertEquals("text/plain", resp2.get("Content-Type"));
+ Assert.assertTrue(resp2.getContent().contains("Host=Default\nResource=R1\n"));
+ assertThat(resp2.get("Connection"),is(not("close")));
- HttpResponseTester resp3 = responses.get(2);
- resp3.assertStatusOK();
- resp3.assertContentType("text/plain");
- resp3.assertBody("Host=Default\nResource=R2\n");
- assertThat(resp3.getHeader("Connection"),is("close"));
+ HttpTester.Response resp3 = responses.get(2);
+ System.err.println(resp3.toString());
+ Assert.assertEquals(HttpStatus.OK_200, resp3.getStatus());
+ Assert.assertEquals("text/plain", resp3.get("Content-Type"));
+ Assert.assertTrue(resp3.getContent().contains("Host=Default\nResource=R2\n"));
+ assertThat(resp3.get("Connection"),is("close"));
}
}
diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpTesting.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpTesting.java
index 2e05e8b..c81fa3f 100644
--- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpTesting.java
+++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpTesting.java
@@ -18,19 +18,25 @@
package org.eclipse.jetty.test.support.rawhttp;
+import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.io.StringReader;
import java.io.StringWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
import java.util.List;
-import org.eclipse.jetty.test.support.StringUtil;
+import org.eclipse.jetty.http.HttpTester;
+import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.StringUtil;
/**
* Testing utility for performing RAW HTTP request/response.
@@ -42,6 +48,102 @@
private InetAddress serverHost;
private int serverPort;
private int timeoutMillis = 5000;
+
+
+ public static List<HttpTester.Response> getParts (String boundary, HttpTester.Response response) throws IOException
+ {
+ List<HttpTester.Response> parts = new ArrayList<HttpTester.Response>();
+
+ BufferedReader buf = new BufferedReader(new StringReader(response.getContent()));
+ String line;
+ String startBoundary = "--" + boundary;
+ String endBoundary = "--" + boundary + "--";
+
+ StringBuffer partBuff = null;
+ boolean parsingHeader = true;
+ boolean previousBodyLine = false;
+
+ while ((line = buf.readLine()) != null)
+ {
+ if (line.equals(startBoundary))
+ {
+ // end of multipart, start a new one.
+ if (partBuff != null)
+ {
+ HttpTester.Response part = HttpTester.parseResponse(partBuff.toString());
+ parts.add(part);
+ }
+ partBuff = new StringBuffer();
+ parsingHeader = true;
+ previousBodyLine = false;
+ continue;
+ }
+
+ if (line.equals(endBoundary))
+ {
+ if (partBuff != null)
+ {
+ HttpTester.Response part = HttpTester.parseResponse(partBuff.toString());
+ parts.add(part);
+ }
+ break;
+ }
+
+ if (parsingHeader)
+ {
+ if (line.equals(""))
+ {
+ parsingHeader = false;
+ continue;
+ }
+
+ partBuff.append(line);
+ }
+ else
+ {
+ if (previousBodyLine)
+ {
+ partBuff.append("\n");
+ }
+ partBuff.append(line);
+ previousBodyLine = true;
+ }
+ }
+
+ return parts;
+
+ }
+
+
+
+ public static List<HttpTester.Response> readResponses(ByteBuffer buffer) throws IOException
+ {
+ List<HttpTester.Response> list = new ArrayList<>();
+
+ while(BufferUtil.hasContent(buffer))
+ {
+ HttpTester.Response response = HttpTester.parseResponse(buffer);
+ if (response == null)
+ break;
+ list.add(HttpTester.parseResponse(buffer));
+ }
+ return list;
+ }
+
+ public static List<HttpTester.Response> readResponses(String string) throws IOException
+ {
+ List<HttpTester.Response> list = new ArrayList<>();
+
+ ByteBuffer buffer = BufferUtil.toBuffer(string);
+ while(BufferUtil.hasContent(buffer))
+ {
+ HttpTester.Response response = HttpTester.parseResponse(buffer);
+ if (response == null)
+ break;
+ list.add(response);
+ }
+ return list;
+ }
public HttpTesting(HttpSocket httpSocket, InetAddress host, int port)
{
@@ -115,11 +217,25 @@
* @return the response object
* @throws IOException
*/
- public HttpResponseTester read(Socket sock) throws IOException
+ public HttpTester.Response read(Socket sock) throws IOException
{
- HttpResponseTester response = new HttpResponseTester();
- response.parse(readRaw(sock));
- return response;
+ return HttpTester.parseResponse(readRaw(sock));
+ }
+
+
+ public List<HttpTester.Response> readResponses(Socket sock) throws IOException
+ {
+ List<HttpTester.Response> list = new ArrayList<>();
+ String r = readRaw(sock);
+ ByteBuffer buffer = BufferUtil.toBuffer(r);
+ while(BufferUtil.hasContent(buffer))
+ {
+ HttpTester.Response response = HttpTester.parseResponse(buffer);
+ if (response == null)
+ break;
+ list.add(response);
+ }
+ return list;
}
/**
@@ -130,16 +246,15 @@
* @return the response object
* @throws IOException
*/
- public HttpResponseTester readAvailable(Socket sock) throws IOException
+ public HttpTester.Response readAvailable(Socket sock) throws IOException
{
- HttpResponseTester response = new HttpResponseTester();
+
String rawResponse = readRawAvailable(sock);
if (StringUtil.isBlank(rawResponse))
{
return null;
}
- response.parse(rawResponse);
- return response;
+ return HttpTester.parseResponse(rawResponse);
}
/**
@@ -199,7 +314,7 @@
* @return the response
* @throws IOException
*/
- public HttpResponseTester request(CharSequence rawRequest) throws IOException
+ public HttpTester.Response request(CharSequence rawRequest) throws IOException
{
Socket sock = open();
try
@@ -223,10 +338,10 @@
* @return the response
* @throws IOException
*/
- public HttpResponseTester request(HttpRequestTester request) throws IOException
+ public HttpTester.Response request(HttpTester.Request request) throws IOException
{
- String rawRequest = request.generate();
- return request(rawRequest);
+ ByteBuffer byteBuff = request.generate();
+ return request(BufferUtil.toString(byteBuff));
}
/**
@@ -237,7 +352,7 @@
* @return the responses.
* @throws IOException
*/
- public List<HttpResponseTester> requests(CharSequence rawRequests) throws IOException
+ public List<HttpTester.Response> requests(CharSequence rawRequests) throws IOException
{
Socket sock = open();
try
@@ -247,7 +362,7 @@
// Collect response
String rawResponses = IO.toString(sock.getInputStream());
DEBUG("--raw-response--\n" + rawResponses);
- return HttpResponseTester.parseMulti(rawResponses);
+ return readResponses(rawResponses);
}
finally
{
@@ -255,24 +370,6 @@
}
}
- /**
- * Initiate a multiple HTTP requests, parse the responses
- *
- * @param requests
- * the request objects.
- * @return the response objects.
- * @throws IOException
- */
- public List<HttpResponseTester> requests(List<HttpRequestTester> requests) throws IOException
- {
- StringBuffer rawRequest = new StringBuffer();
- for (HttpRequestTester request : requests)
- {
- rawRequest.append(request.generate());
- }
-
- return requests(rawRequest);
- }
/**
* Send a data (as request) to open socket.
diff --git a/tests/test-integration/src/test/resources/DefaultHandler.xml b/tests/test-integration/src/test/resources/DefaultHandler.xml
index dd6805d..644e7f9 100644
--- a/tests/test-integration/src/test/resources/DefaultHandler.xml
+++ b/tests/test-integration/src/test/resources/DefaultHandler.xml
@@ -12,32 +12,23 @@
<Configure id="Server" class="org.eclipse.jetty.server.Server">
- <!-- =========================================================== -->
- <!-- Server Thread Pool -->
- <!-- =========================================================== -->
- <Set name="ThreadPool">
- <New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
- <Set name="minThreads">10</Set>
- <Set name="maxThreads">200</Set>
- </New>
- </Set>
+ <New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
+ <Set name="secureScheme">https</Set>
+ <Set name="securePort"><Property name="jetty.secure.port" default="8443" /></Set>
+ <Set name="outputBufferSize">32768</Set>
+ <Set name="requestHeaderSize">8192</Set>
+ <Set name="responseHeaderSize">8192</Set>
+ <Set name="sendServerVersion">true</Set>
+ <Set name="sendDateHeader">false</Set>
+ <Set name="headerCacheSize">512</Set>
- <!-- =========================================================== -->
- <!-- Set connectors -->
- <!-- =========================================================== -->
+ <!-- Uncomment to enable handling of X-Forwarded- style headers
+ <Call name="addCustomizer">
+ <Arg><New class="org.eclipse.jetty.server.ForwardedRequestCustomizer"/></Arg>
+ </Call>
+ -->
+ </New>
- <Call name="addConnector">
- <Arg>
- <New class="org.eclipse.jetty.server.ServerConnector">
- <Set name="host"><SystemProperty name="jetty.host" /></Set>
- <Set name="port"><SystemProperty name="jetty.port" default="0"/></Set>
- <Set name="idleTimeout">300000</Set>
- <Set name="Acceptors">2</Set>
- <Set name="statsOn">false</Set>
- <!--<Set name="confidentialPort">8443</Set>-->
- </New>
- </Arg>
- </Call>
<!-- =========================================================== -->
<!-- Set handler Collection Structure -->
diff --git a/tests/test-integration/src/test/resources/NIOHttp.xml b/tests/test-integration/src/test/resources/NIOHttp.xml
index 4372a16..793da7a 100644
--- a/tests/test-integration/src/test/resources/NIOHttp.xml
+++ b/tests/test-integration/src/test/resources/NIOHttp.xml
@@ -6,17 +6,27 @@
<!-- =========================================================== -->
<!-- Set connectors -->
<!-- =========================================================== -->
-
+
<Call name="addConnector">
- <Arg>
- <New class="org.eclipse.jetty.server.ServerConnector">
- <Set name="host"><SystemProperty name="jetty.host" /></Set>
- <Set name="port"><SystemProperty name="jetty.port" default="0"/></Set>
- <Set name="idleTimeout">300000</Set>
- <Set name="Acceptors">2</Set>
- <Set name="statsOn">false</Set>
- </New>
- </Arg>
- </Call>
+ <Arg>
+ <New class="org.eclipse.jetty.server.ServerConnector">
+ <Arg name="server"><Ref refid="Server" /></Arg>
+ <Arg name="factories">
+ <Array type="org.eclipse.jetty.server.ConnectionFactory">
+ <Item>
+ <New class="org.eclipse.jetty.server.HttpConnectionFactory">
+ <Arg name="config"><Ref refid="httpConfig" /></Arg>
+ </New>
+ </Item>
+ </Array>
+ </Arg>
+ <Set name="host"><Property name="jetty.host" /></Set>
+ <Set name="port"><Property name="jetty.port" default="8080" /></Set>
+ <Set name="idleTimeout"><Property name="http.timeout" default="30000"/></Set>
+ </New>
+ </Arg>
+ </Call>
+
+
</Configure>
diff --git a/tests/test-integration/src/test/resources/NIOHttps.xml b/tests/test-integration/src/test/resources/NIOHttps.xml
index d99d67e..ff42c72 100644
--- a/tests/test-integration/src/test/resources/NIOHttps.xml
+++ b/tests/test-integration/src/test/resources/NIOHttps.xml
@@ -5,21 +5,33 @@
<!-- =========================================================== -->
<!-- Set connectors -->
- <!-- =========================================================== -->
-
- <Call name="addConnector">
- <Arg>
- <New class="org.eclipse.jetty.server.ServerConnector">
- <Set name="host"><SystemProperty name="jetty.host" /></Set>
- <Set name="port"><SystemProperty name="jetty.port" default="0"/></Set>
- <Set name="idleTimeout">300000</Set>
- <Set name="Acceptors">2</Set>
- <Set name="statsOn">false</Set>
- <Set name="keystore"><Property name="test.resourcesdir" default="src/test/resources" />/keystore</Set>
- <Set name="password">OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4</Set>
- <Set name="keyPassword">OBF:1u2u1wml1z7s1z7a1wnl1u2g</Set>
- </New>
- </Arg>
- </Call>
+ <!-- =========================================================== -->
+ <Call id="httpsConnector" name="addConnector">
+ <Arg>
+ <New class="org.eclipse.jetty.server.ServerConnector">
+ <Arg name="server"><Ref refid="Server" /></Arg>
+ <Arg name="factories">
+ <Array type="org.eclipse.jetty.server.ConnectionFactory">
+ <Item>
+ <New class="org.eclipse.jetty.server.SslConnectionFactory">
+ <Arg name="next">http/1.1</Arg>
+ <Arg name="sslContextFactory"><Ref refid="sslContextFactory"/></Arg>
+ </New>
+ </Item>
+ <Item>
+ <New class="org.eclipse.jetty.server.HttpConnectionFactory">
+ <Arg name="config"><Ref refid="sslHttpConfig"/></Arg>
+ </New>
+ </Item>
+ </Array>
+ </Arg>
+ <Set name="host"><Property name="jetty.host" /></Set>
+ <Set name="port"><Property name="jetty.https.port" default="8443" /></Set>
+ <Set name="idleTimeout">30000</Set>
+ </New>
+ </Arg>
+ </Call>
+
+
</Configure>
diff --git a/tests/test-integration/src/test/resources/RFC2616Base.xml b/tests/test-integration/src/test/resources/RFC2616Base.xml
index b110e94..3cc7a4b 100644
--- a/tests/test-integration/src/test/resources/RFC2616Base.xml
+++ b/tests/test-integration/src/test/resources/RFC2616Base.xml
@@ -12,38 +12,22 @@
<Configure id="Server" class="org.eclipse.jetty.server.Server">
- <!-- =========================================================== -->
- <!-- Server Thread Pool -->
- <!-- =========================================================== -->
- <Set name="ThreadPool">
- <New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
- <Set name="minThreads">10</Set>
- <Set name="maxThreads">200</Set>
- </New>
- </Set>
+ <New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
+ <Set name="secureScheme">https</Set>
+ <Set name="securePort"><Property name="jetty.secure.port" default="8443" /></Set>
+ <Set name="outputBufferSize">32768</Set>
+ <Set name="requestHeaderSize">8192</Set>
+ <Set name="responseHeaderSize">8192</Set>
+ <Set name="sendServerVersion">true</Set>
+ <Set name="sendDateHeader">false</Set>
+ <Set name="headerCacheSize">512</Set>
- <!-- =========================================================== -->
- <!-- No connectors Set Here. -->
- <!-- See: -->
- <!-- BIOHttp.xml -->
- <!-- BIOHttps.xml -->
- <!-- NIOHttp.xml -->
- <!-- NIOHttps.xml -->
- <!-- =========================================================== -->
-
- <!--
- <Call name="addConnector">
- <Arg>
- <New class="org.eclipse.jetty.server.ServerConnector">
- <Set name="host"><SystemProperty name="jetty.host" /></Set>
- <Set name="port"><SystemProperty name="jetty.port" default="0"/></Set>
- <Set name="idleTimeout">300000</Set>
- <Set name="Acceptors">2</Set>
- <Set name="statsOn">false</Set>
- </New>
- </Arg>
- </Call>
- -->
+ <!-- Uncomment to enable handling of X-Forwarded- style headers
+ <Call name="addCustomizer">
+ <Arg><New class="org.eclipse.jetty.server.ForwardedRequestCustomizer"/></Arg>
+ </Call>
+ -->
+ </New>
<!-- =========================================================== -->
<!-- Set handler Collection Structure -->
@@ -88,52 +72,37 @@
</New>
</Set>
- <Call name="addBean">
- <Arg>
- <New class="org.eclipse.jetty.deploy.ContextDeployer">
- <Set name="contexts"><Ref refid="WebappContexts"/></Set>
- <Set name="configurationDir"><Property name="test.resourcesdir" default="src/test/resources"/>/webapp-contexts/RFC2616</Set>
- <Set name="scanInterval">0</Set>
- <Set name="configurationManager">
- <New class="org.eclipse.jetty.deploy.PropertiesConfigurationManager">
- <Set name="file"><Property name="test.targetdir" default="target"/>/testable-jetty-server-config.properties</Set>
- </New>
- </Set>
- </New>
- </Arg>
- </Call>
+ <Call name="addBean">
+ <Arg>
+ <New id="DeploymentManager" class="org.eclipse.jetty.deploy.DeploymentManager">
+ <Set name="contexts">
+ <Ref refid="Contexts" />
+ </Set>
+ <Call name="setContextAttribute">
+ <Arg>org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern</Arg>
+ <Arg>.*/servlet-api-[^/]*\.jar$</Arg>
+ </Call>
- <!-- =========================================================== -->
- <!-- Configure the webapp deployer. -->
- <!-- A webapp deployer will deploy standard webapps discovered -->
- <!-- in a directory at startup, without the need for additional -->
- <!-- configuration files. It does not support hot deploy or -->
- <!-- non standard contexts (see ContextDeployer above). -->
- <!-- -->
- <!-- This deployer is configured to deploy webapps from the -->
- <!-- $JETTY_HOME/webapps directory -->
- <!-- -->
- <!-- Normally only one type of deployer need be used. -->
- <!-- -->
- <!-- =========================================================== -->
- <!--
- <Call name="addBean">
- <Arg>
- <New class="org.eclipse.jetty.deploy.WebAppDeployer">
- <Set name="contexts"><Ref refid="WebappContexts"/></Set>
- <Set name="webAppDir"><Property name="test.targetdir" default="target"/>/webapps</Set>
- <Set name="parentLoaderPriority">false</Set>
- <Set name="extract">true</Set>
- <Set name="allowDuplicates">false</Set>
- <Set name="defaultsDescriptor"><Property name="test.resourcesdir" default="src/test/resources"/>/webdefault.xml</Set>
- <Call name="setAttribute">
- <Arg>org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern</Arg>
- <Arg>.*/jsp-api-[^/]*\.jar$|.*/jsp-[^/]*\.jar$</Arg>
- </Call>
- </New>
- </Arg>
- </Call>
- -->
+ <Call id="webappprovider" name="addAppProvider">
+ <Arg>
+ <New class="org.eclipse.jetty.deploy.providers.WebAppProvider">
+ <Set name="monitoredDirName"><Property name="test.resourcesdir" default="src/test/resources" />/webapps-contexts/RFC2616</Set>
+ <Set name="scanInterval">1</Set>
+ <Set name="extractWars">true</Set>
+ <Set name="configurationManager">
+ <New class="org.eclipse.jetty.deploy.PropertiesConfigurationManager">
+ <Set name="file"><Property name="test.targetdir" default="target"/>/testable-jetty-server-config.properties</Set>
+ </New>
+ </Set>
+ </New>
+ </Arg>
+ </Call>
+
+ </New>
+ </Arg>
+ </Call>
+
+
<!-- =========================================================== -->
<!-- extra options -->
diff --git a/tests/test-integration/src/test/resources/ssl.xml b/tests/test-integration/src/test/resources/ssl.xml
new file mode 100644
index 0000000..84de070
--- /dev/null
+++ b/tests/test-integration/src/test/resources/ssl.xml
@@ -0,0 +1,33 @@
+<Configure id="sslContextFactory" class="org.eclipse.jetty.util.ssl.SslContextFactory">
+ <Set name="KeyStorePath"><Property name="jetty.home" default="." />/<Property name="jetty.keystore" default="keystore"/></Set>
+ <Set name="KeyStorePassword"><Property name="jetty.keystore.password" default="OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4"/></Set>
+ <Set name="KeyManagerPassword"><Property name="jetty.keymanager.password" default="OBF:1u2u1wml1z7s1z7a1wnl1u2g"/></Set>
+ <Set name="TrustStorePath"><Property name="jetty.home" default="." />/<Property name="jetty.truststore" default="keystore"/></Set>
+ <Set name="TrustStorePassword"><Property name="jetty.truststore.password" default="OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4"/></Set>
+ <Set name="EndpointIdentificationAlgorithm"></Set>
+ <Set name="ExcludeCipherSuites">
+ <Array type="String">
+ <Item>SSL_RSA_WITH_DES_CBC_SHA</Item>
+ <Item>SSL_DHE_RSA_WITH_DES_CBC_SHA</Item>
+ <Item>SSL_DHE_DSS_WITH_DES_CBC_SHA</Item>
+ <Item>SSL_RSA_EXPORT_WITH_RC4_40_MD5</Item>
+ <Item>SSL_RSA_EXPORT_WITH_DES40_CBC_SHA</Item>
+ <Item>SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA</Item>
+ <Item>SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA</Item>
+ </Array>
+ </Set>
+
+ <!-- =========================================================== -->
+ <!-- Create a TLS specific HttpConfiguration based on the -->
+ <!-- common HttpConfiguration defined in jetty.xml -->
+ <!-- Add a SecureRequestCustomizer to extract certificate and -->
+ <!-- session information -->
+ <!-- =========================================================== -->
+ <New id="sslHttpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
+ <Arg><Ref refid="httpConfig"/></Arg>
+ <Call name="addCustomizer">
+ <Arg><New class="org.eclipse.jetty.server.SecureRequestCustomizer"/></Arg>
+ </Call>
+ </New>
+
+</Configure>
diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractNewSessionTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractNewSessionTest.java
index a3d3af0..fc8fa98 100644
--- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractNewSessionTest.java
+++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractNewSessionTest.java
@@ -57,12 +57,12 @@
@Test
public void testNewSession() throws Exception
{
- String contextPath = "";
String servletMapping = "/server";
int scavengePeriod = 3;
AbstractTestServer server = createServer(0, 1, scavengePeriod);
- ServletContextHandler context = server.addContext(contextPath);
+ ServletContextHandler context = server.addContext("/");
context.addServlet(TestServlet.class, servletMapping);
+ String contextPath = "";
try
{
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 303f35c..d16b737 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
@@ -75,7 +75,7 @@
result = request.isUserInRole("manager");
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?" <span class=\"pass\">PASS":" <span class=\"fail\">FAIL")+"</span></b>");
+ out.println("<br/><b>Result: isUserInRole(\"user\")="+result+":"+ (result?" <span class=\"pass\">PASS":" <span class=\"fail\">FAIL")+"</span></b>");
String context = _config.getServletContext().getContextPath();
if (!context.endsWith("/"))
context += "/";