Merge branch 'master' into release-9

This commit is contained in:
Jesse McConnell 2013-08-13 13:23:41 -05:00
commit e6f1e8a52a
142 changed files with 5342 additions and 3011 deletions

View File

@ -108,7 +108,6 @@ jetty-9.0.4.v20130625 - 25 June 2013
+ 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 @@ jetty-9.0.0.v20130308 - 08 March 2013
+ 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

View File

@ -23,8 +23,6 @@ import org.eclipse.jetty.server.Server;
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.

View File

@ -55,8 +55,7 @@ public class SpdyServer
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);

View File

@ -71,6 +71,11 @@
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>

View File

@ -753,7 +753,8 @@ public class AnnotationParser
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 @@ public class AnnotationParser
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 @@ public class AnnotationParser
{
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 @@ public class AnnotationParser
scanClass(r.getInputStream());
return;
}
if (LOG.isDebugEnabled()) LOG.warn("Resource not scannable for classes: {}", uri);
}
@ -929,29 +925,8 @@ public class AnnotationParser
{
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 @@ public class AnnotationParser
}
}
}
/**
* 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 @@ public class AnnotationParser
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;
}
}

View File

@ -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 @@ public class TestAnnotationParser
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 @@ public class TestAnnotationParser
});
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 @@ public class TestAnnotationParser
{
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);
}
}
}

Binary file not shown.

View File

@ -176,11 +176,7 @@ public class HttpClient extends ContainerLifeCycle
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 @@ public class HttpClient extends ContainerLifeCycle
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())

View File

@ -53,7 +53,7 @@ public class HttpConnection extends AbstractConnection implements Connection
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 @@ public class HttpConnection extends AbstractConnection implements Connection
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 @@ public class HttpConnection extends AbstractConnection implements Connection
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 @@ public class HttpConnection extends AbstractConnection implements Connection
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 @@ public class HttpConnection extends AbstractConnection implements Connection
path = "/";
request.path(path);
}
if (destination.isProxied() && HttpMethod.CONNECT != method)
if (destination.isProxied() && !HttpMethod.CONNECT.is(method))
{
path = request.getURI().toString();
request.path(path);

View File

@ -428,6 +428,19 @@ public class HttpDestination implements Destination, Closeable, Dumpable
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 @@ public class HttpDestination implements Destination, Closeable, Dumpable
{
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));
}
}
}

View File

@ -43,6 +43,7 @@ public class HttpExchange
private volatile HttpConnection connection;
private volatile Throwable requestFailure;
private volatile Throwable responseFailure;
public HttpExchange(HttpConversation conversation, HttpDestination destination, Request request, List<Response.ResponseListener> listeners)
{

View File

@ -68,21 +68,30 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
{
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 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
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 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
return updated;
}
@Override
public String toString()
{
return String.format("%s@%x on %s", getClass().getSimpleName(), hashCode(), connection);
}
private enum State
{
IDLE, RECEIVE, FAILURE

View File

@ -29,6 +29,7 @@ import java.util.ArrayList;
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 @@ public class HttpRequest implements Request
private String scheme;
private String path;
private String query;
private HttpMethod method;
private String method;
private HttpVersion version;
private long idleTimeout;
private long timeout;
@ -126,6 +127,12 @@ public class HttpRequest implements Request
@Override
public HttpMethod getMethod()
{
return HttpMethod.fromString(method);
}
@Override
public String method()
{
return method;
}
@ -133,7 +140,14 @@ public class HttpRequest implements Request
@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 @@ public class HttpRequest implements Request
{
params.add(name, value);
this.query = buildQuery();
this.uri = buildURI(true);
return this;
}
@ -541,7 +556,13 @@ public class HttpRequest implements Request
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 @@ public class HttpRequest implements Request
@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());
}
}

View File

@ -176,7 +176,7 @@ public class HttpSender implements AsyncContentProvider.Listener
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 @@ public class HttpSender implements AsyncContentProvider.Listener
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)

View File

@ -95,8 +95,9 @@ public class RedirectProtocolHandler extends Response.Listener.Empty implements
{
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 @@ public class RedirectProtocolHandler extends Response.Listener.Empty implements
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 @@ public class RedirectProtocolHandler extends Response.Listener.Empty implements
}
}
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);

View File

@ -76,16 +76,29 @@ public interface Request
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()

View File

@ -221,7 +221,7 @@ public class DigestAuthentication implements Authentication
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)));

View File

@ -108,13 +108,15 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
}
});
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 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
String value = "1";
final String query = name + "=" + value;
final String path = "/path";
String pathQuery = path + "?" + query;
start(new AbstractHandler()
{
@Override
@ -150,6 +153,7 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
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 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
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 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
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 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
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 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
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 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
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());
}
}

View File

@ -188,13 +188,15 @@ if [ -z "$JETTY_HOME" ]
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

View File

@ -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>

View File

@ -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();
}
}

View File

@ -20,18 +20,26 @@ package org.eclipse.jetty.http.spi;
import com.sun.net.httpserver.HttpContext;
import com.sun.net.httpserver.HttpHandler;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.NetworkConnector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.ThreadPool;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executor;
@ -68,10 +76,10 @@ public class JettyHttpServer extends com.sun.net.httpserver.HttpServer
public void bind(InetSocketAddress addr, int backlog) throws IOException
{
// check if there is already a connector listening
Connector[] connectors = _server.getConnectors();
Collection<NetworkConnector> connectors = _server.getBeans(NetworkConnector.class);
if (connectors != null)
{
for (Connector connector : connectors)
for (NetworkConnector connector : connectors)
{
if (connector.getPort() == addr.getPort()) {
if (LOG.isDebugEnabled()) LOG.debug("server already bound to port " + addr.getPort() + ", no need to rebind");
@ -86,8 +94,7 @@ public class JettyHttpServer extends com.sun.net.httpserver.HttpServer
this._addr = addr;
if (LOG.isDebugEnabled()) LOG.debug("binding server to port " + addr.getPort());
SelectChannelConnector connector = new SelectChannelConnector();
connector.setAcceptors(1);
ServerConnector connector = new ServerConnector(_server);
connector.setPort(addr.getPort());
connector.setHost(addr.getHostName());
_server.addConnector(connector);
@ -121,19 +128,20 @@ public class JettyHttpServer extends com.sun.net.httpserver.HttpServer
{
if (executor == null)
throw new IllegalArgumentException("missing required 'executor' argument");
if (!(executor instanceof ThreadPoolExecutor))
throw new IllegalArgumentException("only java.util.concurrent.ThreadPoolExecutor instances are allowed, got: " + executor.getClass().getName());
if (LOG.isDebugEnabled()) LOG.debug("using ThreadPoolExecutor for server thread pool");
this._executor = (ThreadPoolExecutor) executor;
_server.setThreadPool(new ThreadPoolExecutorAdapter(_executor));
ThreadPool threadPool = _server.getThreadPool();
if (threadPool instanceof DelegatingThreadPool)
((DelegatingThreadPool)_server.getThreadPool()).setExecutor(executor);
else
throw new UnsupportedOperationException("!DelegatingThreadPool");
}
@Override
public Executor getExecutor()
{
return _executor;
ThreadPool threadPool = _server.getThreadPool();
if (threadPool instanceof DelegatingThreadPool)
return ((DelegatingThreadPool)_server.getThreadPool()).getExecutor();
return threadPool;
}
@Override

View File

@ -26,6 +26,9 @@ import org.eclipse.jetty.server.Server;
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 @@ public class JettyHttpServerProvider extends HttpServerProvider
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;
}

View File

@ -1,124 +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.http.spi;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.ThreadPool;
/**
* Jetty {@link ThreadPool} that bridges requests to a {@link ThreadPoolExecutor}.
*/
public class ThreadPoolExecutorAdapter extends AbstractLifeCycle implements ThreadPool
{
private static final Logger LOG = Log.getLogger(ThreadPoolExecutorAdapter.class);
private ThreadPoolExecutor executor;
public ThreadPoolExecutorAdapter(ThreadPoolExecutor executor)
{
this.executor = executor;
}
public boolean dispatch(Runnable job)
{
try
{
executor.execute(job);
return true;
}
catch(RejectedExecutionException e)
{
LOG.warn(e);
return false;
}
}
public int getIdleThreads()
{
return executor.getPoolSize()-executor.getActiveCount();
}
public int getThreads()
{
return executor.getPoolSize();
}
public boolean isLowOnThreads()
{
return executor.getActiveCount()>=executor.getMaximumPoolSize();
}
public void join() throws InterruptedException
{
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
}
public boolean isFailed()
{
return false;
}
public boolean isRunning()
{
return !executor.isTerminated() && !executor.isTerminating();
}
public boolean isStarted()
{
return !executor.isTerminated() && !executor.isTerminating();
}
public boolean isStarting()
{
return false;
}
public boolean isStopped()
{
return executor.isTerminated();
}
public boolean isStopping()
{
return executor.isTerminating();
}
protected void doStart() throws Exception
{
if (executor.isTerminated() || executor.isTerminating() || executor.isShutdown())
throw new IllegalStateException("Cannot restart");
}
protected void doStop() throws Exception
{
executor.shutdown();
if (!executor.awaitTermination(60, TimeUnit.SECONDS))
executor.shutdownNow();
}
}

View File

@ -68,6 +68,7 @@ public class HttpField
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

View File

@ -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 @@ import org.eclipse.jetty.util.log.Logger;
/**
* 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 @@ public class HttpFields implements Iterable<HttpField>
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 @@ public class HttpFields implements Iterable<HttpField>
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 @@ public class HttpFields implements Iterable<HttpField>
}
}
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 @@ public class HttpFields implements Iterable<HttpField>
}
/* ------------------------------------------------------------ */
/** 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);
}
}

View File

@ -55,13 +55,19 @@ public class HttpGenerator
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 class HttpGenerator
/* ------------------------------------------------------------------------------- */
public HttpGenerator()
{
this(false,false);
}
/* ------------------------------------------------------------------------------- */
public HttpGenerator(boolean sendServerVersion,boolean sendXPoweredBy)
{
_send=(sendServerVersion?SEND_SERVER:0) | (sendXPoweredBy?SEND_XPOWEREDBY:0);
}
/* ------------------------------------------------------------------------------- */
@ -86,15 +99,17 @@ public class HttpGenerator
}
/* ------------------------------------------------------------ */
@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 @@ public class HttpGenerator
/* ------------------------------------------------------------ */
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 @@ public class HttpGenerator
case CONNECTION:
{
if (_request!=null)
if (request!=null)
field.putTo(header);
// Lookup and/or split connection value field
@ -619,7 +634,7 @@ public class HttpGenerator
case CLOSE:
{
close=true;
if (_response!=null)
if (response!=null)
{
_persistent=false;
if (_endOfContent == EndOfContent.UNKNOWN_CONTENT)
@ -633,7 +648,7 @@ public class HttpGenerator
if (_info.getHttpVersion() == HttpVersion.HTTP_1_0)
{
keep_alive = true;
if (_response!=null)
if (response!=null)
_persistent=true;
}
break;
@ -656,11 +671,8 @@ public class HttpGenerator
case SERVER:
{
if (getSendServerVersion())
{
has_server=true;
field.putTo(header);
}
send=send&~SEND_SERVER;
field.putTo(header);
break;
}
@ -680,7 +692,7 @@ public class HttpGenerator
// 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 @@ public class HttpGenerator
// 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 @@ public class HttpGenerator
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 @@ public class HttpGenerator
{
// 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 @@ public class HttpGenerator
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 @@ public class HttpGenerator
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 @@ public class HttpGenerator
}
// 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 @@ public class HttpGenerator
}
}
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 @@ public class HttpGenerator
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")
};
/* ------------------------------------------------------------------------------- */
/* ------------------------------------------------------------------------------- */

View File

@ -108,6 +108,8 @@ public enum HttpHeader
SET_COOKIE2("Set-Cookie2"),
MIME_VERSION("MIME-Version"),
IDENTITY("identity"),
X_POWERED_BY("X-Powered-By"),
UNKNOWN("::UNKNOWN::");

View File

@ -41,7 +41,7 @@ public enum HttpMethod
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 @@ public enum HttpMethod
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 @@ public enum HttpMethod
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 @@ public enum HttpMethod
}
/* ------------------------------------------------------------ */
/**
/**
* 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 @@ public enum HttpMethod
{
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 enum HttpMethod
/* ------------------------------------------------------------ */
public boolean is(String s)
{
return toString().equalsIgnoreCase(s);
return toString().equalsIgnoreCase(s);
}
/* ------------------------------------------------------------ */
public ByteBuffer asBuffer()
{
return _buffer.asReadOnlyBuffer();
}
/* ------------------------------------------------------------ */
public String asString()
{

View File

@ -63,11 +63,18 @@ import org.eclipse.jetty.util.log.Logger;
* 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 @@ public class HttpParser
REQUEST_VERSION,
REASON,
HEADER,
HEADER_NAME,
HEADER_IN_NAME,
HEADER_VALUE,
HEADER_IN_VALUE,
@ -96,10 +102,12 @@ public class HttpParser
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 class HttpParser
/* ------------------------------------------------------------------------------- */
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 @@ public class HttpParser
return _state == state;
}
/* ------------------------------------------------------------------------------- */
private static class BadMessage extends Error
{
private final int _code;
@ -309,7 +332,7 @@ public class HttpParser
if (ch==HttpTokens.LINE_FEED)
return ch;
throw new BadMessage();
throw new BadMessage("Bad EOL");
}
// Defer lookup of LF
@ -319,7 +342,7 @@ public class HttpParser
// 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 @@ public class HttpParser
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 @@ public class HttpParser
{
// process each character
byte ch=next(buffer);
if (ch==-1)
return true;
if (ch==0)
continue;
@ -429,16 +452,15 @@ public class HttpParser
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 @@ public class HttpParser
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 @@ public class HttpParser
}
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 @@ public class HttpParser
}
}
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 @@ public class HttpParser
}
else
{
throw new IllegalStateException();
throw new BadMessage();
}
break;
@ -561,7 +580,7 @@ public class HttpParser
break;
case SPACE2:
if (ch > HttpTokens.SPACE || ch<0)
if (ch > HttpTokens.SPACE)
{
_string.setLength(0);
_string.append((char)ch);
@ -620,13 +639,18 @@ public class HttpParser
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 @@ public class HttpParser
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 @@ public class HttpParser
}
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 @@ public class HttpParser
{
// process each character
byte ch=next(buffer);
if (ch==-1)
return true;
if (ch==0)
continue;
@ -827,14 +854,18 @@ public class HttpParser
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;
}
else
{
setString(_valueString);
_string.append(' ');
_length++;
_valueString=null;
}
_length=_string.length();
_valueString=null;
setState(State.HEADER_VALUE);
break;
}
@ -917,6 +948,8 @@ public class HttpParser
break;
}
}
else if (ch<=HttpTokens.SPACE)
throw new BadMessage();
else
{
if (buffer.hasRemaining())
@ -928,14 +961,35 @@ public class HttpParser
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 @@ public class HttpParser
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 @@ public class HttpParser
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 @@ public class HttpParser
{
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 @@ public class HttpParser
{
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 class HttpParser
*/
public boolean shutdownInput()
{
LOG.debug("shutdownInput {}", this);
if (DEBUG)
LOG.debug("shutdownInput {}", this);
// was this unexpected?
switch(_state)
@ -1437,6 +1417,8 @@ public class HttpParser
/* ------------------------------------------------------------------------------- */
public void close()
{
if (DEBUG)
LOG.debug("close {}", this);
switch(_state)
{
case START:
@ -1469,6 +1451,8 @@ public class HttpParser
/* ------------------------------------------------------------------------------- */
public void reset()
{
if (DEBUG)
LOG.debug("reset {}", this);
// reset state
setState(State.START);
_endOfContent=EndOfContent.UNKNOWN_CONTENT;
@ -1483,7 +1467,8 @@ public class HttpParser
/* ------------------------------------------------------------------------------- */
private void setState(State state)
{
// LOG.debug("{} --> {}",_state,state);
if (DEBUG)
LOG.debug("{} --> {}",_state,state);
_state=state;
}

View File

@ -92,6 +92,19 @@ public class HttpTester
_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

View File

@ -279,10 +279,10 @@ public class HttpFieldsTest
//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 @@ public class HttpFieldsTest
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 @@ public class HttpFieldsTest
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 @@ public class HttpFieldsTest
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 @@ public class HttpFieldsTest
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 @@ public class HttpFieldsTest
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 @@ public class HttpFieldsTest
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);
}

View File

@ -34,6 +34,7 @@ import java.util.List;
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 @@ public class HttpGeneratorServerTest
}
}
}
@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

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.http;
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 java.util.List;
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;
@ -313,6 +316,115 @@ public class HttpParserTest
assertEquals(9, _h);
}
@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
{
@ -426,7 +538,6 @@ public class HttpParserTest
+ "\015\012"
+ "0123456789\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
@ -1125,7 +1236,7 @@ public class HttpParserTest
@Override
public void badMessage(int status, String reason)
{
_bad=reason;
_bad=reason==null?(""+status):reason;
}
@Override

View File

@ -57,7 +57,7 @@ public class NetworkTrafficSelectChannelEndPoint extends SelectChannelEndPoint
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)

View File

@ -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.EofException;
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 @@ public class SslConnection extends AbstractConnection
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()

View File

@ -20,10 +20,10 @@ package org.eclipse.jetty.jaas.spi;
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 @@ public class PropertyFileLoginModule extends AbstractLoginModule
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 @@ public class PropertyFileLoginModule extends AbstractLoginModule
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));
}
/**

View File

@ -75,7 +75,14 @@ public class ContextFactory implements ObjectFactory
* 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 @@ public class ContextFactory implements ObjectFactory
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 @@ public class ContextFactory implements ObjectFactory
/**
* 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

View File

@ -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>

View File

@ -203,6 +203,12 @@ public abstract class AbstractJettyMojo extends AbstractMojo
*/
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 @@ public abstract class AbstractJettyMojo extends AbstractMojo
*/
protected List pluginArtifacts;
/**
* A ServerConnector to use.
@ -558,6 +565,11 @@ public abstract class AbstractJettyMojo extends AbstractMojo
getLog().info("Started Jetty Server");
if ( dumpOnStart )
{
getLog().info(this.server.dump());
}
// start the scanner thread (if necessary) on the main webapp
configureScanner ();
startScanner();

View File

@ -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 @@ public class JettyStopMojo extends AbstractMojo
* @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 @@ public class JettyStopMojo extends AbstractMojo
{
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)

View File

@ -450,6 +450,7 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
/**
* is the session id known to mongo, and is it valid
*/
@Override
public boolean idInUse(String sessionId)
{
/*
@ -473,6 +474,7 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
}
/* ------------------------------------------------------------ */
@Override
public void addSession(HttpSession session)
{
if (session == null)
@ -494,6 +496,7 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
}
/* ------------------------------------------------------------ */
@Override
public void removeSession(HttpSession session)
{
if (session == null)
@ -508,6 +511,7 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
}
/* ------------------------------------------------------------ */
@Override
public void invalidateAll(String sessionId)
{
synchronized (_sessionsIds)
@ -520,7 +524,7 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
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 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
}
}
}
}
/* ------------------------------------------------------------ */
// 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 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
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();

View File

@ -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;
}
}

View File

@ -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;

View File

@ -31,6 +31,7 @@ import javax.naming.Name;
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 @@ public class EnvConfiguration extends AbstractConfiguration
//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 @@ public class EnvConfiguration extends AbstractConfiguration
}
finally
{
ContextFactory.disassociateClassLoader();
Thread.currentThread().setContextClassLoader(oldLoader);
}
}
@ -251,6 +254,7 @@ public class EnvConfiguration extends AbstractConfiguration
{
ClassLoader old_loader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(wac.getClassLoader());
ContextFactory.associateClassLoader(wac.getClassLoader());
try
{
Context context = new InitialContext();
@ -259,8 +263,9 @@ public class EnvConfiguration extends AbstractConfiguration
}
finally
{
Thread.currentThread().setContextClassLoader(old_loader);
}
ContextFactory.disassociateClassLoader();
Thread.currentThread().setContextClassLoader(old_loader);
}
}
private static class Bound

View File

@ -35,6 +35,7 @@ import org.eclipse.jetty.client.api.ContentResponse;
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 @@ public class BalancerServletTest
if (nodeName != null)
{
HashSessionIdManager sessionIdManager = new HashSessionIdManager();
AbstractSessionIdManager sessionIdManager = new HashSessionIdManager();
sessionIdManager.setWorkerName(nodeName);
server.setSessionIdManager(sessionIdManager);
}

View File

@ -20,11 +20,13 @@ package org.eclipse.jetty.proxy;
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.eclipse.jetty.util.ssl.SslContextFactory;
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 @@ public class ProxyTunnellingTest
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 @@ public class ProxyTunnellingTest
}
}
@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

View File

@ -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 @@ public class RewritePatternRule extends PatternRule implements Rule.ApplyURI
/* ------------------------------------------------------------ */
/**
* 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&param1=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);
}
}
/* ------------------------------------------------------------ */

View File

@ -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 @@ public class RewritePatternRuleTest extends AbstractRuleTestCase
}
@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));
}
}

View File

@ -54,13 +54,19 @@ public class DefaultUserIdentity implements UserIdentity
}
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;
}

View File

@ -212,6 +212,9 @@ public abstract class MappedLoginService extends AbstractLifeCycle implements Lo
*/
public UserIdentity login(String username, Object credentials)
{
if (username == null)
return null;
UserIdentity user = _users.get(username);
if (user==null)

View File

@ -116,6 +116,9 @@ public class DeferredAuthentication implements Authentication.Deferred
@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)
{

View File

@ -25,6 +25,7 @@ import javax.servlet.http.Cookie;
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 @@ public abstract class AbstractNCSARequestLog extends AbstractLifeCycle implement
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 @@ public abstract class AbstractNCSARequestLog extends AbstractLifeCycle implement
}
}
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 @@ public abstract class AbstractNCSARequestLog extends AbstractLifeCycle implement
}
/**
* 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;
}
/**

View File

@ -38,7 +38,7 @@ public class AsyncContextState implements AsyncContext
_state=state;
}
private HttpChannelState state()
HttpChannelState state()
{
HttpChannelState state=_state;
if (state==null)

View File

@ -23,6 +23,7 @@ import java.net.InetSocketAddress;
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 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
}
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;

View File

@ -71,13 +71,12 @@ public class HttpChannelState
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 @@ public class HttpChannelState
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 @@ public class HttpChannelState
_event.setDispatchTarget(context,path);
_dispatched=true;
break;
default:
throw new IllegalStateException(this.getStatusString());
}
@ -393,7 +392,7 @@ public class HttpChannelState
break;
}
}
scheduleDispatch();
}
@ -528,7 +527,7 @@ public class HttpChannelState
return _expired;
}
}
public boolean isInitial()
{
synchronized(this)
@ -563,13 +562,23 @@ public class HttpChannelState
}
}
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;

View File

@ -22,6 +22,7 @@ import java.util.List;
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 @@ import org.eclipse.jetty.util.annotation.ManagedObject;
@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 @@ public class HttpConfiguration
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 @@ public class HttpConfiguration
_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)
{

View File

@ -91,8 +91,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
_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 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
_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 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
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

View File

@ -373,7 +373,8 @@ public class HttpOutput extends ServletOutputStream
/* ------------------------------------------------------------ */
/** 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 @@ public class HttpOutput extends ServletOutputStream
/* ------------------------------------------------------------ */
/** 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 @@ public class HttpOutput extends ServletOutputStream
@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 @@ public class HttpOutput extends ServletOutputStream
{
super.failed(x);
_channel.getByteBufferPool().release(_buffer);
try
{
_in.close();
}
catch (IOException e)
{
LOG.ignore(e);
}
}
}
@ -531,30 +551,38 @@ public class HttpOutput extends ServletOutputStream
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 @@ public class HttpOutput extends ServletOutputStream
{
super.failed(x);
_channel.getByteBufferPool().release(_buffer);
try
{
_in.close();
}
catch (IOException e)
{
LOG.ignore(e);
}
}
}
}

View File

@ -28,6 +28,7 @@ import java.io.UnsupportedEncodingException;
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.HashMap;
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.HttpCookie;
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 @@ public class Request implements HttpServletRequest
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 @@ public class Request implements HttpServletRequest
return null;
}
/* ------------------------------------------------------------ */
/**
* Get timestamp of the request dispatch
*
* @return timestamp
*/
public long getDispatchTime()
{
return _dispatchTime;
}
/* ------------------------------------------------------------ */
public boolean isHandled()
@ -1714,7 +1702,16 @@ public class Request implements HttpServletRequest
// check encoding is supported
if (!StringUtil.isUTF8(encoding))
Charset.forName(encoding);
{
try
{
Charset.forName(encoding);
}
catch (UnsupportedCharsetException e)
{
throw new UnsupportedEncodingException(e.getMessage());
}
}
}
/* ------------------------------------------------------------ */
@ -1996,18 +1993,6 @@ public class Request implements HttpServletRequest
_scope = scope;
}
/* ------------------------------------------------------------ */
/**
* Set timetstamp of request dispatch
*
* @param value
* timestamp
*/
public void setDispatchTime(long value)
{
_dispatchTime = value;
}
/* ------------------------------------------------------------ */
@Override
public AsyncContext startAsync() throws IllegalStateException

View File

@ -281,8 +281,8 @@ public class Server extends HandlerWrapper implements Attributes
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

View File

@ -98,6 +98,23 @@ public class ServerConnector extends AbstractNetworkConnector
{
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.

View File

@ -102,6 +102,13 @@ public class ShutdownMonitor
// 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 @@ public class ShutdownMonitor
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 @@ public class ShutdownMonitor
}
catch (IOException ignore)
{
/* ignore */
debug(ignore);
}
}
@ -265,10 +271,27 @@ public class ShutdownMonitor
}
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)

View File

@ -101,8 +101,8 @@ import org.eclipse.jetty.util.resource.Resource;
@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 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
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 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
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 class ContextHandler extends ScopedHandler implements Attributes, Gracefu
*/
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 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
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 class ContextHandler extends ScopedHandler implements Attributes, Gracefu
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 class ContextHandler extends ScopedHandler implements Attributes, Gracefu
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);

View File

@ -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 @@ public class ContextHandlerCollection extends HandlerCollection
{
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 @@ public class ContextHandlerCollection extends HandlerCollection
@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;
}
// 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);
}
_contextMap=contextMap;
//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 @@ public class ContextHandlerCollection extends HandlerCollection
@Override
public void setHandlers(Handler[] handlers)
{
_contextMap=null;
_contexts=null;
super.setHandlers(handlers);
if (isStarted())
mapContexts();
@ -199,67 +180,31 @@ public class ContextHandlerCollection extends HandlerCollection
}
// 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 @@ public class ContextHandlerCollection extends HandlerCollection
_contextClass = contextClass;
}
/* ------------------------------------------------------------ */
private String normalizeHostname( String host )
{
if ( host == null )
return null;
if ( host.endsWith( "." ) )
return host.substring( 0, host.length() -1);
return host;
}
}

View File

@ -20,15 +20,18 @@ package org.eclipse.jetty.server.handler;
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 @@ import org.eclipse.jetty.util.log.Logger;
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 class RequestLogHandler extends HandlerWrapper
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 @@ public class RequestLogHandler extends HandlerWrapper
{
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)
{
}
}
}

View File

@ -37,6 +37,7 @@ public abstract class AbstractSessionIdManager extends AbstractLifeCycle impleme
protected Random _random;
protected boolean _weakRandom;
protected String _workerName;
protected String _workerAttr;
protected long _reseed=100000L;
/* ------------------------------------------------------------ */
@ -58,6 +59,7 @@ public abstract class AbstractSessionIdManager extends AbstractLifeCycle impleme
*
* @return String or null
*/
@Override
public String getWorkerName()
{
return _workerName;
@ -67,11 +69,16 @@ public abstract class AbstractSessionIdManager extends AbstractLifeCycle impleme
/**
* 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 @@ public abstract class AbstractSessionIdManager extends AbstractLifeCycle impleme
*
* @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 @@ public abstract class AbstractSessionIdManager extends AbstractLifeCycle impleme
/* ------------------------------------------------------------ */
@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 @@ public abstract class AbstractSessionIdManager extends AbstractLifeCycle impleme
_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;
}
}

View File

@ -459,16 +459,16 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
{
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 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
else
{
cookie = new HttpCookie(
_sessionCookie,
_cookieConfig.getName(),
id,
_sessionDomain,
_cookieConfig.getDomain(),
sessionPath,
_cookieConfig.getMaxAge(),
_cookieConfig.isHttpOnly(),
@ -796,9 +796,11 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
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 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement
@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;
}

View File

@ -81,38 +81,7 @@ public class HashSessionIdManager extends AbstractSessionIdManager
}
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 @@ public class HashSessionIdManager extends AbstractSessionIdManager
/**
* @see SessionIdManager#idInUse(String)
*/
@Override
public boolean idInUse(String id)
{
synchronized (this)
@ -144,6 +114,7 @@ public class HashSessionIdManager extends AbstractSessionIdManager
/**
* @see SessionIdManager#addSession(HttpSession)
*/
@Override
public void addSession(HttpSession session)
{
String id = getClusterId(session.getId());
@ -165,6 +136,7 @@ public class HashSessionIdManager extends AbstractSessionIdManager
/**
* @see SessionIdManager#removeSession(HttpSession)
*/
@Override
public void removeSession(HttpSession session)
{
String id = getClusterId(session.getId());
@ -199,6 +171,7 @@ public class HashSessionIdManager extends AbstractSessionIdManager
/**
* @see SessionIdManager#invalidateAll(String)
*/
@Override
public void invalidateAll(String id)
{
Collection<WeakReference<HttpSession>> sessions;
@ -221,6 +194,7 @@ public class HashSessionIdManager extends AbstractSessionIdManager
/* ------------------------------------------------------------ */
@Override
public void renewSessionId (String oldClusterId, String oldNodeId, HttpServletRequest request)
{
//generate a new id

View File

@ -29,11 +29,7 @@ import java.sql.PreparedStatement;
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 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
}
@Override
public void addSession(HttpSession session)
{
if (session == null)
@ -422,6 +419,7 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
@Override
public void removeSession(HttpSession session)
{
if (session == null)
@ -456,32 +454,7 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
}
/**
* 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 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
*
* @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 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
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 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
}
@Override
public void renewSessionId (String oldClusterId, String oldNodeId, HttpServletRequest request)
{
//generate a new id
@ -556,7 +531,7 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
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 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
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();

View File

@ -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;
}
}
}
}

View File

@ -48,6 +48,7 @@ import javax.servlet.http.HttpServletResponse;
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;

View File

@ -33,6 +33,7 @@ import org.eclipse.jetty.http.HttpURI;
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 @@ public class HttpURITest
{
}
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));
}

View File

@ -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.server.handler.ContextHandler;
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 @@ public class RequestTest
@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 @@ public class RequestTest
//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 @@ public class RequestTest
"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 @@ public class RequestTest
};
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 @@ public class RequestTest
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 @@ public class RequestTest
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 @@ public class RequestTest
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 @@ public class RequestTest
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 @@ public class RequestTest
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 @@ public class RequestTest
results.clear();
_connector.getResponses(
response=_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: [::1]\n"+
"x-forwarded-for: remote\n"+
@ -450,6 +443,7 @@ public class RequestTest
"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 @@ public class RequestTest
results.clear();
_connector.getResponses(
response=_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: [::1]:8888\n"+
"Connection: close\n"+
@ -465,18 +459,11 @@ public class RequestTest
"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 @@ public class RequestTest
}
}
@Test(expected = UnsupportedEncodingException.class)
public void testNotSupportedCharacterEncoding() throws UnsupportedEncodingException
{
Request request = new Request(null, null);
request.setCharacterEncoding("doesNotExist");
}
interface RequestTester
{

View File

@ -601,7 +601,7 @@ public class ResponseTest
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 @@ public class ResponseTest
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

View File

@ -23,6 +23,8 @@ import static org.junit.Assert.assertFalse;
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.Connector;
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 @@ public class ContextHandlerCollectionTest
ContextHandler context = new ContextHandler("/");
IsHandledHandler handler = new IsHandledHandler();
IsHandledHandler handler = new IsHandledHandler("H");
context.setHandler(handler);
ContextHandlerCollection c = new ContextHandlerCollection();
@ -149,7 +212,10 @@ public class ContextHandlerCollectionTest
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 @@ public class ContextHandlerCollectionTest
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 @@ public class ContextHandlerCollectionTest
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;
}
}

View File

@ -102,7 +102,7 @@ public class HashSessionManagerTest
Assert.assertTrue(testDir.exists());
Assert.assertTrue(testDir.canWrite());
HashSessionIdManager idManager = new HashSessionIdManager();
AbstractSessionIdManager idManager = new HashSessionIdManager();
idManager.setWorkerName("foo");
manager.setSessionIdManager(idManager);

View File

@ -58,6 +58,7 @@ public class SessionCookieTest
/**
* @see org.eclipse.jetty.server.SessionIdManager#idInUse(java.lang.String)
*/
@Override
public boolean idInUse(String id)
{
return false;
@ -66,6 +67,7 @@ public class SessionCookieTest
/**
* @see org.eclipse.jetty.server.SessionIdManager#addSession(javax.servlet.http.HttpSession)
*/
@Override
public void addSession(HttpSession session)
{
@ -74,6 +76,7 @@ public class SessionCookieTest
/**
* @see org.eclipse.jetty.server.SessionIdManager#removeSession(javax.servlet.http.HttpSession)
*/
@Override
public void removeSession(HttpSession session)
{
@ -82,28 +85,12 @@ public class SessionCookieTest
/**
* @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 @@ public class SessionCookieTest
/**
* @see org.eclipse.jetty.server.session.AbstractSessionManager#addSession(org.eclipse.jetty.server.session.AbstractSession)
*/
@Override
protected void addSession(AbstractSession session)
{
@ -127,6 +115,7 @@ public class SessionCookieTest
/**
* @see org.eclipse.jetty.server.session.AbstractSessionManager#getSession(java.lang.String)
*/
@Override
public AbstractSession getSession(String idInCluster)
{
return null;
@ -135,6 +124,7 @@ public class SessionCookieTest
/**
* @see org.eclipse.jetty.server.session.AbstractSessionManager#invalidateSessions()
*/
@Override
protected void invalidateSessions() throws Exception
{
@ -143,6 +133,7 @@ public class SessionCookieTest
/**
* @see org.eclipse.jetty.server.session.AbstractSessionManager#newSession(javax.servlet.http.HttpServletRequest)
*/
@Override
protected AbstractSession newSession(HttpServletRequest request)
{
return null;
@ -151,6 +142,7 @@ public class SessionCookieTest
/**
* @see org.eclipse.jetty.server.session.AbstractSessionManager#removeSession(java.lang.String)
*/
@Override
protected boolean removeSession(String idInCluster)
{
return false;

View File

@ -21,6 +21,8 @@ package org.eclipse.jetty.servlet;
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.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.RequestLog;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.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 @@ public class AsyncServletTest
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 @@ public class AsyncServletTest
@After
public void tearDown() throws Exception
{
assertEquals(_expectedLogs,_log.size());
Assert.assertThat(_log.get(0),Matchers.containsString(_expectedCode));
_server.stop();
}
@ -105,6 +127,7 @@ public class AsyncServletTest
@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 @@ public class AsyncServletTest
@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 @@ public class AsyncServletTest
@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 @@ public class AsyncServletTest
@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 @@ public class AsyncServletTest
((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());
}
}
}

View File

@ -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 @@ public class DoSFilter implements Filter
* 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;

View File

@ -45,10 +45,12 @@ import javax.servlet.http.HttpServletRequestWrapper;
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 @@ public class MultiPartFilter implements Filter
{
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 @@ public class MultiPartFilter implements Filter
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 @@ public class MultiPartFilter implements Filter
{
try
{
v[i]=new String((byte[])o,_encoding);
v[i]=getParameterBytesAsString(name, (byte[])o);
}
catch(Exception e)
{
@ -341,5 +341,23 @@ public class MultiPartFilter implements Filter
{
_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);
}
}
}

View File

@ -243,7 +243,7 @@ public abstract class CompressedResponseWrapper extends HttpServletResponseWrapp
if (_writer!=null)
_writer.flush();
if (_compressedStream!=null)
_compressedStream.finish();
_compressedStream.flush();
else
getResponse().flushBuffer();
}

View File

@ -32,6 +32,7 @@ import org.eclipse.jetty.servlets.gzip.GzipTester;
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 @@ public class GzipFilterContentLengthTest
{ 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 }

View File

@ -26,11 +26,13 @@ import static org.junit.Assert.assertNotNull;
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.HttpServlet;
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 class MultipartFilterTest
public void tearDown() throws Exception
{
tester.stop();
tester=null;
}
@Test
@ -698,8 +703,6 @@ public class MultipartFilterTest
assertTrue(response.getContent().contains("aaaa,bbbbb"));
}
@Test
public void testContentTypeWithCharSet() throws Exception
{
// generated and parsed test
@ -730,6 +733,39 @@ public class MultipartFilterTest
}
@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 @@ public class MultipartFilterTest
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;

View File

@ -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 @@ public class GzipTester
// 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));

View File

@ -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();
}
}
}

View File

@ -218,11 +218,22 @@ public class StandardSession implements ISession, Parser.Listener, Dumpable
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 @@ public class StandardSession implements ISession, Parser.Listener, Dumpable
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 @@ public class StandardSession implements ISession, Parser.Listener, Dumpable
}
}
private void append(FrameBytes frameBytes)
void append(FrameBytes frameBytes)
{
Throwable failure;
synchronized (queue)
@ -1215,7 +1227,7 @@ public class StandardSession implements ISession, Parser.Listener, Dumpable
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;

View File

@ -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);
}
}

View File

@ -105,7 +105,8 @@ public class HTTPSPDYServerConnectionFactory extends SPDYServerConnectionFactory
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);

View File

@ -28,6 +28,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
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.HeadersInfo;
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 @@ public class HttpTransportOverSPDY implements HttpTransport
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 @@ public class HttpTransportOverSPDY implements HttpTransport
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 @@ public class HttpTransportOverSPDY implements HttpTransport
}
@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 @@ public class HttpTransportOverSPDY implements HttpTransport
// 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 @@ public class HttpTransportOverSPDY implements HttpTransport
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 @@ public class HttpTransportOverSPDY implements HttpTransport
@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 @@ public class HttpTransportOverSPDY implements HttpTransport
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 @@ public class HttpTransportOverSPDY implements HttpTransport
{
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 @@ public class HttpTransportOverSPDY implements HttpTransport
{
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 @@ public class HttpTransportOverSPDY implements HttpTransport
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 @@ public class HttpTransportOverSPDY implements HttpTransport
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 class HttpTransportOverSPDY implements HttpTransport
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 @@ public class HttpTransportOverSPDY implements HttpTransport
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 @@ public class HttpTransportOverSPDY implements HttpTransport
}
return pushHeaders;
}
private void complete()
{
synchronized (this)
{
active = false;
}
sendNextResourceData();
}
}
private static class PushResource
@ -383,5 +408,14 @@ public class HttpTransportOverSPDY implements HttpTransport
{
return pushRequestHeaders;
}
@Override
public String toString()
{
return "PushResource{" +
"pushStream=" + pushStream +
", pushRequestHeaders=" + pushRequestHeaders +
'}';
}
}
}

View File

@ -141,6 +141,7 @@ public class HTTPProxyEngine extends ProxyEngine
{
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()
{

View File

@ -87,6 +87,8 @@ public abstract class ProxyEngine
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);
}

View File

@ -99,7 +99,10 @@ public abstract class AbstractHTTPSPDYTest
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;
}

View File

@ -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.ArgumentCaptor;
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 @@ public class HttpTransportOverSPDYTest
@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 @@ public class HttpTransportOverSPDYTest
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 @@ public class HttpTransportOverSPDYTest
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 @@ public class HttpTransportOverSPDYTest
{
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 @@ public class HttpTransportOverSPDYTest
{
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 class HttpTransportOverSPDYTest
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

View File

@ -60,7 +60,6 @@ import org.eclipse.jetty.util.log.Logger;
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 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest
}
@Test
@Ignore
public void testPushResourceAreSentNonInterleaved() throws Exception
{
final CountDownLatch allExpectedPushesReceivedLatch = new CountDownLatch(4);

View File

@ -29,7 +29,6 @@ import java.util.Arrays;
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 javax.servlet.http.HttpServletResponse;
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 org.junit.Test;
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 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
{
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 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
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 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
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,11 +231,55 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
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
{

View File

@ -219,6 +219,8 @@ public class ProxySPDYToHTTPTest
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 @@ public class ProxySPDYToHTTPTest
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)

View File

@ -36,13 +36,14 @@ import org.eclipse.jetty.spdy.api.SynInfo;
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 @@ public class MaxConcurrentStreamTest extends AbstractTest
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);
}
}

View File

@ -39,8 +39,7 @@ import org.eclipse.jetty.util.log.Logger;
/**
* 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 @@ import org.eclipse.jetty.util.log.Logger;
* null ==> null
* bool ==> Boolean
* </pre>
*
* </p>
* <p>
* The java to JSON mapping is:
*
* <pre>
@ -68,30 +65,27 @@ import org.eclipse.jetty.util.log.Logger;
* 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 @@ public class JSON
{
}
/* ------------------------------------------------------------ */
/**
* @return the initial stringBuffer size to use when creating JSON strings
* (default 1024)
@ -115,7 +108,6 @@ public class JSON
return _stringBufferSize;
}
/* ------------------------------------------------------------ */
/**
* @param stringBufferSize
* the initial stringBuffer size to use when creating JSON
@ -126,7 +118,6 @@ public class JSON
_stringBufferSize = stringBufferSize;
}
/* ------------------------------------------------------------ */
/**
* Register a {@link Convertor} for a class or interface.
*
@ -140,19 +131,16 @@ public class JSON
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 @@ public class JSON
return buffer.toString();
}
/* ------------------------------------------------------------ */
public static String toString(Map object)
{
StringBuilder buffer = new StringBuilder(DEFAULT.getStringBufferSize());
@ -168,7 +155,6 @@ public class JSON
return buffer.toString();
}
/* ------------------------------------------------------------ */
public static String toString(Object[] array)
{
StringBuilder buffer = new StringBuilder(DEFAULT.getStringBufferSize());
@ -176,7 +162,6 @@ public class JSON
return buffer.toString();
}
/* ------------------------------------------------------------ */
/**
* @param s
* String containing JSON object or array.
@ -187,7 +172,6 @@ public class JSON
return DEFAULT.parse(new StringSource(s),false);
}
/* ------------------------------------------------------------ */
/**
* @param s
* String containing JSON object or array.
@ -200,7 +184,6 @@ public class JSON
return DEFAULT.parse(new StringSource(s),stripOuterComment);
}
/* ------------------------------------------------------------ */
/**
* @param in
* Reader containing JSON object or array.
@ -211,7 +194,6 @@ public class JSON
return DEFAULT.parse(new ReaderSource(in),false);
}
/* ------------------------------------------------------------ */
/**
* @param in
* Reader containing JSON object or array.
@ -224,7 +206,6 @@ public class JSON
return DEFAULT.parse(new ReaderSource(in),stripOuterComment);
}
/* ------------------------------------------------------------ */
/**
* @deprecated use {@link #parse(Reader)}
* @param in
@ -237,7 +218,6 @@ public class JSON
return DEFAULT.parse(new StringSource(IO.toString(in)),false);
}
/* ------------------------------------------------------------ */
/**
* @deprecated use {@link #parse(Reader, boolean)}
* @param in
@ -252,7 +232,6 @@ public class JSON
return DEFAULT.parse(new StringSource(IO.toString(in)),stripOuterComment);
}
/* ------------------------------------------------------------ */
/**
* Convert Object to JSON
*
@ -267,7 +246,6 @@ public class JSON
return buffer.toString();
}
/* ------------------------------------------------------------ */
/**
* Convert JSON to Object
*
@ -287,7 +265,6 @@ public class JSON
append((Appendable)buffer,object);
}
/* ------------------------------------------------------------ */
/**
* Append object as JSON to string buffer.
*
@ -301,32 +278,58 @@ public class JSON
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 @@ public class JSON
}
}
/* ------------------------------------------------------------ */
@Deprecated
public void appendNull(StringBuffer buffer)
{
appendNull((Appendable)buffer);
}
/* ------------------------------------------------------------ */
public void appendNull(Appendable buffer)
{
try
@ -355,14 +356,12 @@ public class JSON
}
}
/* ------------------------------------------------------------ */
@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 @@ public class JSON
});
}
/* ------------------------------------------------------------ */
@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 @@ public class JSON
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 @@ public class JSON
}
}
/* ------------------------------------------------------------ */
@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 @@ public class JSON
}
}
/* ------------------------------------------------------------ */
@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 @@ public class JSON
}
}
/* ------------------------------------------------------------ */
@Deprecated
public void appendBoolean(StringBuffer buffer, Boolean b)
{
appendBoolean((Appendable)buffer,b);
}
/* ------------------------------------------------------------ */
public void appendBoolean(Appendable buffer, Boolean b)
{
try
@ -535,7 +522,7 @@ public class JSON
appendNull(buffer);
return;
}
buffer.append(b.booleanValue()?"true":"false");
buffer.append(b?"true":"false");
}
catch (IOException e)
{
@ -543,14 +530,12 @@ public class JSON
}
}
/* ------------------------------------------------------------ */
@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 @@ public class JSON
}
}
/* ------------------------------------------------------------ */
@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 @@ public class JSON
// 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 @@ public class JSON
return map;
}
/* ------------------------------------------------------------ */
/**
* Register a {@link Convertor} for a class or interface.
*
@ -658,7 +634,6 @@ public class JSON
_convertors.put(forClass.getName(),convertor);
}
/* ------------------------------------------------------------ */
/**
* Lookup a convertor for a class.
* <p>
@ -677,7 +652,7 @@ public class JSON
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 @@ public class JSON
return convertor;
}
/* ------------------------------------------------------------ */
/**
* Register a {@link JSON.Convertor} for a named class or interface.
*
@ -706,7 +680,6 @@ public class JSON
_convertors.put(name,convertor);
}
/* ------------------------------------------------------------ */
/**
* Lookup a convertor for a named class.
*
@ -716,14 +689,12 @@ public class JSON
*/
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 @@ public class JSON
return o;
}
/* ------------------------------------------------------------ */
public Object parse(Source source)
{
int comment_state = 0; // 0=no comment, 1="/", 2="/*", 3="/* *" -1="//"
@ -911,13 +881,11 @@ public class JSON
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 @@ public class JSON
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 @@ public class JSON
}
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 @@ public class JSON
throw new IllegalStateException("unexpected end of array");
}
/* ------------------------------------------------------------ */
protected String parseString(Source source)
{
if (source.next() != '"')
@ -1110,7 +1076,6 @@ public class JSON
else if (c == '\\')
{
escape = true;
continue;
}
else if (c == '\"')
{
@ -1118,7 +1083,9 @@ public class JSON
return toString(scratch,0,i);
}
else
{
scratch[i++] = c;
}
}
// Missing end quote, but return string anyway ?
@ -1175,17 +1142,19 @@ public class JSON
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 @@ public class JSON
}
/* ------------------------------------------------------------ */
protected void seekTo(char seek, Source source)
{
while (source.hasNext())
@ -1287,7 +1255,6 @@ public class JSON
throw new IllegalStateException("Expected '" + seek + "'");
}
/* ------------------------------------------------------------ */
protected char seekTo(String seek, Source source)
{
while (source.hasNext())
@ -1306,7 +1273,6 @@ public class JSON
throw new IllegalStateException("Expected one of '" + seek + "'");
}
/* ------------------------------------------------------------ */
protected static void complete(String seek, Source source)
{
int i = 0;
@ -1398,7 +1364,7 @@ public class JSON
_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 class JSON
}
}
/* ------------------------------------------------------------ */
public interface Source
{
boolean hasNext();
@ -1456,7 +1421,6 @@ public class JSON
char[] scratchBuffer();
}
/* ------------------------------------------------------------ */
public static class StringSource implements Source
{
private final String string;
@ -1500,7 +1464,6 @@ public class JSON
}
}
/* ------------------------------------------------------------ */
public static class ReaderSource implements Source
{
private Reader _reader;
@ -1567,7 +1530,6 @@ public class JSON
}
/* ------------------------------------------------------------ */
/**
* JSON Output class for use by {@link Convertible}.
*/
@ -1586,7 +1548,6 @@ public class JSON
public void add(String name, boolean value);
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/**
* JSON Convertible object. Object can implement this interface in a similar
@ -1607,7 +1568,6 @@ public class JSON
public void fromJSON(Map object);
}
/* ------------------------------------------------------------ */
/**
* Static JSON Convertor.
* <p>
@ -1626,7 +1586,6 @@ public class JSON
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 class JSON
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 @@ public class JSON
{
private String _json;
/* ------------------------------------------------------------ */
/**
* Construct a literal JSON instance for use by
* {@link JSON#toString(Object)}. If {@link Log#isDebugEnabled()} is

View File

@ -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);
}
}
}

Some files were not shown because too many files have changed in this diff Show More