Merge branch 'master' into release-9

This commit is contained in:
Jesse McConnell 2013-10-31 13:44:34 -05:00
commit c28525ab34
66 changed files with 2315 additions and 814 deletions

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.annotations;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
@ -770,8 +771,8 @@ public class AnnotationParser
else
{
//we've already verified the directories, so just verify the class file name
String filename = res.getFile().getName();
if (isValidClassFileName(filename))
File file = res.getFile();
if (isValidClassFileName((file==null?null:file.getName())))
{
String name = res.getName();
if ((resolver == null)|| (!resolver.isExcluded(name) && (!isParsed(name) || resolver.shouldOverride(name))))
@ -782,6 +783,10 @@ public class AnnotationParser
}
}
else
{
if (LOG.isDebugEnabled()) LOG.debug("Skipping scan on invalid file {}", res);
}
}
}
catch (Exception ex)

View File

@ -23,6 +23,7 @@ import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.charset.UnsupportedCharsetException;
import java.nio.file.Path;
import java.util.ArrayList;
@ -43,6 +44,7 @@ import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.util.FutureResponseListener;
import org.eclipse.jetty.client.util.PathContentProvider;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
@ -54,7 +56,7 @@ public class HttpRequest implements Request
private static final AtomicLong ids = new AtomicLong();
private final HttpFields headers = new HttpFields();
private final Fields params = new Fields();
private final Fields params = new Fields(true);
private final Map<String, Object> attributes = new HashMap<>();
private final List<RequestListener> requestListeners = new ArrayList<>();
private final List<Response.ResponseListener> responseListeners = new ArrayList<>();
@ -295,93 +297,184 @@ public class HttpRequest implements Request
}
@Override
public Request onRequestQueued(QueuedListener listener)
public Request onRequestQueued(final QueuedListener listener)
{
this.requestListeners.add(listener);
this.requestListeners.add(new QueuedListener()
{
@Override
public void onQueued(Request request)
{
listener.onQueued(request);
}
});
return this;
}
@Override
public Request onRequestBegin(BeginListener listener)
public Request onRequestBegin(final BeginListener listener)
{
this.requestListeners.add(listener);
this.requestListeners.add(new BeginListener()
{
@Override
public void onBegin(Request request)
{
listener.onBegin(request);
}
});
return this;
}
@Override
public Request onRequestHeaders(HeadersListener listener)
public Request onRequestHeaders(final HeadersListener listener)
{
this.requestListeners.add(listener);
this.requestListeners.add(new HeadersListener()
{
@Override
public void onHeaders(Request request)
{
listener.onHeaders(request);
}
});
return this;
}
@Override
public Request onRequestCommit(CommitListener listener)
public Request onRequestCommit(final CommitListener listener)
{
this.requestListeners.add(listener);
this.requestListeners.add(new CommitListener()
{
@Override
public void onCommit(Request request)
{
listener.onCommit(request);
}
});
return this;
}
@Override
public Request onRequestContent(ContentListener listener)
public Request onRequestContent(final ContentListener listener)
{
this.requestListeners.add(listener);
this.requestListeners.add(new ContentListener()
{
@Override
public void onContent(Request request, ByteBuffer content)
{
listener.onContent(request, content);
}
});
return this;
}
@Override
public Request onRequestSuccess(SuccessListener listener)
public Request onRequestSuccess(final SuccessListener listener)
{
this.requestListeners.add(listener);
this.requestListeners.add(new SuccessListener()
{
@Override
public void onSuccess(Request request)
{
listener.onSuccess(request);
}
});
return this;
}
@Override
public Request onRequestFailure(FailureListener listener)
public Request onRequestFailure(final FailureListener listener)
{
this.requestListeners.add(listener);
this.requestListeners.add(new FailureListener()
{
@Override
public void onFailure(Request request, Throwable failure)
{
listener.onFailure(request, failure);
}
});
return this;
}
@Override
public Request onResponseBegin(Response.BeginListener listener)
public Request onResponseBegin(final Response.BeginListener listener)
{
this.responseListeners.add(listener);
this.responseListeners.add(new Response.BeginListener()
{
@Override
public void onBegin(Response response)
{
listener.onBegin(response);
}
});
return this;
}
@Override
public Request onResponseHeader(Response.HeaderListener listener)
public Request onResponseHeader(final Response.HeaderListener listener)
{
this.responseListeners.add(listener);
this.responseListeners.add(new Response.HeaderListener()
{
@Override
public boolean onHeader(Response response, HttpField field)
{
return listener.onHeader(response, field);
}
});
return this;
}
@Override
public Request onResponseHeaders(Response.HeadersListener listener)
public Request onResponseHeaders(final Response.HeadersListener listener)
{
this.responseListeners.add(listener);
this.responseListeners.add(new Response.HeadersListener()
{
@Override
public void onHeaders(Response response)
{
listener.onHeaders(response);
}
});
return this;
}
@Override
public Request onResponseContent(Response.ContentListener listener)
public Request onResponseContent(final Response.ContentListener listener)
{
this.responseListeners.add(listener);
this.responseListeners.add(new Response.ContentListener()
{
@Override
public void onContent(Response response, ByteBuffer content)
{
listener.onContent(response, content);
}
});
return this;
}
@Override
public Request onResponseSuccess(Response.SuccessListener listener)
public Request onResponseSuccess(final Response.SuccessListener listener)
{
this.responseListeners.add(listener);
this.responseListeners.add(new Response.SuccessListener()
{
@Override
public void onSuccess(Response response)
{
listener.onSuccess(response);
}
});
return this;
}
@Override
public Request onResponseFailure(Response.FailureListener listener)
public Request onResponseFailure(final Response.FailureListener listener)
{
this.responseListeners.add(listener);
this.responseListeners.add(new Response.FailureListener()
{
@Override
public void onFailure(Response response, Throwable failure)
{
listener.onFailure(response, failure);
}
});
return this;
}

View File

@ -360,7 +360,7 @@ public class HttpSender implements AsyncContentProvider.Listener
if (!commit(request))
return false;
if (content != null)
if (content != null && content.hasRemaining())
{
RequestNotifier notifier = connection.getDestination().getRequestNotifier();
notifier.notifyContent(request, content);

View File

@ -64,21 +64,25 @@ public abstract class BufferingResponseListener extends Response.Listener.Empty
HttpFields headers = response.getHeaders();
long length = headers.getLongField(HttpHeader.CONTENT_LENGTH.asString());
if (length > maxLength)
response.abort(new IllegalArgumentException("Buffering capacity exceeded"));
String contentType = headers.get(HttpHeader.CONTENT_TYPE);
if (contentType != null)
{
String charset = "charset=";
int index = contentType.toLowerCase(Locale.ENGLISH).indexOf(charset);
if (index > 0)
response.abort(new IllegalArgumentException("Buffering capacity exceeded"));
}
else
{
String contentType = headers.get(HttpHeader.CONTENT_TYPE);
if (contentType != null)
{
String encoding = contentType.substring(index + charset.length());
// Sometimes charsets arrive with an ending semicolon
index = encoding.indexOf(';');
String charset = "charset=";
int index = contentType.toLowerCase(Locale.ENGLISH).indexOf(charset);
if (index > 0)
encoding = encoding.substring(0, index);
this.encoding = encoding;
{
String encoding = contentType.substring(index + charset.length());
// Sometimes charsets arrive with an ending semicolon
index = encoding.indexOf(';');
if (index > 0)
encoding = encoding.substring(0, index);
this.encoding = encoding;
}
}
}
}
@ -88,12 +92,16 @@ public abstract class BufferingResponseListener extends Response.Listener.Empty
{
long newLength = buffer.length + content.remaining();
if (newLength > maxLength)
{
response.abort(new IllegalArgumentException("Buffering capacity exceeded"));
byte[] newBuffer = new byte[(int)newLength];
System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
content.get(newBuffer, buffer.length, content.remaining());
buffer = newBuffer;
}
else
{
byte[] newBuffer = new byte[(int)newLength];
System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
content.get(newBuffer, buffer.length, content.remaining());
buffer = newBuffer;
}
}
@Override

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.client.util;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.Iterator;
@ -25,6 +26,8 @@ import java.util.NoSuchElementException;
import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* A {@link ContentProvider} for an {@link InputStream}.
@ -33,19 +36,26 @@ import org.eclipse.jetty.util.BufferUtil;
* Invocations to the {@link #iterator()} method after the first will return an "empty" iterator
* because the stream has been consumed on the first invocation.
* <p />
* It is possible to specify, at the constructor, a buffer size used to read content from the
* stream, by default 4096 bytes.
* <p />
* However, it is possible for subclasses to override {@link #onRead(byte[], int, int)} to copy
* the content read from the stream to another location (for example a file), and be able to
* support multiple invocations of {@link #iterator()}, returning the iterator provided by this
* class on the first invocation, and an iterator on the bytes copied to the other location
* for subsequent invocations.
* <p />
* It is possible to specify, at the constructor, a buffer size used to read content from the
* stream, by default 4096 bytes.
* <p />
* The {@link InputStream} passed to the constructor is by default closed when is it fully
* consumed (or when an exception is thrown while reading it), unless otherwise specified
* to the {@link #InputStreamContentProvider(java.io.InputStream, int, boolean) constructor}.
*/
public class InputStreamContentProvider implements ContentProvider
{
private static final Logger LOG = Log.getLogger(InputStreamContentProvider.class);
private final InputStream stream;
private final int bufferSize;
private final boolean autoClose;
public InputStreamContentProvider(InputStream stream)
{
@ -53,9 +63,15 @@ public class InputStreamContentProvider implements ContentProvider
}
public InputStreamContentProvider(InputStream stream, int bufferSize)
{
this(stream, bufferSize, true);
}
public InputStreamContentProvider(InputStream stream, int bufferSize, boolean autoClose)
{
this.stream = stream;
this.bufferSize = bufferSize;
this.autoClose = autoClose;
}
@Override
@ -107,6 +123,7 @@ public class InputStreamContentProvider implements ContentProvider
}
else if (read < 0)
{
close();
return false;
}
else
@ -122,6 +139,7 @@ public class InputStreamContentProvider implements ContentProvider
failure = x;
// Signal we have more content to cause a call to
// next() which will throw NoSuchElementException.
close();
return true;
}
return false;
@ -145,6 +163,21 @@ public class InputStreamContentProvider implements ContentProvider
{
throw new UnsupportedOperationException();
}
private void close()
{
if (autoClose)
{
try
{
stream.close();
}
catch (IOException x)
{
LOG.ignore(x);
}
}
}
};
}
}

View File

@ -37,6 +37,11 @@ public class StringContentProvider extends BytesContentProvider
public StringContentProvider(String content, String encoding)
{
super(content.getBytes(Charset.forName(encoding)));
this(content, Charset.forName(encoding));
}
public StringContentProvider(String content, Charset charset)
{
super(content.getBytes(charset));
}
}

View File

@ -28,7 +28,9 @@ import java.nio.channels.UnresolvedAddressException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
@ -52,6 +54,7 @@ import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.BytesContentProvider;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
@ -824,4 +827,181 @@ public class HttpClientTest extends AbstractHttpClientServerTest
});
Assert.assertTrue(latch.await(10, TimeUnit.SECONDS));
}
@Test
public void testCustomUserAgent() throws Exception
{
final String userAgent = "Test/1.0";
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);
ArrayList<String> userAgents = Collections.list(request.getHeaders("User-Agent"));
Assert.assertEquals(1, userAgents.size());
Assert.assertEquals(userAgent, userAgents.get(0));
}
});
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.agent(userAgent)
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(200, response.getStatus());
response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.header(HttpHeader.USER_AGENT, userAgent)
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(200, response.getStatus());
}
@Test
public void testRequestListenerForMultipleEventsIsInvokedOncePerEvent() throws Exception
{
start(new EmptyServerHandler());
final AtomicInteger counter = new AtomicInteger();
Request.Listener listener = new Request.Listener()
{
@Override
public void onQueued(Request request)
{
counter.incrementAndGet();
}
@Override
public void onBegin(Request request)
{
counter.incrementAndGet();
}
@Override
public void onHeaders(Request request)
{
counter.incrementAndGet();
}
@Override
public void onCommit(Request request)
{
counter.incrementAndGet();
}
@Override
public void onContent(Request request, ByteBuffer content)
{
// Should not be invoked
counter.incrementAndGet();
}
@Override
public void onFailure(Request request, Throwable failure)
{
// Should not be invoked
counter.incrementAndGet();
}
@Override
public void onSuccess(Request request)
{
counter.incrementAndGet();
}
};
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.onRequestQueued(listener)
.onRequestBegin(listener)
.onRequestHeaders(listener)
.onRequestCommit(listener)
.onRequestContent(listener)
.onRequestSuccess(listener)
.onRequestFailure(listener)
.listener(listener)
.send();
Assert.assertEquals(200, response.getStatus());
int expectedEventsTriggeredByOnRequestXXXListeners = 5;
int expectedEventsTriggeredByListener = 5;
int expected = expectedEventsTriggeredByOnRequestXXXListeners + expectedEventsTriggeredByListener;
Assert.assertEquals(expected, counter.get());
}
@Test
public void testResponseListenerForMultipleEventsIsInvokedOncePerEvent() throws Exception
{
start(new EmptyServerHandler());
final AtomicInteger counter = new AtomicInteger();
final CountDownLatch latch = new CountDownLatch(1);
Response.Listener listener = new Response.Listener()
{
@Override
public void onBegin(Response response)
{
counter.incrementAndGet();
}
@Override
public boolean onHeader(Response response, HttpField field)
{
// Number of header may vary, so don't count
return true;
}
@Override
public void onHeaders(Response response)
{
counter.incrementAndGet();
}
@Override
public void onContent(Response response, ByteBuffer content)
{
// Should not be invoked
counter.incrementAndGet();
}
@Override
public void onSuccess(Response response)
{
counter.incrementAndGet();
}
@Override
public void onFailure(Response response, Throwable failure)
{
// Should not be invoked
counter.incrementAndGet();
}
@Override
public void onComplete(Result result)
{
Assert.assertEquals(200, result.getResponse().getStatus());
counter.incrementAndGet();
latch.countDown();
}
};
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.onResponseBegin(listener)
.onResponseHeader(listener)
.onResponseHeaders(listener)
.onResponseContent(listener)
.onResponseSuccess(listener)
.onResponseFailure(listener)
.send(listener);
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
int expectedEventsTriggeredByOnResponseXXXListeners = 3;
int expectedEventsTriggeredByCompletionListener = 4;
int expected = expectedEventsTriggeredByOnResponseXXXListeners + expectedEventsTriggeredByCompletionListener;
Assert.assertEquals(expected, counter.get());
}
}

View File

@ -312,4 +312,30 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
}
@Test
public void testCaseSensitiveParameterName() throws Exception
{
final String name1 = "a";
final String name2 = "A";
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(name1, request.getParameter(name1));
Assert.assertEquals(name2, request.getParameter(name2));
}
});
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.path("/path?" + name1 + "=" + name1)
.param(name2, name2)
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
}
}

View File

@ -69,7 +69,7 @@ public class SslBytesClientTest extends SslBytesTest
client.start();
SSLContext sslContext = sslContextFactory.getSslContext();
acceptor = (SSLServerSocket)sslContext.getServerSocketFactory().createServerSocket(43191);
acceptor = (SSLServerSocket)sslContext.getServerSocketFactory().createServerSocket(0);
int serverPort = acceptor.getLocalPort();

View File

@ -84,6 +84,7 @@ public class SslBytesServerTest extends SslBytesTest
private ExecutorService threadPool;
private Server server;
private SslContextFactory sslContextFactory;
private int serverPort;
private SSLContext sslContext;
private SimpleProxy proxy;
private Runnable idleHook;
@ -211,7 +212,7 @@ public class SslBytesServerTest extends SslBytesTest
}
});
server.start();
int serverPort = connector.getLocalPort();
serverPort = connector.getLocalPort();
sslContext = sslContextFactory.getSslContext();
@ -292,6 +293,88 @@ public class SslBytesServerTest extends SslBytesTest
closeClient(client);
}
@Test
public void testHandshakeWithResumedSessionThenClose() throws Exception
{
// First socket will establish the SSL session
SSLSocket client1 = newClient();
SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
client1.startHandshake();
client1.close();
Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
int proxyPort = proxy.getPort();
proxy.stop();
proxy = new SimpleProxy(threadPool, proxyPort, "localhost", serverPort);
proxy.start();
logger.info("proxy:{} <==> server:{}", proxy.getPort(), serverPort);
final SSLSocket client2 = newClient(proxy);
Future<Object> handshake = threadPool.submit(new Callable<Object>()
{
@Override
public Object call() throws Exception
{
client2.startHandshake();
return null;
}
});
// Client Hello with SessionID
TLSRecord record = proxy.readFromClient();
Assert.assertNotNull(record);
proxy.flushToServer(record);
// Server Hello
record = proxy.readFromServer();
Assert.assertNotNull(record);
proxy.flushToClient(record);
// Change Cipher Spec
record = proxy.readFromServer();
Assert.assertNotNull(record);
proxy.flushToClient(record);
// Server Done
record = proxy.readFromServer();
Assert.assertNotNull(record);
proxy.flushToClient(record);
// Client Key Exchange
record = proxy.readFromClient();
Assert.assertNotNull(record);
// Client Done
TLSRecord doneRecord = proxy.readFromClient();
Assert.assertNotNull(doneRecord);
// Close
client2.close();
TLSRecord closeRecord = proxy.readFromClient();
Assert.assertNotNull(closeRecord);
Assert.assertEquals(TLSRecord.Type.ALERT, closeRecord.getType());
// Flush to server Client Key Exchange + Client Done + Close in one chunk
byte[] recordBytes = record.getBytes();
byte[] doneBytes = doneRecord.getBytes();
byte[] closeRecordBytes = closeRecord.getBytes();
byte[] chunk = new byte[recordBytes.length + doneBytes.length + closeRecordBytes.length];
System.arraycopy(recordBytes, 0, chunk, 0, recordBytes.length);
System.arraycopy(doneBytes, 0, chunk, recordBytes.length, doneBytes.length);
System.arraycopy(closeRecordBytes, 0, chunk, recordBytes.length + doneBytes.length, closeRecordBytes.length);
proxy.flushToServer(0, chunk);
// Close the raw socket
proxy.flushToServer(null);
// Expect the server to send a FIN as well
record = proxy.readFromServer();
Assert.assertNull(record);
// Check that we did not spin
TimeUnit.MILLISECONDS.sleep(500);
Assert.assertThat(sslFills.get(), Matchers.lessThan(20));
Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
Assert.assertThat(httpParses.get(), Matchers.lessThan(20));
}
@Test
public void testHandshakeWithSplitBoundary() throws Exception
{
@ -1794,6 +1877,11 @@ public class SslBytesServerTest extends SslBytesTest
}
private SSLSocket newClient() throws IOException, InterruptedException
{
return newClient(proxy);
}
private SSLSocket newClient(SimpleProxy proxy) throws IOException, InterruptedException
{
SSLSocket client = (SSLSocket)sslContext.getSocketFactory().createSocket("localhost", proxy.getPort());
client.setUseClientMode(true);

View File

@ -105,6 +105,7 @@ public abstract class SslBytesTest
{
private final CountDownLatch latch = new CountDownLatch(1);
private final ExecutorService threadPool;
private final int proxyPort;
private final String serverHost;
private final int serverPort;
private volatile ServerSocket serverSocket;
@ -112,15 +113,21 @@ public abstract class SslBytesTest
private volatile Socket client;
public SimpleProxy(ExecutorService threadPool, String serverHost, int serverPort)
{
this(threadPool, 0, serverHost, serverPort);
}
public SimpleProxy(ExecutorService threadPool, int proxyPort, String serverHost, int serverPort)
{
this.threadPool = threadPool;
this.proxyPort = proxyPort;
this.serverHost = serverHost;
this.serverPort = serverPort;
}
public void start() throws Exception
{
serverSocket = new ServerSocket(0);
serverSocket = new ServerSocket(proxyPort);
Thread acceptor = new Thread(this);
acceptor.start();
server = new Socket(serverHost, serverPort);

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.http;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@ -39,6 +40,7 @@ import org.eclipse.jetty.util.URIUtil;
* /foo/* - a prefix path specification (must end '/*').
* *.ext - a suffix path specification.
* / - the default path specification.
* "" - the / path specification
* </PRE>
* Matching is performed in the following order <NL>
* <LI>Exact match.
@ -267,20 +269,22 @@ public class PathMap<O> extends HashMap<String,O>
/** Get all entries matched by the path.
* Best match first.
* @param path Path to match
* @return LazyList of Map.Entry instances key=pathSpec
* @return List of Map.Entry instances key=pathSpec
*/
public Object getLazyMatches(String path)
public List<? extends Map.Entry<String,O>> getMatches(String path)
{
MappedEntry<O> entry;
Object entries=null;
List<MappedEntry<O>> entries=new ArrayList<>();
if (path==null)
return LazyList.getList(entries);
return entries;
if (path.length()==0)
return _defaultSingletonList;
// try exact match
entry=_exactMap.get(path);
if (entry!=null)
entries=LazyList.add(entries,entry);
entries.add(entry);
// prefix search
int l=path.length();
@ -293,14 +297,14 @@ public class PathMap<O> extends HashMap<String,O>
break;
String key = entry.getKey();
if (key.length()-2>=path.length() || path.charAt(key.length()-2)=='/')
entries=LazyList.add(entries,entry);
entries.add(entry);
i=key.length()-3;
}
// Prefix Default
if (_prefixDefault!=null)
entries=LazyList.add(entries,_prefixDefault);
entries.add(_prefixDefault);
// Extension search
i=0;
@ -309,32 +313,24 @@ public class PathMap<O> extends HashMap<String,O>
{
entry=suffix_map.get(path,i+1,l-i-1);
if (entry!=null)
entries=LazyList.add(entries,entry);
entries.add(entry);
}
// root match
if ("/".equals(path))
{
entry=_exactMap.get("");
if (entry!=null)
entries.add(entry);
}
// Default
if (_default!=null)
{
// Optimization for just the default
if (entries==null)
return _defaultSingletonList;
entries=LazyList.add(entries,_default);
}
entries.add(_default);
return entries;
}
/* --------------------------------------------------------------- */
/** Get all entries matched by the path.
* Best match first.
* @param path Path to match
* @return List of Map.Entry instances key=pathSpec
*/
public List<Map.Entry<String,O>> getMatches(String path)
{
return LazyList.getList(getLazyMatches(path));
}
/* --------------------------------------------------------------- */
/** Return whether the path matches any entries in the PathMap,
@ -381,6 +377,7 @@ public class PathMap<O> extends HashMap<String,O>
_suffixMap=new ArrayTernaryTrie<>(false);
_default=null;
_defaultSingletonList=null;
_prefixDefault=null;
super.clear();
}

View File

@ -65,6 +65,7 @@ public class PathMapTest
{ "/animal/path.gz", "5"},
{ "/Other/path", "8"},
{ "/\u20ACuro/path", "11"},
{ "/", "10"},
};
for (String[] test : tests)
@ -78,8 +79,8 @@ public class PathMapTest
p.getMatches("/animal/bird/path.tar.gz").toString());
assertEquals("Dir matches", "[/animal/fish/*=4, /animal/*=5, /=8]", p.getMatches("/animal/fish/").toString());
assertEquals("Dir matches", "[/animal/fish/*=4, /animal/*=5, /=8]", p.getMatches("/animal/fish").toString());
assertEquals("Dir matches", "[/=8]", p.getMatches("/").toString());
assertEquals("Dir matches", "[=10, /=8]", p.getMatches("").toString());
assertEquals("Root matches", "[=10, /=8]",p.getMatches("/").toString());
assertEquals("Dir matches", "[/=8]", p.getMatches("").toString());
assertEquals("pathMatch exact", "/Foo/bar", PathMap.pathMatch("/Foo/bar", "/Foo/bar"));
assertEquals("pathMatch prefix", "/Foo", PathMap.pathMatch("/Foo/*", "/Foo/bar"));

View File

@ -189,12 +189,12 @@ public class SslConnection extends AbstractConnection
// filling.
if (DEBUG)
LOG.debug("onFillable enter {}", getEndPoint());
LOG.debug("onFillable enter {}", _decryptedEndPoint);
// We have received a close handshake, close the end point to send FIN.
if (_decryptedEndPoint.isInputShutdown())
getEndPoint().close();
_decryptedEndPoint.close();
// wake up whoever is doing the fill or the flush so they can
// do all the filling, unwrapping, wrapping and flushing
_decryptedEndPoint.getFillInterest().fillable();
@ -210,7 +210,7 @@ public class SslConnection extends AbstractConnection
}
if (DEBUG)
LOG.debug("onFillable exit {}", getEndPoint());
LOG.debug("onFillable exit {}", _decryptedEndPoint);
}
@Override
@ -312,7 +312,7 @@ public class SslConnection extends AbstractConnection
fail_filler = true;
}
}
final boolean filler_failed=fail_filler;
getExecutor().execute(new Runnable()
@ -508,142 +508,142 @@ public class SslConnection extends AbstractConnection
int net_filled = getEndPoint().fill(_encryptedInput);
if (DEBUG)
LOG.debug("{} filled {} encrypted bytes", SslConnection.this, net_filled);
if (net_filled > 0)
_underFlown = false;
// Let's try the SSL thang even if we have no net data because in that
// case we want to fall through to the handshake handling
int pos = BufferUtil.flipToFill(app_in);
SSLEngineResult unwrapResult = _sslEngine.unwrap(_encryptedInput, app_in);
BufferUtil.flipToFlush(app_in, pos);
if (DEBUG)
LOG.debug("{} unwrap {}", SslConnection.this, unwrapResult);
Status unwrapResultStatus = unwrapResult.getStatus();
HandshakeStatus unwrapHandshakeStatus = unwrapResult.getHandshakeStatus();
HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
// and deal with the results
switch (unwrapResultStatus)
decryption: while (true)
{
case BUFFER_OVERFLOW:
throw new IllegalStateException();
// Let's unwrap even if we have no net data because in that
// case we want to fall through to the handshake handling
int pos = BufferUtil.flipToFill(app_in);
SSLEngineResult unwrapResult = _sslEngine.unwrap(_encryptedInput, app_in);
BufferUtil.flipToFlush(app_in, pos);
if (DEBUG)
LOG.debug("{} unwrap {}", SslConnection.this, unwrapResult);
case CLOSED:
// Dang! we have to care about the handshake state specially for close
switch (handshakeStatus)
{
case NOT_HANDSHAKING:
// We were not handshaking, so just tell the app we are closed
return -1;
HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
HandshakeStatus unwrapHandshakeStatus = unwrapResult.getHandshakeStatus();
Status unwrapResultStatus = unwrapResult.getStatus();
case NEED_TASK:
// run the task
_sslEngine.getDelegatedTask().run();
continue;
_underFlown = unwrapResultStatus == Status.BUFFER_UNDERFLOW;
case NEED_WRAP:
// we need to send some handshake data (probably to send a close handshake).
// but that will not enable any extra data to fill, so we just return -1
// The wrapping can be done by any output drivers doing flushing or shutdown output.
return -1;
}
throw new IllegalStateException();
default:
if (unwrapHandshakeStatus == HandshakeStatus.FINISHED && !_handshaken)
{
_handshaken = true;
if (DEBUG)
LOG.debug("{} handshake completed client-side", SslConnection.this);
}
// Check whether renegotiation is allowed
if (_handshaken && handshakeStatus != HandshakeStatus.NOT_HANDSHAKING && !isRenegotiationAllowed())
{
if (DEBUG)
LOG.debug("{} renegotiation denied", SslConnection.this);
if (_underFlown)
{
if (net_filled < 0)
closeInbound();
return -1;
}
if (net_filled <= 0)
return net_filled;
}
if (unwrapResultStatus == Status.BUFFER_UNDERFLOW)
_underFlown = true;
// If bytes were produced, don't bother with the handshake status;
// pass the decrypted data to the application, which will perform
// another call to fill() or flush().
if (unwrapResult.bytesProduced() > 0)
switch (unwrapResultStatus)
{
case CLOSED:
{
if (app_in == buffer)
return unwrapResult.bytesProduced();
return BufferUtil.flipPutFlip(_decryptedInput, buffer);
}
// Dang! we have to care about the handshake state
switch (handshakeStatus)
{
case NOT_HANDSHAKING:
// we just didn't read anything.
if (net_filled < 0)
switch (handshakeStatus)
{
case NOT_HANDSHAKING:
{
closeInbound();
// We were not handshaking, so just tell the app we are closed
return -1;
}
return 0;
case NEED_TASK:
// run the task
_sslEngine.getDelegatedTask().run();
continue;
case NEED_WRAP:
// we need to send some handshake data
// if we are called from flush
if (buffer == __FLUSH_CALLED_FILL)
return 0; // let it do the wrapping
_fillRequiresFlushToProgress = true;
flush(__FILL_CALLED_FLUSH);
if (BufferUtil.isEmpty(_encryptedOutput))
case NEED_TASK:
{
// the flush completed so continue
_fillRequiresFlushToProgress = false;
_sslEngine.getDelegatedTask().run();
continue;
}
return 0;
case NEED_UNWRAP:
// if we just filled some net data
if (net_filled < 0)
case NEED_WRAP:
{
closeInbound();
// We need to send some handshake data (probably the close handshake).
// We return -1 so that the application can drive the close by flushing
// or shutting down the output.
return -1;
}
else if (net_filled > 0)
default:
{
// maybe we will fill some more on a retry
throw new IllegalStateException();
}
}
}
case BUFFER_UNDERFLOW:
case OK:
{
if (unwrapHandshakeStatus == HandshakeStatus.FINISHED && !_handshaken)
{
_handshaken = true;
if (DEBUG)
LOG.debug("{} {} handshake completed", SslConnection.this,
_sslEngine.getUseClientMode() ? "client-side" : "resumed session server-side");
}
// Check whether renegotiation is allowed
if (_handshaken && handshakeStatus != HandshakeStatus.NOT_HANDSHAKING && !isRenegotiationAllowed())
{
if (DEBUG)
LOG.debug("{} renegotiation denied", SslConnection.this);
closeInbound();
return -1;
}
// If bytes were produced, don't bother with the handshake status;
// pass the decrypted data to the application, which will perform
// another call to fill() or flush().
if (unwrapResult.bytesProduced() > 0)
{
if (app_in == buffer)
return unwrapResult.bytesProduced();
return BufferUtil.flipPutFlip(_decryptedInput, buffer);
}
switch (handshakeStatus)
{
case NOT_HANDSHAKING:
{
if (_underFlown)
break decryption;
continue;
}
else
case NEED_TASK:
{
if (_encryptedInput.hasRemaining())
_sslEngine.getDelegatedTask().run();
continue;
}
case NEED_WRAP:
{
// If we are called from flush()
// return to let it do the wrapping.
if (buffer == __FLUSH_CALLED_FILL)
return 0;
_fillRequiresFlushToProgress = true;
flush(__FILL_CALLED_FLUSH);
if (BufferUtil.isEmpty(_encryptedOutput))
{
// if there are more encrypted bytes,
// then we need to unwrap more, we don't
// care if net_filled is zero
// The flush wrote all the encrypted bytes so continue to fill
_fillRequiresFlushToProgress = false;
continue;
}
// we need to wait for more net data
return 0;
else
{
// The flush did not complete, return from fill()
// and let the write completion mechanism to kick in.
return 0;
}
}
case FINISHED:
throw new IllegalStateException();
case NEED_UNWRAP:
{
if (_underFlown)
break decryption;
continue;
}
default:
{
throw new IllegalStateException();
}
}
}
default:
{
throw new IllegalStateException();
}
}
}
}
}
@ -778,7 +778,7 @@ public class SslConnection extends AbstractConnection
{
_handshaken = true;
if (DEBUG)
LOG.debug("{} handshake completed server-side", SslConnection.this);
LOG.debug("{} {} handshake completed", SslConnection.this, "server-side");
}
HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
@ -824,7 +824,7 @@ public class SslConnection extends AbstractConnection
if (handshakeStatus == HandshakeStatus.NEED_WRAP)
continue;
}
return allConsumed&&BufferUtil.isEmpty(_encryptedOutput);
return allConsumed && BufferUtil.isEmpty(_encryptedOutput);
case FINISHED:
throw new IllegalStateException();
@ -860,17 +860,18 @@ public class SslConnection extends AbstractConnection
public void shutdownOutput()
{
boolean ishut = isInputShutdown();
boolean oshut = isOutputShutdown();
if (DEBUG)
LOG.debug("{} shutdownOutput: oshut={}, ishut={}", SslConnection.this, isOutputShutdown(), ishut);
LOG.debug("{} shutdownOutput: oshut={}, ishut={}", SslConnection.this, oshut, ishut);
if (ishut)
{
// Aggressively close, since inbound close alert has already been processed
// and the TLS specification allows to close the connection directly, which
// is what most other implementations expect: a FIN rather than a TLS close
// reply. If a TLS close reply is sent, most implementation send a RST.
// reply. If a TLS close reply is sent, most implementations send a RST.
getEndPoint().close();
}
else
else if (!oshut)
{
try
{
@ -895,6 +896,8 @@ public class SslConnection extends AbstractConnection
@Override
public void close()
{
// First send the TLS Close Alert, then the FIN
shutdownOutput();
getEndPoint().close();
}

View File

@ -31,6 +31,7 @@ import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
@ -199,9 +200,14 @@ public abstract class AbstractLoginModule implements LoginModule
callbacks[2] = new PasswordCallback("Enter password", false); //only used if framework does not support the ObjectCallback
return callbacks;
}
public boolean isIgnored ()
{
return false;
}
public abstract UserInfo getUserInfo (String username) throws Exception;
@ -214,7 +220,10 @@ public abstract class AbstractLoginModule implements LoginModule
public boolean login() throws LoginException
{
try
{
{
if (isIgnored())
return false;
if (callbackHandler == null)
throw new LoginException ("No callback handler");
@ -231,7 +240,7 @@ public abstract class AbstractLoginModule implements LoginModule
if ((webUserName == null) || (webCredential == null))
{
setAuthenticated(false);
return isAuthenticated();
throw new FailedLoginException();
}
UserInfo userInfo = getUserInfo(webUserName);
@ -239,12 +248,16 @@ public abstract class AbstractLoginModule implements LoginModule
if (userInfo == null)
{
setAuthenticated(false);
return isAuthenticated();
throw new FailedLoginException();
}
currentUser = new JAASUserInfo(userInfo);
setAuthenticated(currentUser.checkCredential(webCredential));
return isAuthenticated();
if (isAuthenticated())
return true;
else
throw new FailedLoginException();
}
catch (IOException e)
{
@ -256,7 +269,8 @@ public abstract class AbstractLoginModule implements LoginModule
}
catch (Exception e)
{
e.printStackTrace();
if (e instanceof LoginException)
throw (LoginException)e;
throw new LoginException (e.toString());
}
}

View File

@ -112,9 +112,10 @@ public class PropertyFileLoginModule extends AbstractLoginModule
PropertyUserStore propertyUserStore = _propertyUserStores.get(_filename);
if (propertyUserStore == null)
throw new IllegalStateException("PropertyUserStore should never be null here!");
LOG.debug("Checking PropertyUserStore "+_filename+" for "+userName);
UserIdentity userIdentity = propertyUserStore.getUserIdentity(userName);
if(userIdentity==null)
if (userIdentity==null)
return null;
Set<Principal> principals = userIdentity.getSubject().getPrincipals();
@ -127,7 +128,7 @@ public class PropertyFileLoginModule extends AbstractLoginModule
}
Credential credential = (Credential)userIdentity.getSubject().getPrivateCredentials().iterator().next();
LOG.debug("Found: " + userName + " in PropertyUserStore");
LOG.debug("Found: " + userName + " in PropertyUserStore "+_filename);
return new UserInfo(userName, credential, roles);
}

View File

@ -68,7 +68,7 @@ import org.eclipse.jetty.util.IO;
* </p>
*
* @goal run-forked
* @requiresDependencyResolution compile+runtime
* @requiresDependencyResolution test
* @execute phase="test-compile"
* @description Runs Jetty in forked JVM on an unassembled webapp
*

View File

@ -29,6 +29,7 @@ import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.plus.webapp.EnvConfiguration;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.FilterMapping;
@ -110,7 +111,7 @@ public class JettyWebAppContext extends WebAppContext
new FragmentConfiguration(),
_envConfig = new EnvConfiguration(),
new org.eclipse.jetty.plus.webapp.PlusConfiguration(),
new MavenAnnotationConfiguration(),
new AnnotationConfiguration(),
new JettyWebXmlConfiguration()
});
// Turn off copyWebInf option as it is not applicable for plugin.

View File

@ -1,109 +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.maven.plugin;
import java.io.File;
import org.eclipse.jetty.annotations.AbstractDiscoverableAnnotationHandler;
import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.annotations.AnnotationParser;
import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler;
import org.eclipse.jetty.annotations.ClassNameResolver;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.MetaData;
import org.eclipse.jetty.webapp.WebAppContext;
public class MavenAnnotationConfiguration extends AnnotationConfiguration
{
private static final Logger LOG = Log.getLogger(MavenAnnotationConfiguration.class);
/* ------------------------------------------------------------ */
@Override
public void parseWebInfClasses(final WebAppContext context, final AnnotationParser parser) throws Exception
{
JettyWebAppContext jwac = (JettyWebAppContext)context;
if (jwac.getClassPathFiles() == null || jwac.getClassPathFiles().size() == 0)
super.parseWebInfClasses (context, parser);
else
{
LOG.debug("Scanning classes ");
//Look for directories on the classpath and process each one of those
MetaData metaData = context.getMetaData();
if (metaData == null)
throw new IllegalStateException ("No metadata");
parser.clearHandlers();
for (DiscoverableAnnotationHandler h:_discoverableAnnotationHandlers)
{
if (h instanceof AbstractDiscoverableAnnotationHandler)
((AbstractDiscoverableAnnotationHandler)h).setResource(null); //
}
parser.registerHandlers(_discoverableAnnotationHandlers);
parser.registerHandler(_classInheritanceHandler);
parser.registerHandlers(_containerInitializerAnnotationHandlers);
for (File f:jwac.getClassPathFiles())
{
//scan the equivalent of the WEB-INF/classes directory that has been synthesised by the plugin
if (f.isDirectory() && f.exists())
{
doParse(context, parser, Resource.newResource(f.toURI()));
}
}
//if an actual WEB-INF/classes directory also exists (eg because of overlayed wars) then scan that
//too
if (context.getWebInf() != null && context.getWebInf().exists())
{
Resource classesDir = context.getWebInf().addPath("classes/");
if (classesDir.exists())
{
doParse(context, parser, classesDir);
}
}
}
}
public void doParse (final WebAppContext context, final AnnotationParser parser, Resource resource)
throws Exception
{
parser.parseDir(resource, new ClassNameResolver()
{
public boolean isExcluded (String name)
{
if (context.isSystemClass(name)) return true;
if (context.isServerClass(name)) return false;
return false;
}
public boolean shouldOverride (String name)
{
//looking at webapp classpath, found already-parsed class of same name - did it come from system or duplicate in webapp?
if (context.isParentLoaderPriority())
return false;
return true;
}
});
}
}

View File

@ -239,6 +239,45 @@ public class MavenWebInfConfiguration extends WebInfConfiguration
}
/**
* Add in the classes dirs from test/classes and target/classes
* @see org.eclipse.jetty.webapp.WebInfConfiguration#findClassDirs(org.eclipse.jetty.webapp.WebAppContext)
*/
@Override
protected List<Resource> findClassDirs(WebAppContext context) throws Exception
{
List<Resource> list = new ArrayList<Resource>();
JettyWebAppContext jwac = (JettyWebAppContext)context;
if (jwac.getClassPathFiles() != null)
{
for (File f: jwac.getClassPathFiles())
{
if (f.exists() && f.isDirectory())
{
try
{
list.add(Resource.newResource(f.toURI()));
}
catch (Exception e)
{
LOG.warn("Bad url ", e);
}
}
}
}
List<Resource> classesDirs = super.findClassDirs(context);
if (classesDirs != null)
list.addAll(classesDirs);
return list;
}
protected Resource unpackOverlay (WebAppContext context, Overlay overlay)
throws IOException

View File

@ -25,6 +25,7 @@
<Item>127.0.0.2/black.html</Item>
</Array>
</Set>
<Set name="whiteListByPath">false</Set>
</New>
</Set>

View File

@ -27,6 +27,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.DispatcherType;
import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
@ -273,10 +274,27 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
if (_request.getHttpChannelState().isExpired())
{
_request.setDispatcherType(DispatcherType.ERROR);
Throwable ex=_state.getAsyncContextEvent().getThrowable();
String reason="Async Timeout";
if (ex!=null)
{
reason="Async Exception";
_request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,ex);
}
_request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,new Integer(500));
_request.setAttribute(RequestDispatcher.ERROR_MESSAGE,"Async Timeout");
_request.setAttribute(RequestDispatcher.ERROR_MESSAGE,reason);
_request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI,_request.getRequestURI());
_response.setStatusWithReason(500,"Async Timeout");
_response.setStatusWithReason(500,reason);
ErrorHandler eh = _state.getContextHandler().getErrorHandler();
if (eh instanceof ErrorHandler.ErrorPageMapper)
{
String error_page=((ErrorHandler.ErrorPageMapper)eh).getErrorPage((HttpServletRequest)_state.getAsyncContextEvent().getSuppliedRequest());
if (error_page!=null)
_state.getAsyncContextEvent().setDispatchTarget(_state.getContextHandler().getServletContext(),error_page);
}
}
else
_request.setDispatcherType(DispatcherType.ASYNC);
@ -362,6 +380,7 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
boolean committed = sendResponse(info, null, true);
if (!committed)
LOG.warn("Could not send response error 500: "+x);
_request.getAsyncContext().complete();
}
else if (isCommitted())
{

View File

@ -348,7 +348,7 @@ public class HttpChannelState
protected void expired()
{
final List<AsyncListener> aListeners;
AsyncEvent event;
AsyncContextEvent event;
synchronized (this)
{
switch(_state)
@ -374,7 +374,10 @@ public class HttpChannelState
}
catch(Exception e)
{
LOG.warn(e);
LOG.debug(e);
event.setThrowable(e);
_channel.getRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,e);
break;
}
}
}

View File

@ -647,7 +647,8 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
{
case NEED_HEADER:
{
if (_lastContent && _content!=null && BufferUtil.space(_content)>_config.getResponseHeaderSize() && _content.hasArray() )
// Look for optimisation to avoid allocating a _header buffer
if (_lastContent && _content!=null && !_content.isReadOnly() && _content.hasArray() && BufferUtil.space(_content)>_config.getResponseHeaderSize() )
{
// use spare space in content buffer for header buffer
int p=_content.position();

View File

@ -207,7 +207,8 @@ public class HttpOutput extends ServletOutputStream
// write any remaining content in the buffer directly
if (len>0)
_channel.write(ByteBuffer.wrap(b, off, len), complete);
// pass as readonly to avoid space stealing optimisation in HttpConnection
_channel.write(ByteBuffer.wrap(b, off, len).asReadOnlyBuffer(), complete);
else if (complete)
_channel.write(BufferUtil.EMPTY_BUFFER,complete);
@ -299,12 +300,14 @@ public class HttpOutput extends ServletOutputStream
/* ------------------------------------------------------------ */
/** Blocking send of content.
* @param content The content to send
* @param content The content to send.
* @throws IOException
*/
public void sendContent(ByteBuffer content) throws IOException
{
final BlockingCallback callback =_channel.getWriteBlockingCallback();
if (content.hasArray()&&content.limit()<content.capacity())
content=content.asReadOnlyBuffer();
_channel.write(content,true,callback);
callback.block();
}
@ -346,7 +349,6 @@ public class HttpOutput extends ServletOutputStream
callback.block();
}
/* ------------------------------------------------------------ */
/** Asynchronous send of content.
* @param content The content to send
@ -354,6 +356,8 @@ public class HttpOutput extends ServletOutputStream
*/
public void sendContent(ByteBuffer content, final Callback callback)
{
if (content.hasArray()&&content.limit()<content.capacity())
content=content.asReadOnlyBuffer();
_channel.write(content,true,new Callback()
{
@Override

View File

@ -1500,6 +1500,9 @@ public class Request implements HttpServletRequest
/* ------------------------------------------------------------ */
protected void recycle()
{
if (_context != null)
throw new IllegalStateException("Request in context!");
if (_inputState == __READER)
{
try
@ -1515,6 +1518,7 @@ public class Request implements HttpServletRequest
}
}
_dispatcherType=null;
setAuthentication(Authentication.NOT_CHECKED);
getHttpChannelState().recycle();
if (_async!=null)
@ -1522,8 +1526,6 @@ public class Request implements HttpServletRequest
_async=null;
_asyncSupported = true;
_handled = false;
if (_context != null)
throw new IllegalStateException("Request in context!");
if (_attributes != null)
_attributes.clearAttributes();
_characterEncoding = null;
@ -1532,7 +1534,9 @@ public class Request implements HttpServletRequest
_cookies.reset();
_cookiesExtracted = false;
_context = null;
_newContext=false;
_serverName = null;
_httpMethod=null;
_httpMethodString = null;
_pathInfo = null;
_port = 0;
@ -1541,6 +1545,7 @@ public class Request implements HttpServletRequest
_queryString = null;
_requestedSessionId = null;
_requestedSessionIdFromCookie = false;
_secure=false;
_session = null;
_sessionManager = null;
_requestURI = null;

View File

@ -359,9 +359,10 @@ public class Server extends HandlerWrapper implements Attributes
futures.add(connector.shutdown());
// Then tell the contexts that we are shutting down
Handler[] contexts = getChildHandlersByClass(Graceful.class);
for (Handler context : contexts)
futures.add(((Graceful)context).shutdown());
Handler[] gracefuls = getChildHandlersByClass(Graceful.class);
for (Handler graceful : gracefuls)
futures.add(((Graceful)graceful).shutdown());
// Shall we gracefully wait for zero connections?
long stopTimeout = getStopTimeout();

View File

@ -1606,27 +1606,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
path = URIUtil.canonicalPath(path);
Resource resource = _baseResource.addPath(path);
// Is the resource aliased?
if (resource.getAlias() != null)
{
if (LOG.isDebugEnabled())
LOG.debug("Aliased resource: " + resource + "~=" + resource.getAlias());
// alias checks
for (Iterator<AliasCheck> i=_aliasChecks.iterator();i.hasNext();)
{
AliasCheck check = i.next();
if (check.check(path,resource))
{
if (LOG.isDebugEnabled())
LOG.debug("Aliased resource: " + resource + " approved by " + check);
return resource;
}
}
return null;
}
return resource;
if (checkAlias(path,resource))
return resource;
return null;
}
catch (Exception e)
{
@ -1636,6 +1618,31 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
return null;
}
/* ------------------------------------------------------------ */
public boolean checkAlias(String path, Resource resource)
{
// Is the resource aliased?
if (resource.getAlias() != null)
{
if (LOG.isDebugEnabled())
LOG.debug("Aliased resource: " + resource + "~=" + resource.getAlias());
// alias checks
for (Iterator<AliasCheck> i=_aliasChecks.iterator();i.hasNext();)
{
AliasCheck check = i.next();
if (check.check(path,resource))
{
if (LOG.isDebugEnabled())
LOG.debug("Aliased resource: " + resource + " approved by " + check);
return true;
}
}
return false;
}
return true;
}
/* ------------------------------------------------------------ */
/**
* Convert URL to Resource wrapper for {@link Resource#newResource(URL)} enables extensions to provide alternate resource implementations.
@ -1722,6 +1729,16 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
{
return _aliasChecks;
}
/* ------------------------------------------------------------ */
/**
* @param checks list of AliasCheck instances
*/
public void setAliasChecks(List<AliasCheck> checks)
{
_aliasChecks.clear();
_aliasChecks.addAll(checks);
}
/* ------------------------------------------------------------ */
/**
@ -2663,8 +2680,10 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
if (a.length()>r.length())
return a.startsWith(r) && a.length()==r.length()+1 && a.endsWith("/");
else
if (a.length()<r.length())
return r.startsWith(a) && r.length()==a.length()+1 && r.endsWith("/");
return a.equals(r);
}
}

View File

@ -23,6 +23,8 @@ import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import javax.activation.MimeType;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -30,9 +32,12 @@ import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.server.Dispatcher;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.ByteArrayISO8859Writer;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */
/** Handler for Error pages
@ -42,7 +47,10 @@ import org.eclipse.jetty.util.ByteArrayISO8859Writer;
*
*/
public class ErrorHandler extends AbstractHandler
{
{
private static final Logger LOG = Log.getLogger(ErrorHandler.class);
public final static String ERROR_PAGE="org.eclipse.jetty.server.error_page";
boolean _showStacks=true;
boolean _showMessageInTitle=true;
String _cacheControl="must-revalidate,no-cache,no-store";
@ -54,11 +62,44 @@ public class ErrorHandler extends AbstractHandler
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
{
baseRequest.setHandled(true);
String method = request.getMethod();
if(!HttpMethod.GET.is(method) && !HttpMethod.POST.is(method) && !HttpMethod.HEAD.is(method))
if (!HttpMethod.GET.is(method) && !HttpMethod.POST.is(method) && !HttpMethod.HEAD.is(method))
{
baseRequest.setHandled(true);
return;
response.setContentType(MimeTypes.Type.TEXT_HTML_8859_1.asString());
}
if (this instanceof ErrorPageMapper)
{
String error_page=((ErrorPageMapper)this).getErrorPage(request);
if (error_page!=null && request.getServletContext()!=null)
{
String old_error_page=(String)request.getAttribute(ERROR_PAGE);
if (old_error_page==null || !old_error_page.equals(error_page))
{
request.setAttribute(ERROR_PAGE, error_page);
Dispatcher dispatcher = (Dispatcher) request.getServletContext().getRequestDispatcher(error_page);
try
{
if(dispatcher!=null)
{
dispatcher.error(request, response);
return;
}
LOG.warn("No error page "+error_page);
}
catch (ServletException e)
{
LOG.warn(Log.EXCEPTION, e);
return;
}
}
}
}
baseRequest.setHandled(true);
response.setContentType(MimeTypes.Type.TEXT_HTML_8859_1.asString());
if (_cacheControl!=null)
response.setHeader(HttpHeader.CACHE_CONTROL.asString(), _cacheControl);
ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(4096);
@ -235,4 +276,10 @@ public class ErrorHandler extends AbstractHandler
}
}
}
/* ------------------------------------------------------------ */
public interface ErrorPageMapper
{
String getErrorPage(HttpServletRequest request);
}
}

View File

@ -20,8 +20,6 @@ package org.eclipse.jetty.server.handler;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
@ -54,11 +52,17 @@ import org.eclipse.jetty.util.log.Logger;
* entries, that are then further refined by several specific black list exceptions
* </ul>
* <p>
* An empty white list is treated as match all. If there is at least one entry in
* By default an empty white list is treated as match all. If there is at least one entry in
* the white list, then a request must match a white list entry. Black list entries
* are always applied, so that even if an entry matches the white list, a black list
* entry will override it.
* <p>
* <p>
* You can change white list policy setting whiteListByPath to true. In this mode a request will be white listed
* IF it has a matching URL in the white list, otherwise the black list applies, e.g. in default mode when
* whiteListByPath = false and wl = "127.0.0.1|/foo", /bar request from 127.0.0.1 will be blacklisted,
* if whiteListByPath=true then not.
* </p>
* Internet addresses may be specified as absolute address or as a combination of
* four octet wildcard specifications (a.b.c.d) that are defined as follows.
* </p>
@ -101,9 +105,10 @@ import org.eclipse.jetty.util.log.Logger;
public class IPAccessHandler extends HandlerWrapper
{
private static final Logger LOG = Log.getLogger(IPAccessHandler.class);
IPAddressMap<PathMap> _white = new IPAddressMap<PathMap>();
IPAddressMap<PathMap> _black = new IPAddressMap<PathMap>();
// true means nodefault match
PathMap<IPAddressMap<Boolean>> _white = new PathMap<IPAddressMap<Boolean>>(true);
PathMap<IPAddressMap<Boolean>> _black = new PathMap<IPAddressMap<Boolean>>(true);
boolean _whiteListByPath = false;
/* ------------------------------------------------------------ */
/**
@ -175,6 +180,17 @@ public class IPAccessHandler extends HandlerWrapper
set(entries, _black);
}
/* ------------------------------------------------------------ */
/**
* Re-initialize the mode of path matching
*
* @param whiteListByPath matching mode
*/
public void setWhiteListByPath(boolean whiteListByPath)
{
this._whiteListByPath = whiteListByPath;
}
/* ------------------------------------------------------------ */
/**
* Checks the incoming request against the whitelist and blacklist
@ -185,7 +201,7 @@ public class IPAccessHandler extends HandlerWrapper
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
// Get the real remote IP (not the one set by the forwarded headers (which may be forged))
HttpChannel channel = baseRequest.getHttpChannel();
HttpChannel<?> channel = baseRequest.getHttpChannel();
if (channel!=null)
{
EndPoint endp=channel.getEndPoint();
@ -213,7 +229,7 @@ public class IPAccessHandler extends HandlerWrapper
* @param entry new entry
* @param patternMap target address pattern map
*/
protected void add(String entry, IPAddressMap<PathMap> patternMap)
protected void add(String entry, PathMap<IPAddressMap<Boolean>> patternMap)
{
if (entry != null && entry.length() > 0)
{
@ -237,14 +253,15 @@ public class IPAccessHandler extends HandlerWrapper
if (path!=null && (path.startsWith("|") || path.startsWith("/*.")))
path=path.substring(1);
PathMap pathMap = patternMap.get(addr);
if (pathMap == null)
IPAddressMap<Boolean> addrMap = patternMap.get(path);
if (addrMap == null)
{
pathMap = new PathMap(true);
patternMap.put(addr,pathMap);
addrMap = new IPAddressMap<Boolean>();
patternMap.put(path,addrMap);
}
if (path != null && !"".equals(path))
pathMap.put(path,path);
if (addr != null && !"".equals(addr))
// MUST NOT BE null
addrMap.put(addr, true);
if (deprecated)
LOG.debug(toString() +" - deprecated specification syntax: "+entry);
@ -259,7 +276,7 @@ public class IPAccessHandler extends HandlerWrapper
* @param entries new entries
* @param patternMap target address pattern map
*/
protected void set(String[] entries, IPAddressMap<PathMap> patternMap)
protected void set(String[] entries, PathMap<IPAddressMap<Boolean>> patternMap)
{
patternMap.clear();
@ -286,38 +303,40 @@ public class IPAccessHandler extends HandlerWrapper
if (_white.size()>0)
{
boolean match = false;
boolean matchedByPath = false;
Object whiteObj = _white.getLazyMatches(addr);
if (whiteObj != null)
for (Map.Entry<String,IPAddressMap<Boolean>> entry : _white.getMatches(path))
{
List whiteList = (whiteObj instanceof List) ? (List)whiteObj : Collections.singletonList(whiteObj);
for (Object entry: whiteList)
matchedByPath=true;
IPAddressMap<Boolean> addrMap = entry.getValue();
if ((addrMap!=null && (addrMap.size()==0 || addrMap.match(addr)!=null)))
{
PathMap pathMap = ((Map.Entry<String,PathMap>)entry).getValue();
if (match = (pathMap!=null && (pathMap.size()==0 || pathMap.match(path)!=null)))
break;
match=true;
break;
}
}
if (!match)
return false;
if (_whiteListByPath)
{
if (matchedByPath && !match)
return false;
}
else
{
if (!match)
return false;
}
}
if (_black.size() > 0)
{
Object blackObj = _black.getLazyMatches(addr);
if (blackObj != null)
for (Map.Entry<String,IPAddressMap<Boolean>> entry : _black.getMatches(path))
{
List blackList = (blackObj instanceof List) ? (List)blackObj : Collections.singletonList(blackObj);
for (Object entry: blackList)
{
PathMap pathMap = ((Map.Entry<String,PathMap>)entry).getValue();
if (pathMap!=null && (pathMap.size()==0 || pathMap.match(path)!=null))
return false;
}
IPAddressMap<Boolean> addrMap = entry.getValue();
if (addrMap!=null && (addrMap.size()==0 || addrMap.match(addr)!=null))
return false;
}
}
return true;
@ -327,6 +346,7 @@ public class IPAccessHandler extends HandlerWrapper
/**
* Dump the handler configuration
*/
@Override
public String dump()
{
StringBuilder buf = new StringBuilder();
@ -348,11 +368,11 @@ public class IPAccessHandler extends HandlerWrapper
* @param buf buffer
* @param patternMap pattern map to dump
*/
protected void dump(StringBuilder buf, IPAddressMap<PathMap> patternMap)
protected void dump(StringBuilder buf, PathMap<IPAddressMap<Boolean>> patternMap)
{
for (String addr: patternMap.keySet())
for (String path: patternMap.keySet())
{
for (Object path: ((PathMap)patternMap.get(addr)).values())
for (String addr: patternMap.get(path).keySet())
{
buf.append("# ");
buf.append(addr);

View File

@ -19,8 +19,11 @@
package org.eclipse.jetty.server.handler;
import java.io.IOException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
@ -32,14 +35,16 @@ import org.eclipse.jetty.server.AsyncContextEvent;
import org.eclipse.jetty.server.HttpChannelState;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.annotation.ManagedOperation;
import org.eclipse.jetty.util.component.Graceful;
import org.eclipse.jetty.util.statistic.CounterStatistic;
import org.eclipse.jetty.util.statistic.SampleStatistic;
@ManagedObject("Request Statistics Gathering")
public class StatisticsHandler extends HandlerWrapper
public class StatisticsHandler extends HandlerWrapper implements Graceful
{
private final AtomicLong _statsStartedAt = new AtomicLong();
@ -59,6 +64,8 @@ public class StatisticsHandler extends HandlerWrapper
private final AtomicInteger _responses5xx = new AtomicInteger();
private final AtomicLong _responsesTotalBytes = new AtomicLong();
private final AtomicReference<FutureCallback> _shutdown=new AtomicReference<>();
private final AsyncListener _onCompletion = new AsyncListener()
{
@Override
@ -81,21 +88,27 @@ public class StatisticsHandler extends HandlerWrapper
@Override
public void onComplete(AsyncEvent event) throws IOException
{
HttpChannelState state = ((AsyncContextEvent)event).getHttpChannelState();
Request request = state.getBaseRequest();
final long elapsed = System.currentTimeMillis()-request.getTimeStamp();
_requestStats.decrement();
long d=_requestStats.decrement();
_requestTimeStats.set(elapsed);
updateResponse(request);
if (!state.isDispatched())
_asyncWaitStats.decrement();
// If we have no more dispatches, should we signal shutdown?
if (d==0)
{
FutureCallback shutdown = _shutdown.get();
if (shutdown!=null)
shutdown.succeeded();
}
}
};
/**
@ -164,9 +177,18 @@ public class StatisticsHandler extends HandlerWrapper
}
else if (state.isInitial())
{
_requestStats.decrement();
long d=_requestStats.decrement();
_requestTimeStats.set(dispatched);
updateResponse(request);
// If we have no more dispatches, should we signal shutdown?
FutureCallback shutdown = _shutdown.get();
if (shutdown!=null)
{
httpResponse.flushBuffer();
if (d==0)
shutdown.succeeded();
}
}
// else onCompletion will handle it.
}
@ -207,9 +229,20 @@ public class StatisticsHandler extends HandlerWrapper
@Override
protected void doStart() throws Exception
{
_shutdown.set(null);
super.doStart();
statsReset();
}
@Override
protected void doStop() throws Exception
{
super.doStop();
FutureCallback shutdown = _shutdown.get();
if (shutdown!=null && !shutdown.isDone())
shutdown.failed(new TimeoutException());
}
/**
* @return the number of requests handled by this handler
@ -525,4 +558,15 @@ public class StatisticsHandler extends HandlerWrapper
return sb.toString();
}
@Override
public Future<Void> shutdown()
{
FutureCallback shutdown=new FutureCallback(false);
_shutdown.compareAndSet(null,shutdown);
shutdown=_shutdown.get();
if (_dispatchedStats.getCurrent()==0)
shutdown.succeeded();
return shutdown;
}
}

View File

@ -244,7 +244,7 @@ public class JDBCSessionManager extends AbstractSessionManager
@Override
public void setAttribute (String name, Object value)
{
_dirty=updateAttribute(name, value);
_dirty = (updateAttribute(name, value) || _dirty);
}
@Override

View File

@ -0,0 +1,105 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.server;
import java.io.IOException;
import java.net.Socket;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.StatisticsHandler;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class GracefulStopTest
{
private Server server;
@Before
public void setup() throws Exception
{
server = new Server(0);
StatisticsHandler stats = new StatisticsHandler();
TestHandler test=new TestHandler();
server.setHandler(stats);
stats.setHandler(test);
server.setStopTimeout(10 * 1000);
server.start();
}
@Test
public void testGraceful() throws Exception
{
new Thread()
{
@Override
public void run()
{
try
{
TimeUnit.SECONDS.sleep(1);
server.stop();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}.start();
try(Socket socket = new Socket("localhost",server.getBean(NetworkConnector.class).getLocalPort());)
{
socket.getOutputStream().write("GET / HTTP/1.0\r\n\r\n".getBytes(StringUtil.__ISO_8859_1_CHARSET));
String out = IO.toString(socket.getInputStream());
Assert.assertThat(out,Matchers.containsString("200 OK"));
}
}
private static class TestHandler extends AbstractHandler
{
@Override
public void handle(final String s, final Request request, final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse)
throws IOException, ServletException
{
try
{
TimeUnit.SECONDS.sleep(2);
}
catch (InterruptedException e)
{
}
httpServletResponse.getWriter().write("OK");
httpServletResponse.setStatus(200);
request.setHandled(true);
}
}
}

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.server;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.io.FilterInputStream;
@ -26,6 +27,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.util.Arrays;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@ -35,6 +37,7 @@ 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.Assert;
import org.junit.Before;
import org.junit.Test;
@ -78,6 +81,26 @@ public class HttpOutputTest
assertThat(response,containsString("HTTP/1.1 200 OK"));
}
@Test
public void testSendArray() throws Exception
{
byte[] buffer=new byte[16*1024];
Arrays.fill(buffer,0,4*1024,(byte)0x99);
Arrays.fill(buffer,4*1024,12*1024,(byte)0x58);
Arrays.fill(buffer,12*1024,16*1024,(byte)0x66);
_handler._content=ByteBuffer.wrap(buffer);
_handler._content.limit(12*1024);
_handler._content.position(4*1024);
String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
assertThat(response,containsString("HTTP/1.1 200 OK"));
assertThat(response,containsString("\r\nXXXXXXXXXXXXXXXXXXXXXXXXXXX"));
for (int i=0;i<4*1024;i++)
assertEquals("i="+i,(byte)0x99,buffer[i]);
for (int i=12*1024;i<16*1024;i++)
assertEquals("i="+i,(byte)0x66,buffer[i]);
}
@Test
public void testSendInputStreamSimple() throws Exception
{
@ -195,6 +218,7 @@ public class HttpOutputTest
{
InputStream _contentInputStream;
ReadableByteChannel _contentChannel;
ByteBuffer _content;
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
@ -204,6 +228,17 @@ public class HttpOutputTest
HttpOutput out = (HttpOutput) response.getOutputStream();
if (_content!=null)
{
response.setContentLength(_content.remaining());
if (_content.hasArray())
out.write(_content.array(),_content.arrayOffset()+_content.position(),_content.remaining());
else
out.sendContent(_content);
_content=null;
return;
}
if (_contentInputStream!=null)
{
out.sendContent(_contentInputStream);

View File

@ -228,7 +228,6 @@ public class ContextHandlerGetResourceTest
assertEquals(docroot,new File(url.toURI()).getParentFile());
}
@Test
public void testTooNormal() throws Exception
{
@ -293,6 +292,23 @@ public class ContextHandlerGetResourceTest
URL url=context.getServletContext().getResource(path);
assertNull(url);
}
@Test
public void testSlashSlash() throws Exception
{
String path="//subdir/data.txt";
Resource resource=context.getResource(path);
assertNull(resource);
URL url=context.getServletContext().getResource(path);
assertNull(url);
path="/subdir//data.txt";
resource=context.getResource(path);
assertNull(resource);
url=context.getServletContext().getResource(path);
assertNull(url);
}
@Test
public void testAliasedFile() throws Exception

View File

@ -64,6 +64,7 @@ public class IPAccessHandlerTest
private String _host;
private String _uri;
private String _code;
private boolean _byPath;
@BeforeClass
public static void setUp()
@ -76,6 +77,7 @@ public class IPAccessHandlerTest
_handler = new IPAccessHandler();
_handler.setHandler(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
@ -95,13 +97,14 @@ public class IPAccessHandlerTest
}
/* ------------------------------------------------------------ */
public IPAccessHandlerTest(String white, String black, String host, String uri, String code)
public IPAccessHandlerTest(String white, String black, String host, String uri, String code, boolean byPath)
{
_white = white;
_black = black;
_host = host;
_uri = uri;
_code = code;
_byPath = byPath;
}
/* ------------------------------------------------------------ */
@ -111,6 +114,7 @@ public class IPAccessHandlerTest
{
_handler.setWhite(_white.split(";",-1));
_handler.setBlack(_black.split(";",-1));
_handler.setWhiteListByPath(_byPath);
String request = "GET " + _uri + " HTTP/1.1\n" + "Host: "+ _host + "\n\n";
Socket socket = new Socket("127.0.0.1", _connector.getLocalPort());
@ -124,7 +128,10 @@ public class IPAccessHandlerTest
output.flush();
Response response = readResponse(input);
assertEquals(_code, response.getCode());
Object[] params = new Object[]{
"Request WBHUC", _white, _black, _host, _uri, _code,
"Response", response.getCode()};
assertEquals(Arrays.deepToString(params), _code, response.getCode());
}
finally
{
@ -244,157 +251,313 @@ public class IPAccessHandlerTest
public static Collection<Object[]> data() {
Object[][] data = new Object[][] {
// Empty lists
{"", "", "127.0.0.1", "/", "200"},
{"", "", "127.0.0.1", "/dump/info", "200"},
{"", "", "127.0.0.1", "/", "200", false},
{"", "", "127.0.0.1", "/dump/info", "200", false},
// White list
{"127.0.0.1", "", "127.0.0.1", "/", "200"},
{"127.0.0.1", "", "127.0.0.1", "/dispatch", "200"},
{"127.0.0.1", "", "127.0.0.1", "/dump/info", "200"},
{"127.0.0.1", "", "127.0.0.1", "/", "200", false},
{"127.0.0.1", "", "127.0.0.1", "/dispatch", "200", false},
{"127.0.0.1", "", "127.0.0.1", "/dump/info", "200", false},
{"127.0.0.1|/", "", "127.0.0.1", "/", "200"},
{"127.0.0.1|/", "", "127.0.0.1", "/dispatch", "403"},
{"127.0.0.1|/", "", "127.0.0.1", "/dump/info", "403"},
{"127.0.0.1|/", "", "127.0.0.1", "/", "200", false},
{"127.0.0.1|/", "", "127.0.0.1", "/dispatch", "403", false},
{"127.0.0.1|/", "", "127.0.0.1", "/dump/info", "403", false},
{"127.0.0.1|/*", "", "127.0.0.1", "/", "200"},
{"127.0.0.1|/*", "", "127.0.0.1", "/dispatch", "200"},
{"127.0.0.1|/*", "", "127.0.0.1", "/dump/info", "200"},
{"127.0.0.1|/*", "", "127.0.0.1", "/", "200", false},
{"127.0.0.1|/*", "", "127.0.0.1", "/dispatch", "200", false},
{"127.0.0.1|/*", "", "127.0.0.1", "/dump/info", "200", false},
{"127.0.0.1|/dump/*", "", "127.0.0.1", "/", "403"},
{"127.0.0.1|/dump/*", "", "127.0.0.1", "/dispatch", "403"},
{"127.0.0.1|/dump/*", "", "127.0.0.1", "/dump/info", "200"},
{"127.0.0.1|/dump/*", "", "127.0.0.1", "/dump/test", "200"},
{"127.0.0.1|/dump/*", "", "127.0.0.1", "/", "403", false},
{"127.0.0.1|/dump/*", "", "127.0.0.1", "/dispatch", "403", false},
{"127.0.0.1|/dump/*", "", "127.0.0.1", "/dump/info", "200", false},
{"127.0.0.1|/dump/*", "", "127.0.0.1", "/dump/test", "200", false},
{"127.0.0.1|/dump/info", "", "127.0.0.1", "/", "403"},
{"127.0.0.1|/dump/info", "", "127.0.0.1", "/dispatch", "403"},
{"127.0.0.1|/dump/info", "", "127.0.0.1", "/dump/info", "200"},
{"127.0.0.1|/dump/info", "", "127.0.0.1", "/dump/test", "403"},
{"127.0.0.1|/dump/info", "", "127.0.0.1", "/", "403", false},
{"127.0.0.1|/dump/info", "", "127.0.0.1", "/dispatch", "403", false},
{"127.0.0.1|/dump/info", "", "127.0.0.1", "/dump/info", "200", false},
{"127.0.0.1|/dump/info", "", "127.0.0.1", "/dump/test", "403", false},
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/", "403"},
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dispatch", "403"},
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dump/info", "200"},
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dump/test", "200"},
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dump/fail", "403"},
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/", "403", false},
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dispatch", "403", false},
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dump/info", "200", false},
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dump/test", "200", false},
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dump/fail", "403", false},
{"127.0.0.0-2|", "", "127.0.0.1", "/", "200"},
{"127.0.0.0-2|", "", "127.0.0.1", "/dump/info", "200"},
{"127.0.0.0-2|", "", "127.0.0.1", "/", "200", false},
{"127.0.0.0-2|", "", "127.0.0.1", "/dump/info", "403", false},
{"127.0.0.0-2|/", "", "127.0.0.1", "/", "200"},
{"127.0.0.0-2|/", "", "127.0.0.1", "/dispatch", "403"},
{"127.0.0.0-2|/", "", "127.0.0.1", "/dump/info", "403"},
{"127.0.0.0-2|/", "", "127.0.0.1", "/", "200", false},
{"127.0.0.0-2|/", "", "127.0.0.1", "/dispatch", "403", false},
{"127.0.0.0-2|/", "", "127.0.0.1", "/dump/info", "403", false},
{"127.0.0.0-2|/dump/*", "", "127.0.0.1", "/", "403"},
{"127.0.0.0-2|/dump/*", "", "127.0.0.1", "/dispatch", "403"},
{"127.0.0.0-2|/dump/*", "", "127.0.0.1", "/dump/info", "200"},
{"127.0.0.0-2|/dump/*", "", "127.0.0.1", "/", "403", false},
{"127.0.0.0-2|/dump/*", "", "127.0.0.1", "/dispatch", "403", false},
{"127.0.0.0-2|/dump/*", "", "127.0.0.1", "/dump/info", "200", false},
{"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/", "403"},
{"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/dispatch", "403"},
{"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/dump/info", "200"},
{"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/dump/test", "403"},
{"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/", "403", false},
{"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/dispatch", "403", false},
{"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/dump/info", "200", false},
{"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/dump/test", "403", false},
{"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/", "403"},
{"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dispatch", "403"},
{"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dump/info", "200"},
{"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dump/test", "200"},
{"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dump/fail", "403"},
{"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/", "403", false},
{"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dispatch", "403", false},
{"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dump/info", "200", false},
{"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dump/test", "200", false},
{"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dump/fail", "403", false},
// Black list
{"", "127.0.0.1", "127.0.0.1", "/", "403"},
{"", "127.0.0.1", "127.0.0.1", "/dispatch", "403"},
{"", "127.0.0.1", "127.0.0.1", "/dump/info", "403"},
{"", "127.0.0.1", "127.0.0.1", "/", "403", false},
{"", "127.0.0.1", "127.0.0.1", "/dispatch", "403", false},
{"", "127.0.0.1", "127.0.0.1", "/dump/info", "403", false},
{"", "127.0.0.1|/", "127.0.0.1", "/", "403"},
{"", "127.0.0.1|/", "127.0.0.1", "/dispatch", "200"},
{"", "127.0.0.1|/", "127.0.0.1", "/dump/info", "200"},
{"", "127.0.0.1|/", "127.0.0.1", "/", "403", false},
{"", "127.0.0.1|/", "127.0.0.1", "/dispatch", "200", false},
{"", "127.0.0.1|/", "127.0.0.1", "/dump/info", "200", false},
{"", "127.0.0.1|/*", "127.0.0.1", "/", "403"},
{"", "127.0.0.1|/*", "127.0.0.1", "/dispatch", "403"},
{"", "127.0.0.1|/*", "127.0.0.1", "/dump/info", "403"},
{"", "127.0.0.1|/*", "127.0.0.1", "/", "403", false},
{"", "127.0.0.1|/*", "127.0.0.1", "/dispatch", "403", false},
{"", "127.0.0.1|/*", "127.0.0.1", "/dump/info", "403", false},
{"", "127.0.0.1|/dump/*", "127.0.0.1", "/", "200"},
{"", "127.0.0.1|/dump/*", "127.0.0.1", "/dispatch", "200"},
{"", "127.0.0.1|/dump/*", "127.0.0.1", "/dump/info", "403"},
{"", "127.0.0.1|/dump/*", "127.0.0.1", "/dump/test", "403"},
{"", "127.0.0.1|/dump/*", "127.0.0.1", "/", "200", false},
{"", "127.0.0.1|/dump/*", "127.0.0.1", "/dispatch", "200", false},
{"", "127.0.0.1|/dump/*", "127.0.0.1", "/dump/info", "403", false},
{"", "127.0.0.1|/dump/*", "127.0.0.1", "/dump/test", "403", false},
{"", "127.0.0.1|/dump/info", "127.0.0.1", "/", "200"},
{"", "127.0.0.1|/dump/info", "127.0.0.1", "/dispatch", "200"},
{"", "127.0.0.1|/dump/info", "127.0.0.1", "/dump/info", "403"},
{"", "127.0.0.1|/dump/info", "127.0.0.1", "/dump/test", "200"},
{"", "127.0.0.1|/dump/info", "127.0.0.1", "/", "200", false},
{"", "127.0.0.1|/dump/info", "127.0.0.1", "/dispatch", "200", false},
{"", "127.0.0.1|/dump/info", "127.0.0.1", "/dump/info", "403", false},
{"", "127.0.0.1|/dump/info", "127.0.0.1", "/dump/test", "200", false},
{"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/", "200"},
{"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dispatch", "200"},
{"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dump/info", "403"},
{"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dump/test", "403"},
{"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dump/fail", "200"},
{"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/", "200", false},
{"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dispatch", "200", false},
{"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dump/info", "403", false},
{"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dump/test", "403", false},
{"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dump/fail", "200", false},
{"", "127.0.0.0-2|", "127.0.0.1", "/", "403"},
{"", "127.0.0.0-2|", "127.0.0.1", "/dump/info", "403"},
{"", "127.0.0.0-2|", "127.0.0.1", "/", "403", false},
{"", "127.0.0.0-2|", "127.0.0.1", "/dump/info", "200", false},
{"", "127.0.0.0-2|/", "127.0.0.1", "/", "403"},
{"", "127.0.0.0-2|/", "127.0.0.1", "/dispatch", "200"},
{"", "127.0.0.0-2|/", "127.0.0.1", "/dump/info", "200"},
{"", "127.0.0.0-2|/", "127.0.0.1", "/", "403", false},
{"", "127.0.0.0-2|/", "127.0.0.1", "/dispatch", "200", false},
{"", "127.0.0.0-2|/", "127.0.0.1", "/dump/info", "200", false},
{"", "127.0.0.0-2|/dump/*", "127.0.0.1", "/", "200"},
{"", "127.0.0.0-2|/dump/*", "127.0.0.1", "/dispatch", "200"},
{"", "127.0.0.0-2|/dump/*", "127.0.0.1", "/dump/info", "403"},
{"", "127.0.0.0-2|/dump/*", "127.0.0.1", "/", "200", false},
{"", "127.0.0.0-2|/dump/*", "127.0.0.1", "/dispatch", "200", false},
{"", "127.0.0.0-2|/dump/*", "127.0.0.1", "/dump/info", "403", false},
{"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/", "200"},
{"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/dispatch", "200"},
{"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/dump/info", "403"},
{"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/dump/test", "200"},
{"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/", "200", false},
{"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/dispatch", "200", false},
{"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/dump/info", "403", false},
{"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/dump/test", "200", false},
{"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/", "200"},
{"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dispatch", "200"},
{"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dump/info", "403"},
{"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dump/test", "403"},
{"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dump/fail", "200"},
{"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/", "200", false},
{"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dispatch", "200", false},
{"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dump/info", "403", false},
{"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dump/test", "403", false},
{"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dump/fail", "200", false},
// Both lists
{"127.0.0.1|/dump", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump", "200"},
{"127.0.0.1|/dump", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/info", "403"},
{"127.0.0.1|/dump", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/fail", "403"},
{"127.0.0.1|/dump", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump", "200", false},
{"127.0.0.1|/dump", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/info", "403", false},
{"127.0.0.1|/dump", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/fail", "403", false},
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump", "200"},
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/info", "200"},
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/fail", "403"},
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump", "200", false},
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/info", "200", false},
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/fail", "403", false},
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump", "200"},
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump/info", "200"},
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump/test", "403"},
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump/fail", "403"},
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump", "200", false},
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump/info", "200", false},
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump/test", "403", false},
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump/fail", "403", false},
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump", "403"},
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump/info", "200"},
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump/test", "403"},
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump/fail", "403"},
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump", "403", false},
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump/info", "200", false},
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump/test", "403", false},
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump/fail", "403", false},
{"127.0.0.1|/;127.0.0.0-2|/dump/*", "127.0.0.0,1|/dump/fail", "127.0.0.1", "/", "200"},
{"127.0.0.1|/;127.0.0.0-2|/dump/*", "127.0.0.0,1|/dump/fail", "127.0.0.1", "/dump/info", "200"},
{"127.0.0.1|/;127.0.0.0-2|/dump/*", "127.0.0.0,1|/dump/fail", "127.0.0.1", "/dump/fail", "403"},
{"127.0.0.1|/;127.0.0.0-2|/dump/*", "127.0.0.0,1|/dump/fail", "127.0.0.1", "/", "200", false},
{"127.0.0.1|/;127.0.0.0-2|/dump/*", "127.0.0.0,1|/dump/fail", "127.0.0.1", "/dump/info", "200", false},
{"127.0.0.1|/;127.0.0.0-2|/dump/*", "127.0.0.0,1|/dump/fail", "127.0.0.1", "/dump/fail", "403", false},
// Different address
{"127.0.0.2", "", "127.0.0.1", "/", "403"},
{"127.0.0.2", "", "127.0.0.1", "/dump/info", "403"},
{"127.0.0.2", "", "127.0.0.1", "/", "403", false},
{"127.0.0.2", "", "127.0.0.1", "/dump/info", "403", false},
{"127.0.0.2|/dump/*", "", "127.0.0.1", "/", "403"},
{"127.0.0.2|/dump/*", "", "127.0.0.1", "/dump/info", "403"},
{"127.0.0.2|/dump/*", "", "127.0.0.1", "/", "403", false},
{"127.0.0.2|/dump/*", "", "127.0.0.1", "/dump/info", "403", false},
{"127.0.0.2|/dump/info", "", "127.0.0.1", "/", "403"},
{"127.0.0.2|/dump/info", "", "127.0.0.1", "/dump/info", "403"},
{"127.0.0.2|/dump/info", "", "127.0.0.1", "/dump/test", "403"},
{"127.0.0.2|/dump/info", "", "127.0.0.1", "/", "403", false},
{"127.0.0.2|/dump/info", "", "127.0.0.1", "/dump/info", "403", false},
{"127.0.0.2|/dump/info", "", "127.0.0.1", "/dump/test", "403", false},
{"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/", "403"},
{"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dispatch", "403"},
{"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dump/info", "200"},
{"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dump/test", "403"},
{"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dump/fail", "403"},
{"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/", "403", false},
{"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dispatch", "403", false},
{"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dump/info", "200", false},
{"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dump/test", "403", false},
{"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dump/fail", "403", false},
{"172.0.0.0-255", "", "127.0.0.1", "/", "403"},
{"172.0.0.0-255", "", "127.0.0.1", "/dump/info", "403"},
{"172.0.0.0-255", "", "127.0.0.1", "/", "403", false},
{"172.0.0.0-255", "", "127.0.0.1", "/dump/info", "403", false},
{"172.0.0.0-255|/dump/*;127.0.0.0-255|/dump/*", "", "127.0.0.1", "/", "403"},
{"172.0.0.0-255|/dump/*;127.0.0.0-255|/dump/*", "", "127.0.0.1", "/dispatch", "403"},
{"172.0.0.0-255|/dump/*;127.0.0.0-255|/dump/*", "", "127.0.0.1", "/dump/info", "200"},
{"172.0.0.0-255|/dump/*;127.0.0.0-255|/dump/*", "", "127.0.0.1", "/", "403", false},
{"172.0.0.0-255|/dump/*;127.0.0.0-255|/dump/*", "", "127.0.0.1", "/dispatch", "403", false},
{"172.0.0.0-255|/dump/*;127.0.0.0-255|/dump/*", "", "127.0.0.1", "/dump/info", "200", false},
/*-----------------------------------------------------------------------------------------*/
// Match by path starts with [117]
// test cases affected by _whiteListByPath highlighted accordingly
{"", "", "127.0.0.1", "/", "200", true},
{"", "", "127.0.0.1", "/dump/info", "200", true},
// White list
{"127.0.0.1", "", "127.0.0.1", "/", "200", true},
{"127.0.0.1", "", "127.0.0.1", "/dispatch", "200", true},
{"127.0.0.1", "", "127.0.0.1", "/dump/info", "200", true},
{"127.0.0.1|/", "", "127.0.0.1", "/", "200", true},
{"127.0.0.1|/", "", "127.0.0.1", "/dispatch", "200", true}, // _whiteListByPath
{"127.0.0.1|/", "", "127.0.0.1", "/dump/info", "200", true}, // _whiteListByPath
{"127.0.0.1|/*", "", "127.0.0.1", "/", "200", true},
{"127.0.0.1|/*", "", "127.0.0.1", "/dispatch", "200", true},
{"127.0.0.1|/*", "", "127.0.0.1", "/dump/info", "200", true},
{"127.0.0.1|/dump/*", "", "127.0.0.1", "/", "200", true}, // _whiteListByPath
{"127.0.0.1|/dump/*", "", "127.0.0.1", "/dispatch", "200", true}, // _whiteListByPath
{"127.0.0.1|/dump/*", "", "127.0.0.1", "/dump/info", "200", true},
{"127.0.0.1|/dump/*", "", "127.0.0.1", "/dump/test", "200", true},
{"127.0.0.1|/dump/info", "", "127.0.0.1", "/", "200", true}, // _whiteListByPath
{"127.0.0.1|/dump/info", "", "127.0.0.1", "/dispatch", "200", true}, // _whiteListByPath
{"127.0.0.1|/dump/info", "", "127.0.0.1", "/dump/info", "200", true},
{"127.0.0.1|/dump/info", "", "127.0.0.1", "/dump/test", "200", true}, // _whiteListByPath
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/", "200", true}, // _whiteListByPath
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dispatch", "200", true}, // _whiteListByPath
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dump/info", "200", true},
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dump/test", "200", true},
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "", "127.0.0.1", "/dump/fail", "200", true}, // _whiteListByPath
{"127.0.0.0-2|", "", "127.0.0.1", "/", "200", true},
{"127.0.0.0-2|", "", "127.0.0.1", "/dump/info", "200", true},
{"127.0.0.0-2|/", "", "127.0.0.1", "/", "200", true},
{"127.0.0.0-2|/", "", "127.0.0.1", "/dispatch", "200", true}, // _whiteListByPath
{"127.0.0.0-2|/", "", "127.0.0.1", "/dump/info", "200", true}, // _whiteListByPath
{"127.0.0.0-2|/dump/*", "", "127.0.0.1", "/", "200", true}, // _whiteListByPath
{"127.0.0.0-2|/dump/*", "", "127.0.0.1", "/dispatch", "200", true}, // _whiteListByPath
{"127.0.0.0-2|/dump/*", "", "127.0.0.1", "/dump/info", "200", true},
{"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/", "200", true}, // _whiteListByPath
{"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/dispatch", "200", true}, // _whiteListByPath
{"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/dump/info", "200", true},
{"127.0.0.0-2|/dump/info", "", "127.0.0.1", "/dump/test", "200", true}, // _whiteListByPath
{"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/", "200", true}, // _whiteListByPath
{"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dispatch", "200", true}, // _whiteListByPath
{"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dump/info", "200", true},
{"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dump/test", "200", true},
{"127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "", "127.0.0.1", "/dump/fail", "200", true}, // _whiteListByPath
// Black list
{"", "127.0.0.1", "127.0.0.1", "/", "403", true},
{"", "127.0.0.1", "127.0.0.1", "/dispatch", "403", true},
{"", "127.0.0.1", "127.0.0.1", "/dump/info", "403", true},
{"", "127.0.0.1|/", "127.0.0.1", "/", "403", true},
{"", "127.0.0.1|/", "127.0.0.1", "/dispatch", "200", true},
{"", "127.0.0.1|/", "127.0.0.1", "/dump/info", "200", true},
{"", "127.0.0.1|/*", "127.0.0.1", "/", "403", true},
{"", "127.0.0.1|/*", "127.0.0.1", "/dispatch", "403", true},
{"", "127.0.0.1|/*", "127.0.0.1", "/dump/info", "403", true},
{"", "127.0.0.1|/dump/*", "127.0.0.1", "/", "200", true},
{"", "127.0.0.1|/dump/*", "127.0.0.1", "/dispatch", "200", true},
{"", "127.0.0.1|/dump/*", "127.0.0.1", "/dump/info", "403", true},
{"", "127.0.0.1|/dump/*", "127.0.0.1", "/dump/test", "403", true},
{"", "127.0.0.1|/dump/info", "127.0.0.1", "/", "200", true},
{"", "127.0.0.1|/dump/info", "127.0.0.1", "/dispatch", "200", true},
{"", "127.0.0.1|/dump/info", "127.0.0.1", "/dump/info", "403", true},
{"", "127.0.0.1|/dump/info", "127.0.0.1", "/dump/test", "200", true},
{"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/", "200", true},
{"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dispatch", "200", true},
{"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dump/info", "403", true},
{"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dump/test", "403", true},
{"", "127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1", "/dump/fail", "200", true},
{"", "127.0.0.0-2|", "127.0.0.1", "/", "403", true},
{"", "127.0.0.0-2|", "127.0.0.1", "/dump/info", "200", true},
{"", "127.0.0.0-2|/", "127.0.0.1", "/", "403", true},
{"", "127.0.0.0-2|/", "127.0.0.1", "/dispatch", "200", true},
{"", "127.0.0.0-2|/", "127.0.0.1", "/dump/info", "200", true},
{"", "127.0.0.0-2|/dump/*", "127.0.0.1", "/", "200", true},
{"", "127.0.0.0-2|/dump/*", "127.0.0.1", "/dispatch", "200", true},
{"", "127.0.0.0-2|/dump/*", "127.0.0.1", "/dump/info", "403", true},
{"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/", "200", true},
{"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/dispatch", "200", true},
{"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/dump/info", "403", true},
{"", "127.0.0.0-2|/dump/info", "127.0.0.1", "/dump/test", "200", true},
{"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/", "200", true},
{"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dispatch", "200", true},
{"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dump/info", "403", true},
{"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dump/test", "403", true},
{"", "127.0.0.0-2|/dump/info;127.0.0.0-2|/dump/test", "127.0.0.1", "/dump/fail", "200", true},
// Both lists
{"127.0.0.1|/dump", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump", "200", true},
{"127.0.0.1|/dump", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/info", "200", true}, // _whiteListByPath
{"127.0.0.1|/dump", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/fail", "403", true},
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump", "200", true},
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/info", "200", true},
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/fail", "127.0.0.1", "/dump/fail", "403", true},
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump", "200", true},
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump/info", "200", true},
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump/test", "403", true},
{"127.0.0.1|/dump/*", "127.0.0.1|/dump/test;127.0.0.1|/dump/fail", "127.0.0.1", "/dump/fail", "403", true},
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump", "200", true}, // _whiteListByPath
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump/info", "200", true},
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump/test", "403", true},
{"127.0.0.1|/dump/info;127.0.0.1|/dump/test", "127.0.0.1|/dump/test", "127.0.0.1", "/dump/fail", "200", true}, // _whiteListByPath
{"127.0.0.1|/;127.0.0.0-2|/dump/*", "127.0.0.0,1|/dump/fail", "127.0.0.1", "/", "200", true},
{"127.0.0.1|/;127.0.0.0-2|/dump/*", "127.0.0.0,1|/dump/fail", "127.0.0.1", "/dump/info", "200", true},
{"127.0.0.1|/;127.0.0.0-2|/dump/*", "127.0.0.0,1|/dump/fail", "127.0.0.1", "/dump/fail", "403", true},
// Different address
{"127.0.0.2", "", "127.0.0.1", "/", "403", true},
{"127.0.0.2", "", "127.0.0.1", "/dump/info", "403", true},
{"127.0.0.2|/dump/*", "", "127.0.0.1", "/", "200", true}, // _whiteListByPath
{"127.0.0.2|/dump/*", "", "127.0.0.1", "/dump/info", "403", true},
{"127.0.0.2|/dump/info", "", "127.0.0.1", "/", "200", true}, // _whiteListByPath
{"127.0.0.2|/dump/info", "", "127.0.0.1", "/dump/info", "403", true},
{"127.0.0.2|/dump/info", "", "127.0.0.1", "/dump/test", "200", true}, // _whiteListByPath
{"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/", "200", true}, // _whiteListByPath
{"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dispatch", "200", true}, // _whiteListByPath
{"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dump/info", "200", true},
{"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dump/test", "403", true},
{"127.0.0.1|/dump/info;127.0.0.2|/dump/test", "", "127.0.0.1", "/dump/fail", "200", true}, // _whiteListByPath
{"172.0.0.0-255", "", "127.0.0.1", "/", "403", true},
{"172.0.0.0-255", "", "127.0.0.1", "/dump/info", "403", true},
{"172.0.0.0-255|/dump/*;127.0.0.0-255|/dump/*", "", "127.0.0.1", "/", "200", true}, // _whiteListByPath
{"172.0.0.0-255|/dump/*;127.0.0.0-255|/dump/*", "", "127.0.0.1", "/dispatch", "200", true}, // _whiteListByPath
{"172.0.0.0-255|/dump/*;127.0.0.0-255|/dump/*", "", "127.0.0.1", "/dump/info", "200", true},
};
return Arrays.asList(data);
};

View File

@ -354,6 +354,12 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
if (_resourceBase!=null)
{
r = _resourceBase.addPath(pathInContext);
if (!_contextHandler.checkAlias(pathInContext,r))
r=null;
}
else if (_servletContext instanceof ContextHandler.Context)
{
r = _contextHandler.getResource(pathInContext);
}
else
{

View File

@ -18,24 +18,18 @@
package org.eclipse.jetty.servlet;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.server.Dispatcher;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */
/** Error Page Error Handler
@ -44,11 +38,8 @@ import org.eclipse.jetty.util.log.Logger;
* the internal ERROR style of dispatch.
*
*/
public class ErrorPageErrorHandler extends ErrorHandler
public class ErrorPageErrorHandler extends ErrorHandler implements ErrorHandler.ErrorPageMapper
{
private static final Logger LOG = Log.getLogger(ErrorPageErrorHandler.class);
public final static String ERROR_PAGE="org.eclipse.jetty.server.error_page";
public final static String GLOBAL_ERROR_PAGE = "org.eclipse.jetty.server.error_page.global";
protected ServletContext _servletContext;
@ -59,104 +50,63 @@ public class ErrorPageErrorHandler extends ErrorHandler
public ErrorPageErrorHandler()
{}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.server.handler.ErrorHandler#handle(String, Request, HttpServletRequest, HttpServletResponse)
*/
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
public String getErrorPage(HttpServletRequest request)
{
String method = request.getMethod();
if (!HttpMethod.GET.is(method) && !HttpMethod.POST.is(method) && !HttpMethod.HEAD.is(method))
String error_page= null;
Class<?> exClass= (Class<?>)request.getAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE);
if (ServletException.class.equals(exClass))
{
baseRequest.setHandled(true);
return;
error_page= (String)_errorPages.get(exClass.getName());
if (error_page == null)
{
Throwable th= (Throwable)request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
while (th instanceof ServletException)
th= ((ServletException)th).getRootCause();
if (th != null)
exClass= th.getClass();
}
}
if (_errorPages!=null)
while (error_page == null && exClass != null )
{
String error_page= null;
Class<?> exClass= (Class<?>)request.getAttribute(Dispatcher.ERROR_EXCEPTION_TYPE);
error_page= (String)_errorPages.get(exClass.getName());
exClass= exClass.getSuperclass();
}
if (ServletException.class.equals(exClass))
if (error_page == null)
{
// look for an exact code match
Integer code=(Integer)request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if (code!=null)
{
error_page= (String)_errorPages.get(exClass.getName());
if (error_page == null)
error_page= (String)_errorPages.get(Integer.toString(code));
// if still not found
if ((error_page == null) && (_errorPageList != null))
{
Throwable th= (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
while (th instanceof ServletException)
th= ((ServletException)th).getRootCause();
if (th != null)
exClass= th.getClass();
}
}
while (error_page == null && exClass != null )
{
error_page= (String)_errorPages.get(exClass.getName());
exClass= exClass.getSuperclass();
}
if (error_page == null)
{
// look for an exact code match
Integer code=(Integer)request.getAttribute(Dispatcher.ERROR_STATUS_CODE);
if (code!=null)
{
error_page= (String)_errorPages.get(Integer.toString(code));
// if still not found
if ((error_page == null) && (_errorPageList != null))
// look for an error code range match.
for (int i = 0; i < _errorPageList.size(); i++)
{
// look for an error code range match.
for (int i = 0; i < _errorPageList.size(); i++)
ErrorCodeRange errCode = (ErrorCodeRange) _errorPageList.get(i);
if (errCode.isInRange(code))
{
ErrorCodeRange errCode = (ErrorCodeRange) _errorPageList.get(i);
if (errCode.isInRange(code))
{
error_page = errCode.getUri();
break;
}
error_page = errCode.getUri();
break;
}
}
}
}
//try new servlet 3.0 global error page
if (error_page == null)
{
error_page = _errorPages.get(GLOBAL_ERROR_PAGE);
}
if (error_page!=null)
{
String old_error_page=(String)request.getAttribute(ERROR_PAGE);
if (old_error_page==null || !old_error_page.equals(error_page))
{
request.setAttribute(ERROR_PAGE, error_page);
Dispatcher dispatcher = (Dispatcher) _servletContext.getRequestDispatcher(error_page);
try
{
if(dispatcher!=null)
{
dispatcher.error(request, response);
return;
}
else
{
LOG.warn("No error page "+error_page);
}
}
catch (ServletException e)
{
LOG.warn(Log.EXCEPTION, e);
return;
}
}
}
}
super.handle(target, baseRequest, request, response);
//try new servlet 3.0 global error page
if (error_page == null)
{
error_page = _errorPages.get(GLOBAL_ERROR_PAGE);
}
return error_page;
}
/* ------------------------------------------------------------ */

View File

@ -216,6 +216,8 @@ public class Holder<T> extends AbstractLifeCycle implements Dumpable
{
_className = className;
_class=null;
if (_name==null)
_name=className+"-"+Integer.toHexString(this.hashCode());
}
/* ------------------------------------------------------------ */
@ -229,7 +231,7 @@ public class Holder<T> extends AbstractLifeCycle implements Dumpable
{
_className=held.getName();
if (_name==null)
_name=held.getName()+"-"+this.hashCode();
_name=held.getName()+"-"+Integer.toHexString(this.hashCode());
}
}

View File

@ -569,10 +569,10 @@ public class ServletHandler extends ScopedHandler
LOG.debug(request.toString());
}
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,th.getClass());
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,th);
if (!response.isCommitted())
{
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,th.getClass());
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,th);
if (th instanceof UnavailableException)
{
UnavailableException ue = (UnavailableException)th;
@ -586,6 +586,10 @@ public class ServletHandler extends ScopedHandler
}
else
LOG.debug("Response already committed for handling "+th);
// Complete async requests
if (request.isAsyncStarted())
request.getAsyncContext().complete();
}
catch(Error e)
{
@ -596,15 +600,16 @@ public class ServletHandler extends ScopedHandler
LOG.warn("Error for "+request.getRequestURI(),e);
if(LOG.isDebugEnabled())LOG.debug(request.toString());
// TODO httpResponse.getHttpConnection().forceClose();
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,e.getClass());
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,e);
if (!response.isCommitted())
{
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,e.getClass());
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,e);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
else
LOG.debug("Response already committed for handling ",e);
// Complete async requests
if (request.isAsyncStarted())
request.getAsyncContext().complete();
}
finally
{
@ -843,7 +848,6 @@ public class ServletHandler extends ScopedHandler
public ServletHolder addServletWithMapping (String className,String pathSpec)
{
ServletHolder holder = newServletHolder(Holder.Source.EMBEDDED);
holder.setName(className+"-"+(_servlets==null?0:_servlets.length));
holder.setClassName(className);
addServletWithMapping(holder,pathSpec);
return holder;
@ -956,7 +960,6 @@ public class ServletHandler extends ScopedHandler
public FilterHolder addFilterWithMapping (String className,String pathSpec,EnumSet<DispatcherType> dispatches)
{
FilterHolder holder = newFilterHolder(Holder.Source.EMBEDDED);
holder.setName(className+"-"+_filters.length);
holder.setClassName(className);
addFilterWithMapping(holder,pathSpec,dispatches);
@ -1025,7 +1028,6 @@ public class ServletHandler extends ScopedHandler
public FilterHolder addFilterWithMapping (String className,String pathSpec,int dispatches)
{
FilterHolder holder = newFilterHolder(Holder.Source.EMBEDDED);
holder.setName(className+"-"+_filters.length);
holder.setClassName(className);
addFilterWithMapping(holder,pathSpec,dispatches);

View File

@ -199,8 +199,6 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
int c=(_className!=null && sh._className!=null)?_className.compareTo(sh._className):0;
if (c==0)
c=_name.compareTo(sh._name);
if (c==0)
c=this.hashCode()>sh.hashCode()?1:-1;
return c;
}

View File

@ -28,7 +28,10 @@ import java.io.IOException;
import java.io.StringReader;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.DispatcherType;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
@ -38,6 +41,7 @@ import javax.servlet.http.HttpServletResponseWrapper;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.QuietServletException;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.DefaultHandler;
@ -65,16 +69,25 @@ public class AsyncContextTest
_server = new Server();
_contextHandler = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
_connector = new LocalConnector(_server);
_connector.setIdleTimeout(30000);
_connector.setIdleTimeout(5000);
_server.setConnectors(new Connector[]
{ _connector });
_contextHandler.setContextPath("/");
_contextHandler.setContextPath("/ctx");
_contextHandler.addServlet(new ServletHolder(new TestServlet()),"/servletPath");
_contextHandler.addServlet(new ServletHolder(new TestServlet()),"/path with spaces/servletPath");
_contextHandler.addServlet(new ServletHolder(new TestServlet2()),"/servletPath2");
_contextHandler.addServlet(new ServletHolder(new TestStartThrowServlet()),"/startthrow/*");
_contextHandler.addServlet(new ServletHolder(new ForwardingServlet()),"/forward");
_contextHandler.addServlet(new ServletHolder(new AsyncDispatchingServlet()),"/dispatchingServlet");
_contextHandler.addServlet(new ServletHolder(new ExpireServlet()),"/expire/*");
_contextHandler.addServlet(new ServletHolder(new BadExpireServlet()),"/badexpire/*");
_contextHandler.addServlet(new ServletHolder(new ErrorServlet()),"/error/*");
ErrorPageErrorHandler error_handler = new ErrorPageErrorHandler();
_contextHandler.setErrorHandler(error_handler);
error_handler.addErrorPage(500,"/error/500");
error_handler.addErrorPage(IOException.class.getName(),"/error/IOE");
HandlerList handlers = new HandlerList();
handlers.setHandlers(new Handler[]
@ -93,7 +106,7 @@ public class AsyncContextTest
@Test
public void testSimpleAsyncContext() throws Exception
{
String request = "GET /servletPath HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n"
String request = "GET /ctx/servletPath HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n"
+ "Connection: close\r\n" + "\r\n";
String responseString = _connector.getResponses(request);
@ -106,10 +119,29 @@ public class AsyncContextTest
}
@Test
public void testStartThrow() throws Exception
{
String request = "GET /ctx/startthrow HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n"
+ "Connection: close\r\n" + "\r\n";
String responseString = _connector.getResponses(request);
BufferedReader br = new BufferedReader(new StringReader(responseString));
assertEquals("HTTP/1.1 500 Server Error",br.readLine());
br.readLine();// connection close
br.readLine();// server
br.readLine();// empty
Assert.assertEquals("error servlet","ERROR: /error",br.readLine());
Assert.assertEquals("error servlet","PathInfo= /IOE",br.readLine());
Assert.assertEquals("error servlet","EXCEPTION: java.io.IOException: Test",br.readLine());
}
@Test
public void testDispatchAsyncContext() throws Exception
{
String request = "GET /servletPath?dispatch=true HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n"
String request = "GET /ctx/servletPath?dispatch=true HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n"
+ "Connection: close\r\n" + "\r\n";
String responseString = _connector.getResponses(request);
@ -120,8 +152,8 @@ public class AsyncContextTest
Assert.assertEquals("servlet path attr is original","async:run:attr:servletPath:/servletPath",br.readLine());
Assert.assertEquals("path info attr is correct","async:run:attr:pathInfo:null",br.readLine());
Assert.assertEquals("query string attr is correct","async:run:attr:queryString:dispatch=true",br.readLine());
Assert.assertEquals("context path attr is correct","async:run:attr:contextPath:",br.readLine());
Assert.assertEquals("request uri attr is correct","async:run:attr:requestURI:/servletPath",br.readLine());
Assert.assertEquals("context path attr is correct","async:run:attr:contextPath:/ctx",br.readLine());
Assert.assertEquals("request uri attr is correct","async:run:attr:requestURI:/ctx/servletPath",br.readLine());
try
{
@ -137,7 +169,7 @@ public class AsyncContextTest
@Test
public void testDispatchAsyncContextEncodedPathAndQueryString() throws Exception
{
String request = "GET /path%20with%20spaces/servletPath?dispatch=true&queryStringWithEncoding=space%20space HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n"
String request = "GET /ctx/path%20with%20spaces/servletPath?dispatch=true&queryStringWithEncoding=space%20space HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n"
+ "Connection: close\r\n" + "\r\n";
String responseString = _connector.getResponses(request);
@ -148,16 +180,14 @@ public class AsyncContextTest
assertThat("servlet path attr is original",br.readLine(),equalTo("async:run:attr:servletPath:/path with spaces/servletPath"));
assertThat("path info attr is correct",br.readLine(),equalTo("async:run:attr:pathInfo:null"));
assertThat("query string attr is correct",br.readLine(),equalTo("async:run:attr:queryString:dispatch=true&queryStringWithEncoding=space%20space"));
assertThat("context path attr is correct",br.readLine(),equalTo("async:run:attr:contextPath:"));
assertThat("request uri attr is correct",br.readLine(),equalTo("async:run:attr:requestURI:/path%20with%20spaces/servletPath"));
assertThat("context path attr is correct",br.readLine(),equalTo("async:run:attr:contextPath:/ctx"));
assertThat("request uri attr is correct",br.readLine(),equalTo("async:run:attr:requestURI:/ctx/path%20with%20spaces/servletPath"));
}
@Test
public void testSimpleWithContextAsyncContext() throws Exception
{
_contextHandler.setContextPath("/foo");
String request = "GET /foo/servletPath HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n"
String request = "GET /ctx/servletPath HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n"
+ "Connection: close\r\n" + "\r\n";
String responseString = _connector.getResponses(request);
@ -172,9 +202,7 @@ public class AsyncContextTest
@Test
public void testDispatchWithContextAsyncContext() throws Exception
{
_contextHandler.setContextPath("/foo");
String request = "GET /foo/servletPath?dispatch=true HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n"
String request = "GET /ctx/servletPath?dispatch=true HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n"
+ "Connection: close\r\n" + "\r\n";
String responseString = _connector.getResponses(request);
@ -186,14 +214,14 @@ public class AsyncContextTest
Assert.assertEquals("servlet path attr is original","async:run:attr:servletPath:/servletPath",br.readLine());
Assert.assertEquals("path info attr is correct","async:run:attr:pathInfo:null",br.readLine());
Assert.assertEquals("query string attr is correct","async:run:attr:queryString:dispatch=true",br.readLine());
Assert.assertEquals("context path attr is correct","async:run:attr:contextPath:/foo",br.readLine());
Assert.assertEquals("request uri attr is correct","async:run:attr:requestURI:/foo/servletPath",br.readLine());
Assert.assertEquals("context path attr is correct","async:run:attr:contextPath:/ctx",br.readLine());
Assert.assertEquals("request uri attr is correct","async:run:attr:requestURI:/ctx/servletPath",br.readLine());
}
@Test
public void testDispatch() throws Exception
{
String request = "GET /forward HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Connection: close\r\n"
String request = "GET /ctx/forward HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Connection: close\r\n"
+ "\r\n";
String responseString = _connector.getResponses(request);
@ -204,7 +232,7 @@ public class AsyncContextTest
@Test
public void testDispatchRequestResponse() throws Exception
{
String request = "GET /forward?dispatchRequestResponse=true HTTP/1.1\r\n" +
String request = "GET /ctx/forward?dispatchRequestResponse=true HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Content-Type: application/x-www-form-urlencoded\r\n" +
"Connection: close\r\n" +
@ -282,6 +310,49 @@ public class AsyncContextTest
}
}
@Test
public void testExpire() throws Exception
{
String request = "GET /ctx/expire HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Content-Type: application/x-www-form-urlencoded\r\n" +
"Connection: close\r\n" +
"\r\n";
String responseString = _connector.getResponses(request);
BufferedReader br = new BufferedReader(new StringReader(responseString));
assertEquals("HTTP/1.1 500 Async Timeout",br.readLine());
br.readLine();// connection close
br.readLine();// server
br.readLine();// empty
Assert.assertEquals("error servlet","ERROR: /error",br.readLine());
}
@Test
public void testBadExpire() throws Exception
{
String request = "GET /ctx/badexpire HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Content-Type: application/x-www-form-urlencoded\r\n" +
"Connection: close\r\n" +
"\r\n";
String responseString = _connector.getResponses(request);
BufferedReader br = new BufferedReader(new StringReader(responseString));
assertEquals("HTTP/1.1 500 Async Exception",br.readLine());
br.readLine();// connection close
br.readLine();// server
br.readLine();// empty
Assert.assertEquals("error servlet","ERROR: /error",br.readLine());
Assert.assertEquals("error servlet","PathInfo= /500",br.readLine());
Assert.assertEquals("error servlet","EXCEPTION: java.io.IOException: TEST",br.readLine());
}
private class DispatchingRunnable implements Runnable
{
private AsyncContext asyncContext;
@ -308,6 +379,74 @@ public class AsyncContextTest
_server.join();
}
private class ErrorServlet extends HttpServlet
{
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
response.getOutputStream().print("ERROR: " + request.getServletPath() + "\n");
response.getOutputStream().print("PathInfo= " + request.getPathInfo() + "\n");
if (request.getAttribute(RequestDispatcher.ERROR_EXCEPTION)!=null)
response.getOutputStream().print("EXCEPTION: " + request.getAttribute(RequestDispatcher.ERROR_EXCEPTION) + "\n");
}
}
private class ExpireServlet extends HttpServlet
{
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
if (request.getDispatcherType()==DispatcherType.REQUEST)
{
AsyncContext asyncContext = request.startAsync();
asyncContext.setTimeout(100);
}
}
}
private class BadExpireServlet extends HttpServlet
{
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
if (request.getDispatcherType()==DispatcherType.REQUEST)
{
AsyncContext asyncContext = request.startAsync();
asyncContext.addListener(new AsyncListener()
{
@Override
public void onTimeout(AsyncEvent event) throws IOException
{
throw new IOException("TEST");
}
@Override
public void onStartAsync(AsyncEvent event) throws IOException
{
}
@Override
public void onError(AsyncEvent event) throws IOException
{
}
@Override
public void onComplete(AsyncEvent event) throws IOException
{
}
});
asyncContext.setTimeout(100);
}
}
}
private class TestServlet extends HttpServlet
{
private static final long serialVersionUID = 1L;
@ -347,6 +486,21 @@ public class AsyncContextTest
asyncContext.start(new AsyncRunnable(asyncContext));
}
}
private class TestStartThrowServlet extends HttpServlet
{
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
if (request.getDispatcherType()==DispatcherType.REQUEST)
{
request.startAsync(request, response);
throw new QuietServletException(new IOException("Test"));
}
}
}
private class AsyncRunnable implements Runnable
{

View File

@ -23,6 +23,7 @@ import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.util.EnumSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -39,6 +40,7 @@ import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.OS;
@ -414,6 +416,40 @@ public class DefaultServletTest
}
}
@Test
public void testResourceBase() throws Exception
{
testdir.ensureEmpty();
File resBase = testdir.getFile("docroot");
FS.ensureDirExists(resBase);
File foobar = new File(resBase, "foobar.txt");
File link = new File(resBase, "link.txt");
createFile(foobar, "Foo Bar");
String resBasePath = resBase.getAbsolutePath();
ServletHolder defholder = context.addServlet(DefaultServlet.class, "/");
defholder.setInitParameter("resourceBase", resBasePath);
defholder.setInitParameter("gzip", "false");
String response;
response = connector.getResponses("GET /context/foobar.txt HTTP/1.0\r\n\r\n");
assertResponseContains("Foo Bar", response);
if (!OS.IS_WINDOWS)
{
Files.createSymbolicLink(link.toPath(),foobar.toPath());
response = connector.getResponses("GET /context/link.txt HTTP/1.0\r\n\r\n");
assertResponseContains("404", response);
context.addAliasCheck(new ContextHandler.ApproveAliases());
response = connector.getResponses("GET /context/link.txt HTTP/1.0\r\n\r\n");
assertResponseContains("Foo Bar", response);
}
}
@Test
public void testWelcomeExactServlet() throws Exception
{

View File

@ -91,7 +91,7 @@ public class ErrorPageTest
assertThat(response,Matchers.containsString("ERROR_CODE: 599"));
assertThat(response,Matchers.containsString("ERROR_EXCEPTION: null"));
assertThat(response,Matchers.containsString("ERROR_EXCEPTION_TYPE: null"));
assertThat(response,Matchers.containsString("ERROR_SERVLET: org.eclipse.jetty.servlet.ErrorPageTest$FailServlet-1"));
assertThat(response,Matchers.containsString("ERROR_SERVLET: org.eclipse.jetty.servlet.ErrorPageTest$FailServlet-"));
assertThat(response,Matchers.containsString("ERROR_REQUEST_URI: /fail/code"));
}
@ -104,7 +104,7 @@ public class ErrorPageTest
assertThat(response,Matchers.containsString("ERROR_CODE: 500"));
assertThat(response,Matchers.containsString("ERROR_EXCEPTION: java.lang.IllegalStateException"));
assertThat(response,Matchers.containsString("ERROR_EXCEPTION_TYPE: class java.lang.IllegalStateException"));
assertThat(response,Matchers.containsString("ERROR_SERVLET: org.eclipse.jetty.servlet.ErrorPageTest$FailServlet-1"));
assertThat(response,Matchers.containsString("ERROR_SERVLET: org.eclipse.jetty.servlet.ErrorPageTest$FailServlet-"));
assertThat(response,Matchers.containsString("ERROR_REQUEST_URI: /fail/exception"));
}
@ -117,7 +117,7 @@ public class ErrorPageTest
assertThat(response,Matchers.containsString("ERROR_CODE: 598"));
assertThat(response,Matchers.containsString("ERROR_EXCEPTION: null"));
assertThat(response,Matchers.containsString("ERROR_EXCEPTION_TYPE: null"));
assertThat(response,Matchers.containsString("ERROR_SERVLET: org.eclipse.jetty.servlet.ErrorPageTest$FailServlet-1"));
assertThat(response,Matchers.containsString("ERROR_SERVLET: org.eclipse.jetty.servlet.ErrorPageTest$FailServlet-"));
assertThat(response,Matchers.containsString("ERROR_REQUEST_URI: /fail/global"));
}
@ -130,7 +130,7 @@ public class ErrorPageTest
assertThat(response,Matchers.containsString("ERROR_CODE: 500"));
assertThat(response,Matchers.containsString("ERROR_EXCEPTION: java.lang.NumberFormatException: For input string: \"NAN\""));
assertThat(response,Matchers.containsString("ERROR_EXCEPTION_TYPE: class java.lang.NumberFormatException"));
assertThat(response,Matchers.containsString("ERROR_SERVLET: org.eclipse.jetty.servlet.ErrorPageTest$FailServlet-1"));
assertThat(response,Matchers.containsString("ERROR_SERVLET: org.eclipse.jetty.servlet.ErrorPageTest$FailServlet-"));
assertThat(response,Matchers.containsString("ERROR_REQUEST_URI: /fail/global"));
}

View File

@ -37,8 +37,10 @@ import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Ignore;
import org.junit.Test;
@Ignore("Reliance on external server fails test")
public class SSLExternalServerTest extends AbstractHTTPSPDYTest
{
public SSLExternalServerTest(short version)

View File

@ -20,7 +20,6 @@ package org.eclipse.jetty.spdy.server;
import java.io.IOException;
import java.util.List;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
@ -63,33 +62,58 @@ public class NextProtoNegoServerConnection extends AbstractConnection implements
@Override
public void onFillable()
{
while (true)
{
int filled = fill();
if (filled == 0 && nextProtocol == null)
fillInterested();
if (filled <= 0 || nextProtocol != null)
break;
}
int filled = fill();
if (nextProtocol == null && engine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING)
if (filled == 0)
{
// The client sent the NPN extension, but did not send the NextProtocol
// message with the chosen protocol so we need to close
LOG.debug("{} missing next protocol. SSLEngine: {}", this, engine);
if (nextProtocol == null)
{
if (engine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING)
{
// Here the SSL handshake is finished, but while the client sent
// the NPN extension, the server application did not select the
// protocol; we need to close as the protocol cannot be negotiated.
LOG.debug("{} missing next protocol. SSLEngine: {}", this, engine);
close();
}
else
{
// Here the SSL handshake is not finished yet but we filled 0 bytes,
// so we need to read more.
fillInterested();
}
}
else
{
ConnectionFactory connectionFactory = connector.getConnectionFactory(nextProtocol);
if (connectionFactory == null)
{
LOG.debug("{} application selected protocol '{}', but no correspondent {} has been configured",
this, nextProtocol, ConnectionFactory.class.getName());
close();
}
else
{
EndPoint endPoint = getEndPoint();
Connection oldConnection = endPoint.getConnection();
Connection newConnection = connectionFactory.newConnection(connector, endPoint);
LOG.debug("{} switching from {} to {}", this, oldConnection, newConnection);
oldConnection.onClose();
endPoint.setConnection(newConnection);
getEndPoint().getConnection().onOpen();
}
}
}
else if (filled < 0)
{
// Something went bad, we need to close.
LOG.debug("{} closing on client close", this);
close();
}
if (nextProtocol != null)
else
{
ConnectionFactory connectionFactory = connector.getConnectionFactory(nextProtocol);
EndPoint endPoint = getEndPoint();
Connection oldConnection = endPoint.getConnection();
oldConnection.onClose();
Connection connection = connectionFactory.newConnection(connector, endPoint);
LOG.debug("{} switching from {} to {}", this, oldConnection, connection);
endPoint.setConnection(connection);
getEndPoint().getConnection().onOpen();
// Must never happen, since we fill using an empty buffer
throw new IllegalStateException();
}
}
@ -102,8 +126,7 @@ public class NextProtoNegoServerConnection extends AbstractConnection implements
catch (IOException x)
{
LOG.debug(x);
NextProtoNego.remove(engine);
getEndPoint().close();
close();
return -1;
}
}
@ -127,4 +150,13 @@ public class NextProtoNegoServerConnection extends AbstractConnection implements
nextProtocol = protocol != null ? protocol : defaultProtocol;
NextProtoNego.remove(engine);
}
@Override
public void close()
{
NextProtoNego.remove(engine);
EndPoint endPoint = getEndPoint();
endPoint.shutdownOutput();
endPoint.close();
}
}

View File

@ -19,14 +19,22 @@
package org.eclipse.jetty.spdy.server;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.List;
import java.util.concurrent.Executor;
import javax.net.ssl.SSLEngine;
import org.eclipse.jetty.npn.NextProtoNego;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
import org.eclipse.jetty.spdy.client.SPDYClient;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class SSLSynReplyTest extends SynReplyTest
{
@ -51,4 +59,119 @@ public class SSLSynReplyTest extends SynReplyTest
{
NextProtoNego.debug = true;
}
@Test
public void testGentleCloseDuringHandshake() throws Exception
{
InetSocketAddress address = startServer(version, null);
SslContextFactory sslContextFactory = newSslContextFactory();
sslContextFactory.start();
SSLEngine sslEngine = sslContextFactory.newSSLEngine(address);
sslEngine.setUseClientMode(true);
NextProtoNego.put(sslEngine, new NextProtoNego.ClientProvider()
{
@Override
public boolean supports()
{
return true;
}
@Override
public void unsupported()
{
}
@Override
public String selectProtocol(List<String> protocols)
{
return null;
}
});
sslEngine.beginHandshake();
ByteBuffer encrypted = ByteBuffer.allocate(sslEngine.getSession().getPacketBufferSize());
sslEngine.wrap(BufferUtil.EMPTY_BUFFER, encrypted);
encrypted.flip();
try (SocketChannel channel = SocketChannel.open(address))
{
// Send ClientHello, immediately followed by TLS Close Alert and then by FIN
channel.write(encrypted);
sslEngine.closeOutbound();
encrypted.clear();
sslEngine.wrap(BufferUtil.EMPTY_BUFFER, encrypted);
encrypted.flip();
channel.write(encrypted);
channel.shutdownOutput();
// Read ServerHello from server
encrypted.clear();
int read = channel.read(encrypted);
encrypted.flip();
Assert.assertTrue(read > 0);
// Cannot decrypt, as the SSLEngine has been already closed
// Now if we read more, we should either read the TLS Close Alert, or directly -1
encrypted.clear();
read = channel.read(encrypted);
// Sending a TLS Close Alert during handshake results in an exception when
// unwrapping that the server react to by closing the connection abruptly.
Assert.assertTrue(read < 0);
}
}
@Test
public void testAbruptCloseDuringHandshake() throws Exception
{
InetSocketAddress address = startServer(version, null);
SslContextFactory sslContextFactory = newSslContextFactory();
sslContextFactory.start();
SSLEngine sslEngine = sslContextFactory.newSSLEngine(address);
sslEngine.setUseClientMode(true);
NextProtoNego.put(sslEngine, new NextProtoNego.ClientProvider()
{
@Override
public boolean supports()
{
return true;
}
@Override
public void unsupported()
{
}
@Override
public String selectProtocol(List<String> protocols)
{
return null;
}
});
sslEngine.beginHandshake();
ByteBuffer encrypted = ByteBuffer.allocate(sslEngine.getSession().getPacketBufferSize());
sslEngine.wrap(BufferUtil.EMPTY_BUFFER, encrypted);
encrypted.flip();
try (SocketChannel channel = SocketChannel.open(address))
{
// Send ClientHello, immediately followed by FIN (no TLS Close Alert)
channel.write(encrypted);
channel.shutdownOutput();
// Read ServerHello from server
encrypted.clear();
int read = channel.read(encrypted);
encrypted.flip();
Assert.assertTrue(read > 0);
ByteBuffer decrypted = ByteBuffer.allocate(sslEngine.getSession().getApplicationBufferSize());
sslEngine.unwrap(encrypted, decrypted);
// Now if we read more, we should either read the TLS Close Alert, or directly -1
encrypted.clear();
read = channel.read(encrypted);
// Since we have close the connection abruptly, the server also does so
Assert.assertTrue(read < 0);
}
}
}

View File

@ -31,7 +31,7 @@ Command Line Options:
property set and the stop command must have the same property.
--daemon Start in daemon mode with stderr and stdout
redirected to ${jetty.log}/start.log
redirected to ${jetty.logs}/start.log
--config=<file> Specify an alternate start.config file.
The default is the start.config file inside

View File

@ -29,20 +29,33 @@ import java.util.Set;
/**
* <p>A container for name/value pairs, known as fields.</p>
* <p>A {@link Field} is composed of a case-insensitive name string and
* <p>A {@link Field} is composed of a name string that can be case-sensitive
* or case-insensitive (by specifying the option at the constructor) and
* of a case-sensitive set of value strings.</p>
* <p>The implementation of this class is not thread safe.</p>
*/
public class Fields implements Iterable<Fields.Field>
{
private final boolean caseSensitive;
private final Map<String, Field> fields;
/**
* <p>Creates an empty modifiable {@link Fields} instance.</p>
* <p>Creates an empty, modifiable, case insensitive {@link Fields} instance.</p>
* @see #Fields(Fields, boolean)
*/
public Fields()
{
this(false);
}
/**
* <p>Creates an empty, modifiable, case insensitive {@link Fields} instance.</p>
* @param caseSensitive whether this {@link Fields} instance must be case sensitive
* @see #Fields(Fields, boolean)
*/
public Fields(boolean caseSensitive)
{
this.caseSensitive = caseSensitive;
fields = new LinkedHashMap<>();
}
@ -55,6 +68,7 @@ public class Fields implements Iterable<Fields.Field>
*/
public Fields(Fields original, boolean immutable)
{
this.caseSensitive = original.caseSensitive;
Map<String, Field> copy = new LinkedHashMap<>();
copy.putAll(original.fields);
fields = immutable ? Collections.unmodifiableMap(copy) : copy;
@ -68,7 +82,18 @@ public class Fields implements Iterable<Fields.Field>
if (obj == null || getClass() != obj.getClass())
return false;
Fields that = (Fields)obj;
return fields.equals(that.fields);
if (size() != that.size())
return false;
if (caseSensitive != that.caseSensitive)
return false;
for (Map.Entry<String, Field> entry : fields.entrySet())
{
String name = entry.getKey();
Field value = entry.getValue();
if (!value.equals(that.get(name), caseSensitive))
return false;
}
return true;
}
@Override
@ -88,13 +113,18 @@ public class Fields implements Iterable<Fields.Field>
return result;
}
private String normalizeName(String name)
{
return caseSensitive ? name : name.toLowerCase(Locale.ENGLISH);
}
/**
* @param name the field name
* @return the {@link Field} with the given name, or null if no such field exists
*/
public Field get(String name)
{
return fields.get(name.trim().toLowerCase(Locale.ENGLISH));
return fields.get(normalizeName(name));
}
/**
@ -105,10 +135,9 @@ public class Fields implements Iterable<Fields.Field>
*/
public void put(String name, String value)
{
name = name.trim();
// Preserve the case for the field name
Field field = new Field(name, value);
fields.put(name.toLowerCase(Locale.ENGLISH), field);
fields.put(normalizeName(name), field);
}
/**
@ -119,7 +148,7 @@ public class Fields implements Iterable<Fields.Field>
public void put(Field field)
{
if (field != null)
fields.put(field.name().toLowerCase(Locale.ENGLISH), field);
fields.put(normalizeName(field.name()), field);
}
/**
@ -131,17 +160,18 @@ public class Fields implements Iterable<Fields.Field>
*/
public void add(String name, String value)
{
name = name.trim();
Field field = fields.get(name.toLowerCase(Locale.ENGLISH));
String key = normalizeName(name);
Field field = fields.get(key);
if (field == null)
{
// Preserve the case for the field name
field = new Field(name, value);
fields.put(name.toLowerCase(Locale.ENGLISH), field);
fields.put(key, field);
}
else
{
field = new Field(field.name(), field.values(), value);
fields.put(name.toLowerCase(Locale.ENGLISH), field);
fields.put(key, field);
}
}
@ -153,8 +183,7 @@ public class Fields implements Iterable<Fields.Field>
*/
public Field remove(String name)
{
name = name.trim();
return fields.remove(name.toLowerCase(Locale.ENGLISH));
return fields.remove(normalizeName(name));
}
/**
@ -219,6 +248,17 @@ public class Fields implements Iterable<Fields.Field>
System.arraycopy(moreValues, 0, this.values, values.length, moreValues.length);
}
public boolean equals(Field that, boolean caseSensitive)
{
if (this == that)
return true;
if (that == null)
return false;
if (caseSensitive)
return equals(that);
return name.equalsIgnoreCase(that.name) && Arrays.equals(values, that.values);
}
@Override
public boolean equals(Object obj)
{
@ -227,15 +267,13 @@ public class Fields implements Iterable<Fields.Field>
if (obj == null || getClass() != obj.getClass())
return false;
Field that = (Field)obj;
// Field names must be lowercase, thus we lowercase them before transmission, but keep them as is
// internally. That's why we've to compare them case insensitive.
return name.equalsIgnoreCase(that.name) && Arrays.equals(values, that.values);
return name.equals(that.name) && Arrays.equals(values, that.values);
}
@Override
public int hashCode()
{
int result = name.toLowerCase(Locale.ENGLISH).hashCode();
int result = name.hashCode();
result = 31 * result + Arrays.hashCode(values);
return result;
}

View File

@ -35,12 +35,12 @@ public class MultiPartOutputStream extends FilterOutputStream
private static final byte[] __CRLF={'\r','\n'};
private static final byte[] __DASHDASH={'-','-'};
public static String MULTIPART_MIXED="multipart/mixed";
public static String MULTIPART_X_MIXED_REPLACE="multipart/x-mixed-replace";
public static final String MULTIPART_MIXED="multipart/mixed";
public static final String MULTIPART_X_MIXED_REPLACE="multipart/x-mixed-replace";
/* ------------------------------------------------------------ */
private String boundary;
private byte[] boundaryBytes;
private final String boundary;
private final byte[] boundaryBytes;
/* ------------------------------------------------------------ */
private boolean inPart=false;
@ -54,8 +54,15 @@ public class MultiPartOutputStream extends FilterOutputStream
boundary = "jetty"+System.identityHashCode(this)+
Long.toString(System.currentTimeMillis(),36);
boundaryBytes=boundary.getBytes(StringUtil.__ISO_8859_1);
}
inPart=false;
public MultiPartOutputStream(OutputStream out, String boundary)
throws IOException
{
super(out);
this.boundary = boundary;
boundaryBytes=boundary.getBytes(StringUtil.__ISO_8859_1);
}
/* ------------------------------------------------------------ */
@ -66,14 +73,20 @@ public class MultiPartOutputStream extends FilterOutputStream
public void close()
throws IOException
{
if (inPart)
try
{
if (inPart)
out.write(__CRLF);
out.write(__DASHDASH);
out.write(boundaryBytes);
out.write(__DASHDASH);
out.write(__CRLF);
out.write(__DASHDASH);
out.write(boundaryBytes);
out.write(__DASHDASH);
out.write(__CRLF);
inPart=false;
super.close();
inPart=false;
}
finally
{
super.close();
}
}
/* ------------------------------------------------------------ */

View File

@ -108,8 +108,28 @@ public class FileResource extends Resource
{
File file=new File(uri);
_file=file;
URI file_uri=_file.toURI();
_uri=normalizeURI(_file,uri);
_alias=checkAlias(_file);
if (!_uri.equals(file_uri) && !_uri.toString().equals(file_uri.toString()))
{
// URI and File URI are different. Is it just an encoding difference?
if (!file_uri.toString().equals(URIUtil.decodePath(uri.toString())))
{
try
{
_alias=_file.toURI().toURL();
}
catch (MalformedURLException e)
{
throw new IllegalArgumentException(e);
}
}
else
_alias=checkAlias(_file);
}
else
_alias=checkAlias(_file);
}
/* -------------------------------------------------------- */
@ -394,7 +414,7 @@ public class FileResource extends Resource
{
try
{
return _file.toURI().toURL();
return new URL(_uri);
}
catch (MalformedURLException e)
{

View File

@ -55,37 +55,31 @@ public class CounterStatistic
/**
* @param delta the amount to add to the count
*/
public void add(final long delta)
public long add(final long delta)
{
long value=_curr.addAndGet(delta);
if (delta > 0)
{
_total.addAndGet(delta);
Atomics.updateMax(_max,value);
}
/* ------------------------------------------------------------ */
/**
* @param delta the amount to subtract the count by.
*/
public void subtract(final long delta)
{
add(-delta);
Atomics.updateMax(_max,value);
}
return value;
}
/* ------------------------------------------------------------ */
/**
*/
public void increment()
public long increment()
{
add(1);
return add(1);
}
/* ------------------------------------------------------------ */
/**
*/
public void decrement()
public long decrement()
{
add(-1);
return add(-1);
}
/* ------------------------------------------------------------ */

View File

@ -27,6 +27,7 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import org.eclipse.jetty.toolchain.test.OS;
import org.eclipse.jetty.toolchain.test.TestingDir;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.UrlEncoded;
@ -34,6 +35,7 @@ import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import static org.junit.Assume.assumeTrue;
public class FileResourceTest
{
@ -53,6 +55,19 @@ public class FileResourceTest
String decoded = UrlEncoded.decodeString(raw,0,raw.length(),StringUtil.__UTF8_CHARSET);
return new URL(decoded);
}
@Test
public void testSemicolon() throws Exception
{
assumeTrue(!OS.IS_WINDOWS);
createDummyFile("foo;");
try(Resource base = new FileResource(testdir.getDir());)
{
Resource res = base.addPath("foo;");
Assert.assertNull(res.getAlias());
}
}
@Test
public void testExist_Normal() throws Exception
@ -60,8 +75,10 @@ public class FileResourceTest
createDummyFile("a.jsp");
URI ref = testdir.getDir().toURI().resolve("a.jsp");
FileResource fileres = new FileResource(decode(ref.toURL()));
Assert.assertThat("FileResource: " + fileres,fileres.exists(),is(true));
try(FileResource fileres = new FileResource(decode(ref.toURL()));)
{
Assert.assertThat("FileResource: " + fileres,fileres.exists(),is(true));
}
}
@Ignore("Cannot get null to be seen by FileResource")
@ -70,12 +87,17 @@ public class FileResourceTest
{
createDummyFile("a.jsp");
try {
try
{
// request with null at end
URI ref = testdir.getDir().toURI().resolve("a.jsp%00");
FileResource fileres = new FileResource(decode(ref.toURL()));
Assert.assertThat("FileResource: " + fileres,fileres.exists(),is(false));
} catch(URISyntaxException e) {
try(FileResource fileres = new FileResource(decode(ref.toURL()));)
{
Assert.assertThat("FileResource: " + fileres,fileres.exists(),is(false));
}
}
catch(URISyntaxException e)
{
// Valid path
}
}
@ -86,12 +108,17 @@ public class FileResourceTest
{
createDummyFile("a.jsp");
try {
try
{
// request with null and x at end
URI ref = testdir.getDir().toURI().resolve("a.jsp%00x");
FileResource fileres = new FileResource(decode(ref.toURL()));
Assert.assertThat("FileResource: " + fileres,fileres.exists(),is(false));
} catch(URISyntaxException e) {
try(FileResource fileres = new FileResource(decode(ref.toURL()));)
{
Assert.assertThat("FileResource: " + fileres,fileres.exists(),is(false));
}
}
catch(URISyntaxException e)
{
// Valid path
}
}

View File

@ -483,10 +483,10 @@ public class ResourceTest
assumeTrue(OS.IS_WINDOWS);
String path = __userURL.toURI().getPath().replace('/','\\')+"resource.txt";
System.err.println(path);
//System.err.println(path);
Resource resource = Resource.newResource(path, false);
System.err.println(resource);
//System.err.println(resource);
assertTrue(resource.exists());
/*

View File

@ -181,6 +181,16 @@ public class UpgradeRequest
{
return Collections.unmodifiableMap(parameters);
}
public String getProtocolVersion()
{
String version = getHeader("Sec-WebSocket-Version");
if (version == null)
{
return "13"; // Default
}
return version;
}
public String getQueryString()
{

View File

@ -163,7 +163,7 @@ public class UpgradeResponse
/**
* Set the list of extensions that are approved for use with this websocket.
* <p>
* This is Advanced usage of the {@link WebSocketCreator} to allow for a custom set of negotiated extensions.
* This is Advanced usage of the WebSocketCreator to allow for a custom set of negotiated extensions.
* <p>
* Notes:
* <ul>

View File

@ -209,11 +209,11 @@ public class UpgradeConnection extends AbstractConnection
{
EndPoint endp = getEndPoint();
Executor executor = getExecutor();
WebSocketClientConnection connection = new WebSocketClientConnection(endp,executor,connectPromise);
// Initialize / Negotiate Extensions
EventDriver websocket = connectPromise.getDriver();
WebSocketPolicy policy = connectPromise.getClient().getPolicy();
WebSocketPolicy policy = websocket.getPolicy();
WebSocketClientConnection connection = new WebSocketClientConnection(endp,executor,connectPromise,policy);
WebSocketSession session = new WebSocketSession(request.getRequestURI(),websocket,connection);
session.setPolicy(policy);

View File

@ -27,6 +27,7 @@ import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.ProtocolException;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
@ -45,9 +46,9 @@ public class WebSocketClientConnection extends AbstractWebSocketConnection
private final Masker masker;
private final AtomicBoolean opened = new AtomicBoolean(false);
public WebSocketClientConnection(EndPoint endp, Executor executor, ConnectPromise connectPromise)
public WebSocketClientConnection(EndPoint endp, Executor executor, ConnectPromise connectPromise, WebSocketPolicy policy)
{
super(endp,executor,connectPromise.getClient().getScheduler(),connectPromise.getClient().getPolicy(),connectPromise.getClient().getBufferPool());
super(endp,executor,connectPromise.getClient().getScheduler(),policy,connectPromise.getClient().getBufferPool());
this.connectPromise = connectPromise;
this.masker = connectPromise.getMasker();
assert (this.masker != null);

View File

@ -0,0 +1,102 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.client;
import static org.hamcrest.Matchers.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.toolchain.test.EventQueue;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.junit.Assert;
@WebSocket(maxMessageSize = 100*1024)
public class MaxMessageSocket
{
private static final Logger LOG = Log.getLogger(MaxMessageSocket.class);
private Session session;
public CountDownLatch openLatch = new CountDownLatch(1);
public CountDownLatch closeLatch = new CountDownLatch(1);
public CountDownLatch dataLatch = new CountDownLatch(1);
public EventQueue<String> messageQueue = new EventQueue<>();
public EventQueue<Throwable> errorQueue = new EventQueue<>();
public int closeCode = -1;
public StringBuilder closeMessage = new StringBuilder();
@OnWebSocketConnect
public void onConnect(Session session)
{
this.session = session;
openLatch.countDown();
}
@OnWebSocketClose
public void onClose(int statusCode, String reason)
{
LOG.debug("onWebSocketClose({},{})",statusCode,reason);
closeCode = statusCode;
closeMessage.append(reason);
closeLatch.countDown();
}
@OnWebSocketMessage
public void onMessage(String message)
{
LOG.debug("onWebSocketText({})",message);
messageQueue.offer(message);
dataLatch.countDown();
}
@OnWebSocketError
public void onError(Throwable cause)
{
LOG.debug("onWebSocketError",cause);
Assert.assertThat("Error capture",errorQueue.offer(cause),is(true));
}
public Session getSession()
{
return this.session;
}
public void awaitConnect(int duration, TimeUnit unit) throws InterruptedException
{
Assert.assertThat("Client Socket connected",openLatch.await(duration,unit),is(true));
}
public void waitForMessage(int timeoutDuration, TimeUnit timeoutUnit) throws InterruptedException
{
LOG.debug("Waiting for message");
Assert.assertThat("Message Received",dataLatch.await(timeoutDuration,timeoutUnit),is(true));
}
public void assertMessage(String expected)
{
String actual = messageQueue.poll();
Assert.assertEquals("Message",expected,actual);
}
}

View File

@ -22,11 +22,13 @@ import static org.hamcrest.Matchers.*;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.toolchain.test.AdvancedRunner;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.UpgradeRequest;
import org.eclipse.jetty.websocket.client.blockhead.BlockheadServer;
@ -42,14 +44,6 @@ import org.junit.runner.RunWith;
public class WebSocketClientTest
{
private BlockheadServer server;
private WebSocketClient client;
@Before
public void startClient() throws Exception
{
client = new WebSocketClient();
client.start();
}
@Before
public void startServer() throws Exception
@ -58,12 +52,6 @@ public class WebSocketClientTest
server.start();
}
@After
public void stopClient() throws Exception
{
client.stop();
}
@After
public void stopServer() throws Exception
{
@ -73,79 +61,106 @@ public class WebSocketClientTest
@Test(expected = IllegalArgumentException.class)
public void testAddExtension_NotInstalled() throws Exception
{
TrackingSocket cliSock = new TrackingSocket();
WebSocketClient client = new WebSocketClient();
client.start();
try
{
TrackingSocket cliSock = new TrackingSocket();
client.getPolicy().setIdleTimeout(10000);
client.getPolicy().setIdleTimeout(10000);
URI wsUri = server.getWsUri();
ClientUpgradeRequest request = new ClientUpgradeRequest();
request.setSubProtocols("echo");
request.addExtensions("x-bad");
URI wsUri = server.getWsUri();
ClientUpgradeRequest request = new ClientUpgradeRequest();
request.setSubProtocols("echo");
request.addExtensions("x-bad");
// Should trigger failure on bad extension
client.connect(cliSock,wsUri,request);
// Should trigger failure on bad extension
client.connect(cliSock,wsUri,request);
}
finally
{
client.stop();
}
}
@Test
public void testBasicEcho_FromClient() throws Exception
{
TrackingSocket cliSock = new TrackingSocket();
WebSocketClient client = new WebSocketClient();
client.start();
try
{
TrackingSocket cliSock = new TrackingSocket();
client.getPolicy().setIdleTimeout(10000);
client.getPolicy().setIdleTimeout(10000);
URI wsUri = server.getWsUri();
ClientUpgradeRequest request = new ClientUpgradeRequest();
request.setSubProtocols("echo");
Future<Session> future = client.connect(cliSock,wsUri,request);
URI wsUri = server.getWsUri();
ClientUpgradeRequest request = new ClientUpgradeRequest();
request.setSubProtocols("echo");
Future<Session> future = client.connect(cliSock,wsUri,request);
final ServerConnection srvSock = server.accept();
srvSock.upgrade();
final ServerConnection srvSock = server.accept();
srvSock.upgrade();
Session sess = future.get(500,TimeUnit.MILLISECONDS);
Assert.assertThat("Session",sess,notNullValue());
Assert.assertThat("Session.open",sess.isOpen(),is(true));
Assert.assertThat("Session.upgradeRequest",sess.getUpgradeRequest(),notNullValue());
Assert.assertThat("Session.upgradeResponse",sess.getUpgradeResponse(),notNullValue());
Session sess = future.get(500,TimeUnit.MILLISECONDS);
Assert.assertThat("Session",sess,notNullValue());
Assert.assertThat("Session.open",sess.isOpen(),is(true));
Assert.assertThat("Session.upgradeRequest",sess.getUpgradeRequest(),notNullValue());
Assert.assertThat("Session.upgradeResponse",sess.getUpgradeResponse(),notNullValue());
cliSock.assertWasOpened();
cliSock.assertNotClosed();
cliSock.assertWasOpened();
cliSock.assertNotClosed();
Assert.assertThat("client.connectionManager.sessions.size",client.getConnectionManager().getSessions().size(),is(1));
Assert.assertThat("client.connectionManager.sessions.size",client.getConnectionManager().getSessions().size(),is(1));
cliSock.getSession().getRemote().sendStringByFuture("Hello World!");
srvSock.echoMessage(1,TimeUnit.MILLISECONDS,500);
// wait for response from server
cliSock.waitForMessage(500,TimeUnit.MILLISECONDS);
cliSock.getSession().getRemote().sendStringByFuture("Hello World!");
srvSock.echoMessage(1,TimeUnit.MILLISECONDS,500);
// wait for response from server
cliSock.waitForMessage(500,TimeUnit.MILLISECONDS);
cliSock.assertMessage("Hello World!");
cliSock.assertMessage("Hello World!");
}
finally
{
client.stop();
}
}
@Test
public void testBasicEcho_FromServer() throws Exception
{
TrackingSocket wsocket = new TrackingSocket();
Future<Session> future = client.connect(wsocket,server.getWsUri());
WebSocketClient client = new WebSocketClient();
client.start();
try
{
TrackingSocket wsocket = new TrackingSocket();
Future<Session> future = client.connect(wsocket,server.getWsUri());
// Server
final ServerConnection srvSock = server.accept();
srvSock.upgrade();
// Server
final ServerConnection srvSock = server.accept();
srvSock.upgrade();
// Validate connect
Session sess = future.get(500,TimeUnit.MILLISECONDS);
Assert.assertThat("Session",sess,notNullValue());
Assert.assertThat("Session.open",sess.isOpen(),is(true));
Assert.assertThat("Session.upgradeRequest",sess.getUpgradeRequest(),notNullValue());
Assert.assertThat("Session.upgradeResponse",sess.getUpgradeResponse(),notNullValue());
// Validate connect
Session sess = future.get(500,TimeUnit.MILLISECONDS);
Assert.assertThat("Session",sess,notNullValue());
Assert.assertThat("Session.open",sess.isOpen(),is(true));
Assert.assertThat("Session.upgradeRequest",sess.getUpgradeRequest(),notNullValue());
Assert.assertThat("Session.upgradeResponse",sess.getUpgradeResponse(),notNullValue());
// Have server send initial message
srvSock.write(WebSocketFrame.text("Hello World"));
// Have server send initial message
srvSock.write(WebSocketFrame.text("Hello World"));
// Verify connect
future.get(500,TimeUnit.MILLISECONDS);
wsocket.assertWasOpened();
wsocket.awaitMessage(1,TimeUnit.SECONDS,2);
// Verify connect
future.get(500,TimeUnit.MILLISECONDS);
wsocket.assertWasOpened();
wsocket.awaitMessage(1,TimeUnit.SECONDS,2);
wsocket.assertMessage("Hello World");
wsocket.assertMessage("Hello World");
}
finally
{
client.stop();
}
}
@Test
@ -158,7 +173,7 @@ public class WebSocketClientTest
TrackingSocket wsocket = new TrackingSocket();
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(wsocket,wsUri);
Future<Session> future = fact.connect(wsocket,wsUri);
ServerConnection ssocket = server.accept();
ssocket.upgrade();
@ -198,7 +213,7 @@ public class WebSocketClientTest
TrackingSocket wsocket = new TrackingSocket();
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(wsocket,wsUri);
Future<Session> future = factSmall.connect(wsocket,wsUri);
ServerConnection ssocket = server.accept();
ssocket.upgrade();
@ -226,6 +241,48 @@ public class WebSocketClientTest
}
}
@Test
public void testMaxMessageSize() throws Exception
{
WebSocketClient client = new WebSocketClient();
client.start();
try
{
MaxMessageSocket wsocket = new MaxMessageSocket();
URI wsUri = server.getWsUri();
Future<Session> future = client.connect(wsocket,wsUri);
ServerConnection ssocket = server.accept();
ssocket.upgrade();
wsocket.awaitConnect(1,TimeUnit.SECONDS);
Session sess = future.get(500,TimeUnit.MILLISECONDS);
Assert.assertThat("Session",sess,notNullValue());
Assert.assertThat("Session.open",sess.isOpen(),is(true));
// Create string that is larger than default size of 64k
// but smaller than maxMessageSize of 100k
byte buf[] = new byte[80*1024];
Arrays.fill(buf,(byte)'x');
String msg = StringUtil.toUTF8String(buf,0,buf.length);
wsocket.getSession().getRemote().sendStringByFuture(msg);
ssocket.echoMessage(1,TimeUnit.MILLISECONDS,500);
// wait for response from server
wsocket.waitForMessage(500,TimeUnit.MILLISECONDS);
wsocket.assertMessage(msg);
Assert.assertTrue(wsocket.dataLatch.await(1000,TimeUnit.SECONDS));
}
finally
{
client.stop();
}
}
@Test
public void testParameterMap() throws Exception
{
@ -236,7 +293,7 @@ public class WebSocketClientTest
TrackingSocket wsocket = new TrackingSocket();
URI wsUri = server.getWsUri().resolve("/test?snack=cashews&amount=handful&brand=off");
Future<Session> future = client.connect(wsocket,wsUri);
Future<Session> future = fact.connect(wsocket,wsUri);
ServerConnection ssocket = server.accept();
ssocket.upgrade();

View File

@ -101,6 +101,7 @@ public class BlockheadServer
this.socket = socket;
this.incomingFrames = new IncomingFramesCapture();
this.policy = WebSocketPolicy.newServerPolicy();
this.policy.setMaxMessageSize(100000);
this.bufferPool = new MappedByteBufferPool(BUFFER_SIZE);
this.parser = new Parser(policy,bufferPool);
this.parseCount = new AtomicInteger(0);

View File

@ -361,8 +361,6 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc
/**
* Open/Activate the session
*
* @throws IOException
*/
public void open()
{
@ -423,6 +421,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc
public void setUpgradeRequest(UpgradeRequest request)
{
this.upgradeRequest = request;
this.protocolVersion = request.getProtocolVersion();
}
public void setUpgradeResponse(UpgradeResponse response)

View File

@ -22,12 +22,16 @@ import static org.hamcrest.Matchers.*;
import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.UpgradeRequest;
import org.eclipse.jetty.websocket.api.UpgradeResponse;
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.OpCode;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
import org.eclipse.jetty.websocket.server.examples.echo.BigEchoSocket;
@ -87,7 +91,7 @@ public class AnnotatedMaxMessageSizeTest
}
@Test
public void testEcho() throws IOException, Exception
public void testEchoGood() throws IOException, Exception
{
BlockheadClient client = new BlockheadClient(serverUri);
try
@ -111,4 +115,33 @@ public class AnnotatedMaxMessageSizeTest
client.close();
}
}
@Test
public void testEchoTooBig() throws IOException, Exception
{
BlockheadClient client = new BlockheadClient(serverUri);
try
{
client.setProtocols("echo");
client.connect();
client.sendStandardRequest();
client.expectUpgradeResponse();
// Generate text frame
byte buf[] = new byte[90*1024]; // buffer bigger than maxMessageSize
Arrays.fill(buf,(byte)'x');
client.write(WebSocketFrame.text().setPayload(buf));
// Read frame (hopefully close frame saying its too large)
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500);
WebSocketFrame tf = capture.getFrames().poll();
Assert.assertThat("Frame is close", tf.getOpCode(), is(OpCode.CLOSE));
CloseInfo close = new CloseInfo(tf);
Assert.assertThat("Close Code", close.getStatusCode(), is(StatusCode.MESSAGE_TOO_LARGE));
}
finally
{
client.close();
}
}
}

14
pom.xml
View File

@ -741,7 +741,7 @@
</plugins>
</build>
</profile>
<profile>
<profile>
<id>7u9</id>
<activation>
<property>
@ -849,6 +849,18 @@
<npn.version>1.1.6.v20130911</npn.version>
</properties>
</profile>
<profile>
<id>7u45</id>
<activation>
<property>
<name>java.version</name>
<value>1.7.0_45</value>
</property>
</activation>
<properties>
<npn.version>1.1.6.v20130911</npn.version>
</properties>
</profile>
</profiles>
</project>

View File

@ -29,6 +29,11 @@ detected.
<Set name="defaultsDescriptor"><Property name="jetty.home" default="."/>/etc/webdefault.xml</Set>
<Set name="overrideDescriptor"><Property name="jetty.webapps" default="."/>/test.d/override-web.xml</Set>
<!-- Enable symlinks
<Call name="addAliasCheck">
<Arg><New class="org.eclipse.jetty.server.handler.AllowSymLinkAliasChecker"/></Arg>
</Call>
-->
<!-- virtual hosts
<Set name="virtualHosts">

View File

@ -668,6 +668,11 @@ public class Dump extends HttpServlet
try{pout.write("<td>"+getServletContext().getResourcePaths(res)+"</td>");}
catch(Exception e) {pout.write("<td>"+"" +e+"</td>");}
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getServletContext().getRealPath(...):&nbsp;</th>");
try{pout.write("<td>"+getServletContext().getRealPath(res)+"</td>");}
catch(Exception e) {pout.write("<td>"+"" +e+"</td>");}
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getServletContext().getContext(...):&nbsp;</th>");
@ -690,8 +695,12 @@ public class Dump extends HttpServlet
if (cp==null || "/".equals(cp))
cp="";
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getServletContext().getContext(...),getRequestDispatcher(...):&nbsp;</th>");
pout.write("<td>"+getServletContext().getContext(res).getRequestDispatcher(res.substring(cp.length()))+"</td>");
pout.write("<th align=\"right\">getServletContext().getContext(...).getRequestDispatcher(...):&nbsp;</th>");
pout.write("<td>"+context.getRequestDispatcher(res.substring(cp.length()))+"</td>");
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getServletContext().getContext(...).getRealPath(...):&nbsp;</th>");
pout.write("<td>"+context.getRealPath(res.substring(cp.length()))+"</td>");
}
pout.write("</tr><tr>\n");