Merge branch 'jetty-7' into release-7

This commit is contained in:
Jesse McConnell 2013-03-12 08:34:31 -05:00
commit d5328b0932
70 changed files with 2286 additions and 981 deletions

View File

@ -25,12 +25,13 @@ import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import org.eclipse.jetty.util.Loader;
@ -54,7 +55,7 @@ public class AnnotationParser
{
private static final Logger LOG = Log.getLogger(AnnotationParser.class);
protected List<String> _parsedClassNames = new ArrayList<String>();
protected Set<String> _parsedClassNames = new HashSet<String>();
protected Map<String, List<DiscoverableAnnotationHandler>> _annotationHandlers = new HashMap<String, List<DiscoverableAnnotationHandler>>();
protected List<ClassHandler> _classHandlers = new ArrayList<ClassHandler>();
protected List<MethodHandler> _methodHandlers = new ArrayList<MethodHandler>();

View File

@ -0,0 +1,64 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.client;
import java.io.IOException;
import java.net.Socket;
import org.eclipse.jetty.http.HttpSchemes;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Test;
public class ExternalSiteTest
{
@Test
public void testExternalSSLSite() throws Exception
{
HttpClient client = new HttpClient(new SslContextFactory());
client.start();
String host = "api-3t.paypal.com";
int port = 443;
// Verify that we have connectivity
try
{
new Socket(host, port).close();
}
catch (IOException x)
{
Assume.assumeNoException(x);
}
ContentExchange exchange = new ContentExchange(true);
exchange.setScheme(HttpSchemes.HTTPS_BUFFER);
exchange.setAddress(new Address(host, port));
exchange.setRequestURI("/nvp");
client.send(exchange);
int done = exchange.waitForDone();
Assert.assertEquals(HttpExchange.STATUS_COMPLETED, done);
Assert.assertEquals(HttpStatus.OK_200, exchange.getResponseStatus());
Assert.assertNotNull(exchange.getResponseContentBytes());
client.stop();
}
}

View File

@ -18,8 +18,6 @@
package org.eclipse.jetty.client;
import static org.hamcrest.Matchers.*;
import java.io.BufferedReader;
import java.io.EOFException;
import java.io.File;
@ -39,7 +37,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSocket;
@ -72,6 +69,11 @@ import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.lessThan;
import static org.hamcrest.Matchers.not;
public class SslBytesServerTest extends SslBytesTest
{
private final AtomicInteger sslHandles = new AtomicInteger();
@ -847,7 +849,9 @@ public class SslBytesServerTest extends SslBytesTest
// Close the raw socket, this generates a truncation attack
proxy.flushToServer((TLSRecord)null);
// Expect raw close from server
// Expect alert + raw close from server
record = proxy.readFromServer();
Assert.assertEquals(TLSRecord.Type.ALERT, record.getType());
record = proxy.readFromServer();
Assert.assertNull(String.valueOf(record), record);
proxy.flushToClient(record);

View File

@ -537,7 +537,7 @@ case "$ACTION" in
;;
check)
check|status)
echo "Checking arguments to Jetty: "
echo "JETTY_HOME = $JETTY_HOME"
echo "JETTY_CONF = $JETTY_CONF"

View File

@ -1017,7 +1017,7 @@ public class HttpGenerator extends AbstractGenerator
{
if (_needCRLF)
{
if (_buffer == null && _header.space() >= 2)
if (_buffer == null && _header != null && _header.space() >= 2)
{
_header.put(HttpTokens.CRLF);
_needCRLF = false;
@ -1031,7 +1031,7 @@ public class HttpGenerator extends AbstractGenerator
if (!_needCRLF && _needEOC)
{
if (_buffer == null && _header.space() >= LAST_CHUNK.length)
if (_buffer == null && _header != null && _header.space() >= LAST_CHUNK.length)
{
if (!_head)
{

View File

@ -225,8 +225,8 @@ public class HttpParser implements Parser
/* ------------------------------------------------------------------------------- */
/**
* Parse until END state.
* This method will parse any remaining content in the current buffer. It does not care about the
* {@link #getState current state} of the parser.
* This method will parse any remaining content in the current buffer as long as there is
* no unconsumed content. It does not care about the {@link #getState current state} of the parser.
* @see #parse
* @see #parseNext
*/
@ -235,7 +235,7 @@ public class HttpParser implements Parser
boolean progress=parseNext()>0;
// continue parsing
while (!isComplete() && _buffer!=null && _buffer.length()>0)
while (!isComplete() && _buffer!=null && _buffer.length()>0 && !_contentView.hasContent())
{
progress |= parseNext()>0;
}

View File

@ -31,6 +31,9 @@ import org.eclipse.jetty.util.StringUtil;
*/
public class ByteArrayBuffer extends AbstractBuffer
{
// Set a maximum size to a write for the writeTo method, to ensure that very large content is not
// written as a single write (which may fall foul to write timeouts if consumed slowly).
final static int MAX_WRITE=Integer.getInteger("org.eclipse.jetty.io.ByteArrayBuffer.MAX_WRITE",128*1024);
final protected byte[] _bytes;
protected ByteArrayBuffer(int size, int access, boolean isVolatile)
@ -356,7 +359,20 @@ public class ByteArrayBuffer extends AbstractBuffer
public void writeTo(OutputStream out)
throws IOException
{
out.write(_bytes,getIndex(),length());
int len=length();
if (MAX_WRITE>0 && len>MAX_WRITE)
{
int off=getIndex();
while(len>0)
{
int c=len>MAX_WRITE?MAX_WRITE:len;
out.write(_bytes,off,c);
off+=c;
len-=c;
}
}
else
out.write(_bytes,getIndex(),len);
if (!isImmutable())
clear();
}

View File

@ -406,7 +406,7 @@ public class SslConnection extends AbstractConnection implements AsyncConnection
// pass on ishut/oshut state
if (_endp.isOpen() && _endp.isInputShutdown() && !_inbound.hasContent())
_engine.closeInbound();
closeInbound();
if (_endp.isOpen() && _engine.isOutboundDone() && !_outbound.hasContent())
_endp.shutdownOutput();
@ -428,6 +428,18 @@ public class SslConnection extends AbstractConnection implements AsyncConnection
return some_progress;
}
private void closeInbound()
{
try
{
_engine.closeInbound();
}
catch (SSLException x)
{
_logger.debug(x);
}
}
private synchronized boolean wrap(final Buffer buffer) throws IOException
{
ByteBuffer bbuf=extractByteBuffer(buffer);

View File

@ -26,6 +26,7 @@ import java.nio.channels.SocketChannel;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSocket;
import org.eclipse.jetty.io.EndPoint;
@ -188,12 +189,28 @@ public class SelectChannelEndPointSslTest extends SelectChannelEndPointTest
Assert.assertEquals("HelloWorld",reply);
SelectorManager.LOG.info("javax.net.ssl.SSLException: Inbound closed... is expected soon");
if (debug) System.err.println("\nSudden Death");
if (debug) System.err.println("Shutting down output");
client.socket().shutdownOutput();
filled=client.read(sslIn);
if (debug) System.err.println("in="+filled);
sslIn.flip();
try
{
// Since the client closed abruptly, the server is sending a close alert with a failure
engine.unwrap(sslIn, appIn);
Assert.fail();
}
catch (SSLException x)
{
// Expected
}
sslIn.clear();
filled = client.read(sslIn);
Assert.assertEquals(-1, filled);
Assert.assertFalse(server.isOpen());
}
@Test

View File

@ -52,9 +52,11 @@ import org.eclipse.jetty.util.log.Logger;
* specific to a webapp).
*
* The context selected is based on classloaders. First
* we try looking in at the classloader that is associated
* with the current webapp context (if there is one). If
* not, we use the thread context classloader.
* we try looking at the thread context classloader if it is set, and walk its
* hierarchy, creating a context if none is found. If the thread context classloader
* is not set, then we use the classloader associated with the current Context.
*
* If there is no current context, or no classloader, we return null.
*
* Created: Fri Jun 27 09:26:40 2003
*
@ -80,9 +82,16 @@ public class ContextFactory implements ObjectFactory
/**
* Find or create a context which pertains to a classloader.
*
* We use either the classloader for the current ContextHandler if
* we are handling a request, OR we use the thread context classloader
* if we are not processing a request.
* If the thread context classloader is set, we try to find an already-created naming context
* for it. If one does not exist, we walk its classloader hierarchy until one is found, or we
* run out of parent classloaders. In the latter case, we will create a new naming context associated
* with the original thread context classloader.
*
* If the thread context classloader is not set, we obtain the classloader from the current
* jetty Context, and look for an already-created naming context.
*
* If there is no current jetty Context, or it has no associated classloader, we
* return null.
* @see javax.naming.spi.ObjectFactory#getObjectInstance(java.lang.Object, javax.naming.Name, javax.naming.Context, java.util.Hashtable)
*/
public Object getObjectInstance (Object obj,
@ -99,41 +108,89 @@ public class ContextFactory implements ObjectFactory
return ctx;
}
ClassLoader loader = null;
loader = Thread.currentThread().getContextClassLoader();
if (__log.isDebugEnabled() && loader != null) __log.debug("Using thread context classloader");
if (loader == null && ContextHandler.getCurrentContext() != null)
ClassLoader tccl = Thread.currentThread().getContextClassLoader();
ClassLoader loader = tccl;
//If the thread context classloader is set, then try its hierarchy to find a matching context
if (loader != null)
{
loader = ContextHandler.getCurrentContext().getContextHandler().getClassLoader();
if (__log.isDebugEnabled() && loader != null) __log.debug("Using classloader of current org.eclipse.jetty.server.handler.ContextHandler");
if (__log.isDebugEnabled() && loader != null) __log.debug("Trying thread context classloader");
while (ctx == null && loader != null)
{
ctx = getContextForClassLoader(loader);
if (ctx == null && loader != null)
loader = loader.getParent();
}
//Get the context matching the classloader
ctx = (Context)__contextMap.get(loader);
//The map does not contain an entry for this classloader
if (ctx == null)
{
//Didn't find a context to match, make one
ctx = newNamingContext(obj, tccl, env, name, nameCtx);
__contextMap.put (tccl, ctx);
if(__log.isDebugEnabled())__log.debug("Made context "+name.get(0)+" for classloader: "+tccl);
}
return ctx;
}
//If trying thread context classloader hierarchy failed, try the
//classloader associated with the current context
if (ContextHandler.getCurrentContext() != null)
{
if (__log.isDebugEnabled() && loader != null) __log.debug("Trying classloader of current org.eclipse.jetty.server.handler.ContextHandler");
loader = ContextHandler.getCurrentContext().getContextHandler().getClassLoader();
ctx = (Context)__contextMap.get(loader);
if (ctx == null && loader != null)
{
ctx = newNamingContext(obj, loader, env, name, nameCtx);
__contextMap.put (loader, ctx);
if(__log.isDebugEnabled())__log.debug("Made context "+name.get(0)+" for classloader: "+loader);
}
return ctx;
}
return null;
}
/**
* Create a new NamingContext.
* @param obj
* @param loader
* @param env
* @param name
* @param parentCtx
* @return
* @throws Exception
*/
public NamingContext newNamingContext(Object obj, ClassLoader loader, Hashtable env, Name name, Context parentCtx)
throws Exception
{
Reference ref = (Reference)obj;
StringRefAddr parserAddr = (StringRefAddr)ref.get("parser");
String parserClassName = (parserAddr==null?null:(String)parserAddr.getContent());
NameParser parser = (NameParser)(parserClassName==null?null:loader.loadClass(parserClassName).newInstance());
ctx = new NamingContext (env,
return new NamingContext (env,
name.get(0),
(NamingContext)nameCtx,
(NamingContext)parentCtx,
parser);
if(__log.isDebugEnabled())__log.debug("Made context "+name.get(0)+" for classloader: "+loader);
__contextMap.put (loader, ctx);
}
return ctx;
}
/**
* Find the naming Context for the given classloader
* @param loader
* @return
*/
public Context getContextForClassLoader(ClassLoader loader)
{
if (loader == null)
return null;
return (Context)__contextMap.get(loader);
}
/**

View File

@ -36,13 +36,17 @@ import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.naming.spi.ObjectFactory;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.eclipse.jetty.jndi.ContextFactory;
import org.eclipse.jetty.jndi.NamingContext;
import org.eclipse.jetty.jndi.NamingUtil;
import org.eclipse.jetty.jndi.local.localContextRoot;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
@ -73,69 +77,136 @@ public class TestJNDI
}
@Test
public void testIt() throws Exception
public void testThreadContextClassloaderAndCurrentContext()
throws Exception
{
//set up some classloaders
Thread currentThread = Thread.currentThread();
ClassLoader currentLoader = currentThread.getContextClassLoader();
ClassLoader childLoader1 = new URLClassLoader(new URL[0], currentLoader);
ClassLoader childLoader2 = new URLClassLoader(new URL[0], currentLoader);
//create a jetty context, and start it so that its classloader it created
//and it is the current context
ClassLoader currentLoader = Thread.currentThread().getContextClassLoader();
ContextHandler ch = new ContextHandler();
URLClassLoader chLoader = new URLClassLoader(new URL[0], currentLoader);
ch.setClassLoader(chLoader);
//Create another one
ContextHandler ch2 = new ContextHandler();
URLClassLoader ch2Loader = new URLClassLoader(new URL[0], currentLoader);
ch2.setClassLoader(ch2Loader);
try
{
//Uncomment to aid with debug
/*
javaRootURLContext.getRoot().addListener(new NamingContext.Listener()
ch.setContextPath("/ch");
ch.addEventListener(new ServletContextListener()
{
public void unbind(NamingContext ctx, Binding binding)
private Context comp;
private Object testObj = new Object();
public void contextInitialized(ServletContextEvent sce)
{
System.err.println("java unbind "+binding+" from "+ctx.getName());
}
public Binding bind(NamingContext ctx, Binding binding)
{
System.err.println("java bind "+binding+" to "+ctx.getName());
return binding;
}
});
localContextRoot.getRoot().addListener(new NamingContext.Listener()
{
public void unbind(NamingContext ctx, Binding binding)
{
System.err.println("local unbind "+binding+" from "+ctx.getName());
}
public Binding bind(NamingContext ctx, Binding binding)
{
System.err.println("local bind "+binding+" to "+ctx.getName());
return binding;
}
});
*/
//set the current thread's classloader
currentThread.setContextClassLoader(childLoader1);
InitialContext initCtxA = new InitialContext();
initCtxA.bind ("blah", "123");
assertEquals ("123", initCtxA.lookup("blah"));
initCtxA.destroySubcontext("blah");
try
{
initCtxA.lookup("blah");
fail("context blah was not destroyed");
InitialContext initCtx = new InitialContext();
Context java = (Context)initCtx.lookup("java:");
assertNotNull(java);
comp = (Context)initCtx.lookup("java:comp");
assertNotNull(comp);
Context env = ((Context)comp).createSubcontext("env");
assertNotNull(env);
env.bind("ch", testObj);
}
catch (Exception e)
{
throw new IllegalStateException(e);
}
}
public void contextDestroyed(ServletContextEvent sce)
{
try
{
assertNotNull(comp);
assertEquals(testObj,comp.lookup("env/ch"));
comp.destroySubcontext("env");
}
catch (Exception e)
{
throw new IllegalStateException(e);
}
}
});
//Starting the context makes it current and creates a classloader for it
ch.start();
ch2.setContextPath("/ch2");
ch2.addEventListener(new ServletContextListener()
{
private Context comp;
private Object testObj = new Object();
public void contextInitialized(ServletContextEvent sce)
{
try
{
InitialContext initCtx = new InitialContext();
comp = (Context)initCtx.lookup("java:comp");
assertNotNull(comp);
//another context's bindings should not be visible
Context env = ((Context)comp).createSubcontext("env");
try
{
env.lookup("ch");
fail("java:comp/env visible from another context!");
}
catch (NameNotFoundException e)
{
//expected
}
}
catch (Exception e)
{
throw new IllegalStateException(e);
}
}
public void contextDestroyed(ServletContextEvent sce)
{
try
{
assertNotNull(comp);
comp.destroySubcontext("env");
}
catch (Exception e)
{
throw new IllegalStateException(e);
}
}
});
//make the new context the current one
ch2.start();
}
finally
{
ch.stop();
ch2.stop();
Thread.currentThread().setContextClassLoader(currentLoader);
}
}
@Test
public void testJavaNameParsing() throws Exception
{
Thread currentThread = Thread.currentThread();
ClassLoader currentLoader = currentThread.getContextClassLoader();
ClassLoader childLoader1 = new URLClassLoader(new URL[0], currentLoader);
//set the current thread's classloader
currentThread.setContextClassLoader(childLoader1);
try
{
InitialContext initCtx = new InitialContext();
Context sub0 = (Context)initCtx.lookup("java:");
@ -185,10 +256,74 @@ public class TestJNDI
Context fee = ncontext.createSubcontext("fee");
fee.bind ("fi", "88");
assertEquals("88", initCtxA.lookup("java:/fee/fi"));
assertEquals("88", initCtxA.lookup("java:/fee/fi/"));
assertTrue (initCtxA.lookup("java:/fee/") instanceof javax.naming.Context);
assertEquals("88", initCtx.lookup("java:/fee/fi"));
assertEquals("88", initCtx.lookup("java:/fee/fi/"));
assertTrue (initCtx.lookup("java:/fee/") instanceof javax.naming.Context);
}
finally
{
InitialContext ic = new InitialContext();
Context java = (Context)ic.lookup("java:");
java.destroySubcontext("fee");
currentThread.setContextClassLoader(currentLoader);
}
}
@Test
public void testIt() throws Exception
{
//set up some classloaders
Thread currentThread = Thread.currentThread();
ClassLoader currentLoader = currentThread.getContextClassLoader();
ClassLoader childLoader1 = new URLClassLoader(new URL[0], currentLoader);
ClassLoader childLoader2 = new URLClassLoader(new URL[0], currentLoader);
try
{
//Uncomment to aid with debug
/*
javaRootURLContext.getRoot().addListener(new NamingContext.Listener()
{
public void unbind(NamingContext ctx, Binding binding)
{
System.err.println("java unbind "+binding+" from "+ctx.getName());
}
public Binding bind(NamingContext ctx, Binding binding)
{
System.err.println("java bind "+binding+" to "+ctx.getName());
return binding;
}
});
localContextRoot.getRoot().addListener(new NamingContext.Listener()
{
public void unbind(NamingContext ctx, Binding binding)
{
System.err.println("local unbind "+binding+" from "+ctx.getName());
}
public Binding bind(NamingContext ctx, Binding binding)
{
System.err.println("local bind "+binding+" to "+ctx.getName());
return binding;
}
});
*/
//Set up the tccl before doing any jndi operations
currentThread.setContextClassLoader(childLoader1);
InitialContext initCtx = new InitialContext();
//Test we can lookup the root java: naming tree
Context sub0 = (Context)initCtx.lookup("java:");
assertNotNull(sub0);
//Test that we cannot bind java:comp as it should
//already be bound
try
{
Context sub1 = sub0.createSubcontext ("comp");
@ -201,8 +336,10 @@ public class TestJNDI
//check bindings at comp
Context sub1 = (Context)initCtx.lookup("java:comp");
assertNotNull(sub1);
Context sub2 = sub1.createSubcontext ("env");
assertNotNull(sub2);
initCtx.bind ("java:comp/env/rubbish", "abc");
assertEquals ("abc", initCtx.lookup("java:comp/env/rubbish"));
@ -305,7 +442,6 @@ public class TestJNDI
//expected failure to modify immutable context
}
//test what happens when you close an initial context that was used
initCtx.close();
}
finally
@ -320,61 +456,7 @@ public class TestJNDI
comp.destroySubcontext("env");
comp.unbind("crud");
comp.unbind("crud2");
}
}
@Test
public void testParent()
throws Exception
{
//set up some classloaders
Thread currentThread = Thread.currentThread();
ClassLoader parentLoader = currentThread.getContextClassLoader();
ClassLoader childLoader1 = new URLClassLoader(new URL[0], parentLoader);
try
{
//Test creating a comp for the parent loader does not leak to child
InitialContext initCtx = new InitialContext();
Context comp = (Context)initCtx.lookup("java:comp");
assertNotNull(comp);
Context env = (Context)comp.createSubcontext("env");
assertNotNull(env);
env.bind("foo", "aaabbbcccddd");
assertEquals("aaabbbcccddd", (String)initCtx.lookup("java:comp/env/foo"));
//Change to child loader
currentThread.setContextClassLoader(childLoader1);
comp = (Context)initCtx.lookup("java:comp");
Context childEnv = (Context)comp.createSubcontext("env");
assertNotSame(env, childEnv);
childEnv.bind("foo", "eeefffggghhh");
assertEquals("eeefffggghhh", (String)initCtx.lookup("java:comp/env/foo"));
//Change back to parent
currentThread.setContextClassLoader(parentLoader);
assertEquals("aaabbbcccddd", (String)initCtx.lookup("java:comp/env/foo"));
}
finally
{
//make some effort to clean up
InitialContext ic = new InitialContext();
currentThread.setContextClassLoader(parentLoader);
Context comp = (Context)ic.lookup("java:comp");
comp.destroySubcontext("env");
currentThread.setContextClassLoader(childLoader1);
comp = (Context)ic.lookup("java:comp");
comp.destroySubcontext("env");
currentThread.setContextClassLoader(currentLoader);
}
}
}

View File

@ -224,6 +224,15 @@ public class TestLocalJNDI
assertEquals("333", (String)o);
assertEquals("333", ic.lookup(name));
ic.destroySubcontext("a");
try
{
ic.lookup("a");
fail("context a was not destroyed");
}
catch (NameNotFoundException e)
{
//expected
}
name = parser.parse("");
name.add("x");

View File

@ -136,9 +136,9 @@ public class JettyContextHandlerServiceTracker implements ServiceListener
//if this was not a service that another of our deployers may have deployed (in which case they will undeploy it)
String watermark = (String)sr.getProperty(OSGiWebappConstants.WATERMARK);
if (watermark != null && !"".equals(watermark))
{
//Get a jetty deployer targetted to the named server instance, or the default one if not named
//The individual deployer will decide if it can remove the context or not
String serverName = (String)sr.getProperty(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME);
Map<ServiceReference, ServiceProvider> candidates = getDeployers(serverName);
if (candidates != null)
@ -159,7 +159,6 @@ public class JettyContextHandlerServiceTracker implements ServiceListener
}
}
}
}
if (ev.getType() == ServiceEvent.UNREGISTERING)
{
break;
@ -181,7 +180,7 @@ public class JettyContextHandlerServiceTracker implements ServiceListener
}
String watermark = (String)sr.getProperty(OSGiWebappConstants.WATERMARK);
if (watermark != null && !"".equals(watermark))
return; //another of our deployers is responsible for handling service registrations for this context
return; //one of our deployers just registered the context as an OSGi service, so we can ignore it
//Get a jetty deployer targetted to the named server instance, or the default one if not named
String serverName = (String)sr.getProperty(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME);

View File

@ -107,6 +107,8 @@
compilation time. -->
<_nouses>true</_nouses>
<Import-Package>
javax.servlet;version="2.5.0",
javax.servlet.resources;version="2.5.0",
org.osgi.framework,
org.osgi.service.cm;version="1.2.0",
org.osgi.service.packageadmin,

View File

@ -21,6 +21,9 @@ package com.acme.osgi;
import java.util.Dictionary;
import java.util.Hashtable;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.osgi.framework.Bundle;
@ -43,9 +46,21 @@ public class Activator implements BundleActivator
*
* @param context
*/
public void start(BundleContext context) throws Exception
public void start(final BundleContext context) throws Exception
{
ContextHandler ch = new ContextHandler();
ch.addEventListener(new ServletContextListener ()
{
public void contextInitialized(ServletContextEvent sce)
{
System.err.println("Context is initialized");
}
public void contextDestroyed(ServletContextEvent sce)
{
System.err.println("CONTEXT IS DESTROYED!");
}
});
Dictionary props = new Hashtable();
props.put("contextPath","/acme");
props.put("Jetty-ContextFilePath", "acme.xml");

View File

@ -38,6 +38,7 @@ import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.Inject;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.container.def.PaxRunnerOptions;
@ -69,6 +70,8 @@ public class JettyOSGiBootContextAsService
{
ArrayList<Option> options = new ArrayList<Option>();
options.addAll(TestJettyOSGiBootCore.provisionCoreJetty());
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*",
"org.w3c.*", "javax.xml.*"));
File base = MavenTestingUtils.getBasedir();
File src = new File (base, "src");
@ -173,8 +176,18 @@ public class JettyOSGiBootContextAsService
ServiceReference[] refs = bundleContext.getServiceReferences(ContextHandler.class.getName(), null);
Assert.assertNotNull(refs);
Assert.assertEquals(1,refs.length);
String[] keys = refs[0].getPropertyKeys();
if (keys != null)
{
for (String k:keys)
System.err.println("service property: "+k+", "+refs[0].getProperty(k));
}
ContextHandler ch = (ContextHandler)bundleContext.getService(refs[0]);
Assert.assertEquals("/acme", ch.getContextPath());
testWebBundle.stop();
//Check you can see CONTEXT DESTROYED on stderr. TODO: think of a better way to communicate this to the test
}

View File

@ -41,6 +41,7 @@ import org.eclipse.jetty.webapp.WebAppContext;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.Inject;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.container.def.PaxRunnerOptions;
@ -82,7 +83,8 @@ public class TestJettyOSGiBootWithJsp
ArrayList<Option> options = new ArrayList<Option>();
options.addAll(TestJettyOSGiBootCore.provisionCoreJetty());
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*",
"org.w3c.*", "javax.xml.*"));
// Enable Logging
if(LOGGING_ENABLED) {
options.addAll(Arrays.asList(options(

View File

@ -157,7 +157,7 @@ public class ShutdownMonitor extends Thread
return;
}
while (true)
while (serverSocket != null)
{
Socket socket = null;
try
@ -190,7 +190,9 @@ public class ShutdownMonitor extends Thread
// Shutdown Monitor
debug("Shutting down monitor");
close(socket);
socket = null;
close(serverSocket);
serverSocket = null;
if (exitVm)
{

View File

@ -202,6 +202,12 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
return _lastAccessed;
}
/* ------------------------------------------------------------- */
public void setLastAccessedTime(long time)
{
_lastAccessed = time;
}
/* ------------------------------------------------------------- */
public int getMaxInactiveInterval()
{
@ -307,16 +313,19 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
_manager.removeSession(this,true);
// Notify listeners and unbind values
boolean do_invalidate=false;
synchronized (this)
{
if (!_invalid)
{
if (_requests<=0)
doInvalidate();
do_invalidate=true;
else
_doInvalidate=true;
}
}
if (do_invalidate)
doInvalidate();
}
/* ------------------------------------------------------------- */

View File

@ -37,6 +37,7 @@ public abstract class AbstractSessionIdManager extends AbstractLifeCycle impleme
protected Random _random;
protected boolean _weakRandom;
protected String _workerName;
protected long _reseed=100000L;
/* ------------------------------------------------------------ */
public AbstractSessionIdManager()
@ -50,6 +51,24 @@ public abstract class AbstractSessionIdManager extends AbstractLifeCycle impleme
}
/* ------------------------------------------------------------ */
/**
* @return the reseed probability
*/
public long getReseed()
{
return _reseed;
}
/* ------------------------------------------------------------ */
/** Set the reseed probability.
* @param reseed If non zero then when a random long modulo the reseed value == 1, the {@link SecureRandom} will be reseeded.
*/
public void setReseed(long reseed)
{
_reseed = reseed;
}
/* ------------------------------------------------------------ */
/**
* Get the workname. If set, the workername is dot appended to the session
@ -125,6 +144,22 @@ public abstract class AbstractSessionIdManager extends AbstractLifeCycle impleme
:_random.nextLong();
if (r0<0)
r0=-r0;
// random chance to reseed
if (_reseed>0 && (r0%_reseed)== 1L)
{
LOG.debug("Reseeding {}",this);
if (_random instanceof SecureRandom)
{
SecureRandom secure = (SecureRandom)_random;
secure.setSeed(secure.generateSeed(8));
}
else
{
_random.setSeed(_random.nextLong()^System.currentTimeMillis()^request.hashCode()^Runtime.getRuntime().freeMemory());
}
}
long r1=_weakRandom
?(hashCode()^Runtime.getRuntime().freeMemory()^_random.nextInt()^(((long)request.hashCode())<<32))
:_random.nextLong();

View File

@ -821,7 +821,7 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
statement = connection.prepareStatement(_deleteOldExpiredSessions);
statement.setLong(1, upperBound);
int rows = statement.executeUpdate();
if (LOG.isDebugEnabled()) LOG.debug("Deleted "+rows+" rows");
if (LOG.isDebugEnabled()) LOG.debug("Deleted "+rows+" rows of old sessions expired before "+upperBound);
}
}
}

View File

@ -78,139 +78,114 @@ public class JDBCSessionManager extends AbstractSessionManager
protected JDBCSessionIdManager _jdbcSessionIdMgr = null;
protected long _saveIntervalSec = 60; //only persist changes to session access times every 60 secs
/**
* SessionData
* Session
*
* Persistable data about a session.
* Session instance.
*/
public class SessionData
public class Session extends AbstractSession
{
private final String _id;
private String _rowId;
private long _accessed;
private long _lastAccessed;
private long _maxIdleMs=-1;
private static final long serialVersionUID = 5208464051134226143L;
/**
* If dirty, session needs to be (re)persisted
*/
private boolean _dirty=false;
/**
* Time in msec since the epoch that a session cookie was set for this session
*/
private long _cookieSet;
private long _created;
private Map<String,Object> _attributes;
private String _lastNode;
private String _canonicalContext;
private long _lastSaved;
/**
* Time in msec since the epoch that the session will expire
*/
private long _expiryTime;
/**
* Time in msec since the epoch that the session was last persisted
*/
private long _lastSaved;
/**
* Unique identifier of the last node to host the session
*/
private String _lastNode;
/**
* Virtual host for context (used to help distinguish 2 sessions with same id on different contexts)
*/
private String _virtualHost;
public SessionData (String sessionId)
/**
* Unique row in db for session
*/
private String _rowId;
/**
* Mangled context name (used to help distinguish 2 sessions with same id on different contexts)
*/
private String _canonicalContext;
/**
* Session from a request.
*
* @param request
*/
protected Session (HttpServletRequest request)
{
_id=sessionId;
_created=System.currentTimeMillis();
_accessed = _created;
_attributes = new HashMap<String,Object>();
super(JDBCSessionManager.this,request);
int maxInterval=getMaxInactiveInterval();
_expiryTime = (maxInterval <= 0 ? 0 : (System.currentTimeMillis() + maxInterval*1000L));
_virtualHost = JDBCSessionManager.getVirtualHost(_context);
_canonicalContext = canonicalize(_context.getContextPath());
_lastNode = getSessionIdManager().getWorkerName();
}
public SessionData (String sessionId,Map<String,Object> attributes)
{
_id=sessionId;
_created=System.currentTimeMillis();
_accessed = _created;
_attributes = attributes;
_lastNode = getSessionIdManager().getWorkerName();
}
public synchronized String getId ()
{
return _id;
}
public synchronized long getCreated ()
{
return _created;
}
protected synchronized void setCreated (long ms)
{
_created = ms;
}
public synchronized long getAccessed ()
{
return _accessed;
}
protected synchronized void setAccessed (long ms)
{
_accessed = ms;
}
public synchronized void setMaxIdleMs (long ms)
{
_maxIdleMs = ms;
}
public synchronized long getMaxIdleMs()
{
return _maxIdleMs;
}
public synchronized void setLastAccessed (long ms)
{
_lastAccessed = ms;
}
public synchronized long getLastAccessed()
{
return _lastAccessed;
}
public void setCookieSet (long ms)
{
_cookieSet = ms;
}
public synchronized long getCookieSet ()
{
return _cookieSet;
}
public synchronized void setRowId (String rowId)
/**
* Session restored from database
* @param sessionId
* @param rowId
* @param created
* @param accessed
*/
protected Session (String sessionId, String rowId, long created, long accessed)
{
super(JDBCSessionManager.this, created, accessed, sessionId);
_rowId = rowId;
}
protected synchronized String getRowId()
{
return _rowId;
}
protected synchronized Map<String,Object> getAttributeMap ()
protected synchronized void setRowId(String rowId)
{
return _attributes;
_rowId = rowId;
}
protected synchronized void setAttributeMap (Map<String,Object> map)
public synchronized void setVirtualHost (String vhost)
{
_attributes = map;
_virtualHost=vhost;
}
public synchronized void setLastNode (String node)
public synchronized String getVirtualHost ()
{
_lastNode=node;
}
public synchronized String getLastNode ()
{
return _lastNode;
}
public synchronized void setCanonicalContext(String str)
{
_canonicalContext=str;
}
public synchronized String getCanonicalContext ()
{
return _canonicalContext;
return _virtualHost;
}
public synchronized long getLastSaved ()
@ -233,69 +208,35 @@ public class JDBCSessionManager extends AbstractSessionManager
return _expiryTime;
}
public synchronized void setVirtualHost (String vhost)
public synchronized void setCanonicalContext(String str)
{
_virtualHost=vhost;
_canonicalContext=str;
}
public synchronized String getVirtualHost ()
public synchronized String getCanonicalContext ()
{
return _virtualHost;
return _canonicalContext;
}
@Override
public String toString ()
public void setCookieSet (long ms)
{
return "Session rowId="+_rowId+",id="+_id+",lastNode="+_lastNode+
",created="+_created+",accessed="+_accessed+
",lastAccessed="+_lastAccessed+",cookieSet="+_cookieSet+
"lastSaved="+_lastSaved;
}
_cookieSet = ms;
}
/**
* Session
*
* Session instance in memory of this node.
*/
public class Session extends AbstractSession
public synchronized long getCookieSet ()
{
private static final long serialVersionUID = 5208464051134226143L;
private final SessionData _data;
private boolean _dirty=false;
/**
* Session from a request.
*
* @param request
*/
protected Session (HttpServletRequest request)
{
super(JDBCSessionManager.this,request);
_data = new SessionData(getClusterId(),getAttributeMap());
if (_dftMaxIdleSecs>0)
_data.setMaxIdleMs(_dftMaxIdleSecs*1000L);
_data.setCanonicalContext(canonicalize(_context.getContextPath()));
_data.setVirtualHost(getVirtualHost(_context));
int maxInterval=getMaxInactiveInterval();
_data.setExpiryTime(maxInterval <= 0 ? 0 : (System.currentTimeMillis() + maxInterval*1000L));
return _cookieSet;
}
/**
* Session restored in database.
* @param data
*/
protected Session (long accessed, SessionData data)
public synchronized void setLastNode (String node)
{
super(JDBCSessionManager.this,data.getCreated(), accessed, data.getId());
_data=data;
if (_dftMaxIdleSecs>0)
_data.setMaxIdleMs(_dftMaxIdleSecs*1000L);
addAttributes(_data.getAttributeMap());
_data.setAttributeMap(getAttributeMap());
_lastNode=node;
}
public synchronized String getLastNode ()
{
return _lastNode;
}
@Override
@ -315,7 +256,7 @@ public class JDBCSessionManager extends AbstractSessionManager
@Override
protected void cookieSet()
{
_data.setCookieSet(_data.getAccessed());
_cookieSet = getAccessed();
}
/**
@ -326,18 +267,20 @@ public class JDBCSessionManager extends AbstractSessionManager
*/
@Override
protected boolean access(long time)
{
synchronized (this)
{
if (super.access(time))
{
_data.setLastAccessed(_data.getAccessed());
_data.setAccessed(time);
int maxInterval=getMaxInactiveInterval();
_data.setExpiryTime(maxInterval <= 0 ? 0 : (time + maxInterval*1000L));
_expiryTime = (maxInterval <= 0 ? 0 : (time + maxInterval*1000L));
return true;
}
return false;
}
}
/**
* Exit from session
@ -345,6 +288,8 @@ public class JDBCSessionManager extends AbstractSessionManager
*/
@Override
protected void complete()
{
synchronized (this)
{
super.complete();
try
@ -354,12 +299,12 @@ public class JDBCSessionManager extends AbstractSessionManager
//The session attributes have changed, write to the db, ensuring
//http passivation/activation listeners called
willPassivate();
updateSession(_data);
updateSession(this);
didActivate();
}
else if ((_data._accessed - _data._lastSaved) >= (getSaveInterval() * 1000L))
else if ((getAccessed() - _lastSaved) >= (getSaveInterval() * 1000L))
{
updateSessionAccessTime(_data);
updateSessionAccessTime(this);
}
}
catch (Exception e)
@ -371,6 +316,7 @@ public class JDBCSessionManager extends AbstractSessionManager
_dirty=false;
}
}
}
@Override
protected void timeout() throws IllegalStateException
@ -379,6 +325,15 @@ public class JDBCSessionManager extends AbstractSessionManager
LOG.debug("Timing out session id="+getClusterId());
super.timeout();
}
@Override
public String toString ()
{
return "Session rowId="+_rowId+",id="+getId()+",lastNode="+_lastNode+
",created="+getCreationTime()+",accessed="+getAccessed()+
",lastAccessed="+getLastAccessedTime()+",cookieSet="+_cookieSet+
",lastSaved="+_lastSaved+",expiry="+_expiryTime;
}
}
@ -387,7 +342,7 @@ public class JDBCSessionManager extends AbstractSessionManager
/**
* ClassLoadingObjectInputStream
*
*
* Used to persist the session attribute map
*/
protected class ClassLoadingObjectInputStream extends ObjectInputStream
{
@ -416,8 +371,6 @@ public class JDBCSessionManager extends AbstractSessionManager
}
/**
* Set the time in seconds which is the interval between
* saving the session access time to the database.
@ -466,7 +419,7 @@ public class JDBCSessionManager extends AbstractSessionManager
/**
* A session has been requested by it's id on this node.
* A session has been requested by its id on this node.
*
* Load the session by id AND context path from the database.
* Multiple contexts may share the same session id (due to dispatching)
@ -484,11 +437,10 @@ public class JDBCSessionManager extends AbstractSessionManager
@Override
public Session getSession(String idInCluster)
{
Session session = (Session)_sessions.get(idInCluster);
Session session = null;
Session memSession = (Session)_sessions.get(idInCluster);
synchronized (this)
{
try
{
//check if we need to reload the session -
//as an optimization, don't reload on every access
@ -498,84 +450,95 @@ public class JDBCSessionManager extends AbstractSessionManager
//re-migrated to this node. This should be an extremely rare occurrence,
//as load-balancers are generally well-behaved and consistently send
//sessions to the same node, changing only iff that node fails.
SessionData data = null;
//Session data = null;
long now = System.currentTimeMillis();
if (LOG.isDebugEnabled())
{
if (session==null)
if (memSession==null)
LOG.debug("getSession("+idInCluster+"): not in session map,"+
" now="+now+
" lastSaved="+(session==null?0:session._data._lastSaved)+
" lastSaved="+(memSession==null?0:memSession._lastSaved)+
" interval="+(_saveIntervalSec * 1000L));
else
LOG.debug("getSession("+idInCluster+"): in session map, "+
" now="+now+
" lastSaved="+(session==null?0:session._data._lastSaved)+
" lastSaved="+(memSession==null?0:memSession._lastSaved)+
" interval="+(_saveIntervalSec * 1000L)+
" lastNode="+session._data.getLastNode()+
" lastNode="+memSession._lastNode+
" thisNode="+getSessionIdManager().getWorkerName()+
" difference="+(now - session._data._lastSaved));
" difference="+(now - memSession._lastSaved));
}
if (session==null || ((now - session._data._lastSaved) >= (_saveIntervalSec * 1000L)))
try
{
LOG.debug("getSession("+idInCluster+"): no session in session map or stale session. Reloading session data from db.");
data = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context));
if (memSession==null)
{
LOG.debug("getSession("+idInCluster+"): no session in session map. Reloading session data from db.");
session = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context));
}
else if ((now - session._data._lastSaved) >= (_saveIntervalSec * 1000L))
else if ((now - memSession._lastSaved) >= (_saveIntervalSec * 1000L))
{
LOG.debug("getSession("+idInCluster+"): stale session. Reloading session data from db.");
data = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context));
session = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context));
}
else
{
LOG.debug("getSession("+idInCluster+"): session in session map");
data = session._data;
session = memSession;
}
}
catch (Exception e)
{
LOG.warn("Unable to load session "+idInCluster, e);
return null;
}
if (data != null)
//If we have a session
if (session != null)
{
if (!data.getLastNode().equals(getSessionIdManager().getWorkerName()) || session==null)
//If the session was last used on a different node, or session doesn't exist on this node
if (!session.getLastNode().equals(getSessionIdManager().getWorkerName()) || memSession==null)
{
//if the session has no expiry, or it is not already expired
if (data._expiryTime <= 0 || data._expiryTime > now)
//if session doesn't expire, or has not already expired, update it and put it in this nodes' memory
if (session._expiryTime <= 0 || session._expiryTime > now)
{
if (LOG.isDebugEnabled()) LOG.debug("getSession("+idInCluster+"): lastNode="+data.getLastNode()+" thisNode="+getSessionIdManager().getWorkerName());
data.setLastNode(getSessionIdManager().getWorkerName());
//session last used on a different node, or we don't have it in memory
session = new Session(now,data);
if (LOG.isDebugEnabled())
LOG.debug("getSession("+idInCluster+"): lastNode="+session.getLastNode()+" thisNode="+getSessionIdManager().getWorkerName());
session.setLastNode(getSessionIdManager().getWorkerName());
_sessions.put(idInCluster, session);
//update in db: if unable to update, session will be scavenged later
try
{
updateSessionNode(session);
session.didActivate();
//TODO is this the best way to do this? Or do this on the way out using
//the _dirty flag?
updateSessionNode(data);
}
catch (Exception e)
{
LOG.warn("Unable to update freshly loaded session "+idInCluster, e);
return null;
}
}
else
{
LOG.debug("getSession ({}): Session has expired", idInCluster);
session=null;
}
}
else
LOG.debug("getSession({}): Session not stale {}", idInCluster,session._data);
//session in db shares same id, but is not for this context
LOG.debug("getSession({}): Session not stale {}", idInCluster,session);
}
else
{
//No session in db with matching id and context path.
session=null;
LOG.debug("getSession({}): No session in database matching id={}",idInCluster,idInCluster);
}
return session;
}
catch (Exception e)
{
LOG.warn("Unable to load session from database", e);
return null;
}
}
}
/**
@ -674,7 +637,7 @@ public class JDBCSessionManager extends AbstractSessionManager
try
{
if (session != null)
deleteSession(session._data);
deleteSession(session);
}
catch (Exception e)
{
@ -704,11 +667,14 @@ public class JDBCSessionManager extends AbstractSessionManager
//TODO or delay the store until exit out of session? If we crash before we store it
//then session data will be lost.
try
{
synchronized (session)
{
session.willPassivate();
storeSession(((JDBCSessionManager.Session)session)._data);
storeSession(((JDBCSessionManager.Session)session));
session.didActivate();
}
}
catch (Exception e)
{
LOG.warn("Unable to store new session id="+session.getId() , e);
@ -826,17 +792,17 @@ public class JDBCSessionManager extends AbstractSessionManager
* @return the session data that was loaded
* @throws Exception
*/
protected SessionData loadSession (final String id, final String canonicalContextPath, final String vhost)
protected Session loadSession (final String id, final String canonicalContextPath, final String vhost)
throws Exception
{
final AtomicReference<SessionData> _reference = new AtomicReference<SessionData>();
final AtomicReference<Session> _reference = new AtomicReference<Session>();
final AtomicReference<Exception> _exception = new AtomicReference<Exception>();
Runnable load = new Runnable()
{
@SuppressWarnings("unchecked")
public void run()
{
SessionData data = null;
Session session = null;
Connection connection=null;
PreparedStatement statement = null;
try
@ -846,28 +812,25 @@ public class JDBCSessionManager extends AbstractSessionManager
ResultSet result = statement.executeQuery();
if (result.next())
{
data = new SessionData(id);
data.setRowId(result.getString(_jdbcSessionIdMgr._sessionTableRowId));
data.setCookieSet(result.getLong("cookieTime"));
data.setLastAccessed(result.getLong("lastAccessTime"));
data.setAccessed (result.getLong("accessTime"));
data.setCreated(result.getLong("createTime"));
data.setLastNode(result.getString("lastNode"));
data.setLastSaved(result.getLong("lastSavedTime"));
data.setExpiryTime(result.getLong("expiryTime"));
data.setCanonicalContext(result.getString("contextPath"));
data.setVirtualHost(result.getString("virtualHost"));
session = new Session(id, result.getString(_jdbcSessionIdMgr._sessionTableRowId), result.getLong("createTime"), result.getLong("accessTime"));
session.setCookieSet(result.getLong("cookieTime"));
session.setLastAccessedTime(result.getLong("lastAccessTime"));
session.setLastNode(result.getString("lastNode"));
session.setLastSaved(result.getLong("lastSavedTime"));
session.setExpiryTime(result.getLong("expiryTime"));
session.setCanonicalContext(result.getString("contextPath"));
session.setVirtualHost(result.getString("virtualHost"));
InputStream is = ((JDBCSessionIdManager)getSessionIdManager())._dbAdaptor.getBlobInputStream(result, "map");
ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream (is);
Object o = ois.readObject();
data.setAttributeMap((Map<String,Object>)o);
session.addAttributes((Map<String,Object>)o);
ois.close();
if (LOG.isDebugEnabled())
LOG.debug("LOADED session "+data);
LOG.debug("LOADED session "+session);
}
_reference.set(data);
_reference.set(session);
}
catch (Exception e)
{
@ -890,7 +853,12 @@ public class JDBCSessionManager extends AbstractSessionManager
_context.getContextHandler().handle(load);
if (_exception.get()!=null)
{
//if the session could not be restored, take its id out of the pool of currently-in-use
//session ids
_jdbcSessionIdMgr.removeSession(id);
throw _exception.get();
}
return _reference.get();
}
@ -901,10 +869,10 @@ public class JDBCSessionManager extends AbstractSessionManager
* @param data
* @throws Exception
*/
protected void storeSession (SessionData data)
protected void storeSession (Session session)
throws Exception
{
if (data==null)
if (session==null)
return;
//put into the database
@ -912,38 +880,38 @@ public class JDBCSessionManager extends AbstractSessionManager
PreparedStatement statement = null;
try
{
String rowId = calculateRowId(data);
String rowId = calculateRowId(session);
long now = System.currentTimeMillis();
connection.setAutoCommit(true);
statement = connection.prepareStatement(_jdbcSessionIdMgr._insertSession);
statement.setString(1, rowId); //rowId
statement.setString(2, data.getId()); //session id
statement.setString(3, data.getCanonicalContext()); //context path
statement.setString(4, data.getVirtualHost()); //first vhost
statement.setString(2, session.getId()); //session id
statement.setString(3, session.getCanonicalContext()); //context path
statement.setString(4, session.getVirtualHost()); //first vhost
statement.setString(5, getSessionIdManager().getWorkerName());//my node id
statement.setLong(6, data.getAccessed());//accessTime
statement.setLong(7, data.getLastAccessed()); //lastAccessTime
statement.setLong(8, data.getCreated()); //time created
statement.setLong(9, data.getCookieSet());//time cookie was set
statement.setLong(6, session.getAccessed());//accessTime
statement.setLong(7, session.getLastAccessedTime()); //lastAccessTime
statement.setLong(8, session.getCreationTime()); //time created
statement.setLong(9, session.getCookieSet());//time cookie was set
statement.setLong(10, now); //last saved time
statement.setLong(11, data.getExpiryTime());
statement.setLong(11, session.getExpiryTime());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(data.getAttributeMap());
oos.writeObject(session.getAttributeMap());
byte[] bytes = baos.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
statement.setBinaryStream(12, bais, bytes.length);//attribute map as blob
statement.executeUpdate();
data.setRowId(rowId); //set it on the in-memory data as well as in db
data.setLastSaved(now);
session.setRowId(rowId); //set it on the in-memory data as well as in db
session.setLastSaved(now);
if (LOG.isDebugEnabled())
LOG.debug("Stored session "+data);
LOG.debug("Stored session "+session);
}
finally
{
@ -956,10 +924,10 @@ public class JDBCSessionManager extends AbstractSessionManager
/**
* Update data on an existing persisted session.
*
* @param data
* @param data the session
* @throws Exception
*/
protected void updateSession (SessionData data)
protected void updateSession (Session data)
throws Exception
{
if (data==null)
@ -974,7 +942,7 @@ public class JDBCSessionManager extends AbstractSessionManager
statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSession);
statement.setString(1, getSessionIdManager().getWorkerName());//my node id
statement.setLong(2, data.getAccessed());//accessTime
statement.setLong(3, data.getLastAccessed()); //lastAccessTime
statement.setLong(3, data.getLastAccessedTime()); //lastAccessTime
statement.setLong(4, now); //last saved time
statement.setLong(5, data.getExpiryTime());
@ -1003,10 +971,10 @@ public class JDBCSessionManager extends AbstractSessionManager
/**
* Update the node on which the session was last seen to be my node.
*
* @param data
* @param data the session
* @throws Exception
*/
protected void updateSessionNode (SessionData data)
protected void updateSessionNode (Session data)
throws Exception
{
String nodeId = getSessionIdManager().getWorkerName();
@ -1033,10 +1001,10 @@ public class JDBCSessionManager extends AbstractSessionManager
/**
* Persist the time the session was last accessed.
*
* @param data
* @param data the session
* @throws Exception
*/
private void updateSessionAccessTime (SessionData data)
private void updateSessionAccessTime (Session data)
throws Exception
{
Connection connection = getConnection();
@ -1048,7 +1016,7 @@ public class JDBCSessionManager extends AbstractSessionManager
statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSessionAccessTime);
statement.setString(1, getSessionIdManager().getWorkerName());
statement.setLong(2, data.getAccessed());
statement.setLong(3, data.getLastAccessed());
statement.setLong(3, data.getLastAccessedTime());
statement.setLong(4, now);
statement.setLong(5, data.getExpiryTime());
statement.setString(6, data.getRowId());
@ -1075,7 +1043,7 @@ public class JDBCSessionManager extends AbstractSessionManager
* @param data
* @throws Exception
*/
protected void deleteSession (SessionData data)
protected void deleteSession (Session data)
throws Exception
{
Connection connection = getConnection();
@ -1116,7 +1084,7 @@ public class JDBCSessionManager extends AbstractSessionManager
* @param data
* @return
*/
private String calculateRowId (SessionData data)
private String calculateRowId (Session data)
{
String rowId = canonicalize(_context.getContextPath());
rowId = rowId + "_" + getVirtualHost(_context);
@ -1131,7 +1099,7 @@ public class JDBCSessionManager extends AbstractSessionManager
*
* @return 0.0.0.0 if no virtual host is defined
*/
private String getVirtualHost (ContextHandler.Context context)
private static String getVirtualHost (ContextHandler.Context context)
{
String vhost = "0.0.0.0";
@ -1151,7 +1119,7 @@ public class JDBCSessionManager extends AbstractSessionManager
* @param path
* @return
*/
private String canonicalize (String path)
private static String canonicalize (String path)
{
if (path==null)
return "";

View File

@ -0,0 +1,141 @@
//
// ========================================================================
// 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.servlet;
import java.io.IOException;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.eclipse.jetty.server.Dispatcher;
import org.eclipse.jetty.server.AbstractHttpConnection;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.Resource;
/* ------------------------------------------------------------ */
/** Servlet handling JSP Property Group mappings
* <p>
* This servlet is mapped to by any URL pattern for a JSP property group.
* Resources handled by this servlet that are not directories will be passed
* directly to the JSP servlet. Resources that are directories will be
* passed directly to the default servlet.
*/
public class JspPropertyGroupServlet extends GenericServlet
{
private static final long serialVersionUID = 3681783214726776945L;
public final static String NAME = "__org.eclipse.jetty.servlet.JspPropertyGroupServlet__";
private final ServletHandler _servletHandler;
private final ContextHandler _contextHandler;
private ServletHolder _dftServlet;
private ServletHolder _jspServlet;
private boolean _starJspMapped;
public JspPropertyGroupServlet(ContextHandler context, ServletHandler servletHandler)
{
_contextHandler=context;
_servletHandler=servletHandler;
}
@Override
public void init() throws ServletException
{
String jsp_name = "jsp";
ServletMapping servlet_mapping =_servletHandler.getServletMapping("*.jsp");
if (servlet_mapping!=null)
{
_starJspMapped=true;
//now find the jsp servlet, ignoring the mapping that is for ourself
ServletMapping[] mappings = _servletHandler.getServletMappings();
for (ServletMapping m:mappings)
{
String[] paths = m.getPathSpecs();
if (paths!=null)
{
for (String path:paths)
{
if ("*.jsp".equals(path) && !NAME.equals(m.getServletName()))
servlet_mapping = m;
}
}
}
jsp_name=servlet_mapping.getServletName();
}
_jspServlet=_servletHandler.getServlet(jsp_name);
String dft_name="default";
ServletMapping default_mapping=_servletHandler.getServletMapping("/");
if (default_mapping!=null)
dft_name=default_mapping.getServletName();
_dftServlet=_servletHandler.getServlet(dft_name);
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
{
Request request=(req instanceof Request)?(Request)req:AbstractHttpConnection.getCurrentConnection().getRequest();
String servletPath=null;
String pathInfo=null;
if (request.getAttribute(Dispatcher.INCLUDE_REQUEST_URI)!=null)
{
servletPath=(String)request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH);
pathInfo=(String)request.getAttribute(Dispatcher.INCLUDE_PATH_INFO);
if (servletPath==null)
{
servletPath=request.getServletPath();
pathInfo=request.getPathInfo();
}
}
else
{
servletPath = request.getServletPath();
pathInfo = request.getPathInfo();
}
String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
if (pathInContext.endsWith("/"))
{
_dftServlet.getServlet().service(req,res);
}
else if (_starJspMapped && pathInContext.toLowerCase().endsWith(".jsp"))
{
_jspServlet.getServlet().service(req,res);
}
else
{
Resource resource = _contextHandler.getResource(pathInContext);
if (resource!=null && resource.isDirectory())
_dftServlet.getServlet().service(req,res);
else
_jspServlet.getServlet().service(req,res);
}
}
}

View File

@ -513,12 +513,12 @@ public class ServletHandler extends ScopedHandler
{
UnavailableException ue = (UnavailableException)th;
if (ue.isPermanent())
response.sendError(HttpServletResponse.SC_NOT_FOUND,th.getMessage());
response.sendError(HttpServletResponse.SC_NOT_FOUND);
else
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,th.getMessage());
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
}
else
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,th.getMessage());
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
else
LOG.debug("Response already committed for handling "+th);
@ -535,7 +535,7 @@ public class ServletHandler extends ScopedHandler
{
request.setAttribute(Dispatcher.ERROR_EXCEPTION_TYPE,e.getClass());
request.setAttribute(Dispatcher.ERROR_EXCEPTION,e);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,e.getMessage());
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
else
LOG.debug("Response already committed for handling ",e);

View File

@ -0,0 +1,164 @@
//
// ========================================================================
// 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.servlet;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.io.IOException;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Dispatcher;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.StdErrLog;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
*
*/
public class ErrorPageTest
{
private Server _server;
private LocalConnector _connector;
@Before
public void init() throws Exception
{
_server = new Server();
_connector = new LocalConnector();
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SECURITY|ServletContextHandler.NO_SESSIONS);
_server.setSendServerVersion(false);
_server.addConnector(_connector);
_server.setHandler(context);
context.setContextPath("/");
context.addServlet(DefaultServlet.class, "/");
context.addServlet(FailServlet.class, "/fail/*");
context.addServlet(ErrorServlet.class, "/error/*");
ErrorPageErrorHandler error = new ErrorPageErrorHandler();
context.setErrorHandler(error);
error.addErrorPage(599,"/error/599");
error.addErrorPage(IllegalStateException.class.getCanonicalName(),"/error/TestException");
error.addErrorPage(ErrorPageErrorHandler.GLOBAL_ERROR_PAGE,"/error/GlobalErrorPage");
_server.start();
((StdErrLog)Log.getLogger(ServletHandler.class)).setHideStacks(true);
}
@After
public void destroy() throws Exception
{
((StdErrLog)Log.getLogger(ServletHandler.class)).setHideStacks(false);
_server.stop();
_server.join();
}
@Test
public void testErrorCode() throws Exception
{
String response = _connector.getResponses("GET /fail/code?code=599 HTTP/1.0\r\n\r\n");
assertThat(response,Matchers.containsString("HTTP/1.1 599 599"));
assertThat(response,Matchers.containsString("ERROR_PAGE: /599"));
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_REQUEST_URI: /fail/code"));
}
@Test
public void testErrorException() throws Exception
{
String response = _connector.getResponses("GET /fail/exception HTTP/1.0\r\n\r\n");
assertThat(response,Matchers.containsString("HTTP/1.1 500 Server Error"));
assertThat(response,Matchers.containsString("ERROR_PAGE: /TestException"));
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_REQUEST_URI: /fail/exception"));
}
@Test
public void testGlobalErrorCode() throws Exception
{
String response = _connector.getResponses("GET /fail/global?code=598 HTTP/1.0\r\n\r\n");
assertThat(response,Matchers.containsString("HTTP/1.1 598 598"));
assertThat(response,Matchers.containsString("ERROR_PAGE: /GlobalErrorPage"));
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_REQUEST_URI: /fail/global"));
}
@Test
public void testGlobalErrorException() throws Exception
{
String response = _connector.getResponses("GET /fail/global?code=NAN HTTP/1.0\r\n\r\n");
assertThat(response,Matchers.containsString("HTTP/1.1 500 Server Error"));
assertThat(response,Matchers.containsString("ERROR_PAGE: /GlobalErrorPage"));
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_REQUEST_URI: /fail/global"));
}
public static class FailServlet extends HttpServlet implements Servlet
{
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
String code=request.getParameter("code");
if (code!=null)
response.sendError(Integer.parseInt(code));
else
throw new ServletException(new IllegalStateException());
}
}
public static class ErrorServlet extends HttpServlet implements Servlet
{
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
response.getWriter().println("ERROR_PAGE: "+request.getPathInfo());
response.getWriter().println("ERROR_MESSAGE: "+request.getAttribute(Dispatcher.ERROR_MESSAGE));
response.getWriter().println("ERROR_CODE: "+request.getAttribute(Dispatcher.ERROR_STATUS_CODE));
response.getWriter().println("ERROR_EXCEPTION: "+request.getAttribute(Dispatcher.ERROR_EXCEPTION));
response.getWriter().println("ERROR_EXCEPTION_TYPE: "+request.getAttribute(Dispatcher.ERROR_EXCEPTION_TYPE));
response.getWriter().println("ERROR_SERVLET: "+request.getAttribute(Dispatcher.ERROR_SERVLET_NAME));
response.getWriter().println("ERROR_REQUEST_URI: "+request.getAttribute(Dispatcher.ERROR_REQUEST_URI));
}
}
}

View File

@ -84,6 +84,12 @@
<artifactId>javax.servlet</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jmx</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>test-jetty-servlet</artifactId>

View File

@ -19,16 +19,17 @@
package org.eclipse.jetty.servlets;
import java.io.IOException;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Map;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Queue;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
@ -54,9 +55,9 @@ import org.eclipse.jetty.util.thread.Timeout;
/**
* Denial of Service filter
*
* <p/>
* <p>
* This filter is based on the {@link QoSFilter}. it is useful for limiting
* This filter is useful for limiting
* exposure to abuse from request flooding, whether malicious, or as a result of
* a misconfigured client.
* <p>
@ -73,46 +74,46 @@ import org.eclipse.jetty.util.thread.Timeout;
* implemented, in order to uniquely identify authenticated users.
* <p>
* The following init parameters control the behavior of the filter:<dl>
*
* <p/>
* <dt>maxRequestsPerSec</dt>
* <dd>the maximum number of requests from a connection per
* second. Requests in excess of this are first delayed,
* then throttled.</dd>
*
* <p/>
* <dt>delayMs</dt>
* <dd>is the delay given to all requests over the rate limit,
* before they are considered at all. -1 means just reject request,
* 0 means no delay, otherwise it is the delay.</dd>
*
* <p/>
* <dt>maxWaitMs</dt>
* <dd>how long to blocking wait for the throttle semaphore.</dd>
*
* <p/>
* <dt>throttledRequests</dt>
* <dd>is the number of requests over the rate limit able to be
* considered at once.</dd>
*
* <p/>
* <dt>throttleMs</dt>
* <dd>how long to async wait for semaphore.</dd>
*
* <p/>
* <dt>maxRequestMs</dt>
* <dd>how long to allow this request to run.</dd>
*
* <p/>
* <dt>maxIdleTrackerMs</dt>
* <dd>how long to keep track of request rates for a connection,
* before deciding that the user has gone away, and discarding it</dd>
*
* <p/>
* <dt>insertHeaders</dt>
* <dd>if true , insert the DoSFilter headers into the response. Defaults to true.</dd>
*
* <p/>
* <dt>trackSessions</dt>
* <dd>if true, usage rate is tracked by session if a session exists. Defaults to true.</dd>
*
* <p/>
* <dt>remotePort</dt>
* <dd>if true and session tracking is not used, then rate is tracked by IP+port (effectively connection). Defaults to false.</dd>
*
* <p/>
* <dt>ipWhitelist</dt>
* <dd>a comma-separated list of IP addresses that will not be rate limited</dd>
*
* <p/>
* <dt>managedAttr</dt>
* <dd>if set to true, then this servlet is set as a {@link ServletContext} attribute with the
* filter name as the attribute name. This allows context external mechanism (eg JMX via {@link ContextHandler#MANAGED_ATTRIBUTES}) to
@ -120,64 +121,62 @@ import org.eclipse.jetty.util.thread.Timeout;
* </dl>
* </p>
*/
public class DoSFilter implements Filter
{
private static final Logger LOG = Log.getLogger(DoSFilter.class);
final static String __TRACKER = "DoSFilter.Tracker";
final static String __THROTTLED = "DoSFilter.Throttled";
private static final Pattern IP_PATTERN = Pattern.compile("(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})");
private static final Pattern CIDR_PATTERN = Pattern.compile(IP_PATTERN + "/(\\d{1,2})");
final static int __DEFAULT_MAX_REQUESTS_PER_SEC = 25;
final static int __DEFAULT_DELAY_MS = 100;
final static int __DEFAULT_THROTTLE = 5;
final static int __DEFAULT_WAIT_MS=50;
final static long __DEFAULT_THROTTLE_MS = 30000L;
final static long __DEFAULT_MAX_REQUEST_MS_INIT_PARAM=30000L;
final static long __DEFAULT_MAX_IDLE_TRACKER_MS_INIT_PARAM=30000L;
private static final String __TRACKER = "DoSFilter.Tracker";
private static final String __THROTTLED = "DoSFilter.Throttled";
final static String MANAGED_ATTR_INIT_PARAM="managedAttr";
final static String MAX_REQUESTS_PER_S_INIT_PARAM = "maxRequestsPerSec";
final static String DELAY_MS_INIT_PARAM = "delayMs";
final static String THROTTLED_REQUESTS_INIT_PARAM = "throttledRequests";
final static String MAX_WAIT_INIT_PARAM="maxWaitMs";
final static String THROTTLE_MS_INIT_PARAM = "throttleMs";
final static String MAX_REQUEST_MS_INIT_PARAM="maxRequestMs";
final static String MAX_IDLE_TRACKER_MS_INIT_PARAM="maxIdleTrackerMs";
final static String INSERT_HEADERS_INIT_PARAM="insertHeaders";
final static String TRACK_SESSIONS_INIT_PARAM="trackSessions";
final static String REMOTE_PORT_INIT_PARAM="remotePort";
final static String IP_WHITELIST_INIT_PARAM="ipWhitelist";
private static final int __DEFAULT_MAX_REQUESTS_PER_SEC = 25;
private static final int __DEFAULT_DELAY_MS = 100;
private static final int __DEFAULT_THROTTLE = 5;
private static final int __DEFAULT_MAX_WAIT_MS = 50;
private static final long __DEFAULT_THROTTLE_MS = 30000L;
private static final long __DEFAULT_MAX_REQUEST_MS_INIT_PARAM = 30000L;
private static final long __DEFAULT_MAX_IDLE_TRACKER_MS_INIT_PARAM = 30000L;
final static int USER_AUTH = 2;
final static int USER_SESSION = 2;
final static int USER_IP = 1;
final static int USER_UNKNOWN = 0;
static final String MANAGED_ATTR_INIT_PARAM = "managedAttr";
static final String MAX_REQUESTS_PER_S_INIT_PARAM = "maxRequestsPerSec";
static final String DELAY_MS_INIT_PARAM = "delayMs";
static final String THROTTLED_REQUESTS_INIT_PARAM = "throttledRequests";
static final String MAX_WAIT_INIT_PARAM = "maxWaitMs";
static final String THROTTLE_MS_INIT_PARAM = "throttleMs";
static final String MAX_REQUEST_MS_INIT_PARAM = "maxRequestMs";
static final String MAX_IDLE_TRACKER_MS_INIT_PARAM = "maxIdleTrackerMs";
static final String INSERT_HEADERS_INIT_PARAM = "insertHeaders";
static final String TRACK_SESSIONS_INIT_PARAM = "trackSessions";
static final String REMOTE_PORT_INIT_PARAM = "remotePort";
static final String IP_WHITELIST_INIT_PARAM = "ipWhitelist";
static final String ENABLED_INIT_PARAM = "enabled";
ServletContext _context;
protected String _name;
protected long _delayMs;
protected long _throttleMs;
protected long _maxWaitMs;
protected long _maxRequestMs;
protected long _maxIdleTrackerMs;
protected boolean _insertHeaders;
protected boolean _trackSessions;
protected boolean _remotePort;
protected int _throttledRequests;
protected Semaphore _passes;
protected Queue<Continuation>[] _queue;
protected ContinuationListener[] _listener;
protected int _maxRequestsPerSec;
protected final ConcurrentHashMap<String, RateTracker> _rateTrackers=new ConcurrentHashMap<String, RateTracker>();
protected String _whitelistStr;
private final HashSet<String> _whitelist = new HashSet<String>();
private static final int USER_AUTH = 2;
private static final int USER_SESSION = 2;
private static final int USER_IP = 1;
private static final int USER_UNKNOWN = 0;
private ServletContext _context;
private volatile long _delayMs;
private volatile long _throttleMs;
private volatile long _maxWaitMs;
private volatile long _maxRequestMs;
private volatile long _maxIdleTrackerMs;
private volatile boolean _insertHeaders;
private volatile boolean _trackSessions;
private volatile boolean _remotePort;
private volatile boolean _enabled;
private Semaphore _passes;
private volatile int _throttledRequests;
private volatile int _maxRequestsPerSec;
private Queue<Continuation>[] _queue;
private ContinuationListener[] _listeners;
private final ConcurrentHashMap<String, RateTracker> _rateTrackers = new ConcurrentHashMap<String, RateTracker>();
private final List<String> _whitelist = new CopyOnWriteArrayList<String>();
private final Timeout _requestTimeoutQ = new Timeout();
private final Timeout _trackerTimeoutQ = new Timeout();
private Thread _timerThread;
private volatile boolean _running;
@ -186,13 +185,13 @@ public class DoSFilter implements Filter
_context = filterConfig.getServletContext();
_queue = new Queue[getMaxPriority() + 1];
_listener = new ContinuationListener[getMaxPriority() + 1];
_listeners = new ContinuationListener[getMaxPriority() + 1];
for (int p = 0; p < _queue.length; p++)
{
_queue[p] = new ConcurrentLinkedQueue<Continuation>();
final int priority = p;
_listener[p] = new ContinuationListener()
_listeners[p] = new ContinuationListener()
{
public void onComplete(Continuation continuation)
{
@ -207,55 +206,65 @@ public class DoSFilter implements Filter
_rateTrackers.clear();
int baseRateLimit = __DEFAULT_MAX_REQUESTS_PER_SEC;
if (filterConfig.getInitParameter(MAX_REQUESTS_PER_S_INIT_PARAM) != null)
baseRateLimit = Integer.parseInt(filterConfig.getInitParameter(MAX_REQUESTS_PER_S_INIT_PARAM));
_maxRequestsPerSec = baseRateLimit;
int maxRequests = __DEFAULT_MAX_REQUESTS_PER_SEC;
String parameter = filterConfig.getInitParameter(MAX_REQUESTS_PER_S_INIT_PARAM);
if (parameter != null)
maxRequests = Integer.parseInt(parameter);
setMaxRequestsPerSec(maxRequests);
long delay = __DEFAULT_DELAY_MS;
if (filterConfig.getInitParameter(DELAY_MS_INIT_PARAM) != null)
delay = Integer.parseInt(filterConfig.getInitParameter(DELAY_MS_INIT_PARAM));
_delayMs = delay;
parameter = filterConfig.getInitParameter(DELAY_MS_INIT_PARAM);
if (parameter != null)
delay = Long.parseLong(parameter);
setDelayMs(delay);
int throttledRequests = __DEFAULT_THROTTLE;
if (filterConfig.getInitParameter(THROTTLED_REQUESTS_INIT_PARAM) != null)
throttledRequests = Integer.parseInt(filterConfig.getInitParameter(THROTTLED_REQUESTS_INIT_PARAM));
_passes = new Semaphore(throttledRequests,true);
_throttledRequests = throttledRequests;
parameter = filterConfig.getInitParameter(THROTTLED_REQUESTS_INIT_PARAM);
if (parameter != null)
throttledRequests = Integer.parseInt(parameter);
setThrottledRequests(throttledRequests);
long wait = __DEFAULT_WAIT_MS;
if (filterConfig.getInitParameter(MAX_WAIT_INIT_PARAM) != null)
wait = Integer.parseInt(filterConfig.getInitParameter(MAX_WAIT_INIT_PARAM));
_maxWaitMs = wait;
long maxWait = __DEFAULT_MAX_WAIT_MS;
parameter = filterConfig.getInitParameter(MAX_WAIT_INIT_PARAM);
if (parameter != null)
maxWait = Long.parseLong(parameter);
setMaxWaitMs(maxWait);
long suspend = __DEFAULT_THROTTLE_MS;
if (filterConfig.getInitParameter(THROTTLE_MS_INIT_PARAM) != null)
suspend = Integer.parseInt(filterConfig.getInitParameter(THROTTLE_MS_INIT_PARAM));
_throttleMs = suspend;
long throttle = __DEFAULT_THROTTLE_MS;
parameter = filterConfig.getInitParameter(THROTTLE_MS_INIT_PARAM);
if (parameter != null)
throttle = Long.parseLong(parameter);
setThrottleMs(throttle);
long maxRequestMs = __DEFAULT_MAX_REQUEST_MS_INIT_PARAM;
if (filterConfig.getInitParameter(MAX_REQUEST_MS_INIT_PARAM) != null )
maxRequestMs = Long.parseLong(filterConfig.getInitParameter(MAX_REQUEST_MS_INIT_PARAM));
_maxRequestMs = maxRequestMs;
parameter = filterConfig.getInitParameter(MAX_REQUEST_MS_INIT_PARAM);
if (parameter != null)
maxRequestMs = Long.parseLong(parameter);
setMaxRequestMs(maxRequestMs);
long maxIdleTrackerMs = __DEFAULT_MAX_IDLE_TRACKER_MS_INIT_PARAM;
if (filterConfig.getInitParameter(MAX_IDLE_TRACKER_MS_INIT_PARAM) != null )
maxIdleTrackerMs = Long.parseLong(filterConfig.getInitParameter(MAX_IDLE_TRACKER_MS_INIT_PARAM));
_maxIdleTrackerMs = maxIdleTrackerMs;
parameter = filterConfig.getInitParameter(MAX_IDLE_TRACKER_MS_INIT_PARAM);
if (parameter != null)
maxIdleTrackerMs = Long.parseLong(parameter);
setMaxIdleTrackerMs(maxIdleTrackerMs);
_whitelistStr = "";
if (filterConfig.getInitParameter(IP_WHITELIST_INIT_PARAM) !=null )
_whitelistStr = filterConfig.getInitParameter(IP_WHITELIST_INIT_PARAM);
initWhitelist();
String whiteList = "";
parameter = filterConfig.getInitParameter(IP_WHITELIST_INIT_PARAM);
if (parameter != null)
whiteList = parameter;
setWhitelist(whiteList);
String tmp = filterConfig.getInitParameter(INSERT_HEADERS_INIT_PARAM);
_insertHeaders = tmp==null || Boolean.parseBoolean(tmp);
parameter = filterConfig.getInitParameter(INSERT_HEADERS_INIT_PARAM);
setInsertHeaders(parameter == null || Boolean.parseBoolean(parameter));
tmp = filterConfig.getInitParameter(TRACK_SESSIONS_INIT_PARAM);
_trackSessions = tmp==null || Boolean.parseBoolean(tmp);
parameter = filterConfig.getInitParameter(TRACK_SESSIONS_INIT_PARAM);
setTrackSessions(parameter == null || Boolean.parseBoolean(parameter));
tmp = filterConfig.getInitParameter(REMOTE_PORT_INIT_PARAM);
_remotePort = tmp!=null&& Boolean.parseBoolean(tmp);
parameter = filterConfig.getInitParameter(REMOTE_PORT_INIT_PARAM);
setRemotePort(parameter != null && Boolean.parseBoolean(parameter));
parameter = filterConfig.getInitParameter(ENABLED_INIT_PARAM);
setEnabled(parameter == null || Boolean.parseBoolean(parameter));
_requestTimeoutQ.setNow();
_requestTimeoutQ.setDuration(_maxRequestMs);
@ -272,17 +281,10 @@ public class DoSFilter implements Filter
{
while (_running)
{
long now;
synchronized (_requestTimeoutQ)
{
now = _requestTimeoutQ.setNow();
long now = _requestTimeoutQ.setNow();
_requestTimeoutQ.tick();
}
synchronized (_trackerTimeoutQ)
{
_trackerTimeoutQ.setNow(now);
_trackerTimeoutQ.tick();
}
try
{
Thread.sleep(100);
@ -295,7 +297,7 @@ public class DoSFilter implements Filter
}
finally
{
LOG.info("DoSFilter timer exited");
LOG.debug("DoSFilter timer exited");
}
}
});
@ -305,11 +307,18 @@ public class DoSFilter implements Filter
_context.setAttribute(filterConfig.getFilterName(), this);
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterchain) throws IOException, ServletException
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException
{
final HttpServletRequest srequest = (HttpServletRequest)request;
final HttpServletResponse sresponse = (HttpServletResponse)response;
doFilter((HttpServletRequest)request, (HttpServletResponse)response, filterChain);
}
protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException
{
if (!isEnabled())
{
filterChain.doFilter(request, response);
return;
}
final long now = _requestTimeoutQ.getNow();
@ -329,22 +338,24 @@ public class DoSFilter implements Filter
// pass it through if we are not currently over the rate limit
if (!overRateLimit)
{
doFilterChain(filterchain,srequest,sresponse);
doFilterChain(filterChain, request, response);
return;
}
// We are over the limit.
LOG.warn("DOS ALERT: ip="+srequest.getRemoteAddr()+",session="+srequest.getRequestedSessionId()+",user="+srequest.getUserPrincipal());
LOG.warn("DOS ALERT: ip=" + request.getRemoteAddr() + ",session=" + request.getRequestedSessionId() + ",user=" + request.getUserPrincipal());
// So either reject it, delay it or throttle it
switch((int)_delayMs)
long delayMs = getDelayMs();
boolean insertHeaders = isInsertHeaders();
switch ((int)delayMs)
{
case -1:
{
// Reject this request
if (_insertHeaders)
((HttpServletResponse)response).addHeader("DoSFilter","unavailable");
((HttpServletResponse)response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
if (insertHeaders)
response.addHeader("DoSFilter", "unavailable");
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
return;
}
case 0:
@ -356,12 +367,12 @@ public class DoSFilter implements Filter
default:
{
// insert a delay before throttling the request
if (_insertHeaders)
((HttpServletResponse)response).addHeader("DoSFilter","delayed");
if (insertHeaders)
response.addHeader("DoSFilter", "delayed");
Continuation continuation = ContinuationSupport.getContinuation(request);
request.setAttribute(__TRACKER, tracker);
if (_delayMs > 0)
continuation.setTimeout(_delayMs);
if (delayMs > 0)
continuation.setTimeout(delayMs);
continuation.suspend();
return;
}
@ -373,7 +384,7 @@ public class DoSFilter implements Filter
try
{
// check if we can afford to accept another request at this time
accepted = _passes.tryAcquire(_maxWaitMs,TimeUnit.MILLISECONDS);
accepted = _passes.tryAcquire(getMaxWaitMs(), TimeUnit.MILLISECONDS);
if (!accepted)
{
@ -381,17 +392,18 @@ public class DoSFilter implements Filter
final Continuation continuation = ContinuationSupport.getContinuation(request);
Boolean throttled = (Boolean)request.getAttribute(__THROTTLED);
if (throttled!=Boolean.TRUE && _throttleMs>0)
long throttleMs = getThrottleMs();
if (throttled != Boolean.TRUE && throttleMs > 0)
{
int priority = getPriority(request, tracker);
request.setAttribute(__THROTTLED, Boolean.TRUE);
if (_insertHeaders)
((HttpServletResponse)response).addHeader("DoSFilter","throttled");
if (_throttleMs > 0)
continuation.setTimeout(_throttleMs);
if (isInsertHeaders())
response.addHeader("DoSFilter", "throttled");
if (throttleMs > 0)
continuation.setTimeout(throttleMs);
continuation.suspend();
continuation.addContinuationListener(_listener[priority]);
continuation.addContinuationListener(_listeners[priority]);
_queue[priority].add(continuation);
return;
}
@ -407,19 +419,19 @@ public class DoSFilter implements Filter
// if we were accepted (either immediately or after throttle)
if (accepted)
// call the chain
doFilterChain(filterchain,srequest,sresponse);
doFilterChain(filterChain, request, response);
else
{
// fail the request
if (_insertHeaders)
((HttpServletResponse)response).addHeader("DoSFilter","unavailable");
((HttpServletResponse)response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
if (isInsertHeaders())
response.addHeader("DoSFilter", "unavailable");
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
}
}
catch (InterruptedException e)
{
_context.log("DoS", e);
((HttpServletResponse)response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
}
finally
{
@ -440,15 +452,7 @@ public class DoSFilter implements Filter
}
}
/**
* @param chain
* @param request
* @param response
* @throws IOException
* @throws ServletException
*/
protected void doFilterChain(FilterChain chain, final HttpServletRequest request, final HttpServletResponse response)
throws IOException, ServletException
protected void doFilterChain(FilterChain chain, final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException
{
final Thread thread = Thread.currentThread();
@ -461,25 +465,20 @@ public class DoSFilter implements Filter
};
try
{
synchronized (_requestTimeoutQ)
{
_requestTimeoutQ.schedule(requestTimeout);
}
chain.doFilter(request, response);
}
finally
{
synchronized (_requestTimeoutQ)
{
requestTimeout.cancel();
}
}
}
/**
* Takes drastic measures to return this response and stop this thread.
* Due to the way the connection is interrupted, may return mixed up headers.
*
* @param request current request
* @param response current response, which must be stopped
* @param thread the handling thread
@ -514,11 +513,11 @@ public class DoSFilter implements Filter
/**
* Get priority for this request, based on user type
*
* @param request
* @param tracker
* @return priority
* @param request the current request
* @param tracker the rate tracker for this request
* @return the priority for this request
*/
protected int getPriority(ServletRequest request, RateTracker tracker)
protected int getPriority(HttpServletRequest request, RateTracker tracker)
{
if (extractUserId(request) != null)
return USER_AUTH;
@ -540,21 +539,20 @@ public class DoSFilter implements Filter
* track of this connection's request rate. If this is not the first request
* from this connection, return the existing object with the stored stats.
* If it is the first request, then create a new request tracker.
*
* <p/>
* Assumes that each connection has an identifying characteristic, and goes
* through them in order, taking the first that matches: user id (logged
* in), session id, client IP address. Unidentifiable connections are lumped
* into one.
*
* <p/>
* When a session expires, its rate tracker is automatically deleted.
*
* @param request
* @param request the current request
* @return the request rate tracker for the current connection
*/
public RateTracker getRateTracker(ServletRequest request)
{
HttpServletRequest srequest = (HttpServletRequest)request;
HttpSession session=srequest.getSession(false);
HttpSession session = ((HttpServletRequest)request).getSession(false);
String loadId = extractUserId(request);
final int type;
@ -580,48 +578,79 @@ public class DoSFilter implements Filter
if (tracker == null)
{
RateTracker t;
if (_whitelist.contains(request.getRemoteAddr()))
{
t = new FixedRateTracker(loadId,type,_maxRequestsPerSec);
}
else
{
t = new RateTracker(loadId,type,_maxRequestsPerSec);
}
tracker=_rateTrackers.putIfAbsent(loadId,t);
if (tracker==null)
tracker=t;
boolean allowed = checkWhitelist(_whitelist, request.getRemoteAddr());
tracker = allowed ? new FixedRateTracker(loadId, type, _maxRequestsPerSec)
: new RateTracker(loadId, type, _maxRequestsPerSec);
RateTracker existing = _rateTrackers.putIfAbsent(loadId, tracker);
if (existing != null)
tracker = existing;
if (type == USER_IP)
{
// USER_IP expiration from _rateTrackers is handled by the _trackerTimeoutQ
synchronized (_trackerTimeoutQ)
{
_trackerTimeoutQ.schedule(tracker);
}
}
else if (session != null)
{
// USER_SESSION expiration from _rateTrackers are handled by the HttpSessionBindingListener
session.setAttribute(__TRACKER, tracker);
}
}
return tracker;
}
protected boolean checkWhitelist(List<String> whitelist, String candidate)
{
for (String address : whitelist)
{
if (address.contains("/"))
{
if (subnetMatch(address, candidate))
return true;
}
else
{
if (address.equals(candidate))
return true;
}
}
return false;
}
protected boolean subnetMatch(String subnetAddress, String candidate)
{
Matcher matcher = CIDR_PATTERN.matcher(subnetAddress);
int subnet = intFromAddress(matcher);
int prefix = Integer.parseInt(matcher.group(5));
// Sets the most significant prefix bits to 1
// If prefix == 8 => 11111111_00000000_00000000_00000000
int mask = ~((1 << (32 - prefix)) - 1);
int ip = intFromAddress(IP_PATTERN.matcher(candidate));
return (ip & mask) == (subnet & mask);
}
private int intFromAddress(Matcher matcher)
{
int result = 0;
if (matcher.matches())
{
for (int i = 0; i < 4; ++i)
{
int b = Integer.parseInt(matcher.group(i + 1));
result |= b << 8 * (3 - i);
}
return result;
}
throw new IllegalStateException();
}
public void destroy()
{
_running = false;
_timerThread.interrupt();
synchronized (_requestTimeoutQ)
{
_requestTimeoutQ.cancelAll();
}
synchronized (_trackerTimeoutQ)
{
_trackerTimeoutQ.cancelAll();
}
_rateTrackers.clear();
_whitelist.clear();
}
@ -630,7 +659,7 @@ public class DoSFilter implements Filter
* Returns the user id, used to track this connection.
* This SHOULD be overridden by subclasses.
*
* @param request
* @param request the current request
* @return a unique user id, if logged in; otherwise null.
*/
protected String extractUserId(ServletRequest request)
@ -638,21 +667,6 @@ public class DoSFilter implements Filter
return null;
}
/* ------------------------------------------------------------ */
/**
* Initialize the IP address whitelist
*/
protected void initWhitelist()
{
_whitelist.clear();
StringTokenizer tokenizer = new StringTokenizer(_whitelistStr, ",");
while (tokenizer.hasMoreTokens())
_whitelist.add(tokenizer.nextToken().trim());
LOG.info("Whitelisted IP addresses: {}", _whitelist.toString());
}
/* ------------------------------------------------------------ */
/**
* Get maximum number of requests from a connection per
* second. Requests in excess of this are first delayed,
@ -665,7 +679,6 @@ public class DoSFilter implements Filter
return _maxRequestsPerSec;
}
/* ------------------------------------------------------------ */
/**
* Get maximum number of requests from a connection per
* second. Requests in excess of this are first delayed,
@ -678,7 +691,6 @@ public class DoSFilter implements Filter
_maxRequestsPerSec = value;
}
/* ------------------------------------------------------------ */
/**
* Get delay (in milliseconds) that is applied to all requests
* over the rate limit, before they are considered at all.
@ -688,7 +700,6 @@ public class DoSFilter implements Filter
return _delayMs;
}
/* ------------------------------------------------------------ */
/**
* Set delay (in milliseconds) that is applied to all requests
* over the rate limit, before they are considered at all.
@ -700,7 +711,6 @@ public class DoSFilter implements Filter
_delayMs = value;
}
/* ------------------------------------------------------------ */
/**
* Get maximum amount of time (in milliseconds) the filter will
* blocking wait for the throttle semaphore.
@ -712,7 +722,6 @@ public class DoSFilter implements Filter
return _maxWaitMs;
}
/* ------------------------------------------------------------ */
/**
* Set maximum amount of time (in milliseconds) the filter will
* blocking wait for the throttle semaphore.
@ -724,7 +733,6 @@ public class DoSFilter implements Filter
_maxWaitMs = value;
}
/* ------------------------------------------------------------ */
/**
* Get number of requests over the rate limit able to be
* considered at once.
@ -736,7 +744,6 @@ public class DoSFilter implements Filter
return _throttledRequests;
}
/* ------------------------------------------------------------ */
/**
* Set number of requests over the rate limit able to be
* considered at once.
@ -745,11 +752,11 @@ public class DoSFilter implements Filter
*/
public void setThrottledRequests(int value)
{
_passes = new Semaphore((value-_throttledRequests+_passes.availablePermits()), true);
int permits = _passes == null ? 0 : _passes.availablePermits();
_passes = new Semaphore((value - _throttledRequests + permits), true);
_throttledRequests = value;
}
/* ------------------------------------------------------------ */
/**
* Get amount of time (in milliseconds) to async wait for semaphore.
*
@ -760,7 +767,6 @@ public class DoSFilter implements Filter
return _throttleMs;
}
/* ------------------------------------------------------------ */
/**
* Set amount of time (in milliseconds) to async wait for semaphore.
*
@ -771,7 +777,6 @@ public class DoSFilter implements Filter
_throttleMs = value;
}
/* ------------------------------------------------------------ */
/**
* Get maximum amount of time (in milliseconds) to allow
* the request to process.
@ -783,7 +788,6 @@ public class DoSFilter implements Filter
return _maxRequestMs;
}
/* ------------------------------------------------------------ */
/**
* Set maximum amount of time (in milliseconds) to allow
* the request to process.
@ -795,7 +799,6 @@ public class DoSFilter implements Filter
_maxRequestMs = value;
}
/* ------------------------------------------------------------ */
/**
* Get maximum amount of time (in milliseconds) to keep track
* of request rates for a connection, before deciding that
@ -808,7 +811,6 @@ public class DoSFilter implements Filter
return _maxIdleTrackerMs;
}
/* ------------------------------------------------------------ */
/**
* Set maximum amount of time (in milliseconds) to keep track
* of request rates for a connection, before deciding that
@ -821,7 +823,6 @@ public class DoSFilter implements Filter
_maxIdleTrackerMs = value;
}
/* ------------------------------------------------------------ */
/**
* Check flag to insert the DoSFilter headers into the response.
*
@ -832,7 +833,6 @@ public class DoSFilter implements Filter
return _insertHeaders;
}
/* ------------------------------------------------------------ */
/**
* Set flag to insert the DoSFilter headers into the response.
*
@ -843,7 +843,6 @@ public class DoSFilter implements Filter
_insertHeaders = value;
}
/* ------------------------------------------------------------ */
/**
* Get flag to have usage rate tracked by session if a session exists.
*
@ -854,9 +853,9 @@ public class DoSFilter implements Filter
return _trackSessions;
}
/* ------------------------------------------------------------ */
/**
* Set flag to have usage rate tracked by session if a session exists.
*
* @param value value of the flag
*/
public void setTrackSessions(boolean value)
@ -864,7 +863,6 @@ public class DoSFilter implements Filter
_trackSessions = value;
}
/* ------------------------------------------------------------ */
/**
* Get flag to have usage rate tracked by IP+port (effectively connection)
* if session tracking is not used.
@ -876,8 +874,6 @@ public class DoSFilter implements Filter
return _remotePort;
}
/* ------------------------------------------------------------ */
/**
* Set flag to have usage rate tracked by IP+port (effectively connection)
* if session tracking is not used.
@ -889,7 +885,22 @@ public class DoSFilter implements Filter
_remotePort = value;
}
/* ------------------------------------------------------------ */
/**
* @return whether this filter is enabled
*/
public boolean isEnabled()
{
return _enabled;
}
/**
* @param enabled whether this filter is enabled
*/
public void setEnabled(boolean enabled)
{
_enabled = enabled;
}
/**
* Get a list of IP addresses that will not be rate limited.
*
@ -897,11 +908,17 @@ public class DoSFilter implements Filter
*/
public String getWhitelist()
{
return _whitelistStr;
StringBuilder result = new StringBuilder();
for (Iterator<String> iterator = _whitelist.iterator(); iterator.hasNext();)
{
String address = iterator.next();
result.append(address);
if (iterator.hasNext())
result.append(",");
}
return result.toString();
}
/* ------------------------------------------------------------ */
/**
* Set a list of IP addresses that will not be rate limited.
*
@ -909,8 +926,40 @@ public class DoSFilter implements Filter
*/
public void setWhitelist(String value)
{
_whitelistStr = value;
initWhitelist();
List<String> result = new ArrayList<String>();
for (String address : value.split(","))
addWhitelistAddress(result, address);
_whitelist.clear();
_whitelist.addAll(result);
LOG.debug("Whitelisted IP addresses: {}", result);
}
public void clearWhitelist()
{
_whitelist.clear();
}
public boolean addWhitelistAddress(String address)
{
return addWhitelistAddress(_whitelist, address);
}
private boolean addWhitelistAddress(List<String> list, String address)
{
address = address.trim();
if (address.length() > 0)
{
if (CIDR_PATTERN.matcher(address).matches() || IP_PATTERN.matcher(address).matches())
return list.add(address);
else
LOG.warn("Ignoring malformed whitelist IP address {}", address);
}
return false;
}
public boolean removeWhitelistAddress(String address)
{
return _whitelist.remove(address);
}
/**
@ -924,7 +973,6 @@ public class DoSFilter implements Filter
transient protected final long[] _timestamps;
transient protected int _next;
public RateTracker(String id, int type, int maxRequestsPerSecond)
{
_id = id;
@ -946,11 +994,9 @@ public class DoSFilter implements Filter
_next = (_next + 1) % _timestamps.length;
}
boolean exceeded=last!=0 && (now-last)<1000L;
return exceeded;
return last != 0 && (now - last) < 1000L;
}
public String getId()
{
return _id;
@ -961,29 +1007,27 @@ public class DoSFilter implements Filter
return _type;
}
public void valueBound(HttpSessionBindingEvent event)
{
if (LOG.isDebugEnabled())
LOG.debug("Value bound:"+_id);
LOG.debug("Value bound: {}", getId());
}
public void valueUnbound(HttpSessionBindingEvent event)
{
//take the tracker out of the list of trackers
if (_rateTrackers != null)
_rateTrackers.remove(_id);
if (LOG.isDebugEnabled()) LOG.debug("Tracker removed: "+_id);
if (LOG.isDebugEnabled())
LOG.debug("Tracker removed: {}", getId());
}
public void sessionWillPassivate(HttpSessionEvent se)
{
//take the tracker of the list of trackers (if its still there)
//and ensure that we take ourselves out of the session so we are not saved
if (_rateTrackers != null)
_rateTrackers.remove(_id);
se.getSession().removeAttribute(__TRACKER);
if (LOG.isDebugEnabled()) LOG.debug("Value removed: "+_id);
if (LOG.isDebugEnabled()) LOG.debug("Value removed: {}", getId());
}
public void sessionDidActivate(HttpSessionEvent se)
@ -991,10 +1035,7 @@ public class DoSFilter implements Filter
LOG.warn("Unexpected session activation");
}
public void expired()
{
if (_rateTrackers != null && _trackerTimeoutQ != null)
{
long now = _trackerTimeoutQ.getNow();
int latestIndex = _next == 0 ? (_timestamps.length - 1) : (_next - 1);
@ -1006,15 +1047,12 @@ public class DoSFilter implements Filter
else
_rateTrackers.remove(_id);
}
}
@Override
public String toString()
{
return "RateTracker/" + _id + "/" + _type;
}
}
class FixedRateTracker extends RateTracker

View File

@ -90,6 +90,8 @@ import org.eclipse.jetty.util.log.Logger;
* deflateNoWrap The noWrap setting for deflate compression. Defaults to true. (true/false)
* See: {@link java.util.zip.Deflater#Deflater(int, boolean)}
*
* methods Comma separated list of HTTP methods to compress. If not set, only GET requests are compressed.
*
* mimeTypes Comma separated list of mime types to compress. See description above.
*
* excludedAgents Comma separated list of user agents to exclude from compression. Does a
@ -127,6 +129,8 @@ public class GzipFilter extends UserAgentFilter
protected int _minGzipSize=256;
protected int _deflateCompressionLevel=Deflater.DEFAULT_COMPRESSION;
protected boolean _deflateNoWrap = true;
protected final Set<String> _methods=new HashSet<String>();
protected Set<String> _excludedAgents;
protected Set<Pattern> _excludedAgentPatterns;
protected Set<String> _excludedPaths;
@ -166,6 +170,16 @@ public class GzipFilter extends UserAgentFilter
if (tmp!=null)
_deflateNoWrap=Boolean.parseBoolean(tmp);
tmp=filterConfig.getInitParameter("methods");
if (tmp!=null)
{
StringTokenizer tok = new StringTokenizer(tmp,",",false);
while (tok.hasMoreTokens())
_methods.add(tok.nextToken().trim().toUpperCase());
}
else
_methods.add(HttpMethods.GET);
tmp=filterConfig.getInitParameter("mimeTypes");
if (tmp!=null)
{
@ -235,9 +249,9 @@ public class GzipFilter extends UserAgentFilter
HttpServletRequest request=(HttpServletRequest)req;
HttpServletResponse response=(HttpServletResponse)res;
// If not a GET or an Excluded URI - no Vary because no matter what client, this URI is always excluded
// If not a supported method or it is an Excluded URI - no Vary because no matter what client, this URI is always excluded
String requestURI = request.getRequestURI();
if (!HttpMethods.GET.equalsIgnoreCase(request.getMethod()) || isExcludedPath(requestURI))
if (!_methods.contains(request.getMethod()) || isExcludedPath(requestURI))
{
super.doFilter(request,response,chain);
return;

View File

@ -581,6 +581,12 @@ public class ProxyServlet implements Servlet
String hdr = (String)enm.nextElement();
String lhdr = hdr.toLowerCase();
if ("transfer-encoding".equals(lhdr))
{
if (request.getHeader("transfer-encoding").indexOf("chunk")>=0)
hasContent = true;
}
if (_DontProxyHeaders.contains(lhdr))
continue;
if (connectionHdr != null && connectionHdr.indexOf(lhdr) >= 0)

View File

@ -9,4 +9,10 @@ maxIdleTrackerMs: maximum amount of time (in milliseconds) to keep track of requ
insertHeaders: insert the DoSFilter headers into the response.
trackSessions: usage rate is tracked by session if a session exists.
remotePort: usage rate is tracked by IP+port (effectively connection) if session tracking is not used.
ipWhitelist: list of IP addresses that will not be rate limited.
enabled: whether this filter is enabled
whitelist: comma separated list of IP addresses that will not be rate limited.
clearWhitelist(): clears the list of IP addresses that will not be rate limited.
addWhitelistAddress(java.lang.String):ACTION: adds an IP address that will not be rate limited.
addWhitelistAddress(java.lang.String)[0]:address: the IP address that will not be rate limited.
removeWhitelistAddress(java.lang.String):ACTION: removes an IP address that will not be rate limited.
removeWhitelistAddress(java.lang.String)[0]:address: the IP address that will not be rate limited.

View File

@ -0,0 +1,88 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.servlets;
import java.lang.management.ManagementFactory;
import java.util.EnumSet;
import java.util.Set;
import javax.management.Attribute;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.DispatcherType;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.junit.Assert;
import org.junit.Test;
public class DoSFilterJMXTest
{
@Test
public void testDoSFilterJMX() throws Exception
{
Server server = new Server();
Connector connector = new SelectChannelConnector();
connector.setPort(0);
server.addConnector(connector);
ServletContextHandler context = new ServletContextHandler(server, "/", ServletContextHandler.SESSIONS);
DoSFilter filter = new DoSFilter();
FilterHolder holder = new FilterHolder(filter);
String name = "dos";
holder.setName(name);
holder.setInitParameter(DoSFilter.MANAGED_ATTR_INIT_PARAM, "true");
context.addFilter(holder, "/*", EnumSet.of(DispatcherType.REQUEST));
context.setInitParameter(ServletContextHandler.MANAGED_ATTRIBUTES, name);
MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
MBeanContainer mbeanContainer = new MBeanContainer(mbeanServer);
server.addBean(mbeanContainer);
server.getContainer().addEventListener(mbeanContainer);
server.start();
String domain = DoSFilter.class.getPackage().getName();
Set<ObjectName> mbeanNames = mbeanServer.queryNames(ObjectName.getInstance(domain + ":*"), null);
Assert.assertEquals(1, mbeanNames.size());
ObjectName objectName = mbeanNames.iterator().next();
boolean value = (Boolean)mbeanServer.getAttribute(objectName, "enabled");
mbeanServer.setAttribute(objectName, new Attribute("enabled", !value));
Assert.assertEquals(!value, filter.isEnabled());
String whitelist = (String)mbeanServer.getAttribute(objectName, "whitelist");
String address = "127.0.0.1";
Assert.assertFalse(whitelist.contains(address));
boolean result = (Boolean)mbeanServer.invoke(objectName, "addWhitelistAddress", new Object[]{address}, new String[]{String.class.getName()});
Assert.assertTrue(result);
whitelist = (String)mbeanServer.getAttribute(objectName, "whitelist");
Assert.assertTrue(whitelist.contains(address));
result = (Boolean)mbeanServer.invoke(objectName, "removeWhitelistAddress", new Object[]{address}, new String[]{String.class.getName()});
Assert.assertTrue(result);
whitelist = (String)mbeanServer.getAttribute(objectName, "whitelist");
Assert.assertFalse(whitelist.contains(address));
server.stop();
}
}

View File

@ -18,18 +18,21 @@
package org.eclipse.jetty.servlets;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.servlets.DoSFilter.RateTracker;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class DoSFilterTest extends AbstractDoSFilterTest
{
private static final Logger LOG = Log.getLogger(DoSFilterTest.class);
@ -69,6 +72,21 @@ public class DoSFilterTest extends AbstractDoSFilterTest
assertFalse("Should not exceed as we sleep 300s for each hit and thus do less than 4 hits/s",exceeded);
}
@Test
public void testWhitelist() throws Exception
{
DoSFilter filter = new DoSFilter();
List<String> whitelist = new ArrayList<String>();
whitelist.add("192.168.0.1");
whitelist.add("10.0.0.0/8");
Assert.assertTrue(filter.checkWhitelist(whitelist, "192.168.0.1"));
Assert.assertFalse(filter.checkWhitelist(whitelist, "192.168.0.2"));
Assert.assertFalse(filter.checkWhitelist(whitelist, "11.12.13.14"));
Assert.assertTrue(filter.checkWhitelist(whitelist, "10.11.12.13"));
Assert.assertTrue(filter.checkWhitelist(whitelist, "10.0.0.0"));
Assert.assertFalse(filter.checkWhitelist(whitelist, "0.0.0.0"));
}
private boolean hitRateTracker(DoSFilter doSFilter, int sleep) throws InterruptedException
{
boolean exceeded = false;

View File

@ -111,7 +111,7 @@ public class GzipFilterContentLengthTest
try
{
tester.start();
tester.assertIsResponseGzipCompressed(testfile.getName());
tester.assertIsResponseGzipCompressed("GET",testfile.getName());
}
finally
{
@ -131,7 +131,7 @@ public class GzipFilterContentLengthTest
try
{
tester.start();
tester.assertIsResponseNotGzipCompressed(testfile.getName(),filesize,HttpStatus.OK_200);
tester.assertIsResponseNotGzipCompressed("GET",testfile.getName(),filesize,HttpStatus.OK_200);
}
finally
{

View File

@ -103,6 +103,50 @@ public class GzipFilterDefaultTest
@Rule
public TestingDir testingdir = new TestingDir();
@Test
public void testIsGzipByMethod() throws Exception
{
GzipTester tester = new GzipTester(testingdir, compressionType);
// Test content that is smaller than the buffer.
int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE * 2;
tester.prepareServerFile("file.txt",filesize);
FilterHolder holder = tester.setContentServlet(GetServlet.class);
holder.setInitParameter("mimeTypes","text/plain");
holder.setInitParameter("methods","POST,WIBBLE");
try
{
tester.start();
tester.assertIsResponseGzipCompressed("POST","file.txt");
tester.assertIsResponseGzipCompressed("WIBBLE","file.txt");
tester.assertIsResponseNotGzipCompressed("GET","file.txt",filesize,200);
}
finally
{
tester.stop();
}
}
public static class GetServlet extends DefaultServlet
{
public GetServlet()
{
super();
}
@Override
public void service(HttpServletRequest req, HttpServletResponse resp) throws IOException,ServletException
{
doGet(req,resp);
}
}
@Test
public void testIsGzipCompressedTiny() throws Exception
{
@ -118,7 +162,7 @@ public class GzipFilterDefaultTest
try
{
tester.start();
HttpTester http = tester.assertIsResponseGzipCompressed("file.txt");
HttpTester http = tester.assertIsResponseGzipCompressed("GET","file.txt");
Assert.assertEquals("Accept-Encoding",http.getHeader("Vary"));
}
finally
@ -142,7 +186,7 @@ public class GzipFilterDefaultTest
try
{
tester.start();
HttpTester http = tester.assertIsResponseGzipCompressed("file.txt");
HttpTester http = tester.assertIsResponseGzipCompressed("GET","file.txt");
Assert.assertEquals("Accept-Encoding",http.getHeader("Vary"));
}
finally
@ -166,7 +210,7 @@ public class GzipFilterDefaultTest
try
{
tester.start();
HttpTester http = tester.assertIsResponseGzipCompressed("file.txt");
HttpTester http = tester.assertIsResponseGzipCompressed("GET","file.txt");
Assert.assertEquals("Accept-Encoding",http.getHeader("Vary"));
}
finally
@ -190,7 +234,7 @@ public class GzipFilterDefaultTest
try
{
tester.start();
HttpTester http = tester.assertIsResponseGzipCompressed("file.txt");
HttpTester http = tester.assertIsResponseGzipCompressed("GET","file.txt");
Assert.assertEquals("Accept-Encoding",http.getHeader("Vary"));
}
finally
@ -213,7 +257,7 @@ public class GzipFilterDefaultTest
try
{
tester.start();
HttpTester http = tester.assertIsResponseNotGzipCompressed("file.txt", filesize, HttpStatus.OK_200);
HttpTester http = tester.assertIsResponseNotGzipCompressed("GET","file.txt", filesize, HttpStatus.OK_200);
Assert.assertEquals("Accept-Encoding",http.getHeader("Vary"));
}
finally
@ -236,7 +280,7 @@ public class GzipFilterDefaultTest
try
{
tester.start();
HttpTester http = tester.assertIsResponseNotGzipCompressed("file.mp3", filesize, HttpStatus.OK_200);
HttpTester http = tester.assertIsResponseNotGzipCompressed("GET","file.mp3", filesize, HttpStatus.OK_200);
Assert.assertNull(http.getHeader("Vary"));
}
finally
@ -257,7 +301,7 @@ public class GzipFilterDefaultTest
try
{
tester.start();
tester.assertIsResponseNotGzipCompressed(-1, 204);
tester.assertIsResponseNotGzipCompressed("GET",-1, 204);
}
finally
{
@ -278,7 +322,7 @@ public class GzipFilterDefaultTest
try
{
tester.start();
tester.assertIsResponseNotGzipCompressedAndEqualToExpectedString("error message", -1, 400);
tester.assertIsResponseNotGzipCompressedAndEqualToExpectedString("GET","error message", -1, 400);
}
finally
{
@ -302,7 +346,7 @@ public class GzipFilterDefaultTest
try
{
tester.start();
tester.assertIsResponseNotGzipCompressed("file.txt",filesize,HttpStatus.OK_200);
tester.assertIsResponseNotGzipCompressed("GET","file.txt",filesize,HttpStatus.OK_200);
}
finally
{
@ -326,7 +370,7 @@ public class GzipFilterDefaultTest
try
{
tester.start();
tester.assertIsResponseNotGzipCompressed("file.txt",filesize,HttpStatus.OK_200);
tester.assertIsResponseNotGzipCompressed("GET","file.txt",filesize,HttpStatus.OK_200);
}
finally
{
@ -348,7 +392,7 @@ public class GzipFilterDefaultTest
try
{
tester.start();
tester.assertIsResponseNotGzipCompressed("file.txt",filesize,HttpStatus.OK_200);
tester.assertIsResponseNotGzipCompressed("GET","file.txt",filesize,HttpStatus.OK_200);
}
finally
{
@ -370,7 +414,7 @@ public class GzipFilterDefaultTest
try
{
tester.start();
tester.assertIsResponseNotGzipCompressed("file.txt",filesize,HttpStatus.OK_200);
tester.assertIsResponseNotGzipCompressed("GET","file.txt",filesize,HttpStatus.OK_200);
}
finally
{

View File

@ -107,7 +107,7 @@ public class IncludableGzipFilterMinSizeTest
try {
tester.start();
tester.assertIsResponseGzipCompressed("big_script.js");
tester.assertIsResponseGzipCompressed("GET","big_script.js");
} finally {
tester.stop();
}

View File

@ -598,7 +598,7 @@ public class MultipartFilterTest
response.parse(tester.getResponses(request.generate()));
assertTrue(response.getMethod()==null);
assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, response.getStatus());
assertTrue(response.getReason().startsWith("Missing content"));
assertTrue(response.getContent().indexOf("Missing content")>=0);
}
@Test
@ -621,7 +621,7 @@ public class MultipartFilterTest
response.parse(tester.getResponses(request.generate()));
assertTrue(response.getMethod()==null);
assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, response.getStatus());
assertTrue(response.getReason().startsWith("Missing initial"));
assertTrue(response.getContent().indexOf("Missing initial")>=0);
}
@ -645,7 +645,7 @@ public class MultipartFilterTest
response.parse(tester.getResponses(request.generate()));
assertTrue(response.getMethod()==null);
assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, response.getStatus());
assertTrue(response.getReason().startsWith("Missing initial"));
assertTrue(response.getContent().indexOf("Missing initial")>=0);
}
@Test

View File

@ -32,6 +32,8 @@ import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
import org.hamcrest.core.Is;
import org.hamcrest.core.IsEqual;
import org.junit.After;
@ -44,6 +46,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.MalformedURLException;
import java.net.Socket;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@ -239,4 +242,48 @@ public class ProxyServletTest
exchange.waitForDone();
assertThat(excepted.get(),equalTo(true));
}
@Test
public void testChunkedPut() throws Exception
{
init(new HttpServlet()
{
@Override
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
resp.setContentType("text/plain");
String message=IO.toString(req.getInputStream());
resp.getOutputStream().print(message);
}
});
Socket client = new Socket("localhost",_connector.getLocalPort());
client.setSoTimeout(1000000);
client.getOutputStream().write((
"PUT /proxy/test HTTP/1.1\r\n"+
"Host: localhost:"+_connector.getLocalPort()+"\r\n"+
"Transfer-Encoding: chunked\r\n"+
"Connection: close\r\n"+
"\r\n"+
"A\r\n"+
"0123456789\r\n"+
"9\r\n"+
"ABCDEFGHI\r\n"+
"8\r\n"+
"JKLMNOPQ\r\n"+
"7\r\n"+
"RSTUVWX\r\n"+
"2\r\n"+
"YZ\r\n"+
"0\r\n"
).getBytes(StringUtil.__ISO_8859_1));
String response=IO.toString(client.getInputStream());
Assert.assertTrue(response.contains("200 OK"));
Assert.assertTrue(response.contains("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
}
}

View File

@ -73,18 +73,18 @@ public class GzipTester
// DOES NOT WORK IN WINDOWS - this.testdir.ensureEmpty();
}
public HttpTester assertIsResponseGzipCompressed(String filename) throws Exception
public HttpTester assertIsResponseGzipCompressed(String method,String filename) throws Exception
{
return assertIsResponseGzipCompressed(filename,filename);
return assertIsResponseGzipCompressed(method,filename,filename);
}
public HttpTester assertIsResponseGzipCompressed(String requestedFilename, String serverFilename) throws Exception
public HttpTester assertIsResponseGzipCompressed(String method,String requestedFilename, String serverFilename) throws Exception
{
System.err.printf("[GzipTester] requesting /context/%s%n",requestedFilename);
HttpTester request = new HttpTester();
HttpTester response = new HttpTester();
request.setMethod("GET");
request.setMethod(method);
request.setVersion("HTTP/1.0");
request.setHeader("Host","tester");
request.setHeader("Accept-Encoding",compressionType);
@ -245,10 +245,10 @@ public class GzipTester
* passing -1 will disable the Content-Length assertion)
* @throws Exception
*/
public HttpTester assertIsResponseNotGzipCompressed(String filename, int expectedFilesize, int status) throws Exception
public HttpTester assertIsResponseNotGzipCompressed(String method,String filename, int expectedFilesize, int status) throws Exception
{
String uri = "/context/"+filename;
HttpTester response = executeRequest(uri);
HttpTester response = executeRequest(method,uri);
assertResponseHeaders(expectedFilesize,status,response);
// Assert that the contents are what we expect.
@ -276,10 +276,10 @@ public class GzipTester
* passing -1 will disable the Content-Length assertion)
* @throws Exception
*/
public void assertIsResponseNotGzipCompressedAndEqualToExpectedString(String expectedResponse, int expectedFilesize, int status) throws Exception
public void assertIsResponseNotGzipCompressedAndEqualToExpectedString(String method,String expectedResponse, int expectedFilesize, int status) throws Exception
{
String uri = "/context/";
HttpTester response = executeRequest(uri);
HttpTester response = executeRequest(method,uri);
assertResponseHeaders(expectedFilesize,status,response);
String actual = readResponse(response);
@ -295,10 +295,10 @@ public class GzipTester
* passing -1 will disable the Content-Length assertion)
* @throws Exception
*/
public void assertIsResponseNotGzipCompressed(int expectedFilesize, int status) throws Exception
public void assertIsResponseNotGzipCompressed(String method,int expectedFilesize, int status) throws Exception
{
String uri = "/context/";
HttpTester response = executeRequest(uri);
HttpTester response = executeRequest(method, uri);
assertResponseHeaders(expectedFilesize,status,response);
}
@ -315,13 +315,13 @@ public class GzipTester
}
}
private HttpTester executeRequest(String uri) throws IOException, Exception
private HttpTester executeRequest(String method,String uri) throws IOException, Exception
{
System.err.printf("[GzipTester] requesting %s%n",uri);
HttpTester request = new HttpTester();
HttpTester response = new HttpTester();
request.setMethod("GET");
request.setMethod(method);
request.setVersion("HTTP/1.0");
request.setHeader("Host","tester");
request.setHeader("Accept-Encoding",compressionType);

View File

@ -336,10 +336,20 @@ public class UrlEncoded extends MultiMap implements Cloneable
i++;
if (i+4<end)
buffer.getStringBuilder().append(Character.toChars((convertHexDigit(raw[++i])<<12) +(convertHexDigit(raw[++i])<<8) + (convertHexDigit(raw[++i])<<4) +convertHexDigit(raw[++i])));
else
{
buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT);
i=end;
}
}
else
buffer.append((byte)((convertHexDigit(raw[++i])<<4) + convertHexDigit(raw[++i])));
}
else
{
buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT);
i=end;
}
break;
default:
@ -356,13 +366,13 @@ public class UrlEncoded extends MultiMap implements Cloneable
if (key != null)
{
value = buffer.length()==0?"":buffer.toString();
value = buffer.length()==0?"":buffer.toReplacedString();
buffer.reset();
map.add(key,value);
}
else if (buffer.length()>0)
{
map.add(buffer.toString(),"");
map.add(buffer.toReplacedString(),"");
}
}
}
@ -763,7 +773,10 @@ public class UrlEncoded extends MultiMap implements Cloneable
buffer.getStringBuffer().append(unicode);
}
else
{
i=length;
buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT);
}
}
else
{
@ -773,14 +786,23 @@ public class UrlEncoded extends MultiMap implements Cloneable
buffer.append(b);
}
}
catch(NotUtf8Exception e)
{
LOG.warn(e.toString());
LOG.debug(e);
}
catch(NumberFormatException nfe)
{
LOG.debug(nfe);
buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT);
}
}
else
{
buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT);
i=length;
}
}
else if (buffer!=null)
buffer.getStringBuffer().append(c);
}
@ -792,7 +814,7 @@ public class UrlEncoded extends MultiMap implements Cloneable
return encoded.substring(offset,offset+length);
}
return buffer.toString();
return buffer.toReplacedString();
}
else
{
@ -842,6 +864,8 @@ public class UrlEncoded extends MultiMap implements Cloneable
try
{
if ('u'==encoded.charAt(offset+i+1))
{
if (i+6<length)
{
int o=offset+i+2;
i+=6;
@ -851,6 +875,12 @@ public class UrlEncoded extends MultiMap implements Cloneable
n+=reencoded.length;
}
else
{
ba[n++] = (byte)'?';
i=length;
}
}
else
{
int o=offset+i+1;
i+=3;
@ -866,8 +896,8 @@ public class UrlEncoded extends MultiMap implements Cloneable
}
else
{
ba[n++] = (byte)'%';
i++;
ba[n++] = (byte)'?';
i=length;
}
}
else if (c=='+')

View File

@ -20,6 +20,9 @@ package org.eclipse.jetty.util;
import java.io.IOException;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */
/**
* Utf8 Appendable abstract base class
@ -46,6 +49,7 @@ import java.io.IOException;
**/
public abstract class Utf8Appendable
{
protected static final Logger LOG = Log.getLogger(Utf8Appendable.class);
public static final char REPLACEMENT = '\ufffd';
private static final int UTF8_ACCEPT = 0;
private static final int UTF8_REJECT = 12;
@ -192,4 +196,43 @@ public abstract class Utf8Appendable
super("Not valid UTF8! "+reason);
}
}
protected void checkState()
{
if (!isUtf8SequenceComplete())
{
_codep=0;
_state = UTF8_ACCEPT;
try
{
_appendable.append(REPLACEMENT);
}
catch(IOException e)
{
throw new RuntimeException(e);
}
throw new NotUtf8Exception("incomplete UTF8 sequence");
}
}
public String toReplacedString()
{
if (!isUtf8SequenceComplete())
{
_codep=0;
_state = UTF8_ACCEPT;
try
{
_appendable.append(REPLACEMENT);
}
catch(IOException e)
{
throw new RuntimeException(e);
}
Throwable th= new NotUtf8Exception("incomplete UTF8 sequence");
LOG.warn(th.toString());
LOG.debug(th);
}
return _appendable.toString();
}
}

View File

@ -72,10 +72,4 @@ public class Utf8StringBuffer extends Utf8Appendable
checkState();
return _buffer.toString();
}
private void checkState()
{
if (!isUtf8SequenceComplete())
throw new IllegalArgumentException("Tried to read incomplete UTF8 decoded String");
}
}

View File

@ -74,9 +74,5 @@ public class Utf8StringBuilder extends Utf8Appendable
return _buffer.toString();
}
private void checkState()
{
if (!isUtf8SequenceComplete())
throw new IllegalArgumentException("Tried to read incomplete UTF8 decoded String");
}
}

View File

@ -65,6 +65,19 @@ class JarFileResource extends JarResource
_list=null;
_entry=null;
_file=null;
if ( _jarFile != null )
{
try
{
_jarFile.close();
}
catch ( IOException ioe )
{
LOG.ignore(ioe);
}
}
_jarFile=null;
super.release();
}
@ -166,7 +179,7 @@ class JarFileResource extends JarResource
if (jarFile!=null && _entry==null && !_directory)
{
// OK - we have a JarFile, lets look at the entries for our path
Enumeration e=jarFile.entries();
Enumeration<JarEntry> e=jarFile.entries();
while(e.hasMoreElements())
{
JarEntry entry = (JarEntry) e.nextElement();
@ -301,12 +314,11 @@ class JarFileResource extends JarResource
}
}
Enumeration e=jarFile.entries();
Enumeration<JarEntry> e=jarFile.entries();
String dir=_urlString.substring(_urlString.indexOf("!/")+2);
while(e.hasMoreElements())
{
JarEntry entry = (JarEntry) e.nextElement();
JarEntry entry = e.nextElement();
String name=entry.getName().replace('\\','/');
if(!name.startsWith(dir) || name.length()==dir.length())
{

View File

@ -22,6 +22,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.jetty.util.component.Destroyable;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -79,7 +80,7 @@ public class ShutdownThread extends Thread
catch(Exception e)
{
LOG.ignore(e);
LOG.info("shutdown already commenced");
LOG.debug("shutdown already commenced");
}
}
@ -131,6 +132,12 @@ public class ShutdownThread extends Thread
lifeCycle.stop();
LOG.debug("Stopped {}",lifeCycle);
}
if (lifeCycle instanceof Destroyable)
{
((Destroyable)lifeCycle).destroy();
LOG.debug("Destroyed {}",lifeCycle);
}
}
catch (Exception ex)
{

View File

@ -159,11 +159,38 @@ public class URLEncodedTest
public void testBadEncoding() throws UnsupportedEncodingException
{
UrlEncoded url_encoded = new UrlEncoded();
url_encoded.decode("Name15=xx%zz", "UTF-8");
url_encoded.decode("Name15=xx%zzyy", "UTF-8");
assertEquals("encoded param size",1, url_encoded.size());
assertEquals("encoded get", "xx\ufffd", url_encoded.getString("Name15"));
assertEquals("encoded get", "xx\ufffdyy", url_encoded.getString("Name15"));
assertEquals("xxx",UrlEncoded.decodeString("xxx%u123",0,5,"UTF-8"));
byte[] bad="Name=%FF%FF%FF".getBytes("UTF-8");
MultiMap<String> map = new MultiMap<String>();
UrlEncoded.decodeUtf8To(bad,0,bad.length,map);
assertEquals("encoded param size",1, map.size());
assertEquals("encoded get", "\ufffd\ufffd\ufffd", map.getString("Name"));
url_encoded.clear();
url_encoded.decode("Name=%FF%FF%FF", "UTF-8");
assertEquals("encoded param size",1, url_encoded.size());
assertEquals("encoded get", "\ufffd\ufffd\ufffd", url_encoded.getString("Name"));
url_encoded.clear();
url_encoded.decode("Name=%EF%EF%EF", "UTF-8");
assertEquals("encoded param size",1, url_encoded.size());
assertEquals("encoded get", "\ufffd\ufffd", url_encoded.getString("Name"));
assertEquals("x",UrlEncoded.decodeString("x",0,1,"UTF-8"));
assertEquals("x\ufffd",UrlEncoded.decodeString("x%",0,2,"UTF-8"));
assertEquals("x\ufffd",UrlEncoded.decodeString("x%2",0,3,"UTF-8"));
assertEquals("x ",UrlEncoded.decodeString("x%20",0,4,"UTF-8"));
assertEquals("xxx",UrlEncoded.decodeString("xxx",0,3,"UTF-8"));
assertEquals("xxx\ufffd",UrlEncoded.decodeString("xxx%",0,4,"UTF-8"));
assertEquals("xxx\ufffd",UrlEncoded.decodeString("xxx%u",0,5,"UTF-8"));
assertEquals("xxx\ufffd",UrlEncoded.decodeString("xxx%u1",0,6,"UTF-8"));
assertEquals("xxx\ufffd",UrlEncoded.decodeString("xxx%u12",0,7,"UTF-8"));
assertEquals("xxx\ufffd",UrlEncoded.decodeString("xxx%u123",0,8,"UTF-8"));
assertEquals("xxx\u1234",UrlEncoded.decodeString("xxx%u1234",0,9,"UTF-8"));
}

View File

@ -35,8 +35,11 @@ import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.TimeZone;
import java.util.jar.JarFile;
import java.util.zip.ZipFile;
import junit.framework.Assert;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.OS;
import org.eclipse.jetty.util.IO;
@ -125,14 +128,14 @@ public class ResourceTest
URI uri = file.toURI();
__userURL=uri.toURL();
__userURL = new URL(__userURL.toString() + "src/test/java/org/eclipse/jetty/util/resource/");
__userURL = MavenTestingUtils.getTestResourcesDir().toURI().toURL();
FilePermission perm = (FilePermission) __userURL.openConnection().getPermission();
__userDir = new File(perm.getName()).getCanonicalPath() + File.separatorChar;
__relDir = "src/test/java/org/eclipse/jetty/util/resource/".replace('/', File.separatorChar);
__relDir = "src/test/resources/".replace('/', File.separatorChar);
System.err.println("User Dir="+__userDir);
System.err.println("Rel Dir="+__relDir);
System.err.println("User URL="+__userURL);
//System.err.println("User Dir="+__userDir);
//System.err.println("Rel Dir="+__relDir);
//System.err.println("User URL="+__userURL);
tmpFile=File.createTempFile("test",null).getCanonicalFile();
tmpFile.deleteOnExit();
@ -146,15 +149,15 @@ public class ResourceTest
data[i++]=new Data(__userURL,EXISTS,DIR);
data[i++]=new Data(__userDir,EXISTS,DIR);
data[i++]=new Data(__relDir,EXISTS,DIR);
data[i++]=new Data(__userURL+"ResourceTest.java",EXISTS,!DIR);
data[i++]=new Data(__userDir+"ResourceTest.java",EXISTS,!DIR);
data[i++]=new Data(__relDir+"ResourceTest.java",EXISTS,!DIR);
data[i++]=new Data(__userURL+"jetty-logging.properties",EXISTS,!DIR);
data[i++]=new Data(__userDir+"jetty-logging.properties",EXISTS,!DIR);
data[i++]=new Data(__relDir+"jetty-logging.properties",EXISTS,!DIR);
data[i++]=new Data(__userURL+"NoName.txt",!EXISTS,!DIR);
data[i++]=new Data(__userDir+"NoName.txt",!EXISTS,!DIR);
data[i++]=new Data(__relDir+"NoName.txt",!EXISTS,!DIR);
data[i++]=new Data(data[rt],"ResourceTest.java",EXISTS,!DIR);
data[i++]=new Data(data[rt],"/ResourceTest.java",EXISTS,!DIR);
data[i++]=new Data(data[rt],"jetty-logging.properties",EXISTS,!DIR);
data[i++]=new Data(data[rt],"/jetty-logging.properties",EXISTS,!DIR);
data[i++]=new Data(data[rt],"NoName.txt",!EXISTS,!DIR);
data[i++]=new Data(data[rt],"/NoName.txt",!EXISTS,!DIR);
@ -327,8 +330,7 @@ public class ResourceTest
{
String s = "jar:"+__userURL+"TestData/test.zip!/subdir/numbers";
// TODO move this into src/test/resources!!!
ZipFile zf = new ZipFile(MavenTestingUtils.getProjectFile("src/test/java/org/eclipse/jetty/util/resource/TestData/test.zip"));
ZipFile zf = new ZipFile(MavenTestingUtils.getTestResourceFile("TestData/test.zip"));
long last = zf.getEntry("subdir/numbers").getTime();

View File

@ -0,0 +1,3 @@
Manifest-Version: 1.0
Created-By: 1.2.2 (Sun Microsystems Inc.)

View File

@ -0,0 +1 @@
ABCDEFGHIJKLMNOPQRSTUVWXYZ

View File

@ -0,0 +1 @@
1234567890

View File

@ -0,0 +1 @@
ABCDEFGHIJKLMNOPQRSTUVWXYZ

View File

@ -0,0 +1 @@
1234567890

View File

@ -0,0 +1 @@
ABCDEFGHIJKLMNOPQRSTUVWXYZ

View File

@ -0,0 +1 @@
1234567890

View File

@ -60,10 +60,14 @@ public abstract class Descriptor
if (_root == null)
{
//boolean oldValidating = _processor.getParser().getValidating();
//_processor.getParser().setValidating(_validating);
_root = _parser.parse(_xml.getURL().toString());
//_processor.getParser().setValidating(oldValidating);
try
{
_root = _parser.parse(_xml.getInputStream());
}
finally
{
_xml.release();
}
}
}

View File

@ -39,7 +39,9 @@ import org.eclipse.jetty.server.DispatcherType;
import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.FilterMapping;
import org.eclipse.jetty.servlet.JspPropertyGroupServlet;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.ServletMapping;
import org.eclipse.jetty.util.LazyList;
@ -866,23 +868,20 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
if (paths.size() > 0)
{
String jspName = "jsp";
Map.Entry entry = context.getServletHandler().getHolderEntry("test.jsp");
if (entry != null)
ServletHandler handler = context.getServletHandler();
ServletHolder jsp_pg_servlet = handler.getServlet(JspPropertyGroupServlet.NAME);
if (jsp_pg_servlet==null)
{
ServletHolder holder = (ServletHolder) entry.getValue();
jspName = holder.getName();
jsp_pg_servlet=new ServletHolder(JspPropertyGroupServlet.NAME,new JspPropertyGroupServlet(context,handler));
handler.addServlet(jsp_pg_servlet);
}
if (jspName != null)
{
ServletMapping mapping = new ServletMapping();
mapping.setServletName(jspName);
mapping.setServletName(JspPropertyGroupServlet.NAME);
mapping.setPathSpecs(paths.toArray(new String[paths.size()]));
context.getServletHandler().addServletMapping(mapping);
}
}
}
/**
* @param context

View File

@ -109,6 +109,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
"org.eclipse.jetty.plus.jaas.", // webapp cannot change jaas classes
"org.eclipse.jetty.websocket.WebSocket", // WebSocket is a jetty extension
"org.eclipse.jetty.websocket.WebSocketFactory", // WebSocket is a jetty extension
"org.eclipse.jetty.websocket.WebSocketServlet", // webapp cannot change WebSocketServlet
"org.eclipse.jetty.servlet.DefaultServlet" // webapp cannot change default servlets
} ;
@ -123,6 +124,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
"-org.eclipse.jetty.plus.jaas.", // don't hide jaas classes
"-org.eclipse.jetty.websocket.WebSocket", // WebSocket is a jetty extension
"-org.eclipse.jetty.websocket.WebSocketFactory", // WebSocket is a jetty extension
"-org.eclipse.jetty.websocket.WebSocketServlet", // don't hide WebSocketServlet
"-org.eclipse.jetty.servlet.DefaultServlet", // don't hide default servlet
"-org.eclipse.jetty.servlet.listener.", // don't hide useful listeners
"org.eclipse.jetty." // hide other jetty classes

View File

@ -230,6 +230,8 @@ public class WebSocketFactory extends AbstractLifeCycle
// Old pre-RFC version specifications (header not present in RFC-6455)
draft = request.getIntHeader("Sec-WebSocket-Draft");
}
// Remember requested version for possible error message later
int requestedVersion = draft;
AbstractHttpConnection http = AbstractHttpConnection.getCurrentConnection();
if (http instanceof BlockingHttpConnection)
throw new IllegalStateException("Websockets not supported on blocking connectors");
@ -252,7 +254,7 @@ public class WebSocketFactory extends AbstractLifeCycle
draft=Integer.MAX_VALUE;
switch (draft)
{
case -1: // unspecified draft/version
case -1: // unspecified draft/version (such as early OSX Safari 5.1 and iOS 5.x)
case 0: // Old school draft/version
{
connection = new WebSocketServletConnectionD00(this, websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol);
@ -283,7 +285,6 @@ public class WebSocketFactory extends AbstractLifeCycle
}
default:
{
LOG.warn("Unsupported Websocket version: " + draft);
// Per RFC 6455 - 4.4 - Supporting Multiple Versions of WebSocket Protocol
// Using the examples as outlined
String versions="13";
@ -295,7 +296,20 @@ public class WebSocketFactory extends AbstractLifeCycle
versions+=", 0";
response.setHeader("Sec-WebSocket-Version", versions);
throw new HttpException(400, "Unsupported websocket version specification: " + draft);
// Make error clear for developer / end-user
StringBuilder err = new StringBuilder();
err.append("Unsupported websocket client version specification ");
if(requestedVersion >= 0) {
err.append("[").append(requestedVersion).append("]");
} else {
err.append("<Unspecified, likely a pre-draft version of websocket>");
}
err.append(", configured minVersion [").append(_minVersion).append("]");
err.append(", reported supported versions [").append(versions).append("]");
LOG.warn(err.toString()); // Log it
// use spec language for unsupported versions
throw new HttpException(400, "Unsupported websocket version specification"); // Tell client
}
}

View File

@ -0,0 +1,116 @@
//
// ========================================================================
// 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;
import static org.hamcrest.Matchers.*;
import java.net.URI;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.websocket.helper.CaptureSocket;
import org.eclipse.jetty.websocket.helper.SafariD00;
import org.eclipse.jetty.websocket.helper.WebSocketCaptureServlet;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
public class WebSocketMinVersionTest
{
private Server server;
private WebSocketCaptureServlet servlet;
private URI serverUri;
@BeforeClass
public static void initLogging()
{
// Configure Logging
// System.setProperty("org.eclipse.jetty.util.log.class",StdErrLog.class.getName());
System.setProperty("org.eclipse.jetty.websocket.helper.LEVEL","DEBUG");
}
@Before
public void startServer() throws Exception
{
// Configure Server
server = new Server(0);
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
server.setHandler(context);
// Serve capture servlet
servlet = new WebSocketCaptureServlet();
ServletHolder holder = new ServletHolder(servlet);
holder.setInitParameter("minVersion","8");
context.addServlet(holder,"/");
// Start Server
server.start();
Connector conn = server.getConnectors()[0];
String host = conn.getHost();
if (host == null)
{
host = "localhost";
}
int port = conn.getLocalPort();
serverUri = new URI(String.format("ws://%s:%d/",host,port));
// System.out.printf("Server URI: %s%n",serverUri);
}
@Test
public void testAttemptUpgrade() throws Exception
{
SafariD00 safari = new SafariD00(serverUri);
try
{
safari.connect();
safari.issueHandshake();
Assert.fail("Expected upgrade failure");
}
catch(IllegalStateException e) {
String respHeader = e.getMessage();
Assert.assertThat("Response Header", respHeader, containsString("HTTP/1.1 400 Unsupported websocket version specification"));
}
finally
{
// System.out.println("Closing client socket");
safari.disconnect();
}
}
public static void threadSleep(int dur, TimeUnit unit) throws InterruptedException
{
long ms = TimeUnit.MILLISECONDS.convert(dur,unit);
Thread.sleep(ms);
}
@After
public void stopServer() throws Exception
{
server.stop();
}
}

View File

@ -23,6 +23,7 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
@ -35,6 +36,7 @@ import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.log.StdErrLog;
import org.junit.Assert;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.is;
public class SafariD00
@ -109,12 +111,14 @@ public class SafariD00
// Read HTTP 101 Upgrade / Handshake Response
InputStreamReader reader = new InputStreamReader(in);
StringBuilder respHeaders = new StringBuilder();
LOG.debug("Reading http headers");
int crlfs = 0;
while (true)
{
int read = in.read();
respHeaders.append((char)read);
if (read == '\r' || read == '\n')
++crlfs;
else
@ -123,6 +127,16 @@ public class SafariD00
break;
}
if(respHeaders.toString().startsWith("HTTP/1.1 101 ") == false) {
String respLine = respHeaders.toString();
int idx = respLine.indexOf('\r');
if(idx > 0) {
respLine = respLine.substring(0,idx);
}
LOG.debug("Response Headers: {}",respHeaders.toString());
throw new IllegalStateException(respLine);
}
// Read expected handshake hixie bytes
byte hixieHandshakeExpected[] = TypeUtil.fromHexString("c7438d956cf611a6af70603e6fa54809");
byte hixieHandshake[] = new byte[hixieHandshakeExpected.length];

View File

@ -176,7 +176,7 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-websocket</artifactId>
<version>${project.version}</version>
<!--scope>provided</scope-->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>

View File

@ -29,12 +29,10 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.jasper.servlet.JspServlet;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.NoJspServlet;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
@ -44,7 +42,6 @@ import org.eclipse.jetty.util.log.Logger;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@ -66,15 +63,21 @@ public class JspAndDefaultWithAliasesTest
{
List<String[]> data = new ArrayList<String[]>();
double javaVersion = Double.parseDouble(System.getProperty("java.specification.version"));
// @formatter:off
data.add(new String[] { "false","/dump.jsp" });
data.add(new String[] { "true", "/dump.jsp%00" });
data.add(new String[] { "false","/dump.jsp%00x" });
data.add(new String[] { "false","/dump.jsp%00/" });
data.add(new String[] { "false","/dump.jsp%00x/" });
data.add(new String[] { "false","/dump.jsp%00x/dump.jsp" });
data.add(new String[] { "false","/dump.jsp%00/dump.jsp" });
if (javaVersion >= 1.7)
{
data.add(new String[] { "false","/dump.jsp%00x" });
data.add(new String[] { "false","/dump.jsp%00x/" });
data.add(new String[] { "false","/dump.jsp%00/index.html" });
}
// @formatter:on
return data;

View File

@ -29,7 +29,6 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.jasper.servlet.JspServlet;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
@ -65,15 +64,21 @@ public class JspAndDefaultWithoutAliasesTest
{
List<Object[]> data = new ArrayList<Object[]>();
double javaVersion = Double.parseDouble(System.getProperty("java.specification.version"));
// @formatter:off
data.add(new Object[] { "/dump.jsp" });
data.add(new Object[] { "/dump.jsp%00" });
data.add(new Object[] { "/dump.jsp%00x" });
data.add(new Object[] { "/dump.jsp%00/" });
data.add(new Object[] { "/dump.jsp%00x/" });
data.add(new Object[] { "/dump.jsp%00x/dump.jsp" });
data.add(new Object[] { "/dump.jsp%00/dump.jsp" });
data.add(new Object[] { "/dump.jsp%00/index.html" });
if (javaVersion >= 1.7)
{
data.add(new Object[] { "/dump.jsp%00/" });
data.add(new Object[] { "/dump.jsp%00x/" });
}
// @formatter:on
return data;

View File

@ -53,10 +53,6 @@ public class JdbcTestServer extends AbstractTestServer
super(port, maxInactivePeriod, scavengePeriod, DEFAULT_CONNECTION_URL);
}
public JdbcTestServer (int port, boolean optimize)
{
super(port);
}
/**
* @see org.eclipse.jetty.server.session.AbstractTestServer#newSessionHandler(org.eclipse.jetty.server.SessionManager)

View File

@ -0,0 +1,153 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.server.session;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.FileWriter;
import java.net.URL;
import java.net.URLClassLoader;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.ContentExchange;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.http.HttpMethods;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.StdErrLog;
import org.eclipse.jetty.webapp.WebAppContext;
import org.junit.Test;
/**
* ReloadedSessionMissingClassTest
*
*
*
*/
public class ReloadedSessionMissingClassTest
{
@Test
public void testSessionReloadWithMissingClass() throws Exception
{
((StdErrLog)Log.getLogger(org.eclipse.jetty.server.session.JDBCSessionManager.class)).setHideStacks(true);
String contextPath = "/foo";
File srcDir = new File(System.getProperty("basedir"), "src");
File targetDir = new File(System.getProperty("basedir"), "target");
File testDir = new File (srcDir, "test");
File resourcesDir = new File (testDir, "resources");
File unpackedWarDir = new File (targetDir, "foo");
if (unpackedWarDir.exists())
IO.delete(unpackedWarDir);
unpackedWarDir.mkdir();
File webInfDir = new File (unpackedWarDir, "WEB-INF");
webInfDir.mkdir();
File webXml = new File(webInfDir, "web.xml");
String xml =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<web-app xmlns=\"http://java.sun.com/xml/ns/j2ee\"\n" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
" xsi:schemaLocation=\"http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd\"\n" +
" version=\"2.4\">\n" +
"\n" +
"<session-config>\n"+
" <session-timeout>1</session-timeout>\n" +
"</session-config>\n"+
"</web-app>";
FileWriter w = new FileWriter(webXml);
w.write(xml);
w.close();
File foobarJar = new File (resourcesDir, "foobar.jar");
File foobarNOfooJar = new File (resourcesDir, "foobarNOfoo.jar");
URL[] foobarUrls = new URL[]{foobarJar.toURI().toURL()};
URL[] barUrls = new URL[]{foobarNOfooJar.toURI().toURL()};
URLClassLoader loaderWithFoo = new URLClassLoader(foobarUrls, Thread.currentThread().getContextClassLoader());
URLClassLoader loaderWithoutFoo = new URLClassLoader(barUrls, Thread.currentThread().getContextClassLoader());
AbstractTestServer server1 = new JdbcTestServer(0);
WebAppContext webApp = server1.addWebAppContext(unpackedWarDir.getCanonicalPath(), contextPath);
webApp.setClassLoader(loaderWithFoo);
webApp.addServlet("Bar", "/bar");
server1.start();
int port1 = server1.getPort();
try
{
HttpClient client = new HttpClient();
client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
client.start();
try
{
// Perform one request to server1 to create a session
ContentExchange exchange1 = new ContentExchange(true);
exchange1.setMethod(HttpMethods.GET);
exchange1.setURL("http://localhost:" + port1 + contextPath +"/bar?action=set");
client.send(exchange1);
exchange1.waitForDone();
assertEquals( HttpServletResponse.SC_OK, exchange1.getResponseStatus());
String sessionCookie = exchange1.getResponseFields().getStringField("Set-Cookie");
assertTrue(sessionCookie != null);
String sessionId = (String)webApp.getServletContext().getAttribute("foo");
assertNotNull(sessionId);
// Mangle the cookie, replacing Path with $Path, etc.
sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
//Stop the webapp
webApp.stop();
webApp.setClassLoader(loaderWithoutFoo);
webApp.addServlet("Bar", "/bar");
//restart webapp
webApp.start();
ContentExchange exchange2 = new ContentExchange(true);
exchange2.setMethod(HttpMethods.GET);
exchange2.setURL("http://localhost:" + port1 + contextPath + "/bar?action=get");
exchange2.getRequestFields().add("Cookie", sessionCookie);
client.send(exchange2);
exchange2.waitForDone();
assertEquals(HttpServletResponse.SC_OK,exchange2.getResponseStatus());
String afterStopSessionId = (String)webApp.getServletContext().getAttribute("foo.session");
assertNotNull(afterStopSessionId);
assertTrue(!afterStopSessionId.equals(sessionId));
}
finally
{
client.stop();
}
}
finally
{
server1.stop();
}
}
}