Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGreg Wilkins2009-03-24 21:07:27 +0000
committerGreg Wilkins2009-03-24 21:07:27 +0000
commitda627b843fe81fa0fe52a046c1be8595630e9ae7 (patch)
tree5dd3804b874cf01be38575a02b5658a02113f78f /jetty-server/src/main/java/org/eclipse/jetty/server
parentbc1e0bd10201d8a14f20a81e3b93076af6408fe4 (diff)
downloadorg.eclipse.jetty.project-da627b843fe81fa0fe52a046c1be8595630e9ae7.tar.gz
org.eclipse.jetty.project-da627b843fe81fa0fe52a046c1be8595630e9ae7.tar.xz
org.eclipse.jetty.project-da627b843fe81fa0fe52a046c1be8595630e9ae7.zip
jetty @ eclipse initial commit
git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@8 7e9141cc-0065-0410-87d8-b60c137991c4
Diffstat (limited to 'jetty-server/src/main/java/org/eclipse/jetty/server')
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java990
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/AsyncRequest.java708
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/Connector.java322
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java296
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java571
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/Handler.java48
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/HandlerContainer.java33
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java1114
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java62
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/HttpOnlyCookie.java45
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java173
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/HttpWriter.java266
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/InclusiveByteRange.java211
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java221
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/NCSARequestLog.java513
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/Request.java1866
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/RequestLog.java26
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java555
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/Response.java1191
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/RetryRequest.java29
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/Server.java779
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/Servlet3Continuation.java115
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/SessionIdManager.java83
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/SessionManager.java327
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/UserIdentity.java136
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/bio/SocketConnector.java264
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandler.java97
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java88
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/handler/CompleteHandler.java37
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java1903
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java319
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java193
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java175
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java220
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerList.java54
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java179
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/handler/MovedContextHandler.java158
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/handler/RequestLogHandler.java132
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java335
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java369
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/nio/AbstractNIOConnector.java77
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/nio/BlockingChannelConnector.java189
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/nio/InheritedChannelConnector.java66
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/nio/NIOConnector.java26
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/nio/SelectChannelConnector.java328
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionIdManager.java161
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java1182
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionIdManager.java256
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java652
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java695
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java1080
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java291
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/ssl/ServletSSL.java83
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSelectChannelConnector.java703
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSocketConnector.java665
55 files changed, 21657 insertions, 0 deletions
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java
new file mode 100644
index 0000000000..1d3430ae2b
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java
@@ -0,0 +1,990 @@
+// ========================================================================
+// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+
+import javax.servlet.ServletRequest;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpSchemes;
+import org.eclipse.jetty.io.AbstractBuffers;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.thread.ThreadPool;
+
+
+/** Abstract Connector implementation.
+ * This abstract implementation of the Connector interface provides:<ul>
+ * <li>AbstractLifeCycle implementation</li>
+ * <li>Implementations for connector getters and setters</li>
+ * <li>Buffer management</li>
+ * <li>Socket configuration</li>
+ * <li>Base acceptor thread</li>
+ * <li>Optional reverse proxy headers checking</li>
+ * </ul>
+ *
+ *
+ */
+public abstract class AbstractConnector extends AbstractBuffers implements Connector
+{
+ private String _name;
+
+ private Server _server;
+ private ThreadPool _threadPool;
+ private String _host;
+ private int _port=0;
+ private String _integralScheme=HttpSchemes.HTTPS;
+ private int _integralPort=0;
+ private String _confidentialScheme=HttpSchemes.HTTPS;
+ private int _confidentialPort=0;
+ private int _acceptQueueSize=0;
+ private int _acceptors=1;
+ private int _acceptorPriorityOffset=0;
+ private boolean _useDNS;
+ private boolean _forwarded;
+ private String _hostHeader;
+ private String _forwardedHostHeader = "X-Forwarded-Host"; // default to mod_proxy_http header
+ private String _forwardedServerHeader = "X-Forwarded-Server"; // default to mod_proxy_http header
+ private String _forwardedForHeader = "X-Forwarded-For"; // default to mod_proxy_http header
+ private boolean _reuseAddress=true;
+
+ protected int _maxIdleTime=200000;
+ protected int _lowResourceMaxIdleTime=-1;
+ protected int _soLingerTime=-1;
+
+ private transient Thread[] _acceptorThread;
+
+ Object _statsLock = new Object();
+ transient long _statsStartedAt=-1;
+ transient int _requests;
+ transient int _connections; // total number of connections made to server
+
+ transient int _connectionsOpen; // number of connections currently open
+ transient int _connectionsOpenMin; // min number of connections open simultaneously
+ transient int _connectionsOpenMax; // max number of connections open simultaneously
+
+ transient long _connectionsDurationMin; // min duration of a connection
+ transient long _connectionsDurationMax; // max duration of a connection
+ transient long _connectionsDurationTotal; // total duration of all coneection
+
+ transient int _connectionsRequestsMin; // min requests per connection
+ transient int _connectionsRequestsMax; // max requests per connection
+
+
+ /* ------------------------------------------------------------------------------- */
+ /**
+ */
+ public AbstractConnector()
+ {
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /*
+ */
+ public Server getServer()
+ {
+ return _server;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public void setServer(Server server)
+ {
+ _server=server;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /*
+ * @see org.eclipse.jetty.http.HttpListener#getHttpServer()
+ */
+ public ThreadPool getThreadPool()
+ {
+ return _threadPool;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public void setThreadPool(ThreadPool pool)
+ {
+ _threadPool=pool;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /**
+ */
+ public void setHost(String host)
+ {
+ _host=host;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /*
+ */
+ public String getHost()
+ {
+ return _host;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /*
+ * @see org.eclipse.jetty.server.server.HttpListener#setPort(int)
+ */
+ public void setPort(int port)
+ {
+ _port=port;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /*
+ * @see org.eclipse.jetty.server.server.HttpListener#getPort()
+ */
+ public int getPort()
+ {
+ return _port;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the maxIdleTime.
+ */
+ public int getMaxIdleTime()
+ {
+ return _maxIdleTime;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the maximum Idle time for a connection, which roughly translates
+ * to the {@link Socket#setSoTimeout(int)} call, although with NIO
+ * implementations other mechanisms may be used to implement the timeout.
+ * The max idle time is applied:<ul>
+ * <li>When waiting for a new request to be received on a connection</li>
+ * <li>When reading the headers and content of a request</li>
+ * <li>When writing the headers and content of a response</li>
+ * </ul>
+ * Jetty interprets this value as the maximum time between some progress being
+ * made on the connection. So if a single byte is read or written, then the
+ * timeout (if implemented by jetty) is reset. However, in many instances,
+ * the reading/writing is delegated to the JVM, and the semantic is more
+ * strictly enforced as the maximum time a single read/write operation can
+ * take. Note, that as Jetty supports writes of memory mapped file buffers,
+ * then a write may take many 10s of seconds for large content written to a
+ * slow device.
+ * <p>
+ * Previously, Jetty supported separate idle timeouts and IO operation timeouts,
+ * however the expense of changing the value of soTimeout was significant, so
+ * these timeouts were merged. With the advent of NIO, it may be possible to
+ * again differentiate these values (if there is demand).
+ *
+ * @param maxIdleTime The maxIdleTime to set.
+ */
+ public void setMaxIdleTime(int maxIdleTime)
+ {
+ _maxIdleTime = maxIdleTime;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the maxIdleTime.
+ */
+ public int getLowResourceMaxIdleTime()
+ {
+ return _lowResourceMaxIdleTime;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param maxIdleTime The maxIdleTime to set.
+ */
+ public void setLowResourceMaxIdleTime(int maxIdleTime)
+ {
+ _lowResourceMaxIdleTime = maxIdleTime;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the soLingerTime.
+ */
+ public int getSoLingerTime()
+ {
+ return _soLingerTime;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the acceptQueueSize.
+ */
+ public int getAcceptQueueSize()
+ {
+ return _acceptQueueSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param acceptQueueSize The acceptQueueSize to set.
+ */
+ public void setAcceptQueueSize(int acceptQueueSize)
+ {
+ _acceptQueueSize = acceptQueueSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the number of acceptor threads.
+ */
+ public int getAcceptors()
+ {
+ return _acceptors;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param acceptors The number of acceptor threads to set.
+ */
+ public void setAcceptors(int acceptors)
+ {
+ _acceptors = acceptors;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param soLingerTime The soLingerTime to set or -1 to disable.
+ */
+ public void setSoLingerTime(int soLingerTime)
+ {
+ _soLingerTime = soLingerTime;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void doStart() throws Exception
+ {
+ if (_server==null)
+ throw new IllegalStateException("No server");
+
+ // open listener port
+ open();
+
+ super.doStart();
+
+ if (_threadPool==null)
+ _threadPool=_server.getThreadPool();
+ if (_threadPool!=_server.getThreadPool() && (_threadPool instanceof LifeCycle))
+ ((LifeCycle)_threadPool).start();
+
+ // Start selector thread
+ synchronized(this)
+ {
+ _acceptorThread=new Thread[getAcceptors()];
+
+ for (int i=0;i<_acceptorThread.length;i++)
+ {
+ if (!_threadPool.dispatch(new Acceptor(i)))
+ {
+ Log.warn("insufficient maxThreads configured for {}",this);
+ break;
+ }
+ }
+ }
+
+ Log.info("Started {}",this);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void doStop() throws Exception
+ {
+ try{close();} catch(IOException e) {Log.warn(e);}
+
+ if (_threadPool==_server.getThreadPool())
+ _threadPool=null;
+ else if (_threadPool instanceof LifeCycle)
+ ((LifeCycle)_threadPool).stop();
+
+ super.doStop();
+
+ Thread[] acceptors=null;
+ synchronized(this)
+ {
+ acceptors=_acceptorThread;
+ _acceptorThread=null;
+ }
+ if (acceptors != null)
+ {
+ for (int i=0;i<acceptors.length;i++)
+ {
+ Thread thread=acceptors[i];
+ if (thread!=null)
+ thread.interrupt();
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void join() throws InterruptedException
+ {
+ Thread[] threads=_acceptorThread;
+ if (threads!=null)
+ for (int i=0;i<threads.length;i++)
+ if (threads[i]!=null)
+ threads[i].join();
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void configure(Socket socket)
+ throws IOException
+ {
+ try
+ {
+ socket.setTcpNoDelay(true);
+ if (_maxIdleTime >= 0)
+ socket.setSoTimeout(_maxIdleTime);
+ if (_soLingerTime >= 0)
+ socket.setSoLinger(true, _soLingerTime/1000);
+ else
+ socket.setSoLinger(false, 0);
+ }
+ catch (Exception e)
+ {
+ Log.ignore(e);
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public void customize(EndPoint endpoint, Request request)
+ throws IOException
+ {
+ if (isForwarded())
+ checkForwardedHeaders(endpoint, request);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void checkForwardedHeaders(EndPoint endpoint, Request request)
+ throws IOException
+ {
+ HttpFields httpFields = request.getConnection().getRequestFields();
+
+ // Retrieving headers from the request
+ String forwardedHost = getLeftMostValue(httpFields.getStringField(getForwardedHostHeader()));
+ String forwardedServer = getLeftMostValue(httpFields.getStringField(getForwardedServerHeader()));
+ String forwardedFor = getLeftMostValue(httpFields.getStringField(getForwardedForHeader()));
+
+ if (_hostHeader!=null)
+ {
+ // Update host header
+ httpFields.put(HttpHeaders.HOST_BUFFER, _hostHeader);
+ request.setServerName(null);
+ request.setServerPort(-1);
+ request.getServerName();
+ }
+ else if (forwardedHost != null)
+ {
+ // Update host header
+ httpFields.put(HttpHeaders.HOST_BUFFER, forwardedHost);
+ request.setServerName(null);
+ request.setServerPort(-1);
+ request.getServerName();
+ }
+
+ if (forwardedServer != null)
+ {
+ // Use provided server name
+ request.setServerName(forwardedServer);
+ }
+
+ if (forwardedFor != null)
+ {
+ request.setRemoteAddr(forwardedFor);
+ InetAddress inetAddress = null;
+
+ if (_useDNS)
+ {
+ try
+ {
+ inetAddress = InetAddress.getByName(forwardedFor);
+ }
+ catch (UnknownHostException e)
+ {
+ Log.ignore(e);
+ }
+ }
+
+ request.setRemoteHost(inetAddress==null?forwardedFor:inetAddress.getHostName());
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected String getLeftMostValue(String headerValue) {
+ if (headerValue == null)
+ return null;
+
+ int commaIndex = headerValue.indexOf(',');
+
+ if (commaIndex == -1)
+ {
+ // Single value
+ return headerValue;
+ }
+
+ // The left-most value is the farthest downstream client
+ return headerValue.substring(0, commaIndex);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void persist(EndPoint endpoint)
+ throws IOException
+ {
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.Connector#getConfidentialPort()
+ */
+ public int getConfidentialPort()
+ {
+ return _confidentialPort;
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.Connector#getConfidentialScheme()
+ */
+ public String getConfidentialScheme()
+ {
+ return _confidentialScheme;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.Connector#isConfidential(org.eclipse.jetty.server.Request)
+ */
+ public boolean isIntegral(Request request)
+ {
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.Connector#getConfidentialPort()
+ */
+ public int getIntegralPort()
+ {
+ return _integralPort;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.Connector#getIntegralScheme()
+ */
+ public String getIntegralScheme()
+ {
+ return _integralScheme;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.Connector#isConfidential(org.eclipse.jetty.server.Request)
+ */
+ public boolean isConfidential(Request request)
+ {
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param confidentialPort The confidentialPort to set.
+ */
+ public void setConfidentialPort(int confidentialPort)
+ {
+ _confidentialPort = confidentialPort;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param confidentialScheme The confidentialScheme to set.
+ */
+ public void setConfidentialScheme(String confidentialScheme)
+ {
+ _confidentialScheme = confidentialScheme;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param integralPort The integralPort to set.
+ */
+ public void setIntegralPort(int integralPort)
+ {
+ _integralPort = integralPort;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param integralScheme The integralScheme to set.
+ */
+ public void setIntegralScheme(String integralScheme)
+ {
+ _integralScheme = integralScheme;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected abstract void accept(int acceptorID) throws IOException, InterruptedException;
+
+ /* ------------------------------------------------------------ */
+ public void stopAccept(int acceptorID) throws Exception
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean getResolveNames()
+ {
+ return _useDNS;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setResolveNames(boolean resolve)
+ {
+ _useDNS=resolve;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Is reverse proxy handling on?
+ * @return true if this connector is checking the x-forwarded-for/host/server headers
+ */
+ public boolean isForwarded()
+ {
+ return _forwarded;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set reverse proxy handling
+ * @param check true if this connector is checking the x-forwarded-for/host/server headers
+ */
+ public void setForwarded(boolean check)
+ {
+ if (check)
+ Log.debug(this+" is forwarded");
+ _forwarded=check;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getHostHeader()
+ {
+ return _hostHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set a forced valued for the host header to control what is returned
+ * by {@link ServletRequest#getServerName()} and {@link ServletRequest#getServerPort()}.
+ * This value is only used if {@link #isForwarded()} is true.
+ * @param hostHeader The value of the host header to force.
+ */
+ public void setHostHeader(String hostHeader)
+ {
+ _hostHeader=hostHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getForwardedHostHeader()
+ {
+ return _forwardedHostHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param forwardedHostHeader The header name for forwarded hosts (default x-forwarded-host)
+ */
+ public void setForwardedHostHeader(String forwardedHostHeader)
+ {
+ _forwardedHostHeader=forwardedHostHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getForwardedServerHeader()
+ {
+ return _forwardedServerHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param forwardedHostHeader The header name for forwarded server (default x-forwarded-server)
+ */
+ public void setForwardedServerHeader(String forwardedServerHeader)
+ {
+ _forwardedServerHeader=forwardedServerHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getForwardedForHeader()
+ {
+ return _forwardedForHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param forwardedHostHeader The header name for forwarded for (default x-forwarded-for)
+ */
+ public void setForwardedForHeader(String forwardedRemoteAddressHeade)
+ {
+ _forwardedForHeader=forwardedRemoteAddressHeade;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String toString()
+ {
+ String name = this.getClass().getName();
+ int dot = name.lastIndexOf('.');
+ if (dot>0)
+ name=name.substring(dot+1);
+
+ return name+"@"+(getHost()==null?"0.0.0.0":getHost())+":"+(getLocalPort()<=0?getPort():getLocalPort());
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ private class Acceptor implements Runnable
+ {
+ int _acceptor=0;
+
+ Acceptor(int id)
+ {
+ _acceptor=id;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void run()
+ {
+ Thread current = Thread.currentThread();
+ synchronized(AbstractConnector.this)
+ {
+ if (_acceptorThread==null)
+ return;
+
+ _acceptorThread[_acceptor]=current;
+ }
+ String name =_acceptorThread[_acceptor].getName();
+ current.setName(name+" - Acceptor"+_acceptor+" "+AbstractConnector.this);
+ int old_priority=current.getPriority();
+
+ try
+ {
+ current.setPriority(old_priority-_acceptorPriorityOffset);
+ while (isRunning() && getConnection()!=null)
+ {
+ try
+ {
+ accept(_acceptor);
+ }
+ catch(EofException e)
+ {
+ Log.ignore(e);
+ }
+ catch(IOException e)
+ {
+ Log.ignore(e);
+ }
+ catch(ThreadDeath e)
+ {
+ Log.warn(e);
+ throw e;
+ }
+ catch(Throwable e)
+ {
+ Log.warn(e);
+ }
+ }
+ }
+ finally
+ {
+ current.setPriority(old_priority);
+ current.setName(name);
+ try
+ {
+ if (_acceptor==0)
+ close();
+ }
+ catch (IOException e)
+ {
+ Log.warn(e);
+ }
+
+ synchronized(AbstractConnector.this)
+ {
+ if (_acceptorThread!=null)
+ _acceptorThread[_acceptor]=null;
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getName()
+ {
+ if (_name==null)
+ _name= (getHost()==null?"0.0.0.0":getHost())+":"+(getLocalPort()<=0?getPort():getLocalPort());
+ return _name;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setName(String name)
+ {
+ _name = name;
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Get the number of requests handled by this context
+ * since last call of statsReset(). If setStatsOn(false) then this
+ * is undefined.
+ */
+ public int getRequests() {return _requests;}
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the connectionsDurationMin.
+ */
+ public long getConnectionsDurationMin()
+ {
+ return _connectionsDurationMin;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the connectionsDurationTotal.
+ */
+ public long getConnectionsDurationTotal()
+ {
+ return _connectionsDurationTotal;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the connectionsOpenMin.
+ */
+ public int getConnectionsOpenMin()
+ {
+ return _connectionsOpenMin;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the connectionsRequestsMin.
+ */
+ public int getConnectionsRequestsMin()
+ {
+ return _connectionsRequestsMin;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Number of connections accepted by the server since
+ * statsReset() called. Undefined if setStatsOn(false).
+ */
+ public int getConnections() {return _connections;}
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Number of connections currently open that were opened
+ * since statsReset() called. Undefined if setStatsOn(false).
+ */
+ public int getConnectionsOpen() {return _connectionsOpen;}
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Maximum number of connections opened simultaneously
+ * since statsReset() called. Undefined if setStatsOn(false).
+ */
+ public int getConnectionsOpenMax() {return _connectionsOpenMax;}
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Average duration in milliseconds of open connections
+ * since statsReset() called. Undefined if setStatsOn(false).
+ */
+ public long getConnectionsDurationAve() {return _connections==0?0:(_connectionsDurationTotal/_connections);}
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Maximum duration in milliseconds of an open connection
+ * since statsReset() called. Undefined if setStatsOn(false).
+ */
+ public long getConnectionsDurationMax() {return _connectionsDurationMax;}
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Average number of requests per connection
+ * since statsReset() called. Undefined if setStatsOn(false).
+ */
+ public int getConnectionsRequestsAve() {return _connections==0?0:(_requests/_connections);}
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Maximum number of requests per connection
+ * since statsReset() called. Undefined if setStatsOn(false).
+ */
+ public int getConnectionsRequestsMax() {return _connectionsRequestsMax;}
+
+
+
+ /* ------------------------------------------------------------ */
+ /** Reset statistics.
+ */
+ public void statsReset()
+ {
+ _statsStartedAt=_statsStartedAt==-1?-1:System.currentTimeMillis();
+
+ _connections=0;
+
+ _connectionsOpenMin=_connectionsOpen;
+ _connectionsOpenMax=_connectionsOpen;
+ _connectionsOpen=0;
+
+ _connectionsDurationMin=0;
+ _connectionsDurationMax=0;
+ _connectionsDurationTotal=0;
+
+ _requests=0;
+
+ _connectionsRequestsMin=0;
+ _connectionsRequestsMax=0;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setStatsOn(boolean on)
+ {
+ if (on && _statsStartedAt!=-1)
+ return;
+ Log.debug("Statistics on = "+on+" for "+this);
+ statsReset();
+ _statsStartedAt=on?System.currentTimeMillis():-1;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if statistics collection is turned on.
+ */
+ public boolean getStatsOn()
+ {
+ return _statsStartedAt!=-1;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Timestamp stats were started at.
+ */
+ public long getStatsOnMs()
+ {
+ return (_statsStartedAt!=-1)?(System.currentTimeMillis()-_statsStartedAt):0;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void connectionOpened(HttpConnection connection)
+ {
+ if (_statsStartedAt==-1)
+ return;
+ synchronized(_statsLock)
+ {
+ _connectionsOpen++;
+ if (_connectionsOpen > _connectionsOpenMax)
+ _connectionsOpenMax=_connectionsOpen;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void connectionClosed(HttpConnection connection)
+ {
+ if (_statsStartedAt>=0)
+ {
+ long duration=System.currentTimeMillis()-connection.getTimeStamp();
+ int requests=connection.getRequests();
+ synchronized(_statsLock)
+ {
+ _requests+=requests;
+ _connections++;
+ _connectionsOpen--;
+ _connectionsDurationTotal+=duration;
+ if (_connectionsOpen<0)
+ _connectionsOpen=0;
+ if (_connectionsOpen<_connectionsOpenMin)
+ _connectionsOpenMin=_connectionsOpen;
+ if (_connectionsDurationMin==0 || duration<_connectionsDurationMin)
+ _connectionsDurationMin=duration;
+ if (duration>_connectionsDurationMax)
+ _connectionsDurationMax=duration;
+ if (_connectionsRequestsMin==0 || requests<_connectionsRequestsMin)
+ _connectionsRequestsMin=requests;
+ if (requests>_connectionsRequestsMax)
+ _connectionsRequestsMax=requests;
+ }
+ }
+
+ if (connection!=null)
+ connection.destroy();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the acceptorPriority
+ */
+ public int getAcceptorPriorityOffset()
+ {
+ return _acceptorPriorityOffset;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the priority offset of the acceptor threads. The priority is adjusted by
+ * this amount (default 0) to either favour the acceptance of new threads and newly active
+ * connections or to favour the handling of already dispatched connections.
+ * @param offset the amount to alter the priority of the acceptor threads.
+ */
+ public void setAcceptorPriorityOffset(int offset)
+ {
+ _acceptorPriorityOffset=offset;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if the the server socket will be opened in SO_REUSEADDR mode.
+ */
+ public boolean getReuseAddress()
+ {
+ return _reuseAddress;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param reuseAddress True if the the server socket will be opened in SO_REUSEADDR mode.
+ */
+ public void setReuseAddress(boolean reuseAddress)
+ {
+ _reuseAddress=reuseAddress;
+ }
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncRequest.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncRequest.java
new file mode 100644
index 0000000000..4bb375d48d
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncRequest.java
@@ -0,0 +1,708 @@
+// ========================================================================
+// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+
+package org.eclipse.jetty.server;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.eclipse.jetty.io.AsyncEndPoint;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.thread.Timeout;
+
+/* ------------------------------------------------------------ */
+/** Asyncrhonous Request.
+ *
+ *
+ *
+ */
+public class AsyncRequest implements AsyncContext
+{
+ // STATES:
+ private static final int __IDLE=0; // Idle request
+ private static final int __DISPATCHED=1; // Request dispatched to filter/servlet
+ private static final int __SUSPENDING=2; // Suspend called, but not yet returned to container
+ private static final int __REDISPATCHING=3;// resumed while dispatched
+ private static final int __SUSPENDED=4; // Suspended and parked
+ private static final int __UNSUSPENDING=5; // Has been scheduled
+ private static final int __REDISPATCHED=6; // Request redispatched to filter/servlet
+ private static final int __COMPLETING=7; // complete while dispatched
+ private static final int __UNCOMPLETED=8; // Request is completable
+ private static final int __COMPLETE=9; // Request is complete
+
+ // State table
+ // __HANDLE __UNHANDLE __SUSPEND __REDISPATCH
+ // IDLE */ { __DISPATCHED, __Illegal, __Illegal, __Illegal },
+ // DISPATCHED */ { __Illegal, __UNCOMPLETED, __SUSPENDING, __Ignore },
+ // SUSPENDING */ { __Illegal, __SUSPENDED, __Illegal,__REDISPATCHING },
+ // REDISPATCHING */ { __Illegal, _REDISPATCHED, __Ignored, __Ignore },
+ // COMPLETING */ { __Illegal, __UNCOMPLETED, __Illegal, __Illegal },
+ // SUSPENDED */ { __REDISPATCHED, __Illegal, __Illegal, __UNSUSPENDING },
+ // UNSUSPENDING */ { __REDISPATCHED, __Illegal, __Illegal, __Ignore },
+ // REDISPATCHED */ { __Illegal, __UNCOMPLETED, __SUSPENDING, __Ignore },
+
+
+ /* ------------------------------------------------------------ */
+ protected HttpConnection _connection;
+ protected Object _listeners;
+
+ /* ------------------------------------------------------------ */
+ private int _state;
+ private boolean _initial;
+ private long _timeoutMs;
+ private AsyncEventState _event;
+
+ /* ------------------------------------------------------------ */
+ protected AsyncRequest()
+ {
+ _state=__IDLE;
+ _initial=true;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected AsyncRequest(final HttpConnection connection)
+ {
+ this();
+ if (connection!=null)
+ setConnection(connection);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void setConnection(final HttpConnection connection)
+ {
+ _connection=connection;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setAsyncTimeout(long ms)
+ {
+ _timeoutMs=ms;
+ }
+
+ /* ------------------------------------------------------------ */
+ public long getAsyncTimeout()
+ {
+ return _timeoutMs;
+ }
+
+ /* ------------------------------------------------------------ */
+ public AsyncEventState getAsyncEventState()
+ {
+ return _event;
+ }
+
+ /* ------------------------------------------------------------ */
+ /* (non-Javadoc)
+ * @see javax.servlet.ServletRequest#isInitial()
+ */
+ public boolean isInitial()
+ {
+ synchronized(this)
+ {
+ return _initial;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /* (non-Javadoc)
+ * @see javax.servlet.ServletRequest#isSuspended()
+ */
+ public boolean isSuspended()
+ {
+ synchronized(this)
+ {
+ switch(_state)
+ {
+ case __SUSPENDING:
+ case __REDISPATCHING:
+ case __COMPLETING:
+ case __SUSPENDED:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public String toString()
+ {
+ return getStatusString();
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getStatusString()
+ {
+ synchronized (this)
+ {
+ return
+ ((_state==__IDLE)?"IDLE":
+ (_state==__DISPATCHED)?"DISPATCHED":
+ (_state==__SUSPENDING)?"SUSPENDING":
+ (_state==__SUSPENDED)?"SUSPENDED":
+ (_state==__REDISPATCHING)?"REDISPATCHING":
+ (_state==__UNSUSPENDING)?"UNSUSPENDING":
+ (_state==__REDISPATCHED)?"REDISPATCHED":
+ (_state==__COMPLETING)?"COMPLETING":
+ (_state==__UNCOMPLETED)?"UNCOMPLETED":
+ (_state==__COMPLETE)?"COMPLETE":
+ ("UNKNOWN?"+_state))+
+ (_initial?",initial":"");
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /* (non-Javadoc)
+ * @see javax.servlet.ServletRequest#resume()
+ */
+ protected boolean handling()
+ {
+ synchronized (this)
+ {
+ switch(_state)
+ {
+ case __DISPATCHED:
+ case __REDISPATCHED:
+ case __COMPLETE:
+ throw new IllegalStateException(this.getStatusString());
+
+ case __IDLE:
+ _initial=true;
+ _state=__DISPATCHED;
+ return true;
+
+ case __SUSPENDING:
+ case __REDISPATCHING:
+ throw new IllegalStateException(this.getStatusString());
+
+ case __COMPLETING:
+ _state=__UNCOMPLETED;
+ return false;
+
+ case __SUSPENDED:
+ cancelTimeout();
+ case __UNSUSPENDING:
+ _state=__REDISPATCHED;
+ return true;
+
+ default:
+ throw new IllegalStateException(""+_state);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /* (non-Javadoc)
+ * @see javax.servlet.ServletRequest#suspend(long)
+ */
+ protected void suspend(final ServletContext context,
+ final ServletRequest request,
+ final ServletResponse response)
+ {
+ synchronized (this)
+ {
+ _event=new AsyncEventState(context,request,response);
+
+ switch(_state)
+ {
+ case __DISPATCHED:
+ case __REDISPATCHED:
+ _state=__SUSPENDING;
+ return;
+
+ case __IDLE:
+ throw new IllegalStateException(this.getStatusString());
+
+ case __SUSPENDING:
+ case __REDISPATCHING:
+ return;
+
+ case __COMPLETING:
+ case __SUSPENDED:
+ case __UNSUSPENDING:
+ case __COMPLETE:
+ throw new IllegalStateException(this.getStatusString());
+
+ default:
+ throw new IllegalStateException(""+_state);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Signal that the HttpConnection has finished handling the request.
+ * For blocking connectors, this call may block if the request has
+ * been suspended (startAsync called).
+ * @return true if handling is complete, false if the request should
+ * be handled again (eg because of a resume that happened before unhandle was called)
+ */
+ protected boolean unhandle()
+ {
+ synchronized (this)
+ {
+ switch(_state)
+ {
+ case __REDISPATCHED:
+ case __DISPATCHED:
+ _state=__UNCOMPLETED;
+ return true;
+
+ case __IDLE:
+ throw new IllegalStateException(this.getStatusString());
+
+ case __SUSPENDING:
+ _initial=false;
+ _state=__SUSPENDED;
+ scheduleTimeout(); // could block and change state.
+ if (_state==__SUSPENDED)
+ return true;
+ else if (_state==__COMPLETING)
+ {
+ _state=__UNCOMPLETED;
+ return true;
+ }
+ _initial=false;
+ _state=__REDISPATCHED;
+ return false;
+
+ case __REDISPATCHING:
+ _initial=false;
+ _state=__REDISPATCHED;
+ return false;
+
+ case __COMPLETING:
+ _initial=false;
+ _state=__UNCOMPLETED;
+ return true;
+
+ case __SUSPENDED:
+ case __UNSUSPENDING:
+ default:
+ throw new IllegalStateException(this.getStatusString());
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void dispatch()
+ {
+ boolean dispatch=false;
+ synchronized (this)
+ {
+ switch(_state)
+ {
+ case __REDISPATCHED:
+ case __DISPATCHED:
+ case __IDLE:
+ case __REDISPATCHING:
+ case __COMPLETING:
+ case __COMPLETE:
+ case __UNCOMPLETED:
+ return;
+
+ case __SUSPENDING:
+ _state=__REDISPATCHING;
+ return;
+
+ case __SUSPENDED:
+ dispatch=true;
+ _state=__UNSUSPENDING;
+ break;
+
+ case __UNSUSPENDING:
+ return;
+
+ default:
+ throw new IllegalStateException(this.getStatusString());
+ }
+ }
+
+ if (dispatch)
+ {
+ cancelTimeout();
+ scheduleDispatch();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void expired()
+ {
+ synchronized (this)
+ {
+ switch(_state)
+ {
+ case __SUSPENDING:
+ case __SUSPENDED:
+ break;
+ default:
+ return;
+ }
+ }
+
+ if (_listeners!=null)
+ {
+ for(int i=0;i<LazyList.size(_listeners);i++)
+ {
+ try
+ {
+ AsyncListener listener=((AsyncListener)LazyList.get(_listeners,i));
+ listener.onTimeout(_event);
+ }
+ catch(Exception e)
+ {
+ Log.warn(e);
+ }
+ }
+ }
+
+ synchronized (this)
+ {
+ switch(_state)
+ {
+ case __SUSPENDING:
+ case __SUSPENDED:
+ if (false) // TODO
+ dispatch();
+ else
+ complete();
+ default:
+ return;
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /* (non-Javadoc)
+ * @see javax.servlet.ServletRequest#complete()
+ */
+ public void complete()
+ {
+ // just like resume, except don't set _resumed=true;
+ boolean dispatch=false;
+ synchronized (this)
+ {
+ switch(_state)
+ {
+ case __IDLE:
+ case __COMPLETE:
+ case __REDISPATCHING:
+ case __COMPLETING:
+ case __UNSUSPENDING:
+ return;
+
+ case __DISPATCHED:
+ case __REDISPATCHED:
+ throw new IllegalStateException(this.getStatusString());
+
+ case __SUSPENDING:
+ _state=__COMPLETING;
+ return;
+
+ case __SUSPENDED:
+ _state=__COMPLETING;
+ dispatch=true;
+ break;
+
+ default:
+ throw new IllegalStateException(this.getStatusString());
+ }
+ }
+
+ if (dispatch)
+ {
+ cancelTimeout();
+ scheduleDispatch();
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /* (non-Javadoc)
+ * @see javax.servlet.ServletRequest#complete()
+ */
+ protected void doComplete()
+ {
+ synchronized (this)
+ {
+ switch(_state)
+ {
+ case __UNCOMPLETED:
+ _state=__COMPLETE;
+ break;
+
+ default:
+ throw new IllegalStateException(this.getStatusString());
+ }
+ }
+
+ if (_listeners!=null)
+ {
+ for(int i=0;i<LazyList.size(_listeners);i++)
+ {
+ try
+ {
+ ((AsyncListener)LazyList.get(_listeners,i)).onComplete(_event);
+ }
+ catch(Exception e)
+ {
+ Log.warn(e);
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void recycle()
+ {
+ synchronized (this)
+ {
+ switch(_state)
+ {
+ case __DISPATCHED:
+ case __REDISPATCHED:
+ throw new IllegalStateException(getStatusString());
+ default:
+ _state=__IDLE;
+ }
+ _initial = true;
+ cancelTimeout();
+ _event=null;
+ _timeoutMs=60000L; // TODO configure
+ _listeners=null;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void cancel()
+ {
+ synchronized (this)
+ {
+ _state=__COMPLETE;
+ _initial = false;
+ cancelTimeout();
+ _event=null;
+ _listeners=null;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void scheduleDispatch()
+ {
+ EndPoint endp=_connection.getEndPoint();
+ if (!endp.isBlocking())
+ {
+ ((AsyncEndPoint)endp).dispatch();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void scheduleTimeout()
+ {
+ EndPoint endp=_connection.getEndPoint();
+ if (endp.isBlocking())
+ {
+ synchronized(this)
+ {
+ long expire_at = System.currentTimeMillis()+_timeoutMs;
+ long wait=_timeoutMs;
+ while (_timeoutMs>0 && wait>0)
+ {
+ try
+ {
+ this.wait(wait);
+ }
+ catch (InterruptedException e)
+ {
+ Log.ignore(e);
+ }
+ wait=expire_at-System.currentTimeMillis();
+ }
+
+ if (_timeoutMs>0 && wait<=0)
+ expired();
+ }
+ }
+ else
+ _connection.scheduleTimeout(_event._timeout,_timeoutMs);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void cancelTimeout()
+ {
+ EndPoint endp=_connection.getEndPoint();
+ if (endp.isBlocking())
+ {
+ synchronized(this)
+ {
+ _timeoutMs=0;
+ this.notifyAll();
+ }
+ }
+ else if (_event!=null)
+ _connection.cancelTimeout(_event._timeout);
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isCompleting()
+ {
+ return _state==__COMPLETING;
+ }
+
+ /* ------------------------------------------------------------ */
+ boolean isUncompleted()
+ {
+ return _state==__UNCOMPLETED;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isComplete()
+ {
+ return _state==__COMPLETE;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public boolean isAsyncStarted()
+ {
+ switch(_state)
+ {
+ case __SUSPENDING:
+ case __REDISPATCHING:
+ case __UNSUSPENDING:
+ case __SUSPENDED:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public boolean isAsync()
+ {
+ switch(_state)
+ {
+ case __IDLE:
+ case __DISPATCHED:
+ return false;
+
+ default:
+ return true;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void dispatch(ServletContext context, String path)
+ {
+ _event._dispatchContext=context;
+ _event._path=path;
+ dispatch();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void dispatch(String path)
+ {
+ _event._path=path;
+ dispatch();
+ }
+
+ /* ------------------------------------------------------------ */
+ public ServletRequest getRequest()
+ {
+ if (_event!=null)
+ return _event.getRequest();
+ return _connection.getRequest();
+ }
+
+ /* ------------------------------------------------------------ */
+ public ServletResponse getResponse()
+ {
+ if (_event!=null)
+ return _event.getResponse();
+ return _connection.getResponse();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void start(Runnable run)
+ {
+ ((Context)_event.getServletContext()).getContextHandler().handle(run);
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean hasOriginalRequestAndResponse()
+ {
+ return (_event!=null && _event.getRequest()==_connection._request && _event.getResponse()==_connection._response);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ContextHandler getContextHandler()
+ {
+ if (_event!=null)
+ return ((Context)_event.getServletContext()).getContextHandler();
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public class AsyncEventState extends AsyncEvent
+ {
+ final Timeout.Task _timeout;
+ final ServletContext _suspendedContext;
+ ServletContext _dispatchContext;
+ String _path;
+
+ public AsyncEventState(ServletContext context, ServletRequest request, ServletResponse response)
+ {
+ super(request,response);
+ _suspendedContext=context;
+ _timeout= new Timeout.Task()
+ {
+ public void expired()
+ {
+ AsyncRequest.this.expired();
+ }
+ };
+ }
+
+ public ServletContext getSuspendedContext()
+ {
+ return _suspendedContext;
+ }
+
+ public ServletContext getDispatchContext()
+ {
+ return _dispatchContext;
+ }
+
+ public ServletContext getServletContext()
+ {
+ return _dispatchContext==null?_suspendedContext:_dispatchContext;
+ }
+
+ public String getPath()
+ {
+ return _path;
+ }
+ }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Connector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Connector.java
new file mode 100644
index 0000000000..e5976995e5
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Connector.java
@@ -0,0 +1,322 @@
+// ========================================================================
+// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.io.Buffers;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/** HTTP Connector.
+ * Implementations of this interface provide connectors for the HTTP protocol.
+ * A connector receives requests (normally from a socket) and calls the
+ * handle method of the Handler object. These operations are performed using
+ * threads from the ThreadPool set on the connector.
+ *
+ * When a connector is registered with an instance of Server, then the server
+ * will set itself as both the ThreadPool and the Handler. Note that a connector
+ * can be used without a Server if a thread pool and handler are directly provided.
+ *
+ *
+ *
+ */
+public interface Connector extends LifeCycle, Buffers
+{
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the name of the connector. Defaults to the HostName:port
+ */
+ String getName();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Opens the connector
+ * @throws IOException
+ */
+ void open() throws IOException;
+
+ /* ------------------------------------------------------------ */
+ void close() throws IOException;
+
+ /* ------------------------------------------------------------ */
+ void setServer(Server server);
+
+ /* ------------------------------------------------------------ */
+ Server getServer();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the headerBufferSize.
+ */
+ int getHeaderBufferSize();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the size of the buffer to be used for request and response headers.
+ * An idle connection will at most have one buffer of this size allocated.
+ * @param headerBufferSize The headerBufferSize to set.
+ */
+ void setHeaderBufferSize(int headerBufferSize);
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the requestBufferSize.
+ */
+ int getRequestBufferSize();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the size of the content buffer for receiving requests.
+ * These buffers are only used for active connections that have
+ * requests with bodies that will not fit within the header buffer.
+ * @param requestBufferSize The requestBufferSize to set.
+ */
+ void setRequestBufferSize(int requestBufferSize);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the responseBufferSize.
+ */
+ int getResponseBufferSize();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the size of the content buffer for sending responses.
+ * These buffers are only used for active connections that are sending
+ * responses with bodies that will not fit within the header buffer.
+ * @param responseBufferSize The responseBufferSize to set.
+ */
+ void setResponseBufferSize(int responseBufferSize);
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The port to use when redirecting a request if a data constraint of integral is
+ * required. See {@link org.eclipse.jetty.server.server.security.Constraint#getDataConstraint()}
+ */
+ int getIntegralPort();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The schema to use when redirecting a request if a data constraint of integral is
+ * required. See {@link org.eclipse.jetty.server.server.security.Constraint#getDataConstraint()}
+ */
+ String getIntegralScheme();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param request A request
+ * @return true if the request is integral. This normally means the https schema has been used.
+ */
+ boolean isIntegral(Request request);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The port to use when redirecting a request if a data constraint of confidential is
+ * required. See {@link org.eclipse.jetty.server.server.security.Constraint#getDataConstraint()}
+ */
+ int getConfidentialPort();
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The schema to use when redirecting a request if a data constraint of confidential is
+ * required. See {@link org.eclipse.jetty.server.server.security.Constraint#getDataConstraint()}
+ */
+ String getConfidentialScheme();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param request A request
+ * @return true if the request is confidential. This normally means the https schema has been used.
+ */
+ boolean isConfidential(Request request);
+
+ /* ------------------------------------------------------------ */
+ /** Customize a request for an endpoint.
+ * Called on every request to allow customization of the request for
+ * the particular endpoint (eg security properties from a SSL connection).
+ * @param endpoint
+ * @param request
+ * @throws IOException
+ */
+ void customize(EndPoint endpoint, Request request) throws IOException;
+
+ /* ------------------------------------------------------------ */
+ /** Persist an endpoint.
+ * Called after every request if the connection is to remain open.
+ * @param endpoint
+ * @param request
+ * @throws IOException
+ */
+ void persist(EndPoint endpoint) throws IOException;
+
+ /* ------------------------------------------------------------ */
+ String getHost();
+
+ /* ------------------------------------------------------------ */
+ void setHost(String hostname);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param port The port fto listen of for connections or 0 if any available
+ * port may be used.
+ */
+ void setPort(int port);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The configured port for the connector or 0 if any available
+ * port may be used.
+ */
+ int getPort();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The actual port the connector is listening on or -1 if there
+ * is no port or the connector is not open.
+ */
+ int getLocalPort();
+
+ /* ------------------------------------------------------------ */
+ int getMaxIdleTime();
+ void setMaxIdleTime(int ms);
+
+ /* ------------------------------------------------------------ */
+ int getLowResourceMaxIdleTime();
+ void setLowResourceMaxIdleTime(int ms);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the underlying socket, channel, buffer etc. for the connector.
+ */
+ Object getConnection();
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if names resolution should be done.
+ */
+ boolean getResolveNames();
+
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Get the number of requests handled by this connector
+ * since last call of statsReset(). If setStatsOn(false) then this
+ * is undefined.
+ */
+ public int getRequests();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the connectionsDurationMin.
+ */
+ public long getConnectionsDurationMin();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the connectionsDurationTotal.
+ */
+ public long getConnectionsDurationTotal();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the connectionsOpenMin.
+ */
+ public int getConnectionsOpenMin();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the connectionsRequestsMin.
+ */
+ public int getConnectionsRequestsMin();
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Number of connections accepted by the server since
+ * statsReset() called. Undefined if setStatsOn(false).
+ */
+ public int getConnections() ;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Number of connections currently open that were opened
+ * since statsReset() called. Undefined if setStatsOn(false).
+ */
+ public int getConnectionsOpen() ;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Maximum number of connections opened simultaneously
+ * since statsReset() called. Undefined if setStatsOn(false).
+ */
+ public int getConnectionsOpenMax() ;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Average duration in milliseconds of open connections
+ * since statsReset() called. Undefined if setStatsOn(false).
+ */
+ public long getConnectionsDurationAve() ;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Maximum duration in milliseconds of an open connection
+ * since statsReset() called. Undefined if setStatsOn(false).
+ */
+ public long getConnectionsDurationMax();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Average number of requests per connection
+ * since statsReset() called. Undefined if setStatsOn(false).
+ */
+ public int getConnectionsRequestsAve() ;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Maximum number of requests per connection
+ * since statsReset() called. Undefined if setStatsOn(false).
+ */
+ public int getConnectionsRequestsMax();
+
+
+
+ /* ------------------------------------------------------------ */
+ /** Reset statistics.
+ */
+ public void statsReset();
+
+ /* ------------------------------------------------------------ */
+ public void setStatsOn(boolean on);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if statistics collection is turned on.
+ */
+ public boolean getStatsOn();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Timestamp stats were started at.
+ */
+ public long getStatsOnMs();
+
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java b/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java
new file mode 100644
index 0000000000..d3e98271ad
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java
@@ -0,0 +1,296 @@
+// ========================================================================
+// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+
+package org.eclipse.jetty.server;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+
+
+/* ------------------------------------------------------------ */
+/** Cookie parser
+ * <p>Optimized stateful cookie parser. Cookies fields are added with the
+ * {@link #addCookieField(String)} method and parsed on the next subsequent
+ * call to {@link #getCookies()}.
+ * If the added fields are identical to those last added (as strings), then the
+ * cookies are not re parsed.
+ *
+ *
+ */
+public class CookieCutter
+{
+ private static final byte STATE_DELIMITER = 1;
+ private static final byte STATE_NAME = 2;
+ private static final byte STATE_VALUE = 4;
+ private static final byte STATE_QUOTED_VALUE = 8;
+ private static final byte STATE_UNQUOTED_VALUE = 16;
+
+ private Cookie[] _cookies;
+ private String[] _fields;
+ int _added=0;
+ boolean _dirty;
+ HttpServletRequest _request;
+
+ public CookieCutter()
+ {
+
+ }
+
+ public CookieCutter(HttpServletRequest request)
+ {
+ _request = request;
+ }
+
+ public Cookie[] getCookies()
+ {
+ if (_added>0)
+ {
+ if (!_dirty && _added==_fields.length)
+ {
+ // same cookies as last time!
+ _added=0;
+ return _cookies;
+ }
+
+ parseFields();
+ }
+ return _cookies;
+ }
+
+ public void setCookies(Cookie[] cookies)
+ {
+ _dirty=false;
+ _added=0;
+ _cookies=cookies;
+ }
+
+ public void reset()
+ {
+ _fields=null;
+ _cookies=null;
+ }
+
+ public void addCookieField(String f)
+ {
+ if (!_dirty &&
+ _fields!=null &&
+ _fields.length>_added &&
+ _fields[_added].equals(f))
+ {
+ _added++;
+ return;
+ }
+
+ if (_dirty)
+ {
+ _added++;
+ _fields=(String[])LazyList.addToArray(_fields,f,String.class);
+ }
+ else
+ {
+ _dirty=true;
+ if (_added>0)
+ {
+ String[] fields=new String[_added+1];
+ System.arraycopy(_fields,0,fields,0,_added);
+ fields[_added++]=f;
+ _fields=fields;
+ }
+ else
+ {
+ _fields = new String[]{f};
+ _added=1;
+ }
+
+ }
+ }
+
+ protected void parseFields()
+ {
+ Object cookies = null;
+
+ int version = 0;
+
+ // For each cookie field
+ for (int f=0;f<_added;f++)
+ {
+ String hdr = _fields[f];
+
+ // Parse the header
+ String name = null;
+ String value = null;
+
+ Cookie cookie = null;
+
+ byte state = STATE_NAME;
+ for (int i = 0, tokenstart = 0, length = hdr.length(); i < length; i++)
+ {
+ char c = hdr.charAt(i);
+ switch (c)
+ {
+ case ',':
+ case ';':
+ switch (state)
+ {
+ case STATE_DELIMITER:
+ state = STATE_NAME;
+ tokenstart = i + 1;
+ break;
+ case STATE_UNQUOTED_VALUE:
+ state = STATE_NAME;
+ value = hdr.substring(tokenstart, i).trim();
+ if(_request!=null && _request.isRequestedSessionIdFromURL())
+ value = URIUtil.decodePath(value);
+ tokenstart = i + 1;
+ break;
+ case STATE_NAME:
+ name = hdr.substring(tokenstart, i);
+ value = "";
+ tokenstart = i + 1;
+ break;
+ case STATE_VALUE:
+ state = STATE_NAME;
+ value = "";
+ tokenstart = i + 1;
+ break;
+ }
+ break;
+ case '=':
+ switch (state)
+ {
+ case STATE_NAME:
+ state = STATE_VALUE;
+ name = hdr.substring(tokenstart, i);
+ tokenstart = i + 1;
+ break;
+ case STATE_VALUE:
+ state = STATE_UNQUOTED_VALUE;
+ tokenstart = i;
+ break;
+ }
+ break;
+ case '"':
+ switch (state)
+ {
+ case STATE_VALUE:
+ state = STATE_QUOTED_VALUE;
+ tokenstart = i + 1;
+ break;
+ case STATE_QUOTED_VALUE:
+ state = STATE_DELIMITER;
+ value = hdr.substring(tokenstart, i);
+ break;
+ }
+ break;
+ case ' ':
+ case '\t':
+ break;
+ default:
+ switch (state)
+ {
+ case STATE_VALUE:
+ state = STATE_UNQUOTED_VALUE;
+ tokenstart = i;
+ break;
+ case STATE_DELIMITER:
+ state = STATE_NAME;
+ tokenstart = i;
+ break;
+ }
+ }
+
+ if (i + 1 == length)
+ {
+ switch (state)
+ {
+ case STATE_UNQUOTED_VALUE:
+ value = hdr.substring(tokenstart).trim();
+ if(_request!=null && _request.isRequestedSessionIdFromURL())
+ value = URIUtil.decodePath(value);
+ break;
+ case STATE_NAME:
+ name = hdr.substring(tokenstart);
+ value = "";
+ break;
+ case STATE_VALUE:
+ value = "";
+ break;
+ }
+ }
+
+ if (name != null && value != null)
+ {
+ name = name.trim();
+
+ try
+ {
+ if (name.startsWith("$"))
+ {
+ String lowercaseName = name.toLowerCase();
+ if ("$path".equals(lowercaseName))
+ {
+ cookie.setPath(value);
+ }
+ else if ("$domain".equals(lowercaseName))
+ {
+ cookie.setDomain(value);
+ }
+ else if ("$version".equals(lowercaseName))
+ {
+ version = Integer.parseInt(value);
+ }
+ }
+ else
+ {
+ cookie = new Cookie(name, value);
+
+ if (version > 0)
+ {
+ cookie.setVersion(version);
+ }
+
+ cookies = LazyList.add(cookies, cookie);
+ }
+ }
+ catch (Exception e)
+ {
+ Log.ignore(e);
+ }
+
+ name = null;
+ value = null;
+ }
+ }
+ }
+
+ int l = LazyList.size(cookies);
+ if (l>0)
+ {
+ if (_cookies != null && _cookies.length == l)
+ {
+ for (int i = 0; i < l; i++)
+ _cookies[i] = (Cookie) LazyList.get(cookies, i);
+ }
+ else
+ _cookies = (Cookie[]) LazyList.toArray(cookies,Cookie.class);
+ }
+
+ _added=0;
+ _dirty=false;
+
+ }
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java
new file mode 100644
index 0000000000..50e16efbc7
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java
@@ -0,0 +1,571 @@
+// ========================================================================
+// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.UrlEncoded;
+
+/* ------------------------------------------------------------ */
+/** Servlet RequestDispatcher.
+ *
+ *
+ */
+public class Dispatcher implements RequestDispatcher
+{
+ /** Dispatch include attribute names */
+ public final static String __INCLUDE_PREFIX="javax.servlet.include.";
+ public final static String __INCLUDE_REQUEST_URI= INCLUDE_REQUEST_URI;
+ public final static String __INCLUDE_CONTEXT_PATH= INCLUDE_CONTEXT_PATH;
+ public final static String __INCLUDE_SERVLET_PATH= INCLUDE_SERVLET_PATH;
+ public final static String __INCLUDE_PATH_INFO= INCLUDE_PATH_INFO;
+ public final static String __INCLUDE_QUERY_STRING= INCLUDE_QUERY_STRING;
+
+ /** Dispatch include attribute names */
+ public final static String __FORWARD_PREFIX="javax.servlet.forward.";
+ public final static String __FORWARD_REQUEST_URI= FORWARD_REQUEST_URI;
+ public final static String __FORWARD_CONTEXT_PATH= FORWARD_CONTEXT_PATH;
+ public final static String __FORWARD_SERVLET_PATH= FORWARD_SERVLET_PATH;
+ public final static String __FORWARD_PATH_INFO= FORWARD_PATH_INFO;
+ public final static String __FORWARD_QUERY_STRING= FORWARD_QUERY_STRING;
+
+ /** JSP attributes */
+ public final static String __JSP_FILE="org.apache.catalina.jsp_file";
+
+ /* ------------------------------------------------------------ */
+ private ContextHandler _contextHandler;
+ private String _uri;
+ private String _path;
+ private String _dQuery;
+ private String _named;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param contextHandler
+ * @param uriInContext
+ * @param pathInContext
+ * @param query
+ */
+ public Dispatcher(ContextHandler contextHandler, String uri, String pathInContext, String query)
+ {
+ _contextHandler=contextHandler;
+ _uri=uri;
+ _path=pathInContext;
+ _dQuery=query;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Constructor.
+ * @param servletHandler
+ * @param name
+ */
+ public Dispatcher(ContextHandler contextHandler,String name)
+ throws IllegalStateException
+ {
+ _contextHandler=contextHandler;
+ _named=name;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.RequestDispatcher#forward(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
+ */
+ public void forward(ServletRequest request, ServletResponse response) throws ServletException, IOException
+ {
+ forward(request, response, DispatcherType.FORWARD);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.RequestDispatcher#forward(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
+ */
+ public void error(ServletRequest request, ServletResponse response) throws ServletException, IOException
+ {
+ forward(request, response, DispatcherType.ERROR);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.RequestDispatcher#include(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
+ */
+ public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException
+ {
+ Request base_request=(request instanceof Request)?((Request)request):HttpConnection.getCurrentConnection().getRequest();
+ request.removeAttribute(__JSP_FILE); // TODO remove when glassfish 1044 is fixed
+
+ // TODO - allow stream or writer????
+
+ DispatcherType old_type = base_request.getDispatcherType();
+ Attributes old_attr=base_request.getAttributes();
+ MultiMap old_params=base_request.getParameters();
+ try
+ {
+ base_request.setDispatcherType(DispatcherType.INCLUDE);
+ base_request.getConnection().include();
+ if (_named!=null)
+ _contextHandler.doHandle(_named, base_request,(HttpServletRequest)request, (HttpServletResponse)response);
+ else
+ {
+ String query=_dQuery;
+
+ if (query!=null)
+ {
+ MultiMap parameters=new MultiMap();
+ UrlEncoded.decodeTo(query,parameters,request.getCharacterEncoding());
+
+ if (old_params!=null && old_params.size()>0)
+ {
+ // Merge parameters.
+ Iterator iter = old_params.entrySet().iterator();
+ while (iter.hasNext())
+ {
+ Map.Entry entry = (Map.Entry)iter.next();
+ String name=(String)entry.getKey();
+ Object values=entry.getValue();
+ for (int i=0;i<LazyList.size(values);i++)
+ parameters.add(name, LazyList.get(values, i));
+ }
+
+ }
+ base_request.setParameters(parameters);
+ }
+
+ IncludeAttributes attr = new IncludeAttributes(old_attr);
+
+ attr._requestURI=_uri;
+ attr._contextPath=_contextHandler.getContextPath();
+ attr._servletPath=null; // set by ServletHandler
+ attr._pathInfo=_path;
+ attr._query=query;
+
+ base_request.setAttributes(attr);
+
+ _contextHandler.doHandle(_named==null?_path:_named, base_request,(HttpServletRequest)request, (HttpServletResponse)response);
+ }
+ }
+ finally
+ {
+ base_request.setAttributes(old_attr);
+ base_request.getConnection().included();
+ base_request.setParameters(old_params);
+ base_request.setDispatcherType(old_type);
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.RequestDispatcher#forward(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
+ */
+ protected void forward(ServletRequest request, ServletResponse response, DispatcherType dispatch) throws ServletException, IOException
+ {
+ Request base_request=(request instanceof Request)?((Request)request):HttpConnection.getCurrentConnection().getRequest();
+ Response base_response=(Response)base_request.getResponse();
+ base_response.fwdReset();
+ request.removeAttribute(__JSP_FILE); // TODO remove when glassfish 1044 is fixed
+
+ String old_uri=base_request.getRequestURI();
+ String old_context_path=base_request.getContextPath();
+ String old_servlet_path=base_request.getServletPath();
+ String old_path_info=base_request.getPathInfo();
+ String old_query=base_request.getQueryString();
+ Attributes old_attr=base_request.getAttributes();
+ MultiMap old_params=base_request.getParameters();
+ DispatcherType old_type=base_request.getDispatcherType();
+
+ try
+ {
+ base_request.setDispatcherType(dispatch);
+
+ if (_named!=null)
+ _contextHandler.doHandle(_named, base_request,(HttpServletRequest)request, (HttpServletResponse)response);
+ else
+ {
+ String query=_dQuery;
+
+ if (query!=null)
+ {
+ MultiMap parameters=new MultiMap();
+ UrlEncoded.decodeTo(query,parameters,request.getCharacterEncoding());
+
+ boolean rewrite_old_query = false;
+
+ if( old_params == null )
+ {
+ base_request.getParameterNames(); // force parameters to be evaluated
+ old_params = base_request.getParameters();
+ }
+
+ if (old_params!=null && old_params.size()>0)
+ {
+ // Merge parameters; new parameters of the same name take precedence.
+ Iterator iter = old_params.entrySet().iterator();
+ while (iter.hasNext())
+ {
+ Map.Entry entry = (Map.Entry)iter.next();
+ String name=(String)entry.getKey();
+
+ if (parameters.containsKey(name))
+ {
+ rewrite_old_query = true;
+ }
+ else
+ {
+ Object values=entry.getValue();
+ for (int i=0;i<LazyList.size(values);i++)
+ {
+ parameters.add(name, LazyList.get(values, i));
+ }
+ }
+ }
+ }
+
+ if (old_query != null && old_query.length()>0)
+ {
+ if ( rewrite_old_query )
+ {
+ StringBuilder overridden_query_string = new StringBuilder();
+ MultiMap overridden_old_query = new MultiMap();
+ UrlEncoded.decodeTo(old_query,overridden_old_query,request.getCharacterEncoding());
+
+ MultiMap overridden_new_query = new MultiMap();
+ UrlEncoded.decodeTo(query,overridden_new_query,request.getCharacterEncoding());
+
+ Iterator iter = overridden_old_query.entrySet().iterator();
+ while (iter.hasNext())
+ {
+ Map.Entry entry = (Map.Entry)iter.next();
+ String name=(String)entry.getKey();
+ if(!overridden_new_query.containsKey(name))
+ {
+ Object values=entry.getValue();
+ for (int i=0;i<LazyList.size(values);i++)
+ {
+ overridden_query_string.append("&"+name+"="+LazyList.get(values, i));
+ }
+ }
+ }
+
+ query = query + overridden_query_string;
+ }
+ else
+ {
+ query=query+"&"+old_query;
+ }
+ }
+
+ base_request.setParameters(parameters);
+ base_request.setQueryString(query);
+ }
+
+ ForwardAttributes attr = new ForwardAttributes(old_attr);
+
+ //If we have already been forwarded previously, then keep using the established
+ //original value. Otherwise, this is the first forward and we need to establish the values.
+ //Note: the established value on the original request for pathInfo and
+ //for queryString is allowed to be null, but cannot be null for the other values.
+ if ((String)old_attr.getAttribute(__FORWARD_REQUEST_URI) != null)
+ {
+ attr._pathInfo=(String)old_attr.getAttribute(__FORWARD_PATH_INFO);
+ attr._query=(String)old_attr.getAttribute(__FORWARD_QUERY_STRING);
+ attr._requestURI=(String)old_attr.getAttribute(__FORWARD_REQUEST_URI);
+ attr._contextPath=(String)old_attr.getAttribute(__FORWARD_CONTEXT_PATH);
+ attr._servletPath=(String)old_attr.getAttribute(__FORWARD_SERVLET_PATH);
+ }
+ else
+ {
+ attr._pathInfo=old_path_info;
+ attr._query=old_query;
+ attr._requestURI=old_uri;
+ attr._contextPath=old_context_path;
+ attr._servletPath=old_servlet_path;
+ }
+
+
+
+ base_request.setRequestURI(_uri);
+ base_request.setContextPath(_contextHandler.getContextPath());
+ base_request.setAttributes(attr);
+ base_request.setQueryString(query);
+
+ _contextHandler.doHandle(_path, base_request,(HttpServletRequest)request, (HttpServletResponse)response);
+
+ if (base_request.getConnection().getResponse().isWriting())
+ {
+ try {response.getWriter().close();}
+ catch(IllegalStateException e) { response.getOutputStream().close(); }
+ }
+ else
+ {
+ try {response.getOutputStream().close();}
+ catch(IllegalStateException e) { response.getWriter().close(); }
+ }
+ }
+ }
+ finally
+ {
+ base_request.setRequestURI(old_uri);
+ base_request.setContextPath(old_context_path);
+ base_request.setServletPath(old_servlet_path);
+ base_request.setPathInfo(old_path_info);
+ base_request.setAttributes(old_attr);
+ base_request.setParameters(old_params);
+ base_request.setQueryString(old_query);
+ base_request.setDispatcherType(old_type);
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ private class ForwardAttributes implements Attributes
+ {
+ Attributes _attr;
+
+ String _requestURI;
+ String _contextPath;
+ String _servletPath;
+ String _pathInfo;
+ String _query;
+
+ ForwardAttributes(Attributes attributes)
+ {
+ _attr=attributes;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Object getAttribute(String key)
+ {
+ if (Dispatcher.this._named==null)
+ {
+ if (key.equals(__FORWARD_PATH_INFO))
+ return _pathInfo;
+ if (key.equals(__FORWARD_REQUEST_URI))
+ return _requestURI;
+ if (key.equals(__FORWARD_SERVLET_PATH))
+ return _servletPath;
+ if (key.equals(__FORWARD_CONTEXT_PATH))
+ return _contextPath;
+ if (key.equals(__FORWARD_QUERY_STRING))
+ return _query;
+ }
+
+ if (key.startsWith(__INCLUDE_PREFIX))
+ return null;
+
+ return _attr.getAttribute(key);
+ }
+
+ /* ------------------------------------------------------------ */
+ public Enumeration getAttributeNames()
+ {
+ HashSet set=new HashSet();
+ Enumeration e=_attr.getAttributeNames();
+ while(e.hasMoreElements())
+ {
+ String name=(String)e.nextElement();
+ if (!name.startsWith(__INCLUDE_PREFIX) &&
+ !name.startsWith(__FORWARD_PREFIX))
+ set.add(name);
+ }
+
+ if (_named==null)
+ {
+ if (_pathInfo!=null)
+ set.add(__FORWARD_PATH_INFO);
+ else
+ set.remove(__FORWARD_PATH_INFO);
+ set.add(__FORWARD_REQUEST_URI);
+ set.add(__FORWARD_SERVLET_PATH);
+ set.add(__FORWARD_CONTEXT_PATH);
+ if (_query!=null)
+ set.add(__FORWARD_QUERY_STRING);
+ else
+ set.remove(__FORWARD_QUERY_STRING);
+ }
+
+ return Collections.enumeration(set);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setAttribute(String key, Object value)
+ {
+ if (_named==null && key.startsWith("javax.servlet."))
+ {
+ if (key.equals(__FORWARD_PATH_INFO))
+ _pathInfo=(String)value;
+ else if (key.equals(__FORWARD_REQUEST_URI))
+ _requestURI=(String)value;
+ else if (key.equals(__FORWARD_SERVLET_PATH))
+ _servletPath=(String)value;
+ else if (key.equals(__FORWARD_CONTEXT_PATH))
+ _contextPath=(String)value;
+ else if (key.equals(__FORWARD_QUERY_STRING))
+ _query=(String)value;
+
+ else if (value==null)
+ _attr.removeAttribute(key);
+ else
+ _attr.setAttribute(key,value);
+ }
+ else if (value==null)
+ _attr.removeAttribute(key);
+ else
+ _attr.setAttribute(key,value);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String toString()
+ {
+ return "FORWARD+"+_attr.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void clearAttributes()
+ {
+ throw new IllegalStateException();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void removeAttribute(String name)
+ {
+ setAttribute(name,null);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ private class IncludeAttributes implements Attributes
+ {
+ Attributes _attr;
+
+ String _requestURI;
+ String _contextPath;
+ String _servletPath;
+ String _pathInfo;
+ String _query;
+
+ IncludeAttributes(Attributes attributes)
+ {
+ _attr=attributes;
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public Object getAttribute(String key)
+ {
+ if (Dispatcher.this._named==null)
+ {
+ if (key.equals(__INCLUDE_PATH_INFO)) return _pathInfo;
+ if (key.equals(__INCLUDE_SERVLET_PATH)) return _servletPath;
+ if (key.equals(__INCLUDE_CONTEXT_PATH)) return _contextPath;
+ if (key.equals(__INCLUDE_QUERY_STRING)) return _query;
+ if (key.equals(__INCLUDE_REQUEST_URI)) return _requestURI;
+ }
+ else if (key.startsWith(__INCLUDE_PREFIX))
+ return null;
+
+
+ return _attr.getAttribute(key);
+ }
+
+ /* ------------------------------------------------------------ */
+ public Enumeration getAttributeNames()
+ {
+ HashSet set=new HashSet();
+ Enumeration e=_attr.getAttributeNames();
+ while(e.hasMoreElements())
+ {
+ String name=(String)e.nextElement();
+ if (!name.startsWith(__INCLUDE_PREFIX))
+ set.add(name);
+ }
+
+ if (_named==null)
+ {
+ if (_pathInfo!=null)
+ set.add(__INCLUDE_PATH_INFO);
+ else
+ set.remove(__INCLUDE_PATH_INFO);
+ set.add(__INCLUDE_REQUEST_URI);
+ set.add(__INCLUDE_SERVLET_PATH);
+ set.add(__INCLUDE_CONTEXT_PATH);
+ if (_query!=null)
+ set.add(__INCLUDE_QUERY_STRING);
+ else
+ set.remove(__INCLUDE_QUERY_STRING);
+ }
+
+ return Collections.enumeration(set);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setAttribute(String key, Object value)
+ {
+ if (_named==null && key.startsWith("javax.servlet."))
+ {
+ if (key.equals(__INCLUDE_PATH_INFO)) _pathInfo=(String)value;
+ else if (key.equals(__INCLUDE_REQUEST_URI)) _requestURI=(String)value;
+ else if (key.equals(__INCLUDE_SERVLET_PATH)) _servletPath=(String)value;
+ else if (key.equals(__INCLUDE_CONTEXT_PATH)) _contextPath=(String)value;
+ else if (key.equals(__INCLUDE_QUERY_STRING)) _query=(String)value;
+ else if (value==null)
+ _attr.removeAttribute(key);
+ else
+ _attr.setAttribute(key,value);
+ }
+ else if (value==null)
+ _attr.removeAttribute(key);
+ else
+ _attr.setAttribute(key,value);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String toString()
+ {
+ return "INCLUDE+"+_attr.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void clearAttributes()
+ {
+ throw new IllegalStateException();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void removeAttribute(String name)
+ {
+ setAttribute(name,null);
+ }
+ }
+};
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Handler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Handler.java
new file mode 100644
index 0000000000..3e0c3ff5ab
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Handler.java
@@ -0,0 +1,48 @@
+// ========================================================================
+// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.util.component.LifeCycle;
+
+
+public interface Handler extends LifeCycle
+{
+ /* ------------------------------------------------------------ */
+ /** Handle a request.
+ * @param target The target of the request - either a URI or a name.
+ * @param request The request either as the {@link Request}
+ * object or a wrapper of that request. The {@link HttpConnection#getCurrentConnection()}
+ * method can be used access the Request object if required.
+ * @param response The response as the {@link Response}
+ * object or a wrapper of that request. The {@link HttpConnection#getCurrentConnection()}
+ * method can be used access the Response object if required.
+ * @throws IOException
+ * @throws ServletException
+ */
+ public void handle(String target, HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException;
+
+ public void setServer(Server server);
+ public Server getServer();
+
+ public void destroy();
+
+}
+
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HandlerContainer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HandlerContainer.java
new file mode 100644
index 0000000000..e242f7d30f
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HandlerContainer.java
@@ -0,0 +1,33 @@
+// ========================================================================
+// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+
+package org.eclipse.jetty.server;
+
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/**
+ * A Handler that contains other Handlers.
+ * <p>
+ * The contained handlers may be one (see @{link {@link org.eclipse.jetty.server.server.handler.HandlerWrapper})
+ * or many (see {@link org.eclipse.jetty.server.server.handler.HandlerList} or {@link org.eclipse.jetty.server.server.handler.HandlerCollection}.
+ *
+ */
+public interface HandlerContainer extends LifeCycle
+{
+ public void addHandler(Handler handler);
+ public void removeHandler(Handler handler);
+
+ public Handler[] getChildHandlers();
+ public Handler[] getChildHandlersByClass(Class<?> byclass);
+ public Handler getChildHandlerByClass(Class<?> byclass);
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
new file mode 100644
index 0000000000..3d3584d526
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
@@ -0,0 +1,1114 @@
+// ========================================================================
+// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.AbstractGenerator;
+import org.eclipse.jetty.http.EncodedHttpURI;
+import org.eclipse.jetty.http.Generator;
+import org.eclipse.jetty.http.HttpContent;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpHeaderValues;
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.HttpVersions;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.http.Parser;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.io.HttpException;
+import org.eclipse.jetty.io.BufferCache.CachedBuffer;
+import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.thread.Timeout;
+
+/**
+ * <p>A HttpConnection represents the connection of a HTTP client to the server
+ * and is created by an instance of a {@link Connector}. It's prime function is
+ * to associate {@link Request} and {@link Response} instances with a {@link EndPoint}.
+ * </p>
+ * <p>
+ * A connection is also the prime mechanism used by jetty to recycle objects without
+ * pooling. The {@link Request}, {@link Response}, {@link HttpParser}, {@link HttpGenerator}
+ * and {@link HttpFields} instances are all recycled for the duraction of
+ * a connection. Where appropriate, allocated buffers are also kept associated
+ * with the connection via the parser and/or generator.
+ * </p>
+ *
+ *
+ *
+ *
+ */
+public class HttpConnection implements Connection
+{
+ private static int UNKNOWN = -2;
+ private static ThreadLocal<HttpConnection> __currentConnection = new ThreadLocal<HttpConnection>();
+
+ private final long _timeStamp=System.currentTimeMillis();
+ private int _requests;
+ private volatile boolean _handling;
+
+ protected final Connector _connector;
+ protected final EndPoint _endp;
+ protected final Server _server;
+ protected final HttpURI _uri;
+
+ protected final Parser _parser;
+ protected final HttpFields _requestFields;
+ protected final Request _request;
+ protected ServletInputStream _in;
+
+ protected final Generator _generator;
+ protected final HttpFields _responseFields;
+ protected final Response _response;
+ protected Output _out;
+ protected OutputWriter _writer;
+ protected PrintWriter _printWriter;
+
+ int _include;
+
+ private Object _associatedObject; // associated object
+
+ private transient int _expect = UNKNOWN;
+ private transient int _version = UNKNOWN;
+ private transient boolean _head = false;
+ private transient boolean _host = false;
+ private transient boolean _delayedHandling=false;
+
+ /* ------------------------------------------------------------ */
+ public static HttpConnection getCurrentConnection()
+ {
+ return (HttpConnection) __currentConnection.get();
+ }
+
+ /* ------------------------------------------------------------ */
+ protected static void setCurrentConnection(HttpConnection connection)
+ {
+ __currentConnection.set(connection);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Constructor
+ *
+ */
+ public HttpConnection(Connector connector, EndPoint endpoint, Server server)
+ {
+ _uri = URIUtil.__CHARSET==StringUtil.__UTF8?new HttpURI():new EncodedHttpURI(URIUtil.__CHARSET);
+ _connector = connector;
+ _endp = endpoint;
+ _parser = new HttpParser(_connector, endpoint, new RequestHandler(), _connector.getHeaderBufferSize(), _connector.getRequestBufferSize());
+ _requestFields = new HttpFields();
+ _responseFields = new HttpFields();
+ _request = new Request(this);
+ _response = new Response(this);
+ _generator = new HttpGenerator(_connector, _endp, _connector.getHeaderBufferSize(), _connector.getResponseBufferSize());
+ _generator.setSendServerVersion(server.getSendServerVersion());
+ _server = server;
+ }
+
+ protected HttpConnection(Connector connector, EndPoint endpoint, Server server,
+ Parser parser, Generator generator, Request request)
+ {
+ _uri = URIUtil.__CHARSET==StringUtil.__UTF8?new HttpURI():new EncodedHttpURI(URIUtil.__CHARSET);
+ _connector = connector;
+ _endp = endpoint;
+ _parser = parser;
+ _requestFields = new HttpFields();
+ _responseFields = new HttpFields();
+ _request = request;
+ _response = new Response(this);
+ _generator = generator;
+ _generator.setSendServerVersion(server.getSendServerVersion());
+ _server = server;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void destroy()
+ {
+ synchronized(this)
+ {
+ while(_handling)
+ Thread.yield();
+
+ if (_parser!=null)
+ _parser.reset(true);
+
+ if (_generator!=null)
+ _generator.reset(true);
+
+ if (_requestFields!=null)
+ _requestFields.destroy();
+
+ if (_responseFields!=null)
+ _responseFields.destroy();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the parser used by this connection
+ */
+ public Parser getParser()
+ {
+ return _parser;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the number of requests handled by this connection
+ */
+ public int getRequests()
+ {
+ return _requests;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The time this connection was established.
+ */
+ public long getTimeStamp()
+ {
+ return _timeStamp;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the associatedObject.
+ */
+ public Object getAssociatedObject()
+ {
+ return _associatedObject;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param associatedObject The associatedObject to set.
+ */
+ public void setAssociatedObject(Object associatedObject)
+ {
+ _associatedObject = associatedObject;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the connector.
+ */
+ public Connector getConnector()
+ {
+ return _connector;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the requestFields.
+ */
+ public HttpFields getRequestFields()
+ {
+ return _requestFields;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the responseFields.
+ */
+ public HttpFields getResponseFields()
+ {
+ return _responseFields;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The result of calling {@link #getConnector}.{@link Connector#isConfidential(Request) isCondidential}(request), or false
+ * if there is no connector.
+ */
+ public boolean isConfidential(Request request)
+ {
+ if (_connector!=null)
+ return _connector.isConfidential(request);
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Find out if the request is INTEGRAL security.
+ * @param request
+ * @return <code>true</code> if there is a {@link #getConnector() connector} and it considers <code>request</code>
+ * to be {@link Connector#isIntegral(Request) integral}
+ */
+ public boolean isIntegral(Request request)
+ {
+ if (_connector!=null)
+ return _connector.isIntegral(request);
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The {@link EndPoint} for this connection.
+ */
+ public EndPoint getEndPoint()
+ {
+ return _endp;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return <code>false</code> (this method is not yet implemented)
+ */
+ public boolean getResolveNames()
+ {
+ return _connector.getResolveNames();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the request.
+ */
+ public Request getRequest()
+ {
+ return _request;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the response.
+ */
+ public Response getResponse()
+ {
+ return _response;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The input stream for this connection. The stream will be created if it does not already exist.
+ */
+ public ServletInputStream getInputStream() throws IOException
+ {
+ // If the client is expecting 100 CONTINUE, then send it now.
+ if (_expect == HttpHeaderValues.CONTINUE_ORDINAL)
+ {
+ if (((HttpParser)_parser).getHeaderBuffer()==null || ((HttpParser)_parser).getHeaderBuffer().length()<2)
+ {
+ _generator.setResponse(HttpStatus.CONTINUE_100, null);
+ _generator.completeHeader(null, true);
+ _generator.complete();
+ _generator.reset(false);
+ }
+ _expect = UNKNOWN;
+ }
+
+ if (_in == null)
+ _in = new HttpInput(((HttpParser)_parser),_connector.getMaxIdleTime());
+ return _in;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The output stream for this connection. The stream will be created if it does not already exist.
+ */
+ public ServletOutputStream getOutputStream()
+ {
+ if (_out == null)
+ _out = new Output();
+ return _out;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return A {@link PrintWriter} wrapping the {@link #getOutputStream output stream}. The writer is created if it
+ * does not already exist.
+ */
+ public PrintWriter getPrintWriter(String encoding)
+ {
+ getOutputStream();
+ if (_writer==null)
+ {
+ _writer=new OutputWriter();
+ _printWriter=new PrintWriter(_writer)
+ {
+ /* ------------------------------------------------------------ */
+ /*
+ * @see java.io.PrintWriter#close()
+ */
+ public void close()
+ {
+ try
+ {
+ out.close();
+ }
+ catch(IOException e)
+ {
+ Log.debug(e);
+ setError();
+ }
+ }
+
+ };
+ }
+ _writer.setCharacterEncoding(encoding);
+ return _printWriter;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isResponseCommitted()
+ {
+ return _generator.isCommitted();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void handle() throws IOException
+ {
+ // Loop while more in buffer
+ boolean more_in_buffer =true; // assume true until proven otherwise
+ boolean progress=true;
+
+ try
+ {
+ _handling=true;
+ setCurrentConnection(this);
+
+ while (more_in_buffer)
+ {
+ try
+ {
+ if (_request._async.isAsync())
+ {
+ Log.debug("resume request",_request);
+ if (!_request._async.isComplete())
+ handleRequest();
+ else if (!_parser.isComplete())
+ progress|=_parser.parseAvailable()>0;
+
+ if (_generator.isCommitted() && !_generator.isComplete())
+ _generator.flushBuffer();
+ if (_endp.isBufferingOutput())
+ _endp.flush();
+ }
+ else
+ {
+ // If we are not ended then parse available
+ if (!_parser.isComplete())
+ progress|=_parser.parseAvailable()>0;
+
+ // Do we have more generating to do?
+ // Loop here because some writes may take multiple steps and
+ // we need to flush them all before potentially blocking in the
+ // next loop.
+ while (_generator.isCommitted() && !_generator.isComplete())
+ {
+ long written=_generator.flushBuffer();
+ if (written<=0)
+ break;
+ progress=true;
+ if (_endp.isBufferingOutput())
+ _endp.flush();
+ }
+
+ // Flush buffers
+ if (_endp.isBufferingOutput())
+ {
+ _endp.flush();
+ if (!_endp.isBufferingOutput())
+ progress=true;
+ }
+
+ if (!progress)
+ return;
+ progress=false;
+ }
+ }
+ catch (HttpException e)
+ {
+ if (Log.isDebugEnabled())
+ {
+ Log.debug("uri="+_uri);
+ Log.debug("fields="+_requestFields);
+ Log.debug(e);
+ }
+ _generator.sendError(e.getStatus(), e.getReason(), null, true);
+
+ _parser.reset(true);
+ _endp.close();
+ throw e;
+ }
+ finally
+ {
+ more_in_buffer = _parser.isMoreInBuffer() || _endp.isBufferingInput();
+
+ if (_parser.isComplete() && _generator.isComplete() && !_endp.isBufferingOutput())
+ {
+ if (!_generator.isPersistent())
+ {
+ _parser.reset(true);
+ more_in_buffer=false;
+ }
+
+ reset(!more_in_buffer);
+ progress=true;
+ }
+
+ if (_request.isAsyncStarted())
+ {
+ Log.debug("return with suspended request");
+ more_in_buffer=false;
+ }
+ else if (_generator.isCommitted() && !_generator.isComplete() && _endp instanceof SelectChannelEndPoint) // TODO remove SelectChannel dependency
+ ((SelectChannelEndPoint)_endp).setWritable(false);
+ }
+ }
+ }
+ finally
+ {
+ setCurrentConnection(null);
+ _handling=false;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void scheduleTimeout(Timeout.Task task, long timeoutMs)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void cancelTimeout(Timeout.Task task)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void reset(boolean returnBuffers)
+ {
+ _parser.reset(returnBuffers); // TODO maybe only release when low on resources
+ _requestFields.clear();
+ _request.recycle();
+
+ _generator.reset(returnBuffers); // TODO maybe only release when low on resources
+ _responseFields.clear();
+ _response.recycle();
+
+ _uri.clear();
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void handleRequest() throws IOException
+ {
+ boolean handling=_server.isRunning() && _request._async.handling();
+ boolean error = false;
+
+ String threadName=null;
+ try
+ {
+ if (Log.isDebugEnabled())
+ {
+ threadName=Thread.currentThread().getName();
+ Thread.currentThread().setName(threadName+" - "+_uri);
+ }
+
+ // Loop here to handle async request redispatches.
+ // The loop is controlled by the call to async.unhandle in the
+ // finally block below. If call is from a non-blocking connector,
+ // then the unhandle will return false only if an async dispatch has
+ // already happened when unhandle is called. For a blocking connector,
+ // the wait for the asynchronous dispatch or timeout actually happens
+ // within the call to unhandle().
+ while (handling)
+ {
+ _request.setHandled(false);
+ try
+ {
+ String info=URIUtil.canonicalPath(_uri.getDecodedPath());
+ if (info==null)
+ throw new HttpException(400);
+ _request.setPathInfo(info);
+
+ if (_out!=null)
+ _out.reopen();
+
+ if (_request._async.isInitial())
+ {
+ _request.setDispatcherType(DispatcherType.REQUEST);
+ _connector.customize(_endp, _request);
+ _server.handle(this);
+ }
+ else
+ {
+ _request.setDispatcherType(DispatcherType.ASYNC);
+ _server.handleAsync(this);
+ }
+
+ }
+ catch (RetryRequest r)
+ {
+ Log.ignore(r);
+ }
+ catch (EofException e)
+ {
+ Log.ignore(e);
+ error=true;
+ }
+ catch (HttpException e)
+ {
+ Log.debug(e);
+ _request.setHandled(true);
+ _response.sendError(e.getStatus(), e.getReason());
+ error=true;
+ }
+ catch (Exception e)
+ {
+ Log.warn(e);
+ _request.setHandled(true);
+ _generator.sendError(500, null, null, true);
+ error=true;
+ }
+ catch (Error e)
+ {
+ Log.warn(e);
+ _request.setHandled(true);
+ _generator.sendError(500, null, null, true);
+ error=true;
+ }
+ finally
+ {
+ handling = !_request._async.unhandle() && _server != null;
+ }
+ }
+ }
+ finally
+ {
+ if (threadName!=null)
+ Thread.currentThread().setName(threadName);
+
+ if (_request._async.isUncompleted())
+ {
+ _request._async.doComplete();
+
+ if (_expect == HttpHeaderValues.CONTINUE_ORDINAL)
+ {
+ // Continue not sent so don't parse any content
+ _expect = UNKNOWN;
+ if (_parser instanceof HttpParser)
+ ((HttpParser)_parser).setState(HttpParser.STATE_END);
+ }
+
+ if(_endp.isOpen())
+ {
+ if (_generator.isPersistent())
+ _connector.persist(_endp);
+
+ if (error)
+ _endp.close();
+ else
+ {
+ if (!_response.isCommitted() && !_request.isHandled())
+ _response.sendError(HttpServletResponse.SC_NOT_FOUND);
+ _response.complete();
+ }
+ }
+ else
+ {
+ _response.complete();
+ }
+
+ _request.setHandled(true);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void commitResponse(boolean last) throws IOException
+ {
+ if (!_generator.isCommitted())
+ {
+ _generator.setResponse(_response.getStatus(), _response.getReason());
+ _generator.completeHeader(_responseFields, last);
+ }
+ if (last)
+ _generator.complete();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void completeResponse() throws IOException
+ {
+ if (!_generator.isCommitted())
+ {
+ _generator.setResponse(_response.getStatus(), _response.getReason());
+ _generator.completeHeader(_responseFields, HttpGenerator.LAST);
+ }
+
+ _generator.complete();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void flushResponse() throws IOException
+ {
+ try
+ {
+ commitResponse(HttpGenerator.MORE);
+ _generator.flushBuffer();
+ }
+ catch(IOException e)
+ {
+ throw (e instanceof EofException) ? e:new EofException(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public Generator getGenerator()
+ {
+ return _generator;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public boolean isIncluding()
+ {
+ return _include>0;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void include()
+ {
+ _include++;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void included()
+ {
+ _include--;
+ if (_out!=null)
+ _out.reopen();
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isIdle()
+ {
+ return _generator.isIdle() && (_parser.isIdle() || _delayedHandling);
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ private class RequestHandler extends HttpParser.EventHandler
+ {
+ private String _charset;
+
+ /*
+ *
+ * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#startRequest(org.eclipse.io.Buffer,
+ * org.eclipse.io.Buffer, org.eclipse.io.Buffer)
+ */
+ public void startRequest(Buffer method, Buffer uri, Buffer version) throws IOException
+ {
+ _host = false;
+ _expect = UNKNOWN;
+ _delayedHandling=false;
+ _charset=null;
+
+ if(_request.getTimeStamp()==0)
+ _request.setTimeStamp(System.currentTimeMillis());
+ _request.setMethod(method.toString());
+
+ try
+ {
+ _uri.parse(uri.array(), uri.getIndex(), uri.length());
+ _request.setUri(_uri);
+
+ if (version==null)
+ {
+ _request.setProtocol(HttpVersions.HTTP_0_9);
+ _version=HttpVersions.HTTP_0_9_ORDINAL;
+ }
+ else
+ {
+ version= HttpVersions.CACHE.get(version);
+ _version = HttpVersions.CACHE.getOrdinal(version);
+ if (_version <= 0) _version = HttpVersions.HTTP_1_0_ORDINAL;
+ _request.setProtocol(version.toString());
+ }
+
+ _head = method == HttpMethods.HEAD_BUFFER; // depends on method being decached.
+ }
+ catch (Exception e)
+ {
+ throw new HttpException(HttpStatus.BAD_REQUEST_400,null,e);
+ }
+ }
+
+ /*
+ * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#parsedHeaderValue(org.eclipse.io.Buffer)
+ */
+ public void parsedHeader(Buffer name, Buffer value)
+ {
+ int ho = HttpHeaders.CACHE.getOrdinal(name);
+ switch (ho)
+ {
+ case HttpHeaders.HOST_ORDINAL:
+ // TODO check if host matched a host in the URI.
+ _host = true;
+ break;
+
+ case HttpHeaders.EXPECT_ORDINAL:
+ value = HttpHeaderValues.CACHE.lookup(value);
+ _expect = HttpHeaderValues.CACHE.getOrdinal(value);
+ break;
+
+ case HttpHeaders.ACCEPT_ENCODING_ORDINAL:
+ case HttpHeaders.USER_AGENT_ORDINAL:
+ value = HttpHeaderValues.CACHE.lookup(value);
+ break;
+
+ case HttpHeaders.CONTENT_TYPE_ORDINAL:
+ value = MimeTypes.CACHE.lookup(value);
+ _charset=MimeTypes.getCharsetFromContentType(value);
+ break;
+
+ case HttpHeaders.CONNECTION_ORDINAL:
+ //looks rather clumsy, but the idea is to optimize for a single valued header
+ int ordinal = HttpHeaderValues.CACHE.getOrdinal(value);
+ switch(ordinal)
+ {
+ case -1:
+ {
+ String[] values = value.toString().split(",");
+ for (int i=0;values!=null && i<values.length;i++)
+ {
+ CachedBuffer cb = HttpHeaderValues.CACHE.get(values[i].trim());
+
+ if (cb!=null)
+ {
+ switch(cb.getOrdinal())
+ {
+ case HttpHeaderValues.CLOSE_ORDINAL:
+ _responseFields.add(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.CLOSE_BUFFER);
+ _generator.setPersistent(false);
+ break;
+
+ case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
+ if (_version==HttpVersions.HTTP_1_0_ORDINAL)
+ _responseFields.add(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.KEEP_ALIVE_BUFFER);
+ break;
+ }
+ }
+ }
+ break;
+ }
+ case HttpHeaderValues.CLOSE_ORDINAL:
+ _responseFields.put(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.CLOSE_BUFFER);
+ _generator.setPersistent(false);
+ break;
+
+ case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
+ if (_version==HttpVersions.HTTP_1_0_ORDINAL)
+ _responseFields.put(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.KEEP_ALIVE_BUFFER);
+ break;
+ }
+ }
+
+ _requestFields.add(name, value);
+ }
+
+ /*
+ * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#headerComplete()
+ */
+ public void headerComplete() throws IOException
+ {
+ _requests++;
+ _generator.setVersion(_version);
+ switch (_version)
+ {
+ case HttpVersions.HTTP_0_9_ORDINAL:
+ break;
+ case HttpVersions.HTTP_1_0_ORDINAL:
+ _generator.setHead(_head);
+ break;
+ case HttpVersions.HTTP_1_1_ORDINAL:
+ _generator.setHead(_head);
+
+ if (_server.getSendDateHeader())
+ _responseFields.put(HttpHeaders.DATE_BUFFER, _request.getTimeStampBuffer(),_request.getTimeStamp());
+
+ if (!_host)
+ {
+ _generator.setResponse(HttpStatus.BAD_REQUEST_400, null);
+ _responseFields.put(HttpHeaders.CONNECTION_BUFFER, HttpHeaderValues.CLOSE_BUFFER);
+ _generator.completeHeader(_responseFields, true);
+ _generator.complete();
+ return;
+ }
+
+ if (_expect != UNKNOWN)
+ {
+ if (_expect == HttpHeaderValues.CONTINUE_ORDINAL)
+ {
+ }
+ else if (_expect == HttpHeaderValues.PROCESSING_ORDINAL)
+ {
+ }
+ else
+ {
+ _generator.sendError(HttpStatus.EXPECTATION_FAILED_417, null, null, true);
+ return;
+ }
+ }
+
+ break;
+ default:
+ }
+
+ if(_charset!=null)
+ _request.setCharacterEncodingUnchecked(_charset);
+
+ // Either handle now or wait for first content
+ if ((((HttpParser)_parser).getContentLength()<=0 && !((HttpParser)_parser).isChunking())||_expect==HttpHeaderValues.CONTINUE_ORDINAL)
+ handleRequest();
+ else
+ _delayedHandling=true;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#content(int, org.eclipse.io.Buffer)
+ */
+ public void content(Buffer ref) throws IOException
+ {
+ if (_delayedHandling)
+ {
+ _delayedHandling=false;
+ handleRequest();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#messageComplete(int)
+ */
+ public void messageComplete(long contentLength) throws IOException
+ {
+ if (_delayedHandling)
+ {
+ _delayedHandling=false;
+ handleRequest();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#startResponse(org.eclipse.io.Buffer, int,
+ * org.eclipse.io.Buffer)
+ */
+ public void startResponse(Buffer version, int status, Buffer reason)
+ {
+ Log.debug("Bad request!: "+version+" "+status+" "+reason);
+ }
+
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public class Output extends HttpOutput
+ {
+ Output()
+ {
+ super((AbstractGenerator)HttpConnection.this._generator,_connector.getMaxIdleTime());
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see java.io.OutputStream#close()
+ */
+ public void close() throws IOException
+ {
+ if (_closed)
+ return;
+
+ if (!isIncluding() && !_generator.isCommitted())
+ commitResponse(HttpGenerator.LAST);
+ else
+ flushResponse();
+
+ super.close();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see java.io.OutputStream#flush()
+ */
+ public void flush() throws IOException
+ {
+ if (!_generator.isCommitted())
+ commitResponse(HttpGenerator.MORE);
+ super.flush();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletOutputStream#print(java.lang.String)
+ */
+ public void print(String s) throws IOException
+ {
+ if (_closed)
+ throw new IOException("Closed");
+ PrintWriter writer=getPrintWriter(null);
+ writer.print(s);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void sendResponse(Buffer response) throws IOException
+ {
+ ((HttpGenerator)_generator).sendResponse(response);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void sendContent(Object content) throws IOException
+ {
+ Resource resource=null;
+
+ if (_closed)
+ throw new IOException("Closed");
+
+ if (_generator.getContentWritten() > 0) throw new IllegalStateException("!empty");
+
+ if (content instanceof HttpContent)
+ {
+ HttpContent c = (HttpContent) content;
+ Buffer contentType = c.getContentType();
+ if (contentType != null && !_responseFields.containsKey(HttpHeaders.CONTENT_TYPE_BUFFER))
+ {
+ String enc = _response.getSetCharacterEncoding();
+ if(enc==null)
+ _responseFields.add(HttpHeaders.CONTENT_TYPE_BUFFER, contentType);
+ else
+ {
+ if(contentType instanceof CachedBuffer)
+ {
+ CachedBuffer content_type = ((CachedBuffer)contentType).getAssociate(enc);
+ if(content_type!=null)
+ _responseFields.put(HttpHeaders.CONTENT_TYPE_BUFFER, content_type);
+ else
+ {
+ _responseFields.put(HttpHeaders.CONTENT_TYPE_BUFFER,
+ contentType+";charset="+QuotedStringTokenizer.quote(enc,";= "));
+ }
+ }
+ else
+ {
+ _responseFields.put(HttpHeaders.CONTENT_TYPE_BUFFER,
+ contentType+";charset="+QuotedStringTokenizer.quote(enc,";= "));
+ }
+ }
+ }
+ if (c.getContentLength() > 0)
+ _responseFields.putLongField(HttpHeaders.CONTENT_LENGTH_BUFFER, c.getContentLength());
+ Buffer lm = c.getLastModified();
+ long lml=c.getResource().lastModified();
+ if (lm != null)
+ _responseFields.put(HttpHeaders.LAST_MODIFIED_BUFFER, lm,lml);
+ else if (c.getResource()!=null)
+ {
+ if (lml!=-1)
+ _responseFields.putDateField(HttpHeaders.LAST_MODIFIED_BUFFER, lml);
+ }
+
+ content = c.getBuffer();
+ if (content==null)
+ content=c.getInputStream();
+ }
+ else if (content instanceof Resource)
+ {
+ resource=(Resource)content;
+ _responseFields.putDateField(HttpHeaders.LAST_MODIFIED_BUFFER, resource.lastModified());
+ content=resource.getInputStream();
+ }
+
+
+ if (content instanceof Buffer)
+ {
+ _generator.addContent((Buffer) content, HttpGenerator.LAST);
+ commitResponse(HttpGenerator.LAST);
+ }
+ else if (content instanceof InputStream)
+ {
+ InputStream in = (InputStream)content;
+
+ try
+ {
+ int max = _generator.prepareUncheckedAddContent();
+ Buffer buffer = _generator.getUncheckedBuffer();
+
+ int len=buffer.readFrom(in,max);
+
+ while (len>=0)
+ {
+ _generator.completeUncheckedAddContent();
+ _out.flush();
+
+ max = _generator.prepareUncheckedAddContent();
+ buffer = _generator.getUncheckedBuffer();
+ len=buffer.readFrom(in,max);
+ }
+ _generator.completeUncheckedAddContent();
+ _out.flush();
+ }
+ finally
+ {
+ if (resource!=null)
+ resource.release();
+ else
+ in.close();
+
+ }
+ }
+ else
+ throw new IllegalArgumentException("unknown content type?");
+
+
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public class OutputWriter extends HttpWriter
+ {
+ OutputWriter()
+ {
+ super(HttpConnection.this._out);
+ }
+ }
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java
new file mode 100644
index 0000000000..d817e55bc8
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java
@@ -0,0 +1,62 @@
+// ========================================================================
+// Copyright (c) 2009-2009 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+
+import javax.servlet.ServletInputStream;
+
+import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.io.Buffer;
+
+public class HttpInput extends ServletInputStream
+{
+ protected final HttpParser _parser;
+ protected final long _maxIdleTime;
+
+ /* ------------------------------------------------------------ */
+ public HttpInput(HttpParser parser, long maxIdleTime)
+ {
+ _parser=parser;
+ _maxIdleTime=maxIdleTime;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see java.io.InputStream#read()
+ */
+ public int read() throws IOException
+ {
+ int c=-1;
+ Buffer content=_parser.blockForContent(_maxIdleTime);
+ if (content!=null)
+ c= 0xff & content.get();
+ return c;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see java.io.InputStream#read(byte[], int, int)
+ */
+ public int read(byte[] b, int off, int len) throws IOException
+ {
+ int l=-1;
+ Buffer content=_parser.blockForContent(_maxIdleTime);
+ if (content!=null)
+ l= content.get(b, off, len);
+ return l;
+ }
+
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOnlyCookie.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOnlyCookie.java
new file mode 100644
index 0000000000..6d21c11807
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOnlyCookie.java
@@ -0,0 +1,45 @@
+// ========================================================================
+// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+
+package org.eclipse.jetty.server;
+
+import javax.servlet.http.Cookie;
+
+/* ------------------------------------------------------------ */
+/** HttpOnlyCookie.
+ *
+ * <p>
+ * Implements {@link javax.servlet.Cookie} from the {@link javax.servlet} package.
+ * </p>
+ * This derivation of javax.servlet.http.Cookie can be used to indicate
+ * that the microsoft httponly extension should be used.
+ * The addSetCookie method on HttpFields checks for this type.
+ * @deprecated use {@link javax.servlet.Cookie#setHttpOnly(boolean)}
+ *
+ *
+ */
+public class HttpOnlyCookie extends Cookie
+{
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param name
+ * @param value
+ */
+ public HttpOnlyCookie(String name, String value)
+ {
+ super(name, value);
+ setHttpOnly(true);
+ }
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java
new file mode 100644
index 0000000000..75a0a53d18
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java
@@ -0,0 +1,173 @@
+// ========================================================================
+// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import javax.servlet.ServletOutputStream;
+
+import org.eclipse.jetty.http.AbstractGenerator;
+import org.eclipse.jetty.http.Generator;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.util.ByteArrayOutputStream2;
+
+/** Output.
+ *
+ * <p>
+ * Implements {@link javax.servlet.ServletOutputStream} from the {@link javax.servlet} package.
+ * </p>
+ * A {@link ServletOutputStream} implementation that writes content
+ * to a {@link AbstractGenerator}. The class is designed to be reused
+ * and can be reopened after a close.
+ */
+public class HttpOutput extends ServletOutputStream
+{
+ protected final AbstractGenerator _generator;
+ protected final long _maxIdleTime;
+ protected final ByteArrayBuffer _buf = new ByteArrayBuffer(AbstractGenerator.NO_BYTES);
+ protected boolean _closed;
+
+ // These are held here for reuse by Writer
+ String _characterEncoding;
+ Writer _converter;
+ char[] _chars;
+ ByteArrayOutputStream2 _bytes;
+
+
+ /* ------------------------------------------------------------ */
+ public HttpOutput(AbstractGenerator generator, long maxIdleTime)
+ {
+ _generator=generator;
+ _maxIdleTime=maxIdleTime;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see java.io.OutputStream#close()
+ */
+ public void close() throws IOException
+ {
+ _closed=true;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void reopen()
+ {
+ _closed=false;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void flush() throws IOException
+ {
+ _generator.flush(_maxIdleTime);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void write(byte[] b, int off, int len) throws IOException
+ {
+ _buf.wrap(b, off, len);
+ write(_buf);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see java.io.OutputStream#write(byte[])
+ */
+ public void write(byte[] b) throws IOException
+ {
+ _buf.wrap(b);
+ write(_buf);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see java.io.OutputStream#write(int)
+ */
+ public void write(int b) throws IOException
+ {
+ if (_closed)
+ throw new IOException("Closed");
+ if (!_generator.isOpen())
+ throw new EofException();
+
+ // Block until we can add _content.
+ while (_generator.isBufferFull())
+ {
+ _generator.blockForOutput(_maxIdleTime);
+ if (_closed)
+ throw new IOException("Closed");
+ if (!_generator.isOpen())
+ throw new EofException();
+ }
+
+ // Add the _content
+ if (_generator.addContent((byte)b))
+ // Buffers are full so flush.
+ flush();
+
+ if (_generator.isContentWritten())
+ {
+ flush();
+ close();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ private void write(Buffer buffer) throws IOException
+ {
+ if (_closed)
+ throw new IOException("Closed");
+ if (!_generator.isOpen())
+ throw new EofException();
+
+ // Block until we can add _content.
+ while (_generator.isBufferFull())
+ {
+ _generator.blockForOutput(_maxIdleTime);
+ if (_closed)
+ throw new IOException("Closed");
+ if (!_generator.isOpen())
+ throw new EofException();
+ }
+
+ // Add the _content
+ _generator.addContent(buffer, Generator.MORE);
+
+ // Have to flush and complete headers?
+ if (_generator.isBufferFull())
+ flush();
+
+ if (_generator.isContentWritten())
+ {
+ flush();
+ close();
+ }
+
+ // Block until our buffer is free
+ while (buffer.length() > 0 && _generator.isOpen())
+ _generator.blockForOutput(_maxIdleTime);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletOutputStream#print(java.lang.String)
+ */
+ public void print(String s) throws IOException
+ {
+ write(s.getBytes());
+ }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpWriter.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpWriter.java
new file mode 100644
index 0000000000..723802d91e
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpWriter.java
@@ -0,0 +1,266 @@
+// ========================================================================
+// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+import org.eclipse.jetty.http.AbstractGenerator;
+import org.eclipse.jetty.util.ByteArrayOutputStream2;
+import org.eclipse.jetty.util.StringUtil;
+
+/** OutputWriter.
+ * A writer that can wrap a {@link HttpOutput} stream and provide
+ * character encodings.
+ *
+ * The UTF-8 encoding is done by this class and no additional
+ * buffers or Writers are used.
+ * The UTF-8 code was inspired by http://javolution.org
+ */
+public class HttpWriter extends Writer
+{
+ private static final int WRITE_CONV = 0;
+ private static final int WRITE_ISO1 = 1;
+ private static final int WRITE_UTF8 = 2;
+
+ HttpOutput _out;
+ AbstractGenerator _generator;
+ int _writeMode;
+ int _surrogate;
+
+ /* ------------------------------------------------------------ */
+ public HttpWriter(HttpOutput out)
+ {
+ _out=out;
+ _generator=_out._generator;
+
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setCharacterEncoding(String encoding)
+ {
+ if (encoding == null || StringUtil.__ISO_8859_1.equalsIgnoreCase(encoding))
+ {
+ _writeMode = WRITE_ISO1;
+ }
+ else if (StringUtil.__UTF8.equalsIgnoreCase(encoding))
+ {
+ _writeMode = WRITE_UTF8;
+ }
+ else
+ {
+ _writeMode = WRITE_CONV;
+ if (_out._characterEncoding == null || !_out._characterEncoding.equalsIgnoreCase(encoding))
+ _out._converter = null; // Set lazily in getConverter()
+ }
+
+ _out._characterEncoding = encoding;
+ if (_out._bytes==null)
+ _out._bytes = new ByteArrayOutputStream2(AbstractGenerator.MAX_OUTPUT_CHARS);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void close() throws IOException
+ {
+ _out.close();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void flush() throws IOException
+ {
+ _out.flush();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void write (String s,int offset, int length) throws IOException
+ {
+ while (length > AbstractGenerator.MAX_OUTPUT_CHARS)
+ {
+ write(s, offset, AbstractGenerator.MAX_OUTPUT_CHARS);
+ offset += AbstractGenerator.MAX_OUTPUT_CHARS;
+ length -= AbstractGenerator.MAX_OUTPUT_CHARS;
+ }
+
+ if (_out._chars==null)
+ {
+ _out._chars = new char[AbstractGenerator.MAX_OUTPUT_CHARS];
+ }
+ char[] chars = _out._chars;
+ s.getChars(offset, offset + length, chars, 0);
+ write(chars, 0, length);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void write (char[] s,int offset, int length) throws IOException
+ {
+ HttpOutput out = _out;
+
+ while (length > 0)
+ {
+ out._bytes.reset();
+ int chars = length>AbstractGenerator.MAX_OUTPUT_CHARS?AbstractGenerator.MAX_OUTPUT_CHARS:length;
+
+ switch (_writeMode)
+ {
+ case WRITE_CONV:
+ {
+ Writer converter=getConverter();
+ converter.write(s, offset, chars);
+ converter.flush();
+ }
+ break;
+
+ case WRITE_ISO1:
+ {
+ byte[] buffer=out._bytes.getBuf();
+ int bytes=out._bytes.getCount();
+
+ if (chars>buffer.length-bytes)
+ chars=buffer.length-bytes;
+
+ for (int i = 0; i < chars; i++)
+ {
+ int c = s[offset+i];
+ buffer[bytes++]=(byte)(c<256?c:'?'); // ISO-1 and UTF-8 match for 0 - 255
+ }
+ if (bytes>=0)
+ out._bytes.setCount(bytes);
+
+ break;
+ }
+
+ case WRITE_UTF8:
+ {
+ byte[] buffer=out._bytes.getBuf();
+ int bytes=out._bytes.getCount();
+
+ if (bytes+chars>buffer.length)
+ chars=buffer.length-bytes;
+
+ for (int i = 0; i < chars; i++)
+ {
+ int code = s[offset+i];
+
+ if ((code & 0xffffff80) == 0)
+ {
+ // 1b
+ buffer[bytes++]=(byte)(code);
+ }
+ else if((code&0xfffff800)==0)
+ {
+ // 2b
+ if (bytes+2>buffer.length)
+ {
+ chars=i;
+ break;
+ }
+ buffer[bytes++]=(byte)(0xc0|(code>>6));
+ buffer[bytes++]=(byte)(0x80|(code&0x3f));
+
+ if (bytes+chars-i-1>buffer.length)
+ chars-=1;
+ }
+ else if((code&0xffff0000)==0)
+ {
+ // 3b
+ if (bytes+3>buffer.length)
+ {
+ chars=i;
+ break;
+ }
+ buffer[bytes++]=(byte)(0xe0|(code>>12));
+ buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
+ buffer[bytes++]=(byte)(0x80|(code&0x3f));
+
+ if (bytes+chars-i-1>buffer.length)
+ chars-=2;
+ }
+ else if((code&0xff200000)==0)
+ {
+ // 4b
+ if (bytes+4>buffer.length)
+ {
+ chars=i;
+ break;
+ }
+ buffer[bytes++]=(byte)(0xf0|(code>>18));
+ buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
+ buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
+ buffer[bytes++]=(byte)(0x80|(code&0x3f));
+
+ if (bytes+chars-i-1>buffer.length)
+ chars-=3;
+ }
+ else if((code&0xf4000000)==0)
+ {
+ // 5b
+ if (bytes+5>buffer.length)
+ {
+ chars=i;
+ break;
+ }
+ buffer[bytes++]=(byte)(0xf8|(code>>24));
+ buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f));
+ buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
+ buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
+ buffer[bytes++]=(byte)(0x80|(code&0x3f));
+
+ if (bytes+chars-i-1>buffer.length)
+ chars-=4;
+ }
+ else if((code&0x80000000)==0)
+ {
+ // 6b
+ if (bytes+6>buffer.length)
+ {
+ chars=i;
+ break;
+ }
+ buffer[bytes++]=(byte)(0xfc|(code>>30));
+ buffer[bytes++]=(byte)(0x80|((code>>24)&0x3f));
+ buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f));
+ buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
+ buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
+ buffer[bytes++]=(byte)(0x80|(code&0x3f));
+
+ if (bytes+chars-i-1>buffer.length)
+ chars-=5;
+ }
+ else
+ {
+ buffer[bytes++]=(byte)('?');
+ }
+ }
+ out._bytes.setCount(bytes);
+ break;
+ }
+ default:
+ throw new IllegalStateException();
+ }
+
+ out._bytes.writeTo(out);
+ length-=chars;
+ offset+=chars;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ private Writer getConverter() throws IOException
+ {
+ if (_out._converter == null)
+ _out._converter = new OutputStreamWriter(_out._bytes, _out._characterEncoding);
+ return _out._converter;
+ }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/InclusiveByteRange.java b/jetty-server/src/main/java/org/eclipse/jetty/server/InclusiveByteRange.java
new file mode 100644
index 0000000000..e99850c117
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/InclusiveByteRange.java
@@ -0,0 +1,211 @@
+// ========================================================================
+// Copyright (c) 2002-2009 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+
+package org.eclipse.jetty.server;
+
+import java.util.Enumeration;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.log.Log;
+
+/* ------------------------------------------------------------ */
+/** Byte range inclusive of end points.
+ * <PRE>
+ *
+ * parses the following types of byte ranges:
+ *
+ * bytes=100-499
+ * bytes=-300
+ * bytes=100-
+ * bytes=1-2,2-3,6-,-2
+ *
+ * given an entity length, converts range to string
+ *
+ * bytes 100-499/500
+ *
+ * </PRE>
+ *
+ * Based on RFC2616 3.12, 14.16, 14.35.1, 14.35.2
+ * @version $version$
+ *
+ */
+public class InclusiveByteRange
+{
+ long first = 0;
+ long last = 0;
+
+ public InclusiveByteRange(long first, long last)
+ {
+ this.first = first;
+ this.last = last;
+ }
+
+ public long getFirst()
+ {
+ return first;
+ }
+
+ public long getLast()
+ {
+ return last;
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param headers Enumeration of Range header fields.
+ * @param size Size of the resource.
+ * @return LazyList of satisfiable ranges
+ */
+ public static List satisfiableRanges(Enumeration headers,long size)
+ {
+ Object satRanges=null;
+
+ // walk through all Range headers
+ headers:
+ while (headers.hasMoreElements())
+ {
+ String header = (String) headers.nextElement();
+ StringTokenizer tok = new StringTokenizer(header,"=,",false);
+ String t=null;
+ try
+ {
+ // read all byte ranges for this header
+ while (tok.hasMoreTokens())
+ {
+ t=tok.nextToken().trim();
+
+ long first = -1;
+ long last = -1;
+ int d=t.indexOf('-');
+ if (d<0 || t.indexOf("-",d+1)>=0)
+ {
+ if ("bytes".equals(t))
+ continue;
+ Log.warn("Bad range format: {}",t);
+ continue headers;
+ }
+ else if (d==0)
+ {
+ if (d+1<t.length())
+ last = Long.parseLong(t.substring(d+1).trim());
+ else
+ {
+ Log.warn("Bad range format: {}",t);
+ continue headers;
+ }
+ }
+ else if (d+1<t.length())
+ {
+ first = Long.parseLong(t.substring(0,d).trim());
+ last = Long.parseLong(t.substring(d+1).trim());
+ }
+ else
+ first = Long.parseLong(t.substring(0,d).trim());
+
+
+ if (first == -1 && last == -1)
+ continue headers;
+
+ if (first != -1 && last != -1 && (first > last))
+ continue headers;
+
+ if (first<size)
+ {
+ InclusiveByteRange range = new
+ InclusiveByteRange(first, last);
+ satRanges=LazyList.add(satRanges,range);
+ }
+ }
+ }
+ catch(Exception e)
+ {
+ Log.warn("Bad range format: "+t);
+ Log.ignore(e);
+ }
+ }
+ return LazyList.getList(satRanges,true);
+ }
+
+ /* ------------------------------------------------------------ */
+ public long getFirst(long size)
+ {
+ if (first<0)
+ {
+ long tf=size-last;
+ if (tf<0)
+ tf=0;
+ return tf;
+ }
+ return first;
+ }
+
+ /* ------------------------------------------------------------ */
+ public long getLast(long size)
+ {
+ if (first<0)
+ return size-1;
+
+ if (last<0 ||last>=size)
+ return size-1;
+ return last;
+ }
+
+ /* ------------------------------------------------------------ */
+ public long getSize(long size)
+ {
+ return getLast(size)-getFirst(size)+1;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public String toHeaderRangeString(long size)
+ {
+ StringBuilder sb = new StringBuilder(40);
+ sb.append("bytes ");
+ sb.append(getFirst(size));
+ sb.append('-');
+ sb.append(getLast(size));
+ sb.append("/");
+ sb.append(size);
+ return sb.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ public static String to416HeaderRangeString(long size)
+ {
+ StringBuilder sb = new StringBuilder(40);
+ sb.append("bytes */");
+ sb.append(size);
+ return sb.toString();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder(60);
+ sb.append(Long.toString(first));
+ sb.append(":");
+ sb.append(Long.toString(last));
+ return sb.toString();
+ }
+
+
+}
+
+
+
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java
new file mode 100644
index 0000000000..1eed8de27d
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java
@@ -0,0 +1,221 @@
+// ========================================================================
+// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.io.ByteArrayEndPoint;
+import org.eclipse.jetty.util.StringUtil;
+
+public class LocalConnector extends AbstractConnector
+{
+ ByteArrayEndPoint _endp;
+ ByteArrayBuffer _in;
+ ByteArrayBuffer _out;
+
+ Server _server;
+ boolean _accepting;
+ boolean _keepOpen;
+
+ public LocalConnector()
+ {
+ setPort(1);
+ }
+
+ /* ------------------------------------------------------------ */
+ public Object getConnection()
+ {
+ return _endp;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public void setServer(Server server)
+ {
+ super.setServer(server);
+ this._server=server;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void clear()
+ {
+ _in.clear();
+ _out.clear();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void reopen()
+ {
+ _in.clear();
+ _out.clear();
+ _endp = new ByteArrayEndPoint();
+ _endp.setIn(_in);
+ _endp.setOut(_out);
+ _endp.setGrowOutput(true);
+ _accepting=false;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void doStart()
+ throws Exception
+ {
+ _in=new ByteArrayBuffer(8192);
+ _out=new ByteArrayBuffer(8192);
+ _endp = new ByteArrayEndPoint();
+ _endp.setIn(_in);
+ _endp.setOut(_out);
+ _endp.setGrowOutput(true);
+ _accepting=false;
+
+ super.doStart();
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getResponses(String requests)
+ throws Exception
+ {
+ return getResponses(requests,false);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getResponses(String requests, boolean keepOpen)
+ throws Exception
+ {
+ // System.out.println("\nREQUESTS :\n"+requests);
+ // System.out.flush();
+ ByteArrayBuffer buf=new ByteArrayBuffer(requests,StringUtil.__ISO_8859_1);
+ if (_in.space()<buf.length())
+ {
+ ByteArrayBuffer n = new ByteArrayBuffer(_in.length()+buf.length());
+ n.put(_in);
+ _in=n;
+ _endp.setIn(_in);
+ }
+ _in.put(buf);
+
+ synchronized (this)
+ {
+ _keepOpen=keepOpen;
+ _accepting=true;
+ this.notify();
+
+ while(_accepting)
+ this.wait();
+ }
+
+ // System.err.println("\nRESPONSES:\n"+out);
+ _out=_endp.getOut();
+ return _out.toString(StringUtil.__ISO_8859_1);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteArrayBuffer getResponses(ByteArrayBuffer buf, boolean keepOpen)
+ throws Exception
+ {
+ if (_in.space()<buf.length())
+ {
+ ByteArrayBuffer n = new ByteArrayBuffer(_in.length()+buf.length());
+ n.put(_in);
+ _in=n;
+ _endp.setIn(_in);
+ }
+ _in.put(buf);
+
+ synchronized (this)
+ {
+ _keepOpen=keepOpen;
+ _accepting=true;
+ this.notify();
+
+ while(_accepting)
+ this.wait();
+ }
+
+ // System.err.println("\nRESPONSES:\n"+out);
+ _out=_endp.getOut();
+ return _out;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Buffer newBuffer(int size)
+ {
+ return new ByteArrayBuffer(size);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void accept(int acceptorID) throws IOException, InterruptedException
+ {
+ HttpConnection connection=null;
+
+ while (isRunning())
+ {
+ synchronized (this)
+ {
+ try
+ {
+ while(!_accepting)
+ this.wait();
+ }
+ catch(InterruptedException e)
+ {
+ return;
+ }
+ }
+
+ try
+ {
+ if (connection==null)
+ {
+ connection=new HttpConnection(this,_endp,getServer());
+ connectionOpened(connection);
+ }
+ while (_in.length()>0)
+ connection.handle();
+ }
+ finally
+ {
+ if (!_keepOpen)
+ {
+ connectionClosed(connection);
+ connection.destroy();
+ connection=null;
+ }
+ synchronized (this)
+ {
+ _accepting=false;
+ this.notify();
+ }
+ }
+ }
+ }
+
+
+ public void open() throws IOException
+ {
+ }
+
+ public void close() throws IOException
+ {
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public int getLocalPort()
+ {
+ return -1;
+ }
+
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/NCSARequestLog.java b/jetty-server/src/main/java/org/eclipse/jetty/server/NCSARequestLog.java
new file mode 100644
index 0000000000..6a7587de84
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/NCSARequestLog.java
@@ -0,0 +1,513 @@
+// ========================================================================
+// Copyright (c) 1997-2009 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import javax.servlet.http.Cookie;
+
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.util.DateCache;
+import org.eclipse.jetty.util.RolloverFileOutputStream;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.Utf8StringBuilder;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+
+/**
+ * This {@link RequestLog} implementation outputs logs in the pseudo-standard
+ * NCSA common log format. Configuration options allow a choice between the
+ * standard Common Log Format (as used in the 3 log format) and the Combined Log
+ * Format (single log format). This log format can be output by most web
+ * servers, and almost all web log analysis software can understand these
+ * formats.
+ *
+ *
+ *
+ *
+ * @org.apache.xbean.XBean element="ncsaLog"
+ */
+public class NCSARequestLog extends AbstractLifeCycle implements RequestLog
+{
+ private String _filename;
+ private boolean _extended;
+ private boolean _append;
+ private int _retainDays;
+ private boolean _closeOut;
+ private boolean _preferProxiedForAddress;
+ private String _logDateFormat = "dd/MMM/yyyy:HH:mm:ss Z";
+ private String _filenameDateFormat = null;
+ private Locale _logLocale = Locale.getDefault();
+ private String _logTimeZone = "GMT";
+ private String[] _ignorePaths;
+ private boolean _logLatency = false;
+ private boolean _logCookies = false;
+ private boolean _logServer = false;
+
+ private transient OutputStream _out;
+ private transient OutputStream _fileOut;
+ private transient DateCache _logDateCache;
+ private transient PathMap _ignorePathMap;
+ private transient Writer _writer;
+ private transient ArrayList _buffers;
+ private transient char[] _copy;
+
+ public NCSARequestLog()
+ {
+ _extended = true;
+ _append = true;
+ _retainDays = 31;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param filename
+ * The filename for the request log. This may be in the
+ * format expected by {@link RolloverFileOutputStream}
+ */
+ public NCSARequestLog(String filename)
+ {
+ _extended = true;
+ _append = true;
+ _retainDays = 31;
+ setFilename(filename);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param filename
+ * The filename for the request log. This may be in the
+ * format expected by {@link RolloverFileOutputStream}
+ */
+ public void setFilename(String filename)
+ {
+ if (filename != null)
+ {
+ filename = filename.trim();
+ if (filename.length() == 0)
+ filename = null;
+ }
+ _filename = filename;
+ }
+
+ public String getFilename()
+ {
+ return _filename;
+ }
+
+ public String getDatedFilename()
+ {
+ if (_fileOut instanceof RolloverFileOutputStream)
+ return ((RolloverFileOutputStream)_fileOut).getDatedFilename();
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param format
+ * Format for the timestamps in the log file. If not set, the
+ * pre-formated request timestamp is used.
+ */
+ public void setLogDateFormat(String format)
+ {
+ _logDateFormat = format;
+ }
+
+ public String getLogDateFormat()
+ {
+ return _logDateFormat;
+ }
+
+ public void setLogLocale(Locale logLocale)
+ {
+ _logLocale = logLocale;
+ }
+
+ public Locale getLogLocale()
+ {
+ return _logLocale;
+ }
+
+ public void setLogTimeZone(String tz)
+ {
+ _logTimeZone = tz;
+ }
+
+ public String getLogTimeZone()
+ {
+ return _logTimeZone;
+ }
+
+ public void setRetainDays(int retainDays)
+ {
+ _retainDays = retainDays;
+ }
+
+ public int getRetainDays()
+ {
+ return _retainDays;
+ }
+
+ public void setExtended(boolean extended)
+ {
+ _extended = extended;
+ }
+
+ public boolean isExtended()
+ {
+ return _extended;
+ }
+
+ public void setAppend(boolean append)
+ {
+ _append = append;
+ }
+
+ public boolean isAppend()
+ {
+ return _append;
+ }
+
+ public void setIgnorePaths(String[] ignorePaths)
+ {
+ _ignorePaths = ignorePaths;
+ }
+
+ public String[] getIgnorePaths()
+ {
+ return _ignorePaths;
+ }
+
+ public void setLogCookies(boolean logCookies)
+ {
+ _logCookies = logCookies;
+ }
+
+ public boolean getLogCookies()
+ {
+ return _logCookies;
+ }
+
+ public boolean getLogServer()
+ {
+ return _logServer;
+ }
+
+ public void setLogServer(boolean logServer)
+ {
+ _logServer = logServer;
+ }
+
+ public void setLogLatency(boolean logLatency)
+ {
+ _logLatency = logLatency;
+ }
+
+ public boolean getLogLatency()
+ {
+ return _logLatency;
+ }
+
+ public void setPreferProxiedForAddress(boolean preferProxiedForAddress)
+ {
+ _preferProxiedForAddress = preferProxiedForAddress;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void log(Request request, Response response)
+ {
+ if (!isStarted())
+ return;
+
+ try
+ {
+ if (_ignorePathMap != null && _ignorePathMap.getMatch(request.getRequestURI()) != null)
+ return;
+
+ if (_fileOut == null)
+ return;
+
+ Utf8StringBuilder u8buf;
+ StringBuilder buf;
+ synchronized(_writer)
+ {
+ int size=_buffers.size();
+ u8buf = size==0?new Utf8StringBuilder(160):(Utf8StringBuilder)_buffers.remove(size-1);
+ buf = u8buf.getStringBuilder();
+ }
+
+ if (_logServer)
+ {
+ buf.append(request.getServerName());
+ buf.append(' ');
+ }
+
+ String addr = null;
+ if (_preferProxiedForAddress)
+ {
+ addr = request.getHeader(HttpHeaders.X_FORWARDED_FOR);
+ }
+
+ if (addr == null)
+ addr = request.getRemoteAddr();
+
+ buf.append(addr);
+ buf.append(" - ");
+ String user = request.getRemoteUser();
+ buf.append((user == null)?" - ":user);
+ buf.append(" [");
+ if (_logDateCache != null)
+ buf.append(_logDateCache.format(request.getTimeStamp()));
+ else
+ buf.append(request.getTimeStampBuffer().toString());
+
+ buf.append("] \"");
+ buf.append(request.getMethod());
+ buf.append(' ');
+
+ request.getUri().writeTo(u8buf);
+
+ buf.append(' ');
+ buf.append(request.getProtocol());
+ buf.append("\" ");
+ if (request.getAsyncRequest().isInitial())
+ {
+ int status = response.getStatus();
+ if (status <= 0)
+ status = 404;
+ buf.append((char)('0' + ((status / 100) % 10)));
+ buf.append((char)('0' + ((status / 10) % 10)));
+ buf.append((char)('0' + (status % 10)));
+ }
+ else
+ buf.append("Async");
+
+ long responseLength = response.getContentCount();
+ if (responseLength >= 0)
+ {
+ buf.append(' ');
+ if (responseLength > 99999)
+ buf.append(responseLength);
+ else
+ {
+ if (responseLength > 9999)
+ buf.append((char)('0' + ((responseLength / 10000) % 10)));
+ if (responseLength > 999)
+ buf.append((char)('0' + ((responseLength / 1000) % 10)));
+ if (responseLength > 99)
+ buf.append((char)('0' + ((responseLength / 100) % 10)));
+ if (responseLength > 9)
+ buf.append((char)('0' + ((responseLength / 10) % 10)));
+ buf.append((char)('0' + (responseLength) % 10));
+ }
+ buf.append(' ');
+ }
+ else
+ buf.append(" - ");
+
+ if (!_extended && !_logCookies && !_logLatency)
+ {
+ synchronized(_writer)
+ {
+ buf.append(StringUtil.__LINE_SEPARATOR);
+ int l=buf.length();
+ if (l>_copy.length)
+ l=_copy.length;
+ buf.getChars(0,l,_copy,0);
+ _writer.write(_copy,0,l);
+ _writer.flush();
+ u8buf.reset();
+ _buffers.add(u8buf);
+ }
+ }
+ else
+ {
+ synchronized(_writer)
+ {
+ int l=buf.length();
+ if (l>_copy.length)
+ l=_copy.length;
+ buf.getChars(0,l,_copy,0);
+ _writer.write(_copy,0,l);
+ u8buf.reset();
+ _buffers.add(u8buf);
+
+ // TODO do outside synchronized scope
+ if (_extended)
+ logExtended(request, response, _writer);
+
+ // TODO do outside synchronized scope
+ if (_logCookies)
+ {
+ Cookie[] cookies = request.getCookies();
+ if (cookies == null || cookies.length == 0)
+ _writer.write(" -");
+ else
+ {
+ _writer.write(" \"");
+ for (int i = 0; i < cookies.length; i++)
+ {
+ if (i != 0)
+ _writer.write(';');
+ _writer.write(cookies[i].getName());
+ _writer.write('=');
+ _writer.write(cookies[i].getValue());
+ }
+ _writer.write('\"');
+ }
+ }
+
+ if (_logLatency)
+ {
+ _writer.write(' ');
+ _writer.write(TypeUtil.toString(System.currentTimeMillis() - request.getTimeStamp()));
+ }
+
+ _writer.write(StringUtil.__LINE_SEPARATOR);
+ _writer.flush();
+ }
+ }
+ }
+ catch (IOException e)
+ {
+ Log.warn(e);
+ }
+
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void logExtended(Request request,
+ Response response,
+ Writer writer) throws IOException
+ {
+ String referer = request.getHeader(HttpHeaders.REFERER);
+ if (referer == null)
+ writer.write("\"-\" ");
+ else
+ {
+ writer.write('"');
+ writer.write(referer);
+ writer.write("\" ");
+ }
+
+ String agent = request.getHeader(HttpHeaders.USER_AGENT);
+ if (agent == null)
+ writer.write("\"-\" ");
+ else
+ {
+ writer.write('"');
+ writer.write(agent);
+ writer.write('"');
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void doStart() throws Exception
+ {
+ if (_logDateFormat != null)
+ {
+ _logDateCache = new DateCache(_logDateFormat,_logLocale);
+ _logDateCache.setTimeZoneID(_logTimeZone);
+ }
+
+ if (_filename != null)
+ {
+ _fileOut = new RolloverFileOutputStream(_filename,_append,_retainDays,TimeZone.getTimeZone(_logTimeZone),_filenameDateFormat,null);
+ _closeOut = true;
+ Log.info("Opened " + getDatedFilename());
+ }
+ else
+ _fileOut = System.err;
+
+ _out = _fileOut;
+
+ if (_ignorePaths != null && _ignorePaths.length > 0)
+ {
+ _ignorePathMap = new PathMap();
+ for (int i = 0; i < _ignorePaths.length; i++)
+ _ignorePathMap.put(_ignorePaths[i],_ignorePaths[i]);
+ }
+ else
+ _ignorePathMap = null;
+
+ _writer = new OutputStreamWriter(_out);
+ _buffers = new ArrayList();
+ _copy = new char[1024];
+ super.doStart();
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ try
+ {
+ if (_writer != null)
+ _writer.flush();
+ }
+ catch (IOException e)
+ {
+ Log.ignore(e);
+ }
+ if (_out != null && _closeOut)
+ try
+ {
+ _out.close();
+ }
+ catch (IOException e)
+ {
+ Log.ignore(e);
+ }
+
+ _out = null;
+ _fileOut = null;
+ _closeOut = false;
+ _logDateCache = null;
+ _writer = null;
+ _buffers = null;
+ _copy = null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the log File Date Format
+ */
+ public String getFilenameDateFormat()
+ {
+ return _filenameDateFormat;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the log file date format.
+ *
+ * @see {@link RolloverFileOutputStream#RolloverFileOutputStream(String, boolean, int, TimeZone, String, String)}
+ * @param logFileDateFormat
+ * the logFileDateFormat to pass to
+ * {@link RolloverFileOutputStream}
+ */
+ public void setFilenameDateFormat(String logFileDateFormat)
+ {
+ _filenameDateFormat = logFileDateFormat;
+ }
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
new file mode 100644
index 0000000000..431ae744c8
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
@@ -0,0 +1,1866 @@
+// ========================================================================
+// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+
+package org.eclipse.jetty.server;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.security.Principal;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.security.auth.login.LoginException;
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletRequestAttributeEvent;
+import javax.servlet.ServletRequestAttributeListener;
+import javax.servlet.ServletRequestListener;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.HttpVersions;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.BufferUtil;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.nio.DirectNIOBuffer;
+import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
+import org.eclipse.jetty.io.nio.NIOBuffer;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.AttributesMap;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.UrlEncoded;
+import org.eclipse.jetty.util.ajax.Continuation;
+import org.eclipse.jetty.util.log.Log;
+
+/* ------------------------------------------------------------ */
+/** Jetty Request.
+ * <p>
+ * Implements {@link javax.servlet.http.HttpServletRequest} from the {@link javax.servlet.http} package.
+ * </p>
+ * <p>
+ * The standard interface of mostly getters,
+ * is extended with setters so that the request is mutable by the handlers that it is
+ * passed to. This allows the request object to be as lightweight as possible and not
+ * actually implement any significant behaviour. For example<ul>
+ *
+ * <li>The {@link Request#getContextPath} method will return null, until the requeset has been
+ * passed to a {@link ContextHandler} which matches the {@link Request#getPathInfo} with a context
+ * path and calls {@link Request#setContextPath} as a result.</li>
+ *
+ * <li>the HTTP session methods
+ * will all return null sessions until such time as a request has been passed to
+ * a {@link org.eclipse.jetty.servlet.SessionHandler} which checks for session cookies
+ * and enables the ability to create new sessions.</li>
+ *
+ * <li>The {@link Request#getServletPath} method will return null until the request has been
+ * passed to a {@link org.eclipse.jetty.servlet.ServletHandler} and the pathInfo matched
+ * against the servlet URL patterns and {@link Request#setServletPath} called as a result.</li>
+ * </ul>
+ *
+ * A request instance is created for each {@link HttpConnection} accepted by the server
+ * and recycled for each HTTP request received via that connection. An effort is made
+ * to avoid reparsing headers and cookies that are likely to be the same for
+ * requests from the same connection.
+ *
+ *
+ *
+ */
+public class Request implements HttpServletRequest
+{
+ private static final String __ASYNC_FWD="org.eclipse.asyncfwd";
+ private static final Collection __defaultLocale = Collections.singleton(Locale.getDefault());
+ private static final int __NONE=0, _STREAM=1, __READER=2;
+
+ /* ------------------------------------------------------------ */
+ public static Request getRequest(HttpServletRequest request)
+ {
+ if (request instanceof Request)
+ return (Request) request;
+
+ return HttpConnection.getCurrentConnection().getRequest();
+ }
+ protected final AsyncRequest _async = new AsyncRequest();
+ private boolean _asyncSupported=true;
+ private Attributes _attributes;
+ private String _authType;
+ private MultiMap<String> _baseParameters;
+ private String _characterEncoding;
+ protected HttpConnection _connection;
+ private ContextHandler.Context _context;
+ private String _contextPath;
+ private Continuation _continuation;
+ private CookieCutter _cookies;
+ private boolean _cookiesExtracted=false;
+ private DispatcherType _dispatcherType;
+ private boolean _dns=false;
+ private EndPoint _endp;
+ private boolean _handled =false;
+ private int _inputState=__NONE;
+ private String _method;
+ private MultiMap<String> _parameters;
+ private boolean _paramsExtracted;
+ private String _pathInfo;
+ private int _port;
+ private String _protocol=HttpVersions.HTTP_1_1;
+ private String _queryEncoding;
+ private String _queryString;
+ private BufferedReader _reader;
+ private String _readerEncoding;
+ private String _remoteAddr;
+ private String _remoteHost;
+ private Object _requestAttributeListeners;
+ private String _requestedSessionId;
+ private boolean _requestedSessionIdFromCookie=false;
+ private Object _requestListeners;
+ private String _requestURI;
+ private Map<Object,HttpSession> _savedNewSessions;
+ private String _scheme=URIUtil.HTTP;
+ private String _serverName;
+ private String _servletName;
+ private String _servletPath;
+ private HttpSession _session;
+ private SessionManager _sessionManager;
+ private long _timeStamp;
+ private Buffer _timeStampBuffer;
+ private HttpURI _uri;
+
+ private UserIdentity _userIdentity = UserIdentity.UNAUTHENTICATED_IDENTITY;
+
+ /* ------------------------------------------------------------ */
+ public Request()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public Request(HttpConnection connection)
+ {
+ setConnection(connection);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void addAsyncListener(AsyncListener listener)
+ {
+ _async._listeners=LazyList.add(_async._listeners,listener);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void addAsyncListener(final AsyncListener listener, ServletRequest servletRequest, ServletResponse servletResponse)
+ {
+ final AsyncEvent event = new AsyncEvent(servletRequest,servletResponse);
+
+ _async._listeners=LazyList.add(_async._listeners,new AsyncListener()
+ {
+ public void onComplete(AsyncEvent ev) throws IOException
+ {
+ listener.onComplete(event);
+ }
+
+ public void onTimeout(AsyncEvent ev) throws IOException
+ {
+ listener.onComplete(event);
+ }
+ });
+ }
+
+ /* ------------------------------------------------------------ */
+ public void addEventListener(final EventListener listener)
+ {
+ if (listener instanceof ServletRequestAttributeListener)
+ _requestAttributeListeners= LazyList.add(_requestAttributeListeners, listener);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * Extract Paramters from query string and/or form _content.
+ */
+ private void extractParameters()
+ {
+ if (_baseParameters == null)
+ _baseParameters = new MultiMap(16);
+
+ if (_paramsExtracted)
+ {
+ if (_parameters==null)
+ _parameters=_baseParameters;
+ return;
+ }
+
+ _paramsExtracted = true;
+
+ // Handle query string
+ if (_uri!=null && _uri.hasQuery())
+ {
+ if (_queryEncoding==null)
+ _uri.decodeQueryTo(_baseParameters);
+ else
+ {
+ try
+ {
+ _uri.decodeQueryTo(_baseParameters,_queryEncoding);
+
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ if (Log.isDebugEnabled())
+ Log.warn(e);
+ else
+ Log.warn(e.toString());
+ }
+ }
+
+ }
+
+ // handle any _content.
+ String encoding = getCharacterEncoding();
+ String content_type = getContentType();
+ if (content_type != null && content_type.length() > 0)
+ {
+ content_type = HttpFields.valueParameters(content_type, null);
+
+ if (MimeTypes.FORM_ENCODED.equalsIgnoreCase(content_type) &&
+ (HttpMethods.POST.equals(getMethod()) || HttpMethods.PUT.equals(getMethod())))
+ {
+ int content_length = getContentLength();
+ if (content_length != 0)
+ {
+ try
+ {
+ int maxFormContentSize=-1;
+
+ if (_context!=null)
+ maxFormContentSize=_context.getContextHandler().getMaxFormContentSize();
+ else
+ {
+ Integer size = (Integer)_connection.getConnector().getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormContentSize");
+ if (size!=null)
+ maxFormContentSize =size.intValue();
+ }
+
+ if (content_length>maxFormContentSize && maxFormContentSize > 0)
+ {
+ throw new IllegalStateException("Form too large"+content_length+">"+maxFormContentSize);
+ }
+ InputStream in = getInputStream();
+
+ // Add form params to query params
+ UrlEncoded.decodeTo(in, _baseParameters, encoding,content_length<0?maxFormContentSize:-1);
+ }
+ catch (IOException e)
+ {
+ if (Log.isDebugEnabled())
+ Log.warn(e);
+ else
+ Log.warn(e.toString());
+ }
+ }
+ }
+ }
+
+ if (_parameters==null)
+ _parameters=_baseParameters;
+ else if (_parameters!=_baseParameters)
+ {
+ // Merge parameters (needed if parameters extracted after a forward).
+ Iterator iter = _baseParameters.entrySet().iterator();
+ while (iter.hasNext())
+ {
+ Map.Entry entry = (Map.Entry)iter.next();
+ String name=(String)entry.getKey();
+ Object values=entry.getValue();
+ for (int i=0;i<LazyList.size(values);i++)
+ _parameters.add(name, LazyList.get(values, i));
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public AsyncContext getAsyncContext()
+ {
+ if (_async.isInitial() && !isAsyncStarted())
+ throw new IllegalStateException(_async.getStatusString());
+ return _async;
+ }
+
+ /* ------------------------------------------------------------ */
+ public AsyncRequest getAsyncRequest()
+ {
+ return _async;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getAttribute(java.lang.String)
+ */
+ public Object getAttribute(String name)
+ {
+ if ("org.eclipse.jetty.util.ajax.Continuation".equals(name))
+ return getContinuation(true);
+
+ if (DispatcherType.ASYNC.equals(_dispatcherType))
+ {
+ // TODO handle forwards(path!)
+ if (name.equals(Dispatcher.__FORWARD_PATH_INFO)) return getPathInfo();
+ if (name.equals(Dispatcher.__FORWARD_REQUEST_URI)) return getRequestURI();
+ if (name.equals(Dispatcher.__FORWARD_SERVLET_PATH)) return getServletPath();
+ if (name.equals(Dispatcher.__FORWARD_CONTEXT_PATH)) return getContextPath();
+ if (name.equals(Dispatcher.__FORWARD_QUERY_STRING)) return getQueryString();
+ }
+
+ if (_attributes==null)
+ return null;
+ return _attributes.getAttribute(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getAttributeNames()
+ */
+ public Enumeration getAttributeNames()
+ {
+ if (_attributes==null)
+ return Collections.enumeration(Collections.EMPTY_LIST);
+ return AttributesMap.getAttributeNamesCopy(_attributes);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ */
+ public Attributes getAttributes()
+ {
+ if (_attributes==null)
+ _attributes=new AttributesMap();
+ return _attributes;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getAuthType()
+ */
+ public String getAuthType()
+ {
+ return _authType;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getCharacterEncoding()
+ */
+ public String getCharacterEncoding()
+ {
+ return _characterEncoding;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the connection.
+ */
+ public HttpConnection getConnection()
+ {
+ return _connection;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getContentLength()
+ */
+ public int getContentLength()
+ {
+ return (int)_connection.getRequestFields().getLongField(HttpHeaders.CONTENT_LENGTH_BUFFER);
+ }
+
+ public long getContentRead()
+ {
+ if (_connection==null || _connection.getParser()==null)
+ return -1;
+
+ return ((HttpParser)_connection.getParser()).getContentRead();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getContentType()
+ */
+ public String getContentType()
+ {
+ return _connection.getRequestFields().getStringField(HttpHeaders.CONTENT_TYPE_BUFFER);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The current {@link Context context} used for this request, or <code>null</code> if {@link #setContext} has not yet
+ * been called.
+ */
+ public Context getContext()
+ {
+ return _context;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getContextPath()
+ */
+ public String getContextPath()
+ {
+ return _contextPath;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @deprecated
+ */
+ public Continuation getContinuation()
+ {
+ return _continuation;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @deprecated
+ */
+ public Continuation getContinuation(boolean create)
+ {
+ if (_continuation==null && create)
+ _continuation=new Servlet3Continuation(this);
+ return _continuation;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getCookies()
+ */
+ public Cookie[] getCookies()
+ {
+ if (_cookiesExtracted)
+ return _cookies==null?null:_cookies.getCookies();
+
+ // Handle no cookies
+ if (!_connection.getRequestFields().containsKey(HttpHeaders.COOKIE_BUFFER))
+ {
+ _cookiesExtracted = true;
+ if (_cookies!=null)
+ _cookies.reset();
+ return null;
+ }
+
+ if (_cookies==null)
+ _cookies=new CookieCutter(this);
+
+ Enumeration enm = _connection.getRequestFields().getValues(HttpHeaders.COOKIE_BUFFER);
+ while (enm.hasMoreElements())
+ {
+ String c = (String)enm.nextElement();
+ _cookies.addCookieField(c);
+ }
+ _cookiesExtracted=true;
+
+ return _cookies.getCookies();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getDateHeader(java.lang.String)
+ */
+ public long getDateHeader(String name)
+ {
+ return _connection.getRequestFields().getDateField(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ public DispatcherType getDispatcherType()
+ {
+ return _dispatcherType;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getHeader(java.lang.String)
+ */
+ public String getHeader(String name)
+ {
+ return _connection.getRequestFields().getStringField(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getHeaderNames()
+ */
+ public Enumeration getHeaderNames()
+ {
+ return _connection.getRequestFields().getFieldNames();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getHeaders(java.lang.String)
+ */
+ public Enumeration getHeaders(String name)
+ {
+ Enumeration e = _connection.getRequestFields().getValues(name);
+ if (e==null)
+ return Collections.enumeration(Collections.EMPTY_LIST);
+ return e;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the inputState.
+ */
+ public int getInputState()
+ {
+ return _inputState;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getInputStream()
+ */
+ public ServletInputStream getInputStream() throws IOException
+ {
+ if (_inputState!=__NONE && _inputState!=_STREAM)
+ throw new IllegalStateException("READER");
+ _inputState=_STREAM;
+ return _connection.getInputStream();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getIntHeader(java.lang.String)
+ */
+ public int getIntHeader(String name)
+ {
+ return (int)_connection.getRequestFields().getLongField(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getLocalAddr()
+ */
+ public String getLocalAddr()
+ {
+ return _endp==null?null:_endp.getLocalAddr();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getLocale()
+ */
+ public Locale getLocale()
+ {
+ Enumeration enm = _connection.getRequestFields().getValues(HttpHeaders.ACCEPT_LANGUAGE, HttpFields.__separators);
+
+ // handle no locale
+ if (enm == null || !enm.hasMoreElements())
+ return Locale.getDefault();
+
+ // sort the list in quality order
+ List acceptLanguage = HttpFields.qualityList(enm);
+ if (acceptLanguage.size()==0)
+ return Locale.getDefault();
+
+ int size=acceptLanguage.size();
+
+ // convert to locals
+ for (int i=0; i<size; i++)
+ {
+ String language = (String)acceptLanguage.get(i);
+ language=HttpFields.valueParameters(language,null);
+ String country = "";
+ int dash = language.indexOf('-');
+ if (dash > -1)
+ {
+ country = language.substring(dash + 1).trim();
+ language = language.substring(0,dash).trim();
+ }
+ return new Locale(language,country);
+ }
+
+ return Locale.getDefault();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getLocales()
+ */
+ public Enumeration getLocales()
+ {
+
+ Enumeration enm = _connection.getRequestFields().getValues(HttpHeaders.ACCEPT_LANGUAGE, HttpFields.__separators);
+
+ // handle no locale
+ if (enm == null || !enm.hasMoreElements())
+ return Collections.enumeration(__defaultLocale);
+
+ // sort the list in quality order
+ List acceptLanguage = HttpFields.qualityList(enm);
+
+ if (acceptLanguage.size()==0)
+ return
+ Collections.enumeration(__defaultLocale);
+
+ Object langs = null;
+ int size=acceptLanguage.size();
+
+ // convert to locals
+ for (int i=0; i<size; i++)
+ {
+ String language = (String)acceptLanguage.get(i);
+ language=HttpFields.valueParameters(language,null);
+ String country = "";
+ int dash = language.indexOf('-');
+ if (dash > -1)
+ {
+ country = language.substring(dash + 1).trim();
+ language = language.substring(0,dash).trim();
+ }
+ langs=LazyList.ensureSize(langs,size);
+ langs=LazyList.add(langs,new Locale(language,country));
+ }
+
+ if (LazyList.size(langs)==0)
+ return Collections.enumeration(__defaultLocale);
+
+ return Collections.enumeration(LazyList.getList(langs));
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getLocalName()
+ */
+ public String getLocalName()
+ {
+ if (_dns)
+ return _endp==null?null:_endp.getLocalHost();
+ return _endp==null?null:_endp.getLocalAddr();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getLocalPort()
+ */
+ public int getLocalPort()
+ {
+ return _endp==null?0:_endp.getLocalPort();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getMethod()
+ */
+ public String getMethod()
+ {
+ return _method;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getParameter(java.lang.String)
+ */
+ public String getParameter(String name)
+ {
+ if (!_paramsExtracted)
+ extractParameters();
+ return (String) _parameters.getValue(name, 0);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getParameterMap()
+ */
+ public Map getParameterMap()
+ {
+ if (!_paramsExtracted)
+ extractParameters();
+
+ return Collections.unmodifiableMap(_parameters.toStringArrayMap());
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getParameterNames()
+ */
+ public Enumeration getParameterNames()
+ {
+ if (!_paramsExtracted)
+ extractParameters();
+ return Collections.enumeration(_parameters.keySet());
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the parameters.
+ */
+ public MultiMap getParameters()
+ {
+ return _parameters;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getParameterValues(java.lang.String)
+ */
+ public String[] getParameterValues(String name)
+ {
+ if (!_paramsExtracted)
+ extractParameters();
+ List vals = _parameters.getValues(name);
+ if (vals==null)
+ return null;
+ return (String[])vals.toArray(new String[vals.size()]);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getPathInfo()
+ */
+ public String getPathInfo()
+ {
+ return _pathInfo;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getPathTranslated()
+ */
+ public String getPathTranslated()
+ {
+ if (_pathInfo==null || _context==null)
+ return null;
+ return _context.getRealPath(_pathInfo);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getProtocol()
+ */
+ public String getProtocol()
+ {
+ return _protocol;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getQueryEncoding()
+ {
+ return _queryEncoding;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getQueryString()
+ */
+ public String getQueryString()
+ {
+ if (_queryString==null && _uri!=null)
+ {
+ if (_queryEncoding==null)
+ _queryString=_uri.getQuery();
+ else
+ _queryString=_uri.getQuery(_queryEncoding);
+ }
+ return _queryString;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getReader()
+ */
+ public BufferedReader getReader() throws IOException
+ {
+ if (_inputState!=__NONE && _inputState!=__READER)
+ throw new IllegalStateException("STREAMED");
+
+ if (_inputState==__READER)
+ return _reader;
+
+ String encoding=getCharacterEncoding();
+ if (encoding==null)
+ encoding=StringUtil.__ISO_8859_1;
+
+ if (_reader==null || !encoding.equalsIgnoreCase(_readerEncoding))
+ {
+ final ServletInputStream in = getInputStream();
+ _readerEncoding=encoding;
+ _reader=new BufferedReader(new InputStreamReader(in,encoding))
+ {
+ public void close() throws IOException
+ {
+ in.close();
+ }
+ };
+ }
+ _inputState=__READER;
+ return _reader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getRealPath(java.lang.String)
+ */
+ public String getRealPath(String path)
+ {
+ if (_context==null)
+ return null;
+ return _context.getRealPath(path);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getRemoteAddr()
+ */
+ public String getRemoteAddr()
+ {
+ if (_remoteAddr != null)
+ return _remoteAddr;
+ return _endp==null?null:_endp.getRemoteAddr();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getRemoteHost()
+ */
+ public String getRemoteHost()
+ {
+ if (_dns)
+ {
+ if (_remoteHost != null)
+ {
+ return _remoteHost;
+ }
+ return _endp==null?null:_endp.getRemoteHost();
+ }
+ return getRemoteAddr();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getRemotePort()
+ */
+ public int getRemotePort()
+ {
+ return _endp==null?0:_endp.getRemotePort();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getRemoteUser()
+ */
+ public String getRemoteUser()
+ {
+ Principal p = getUserPrincipal();
+ if (p==null)
+ return null;
+ return p.getName();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getRequestDispatcher(java.lang.String)
+ */
+ public RequestDispatcher getRequestDispatcher(String path)
+ {
+ if (path == null || _context==null)
+ return null;
+
+ // handle relative path
+ if (!path.startsWith("/"))
+ {
+ String relTo=URIUtil.addPaths(_servletPath,_pathInfo);
+ int slash=relTo.lastIndexOf("/");
+ if (slash>1)
+ relTo=relTo.substring(0,slash+1);
+ else
+ relTo="/";
+ path=URIUtil.addPaths(relTo,path);
+ }
+
+ return _context.getRequestDispatcher(path);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getRequestedSessionId()
+ */
+ public String getRequestedSessionId()
+ {
+ return _requestedSessionId;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getRequestURI()
+ */
+ public String getRequestURI()
+ {
+ if (_requestURI==null && _uri!=null)
+ _requestURI=_uri.getPathAndParam();
+ return _requestURI;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getRequestURL()
+ */
+ public StringBuffer getRequestURL()
+ {
+ StringBuffer url = new StringBuffer(48);
+ synchronized (url)
+ {
+ String scheme = getScheme();
+ int port = getServerPort();
+
+ url.append(scheme);
+ url.append("://");
+ url.append(getServerName());
+ if (_port>0 &&
+ ((scheme.equalsIgnoreCase(URIUtil.HTTP) && port != 80) ||
+ (scheme.equalsIgnoreCase(URIUtil.HTTPS) && port != 443)))
+ {
+ url.append(':');
+ url.append(_port);
+ }
+
+ url.append(getRequestURI());
+ return url;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public Response getResponse()
+ {
+ return _connection._response;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Reconstructs the URL the client used to make the request. The returned URL contains a
+ * protocol, server name, port number, and, but it does not include a path.
+ * <p>
+ * Because this method returns a <code>StringBuffer</code>, not a string, you can modify the
+ * URL easily, for example, to append path and query parameters.
+ *
+ * This method is useful for creating redirect messages and for reporting errors.
+ *
+ * @return "scheme://host:port"
+ */
+ public StringBuilder getRootURL()
+ {
+ StringBuilder url = new StringBuilder(48);
+ String scheme = getScheme();
+ int port = getServerPort();
+
+ url.append(scheme);
+ url.append("://");
+ url.append(getServerName());
+
+ if (port > 0 && ((scheme.equalsIgnoreCase("http") && port != 80) || (scheme.equalsIgnoreCase("https") && port != 443)))
+ {
+ url.append(':');
+ url.append(port);
+ }
+ return url;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getScheme()
+ */
+ public String getScheme()
+ {
+ return _scheme;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getServerName()
+ */
+ public String getServerName()
+ {
+ // Return already determined host
+ if (_serverName != null)
+ return _serverName;
+
+ // Return host from absolute URI
+ _serverName = _uri.getHost();
+ _port = _uri.getPort();
+ if (_serverName != null)
+ return _serverName;
+
+ // Return host from header field
+ Buffer hostPort = _connection.getRequestFields().get(HttpHeaders.HOST_BUFFER);
+ if (hostPort!=null)
+ {
+ for (int i=hostPort.length();i-->0;)
+ {
+ if (hostPort.peek(hostPort.getIndex()+i)==':')
+ {
+ _serverName=BufferUtil.to8859_1_String(hostPort.peek(hostPort.getIndex(), i));
+ _port=BufferUtil.toInt(hostPort.peek(hostPort.getIndex()+i+1, hostPort.length()-i-1));
+ return _serverName;
+ }
+ }
+ if (_serverName==null || _port<0)
+ {
+ _serverName=BufferUtil.to8859_1_String(hostPort);
+ _port = 0;
+ }
+
+ return _serverName;
+ }
+
+ // Return host from connection
+ if (_connection != null)
+ {
+ _serverName = getLocalName();
+ _port = getLocalPort();
+ if (_serverName != null && !StringUtil.ALL_INTERFACES.equals(_serverName))
+ return _serverName;
+ }
+
+ // Return the local host
+ try
+ {
+ _serverName = InetAddress.getLocalHost().getHostAddress();
+ }
+ catch (java.net.UnknownHostException e)
+ {
+ Log.ignore(e);
+ }
+ return _serverName;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getServerPort()
+ */
+ public int getServerPort()
+ {
+ if (_port<=0)
+ {
+ if (_serverName==null)
+ getServerName();
+
+ if (_port<=0)
+ {
+ if (_serverName!=null && _uri!=null)
+ _port = _uri.getPort();
+ else
+ _port = _endp==null?0:_endp.getLocalPort();
+ }
+ }
+
+ if (_port<=0)
+ {
+ if (getScheme().equalsIgnoreCase(URIUtil.HTTPS))
+ return 443;
+ return 80;
+ }
+ return _port;
+ }
+
+ /* ------------------------------------------------------------ */
+ public ServletContext getServletContext()
+ {
+ return _context;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ */
+ public String getServletName()
+ {
+ return _servletName;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getServletPath()
+ */
+ public String getServletPath()
+ {
+ if (_servletPath==null)
+ _servletPath="";
+ return _servletPath;
+ }
+
+ /* ------------------------------------------------------------ */
+ public ServletResponse getServletResponse()
+ {
+ return _connection.getResponse();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getSession()
+ */
+ public HttpSession getSession()
+ {
+ return getSession(true);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getSession(boolean)
+ */
+ public HttpSession getSession(boolean create)
+ {
+ if (_sessionManager==null && create)
+ throw new IllegalStateException("No SessionHandler or SessionManager");
+
+ if (_session != null && _sessionManager!=null && _sessionManager.isValid(_session))
+ return _session;
+
+ _session=null;
+
+ String id=getRequestedSessionId();
+
+ if (id != null && _sessionManager!=null)
+ {
+ _session=_sessionManager.getHttpSession(id);
+ if (_session == null && !create)
+ return null;
+ }
+
+ if (_session == null && _sessionManager!=null && create )
+ {
+ _session=_sessionManager.newHttpSession(this);
+ Cookie cookie=_sessionManager.getSessionCookie(_session,getContextPath(),isSecure());
+ if (cookie!=null)
+ _connection.getResponse().addCookie(cookie);
+ }
+
+ return _session;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the sessionManager.
+ */
+ public SessionManager getSessionManager()
+ {
+ return _sessionManager;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Get Request TimeStamp
+ *
+ * @return The time that the request was received.
+ */
+ public long getTimeStamp()
+ {
+ return _timeStamp;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Get Request TimeStamp
+ *
+ * @return The time that the request was received.
+ */
+ public Buffer getTimeStampBuffer()
+ {
+ if (_timeStampBuffer == null && _timeStamp > 0)
+ _timeStampBuffer = HttpFields.__dateCache.formatBuffer(_timeStamp);
+ return _timeStampBuffer;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the uri.
+ */
+ public HttpURI getUri()
+ {
+ return _uri;
+ }
+
+ public UserIdentity getUserIdentity()
+ {
+ return _userIdentity;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getUserPrincipal()
+ */
+ public Principal getUserPrincipal()
+ {
+ return _userIdentity.getUserPrincipal();
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isAsyncStarted()
+ {
+ return _async.isAsyncStarted();
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isAsyncSupported()
+ {
+ return _asyncSupported;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isHandled()
+ {
+ return _handled;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromCookie()
+ */
+ public boolean isRequestedSessionIdFromCookie()
+ {
+ return _requestedSessionId!=null && _requestedSessionIdFromCookie;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromUrl()
+ */
+ public boolean isRequestedSessionIdFromUrl()
+ {
+ return _requestedSessionId!=null && !_requestedSessionIdFromCookie;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromURL()
+ */
+ public boolean isRequestedSessionIdFromURL()
+ {
+ return _requestedSessionId!=null && !_requestedSessionIdFromCookie;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdValid()
+ */
+ public boolean isRequestedSessionIdValid()
+ {
+ if (_requestedSessionId==null)
+ return false;
+
+ HttpSession session=getSession(false);
+ return (session==null?false:_sessionManager.getIdManager().getClusterId(_requestedSessionId).equals(_sessionManager.getClusterId(session)));
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#isSecure()
+ */
+ public boolean isSecure()
+ {
+ return _connection.isConfidential(this);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#isUserInRole(java.lang.String)
+ */
+ public boolean isUserInRole(String role)
+ {
+ return _userIdentity!=null && _userIdentity.isUserInRole(role);
+ }
+
+ /* ------------------------------------------------------------ */
+ public HttpSession recoverNewSession(Object key)
+ {
+ if (_savedNewSessions==null)
+ return null;
+ return (HttpSession) _savedNewSessions.get(key);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void recycle()
+ {
+ _authType=null;
+ _async.recycle();
+ _asyncSupported=true;
+ _handled=false;
+ if (_context!=null)
+ throw new IllegalStateException("Request in context!");
+ if(_attributes!=null)
+ _attributes.clearAttributes();
+ _characterEncoding=null;
+ _queryEncoding=null;
+ _context=null;
+ _serverName=null;
+ _method=null;
+ _pathInfo=null;
+ _port=0;
+ _protocol=HttpVersions.HTTP_1_1;
+ _queryString=null;
+ _requestedSessionId=null;
+ _requestedSessionIdFromCookie=false;
+ _session=null;
+ _requestURI=null;
+ _scheme=URIUtil.HTTP;
+ _servletPath=null;
+ _timeStamp=0;
+ _timeStampBuffer=null;
+ _uri=null;
+ _userIdentity=UserIdentity.UNAUTHENTICATED_IDENTITY;
+ if (_baseParameters!=null)
+ _baseParameters.clear();
+ _parameters=null;
+ _paramsExtracted=false;
+ _inputState=__NONE;
+
+ _cookiesExtracted=false;
+ if (_savedNewSessions!=null)
+ _savedNewSessions.clear();
+ _savedNewSessions=null;
+ if (_continuation!=null && _continuation.isPending())
+ _continuation.reset();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#removeAttribute(java.lang.String)
+ */
+ public void removeAttribute(String name)
+ {
+ Object old_value=_attributes==null?null:_attributes.getAttribute(name);
+
+ if (_attributes!=null)
+ _attributes.removeAttribute(name);
+
+ if (old_value!=null)
+ {
+ if (_requestAttributeListeners!=null)
+ {
+ final ServletRequestAttributeEvent event =
+ new ServletRequestAttributeEvent(_context,this,name, old_value);
+ final int size=LazyList.size(_requestAttributeListeners);
+ for(int i=0;i<size;i++)
+ {
+ final EventListener listener = (ServletRequestAttributeListener)LazyList.get(_requestAttributeListeners,i);
+ if (listener instanceof ServletRequestAttributeListener)
+ {
+ final ServletRequestAttributeListener l = (ServletRequestAttributeListener)listener;
+ ((ServletRequestAttributeListener)l).attributeRemoved(event);
+ }
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void removeEventListener(final EventListener listener)
+ {
+ _requestAttributeListeners= LazyList.remove(_requestAttributeListeners, listener);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void saveNewSession(Object key,HttpSession session)
+ {
+ if (_savedNewSessions==null)
+ _savedNewSessions=new HashMap<Object,HttpSession>();
+ _savedNewSessions.put(key,session);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setAsyncSupported(boolean supported)
+ {
+ _asyncSupported=supported;
+ }
+ /* ------------------------------------------------------------ */
+ public void setAsyncTimeout(long timeout)
+ {
+ _async.setAsyncTimeout(timeout);
+ }
+ /* ------------------------------------------------------------ */
+ /*
+ * Set a request attribute.
+ * if the attribute name is "org.eclipse.jetty.server.server.Request.queryEncoding" then
+ * the value is also passed in a call to {@link #setQueryEncoding}.
+ *
+ * if the attribute name is "org.eclipse.jetty.server.server.ResponseBuffer", then
+ * the response buffer is flushed with @{link #flushResponseBuffer}
+ *
+ * @see javax.servlet.ServletRequest#setAttribute(java.lang.String, java.lang.Object)
+ */
+ public void setAttribute(String name, Object value)
+ {
+ Object old_value=_attributes==null?null:_attributes.getAttribute(name);
+
+ if ("org.eclipse.jetty.server.Request.queryEncoding".equals(name))
+ setQueryEncoding(value==null?null:value.toString());
+ else if("org.eclipse.jetty.server.sendContent".equals(name))
+ {
+ try
+ {
+ ((HttpConnection.Output)getServletResponse().getOutputStream()).sendContent(value);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+ else if("org.eclipse.jetty.server.ResponseBuffer".equals(name))
+ {
+ try
+ {
+ ByteBuffer byteBuffer=(ByteBuffer)value;
+ synchronized (byteBuffer)
+ {
+ NIOBuffer buffer = byteBuffer.isDirect()
+ ?(NIOBuffer)new DirectNIOBuffer(byteBuffer,true)
+ :(NIOBuffer)new IndirectNIOBuffer(byteBuffer,true);
+ ((HttpConnection.Output)getServletResponse().getOutputStream()).sendResponse(buffer);
+ }
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ if (_attributes==null)
+ _attributes=new AttributesMap();
+ _attributes.setAttribute(name, value);
+
+ if (_requestAttributeListeners!=null)
+ {
+ final ServletRequestAttributeEvent event =
+ new ServletRequestAttributeEvent(_context,this,name, old_value==null?value:old_value);
+ final int size=LazyList.size(_requestAttributeListeners);
+ for(int i=0;i<size;i++)
+ {
+ final EventListener listener = (ServletRequestAttributeListener)LazyList.get(_requestAttributeListeners,i);
+ if (listener instanceof ServletRequestAttributeListener)
+ {
+ final ServletRequestAttributeListener l = (ServletRequestAttributeListener)listener;
+
+ if (old_value==null)
+ l.attributeAdded(event);
+ else if (value==null)
+ l.attributeRemoved(event);
+ else
+ l.attributeReplaced(event);
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ */
+ public void setAttributes(Attributes attributes)
+ {
+ _attributes=attributes;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setAuthType(String authType)
+ {
+ _authType=authType;
+ }
+
+ /* ------------------------------------------------------------ */
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#setCharacterEncoding(java.lang.String)
+ */
+ public void setCharacterEncoding(String encoding) throws UnsupportedEncodingException
+ {
+ if (_inputState!=__NONE)
+ return;
+
+ _characterEncoding=encoding;
+
+ // check encoding is supported
+ if (!StringUtil.isUTF8(encoding))
+ "".getBytes(encoding);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#setCharacterEncoding(java.lang.String)
+ */
+ public void setCharacterEncodingUnchecked(String encoding)
+ {
+ _characterEncoding=encoding;
+ }
+
+ /* ------------------------------------------------------------ */
+ //final so we can safely call this from constructor
+ protected final void setConnection(HttpConnection connection)
+ {
+ _connection=connection;
+ _async.setConnection(connection);
+ _endp=connection.getEndPoint();
+ _dns=connection.getResolveNames();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getContentType()
+ */
+ public void setContentType(String contentType)
+ {
+ _connection.getRequestFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,contentType);
+
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param context
+ */
+ public void setContext(Context context)
+ {
+ _context=context;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Sets the "context path" for this request
+ * @see HttpServletRequest#getContextPath
+ */
+ public void setContextPath(String contextPath)
+ {
+ _contextPath = contextPath;
+ }
+
+ /* ------------------------------------------------------------ */
+ void setContinuation(Continuation cont)
+ {
+ _continuation=cont;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param cookies The cookies to set.
+ */
+ public void setCookies(Cookie[] cookies)
+ {
+ if (_cookies==null)
+ _cookies=new CookieCutter(this);
+ _cookies.setCookies(cookies);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setDispatcherType(DispatcherType type)
+ {
+ _dispatcherType=type;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setHandled(boolean h)
+ {
+ _handled=h;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param method The method to set.
+ */
+ public void setMethod(String method)
+ {
+ _method = method;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param parameters The parameters to set.
+ */
+ public void setParameters(MultiMap parameters)
+ {
+ _parameters= (parameters==null)?_baseParameters:parameters;
+ if (_paramsExtracted && _parameters==null)
+ throw new IllegalStateException();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param pathInfo The pathInfo to set.
+ */
+ public void setPathInfo(String pathInfo)
+ {
+ _pathInfo = pathInfo;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param protocol The protocol to set.
+ */
+ public void setProtocol(String protocol)
+ {
+ _protocol = protocol;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the character encoding used for the query string.
+ * This call will effect the return of getQueryString and getParamaters.
+ * It must be called before any geParameter methods.
+ *
+ * The request attribute "org.eclipse.jetty.server.server.Request.queryEncoding"
+ * may be set as an alternate method of calling setQueryEncoding.
+ *
+ * @param queryEncoding
+ */
+ public void setQueryEncoding(String queryEncoding)
+ {
+ _queryEncoding=queryEncoding;
+ _queryString=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param queryString The queryString to set.
+ */
+ public void setQueryString(String queryString)
+ {
+ _queryString = queryString;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param addr The address to set.
+ */
+ public void setRemoteAddr(String addr)
+ {
+ _remoteAddr = addr;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param host The host to set.
+ */
+ public void setRemoteHost(String host)
+ {
+ _remoteHost = host;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param requestedSessionId The requestedSessionId to set.
+ */
+ public void setRequestedSessionId(String requestedSessionId)
+ {
+ _requestedSessionId = requestedSessionId;
+ }
+ /* ------------------------------------------------------------ */
+ /**
+ * @param requestedSessionIdCookie The requestedSessionIdCookie to set.
+ */
+ public void setRequestedSessionIdFromCookie(boolean requestedSessionIdCookie)
+ {
+ _requestedSessionIdFromCookie = requestedSessionIdCookie;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param requestListeners {@link LazyList} of {@link ServletRequestListener}s
+ */
+ public void setRequestListeners(Object requestListeners)
+ {
+ _requestListeners=requestListeners;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param requestURI The requestURI to set.
+ */
+ public void setRequestURI(String requestURI)
+ {
+ _requestURI = requestURI;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param scheme The scheme to set.
+ */
+ public void setScheme(String scheme)
+ {
+ _scheme = scheme;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param host The host to set.
+ */
+ public void setServerName(String host)
+ {
+ _serverName = host;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param port The port to set.
+ */
+ public void setServerPort(int port)
+ {
+ _port = port;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param name The servletName to set.
+ */
+ public void setServletName(String name)
+ {
+ _servletName = name;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param servletPath The servletPath to set.
+ */
+ public void setServletPath(String servletPath)
+ {
+ _servletPath = servletPath;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param session The session to set.
+ */
+ public void setSession(HttpSession session)
+ {
+ _session = session;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param sessionManager The sessionManager to set.
+ */
+ public void setSessionManager(SessionManager sessionManager)
+ {
+ _sessionManager = sessionManager;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setTimeStamp(long ts)
+ {
+ _timeStamp = ts;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param uri The uri to set.
+ */
+ public void setUri(HttpURI uri)
+ {
+ _uri = uri;
+ }
+
+ public void setUserIdentity(UserIdentity userIdentity)
+ {
+ if (userIdentity == null)
+ throw new NullPointerException("No UserIdentity");
+ _userIdentity = userIdentity;
+ }
+
+ /* ------------------------------------------------------------ */
+ public AsyncContext startAsync() throws IllegalStateException
+ {
+ if (!_asyncSupported)
+ throw new IllegalStateException("!asyncSupported");
+ _async.suspend(_context,this,_connection._response);
+ return _async;
+ }
+
+ /* ------------------------------------------------------------ */
+ public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException
+ {
+ if (!_asyncSupported)
+ throw new IllegalStateException("!asyncSupported");
+ _async.suspend(_context,servletRequest,servletResponse);
+ return _async;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return {@link LazyList} of {@link ServletRequestListener}s
+ */
+ public Object takeRequestListeners()
+ {
+ final Object listeners=_requestListeners;
+ _requestListeners=null;
+ return listeners;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String toString()
+ {
+ return (_handled?"[":"(")+getMethod()+" "+_uri+(_handled?"]@":")@")+hashCode()+" "+super.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.servlet.http.HttpServletRequest#login(javax.servlet.http.HttpServletResponse)
+ */
+ public boolean login(HttpServletResponse response) throws IOException, LoginException
+ {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.servlet.http.HttpServletRequest#login(java.lang.String, java.lang.String)
+ */
+ public void login(String username, String password) throws LoginException
+ {
+ // TODO Auto-generated method stub
+
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.servlet.http.HttpServletRequest#logout()
+ */
+ public void logout() throws LoginException
+ {
+ // TODO Auto-generated method stub
+
+ }
+
+}
+
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/RequestLog.java b/jetty-server/src/main/java/org/eclipse/jetty/server/RequestLog.java
new file mode 100644
index 0000000000..2e95bbc0e0
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/RequestLog.java
@@ -0,0 +1,26 @@
+// ========================================================================
+// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+
+package org.eclipse.jetty.server;
+
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/**
+ * A <code>RequestLog</code> can be attached to a {@link org.eclipse.jetty.server.server.handler.RequestLogHandler} to enable logging of requests/responses.
+ *
+ * @see Server#setRequestLog
+ */
+public interface RequestLog extends LifeCycle
+{
+ public void log(Request request, Response response);
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java
new file mode 100644
index 0000000000..09b3626015
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java
@@ -0,0 +1,555 @@
+// ========================================================================
+// Copyright (c) 2000-2009 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jetty.http.HttpContent;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.io.View;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.resource.ResourceFactory;
+
+
+/* ------------------------------------------------------------ */
+/**
+ *
+ */
+public class ResourceCache extends AbstractLifeCycle implements Serializable
+{
+ private int _maxCachedFileSize =1024*1024;
+ private int _maxCachedFiles=2048;
+ private int _maxCacheSize =16*1024*1024;
+ private MimeTypes _mimeTypes;
+
+ protected transient Map _cache;
+ protected transient int _cachedSize;
+ protected transient int _cachedFiles;
+ protected transient Content _mostRecentlyUsed;
+ protected transient Content _leastRecentlyUsed;
+
+ /* ------------------------------------------------------------ */
+ /** Constructor.
+ */
+ public ResourceCache(MimeTypes mimeTypes)
+ {
+ _mimeTypes=mimeTypes;
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getCachedSize()
+ {
+ return _cachedSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getCachedFiles()
+ {
+ return _cachedFiles;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public int getMaxCachedFileSize()
+ {
+ return _maxCachedFileSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setMaxCachedFileSize(int maxCachedFileSize)
+ {
+ _maxCachedFileSize = maxCachedFileSize;
+ flushCache();
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getMaxCacheSize()
+ {
+ return _maxCacheSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setMaxCacheSize(int maxCacheSize)
+ {
+ _maxCacheSize = maxCacheSize;
+ flushCache();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the maxCachedFiles.
+ */
+ public int getMaxCachedFiles()
+ {
+ return _maxCachedFiles;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param maxCachedFiles The maxCachedFiles to set.
+ */
+ public void setMaxCachedFiles(int maxCachedFiles)
+ {
+ _maxCachedFiles = maxCachedFiles;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void flushCache()
+ {
+ if (_cache!=null)
+ {
+ synchronized(this)
+ {
+ ArrayList<Content> values=new ArrayList<Content>(_cache.values());
+ for (Content content : values)
+ content.invalidate();
+
+ _cache.clear();
+
+ _cachedSize=0;
+ _cachedFiles=0;
+ _mostRecentlyUsed=null;
+ _leastRecentlyUsed=null;
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get a Entry from the cache.
+ * Get either a valid entry object or create a new one if possible.
+ *
+ * @param pathInContext The key into the cache
+ * @param factory If no matching entry is found, this {@link ResourceFactory} will be used to create the {@link Resource}
+ * for the new enry that is created.
+ * @return The entry matching <code>pathInContext</code>, or a new entry if no matching entry was found
+ */
+ public Content lookup(String pathInContext, ResourceFactory factory)
+ throws IOException
+ {
+ Content content=null;
+
+ // Look up cache operations
+ synchronized(_cache)
+ {
+ // Look for it in the cache
+ content = (Content)_cache.get(pathInContext);
+
+ if (content!=null && content.isValid())
+ {
+ return content;
+ }
+ }
+ Resource resource=factory.getResource(pathInContext);
+ return load(pathInContext,resource);
+ }
+
+ /* ------------------------------------------------------------ */
+ public Content lookup(String pathInContext, Resource resource)
+ throws IOException
+ {
+ Content content=null;
+
+ // Look up cache operations
+ synchronized(_cache)
+ {
+ // Look for it in the cache
+ content = (Content)_cache.get(pathInContext);
+
+ if (content!=null && content.isValid())
+ {
+ return content;
+ }
+ }
+ return load(pathInContext,resource);
+ }
+
+ /* ------------------------------------------------------------ */
+ private Content load(String pathInContext, Resource resource)
+ throws IOException
+ {
+ Content content=null;
+ if (resource!=null && resource.exists() && !resource.isDirectory())
+ {
+ long len = resource.length();
+ if (len>0 && len<_maxCachedFileSize && len<_maxCacheSize)
+ {
+ int must_be_smaller_than=_maxCacheSize-(int)len;
+
+ synchronized(_cache)
+ {
+ // check the cache is not full of locked content before loading content
+
+ while(_leastRecentlyUsed!=null && (_cachedSize>must_be_smaller_than || (_maxCachedFiles>0 && _cachedFiles>=_maxCachedFiles)))
+ _leastRecentlyUsed.invalidate();
+
+ if(_cachedSize>must_be_smaller_than || (_maxCachedFiles>0 && _cachedFiles>=_maxCachedFiles))
+ return null;
+ }
+
+ content = new Content(resource);
+ fill(content);
+
+ synchronized(_cache)
+ {
+ // check that somebody else did not fill this spot.
+ Content content2 =(Content)_cache.get(pathInContext);
+ if (content2!=null)
+ {
+ content.release();
+ return content2;
+ }
+
+ while(_leastRecentlyUsed!=null && (_cachedSize>must_be_smaller_than || (_maxCachedFiles>0 && _cachedFiles>=_maxCachedFiles)))
+ _leastRecentlyUsed.invalidate();
+
+ if(_cachedSize>must_be_smaller_than || (_maxCachedFiles>0 && _cachedFiles>=_maxCachedFiles))
+ return null; // this could waste an allocated File or DirectBuffer
+
+ content.cache(pathInContext);
+
+ return content;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Remember a Resource Miss!
+ * @param pathInContext
+ * @param resource
+ * @throws IOException
+ */
+ public void miss(String pathInContext, Resource resource)
+ throws IOException
+ {
+ synchronized(_cache)
+ {
+ while(_maxCachedFiles>0 && _cachedFiles>=_maxCachedFiles && _leastRecentlyUsed!=null)
+ _leastRecentlyUsed.invalidate();
+ if (_maxCachedFiles>0 && _cachedFiles>=_maxCachedFiles)
+ return;
+
+ // check that somebody else did not fill this spot.
+ Miss miss = new Miss(resource);
+ Content content2 =(Content)_cache.get(pathInContext);
+ if (content2!=null)
+ {
+ miss.release();
+ return;
+ }
+
+ miss.cache(pathInContext);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public synchronized void doStart()
+ throws Exception
+ {
+ _cache=new HashMap();
+ _cachedSize=0;
+ _cachedFiles=0;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Stop the context.
+ */
+ public void doStop()
+ throws InterruptedException
+ {
+ flushCache();
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void fill(Content content)
+ throws IOException
+ {
+ try
+ {
+ InputStream in = content.getResource().getInputStream();
+ int len=(int)content.getResource().length();
+ Buffer buffer = new ByteArrayBuffer(len);
+ buffer.readFrom(in,len);
+ in.close();
+ content.setBuffer(buffer);
+ }
+ finally
+ {
+ content.getResource().release();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /** MetaData associated with a context Resource.
+ */
+ public class Content implements HttpContent
+ {
+ boolean _locked;
+ String _key;
+ Resource _resource;
+ long _lastModified;
+ Content _prev;
+ Content _next;
+
+ Buffer _lastModifiedBytes;
+ Buffer _contentType;
+ Buffer _buffer;
+
+ /* ------------------------------------------------------------ */
+ Content(Resource resource)
+ {
+ _resource=resource;
+
+ _next=this;
+ _prev=this;
+ _contentType=_mimeTypes.getMimeByExtension(_resource.toString());
+
+ _lastModified=resource.lastModified();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if the content is locked in the cache
+ */
+ public boolean isLocked()
+ {
+ return _locked;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param locked true if the content is locked in the cache
+ */
+ public void setLocked(boolean locked)
+ {
+ synchronized (_cache)
+ {
+ if (_locked && !locked)
+ {
+ _locked = locked;
+ _next=_mostRecentlyUsed;
+ _mostRecentlyUsed=this;
+ if (_next!=null)
+ _next._prev=this;
+ _prev=null;
+ if (_leastRecentlyUsed==null)
+ _leastRecentlyUsed=this;
+ }
+ else if (!_locked && locked)
+ {
+ if (_prev!=null)
+ _prev._next=_next;
+ if (_next!=null)
+ _next._prev=_prev;
+ _next=_prev=null;
+ }
+ else
+ _locked = locked;
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ void cache(String pathInContext)
+ {
+ _key=pathInContext;
+
+ if (!_locked)
+ {
+ _next=_mostRecentlyUsed;
+ _mostRecentlyUsed=this;
+ if (_next!=null)
+ _next._prev=this;
+ _prev=null;
+ if (_leastRecentlyUsed==null)
+ _leastRecentlyUsed=this;
+ }
+ _cache.put(_key,this);
+ if (_buffer!=null)
+ _cachedSize+=_buffer.length();
+ _cachedFiles++;
+ if (_lastModified!=-1)
+ _lastModifiedBytes=new ByteArrayBuffer(HttpFields.formatDate(_lastModified));
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getKey()
+ {
+ return _key;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isCached()
+ {
+ return _key!=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Resource getResource()
+ {
+ return _resource;
+ }
+
+ /* ------------------------------------------------------------ */
+ boolean isValid()
+ {
+ if (_lastModified==_resource.lastModified())
+ {
+ if (!_locked && _mostRecentlyUsed!=this)
+ {
+ Content tp = _prev;
+ Content tn = _next;
+
+ _next=_mostRecentlyUsed;
+ _mostRecentlyUsed=this;
+ if (_next!=null)
+ _next._prev=this;
+ _prev=null;
+
+ if (tp!=null)
+ tp._next=tn;
+ if (tn!=null)
+ tn._prev=tp;
+
+ if (_leastRecentlyUsed==this && tp!=null)
+ _leastRecentlyUsed=tp;
+ }
+ return true;
+ }
+
+ invalidate();
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void invalidate()
+ {
+ synchronized(this)
+ {
+ // Invalidate it
+ _cache.remove(_key);
+ _key=null;
+ if (_buffer!=null)
+ _cachedSize=_cachedSize-(int)_buffer.length();
+ _cachedFiles--;
+
+ if (_mostRecentlyUsed==this)
+ _mostRecentlyUsed=_next;
+ else
+ _prev._next=_next;
+
+ if (_leastRecentlyUsed==this)
+ _leastRecentlyUsed=_prev;
+ else
+ _next._prev=_prev;
+
+ _prev=null;
+ _next=null;
+ if (_resource!=null)
+ _resource.release();
+ _resource=null;
+
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public Buffer getLastModified()
+ {
+ return _lastModifiedBytes;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Buffer getContentType()
+ {
+ return _contentType;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setContentType(Buffer type)
+ {
+ _contentType=type;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void release()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public Buffer getBuffer()
+ {
+ if (_buffer==null)
+ return null;
+ return new View(_buffer);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setBuffer(Buffer buffer)
+ {
+ _buffer=buffer;
+ }
+
+ /* ------------------------------------------------------------ */
+ public long getContentLength()
+ {
+ if (_buffer==null)
+ return -1;
+ return _buffer.length();
+ }
+
+ /* ------------------------------------------------------------ */
+ public InputStream getInputStream() throws IOException
+ {
+ return _resource.getInputStream();
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /** MetaData associated with a context Resource.
+ */
+ public class Miss extends Content
+ {
+ Miss(Resource resource)
+ {
+ super(resource);
+ }
+
+ /* ------------------------------------------------------------ */
+ boolean isValid()
+ {
+ if (_resource.exists())
+ {
+ invalidate();
+ return false;
+ }
+ return true;
+ }
+ }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java
new file mode 100644
index 0000000000..8fc497162f
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java
@@ -0,0 +1,1191 @@
+// ========================================================================
+// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Locale;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.http.Generator;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpHeaderValues;
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpVersions;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.io.BufferCache.CachedBuffer;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ErrorHandler;
+import org.eclipse.jetty.util.ByteArrayISO8859Writer;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+
+/* ------------------------------------------------------------ */
+/** Response.
+ * <p>
+ * Implements {@link javax.servlet.HttpServletResponse} from the {@link javax.servlet} package.
+ * </p>
+ *
+ *
+ *
+ */
+public class Response implements HttpServletResponse
+{
+ public static final int
+ NONE=0,
+ STREAM=1,
+ WRITER=2;
+
+ /**
+ * If a header name starts with this string, the header (stripped of the prefix)
+ * can be set during include using only {@link #setHeader(String, String)} or
+ * {@link #addHeader(String, String)}.
+ */
+ public static String SET_INCLUDE_HEADER_PREFIX = "org.eclipse.jetty.server.include.";
+
+ private static PrintWriter __nullPrintWriter;
+ private static ServletOutputStream __nullServletOut;
+
+ static
+ {
+ try{
+ __nullPrintWriter = new PrintWriter(IO.getNullWriter());
+ __nullServletOut = new NullOutput();
+ }
+ catch (Exception e)
+ {
+ Log.warn(e);
+ }
+ }
+
+ private HttpConnection _connection;
+ private int _status=SC_OK;
+ private String _reason;
+ private Locale _locale;
+ private String _mimeType;
+ private CachedBuffer _cachedMimeType;
+ private String _characterEncoding;
+ private boolean _explicitEncoding;
+ private String _contentType;
+ private int _outputState;
+ private PrintWriter _writer;
+
+ /* ------------------------------------------------------------ */
+ /**
+ *
+ */
+ public Response(HttpConnection connection)
+ {
+ _connection=connection;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletResponse#reset()
+ */
+ protected void recycle()
+ {
+ _status=SC_OK;
+ _reason=null;
+ _locale=null;
+ _mimeType=null;
+ _cachedMimeType=null;
+ _characterEncoding=null;
+ _explicitEncoding=false;
+ _contentType=null;
+ _outputState=NONE;
+ _writer=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletResponse#addCookie(javax.servlet.http.Cookie)
+ */
+ public void addCookie(Cookie cookie)
+ {
+ _connection.getResponseFields().addSetCookie(cookie.getName(),
+ cookie.getValue(),
+ cookie.getDomain(),
+ cookie.getPath(),
+ cookie.getMaxAge(),
+ cookie.getComment(),
+ cookie.getSecure(),
+ cookie.isHttpOnly(),
+ cookie.getVersion());
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletResponse#containsHeader(java.lang.String)
+ */
+ public boolean containsHeader(String name)
+ {
+ return _connection.getResponseFields().containsKey(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletResponse#encodeURL(java.lang.String)
+ */
+ public String encodeURL(String url)
+ {
+ Request request=_connection.getRequest();
+ SessionManager sessionManager = request.getSessionManager();
+ if (sessionManager==null)
+ return url;
+ String sessionURLPrefix = sessionManager.getSessionIdPathParameterNamePrefix();
+ if (sessionURLPrefix==null)
+ return url;
+
+ // should not encode if cookies in evidence
+ if (url==null || request==null || request.isRequestedSessionIdFromCookie())
+ {
+ int prefix=url.indexOf(sessionURLPrefix);
+ if (prefix!=-1)
+ {
+ int suffix=url.indexOf("?",prefix);
+ if (suffix<0)
+ suffix=url.indexOf("#",prefix);
+
+ if (suffix<=prefix)
+ return url.substring(0,prefix);
+ return url.substring(0,prefix)+url.substring(suffix);
+ }
+ return url;
+ }
+
+ // get session;
+ HttpSession session=request.getSession(false);
+
+ // no session
+ if (session == null)
+ return url;
+
+
+ // invalid session
+ if (!sessionManager.isValid(session))
+ return url;
+
+ String id=sessionManager.getNodeId(session);
+
+
+ // TODO Check host and port are for this server
+ // Already encoded
+ int prefix=url.indexOf(sessionURLPrefix);
+ if (prefix!=-1)
+ {
+ int suffix=url.indexOf("?",prefix);
+ if (suffix<0)
+ suffix=url.indexOf("#",prefix);
+
+ if (suffix<=prefix)
+ return url.substring(0,prefix+sessionURLPrefix.length())+id;
+ return url.substring(0,prefix+sessionURLPrefix.length())+id+
+ url.substring(suffix);
+ }
+
+ // edit the session
+ int suffix=url.indexOf('?');
+ if (suffix<0)
+ suffix=url.indexOf('#');
+ if (suffix<0)
+ return url+sessionURLPrefix+id;
+ return url.substring(0,suffix)+
+ sessionURLPrefix+id+url.substring(suffix);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletResponse#encodeRedirectURL(java.lang.String)
+ */
+ public String encodeRedirectURL(String url)
+ {
+ return encodeURL(url);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletResponse#encodeUrl(java.lang.String)
+ */
+ public String encodeUrl(String url)
+ {
+ return encodeURL(url);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletResponse#encodeRedirectUrl(java.lang.String)
+ */
+ public String encodeRedirectUrl(String url)
+ {
+ return encodeURL(url);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletResponse#sendError(int, java.lang.String)
+ */
+ public void sendError(int code, String message) throws IOException
+ {
+ if (_connection.isIncluding())
+ return;
+
+ if (isCommitted())
+ Log.warn("Committed before "+code+" "+message);
+
+ resetBuffer();
+ _characterEncoding=null;
+ setHeader(HttpHeaders.EXPIRES,null);
+ setHeader(HttpHeaders.LAST_MODIFIED,null);
+ setHeader(HttpHeaders.CACHE_CONTROL,null);
+ setHeader(HttpHeaders.CONTENT_TYPE,null);
+ setHeader(HttpHeaders.CONTENT_LENGTH,null);
+
+ _outputState=NONE;
+ setStatus(code,message);
+
+ if (message==null)
+ message=HttpStatus.getCode(code).getMessage();
+
+ // If we are allowed to have a body
+ if (code!=SC_NO_CONTENT &&
+ code!=SC_NOT_MODIFIED &&
+ code!=SC_PARTIAL_CONTENT &&
+ code>=SC_OK)
+ {
+ Request request = _connection.getRequest();
+
+ ErrorHandler error_handler = null;
+ ContextHandler.Context context = request.getContext();
+ if (context!=null)
+ error_handler=context.getContextHandler().getErrorHandler();
+ if (error_handler!=null)
+ {
+ // TODO - probably should reset these after the request?
+ request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,new Integer(code));
+ request.setAttribute(RequestDispatcher.ERROR_MESSAGE, message);
+ request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, request.getRequestURI());
+ request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME,request.getServletName());
+
+ error_handler.handle(null,_connection.getRequest(),this);
+ }
+ else
+ {
+ setHeader(HttpHeaders.CACHE_CONTROL, "must-revalidate,no-cache,no-store");
+ setContentType(MimeTypes.TEXT_HTML_8859_1);
+ ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(2048);
+ if (message != null)
+ {
+ message= StringUtil.replace(message, "&", "&amp;");
+ message= StringUtil.replace(message, "<", "&lt;");
+ message= StringUtil.replace(message, ">", "&gt;");
+ }
+ String uri= request.getRequestURI();
+ if (uri!=null)
+ {
+ uri= StringUtil.replace(uri, "&", "&amp;");
+ uri= StringUtil.replace(uri, "<", "&lt;");
+ uri= StringUtil.replace(uri, ">", "&gt;");
+ }
+
+ writer.write("<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html;charset=ISO-8859-1\"/>\n");
+ writer.write("<title>Error ");
+ writer.write(Integer.toString(code));
+ writer.write(' ');
+ if (message==null)
+ message=HttpStatus.getCode(code).getMessage();
+ writer.write(message);
+ writer.write("</title>\n</head>\n<body>\n<h2>HTTP ERROR: ");
+ writer.write(Integer.toString(code));
+ writer.write("</h2>\n<p>Problem accessing ");
+ writer.write(uri);
+ writer.write(". Reason:\n<pre> ");
+ writer.write(message);
+ writer.write("</pre>");
+ writer.write("</p>\n<hr /><i><small>Powered by Jetty://</small></i>");
+
+ for (int i= 0; i < 20; i++)
+ writer.write("\n ");
+ writer.write("\n</body>\n</html>\n");
+
+ writer.flush();
+ setContentLength(writer.size());
+ writer.writeTo(getOutputStream());
+ writer.destroy();
+ }
+ }
+ else if (code!=SC_PARTIAL_CONTENT)
+ {
+ _connection.getRequestFields().remove(HttpHeaders.CONTENT_TYPE_BUFFER);
+ _connection.getRequestFields().remove(HttpHeaders.CONTENT_LENGTH_BUFFER);
+ _characterEncoding=null;
+ _mimeType=null;
+ _cachedMimeType=null;
+ }
+
+ complete();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletResponse#sendError(int)
+ */
+ public void sendError(int sc) throws IOException
+ {
+ if (sc==102)
+ sendProcessing();
+ else
+ sendError(sc,null);
+ }
+
+ /* ------------------------------------------------------------ */
+ /* Send a 102-Processing response.
+ * If the connection is a HTTP connection, the version is 1.1 and the
+ * request has a Expect header starting with 102, then a 102 response is
+ * sent. This indicates that the request still be processed and real response
+ * can still be sent. This method is called by sendError if it is passed 102.
+ * @see javax.servlet.http.HttpServletResponse#sendError(int)
+ */
+ public void sendProcessing() throws IOException
+ {
+ Generator g = _connection.getGenerator();
+ if (g instanceof HttpGenerator)
+ {
+ HttpGenerator generator = (HttpGenerator)g;
+
+ String expect = _connection.getRequest().getHeader(HttpHeaders.EXPECT);
+
+ if (expect!=null && expect.startsWith("102") && generator.getVersion()>=HttpVersions.HTTP_1_1_ORDINAL)
+ {
+ boolean was_persistent=generator.isPersistent();
+ generator.setResponse(102,null);
+ generator.completeHeader(null,true);
+ generator.setPersistent(true);
+ generator.complete();
+ generator.flushBuffer();
+ generator.reset(false);
+ generator.setPersistent(was_persistent);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletResponse#sendRedirect(java.lang.String)
+ */
+ public void sendRedirect(String location) throws IOException
+ {
+ if (_connection.isIncluding())
+ return;
+
+ if (location==null)
+ throw new IllegalArgumentException();
+
+ if (!URIUtil.hasScheme(location))
+ {
+ StringBuilder buf = _connection.getRequest().getRootURL();
+ if (location.startsWith("/"))
+ buf.append(URIUtil.canonicalPath(location));
+ else
+ {
+ String path=_connection.getRequest().getRequestURI();
+ String parent=(path.endsWith("/"))?path:URIUtil.parentPath(path);
+ location=URIUtil.canonicalPath(URIUtil.addPaths(parent,location));
+ if(location==null)
+ throw new IllegalStateException("path cannot be above root");
+ if (!location.startsWith("/"))
+ buf.append('/');
+ buf.append(location);
+ }
+
+ location=buf.toString();
+ }
+ resetBuffer();
+
+ setHeader(HttpHeaders.LOCATION,location);
+ setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
+ complete();
+
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletResponse#setDateHeader(java.lang.String, long)
+ */
+ public void setDateHeader(String name, long date)
+ {
+ if (!_connection.isIncluding())
+ _connection.getResponseFields().putDateField(name, date);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletResponse#addDateHeader(java.lang.String, long)
+ */
+ public void addDateHeader(String name, long date)
+ {
+ if (!_connection.isIncluding())
+ _connection.getResponseFields().addDateField(name, date);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletResponse#setHeader(java.lang.String, java.lang.String)
+ */
+ public void setHeader(String name, String value)
+ {
+ if (_connection.isIncluding())
+ {
+ if (name.startsWith(SET_INCLUDE_HEADER_PREFIX))
+ name=name.substring(SET_INCLUDE_HEADER_PREFIX.length());
+ else
+ return;
+ }
+ _connection.getResponseFields().put(name, value);
+ if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name))
+ {
+ if (value==null)
+ _connection._generator.setContentLength(-1);
+ else
+ _connection._generator.setContentLength(Long.parseLong(value));
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ */
+ public String getHeader(String name)
+ {
+ return _connection.getResponseFields().getStringField(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ */
+ public Enumeration getHeaders(String name)
+ {
+ Enumeration e = _connection.getResponseFields().getValues(name);
+ if (e==null)
+ return Collections.enumeration(Collections.EMPTY_LIST);
+ return e;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletResponse#addHeader(java.lang.String, java.lang.String)
+ */
+ public void addHeader(String name, String value)
+ {
+ if (_connection.isIncluding())
+ {
+ if (name.startsWith(SET_INCLUDE_HEADER_PREFIX))
+ name=name.substring(SET_INCLUDE_HEADER_PREFIX.length());
+ else
+ return;
+ }
+
+ _connection.getResponseFields().add(name, value);
+ if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name))
+ _connection._generator.setContentLength(Long.parseLong(value));
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletResponse#setIntHeader(java.lang.String, int)
+ */
+ public void setIntHeader(String name, int value)
+ {
+ if (!_connection.isIncluding())
+ {
+ _connection.getResponseFields().putLongField(name, value);
+ if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name))
+ _connection._generator.setContentLength(value);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletResponse#addIntHeader(java.lang.String, int)
+ */
+ public void addIntHeader(String name, int value)
+ {
+ if (!_connection.isIncluding())
+ {
+ _connection.getResponseFields().addLongField(name, value);
+ if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name))
+ _connection._generator.setContentLength(value);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletResponse#setStatus(int)
+ */
+ public void setStatus(int sc)
+ {
+ setStatus(sc,null);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletResponse#setStatus(int, java.lang.String)
+ */
+ public void setStatus(int sc, String sm)
+ {
+ if (sc<=0)
+ throw new IllegalArgumentException();
+ if (!_connection.isIncluding())
+ {
+ _status=sc;
+ _reason=sm;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletResponse#getCharacterEncoding()
+ */
+ public String getCharacterEncoding()
+ {
+ if (_characterEncoding==null)
+ _characterEncoding=StringUtil.__ISO_8859_1;
+ return _characterEncoding;
+ }
+
+ /* ------------------------------------------------------------ */
+ String getSetCharacterEncoding()
+ {
+ return _characterEncoding;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletResponse#getContentType()
+ */
+ public String getContentType()
+ {
+ return _contentType;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletResponse#getOutputStream()
+ */
+ public ServletOutputStream getOutputStream() throws IOException
+ {
+ if (_outputState!=NONE && _outputState!=STREAM)
+ throw new IllegalStateException("WRITER");
+
+ _outputState=STREAM;
+ return _connection.getOutputStream();
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isWriting()
+ {
+ return _outputState==WRITER;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isOutputing()
+ {
+ return _outputState!=NONE;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletResponse#getWriter()
+ */
+ public PrintWriter getWriter() throws IOException
+ {
+ if (_outputState!=NONE && _outputState!=WRITER)
+ throw new IllegalStateException("STREAM");
+
+ /* if there is no writer yet */
+ if (_writer==null)
+ {
+ /* get encoding from Content-Type header */
+ String encoding = _characterEncoding;
+
+ if (encoding==null)
+ {
+ /* implementation of educated defaults */
+ if(_mimeType!=null)
+ encoding = null; // TODO getHttpContext().getEncodingByMimeType(_mimeType);
+
+ if (encoding==null)
+ encoding = StringUtil.__ISO_8859_1;
+
+ setCharacterEncoding(encoding);
+ }
+
+ /* construct Writer using correct encoding */
+ _writer = _connection.getPrintWriter(encoding);
+ }
+ _outputState=WRITER;
+ return _writer;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletResponse#setCharacterEncoding(java.lang.String)
+ */
+ public void setCharacterEncoding(String encoding)
+ {
+ if (_connection.isIncluding())
+ return;
+
+ if (this._outputState==0 && !isCommitted())
+ {
+ _explicitEncoding=true;
+
+ if (encoding==null)
+ {
+ // Clear any encoding.
+ if (_characterEncoding!=null)
+ {
+ _characterEncoding=null;
+ if (_cachedMimeType!=null)
+ _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_cachedMimeType);
+ else
+ _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_mimeType);
+ }
+ }
+ else
+ {
+ // No, so just add this one to the mimetype
+ _characterEncoding=encoding;
+ if (_contentType!=null)
+ {
+ int i0=_contentType.indexOf(';');
+ if (i0<0)
+ {
+ _contentType=null;
+ if(_cachedMimeType!=null)
+ {
+ CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding);
+ if (content_type!=null)
+ {
+ _contentType=content_type.toString();
+ _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,content_type);
+ }
+ }
+
+ if (_contentType==null)
+ {
+ _contentType = _mimeType+";charset="+QuotedStringTokenizer.quote(_characterEncoding,";= ");
+ _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+ }
+ }
+ else
+ {
+ int i1=_contentType.indexOf("charset=",i0);
+ if (i1<0)
+ {
+ _contentType = _contentType+" charset="+QuotedStringTokenizer.quote(_characterEncoding,";= ");
+ }
+ else
+ {
+ int i8=i1+8;
+ int i2=_contentType.indexOf(" ",i8);
+ if (i2<0)
+ _contentType=_contentType.substring(0,i8)+QuotedStringTokenizer.quote(_characterEncoding,";= ");
+ else
+ _contentType=_contentType.substring(0,i8)+QuotedStringTokenizer.quote(_characterEncoding,";= ")+_contentType.substring(i2);
+ }
+ _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+ }
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletResponse#setContentLength(int)
+ */
+ public void setContentLength(int len)
+ {
+ // Protect from setting after committed as default handling
+ // of a servlet HEAD request ALWAYS sets _content length, even
+ // if the getHandling committed the response!
+ if (isCommitted() || _connection.isIncluding())
+ return;
+ _connection._generator.setContentLength(len);
+ if (len>=0)
+ {
+ _connection.getResponseFields().putLongField(HttpHeaders.CONTENT_LENGTH, len);
+ if (_connection._generator.isContentWritten())
+ {
+ if (_outputState==WRITER)
+ _writer.close();
+ else if (_outputState==STREAM)
+ {
+ try
+ {
+ getOutputStream().close();
+ }
+ catch(IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletResponse#setContentLength(int)
+ */
+ public void setLongContentLength(long len)
+ {
+ // Protect from setting after committed as default handling
+ // of a servlet HEAD request ALWAYS sets _content length, even
+ // if the getHandling committed the response!
+ if (isCommitted() || _connection.isIncluding())
+ return;
+ _connection._generator.setContentLength(len);
+ _connection.getResponseFields().putLongField(HttpHeaders.CONTENT_LENGTH, len);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletResponse#setContentType(java.lang.String)
+ */
+ public void setContentType(String contentType)
+ {
+ if (isCommitted() || _connection.isIncluding())
+ return;
+
+ // Yes this method is horribly complex.... but there are lots of special cases and
+ // as this method is called on every request, it is worth trying to save string creation.
+ //
+
+ if (contentType==null)
+ {
+ if (_locale==null)
+ _characterEncoding=null;
+ _mimeType=null;
+ _cachedMimeType=null;
+ _contentType=null;
+ _connection.getResponseFields().remove(HttpHeaders.CONTENT_TYPE_BUFFER);
+ }
+ else
+ {
+ // Look for encoding in contentType
+ int i0=contentType.indexOf(';');
+
+ if (i0>0)
+ {
+ // we have content type parameters
+
+ // Extract params off mimetype
+ _mimeType=contentType.substring(0,i0).trim();
+ _cachedMimeType=MimeTypes.CACHE.get(_mimeType);
+
+ // Look for charset
+ int i1=contentType.indexOf("charset=",i0+1);
+ if (i1>=0)
+ {
+ _explicitEncoding=true;
+ int i8=i1+8;
+ int i2 = contentType.indexOf(' ',i8);
+
+ if (_outputState==WRITER)
+ {
+ // strip the charset and ignore;
+ if ((i1==i0+1 && i2<0) || (i1==i0+2 && i2<0 && contentType.charAt(i0+1)==' '))
+ {
+ if (_cachedMimeType!=null)
+ {
+ CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding);
+ if (content_type!=null)
+ {
+ _contentType=content_type.toString();
+ _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,content_type);
+ }
+ else
+ {
+ _contentType=_mimeType+";charset="+_characterEncoding;
+ _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+ }
+ }
+ else
+ {
+ _contentType=_mimeType+";charset="+_characterEncoding;
+ _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+ }
+ }
+ else if (i2<0)
+ {
+ _contentType=contentType.substring(0,i1)+" charset="+QuotedStringTokenizer.quote(_characterEncoding,";= ");
+ _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+ }
+ else
+ {
+ _contentType=contentType.substring(0,i1)+contentType.substring(i2)+" charset="+QuotedStringTokenizer.quote(_characterEncoding,";= ");
+ _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+ }
+ }
+ else if ((i1==i0+1 && i2<0) || (i1==i0+2 && i2<0 && contentType.charAt(i0+1)==' '))
+ {
+ // The params are just the char encoding
+ _cachedMimeType=MimeTypes.CACHE.get(_mimeType);
+ _characterEncoding = QuotedStringTokenizer.unquote(contentType.substring(i8));
+
+ if (_cachedMimeType!=null)
+ {
+ CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding);
+ if (content_type!=null)
+ {
+ _contentType=content_type.toString();
+ _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,content_type);
+ }
+ else
+ {
+ _contentType=contentType;
+ _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+ }
+ }
+ else
+ {
+ _contentType=contentType;
+ _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+ }
+ }
+ else if (i2>0)
+ {
+ _characterEncoding = QuotedStringTokenizer.unquote(contentType.substring(i8,i2));
+ _contentType=contentType;
+ _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+ }
+ else
+ {
+ _characterEncoding = QuotedStringTokenizer.unquote(contentType.substring(i8));
+ _contentType=contentType;
+ _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+ }
+ }
+ else // No encoding in the params.
+ {
+ _cachedMimeType=null;
+ _contentType=_characterEncoding==null?contentType:contentType+" charset="+QuotedStringTokenizer.quote(_characterEncoding,";= ");
+ _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+ }
+ }
+ else // No params at all
+ {
+ _mimeType=contentType;
+ _cachedMimeType=MimeTypes.CACHE.get(_mimeType);
+
+ if (_characterEncoding!=null)
+ {
+ if (_cachedMimeType!=null)
+ {
+ CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding);
+ if (content_type!=null)
+ {
+ _contentType=content_type.toString();
+ _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,content_type);
+ }
+ else
+ {
+ _contentType=_mimeType+";charset="+QuotedStringTokenizer.quote(_characterEncoding,";= ");
+ _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+ }
+ }
+ else
+ {
+ _contentType=contentType+";charset="+QuotedStringTokenizer.quote(_characterEncoding,";= ");
+ _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+ }
+ }
+ else if (_cachedMimeType!=null)
+ {
+ _contentType=_cachedMimeType.toString();
+ _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_cachedMimeType);
+ }
+ else
+ {
+ _contentType=contentType;
+ _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletResponse#setBufferSize(int)
+ */
+ public void setBufferSize(int size)
+ {
+ if (isCommitted() || getContentCount()>0)
+ throw new IllegalStateException("Committed or content written");
+ _connection.getGenerator().increaseContentBufferSize(size);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletResponse#getBufferSize()
+ */
+ public int getBufferSize()
+ {
+ return _connection.getGenerator().getContentBufferSize();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletResponse#flushBuffer()
+ */
+ public void flushBuffer() throws IOException
+ {
+ _connection.flushResponse();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletResponse#reset()
+ */
+ public void reset()
+ {
+ fwdReset();
+ _status=200;
+ _reason=null;
+
+ HttpFields response_fields=_connection.getResponseFields();
+
+ response_fields.clear();
+ String connection=_connection.getRequestFields().getStringField(HttpHeaders.CONNECTION_BUFFER);
+ if (connection!=null)
+ {
+ String[] values = connection.split(",");
+ for (int i=0;values!=null && i<values.length;i++)
+ {
+ CachedBuffer cb = HttpHeaderValues.CACHE.get(values[0].trim());
+
+ if (cb!=null)
+ {
+ switch(cb.getOrdinal())
+ {
+ case HttpHeaderValues.CLOSE_ORDINAL:
+ response_fields.put(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.CLOSE_BUFFER);
+ break;
+
+ case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
+ if (HttpVersions.HTTP_1_0.equalsIgnoreCase(_connection.getRequest().getProtocol()))
+ response_fields.put(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.KEEP_ALIVE);
+ break;
+ case HttpHeaderValues.TE_ORDINAL:
+ response_fields.put(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.TE);
+ break;
+ }
+ }
+ }
+ }
+
+ if (_connection.getConnector().getServer().getSendDateHeader())
+ {
+ Request request=_connection.getRequest();
+ response_fields.put(HttpHeaders.DATE_BUFFER, request.getTimeStampBuffer(),request.getTimeStamp());
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletResponse#reset()
+ */
+ public void fwdReset()
+ {
+ resetBuffer();
+ _mimeType=null;
+ _cachedMimeType=null;
+ _contentType=null;
+ _characterEncoding=null;
+ _explicitEncoding=false;
+ _locale=null;
+ _outputState=NONE;
+ _writer=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletResponse#resetBuffer()
+ */
+ public void resetBuffer()
+ {
+ if (isCommitted())
+ throw new IllegalStateException("Committed");
+ _connection.getGenerator().resetBuffer();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletResponse#isCommitted()
+ */
+ public boolean isCommitted()
+ {
+ return _connection.isResponseCommitted();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletResponse#setLocale(java.util.Locale)
+ */
+ public void setLocale(Locale locale)
+ {
+ if (locale == null || isCommitted() ||_connection.isIncluding())
+ return;
+
+ _locale = locale;
+ _connection.getResponseFields().put(HttpHeaders.CONTENT_LANGUAGE_BUFFER,locale.toString().replace('_','-'));
+
+ if (_explicitEncoding || _outputState!=0 )
+ return;
+
+ if (_connection.getRequest().getContext()==null)
+ return;
+
+ String charset = _connection.getRequest().getContext().getContextHandler().getLocaleEncoding(locale);
+
+ if (charset!=null && charset.length()>0)
+ {
+ _characterEncoding=charset;
+
+ /* get current MIME type from Content-Type header */
+ String type=getContentType();
+ if (type!=null)
+ {
+ _characterEncoding=charset;
+ int semi=type.indexOf(';');
+ if (semi<0)
+ {
+ _mimeType=type;
+ _contentType= type += ";charset="+charset;
+ }
+ else
+ {
+ _mimeType=type.substring(0,semi);
+ _contentType= _mimeType += ";charset="+charset;
+ }
+
+ _cachedMimeType=MimeTypes.CACHE.get(_mimeType);
+ _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletResponse#getLocale()
+ */
+ public Locale getLocale()
+ {
+ if (_locale==null)
+ return Locale.getDefault();
+ return _locale;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The HTTP status code that has been set for this request. This will be <code>200<code>
+ * ({@link HttpServletResponse#SC_OK}), unless explicitly set through one of the <code>setStatus</code> methods.
+ */
+ public int getStatus()
+ {
+ return _status;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The reason associated with the current {@link #getStatus() status}. This will be <code>null</code>,
+ * unless one of the <code>setStatus</code> methods have been called.
+ */
+ public String getReason()
+ {
+ return _reason;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ */
+ public void complete()
+ throws IOException
+ {
+ _connection.completeResponse();
+ }
+
+ /* ------------------------------------------------------------- */
+ /**
+ * @return the number of bytes actually written in response body
+ */
+ public long getContentCount()
+ {
+ if (_connection==null || _connection.getGenerator()==null)
+ return -1;
+ return _connection.getGenerator().getContentWritten();
+ }
+
+ /* ------------------------------------------------------------ */
+ public HttpFields getHttpFields()
+ {
+ return _connection.getResponseFields();
+ }
+
+ /* ------------------------------------------------------------ */
+ public String toString()
+ {
+ return "HTTP/1.1 "+_status+" "+ (_reason==null?"":_reason) +System.getProperty("line.separator")+
+ _connection.getResponseFields().toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ private static class NullOutput extends ServletOutputStream
+ {
+ public void write(int b) throws IOException
+ {
+ }
+
+ public void print(String s) throws IOException
+ {
+ }
+
+ public void println(String s) throws IOException
+ {
+ }
+
+ public void write(byte[] b, int off, int len) throws IOException
+ {
+ }
+
+ }
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/RetryRequest.java b/jetty-server/src/main/java/org/eclipse/jetty/server/RetryRequest.java
new file mode 100644
index 0000000000..e3546c376e
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/RetryRequest.java
@@ -0,0 +1,29 @@
+// ========================================================================
+// Copyright (c) 2004-2009 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;
+
+/* ------------------------------------------------------------ */
+/** Retry Request
+ * This is thrown by a non-blocking {@link Continuation} such as
+ * {@link SuspendableSelectChannelEndPoint}. While it
+ * extends ThreadDeath, it does not actually stop the thread calling it.
+ * It extends ThreadDeath so as to be an Error that will not be caught
+ * by most frameworks.
+ *
+ *
+ *
+ */
+public class RetryRequest extends ThreadDeath
+{
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java
new file mode 100644
index 0000000000..2deaf4be13
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java
@@ -0,0 +1,779 @@
+// ========================================================================
+// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.server.bio.SocketConnector;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.server.nio.SelectChannelConnector;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.AttributesMap;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.MultiException;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.component.Container;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.util.thread.ThreadPool;
+
+/* ------------------------------------------------------------ */
+/** Jetty HTTP Servlet Server.
+ * This class is the main class for the Jetty HTTP Servlet server.
+ * It aggregates Connectors (HTTP request receivers) and request Handlers.
+ * The server is itself a handler and a ThreadPool. Connectors use the ThreadPool methods
+ * to run jobs that will eventually call the handle method.
+ *
+ * @org.apache.xbean.XBean description="Creates an embedded Jetty web server"
+ */
+public class Server extends HandlerWrapper implements Attributes
+{
+ private static ShutdownHookThread hookThread = new ShutdownHookThread();
+ private static String _version = (Server.class.getPackage()!=null && Server.class.getPackage().getImplementationVersion()!=null)
+ ?Server.class.getPackage().getImplementationVersion()
+ :"7.0.x";
+
+ private ThreadPool _threadPool;
+ private Connector[] _connectors;
+ private Container _container=new Container();
+ private SessionIdManager _sessionIdManager;
+ private boolean _sendServerVersion = true; //send Server: header
+ private boolean _sendDateHeader = false; //send Date: header
+ private AttributesMap _attributes = new AttributesMap();
+ private List<Object> _dependentBeans=new ArrayList<Object>();
+ private int _graceful=0;
+
+ /* ------------------------------------------------------------ */
+ public Server()
+ {
+ setServer(this);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Convenience constructor
+ * Creates server and a {@link SocketConnector} at the passed port.
+ */
+ public Server(int port)
+ {
+ setServer(this);
+
+ Connector connector=new SelectChannelConnector();
+ connector.setPort(port);
+ setConnectors(new Connector[]{connector});
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public static String getVersion()
+ {
+ return _version;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the container.
+ */
+ public Container getContainer()
+ {
+ return _container;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean getStopAtShutdown()
+ {
+ return hookThread.contains(this);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setStopAtShutdown(boolean stop)
+ {
+ if (stop)
+ hookThread.add(this);
+ else
+ hookThread.remove(this);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the connectors.
+ */
+ public Connector[] getConnectors()
+ {
+ return _connectors;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public void addConnector(Connector connector)
+ {
+ setConnectors((Connector[])LazyList.addToArray(getConnectors(), connector, Connector.class));
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Conveniance method which calls {@link #getConnectors()} and {@link #setConnectors(Connector[])} to
+ * remove a connector.
+ * @param connector The connector to remove.
+ */
+ public void removeConnector(Connector connector) {
+ setConnectors((Connector[])LazyList.removeFromArray (getConnectors(), connector));
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the connectors for this server.
+ * Each connector has this server set as it's ThreadPool and its Handler.
+ * @param connectors The connectors to set.
+ */
+ public void setConnectors(Connector[] connectors)
+ {
+ if (connectors!=null)
+ {
+ for (int i=0;i<connectors.length;i++)
+ connectors[i].setServer(this);
+ }
+
+ _container.update(this, _connectors, connectors, "connector");
+ _connectors = connectors;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the threadPool.
+ */
+ public ThreadPool getThreadPool()
+ {
+ return _threadPool;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param threadPool The threadPool to set.
+ */
+ public void setThreadPool(ThreadPool threadPool)
+ {
+ _container.update(this,_threadPool,threadPool, "threadpool",true);
+ _threadPool = threadPool;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void doStart() throws Exception
+ {
+ Log.info("jetty-"+_version);
+ HttpGenerator.setServerVersion(_version);
+ MultiException mex=new MultiException();
+
+ Iterator itor = _dependentBeans.iterator();
+ while (itor.hasNext())
+ {
+ try
+ {
+ Object o=itor.next();
+ if (o instanceof LifeCycle)
+ ((LifeCycle)o).start();
+ }
+ catch (Throwable e) {mex.add(e);}
+ }
+
+ if (_threadPool==null)
+ {
+ QueuedThreadPool tp=new QueuedThreadPool();
+ setThreadPool(tp);
+ }
+
+ if (_sessionIdManager!=null)
+ _sessionIdManager.start();
+
+ try
+ {
+ if (_threadPool instanceof LifeCycle)
+ ((LifeCycle)_threadPool).start();
+ }
+ catch(Throwable e) { mex.add(e);}
+
+ try
+ {
+ super.doStart();
+ }
+ catch(Throwable e)
+ {
+ Log.warn("Error starting handlers",e);
+ }
+
+ if (_connectors!=null)
+ {
+ for (int i=0;i<_connectors.length;i++)
+ {
+ try{_connectors[i].start();}
+ catch(Throwable e)
+ {
+ mex.add(e);
+ }
+ }
+ }
+ mex.ifExceptionThrow();
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void doStop() throws Exception
+ {
+ MultiException mex=new MultiException();
+
+ if (_graceful>0)
+ {
+ if (_connectors!=null)
+ {
+ for (int i=_connectors.length;i-->0;)
+ {
+ Log.info("Graceful shutdown {}",_connectors[i]);
+ try{_connectors[i].close();}catch(Throwable e){mex.add(e);}
+ }
+ }
+
+ Handler[] contexts = getChildHandlersByClass(Graceful.class);
+ for (int c=0;c<contexts.length;c++)
+ {
+ Graceful context=(Graceful)contexts[c];
+ Log.info("Graceful shutdown {}",context);
+ context.setShutdown(true);
+ }
+ Thread.sleep(_graceful);
+ }
+
+ if (_connectors!=null)
+ {
+ for (int i=_connectors.length;i-->0;)
+ try{_connectors[i].stop();}catch(Throwable e){mex.add(e);}
+ }
+
+ try {super.doStop(); } catch(Throwable e) { mex.add(e);}
+
+ if (_sessionIdManager!=null)
+ _sessionIdManager.stop();
+
+ try
+ {
+ if (_threadPool instanceof LifeCycle)
+ ((LifeCycle)_threadPool).stop();
+ }
+ catch(Throwable e){mex.add(e);}
+
+ if (!_dependentBeans.isEmpty())
+ {
+ ListIterator itor = _dependentBeans.listIterator(_dependentBeans.size());
+ while (itor.hasPrevious())
+ {
+ try
+ {
+ Object o =itor.previous();
+ if (o instanceof LifeCycle)
+ ((LifeCycle)o).stop();
+ }
+ catch (Throwable e) {mex.add(e);}
+ }
+ }
+
+ mex.ifExceptionThrow();
+ }
+
+ /* ------------------------------------------------------------ */
+ /* Handle a request from a connection.
+ * Called to handle a request on the connection when either the header has been received,
+ * or after the entire request has been received (for short requests of known length), or
+ * on the dispatch of an async request.
+ */
+ public void handle(HttpConnection connection) throws IOException, ServletException
+ {
+ final String target=connection.getRequest().getPathInfo();
+ final HttpServletRequest request=connection.getRequest();
+ final HttpServletResponse response=connection.getResponse();
+
+ if (Log.isDebugEnabled())
+ {
+ Log.debug("REQUEST "+target+" on "+connection);
+ handle(target, request, response);
+ Log.debug("RESPONSE "+target+" "+connection.getResponse().getStatus());
+ }
+ else
+ handle(target, request, response);
+ }
+
+ /* ------------------------------------------------------------ */
+ /* Handle a request from a connection.
+ * Called to handle a request on the connection when either the header has been received,
+ * or after the entire request has been received (for short requests of known length), or
+ * on the dispatch of an async request.
+ */
+ public void handleAsync(HttpConnection connection) throws IOException, ServletException
+ {
+ final AsyncRequest async = connection.getRequest().getAsyncRequest();
+ final AsyncRequest.AsyncEventState state = async.getAsyncEventState();
+
+ final Request base_request=connection.getRequest();
+ final String path=state.getPath();
+ if (path!=null)
+ {
+ // this is a dispatch with a path
+ base_request.setAttribute(AsyncContext.ASYNC_REQUEST_URI,base_request.getRequestURI());
+ base_request.setAttribute(AsyncContext.ASYNC_QUERY_STRING,base_request.getQueryString());
+
+ base_request.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,state.getSuspendedContext().getContextPath());
+
+ final String contextPath=state.getServletContext().getContextPath();
+ HttpURI uri = new HttpURI(URIUtil.addPaths(contextPath,path));
+ base_request.setUri(uri);
+ base_request.setRequestURI(null);
+ base_request.setPathInfo(base_request.getRequestURI());
+ base_request.setQueryString(uri.getQuery());
+ }
+
+ final String target=base_request.getPathInfo();
+ final HttpServletRequest request=(HttpServletRequest)async.getRequest();
+ final HttpServletResponse response=(HttpServletResponse)async.getResponse();
+
+ if (Log.isDebugEnabled())
+ {
+ Log.debug("REQUEST "+target+" on "+connection);
+ handle(target, request, response);
+ Log.debug("RESPONSE "+target+" "+connection.getResponse().getStatus());
+ }
+ else
+ handle(target, request, response);
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ public void join() throws InterruptedException
+ {
+ getThreadPool().join();
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the sessionIdManager.
+ */
+ public SessionIdManager getSessionIdManager()
+ {
+ return _sessionIdManager;
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /**
+ * @param sessionIdManager The sessionIdManager to set.
+ */
+ public void setSessionIdManager(SessionIdManager sessionIdManager)
+ {
+ _container.update(this,_sessionIdManager,sessionIdManager, "sessionIdManager",true);
+ _sessionIdManager = sessionIdManager;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setSendServerVersion (boolean sendServerVersion)
+ {
+ _sendServerVersion = sendServerVersion;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean getSendServerVersion()
+ {
+ return _sendServerVersion;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param sendDateHeader
+ */
+ public void setSendDateHeader(boolean sendDateHeader)
+ {
+ _sendDateHeader = sendDateHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean getSendDateHeader()
+ {
+ return _sendDateHeader;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Add a LifeCycle object to be started/stopped
+ * along with the Server.
+ * @deprecated Use {@link #addBean(LifeCycle)}
+ * @param c
+ */
+ public void addLifeCycle (LifeCycle c)
+ {
+ addBean(c);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Add an associated bean.
+ * The bean will be added to the servers {@link Container}
+ * and if it is a {@link LifeCycle} instance, it will be
+ * started/stopped along with the Server.
+ * @param c
+ */
+ public void addBean(Object o)
+ {
+ if (o == null)
+ return;
+
+ if (!_dependentBeans.contains(o))
+ {
+ _dependentBeans.add(o);
+ _container.addBean(o);
+ }
+
+ try
+ {
+ if (isStarted() && o instanceof LifeCycle)
+ ((LifeCycle)o).start();
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException (e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get dependent beans of a specific class
+ * @see #addBean(Object)
+ * @param clazz
+ * @return List of beans.
+ */
+ public <T> List<T> getBeans(Class<T> clazz)
+ {
+ ArrayList<T> beans = new ArrayList<T>();
+ Iterator<?> iter = _dependentBeans.iterator();
+ while (iter.hasNext())
+ {
+ Object o = iter.next();
+ if (clazz.isInstance(o))
+ beans.add((T)o);
+ }
+ return beans;
+ }
+
+ /**
+ * Remove a LifeCycle object to be started/stopped
+ * along with the Server
+ * @deprecated Use {@link #removeBean(Object)}
+ */
+ public void removeLifeCycle (LifeCycle c)
+ {
+ removeBean(c);
+ }
+
+ /**
+ * Remove an associated bean.
+ */
+ public void removeBean (Object o)
+ {
+ if (o == null)
+ return;
+ _dependentBeans.remove(o);
+ _container.removeBean(o);
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /**
+ * ShutdownHook thread for stopping all servers.
+ *
+ * Thread is hooked first time list of servers is changed.
+ */
+ private static class ShutdownHookThread extends Thread
+ {
+ private boolean hooked = false;
+ private ArrayList servers = new ArrayList();
+
+ /**
+ * Hooks this thread for shutdown.
+ *
+ * @see java.lang.Runtime#addShutdownHook(java.lang.Thread)
+ */
+ private void createShutdownHook()
+ {
+ if (!Boolean.getBoolean("JETTY_NO_SHUTDOWN_HOOK") && !hooked)
+ {
+ try
+ {
+ Method shutdownHook = java.lang.Runtime.class.getMethod("addShutdownHook", new Class[]
+ { java.lang.Thread.class});
+ shutdownHook.invoke(Runtime.getRuntime(), new Object[]
+ { this});
+ this.hooked = true;
+ }
+ catch (Exception e)
+ {
+ if (Log.isDebugEnabled())
+ Log.debug("No shutdown hook in JVM ", e);
+ }
+ }
+ }
+
+ /**
+ * Add Server to servers list.
+ */
+ public boolean add(Server server)
+ {
+ createShutdownHook();
+ return this.servers.add(server);
+ }
+
+ /**
+ * Contains Server in servers list?
+ */
+ public boolean contains(Server server)
+ {
+ return this.servers.contains(server);
+ }
+
+ /**
+ * Append all Servers from Collection
+ */
+ public boolean addAll(Collection c)
+ {
+ createShutdownHook();
+ return this.servers.addAll(c);
+ }
+
+ /**
+ * Clear list of Servers.
+ */
+ public void clear()
+ {
+ createShutdownHook();
+ this.servers.clear();
+ }
+
+ /**
+ * Remove Server from list.
+ */
+ public boolean remove(Server server)
+ {
+ createShutdownHook();
+ return this.servers.remove(server);
+ }
+
+ /**
+ * Remove all Servers in Collection from list.
+ */
+ public boolean removeAll(Collection c)
+ {
+ createShutdownHook();
+ return this.servers.removeAll(c);
+ }
+
+ /**
+ * Stop all Servers in list.
+ */
+ public void run()
+ {
+ setName("Shutdown");
+ Log.info("Shutdown hook executing");
+ Iterator it = servers.iterator();
+ while (it.hasNext())
+ {
+ Server svr = (Server) it.next();
+ if (svr == null)
+ continue;
+ try
+ {
+ svr.stop();
+ }
+ catch (Exception e)
+ {
+ Log.warn(e);
+ }
+ Log.info("Shutdown hook complete");
+
+ // Try to avoid JVM crash
+ try
+ {
+ Thread.sleep(1000);
+ }
+ catch (Exception e)
+ {
+ Log.warn(e);
+ }
+ }
+ }
+ }
+
+
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ */
+ public void addHandler(Handler handler)
+ {
+ if (getHandler() == null)
+ setHandler(handler);
+ else if (getHandler() instanceof HandlerCollection)
+ ((HandlerCollection)getHandler()).addHandler(handler);
+ else
+ {
+ HandlerCollection collection=new HandlerCollection();
+ collection.setHandlers(new Handler[]{getHandler(),handler});
+ setHandler(collection);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ */
+ public void removeHandler(Handler handler)
+ {
+ if (getHandler() instanceof HandlerCollection)
+ ((HandlerCollection)getHandler()).removeHandler(handler);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ */
+ public Handler[] getHandlers()
+ {
+ if (getHandler() instanceof HandlerCollection)
+ return ((HandlerCollection)getHandler()).getHandlers();
+
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ */
+ public void setHandlers(Handler[] handlers)
+ {
+ HandlerCollection collection;
+ if (getHandler() instanceof HandlerCollection)
+ collection=(HandlerCollection)getHandler();
+ else
+ {
+ collection=new HandlerCollection();
+ setHandler(collection);
+ }
+
+ collection.setHandlers(handlers);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.util.AttributesMap#clearAttributes()
+ */
+ public void clearAttributes()
+ {
+ _attributes.clearAttributes();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.util.AttributesMap#getAttribute(java.lang.String)
+ */
+ public Object getAttribute(String name)
+ {
+ return _attributes.getAttribute(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.util.AttributesMap#getAttributeNames()
+ */
+ public Enumeration getAttributeNames()
+ {
+ return AttributesMap.getAttributeNamesCopy(_attributes);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.util.AttributesMap#removeAttribute(java.lang.String)
+ */
+ public void removeAttribute(String name)
+ {
+ _attributes.removeAttribute(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.util.AttributesMap#setAttribute(java.lang.String, java.lang.Object)
+ */
+ public void setAttribute(String name, Object attribute)
+ {
+ _attributes.setAttribute(name, attribute);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the graceful
+ */
+ public int getGracefulShutdown()
+ {
+ return _graceful;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set graceful shutdown timeout. If set, the {@link #doStop()} method will not immediately stop the
+ * server. Instead, all {@link Connector}s will be closed so that new connections will not be accepted
+ * and all handlers that implement {@link Graceful} will be put into the shutdown mode so that no new requests
+ * will be accepted, but existing requests can complete. The server will then wait the configured timeout
+ * before stopping.
+ * @param timeoutMS the milliseconds to wait for existing request to complete before stopping the server.
+ *
+ */
+ public void setGracefulShutdown(int timeoutMS)
+ {
+ _graceful=timeoutMS;
+ }
+
+ public String toString()
+ {
+ return this.getClass().getName()+"@"+Integer.toHexString(hashCode());
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /* A handler that can be gracefully shutdown.
+ * Called by doStop if a {@link #setGracefulShutdown} period is set.
+ * TODO move this somewhere better
+ */
+ public interface Graceful extends Handler
+ {
+ public void setShutdown(boolean shutdown);
+ }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Servlet3Continuation.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Servlet3Continuation.java
new file mode 100644
index 0000000000..08703c229d
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Servlet3Continuation.java
@@ -0,0 +1,115 @@
+// ========================================================================
+// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+
+import org.eclipse.jetty.util.ajax.Continuation;
+
+public class Servlet3Continuation implements Continuation, AsyncListener
+{
+ AsyncContext _asyncContext;
+ Request _request;
+ Object _object;
+ RetryRequest _retry;
+ boolean _resumed=false;
+ boolean _timeout=false;
+
+ Servlet3Continuation(Request request)
+ {
+ _request=request;
+ }
+
+ public Object getObject()
+ {
+ return _object;
+ }
+
+ public boolean isExpired()
+ {
+ return _asyncContext!=null && _timeout;
+ }
+
+ public boolean isNew()
+ {
+ return _retry==null;
+ }
+
+ public boolean isPending()
+ {
+ return _asyncContext!=null && (_request.getAsyncRequest().isSuspended() || !_request.getAsyncRequest().isInitial());
+ }
+
+ public boolean isResumed()
+ {
+ return _asyncContext!=null && _resumed;
+ }
+
+ public void reset()
+ {
+ _resumed=false;
+ _timeout=false;
+ }
+
+ public void resume()
+ {
+ if (_asyncContext==null)
+ throw new IllegalStateException();
+ _resumed=true;
+ _asyncContext.dispatch();
+ }
+
+ public void setMutex(Object mutex)
+ {
+ }
+
+ public void setObject(Object o)
+ {
+ _object=o;
+ }
+
+ public boolean suspend(long timeout)
+ {
+ _asyncContext=_request.startAsync();;
+ if (!_request.getAsyncRequest().isInitial()||_resumed||_timeout)
+ {
+ _resumed=false;
+ _timeout=false;
+ return _resumed;
+ }
+
+ _request.setAsyncTimeout(timeout);
+ _request.addAsyncListener(this);
+ if (_retry==null)
+ _retry=new RetryRequest();
+ throw _retry;
+
+ }
+
+ public void onComplete(AsyncEvent event) throws IOException
+ {
+
+ }
+
+ public void onTimeout(AsyncEvent event) throws IOException
+ {
+ _timeout=true;
+ _request.getAsyncRequest().dispatch();
+ }
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/SessionIdManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/SessionIdManager.java
new file mode 100644
index 0000000000..1d76581822
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/SessionIdManager.java
@@ -0,0 +1,83 @@
+// ========================================================================
+// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+
+package org.eclipse.jetty.server;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/** Session ID Manager.
+ * Manages session IDs across multiple contexts.
+ *
+ *
+ */
+/* ------------------------------------------------------------ */
+/**
+ *
+ *
+ */
+public interface SessionIdManager extends LifeCycle
+{
+ /**
+ * @param id The session ID without any cluster node extension
+ * @return True if the session ID is in use by at least one context.
+ */
+ public boolean idInUse(String id);
+
+ /**
+ * Add a session to the list of known sessions for a given ID.
+ * @param session The session
+ */
+ public void addSession(HttpSession session);
+
+ /**
+ * Remove session from the list of known sessions for a given ID.
+ * @param session
+ */
+ public void removeSession(HttpSession session);
+
+ /**
+ * Call {@link HttpSession#invalidate()} on all known sessions for the given id.
+ * @param id The session ID without any cluster node extension
+ */
+ public void invalidateAll(String id);
+
+ /**
+ * @param request
+ * @param created
+ * @return
+ */
+ public String newSessionId(HttpServletRequest request,long created);
+
+ public String getWorkerName();
+
+
+ /* ------------------------------------------------------------ */
+ /** Get a cluster ID from a node ID.
+ * Strip node identifier from a located session ID.
+ * @param nodeId
+ * @return
+ */
+ public String getClusterId(String nodeId);
+
+ /* ------------------------------------------------------------ */
+ /** Get a node ID from a cluster ID and a request
+ * @param clusterId The ID of the session
+ * @param request The request that for the session (or null)
+ * @return The session ID qualified with the node ID.
+ */
+ public String getNodeId(String clusterId,HttpServletRequest request);
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/SessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/SessionManager.java
new file mode 100644
index 0000000000..6546bf182d
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/SessionManager.java
@@ -0,0 +1,327 @@
+// ========================================================================
+// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+
+package org.eclipse.jetty.server;
+
+import java.util.EventListener;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/* --------------------------------------------------------------------- */
+/**
+ * Session Manager.
+ * The API required to manage sessions for a servlet context.
+ *
+ *
+ */
+public interface SessionManager extends LifeCycle
+{
+ /* ------------------------------------------------------------ */
+ /**
+ * Session cookie name.
+ * Defaults to JSESSIONID, but can be set with the
+ * org.eclipse.jetty.servlet.SessionCookie context init parameter.
+ */
+ public final static String __SessionCookieProperty = "org.eclipse.jetty.servlet.SessionCookie";
+ public final static String __DefaultSessionCookie = "JSESSIONID";
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Session id path parameter name.
+ * Defaults to jsessionid, but can be set with the
+ * org.eclipse.jetty.servlet.SessionIdPathParameterName context init parameter.
+ * If set to null or "none" no URL rewriting will be done.
+ */
+ public final static String __SessionIdPathParameterNameProperty = "org.eclipse.jetty.servlet.SessionIdPathParameterName";
+ public final static String __DefaultSessionIdPathParameterName = "jsessionid";
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Session Domain.
+ * If this property is set as a ServletContext InitParam, then it is
+ * used as the domain for session cookies. If it is not set, then
+ * no domain is specified for the session cookie.
+ */
+ public final static String __SessionDomainProperty = "org.eclipse.jetty.servlet.SessionDomain";
+ public final static String __DefaultSessionDomain = null;
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Session Path.
+ * If this property is set as a ServletContext InitParam, then it is
+ * used as the path for the session cookie. If it is not set, then
+ * the context path is used as the path for the cookie.
+ */
+ public final static String __SessionPathProperty = "org.eclipse.jetty.servlet.SessionPath";
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Session Max Age.
+ * If this property is set as a ServletContext InitParam, then it is
+ * used as the max age for the session cookie. If it is not set, then
+ * a max age of -1 is used.
+ */
+ public final static String __MaxAgeProperty = "org.eclipse.jetty.servlet.MaxAge";
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns the <code>HttpSession</code> with the given session id
+ *
+ * @param id the session id
+ * @return the <code>HttpSession</code> with the corresponding id or null if no session with the given id exists
+ */
+ public HttpSession getHttpSession(String id);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Creates a new <code>HttpSession</code>.
+ *
+ * @param request the HttpServletRequest containing the requested session id
+ * @return the new <code>HttpSession</code>
+ */
+ public HttpSession newHttpSession(HttpServletRequest request);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if session cookies should be secure
+ */
+ public boolean getSecureCookies();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if session cookies should be HTTP-only (Microsoft extension)
+ * @see Cookie#isHttpOnly()
+ */
+ public boolean getHttpOnly();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the max period of inactivity, after which the session is invalidated, in seconds.
+ * @see #setMaxInactiveInterval(int)
+ */
+ public int getMaxInactiveInterval();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Sets the max period of inactivity, after which the session is invalidated, in seconds.
+ *
+ * @param seconds the max inactivity period, in seconds.
+ * @see #getMaxInactiveInterval()
+ */
+ public void setMaxInactiveInterval(int seconds);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Sets the {@link SessionHandler}.
+ *
+ * @param handler the <code>SessionHandler</code> object
+ */
+ public void setSessionHandler(SessionHandler handler);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Adds an event listener for session-related events.
+ *
+ * @param listener the session event listener to add
+ * Individual SessionManagers implementations may accept arbitrary listener types,
+ * but they are expected to at least handle HttpSessionActivationListener,
+ * HttpSessionAttributeListener, HttpSessionBindingListener and HttpSessionListener.
+ * @see #removeEventListener(EventListener)
+ */
+ public void addEventListener(EventListener listener);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Removes an event listener for for session-related events.
+ *
+ * @param listener the session event listener to remove
+ * @see #addEventListener(EventListener)
+ */
+ public void removeEventListener(EventListener listener);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Removes all event listeners for session-related events.
+ *
+ * @see #removeEventListener(EventListener)
+ */
+ public void clearEventListeners();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Gets a Cookie for a session.
+ *
+ * @param session the session to which the cookie should refer.
+ * @param contextPath the context to which the cookie should be linked.
+ * The client will only send the cookie value when requesting resources under this path.
+ * @param requestIsSecure whether the client is accessing the server over a secure protocol (i.e. HTTPS).
+ * @return if this <code>SessionManager</code> uses cookies, then this method will return a new
+ * {@link Cookie cookie object} that should be set on the client in order to link future HTTP requests
+ * with the <code>session</code>. If cookies are not in use, this method returns <code>null</code>.
+ */
+ public Cookie getSessionCookie(HttpSession session, String contextPath, boolean requestIsSecure);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the cross context session id manager.
+ * @see #setIdManager(SessionIdManager)
+ */
+ public SessionIdManager getIdManager();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the cross context session id manager.
+ * @deprecated use {@link #getIdManager()}
+ */
+ public SessionIdManager getMetaManager();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Sets the cross context session id manager
+ *
+ * @param idManager the cross context session id manager.
+ * @see #getIdManager()
+ */
+ public void setIdManager(SessionIdManager idManager);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param session the session to test for validity
+ * @return whether the given session is valid, that is, it has not been invalidated.
+ */
+ public boolean isValid(HttpSession session);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param session the session object
+ * @return the unique id of the session within the cluster, extended with an optional node id.
+ * @see #getClusterId(HttpSession)
+ */
+ public String getNodeId(HttpSession session);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param session the session object
+ * @return the unique id of the session within the cluster (without a node id extension)
+ * @see #getNodeId(HttpSession)
+ */
+ public String getClusterId(HttpSession session);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Called by the {@link SessionHandler} when a session is first accessed by a request.
+ *
+ * @param session the session object
+ * @param secure whether the request is secure or not
+ * @return the session cookie. If not null, this cookie should be set on the response to either migrate
+ * the session or to refresh a session cookie that may expire.
+ * @see #complete(HttpSession)
+ */
+ public Cookie access(HttpSession session, boolean secure);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Called by the {@link SessionHandler} when a session is last accessed by a request.
+ *
+ * @param session the session object
+ * @see #access(HttpSession, boolean)
+ */
+ public void complete(HttpSession session);
+
+ /**
+ * Sets the session cookie name.
+ * @param cookieName the session cookie name
+ * @see #getSessionCookie()
+ */
+ public void setSessionCookie(String cookieName);
+
+ /**
+ * @return the session cookie name, by default "JSESSIONID".
+ * @see #setSessionCookie(String)
+ */
+ public String getSessionCookie();
+
+ /**
+ * Sets the session id URL path parameter name.
+ *
+ * @param parameterName the URL path parameter name for session id URL rewriting (null or "none" for no rewriting).
+ * @see #getSessionIdPathParameterName()
+ * @see #getSessionIdPathParameterNamePrefix()
+ */
+ public void setSessionIdPathParameterName(String parameterName);
+
+ /**
+ * @return the URL path parameter name for session id URL rewriting, by default "jsessionid".
+ * @see #setSessionIdPathParameterName(String)
+ */
+ public String getSessionIdPathParameterName();
+
+ /**
+ * @return a formatted version of {@link #getSessionIdPathParameterName()}, by default
+ * ";" + sessionIdParameterName + "=", for easier lookup in URL strings.
+ * @see #getSessionIdPathParameterName()
+ */
+ public String getSessionIdPathParameterNamePrefix();
+
+ /**
+ * Sets the domain to set on the session cookie
+ * @param domain the domain to set on the session cookie
+ * @see #getSessionDomain()
+ */
+ public void setSessionDomain(String domain);
+
+ /**
+ * @return the domain to set on the session cookie
+ * @see #setSessionDomain(String)
+ */
+ public String getSessionDomain();
+
+ /**
+ * Sets the path to set on the session cookie
+ * @param path the path to set on the session cookie
+ * @see #getSessionPath()
+ */
+ public void setSessionPath(String path);
+
+ /**
+ * @return the path to set on the session cookie
+ * @see #setSessionPath(String)
+ */
+ public String getSessionPath();
+
+ /**
+ * Sets the max age to set on the session cookie, in seconds
+ * @param maxCookieAge the max age to set on the session cookie, in seconds
+ * @see #getMaxCookieAge()
+ */
+ public void setMaxCookieAge(int maxCookieAge);
+
+ /**
+ * @return the max age to set on the session cookie, in seconds
+ * @see #setMaxCookieAge(int)
+ */
+ public int getMaxCookieAge();
+
+ /**
+ * @return whether the session management is handled via cookies.
+ */
+ public boolean isUsingCookies();
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/UserIdentity.java b/jetty-server/src/main/java/org/eclipse/jetty/server/UserIdentity.java
new file mode 100644
index 0000000000..314c6c06fd
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/UserIdentity.java
@@ -0,0 +1,136 @@
+// ========================================================================
+// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+
+package org.eclipse.jetty.server;
+import java.security.Principal;
+import java.util.Map;
+
+import javax.security.auth.Subject;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/* ------------------------------------------------------------ */
+/** User object that encapsulates user identity and operations such as run-as-role actions,
+ * checking isUserInRole and getUserPrincipal.
+ *
+ * Implementations of UserIdentity should be immutable so that they may be
+ * cached by Authenticators and LoginServices.
+ *
+ */
+public interface UserIdentity
+{
+ final static String[] NO_ROLES = new String[]{};
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The user subject
+ */
+ Subject getSubject();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The user principal
+ */
+ Principal getUserPrincipal();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The users roles
+ */
+ String[] getRoles();
+
+ /* ------------------------------------------------------------ */
+ /** Check if the user is in a role.
+ * This call is used to satisfy authorization calls from
+ * container code which will be using translated role names.
+ * @param role A role name.
+ * @return True if the user can act in that role.
+ */
+ boolean isUserInRole(String role);
+
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /**
+ * A UserIdentity Scope.
+ * A scope is the environment in which a User Identity is to
+ * be interpreted. Typically it is set by the target servlet of
+ * a request.
+ * @see org.eclipse.jetty.servlet.ServletHolder
+ */
+ interface Scope
+ {
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The context path that the identity is being considered within
+ */
+ String getContextPath();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The name of the identity context. Typically this is the servlet name.
+ */
+ String getName();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The name of a runAs entity. Typically this is a runAs role applied to a servlet.
+ */
+ String getRunAsRole();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return A map of role reference names that converts from names used by application code
+ * to names used by the context deployment.
+ */
+ Map<String,String> getRoleRefMap();
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public interface UnauthticatedUserIdentity extends UserIdentity
+ {
+ UserIdentity login(ServletRequest request, ServletResponse response);
+ UserIdentity login(String username, String password);
+ };
+
+ public static final UserIdentity UNAUTHENTICATED_IDENTITY = new UserIdentity()
+ {
+ public Subject getSubject()
+ {
+ return null;
+ }
+
+ public Principal getUserPrincipal()
+ {
+ return null;
+ }
+
+ public String[] getRoles()
+ {
+ return NO_ROLES;
+ }
+
+ public boolean isUserInRole(String role)
+ {
+ return false;
+ }
+
+ public String toString()
+ {
+ return "UNAUTHENTICATED";
+ }
+ };
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/bio/SocketConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/bio/SocketConnector.java
new file mode 100644
index 0000000000..f89a08c020
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/bio/SocketConnector.java
@@ -0,0 +1,264 @@
+// ========================================================================
+// Copyright (c) 2003-2009 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.bio;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.io.HttpException;
+import org.eclipse.jetty.io.bio.SocketEndPoint;
+import org.eclipse.jetty.server.AbstractConnector;
+import org.eclipse.jetty.server.HttpConnection;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.log.Log;
+
+
+/* ------------------------------------------------------------------------------- */
+/** Socket Connector.
+ * This connector implements a traditional blocking IO and threading model.
+ * Normal JRE sockets are used and a thread is allocated per connection.
+ * Buffers are managed so that large buffers are only allocated to active connections.
+ *
+ * This Connector should only be used if NIO is not available.
+ *
+ * @org.apache.xbean.XBean element="bioConnector" description="Creates a BIO based socket connector"
+ *
+ *
+ */
+public class SocketConnector extends AbstractConnector
+{
+ protected ServerSocket _serverSocket;
+ protected Set _connections;
+
+ /* ------------------------------------------------------------ */
+ /** Constructor.
+ *
+ */
+ public SocketConnector()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public Object getConnection()
+ {
+ return _serverSocket;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void open() throws IOException
+ {
+ // Create a new server socket and set to non blocking mode
+ if (_serverSocket==null || _serverSocket.isClosed())
+ _serverSocket= newServerSocket(getHost(),getPort(),getAcceptQueueSize());
+ _serverSocket.setReuseAddress(getReuseAddress());
+ }
+
+ /* ------------------------------------------------------------ */
+ protected ServerSocket newServerSocket(String host, int port,int backlog) throws IOException
+ {
+ ServerSocket ss= host==null?
+ new ServerSocket(port,backlog):
+ new ServerSocket(port,backlog,InetAddress.getByName(host));
+
+ return ss;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void close() throws IOException
+ {
+ if (_serverSocket!=null)
+ _serverSocket.close();
+ _serverSocket=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void accept(int acceptorID)
+ throws IOException, InterruptedException
+ {
+ Socket socket = _serverSocket.accept();
+ configure(socket);
+
+ Connection connection=new Connection(socket);
+ connection.dispatch();
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /**
+ * Allows subclass to override Conection if required.
+ */
+ protected HttpConnection newHttpConnection(EndPoint endpoint)
+ {
+ return new HttpConnection(this, endpoint, getServer());
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public Buffer newBuffer(int size)
+ {
+ return new ByteArrayBuffer(size);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public void customize(EndPoint endpoint, Request request)
+ throws IOException
+ {
+ Connection connection = (Connection)endpoint;
+ if (connection._sotimeout!=_maxIdleTime)
+ {
+ connection._sotimeout=_maxIdleTime;
+ ((Socket)endpoint.getTransport()).setSoTimeout(_maxIdleTime);
+ }
+
+ super.customize(endpoint, request);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public int getLocalPort()
+ {
+ if (_serverSocket==null || _serverSocket.isClosed())
+ return -1;
+ return _serverSocket.getLocalPort();
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ protected void doStart() throws Exception
+ {
+ _connections=new HashSet();
+ super.doStart();
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ Set set=null;
+
+ synchronized(_connections)
+ {
+ set= new HashSet(_connections);
+ }
+
+ Iterator iter=set.iterator();
+ while(iter.hasNext())
+ {
+ Connection connection = (Connection)iter.next();
+ connection.close();
+ }
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /* ------------------------------------------------------------------------------- */
+ /* ------------------------------------------------------------------------------- */
+ protected class Connection extends SocketEndPoint implements Runnable
+ {
+ boolean _dispatched=false;
+ HttpConnection _connection;
+ int _sotimeout;
+ protected Socket _socket;
+
+ public Connection(Socket socket) throws IOException
+ {
+ super(socket);
+ _connection = newHttpConnection(this);
+ _sotimeout=socket.getSoTimeout();
+ _socket=socket;
+ }
+
+ public void dispatch() throws InterruptedException, IOException
+ {
+ if (getThreadPool()==null || !getThreadPool().dispatch(this))
+ {
+ Log.warn("dispatch failed for {}",_connection);
+ close();
+ }
+ }
+
+ public int fill(Buffer buffer) throws IOException
+ {
+ int l = super.fill(buffer);
+ if (l<0)
+ close();
+ return l;
+ }
+
+ public void close() throws IOException
+ {
+ _connection.getRequest().getAsyncRequest().cancel();
+ super.close();
+ }
+
+ public void run()
+ {
+ try
+ {
+ connectionOpened(_connection);
+ synchronized(_connections)
+ {
+ _connections.add(this);
+ }
+
+ while (isStarted() && !isClosed())
+ {
+ if (_connection.isIdle())
+ {
+ if (getServer().getThreadPool().isLowOnThreads())
+ {
+ int lrmit = getLowResourceMaxIdleTime();
+ if (lrmit>=0 && _sotimeout!= lrmit)
+ {
+ _sotimeout=lrmit;
+ _socket.setSoTimeout(_sotimeout);
+ }
+ }
+ }
+ _connection.handle();
+ }
+ }
+ catch (EofException e)
+ {
+ Log.debug("EOF", e);
+ try{close();}
+ catch(IOException e2){Log.ignore(e2);}
+ }
+ catch (HttpException e)
+ {
+ Log.debug("BAD", e);
+ try{close();}
+ catch(IOException e2){Log.ignore(e2);}
+ }
+ catch(Exception e)
+ {
+ Log.warn("handle failed?",e);
+ try{close();}
+ catch(IOException e2){Log.ignore(e2);}
+ }
+ finally
+ {
+ connectionClosed(_connection);
+ synchronized(_connections)
+ {
+ _connections.remove(this);
+ }
+ }
+ }
+ }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandler.java
new file mode 100644
index 0000000000..a09c5989ec
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandler.java
@@ -0,0 +1,97 @@
+// ========================================================================
+// Copyright (c) 2004-2009 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.handler;
+
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+
+
+/* ------------------------------------------------------------ */
+/** AbstractHandler.
+ *
+ *
+ */
+public abstract class AbstractHandler extends AbstractLifeCycle implements Handler
+{
+ protected String _string;
+ private Server _server;
+
+ /* ------------------------------------------------------------ */
+ /**
+ *
+ */
+ public AbstractHandler()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.thread.LifeCycle#start()
+ */
+ protected void doStart() throws Exception
+ {
+ Log.debug("starting {}",this);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.thread.LifeCycle#stop()
+ */
+ protected void doStop() throws Exception
+ {
+ Log.debug("stopping {}",this);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String toString()
+ {
+ if (_string==null)
+ {
+ _string=super.toString();
+ _string=_string.substring(_string.lastIndexOf('.')+1);
+ }
+ return _string;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setServer(Server server)
+ {
+ Server old_server=_server;
+ if (old_server!=null && old_server!=server)
+ old_server.getContainer().removeBean(this);
+ _server=server;
+ if (_server!=null && _server!=old_server)
+ _server.getContainer().addBean(this);
+ }
+
+ /* ------------------------------------------------------------ */
+ public Server getServer()
+ {
+ return _server;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public void destroy()
+ {
+ if (!isStopped())
+ throw new IllegalStateException("!STOPPED");
+ if (_server!=null)
+ _server.getContainer().removeBean(this);
+ }
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java
new file mode 100644
index 0000000000..b5eeb780bb
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java
@@ -0,0 +1,88 @@
+// ========================================================================
+// Copyright (c) 2004-2009 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.handler;
+
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HandlerContainer;
+import org.eclipse.jetty.util.LazyList;
+
+
+/* ------------------------------------------------------------ */
+/** Abstract Handler Container.
+ * This is the base class for handlers that may contain other handlers.
+ *
+ *
+ *
+ */
+public abstract class AbstractHandlerContainer extends AbstractHandler implements HandlerContainer
+{
+ /* ------------------------------------------------------------ */
+ public AbstractHandlerContainer()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public Handler[] getChildHandlers()
+ {
+ Object list = expandChildren(null,null);
+ return (Handler[])LazyList.toArray(list, Handler.class);
+ }
+
+ /* ------------------------------------------------------------ */
+ public Handler[] getChildHandlersByClass(Class<?> byclass)
+ {
+ Object list = expandChildren(null,byclass);
+ return (Handler[])LazyList.toArray(list, byclass);
+ }
+
+ /* ------------------------------------------------------------ */
+ public Handler getChildHandlerByClass(Class<?> byclass)
+ {
+ // TODO this can be more efficient?
+ Object list = expandChildren(null,byclass);
+ if (list==null)
+ return null;
+ return LazyList.get(list, 0);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected Object expandChildren(Object list, Class<?> byClass)
+ {
+ return list;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected Object expandHandler(Handler handler, Object list, Class<Handler> byClass)
+ {
+ if (handler==null)
+ return list;
+
+ if (handler!=null && (byClass==null || byClass.isAssignableFrom(handler.getClass())))
+ list=LazyList.add(list, handler);
+
+ if (handler instanceof AbstractHandlerContainer)
+ list=((AbstractHandlerContainer)handler).expandChildren(list, byClass);
+ else if (handler instanceof HandlerContainer)
+ {
+ HandlerContainer container = (HandlerContainer)handler;
+ Handler[] handlers=byClass==null?container.getChildHandlers():container.getChildHandlersByClass(byClass);
+ list=LazyList.addArray(list, handlers);
+ }
+
+ return list;
+ }
+
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/CompleteHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/CompleteHandler.java
new file mode 100644
index 0000000000..3311b73c8e
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/CompleteHandler.java
@@ -0,0 +1,37 @@
+// ========================================================================
+// Copyright (c) 2004-2009 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.handler;
+
+import java.util.List;
+
+import javax.servlet.ServletRequest;
+
+import org.eclipse.jetty.server.Request;
+
+/**
+ * An interface for handlers that wish to be notified of request completion.
+ *
+ * If the request attribute COMPLETE_HANDLER_ATTR is set as either a single
+ * CompleteHandler instance or a {@link List} of CompleteHandler instances,
+ * then when the {@link ServletRequest#complete()} method is called, then
+ * the {@link #complete(Request)} method is called for each CompleteHandler.
+ *
+ *
+ *
+ */
+public interface CompleteHandler
+{
+ public final static String COMPLETE_HANDLER_ATTR = "org.eclipse.jetty.server.handler.CompleteHandlers";
+ void complete(Request request);
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
new file mode 100644
index 0000000000..d764f1a602
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
@@ -0,0 +1,1903 @@
+// ========================================================================
+// Copyright (c) 2004-2009 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.handler;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.FilterRegistration;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextAttributeEvent;
+import javax.servlet.ServletContextAttributeListener;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.ServletRequestAttributeListener;
+import javax.servlet.ServletRequestEvent;
+import javax.servlet.ServletRequestListener;
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.HttpException;
+import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HandlerContainer;
+import org.eclipse.jetty.server.HttpConnection;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.AttributesMap;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+
+/* ------------------------------------------------------------ */
+/** ContextHandler.
+ *
+ * This handler wraps a call to handle by setting the context and
+ * servlet path, plus setting the context classloader.
+ *
+ * <p>
+ * If the context init parameter "org.eclipse.jetty.servlet.ManagedAttributes"
+ * is set to a coma separated list of names, then they are treated as context
+ * attribute names, which if set as attributes are passed to the servers Container
+ * so that they may be managed with JMX.
+ *
+ * @org.apache.xbean.XBean description="Creates a basic HTTP context"
+ *
+ *
+ *
+ */
+public class ContextHandler extends HandlerWrapper implements Attributes, Server.Graceful, CompleteHandler
+{
+ private static ThreadLocal<Context> __context=new ThreadLocal<Context>();
+ public static final String MANAGED_ATTRIBUTES = "org.eclipse.jetty.server.servlet.ManagedAttributes";
+
+ /* ------------------------------------------------------------ */
+ /** Get the current ServletContext implementation.
+ * This call is only valid during a call to doStart and is available to
+ * nested handlers to access the context.
+ *
+ * @return ServletContext implementation
+ */
+ public static Context getCurrentContext()
+ {
+ Context context = __context.get();
+ return context;
+ }
+
+ protected Context _scontext;
+
+ private AttributesMap _attributes;
+ private AttributesMap _contextAttributes;
+ private ClassLoader _classLoader;
+ private String _contextPath="/";
+ private Map<String,String> _initParams;
+ private String _displayName;
+ private Resource _baseResource;
+ private MimeTypes _mimeTypes;
+ private Map<String,String> _localeEncodingMap;
+ private String[] _welcomeFiles;
+ private ErrorHandler _errorHandler;
+ private String[] _vhosts;
+ private Set<String> _connectors;
+ private EventListener[] _eventListeners;
+ private Logger _logger;
+ private boolean _shutdown;
+ private boolean _allowNullPathInfo;
+ private int _maxFormContentSize=Integer.getInteger("org.eclipse.jetty.server.Request.maxFormContentSize",200000).intValue();
+ private boolean _compactPath=false;
+ private boolean _aliases=true;
+
+ private Object _contextListeners;
+ private Object _contextAttributeListeners;
+ private Object _requestListeners;
+ private Object _asyncListeners;
+ private Object _requestAttributeListeners;
+ private Set<String> _managedAttributes;
+
+ /* ------------------------------------------------------------ */
+ /**
+ *
+ */
+ public ContextHandler()
+ {
+ super();
+ _scontext=new Context();
+ _attributes=new AttributesMap();
+ _initParams=new HashMap<String,String>();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ *
+ */
+ protected ContextHandler(Context context)
+ {
+ super();
+ _scontext=context;
+ _attributes=new AttributesMap();
+ _initParams=new HashMap<String,String>();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ *
+ */
+ public ContextHandler(String contextPath)
+ {
+ this();
+ setContextPath(contextPath);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ *
+ */
+ public ContextHandler(HandlerContainer parent, String contextPath)
+ {
+ this();
+ setContextPath(contextPath);
+ parent.addHandler(this);
+ }
+
+ /* ------------------------------------------------------------ */
+ public Context getServletContext()
+ {
+ return _scontext;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the allowNullPathInfo true if /context is not redirected to /context/
+ */
+ public boolean getAllowNullPathInfo()
+ {
+ return _allowNullPathInfo;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param allowNullPathInfo true if /context is not redirected to /context/
+ */
+ public void setAllowNullPathInfo(boolean allowNullPathInfo)
+ {
+ _allowNullPathInfo=allowNullPathInfo;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setServer(Server server)
+ {
+ if (_errorHandler!=null)
+ {
+ Server old_server=getServer();
+ if (old_server!=null && old_server!=server)
+ old_server.getContainer().update(this, _errorHandler, null, "error",true);
+ super.setServer(server);
+ if (server!=null && server!=old_server)
+ server.getContainer().update(this, null, _errorHandler, "error",true);
+ _errorHandler.setServer(server);
+ }
+ else
+ super.setServer(server);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the virtual hosts for the context.
+ * Only requests that have a matching host header or fully qualified
+ * URL will be passed to that context with a virtual host name.
+ * A context with no virtual host names or a null virtual host name is
+ * available to all requests that are not served by a context with a
+ * matching virtual host name.
+ * @param vhosts Array of virtual hosts that this context responds to. A
+ * null host name or null/empty array means any hostname is acceptable.
+ * Host names may be String representation of IP addresses. Host names may
+ * start with '*.' to wildcard one level of names.
+ */
+ public void setVirtualHosts( String[] vhosts )
+ {
+ if ( vhosts == null )
+ {
+ _vhosts = vhosts;
+ }
+ else
+ {
+ _vhosts = new String[vhosts.length];
+ for ( int i = 0; i < vhosts.length; i++ )
+ _vhosts[i] = normalizeHostname( vhosts[i]);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get the virtual hosts for the context.
+ * Only requests that have a matching host header or fully qualified
+ * URL will be passed to that context with a virtual host name.
+ * A context with no virtual host names or a null virtual host name is
+ * available to all requests that are not served by a context with a
+ * matching virtual host name.
+ * @return Array of virtual hosts that this context responds to. A
+ * null host name or empty array means any hostname is acceptable.
+ * Host names may be String representation of IP addresses.
+ * Host names may start with '*.' to wildcard one level of names.
+ */
+ public String[] getVirtualHosts()
+ {
+ return _vhosts;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @deprecated use {@link #setConnectorNames(String[])}
+ */
+ public void setHosts(String[] hosts)
+ {
+ setConnectorNames(hosts);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get the hosts for the context.
+ * @deprecated
+ */
+ public String[] getHosts()
+ {
+ return getConnectorNames();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return an array of connector names that this context
+ * will accept a request from.
+ */
+ public String[] getConnectorNames()
+ {
+ if (_connectors==null || _connectors.size()==0)
+ return null;
+
+ return (String[])_connectors.toArray(new String[_connectors.size()]);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the names of accepted connectors.
+ *
+ * Names are either "host:port" or a specific configured name for a connector.
+ *
+ * @param connectors If non null, an array of connector names that this context
+ * will accept a request from.
+ */
+ public void setConnectorNames(String[] connectors)
+ {
+ if (connectors==null || connectors.length==0)
+ _connectors=null;
+ else
+ _connectors= new HashSet<String>(Arrays.asList(connectors));
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getAttribute(java.lang.String)
+ */
+ public Object getAttribute(String name)
+ {
+ return _attributes.getAttribute(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getAttributeNames()
+ */
+ @SuppressWarnings("unchecked")
+ public Enumeration getAttributeNames()
+ {
+ return AttributesMap.getAttributeNamesCopy(_attributes);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the attributes.
+ */
+ public Attributes getAttributes()
+ {
+ return _attributes;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the classLoader.
+ */
+ public ClassLoader getClassLoader()
+ {
+ return _classLoader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Make best effort to extract a file classpath from the context classloader
+ * @return Returns the classLoader.
+ */
+ public String getClassPath()
+ {
+ if ( _classLoader==null || !(_classLoader instanceof URLClassLoader))
+ return null;
+ URLClassLoader loader = (URLClassLoader)_classLoader;
+ URL[] urls =loader.getURLs();
+ StringBuilder classpath=new StringBuilder();
+ for (int i=0;i<urls.length;i++)
+ {
+ try
+ {
+ Resource resource = newResource(urls[i]);
+ File file=resource.getFile();
+ if (file.exists())
+ {
+ if (classpath.length()>0)
+ classpath.append(File.pathSeparatorChar);
+ classpath.append(file.getAbsolutePath());
+ }
+ }
+ catch (IOException e)
+ {
+ Log.debug(e);
+ }
+ }
+ if (classpath.length()==0)
+ return null;
+ return classpath.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the _contextPath.
+ */
+ public String getContextPath()
+ {
+ return _contextPath;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getInitParameter(java.lang.String)
+ */
+ public String getInitParameter(String name)
+ {
+ return (String)_initParams.get(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getInitParameterNames()
+ */
+ @SuppressWarnings("unchecked")
+ public Enumeration getInitParameterNames()
+ {
+ return Collections.enumeration(_initParams.keySet());
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the initParams.
+ */
+ public Map<String,String> getInitParams()
+ {
+ return _initParams;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getServletContextName()
+ */
+ public String getDisplayName()
+ {
+ return _displayName;
+ }
+
+ /* ------------------------------------------------------------ */
+ public EventListener[] getEventListeners()
+ {
+ return _eventListeners;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the context event listeners.
+ * @see ServletContextListener
+ * @see ServletContextAttributeListener
+ * @see ServletRequestListener
+ * @see ServletRequestAttributeListener
+ */
+ public void setEventListeners(EventListener[] eventListeners)
+ {
+ _contextListeners=null;
+ _contextAttributeListeners=null;
+ _requestListeners=null;
+ _requestAttributeListeners=null;
+
+ _eventListeners=eventListeners;
+
+ for (int i=0; eventListeners!=null && i<eventListeners.length;i ++)
+ {
+ EventListener listener = _eventListeners[i];
+
+ if (listener instanceof ServletContextListener)
+ _contextListeners= LazyList.add(_contextListeners, listener);
+
+ if (listener instanceof ServletContextAttributeListener)
+ _contextAttributeListeners= LazyList.add(_contextAttributeListeners, listener);
+
+ if (listener instanceof ServletRequestListener)
+ _requestListeners= LazyList.add(_requestListeners, listener);
+
+ if (listener instanceof ServletRequestAttributeListener)
+ _requestAttributeListeners= LazyList.add(_requestAttributeListeners, listener);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Add a context event listeners.
+ * @see ServletContextListener
+ * @see ServletContextAttributeListener
+ * @see ServletRequestListener
+ * @see ServletRequestAttributeListener
+ */
+ public void addEventListener(EventListener listener)
+ {
+ setEventListeners((EventListener[])LazyList.addToArray(getEventListeners(), listener, EventListener.class));
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if this context is accepting new requests
+ */
+ public boolean isShutdown()
+ {
+ return !_shutdown;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set shutdown status.
+ * This field allows for graceful shutdown of a context. A started context may be put into non accepting state so
+ * that existing requests can complete, but no new requests are accepted.
+ * @param shutdown true if this context is (not?) accepting new requests
+ */
+ public void setShutdown(boolean shutdown)
+ {
+ _shutdown = shutdown;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Logger getLogger()
+ {
+ return _logger;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setLogger(Logger logger)
+ {
+ _logger=logger;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.thread.AbstractLifeCycle#doStart()
+ */
+ protected void doStart() throws Exception
+ {
+ if (_contextPath==null)
+ throw new IllegalStateException("Null contextPath");
+
+ _logger=Log.getLogger(getDisplayName()==null?getContextPath():getDisplayName());
+ ClassLoader old_classloader=null;
+ Thread current_thread=null;
+ Context old_context=null;
+
+ _contextAttributes=new AttributesMap();
+ try
+ {
+
+ // Set the classloader
+ if (_classLoader!=null)
+ {
+ current_thread=Thread.currentThread();
+ old_classloader=current_thread.getContextClassLoader();
+ current_thread.setContextClassLoader(_classLoader);
+ }
+
+
+ if (_mimeTypes==null)
+ _mimeTypes=new MimeTypes();
+
+ old_context=__context.get();
+ __context.set(_scontext);
+
+ if (_errorHandler==null)
+ setErrorHandler(new ErrorHandler());
+
+ // defers the calling of super.doStart()
+ startContext();
+
+
+ }
+ finally
+ {
+ __context.set(old_context);
+
+ // reset the classloader
+ if (_classLoader!=null)
+ {
+ current_thread.setContextClassLoader(old_classloader);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Extensible startContext.
+ * this method is called from {@link ContextHandler#doStart()} instead of a
+ * call to super.doStart(). This allows derived classes to insert additional
+ * handling (Eg configuration) before the call to super.doStart by this method
+ * will start contained handlers.
+ * @see org.eclipse.jetty.Scope.Context
+ * @see org.eclipse.jetty.webapp.WebAppContext
+ */
+ protected void startContext()
+ throws Exception
+ {
+ super.doStart();
+
+ if (_errorHandler!=null)
+ _errorHandler.start();
+
+ // Context listeners
+ if (_contextListeners != null )
+ {
+ ServletContextEvent event= new ServletContextEvent(_scontext);
+ for (int i= 0; i < LazyList.size(_contextListeners); i++)
+ {
+ ((ServletContextListener)LazyList.get(_contextListeners, i)).contextInitialized(event);
+ }
+ }
+
+ String managedAttributes = _initParams.get(MANAGED_ATTRIBUTES);
+ if (managedAttributes!=null)
+ {
+ _managedAttributes=new HashSet<String>();
+ String[] attributes = managedAttributes.toString().split(",");
+ for (String s : attributes)
+ _managedAttributes.add(s);
+
+ Enumeration e = _scontext.getAttributeNames();
+ while(e.hasMoreElements())
+ {
+ String name = (String)e.nextElement();
+ Object value = _scontext.getAttribute(name);
+ setManagedAttribute(name,value);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.thread.AbstractLifeCycle#doStop()
+ */
+ protected void doStop() throws Exception
+ {
+ ClassLoader old_classloader=null;
+ Thread current_thread=null;
+
+ Context old_context=__context.get();
+ __context.set(_scontext);
+ try
+ {
+ // Set the classloader
+ if (_classLoader!=null)
+ {
+ current_thread=Thread.currentThread();
+ old_classloader=current_thread.getContextClassLoader();
+ current_thread.setContextClassLoader(_classLoader);
+ }
+
+ super.doStop();
+
+ // Context listeners
+ if (_contextListeners != null )
+ {
+ ServletContextEvent event= new ServletContextEvent(_scontext);
+ for (int i=LazyList.size(_contextListeners); i-->0;)
+ {
+ ((ServletContextListener)LazyList.get(_contextListeners, i)).contextDestroyed(event);
+ }
+ }
+
+ if (_errorHandler!=null)
+ _errorHandler.stop();
+
+ Enumeration e = _scontext.getAttributeNames();
+ while(e.hasMoreElements())
+ {
+ String name = (String)e.nextElement();
+ setManagedAttribute(name,null);
+ }
+ }
+ finally
+ {
+ __context.set(old_context);
+ // reset the classloader
+ if (_classLoader!=null)
+ current_thread.setContextClassLoader(old_classloader);
+ }
+
+ if (_contextAttributes!=null)
+ _contextAttributes.clearAttributes();
+ _contextAttributes=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+ */
+ public void handle(String target, HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ Request baseRequest=(request instanceof Request)?(Request)request:HttpConnection.getCurrentConnection().getRequest();
+ DispatcherType dispatch=request.getDispatcherType();
+
+ if( !isStarted() || _shutdown || (DispatcherType.REQUEST.equals(dispatch) && baseRequest.isHandled()))
+ return;
+
+ // Check the vhosts
+ if (_vhosts!=null && _vhosts.length>0)
+ {
+ String vhost = normalizeHostname( request.getServerName());
+
+ boolean match=false;
+
+ // TODO non-linear lookup
+ for (int i=0;!match && i<_vhosts.length;i++)
+ {
+ String contextVhost = _vhosts[i];
+ if(contextVhost==null) continue;
+ if(contextVhost.startsWith("*.")) {
+ // wildcard only at the beginning, and only for one additional subdomain level
+ match=contextVhost.regionMatches(true,2,vhost,vhost.indexOf(".")+1,contextVhost.length()-2);
+ } else
+ match=contextVhost.equalsIgnoreCase(vhost);
+ }
+ if (!match)
+ return;
+ }
+
+ // Check the connector
+ if (_connectors!=null && _connectors.size()>0)
+ {
+ String connector=HttpConnection.getCurrentConnection().getConnector().getName();
+ if (connector==null || !_connectors.contains(connector))
+ return;
+ }
+
+ if (_compactPath)
+ target=URIUtil.compactPath(target);
+
+ if (target.startsWith(_contextPath))
+ {
+ if (_contextPath.length()==target.length() && _contextPath.length()>1 &&!_allowNullPathInfo)
+ {
+ // context request must end with /
+ baseRequest.setHandled(true);
+ if (request.getQueryString()!=null)
+ response.sendRedirect(URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH)+"?"+request.getQueryString());
+ else
+ response.sendRedirect(URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH));
+ return;
+ }
+ }
+ else
+ {
+ // Not for this context!
+ return;
+ }
+
+ doHandle(target,baseRequest,request,response);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+ */
+ public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ boolean new_context=false;
+ Context old_context=null;
+ String old_context_path=null;
+ String old_servlet_path=null;
+ String old_path_info=null;
+ ClassLoader old_classloader=null;
+ Thread current_thread=null;
+ String pathInfo=null;
+
+ DispatcherType dispatch=request.getDispatcherType();
+
+ old_context=baseRequest.getContext();
+
+ // Are we already in this context?
+ if (old_context!=_scontext)
+ {
+ new_context=true;
+
+ // check the target.
+ if (DispatcherType.REQUEST.equals(dispatch) || DispatcherType.ASYNC.equals(dispatch))
+ {
+ if (target.length()>_contextPath.length())
+ {
+ if (_contextPath.length()>1)
+ target=target.substring(_contextPath.length());
+ pathInfo=target;
+ }
+ else if (_contextPath.length()==1)
+ {
+ target=URIUtil.SLASH;
+ pathInfo=URIUtil.SLASH;
+ }
+ else
+ {
+ target=URIUtil.SLASH;
+ pathInfo=null;
+ }
+ }
+ }
+
+ try
+ {
+ old_context_path=baseRequest.getContextPath();
+ old_servlet_path=baseRequest.getServletPath();
+ old_path_info=baseRequest.getPathInfo();
+
+ // Update the paths
+ baseRequest.setContext(_scontext);
+ if (!DispatcherType.INCLUDE.equals(dispatch) && target.startsWith("/"))
+ {
+ if (_contextPath.length()==1)
+ baseRequest.setContextPath("");
+ else
+ baseRequest.setContextPath(_contextPath);
+ baseRequest.setServletPath(null);
+ baseRequest.setPathInfo(pathInfo);
+ }
+
+ ServletRequestEvent event=null;
+ if (new_context)
+ {
+ // Set the classloader
+ if (_classLoader!=null)
+ {
+ current_thread=Thread.currentThread();
+ old_classloader=current_thread.getContextClassLoader();
+ current_thread.setContextClassLoader(_classLoader);
+ }
+
+ // Handle the REALLY SILLY request events!
+ baseRequest.setRequestListeners(_requestListeners);
+ if (_requestAttributeListeners!=null)
+ {
+ final int s=LazyList.size(_requestAttributeListeners);
+ for(int i=0;i<s;i++)
+ baseRequest.addEventListener(((EventListener)LazyList.get(_requestAttributeListeners,i)));
+ }
+ }
+
+ // Handle the request
+ try
+ {
+ if (DispatcherType.REQUEST.equals(dispatch) && isProtectedTarget(target))
+ throw new HttpException(HttpServletResponse.SC_NOT_FOUND);
+
+ Handler handler = getHandler();
+ if (handler!=null)
+ handler.handle(target, request, response);
+ }
+ catch(HttpException e)
+ {
+ Log.debug(e);
+ response.sendError(e.getStatus(), e.getReason());
+ }
+ finally
+ {
+ // Handle more REALLY SILLY request events!
+ if (new_context)
+ {
+ baseRequest.takeRequestListeners();
+ if (_requestAttributeListeners!=null)
+ {
+ for(int i=LazyList.size(_requestAttributeListeners);i-->0;)
+ baseRequest.removeEventListener(((EventListener)LazyList.get(_requestAttributeListeners,i)));
+ }
+ }
+ }
+ }
+ finally
+ {
+ if (old_context!=_scontext)
+ {
+ // reset the classloader
+ if (_classLoader!=null)
+ {
+ current_thread.setContextClassLoader(old_classloader);
+ }
+
+ // reset the context and servlet path.
+ baseRequest.setContext(old_context);
+ baseRequest.setContextPath(old_context_path);
+ baseRequest.setServletPath(old_servlet_path);
+ baseRequest.setPathInfo(old_path_info);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /* Handle a runnable in this context
+ */
+ public void handle(Runnable runnable)
+ {
+ ClassLoader old_classloader=null;
+ Thread current_thread=null;
+ try
+ {
+ // Set the classloader
+ if (_classLoader!=null)
+ {
+ current_thread=Thread.currentThread();
+ old_classloader=current_thread.getContextClassLoader();
+ current_thread.setContextClassLoader(_classLoader);
+ }
+
+ runnable.run();
+ }
+ finally
+ {
+ if (old_classloader!=null)
+ {
+ current_thread.setContextClassLoader(old_classloader);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Check the target.
+ * Called by {@link #handle(String, HttpServletRequest, HttpServletResponse)} when a
+ * target within a context is determined. If the target is protected, 404 is returned.
+ * The default implementation always returns false.
+ * @see org.eclipse.jetty.webapp.WebAppContext#isProtectedTarget(String)
+ */
+ /* ------------------------------------------------------------ */
+ protected boolean isProtectedTarget(String target)
+ {
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#removeAttribute(java.lang.String)
+ */
+ public void removeAttribute(String name)
+ {
+ setManagedAttribute(name,null);
+ _attributes.removeAttribute(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ /* Set a context attribute.
+ * Attributes set via this API cannot be overriden by the ServletContext.setAttribute API.
+ * Their lifecycle spans the stop/start of a context. No attribute listener events are
+ * triggered by this API.
+ * @see javax.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object)
+ */
+ public void setAttribute(String name, Object value)
+ {
+ setManagedAttribute(name,value);
+ _attributes.setAttribute(name,value);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param attributes The attributes to set.
+ */
+ public void setAttributes(Attributes attributes)
+ {
+ if (attributes instanceof AttributesMap)
+ {
+ _attributes = (AttributesMap)attributes;
+ Enumeration e = _attributes.getAttributeNames();
+ while (e.hasMoreElements())
+ {
+ String name = (String)e.nextElement();
+ setManagedAttribute(name,attributes.getAttribute(name));
+ }
+ }
+ else
+ {
+ _attributes=new AttributesMap();
+ Enumeration e = attributes.getAttributeNames();
+ while (e.hasMoreElements())
+ {
+ String name = (String)e.nextElement();
+ Object value=attributes.getAttribute(name);
+ setManagedAttribute(name,value);
+ _attributes.setAttribute(name,value);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void clearAttributes()
+ {
+ Enumeration e = _attributes.getAttributeNames();
+ while (e.hasMoreElements())
+ {
+ String name = (String)e.nextElement();
+ setManagedAttribute(name,null);
+ }
+ _attributes.clearAttributes();
+ }
+
+ /* ------------------------------------------------------------ */
+ private void setManagedAttribute(String name, Object value)
+ {
+ if (_managedAttributes!=null && _managedAttributes.contains(name))
+ {
+ Object o =_scontext.getAttribute(name);
+ if (o!=null)
+ getServer().getContainer().removeBean(o);
+ if (value!=null)
+ getServer().getContainer().addBean(value);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param classLoader The classLoader to set.
+ */
+ public void setClassLoader(ClassLoader classLoader)
+ {
+ _classLoader = classLoader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param contextPath The _contextPath to set.
+ */
+ public void setContextPath(String contextPath)
+ {
+ if (contextPath!=null && contextPath.length()>1 && contextPath.endsWith("/"))
+ throw new IllegalArgumentException("ends with /");
+ _contextPath = contextPath;
+
+ if (getServer()!=null && (getServer().isStarting() || getServer().isStarted()))
+ {
+ Handler[] contextCollections = getServer().getChildHandlersByClass(ContextHandlerCollection.class);
+ for (int h=0;contextCollections!=null&& h<contextCollections.length;h++)
+ ((ContextHandlerCollection)contextCollections[h]).mapContexts();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param initParams The initParams to set.
+ */
+ public void setInitParams(Map<String,String> initParams)
+ {
+ if (initParams == null)
+ return;
+ _initParams = new HashMap<String,String>(initParams);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param servletContextName The servletContextName to set.
+ */
+ public void setDisplayName(String servletContextName)
+ {
+ _displayName = servletContextName;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the resourceBase.
+ */
+ public Resource getBaseResource()
+ {
+ if (_baseResource==null)
+ return null;
+ return _baseResource;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the base resource as a string.
+ */
+ public String getResourceBase()
+ {
+ if (_baseResource==null)
+ return null;
+ return _baseResource.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param base The resourceBase to set.
+ */
+ public void setBaseResource(Resource base)
+ {
+ _baseResource=base;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param resourceBase The base resource as a string.
+ */
+ public void setResourceBase(String resourceBase)
+ {
+ try
+ {
+ setBaseResource(newResource(resourceBase));
+ }
+ catch (Exception e)
+ {
+ Log.warn(e);
+ throw new IllegalArgumentException(resourceBase);
+ }
+ }
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if alias checking is performed on resources.
+ */
+ public boolean isAliases()
+ {
+ return _aliases;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param aliases alias checking performed on resources.
+ */
+ public void setAliases(boolean aliases)
+ {
+ _aliases = aliases;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the mimeTypes.
+ */
+ public MimeTypes getMimeTypes()
+ {
+ return _mimeTypes;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param mimeTypes The mimeTypes to set.
+ */
+ public void setMimeTypes(MimeTypes mimeTypes)
+ {
+ _mimeTypes = mimeTypes;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ */
+ public void setWelcomeFiles(String[] files)
+ {
+ _welcomeFiles=files;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The names of the files which the server should consider to be welcome files in this context.
+ * @see <a href="http://jcp.org/aboutJava/communityprocess/final/jsr154/index.html">The Servlet Specification</a>
+ * @see #setWelcomeFiles
+ */
+ public String[] getWelcomeFiles()
+ {
+ return _welcomeFiles;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the errorHandler.
+ */
+ public ErrorHandler getErrorHandler()
+ {
+ return _errorHandler;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param errorHandler The errorHandler to set.
+ */
+ public void setErrorHandler(ErrorHandler errorHandler)
+ {
+ if (errorHandler!=null)
+ errorHandler.setServer(getServer());
+ if (getServer()!=null)
+ getServer().getContainer().update(this, _errorHandler, errorHandler, "errorHandler",true);
+ _errorHandler = errorHandler;
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getMaxFormContentSize()
+ {
+ return _maxFormContentSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setMaxFormContentSize(int maxSize)
+ {
+ _maxFormContentSize=maxSize;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if URLs are compacted to replace multiple '/'s with a single '/'
+ */
+ public boolean isCompactPath()
+ {
+ return _compactPath;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param compactPath True if URLs are compacted to replace multiple '/'s with a single '/'
+ */
+ public void setCompactPath(boolean compactPath)
+ {
+ _compactPath=compactPath;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String toString()
+ {
+
+ return this.getClass().getName()+"@"+Integer.toHexString(hashCode())+"{"+getContextPath()+","+getBaseResource()+"}";
+ }
+
+ /* ------------------------------------------------------------ */
+ public synchronized Class<?> loadClass(String className)
+ throws ClassNotFoundException
+ {
+ if (className==null)
+ return null;
+
+ if (_classLoader==null)
+ return Loader.loadClass(this.getClass(), className);
+
+ return _classLoader.loadClass(className);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public void addLocaleEncoding(String locale,String encoding)
+ {
+ if (_localeEncodingMap==null)
+ _localeEncodingMap=new HashMap<String,String>();
+ _localeEncodingMap.put(locale, encoding);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Get the character encoding for a locale. The full locale name is first
+ * looked up in the map of encodings. If no encoding is found, then the
+ * locale language is looked up.
+ *
+ * @param locale a <code>Locale</code> value
+ * @return a <code>String</code> representing the character encoding for
+ * the locale or null if none found.
+ */
+ public String getLocaleEncoding(Locale locale)
+ {
+ if (_localeEncodingMap==null)
+ return null;
+ String encoding = (String)_localeEncodingMap.get(locale.toString());
+ if (encoding==null)
+ encoding = (String)_localeEncodingMap.get(locale.getLanguage());
+ return encoding;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ */
+ public Resource getResource(String path) throws MalformedURLException
+ {
+ if (path==null || !path.startsWith(URIUtil.SLASH))
+ throw new MalformedURLException(path);
+
+ if (_baseResource==null)
+ return null;
+
+ try
+ {
+ path=URIUtil.canonicalPath(path);
+ Resource resource=_baseResource.addPath(path);
+
+ if (_aliases && resource.getAlias()!=null)
+ {
+ if (resource.exists())
+ Log.warn("Aliased resource: "+resource+"~="+resource.getAlias());
+ else if (Log.isDebugEnabled())
+ Log.debug("Aliased resource: "+resource+"~="+resource.getAlias());
+ return null;
+ }
+
+ return resource;
+ }
+ catch(Exception e)
+ {
+ Log.ignore(e);
+ }
+
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Convert URL to Resource
+ * wrapper for {@link Resource#newResource(URL)} enables extensions to
+ * provide alternate resource implementations.
+ */
+ public Resource newResource(URL url) throws IOException
+ {
+ return Resource.newResource(url);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Convert URL to Resource
+ * wrapper for {@link Resource#newResource(String)} enables extensions to
+ * provide alternate resource implementations.
+ */
+ public Resource newResource(String url) throws IOException
+ {
+ return Resource.newResource(url);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ */
+ public Set<String> getResourcePaths(String path)
+ {
+ try
+ {
+ path=URIUtil.canonicalPath(path);
+ Resource resource=getResource(path);
+
+ if (resource!=null && resource.exists())
+ {
+ if (!path.endsWith(URIUtil.SLASH))
+ path=path+URIUtil.SLASH;
+
+ String[] l=resource.list();
+ if (l!=null)
+ {
+ HashSet<String> set = new HashSet<String>();
+ for(int i=0;i<l.length;i++)
+ set.add(path+l[i]);
+ return set;
+ }
+ }
+ }
+ catch(Exception e)
+ {
+ Log.ignore(e);
+ }
+ return Collections.emptySet();
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ private String normalizeHostname( String host )
+ {
+ if ( host == null )
+ return null;
+
+ if ( host.endsWith( "." ) )
+ return host.substring( 0, host.length() -1);
+
+ return host;
+ }
+
+ public void complete(Request request)
+ {
+ // TODO Auto-generated method stub
+
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Context.
+ * <p>
+ * A partial implementation of {@link javax.servlet.ServletContext}.
+ * A complete implementation is provided by the derived {@link org.eclipse.jetty.servlet.ServletContextHandler.Context}.
+ * </p>
+ *
+ *
+ */
+ public class Context implements ServletContext
+ {
+ /* ------------------------------------------------------------ */
+ protected Context()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public ContextHandler getContextHandler()
+ {
+ // TODO reduce visibility of this method
+ return ContextHandler.this;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getContext(java.lang.String)
+ */
+ public ServletContext getContext(String uripath)
+ {
+ // TODO this is a very poor implementation!
+ // TODO move this to Server
+ ContextHandler context=null;
+ Handler[] handlers = getServer().getChildHandlersByClass(ContextHandler.class);
+ for (int i=0;i<handlers.length;i++)
+ {
+ if (handlers[i]==null || !handlers[i].isStarted())
+ continue;
+ ContextHandler ch = (ContextHandler)handlers[i];
+ String context_path=ch.getContextPath();
+ if (uripath.equals(context_path) || (uripath.startsWith(context_path)&&uripath.charAt(context_path.length())=='/'))
+ {
+ if (context==null || context_path.length()>context.getContextPath().length())
+ context=ch;
+ }
+ }
+
+ if (context!=null)
+ return context._scontext;
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getMajorVersion()
+ */
+ public int getMajorVersion()
+ {
+ return 3;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getMimeType(java.lang.String)
+ */
+ public String getMimeType(String file)
+ {
+ if (_mimeTypes==null)
+ return null;
+ Buffer mime = _mimeTypes.getMimeByExtension(file);
+ if (mime!=null)
+ return mime.toString();
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getMinorVersion()
+ */
+ public int getMinorVersion()
+ {
+ return 0;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getNamedDispatcher(java.lang.String)
+ */
+ public RequestDispatcher getNamedDispatcher(String name)
+ {
+ return null;
+ }
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getRequestDispatcher(java.lang.String)
+ */
+ public RequestDispatcher getRequestDispatcher(String uriInContext)
+ {
+ if (uriInContext == null)
+ return null;
+
+ if (!uriInContext.startsWith("/"))
+ return null;
+
+ try
+ {
+ String query=null;
+ int q=0;
+ if ((q=uriInContext.indexOf('?'))>0)
+ {
+ query=uriInContext.substring(q+1);
+ uriInContext=uriInContext.substring(0,q);
+ }
+ if ((q=uriInContext.indexOf(';'))>0)
+ uriInContext=uriInContext.substring(0,q);
+
+ String pathInContext=URIUtil.canonicalPath(URIUtil.decodePath(uriInContext));
+ String uri=URIUtil.addPaths(getContextPath(), uriInContext);
+ ContextHandler context=ContextHandler.this;
+ return new Dispatcher(context,uri, pathInContext, query);
+ }
+ catch(Exception e)
+ {
+ Log.ignore(e);
+ }
+ return null;
+ }
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getRealPath(java.lang.String)
+ */
+ public String getRealPath(String path)
+ {
+ if(path==null)
+ return null;
+ if(path.length()==0)
+ path = URIUtil.SLASH;
+ else if(path.charAt(0)!='/')
+ path = URIUtil.SLASH + path;
+
+ try
+ {
+ Resource resource=ContextHandler.this.getResource(path);
+ if(resource!=null)
+ {
+ File file = resource.getFile();
+ if (file!=null)
+ return file.getCanonicalPath();
+ }
+ }
+ catch (Exception e)
+ {
+ Log.ignore(e);
+ }
+
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public URL getResource(String path) throws MalformedURLException
+ {
+ Resource resource=ContextHandler.this.getResource(path);
+ if (resource!=null && resource.exists())
+ return resource.getURL();
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getResourceAsStream(java.lang.String)
+ */
+ public InputStream getResourceAsStream(String path)
+ {
+ try
+ {
+ URL url=getResource(path);
+ if (url==null)
+ return null;
+ return url.openStream();
+ }
+ catch(Exception e)
+ {
+ Log.ignore(e);
+ return null;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getResourcePaths(java.lang.String)
+ */
+ public Set getResourcePaths(String path)
+ {
+ return ContextHandler.this.getResourcePaths(path);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getServerInfo()
+ */
+ public String getServerInfo()
+ {
+ return "jetty/"+Server.getVersion();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getServlet(java.lang.String)
+ */
+ public Servlet getServlet(String name) throws ServletException
+ {
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getServletNames()
+ */
+ @SuppressWarnings("unchecked")
+ public Enumeration getServletNames()
+ {
+ return Collections.enumeration(Collections.EMPTY_LIST);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getServlets()
+ */
+ @SuppressWarnings("unchecked")
+ public Enumeration getServlets()
+ {
+ return Collections.enumeration(Collections.EMPTY_LIST);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#log(java.lang.Exception, java.lang.String)
+ */
+ public void log(Exception exception, String msg)
+ {
+ _logger.warn(msg,exception);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#log(java.lang.String)
+ */
+ public void log(String msg)
+ {
+ _logger.info(msg, null, null);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#log(java.lang.String, java.lang.Throwable)
+ */
+ public void log(String message, Throwable throwable)
+ {
+ _logger.warn(message,throwable);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getInitParameter(java.lang.String)
+ */
+ public String getInitParameter(String name)
+ {
+ return ContextHandler.this.getInitParameter(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getInitParameterNames()
+ */
+ @SuppressWarnings("unchecked")
+ public Enumeration getInitParameterNames()
+ {
+ return ContextHandler.this.getInitParameterNames();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getAttribute(java.lang.String)
+ */
+ public synchronized Object getAttribute(String name)
+ {
+ Object o = ContextHandler.this.getAttribute(name);
+ if (o==null && _contextAttributes!=null)
+ o=_contextAttributes.getAttribute(name);
+ return o;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getAttributeNames()
+ */
+ @SuppressWarnings("unchecked")
+ public synchronized Enumeration getAttributeNames()
+ {
+ HashSet<String> set = new HashSet<String>();
+ if (_contextAttributes!=null)
+ {
+ Enumeration<String> e = _contextAttributes.getAttributeNames();
+ while(e.hasMoreElements())
+ set.add(e.nextElement());
+ }
+ Enumeration<String> e = _attributes.getAttributeNames();
+ while(e.hasMoreElements())
+ set.add(e.nextElement());
+
+ return Collections.enumeration(set);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object)
+ */
+ public synchronized void setAttribute(String name, Object value)
+ {
+
+ if (_contextAttributes==null)
+ {
+ // Set it on the handler
+ ContextHandler.this.setAttribute(name, value);
+ return;
+ }
+
+ setManagedAttribute(name,value);
+ Object old_value=_contextAttributes==null?null:_contextAttributes.getAttribute(name);
+
+ if (value==null)
+ _contextAttributes.removeAttribute(name);
+ else
+ _contextAttributes.setAttribute(name,value);
+
+ if (_contextAttributeListeners!=null)
+ {
+ ServletContextAttributeEvent event =
+ new ServletContextAttributeEvent(_scontext,name, old_value==null?value:old_value);
+
+ for(int i=0;i<LazyList.size(_contextAttributeListeners);i++)
+ {
+ ServletContextAttributeListener l = (ServletContextAttributeListener)LazyList.get(_contextAttributeListeners,i);
+
+ if (old_value==null)
+ l.attributeAdded(event);
+ else if (value==null)
+ l.attributeRemoved(event);
+ else
+ l.attributeReplaced(event);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#removeAttribute(java.lang.String)
+ */
+ public synchronized void removeAttribute(String name)
+ {
+ setManagedAttribute(name,null);
+
+ if (_contextAttributes==null)
+ {
+ // Set it on the handler
+ _attributes.removeAttribute(name);
+ return;
+ }
+
+ Object old_value=_contextAttributes.getAttribute(name);
+ _contextAttributes.removeAttribute(name);
+ if (old_value!=null)
+ {
+ if (_contextAttributeListeners!=null)
+ {
+ ServletContextAttributeEvent event =
+ new ServletContextAttributeEvent(_scontext,name, old_value);
+
+ for(int i=0;i<LazyList.size(_contextAttributeListeners);i++)
+ ((ServletContextAttributeListener)LazyList.get(_contextAttributeListeners,i)).attributeRemoved(event);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getServletContextName()
+ */
+ public String getServletContextName()
+ {
+ String name = ContextHandler.this.getDisplayName();
+ if (name==null)
+ name=ContextHandler.this.getContextPath();
+ return name;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getContextPath()
+ {
+ if ((_contextPath != null) && _contextPath.equals(URIUtil.SLASH))
+ return "";
+
+ return _contextPath;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String toString()
+ {
+ return "ServletContext@"+Integer.toHexString(hashCode())+"{"+(getContextPath().equals("")?URIUtil.SLASH:getContextPath())+","+getBaseResource()+"}";
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean setInitParameter(String name, String value)
+ {
+ if (ContextHandler.this.getInitParameter(name)!=null)
+ return false;
+ ContextHandler.this.getInitParams().put(name,value);
+ return true;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.servlet.ServletContext#addFilter(java.lang.String, java.lang.Class)
+ */
+ public FilterRegistration addFilter(String filterName, Class<? extends Filter> filterClass)
+ {
+ Log.warn("Use servlet Context");
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.servlet.ServletContext#addFilter(java.lang.String, java.lang.String)
+ */
+ public FilterRegistration addFilter(String filterName, String className)
+ {
+ Log.warn("Use servlet Context");
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.servlet.ServletContext#addServlet(java.lang.String, java.lang.Class)
+ */
+ public ServletRegistration addServlet(String servletName, Class<? extends Servlet> servletClass)
+ {
+ Log.warn("Use servlet Context");
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.servlet.ServletContext#addServlet(java.lang.String, java.lang.String)
+ */
+ public ServletRegistration addServlet(String servletName, String className)
+ {
+ Log.warn("Use servlet Context");
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.servlet.ServletContext#findFilterRegistration(java.lang.String)
+ */
+ public FilterRegistration findFilterRegistration(String filterName)
+ {
+ Log.warn("Use servlet Context");
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.servlet.ServletContext#findServletRegistration(java.lang.String)
+ */
+ public ServletRegistration findServletRegistration(String servletName)
+ {
+ Log.warn("Use servlet Context");
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.servlet.ServletContext#getDefaultSessionTrackingModes()
+ */
+ public EnumSet<SessionTrackingMode> getDefaultSessionTrackingModes()
+ {
+ Log.warn("Use servlet Context");
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.servlet.ServletContext#getEffectiveSessionTrackingModes()
+ */
+ public EnumSet<SessionTrackingMode> getEffectiveSessionTrackingModes()
+ {
+ Log.warn("Use servlet Context");
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.servlet.ServletContext#getSessionCookieConfig()
+ */
+ public SessionCookieConfig getSessionCookieConfig()
+ {
+ Log.warn("Use servlet Context");
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.servlet.ServletContext#setSessionTrackingModes(java.util.EnumSet)
+ */
+ public void setSessionTrackingModes(EnumSet<SessionTrackingMode> sessionTrackingModes)
+ {
+ Log.warn("Use servlet Context");
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.servlet.ServletContext#addFilter(java.lang.String, javax.servlet.Filter)
+ */
+ public FilterRegistration addFilter(String filterName, Filter filter)
+ {
+ Log.warn("Use servlet Context");
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.servlet.ServletContext#addServlet(java.lang.String, javax.servlet.Servlet)
+ */
+ public ServletRegistration addServlet(String servletName, Servlet servlet)
+ {
+ Log.warn("Use servlet Context");
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.servlet.ServletContext#setSessionTrackingModes(java.util.Set)
+ */
+ public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes)
+ {
+ // TODO Auto-generated method stub
+ Log.warn("Not implemented");
+
+ }
+
+
+ }
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java
new file mode 100644
index 0000000000..4c9a3464b4
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java
@@ -0,0 +1,319 @@
+// ========================================================================
+// Copyright (c) 2006-2009 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.handler;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.server.AsyncRequest;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HandlerContainer;
+import org.eclipse.jetty.server.HttpConnection;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.log.Log;
+
+/* ------------------------------------------------------------ */
+/** ContextHandlerCollection.
+ *
+ * This {@link org.eclipse.jetty.server.handler.HandlerCollection} is creates a
+ * {@link org.eclipse.jetty.http.servlet.PathMap} to it's contained handlers based
+ * on the context path and virtual hosts of any contained {@link org.eclipse.jetty.server.handler.ContextHandler}s.
+ * The contexts do not need to be directly contained, only children of the contained handlers.
+ * Multiple contexts may have the same context path and they are called in order until one
+ * handles the request.
+ *
+ * @org.apache.xbean.XBean element="contexts"
+ */
+public class ContextHandlerCollection extends HandlerCollection
+{
+ private PathMap _contextMap;
+ private Class _contextClass = ContextHandler.class;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Remap the context paths.
+ */
+ public void mapContexts()
+ {
+ PathMap contextMap = new PathMap();
+ Handler[] branches = getHandlers();
+
+
+ for (int b=0;branches!=null && b<branches.length;b++)
+ {
+ Handler[] handlers=null;
+
+ if (branches[b] instanceof ContextHandler)
+ {
+ handlers = new Handler[]{ branches[b] };
+ }
+ else if (branches[b] instanceof HandlerContainer)
+ {
+ handlers = ((HandlerContainer)branches[b]).getChildHandlersByClass(ContextHandler.class);
+ }
+ else
+ continue;
+
+ for (int i=0;i<handlers.length;i++)
+ {
+ ContextHandler handler=(ContextHandler)handlers[i];
+
+ String contextPath=handler.getContextPath();
+
+ if (contextPath==null || contextPath.indexOf(',')>=0 || contextPath.startsWith("*"))
+ throw new IllegalArgumentException ("Illegal context spec:"+contextPath);
+
+ if(!contextPath.startsWith("/"))
+ contextPath='/'+contextPath;
+
+ if (contextPath.length()>1)
+ {
+ if (contextPath.endsWith("/"))
+ contextPath+="*";
+ else if (!contextPath.endsWith("/*"))
+ contextPath+="/*";
+ }
+
+ Object contexts=contextMap.get(contextPath);
+ String[] vhosts=handler.getVirtualHosts();
+
+
+ if (vhosts!=null && vhosts.length>0)
+ {
+ Map hosts;
+
+ if (contexts instanceof Map)
+ hosts=(Map)contexts;
+ else
+ {
+ hosts=new HashMap();
+ hosts.put("*",contexts);
+ contextMap.put(contextPath, hosts);
+ }
+
+ for (int j=0;j<vhosts.length;j++)
+ {
+ String vhost=vhosts[j];
+ contexts=hosts.get(vhost);
+ contexts=LazyList.add(contexts,branches[b]);
+ hosts.put(vhost,contexts);
+ }
+ }
+ else if (contexts instanceof Map)
+ {
+ Map hosts=(Map)contexts;
+ contexts=hosts.get("*");
+ contexts= LazyList.add(contexts, branches[b]);
+ hosts.put("*",contexts);
+ }
+ else
+ {
+ contexts= LazyList.add(contexts, branches[b]);
+ contextMap.put(contextPath, contexts);
+ }
+ }
+ }
+ _contextMap=contextMap;
+
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.server.handler.HandlerCollection#setHandlers(org.eclipse.jetty.server.server.Handler[])
+ */
+ public void setHandlers(Handler[] handlers)
+ {
+ _contextMap=null;
+ super.setHandlers(handlers);
+ if (isStarted())
+ mapContexts();
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void doStart() throws Exception
+ {
+ mapContexts();
+ super.doStart();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.server.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+ */
+ public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ Handler[] handlers = getHandlers();
+ if (handlers==null || handlers.length==0)
+ return;
+
+ Request base_request = HttpConnection.getCurrentConnection().getRequest();
+
+ AsyncRequest async = base_request.getAsyncRequest();
+ if (async!=null)
+ {
+ ContextHandler context=async.getContextHandler();
+ if (context!=null)
+ {
+ context.doHandle(target,base_request,request,response);
+ return;
+ }
+ }
+
+ // data structure which maps a request to a context; first-best match wins
+ // { context path =>
+ // { virtual host => context }
+ // }
+ PathMap map = _contextMap;
+ if (map!=null && target!=null && target.startsWith("/"))
+ {
+ // first, get all contexts matched by context path
+ Object contexts = map.getLazyMatches(target);
+
+ for (int i=0; i<LazyList.size(contexts); i++)
+ {
+ // then, match against the virtualhost of each context
+ Map.Entry entry = (Map.Entry)LazyList.get(contexts, i);
+ Object list = entry.getValue();
+
+ if (list instanceof Map)
+ {
+ Map hosts = (Map)list;
+ String host = normalizeHostname(request.getServerName());
+
+ // explicitly-defined virtual hosts, most specific
+ list=hosts.get(host);
+ for (int j=0; j<LazyList.size(list); j++)
+ {
+ Handler handler = (Handler)LazyList.get(list,j);
+ handler.handle(target,request, response);
+ if (base_request.isHandled())
+ return;
+ }
+
+ // wildcard for one level of names
+ list=hosts.get("*."+host.substring(host.indexOf(".")+1));
+ for (int j=0; j<LazyList.size(list); j++)
+ {
+ Handler handler = (Handler)LazyList.get(list,j);
+ handler.handle(target,request, response);
+ if (base_request.isHandled())
+ return;
+ }
+
+ // no virtualhosts defined for the context, least specific
+ // will handle any request that does not match to a specific virtual host above
+ list=hosts.get("*");
+ for (int j=0; j<LazyList.size(list); j++)
+ {
+ Handler handler = (Handler)LazyList.get(list,j);
+ handler.handle(target,request, response);
+ if (base_request.isHandled())
+ return;
+ }
+ }
+ else
+ {
+ for (int j=0; j<LazyList.size(list); j++)
+ {
+ Handler handler = (Handler)LazyList.get(list,j);
+ handler.handle(target,request, response);
+ if (base_request.isHandled())
+ return;
+ }
+ }
+ }
+ }
+ else
+ {
+ // This may not work in all circumstances... but then I think it should never be called
+ for (int i=0;i<handlers.length;i++)
+ {
+ handlers[i].handle(target,request, response);
+ if ( base_request.isHandled())
+ return;
+ }
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Add a context handler.
+ * @param contextPath The context path to add
+ * @return
+ * @throws IllegalAccessException
+ * @throws InstantiationException
+ */
+ public ContextHandler addContext(String contextPath,String resourceBase)
+ {
+ try
+ {
+ ContextHandler context = (ContextHandler)_contextClass.newInstance();
+ context.setContextPath(contextPath);
+ context.setResourceBase(resourceBase);
+ addHandler(context);
+ return context;
+ }
+ catch (Exception e)
+ {
+ Log.warn(e);
+ throw new Error(e);
+ }
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The class to use to add new Contexts
+ */
+ public Class getContextClass()
+ {
+ return _contextClass;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param contextClass The class to use to add new Contexts
+ */
+ public void setContextClass(Class contextClass)
+ {
+ if (contextClass ==null || !(ContextHandler.class.isAssignableFrom(contextClass)))
+ throw new IllegalArgumentException();
+ _contextClass = contextClass;
+ }
+
+ /* ------------------------------------------------------------ */
+ private String normalizeHostname( String host )
+ {
+ if ( host == null )
+ return null;
+
+ if ( host.endsWith( "." ) )
+ return host.substring( 0, host.length() -1);
+
+ return host;
+ }
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java
new file mode 100644
index 0000000000..4077111992
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java
@@ -0,0 +1,193 @@
+// ========================================================================
+// Copyright (c) 1999-2009 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.handler;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URL;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConnection;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.ByteArrayISO8859Writer;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+
+
+/* ------------------------------------------------------------ */
+/** Default Handler.
+ *
+ * This handle will deal with unhandled requests in the server.
+ * For requests for favicon.ico, the Jetty icon is served.
+ * For reqests to '/' a 404 with a list of known contexts is served.
+ * For all other requests a normal 404 is served.
+ * TODO Implement OPTIONS and TRACE methods for the server.
+ *
+ *
+ * @org.apache.xbean.XBean
+ */
+public class DefaultHandler extends AbstractHandler
+{
+ long _faviconModified=(System.currentTimeMillis()/1000)*1000;
+ byte[] _favicon;
+ boolean _serveIcon=true;
+
+ public DefaultHandler()
+ {
+ try
+ {
+ URL fav = this.getClass().getClassLoader().getResource("org/eclipse/jetty/favicon.ico");
+ if (fav!=null)
+ _favicon=IO.readBytes(fav.openStream());
+ }
+ catch(Exception e)
+ {
+ Log.warn(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+ */
+ public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ Request base_request = request instanceof Request?(Request)request:HttpConnection.getCurrentConnection().getRequest();
+
+ if (response.isCommitted() || base_request.isHandled())
+ return;
+
+ base_request.setHandled(true);
+
+ String method=request.getMethod();
+
+ // little cheat for common request
+ if (_serveIcon && _favicon!=null && method.equals(HttpMethods.GET) && request.getRequestURI().equals("/favicon.ico"))
+ {
+ if (request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE)==_faviconModified)
+ response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+ else
+ {
+ response.setStatus(HttpServletResponse.SC_OK);
+ response.setContentType("image/x-icon");
+ response.setContentLength(_favicon.length);
+ response.setDateHeader(HttpHeaders.LAST_MODIFIED, _faviconModified);
+ response.setHeader(HttpHeaders.CACHE_CONTROL,"max-age=360000,public");
+ response.getOutputStream().write(_favicon);
+ }
+ return;
+ }
+
+
+ if (!method.equals(HttpMethods.GET) || !request.getRequestURI().equals("/"))
+ {
+ response.sendError(HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
+
+ response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+ response.setContentType(MimeTypes.TEXT_HTML);
+
+ ByteArrayISO8859Writer writer = new ByteArrayISO8859Writer(1500);
+
+ String uri=request.getRequestURI();
+ uri=StringUtil.replace(uri,"<","&lt;");
+ uri=StringUtil.replace(uri,">","&gt;");
+
+ writer.write("<HTML>\n<HEAD>\n<TITLE>Error 404 - Not Found");
+ writer.write("</TITLE>\n<BODY>\n<H2>Error 404 - Not Found.</H2>\n");
+ writer.write("No context on this server matched or handled this request.<BR>");
+ writer.write("Contexts known to this server are: <ul>");
+
+
+ Server server = getServer();
+ Handler[] handlers = server==null?null:server.getChildHandlersByClass(ContextHandler.class);
+
+ for (int i=0;handlers!=null && i<handlers.length;i++)
+ {
+ ContextHandler context = (ContextHandler)handlers[i];
+ if (context.isRunning())
+ {
+ writer.write("<li><a href=\"");
+ if (context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
+ writer.write("http://"+context.getVirtualHosts()[0]+":"+request.getLocalPort());
+ writer.write(context.getContextPath());
+ if (context.getContextPath().length()>1 && context.getContextPath().endsWith("/"))
+ writer.write("/");
+ writer.write("\">");
+ writer.write(context.getContextPath());
+ if (context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
+ writer.write("&nbsp;@&nbsp;"+context.getVirtualHosts()[0]+":"+request.getLocalPort());
+ writer.write("&nbsp;--->&nbsp;");
+ writer.write(context.toString());
+ writer.write("</a></li>\n");
+ }
+ else
+ {
+ writer.write("<li>");
+ writer.write(context.getContextPath());
+ if (context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
+ writer.write("&nbsp;@&nbsp;"+context.getVirtualHosts()[0]+":"+request.getLocalPort());
+ writer.write("&nbsp;--->&nbsp;");
+ writer.write(context.toString());
+ if (context.isFailed())
+ writer.write(" [failed]");
+ if (context.isStopped())
+ writer.write(" [stopped]");
+ writer.write("</li>\n");
+ }
+ }
+
+ for (int i=0;i<10;i++)
+ writer.write("\n<!-- Padding for IE -->");
+
+ writer.write("\n</BODY>\n</HTML>\n");
+ writer.flush();
+ response.setContentLength(writer.size());
+ OutputStream out=response.getOutputStream();
+ writer.writeTo(out);
+ out.close();
+
+ return;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns true if the handle can server the jetty favicon.ico
+ */
+ public boolean getServeIcon()
+ {
+ return _serveIcon;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param serveIcon true if the handle can server the jetty favicon.ico
+ */
+ public void setServeIcon(boolean serveIcon)
+ {
+ _serveIcon = serveIcon;
+ }
+
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java
new file mode 100644
index 0000000000..cdbd35f5c2
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java
@@ -0,0 +1,175 @@
+// ========================================================================
+// Copyright (c) 1999-2009 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.handler;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.server.HttpConnection;
+import org.eclipse.jetty.util.ByteArrayISO8859Writer;
+import org.eclipse.jetty.util.StringUtil;
+
+
+/* ------------------------------------------------------------ */
+/** Handler for Error pages
+ * A handler that is registered at the org.eclipse.http.ErrorHandler
+ * context attributed and called by the HttpResponse.sendError method to write a
+ * error page.
+ *
+ *
+ */
+public class ErrorHandler extends AbstractHandler
+{
+ boolean _showStacks=true;
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+ */
+ public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException
+ {
+ HttpConnection connection = HttpConnection.getCurrentConnection();
+ connection.getRequest().setHandled(true);
+ String method = request.getMethod();
+ if(!method.equals(HttpMethods.GET) && !method.equals(HttpMethods.POST))
+ return;
+ response.setContentType(MimeTypes.TEXT_HTML_8859_1);
+ response.setHeader(HttpHeaders.CACHE_CONTROL, "must-revalidate,no-cache,no-store");
+ ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(4096);
+ handleErrorPage(request, writer, connection.getResponse().getStatus(), connection.getResponse().getReason());
+ writer.flush();
+ response.setContentLength(writer.size());
+ writer.writeTo(response.getOutputStream());
+ writer.destroy();
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void handleErrorPage(HttpServletRequest request, Writer writer, int code, String message)
+ throws IOException
+ {
+ writeErrorPage(request, writer, code, message, _showStacks);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void writeErrorPage(HttpServletRequest request, Writer writer, int code, String message, boolean showStacks)
+ throws IOException
+ {
+ if (message == null)
+ message=HttpStatus.getCode(code).getMessage();
+ else
+ {
+ message= StringUtil.replace(message, "&", "&amp;");
+ message= StringUtil.replace(message, "<", "&lt;");
+ message= StringUtil.replace(message, ">", "&gt;");
+ }
+
+ writer.write("<html>\n<head>\n");
+ writeErrorPageHead(request,writer,code,message);
+ writer.write("</head>\n<body>");
+ writeErrorPageBody(request,writer,code,message,showStacks);
+ writer.write("\n</body>\n</html>\n");
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void writeErrorPageHead(HttpServletRequest request, Writer writer, int code, String message)
+ throws IOException
+ {
+ writer.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\"/>\n");
+ writer.write("<title>Error ");
+ writer.write(Integer.toString(code));
+ writer.write(' ');
+ if (message!=null)
+ writer.write(message);
+ writer.write("</title>\n");
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void writeErrorPageBody(HttpServletRequest request, Writer writer, int code, String message, boolean showStacks)
+ throws IOException
+ {
+ String uri= request.getRequestURI();
+ if (uri!=null)
+ {
+ uri= StringUtil.replace(uri, "&", "&amp;");
+ uri= StringUtil.replace(uri, "<", "&lt;");
+ uri= StringUtil.replace(uri, ">", "&gt;");
+ }
+
+ writeErrorPageMessage(request,writer,code,message,uri);
+ if (showStacks)
+ writeErrorPageStacks(request,writer);
+ writer.write("<hr /><i><small>Powered by Jetty://</small></i>");
+ for (int i= 0; i < 20; i++)
+ writer.write("<br/> \n");
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void writeErrorPageMessage(HttpServletRequest request, Writer writer, int code, String message,String uri)
+ throws IOException
+ {
+ writer.write("<h2>HTTP ERROR ");
+ writer.write(Integer.toString(code));
+ writer.write("</h2>\n<p>Problem accessing ");
+ writer.write(uri);
+ writer.write(". Reason:\n<pre> ");
+ writer.write(message);
+ writer.write("</pre></p>");
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void writeErrorPageStacks(HttpServletRequest request, Writer writer)
+ throws IOException
+ {
+ Throwable th = (Throwable)request.getAttribute("javax.servlet.error.exception");
+ while(th!=null)
+ {
+ writer.write("<h3>Caused by:</h3><pre>");
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ th.printStackTrace(pw);
+ pw.flush();
+ writer.write(sw.getBuffer().toString());
+ writer.write("</pre>\n");
+
+ th =th.getCause();
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if stack traces are shown in the error pages
+ */
+ public boolean isShowStacks()
+ {
+ return _showStacks;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param showStacks True if stack traces are shown in the error pages
+ */
+ public void setShowStacks(boolean showStacks)
+ {
+ _showStacks = showStacks;
+ }
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java
new file mode 100644
index 0000000000..51fefd5fb6
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java
@@ -0,0 +1,220 @@
+// ========================================================================
+// Copyright (c) 2004-2009 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.handler;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.MultiException;
+
+/* ------------------------------------------------------------ */
+/** A collection of handlers.
+ * <p>
+ * For derived HandlerCollections, the order or manner of calling
+ * the contained handlers is not defined. The default implementations
+ * calls all handlers in list order, regardless of
+ * the response status or exceptions.
+ * <p>
+ *
+ *
+ * @org.apache.xbean.XBean
+ */
+public class HandlerCollection extends AbstractHandlerContainer
+{
+ private Handler[] _handlers;
+
+ /* ------------------------------------------------------------ */
+ public HandlerCollection()
+ {
+ super();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the handlers.
+ */
+ public Handler[] getHandlers()
+ {
+ return _handlers;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ *
+ * @param handlers The handlers to set.
+ */
+ public void setHandlers(Handler[] handlers)
+ {
+ Handler [] old_handlers = _handlers==null?null:(Handler[])_handlers.clone();
+
+ if (getServer()!=null)
+ getServer().getContainer().update(this, old_handlers, handlers, "handler");
+
+ Server server = getServer();
+ MultiException mex = new MultiException();
+ for (int i=0;handlers!=null && i<handlers.length;i++)
+ {
+ if (handlers[i].getServer()!=server)
+ handlers[i].setServer(server);
+ }
+
+ // quasi atomic.... so don't go doing this under load on a SMP system.
+ _handlers = handlers;
+
+ for (int i=0;old_handlers!=null && i<old_handlers.length;i++)
+ {
+ if (old_handlers[i]!=null)
+ {
+ try
+ {
+ if (old_handlers[i].isStarted())
+ old_handlers[i].stop();
+ }
+ catch (Throwable e)
+ {
+ mex.add(e);
+ }
+ }
+ }
+
+ mex.ifExceptionThrowRuntime();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.server.EventHandler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+ */
+ public void handle(String target, HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ if (_handlers!=null && isStarted())
+ {
+ MultiException mex=null;
+
+ for (int i=0;i<_handlers.length;i++)
+ {
+ try
+ {
+ _handlers[i].handle(target,request, response);
+ }
+ catch(IOException e)
+ {
+ throw e;
+ }
+ catch(RuntimeException e)
+ {
+ throw e;
+ }
+ catch(Exception e)
+ {
+ if (mex==null)
+ mex=new MultiException();
+ mex.add(e);
+ }
+ }
+ if (mex!=null)
+ {
+ if (mex.size()==1)
+ throw new ServletException(mex.getThrowable(0));
+ else
+ throw new ServletException(mex);
+ }
+
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.server.handler.AbstractHandler#doStart()
+ */
+ protected void doStart() throws Exception
+ {
+ MultiException mex=new MultiException();
+ if (_handlers!=null)
+ {
+ for (int i=0;i<_handlers.length;i++)
+ try{_handlers[i].start();}catch(Throwable e){mex.add(e);}
+ }
+ super.doStart();
+ mex.ifExceptionThrow();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.server.handler.AbstractHandler#doStop()
+ */
+ protected void doStop() throws Exception
+ {
+ MultiException mex=new MultiException();
+ try { super.doStop(); } catch(Throwable e){mex.add(e);}
+ if (_handlers!=null)
+ {
+ for (int i=_handlers.length;i-->0;)
+ try{_handlers[i].stop();}catch(Throwable e){mex.add(e);}
+ }
+ mex.ifExceptionThrow();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setServer(Server server)
+ {
+ Server old_server=getServer();
+
+ super.setServer(server);
+
+ Handler[] h=getHandlers();
+ for (int i=0;h!=null && i<h.length;i++)
+ h[i].setServer(server);
+
+ if (server!=null && server!=old_server)
+ server.getContainer().update(this, null,_handlers, "handler");
+
+ }
+
+ /* ------------------------------------------------------------ */
+ /* Add a handler.
+ * This implementation adds the passed handler to the end of the existing collection of handlers.
+ * @see org.eclipse.jetty.server.server.HandlerContainer#addHandler(org.eclipse.jetty.server.server.Handler)
+ */
+ public void addHandler(Handler handler)
+ {
+ setHandlers((Handler[])LazyList.addToArray(getHandlers(), handler, Handler.class));
+ }
+
+ /* ------------------------------------------------------------ */
+ public void removeHandler(Handler handler)
+ {
+ Handler[] handlers = getHandlers();
+
+ if (handlers!=null && handlers.length>0 )
+ setHandlers((Handler[])LazyList.removeFromArray(handlers, handler));
+ }
+
+ /* ------------------------------------------------------------ */
+ protected Object expandChildren(Object list, Class byClass)
+ {
+ Handler[] handlers = getHandlers();
+ for (int i=0;handlers!=null && i<handlers.length;i++)
+ list=expandHandler(handlers[i], list, byClass);
+ return list;
+ }
+
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerList.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerList.java
new file mode 100644
index 0000000000..e8c792400c
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerList.java
@@ -0,0 +1,54 @@
+// ========================================================================
+// Copyright (c) 2006-2009 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.handler;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConnection;
+import org.eclipse.jetty.server.Request;
+
+/* ------------------------------------------------------------ */
+/** HandlerList.
+ * This extension of {@link org.eclipse.jetty.server.server.handler.HandlerCollection} will call
+ * each contained handler in turn until either an exception is thrown, the response
+ * is committed or a positive response status is set.
+ */
+public class HandlerList extends HandlerCollection
+{
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.server.EventHandler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+ */
+ public void handle(String target, HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ Handler[] handlers = getHandlers();
+
+ if (handlers!=null && isStarted())
+ {
+ Request base_request = HttpConnection.getCurrentConnection().getRequest();
+ for (int i=0;i<handlers.length;i++)
+ {
+ handlers[i].handle(target,request, response);
+ if ( base_request.isHandled())
+ return;
+ }
+ }
+ }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java
new file mode 100644
index 0000000000..63021d814d
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java
@@ -0,0 +1,179 @@
+// ========================================================================
+// Copyright (c) 2004-2009 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.handler;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HandlerContainer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/* ------------------------------------------------------------ */
+/** A <code>HandlerWrapper</code> acts as a {@link Handler} but delegates the {@link Handler#handle handle} method and
+ * {@link LifeCycle life cycle} events to a delegate. This is primarily used to implement the <i>Decorator</i> pattern.
+ *
+ */
+public class HandlerWrapper extends AbstractHandlerContainer
+{
+ private Handler _handler;
+
+ /* ------------------------------------------------------------ */
+ /**
+ *
+ */
+ public HandlerWrapper()
+ {
+ super();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the handlers.
+ */
+ public Handler getHandler()
+ {
+ return _handler;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param handler Set the {@link Handler} which should be wrapped.
+ */
+ public void setHandler(Handler handler)
+ {
+ try
+ {
+ Handler old_handler = _handler;
+
+ if (getServer()!=null)
+ getServer().getContainer().update(this, old_handler, handler, "handler");
+
+ if (handler!=null)
+ {
+ handler.setServer(getServer());
+ }
+
+ _handler = handler;
+
+ if (old_handler!=null)
+ {
+ if (old_handler.isStarted())
+ old_handler.stop();
+ }
+ }
+ catch(Exception e)
+ {
+ IllegalStateException ise= new IllegalStateException();
+ ise.initCause(e);
+ throw ise;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Add a handler.
+ * This implementation of addHandler calls setHandler with the
+ * passed handler. If this HandlerWrapper had a previous wrapped
+ * handler, then it is passed to a call to addHandler on the passed
+ * handler. Thus this call can add a handler in a chain of
+ * wrapped handlers.
+ *
+ * @param handler
+ */
+ public void addHandler(Handler handler)
+ {
+ Handler old = getHandler();
+ if (old!=null && !(handler instanceof HandlerContainer))
+ throw new IllegalArgumentException("Cannot add");
+ setHandler(handler);
+ if (old!=null)
+ ((HandlerContainer)handler).addHandler(old);
+ }
+
+
+ public void removeHandler (Handler handler)
+ {
+ Handler old = getHandler();
+ if (old!=null && (old instanceof HandlerContainer))
+ ((HandlerContainer)old).removeHandler(handler);
+ else if (old!=null && handler==old)
+ setHandler(null);
+ else
+ throw new IllegalStateException("Cannot remove");
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.thread.AbstractLifeCycle#doStart()
+ */
+ protected void doStart() throws Exception
+ {
+ if (_handler!=null)
+ _handler.start();
+ super.doStart();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.thread.AbstractLifeCycle#doStop()
+ */
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ if (_handler!=null)
+ _handler.stop();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.server.EventHandler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+ */
+ public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ if (_handler!=null && isStarted())
+ {
+ _handler.handle(target,request, response);
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public void setServer(Server server)
+ {
+ Server old_server=getServer();
+
+ super.setServer(server);
+
+ Handler h=getHandler();
+ if (h!=null)
+ h.setServer(server);
+
+ if (server!=null && server!=old_server)
+ server.getContainer().update(this, null,_handler, "handler");
+ }
+
+
+ /* ------------------------------------------------------------ */
+ protected Object expandChildren(Object list, Class byClass)
+ {
+ return expandHandler(_handler,list,byClass);
+ }
+
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/MovedContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/MovedContextHandler.java
new file mode 100644
index 0000000000..8854a5c6e5
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/MovedContextHandler.java
@@ -0,0 +1,158 @@
+// ========================================================================
+// Copyright (c) 2006-2009 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.handler;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.server.HandlerContainer;
+import org.eclipse.jetty.server.HttpConnection;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.URIUtil;
+
+/* ------------------------------------------------------------ */
+/** Moved ContextHandler.
+ * This context can be used to replace a context that has changed
+ * location. Requests are redirected (either to a fixed URL or to a
+ * new context base).
+ */
+public class MovedContextHandler extends ContextHandler
+{
+ String _newContextURL;
+ boolean _discardPathInfo;
+ boolean _discardQuery;
+ boolean _permanent;
+ Redirector _redirector;
+ String _expires;
+
+ public MovedContextHandler()
+ {
+ _redirector=new Redirector();
+ addHandler(_redirector);
+ setAllowNullPathInfo(true);
+ }
+
+ public MovedContextHandler(HandlerContainer parent, String contextPath, String newContextURL)
+ {
+ super(parent,contextPath);
+ _newContextURL=newContextURL;
+ _redirector=new Redirector();
+ addHandler(_redirector);
+ }
+
+ public boolean isDiscardPathInfo()
+ {
+ return _discardPathInfo;
+ }
+
+ public void setDiscardPathInfo(boolean discardPathInfo)
+ {
+ _discardPathInfo = discardPathInfo;
+ }
+
+ public String getNewContextURL()
+ {
+ return _newContextURL;
+ }
+
+ public void setNewContextURL(String newContextURL)
+ {
+ _newContextURL = newContextURL;
+ }
+
+ public boolean isPermanent()
+ {
+ return _permanent;
+ }
+
+ public void setPermanent(boolean permanent)
+ {
+ _permanent = permanent;
+ }
+
+ public boolean isDiscardQuery()
+ {
+ return _discardQuery;
+ }
+
+ public void setDiscardQuery(boolean discardQuery)
+ {
+ _discardQuery = discardQuery;
+ }
+
+ private class Redirector extends AbstractHandler
+ {
+ public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ if (_newContextURL==null)
+ return;
+
+ Request base_request=(request instanceof Request)?(Request)request:HttpConnection.getCurrentConnection().getRequest();
+
+ String url = _newContextURL;
+ if (!_discardPathInfo && request.getPathInfo()!=null)
+ url=URIUtil.addPaths(url, request.getPathInfo());
+ if (!_discardQuery && request.getQueryString()!=null)
+ url+="?"+request.getQueryString();
+
+ response.sendRedirect(url);
+
+ String path=_newContextURL;
+ if (!_discardPathInfo && request.getPathInfo()!=null)
+ path=URIUtil.addPaths(path, request.getPathInfo());
+
+ StringBuilder location = URIUtil.hasScheme(path)?new StringBuilder():base_request.getRootURL();
+
+ location.append(path);
+ if (!_discardQuery && request.getQueryString()!=null)
+ {
+ location.append('?');
+ location.append(request.getQueryString());
+ }
+
+ response.setHeader(HttpHeaders.LOCATION,location.toString());
+
+ if (_expires!=null)
+ response.setHeader(HttpHeaders.EXPIRES,_expires);
+
+ response.setStatus(_permanent?HttpServletResponse.SC_MOVED_PERMANENTLY:HttpServletResponse.SC_FOUND);
+ response.setContentLength(0);
+ base_request.setHandled(true);
+ }
+
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the expires header value or null if no expires header
+ */
+ public String getExpires()
+ {
+ return _expires;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param expires the expires header value or null if no expires header
+ */
+ public void setExpires(String expires)
+ {
+ _expires = expires;
+ }
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/RequestLogHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/RequestLogHandler.java
new file mode 100644
index 0000000000..bdfebd379a
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/RequestLogHandler.java
@@ -0,0 +1,132 @@
+// ========================================================================
+// Copyright (c) 2006-2009 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.handler;
+
+import java.io.IOException;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.RequestLog;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.log.Log;
+
+
+
+/**
+ * RequestLogHandler.
+ * This handler can be used to wrap an individual context for context logging.
+ *
+ *
+ * @org.apache.xbean.XBean
+ */
+public class RequestLogHandler extends HandlerWrapper
+{
+ private RequestLog _requestLog;
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.server.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+ */
+ public void handle(String target, HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ super.handle(target, request, response);
+ if (DispatcherType.REQUEST.equals(request.getDispatcherType()) && _requestLog!=null)
+ _requestLog.log((Request)request, (Response)response);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setRequestLog(RequestLog requestLog)
+ {
+ //are we changing the request log impl?
+ try
+ {
+ if (_requestLog != null)
+ _requestLog.stop();
+ }
+ catch (Exception e)
+ {
+ Log.warn (e);
+ }
+
+ if (getServer()!=null)
+ getServer().getContainer().update(this, _requestLog, requestLog, "logimpl",true);
+
+ _requestLog = requestLog;
+
+ //if we're already started, then start our request log
+ try
+ {
+ if (isStarted() && (_requestLog != null))
+ _requestLog.start();
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException (e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.server.handler.HandlerWrapper#setServer(org.eclipse.jetty.server.server.Server)
+ */
+ public void setServer(Server server)
+ {
+ if (_requestLog!=null)
+ {
+ if (getServer()!=null && getServer()!=server)
+ getServer().getContainer().update(this, _requestLog, null, "logimpl",true);
+ super.setServer(server);
+ if (server!=null && server!=getServer())
+ server.getContainer().update(this, null,_requestLog, "logimpl",true);
+ }
+ else
+ super.setServer(server);
+ }
+
+ /* ------------------------------------------------------------ */
+ public RequestLog getRequestLog()
+ {
+ return _requestLog;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.server.handler.HandlerWrapper#doStart()
+ */
+ protected void doStart() throws Exception
+ {
+ super.doStart();
+ if (_requestLog!=null)
+ _requestLog.start();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.server.handler.HandlerWrapper#doStop()
+ */
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ if (_requestLog!=null)
+ _requestLog.stop();
+ }
+
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java
new file mode 100644
index 0000000000..73744ccd11
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java
@@ -0,0 +1,335 @@
+// ========================================================================
+// Copyright (c) 1999-2009 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.handler;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.io.WriterOutputStream;
+import org.eclipse.jetty.server.HttpConnection;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.resource.Resource;
+
+
+/* ------------------------------------------------------------ */
+/** Resource Handler.
+ *
+ * This handle will serve static content and handle If-Modified-Since headers.
+ * No caching is done.
+ * Requests that cannot be handled are let pass (Eg no 404's)
+ *
+ *
+ * @org.apache.xbean.XBean
+ */
+public class ResourceHandler extends AbstractHandler
+{
+ ContextHandler _context;
+ Resource _baseResource;
+ String[] _welcomeFiles={"index.html"};
+ MimeTypes _mimeTypes = new MimeTypes();
+ ByteArrayBuffer _cacheControl;
+
+ /* ------------------------------------------------------------ */
+ public ResourceHandler()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public MimeTypes getMimeTypes()
+ {
+ return _mimeTypes;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setMimeTypes(MimeTypes mimeTypes)
+ {
+ _mimeTypes = mimeTypes;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void doStart()
+ throws Exception
+ {
+ Context scontext = ContextHandler.getCurrentContext();
+ _context = (scontext==null?null:scontext.getContextHandler());
+ super.doStart();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the resourceBase.
+ */
+ public Resource getBaseResource()
+ {
+ if (_baseResource==null)
+ return null;
+ return _baseResource;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the base resource as a string.
+ */
+ public String getResourceBase()
+ {
+ if (_baseResource==null)
+ return null;
+ return _baseResource.toString();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param base The resourceBase to set.
+ */
+ public void setBaseResource(Resource base)
+ {
+ _baseResource=base;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param resourceBase The base resource as a string.
+ */
+ public void setResourceBase(String resourceBase)
+ {
+ try
+ {
+ setBaseResource(Resource.newResource(resourceBase));
+ }
+ catch (Exception e)
+ {
+ Log.warn(e);
+ throw new IllegalArgumentException(resourceBase);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the cacheControl header to set on all static content.
+ */
+ public String getCacheControl()
+ {
+ return _cacheControl.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param cacheControl the cacheControl header to set on all static content.
+ */
+ public void setCacheControl(String cacheControl)
+ {
+ _cacheControl=cacheControl==null?null:new ByteArrayBuffer(cacheControl);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ */
+ public Resource getResource(String path) throws MalformedURLException
+ {
+ if (path==null || !path.startsWith("/"))
+ throw new MalformedURLException(path);
+
+ Resource base = _baseResource;
+ if (base==null)
+ {
+ if (_context==null)
+ return null;
+ base=_context.getBaseResource();
+ if (base==null)
+ return null;
+ }
+
+ try
+ {
+ path=URIUtil.canonicalPath(path);
+ Resource resource=base.addPath(path);
+ return resource;
+ }
+ catch(Exception e)
+ {
+ Log.ignore(e);
+ }
+
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected Resource getResource(HttpServletRequest request) throws MalformedURLException
+ {
+ String path_info=request.getPathInfo();
+ if (path_info==null)
+ return null;
+ return getResource(path_info);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public String[] getWelcomeFiles()
+ {
+ return _welcomeFiles;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setWelcomeFiles(String[] welcomeFiles)
+ {
+ _welcomeFiles=welcomeFiles;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected Resource getWelcome(Resource directory) throws MalformedURLException, IOException
+ {
+ for (int i=0;i<_welcomeFiles.length;i++)
+ {
+ Resource welcome=directory.addPath(_welcomeFiles[i]);
+ if (welcome.exists() && !welcome.isDirectory())
+ return welcome;
+ }
+
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+ */
+ public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ Request base_request = request instanceof Request?(Request)request:HttpConnection.getCurrentConnection().getRequest();
+ if (base_request.isHandled())
+ return;
+
+ boolean skipContentBody = false;
+ if(!HttpMethods.GET.equals(request.getMethod()))
+ {
+ if(!HttpMethods.HEAD.equals(request.getMethod()))
+ return;
+ skipContentBody = true;
+ }
+
+ Resource resource=getResource(request);
+
+ if (resource==null || !resource.exists())
+ return;
+
+ // We are going to server something
+ base_request.setHandled(true);
+
+ if (resource.isDirectory())
+ {
+ if (!request.getPathInfo().endsWith(URIUtil.SLASH))
+ {
+ response.sendRedirect(URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH));
+ return;
+ }
+ resource=getWelcome(resource);
+
+ if (resource==null || !resource.exists() || resource.isDirectory())
+ {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+ }
+
+ // set some headers
+ long last_modified=resource.lastModified();
+ if (last_modified>0)
+ {
+ long if_modified=request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE);
+ if (if_modified>0 && last_modified/1000<=if_modified/1000)
+ {
+ response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+ return;
+ }
+ }
+
+ Buffer mime=_mimeTypes.getMimeByExtension(resource.toString());
+ if (mime==null)
+ mime=_mimeTypes.getMimeByExtension(request.getPathInfo());
+
+ // set the headers
+ doResponseHeaders(response,resource,mime!=null?mime.toString():null);
+ response.setDateHeader(HttpHeaders.LAST_MODIFIED,last_modified);
+ if(skipContentBody)
+ return;
+ // Send the content
+ OutputStream out =null;
+ try {out = response.getOutputStream();}
+ catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());}
+
+ // See if a short direct method can be used?
+ if (out instanceof HttpConnection.Output)
+ {
+ // TODO file mapped buffers
+ ((HttpConnection.Output)out).sendContent(resource.getInputStream());
+ }
+ else
+ {
+ // Write content normally
+ resource.writeTo(out,0,resource.length());
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the response headers.
+ * This method is called to set the response headers such as content type and content length.
+ * May be extended to add additional headers.
+ * @param response
+ * @param resource
+ * @param mimeType
+ */
+ protected void doResponseHeaders(HttpServletResponse response, Resource resource, String mimeType)
+ {
+ if (mimeType!=null)
+ response.setContentType(mimeType);
+
+ long length=resource.length();
+
+ if (response instanceof Response)
+ {
+ HttpFields fields = ((Response)response).getHttpFields();
+
+ if (length>0)
+ fields.putLongField(HttpHeaders.CONTENT_LENGTH_BUFFER,length);
+
+ if (_cacheControl!=null)
+ fields.put(HttpHeaders.CACHE_CONTROL_BUFFER,_cacheControl);
+ }
+ else
+ {
+ if (length>0)
+ response.setHeader(HttpHeaders.CONTENT_LENGTH,TypeUtil.toString(length));
+
+ if (_cacheControl!=null)
+ response.setHeader(HttpHeaders.CACHE_CONTROL,_cacheControl.toString());
+ }
+
+ }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java
new file mode 100644
index 0000000000..1aa6c6b00a
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java
@@ -0,0 +1,369 @@
+// ========================================================================
+// Copyright (c) 2006-2009 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.handler;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.AsyncRequest;
+import org.eclipse.jetty.server.HttpConnection;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.util.LazyList;
+
+public class StatisticsHandler extends HandlerWrapper implements CompleteHandler
+{
+ transient long _statsStartedAt;
+
+ transient int _requests;
+
+ transient long _requestsDurationMin; // min request duration
+ transient long _requestsDurationMax; // max request duration
+ transient long _requestsDurationTotal; // total request duration
+ transient long _requestsActiveDurationMin; // min request active duration
+ transient long _requestsActiveDurationMax; // max request active duration
+ transient long _requestsActiveDurationTotal; // total request active duration
+
+ transient int _requestsActive;
+ transient int _requestsActiveMin; // min number of connections handled simultaneously
+ transient int _requestsActiveMax;
+ transient int _requestsResumed;
+ transient int _requestsTimedout; // requests that timed out while suspended
+ transient int _responses1xx; // Informal
+ transient int _responses2xx; // Success
+ transient int _responses3xx; // Redirection
+ transient int _responses4xx; // Client Error
+ transient int _responses5xx; // Server Error
+
+ transient long _responsesBytesTotal;
+
+ /* ------------------------------------------------------------ */
+ public void statsReset()
+ {
+ synchronized(this)
+ {
+ if (isStarted())
+ _statsStartedAt=System.currentTimeMillis();
+ _requests=0;
+ _responses1xx=0;
+ _responses2xx=0;
+ _responses3xx=0;
+ _responses4xx=0;
+ _responses5xx=0;
+
+ _requestsActiveMin=_requestsActive;
+ _requestsActiveMax=_requestsActive;
+
+ _requestsDurationMin=0;
+ _requestsDurationMax=0;
+ _requestsDurationTotal=0;
+
+ _requestsActiveDurationMin=0;
+ _requestsActiveDurationMax=0;
+ _requestsActiveDurationTotal=0;
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ final Request base_request=(request instanceof Request)?((Request)request):HttpConnection.getCurrentConnection().getRequest();
+ final Response base_response=(response instanceof Response)?((Response)response):HttpConnection.getCurrentConnection().getResponse();
+
+ long timestamp0=base_request.getTimeStamp();
+ long timestamp1=timestamp0;
+ try
+ {
+ synchronized(this)
+ {
+ AsyncRequest asyncContextState=base_request.getAsyncRequest();
+
+ if(asyncContextState==null)
+ {
+ _requests++;
+ }
+ else
+ {
+ if(asyncContextState.isInitial())
+ _requests++;
+ else
+ {
+ timestamp1=System.currentTimeMillis();
+ /*
+ if (asyncContextState.isTimeout())
+ _requestsTimedout++;
+ if(asyncContextState.isResumed())
+ _requestsResumed++;
+ */
+ }
+ }
+
+ _requestsActive++;
+ if (_requestsActive>_requestsActiveMax)
+ _requestsActiveMax=_requestsActive;
+ }
+
+ super.handle(target, request, response);
+ }
+ finally
+ {
+ synchronized(this)
+ {
+ _requestsActive--;
+ if (_requestsActive<0)
+ _requestsActive=0;
+ if (_requestsActive < _requestsActiveMin)
+ _requestsActiveMin=_requestsActive;
+
+ long duration = System.currentTimeMillis()-timestamp1;
+ _requestsActiveDurationTotal+=duration;
+ if (_requestsActiveDurationMin==0 || duration<_requestsActiveDurationMin)
+ _requestsActiveDurationMin=duration;
+ if (duration>_requestsActiveDurationMax)
+ _requestsActiveDurationMax=duration;
+
+
+ if(request.isAsyncStarted())
+ {
+ Object list = base_request.getAttribute(COMPLETE_HANDLER_ATTR);
+ base_request.setAttribute(COMPLETE_HANDLER_ATTR, LazyList.add(list, this));
+ }
+ else
+ {
+ duration = System.currentTimeMillis()-timestamp0;
+ addRequestsDurationTotal(duration);
+
+ switch(base_response.getStatus()/100)
+ {
+ case 1: _responses1xx++;break;
+ case 2: _responses2xx++;break;
+ case 3: _responses3xx++;break;
+ case 4: _responses4xx++;break;
+ case 5: _responses5xx++;break;
+ }
+
+ _responsesBytesTotal += base_response.getContentCount();
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void doStart() throws Exception
+ {
+ super.doStart();
+ _statsStartedAt=System.currentTimeMillis();
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Get the number of requests handled by this context
+ * since last call of statsReset(), not counting resumed requests.
+ * If setStatsOn(false) then this is undefined.
+ */
+ public int getRequests() {return _requests;}
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Number of requests currently active.
+ * Undefined if setStatsOn(false).
+ */
+ public int getRequestsActive() {return _requestsActive;}
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Number of requests that have been resumed.
+ * Undefined if setStatsOn(false).
+ */
+ public int getRequestsResumed() {return _requestsResumed;}
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Number of requests that timed out while suspended.
+ * Undefined if setStatsOn(false).
+ */
+ public int getRequestsTimedout() {return _requestsTimedout;}
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Maximum number of active requests
+ * since statsReset() called. Undefined if setStatsOn(false).
+ */
+ public int getRequestsActiveMax() {return _requestsActiveMax;}
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Get the number of responses with a 2xx status returned
+ * by this context since last call of statsReset(). Undefined if
+ * if setStatsOn(false).
+ */
+ public int getResponses1xx() {return _responses1xx;}
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Get the number of responses with a 100 status returned
+ * by this context since last call of statsReset(). Undefined if
+ * if setStatsOn(false).
+ */
+ public int getResponses2xx() {return _responses2xx;}
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Get the number of responses with a 3xx status returned
+ * by this context since last call of statsReset(). Undefined if
+ * if setStatsOn(false).
+ */
+ public int getResponses3xx() {return _responses3xx;}
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Get the number of responses with a 4xx status returned
+ * by this context since last call of statsReset(). Undefined if
+ * if setStatsOn(false).
+ */
+ public int getResponses4xx() {return _responses4xx;}
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Get the number of responses with a 5xx status returned
+ * by this context since last call of statsReset(). Undefined if
+ * if setStatsOn(false).
+ */
+ public int getResponses5xx() {return _responses5xx;}
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Timestamp stats were started at.
+ */
+ public long getStatsOnMs()
+ {
+ return System.currentTimeMillis()-_statsStartedAt;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the requestsActiveMin.
+ */
+ public int getRequestsActiveMin()
+ {
+ return _requestsActiveMin;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the requestsDurationMin.
+ */
+ public long getRequestsDurationMin()
+ {
+ return _requestsDurationMin;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the requestsDurationTotal.
+ */
+ public long getRequestsDurationTotal()
+ {
+ return _requestsDurationTotal;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Average duration of request handling in milliseconds
+ * since statsReset() called. Undefined if setStatsOn(false).
+ */
+ public long getRequestsDurationAve() {return _requests==0?0:(_requestsDurationTotal/_requests);}
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Get maximum duration in milliseconds of request handling
+ * since statsReset() called. Undefined if setStatsOn(false).
+ */
+ public long getRequestsDurationMax() {return _requestsDurationMax;}
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the requestsActiveDurationMin.
+ */
+ public long getRequestsActiveDurationMin()
+ {
+ return _requestsActiveDurationMin;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the requestsActiveDurationTotal.
+ */
+ public long getRequestsActiveDurationTotal()
+ {
+ return _requestsActiveDurationTotal;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Average duration of request handling in milliseconds
+ * since statsReset() called. Undefined if setStatsOn(false).
+ */
+ public long getRequestsActiveDurationAve() {return _requests==0?0:(_requestsActiveDurationTotal/_requests);}
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Get maximum duration in milliseconds of request handling
+ * since statsReset() called. Undefined if setStatsOn(false).
+ */
+ public long getRequestsActiveDurationMax() {return _requestsActiveDurationMax;}
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Total bytes of content sent in responses
+ */
+ public long getResponsesBytesTotal() {return _responsesBytesTotal; }
+
+ private void addRequestsDurationTotal(long duration)
+ {
+ synchronized(this)
+ {
+ _requestsDurationTotal+=duration;
+ if (_requestsDurationMin==0 || duration<_requestsDurationMin)
+ _requestsDurationMin=duration;
+ if (duration>_requestsDurationMax)
+ _requestsDurationMax=duration;
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Handle completed requests.
+ *
+ * @param request
+ * the request which has just completed
+ */
+ public void complete(Request request)
+ {
+ long duration = System.currentTimeMillis() - request.getTimeStamp();
+ addRequestsDurationTotal(duration);
+ }
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/AbstractNIOConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/AbstractNIOConnector.java
new file mode 100644
index 0000000000..6876144b71
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/AbstractNIOConnector.java
@@ -0,0 +1,77 @@
+// ========================================================================
+// Copyright (c) 2004-2009 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.nio;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.nio.DirectNIOBuffer;
+import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
+import org.eclipse.jetty.io.nio.NIOBuffer;
+import org.eclipse.jetty.server.AbstractConnector;
+
+/* ------------------------------------------------------------ */
+/**
+ *
+ *
+ */
+public abstract class AbstractNIOConnector extends AbstractConnector implements NIOConnector
+{
+ private boolean _useDirectBuffers=true;
+
+ /* ------------------------------------------------------------------------------- */
+ public boolean getUseDirectBuffers()
+ {
+ return _useDirectBuffers;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /**
+ * @param direct If True (the default), the connector can use NIO direct buffers.
+ * Some JVMs have memory management issues (bugs) with direct buffers.
+ */
+ public void setUseDirectBuffers(boolean direct)
+ {
+ _useDirectBuffers=direct;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public Buffer newBuffer(int size)
+ {
+ // TODO
+ // Header buffers always byte array buffers (efficiency of random access)
+ // There are lots of things to consider here... DIRECT buffers are faster to
+ // send but more expensive to build and access! so we have choices to make...
+ // + headers are constructed bit by bit and parsed bit by bit, so INDiRECT looks
+ // good for them.
+ // + but will a gather write of an INDIRECT header with a DIRECT body be any good?
+ // this needs to be benchmarked.
+ // + Will it be possible to get a DIRECT header buffer just for the gather writes of
+ // content from file mapped buffers?
+ // + Are gather writes worth the effort? Maybe they will work well with two INDIRECT
+ // buffers being copied into a single kernel buffer?
+ //
+ Buffer buf = null;
+ if (size==getHeaderBufferSize())
+ buf= new IndirectNIOBuffer(size);
+ else
+ buf = _useDirectBuffers
+ ?(NIOBuffer)new DirectNIOBuffer(size)
+ :(NIOBuffer)new IndirectNIOBuffer(size);
+ return buf;
+ }
+
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/BlockingChannelConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/BlockingChannelConnector.java
new file mode 100644
index 0000000000..393bfef14d
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/BlockingChannelConnector.java
@@ -0,0 +1,189 @@
+// ========================================================================
+// Copyright (c) 2003-2009 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.nio;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.nio.channels.ByteChannel;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.io.HttpException;
+import org.eclipse.jetty.io.nio.ChannelEndPoint;
+import org.eclipse.jetty.server.HttpConnection;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.log.Log;
+
+
+/* ------------------------------------------------------------------------------- */
+/** Blocking NIO connector.
+ * This connector uses efficient NIO buffers with a traditional blocking thread model.
+ * Direct NIO buffers are used and a thread is allocated per connections.
+ *
+ * This connector is best used when there are a few very active connections.
+ *
+ * @org.apache.xbean.XBean element="blockingNioConnector" description="Creates a blocking NIO based socket connector"
+ *
+ *
+ *
+ */
+public class BlockingChannelConnector extends AbstractNIOConnector
+{
+ private transient ServerSocketChannel _acceptChannel;
+
+ /* ------------------------------------------------------------ */
+ /** Constructor.
+ *
+ */
+ public BlockingChannelConnector()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public Object getConnection()
+ {
+ return _acceptChannel;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void open() throws IOException
+ {
+ // Create a new server socket and set to non blocking mode
+ _acceptChannel= ServerSocketChannel.open();
+ _acceptChannel.configureBlocking(true);
+
+ // Bind the server socket to the local host and port
+ InetSocketAddress addr = getHost()==null?new InetSocketAddress(getPort()):new InetSocketAddress(getHost(),getPort());
+ _acceptChannel.socket().bind(addr,getAcceptQueueSize());
+ }
+
+ /* ------------------------------------------------------------ */
+ public void close() throws IOException
+ {
+ if (_acceptChannel != null)
+ _acceptChannel.close();
+ _acceptChannel=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void accept(int acceptorID)
+ throws IOException, InterruptedException
+ {
+ SocketChannel channel = _acceptChannel.accept();
+ channel.configureBlocking(true);
+ Socket socket=channel.socket();
+ configure(socket);
+
+ Connection connection=new Connection(channel);
+ connection.dispatch();
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public void customize(EndPoint endpoint, Request request)
+ throws IOException
+ {
+ Connection connection = (Connection)endpoint;
+ if (connection._sotimeout!=_maxIdleTime)
+ {
+ connection._sotimeout=_maxIdleTime;
+ ((SocketChannel)endpoint.getTransport()).socket().setSoTimeout(_maxIdleTime);
+ }
+
+ super.customize(endpoint, request);
+ configure(((SocketChannel)endpoint.getTransport()).socket());
+ }
+
+
+ /* ------------------------------------------------------------------------------- */
+ public int getLocalPort()
+ {
+ if (_acceptChannel==null || !_acceptChannel.isOpen())
+ return -1;
+ return _acceptChannel.socket().getLocalPort();
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /* ------------------------------------------------------------------------------- */
+ /* ------------------------------------------------------------------------------- */
+ private class Connection extends ChannelEndPoint implements Runnable
+ {
+ boolean _dispatched=false;
+ HttpConnection _connection;
+ int _sotimeout;
+
+ Connection(ByteChannel channel)
+ {
+ super(channel);
+ _connection = new HttpConnection(BlockingChannelConnector.this,this,getServer());
+ }
+
+ void dispatch() throws IOException
+ {
+ if (!getThreadPool().dispatch(this))
+ {
+ Log.warn("dispatch failed for {}",_connection);
+ close();
+ }
+ }
+
+ public void run()
+ {
+ try
+ {
+ connectionOpened(_connection);
+
+ while (isOpen())
+ {
+ if (_connection.isIdle())
+ {
+ if (getServer().getThreadPool().isLowOnThreads())
+ {
+ if (_sotimeout!=getLowResourceMaxIdleTime())
+ {
+ _sotimeout=getLowResourceMaxIdleTime();
+ ((SocketChannel)getTransport()).socket().setSoTimeout(_sotimeout);
+ }
+ }
+ }
+ _connection.handle();
+ }
+ }
+ catch (EofException e)
+ {
+ Log.debug("EOF", e);
+ try{close();}
+ catch(IOException e2){Log.ignore(e2);}
+ }
+ catch (HttpException e)
+ {
+ Log.debug("BAD", e);
+ try{close();}
+ catch(IOException e2){Log.ignore(e2);}
+ }
+ catch(Throwable e)
+ {
+ Log.warn("handle failed",e);
+ try{close();}
+ catch(IOException e2){Log.ignore(e2);}
+ }
+ finally
+ {
+ connectionClosed(_connection);
+ }
+ }
+ }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/InheritedChannelConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/InheritedChannelConnector.java
new file mode 100644
index 0000000000..fca291ba7e
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/InheritedChannelConnector.java
@@ -0,0 +1,66 @@
+// ========================================================================
+// Copyright (c) 2008-2009 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.nio;
+
+import java.io.IOException;
+import java.nio.channels.Channel;
+import java.nio.channels.ServerSocketChannel;
+
+import org.eclipse.jetty.util.log.Log;
+
+/**
+ * An implementation of the SelectChannelConnector which first tries to
+ * inherit from a channel provided by the system. If there is no inherited
+ * channel available, or if the inherited channel provided not usable, then
+ * it will fall back upon normal ServerSocketChannel creation.
+ * <p>
+ * Note that System.inheritedChannel() is only available from Java 1.5 onwards.
+ * Trying to use this class under Java 1.4 will be the same as using a normal
+ * SelectChannelConnector.
+ * <p>
+ * Use it with xinetd/inetd, to launch an instance of Jetty on demand. The port
+ * used to access pages on the Jetty instance is the same as the port used to
+ * launch Jetty.
+ *
+ * @author athena
+ */
+public class InheritedChannelConnector extends SelectChannelConnector
+{
+ /* ------------------------------------------------------------ */
+ public void open() throws IOException
+ {
+ synchronized(this)
+ {
+ try
+ {
+ Channel channel = System.inheritedChannel();
+ if ( channel instanceof ServerSocketChannel )
+ _acceptChannel = (ServerSocketChannel)channel;
+ else
+ Log.warn("Unable to use System.inheritedChannel() [" +channel+ "]. Trying a new ServerSocketChannel at " + getHost() + ":" + getPort());
+
+ if ( _acceptChannel != null )
+ _acceptChannel.configureBlocking(false);
+ }
+ catch(NoSuchMethodError e)
+ {
+ Log.warn("Need at least Java 5 to use socket inherited from xinetd/inetd.");
+ }
+
+ if (_acceptChannel == null)
+ super.open();
+ }
+ }
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/NIOConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/NIOConnector.java
new file mode 100644
index 0000000000..16d27566fa
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/NIOConnector.java
@@ -0,0 +1,26 @@
+// ========================================================================
+// Copyright (c) 2006-2009 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.nio;
+
+/**
+ * NIOConnector.
+ * A marker interface that indicates that NIOBuffers can be handled (efficiently) by this Connector.
+ *
+ *
+ *
+ */
+public interface NIOConnector
+{
+ boolean getUseDirectBuffers();
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/SelectChannelConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/SelectChannelConnector.java
new file mode 100644
index 0000000000..14ad86adee
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/SelectChannelConnector.java
@@ -0,0 +1,328 @@
+// ========================================================================
+// Copyright (c) 2003-2009 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.nio;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
+import org.eclipse.jetty.io.nio.SelectorManager;
+import org.eclipse.jetty.io.nio.SelectorManager.SelectSet;
+import org.eclipse.jetty.server.HttpConnection;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.thread.Timeout.Task;
+
+/* ------------------------------------------------------------------------------- */
+/**
+ * Selecting NIO connector.
+ * <p>
+ * This connector uses efficient NIO buffers with a non blocking threading model. Direct NIO buffers
+ * are used and threads are only allocated to connections with requests. Synchronization is used to
+ * simulate blocking for the servlet API, and any unflushed content at the end of request handling
+ * is written asynchronously.
+ * </p>
+ * <p>
+ * This connector is best used when there are a many connections that have idle periods.
+ * </p>
+ * <p>
+ * When used with {@link org.eclipse.jetty.util.ajax.Continuation}, threadless waits are supported. When
+ * a filter or servlet calls getEvent on a Continuation, a {@link org.eclipse.jetty.server.RetryRequest}
+ * runtime exception is thrown to allow the thread to exit the current request handling. Jetty will
+ * catch this exception and will not send a response to the client. Instead the thread is released
+ * and the Continuation is placed on the timer queue. If the Continuation timeout expires, or it's
+ * resume method is called, then the request is again allocated a thread and the request is retried.
+ * The limitation of this approach is that request content is not available on the retried request,
+ * thus if possible it should be read after the continuation or saved as a request attribute or as the
+ * associated object of the Continuation instance.
+ * </p>
+ *
+ * @org.apache.xbean.XBean element="nioConnector" description="Creates an NIO based socket connector"
+ *
+ *
+ *
+ */
+public class SelectChannelConnector extends AbstractNIOConnector
+{
+ protected transient ServerSocketChannel _acceptChannel;
+ private long _lowResourcesConnections;
+ private long _lowResourcesMaxIdleTime;
+
+ private SelectorManager _manager = new SelectorManager()
+ {
+ protected SocketChannel acceptChannel(SelectionKey key) throws IOException
+ {
+ // TODO handle max connections
+ SocketChannel channel = ((ServerSocketChannel)key.channel()).accept();
+ if (channel==null)
+ return null;
+ channel.configureBlocking(false);
+ Socket socket = channel.socket();
+ configure(socket);
+ return channel;
+ }
+
+ public boolean dispatch(Runnable task)
+ {
+ return getThreadPool().dispatch(task);
+ }
+
+ protected void endPointClosed(SelectChannelEndPoint endpoint)
+ {
+ // TODO handle max connections and low resources
+ connectionClosed((HttpConnection)endpoint.getConnection());
+ }
+
+ protected void endPointOpened(SelectChannelEndPoint endpoint)
+ {
+ // TODO handle max connections and low resources
+ connectionOpened((HttpConnection)endpoint.getConnection());
+ }
+
+ protected Connection newConnection(SocketChannel channel,SelectChannelEndPoint endpoint)
+ {
+ return SelectChannelConnector.this.newConnection(channel,endpoint);
+ }
+
+ protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey sKey) throws IOException
+ {
+ return SelectChannelConnector.this.newEndPoint(channel,selectSet,sKey);
+ }
+ };
+
+ /* ------------------------------------------------------------------------------- */
+ /**
+ * Constructor.
+ *
+ */
+ public SelectChannelConnector()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public void accept(int acceptorID) throws IOException
+ {
+ _manager.doSelect(acceptorID);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void close() throws IOException
+ {
+ synchronized(this)
+ {
+ if(_manager.isRunning())
+ {
+ try
+ {
+ _manager.stop();
+ }
+ catch (Exception e)
+ {
+ Log.warn(e);
+ }
+ }
+ if (_acceptChannel != null)
+ _acceptChannel.close();
+ _acceptChannel = null;
+ }
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ @Override
+ public void customize(EndPoint endpoint, Request request) throws IOException
+ {
+ SelectChannelEndPoint cep = ((SelectChannelEndPoint)endpoint);
+ cep.cancelIdle();
+ request.setTimeStamp(cep.getSelectSet().getNow());
+ super.customize(endpoint, request);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ @Override
+ public void persist(EndPoint endpoint) throws IOException
+ {
+ ((SelectChannelEndPoint)endpoint).scheduleIdle();
+ super.persist(endpoint);
+ }
+
+ /* ------------------------------------------------------------ */
+ public Object getConnection()
+ {
+ return _acceptChannel;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public int getLocalPort()
+ {
+ synchronized(this)
+ {
+ if (_acceptChannel==null || !_acceptChannel.isOpen())
+ return -1;
+ return _acceptChannel.socket().getLocalPort();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void open() throws IOException
+ {
+ synchronized(this)
+ {
+ if (_acceptChannel == null)
+ {
+ // Create a new server socket
+ _acceptChannel = ServerSocketChannel.open();
+
+ // Bind the server socket to the local host and port
+ _acceptChannel.socket().setReuseAddress(getReuseAddress());
+ InetSocketAddress addr = getHost()==null?new InetSocketAddress(getPort()):new InetSocketAddress(getHost(),getPort());
+ _acceptChannel.socket().bind(addr,getAcceptQueueSize());
+
+ // Set to non blocking mode
+ _acceptChannel.configureBlocking(false);
+
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setMaxIdleTime(int maxIdleTime)
+ {
+ _manager.setMaxIdleTime(maxIdleTime);
+ super.setMaxIdleTime(maxIdleTime);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the lowResourcesConnections
+ */
+ public long getLowResourcesConnections()
+ {
+ return _lowResourcesConnections;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the number of connections, which if exceeded places this manager in low resources state.
+ * This is not an exact measure as the connection count is averaged over the select sets.
+ * @param lowResourcesConnections the number of connections
+ * @see {@link #setLowResourcesMaxIdleTime(long)}
+ */
+ public void setLowResourcesConnections(long lowResourcesConnections)
+ {
+ _lowResourcesConnections=lowResourcesConnections;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the lowResourcesMaxIdleTime
+ */
+ public long getLowResourcesMaxIdleTime()
+ {
+ return _lowResourcesMaxIdleTime;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the period in ms that a connection is allowed to be idle when this there are more
+ * than {@link #getLowResourcesConnections()} connections. This allows the server to rapidly close idle connections
+ * in order to gracefully handle high load situations.
+ * @param lowResourcesMaxIdleTime the period in ms that a connection is allowed to be idle when resources are low.
+ * @see {@link #setMaxIdleTime(long)}
+ * @deprecated use {@link #setLowResourceMaxIdleTime(int)}
+ */
+ public void setLowResourcesMaxIdleTime(long lowResourcesMaxIdleTime)
+ {
+ _lowResourcesMaxIdleTime=lowResourcesMaxIdleTime;
+ super.setLowResourceMaxIdleTime((int)lowResourcesMaxIdleTime); // TODO fix the name duplications
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the period in ms that a connection is allowed to be idle when this there are more
+ * than {@link #getLowResourcesConnections()} connections. This allows the server to rapidly close idle connections
+ * in order to gracefully handle high load situations.
+ * @param lowResourcesMaxIdleTime the period in ms that a connection is allowed to be idle when resources are low.
+ * @see {@link #setMaxIdleTime(long)}
+ */
+ public void setLowResourceMaxIdleTime(int lowResourcesMaxIdleTime)
+ {
+ _lowResourcesMaxIdleTime=lowResourcesMaxIdleTime;
+ super.setLowResourceMaxIdleTime(lowResourcesMaxIdleTime);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.server.AbstractConnector#doStart()
+ */
+ protected void doStart() throws Exception
+ {
+ _manager.setSelectSets(getAcceptors());
+ _manager.setMaxIdleTime(getMaxIdleTime());
+ _manager.setLowResourcesConnections(getLowResourcesConnections());
+ _manager.setLowResourcesMaxIdleTime(getLowResourcesMaxIdleTime());
+ _manager.start();
+ open();
+ _manager.register(_acceptChannel);
+ super.doStart();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.server.AbstractConnector#doStop()
+ */
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ }
+
+ /* ------------------------------------------------------------ */
+ protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException
+ {
+ return new SelectChannelEndPoint(channel,selectSet,key)
+ {
+ // TODO remove this hack
+ public boolean isReadyForDispatch()
+ {
+ Request request = ((HttpConnection)getConnection()).getRequest();
+ return super.isReadyForDispatch() && !(request.getAsyncRequest().isSuspended());
+ }
+ };
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ protected Connection newConnection(SocketChannel channel,final SelectChannelEndPoint endpoint)
+ {
+ return new HttpConnection(SelectChannelConnector.this,endpoint,getServer())
+ {
+ /* ------------------------------------------------------------ */
+ public void cancelTimeout(Task task)
+ {
+ endpoint.getSelectSet().cancelTimeout(task);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void scheduleTimeout(Task task, long timeoutMs)
+ {
+ endpoint.getSelectSet().scheduleTimeout(task,timeoutMs);
+ }
+ };
+ }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionIdManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionIdManager.java
new file mode 100644
index 0000000000..f02388c7dc
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionIdManager.java
@@ -0,0 +1,161 @@
+// ========================================================================
+// Copyright (c) 2004-2009 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 java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Random;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.SessionIdManager;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+
+public abstract class AbstractSessionIdManager extends AbstractLifeCycle implements SessionIdManager
+{
+ private final static String __NEW_SESSION_ID="org.eclipse.jetty.server.newSessionId";
+ protected final static String SESSION_ID_RANDOM_ALGORITHM = "SHA1PRNG";
+ protected final static String SESSION_ID_RANDOM_ALGORITHM_ALT = "IBMSecureRandom";
+
+ protected Random _random;
+ protected boolean _weakRandom;
+ protected String _workerName;
+ protected Server _server;
+
+
+ public AbstractSessionIdManager(Server server)
+ {
+ _server=server;
+ }
+
+
+ public AbstractSessionIdManager(Server server, Random random)
+ {
+ _random=random;
+ _server=server;
+ }
+
+ public String getWorkerName()
+ {
+ return _workerName;
+ }
+
+ public void setWorkerName (String name)
+ {
+ _workerName=name;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Random getRandom()
+ {
+ return _random;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setRandom(Random random)
+ {
+ _random=random;
+ _weakRandom=false;
+ }
+ /**
+ * Create a new session id if necessary.
+ *
+ * @see org.eclipse.jetty.server.SessionIdManager#newSessionId(javax.servlet.http.HttpServletRequest, long)
+ */
+ public String newSessionId(HttpServletRequest request, long created)
+ {
+ synchronized (this)
+ {
+ // A requested session ID can only be used if it is in use already.
+ String requested_id=request.getRequestedSessionId();
+ if (requested_id!=null)
+ {
+ String cluster_id=getClusterId(requested_id);
+ if (idInUse(cluster_id))
+ return cluster_id;
+ }
+
+ // Else reuse any new session ID already defined for this request.
+ String new_id=(String)request.getAttribute(__NEW_SESSION_ID);
+ if (new_id!=null&&idInUse(new_id))
+ return new_id;
+
+
+
+ // pick a new unique ID!
+ String id=null;
+ while (id==null||id.length()==0||idInUse(id))
+ {
+ long r=_weakRandom
+ ?(hashCode()^Runtime.getRuntime().freeMemory()^_random.nextInt()^(((long)request.hashCode())<<32))
+ :_random.nextLong();
+ r^=created;
+ if (request!=null && request.getRemoteAddr()!=null)
+ r^=request.getRemoteAddr().hashCode();
+ if (r<0)
+ r=-r;
+ id=Long.toString(r,36);
+
+ //add in the id of the node to ensure unique id across cluster
+ //NOTE this is different to the node suffix which denotes which node the request was received on
+ id=_workerName + id;
+ }
+
+ request.setAttribute(__NEW_SESSION_ID,id);
+ return id;
+ }
+ }
+
+
+ public void doStart()
+ {
+ initRandom();
+ }
+
+
+
+
+ /**
+ * Set up a random number generator for the sessionids.
+ *
+ * By preference, use a SecureRandom but allow to be injected.
+ */
+ public void initRandom ()
+ {
+ if (_random==null)
+ {
+ try
+ {
+ _random=SecureRandom.getInstance(SESSION_ID_RANDOM_ALGORITHM);
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ try
+ {
+ _random=SecureRandom.getInstance(SESSION_ID_RANDOM_ALGORITHM_ALT);
+ _weakRandom=false;
+ }
+ catch (NoSuchAlgorithmException e_alt)
+ {
+ Log.warn("Could not generate SecureRandom for session-id randomness",e);
+ _random=new Random();
+ _weakRandom=true;
+ }
+ }
+ }
+ _random.setSeed(_random.nextLong()^System.currentTimeMillis()^hashCode()^Runtime.getRuntime().freeMemory());
+ }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java
new file mode 100644
index 0000000000..d346aa78e6
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java
@@ -0,0 +1,1182 @@
+// ========================================================================
+// Copyright (c) 1999-2009 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 java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionActivationListener;
+import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+import javax.servlet.http.HttpSessionContext;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.SessionIdManager;
+import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+
+/* ------------------------------------------------------------ */
+/**
+ * An Abstract implementation of SessionManager. The partial implementation of
+ * SessionManager interface provides the majority of the handling required to
+ * implement a SessionManager. Concrete implementations of SessionManager based
+ * on AbstractSessionManager need only implement the newSession method to return
+ * a specialized version of the Session inner class that provides an attribute
+ * Map.
+ * <p>
+ * If the property
+ * org.eclipse.jetty.servlet.AbstractSessionManager.23Notifications is set to
+ * true, the 2.3 servlet spec notification style will be used.
+ * <p>
+ *
+ *
+ */
+public abstract class AbstractSessionManager extends AbstractLifeCycle implements SessionManager
+{
+ /* ------------------------------------------------------------ */
+ public final static int __distantFuture=60*60*24*7*52*20;
+
+ private static final HttpSessionContext __nullSessionContext=new NullSessionContext();
+
+ private boolean _usingCookies=true;
+
+ /* ------------------------------------------------------------ */
+ // Setting of max inactive interval for new sessions
+ // -1 means no timeout
+ protected int _dftMaxIdleSecs=-1;
+ protected SessionHandler _sessionHandler;
+ protected boolean _httpOnly=false;
+ protected int _maxSessions=0;
+
+ protected int _minSessions=0;
+ protected SessionIdManager _sessionIdManager;
+ protected boolean _secureCookies=false;
+ protected Object _sessionAttributeListeners;
+ protected Object _sessionListeners;
+
+ protected ClassLoader _loader;
+ protected ContextHandler.Context _context;
+ protected String _sessionCookie=__DefaultSessionCookie;
+ protected String _sessionIdPathParameterName = __DefaultSessionIdPathParameterName;
+ protected String _sessionIdPathParameterNamePrefix =";"+ _sessionIdPathParameterName +"=";
+ protected String _sessionDomain;
+ protected String _sessionPath;
+ protected int _maxCookieAge=-1;
+ protected int _refreshCookieAge;
+ protected boolean _nodeIdInSessionId;
+
+ /* ------------------------------------------------------------ */
+ public AbstractSessionManager()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public Cookie access(HttpSession session,boolean secure)
+ {
+ long now=System.currentTimeMillis();
+
+ Session s = ((SessionIf)session).getSession();
+ s.access(now);
+
+ // Do we need to refresh the cookie?
+ if (isUsingCookies() &&
+ (s.isIdChanged() ||
+ (getMaxCookieAge()>0 && getRefreshCookieAge()>0 && ((now-s.getCookieSetTime())/1000>getRefreshCookieAge()))
+ )
+ )
+ {
+ Cookie cookie=getSessionCookie(session,_context.getContextPath(),secure);
+ s.cookieSet();
+ s.setIdChanged(false);
+ return cookie;
+ }
+
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void addEventListener(EventListener listener)
+ {
+ if (listener instanceof HttpSessionAttributeListener)
+ _sessionAttributeListeners=LazyList.add(_sessionAttributeListeners,listener);
+ if (listener instanceof HttpSessionListener)
+ _sessionListeners=LazyList.add(_sessionListeners,listener);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void clearEventListeners()
+ {
+ _sessionAttributeListeners=null;
+ _sessionListeners=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void complete(HttpSession session)
+ {
+ Session s = ((SessionIf)session).getSession();
+ s.complete();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void doStart() throws Exception
+ {
+ _context=ContextHandler.getCurrentContext();
+ _loader=Thread.currentThread().getContextClassLoader();
+
+ if (_sessionIdManager==null)
+ {
+ Server server=getSessionHandler().getServer();
+ synchronized (server)
+ {
+ _sessionIdManager=server.getSessionIdManager();
+ if (_sessionIdManager==null)
+ {
+ _sessionIdManager=new HashSessionIdManager();
+ server.setSessionIdManager(_sessionIdManager);
+ }
+ }
+ }
+ if (!_sessionIdManager.isStarted())
+ _sessionIdManager.start();
+
+ // Look for a session cookie name
+ String tmp=_context.getInitParameter(SessionManager.__SessionCookieProperty);
+ if (tmp!=null)
+ _sessionCookie=tmp;
+
+ tmp=_context.getInitParameter(SessionManager.__SessionIdPathParameterNameProperty);
+ if (tmp!=null)
+ {
+ setSessionIdPathParameterName(tmp);
+ }
+
+ // set up the max session cookie age if it isn't already
+ if (_maxCookieAge==-1)
+ {
+ if (_context!=null)
+ {
+ String str=_context.getInitParameter(SessionManager.__MaxAgeProperty);
+ if (str!=null)
+ _maxCookieAge=Integer.parseInt(str.trim());
+ }
+ }
+ // set up the session domain if it isn't already
+ if (_sessionDomain==null)
+ {
+ // only try the context initParams
+ if (_context!=null)
+ _sessionDomain=_context.getInitParameter(SessionManager.__SessionDomainProperty);
+ }
+
+ // set up the sessionPath if it isn't already
+ if (_sessionPath==null)
+ {
+ // only the context initParams
+ if (_context!=null)
+ _sessionPath=_context.getInitParameter(SessionManager.__SessionPathProperty);
+ }
+
+ super.doStart();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void doStop() throws Exception
+ {
+ super.doStop();
+
+ invalidateSessions();
+
+ _loader=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the httpOnly.
+ */
+ public boolean getHttpOnly()
+ {
+ return _httpOnly;
+ }
+
+ /* ------------------------------------------------------------ */
+ public HttpSession getHttpSession(String nodeId)
+ {
+ String cluster_id = getIdManager().getClusterId(nodeId);
+
+ synchronized (this)
+ {
+ Session session = getSession(cluster_id);
+
+ if (session!=null && !session.getNodeId().equals(nodeId))
+ session.setIdChanged(true);
+ return session;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the metaManager used for cross context session management
+ */
+ public SessionIdManager getIdManager()
+ {
+ return _sessionIdManager;
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getMaxCookieAge()
+ {
+ return _maxCookieAge;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return seconds
+ */
+ public int getMaxInactiveInterval()
+ {
+ return _dftMaxIdleSecs;
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getMaxSessions()
+ {
+ return _maxSessions;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @deprecated use {@link #getIdManager()}
+ */
+ public SessionIdManager getMetaManager()
+ {
+ return getIdManager();
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getMinSessions()
+ {
+ return _minSessions;
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getRefreshCookieAge()
+ {
+ return _refreshCookieAge;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the secureCookies.
+ */
+ public boolean getSecureCookies()
+ {
+ return _secureCookies;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getSessionCookie()
+ {
+ return _sessionCookie;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Cookie getSessionCookie(HttpSession session, String contextPath, boolean requestIsSecure)
+ {
+ if (isUsingCookies())
+ {
+ String id = getNodeId(session);
+ Cookie cookie=new Cookie(_sessionCookie,id);
+ cookie.setHttpOnly(getHttpOnly());
+
+ cookie.setPath((contextPath==null||contextPath.length()==0)?"/":contextPath);
+ cookie.setMaxAge(getMaxCookieAge());
+ cookie.setSecure(requestIsSecure&&getSecureCookies());
+
+ // set up the overrides
+ if (_sessionDomain!=null)
+ cookie.setDomain(_sessionDomain);
+ if (_sessionPath!=null)
+ cookie.setPath(_sessionPath);
+
+ return cookie;
+ }
+ return null;
+ }
+
+ public String getSessionDomain()
+ {
+ return _sessionDomain;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the sessionHandler.
+ */
+ public SessionHandler getSessionHandler()
+ {
+ return _sessionHandler;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @deprecated. Need to review if it is needed.
+ */
+ public abstract Map getSessionMap();
+
+ /* ------------------------------------------------------------ */
+ public String getSessionPath()
+ {
+ return _sessionPath;
+ }
+
+ /* ------------------------------------------------------------ */
+ public abstract int getSessions();
+
+ /* ------------------------------------------------------------ */
+ public String getSessionIdPathParameterName()
+ {
+ return _sessionIdPathParameterName;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getSessionIdPathParameterNamePrefix()
+ {
+ return _sessionIdPathParameterNamePrefix;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the usingCookies.
+ */
+ public boolean isUsingCookies()
+ {
+ return _usingCookies;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isValid(HttpSession session)
+ {
+ Session s = ((SessionIf)session).getSession();
+ return s.isValid();
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getClusterId(HttpSession session)
+ {
+ Session s = ((SessionIf)session).getSession();
+ return s.getClusterId();
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getNodeId(HttpSession session)
+ {
+ Session s = ((SessionIf)session).getSession();
+ return s.getNodeId();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create a new HttpSession for a request
+ */
+ public HttpSession newHttpSession(HttpServletRequest request)
+ {
+ Session session=newSession(request);
+ session.setMaxInactiveInterval(_dftMaxIdleSecs);
+ addSession(session,true);
+ return session;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void removeEventListener(EventListener listener)
+ {
+ if (listener instanceof HttpSessionAttributeListener)
+ _sessionAttributeListeners=LazyList.remove(_sessionAttributeListeners,listener);
+ if (listener instanceof HttpSessionListener)
+ _sessionListeners=LazyList.remove(_sessionListeners,listener);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void resetStats()
+ {
+ _minSessions=getSessions();
+ _maxSessions=getSessions();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param httpOnly
+ * The httpOnly to set.
+ */
+ public void setHttpOnly(boolean httpOnly)
+ {
+ _httpOnly=httpOnly;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param metaManager The metaManager used for cross context session management.
+ */
+ public void setIdManager(SessionIdManager metaManager)
+ {
+ _sessionIdManager=metaManager;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setMaxCookieAge(int maxCookieAgeInSeconds)
+ {
+ _maxCookieAge=maxCookieAgeInSeconds;
+
+ if (_maxCookieAge>0 && _refreshCookieAge==0)
+ _refreshCookieAge=_maxCookieAge/3;
+
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param seconds
+ */
+ public void setMaxInactiveInterval(int seconds)
+ {
+ _dftMaxIdleSecs=seconds;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @deprecated use {@link #setIdManager(SessionIdManager)}
+ */
+ public void setMetaManager(SessionIdManager metaManager)
+ {
+ setIdManager(metaManager);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setRefreshCookieAge(int ageInSeconds)
+ {
+ _refreshCookieAge=ageInSeconds;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param secureCookies
+ * The secureCookies to set.
+ */
+ public void setSecureCookies(boolean secureCookies)
+ {
+ _secureCookies=secureCookies;
+ }
+
+ public void setSessionCookie(String cookieName)
+ {
+ _sessionCookie=cookieName;
+ }
+
+ public void setSessionDomain(String domain)
+ {
+ _sessionDomain=domain;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param sessionHandler
+ * The sessionHandler to set.
+ */
+ public void setSessionHandler(SessionHandler sessionHandler)
+ {
+ _sessionHandler=sessionHandler;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setSessionPath(String path)
+ {
+ _sessionPath=path;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setSessionIdPathParameterName(String param)
+ {
+ _sessionIdPathParameterName =(param==null||"none".equals(param))?null:param;
+ _sessionIdPathParameterNamePrefix =(param==null||"none".equals(param))?null:(";"+ _sessionIdPathParameterName +"=");
+ }
+ /* ------------------------------------------------------------ */
+ /**
+ * @param usingCookies
+ * The usingCookies to set.
+ */
+ public void setUsingCookies(boolean usingCookies)
+ {
+ _usingCookies=usingCookies;
+ }
+
+
+ protected abstract void addSession(Session session);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Add the session Registers the session with this manager and registers the
+ * session ID with the sessionIDManager;
+ */
+ protected void addSession(Session session, boolean created)
+ {
+ synchronized (_sessionIdManager)
+ {
+ _sessionIdManager.addSession(session);
+ synchronized (this)
+ {
+ addSession(session);
+ if (getSessions()>this._maxSessions)
+ this._maxSessions=getSessions();
+ }
+ }
+
+ if (!created)
+ {
+ session.didActivate();
+ }
+ else if (_sessionListeners!=null)
+ {
+ HttpSessionEvent event=new HttpSessionEvent(session);
+ for (int i=0; i<LazyList.size(_sessionListeners); i++)
+ ((HttpSessionListener)LazyList.get(_sessionListeners,i)).sessionCreated(event);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Get a known existingsession
+ * @param idInCluster The session ID in the cluster, stripped of any worker name.
+ * @return A Session or null if none exists.
+ */
+ public abstract Session getSession(String idInCluster);
+
+ protected abstract void invalidateSessions();
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create a new session instance
+ * @param request
+ * @return
+ */
+ protected abstract Session newSession(HttpServletRequest request);
+
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if the cluster node id (worker id) is returned as part of the session id by {@link HttpSession#getId()}. Default is false.
+ */
+ public boolean isNodeIdInSessionId()
+ {
+ return _nodeIdInSessionId;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param nodeIdInSessionId true if the cluster node id (worker id) will be returned as part of the session id by {@link HttpSession#getId()}. Default is false.
+ */
+ public void setNodeIdInSessionId(boolean nodeIdInSessionId)
+ {
+ _nodeIdInSessionId=nodeIdInSessionId;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Remove session from manager
+ * @param session The session to remove
+ * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and
+ * {@link SessionIdManager#invalidateAll(String)} should be called.
+ */
+ public void removeSession(HttpSession session, boolean invalidate)
+ {
+ Session s = ((SessionIf)session).getSession();
+ removeSession(s,invalidate);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Remove session from manager
+ * @param session The session to remove
+ * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and
+ * {@link SessionIdManager#invalidateAll(String)} should be called.
+ */
+ public void removeSession(Session session, boolean invalidate)
+ {
+ // Remove session from context and global maps
+ synchronized (_sessionIdManager)
+ {
+ boolean removed = false;
+
+ synchronized (this)
+ {
+ //take this session out of the map of sessions for this context
+ if (getSession(session.getClusterId()) != null)
+ {
+ removed = true;
+ removeSession(session.getClusterId());
+ }
+ }
+
+ if (removed)
+ {
+ // Remove session from all context and global id maps
+ _sessionIdManager.removeSession(session);
+ if (invalidate)
+ _sessionIdManager.invalidateAll(session.getClusterId());
+ }
+ }
+
+ if (invalidate && _sessionListeners!=null)
+ {
+ HttpSessionEvent event=new HttpSessionEvent(session);
+ for (int i=LazyList.size(_sessionListeners); i-->0;)
+ ((HttpSessionListener)LazyList.get(_sessionListeners,i)).sessionDestroyed(event);
+ }
+ if (!invalidate)
+ {
+ session.willPassivate();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected abstract void removeSession(String idInCluster);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Null returning implementation of HttpSessionContext
+ *
+ *
+ */
+ public static class NullSessionContext implements HttpSessionContext
+ {
+ /* ------------------------------------------------------------ */
+ private NullSessionContext()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @deprecated From HttpSessionContext
+ */
+ public Enumeration getIds()
+ {
+ return Collections.enumeration(Collections.EMPTY_LIST);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @deprecated From HttpSessionContext
+ */
+ public HttpSession getSession(String id)
+ {
+ return null;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /**
+ * Interface that any session wrapper should implement so that
+ * SessionManager may access the Jetty session implementation.
+ *
+ */
+ public interface SessionIf extends HttpSession
+ {
+ public Session getSession();
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /**
+ *
+ * <p>
+ * Implements {@link javax.servlet.HttpSession} from the {@link javax.servlet} package.
+ * </p>
+ *
+ *
+ */
+ public abstract class Session implements SessionIf, Serializable
+ {
+ protected final String _clusterId; // ID unique within cluster
+ protected final String _nodeId; // ID unique within node
+ protected boolean _idChanged;
+ protected final long _created;
+ protected long _cookieSet;
+ protected long _accessed;
+ protected long _lastAccessed;
+ protected boolean _invalid;
+ protected boolean _doInvalidate;
+ protected long _maxIdleMs=_dftMaxIdleSecs*1000;
+ protected boolean _newSession;
+ protected Map _values;
+ protected int _requests;
+
+ /* ------------------------------------------------------------- */
+ protected Session(HttpServletRequest request)
+ {
+ _newSession=true;
+ _created=System.currentTimeMillis();
+ _clusterId=_sessionIdManager.newSessionId(request,_created);
+ _nodeId=_sessionIdManager.getNodeId(_clusterId,request);
+ _accessed=_created;
+ _requests=1;
+ }
+
+ /* ------------------------------------------------------------- */
+ protected Session(long created, String clusterId)
+ {
+ _created=created;
+ _clusterId=clusterId;
+ _nodeId=_sessionIdManager.getNodeId(_clusterId,null);
+ _accessed=_created;
+ }
+
+ /* ------------------------------------------------------------- */
+ public Session getSession()
+ {
+ return this;
+ }
+
+ /* ------------------------------------------------------------- */
+ protected void initValues()
+ {
+ _values=newAttributeMap();
+ }
+
+ /* ------------------------------------------------------------ */
+ public Object getAttribute(String name)
+ {
+ synchronized (this)
+ {
+ if (_invalid)
+ throw new IllegalStateException();
+
+ if (null == _values)
+ return null;
+
+ return _values.get(name);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public Enumeration getAttributeNames()
+ {
+ synchronized (this)
+ {
+ if (_invalid)
+ throw new IllegalStateException();
+ List names=_values==null?Collections.EMPTY_LIST:new ArrayList(_values.keySet());
+ return Collections.enumeration(names);
+ }
+ }
+
+ /* ------------------------------------------------------------- */
+ public long getCookieSetTime()
+ {
+ return _cookieSet;
+ }
+
+ /* ------------------------------------------------------------- */
+ public long getCreationTime() throws IllegalStateException
+ {
+ if (_invalid)
+ throw new IllegalStateException();
+ return _created;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getId() throws IllegalStateException
+ {
+ return _nodeIdInSessionId?_nodeId:_clusterId;
+ }
+
+ /* ------------------------------------------------------------- */
+ protected String getNodeId()
+ {
+ return _nodeId;
+ }
+
+ /* ------------------------------------------------------------- */
+ protected String getClusterId()
+ {
+ return _clusterId;
+ }
+
+ /* ------------------------------------------------------------- */
+ public long getLastAccessedTime() throws IllegalStateException
+ {
+ if (_invalid)
+ throw new IllegalStateException();
+ return _lastAccessed;
+ }
+
+ /* ------------------------------------------------------------- */
+ public int getMaxInactiveInterval()
+ {
+ if (_invalid)
+ throw new IllegalStateException();
+ return (int)(_maxIdleMs/1000);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpSession#getServletContext()
+ */
+ public ServletContext getServletContext()
+ {
+ return _context;
+ }
+
+ /* ------------------------------------------------------------- */
+ /**
+ * @deprecated
+ */
+ public HttpSessionContext getSessionContext() throws IllegalStateException
+ {
+ if (_invalid)
+ throw new IllegalStateException();
+ return __nullSessionContext;
+ }
+
+ /* ------------------------------------------------------------- */
+ /**
+ * @deprecated As of Version 2.2, this method is replaced by
+ * {@link #getAttribute}
+ */
+ public Object getValue(String name) throws IllegalStateException
+ {
+ return getAttribute(name);
+ }
+
+ /* ------------------------------------------------------------- */
+ /**
+ * @deprecated As of Version 2.2, this method is replaced by
+ * {@link #getAttributeNames}
+ */
+ public String[] getValueNames() throws IllegalStateException
+ {
+ synchronized(this)
+ {
+ if (_invalid)
+ throw new IllegalStateException();
+ if (_values==null)
+ return new String[0];
+ String[] a=new String[_values.size()];
+ return (String[])_values.keySet().toArray(a);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void access(long time)
+ {
+ synchronized(this)
+ {
+ _newSession=false;
+ _lastAccessed=_accessed;
+ _accessed=time;
+ _requests++;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void complete()
+ {
+ synchronized(this)
+ {
+ _requests--;
+ if (_doInvalidate && _requests<=0 )
+ doInvalidate();
+ }
+ }
+
+
+ /* ------------------------------------------------------------- */
+ protected void timeout() throws IllegalStateException
+ {
+ // remove session from context and invalidate other sessions with same ID.
+ removeSession(this,true);
+
+ // Notify listeners and unbind values
+ synchronized (this)
+ {
+ if (_requests<=0)
+ doInvalidate();
+ else
+ _doInvalidate=true;
+ }
+ }
+
+ /* ------------------------------------------------------------- */
+ public void invalidate() throws IllegalStateException
+ {
+ // remove session from context and invalidate other sessions with same ID.
+ removeSession(this,true);
+ doInvalidate();
+ }
+
+ /* ------------------------------------------------------------- */
+ protected void doInvalidate() throws IllegalStateException
+ {
+ try
+ {
+ // Notify listeners and unbind values
+ if (_invalid)
+ throw new IllegalStateException();
+
+ while (_values!=null && _values.size()>0)
+ {
+ ArrayList keys;
+ synchronized (this)
+ {
+ keys=new ArrayList(_values.keySet());
+ }
+
+ Iterator iter=keys.iterator();
+ while (iter.hasNext())
+ {
+ String key=(String)iter.next();
+
+ Object value;
+ synchronized (this)
+ {
+ value=_values.remove(key);
+ }
+ unbindValue(key,value);
+
+ if (_sessionAttributeListeners!=null)
+ {
+ HttpSessionBindingEvent event=new HttpSessionBindingEvent(this,key,value);
+
+ for (int i=0; i<LazyList.size(_sessionAttributeListeners); i++)
+ ((HttpSessionAttributeListener)LazyList.get(_sessionAttributeListeners,i)).attributeRemoved(event);
+ }
+ }
+ }
+ }
+ finally
+ {
+ // mark as invalid
+ _invalid=true;
+ }
+ }
+
+ /* ------------------------------------------------------------- */
+ public boolean isIdChanged()
+ {
+ return _idChanged;
+ }
+
+ /* ------------------------------------------------------------- */
+ public boolean isNew() throws IllegalStateException
+ {
+ if (_invalid)
+ throw new IllegalStateException();
+ return _newSession;
+ }
+
+ /* ------------------------------------------------------------- */
+ /**
+ * @deprecated As of Version 2.2, this method is replaced by
+ * {@link #setAttribute}
+ */
+ public void putValue(java.lang.String name, java.lang.Object value) throws IllegalStateException
+ {
+ setAttribute(name,value);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void removeAttribute(String name)
+ {
+ Object old;
+ synchronized(this)
+ {
+ if (_invalid)
+ throw new IllegalStateException();
+ if (_values==null)
+ return;
+
+ old=_values.remove(name);
+ }
+
+ if (old!=null)
+ {
+ unbindValue(name,old);
+ if (_sessionAttributeListeners!=null)
+ {
+ HttpSessionBindingEvent event=new HttpSessionBindingEvent(this,name,old);
+
+ for (int i=0; i<LazyList.size(_sessionAttributeListeners); i++)
+ ((HttpSessionAttributeListener)LazyList.get(_sessionAttributeListeners,i)).attributeRemoved(event);
+ }
+ }
+
+ }
+
+ /* ------------------------------------------------------------- */
+ /**
+ * @deprecated As of Version 2.2, this method is replaced by
+ * {@link #removeAttribute}
+ */
+ public void removeValue(java.lang.String name) throws IllegalStateException
+ {
+ removeAttribute(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setAttribute(String name, Object value)
+ {
+ Object old_value;
+ if (value==null)
+ {
+ removeAttribute(name);
+ return;
+ }
+
+ synchronized(this)
+ {
+ if (_invalid)
+ throw new IllegalStateException();
+ if (_values==null)
+ _values=newAttributeMap();
+ old_value=_values.put(name,value);
+ }
+
+ if (old_value==null || !value.equals(old_value))
+ {
+ unbindValue(name,old_value);
+ bindValue(name,value);
+
+ if (_sessionAttributeListeners!=null)
+ {
+ HttpSessionBindingEvent event=new HttpSessionBindingEvent(this,name,old_value==null?value:old_value);
+
+ for (int i=0; i<LazyList.size(_sessionAttributeListeners); i++)
+ {
+ HttpSessionAttributeListener l=(HttpSessionAttributeListener)LazyList.get(_sessionAttributeListeners,i);
+
+ if (old_value==null)
+ l.attributeAdded(event);
+ else if (value==null)
+ l.attributeRemoved(event);
+ else
+ l.attributeReplaced(event);
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------- */
+ public void setIdChanged(boolean changed)
+ {
+ _idChanged=changed;
+ }
+
+ /* ------------------------------------------------------------- */
+ public void setMaxInactiveInterval(int secs)
+ {
+ _maxIdleMs=(long)secs*1000;
+ }
+
+ /* ------------------------------------------------------------- */
+ public String toString()
+ {
+ return this.getClass().getName()+":"+getId()+"@"+hashCode();
+ }
+
+ /* ------------------------------------------------------------- */
+ /** If value implements HttpSessionBindingListener, call valueBound() */
+ protected void bindValue(java.lang.String name, Object value)
+ {
+ if (value!=null&&value instanceof HttpSessionBindingListener)
+ ((HttpSessionBindingListener)value).valueBound(new HttpSessionBindingEvent(this,name));
+ }
+
+ /* ------------------------------------------------------------ */
+ protected boolean isValid()
+ {
+ return !_invalid;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected abstract Map newAttributeMap();
+
+ /* ------------------------------------------------------------- */
+ protected void cookieSet()
+ {
+ _cookieSet=_accessed;
+ }
+
+ /* ------------------------------------------------------------- */
+ /** If value implements HttpSessionBindingListener, call valueUnbound() */
+ protected void unbindValue(java.lang.String name, Object value)
+ {
+ if (value!=null&&value instanceof HttpSessionBindingListener)
+ ((HttpSessionBindingListener)value).valueUnbound(new HttpSessionBindingEvent(this,name));
+ }
+
+ /* ------------------------------------------------------------- */
+ protected void willPassivate()
+ {
+ synchronized(this)
+ {
+ HttpSessionEvent event = new HttpSessionEvent(this);
+ for (Iterator iter = _values.values().iterator(); iter.hasNext();)
+ {
+ Object value = iter.next();
+ if (value instanceof HttpSessionActivationListener)
+ {
+ HttpSessionActivationListener listener = (HttpSessionActivationListener) value;
+ listener.sessionWillPassivate(event);
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------- */
+ protected void didActivate()
+ {
+ synchronized(this)
+ {
+ HttpSessionEvent event = new HttpSessionEvent(this);
+ for (Iterator iter = _values.values().iterator(); iter.hasNext();)
+ {
+ Object value = iter.next();
+ if (value instanceof HttpSessionActivationListener)
+ {
+ HttpSessionActivationListener listener = (HttpSessionActivationListener) value;
+ listener.sessionDidActivate(event);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionIdManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionIdManager.java
new file mode 100644
index 0000000000..ba72362d3d
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionIdManager.java
@@ -0,0 +1,256 @@
+// ========================================================================
+// Copyright (c) 2006-2009 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 java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Random;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.server.SessionIdManager;
+import org.eclipse.jetty.server.session.AbstractSessionManager.Session;
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+
+/* ------------------------------------------------------------ */
+/**
+ * HashSessionIdManager. An in-memory implementation of the session ID manager.
+ */
+public class HashSessionIdManager extends AbstractLifeCycle implements SessionIdManager
+{
+ private final static String __NEW_SESSION_ID="org.eclipse.jetty.server.newSessionId";
+ protected final static String SESSION_ID_RANDOM_ALGORITHM = "SHA1PRNG";
+ protected final static String SESSION_ID_RANDOM_ALGORITHM_ALT = "IBMSecureRandom";
+
+ MultiMap<String> _sessions;
+ protected Random _random;
+ private boolean _weakRandom;
+ private String _workerName;
+
+ /* ------------------------------------------------------------ */
+ public HashSessionIdManager()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public HashSessionIdManager(Random random)
+ {
+ _random=random;
+
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Get the workname. If set, the workername is dot appended to the session
+ * ID and can be used to assist session affinity in a load balancer.
+ *
+ * @return String or null
+ */
+ public String getWorkerName()
+ {
+ return _workerName;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the workname. If set, the workername is dot appended to the session
+ * ID and can be used to assist session affinity in a load balancer.
+ *
+ * @param workerName
+ */
+ public void setWorkerName(String workerName)
+ {
+ _workerName=workerName;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get the session ID with any worker ID.
+ *
+ * @param request
+ * @return sessionId plus any worker ID.
+ */
+ public String getNodeId(String clusterId,HttpServletRequest request)
+ {
+ String worker=request==null?null:(String)request.getAttribute("org.eclipse.http.ajp.JVMRoute");
+ if (worker!=null)
+ return clusterId+'.'+worker;
+
+ if (_workerName!=null)
+ return clusterId+'.'+_workerName;
+
+ return clusterId;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get the session ID without any worker ID.
+ *
+ * @param request
+ * @return sessionId without any worker ID.
+ */
+ public String getClusterId(String nodeId)
+ {
+ int dot=nodeId.lastIndexOf('.');
+ return (dot>0)?nodeId.substring(0,dot):nodeId;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void doStart()
+ {
+ if (_random==null)
+ {
+ try
+ {
+ //This operation may block on some systems with low entropy. See this page
+ //for workaround suggestions:
+ //http://docs.codehaus.org/display/JETTY/Connectors+slow+to+startup
+ Log.debug("Init SecureRandom.");
+ _random=SecureRandom.getInstance(SESSION_ID_RANDOM_ALGORITHM);
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ try
+ {
+ _random=SecureRandom.getInstance(SESSION_ID_RANDOM_ALGORITHM_ALT);
+ _weakRandom=false;
+ }
+ catch (NoSuchAlgorithmException e_alt)
+ {
+ Log.warn("Could not generate SecureRandom for session-id randomness",e);
+ _random=new Random();
+ _weakRandom=true;
+ }
+ }
+ }
+ _random.setSeed(_random.nextLong()^System.currentTimeMillis()^hashCode()^Runtime.getRuntime().freeMemory());
+ _sessions=new MultiMap<String>(true);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void doStop()
+ {
+ if (_sessions!=null)
+ _sessions.clear(); // Maybe invalidate?
+ _sessions=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.server.SessionManager.MetaManager#idInUse(java.lang.String)
+ */
+ public boolean idInUse(String id)
+ {
+ return _sessions.containsKey(id);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.server.SessionManager.MetaManager#addSession(javax.servlet.http.HttpSession)
+ */
+ public void addSession(HttpSession session)
+ {
+ _sessions.add(getClusterId(session.getId()),session);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.server.SessionManager.MetaManager#addSession(javax.servlet.http.HttpSession)
+ */
+ public void removeSession(HttpSession session)
+ {
+ _sessions.removeValue(getClusterId(session.getId()),session);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.server.SessionManager.MetaManager#invalidateAll(java.lang.String)
+ */
+ public void invalidateAll(String id)
+ {
+ // Do not use interators as this method tends to be called recursively
+ // by the invalidate calls.
+ while (_sessions.containsKey(id))
+ {
+ Session session=(Session)_sessions.getValue(id,0);
+ if (session.isValid())
+ session.invalidate();
+ else
+ _sessions.removeValue(id,session);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * new Session ID. If the request has a requestedSessionID which is unique,
+ * that is used. The session ID is created as a unique random long XORed with
+ * connection specific information, base 36.
+ * @param request
+ * @param created
+ * @return Session ID.
+ */
+ public String newSessionId(HttpServletRequest request, long created)
+ {
+ synchronized (this)
+ {
+ // A requested session ID can only be used if it is in use already.
+ String requested_id=request.getRequestedSessionId();
+
+ if (requested_id!=null)
+ {
+ String cluster_id=getClusterId(requested_id);
+ if (idInUse(cluster_id))
+ return cluster_id;
+ }
+
+ // Else reuse any new session ID already defined for this request.
+ String new_id=(String)request.getAttribute(__NEW_SESSION_ID);
+ if (new_id!=null&&idInUse(new_id))
+ return new_id;
+
+ // pick a new unique ID!
+ String id=null;
+ while (id==null||id.length()==0||idInUse(id))
+ {
+ long r=_weakRandom
+ ?(hashCode()^Runtime.getRuntime().freeMemory()^_random.nextInt()^(((long)request.hashCode())<<32))
+ :_random.nextLong();
+ r^=created;
+ if (request!=null && request.getRemoteAddr()!=null)
+ r^=request.getRemoteAddr().hashCode();
+ if (r<0)
+ r=-r;
+ id=Long.toString(r,36);
+ }
+
+ request.setAttribute(__NEW_SESSION_ID,id);
+ return id;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public Random getRandom()
+ {
+ return _random;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setRandom(Random random)
+ {
+ _random=random;
+ _weakRandom=false;
+ }
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java
new file mode 100644
index 0000000000..a7b7c97727
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java
@@ -0,0 +1,652 @@
+// ========================================================================
+// Copyright (c) 1996-2009 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 java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.log.Log;
+
+
+/* ------------------------------------------------------------ */
+/** An in-memory implementation of SessionManager.
+ *
+ *
+ */
+public class HashSessionManager extends AbstractSessionManager
+{
+ private static int __id;
+ private Timer _timer;
+ private TimerTask _task;
+ private int _scavengePeriodMs=30000;
+ private int _savePeriodMs=0; //don't do period saves by default
+ private TimerTask _saveTask;
+ protected Map _sessions;
+ private File _storeDir;
+ private boolean _lazyLoad=false;
+ private boolean _sessionsLoaded=false;
+
+ /* ------------------------------------------------------------ */
+ public HashSessionManager()
+ {
+ super();
+ }
+
+ /* ------------------------------------------------------------ */
+ /* (non-Javadoc)
+ * @see org.eclipse.jetty.servlet.AbstractSessionManager#doStart()
+ */
+ public void doStart() throws Exception
+ {
+ _sessions=new ConcurrentHashMap(); // TODO: use syncronizedMap for JDK 1.4
+ super.doStart();
+
+ _timer=new Timer("HashSessionScavenger-"+__id++, true);
+
+ setScavengePeriod(getScavengePeriod());
+
+ if (_storeDir!=null)
+ {
+ if (!_storeDir.exists())
+ _storeDir.mkdir();
+
+ if (!_lazyLoad)
+ restoreSessions();
+ }
+
+ setSavePeriod(getSavePeriod());
+ }
+
+ /* ------------------------------------------------------------ */
+ /* (non-Javadoc)
+ * @see org.eclipse.jetty.servlet.AbstractSessionManager#doStop()
+ */
+ public void doStop() throws Exception
+ {
+
+ if (_storeDir != null)
+ saveSessions();
+
+ super.doStop();
+
+ _sessions.clear();
+ _sessions=null;
+
+ // stop the scavenger
+ synchronized(this)
+ {
+ if (_saveTask!=null)
+ _saveTask.cancel();
+ if (_task!=null)
+ _task.cancel();
+ if (_timer!=null)
+ _timer.cancel();
+ _timer=null;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return seconds
+ */
+ public int getScavengePeriod()
+ {
+ return _scavengePeriodMs/1000;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public Map getSessionMap()
+ {
+ return Collections.unmodifiableMap(_sessions);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public int getSessions()
+ {
+ return _sessions.size();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public void setMaxInactiveInterval(int seconds)
+ {
+ super.setMaxInactiveInterval(seconds);
+ if (_dftMaxIdleSecs>0&&_scavengePeriodMs>_dftMaxIdleSecs*1000)
+ setScavengePeriod((_dftMaxIdleSecs+9)/10);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setSavePeriod (int seconds)
+ {
+ int oldSavePeriod = _savePeriodMs;
+ int period = (seconds * 1000);
+ if (period < 0)
+ period=0;
+ _savePeriodMs=period;
+
+ if (_timer!=null)
+ {
+ synchronized (this)
+ {
+ if (_saveTask!=null)
+ _saveTask.cancel();
+ if (_savePeriodMs > 0 && _storeDir!=null) //only save if we have a directory configured
+ {
+ _saveTask = new TimerTask()
+ {
+ public void run()
+ {
+ try
+ {
+ saveSessions();
+ }
+ catch (Exception e)
+ {
+ Log.warn(e);
+ }
+ }
+ };
+ _timer.schedule(_saveTask,_savePeriodMs,_savePeriodMs);
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getSavePeriod ()
+ {
+ if (_savePeriodMs<=0)
+ return 0;
+
+ return _savePeriodMs/1000;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param seconds
+ */
+ public void setScavengePeriod(int seconds)
+ {
+ if (seconds==0)
+ seconds=60;
+
+ int old_period=_scavengePeriodMs;
+ int period=seconds*1000;
+ if (period>60000)
+ period=60000;
+ if (period<1000)
+ period=1000;
+
+ _scavengePeriodMs=period;
+ if (_timer!=null && (period!=old_period || _task==null))
+ {
+ synchronized (this)
+ {
+ if (_task!=null)
+ _task.cancel();
+ _task = new TimerTask()
+ {
+ public void run()
+ {
+ scavenge();
+ }
+ };
+ _timer.schedule(_task,_scavengePeriodMs,_scavengePeriodMs);
+ }
+ }
+ }
+
+ /* -------------------------------------------------------------- */
+ /**
+ * Find sessions that have timed out and invalidate them. This runs in the
+ * SessionScavenger thread.
+ */
+ private void scavenge()
+ {
+ //don't attempt to scavenge if we are shutting down
+ if (isStopping() || isStopped())
+ return;
+
+ Thread thread=Thread.currentThread();
+ ClassLoader old_loader=thread.getContextClassLoader();
+ try
+ {
+ if (_loader!=null)
+ thread.setContextClassLoader(_loader);
+
+ long now=System.currentTimeMillis();
+
+ try
+ {
+ if (!_sessionsLoaded && _lazyLoad)
+ restoreSessions();
+ }
+ catch(Exception e)
+ {
+ Log.debug(e);
+ }
+
+ // Since Hashtable enumeration is not safe over deletes,
+ // we build a list of stale sessions, then go back and invalidate
+ // them
+ Object stale=null;
+
+ synchronized (HashSessionManager.this)
+ {
+ // For each session
+ for (Iterator i=_sessions.values().iterator(); i.hasNext();)
+ {
+ Session session=(Session)i.next();
+ long idleTime=session._maxIdleMs;
+ if (idleTime>0&&session._accessed+idleTime<now)
+ {
+ // Found a stale session, add it to the list
+ stale=LazyList.add(stale,session);
+ }
+ }
+ }
+
+ // Remove the stale sessions
+ for (int i=LazyList.size(stale); i-->0;)
+ {
+ // check it has not been accessed in the meantime
+ Session session=(Session)LazyList.get(stale,i);
+ long idleTime=session._maxIdleMs;
+ if (idleTime>0&&session._accessed+idleTime<System.currentTimeMillis())
+ {
+ ((Session)session).timeout();
+ int nbsess=this._sessions.size();
+ if (nbsess<this._minSessions)
+ this._minSessions=nbsess;
+ }
+ }
+ }
+ catch (Throwable t)
+ {
+ if (t instanceof ThreadDeath)
+ throw ((ThreadDeath)t);
+ else
+ Log.warn("Problem scavenging sessions", t);
+ }
+ finally
+ {
+ thread.setContextClassLoader(old_loader);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void addSession(AbstractSessionManager.Session session)
+ {
+ _sessions.put(session.getClusterId(),session);
+ }
+
+ /* ------------------------------------------------------------ */
+ public AbstractSessionManager.Session getSession(String idInCluster)
+ {
+ try
+ {
+ if (!_sessionsLoaded && _lazyLoad)
+ restoreSessions();
+ }
+ catch(Exception e)
+ {
+ Log.warn(e);
+ }
+
+ if (_sessions==null)
+ return null;
+
+ return (Session)_sessions.get(idInCluster);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void invalidateSessions()
+ {
+ // Invalidate all sessions to cause unbind events
+ ArrayList sessions=new ArrayList(_sessions.values());
+ for (Iterator i=sessions.iterator(); i.hasNext();)
+ {
+ Session session=(Session)i.next();
+ session.invalidate();
+ }
+ _sessions.clear();
+
+ }
+
+ /* ------------------------------------------------------------ */
+ protected AbstractSessionManager.Session newSession(HttpServletRequest request)
+ {
+ return new Session(request);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected AbstractSessionManager.Session newSession(long created, String clusterId)
+ {
+ return new Session(created,clusterId);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void removeSession(String clusterId)
+ {
+ _sessions.remove(clusterId);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public void setStoreDirectory (File dir)
+ {
+ _storeDir=dir;
+ }
+
+ /* ------------------------------------------------------------ */
+ public File getStoreDirectory ()
+ {
+ return _storeDir;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setLazyLoad(boolean lazyLoad)
+ {
+ _lazyLoad = lazyLoad;
+ }
+
+ public boolean isLazyLoad()
+ {
+ return _lazyLoad;
+ }
+
+ public void restoreSessions () throws Exception
+ {
+ if (_storeDir==null || !_storeDir.exists())
+ {
+ return;
+ }
+
+ if (!_storeDir.canRead())
+ {
+ Log.warn ("Unable to restore Sessions: Cannot read from Session storage directory "+_storeDir.getAbsolutePath());
+ return;
+ }
+
+ File[] files = _storeDir.listFiles();
+ for (int i=0;files!=null&&i<files.length;i++)
+ {
+ try
+ {
+ FileInputStream in = new FileInputStream(files[i]);
+ Session session = restoreSession(in);
+ in.close();
+ addSession(session, false);
+ files[i].delete();
+ }
+ catch (Exception e)
+ {
+ Log.warn("Problem restoring session "+files[i].getName(), e);
+ }
+ }
+
+ _sessionsLoaded = true;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void saveSessions () throws Exception
+ {
+ if (_storeDir==null || !_storeDir.exists())
+ {
+ return;
+ }
+
+ if (!_storeDir.canWrite())
+ {
+ Log.warn ("Unable to save Sessions: Session persistence storage directory "+_storeDir.getAbsolutePath()+ " is not writeable");
+ return;
+ }
+
+ synchronized (this)
+ {
+ Iterator itor = _sessions.entrySet().iterator();
+ while (itor.hasNext())
+ {
+ Map.Entry entry = (Map.Entry)itor.next();
+ String id = (String)entry.getKey();
+ Session session = (Session)entry.getValue();
+ try
+ {
+ File file = new File (_storeDir, id);
+ if (file.exists())
+ file.delete();
+ file.createNewFile();
+ FileOutputStream fos = new FileOutputStream (file);
+ session.save(fos);
+ fos.close();
+ }
+ catch (Exception e)
+ {
+ Log.warn("Problem persisting session "+id, e);
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public Session restoreSession (InputStream is) throws Exception
+ {
+ /*
+ * Take care of this class's fields first by calling
+ * defaultReadObject
+ */
+ DataInputStream in = new DataInputStream(is);
+ String clusterId = in.readUTF();
+ String nodeId = in.readUTF();
+ boolean idChanged = in.readBoolean();
+ long created = in.readLong();
+ long cookieSet = in.readLong();
+ long accessed = in.readLong();
+ long lastAccessed = in.readLong();
+ //boolean invalid = in.readBoolean();
+ //boolean invalidate = in.readBoolean();
+ //long maxIdle = in.readLong();
+ //boolean isNew = in.readBoolean();
+ int requests = in.readInt();
+
+ Session session = (Session)newSession(created, clusterId);
+ session._cookieSet = cookieSet;
+ session._lastAccessed = lastAccessed;
+
+ int size = in.readInt();
+ if (size > 0)
+ {
+ ArrayList keys = new ArrayList();
+ for (int i=0; i<size; i++)
+ {
+ String key = in.readUTF();
+ keys.add(key);
+ }
+ ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(in);
+ for (int i=0;i<size;i++)
+ {
+ Object value = ois.readObject();
+ session.setAttribute((String)keys.get(i),value);
+ }
+ ois.close();
+ }
+ else
+ session.initValues();
+ in.close();
+ return session;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ protected class Session extends AbstractSessionManager.Session
+ {
+ /* ------------------------------------------------------------ */
+ private static final long serialVersionUID=-2134521374206116367L;
+
+ /* ------------------------------------------------------------- */
+ protected Session(HttpServletRequest request)
+ {
+ super(request);
+ }
+
+ /* ------------------------------------------------------------- */
+ protected Session(long created, String clusterId)
+ {
+ super(created, clusterId);
+ }
+
+ /* ------------------------------------------------------------- */
+ public void setMaxInactiveInterval(int secs)
+ {
+ super.setMaxInactiveInterval(secs);
+ if (_maxIdleMs>0&&(_maxIdleMs/10)<_scavengePeriodMs)
+ HashSessionManager.this.setScavengePeriod((secs+9)/10);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected Map newAttributeMap()
+ {
+ return new HashMap(3);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public void invalidate ()
+ throws IllegalStateException
+ {
+ super.invalidate();
+
+ remove();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void remove()
+ {
+ String id=getId();
+ if (id==null)
+ return;
+
+ //all sessions are invalidated when jetty is stopped, make sure we don't
+ //remove all the sessions in this case
+ if (isStopping() || isStopped())
+ return;
+
+ if (_storeDir==null || !_storeDir.exists())
+ {
+ return;
+ }
+
+ File f = new File(_storeDir, id);
+ f.delete();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void save(OutputStream os) throws IOException
+ {
+ DataOutputStream out = new DataOutputStream(os);
+ out.writeUTF(_clusterId);
+ out.writeUTF(_nodeId);
+ out.writeBoolean(_idChanged);
+ out.writeLong( _created);
+ out.writeLong(_cookieSet);
+ out.writeLong(_accessed);
+ out.writeLong(_lastAccessed);
+ /* Don't write these out, as they don't make sense to store because they
+ * either they cannot be true or their value will be restored in the
+ * Session constructor.
+ */
+ //out.writeBoolean(_invalid);
+ //out.writeBoolean(_doInvalidate);
+ //out.writeLong(_maxIdleMs);
+ //out.writeBoolean( _newSession);
+ out.writeInt(_requests);
+ if (_values != null)
+ {
+ out.writeInt(_values.size());
+ Iterator itor = _values.keySet().iterator();
+ while (itor.hasNext())
+ {
+ String key = (String)itor.next();
+ out.writeUTF(key);
+ }
+ itor = _values.values().iterator();
+ ObjectOutputStream oos = new ObjectOutputStream(out);
+ while (itor.hasNext())
+ {
+ oos.writeObject(itor.next());
+ }
+ oos.close();
+ }
+ else
+ out.writeInt(0);
+ out.close();
+ }
+
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ protected class ClassLoadingObjectInputStream extends ObjectInputStream
+ {
+ /* ------------------------------------------------------------ */
+ public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException
+ {
+ super(in);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ClassLoadingObjectInputStream () throws IOException
+ {
+ super();
+ }
+
+ /* ------------------------------------------------------------ */
+ public Class resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException
+ {
+ try
+ {
+ return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader());
+ }
+ catch (ClassNotFoundException e)
+ {
+ return super.resolveClass(cl);
+ }
+ }
+ }
+
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java
new file mode 100644
index 0000000000..0e386e1f28
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java
@@ -0,0 +1,695 @@
+// ========================================================================
+// Copyright (c) 2008-2009 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 java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.sql.Blob;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Random;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import javax.naming.InitialContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import javax.sql.DataSource;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.log.Log;
+
+
+
+/**
+ * JDBCSessionIdManager
+ *
+ * SessionIdManager implementation that uses a database to store in-use session ids,
+ * to support distributed sessions.
+ *
+ */
+public class JDBCSessionIdManager extends AbstractSessionIdManager
+{
+ protected HashSet<String> _sessionIds = new HashSet();
+ protected String _driverClassName;
+ protected String _connectionUrl;
+ protected DataSource _datasource;
+ protected String _jndiName;
+ protected String _sessionIdTable = "JettySessionIds";
+ protected String _sessionTable = "JettySessions";
+ protected Timer _timer; //scavenge timer
+ protected TimerTask _task; //scavenge task
+ protected long _lastScavengeTime;
+ protected long _scavengeIntervalMs = 1000 * 60 * 10; //10mins
+
+
+ protected String _createSessionIdTable;
+ protected String _createSessionTable;
+
+ protected String _selectExpiredSessions;
+ protected String _deleteOldExpiredSessions;
+
+ protected String _insertId;
+ protected String _deleteId;
+ protected String _queryId;
+
+ protected DatabaseAdaptor _dbAdaptor;
+
+
+ /**
+ * DatabaseAdaptor
+ *
+ * Handles differences between databases.
+ *
+ * Postgres uses the getBytes and setBinaryStream methods to access
+ * a "bytea" datatype, which can be up to 1Gb of binary data. MySQL
+ * is happy to use the "blob" type and getBlob() methods instead.
+ *
+ * TODO if the differences become more major it would be worthwhile
+ * refactoring this class.
+ */
+ public class DatabaseAdaptor
+ {
+ String _dbName;
+ boolean _isLower;
+ boolean _isUpper;
+
+
+ public DatabaseAdaptor (DatabaseMetaData dbMeta)
+ throws SQLException
+ {
+ _dbName = dbMeta.getDatabaseProductName().toLowerCase();
+ Log.debug ("Using database "+_dbName);
+ _isLower = dbMeta.storesLowerCaseIdentifiers();
+ _isUpper = dbMeta.storesUpperCaseIdentifiers();
+ }
+
+ /**
+ * Convert a camel case identifier into either upper or lower
+ * depending on the way the db stores identifiers.
+ *
+ * @param identifier
+ * @return
+ */
+ public String convertIdentifier (String identifier)
+ {
+ if (_isLower)
+ return identifier.toLowerCase();
+ if (_isUpper)
+ return identifier.toUpperCase();
+
+ return identifier;
+ }
+
+ public String getBlobType ()
+ {
+ if (_dbName.startsWith("postgres"))
+ return "bytea";
+
+ return "blob";
+ }
+
+ public InputStream getBlobInputStream (ResultSet result, String columnName)
+ throws SQLException
+ {
+ if (_dbName.startsWith("postgres"))
+ {
+ byte[] bytes = result.getBytes(columnName);
+ return new ByteArrayInputStream(bytes);
+ }
+
+ Blob blob = result.getBlob(columnName);
+ return blob.getBinaryStream();
+ }
+ }
+
+
+
+ public JDBCSessionIdManager(Server server)
+ {
+ super(server);
+ }
+
+ public JDBCSessionIdManager(Server server, Random random)
+ {
+ super(server, random);
+ }
+
+ /**
+ * Configure jdbc connection information via a jdbc Driver
+ *
+ * @param driverClassName
+ * @param connectionUrl
+ */
+ public void setDriverInfo (String driverClassName, String connectionUrl)
+ {
+ _driverClassName=driverClassName;
+ _connectionUrl=connectionUrl;
+ }
+
+ public String getDriverClassName()
+ {
+ return _driverClassName;
+ }
+
+ public String getConnectionUrl ()
+ {
+ return _connectionUrl;
+ }
+
+ public void setDatasourceName (String jndi)
+ {
+ _jndiName=jndi;
+ }
+
+ public String getDatasourceName ()
+ {
+ return _jndiName;
+ }
+
+
+ public void setScavengeInterval (long sec)
+ {
+ if (sec<=0)
+ sec=60;
+
+ long old_period=_scavengeIntervalMs;
+ long period=sec*1000;
+
+ _scavengeIntervalMs=period;
+
+ //add a bit of variability into the scavenge time so that not all
+ //nodes with the same scavenge time sync up
+ long tenPercent = _scavengeIntervalMs/10;
+ if ((System.currentTimeMillis()%2) == 0)
+ _scavengeIntervalMs += tenPercent;
+
+ if (Log.isDebugEnabled()) Log.debug("Scavenging every "+_scavengeIntervalMs+" ms");
+ if (_timer!=null && (period!=old_period || _task==null))
+ {
+ synchronized (this)
+ {
+ if (_task!=null)
+ _task.cancel();
+ _task = new TimerTask()
+ {
+ public void run()
+ {
+ scavenge();
+ }
+ };
+ _timer.schedule(_task,_scavengeIntervalMs,_scavengeIntervalMs);
+ }
+ }
+ }
+
+ public long getScavengeInterval ()
+ {
+ return _scavengeIntervalMs/1000;
+ }
+
+
+ public void addSession(HttpSession session)
+ {
+ if (session == null)
+ return;
+
+ synchronized (_sessionIds)
+ {
+ String id = ((JDBCSessionManager.Session)session).getClusterId();
+ try
+ {
+ insert(id);
+ _sessionIds.add(id);
+ }
+ catch (Exception e)
+ {
+ Log.warn("Problem storing session id="+id, e);
+ }
+ }
+ }
+
+ public void removeSession(HttpSession session)
+ {
+ if (session == null)
+ return;
+
+ removeSession(((JDBCSessionManager.Session)session).getClusterId());
+ }
+
+
+
+ public void removeSession (String id)
+ {
+
+ if (id == null)
+ return;
+
+ synchronized (_sessionIds)
+ {
+ if (Log.isDebugEnabled())
+ Log.debug("Removing session id="+id);
+ try
+ {
+ _sessionIds.remove(id);
+ delete(id);
+ }
+ catch (Exception e)
+ {
+ Log.warn("Problem removing session id="+id, e);
+ }
+ }
+
+ }
+
+
+ /**
+ * Get the session id without any node identifier suffix.
+ *
+ * @see org.eclipse.jetty.server.SessionIdManager#getClusterId(java.lang.String)
+ */
+ public String getClusterId(String nodeId)
+ {
+ int dot=nodeId.lastIndexOf('.');
+ return (dot>0)?nodeId.substring(0,dot):nodeId;
+ }
+
+
+ /**
+ * Get the session id, including this node's id as a suffix.
+ *
+ * @see org.eclipse.jetty.server.SessionIdManager#getNodeId(java.lang.String, javax.servlet.http.HttpServletRequest)
+ */
+ public String getNodeId(String clusterId, HttpServletRequest request)
+ {
+ if (_workerName!=null)
+ return clusterId+'.'+_workerName;
+
+ return clusterId;
+ }
+
+
+ public boolean idInUse(String id)
+ {
+ if (id == null)
+ return false;
+
+ String clusterId = getClusterId(id);
+
+ synchronized (_sessionIds)
+ {
+ if (_sessionIds.contains(clusterId))
+ return true; //optimisation - if this session is one we've been managing, we can check locally
+
+ //otherwise, we need to go to the database to check
+ try
+ {
+ return exists(clusterId);
+ }
+ catch (Exception e)
+ {
+ Log.warn("Problem checking inUse for id="+clusterId, e);
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Invalidate the session matching the id on all contexts.
+ *
+ * @see org.eclipse.jetty.server.SessionIdManager#invalidateAll(java.lang.String)
+ */
+ public void invalidateAll(String id)
+ {
+ //take the id out of the list of known sessionids for this node
+ removeSession(id);
+
+ synchronized (_sessionIds)
+ {
+ //tell all contexts that may have a session object with this id to
+ //get rid of them
+ Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
+ for (int i=0; contexts!=null && i<contexts.length; i++)
+ {
+ SessionManager manager = (SessionManager)
+ ((SessionHandler)((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class)).getSessionManager();
+
+ if (manager instanceof JDBCSessionManager)
+ {
+ ((JDBCSessionManager)manager).invalidateSession(id);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Start up the id manager.
+ *
+ * Makes necessary database tables and starts a Session
+ * scavenger thread.
+ *
+ * @see org.eclipse.jetty.server.session.AbstractSessionIdManager#doStart()
+ */
+ public void doStart()
+ {
+ try
+ {
+ initializeDatabase();
+ prepareTables();
+ super.doStart();
+ if (Log.isDebugEnabled()) Log.debug("Scavenging interval = "+getScavengeInterval()+" sec");
+ _timer=new Timer("JDBCSessionScavenger", true);
+ setScavengeInterval(getScavengeInterval());
+ }
+ catch (Exception e)
+ {
+ Log.warn("Problem initialising JettySessionIds table", e);
+ }
+ }
+
+ /**
+ * Stop the scavenger.
+ *
+ * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
+ */
+ public void doStop ()
+ throws Exception
+ {
+ synchronized(this)
+ {
+ if (_task!=null)
+ _task.cancel();
+ if (_timer!=null)
+ _timer.cancel();
+ _timer=null;
+ }
+ super.doStop();
+ }
+
+ /**
+ * Get a connection from the driver or datasource.
+ *
+ * @return
+ * @throws SQLException
+ */
+ protected Connection getConnection ()
+ throws SQLException
+ {
+ if (_datasource != null)
+ return _datasource.getConnection();
+ else
+ return DriverManager.getConnection(_connectionUrl);
+ }
+
+
+ private void initializeDatabase ()
+ throws Exception
+ {
+ if (_jndiName!=null)
+ {
+ InitialContext ic = new InitialContext();
+ _datasource = (DataSource)ic.lookup(_jndiName);
+ }
+ else if (_driverClassName!=null && _connectionUrl!=null)
+ {
+ Class.forName(_driverClassName);
+ }
+ else
+ throw new IllegalStateException("No database configured for sessions");
+ }
+
+
+
+ /**
+ * Set up the tables in the database
+ * @throws SQLException
+ */
+ private void prepareTables()
+ throws SQLException
+ {
+ _createSessionIdTable = "create table "+_sessionIdTable+" (id varchar(60), primary key(id))";
+ _selectExpiredSessions = "select * from "+_sessionTable+" where expiryTime >= ? and expiryTime <= ?";
+ _deleteOldExpiredSessions = "delete from "+_sessionTable+" where expiryTime >0 and expiryTime <= ?";
+
+ _insertId = "insert into "+_sessionIdTable+" (id) values (?)";
+ _deleteId = "delete from "+_sessionIdTable+" where id = ?";
+ _queryId = "select * from "+_sessionIdTable+" where id = ?";
+
+ Connection connection = null;
+ try
+ {
+ //make the id table
+ connection = getConnection();
+ connection.setAutoCommit(true);
+ DatabaseMetaData metaData = connection.getMetaData();
+ _dbAdaptor = new DatabaseAdaptor(metaData);
+
+ //checking for table existence is case-sensitive, but table creation is not
+ String tableName = _dbAdaptor.convertIdentifier(_sessionIdTable);
+ ResultSet result = metaData.getTables(null, null, tableName, null);
+ if (!result.next())
+ {
+ //table does not exist, so create it
+ connection.createStatement().executeUpdate(_createSessionIdTable);
+ }
+
+ //make the session table if necessary
+ tableName = _dbAdaptor.convertIdentifier(_sessionTable);
+ result = metaData.getTables(null, null, tableName, null);
+ if (!result.next())
+ {
+ //table does not exist, so create it
+ String blobType = _dbAdaptor.getBlobType();
+ _createSessionTable = "create table "+_sessionTable+" (rowId varchar(60), sessionId varchar(60), "+
+ " contextPath varchar(60), virtualHost varchar(60), lastNode varchar(60), accessTime bigint, "+
+ " lastAccessTime bigint, createTime bigint, cookieTime bigint, "+
+ " lastSavedTime bigint, expiryTime bigint, map "+blobType+", primary key(rowId))";
+ connection.createStatement().executeUpdate(_createSessionTable);
+ }
+
+ //make some indexes on the JettySessions table
+ String index1 = "idx_"+_sessionTable+"_expiry";
+ String index2 = "idx_"+_sessionTable+"_session";
+
+ result = metaData.getIndexInfo(null, null, tableName, false, false);
+ boolean index1Exists = false;
+ boolean index2Exists = false;
+ while (result.next())
+ {
+ String idxName = result.getString("INDEX_NAME");
+ if (index1.equalsIgnoreCase(idxName))
+ index1Exists = true;
+ else if (index2.equalsIgnoreCase(idxName))
+ index2Exists = true;
+ }
+ if (!(index1Exists && index2Exists))
+ {
+ Statement statement = connection.createStatement();
+ if (!index1Exists)
+ statement.executeUpdate("create index "+index1+" on "+_sessionTable+" (expiryTime)");
+ if (!index2Exists)
+ statement.executeUpdate("create index "+index2+" on "+_sessionTable+" (sessionId, contextPath)");
+ }
+ }
+ finally
+ {
+ if (connection != null)
+ connection.close();
+ }
+ }
+
+ /**
+ * Insert a new used session id into the table.
+ *
+ * @param id
+ * @throws SQLException
+ */
+ private void insert (String id)
+ throws SQLException
+ {
+ Connection connection = null;
+ try
+ {
+ connection = getConnection();
+ connection.setAutoCommit(true);
+ PreparedStatement query = connection.prepareStatement(_queryId);
+ query.setString(1, id);
+ ResultSet result = query.executeQuery();
+ //only insert the id if it isn't in the db already
+ if (!result.next())
+ {
+ PreparedStatement statement = connection.prepareStatement(_insertId);
+ statement.setString(1, id);
+ statement.executeUpdate();
+ }
+ }
+ finally
+ {
+ if (connection != null)
+ connection.close();
+ }
+ }
+
+ /**
+ * Remove a session id from the table.
+ *
+ * @param id
+ * @throws SQLException
+ */
+ private void delete (String id)
+ throws SQLException
+ {
+ Connection connection = null;
+ try
+ {
+ connection = getConnection();
+ connection.setAutoCommit(true);
+ PreparedStatement statement = connection.prepareStatement(_deleteId);
+ statement.setString(1, id);
+ statement.executeUpdate();
+ }
+ finally
+ {
+ if (connection != null)
+ connection.close();
+ }
+ }
+
+
+ /**
+ * Check if a session id exists.
+ *
+ * @param id
+ * @return
+ * @throws SQLException
+ */
+ private boolean exists (String id)
+ throws SQLException
+ {
+ Connection connection = null;
+ try
+ {
+ connection = getConnection();
+ connection.setAutoCommit(true);
+ PreparedStatement statement = connection.prepareStatement(_queryId);
+ statement.setString(1, id);
+ ResultSet result = statement.executeQuery();
+ if (result.next())
+ return true;
+ else
+ return false;
+ }
+ finally
+ {
+ if (connection != null)
+ connection.close();
+ }
+ }
+
+ /**
+ * Look for sessions in the database that have expired.
+ *
+ * We do this in the SessionIdManager and not the SessionManager so
+ * that we only have 1 scavenger, otherwise if there are n SessionManagers
+ * there would be n scavengers, all contending for the database.
+ *
+ * We look first for sessions that expired in the previous interval, then
+ * for sessions that expired previously - these are old sessions that no
+ * node is managing any more and have become stuck in the database.
+ */
+ private void scavenge ()
+ {
+ Connection connection = null;
+ List expiredSessionIds = new ArrayList();
+ try
+ {
+ if (Log.isDebugEnabled()) Log.debug("Scavenge sweep started at "+System.currentTimeMillis());
+ if (_lastScavengeTime > 0)
+ {
+ connection = getConnection();
+ connection.setAutoCommit(true);
+ //"select sessionId from JettySessions where expiryTime > (lastScavengeTime - scanInterval) and expiryTime < lastScavengeTime";
+ PreparedStatement statement = connection.prepareStatement(_selectExpiredSessions);
+ long lowerBound = (_lastScavengeTime - _scavengeIntervalMs);
+ long upperBound = _lastScavengeTime;
+ if (Log.isDebugEnabled()) Log.debug("Searching for sessions expired between "+lowerBound + " and "+upperBound);
+ statement.setLong(1, lowerBound);
+ statement.setLong(2, upperBound);
+ ResultSet result = statement.executeQuery();
+ while (result.next())
+ {
+ String sessionId = result.getString("sessionId");
+ expiredSessionIds.add(sessionId);
+ if (Log.isDebugEnabled()) Log.debug("Found expired sessionId="+sessionId);
+ }
+
+
+ //tell the SessionManagers to expire any sessions with a matching sessionId in memory
+ Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
+ for (int i=0; contexts!=null && i<contexts.length; i++)
+ {
+ SessionManager manager = (SessionManager)
+ ((SessionHandler)((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class)).getSessionManager();
+
+ if (manager instanceof JDBCSessionManager)
+ {
+ ((JDBCSessionManager)manager).expire(expiredSessionIds);
+ }
+ }
+
+ //find all sessions that have expired at least a couple of scanIntervals ago and just delete them
+ upperBound = _lastScavengeTime - (2 * _scavengeIntervalMs);
+ if (upperBound > 0)
+ {
+ if (Log.isDebugEnabled()) Log.debug("Deleting old expired sessions expired before "+upperBound);
+ statement = connection.prepareStatement(_deleteOldExpiredSessions);
+ statement.setLong(1, upperBound);
+ statement.executeUpdate();
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Log.warn("Problem selecting expired sessions", e);
+ }
+ finally
+ {
+ _lastScavengeTime=System.currentTimeMillis();
+ if (Log.isDebugEnabled()) Log.debug("Scavenge sweep ended at "+_lastScavengeTime);
+ if (connection != null)
+ {
+ try
+ {
+ connection.close();
+ }
+ catch (SQLException e)
+ {
+ Log.warn(e);
+ }
+ }
+ }
+ }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java
new file mode 100644
index 0000000000..963a0a57dc
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java
@@ -0,0 +1,1080 @@
+// ========================================================================
+// Copyright (c) 2008-2009 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 java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
+
+import org.eclipse.jetty.server.SessionIdManager;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.log.Log;
+
+/**
+ * JDBCSessionManager
+ *
+ * SessionManager that persists sessions to a database to enable clustering.
+ *
+ * Session data is persisted to the JettySessions table:
+ *
+ * rowId (unique in cluster: webapp name/path + virtualhost + sessionId)
+ * contextPath (of the context owning the session)
+ * sessionId (unique in a context)
+ * lastNode (name of node last handled session)
+ * accessTime (time in ms session was accessed)
+ * lastAccessTime (previous time in ms session was accessed)
+ * createTime (time in ms session created)
+ * cookieTime (time in ms session cookie created)
+ * lastSavedTime (last time in ms session access times were saved)
+ * expiryTime (time in ms that the session is due to expire)
+ * map (attribute map)
+ *
+ * As an optimisation, to prevent thrashing the database, we do not persist
+ * the accessTime and lastAccessTime every time the session is accessed. Rather,
+ * we write it out every so often. The frequency is controlled by the saveIntervalSec
+ * field.
+ */
+public class JDBCSessionManager extends AbstractSessionManager
+{
+ protected String __insertSession;
+ protected String __deleteSession;
+ protected String __selectSession;
+ protected String __updateSession;
+ protected String __updateSessionNode;
+ protected String __updateSessionAccessTime;
+
+ private ConcurrentHashMap _sessions;
+ protected long _saveIntervalSec = 60; //only persist changes to session access times every 60 secs
+
+ /**
+ * SessionData
+ *
+ * Persistable data about a session.
+ */
+ public class SessionData
+ {
+ private String _id;
+ private String _rowId;
+ private long _accessed;
+ private long _lastAccessed;
+ private long _maxIdleMs;
+ private long _cookieSet;
+ private long _created;
+ private Map _attributes;
+ private String _lastNode;
+ private String _canonicalContext;
+ private long _lastSaved;
+ private long _expiryTime;
+ private String _virtualHost;
+
+ public SessionData (String sessionId)
+ {
+ _id=sessionId;
+ _created=System.currentTimeMillis();
+ _accessed = _created;
+ _attributes = new ConcurrentHashMap();
+ _lastNode = getIdManager().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)
+ {
+ _rowId=rowId;
+ }
+
+ protected synchronized String getRowId()
+ {
+ return _rowId;
+ }
+
+ protected synchronized Map getAttributeMap ()
+ {
+ return _attributes;
+ }
+
+ protected synchronized void setAttributeMap (ConcurrentHashMap map)
+ {
+ _attributes = map;
+ }
+
+ public synchronized void setLastNode (String node)
+ {
+ _lastNode=node;
+ }
+
+ public synchronized String getLastNode ()
+ {
+ return _lastNode;
+ }
+
+ public synchronized void setCanonicalContext(String str)
+ {
+ _canonicalContext=str;
+ }
+
+ public synchronized String getCanonicalContext ()
+ {
+ return _canonicalContext;
+ }
+
+ public synchronized long getLastSaved ()
+ {
+ return _lastSaved;
+ }
+
+ public synchronized void setLastSaved (long time)
+ {
+ _lastSaved=time;
+ }
+
+ public synchronized void setExpiryTime (long time)
+ {
+ _expiryTime=time;
+ }
+
+ public synchronized long getExpiryTime ()
+ {
+ return _expiryTime;
+ }
+
+ public synchronized void setVirtualHost (String vhost)
+ {
+ _virtualHost=vhost;
+ }
+
+ public synchronized String getVirtualHost ()
+ {
+ return _virtualHost;
+ }
+
+ public String toString ()
+ {
+ return "Session rowId="+_rowId+",id="+_id+",lastNode="+_lastNode+
+ ",created="+_created+",accessed="+_accessed+
+ ",lastAccessed="+_lastAccessed+",cookieSet="+_cookieSet+
+ "lastSaved="+_lastSaved;
+ }
+ }
+
+
+
+ /**
+ * Session
+ *
+ * Session instance in memory of this node.
+ */
+ public class Session extends AbstractSessionManager.Session
+ {
+ private SessionData _data;
+ private boolean _dirty=false;
+
+ /**
+ * Session from a request.
+ *
+ * @param request
+ */
+ protected Session (HttpServletRequest request)
+ {
+
+ super(request);
+ _data = new SessionData(_clusterId);
+ _data.setMaxIdleMs(_dftMaxIdleSecs*1000);
+ _data.setCanonicalContext(canonicalize(_context.getContextPath()));
+ _data.setVirtualHost(getVirtualHost(_context));
+ _data.setExpiryTime(_maxIdleMs < 0 ? 0 : (System.currentTimeMillis() + _maxIdleMs));
+ _values=_data.getAttributeMap();
+ }
+
+ /**
+ * Session restored in database.
+ * @param row
+ */
+ protected Session (SessionData data)
+ {
+ super(data.getCreated(), data.getId());
+ _data=data;
+ _values=data.getAttributeMap();
+ }
+
+ protected Map newAttributeMap()
+ {
+ return _data.getAttributeMap();
+ }
+
+ public void setAttribute (String name, Object value)
+ {
+ super.setAttribute(name, value);
+ _dirty=true;
+ }
+
+ public void removeAttribute (String name)
+ {
+ super.removeAttribute(name);
+ _dirty=true;
+ }
+
+ protected void cookieSet()
+ {
+ _data.setCookieSet(_data.getAccessed());
+ }
+
+ /**
+ * Entry to session.
+ * Called by SessionHandler on inbound request and the session already exists in this node's memory.
+ *
+ * @see org.eclipse.jetty.server.session.AbstractSessionManager.Session#access(long)
+ */
+ protected void access(long time)
+ {
+ super.access(time);
+ _data.setLastAccessed(_data.getAccessed());
+ _data.setAccessed(time);
+ _data.setExpiryTime(_maxIdleMs < 0 ? 0 : (time + _maxIdleMs));
+ }
+
+ /**
+ * Exit from session
+ * @see org.eclipse.jetty.server.session.AbstractSessionManager.Session#complete()
+ */
+ protected void complete()
+ {
+ super.complete();
+ try
+ {
+ if (_dirty)
+ {
+ //The session attributes have changed, write to the db, ensuring
+ //http passivation/activation listeners called
+ willPassivate();
+ updateSession(_data);
+ didActivate();
+ }
+ else if ((_data._accessed - _data._lastSaved) >= (getSaveInterval() * 1000))
+ updateSessionAccessTime(_data);
+
+ }
+ catch (Exception e)
+ {
+ Log.warn("Problem persisting changed session data id="+getId(), e);
+ }
+ finally
+ {
+ _dirty=false;
+ }
+ }
+
+ protected void timeout() throws IllegalStateException
+ {
+ if (Log.isDebugEnabled()) Log.debug("Timing out session id="+getClusterId());
+ super.timeout();
+ }
+ }
+
+
+
+
+ /**
+ * ClassLoadingObjectInputStream
+ *
+ *
+ */
+ protected class ClassLoadingObjectInputStream extends ObjectInputStream
+ {
+ public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException
+ {
+ super(in);
+ }
+
+ public ClassLoadingObjectInputStream () throws IOException
+ {
+ super();
+ }
+
+ public Class resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException
+ {
+ try
+ {
+ return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader());
+ }
+ catch (ClassNotFoundException e)
+ {
+ return super.resolveClass(cl);
+ }
+ }
+ }
+
+
+
+
+ /**
+ * Set the time in seconds which is the interval between
+ * saving the session access time to the database.
+ *
+ * This is an optimization that prevents the database from
+ * being overloaded when a session is accessed very frequently.
+ *
+ * On session exit, if the session attributes have NOT changed,
+ * the time at which we last saved the accessed
+ * time is compared to the current accessed time. If the interval
+ * is at least saveIntervalSecs, then the access time will be
+ * persisted to the database.
+ *
+ * If any session attribute does change, then the attributes and
+ * the accessed time are persisted.
+ *
+ * @param sec
+ */
+ public void setSaveInterval (long sec)
+ {
+ _saveIntervalSec=sec;
+ }
+
+ public long getSaveInterval ()
+ {
+ return _saveIntervalSec;
+ }
+
+
+ /**
+ * A session has been requested by it's 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)
+ * but they CANNOT share the same contents.
+ *
+ * Check if last node id is my node id, if so, then the session we have
+ * in memory cannot be stale. If another node used the session last, then
+ * we need to refresh from the db.
+ *
+ * NOTE: this method will go to the database, so if you only want to check
+ * for the existence of a Session in memory, use _sessions.get(id) instead.
+ *
+ * @see org.eclipse.jetty.server.session.AbstractSessionManager#getSession(java.lang.String)
+ */
+ public Session getSession(String idInCluster)
+ {
+ Session session = (Session)_sessions.get(idInCluster);
+
+ synchronized (this)
+ {
+ try
+ {
+ //check if we need to reload the session - don't do it on every call
+ //to reduce the load on the database. This introduces a window of
+ //possibility that the node may decide that the session is local to it,
+ //when the session has actually been live on another node, and then
+ //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;
+ long now = System.currentTimeMillis();
+ if (Log.isDebugEnabled()) Log.debug("now="+now+
+ " lastSaved="+(session==null?0:session._data._lastSaved)+
+ " interval="+(_saveIntervalSec * 1000)+
+ " difference="+(now - (session==null?0:session._data._lastSaved)));
+ if (session==null || ((now - session._data._lastSaved) >= (_saveIntervalSec * 1000)))
+ {
+ data = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context));
+ }
+ else
+ data = session._data;
+
+ if (data != null)
+ {
+ if (!data.getLastNode().equals(getIdManager().getWorkerName()) || session==null)
+ {
+ //session last used on a different node, or we don't have it in memory
+ session = new Session(data);
+ _sessions.put(idInCluster, 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);
+ }
+ else
+ if (Log.isDebugEnabled()) Log.debug("Session not stale "+session._data);
+ //session in db shares same id, but is not for this context
+ }
+ else
+ {
+ //No session in db with matching id and context path.
+ session=null;
+ if (Log.isDebugEnabled()) Log.debug("No session in database matching id="+idInCluster);
+ }
+
+ return session;
+ }
+ catch (Exception e)
+ {
+ Log.warn("Unable to load session from database", e);
+ return null;
+ }
+ }
+ }
+
+
+ /**
+ * Get all the sessions as a map of id to Session.
+ *
+ * @see org.eclipse.jetty.server.session.AbstractSessionManager#getSessionMap()
+ */
+ public Map getSessionMap()
+ {
+ return Collections.unmodifiableMap(_sessions);
+ }
+
+
+ /**
+ * Get the number of sessions.
+ *
+ * @see org.eclipse.jetty.server.session.AbstractSessionManager#getSessions()
+ */
+ public int getSessions()
+ {
+ int size = 0;
+ synchronized (this)
+ {
+ size = _sessions.size();
+ }
+ return size;
+ }
+
+
+ /**
+ * Start the session manager.
+ *
+ * @see org.eclipse.jetty.server.session.AbstractSessionManager#doStart()
+ */
+ public void doStart() throws Exception
+ {
+ if (_sessionIdManager==null)
+ throw new IllegalStateException("No session id manager defined");
+
+ prepareTables();
+
+ _sessions = new ConcurrentHashMap();
+ super.doStart();
+ }
+
+
+ /**
+ * Stop the session manager.
+ *
+ * @see org.eclipse.jetty.server.session.AbstractSessionManager#doStop()
+ */
+ public void doStop() throws Exception
+ {
+ _sessions.clear();
+ _sessions = null;
+
+ super.doStop();
+ }
+
+ protected void invalidateSessions()
+ {
+ //Do nothing - we don't want to remove and
+ //invalidate all the sessions because this
+ //method is called from doStop(), and just
+ //because this context is stopping does not
+ //mean that we should remove the session from
+ //any other nodes
+ }
+
+
+ /**
+ * Invalidate a session.
+ *
+ * @param idInCluster
+ */
+ protected void invalidateSession (String idInCluster)
+ {
+ synchronized (this)
+ {
+ Session session = (Session)_sessions.get(idInCluster);
+ if (session != null)
+ {
+ session.invalidate();
+ }
+ }
+ }
+
+ /**
+ * Delete an existing session, both from the in-memory map and
+ * the database.
+ *
+ * @see org.eclipse.jetty.server.session.AbstractSessionManager#removeSession(java.lang.String)
+ */
+ protected void removeSession(String idInCluster)
+ {
+ synchronized (this)
+ {
+ try
+ {
+ Session session = (Session)_sessions.remove(idInCluster);
+ deleteSession(session._data);
+ }
+ catch (Exception e)
+ {
+ Log.warn("Problem deleting session id="+idInCluster, e);
+ }
+ }
+ }
+
+
+ /**
+ * Add a newly created session to our in-memory list for this node and persist it.
+ *
+ * @see org.eclipse.jetty.server.session.AbstractSessionManager#addSession(org.eclipse.jetty.server.session.AbstractSessionManager.Session)
+ */
+ protected void addSession(AbstractSessionManager.Session session)
+ {
+ if (session==null)
+ return;
+
+ synchronized (this)
+ {
+ _sessions.put(session.getClusterId(), session);
+ //TODO or delay the store until exit out of session? If we crash before we store it
+ //then session data will be lost.
+ try
+ {
+ ((JDBCSessionManager.Session)session).willPassivate();
+ storeSession(((JDBCSessionManager.Session)session)._data);
+ ((JDBCSessionManager.Session)session).didActivate();
+ }
+ catch (Exception e)
+ {
+ Log.warn("Unable to store new session id="+session.getId() , e);
+ }
+ }
+ }
+
+
+ /**
+ * Make a new Session.
+ *
+ * @see org.eclipse.jetty.server.session.AbstractSessionManager#newSession(javax.servlet.http.HttpServletRequest)
+ */
+ protected AbstractSessionManager.Session newSession(HttpServletRequest request)
+ {
+ return new Session(request);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Remove session from manager
+ * @param session The session to remove
+ * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and
+ * {@link SessionIdManager#invalidateAll(String)} should be called.
+ */
+ public void removeSession(AbstractSessionManager.Session session, boolean invalidate)
+ {
+ // Remove session from context and global maps
+ synchronized (_sessionIdManager)
+ {
+ boolean removed = false;
+
+ synchronized (this)
+ {
+ //take this session out of the map of sessions for this context
+ if (_sessions.get(session.getClusterId()) != null)
+ {
+ removed = true;
+ removeSession(session.getClusterId());
+ }
+ }
+
+ if (removed)
+ {
+ // Remove session from all context and global id maps
+ _sessionIdManager.removeSession(session);
+ if (invalidate)
+ _sessionIdManager.invalidateAll(session.getClusterId());
+ }
+ }
+
+ if (invalidate && _sessionListeners!=null)
+ {
+ HttpSessionEvent event=new HttpSessionEvent(session);
+ for (int i=LazyList.size(_sessionListeners); i-->0;)
+ ((HttpSessionListener)LazyList.get(_sessionListeners,i)).sessionDestroyed(event);
+ }
+ if (!invalidate)
+ {
+ session.willPassivate();
+ }
+ }
+
+
+ /**
+ * Expire any Sessions we have in memory matching the list of
+ * expired Session ids.
+ *
+ * @param sessionIds
+ */
+ protected void expire (List sessionIds)
+ {
+ //don't attempt to scavenge if we are shutting down
+ if (isStopping() || isStopped())
+ return;
+
+ //Remove any sessions we already have in memory that match the ids
+ Thread thread=Thread.currentThread();
+ ClassLoader old_loader=thread.getContextClassLoader();
+ ListIterator itor = sessionIds.listIterator();
+
+ try
+ {
+ while (itor.hasNext())
+ {
+ String sessionId = (String)itor.next();
+ if (Log.isDebugEnabled()) Log.debug("Expiring session id "+sessionId);
+ Session session = (Session)_sessions.get(sessionId);
+ if (session != null)
+ {
+ session.timeout();
+ itor.remove();
+ int count = this._sessions.size();
+ if (count < this._minSessions)
+ this._minSessions=count;
+ }
+ else
+ {
+ if (Log.isDebugEnabled()) Log.debug("Unrecognized session id="+sessionId);
+ }
+ }
+ }
+ catch (Throwable t)
+ {
+ if (t instanceof ThreadDeath)
+ throw ((ThreadDeath)t);
+ else
+ Log.warn("Problem expiring sessions", t);
+ }
+ finally
+ {
+ thread.setContextClassLoader(old_loader);
+ }
+ }
+
+
+ protected void prepareTables ()
+ {
+ __insertSession = "insert into "+((JDBCSessionIdManager)_sessionIdManager)._sessionTable+
+ " (rowId, sessionId, contextPath, virtualHost, lastNode, accessTime, lastAccessTime, createTime, cookieTime, lastSavedTime, expiryTime, map) "+
+ " values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+
+ __deleteSession = "delete from "+((JDBCSessionIdManager)_sessionIdManager)._sessionTable+
+ " where rowId = ?";
+
+ __selectSession = "select * from "+((JDBCSessionIdManager)_sessionIdManager)._sessionTable+
+ " where sessionId = ? and contextPath = ? and virtualHost = ?";
+
+ __updateSession = "update "+((JDBCSessionIdManager)_sessionIdManager)._sessionTable+
+ " set lastNode = ?, accessTime = ?, lastAccessTime = ?, lastSavedTime = ?, expiryTime = ?, map = ? where rowId = ?";
+
+ __updateSessionNode = "update "+((JDBCSessionIdManager)_sessionIdManager)._sessionTable+
+ " set lastNode = ? where rowId = ?";
+
+ __updateSessionAccessTime = "update "+((JDBCSessionIdManager)_sessionIdManager)._sessionTable+
+ " set lastNode = ?, accessTime = ?, lastAccessTime = ?, lastSavedTime = ?, expiryTime = ? where rowId = ?";
+ }
+
+ /**
+ * Load a session from the database
+ * @param id
+ * @return
+ * @throws Exception
+ */
+ protected SessionData loadSession (String id, String canonicalContextPath, String vhost)
+ throws Exception
+ {
+ SessionData data = null;
+ Connection connection = getConnection();
+ PreparedStatement statement = null;
+ try
+ {
+ statement = connection.prepareStatement(__selectSession);
+ statement.setString(1, id);
+ statement.setString(2, canonicalContextPath);
+ statement.setString(3, vhost);
+ ResultSet result = statement.executeQuery();
+ if (result.next())
+ {
+ data = new SessionData(id);
+ data.setRowId(result.getString("rowId"));
+ 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"));
+
+ InputStream is = ((JDBCSessionIdManager)getIdManager())._dbAdaptor.getBlobInputStream(result, "map");
+ ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream (is);
+ Object o = ois.readObject();
+ data.setAttributeMap((ConcurrentHashMap)o);
+ ois.close();
+
+ if (Log.isDebugEnabled())
+ Log.debug("LOADED session "+data);
+ }
+ return data;
+ }
+ finally
+ {
+ if (connection!=null)
+ connection.close();
+ }
+ }
+
+ /**
+ * Insert a session into the database.
+ *
+ * @param data
+ * @throws Exception
+ */
+ protected void storeSession (SessionData data)
+ throws Exception
+ {
+ if (data==null)
+ return;
+
+ //put into the database
+ Connection connection = getConnection();
+ PreparedStatement statement = null;
+ try
+ {
+ String rowId = calculateRowId(data);
+
+ long now = System.currentTimeMillis();
+ connection.setAutoCommit(true);
+ statement = connection.prepareStatement(__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(5, getIdManager().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(10, now); //last saved time
+ statement.setLong(11, data.getExpiryTime());
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+ oos.writeObject(data.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);
+
+
+ if (Log.isDebugEnabled())
+ Log.debug("Stored session "+data);
+ }
+ finally
+ {
+ if (connection!=null)
+ connection.close();
+ }
+ }
+
+
+ /**
+ * Update data on an existing persisted session.
+ *
+ * @param data
+ * @throws Exception
+ */
+ protected void updateSession (SessionData data)
+ throws Exception
+ {
+ if (data==null)
+ return;
+
+ Connection connection = getConnection();
+ PreparedStatement statement = null;
+ try
+ {
+ long now = System.currentTimeMillis();
+ connection.setAutoCommit(true);
+ statement = connection.prepareStatement(__updateSession);
+ statement.setString(1, getIdManager().getWorkerName());//my node id
+ statement.setLong(2, data.getAccessed());//accessTime
+ statement.setLong(3, data.getLastAccessed()); //lastAccessTime
+ statement.setLong(4, now); //last saved time
+ statement.setLong(5, data.getExpiryTime());
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+ oos.writeObject(data.getAttributeMap());
+ byte[] bytes = baos.toByteArray();
+ ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
+
+ statement.setBinaryStream(6, bais, bytes.length);//attribute map as blob
+ statement.setString(7, data.getRowId()); //rowId
+ statement.executeUpdate();
+
+ data.setLastSaved(now);
+ if (Log.isDebugEnabled())
+ Log.debug("Updated session "+data);
+ }
+ finally
+ {
+ if (connection!=null)
+ connection.close();
+ }
+ }
+
+
+ /**
+ * Update the node on which the session was last seen to be my node.
+ *
+ * @param data
+ * @throws Exception
+ */
+ protected void updateSessionNode (SessionData data)
+ throws Exception
+ {
+ String nodeId = getIdManager().getWorkerName();
+ Connection connection = getConnection();
+ PreparedStatement statement = null;
+ try
+ {
+ connection.setAutoCommit(true);
+ statement = connection.prepareStatement(__updateSessionNode);
+ statement.setString(1, nodeId);
+ statement.setString(2, data.getRowId());
+ statement.executeUpdate();
+ statement.close();
+ if (Log.isDebugEnabled())
+ Log.debug("Updated last node for session id="+data.getId()+", lastNode = "+nodeId);
+ }
+ finally
+ {
+ if (connection!=null)
+ connection.close();
+ }
+ }
+
+ /**
+ * Persist the time the session was last accessed.
+ *
+ * @param data
+ * @throws Exception
+ */
+ private void updateSessionAccessTime (SessionData data)
+ throws Exception
+ {
+ Connection connection = getConnection();
+ PreparedStatement statement = null;
+ try
+ {
+ long now = System.currentTimeMillis();
+ connection.setAutoCommit(true);
+ statement = connection.prepareStatement(__updateSessionAccessTime);
+ statement.setString(1, getIdManager().getWorkerName());
+ statement.setLong(2, data.getAccessed());
+ statement.setLong(3, data.getLastAccessed());
+ statement.setLong(4, now);
+ statement.setLong(5, data.getExpiryTime());
+ statement.setString(6, data.getRowId());
+ statement.executeUpdate();
+ data.setLastSaved(now);
+ statement.close();
+ if (Log.isDebugEnabled())
+ Log.debug("Updated access time session id="+data.getId());
+ }
+ finally
+ {
+ if (connection!=null)
+ connection.close();
+ }
+ }
+
+
+
+
+ /**
+ * Delete a session from the database. Should only be called
+ * when the session has been invalidated.
+ *
+ * @param data
+ * @throws Exception
+ */
+ protected void deleteSession (SessionData data)
+ throws Exception
+ {
+ Connection connection = getConnection();
+ PreparedStatement statement = null;
+ try
+ {
+ connection.setAutoCommit(true);
+ statement = connection.prepareStatement(__deleteSession);
+ statement.setString(1, data.getRowId());
+ statement.executeUpdate();
+ if (Log.isDebugEnabled())
+ Log.debug("Deleted Session "+data);
+ }
+ finally
+ {
+ if (connection!=null)
+ connection.close();
+ }
+ }
+
+
+
+ /**
+ * Get a connection from the driver.
+ * @return
+ * @throws SQLException
+ */
+ private Connection getConnection ()
+ throws SQLException
+ {
+ return ((JDBCSessionIdManager)getIdManager()).getConnection();
+ }
+
+ /**
+ * Calculate a unique id for this session across the cluster.
+ *
+ * Unique id is composed of: contextpath_virtualhost0_sessionid
+ * @param data
+ * @return
+ */
+ private String calculateRowId (SessionData data)
+ {
+ String rowId = canonicalize(_context.getContextPath());
+ rowId = rowId + "_" + getVirtualHost(_context);
+ rowId = rowId+"_"+data.getId();
+ return rowId;
+ }
+
+ /**
+ * Get the first virtual host for the context.
+ *
+ * Used to help identify the exact session/contextPath.
+ *
+ * @return 0.0.0.0 if no virtual host is defined
+ */
+ private String getVirtualHost (ContextHandler.Context context)
+ {
+ String vhost = "0.0.0.0";
+
+ if (context==null)
+ return vhost;
+
+ String [] vhosts = context.getContextHandler().getVirtualHosts();
+ if (vhosts==null || vhosts.length==0 || vhosts[0]==null)
+ return vhost;
+
+ return vhosts[0];
+ }
+
+ /**
+ * Make an acceptable file name from a context path.
+ *
+ * @param path
+ * @return
+ */
+ private String canonicalize (String path)
+ {
+ if (path==null)
+ return "";
+
+ return path.replace('/', '_').replace('.','_').replace('\\','_');
+ }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java
new file mode 100644
index 0000000000..cac62c2e41
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java
@@ -0,0 +1,291 @@
+// ========================================================================
+// Copyright (c) 2004-2009 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 java.io.IOException;
+import java.util.EnumSet;
+import java.util.EventListener;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.ServletException;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.server.HttpConnection;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.RetryRequest;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.util.log.Log;
+
+/* ------------------------------------------------------------ */
+/** SessionHandler.
+ *
+ *
+ *
+ */
+public class SessionHandler extends HandlerWrapper
+{
+ public final static EnumSet<SessionTrackingMode> DEFAULT_TRACKING = EnumSet.of(SessionTrackingMode.COOKIE,SessionTrackingMode.URL);
+
+ /* -------------------------------------------------------------- */
+ private SessionManager _sessionManager;
+
+ /* ------------------------------------------------------------ */
+ /** Constructor.
+ * Construct a SessionHandler witha a HashSessionManager with a standard
+ * java.util.Random generator is created.
+ */
+ public SessionHandler()
+ {
+ this(new HashSessionManager());
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param manager The session manager
+ */
+ public SessionHandler(SessionManager manager)
+ {
+ setSessionManager(manager);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the sessionManager.
+ */
+ public SessionManager getSessionManager()
+ {
+ return _sessionManager;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param sessionManager The sessionManager to set.
+ */
+ public void setSessionManager(SessionManager sessionManager)
+ {
+ if (isStarted())
+ throw new IllegalStateException();
+ SessionManager old_session_manager = _sessionManager;
+
+ if (getServer()!=null)
+ getServer().getContainer().update(this, old_session_manager, sessionManager, "sessionManager",true);
+
+ if (sessionManager!=null)
+ sessionManager.setSessionHandler(this);
+
+ _sessionManager = sessionManager;
+
+ if (old_session_manager!=null)
+ old_session_manager.setSessionHandler(null);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public void setServer(Server server)
+ {
+ Server old_server=getServer();
+ if (old_server!=null && old_server!=server)
+ old_server.getContainer().update(this, _sessionManager, null, "sessionManager",true);
+ super.setServer(server);
+ if (server!=null && server!=old_server)
+ server.getContainer().update(this, null,_sessionManager, "sessionManager",true);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.thread.AbstractLifeCycle#doStart()
+ */
+ protected void doStart() throws Exception
+ {
+ _sessionManager.start();
+ super.doStart();
+ }
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.thread.AbstractLifeCycle#doStop()
+ */
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ _sessionManager.stop();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+ */
+ public void handle(String target, HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ setRequestedId(request);
+
+ Request base_request = (request instanceof Request) ? (Request)request:HttpConnection.getCurrentConnection().getRequest();
+ SessionManager old_session_manager=null;
+ HttpSession old_session=null;
+
+ try
+ {
+ old_session_manager = base_request.getSessionManager();
+ old_session = base_request.getSession(false);
+
+ if (old_session_manager != _sessionManager)
+ {
+ // new session context
+ base_request.setSessionManager(_sessionManager);
+ base_request.setSession(null);
+ }
+
+ // access any existing session
+ HttpSession session=null;
+ if (_sessionManager!=null)
+ {
+ session=base_request.getSession(false);
+ if (session!=null)
+ {
+ if(session!=old_session)
+ {
+ Cookie cookie = _sessionManager.access(session,request.isSecure());
+ if (cookie!=null ) // Handle changed ID or max-age refresh
+ response.addCookie(cookie);
+ }
+ }
+ else
+ {
+ session=base_request.recoverNewSession(_sessionManager);
+ if (session!=null)
+ base_request.setSession(session);
+ }
+ }
+
+ if(Log.isDebugEnabled())
+ {
+ Log.debug("sessionManager="+_sessionManager);
+ Log.debug("session="+session);
+ }
+
+ getHandler().handle(target, request, response);
+ }
+ catch (RetryRequest r)
+ {
+ HttpSession session=base_request.getSession(false);
+ if (session!=null && session.isNew())
+ base_request.saveNewSession(_sessionManager,session);
+ throw r;
+ }
+ finally
+ {
+ HttpSession session=request.getSession(false);
+
+ if (old_session_manager != _sessionManager)
+ {
+ //leaving context, free up the session
+ if (session!=null)
+ _sessionManager.complete(session);
+ base_request.setSessionManager(old_session_manager);
+ base_request.setSession(old_session);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Look for a requested session ID in cookies and URI parameters
+ * @param request
+ * @param dispatch
+ */
+ protected void setRequestedId(HttpServletRequest request)
+ {
+ Request base_request = (request instanceof Request) ? (Request)request:HttpConnection.getCurrentConnection().getRequest();
+ String requested_session_id=request.getRequestedSessionId();
+ if (!DispatcherType.REQUEST.equals(request.getDispatcherType()) || requested_session_id!=null)
+ {
+ return;
+ }
+
+ SessionManager sessionManager = getSessionManager();
+ boolean requested_session_id_from_cookie=false;
+
+ // Look for session id cookie
+ if (_sessionManager.isUsingCookies())
+ {
+ Cookie[] cookies=request.getCookies();
+ if (cookies!=null && cookies.length>0)
+ {
+ for (int i=0;i<cookies.length;i++)
+ {
+ if (sessionManager.getSessionCookie().equalsIgnoreCase(cookies[i].getName()))
+ {
+ if (requested_session_id!=null)
+ {
+ // Multiple jsessionid cookies. Probably due to
+ // multiple paths and/or domains. Pick the first
+ // known session or the last defined cookie.
+ if (sessionManager.getHttpSession(requested_session_id)!=null)
+ break;
+ }
+
+ requested_session_id=cookies[i].getValue();
+ requested_session_id_from_cookie = true;
+ if(Log.isDebugEnabled())Log.debug("Got Session ID "+requested_session_id+" from cookie");
+ }
+ }
+ }
+ }
+
+ if (requested_session_id==null)
+ {
+ String uri = request.getRequestURI();
+
+ int semi = uri.lastIndexOf(';');
+ if (semi>=0)
+ {
+ String path_params=uri.substring(semi+1);
+
+ // check if there is a url encoded session param.
+ String param=sessionManager.getSessionIdPathParameterName();
+ if (param!=null && path_params!=null && path_params.startsWith(param))
+ {
+ requested_session_id = path_params.substring(sessionManager.getSessionIdPathParameterName().length()+1);
+ if(Log.isDebugEnabled())Log.debug("Got Session ID "+requested_session_id+" from URL");
+ }
+ }
+ }
+
+ base_request.setRequestedSessionId(requested_session_id);
+ base_request.setRequestedSessionIdFromCookie(requested_session_id!=null && requested_session_id_from_cookie);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param listener
+ */
+ public void addEventListener(EventListener listener)
+ {
+ if(_sessionManager!=null)
+ _sessionManager.addEventListener(listener);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void clearEventListeners()
+ {
+ if(_sessionManager!=null)
+ _sessionManager.clearEventListeners();
+ }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/ServletSSL.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/ServletSSL.java
new file mode 100644
index 0000000000..3c1cabf1d0
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/ServletSSL.java
@@ -0,0 +1,83 @@
+// ========================================================================
+// Copyright (c) 2001-2009 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.ssl;
+
+/* --------------------------------------------------------------------- */
+/**
+ * Jetty Servlet SSL support utilities.
+ * <p>
+ * A collection of utilities required to support the SSL requirements of the Servlet 2.2 and 2.3
+ * specs.
+ *
+ * <p>
+ * Used by the SSL listener classes.
+ *
+ *
+ */
+public class ServletSSL
+{
+ /* ------------------------------------------------------------ */
+ /**
+ * Given the name of a TLS/SSL cipher suite, return an int representing it effective stream
+ * cipher key strength. i.e. How much entropy material is in the key material being fed into the
+ * encryption routines.
+ *
+ * <p>
+ * This is based on the information on effective key lengths in RFC 2246 - The TLS Protocol
+ * Version 1.0, Appendix C. CipherSuite definitions:
+ *
+ * <pre>
+ * Effective
+ * Cipher Type Key Bits
+ *
+ * NULL * Stream 0
+ * IDEA_CBC Block 128
+ * RC2_CBC_40 * Block 40
+ * RC4_40 * Stream 40
+ * RC4_128 Stream 128
+ * DES40_CBC * Block 40
+ * DES_CBC Block 56
+ * 3DES_EDE_CBC Block 168
+ * </pre>
+ *
+ * @param cipherSuite String name of the TLS cipher suite.
+ * @return int indicating the effective key entropy bit-length.
+ */
+ public static final int deduceKeyLength(String cipherSuite)
+ {
+ // Roughly ordered from most common to least common.
+ if (cipherSuite == null)
+ return 0;
+ else if (cipherSuite.indexOf("WITH_AES_256_") >= 0)
+ return 256;
+ else if (cipherSuite.indexOf("WITH_RC4_128_") >= 0)
+ return 128;
+ else if (cipherSuite.indexOf("WITH_AES_128_") >= 0)
+ return 128;
+ else if (cipherSuite.indexOf("WITH_RC4_40_") >= 0)
+ return 40;
+ else if (cipherSuite.indexOf("WITH_3DES_EDE_CBC_") >= 0)
+ return 168;
+ else if (cipherSuite.indexOf("WITH_IDEA_CBC_") >= 0)
+ return 128;
+ else if (cipherSuite.indexOf("WITH_RC2_CBC_40_") >= 0)
+ return 40;
+ else if (cipherSuite.indexOf("WITH_DES40_CBC_") >= 0)
+ return 40;
+ else if (cipherSuite.indexOf("WITH_DES_CBC_") >= 0)
+ return 56;
+ else
+ return 0;
+ }
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSelectChannelConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSelectChannelConnector.java
new file mode 100644
index 0000000000..4169cc3b65
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSelectChannelConnector.java
@@ -0,0 +1,703 @@
+// ========================================================================
+// Copyright (c) 2004-2009 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.ssl;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.http.HttpSchemes;
+import org.eclipse.jetty.http.security.Password;
+import org.eclipse.jetty.http.ssl.SslSelectChannelEndPoint;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.bio.SocketEndPoint;
+import org.eclipse.jetty.io.nio.DirectNIOBuffer;
+import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
+import org.eclipse.jetty.io.nio.NIOBuffer;
+import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
+import org.eclipse.jetty.io.nio.SelectorManager.SelectSet;
+import org.eclipse.jetty.server.HttpConnection;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.nio.SelectChannelConnector;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.resource.Resource;
+
+/* ------------------------------------------------------------ */
+/**
+ * SslSelectChannelConnector.
+ *
+ * @org.apache.xbean.XBean element="sslConnector" description="Creates an NIO ssl connector"
+ *
+ *
+ *
+ */
+public class SslSelectChannelConnector extends SelectChannelConnector
+{
+ /**
+ * The name of the SSLSession attribute that will contain any cached
+ * information.
+ */
+ static final String CACHED_INFO_ATTR=CachedInfo.class.getName();
+
+ /** Default value for the keystore location path. */
+ public static final String DEFAULT_KEYSTORE=System.getProperty("user.home")+File.separator+".keystore";
+
+ /** String name of key password property. */
+ public static final String KEYPASSWORD_PROPERTY="jetty.ssl.keypassword";
+
+ /** String name of keystore password property. */
+ public static final String PASSWORD_PROPERTY="jetty.ssl.password";
+
+ /** Default value for the cipher Suites. */
+ private String _excludeCipherSuites[]=null;
+
+ /** Default value for the keystore location path. */
+ private String _keystore=DEFAULT_KEYSTORE;
+ private String _keystoreType="JKS"; // type of the key store
+
+ /** Set to true if we require client certificate authentication. */
+ private boolean _needClientAuth=false;
+ private boolean _wantClientAuth=false;
+
+ private transient Password _password;
+ private transient Password _keyPassword;
+ private transient Password _trustPassword;
+ private String _protocol="TLS";
+ private String _algorithm="SunX509"; // cert algorithm
+ private String _provider;
+ private String _secureRandomAlgorithm; // cert algorithm
+ private String _sslKeyManagerFactoryAlgorithm=(Security.getProperty("ssl.KeyManagerFactory.algorithm")==null?"SunX509":Security
+ .getProperty("ssl.KeyManagerFactory.algorithm")); // cert
+ // algorithm
+
+ private String _sslTrustManagerFactoryAlgorithm=(Security.getProperty("ssl.TrustManagerFactory.algorithm")==null?"SunX509":Security
+ .getProperty("ssl.TrustManagerFactory.algorithm")); // cert
+ // algorithm
+
+ private String _truststore;
+ private String _truststoreType="JKS"; // type of the key store
+ private SSLContext _context;
+
+ private int _packetBufferSize;
+ private int _applicationBufferSize;
+ private ConcurrentLinkedQueue<Buffer> _packetBuffers = new ConcurrentLinkedQueue<Buffer>();
+ private ConcurrentLinkedQueue<Buffer> _applicationBuffers = new ConcurrentLinkedQueue<Buffer>();
+
+ /* ------------------------------------------------------------ */
+ /* (non-Javadoc)
+ * @see org.eclipse.jetty.io.AbstractBuffers#getBuffer(int)
+ */
+ public Buffer getBuffer(int size)
+ {
+ // TODO why is this reimplemented?
+ Buffer buffer;
+ if (size==_applicationBufferSize)
+ {
+ buffer = _applicationBuffers.poll();
+ if (buffer==null)
+ buffer=new IndirectNIOBuffer(size);
+ }
+ else if (size==_packetBufferSize)
+ {
+ buffer = _packetBuffers.poll();
+ if (buffer==null)
+ buffer=getUseDirectBuffers()
+ ?(NIOBuffer)new DirectNIOBuffer(size)
+ :(NIOBuffer)new IndirectNIOBuffer(size);
+ }
+ else
+ buffer=super.getBuffer(size);
+
+ return buffer;
+ }
+
+ /* ------------------------------------------------------------ */
+ /* (non-Javadoc)
+ * @see org.eclipse.jetty.io.AbstractBuffers#returnBuffer(org.eclipse.io.Buffer)
+ */
+ public void returnBuffer(Buffer buffer)
+ {
+ buffer.clear();
+ int size=buffer.capacity();
+ ByteBuffer bbuf = ((NIOBuffer)buffer).getByteBuffer();
+ bbuf.position(0);
+ bbuf.limit(size);
+
+ if (size==_applicationBufferSize)
+ _applicationBuffers.add(buffer);
+ else if (size==_packetBufferSize)
+ _packetBuffers.add(buffer);
+ else
+ super.returnBuffer(buffer);
+ }
+
+
+
+ /**
+ * Return the chain of X509 certificates used to negotiate the SSL Session.
+ * <p>
+ * Note: in order to do this we must convert a
+ * javax.security.cert.X509Certificate[], as used by JSSE to a
+ * java.security.cert.X509Certificate[],as required by the Servlet specs.
+ *
+ * @param sslSession
+ * the javax.net.ssl.SSLSession to use as the source of the
+ * cert chain.
+ * @return the chain of java.security.cert.X509Certificates used to
+ * negotiate the SSL connection. <br>
+ * Will be null if the chain is missing or empty.
+ */
+ private static X509Certificate[] getCertChain(SSLSession sslSession)
+ {
+ try
+ {
+ javax.security.cert.X509Certificate javaxCerts[]=sslSession.getPeerCertificateChain();
+ if (javaxCerts==null||javaxCerts.length==0)
+ return null;
+
+ int length=javaxCerts.length;
+ X509Certificate[] javaCerts=new X509Certificate[length];
+
+ java.security.cert.CertificateFactory cf=java.security.cert.CertificateFactory.getInstance("X.509");
+ for (int i=0; i<length; i++)
+ {
+ byte bytes[]=javaxCerts[i].getEncoded();
+ ByteArrayInputStream stream=new ByteArrayInputStream(bytes);
+ javaCerts[i]=(X509Certificate)cf.generateCertificate(stream);
+ }
+
+ return javaCerts;
+ }
+ catch (SSLPeerUnverifiedException pue)
+ {
+ return null;
+ }
+ catch (Exception e)
+ {
+ Log.warn(Log.EXCEPTION,e);
+ return null;
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Allow the Listener a chance to customise the request. before the server
+ * does its stuff. <br>
+ * This allows the required attributes to be set for SSL requests. <br>
+ * The requirements of the Servlet specs are:
+ * <ul>
+ * <li> an attribute named "javax.servlet.request.ssl_session_id" of type
+ * String (since Servlet Spec 3.0).</li>
+ * <li> an attribute named "javax.servlet.request.cipher_suite" of type
+ * String.</li>
+ * <li> an attribute named "javax.servlet.request.key_size" of type Integer.</li>
+ * <li> an attribute named "javax.servlet.request.X509Certificate" of type
+ * java.security.cert.X509Certificate[]. This is an array of objects of type
+ * X509Certificate, the order of this array is defined as being in ascending
+ * order of trust. The first certificate in the chain is the one set by the
+ * client, the next is the one used to authenticate the first, and so on.
+ * </li>
+ * </ul>
+ *
+ * @param endpoint
+ * The Socket the request arrived on. This should be a
+ * {@link SocketEndPoint} wrapping a {@link SSLSocket}.
+ * @param request
+ * HttpRequest to be customised.
+ */
+ @Override
+ public void customize(EndPoint endpoint, Request request) throws IOException
+ {
+ super.customize(endpoint,request);
+ request.setScheme(HttpSchemes.HTTPS);
+
+ SslSelectChannelEndPoint sslHttpChannelEndpoint=(SslSelectChannelEndPoint)endpoint;
+
+ SSLEngine sslEngine=sslHttpChannelEndpoint.getSSLEngine();
+
+ try
+ {
+ SSLSession sslSession=sslEngine.getSession();
+ String cipherSuite=sslSession.getCipherSuite();
+ Integer keySize;
+ X509Certificate[] certs;
+ String idStr;
+
+ CachedInfo cachedInfo=(CachedInfo)sslSession.getValue(CACHED_INFO_ATTR);
+ if (cachedInfo!=null)
+ {
+ keySize=cachedInfo.getKeySize();
+ certs=cachedInfo.getCerts();
+ idStr=cachedInfo.getIdStr();
+ }
+ else
+ {
+ keySize=new Integer(ServletSSL.deduceKeyLength(cipherSuite));
+ certs=getCertChain(sslSession);
+ byte[] bytes = sslSession.getId();
+ idStr = TypeUtil.toHexString(bytes);
+ cachedInfo=new CachedInfo(keySize,certs,idStr);
+ sslSession.putValue(CACHED_INFO_ATTR,cachedInfo);
+ }
+
+ if (certs!=null)
+ request.setAttribute("javax.servlet.request.X509Certificate",certs);
+
+ request.setAttribute("javax.servlet.request.cipher_suite",cipherSuite);
+ request.setAttribute("javax.servlet.request.key_size",keySize);
+ request.setAttribute("javax.servlet.request.ssl_session_id", idStr);
+ }
+ catch (Exception e)
+ {
+ Log.warn(Log.EXCEPTION,e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public SslSelectChannelConnector()
+ {
+ }
+
+ /**
+ *
+ * @deprecated As of Java Servlet API 2.0, with no replacement.
+ *
+ */
+ public String[] getCipherSuites()
+ {
+ return getExcludeCipherSuites();
+ }
+
+ public String[] getExcludeCipherSuites()
+ {
+ return _excludeCipherSuites;
+ }
+
+ /**
+ *
+ * @deprecated As of Java Servlet API 2.0, with no replacement.
+ *
+ *
+ */
+ public void setCipherSuites(String[] cipherSuites)
+ {
+ setExcludeCipherSuites(cipherSuites);
+ }
+
+ public void setExcludeCipherSuites(String[] cipherSuites)
+ {
+ this._excludeCipherSuites=cipherSuites;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setPassword(String password)
+ {
+ _password=Password.getPassword(PASSWORD_PROPERTY,password,null);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setTrustPassword(String password)
+ {
+ _trustPassword=Password.getPassword(PASSWORD_PROPERTY,password,null);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setKeyPassword(String password)
+ {
+ _keyPassword=Password.getPassword(KEYPASSWORD_PROPERTY,password,null);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getAlgorithm()
+ {
+ return (this._algorithm);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setAlgorithm(String algorithm)
+ {
+ this._algorithm=algorithm;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getProtocol()
+ {
+ return _protocol;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setProtocol(String protocol)
+ {
+ _protocol=protocol;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setKeystore(String keystore)
+ {
+ _keystore=keystore;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getKeystore()
+ {
+ return _keystore;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getKeystoreType()
+ {
+ return (_keystoreType);
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean getNeedClientAuth()
+ {
+ return _needClientAuth;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean getWantClientAuth()
+ {
+ return _wantClientAuth;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the value of the needClientAuth property
+ *
+ * @param needClientAuth
+ * true iff we require client certificate authentication.
+ */
+ public void setNeedClientAuth(boolean needClientAuth)
+ {
+ _needClientAuth=needClientAuth;
+ }
+
+ public void setWantClientAuth(boolean wantClientAuth)
+ {
+ _wantClientAuth=wantClientAuth;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setKeystoreType(String keystoreType)
+ {
+ _keystoreType=keystoreType;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getProvider()
+ {
+ return _provider;
+ }
+
+ public String getSecureRandomAlgorithm()
+ {
+ return (this._secureRandomAlgorithm);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getSslKeyManagerFactoryAlgorithm()
+ {
+ return (this._sslKeyManagerFactoryAlgorithm);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getSslTrustManagerFactoryAlgorithm()
+ {
+ return (this._sslTrustManagerFactoryAlgorithm);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getTruststore()
+ {
+ return _truststore;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getTruststoreType()
+ {
+ return _truststoreType;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setProvider(String _provider)
+ {
+ this._provider=_provider;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setSecureRandomAlgorithm(String algorithm)
+ {
+ this._secureRandomAlgorithm=algorithm;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setSslKeyManagerFactoryAlgorithm(String algorithm)
+ {
+ this._sslKeyManagerFactoryAlgorithm=algorithm;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setSslTrustManagerFactoryAlgorithm(String algorithm)
+ {
+ this._sslTrustManagerFactoryAlgorithm=algorithm;
+ }
+
+ public void setTruststore(String truststore)
+ {
+ _truststore=truststore;
+ }
+
+ public void setTruststoreType(String truststoreType)
+ {
+ _truststoreType=truststoreType;
+ }
+
+ public void setSslContext(SSLContext sslContext) {
+ this._context = sslContext;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * By default, we're confidential, given we speak SSL. But, if we've been
+ * told about an confidential port, and said port is not our port, then
+ * we're not. This allows separation of listeners providing INTEGRAL versus
+ * CONFIDENTIAL constraints, such as one SSL listener configured to require
+ * client certs providing CONFIDENTIAL, whereas another SSL listener not
+ * requiring client certs providing mere INTEGRAL constraints.
+ */
+ public boolean isConfidential(Request request)
+ {
+ final int confidentialPort=getConfidentialPort();
+ return confidentialPort==0||confidentialPort==request.getServerPort();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * By default, we're integral, given we speak SSL. But, if we've been told
+ * about an integral port, and said port is not our port, then we're not.
+ * This allows separation of listeners providing INTEGRAL versus
+ * CONFIDENTIAL constraints, such as one SSL listener configured to require
+ * client certs providing CONFIDENTIAL, whereas another SSL listener not
+ * requiring client certs providing mere INTEGRAL constraints.
+ */
+ public boolean isIntegral(Request request)
+ {
+ final int integralPort=getIntegralPort();
+ return integralPort==0||integralPort==request.getServerPort();
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException
+ {
+ return new SslSelectChannelEndPoint(this,channel,selectSet,key,createSSLEngine())
+ {
+ // TODO remove this hack
+ public boolean isReadyForDispatch()
+ {
+ Request request = ((HttpConnection)getConnection()).getRequest();
+ return super.isReadyForDispatch() && !(request.getAsyncRequest().isSuspended());
+ }
+ };
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ protected Connection newConnection(SocketChannel channel, SelectChannelEndPoint endpoint)
+ {
+ HttpConnection connection=(HttpConnection)super.newConnection(channel,endpoint);
+ ((HttpParser)connection.getParser()).setForceContentBuffer(true);
+ return connection;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected SSLEngine createSSLEngine() throws IOException
+ {
+ SSLEngine engine=null;
+ try
+ {
+ engine=_context.createSSLEngine();
+ engine.setUseClientMode(false);
+
+ if (_wantClientAuth)
+ engine.setWantClientAuth(_wantClientAuth);
+ if (_needClientAuth)
+ engine.setNeedClientAuth(_needClientAuth);
+
+ if (_excludeCipherSuites!=null&&_excludeCipherSuites.length>0)
+ {
+ List<String> excludedCSList=Arrays.asList(_excludeCipherSuites);
+ String[] enabledCipherSuites=engine.getEnabledCipherSuites();
+ List<String> enabledCSList=new ArrayList<String>(Arrays.asList(enabledCipherSuites));
+
+ for (String cipherName : excludedCSList)
+ {
+ if (enabledCSList.contains(cipherName))
+ {
+ enabledCSList.remove(cipherName);
+ }
+ }
+ enabledCipherSuites=enabledCSList.toArray(new String[enabledCSList.size()]);
+
+ engine.setEnabledCipherSuites(enabledCipherSuites);
+ }
+
+ }
+ catch (Exception e)
+ {
+ Log.warn("Error creating sslEngine -- closing this connector",e);
+ close();
+ throw new IllegalStateException(e);
+ }
+ return engine;
+ }
+
+
+ protected void doStart() throws Exception
+ {
+ if (_context == null) {
+ _context=createSSLContext();
+ }
+
+ SSLEngine engine=createSSLEngine();
+ SSLSession ssl_session=engine.getSession();
+
+ setHeaderBufferSize(ssl_session.getApplicationBufferSize());
+ setRequestBufferSize(ssl_session.getApplicationBufferSize());
+ setResponseBufferSize(ssl_session.getApplicationBufferSize());
+
+ super.doStart();
+ }
+
+ protected SSLContext createSSLContext() throws Exception
+ {
+ if (_truststore==null)
+ {
+ _truststore=_keystore;
+ _truststoreType=_keystoreType;
+ }
+
+ InputStream keystoreInputStream = null;
+
+ KeyManager[] keyManagers=null;
+ KeyStore keyStore = null;
+ try
+ {
+ if (_keystore!=null)
+ {
+ keystoreInputStream=Resource.newResource(_keystore).getInputStream();
+ keyStore = KeyStore.getInstance(_keystoreType);
+ keyStore.load(keystoreInputStream,_password==null?null:_password.toString().toCharArray());
+ }
+ }
+ finally
+ {
+ if (keystoreInputStream != null)
+ keystoreInputStream.close();
+ }
+
+ KeyManagerFactory keyManagerFactory=KeyManagerFactory.getInstance(_sslKeyManagerFactoryAlgorithm);
+ keyManagerFactory.init(keyStore,_keyPassword==null?(_password==null?null:_password.toString().toCharArray()):_keyPassword.toString().toCharArray());
+ keyManagers=keyManagerFactory.getKeyManagers();
+
+
+ TrustManager[] trustManagers=null;
+ InputStream truststoreInputStream = null;
+ KeyStore trustStore = null;
+ try
+ {
+ if (_truststore!=null)
+ {
+ truststoreInputStream = Resource.newResource(_truststore).getInputStream();
+ trustStore=KeyStore.getInstance(_truststoreType);
+ trustStore.load(truststoreInputStream,_trustPassword==null?null:_trustPassword.toString().toCharArray());
+ }
+ }
+ finally
+ {
+ if (truststoreInputStream != null)
+ truststoreInputStream.close();
+ }
+
+
+ TrustManagerFactory trustManagerFactory=TrustManagerFactory.getInstance(_sslTrustManagerFactoryAlgorithm);
+ trustManagerFactory.init(trustStore);
+ trustManagers=trustManagerFactory.getTrustManagers();
+
+ SecureRandom secureRandom=_secureRandomAlgorithm==null?null:SecureRandom.getInstance(_secureRandomAlgorithm);
+ SSLContext context=_provider==null?SSLContext.getInstance(_protocol):SSLContext.getInstance(_protocol,_provider);
+ context.init(keyManagers,trustManagers,secureRandom);
+ return context;
+ }
+
+ /**
+ * Simple bundle of information that is cached in the SSLSession. Stores the
+ * effective keySize and the client certificate chain.
+ */
+ private class CachedInfo
+ {
+ private X509Certificate[] _certs;
+ private Integer _keySize;
+ private String _idStr;
+
+ CachedInfo(Integer keySize, X509Certificate[] certs,String idStr)
+ {
+ this._keySize=keySize;
+ this._certs=certs;
+ this._idStr=idStr;
+ }
+
+ X509Certificate[] getCerts()
+ {
+ return _certs;
+ }
+
+ Integer getKeySize()
+ {
+ return _keySize;
+ }
+
+ String getIdStr()
+ {
+ return _idStr;
+ }
+ }
+
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSocketConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSocketConnector.java
new file mode 100644
index 0000000000..2919302792
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSocketConnector.java
@@ -0,0 +1,665 @@
+// ========================================================================
+// Copyright (c) 2000-2009 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.ssl;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import org.eclipse.jetty.http.HttpSchemes;
+import org.eclipse.jetty.http.security.Password;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.bio.SocketEndPoint;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.bio.SocketConnector;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.resource.Resource;
+
+/* ------------------------------------------------------------ */
+/**
+ * JSSE Socket Listener.
+ *
+ * This specialization of HttpListener is an abstract listener that can be used as the basis for a
+ * specific JSSE listener.
+ *
+ * This is heavily based on the work from Court Demas, which in turn is based on the work from Forge
+ * Research.
+ *
+ * @org.apache.xbean.XBean element="sslSocketConnector" description="Creates an ssl socket connector"
+ *
+ *
+ *
+ *
+ *
+ */
+public class SslSocketConnector extends SocketConnector
+{
+ /**
+ * The name of the SSLSession attribute that will contain any cached information.
+ */
+ static final String CACHED_INFO_ATTR = CachedInfo.class.getName();
+
+ /** Default value for the keystore location path. */
+ public static final String DEFAULT_KEYSTORE = System.getProperty("user.home") + File.separator
+ + ".keystore";
+
+ /** String name of key password property. */
+ public static final String KEYPASSWORD_PROPERTY = "jetty.ssl.keypassword";
+
+ /** String name of keystore password property. */
+ public static final String PASSWORD_PROPERTY = "jetty.ssl.password";
+
+ /**
+ * Return the chain of X509 certificates used to negotiate the SSL Session.
+ * <p>
+ * Note: in order to do this we must convert a javax.security.cert.X509Certificate[], as used by
+ * JSSE to a java.security.cert.X509Certificate[],as required by the Servlet specs.
+ *
+ * @param sslSession the javax.net.ssl.SSLSession to use as the source of the cert chain.
+ * @return the chain of java.security.cert.X509Certificates used to negotiate the SSL
+ * connection. <br>
+ * Will be null if the chain is missing or empty.
+ */
+ private static X509Certificate[] getCertChain(SSLSession sslSession)
+ {
+ try
+ {
+ javax.security.cert.X509Certificate javaxCerts[] = sslSession.getPeerCertificateChain();
+ if (javaxCerts == null || javaxCerts.length == 0)
+ return null;
+
+ int length = javaxCerts.length;
+ X509Certificate[] javaCerts = new X509Certificate[length];
+
+ java.security.cert.CertificateFactory cf = java.security.cert.CertificateFactory.getInstance("X.509");
+ for (int i = 0; i < length; i++)
+ {
+ byte bytes[] = javaxCerts[i].getEncoded();
+ ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
+ javaCerts[i] = (X509Certificate) cf.generateCertificate(stream);
+ }
+
+ return javaCerts;
+ }
+ catch (SSLPeerUnverifiedException pue)
+ {
+ return null;
+ }
+ catch (Exception e)
+ {
+ Log.warn(Log.EXCEPTION, e);
+ return null;
+ }
+ }
+
+
+
+ /** Default value for the cipher Suites. */
+ private String _excludeCipherSuites[] = null;
+
+ /** Default value for the keystore location path. */
+ private String _keystore=DEFAULT_KEYSTORE ;
+ private String _keystoreType = "JKS"; // type of the key store
+
+ /** Set to true if we require client certificate authentication. */
+ private boolean _needClientAuth = false;
+ private transient Password _password;
+ private transient Password _keyPassword;
+ private transient Password _trustPassword;
+ private String _protocol= "TLS";
+ private String _provider;
+ private String _secureRandomAlgorithm; // cert algorithm
+ private String _sslKeyManagerFactoryAlgorithm = (Security.getProperty("ssl.KeyManagerFactory.algorithm")==null?"SunX509":Security.getProperty("ssl.KeyManagerFactory.algorithm")); // cert algorithm
+ private String _sslTrustManagerFactoryAlgorithm = (Security.getProperty("ssl.TrustManagerFactory.algorithm")==null?"SunX509":Security.getProperty("ssl.TrustManagerFactory.algorithm")); // cert algorithm
+
+ private String _truststore;
+ private String _truststoreType = "JKS"; // type of the key store
+
+ /** Set to true if we would like client certificate authentication. */
+ private boolean _wantClientAuth = false;
+ private int _handshakeTimeout = 0; //0 means use maxIdleTime
+
+ private SSLContext _context;
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Constructor.
+ */
+ public SslSocketConnector()
+ {
+ super();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public void accept(int acceptorID)
+ throws IOException, InterruptedException
+ {
+ Socket socket = _serverSocket.accept();
+ configure(socket);
+
+ Connection connection=new SslConnection(socket);
+ connection.dispatch();
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void configure(Socket socket)
+ throws IOException
+ {
+ super.configure(socket);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected SSLServerSocketFactory createFactory()
+ throws Exception
+ {
+ SSLContext context = _context;
+ if (context == null) {
+ if (_truststore==null)
+ {
+ _truststore=_keystore;
+ _truststoreType=_keystoreType;
+ }
+
+ KeyManager[] keyManagers = null;
+ InputStream keystoreInputStream = null;
+ if (_keystore != null)
+ keystoreInputStream = Resource.newResource(_keystore).getInputStream();
+ KeyStore keyStore = KeyStore.getInstance(_keystoreType);
+ keyStore.load(keystoreInputStream, _password==null?null:_password.toString().toCharArray());
+
+ KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(_sslKeyManagerFactoryAlgorithm);
+ keyManagerFactory.init(keyStore,_keyPassword==null?null:_keyPassword.toString().toCharArray());
+ keyManagers = keyManagerFactory.getKeyManagers();
+
+ TrustManager[] trustManagers = null;
+ InputStream truststoreInputStream = null;
+ if (_truststore != null)
+ truststoreInputStream = Resource.newResource(_truststore).getInputStream();
+ KeyStore trustStore = KeyStore.getInstance(_truststoreType);
+ trustStore.load(truststoreInputStream,_trustPassword==null?null:_trustPassword.toString().toCharArray());
+
+ TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(_sslTrustManagerFactoryAlgorithm);
+ trustManagerFactory.init(trustStore);
+ trustManagers = trustManagerFactory.getTrustManagers();
+
+
+ SecureRandom secureRandom = _secureRandomAlgorithm==null?null:SecureRandom.getInstance(_secureRandomAlgorithm);
+
+ context = _provider==null?SSLContext.getInstance(_protocol):SSLContext.getInstance(_protocol, _provider);
+
+ context.init(keyManagers, trustManagers, secureRandom);
+ }
+
+ return context.getServerSocketFactory();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Allow the Listener a chance to customise the request. before the server does its stuff. <br>
+ * This allows the required attributes to be set for SSL requests. <br>
+ * The requirements of the Servlet specs are:
+ * <ul>
+ * <li> an attribute named "javax.servlet.request.ssl_id" of type String (since Spec 3.0).</li>
+ * <li> an attribute named "javax.servlet.request.cipher_suite" of type String.</li>
+ * <li> an attribute named "javax.servlet.request.key_size" of type Integer.</li>
+ * <li> an attribute named "javax.servlet.request.X509Certificate" of type
+ * java.security.cert.X509Certificate[]. This is an array of objects of type X509Certificate,
+ * the order of this array is defined as being in ascending order of trust. The first
+ * certificate in the chain is the one set by the client, the next is the one used to
+ * authenticate the first, and so on. </li>
+ * </ul>
+ *
+ * @param endpoint The Socket the request arrived on.
+ * This should be a {@link SocketEndPoint} wrapping a {@link SSLSocket}.
+ * @param request HttpRequest to be customised.
+ */
+ public void customize(EndPoint endpoint, Request request)
+ throws IOException
+ {
+ super.customize(endpoint, request);
+ request.setScheme(HttpSchemes.HTTPS);
+
+ SocketEndPoint socket_end_point = (SocketEndPoint)endpoint;
+ SSLSocket sslSocket = (SSLSocket)socket_end_point.getTransport();
+
+ try
+ {
+ SSLSession sslSession = sslSocket.getSession();
+ String cipherSuite = sslSession.getCipherSuite();
+ Integer keySize;
+ String idStr;
+ X509Certificate[] certs;
+
+ CachedInfo cachedInfo = (CachedInfo) sslSession.getValue(CACHED_INFO_ATTR);
+ if (cachedInfo != null)
+ {
+ keySize = cachedInfo.getKeySize();
+ certs = cachedInfo.getCerts();
+ idStr = cachedInfo.getIdStr();
+ }
+ else
+ {
+ keySize = new Integer(ServletSSL.deduceKeyLength(cipherSuite));
+ certs = getCertChain(sslSession);
+ byte[] idBytes = sslSession.getId();
+ idStr = TypeUtil.toHexString(idBytes);
+ cachedInfo = new CachedInfo(keySize, certs,idStr);
+ sslSession.putValue(CACHED_INFO_ATTR, cachedInfo);
+ }
+
+ if (certs != null)
+ request.setAttribute("javax.servlet.request.X509Certificate", certs);
+ else if (_needClientAuth) // Sanity check
+ throw new IllegalStateException("no client auth");
+
+ request.setAttribute("javax.servlet.request.ssl_session_id", idStr);
+ request.setAttribute("javax.servlet.request.cipher_suite", cipherSuite);
+ request.setAttribute("javax.servlet.request.key_size", keySize);
+ }
+ catch (Exception e)
+ {
+ Log.warn(Log.EXCEPTION, e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public String[] getExcludeCipherSuites() {
+ return _excludeCipherSuites;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getKeystore()
+ {
+ return _keystore;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getKeystoreType()
+ {
+ return (_keystoreType);
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean getNeedClientAuth()
+ {
+ return _needClientAuth;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getProtocol()
+ {
+ return _protocol;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getProvider() {
+ return _provider;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getSecureRandomAlgorithm()
+ {
+ return (this._secureRandomAlgorithm);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getSslKeyManagerFactoryAlgorithm()
+ {
+ return (this._sslKeyManagerFactoryAlgorithm);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getSslTrustManagerFactoryAlgorithm()
+ {
+ return (this._sslTrustManagerFactoryAlgorithm);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getTruststore()
+ {
+ return _truststore;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getTruststoreType()
+ {
+ return _truststoreType;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean getWantClientAuth()
+ {
+ return _wantClientAuth;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * By default, we're confidential, given we speak SSL. But, if we've been told about an
+ * confidential port, and said port is not our port, then we're not. This allows separation of
+ * listeners providing INTEGRAL versus CONFIDENTIAL constraints, such as one SSL listener
+ * configured to require client certs providing CONFIDENTIAL, whereas another SSL listener not
+ * requiring client certs providing mere INTEGRAL constraints.
+ */
+ public boolean isConfidential(Request request)
+ {
+ final int confidentialPort = getConfidentialPort();
+ return confidentialPort == 0 || confidentialPort == request.getServerPort();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * By default, we're integral, given we speak SSL. But, if we've been told about an integral
+ * port, and said port is not our port, then we're not. This allows separation of listeners
+ * providing INTEGRAL versus CONFIDENTIAL constraints, such as one SSL listener configured to
+ * require client certs providing CONFIDENTIAL, whereas another SSL listener not requiring
+ * client certs providing mere INTEGRAL constraints.
+ */
+ public boolean isIntegral(Request request)
+ {
+ final int integralPort = getIntegralPort();
+ return integralPort == 0 || integralPort == request.getServerPort();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param addr The {@link SocketAddress address} that this server should listen on
+ * @param backlog See {@link ServerSocket#bind(java.net.SocketAddress, int)}
+ * @return A new {@link ServerSocket socket object} bound to the supplied address with all other
+ * settings as per the current configuration of this connector.
+ * @see #setWantClientAuth
+ * @see #setNeedClientAuth
+ * @see #setCipherSuites
+ * @exception IOException
+ */
+
+ /* ------------------------------------------------------------ */
+ protected ServerSocket newServerSocket(String host, int port,int backlog) throws IOException
+ {
+ SSLServerSocketFactory factory = null;
+ SSLServerSocket socket = null;
+
+ try
+ {
+ factory = createFactory();
+
+ socket = (SSLServerSocket) (host==null?
+ factory.createServerSocket(port,backlog):
+ factory.createServerSocket(port,backlog,InetAddress.getByName(host)));
+
+ if (_wantClientAuth)
+ socket.setWantClientAuth(_wantClientAuth);
+ if (_needClientAuth)
+ socket.setNeedClientAuth(_needClientAuth);
+
+ if (_excludeCipherSuites != null && _excludeCipherSuites.length >0)
+ {
+ List excludedCSList = Arrays.asList(_excludeCipherSuites);
+ String[] enabledCipherSuites = socket.getEnabledCipherSuites();
+ List enabledCSList = new ArrayList(Arrays.asList(enabledCipherSuites));
+ Iterator exIter = excludedCSList.iterator();
+
+ while (exIter.hasNext())
+ {
+ String cipherName = (String)exIter.next();
+ if (enabledCSList.contains(cipherName))
+ {
+ enabledCSList.remove(cipherName);
+ }
+ }
+ enabledCipherSuites = (String[])enabledCSList.toArray(new String[enabledCSList.size()]);
+
+ socket.setEnabledCipherSuites(enabledCipherSuites);
+ }
+
+ }
+ catch (IOException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ Log.warn(Log.EXCEPTION, e);
+ throw new IOException("Could not create JsseListener: " + e.toString());
+ }
+ return socket;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ *
+ */
+ public void setExcludeCipherSuites(String[] cipherSuites) {
+ this._excludeCipherSuites = cipherSuites;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setKeyPassword(String password)
+ {
+ _keyPassword = Password.getPassword(KEYPASSWORD_PROPERTY,password,null);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param keystore The resource path to the keystore, or null for built in keystores.
+ */
+ public void setKeystore(String keystore)
+ {
+ _keystore = keystore;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setKeystoreType(String keystoreType)
+ {
+ _keystoreType = keystoreType;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the value of the needClientAuth property
+ *
+ * @param needClientAuth true iff we require client certificate authentication.
+ */
+ public void setNeedClientAuth(boolean needClientAuth)
+ {
+ _needClientAuth = needClientAuth;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setPassword(String password)
+ {
+ _password = Password.getPassword(PASSWORD_PROPERTY,password,null);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setTrustPassword(String password)
+ {
+ _trustPassword = Password.getPassword(PASSWORD_PROPERTY,password,null);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setProtocol(String protocol)
+ {
+ _protocol = protocol;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setProvider(String _provider) {
+ this._provider = _provider;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setSecureRandomAlgorithm(String algorithm)
+ {
+ this._secureRandomAlgorithm = algorithm;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setSslKeyManagerFactoryAlgorithm(String algorithm)
+ {
+ this._sslKeyManagerFactoryAlgorithm = algorithm;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setSslTrustManagerFactoryAlgorithm(String algorithm)
+ {
+ this._sslTrustManagerFactoryAlgorithm = algorithm;
+ }
+
+
+ public void setTruststore(String truststore)
+ {
+ _truststore = truststore;
+ }
+
+
+ public void setTruststoreType(String truststoreType)
+ {
+ _truststoreType = truststoreType;
+ }
+
+ public void setSslContext(SSLContext sslContext)
+ {
+ _context = sslContext;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the value of the _wantClientAuth property. This property is used when
+ * {@link #newServerSocket(SocketAddress, int) opening server sockets}.
+ *
+ * @param wantClientAuth true iff we want client certificate authentication.
+ * @see SSLServerSocket#setWantClientAuth
+ */
+ public void setWantClientAuth(boolean wantClientAuth)
+ {
+ _wantClientAuth = wantClientAuth;
+ }
+
+ /**
+ * Set the time in milliseconds for so_timeout during ssl handshaking
+ * @param msec a non-zero value will be used to set so_timeout during
+ * ssl handshakes. A zero value means the maxIdleTime is used instead.
+ */
+ public void setHandshakeTimeout (int msec)
+ {
+ _handshakeTimeout = msec;
+ }
+
+
+ public int getHandshakeTimeout ()
+ {
+ return _handshakeTimeout;
+ }
+ /**
+ * Simple bundle of information that is cached in the SSLSession. Stores the effective keySize
+ * and the client certificate chain.
+ */
+ private class CachedInfo
+ {
+ private X509Certificate[] _certs;
+ private Integer _keySize;
+ private String _idStr;
+
+
+ CachedInfo(Integer keySize, X509Certificate[] certs,String id)
+ {
+ this._keySize = keySize;
+ this._certs = certs;
+ this._idStr = id;
+ }
+
+ X509Certificate[] getCerts()
+ {
+ return _certs;
+ }
+
+ Integer getKeySize()
+ {
+ return _keySize;
+ }
+
+ String getIdStr ()
+ {
+ return _idStr;
+ }
+ }
+
+
+ public class SslConnection extends Connection
+ {
+ public SslConnection(Socket socket) throws IOException
+ {
+ super(socket);
+ }
+
+ public void run()
+ {
+ try
+ {
+ int handshakeTimeout = getHandshakeTimeout();
+ int oldTimeout = _socket.getSoTimeout();
+ if (handshakeTimeout > 0)
+ _socket.setSoTimeout(handshakeTimeout);
+
+ ((SSLSocket)_socket).startHandshake();
+
+ if (handshakeTimeout>0)
+ _socket.setSoTimeout(oldTimeout);
+
+ super.run();
+ }
+ catch (SSLException e)
+ {
+ Log.debug(e);
+ try{close();}
+ catch(IOException e2){Log.ignore(e2);}
+ }
+ catch (IOException e)
+ {
+ Log.debug(e);
+ try{close();}
+ catch(IOException e2){Log.ignore(e2);}
+ }
+ }
+ }
+
+}

Back to the top