Merge branch 'jetty-7' into release-7
This commit is contained in:
commit
d5328b0932
|
@ -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>();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------- */
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 "";
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -107,7 +107,7 @@ public class IncludableGzipFilterMinSizeTest
|
|||
|
||||
try {
|
||||
tester.start();
|
||||
tester.assertIsResponseGzipCompressed("big_script.js");
|
||||
tester.assertIsResponseGzipCompressed("GET","big_script.js");
|
||||
} finally {
|
||||
tester.stop();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"));
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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=='+')
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
Manifest-Version: 1.0
|
||||
Created-By: 1.2.2 (Sun Microsystems Inc.)
|
||||
|
|
@ -0,0 +1 @@
|
|||
ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
|
@ -0,0 +1 @@
|
|||
1234567890
|
|
@ -0,0 +1 @@
|
|||
ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
|
@ -0,0 +1 @@
|
|||
1234567890
|
|
@ -0,0 +1 @@
|
|||
ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
|
@ -0,0 +1 @@
|
|||
1234567890
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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];
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue