Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'jetty-server/src/main/java/org')
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java6
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNCSARequestLog.java2
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextState.java7
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/DebugListener.java333
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java43
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java1
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java244
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java6
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java496
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java118
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java171
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java29
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java15
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/NetworkTrafficServerConnector.java4
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java437
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilder.java198
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilderImpl.java122
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/Request.java242
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/RequestLogCollection.java4
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java166
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/ResourceContentFactory.java104
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/Response.java209
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java21
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java15
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java10
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/SocketCustomizationListener.java8
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandler.java32
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/handler/AllowSymLinkAliasChecker.java41
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java129
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/handler/DebugHandler.java6
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java217
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java1
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java2
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java3
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java21
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java6
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionDataStore.java3
37 files changed, 2353 insertions, 1119 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
index bfd8206df9..c46ed08be5 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java
@@ -253,9 +253,11 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
@Override
protected void doStart() throws Exception
{
+ if(_defaultProtocol==null)
+ throw new IllegalStateException("No default protocol for "+this);
_defaultConnectionFactory = getConnectionFactory(_defaultProtocol);
if(_defaultConnectionFactory==null)
- throw new IllegalStateException("No protocol factory for default protocol: "+_defaultProtocol);
+ throw new IllegalStateException("No protocol factory for default protocol '"+_defaultProtocol+"' in "+this);
super.doStart();
@@ -298,7 +300,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
// If we have a stop timeout
long stopTimeout = getStopTimeout();
CountDownLatch stopping=_stopping;
- if (stopTimeout > 0 && stopping!=null)
+ if (stopTimeout > 0 && stopping!=null && getAcceptors()>0)
stopping.await(stopTimeout,TimeUnit.MILLISECONDS);
_stopping=null;
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNCSARequestLog.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNCSARequestLog.java
index ab46bd5217..036b5142ad 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNCSARequestLog.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNCSARequestLog.java
@@ -142,7 +142,7 @@ public abstract class AbstractNCSARequestLog extends AbstractLifeCycle implement
buf.append("] \"");
append(buf,request.getMethod());
buf.append(' ');
- append(buf,request.getHttpURI().toString());
+ append(buf,request.getOriginalURI());
buf.append(' ');
append(buf,request.getProtocol());
buf.append("\" ");
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextState.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextState.java
index 483a41c50b..66e2825302 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextState.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextState.java
@@ -33,11 +33,18 @@ import org.eclipse.jetty.server.handler.ContextHandler;
public class AsyncContextState implements AsyncContext
{
+ private final HttpChannel _channel;
volatile HttpChannelState _state;
public AsyncContextState(HttpChannelState state)
{
_state=state;
+ _channel=_state.getHttpChannel();
+ }
+
+ public HttpChannel getHttpChannel()
+ {
+ return _channel;
}
HttpChannelState state()
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/DebugListener.java b/jetty-server/src/main/java/org/eclipse/jetty/server/DebugListener.java
new file mode 100644
index 0000000000..955a655775
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/DebugListener.java
@@ -0,0 +1,333 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 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.PrintStream;
+import java.util.Locale;
+
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.DispatcherType;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletRequestEvent;
+import javax.servlet.ServletRequestListener;
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.server.handler.ContextHandler.ContextScopeListener;
+import org.eclipse.jetty.util.DateCache;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/** A Context Listener that produces additional debug.
+ * This listener if added to a ContextHandler, will produce additional debug information to
+ * either/or a specific log stream or the standard debug log.
+ * The events produced by {@link ServletContextListener}, {@link ServletRequestListener},
+ * {@link AsyncListener} and {@link ContextScopeListener} are logged.
+ */
+@ManagedObject("Debug Listener")
+public class DebugListener extends AbstractLifeCycle implements ServletContextListener
+{
+ private static final Logger LOG = Log.getLogger(DebugListener.class);
+ private static final DateCache __date=new DateCache("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH);
+
+ private final String _attr = String.format("__R%s@%x",this.getClass().getSimpleName(),System.identityHashCode(this));
+
+ private final PrintStream _out;
+ private boolean _renameThread;
+ private boolean _showHeaders;
+ private boolean _dumpContext;
+
+ public DebugListener()
+ {
+ this(null,false,false,false);
+ }
+
+ public DebugListener(@Name("renameThread") boolean renameThread, @Name("showHeaders") boolean showHeaders, @Name("dumpContext") boolean dumpContext)
+ {
+ this(null,renameThread,showHeaders,dumpContext);
+ }
+
+ public DebugListener(@Name("outputStream") OutputStream out, @Name("renameThread") boolean renameThread, @Name("showHeaders") boolean showHeaders, @Name("dumpContext") boolean dumpContext)
+ {
+ _out=out==null?null:new PrintStream(out);
+ _renameThread=renameThread;
+ _showHeaders=showHeaders;
+ _dumpContext=dumpContext;
+ }
+
+ @ManagedAttribute("Rename thread within context scope")
+ public boolean isRenameThread()
+ {
+ return _renameThread;
+ }
+
+ public void setRenameThread(boolean renameThread)
+ {
+ _renameThread = renameThread;
+ }
+
+ @ManagedAttribute("Show request headers")
+ public boolean isShowHeaders()
+ {
+ return _showHeaders;
+ }
+
+ public void setShowHeaders(boolean showHeaders)
+ {
+ _showHeaders = showHeaders;
+ }
+
+ @ManagedAttribute("Dump contexts at start")
+ public boolean isDumpContext()
+ {
+ return _dumpContext;
+ }
+
+ public void setDumpContext(boolean dumpContext)
+ {
+ _dumpContext = dumpContext;
+ }
+
+ @Override
+ public void contextInitialized(ServletContextEvent sce)
+ {
+ sce.getServletContext().addListener(_servletRequestListener);
+ ContextHandler handler = ContextHandler.getContextHandler(sce.getServletContext());
+ handler.addEventListener(_contextScopeListener);
+ String cname=findContextName(sce.getServletContext());
+ log("^ ctx=%s %s",cname,sce.getServletContext());
+ if (_dumpContext)
+ {
+ if (_out==null)
+ handler.dumpStdErr();
+ else
+ {
+ try
+ {
+ handler.dump(_out);
+ }
+ catch(Exception e)
+ {
+ LOG.warn(e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void contextDestroyed(ServletContextEvent sce)
+ {
+ String cname=findContextName(sce.getServletContext());
+ log("v ctx=%s %s",cname,sce.getServletContext());
+ }
+
+ protected String findContextName(ServletContext context)
+ {
+ if (context==null)
+ return null;
+ String n = (String)context.getAttribute(_attr);
+ if (n==null)
+ {
+ n=String.format("%s@%x",context.getContextPath(),context.hashCode());
+ context.setAttribute(_attr,n);
+ }
+ return n;
+ }
+
+ protected String findRequestName(ServletRequest request)
+ {
+ if (request==null)
+ return null;
+ HttpServletRequest r = (HttpServletRequest)request;
+ String n = (String)request.getAttribute(_attr);
+ if (n==null)
+ {
+ n=String.format("%s@%x",r.getRequestURI(),request.hashCode());
+ request.setAttribute(_attr,n);
+ }
+ return n;
+ }
+
+ protected void log(String format, Object... arg)
+ {
+ if (!isRunning())
+ return;
+
+ String s=String.format(format,arg);
+
+ long now = System.currentTimeMillis();
+ long ms = now%1000;
+ if (_out!=null)
+ _out.printf("%s.%03d:%s%n",__date.formatNow(now),ms,s);
+ if (LOG.isDebugEnabled())
+ LOG.info(s);
+ }
+
+ final AsyncListener _asyncListener = new AsyncListener()
+ {
+ @Override
+ public void onTimeout(AsyncEvent event) throws IOException
+ {
+ String cname=findContextName(((AsyncContextEvent)event).getServletContext());
+ String rname=findRequestName(event.getAsyncContext().getRequest());
+ log("! ctx=%s r=%s onTimeout %s",cname,rname,((AsyncContextEvent)event).getHttpChannelState());
+ }
+
+ @Override
+ public void onStartAsync(AsyncEvent event) throws IOException
+ {
+ String cname=findContextName(((AsyncContextEvent)event).getServletContext());
+ String rname=findRequestName(event.getAsyncContext().getRequest());
+ log("! ctx=%s r=%s onStartAsync %s",cname,rname,((AsyncContextEvent)event).getHttpChannelState());
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException
+ {
+ String cname=findContextName(((AsyncContextEvent)event).getServletContext());
+ String rname=findRequestName(event.getAsyncContext().getRequest());
+ log("!! ctx=%s r=%s onError %s %s",cname,rname,event.getThrowable(),((AsyncContextEvent)event).getHttpChannelState());
+ }
+
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException
+ {
+ AsyncContextEvent ace=(AsyncContextEvent)event;
+ String cname=findContextName(ace.getServletContext());
+ String rname=findRequestName(ace.getAsyncContext().getRequest());
+
+ Request br=Request.getBaseRequest(ace.getAsyncContext().getRequest());
+ Response response = br.getResponse();
+ String headers=_showHeaders?("\n"+response.getHttpFields().toString()):"";
+
+ log("! ctx=%s r=%s onComplete %s %d%s",cname,rname,ace.getHttpChannelState(),response.getStatus(),headers);
+ }
+ };
+
+ final ServletRequestListener _servletRequestListener = new ServletRequestListener()
+ {
+ @Override
+ public void requestInitialized(ServletRequestEvent sre)
+ {
+ String cname=findContextName(sre.getServletContext());
+ HttpServletRequest r = (HttpServletRequest)sre.getServletRequest();
+
+ String rname=findRequestName(r);
+ DispatcherType d = r.getDispatcherType();
+ if (d==DispatcherType.REQUEST)
+ {
+ Request br=Request.getBaseRequest(r);
+
+ String headers=_showHeaders?("\n"+br.getMetaData().getFields().toString()):"";
+
+
+ StringBuffer url=r.getRequestURL();
+ if (r.getQueryString()!=null)
+ url.append('?').append(r.getQueryString());
+ log(">> %s ctx=%s r=%s %s %s %s %s %s%s",d,
+ cname,
+ rname,
+ d,
+ r.getMethod(),
+ url.toString(),
+ r.getProtocol(),
+ br.getHttpChannel(),
+ headers);
+ }
+ else
+ log(">> %s ctx=%s r=%s",d,cname,rname);
+ }
+
+ @Override
+ public void requestDestroyed(ServletRequestEvent sre)
+ {
+ String cname=findContextName(sre.getServletContext());
+ HttpServletRequest r = (HttpServletRequest)sre.getServletRequest();
+ String rname=findRequestName(r);
+ DispatcherType d = r.getDispatcherType();
+ if (sre.getServletRequest().isAsyncStarted())
+ {
+ sre.getServletRequest().getAsyncContext().addListener(_asyncListener);
+ log("<< %s ctx=%s r=%s async=true",d,cname,rname);
+ }
+ else
+ {
+ Request br=Request.getBaseRequest(r);
+ String headers=_showHeaders?("\n"+br.getResponse().getHttpFields().toString()):"";
+ log("<< %s ctx=%s r=%s async=false %d%s",d,cname,rname,Request.getBaseRequest(r).getResponse().getStatus(),headers);
+ }
+ }
+ };
+
+ final ContextHandler.ContextScopeListener _contextScopeListener = new ContextHandler.ContextScopeListener()
+ {
+ @Override
+ public void enterScope(Context context, Request request, Object reason)
+ {
+ String cname=findContextName(context);
+ if (request==null)
+ log("> ctx=%s %s",cname,reason);
+ else
+ {
+ String rname=findRequestName(request);
+
+ if (_renameThread)
+ {
+ Thread thread=Thread.currentThread();
+ thread.setName(String.format("%s#%s",thread.getName(),rname));
+ }
+
+ log("> ctx=%s r=%s %s",cname,rname,reason);
+ }
+ }
+
+
+ @Override
+ public void exitScope(Context context, Request request)
+ {
+ String cname=findContextName(context);
+ if (request==null)
+ log("< ctx=%s",cname);
+ else
+ {
+ String rname=findRequestName(request);
+
+ log("< ctx=%s r=%s",cname,rname);
+ if (_renameThread)
+ {
+ Thread thread=Thread.currentThread();
+ if (thread.getName().endsWith(rname))
+ thread.setName(thread.getName().substring(0,thread.getName().length()-rname.length()-1));
+ }
+ }
+ }
+ };
+}
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
index 5ed9e13edd..1a04bd749a 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java
@@ -31,16 +31,15 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpURI;
-import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.MultiMap;
public class Dispatcher implements RequestDispatcher
{
+ public final static String __ERROR_DISPATCH="org.eclipse.jetty.server.Dispatcher.ERROR";
+
/** Dispatch include attribute names */
public final static String __INCLUDE_PREFIX="javax.servlet.include.";
@@ -76,7 +75,15 @@ public class Dispatcher implements RequestDispatcher
public void error(ServletRequest request, ServletResponse response) throws ServletException, IOException
{
- forward(request, response, DispatcherType.ERROR);
+ try
+ {
+ request.setAttribute(__ERROR_DISPATCH,Boolean.TRUE);
+ forward(request, response, DispatcherType.ERROR);
+ }
+ finally
+ {
+ request.setAttribute(__ERROR_DISPATCH,null);
+ }
}
@Override
@@ -137,9 +144,7 @@ public class Dispatcher implements RequestDispatcher
request = new ServletRequestHttpWrapper(request);
if (!(response instanceof HttpServletResponse))
response = new ServletResponseHttpWrapper(response);
-
- final boolean old_handled=baseRequest.isHandled();
-
+
final HttpURI old_uri=baseRequest.getHttpURI();
final String old_context_path=baseRequest.getContextPath();
final String old_servlet_path=baseRequest.getServletPath();
@@ -151,7 +156,6 @@ public class Dispatcher implements RequestDispatcher
try
{
- baseRequest.setHandled(false);
baseRequest.setDispatcherType(dispatch);
if (_named!=null)
@@ -204,7 +208,6 @@ public class Dispatcher implements RequestDispatcher
}
finally
{
- baseRequest.setHandled(old_handled);
baseRequest.setHttpURI(old_uri);
baseRequest.setContextPath(old_context_path);
baseRequest.setServletPath(old_servlet_path);
@@ -216,28 +219,6 @@ public class Dispatcher implements RequestDispatcher
}
}
- @Deprecated
- public void push(ServletRequest request)
- {
- Request baseRequest = Request.getBaseRequest(request);
- HttpFields fields = new HttpFields(baseRequest.getHttpFields());
-
- String query=baseRequest.getQueryString();
- if (_uri.hasQuery())
- {
- if (query==null)
- query=_uri.getQuery();
- else
- query=query+"&"+_uri.getQuery(); // TODO is this correct semantic?
- }
-
- HttpURI uri = HttpURI.createHttpURI(request.getScheme(),request.getServerName(),request.getServerPort(),_uri.getPath(),baseRequest.getHttpURI().getParam(),query,null);
-
- MetaData.Request push = new MetaData.Request(HttpMethod.GET.asString(),uri,baseRequest.getHttpVersion(),fields);
-
- baseRequest.getHttpChannel().getHttpTransport().push(push);
- }
-
@Override
public String toString()
{
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java
index 813ffd06d6..16aba09eec 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java
@@ -215,6 +215,7 @@ public class ForwardedRequestCustomizer implements Customizer
{
request.setAttribute("javax.servlet.request.ssl_session_id", ssl_session_id);
request.setScheme(HttpScheme.HTTPS.asString());
+ request.setSecure(true);
}
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java
index 4f8cce73a0..942f12bde6 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java
@@ -18,25 +18,24 @@
package org.eclipse.jetty.server;
+import static javax.servlet.RequestDispatcher.ERROR_EXCEPTION;
+import static javax.servlet.RequestDispatcher.ERROR_STATUS_CODE;
+
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.util.List;
-import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.DispatcherType;
-import javax.servlet.RequestDispatcher;
-import javax.servlet.UnavailableException;
-import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpGenerator;
import org.eclipse.jetty.http.HttpHeader;
-import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
@@ -44,6 +43,7 @@ import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.ChannelEndPoint;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.io.RuntimeIOException;
import org.eclipse.jetty.server.HttpChannelState.Action;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ErrorHandler;
@@ -262,6 +262,8 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
handle();
}
+ AtomicReference<Action> caller = new AtomicReference<>();
+
/**
* @return True if the channel is ready to continue handling (ie it is not suspended)
*/
@@ -295,7 +297,6 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
throw new IllegalStateException("state=" + _state);
_request.setHandled(false);
_response.getHttpOutput().reopen();
- _request.setDispatcherType(DispatcherType.REQUEST);
List<HttpConfiguration.Customizer> customizers = _configuration.getCustomizers();
if (!customizers.isEmpty())
@@ -303,7 +304,15 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
for (HttpConfiguration.Customizer customizer : customizers)
customizer.customize(getConnector(), _configuration, _request);
}
- getServer().handle(this);
+ try
+ {
+ _request.setDispatcherType(DispatcherType.REQUEST);
+ getServer().handle(this);
+ }
+ finally
+ {
+ _request.setDispatcherType(null);
+ }
break;
}
@@ -311,67 +320,48 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
{
_request.setHandled(false);
_response.getHttpOutput().reopen();
- _request.setDispatcherType(DispatcherType.ASYNC);
- getServer().handleAsync(this);
+
+ try
+ {
+ _request.setDispatcherType(DispatcherType.ASYNC);
+ getServer().handleAsync(this);
+ }
+ finally
+ {
+ _request.setDispatcherType(null);
+ }
break;
}
case ERROR_DISPATCH:
{
- Throwable ex = _state.getAsyncContextEvent().getThrowable();
-
- // Check for error dispatch loops
- Integer loop_detect = (Integer)_request.getAttribute("org.eclipse.jetty.server.ERROR_DISPATCH");
- if (loop_detect==null)
- loop_detect=1;
+ if (_response.isCommitted())
+ {
+ LOG.warn("Error Dispatch already committed");
+ _transport.abort((Throwable)_request.getAttribute(ERROR_EXCEPTION));
+ }
else
- loop_detect=loop_detect+1;
- _request.setAttribute("org.eclipse.jetty.server.ERROR_DISPATCH",loop_detect);
- if (loop_detect > getHttpConfiguration().getMaxErrorDispatches())
{
- LOG.warn("ERROR_DISPATCH loop detected on {} {}",_request,ex);
+ _response.reset();
+ Integer icode = (Integer)_request.getAttribute(ERROR_STATUS_CODE);
+ int code = icode!=null?icode.intValue():HttpStatus.INTERNAL_SERVER_ERROR_500;
+ _response.setStatus(code);
+ _request.setAttribute(ERROR_STATUS_CODE,code);
+ if (icode==null)
+ _request.setAttribute(ERROR_STATUS_CODE,code);
+ _request.setHandled(false);
+ _response.getHttpOutput().reopen();
+
try
{
- _response.sendError(HttpStatus.INTERNAL_SERVER_ERROR_500);
+ _request.setDispatcherType(DispatcherType.ERROR);
+ getServer().handle(this);
}
finally
{
- _state.errorComplete();
+ _request.setDispatcherType(null);
}
- break loop;
- }
-
- _request.setHandled(false);
- _response.resetBuffer();
- _response.getHttpOutput().reopen();
- _request.setDispatcherType(DispatcherType.ERROR);
-
- String reason;
- if (ex == null || ex instanceof TimeoutException)
- {
- reason = "Async Timeout";
- }
- else
- {
- reason = HttpStatus.Code.INTERNAL_SERVER_ERROR.getMessage();
- _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, ex);
}
-
- _request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, 500);
- _request.setAttribute(RequestDispatcher.ERROR_MESSAGE, reason);
- _request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, _request.getRequestURI());
-
- _response.setStatusWithReason(HttpStatus.INTERNAL_SERVER_ERROR_500, reason);
-
- ErrorHandler eh = ErrorHandler.getErrorHandler(getServer(), _state.getContextHandler());
- if (eh instanceof ErrorHandler.ErrorPageMapper)
- {
- String error_page = ((ErrorHandler.ErrorPageMapper)eh).getErrorPage((HttpServletRequest)_state.getAsyncContextEvent().getSuppliedRequest());
- if (error_page != null)
- _state.getAsyncContextEvent().setDispatchPath(error_page);
- }
-
- getServer().handleAsync(this);
break;
}
@@ -395,24 +385,14 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
break;
}
- case ASYNC_ERROR:
- {
- _state.onError();
- break;
- }
-
case COMPLETE:
{
- // TODO do onComplete here for continuations to work
-// _state.onComplete();
-
if (!_response.isCommitted() && !_request.isHandled())
- _response.sendError(404);
+ _response.sendError(HttpStatus.NOT_FOUND_404);
else
_response.closeOutput();
_request.setHandled(true);
- // TODO do onComplete here to detect errors in final flush
_state.onComplete();
onCompleted();
@@ -426,26 +406,12 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
}
}
}
- catch (EofException|QuietServletException|BadMessageException e)
- {
- if (LOG.isDebugEnabled())
- LOG.debug(e);
- handleException(e);
- }
- catch (Throwable e)
- {
- if ("ContinuationThrowable".equals(e.getClass().getSimpleName()))
- {
- LOG.ignore(e);
- }
+ catch (Throwable failure)
+ {
+ if ("org.eclipse.jetty.continuation.ContinuationThrowable".equals(failure.getClass().getName()))
+ LOG.ignore(failure);
else
- {
- if (_connector.isStarted())
- LOG.warn(String.valueOf(_request.getHttpURI()), e);
- else
- LOG.debug(String.valueOf(_request.getHttpURI()), e);
- handleException(e);
- }
+ handleException(failure);
}
action = _state.unhandle();
@@ -458,6 +424,23 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
return !suspended;
}
+ protected void sendError(int code, String reason)
+ {
+ try
+ {
+ _response.sendError(code, reason);
+ }
+ catch (Throwable x)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Could not send error " + code + " " + reason, x);
+ }
+ finally
+ {
+ _state.errorComplete();
+ }
+ }
+
/**
* <p>Sends an error 500, performing a special logic to detect whether the request is suspended,
* to avoid concurrent writes from the application.</p>
@@ -465,69 +448,61 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
* spawned thread writes the response content; in such case, we attempt to commit the error directly
* bypassing the {@link ErrorHandler} mechanisms and the response OutputStream.</p>
*
- * @param x the Throwable that caused the problem
+ * @param failure the Throwable that caused the problem
*/
- protected void handleException(Throwable x)
+ protected void handleException(Throwable failure)
{
- if (_state.isAsyncStarted())
+ // Unwrap wrapping Jetty exceptions.
+ if (failure instanceof RuntimeIOException)
+ failure = failure.getCause();
+
+ if (failure instanceof QuietServletException || !getServer().isRunning())
{
- // Handle exception via AsyncListener onError
- Throwable root = _state.getAsyncContextEvent().getThrowable();
- if (root==null)
- {
- _state.error(x);
- }
+ if (LOG.isDebugEnabled())
+ LOG.debug(_request.getRequestURI(), failure);
+ }
+ else if (failure instanceof BadMessageException)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.warn(_request.getRequestURI(), failure);
else
- {
- // TODO Can this happen? Should this just be ISE???
- // We've already processed an error before!
- root.addSuppressed(x);
- LOG.warn("Error while handling async error: ", root);
- abort(x);
- _state.errorComplete();
- }
+ LOG.warn("{} {}",_request.getRequestURI(), failure.getMessage());
}
else
{
+ LOG.info(_request.getRequestURI(), failure);
+ }
+
+ try
+ {
try
{
- // Handle error normally
- _request.setHandled(true);
- _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, x);
- _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE, x.getClass());
-
- if (isCommitted())
+ _state.onError(failure);
+ }
+ catch (Exception e)
+ {
+ LOG.warn(e);
+ // Error could not be handled, probably due to error thrown from error dispatch
+ if (_response.isCommitted())
{
- abort(x);
- if (LOG.isDebugEnabled())
- LOG.debug("Could not send response error 500, already committed", x);
+ LOG.warn("ERROR Dispatch failed: ",failure);
+ _transport.abort(failure);
}
else
{
- _response.setHeader(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString());
-
- if (x instanceof BadMessageException)
- {
- BadMessageException bme = (BadMessageException)x;
- _response.sendError(bme.getCode(), bme.getReason());
- }
- else if (x instanceof UnavailableException)
- {
- if (((UnavailableException)x).isPermanent())
- _response.sendError(HttpStatus.NOT_FOUND_404);
- else
- _response.sendError(HttpStatus.SERVICE_UNAVAILABLE_503);
- }
- else
- _response.sendError(HttpStatus.INTERNAL_SERVER_ERROR_500);
+ // Minimal response
+ Integer code=(Integer)_request.getAttribute(ERROR_STATUS_CODE);
+ _response.reset();
+ _response.setStatus(code==null?500:code.intValue());
+ _response.flushBuffer();
}
}
- catch (Throwable e)
- {
- abort(e);
- if (LOG.isDebugEnabled())
- LOG.debug("Could not commit response error 500", e);
- }
+ }
+ catch(Exception e)
+ {
+ failure.addSuppressed(e);
+ LOG.warn("ERROR Dispatch failed: ",failure);
+ _transport.abort(failure);
}
}
@@ -550,8 +525,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
_requests,
_committed.get(),
_state.getState(),
- _state.getState()==HttpChannelState.State.IDLE?"-":_request.getRequestURI()
- );
+ _request.getHttpURI());
}
public void onRequest(MetaData.Request request)
@@ -609,7 +583,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
if (handler!=null)
content=handler.badMessageError(status,reason,fields);
- sendResponse(new MetaData.Response(HttpVersion.HTTP_1_1,status,reason,fields,0),content ,true);
+ sendResponse(new MetaData.Response(HttpVersion.HTTP_1_1,status,reason,fields,BufferUtil.length(content)),content ,true);
}
}
catch (IOException e)
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java
index 8204b8b7e1..c757ca381f 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java
@@ -324,14 +324,14 @@ class HttpChannelOverHttp extends HttpChannel implements HttpParser.RequestHandl
case HTTP_2:
{
- // Allow direct "upgrade" to HTTP_2_0 only if the connector supports h2, but not protocol negotiation
+ // Allow direct "upgrade" to HTTP_2_0 only if the connector supports h2c.
_upgrade=PREAMBLE_UPGRADE_H2C;
if (HttpMethod.PRI.is(_metadata.getMethod()) &&
"*".equals(_metadata.getURI().toString()) &&
_fields.size()==0 &&
upgrade())
- return false;
+ return true;
badMessage(HttpStatus.UPGRADE_REQUIRED_426,null);
return false;
@@ -370,7 +370,7 @@ class HttpChannelOverHttp extends HttpChannel implements HttpParser.RequestHandl
if (LOG.isDebugEnabled())
LOG.debug("upgrade {} {}",this,_upgrade);
- if (_upgrade!=PREAMBLE_UPGRADE_H2C && (_connection==null || !_connection.getValue().contains("Upgrade")))
+ if (_upgrade!=PREAMBLE_UPGRADE_H2C && (_connection==null || !_connection.contains("upgrade")))
throw new BadMessageException(HttpStatus.BAD_REQUEST_400);
// Find the upgrade factory
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java
index bf4af4b46e..2c16602ec4 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java
@@ -18,16 +18,23 @@
package org.eclipse.jetty.server;
+import static javax.servlet.RequestDispatcher.ERROR_EXCEPTION;
+import static javax.servlet.RequestDispatcher.ERROR_MESSAGE;
+import static javax.servlet.RequestDispatcher.ERROR_STATUS_CODE;
+
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.AsyncListener;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletResponse;
+import javax.servlet.UnavailableException;
+import org.eclipse.jetty.http.BadMessageException;
+import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandler.Context;
import org.eclipse.jetty.util.log.Log;
@@ -45,12 +52,13 @@ public class HttpChannelState
private final static long DEFAULT_TIMEOUT=Long.getLong("org.eclipse.jetty.server.HttpChannelState.DEFAULT_TIMEOUT",30000L);
/**
- * The dispatched state of the HttpChannel, used to control the overall lifecycle
+ * The state of the HttpChannel,used to control the overall lifecycle.
*/
public enum State
{
IDLE, // Idle request
DISPATCHED, // Request dispatched to filter/servlet
+ THROWN, // Exception thrown while DISPATCHED
ASYNC_WAIT, // Suspended and waiting
ASYNC_WOKEN, // Dispatch to handle from ASYNC_WAIT
ASYNC_IO, // Dispatched for async IO
@@ -67,7 +75,6 @@ public class HttpChannelState
DISPATCH, // handle a normal request dispatch
ASYNC_DISPATCH, // handle an async request dispatch
ERROR_DISPATCH, // handle a normal error
- ASYNC_ERROR, // handle an async error
WRITE_CALLBACK, // handle an IO write callback
READ_CALLBACK, // handle an IO read callback
COMPLETE, // Complete the response
@@ -76,14 +83,12 @@ public class HttpChannelState
}
/**
- * The state of the servlet async API. This can lead or follow the
- * channel dispatch state and also includes reasons such as expired,
- * dispatched or completed.
+ * The state of the servlet async API.
*/
public enum Async
{
STARTED, // AsyncContext.startAsync() has been called
- DISPATCH, //
+ DISPATCH, // AsyncContext.dispatch() has been called
COMPLETE, // AsyncContext.complete() has been called
EXPIRING, // AsyncContext timeout just happened
EXPIRED, // AsyncContext timeout has been processed
@@ -160,12 +165,18 @@ public class HttpChannelState
{
try(Locker.Lock lock= _locker.lock())
{
- return String.format("%s@%x{s=%s a=%s i=%b r=%s w=%b}",getClass().getSimpleName(),hashCode(),_state,_async,_initial,
- _asyncReadPossible?(_asyncReadUnready?"PU":"P!U"):(_asyncReadUnready?"!PU":"!P!U"),
- _asyncWrite);
+ return toStringLocked();
}
}
+ public String toStringLocked()
+ {
+ return String.format("%s@%x{s=%s a=%s i=%b r=%s w=%b}",getClass().getSimpleName(),hashCode(),_state,_async,_initial,
+ _asyncReadPossible?(_asyncReadUnready?"PU":"P!U"):(_asyncReadUnready?"!PU":"!P!U"),
+ _asyncWrite);
+ }
+
+
private String getStatusStringLocked()
{
return String.format("s=%s i=%b a=%s",_state,_initial,_async);
@@ -184,10 +195,11 @@ public class HttpChannelState
*/
protected Action handling()
{
- if(DEBUG)
- LOG.debug("{} handling {}",this,_state);
try(Locker.Lock lock= _locker.lock())
{
+ if(DEBUG)
+ LOG.debug("handling {}",toStringLocked());
+
switch(_state)
{
case IDLE:
@@ -228,17 +240,15 @@ public class HttpChannelState
_state=State.DISPATCHED;
_async=null;
return Action.ASYNC_DISPATCH;
- case EXPIRING:
- break;
case EXPIRED:
+ case ERRORED:
_state=State.DISPATCHED;
_async=null;
return Action.ERROR_DISPATCH;
case STARTED:
- return Action.WAIT;
+ case EXPIRING:
case ERRORING:
- _state=State.DISPATCHED;
- return Action.ASYNC_ERROR;
+ return Action.WAIT;
default:
throw new IllegalStateException(getStatusStringLocked());
@@ -264,45 +274,53 @@ public class HttpChannelState
try(Locker.Lock lock= _locker.lock())
{
+ if(DEBUG)
+ LOG.debug("startAsync {}",toStringLocked());
+
if (_state!=State.DISPATCHED || _async!=null)
throw new IllegalStateException(this.getStatusStringLocked());
_async=Async.STARTED;
_event=event;
lastAsyncListeners=_asyncListeners;
- _asyncListeners=null;
+ _asyncListeners=null;
}
if (lastAsyncListeners!=null)
{
- for (AsyncListener listener : lastAsyncListeners)
+ Runnable callback=new Runnable()
{
- try
+ @Override
+ public void run()
{
- listener.onStartAsync(event);
+ for (AsyncListener listener : lastAsyncListeners)
+ {
+ try
+ {
+ listener.onStartAsync(event);
+ }
+ catch(Exception e)
+ {
+ // TODO Async Dispatch Error
+ LOG.warn(e);
+ }
+ }
}
- catch(Exception e)
+ @Override
+ public String toString()
{
- // TODO Async Dispatch Error
- LOG.warn(e);
+ return "startAsync";
}
- }
+ };
+
+ runInContext(event,callback);
}
}
- protected void error(Throwable th)
- {
- try(Locker.Lock lock= _locker.lock())
- {
- if (_event!=null)
- _event.addThrowable(th);
- _async=Async.ERRORING;
- }
- }
/**
* Signal that the HttpConnection has finished handling the request.
- * For blocking connectors, this call may block if the request has
+ * For blocking connectors,this call may block if the request has
* been suspended (startAsync called).
* @return next actions
* be handled again (eg because of a resume that happened before unhandle was called)
@@ -313,17 +331,21 @@ public class HttpChannelState
AsyncContextEvent schedule_event=null;
boolean read_interested=false;
- if(DEBUG)
- LOG.debug("{} unhandle {}",this,_state);
-
try(Locker.Lock lock= _locker.lock())
{
+ if(DEBUG)
+ LOG.debug("unhandle {}",toStringLocked());
+
switch(_state)
{
case COMPLETING:
case COMPLETED:
return Action.TERMINATED;
+ case THROWN:
+ _state=State.DISPATCHED;
+ return Action.ERROR_DISPATCH;
+
case DISPATCHED:
case ASYNC_IO:
break;
@@ -349,12 +371,6 @@ public class HttpChannelState
action=Action.ASYNC_DISPATCH;
break;
- case EXPIRED:
- _state=State.DISPATCHED;
- _async=null;
- action = Action.ERROR_DISPATCH;
- break;
-
case STARTED:
if (_asyncReadUnready && _asyncReadPossible)
{
@@ -378,26 +394,27 @@ public class HttpChannelState
break;
case EXPIRING:
- schedule_event=_event;
+ // onTimeout callbacks still being called, so just WAIT
_state=State.ASYNC_WAIT;
action=Action.WAIT;
break;
- case ERRORING:
+ case EXPIRED:
+ // onTimeout handling is complete, but did not dispatch as
+ // we were handling. So do the error dispatch here
_state=State.DISPATCHED;
- action=Action.ASYNC_ERROR;
+ _async=null;
+ action=Action.ERROR_DISPATCH;
break;
-
+
case ERRORED:
_state=State.DISPATCHED;
- action=Action.ERROR_DISPATCH;
_async=null;
+ action=Action.ERROR_DISPATCH;
break;
default:
- _state=State.COMPLETING;
- action=Action.COMPLETE;
- break;
+ throw new IllegalStateException(this.getStatusStringLocked());
}
}
else
@@ -416,13 +433,22 @@ public class HttpChannelState
public void dispatch(ServletContext context, String path)
{
- boolean dispatch;
+ boolean dispatch=false;
+ AsyncContextEvent event;
try(Locker.Lock lock= _locker.lock())
{
+ if(DEBUG)
+ LOG.debug("dispatch {} -> {}",toStringLocked(),path);
+
+ boolean started=false;
+ event=_event;
switch(_async)
{
case STARTED:
+ started=true;
+ break;
case EXPIRING:
+ case ERRORING:
case ERRORED:
break;
default:
@@ -435,27 +461,26 @@ public class HttpChannelState
if (path!=null)
_event.setDispatchPath(path);
- switch(_state)
+ if (started)
{
- case DISPATCHED:
- case ASYNC_IO:
- dispatch=false;
- break;
- case ASYNC_WAIT:
- _state=State.ASYNC_WOKEN;
- dispatch=true;
- break;
- case ASYNC_WOKEN:
- dispatch=false;
- break;
- default:
- LOG.warn("async dispatched when complete {}",this);
- dispatch=false;
- break;
+ switch(_state)
+ {
+ case DISPATCHED:
+ case ASYNC_IO:
+ case ASYNC_WOKEN:
+ break;
+ case ASYNC_WAIT:
+ _state=State.ASYNC_WOKEN;
+ dispatch=true;
+ break;
+ default:
+ LOG.warn("async dispatched when complete {}",this);
+ break;
+ }
}
}
- cancelTimeout();
+ cancelTimeout(event);
if (dispatch)
scheduleDispatch();
}
@@ -466,58 +491,88 @@ public class HttpChannelState
AsyncContextEvent event;
try(Locker.Lock lock= _locker.lock())
{
+ if(DEBUG)
+ LOG.debug("onTimeout {}",toStringLocked());
+
if (_async!=Async.STARTED)
return;
_async=Async.EXPIRING;
event=_event;
listeners=_asyncListeners;
- }
- if (LOG.isDebugEnabled())
- LOG.debug("Async timeout {}",this);
+ }
+ final AtomicReference<Throwable> error=new AtomicReference<Throwable>();
if (listeners!=null)
{
- for (AsyncListener listener : listeners)
+ Runnable task=new Runnable()
{
- try
+ @Override
+ public void run()
{
- listener.onTimeout(event);
+ for (AsyncListener listener : listeners)
+ {
+ try
+ {
+ listener.onTimeout(event);
+ }
+ catch(Throwable x)
+ {
+ LOG.debug("Exception while invoking listener " + listener,x);
+ if (error.get()==null)
+ error.set(x);
+ else
+ error.get().addSuppressed(x);
+ }
+ }
}
- catch(Exception e)
+ @Override
+ public String toString()
{
- LOG.debug(e);
- event.addThrowable(e);
- _channel.getRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,event.getThrowable());
- break;
+ return "onTimeout";
}
- }
+ };
+
+ runInContext(event,task);
}
+ Throwable th=error.get();
boolean dispatch=false;
try(Locker.Lock lock= _locker.lock())
{
- if (_async==Async.EXPIRING)
+ switch(_async)
{
- // If the listeners did not call dispatch() or complete(),
- // then the container must generate an error.
- if (event.getThrowable()==null)
- {
- _async=Async.EXPIRED;
- _event.addThrowable(new TimeoutException("Async API violation"));
- }
- else
- {
- _async=Async.ERRORING;
- }
- if (_state==State.ASYNC_WAIT)
- {
- _state=State.ASYNC_WOKEN;
- dispatch=true;
- }
+ case EXPIRING:
+ _async=th==null ? Async.EXPIRED : Async.ERRORING;
+ break;
+
+ case COMPLETE:
+ case DISPATCH:
+ if (th!=null)
+ {
+ LOG.ignore(th);
+ th=null;
+ }
+ break;
+
+ default:
+ throw new IllegalStateException();
+ }
+
+ if (_state==State.ASYNC_WAIT)
+ {
+ _state=State.ASYNC_WOKEN;
+ dispatch=true;
}
}
+ if (th!=null)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Error after async timeout {}",this,th);
+ onError(th);
+ }
+
if (dispatch)
{
if (LOG.isDebugEnabled())
@@ -528,42 +583,53 @@ public class HttpChannelState
public void complete()
{
+
// just like resume, except don't set _dispatched=true;
boolean handle=false;
+ AsyncContextEvent event;
try(Locker.Lock lock= _locker.lock())
{
+ if(DEBUG)
+ LOG.debug("complete {}",toStringLocked());
+
+ boolean started=false;
+ event=_event;
+
switch(_async)
{
case STARTED:
+ started=true;
+ break;
case EXPIRING:
+ case ERRORING:
case ERRORED:
break;
+ case COMPLETE:
+ return;
default:
throw new IllegalStateException(this.getStatusStringLocked());
}
_async=Async.COMPLETE;
- if (_state==State.ASYNC_WAIT)
+
+ if (started && _state==State.ASYNC_WAIT)
{
handle=true;
_state=State.ASYNC_WOKEN;
}
}
- cancelTimeout();
+ cancelTimeout(event);
if (handle)
- {
- ContextHandler handler=getContextHandler();
- if (handler!=null)
- handler.handle(_channel.getRequest(),_channel);
- else
- _channel.handle();
- }
+ runInContext(event,_channel);
}
public void errorComplete()
{
try(Locker.Lock lock= _locker.lock())
{
+ if(DEBUG)
+ LOG.debug("error complete {}",toStringLocked());
+
_async=Async.COMPLETE;
_event.setDispatchContext(null);
_event.setDispatchPath(null);
@@ -571,40 +637,142 @@ public class HttpChannelState
cancelTimeout();
}
-
- protected void onError()
+
+ protected void onError(Throwable failure)
{
- final List<AsyncListener> aListeners;
+ final List<AsyncListener> listeners;
final AsyncContextEvent event;
-
+ final Request baseRequest = _channel.getRequest();
+
+ int code=HttpStatus.INTERNAL_SERVER_ERROR_500;
+ String reason=null;
+ if (failure instanceof BadMessageException)
+ {
+ BadMessageException bme = (BadMessageException)failure;
+ code = bme.getCode();
+ reason = bme.getReason();
+ }
+ else if (failure instanceof UnavailableException)
+ {
+ if (((UnavailableException)failure).isPermanent())
+ code = HttpStatus.NOT_FOUND_404;
+ else
+ code = HttpStatus.SERVICE_UNAVAILABLE_503;
+ }
+
try(Locker.Lock lock= _locker.lock())
{
- if (_state!=State.DISPATCHED/* || _async!=Async.ERRORING*/)
+ if(DEBUG)
+ LOG.debug("onError {} {}",toStringLocked(),failure);
+
+ // Set error on request.
+ if(_event!=null)
+ {
+ if (_event.getThrowable()!=null)
+ throw new IllegalStateException("Error already set",_event.getThrowable());
+ _event.addThrowable(failure);
+ _event.getSuppliedRequest().setAttribute(ERROR_STATUS_CODE,code);
+ _event.getSuppliedRequest().setAttribute(ERROR_EXCEPTION,failure);
+ _event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,failure==null?null:failure.getClass());
+
+ _event.getSuppliedRequest().setAttribute(ERROR_MESSAGE,reason!=null?reason:null);
+ }
+ else
+ {
+ Throwable error = (Throwable)baseRequest.getAttribute(ERROR_EXCEPTION);
+ if (error!=null)
+ throw new IllegalStateException("Error already set",error);
+ baseRequest.setAttribute(ERROR_STATUS_CODE,code);
+ baseRequest.setAttribute(ERROR_EXCEPTION,failure);
+ baseRequest.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,failure==null?null:failure.getClass());
+ baseRequest.setAttribute(ERROR_MESSAGE,reason!=null?reason:null);
+ }
+
+ // Are we blocking?
+ if (_async==null)
+ {
+ // Only called from within HttpChannel Handling, so much be dispatched, let's stay dispatched!
+ if (_state==State.DISPATCHED)
+ {
+ _state=State.THROWN;
+ return;
+ }
throw new IllegalStateException(this.getStatusStringLocked());
-
- aListeners=_asyncListeners;
+ }
+
+ // We are Async
+ _async=Async.ERRORING;
+ listeners=_asyncListeners;
event=_event;
- _async=Async.ERRORED;
}
- if (event!=null && aListeners!=null)
+ if(listeners!=null)
{
- event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,event.getThrowable());
- event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_MESSAGE,event.getThrowable().getMessage());
- for (AsyncListener listener : aListeners)
+ Runnable task=new Runnable()
{
- try
+ @Override
+ public void run()
{
- listener.onError(event);
+ for (AsyncListener listener : listeners)
+ {
+ try
+ {
+ listener.onError(event);
+ }
+ catch (Throwable x)
+ {
+ LOG.info("Exception while invoking listener " + listener,x);
+ }
+ }
}
- catch(Exception x)
+
+ @Override
+ public String toString()
+ {
+ return "onError";
+ }
+ };
+ runInContext(event,task);
+ }
+
+ boolean dispatch=false;
+ try(Locker.Lock lock= _locker.lock())
+ {
+ switch(_async)
+ {
+ case ERRORING:
+ {
+ // Still in this state ? The listeners did not invoke API methods
+ // and the container must provide a default error dispatch.
+ _async=Async.ERRORED;
+ break;
+ }
+ case DISPATCH:
+ case COMPLETE:
+ {
+ // The listeners called dispatch() or complete().
+ break;
+ }
+ default:
{
- LOG.info("Exception while invoking listener " + listener, x);
+ throw new IllegalStateException(toString());
}
}
+
+ if(_state==State.ASYNC_WAIT)
+ {
+ _state=State.ASYNC_WOKEN;
+ dispatch=true;
+ }
}
- }
+ if(dispatch)
+ {
+ if(LOG.isDebugEnabled())
+ LOG.debug("Dispatch after error {}",this);
+ scheduleDispatch();
+ }
+ }
protected void onComplete()
{
@@ -613,6 +781,9 @@ public class HttpChannelState
try(Locker.Lock lock= _locker.lock())
{
+ if(DEBUG)
+ LOG.debug("onComplete {}",toStringLocked());
+
switch(_state)
{
case COMPLETING:
@@ -631,17 +802,31 @@ public class HttpChannelState
{
if (aListeners!=null)
{
- for (AsyncListener listener : aListeners)
+ Runnable callback = new Runnable()
{
- try
+ @Override
+ public void run()
{
- listener.onComplete(event);
- }
- catch(Exception e)
+ for (AsyncListener listener : aListeners)
+ {
+ try
+ {
+ listener.onComplete(event);
+ }
+ catch(Exception e)
+ {
+ LOG.warn("Exception while invoking listener " + listener,e);
+ }
+ }
+ }
+ @Override
+ public String toString()
{
- LOG.warn(e);
+ return "onComplete";
}
- }
+ };
+
+ runInContext(event,callback);
}
event.completed();
}
@@ -652,6 +837,9 @@ public class HttpChannelState
cancelTimeout();
try(Locker.Lock lock= _locker.lock())
{
+ if(DEBUG)
+ LOG.debug("recycle {}",toStringLocked());
+
switch(_state)
{
case DISPATCHED:
@@ -678,6 +866,9 @@ public class HttpChannelState
cancelTimeout();
try(Locker.Lock lock= _locker.lock())
{
+ if(DEBUG)
+ LOG.debug("upgrade {}",toStringLocked());
+
switch(_state)
{
case IDLE:
@@ -697,7 +888,6 @@ public class HttpChannelState
}
}
-
protected void scheduleDispatch()
{
_channel.execute(_channel);
@@ -717,10 +907,15 @@ public class HttpChannelState
{
event=_event;
}
+ cancelTimeout(event);
+ }
+
+ protected void cancelTimeout(AsyncContextEvent event)
+ {
if (event!=null)
event.cancelTimeoutTask();
}
-
+
public boolean isIdle()
{
try(Locker.Lock lock= _locker.lock())
@@ -779,7 +974,6 @@ public class HttpChannelState
}
}
-
public boolean isAsync()
{
try(Locker.Lock lock= _locker.lock())
@@ -805,7 +999,11 @@ public class HttpChannelState
{
event=_event;
}
+ return getContextHandler(event);
+ }
+ ContextHandler getContextHandler(AsyncContextEvent event)
+ {
if (event!=null)
{
Context context=((Context)event.getServletContext());
@@ -822,11 +1020,25 @@ public class HttpChannelState
{
event=_event;
}
+ return getServletResponse(event);
+ }
+
+ public ServletResponse getServletResponse(AsyncContextEvent event)
+ {
if (event!=null && event.getSuppliedResponse()!=null)
return event.getSuppliedResponse();
return _channel.getResponse();
}
-
+
+ void runInContext(AsyncContextEvent event,Runnable runnable)
+ {
+ ContextHandler contextHandler = getContextHandler(event);
+ if (contextHandler==null)
+ runnable.run();
+ else
+ contextHandler.handle(_channel.getRequest(),runnable);
+ }
+
public Object getAttribute(String name)
{
return _channel.getRequest().getAttribute(name);
@@ -855,6 +1067,9 @@ public class HttpChannelState
boolean interested=false;
try(Locker.Lock lock= _locker.lock())
{
+ if(DEBUG)
+ LOG.debug("onReadUnready {}",toStringLocked());
+
// We were already unready, this is not a state change, so do nothing
if (!_asyncReadUnready)
{
@@ -881,6 +1096,9 @@ public class HttpChannelState
boolean woken=false;
try(Locker.Lock lock= _locker.lock())
{
+ if(DEBUG)
+ LOG.debug("onReadPossible {}",toStringLocked());
+
_asyncReadPossible=true;
if (_state==State.ASYNC_WAIT && _asyncReadUnready)
{
@@ -903,6 +1121,9 @@ public class HttpChannelState
boolean woken=false;
try(Locker.Lock lock= _locker.lock())
{
+ if(DEBUG)
+ LOG.debug("onReadReady {}",toStringLocked());
+
_asyncReadUnready=true;
_asyncReadPossible=true;
if (_state==State.ASYNC_WAIT)
@@ -928,6 +1149,9 @@ public class HttpChannelState
try(Locker.Lock lock= _locker.lock())
{
+ if(DEBUG)
+ LOG.debug("onWritePossible {}",toStringLocked());
+
_asyncWrite=true;
if (_state==State.ASYNC_WAIT)
{
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
index 4f3a84b147..3496017ecd 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
@@ -212,7 +212,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
public void onFillable()
{
if (LOG.isDebugEnabled())
- LOG.debug("{} onFillable enter {}", this, _channel.getState());
+ LOG.debug("{} onFillable enter {} {}", this, _channel.getState(),BufferUtil.toDetailString(_requestBuffer));
HttpConnection last=setCurrentConnection(this);
try
@@ -259,7 +259,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
{
setCurrentConnection(last);
if (LOG.isDebugEnabled())
- LOG.debug("{} onFillable exit {}", this, _channel.getState());
+ LOG.debug("{} onFillable exit {} {}", this, _channel.getState(),BufferUtil.toDetailString(_requestBuffer));
}
}
@@ -272,8 +272,6 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
boolean handled=false;
while (_parser.inContentState())
{
- if (LOG.isDebugEnabled())
- LOG.debug("{} parseContent",this);
int filled = fillRequestBuffer();
boolean handle = parseRequestBuffer();
handled|=handle;
@@ -300,7 +298,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
// No pretend we read -1
_parser.atEOF();
if (LOG.isDebugEnabled())
- LOG.debug("{} filled -1",this);
+ LOG.debug("{} filled -1 {}",this,BufferUtil.toDetailString(_requestBuffer));
return -1;
}
@@ -321,7 +319,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
_parser.atEOF();
if (LOG.isDebugEnabled())
- LOG.debug("{} filled {}",this,filled);
+ LOG.debug("{} filled {} {}",this,filled,BufferUtil.toDetailString(_requestBuffer));
return filled;
}
@@ -517,6 +515,51 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
return new Content(c);
}
+ @Override
+ public void abort(Throwable failure)
+ {
+ // Do a direct close of the output, as this may indicate to a client that the
+ // response is bad either with RST or by abnormal completion of chunked response.
+ getEndPoint().close();
+ }
+
+ @Override
+ public boolean isPushSupported()
+ {
+ return false;
+ }
+
+ @Override
+ public void push(org.eclipse.jetty.http.MetaData.Request request)
+ {
+ LOG.debug("ignore push in {}",this);
+ }
+
+ public void asyncReadFillInterested()
+ {
+ getEndPoint().fillInterested(_asyncReadCallback);
+ }
+
+ public void blockingReadFillInterested()
+ {
+ getEndPoint().fillInterested(_blockingReadCallback);
+ }
+
+ public void blockingReadException(Throwable e)
+ {
+ _blockingReadCallback.failed(e);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s[p=%s,g=%s,c=%s]",
+ super.toString(),
+ _parser,
+ _generator,
+ _channel);
+ }
+
private class Content extends HttpInput.Content
{
public Content(ByteBuffer content)
@@ -646,24 +689,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
{
case NEED_HEADER:
{
- // Look for optimisation to avoid allocating a _header buffer
- /*
- Cannot use this optimisation unless we work out how not to overwrite data in user passed arrays.
- if (_lastContent && _content!=null && !_content.isReadOnly() && _content.hasArray() && BufferUtil.space(_content)>_config.getResponseHeaderSize() )
- {
- // use spare space in content buffer for header buffer
- int p=_content.position();
- int l=_content.limit();
- _content.position(l);
- _content.limit(l+_config.getResponseHeaderSize());
- _header=_content.slice();
- _header.limit(0);
- _content.position(p);
- _content.limit(l);
- }
- else
- */
- _header = _bufferPool.acquire(_config.getResponseHeaderSize(), HEADER_BUFFER_DIRECT);
+ _header = _bufferPool.acquire(_config.getResponseHeaderSize(), HEADER_BUFFER_DIRECT);
continue;
}
@@ -764,48 +790,4 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
return String.format("%s[i=%s,cb=%s]",super.toString(),_info,_callback);
}
}
-
- @Override
- public void abort(Throwable failure)
- {
- // Do a direct close of the output, as this may indicate to a client that the
- // response is bad either with RST or by abnormal completion of chunked response.
- getEndPoint().close();
- }
-
- @Override
- public boolean isPushSupported()
- {
- return false;
- }
-
- /**
- * @see org.eclipse.jetty.server.HttpTransport#push(org.eclipse.jetty.http.MetaData.Request)
- */
- @Override
- public void push(org.eclipse.jetty.http.MetaData.Request request)
- {
- LOG.debug("ignore push in {}",this);
- }
-
- public void asyncReadFillInterested()
- {
- getEndPoint().fillInterested(_asyncReadCallback);
- }
-
- public void blockingReadFillInterested()
- {
- getEndPoint().fillInterested(_blockingReadCallback);
- }
-
- public void blockingReadException(Throwable e)
- {
- _blockingReadCallback.failed(e);
- }
-
- @Override
- public String toString()
- {
- return super.toString()+"<--"+BufferUtil.toDetailString(_requestBuffer);
- }
}
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
index 98b122ba87..f8b14734df 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java
@@ -21,7 +21,9 @@ package org.eclipse.jetty.server;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.nio.ByteBuffer;
+import java.util.ArrayDeque;
import java.util.Objects;
+import java.util.Queue;
import java.util.concurrent.TimeoutException;
import javax.servlet.ReadListener;
@@ -29,7 +31,6 @@ import javax.servlet.ServletInputStream;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.io.RuntimeIOException;
-import org.eclipse.jetty.util.ArrayQueue;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.log.Log;
@@ -48,9 +49,9 @@ public class HttpInput extends ServletInputStream implements Runnable
private final static Logger LOG = Log.getLogger(HttpInput.class);
private final static Content EOF_CONTENT = new EofContent("EOF");
private final static Content EARLY_EOF_CONTENT = new EofContent("EARLY_EOF");
-
+
private final byte[] _oneByteBuffer = new byte[1];
- private final ArrayQueue<Content> _inputQ = new ArrayQueue<>();
+ private final Queue<Content> _inputQ = new ArrayDeque<>();
private final HttpChannelState _channelState;
private ReadListener _listener;
private State _state = STREAM;
@@ -63,21 +64,21 @@ public class HttpInput extends ServletInputStream implements Runnable
if (_channelState.getHttpChannel().getHttpConfiguration().getBlockingTimeout()>0)
_blockingTimeoutAt=0;
}
-
+
protected HttpChannelState getHttpChannelState()
{
return _channelState;
}
-
+
public void recycle()
{
synchronized (_inputQ)
{
- Content item = _inputQ.pollUnsafe();
+ Content item = _inputQ.poll();
while (item != null)
{
item.failed(null);
- item = _inputQ.pollUnsafe();
+ item = _inputQ.poll();
}
_listener = null;
_state = STREAM;
@@ -92,7 +93,7 @@ public class HttpInput extends ServletInputStream implements Runnable
boolean woken=false;
synchronized (_inputQ)
{
- Content content = _inputQ.peekUnsafe();
+ Content content = _inputQ.peek();
if (content==null)
{
try
@@ -103,13 +104,13 @@ public class HttpInput extends ServletInputStream implements Runnable
{
woken=failed(e);
}
- content = _inputQ.peekUnsafe();
+ content = _inputQ.peek();
}
-
+
if (content!=null)
available= remaining(content);
}
-
+
if (woken)
wake();
return available;
@@ -117,10 +118,10 @@ public class HttpInput extends ServletInputStream implements Runnable
private void wake()
{
- _channelState.getHttpChannel().getConnector().getExecutor().execute(_channelState.getHttpChannel());
+ _channelState.getHttpChannel().getConnector().getExecutor().execute(_channelState.getHttpChannel());
}
-
-
+
+
@Override
public int read() throws IOException
{
@@ -137,7 +138,7 @@ public class HttpInput extends ServletInputStream implements Runnable
{
if (_blockingTimeoutAt>=0 && !isAsync())
_blockingTimeoutAt=System.currentTimeMillis()+getHttpChannelState().getHttpChannel().getHttpConfiguration().getBlockingTimeout();
-
+
while(true)
{
Content item = nextContent();
@@ -146,12 +147,12 @@ public class HttpInput extends ServletInputStream implements Runnable
if (LOG.isDebugEnabled())
LOG.debug("{} read {} from {}",this,len,item);
int l = get(item, b, off, len);
-
+
consumeNonContent();
-
+
return l;
}
-
+
if (!_state.blockForContent(this))
return _state.noContent();
}
@@ -159,7 +160,7 @@ public class HttpInput extends ServletInputStream implements Runnable
}
/**
- * Called when derived implementations should attempt to
+ * Called when derived implementations should attempt to
* produce more Content and add it via {@link #addContent(Content)}.
* For protocols that are constantly producing (eg HTTP2) this can
* be left as a noop;
@@ -168,11 +169,11 @@ public class HttpInput extends ServletInputStream implements Runnable
protected void produceContent() throws IOException
{
}
-
+
/**
* Get the next content from the inputQ, calling {@link #produceContent()}
* if need be. EOF is processed and state changed.
- *
+ *
* @return the content or null if none available.
* @throws IOException if retrieving the content fails
*/
@@ -186,7 +187,7 @@ public class HttpInput extends ServletInputStream implements Runnable
}
return content;
}
-
+
/** Poll the inputQ for Content.
* Consumed buffers and {@link PoisonPillContent}s are removed and
* EOF state updated if need be.
@@ -195,11 +196,11 @@ public class HttpInput extends ServletInputStream implements Runnable
protected Content pollContent()
{
// Items are removed only when they are fully consumed.
- Content content = _inputQ.peekUnsafe();
+ Content content = _inputQ.peek();
// Skip consumed items at the head of the queue.
while (content != null && remaining(content) == 0)
{
- _inputQ.pollUnsafe();
+ _inputQ.poll();
content.succeeded();
if (LOG.isDebugEnabled())
LOG.debug("{} consumed {}", this, content);
@@ -212,45 +213,45 @@ public class HttpInput extends ServletInputStream implements Runnable
{
_state=AEOF;
boolean woken = _channelState.onReadReady(); // force callback?
- if (woken)
+ if (woken)
wake();
}
}
else if (content==EARLY_EOF_CONTENT)
_state=EARLY_EOF;
- content = _inputQ.peekUnsafe();
+ content = _inputQ.peek();
}
-
+
return content;
}
- /**
+ /**
*/
protected void consumeNonContent()
{
// Items are removed only when they are fully consumed.
- Content content = _inputQ.peekUnsafe();
+ Content content = _inputQ.peek();
// Skip consumed items at the head of the queue.
while (content != null && remaining(content) == 0)
{
// Defer EOF until read
if (content instanceof EofContent)
break;
-
+
// Consume all other empty content
- _inputQ.pollUnsafe();
+ _inputQ.poll();
content.succeeded();
if (LOG.isDebugEnabled())
LOG.debug("{} consumed {}", this, content);
- content = _inputQ.peekUnsafe();
- }
+ content = _inputQ.peek();
+ }
}
/**
* Get the next readable from the inputQ, calling {@link #produceContent()}
* if need be. EOF is NOT processed and state is not changed.
- *
+ *
* @return the content or EOF or null if none available.
* @throws IOException if retrieving the content fails
*/
@@ -273,22 +274,22 @@ public class HttpInput extends ServletInputStream implements Runnable
protected Content pollReadable()
{
// Items are removed only when they are fully consumed.
- Content content = _inputQ.peekUnsafe();
-
+ Content content = _inputQ.peek();
+
// Skip consumed items at the head of the queue except EOF
while (content != null)
{
if (content==EOF_CONTENT || content==EARLY_EOF_CONTENT || remaining(content)>0)
return content;
-
- _inputQ.pollUnsafe();
+
+ _inputQ.poll();
content.succeeded();
if (LOG.isDebugEnabled())
LOG.debug("{} consumed {}", this, content);
- content = _inputQ.peekUnsafe();
+ content = _inputQ.peek();
}
-
- return content;
+
+ return null;
}
/**
@@ -334,7 +335,7 @@ public class HttpInput extends ServletInputStream implements Runnable
pollContent(); // hungry succeed
}
-
+
/**
* Blocks until some content or some end-of-file event arrives.
*
@@ -358,7 +359,7 @@ public class HttpInput extends ServletInputStream implements Runnable
_inputQ.wait(timeout);
else
_inputQ.wait();
-
+
if (_blockingTimeoutAt>0 && System.currentTimeMillis()>=_blockingTimeoutAt)
throw new TimeoutException();
}
@@ -367,7 +368,7 @@ public class HttpInput extends ServletInputStream implements Runnable
throw (IOException)new InterruptedIOException().initCause(e);
}
}
-
+
/**
* Adds some content to this input stream.
*
@@ -379,16 +380,16 @@ public class HttpInput extends ServletInputStream implements Runnable
boolean woken=false;
synchronized (_inputQ)
{
- _inputQ.addUnsafe(item);
+ _inputQ.offer(item);
if (LOG.isDebugEnabled())
LOG.debug("{} addContent {}", this, item);
-
+
if (_listener==null)
_inputQ.notify();
else
- woken=_channelState.onReadPossible();
+ woken=_channelState.onReadPossible();
}
-
+
return woken;
}
@@ -396,10 +397,10 @@ public class HttpInput extends ServletInputStream implements Runnable
{
synchronized (_inputQ)
{
- return _inputQ.sizeUnsafe()>0;
+ return _inputQ.size()>0;
}
}
-
+
public void unblock()
{
synchronized (_inputQ)
@@ -407,7 +408,7 @@ public class HttpInput extends ServletInputStream implements Runnable
_inputQ.notify();
}
}
-
+
public long getContentConsumed()
{
synchronized (_inputQ)
@@ -450,7 +451,7 @@ public class HttpInput extends ServletInputStream implements Runnable
Content item = nextContent();
if (item == null)
break; // Let's not bother blocking
-
+
skip(item, remaining(item));
}
return isFinished() && !isError();
@@ -470,7 +471,7 @@ public class HttpInput extends ServletInputStream implements Runnable
return _state instanceof ErrorState;
}
}
-
+
public boolean isAsync()
{
synchronized (_inputQ)
@@ -487,7 +488,7 @@ public class HttpInput extends ServletInputStream implements Runnable
return _state instanceof EOFState;
}
}
-
+
@Override
public boolean isReady()
@@ -531,7 +532,7 @@ public class HttpInput extends ServletInputStream implements Runnable
_state = ASYNC;
_listener = readListener;
boolean content=nextContent()!=null;
-
+
if (content)
woken = _channelState.onReadReady();
else
@@ -556,8 +557,8 @@ public class HttpInput extends ServletInputStream implements Runnable
LOG.warn(x);
else
_state = new ErrorState(x);
-
- if (_listener==null)
+
+ if (_listener==null)
_inputQ.notify();
else
woken=_channelState.onReadPossible();
@@ -569,7 +570,7 @@ public class HttpInput extends ServletInputStream implements Runnable
/* ------------------------------------------------------------ */
/*
* <p>
- * While this class is-a Runnable, it should never be dispatched in it's own thread. It is a
+ * While this class is-a Runnable, it should never be dispatched in it's own thread. It is a
* runnable only so that the calling thread can use {@link ContextHandler#handle(Runnable)}
* to setup classloaders etc.
* </p>
@@ -585,7 +586,7 @@ public class HttpInput extends ServletInputStream implements Runnable
{
if (_state==EOF)
return;
-
+
if (_state==AEOF)
{
_state=EOF;
@@ -593,7 +594,7 @@ public class HttpInput extends ServletInputStream implements Runnable
}
listener = _listener;
- error = _state instanceof ErrorState?((ErrorState)_state).getError():null;
+ error = _state instanceof ErrorState?((ErrorState)_state).getError():null;
}
try
@@ -604,9 +605,13 @@ public class HttpInput extends ServletInputStream implements Runnable
listener.onError(error);
}
else if (aeof)
- listener.onAllDataRead();
- else if (error == null)
+ {
+ listener.onAllDataRead();
+ }
+ else
+ {
listener.onDataAvailable();
+ }
}
catch (Throwable e)
{
@@ -629,6 +634,16 @@ public class HttpInput extends ServletInputStream implements Runnable
}
}
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x[c=%d,s=%s]",
+ getClass().getSimpleName(),
+ hashCode(),
+ _contentConsumed,
+ _state);
+ }
+
public static class PoisonPillContent extends Content
{
private final String _name;
@@ -637,14 +652,14 @@ public class HttpInput extends ServletInputStream implements Runnable
super(BufferUtil.EMPTY_BUFFER);
_name=name;
}
-
+
@Override
public String toString()
{
return _name;
}
}
-
+
public static class EofContent extends PoisonPillContent
{
EofContent(String name)
@@ -652,46 +667,46 @@ public class HttpInput extends ServletInputStream implements Runnable
super(name);
}
}
-
+
public static class Content implements Callback
{
private final ByteBuffer _content;
-
+
public Content(ByteBuffer content)
{
_content=content;
}
-
+
@Override
public boolean isNonBlocking()
{
return true;
}
-
-
+
+
public ByteBuffer getContent()
{
return _content;
}
-
+
public boolean hasContent()
{
return _content.hasRemaining();
}
-
+
public int remaining()
{
return _content.remaining();
}
-
+
@Override
public String toString()
{
return String.format("Content@%x{%s}",hashCode(),BufferUtil.toDetailString(_content));
}
}
-
-
+
+
protected static abstract class State
{
public boolean blockForContent(HttpInput in) throws IOException
@@ -708,7 +723,7 @@ public class HttpInput extends ServletInputStream implements Runnable
protected static class EOFState extends State
{
}
-
+
protected class ErrorState extends EOFState
{
final Throwable _error;
@@ -716,7 +731,7 @@ public class HttpInput extends ServletInputStream implements Runnable
{
_error=error;
}
-
+
public Throwable getError()
{
return _error;
@@ -767,7 +782,7 @@ public class HttpInput extends ServletInputStream implements Runnable
return "ASYNC";
}
};
-
+
protected static final State EARLY_EOF = new EOFState()
{
@Override
@@ -791,7 +806,7 @@ public class HttpInput extends ServletInputStream implements Runnable
return "EOF";
}
};
-
+
protected static final State AEOF = new EOFState()
{
@Override
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
index ca35951889..e19a44b4d9 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java
@@ -82,7 +82,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
setWriteListener() READY->owp ise ise ise ise ise
write() OPEN ise PENDING wpe wpe eof
flush() OPEN ise PENDING wpe wpe eof
- close() CLOSED CLOSED CLOSED CLOSED wpe CLOSED
+ close() CLOSED CLOSED CLOSED CLOSED CLOSED CLOSED
isReady() OPEN:true READY:true READY:true UNREADY:false UNREADY:false CLOSED:true
write completed - - - ASYNC READY->owp -
*/
@@ -196,10 +196,14 @@ public class HttpOutput extends ServletOutputStream implements Runnable
return;
}
case UNREADY:
+ case PENDING:
{
- if (_state.compareAndSet(state,OutputState.ERROR))
- _writeListener.onError(_onError==null?new EofException("Async close"):_onError);
- break;
+ if (!_state.compareAndSet(state,OutputState.CLOSED))
+ break;
+ IOException ex = new IOException("Closed while Pending/Unready");
+ LOG.warn(ex.toString());
+ LOG.debug(ex);
+ _channel.abort(ex);
}
default:
{
@@ -286,6 +290,20 @@ public class HttpOutput extends ServletOutputStream implements Runnable
return _state.get()==OutputState.CLOSED;
}
+ public boolean isAsync()
+ {
+ switch(_state.get())
+ {
+ case ASYNC:
+ case READY:
+ case PENDING:
+ case UNREADY:
+ return true;
+ default:
+ return false;
+ }
+ }
+
@Override
public void flush() throws IOException
{
@@ -307,6 +325,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable
return;
case PENDING:
+ return;
+
case UNREADY:
throw new WritePendingException();
@@ -1252,4 +1272,5 @@ public class HttpOutput extends ServletOutputStream implements Runnable
super.onCompleteFailure(x);
}
}
+
}
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
index 7865c6b7f2..b1c1618e63 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java
@@ -197,27 +197,16 @@ public class LocalConnector extends AbstractConnector
}
@Override
- public void close()
- {
- boolean wasOpen=isOpen();
- super.close();
- if (wasOpen)
- {
- getConnection().onClose();
- onClose();
- }
- }
-
- @Override
public void onClose()
{
+ getConnection().onClose();
LocalConnector.this.onEndPointClosed(this);
super.onClose();
_closed.countDown();
}
@Override
- public void shutdownOutput()
+ public void doShutdownOutput()
{
super.shutdownOutput();
close();
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkTrafficServerConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkTrafficServerConnector.java
index cfd96c1808..e4ed0a2c59 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkTrafficServerConnector.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkTrafficServerConnector.java
@@ -26,10 +26,10 @@ import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.ChannelEndPoint;
import org.eclipse.jetty.io.ManagedSelector;
import org.eclipse.jetty.io.NetworkTrafficListener;
import org.eclipse.jetty.io.NetworkTrafficSelectChannelEndPoint;
-import org.eclipse.jetty.io.SelectChannelEndPoint;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.Scheduler;
@@ -84,7 +84,7 @@ public class NetworkTrafficServerConnector extends ServerConnector
}
@Override
- protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
+ protected ChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
{
NetworkTrafficSelectChannelEndPoint endPoint = new NetworkTrafficSelectChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout(), listeners);
return endPoint;
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java
index b72cf5718d..7a5e51cca1 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java
@@ -19,17 +19,23 @@
package org.eclipse.jetty.server;
import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ReadPendingException;
import java.nio.channels.WritePendingException;
+import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.AttributesMap;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -38,14 +44,17 @@ import org.eclipse.jetty.util.log.Logger;
/**
* ConnectionFactory for the PROXY Protocol.
* <p>This factory can be placed in front of any other connection factory
- * to process the proxy line before the normal protocol handling</p>
+ * to process the proxy v1 or v2 line before the normal protocol handling</p>
*
* @see <a href="http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt">http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt</a>
*/
public class ProxyConnectionFactory extends AbstractConnectionFactory
{
+ public static final String TLS_VERSION = "TLS_VERSION";
+
private static final Logger LOG = Log.getLogger(ProxyConnectionFactory.class);
private final String _next;
+ private int _maxProxyHeader=1024;
/* ------------------------------------------------------------ */
/** Proxy Connection Factory that uses the next ConnectionFactory
@@ -63,6 +72,16 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory
_next=nextProtocol;
}
+ public int getMaxProxyHeader()
+ {
+ return _maxProxyHeader;
+ }
+
+ public void setMaxProxyHeader(int maxProxyHeader)
+ {
+ _maxProxyHeader = maxProxyHeader;
+ }
+
@Override
public Connection newConnection(Connector connector, EndPoint endp)
{
@@ -80,10 +99,79 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory
}
}
- return new ProxyConnection(endp,connector,next);
+ return new ProxyProtocolV1orV2Connection(endp,connector,next);
+ }
+
+ public class ProxyProtocolV1orV2Connection extends AbstractConnection
+ {
+ private final Connector _connector;
+ private final String _next;
+ private ByteBuffer _buffer = BufferUtil.allocate(16);
+
+ protected ProxyProtocolV1orV2Connection(EndPoint endp, Connector connector, String next)
+ {
+ super(endp,connector.getExecutor());
+ _connector=connector;
+ _next=next;
+ }
+
+ @Override
+ public void onOpen()
+ {
+ super.onOpen();
+ fillInterested();
+ }
+
+ @Override
+ public void onFillable()
+ {
+ try
+ {
+ while(BufferUtil.space(_buffer)>0)
+ {
+ // Read data
+ int fill=getEndPoint().fill(_buffer);
+ if (fill<0)
+ {
+ getEndPoint().shutdownOutput();
+ return;
+ }
+ if (fill==0)
+ {
+ fillInterested();
+ return;
+ }
+ }
+
+ // Is it a V1?
+ switch(_buffer.get(0))
+ {
+ case 'P':
+ {
+ ProxyProtocolV1Connection v1 = new ProxyProtocolV1Connection(getEndPoint(),_connector,_next,_buffer);
+ getEndPoint().upgrade(v1);
+ return;
+ }
+ case 0x0D:
+ {
+ ProxyProtocolV2Connection v2 = new ProxyProtocolV2Connection(getEndPoint(),_connector,_next,_buffer);
+ getEndPoint().upgrade(v2);
+ return;
+ }
+ default:
+ LOG.warn("Not PROXY protocol for {}",getEndPoint());
+ close();
+ }
+ }
+ catch (Throwable x)
+ {
+ LOG.warn("PROXY error for "+getEndPoint(),x);
+ close();
+ }
+ }
}
- public static class ProxyConnection extends AbstractConnection
+ public static class ProxyProtocolV1Connection extends AbstractConnection
{
// 0 1 2 3 4 5 6
// 98765432109876543210987654321
@@ -97,11 +185,13 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory
private int _fields;
private int _length;
- protected ProxyConnection(EndPoint endp, Connector connector, String next)
+ protected ProxyProtocolV1Connection(EndPoint endp, Connector connector, String next,ByteBuffer buffer)
{
super(endp,connector.getExecutor());
_connector=connector;
_next=next;
+ _length=buffer.remaining();
+ parse(buffer);
}
@Override
@@ -110,16 +200,60 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory
super.onOpen();
fillInterested();
}
+
+
+ private boolean parse(ByteBuffer buffer)
+ {
+ // parse fields
+ while (buffer.hasRemaining())
+ {
+ byte b = buffer.get();
+ if (_fields<6)
+ {
+ if (b==' ' || b=='\r' && _fields==5)
+ {
+ _field[_fields++]=_builder.toString();
+ _builder.setLength(0);
+ }
+ else if (b<' ')
+ {
+ LOG.warn("Bad character {} for {}",b&0xFF,getEndPoint());
+ close();
+ return false;
+ }
+ else
+ {
+ _builder.append((char)b);
+ }
+ }
+ else
+ {
+ if (b=='\n')
+ {
+ _fields=7;
+ return true;
+ }
+ LOG.warn("Bad CRLF for {}",getEndPoint());
+ close();
+ return false;
+ }
+ }
+
+ return true;
+ }
+
@Override
public void onFillable()
{
try
{
ByteBuffer buffer=null;
- loop: while(true)
+ while(_fields<7)
{
// Create a buffer that will not read too much data
+ // since once read it is impossible to push back for the
+ // real connection to read it.
int size=Math.max(1,__size[_fields]-_builder.length());
if (buffer==null || buffer.capacity()!=size)
buffer=BufferUtil.allocate(size);
@@ -147,38 +281,8 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory
return;
}
- // parse fields
- while (buffer.hasRemaining())
- {
- byte b = buffer.get();
- if (_fields<6)
- {
- if (b==' ' || b=='\r' && _fields==5)
- {
- _field[_fields++]=_builder.toString();
- _builder.setLength(0);
- }
- else if (b<' ')
- {
- LOG.warn("Bad character {} for {}",b&0xFF,getEndPoint());
- close();
- return;
- }
- else
- {
- _builder.append((char)b);
- }
- }
- else
- {
- if (b=='\n')
- break loop;
-
- LOG.warn("Bad CRLF for {}",getEndPoint());
- close();
- return;
- }
- }
+ if (!parse(buffer))
+ return;
}
// Check proxy
@@ -197,10 +301,13 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory
ConnectionFactory connectionFactory = _connector.getConnectionFactory(_next);
if (connectionFactory == null)
{
- LOG.info("Next protocol '{}' for {}",_next,getEndPoint());
+ LOG.warn("No Next protocol '{}' for {}",_next,getEndPoint());
close();
return;
}
+
+ if (LOG.isDebugEnabled())
+ LOG.warn("Next protocol '{}' for {} r={} l={}",_next,getEndPoint(),remote,local);
EndPoint endPoint = new ProxyEndPoint(getEndPoint(),remote,local);
Connection newConnection = connectionFactory.newConnection(_connector, endPoint);
@@ -213,8 +320,260 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory
}
}
}
+
+
+ enum Family { UNSPEC, INET, INET6, UNIX };
+ enum Transport { UNSPEC, STREAM, DGRAM };
+ private static final byte[] MAGIC = new byte[]{0x0D,0x0A,0x0D,0x0A,0x00,0x0D,0x0A,0x51,0x55,0x49,0x54,0x0A};
+
+ public class ProxyProtocolV2Connection extends AbstractConnection
+ {
+ private final Connector _connector;
+ private final String _next;
+ private final boolean _local;
+ private final Family _family;
+ private final Transport _transport;
+ private final int _length;
+ private final ByteBuffer _buffer;
+
+ protected ProxyProtocolV2Connection(EndPoint endp, Connector connector, String next,ByteBuffer buffer)
+ throws IOException
+ {
+ super(endp,connector.getExecutor());
+ _connector=connector;
+ _next=next;
+
+ if (buffer.remaining()!=16)
+ throw new IllegalStateException();
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("PROXYv2 header {} for {}",BufferUtil.toHexSummary(buffer),this);
+
+ // struct proxy_hdr_v2 {
+ // uint8_t sig[12]; /* hex 0D 0A 0D 0A 00 0D 0A 51 55 49 54 0A */
+ // uint8_t ver_cmd; /* protocol version and command */
+ // uint8_t fam; /* protocol family and address */
+ // uint16_t len; /* number of following bytes part of the header */
+ // };
+ for (int i=0;i<MAGIC.length;i++)
+ if (buffer.get()!=MAGIC[i])
+ throw new IOException("Bad PROXY protocol v2 signature");
+
+ int versionAndCommand = 0xff & buffer.get();
+ if ((versionAndCommand&0xf0) != 0x20)
+ throw new IOException("Bad PROXY protocol v2 version");
+ _local=(versionAndCommand&0xf)==0x00;
+
+ int transportAndFamily = 0xff & buffer.get();
+ switch(transportAndFamily>>4)
+ {
+ case 0: _family=Family.UNSPEC; break;
+ case 1: _family=Family.INET; break;
+ case 2: _family=Family.INET6; break;
+ case 3: _family=Family.UNIX; break;
+ default:
+ throw new IOException("Bad PROXY protocol v2 family");
+ }
+
+ switch(0xf&transportAndFamily)
+ {
+ case 0: _transport=Transport.UNSPEC; break;
+ case 1: _transport=Transport.STREAM; break;
+ case 2: _transport=Transport.DGRAM; break;
+ default:
+ throw new IOException("Bad PROXY protocol v2 family");
+ }
+
+ _length = buffer.getChar();
+
+ if (!_local && (_family==Family.UNSPEC || _family==Family.UNIX || _transport!=Transport.STREAM))
+ throw new IOException(String.format("Unsupported PROXY protocol v2 mode 0x%x,0x%x",versionAndCommand,transportAndFamily));
+
+ if (_length>_maxProxyHeader)
+ throw new IOException(String.format("Unsupported PROXY protocol v2 mode 0x%x,0x%x,0x%x",versionAndCommand,transportAndFamily,_length));
+
+ _buffer = _length>0?BufferUtil.allocate(_length):BufferUtil.EMPTY_BUFFER;
+ }
+
+ @Override
+ public void onOpen()
+ {
+ super.onOpen();
+ if (_buffer.remaining()==_length)
+ next();
+ else
+ fillInterested();
+ }
+
+ @Override
+ public void onFillable()
+ {
+ try
+ {
+ while(_buffer.remaining()<_length)
+ {
+ // Read data
+ int fill=getEndPoint().fill(_buffer);
+ if (fill<0)
+ {
+ getEndPoint().shutdownOutput();
+ return;
+ }
+ if (fill==0)
+ {
+ fillInterested();
+ return;
+ }
+ }
+ }
+ catch (Throwable x)
+ {
+ LOG.warn("PROXY error for "+getEndPoint(),x);
+ close();
+ return;
+ }
+
+ next();
+ }
+
+ private void next()
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("PROXYv2 next {} from {} for {}",_next,BufferUtil.toHexSummary(_buffer),this);
+
+ // Create the next protocol
+ ConnectionFactory connectionFactory = _connector.getConnectionFactory(_next);
+ if (connectionFactory == null)
+ {
+ LOG.info("Next protocol '{}' for {}",_next,getEndPoint());
+ close();
+ return;
+ }
+
+ // Do we need to wrap the endpoint?
+ EndPoint endPoint=getEndPoint();
+ if (!_local)
+ {
+ try
+ {
+ InetAddress src;
+ InetAddress dst;
+ int sp;
+ int dp;
+
+ switch(_family)
+ {
+ case INET:
+ {
+ byte[] addr=new byte[4];
+ _buffer.get(addr);
+ src = Inet4Address.getByAddress(addr);
+ _buffer.get(addr);
+ dst = Inet4Address.getByAddress(addr);
+ sp = _buffer.getChar();
+ dp = _buffer.getChar();
+
+ break;
+ }
+
+ case INET6:
+ {
+ byte[] addr=new byte[16];
+ _buffer.get(addr);
+ src = Inet6Address.getByAddress(addr);
+ _buffer.get(addr);
+ dst = Inet6Address.getByAddress(addr);
+ sp = _buffer.getChar();
+ dp = _buffer.getChar();
+ break;
+ }
+
+ default:
+ throw new IllegalStateException();
+ }
+
+
+ // Extract Addresses
+ InetSocketAddress remote=new InetSocketAddress(src,sp);
+ InetSocketAddress local =new InetSocketAddress(dst,dp);
+ ProxyEndPoint proxyEndPoint = new ProxyEndPoint(endPoint,remote,local);
+ endPoint = proxyEndPoint;
+
+
+ // Any additional info?
+ while(_buffer.hasRemaining())
+ {
+ int type = 0xff & _buffer.get();
+ int length = _buffer.getShort();
+ byte[] value = new byte[length];
+ _buffer.get(value);
+
+ if (LOG.isDebugEnabled())
+ LOG.debug(String.format("T=%x L=%d V=%s for %s",type,length,TypeUtil.toHexString(value),this));
+
+ // TODO interpret these values
+ switch(type)
+ {
+ case 0x01: // PP2_TYPE_ALPN
+ break;
+ case 0x02: // PP2_TYPE_AUTHORITY
+ break;
+ case 0x20: // PP2_TYPE_SSL
+ {
+ int i=0;
+ int client = 0xff & value[i++];
+ int verify = (0xff & value[i++])<<24 + (0xff & value[i++])<<16 + (0xff & value[i++])<<8 + (0xff&value[i++]);
+ while(i<value.length)
+ {
+ int ssl_type = 0xff & value[i++];
+ int ssl_length = (0xff & value[i++])*0x100 + (0xff&value[i++]);
+ byte[] ssl_val = new byte[ssl_length];
+ System.arraycopy(value,i,ssl_val,0,ssl_length);
+ i+=ssl_length;
+
+ switch(ssl_type)
+ {
+ case 0x21: // PP2_TYPE_SSL_VERSION
+ String version=new String(ssl_val,0,ssl_length,StandardCharsets.ISO_8859_1);
+ if (client==1)
+ proxyEndPoint.setAttribute(TLS_VERSION,version);
+ break;
+
+ default:
+ break;
+ }
+ }
+ break;
+ }
+ case 0x21: // PP2_TYPE_SSL_VERSION
+ break;
+ case 0x22: // PP2_TYPE_SSL_CN
+ break;
+ case 0x30: // PP2_TYPE_NETNS
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("{} {}",getEndPoint(),proxyEndPoint.toString());
+
+
+ }
+ catch(Exception e)
+ {
+ LOG.warn(e);
+ }
+ }
+
+ Connection newConnection = connectionFactory.newConnection(_connector, endPoint);
+ endPoint.upgrade(newConnection);
+ }
+ }
+
- public static class ProxyEndPoint implements EndPoint
+ public static class ProxyEndPoint extends AttributesMap implements EndPoint
{
private final EndPoint _endp;
private final InetSocketAddress _remote;
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilder.java b/jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilder.java
index 5e807b45dc..a16c9960e6 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilder.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilder.java
@@ -25,63 +25,99 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
+
/** Build a request to be pushed.
- * <p>
- * A PushBuilder is obtained by calling {@link Request#getPushBuilder()}
- * which creates an initializes the builder as follows:
+ *
+ * <p>A PushBuilder is obtained by calling {@link
+ * Request#getPushBuilder()} (<code>Eventually HttpServletRequest.getPushBuilder()</code>).
+ * Each call to this method will
+ * return a new instance of a PushBuilder based off the current {@code
+ * HttpServletRequest}. Any mutations to the returned PushBuilder are
+ * not reflected on future returns.</p>
+ *
+ * <p>The instance is initialized as follows:</p>
+ *
* <ul>
- * <li> Each call to getPushBuilder() will return a new instance of a
- * PushBuilder based off the Request. Any mutations to the
- * returned PushBuilder are not reflected on future returns.</li>
+ *
* <li>The method is initialized to "GET"</li>
- * <li>The requests headers are added to the Builder, except for:<ul>
+ *
+ * <li>The existing headers of the current {@link HttpServletRequest}
+ * are added to the builder, except for:
+ *
+ * <ul>
* <li>Conditional headers (eg. If-Modified-Since)
* <li>Range headers
* <li>Expect headers
* <li>Authorization headers
* <li>Referrer headers
- * </ul></li>
- * <li>If the request was Authenticated, an Authorization header will
+ * </ul>
+ *
+ * </li>
+ *
+ * <li>If the request was authenticated, an Authorization header will
* be set with a container generated token that will result in equivalent
- * Authorization for the pushed request</li>
- * <li>The query string from {@link HttpServletRequest#getQueryString()}
- * <li>The {@link HttpServletRequest#getRequestedSessionId()} value, unless at the time
- * of the call {@link HttpServletRequest#getSession(boolean)}
- * has previously been called to create a new {@link HttpSession}, in
- * which case the new session ID will be used as the PushBuilders
- * requested session ID. The source of the requested session id will be the
- * same as for the request</li>
- * <li>The Referer header will be set to {@link HttpServletRequest#getRequestURL()}
- * plus any {@link HttpServletRequest#getQueryString()} </li>
+ * Authorization for the pushed request.</li>
+ *
+ * <li>The {@link HttpServletRequest#getRequestedSessionId()} value,
+ * unless at the time of the call {@link
+ * HttpServletRequest#getSession(boolean)} has previously been called to
+ * create a new {@link HttpSession}, in which case the new session ID
+ * will be used as the PushBuilder's requested session ID. The source of
+ * the requested session id will be the same as for the request</li>
+ *
+ * <li>The Referer(sic) header will be set to {@link
+ * HttpServletRequest#getRequestURL()} plus any {@link
+ * HttpServletRequest#getQueryString()} </li>
+ *
* <li>If {@link HttpServletResponse#addCookie(Cookie)} has been called
* on the associated response, then a corresponding Cookie header will be added
* to the PushBuilder, unless the {@link Cookie#getMaxAge()} is &lt;=0, in which
* case the Cookie will be removed from the builder.</li>
- * <li>If this request has has the conditional headers If-Modified-Since or
- * If-None-Match then the {@link #isConditional()} header is set to true.</li>
- * </ul>
- * <p>A PushBuilder can be customized by chained calls to mutator methods before the
- * {@link #push()} method is called to initiate a push request with the current state
- * of the builder. After the call to {@link #push()}, the builder may be reused for
- * another push, however the {@link #path(String)}, {@link #etag(String)} and
- * {@link #lastModified(String)} values will have been nulled. All other
- * values are retained over calls to {@link #push()}.
+ *
+ * <li>If this request has has the conditional headers If-Modified-Since
+ * or If-None-Match, then the {@link #isConditional()} header is set to
+ * true.</li>
+ *
+ * </ul>
+ *
+ * <p>The {@link #path} method must be called on the {@code PushBuilder}
+ * instance before the call to {@link #push}. Failure to do so must
+ * cause an exception to be thrown from {@link
+ * #push}, as specified in that method.</p>
+ *
+ * <p>A PushBuilder can be customized by chained calls to mutator
+ * methods before the {@link #push()} method is called to initiate an
+ * asynchronous push request with the current state of the builder.
+ * After the call to {@link #push()}, the builder may be reused for
+ * another push, however the implementation must make it so the {@link
+ * #path(String)}, {@link #etag(String)} and {@link
+ * #lastModified(String)} values are cleared before returning from
+ * {@link #push}. All other values are retained over calls to {@link
+ * #push()}.
+ *
+ * @since 4.0
*/
public interface PushBuilder
{
- /** Set the method to be used for the push.
- * Defaults to GET.
+ /**
+ * <p>Set the method to be used for the push.</p>
+ *
+ * <p>Any non-empty String may be used for the method.</p>
+ *
* @param method the method to be used for the push.
* @return this builder.
+ * @throws NullPointerException if the argument is {@code null}
+ * @throws IllegalArgumentException if the argument is the empty String
*/
public abstract PushBuilder method(String method);
/** Set the query string to be used for the push.
- * Defaults to the requests query string.
- * Will be appended to any query String included in a call to {@link #path(String)}. This
- * method should be used instead of a query in {@link #path(String)} when multiple
- * {@link #push()} calls are to be made with the same query string, or to remove a
- * query string obtained from the associated request.
+ *
+ * Will be appended to any query String included in a call to {@link
+ * #path(String)}. Any duplicate parameters must be preserved. This
+ * method should be used instead of a query in {@link #path(String)}
+ * when multiple {@link #push()} calls are to be made with the same
+ * query string.
* @param queryString the query string to be used for the push.
* @return this builder.
*/
@@ -108,33 +144,55 @@ public interface PushBuilder
*/
public abstract PushBuilder conditional(boolean conditional);
- /** Set a header to be used for the push.
+ /**
+ * <p>Set a header to be used for the push. If the builder has an
+ * existing header with the same name, its value is overwritten.</p>
+ *
* @param name The header name to set
* @param value The header value to set
* @return this builder.
*/
public abstract PushBuilder setHeader(String name, String value);
+
- /** Add a header to be used for the push.
+ /**
+ * <p>Add a header to be used for the push.</p>
* @param name The header name to add
* @param value The header value to add
* @return this builder.
*/
public abstract PushBuilder addHeader(String name, String value);
+
+
+ /**
+ * <p>Remove the named header. If the header does not exist, take
+ * no action.</p>
+ *
+ * @param name The name of the header to remove
+ * @return this builder.
+ */
+ public abstract PushBuilder removeHeader(String name);
+
- /** Set the URI path to be used for the push.
- * The path may start with "/" in which case it is treated as an
- * absolute path, otherwise it is relative to the context path of
- * the associated request.
- * There is no path default and {@link #path(String)} must be called
- * before every call to {@link #push()}
+
+ /**
+ * Set the URI path to be used for the push. The path may start
+ * with "/" in which case it is treated as an absolute path,
+ * otherwise it is relative to the context path of the associated
+ * request. There is no path default and {@link #path(String)} must
+ * be called before every call to {@link #push()}. If a query
+ * string is present in the argument {@code path}, its contents must
+ * be merged with the contents previously passed to {@link
+ * #queryString}, preserving duplicates.
+ *
* @param path the URI path to be used for the push, which may include a
* query string.
* @return this builder.
*/
public abstract PushBuilder path(String path);
- /** Set the etag to be used for conditional pushes.
+ /**
+ * Set the etag to be used for conditional pushes.
* The etag will be used only if {@link #isConditional()} is true.
* Defaults to no etag. The value is nulled after every call to
* {@link #push()}
@@ -143,33 +201,44 @@ public interface PushBuilder
*/
public abstract PushBuilder etag(String etag);
- /** Set the last modified date to be used for conditional pushes.
- * The last modified date will be used only if {@link #isConditional()} is true.
- * Defaults to no date. The value is nulled after every call to
- * {@link #push()}
+ /**
+ * Set the last modified date to be used for conditional pushes.
+ * The last modified date will be used only if {@link
+ * #isConditional()} is true. Defaults to no date. The value is
+ * nulled after every call to {@link #push()}
* @param lastModified the last modified date to be used for the push.
* @return this builder.
- * */
+ */
public abstract PushBuilder lastModified(String lastModified);
- /** Push a resource.
- * Push a resource based on the current state of the PushBuilder. If {@link #isConditional()}
- * is true and an etag or lastModified value is provided, then an appropriate conditional header
- * will be generated. If both an etag and lastModified value are provided only an If-None-Match header
- * will be generated. If the builder has a session ID, then the pushed request
- * will include the session ID either as a Cookie or as a URI parameter as appropriate. The builders
- * query string is merged with any passed query string.
- * After initiating the push, the builder has its path, etag and lastModified fields nulled. All
- * other fields are left as is for possible reuse in another push.
- * @throws IllegalArgumentException if the method set expects a request body (eg POST)
+ /** Push a resource given the current state of the builder,
+ * returning immediately without blocking.
+ *
+ * <p>Push a resource based on the current state of the PushBuilder.
+ * If {@link #isConditional()} is true and an etag or lastModified
+ * value is provided, then an appropriate conditional header will be
+ * generated. If both an etag and lastModified value are provided
+ * only an If-None-Match header will be generated. If the builder
+ * has a session ID, then the pushed request will include the
+ * session ID either as a Cookie or as a URI parameter as
+ * appropriate. The builders query string is merged with any passed
+ * query string.</p>
+ *
+ * <p>Before returning from this method, the builder has its path,
+ * etag and lastModified fields nulled. All other fields are left as
+ * is for possible reuse in another push.</p>
+ *
+ * @throws IllegalArgumentException if the method set expects a
+ * request body (eg POST)
+ *
+ * @throws IllegalStateException if there was no call to {@link
+ * #path} on this instance either between its instantiation or the
+ * last call to {@code push()} that did not throw an
+ * IllegalStateException.
*/
public abstract void push();
-
-
-
-
public abstract String getMethod();
public abstract String getQueryString();
public abstract String getSessionId();
@@ -179,7 +248,4 @@ public interface PushBuilder
public abstract String getPath();
public abstract String getEtag();
public abstract String getLastModified();
-
-
-
} \ No newline at end of file
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilderImpl.java b/jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilderImpl.java
index f39cffa878..def6bed888 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilderImpl.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilderImpl.java
@@ -32,14 +32,14 @@ import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */
-/**
+/**
*/
public class PushBuilderImpl implements PushBuilder
-{
+{
private static final Logger LOG = Log.getLogger(PushBuilderImpl.class);
private final static HttpField JettyPush = new HttpField("x-http2-push","PushBuilder");
-
+
private final Request _request;
private final HttpFields _fields;
private String _method;
@@ -49,7 +49,7 @@ public class PushBuilderImpl implements PushBuilder
private String _path;
private String _etag;
private String _lastModified;
-
+
public PushBuilderImpl(Request request, HttpFields fields, String method, String queryString, String sessionId, boolean conditional)
{
super();
@@ -65,124 +65,88 @@ public class PushBuilderImpl implements PushBuilder
}
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#getMethod()
- */
@Override
public String getMethod()
{
return _method;
}
-
+
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#method(java.lang.String)
- */
@Override
public PushBuilder method(String method)
{
_method = method;
return this;
}
-
+
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#getQueryString()
- */
@Override
public String getQueryString()
{
return _queryString;
}
-
+
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#queryString(java.lang.String)
- */
@Override
public PushBuilder queryString(String queryString)
{
_queryString = queryString;
return this;
}
-
+
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#getSessionId()
- */
@Override
public String getSessionId()
{
return _sessionId;
}
-
+
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#sessionId(java.lang.String)
- */
@Override
public PushBuilder sessionId(String sessionId)
{
_sessionId = sessionId;
return this;
}
-
+
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#isConditional()
- */
@Override
public boolean isConditional()
{
return _conditional;
}
-
+
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#conditional(boolean)
- */
@Override
public PushBuilder conditional(boolean conditional)
{
_conditional = conditional;
return this;
}
-
+
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#getHeaderNames()
- */
@Override
public Set<String> getHeaderNames()
{
return _fields.getFieldNamesCollection();
}
-
+
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#getHeader(java.lang.String)
- */
@Override
public String getHeader(String name)
{
return _fields.get(name);
}
-
+
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#setHeader(java.lang.String, java.lang.String)
- */
@Override
public PushBuilder setHeader(String name,String value)
{
_fields.put(name,value);
return this;
}
-
+
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#addHeader(java.lang.String, java.lang.String)
- */
@Override
public PushBuilder addHeader(String name,String value)
{
@@ -190,11 +154,15 @@ public class PushBuilderImpl implements PushBuilder
return this;
}
-
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#getPath()
- */
+ @Override
+ public PushBuilder removeHeader(String name)
+ {
+ _fields.remove(name);
+ return this;
+ }
+
+ /* ------------------------------------------------------------ */
@Override
public String getPath()
{
@@ -202,9 +170,6 @@ public class PushBuilderImpl implements PushBuilder
}
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#path(java.lang.String)
- */
@Override
public PushBuilder path(String path)
{
@@ -213,9 +178,6 @@ public class PushBuilderImpl implements PushBuilder
}
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#getEtag()
- */
@Override
public String getEtag()
{
@@ -223,9 +185,6 @@ public class PushBuilderImpl implements PushBuilder
}
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#etag(java.lang.String)
- */
@Override
public PushBuilder etag(String etag)
{
@@ -234,9 +193,6 @@ public class PushBuilderImpl implements PushBuilder
}
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#getLastModified()
- */
@Override
public String getLastModified()
{
@@ -244,9 +200,6 @@ public class PushBuilderImpl implements PushBuilder
}
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#lastModified(java.lang.String)
- */
@Override
public PushBuilder lastModified(String lastModified)
{
@@ -255,40 +208,36 @@ public class PushBuilderImpl implements PushBuilder
}
/* ------------------------------------------------------------ */
- /* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#push()
- */
@Override
public void push()
{
if (HttpMethod.POST.is(_method) || HttpMethod.PUT.is(_method))
throw new IllegalStateException("Bad Method "+_method);
-
+
if (_path==null || _path.length()==0)
throw new IllegalStateException("Bad Path "+_path);
-
+
String path=_path;
String query=_queryString;
int q=path.indexOf('?');
if (q>=0)
{
- query=(query!=null && query.length()>0)?(_path.substring(q+1)+'&'+query):_path.substring(q+1);
- path=_path.substring(0,q);
+ query=(query!=null && query.length()>0)?(path.substring(q+1)+'&'+query):path.substring(q+1);
+ path=path.substring(0,q);
}
-
+
if (!path.startsWith("/"))
path=URIUtil.addPaths(_request.getContextPath(),path);
-
+
String param=null;
if (_sessionId!=null)
{
if (_request.isRequestedSessionIdFromURL())
param="jsessionid="+_sessionId;
- // TODO else
+ // TODO else
// _fields.add("Cookie","JSESSIONID="+_sessionId);
}
-
+
if (_conditional)
{
if (_etag!=null)
@@ -296,16 +245,17 @@ public class PushBuilderImpl implements PushBuilder
else if (_lastModified!=null)
_fields.add(HttpHeader.IF_MODIFIED_SINCE,_lastModified);
}
-
- HttpURI uri = HttpURI.createHttpURI(_request.getScheme(),_request.getServerName(),_request.getServerPort(),_path,param,query,null);
+
+ HttpURI uri = HttpURI.createHttpURI(_request.getScheme(),_request.getServerName(),_request.getServerPort(),path,param,query,null);
MetaData.Request push = new MetaData.Request(_method,uri,_request.getHttpVersion(),_fields);
-
+
if (LOG.isDebugEnabled())
LOG.debug("Push {} {} inm={} ims={}",_method,uri,_fields.get(HttpHeader.IF_NONE_MATCH),_fields.get(HttpHeader.IF_MODIFIED_SINCE));
-
+
_request.getHttpChannel().getHttpTransport().push(push);
_path=null;
_etag=null;
_lastModified=null;
}
+
}
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
index 1f21e63089..ca6fdf6fb9 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
@@ -126,12 +126,12 @@ public class Request implements HttpServletRequest
private static final Logger LOG = Log.getLogger(Request.class);
private static final Collection<Locale> __defaultLocale = Collections.singleton(Locale.getDefault());
private static final int __NONE = 0, _STREAM = 1, __READER = 2;
-
+
private static final MultiMap<String> NO_PARAMS = new MultiMap<>();
/* ------------------------------------------------------------ */
- /**
+ /**
* Obtain the base {@link Request} instance of a {@link ServletRequest}, by
* coercion, unwrapping or special attribute.
* @param request The request
@@ -141,31 +141,32 @@ public class Request implements HttpServletRequest
{
if (request instanceof Request)
return (Request)request;
-
+
Object channel = request.getAttribute(HttpChannel.class.getName());
if (channel instanceof HttpChannel)
return ((HttpChannel)channel).getRequest();
-
+
while (request instanceof ServletRequestWrapper)
request=((ServletRequestWrapper)request).getRequest();
if (request instanceof Request)
return (Request)request;
-
+
return null;
}
-
-
+
+
private final HttpChannel _channel;
private final List<ServletRequestAttributeListener> _requestAttributeListeners=new ArrayList<>();
private final HttpInput _input;
private MetaData.Request _metadata;
+ private String _originalURI;
private String _contextPath;
private String _servletPath;
private String _pathInfo;
-
+
private boolean _secure;
private boolean _asyncSupported = true;
private boolean _newContext;
@@ -195,7 +196,7 @@ public class Request implements HttpServletRequest
private long _timeStamp;
private MultiPartInputStreamParser _multiPartInputStream; //if the request is a multi-part mime
private AsyncContextState _async;
-
+
/* ------------------------------------------------------------ */
public Request(HttpChannel channel, HttpInput input)
{
@@ -226,7 +227,7 @@ public class Request implements HttpServletRequest
{
return getHttpChannel().getHttpTransport().isPushSupported();
}
-
+
/* ------------------------------------------------------------ */
/** Get a PushBuilder associated with this request initialized as follows:<ul>
* <li>The method is initialized to "GET"</li>
@@ -237,18 +238,18 @@ public class Request implements HttpServletRequest
* <li>Authorization headers
* <li>Referrer headers
* </ul></li>
- * <li>If the request was Authenticated, an Authorization header will
+ * <li>If the request was Authenticated, an Authorization header will
* be set with a container generated token that will result in equivalent
* Authorization</li>
* <li>The query string from {@link #getQueryString()}
* <li>The {@link #getRequestedSessionId()} value, unless at the time
* of the call {@link #getSession(boolean)}
- * has previously been called to create a new {@link HttpSession}, in
- * which case the new session ID will be used as the PushBuilders
+ * has previously been called to create a new {@link HttpSession}, in
+ * which case the new session ID will be used as the PushBuilders
* requested session ID.</li>
* <li>The source of the requested session id will be the same as for
* this request</li>
- * <li>The builders Referer header will be set to {@link #getRequestURL()}
+ * <li>The builders Referer header will be set to {@link #getRequestURL()}
* plus any {@link #getQueryString()} </li>
* <li>If {@link HttpServletResponse#addCookie(Cookie)} has been called
* on the associated response, then a corresponding Cookie header will be added
@@ -256,9 +257,9 @@ public class Request implements HttpServletRequest
* case the Cookie will be removed from the builder.</li>
* <li>If this request has has the conditional headers If-Modified-Since or
* If-None-Match then the {@link PushBuilderImpl#isConditional()} header is set
- * to true.
+ * to true.
* </ul>
- *
+ *
* <p>Each call to getPushBuilder() will return a new instance
* of a PushBuilder based off this Request. Any mutations to the
* returned PushBuilder are not reflected on future returns.
@@ -268,12 +269,12 @@ public class Request implements HttpServletRequest
{
if (!isPushSupported())
throw new IllegalStateException();
-
+
HttpFields fields = new HttpFields(getHttpFields().size()+5);
boolean conditional=false;
UserIdentity user_identity=null;
Authentication authentication=null;
-
+
for (HttpField field : getHttpFields())
{
HttpHeader header = field.getHeader();
@@ -291,7 +292,7 @@ public class Request implements HttpServletRequest
case REFERER:
case COOKIE:
continue;
-
+
case AUTHORIZATION:
user_identity=getUserIdentity();
authentication=_authentication;
@@ -301,13 +302,13 @@ public class Request implements HttpServletRequest
case IF_MODIFIED_SINCE:
conditional=true;
continue;
-
+
default:
fields.add(field);
}
}
}
-
+
String id=null;
try
{
@@ -324,13 +325,13 @@ public class Request implements HttpServletRequest
{
id=getRequestedSessionId();
}
-
+
PushBuilder builder = new PushBuilderImpl(this,fields,getMethod(),getQueryString(),id,conditional);
builder.addHeader("referer",getRequestURL().toString());
-
+
// TODO process any set cookies
// TODO process any user_identity
-
+
return builder;
}
@@ -418,7 +419,7 @@ public class Request implements HttpServletRequest
}
}
}
-
+
}
/* ------------------------------------------------------------ */
@@ -508,7 +509,7 @@ public class Request implements HttpServletRequest
HttpChannelState state = getHttpChannelState();
if (_async==null || !state.isAsyncStarted())
throw new IllegalStateException(state.getStatusString());
-
+
return _async;
}
@@ -647,7 +648,7 @@ public class Request implements HttpServletRequest
{
return _input.getContentConsumed();
}
-
+
/* ------------------------------------------------------------ */
/*
* @see javax.servlet.ServletRequest#getContentType()
@@ -696,7 +697,7 @@ public class Request implements HttpServletRequest
{
if (_cookies == null || _cookies.getCookies().length == 0)
return null;
-
+
return _cookies.getCookies();
}
@@ -720,7 +721,7 @@ public class Request implements HttpServletRequest
//Javadoc for Request.getCookies() stipulates null for no cookies
if (_cookies == null || _cookies.getCookies().length == 0)
return null;
-
+
return _cookies.getCookies();
}
@@ -824,7 +825,7 @@ public class Request implements HttpServletRequest
{
if (_metadata==null)
return Locale.getDefault();
-
+
Enumeration<String> enm = _metadata.getFields().getValues(HttpHeader.ACCEPT_LANGUAGE.toString(),HttpFields.__separators);
// handle no locale
@@ -864,7 +865,7 @@ public class Request implements HttpServletRequest
{
if (_metadata==null)
return Collections.enumeration(__defaultLocale);
-
+
Enumeration<String> enm = _metadata.getFields().getValues(HttpHeader.ACCEPT_LANGUAGE.toString(),HttpFields.__separators);
// handle no locale
@@ -920,7 +921,7 @@ public class Request implements HttpServletRequest
LOG.ignore(e);
}
}
-
+
InetSocketAddress local=_channel.getLocalAddress();
if (local==null)
return "";
@@ -937,22 +938,25 @@ public class Request implements HttpServletRequest
@Override
public String getLocalName()
{
- if (_channel==null)
+ if (_channel!=null)
{
- try
- {
- String name =InetAddress.getLocalHost().getHostName();
- if (StringUtil.ALL_INTERFACES.equals(name))
- return null;
- return name;
- }
- catch (java.net.UnknownHostException e)
- {
- LOG.ignore(e);
- }
+ InetSocketAddress local=_channel.getLocalAddress();
+ if (local!=null)
+ return local.getHostString();
}
- InetSocketAddress local=_channel.getLocalAddress();
- return local.getHostString();
+
+ try
+ {
+ String name =InetAddress.getLocalHost().getHostName();
+ if (StringUtil.ALL_INTERFACES.equals(name))
+ return null;
+ return name;
+ }
+ catch (java.net.UnknownHostException e)
+ {
+ LOG.ignore(e);
+ }
+ return null;
}
/* ------------------------------------------------------------ */
@@ -965,7 +969,7 @@ public class Request implements HttpServletRequest
if (_channel==null)
return 0;
InetSocketAddress local=_channel.getLocalAddress();
- return local.getPort();
+ return local==null?0:local.getPort();
}
/* ------------------------------------------------------------ */
@@ -975,7 +979,7 @@ public class Request implements HttpServletRequest
@Override
public String getMethod()
{
- return _metadata.getMethod();
+ return _metadata==null?null:_metadata.getMethod();
}
/* ------------------------------------------------------------ */
@@ -1042,7 +1046,7 @@ public class Request implements HttpServletRequest
{
if (_queryParameters == null)
extractQueryParameters();
-
+
if (_queryParameters==NO_PARAMS || _queryParameters.size()==0)
_parameters=_contentParameters;
else if (_contentParameters==NO_PARAMS || _contentParameters.size()==0)
@@ -1190,7 +1194,7 @@ public class Request implements HttpServletRequest
/* ------------------------------------------------------------ */
/**
* Access the underlying Remote {@link InetSocketAddress} for this request.
- *
+ *
* @return the remote {@link InetSocketAddress} for this request, or null if the request has no remote (see {@link ServletRequest#getRemoteAddr()} for
* conditions that result in no remote address)
*/
@@ -1213,14 +1217,14 @@ public class Request implements HttpServletRequest
InetSocketAddress remote=_remote;
if (remote==null)
remote=_channel.getRemoteAddress();
-
+
if (remote==null)
return "";
-
+
InetAddress address = remote.getAddress();
if (address==null)
return remote.getHostString();
-
+
return address.getHostAddress();
}
@@ -1270,6 +1274,8 @@ public class Request implements HttpServletRequest
@Override
public RequestDispatcher getRequestDispatcher(String path)
{
+ path = URIUtil.compactPath(path);
+
if (path == null || _context == null)
return null;
@@ -1353,7 +1359,7 @@ public class Request implements HttpServletRequest
@Override
public String getScheme()
{
- String scheme=_metadata.getURI().getScheme();
+ String scheme=_metadata==null?null:_metadata.getURI().getScheme();
return scheme==null?HttpScheme.HTTP.asString():scheme;
}
@@ -1365,7 +1371,7 @@ public class Request implements HttpServletRequest
public String getServerName()
{
String name = _metadata.getURI().getHost();
-
+
// Return already determined host
if (name != null)
return name;
@@ -1414,7 +1420,7 @@ public class Request implements HttpServletRequest
{
HttpURI uri = _metadata.getURI();
int port = (uri.getHost()==null)?findServerPort():uri.getPort();
-
+
// If no port specified, return the default port for the scheme
if (port <= 0)
{
@@ -1422,7 +1428,7 @@ public class Request implements HttpServletRequest
return 443;
return 80;
}
-
+
// return a specific port
return port;
}
@@ -1445,10 +1451,10 @@ public class Request implements HttpServletRequest
// Return host from connection
if (_channel != null)
return getLocalPort();
-
+
return -1;
}
-
+
/* ------------------------------------------------------------ */
@Override
public ServletContext getServletContext()
@@ -1534,7 +1540,7 @@ public class Request implements HttpServletRequest
if (!create)
return null;
-
+
if (getResponse().isCommitted())
throw new IllegalStateException("Response is committed");
@@ -1580,6 +1586,14 @@ public class Request implements HttpServletRequest
/* ------------------------------------------------------------ */
/**
+ * @return Returns the original uri passed in metadata before customization/rewrite
+ */
+ public String getOriginalURI()
+ {
+ return _originalURI;
+ }
+ /* ------------------------------------------------------------ */
+ /**
* @param uri the URI to set
*/
public void setHttpURI(HttpURI uri)
@@ -1631,7 +1645,7 @@ public class Request implements HttpServletRequest
UserIdentity user = ((Authentication.User)_authentication).getUserIdentity();
return user.getUserPrincipal();
}
-
+
return null;
}
@@ -1739,7 +1753,6 @@ public class Request implements HttpServletRequest
return _savedNewSessions.get(key);
}
-
/* ------------------------------------------------------------ */
/**
* @param request the Request metadata
@@ -1747,9 +1760,10 @@ public class Request implements HttpServletRequest
public void setMetaData(org.eclipse.jetty.http.MetaData.Request request)
{
_metadata=request;
+ _originalURI=_metadata.getURIString();
setMethod(request.getMethod());
HttpURI uri = request.getURI();
-
+
String path = uri.getDecodedPath();
String info;
if (path==null || path.length()==0)
@@ -1777,30 +1791,37 @@ public class Request implements HttpServletRequest
}
else
info = URIUtil.canonicalPath(path);// TODO should this be done prior to decoding???
-
+
if (info == null)
{
setPathInfo(path);
throw new BadMessageException(400,"Bad URI");
}
-
+
setPathInfo(info);
}
/* ------------------------------------------------------------ */
+ public org.eclipse.jetty.http.MetaData.Request getMetaData()
+ {
+ return _metadata;
+ }
+
+ /* ------------------------------------------------------------ */
public boolean hasMetaData()
{
return _metadata!=null;
}
-
+
/* ------------------------------------------------------------ */
protected void recycle()
{
_metadata=null;
-
+ _originalURI=null;
+
if (_context != null)
throw new IllegalStateException("Request in context!");
-
+
if (_inputState == __READER)
{
try
@@ -1913,7 +1934,7 @@ public class Request implements HttpServletRequest
setQueryEncoding(value == null?null:value.toString());
else if ("org.eclipse.jetty.server.sendContent".equals(name))
LOG.warn("Deprecated: org.eclipse.jetty.server.sendContent");
-
+
if (_attributes == null)
_attributes = new AttributesMap();
_attributes.setAttribute(name,value);
@@ -2092,7 +2113,7 @@ public class Request implements HttpServletRequest
*
* The request attribute "org.eclipse.jetty.server.server.Request.queryEncoding" may be set as an alternate method of calling setQueryEncoding.
*
- * @param queryEncoding the URI query character encoding
+ * @param queryEncoding the URI query character encoding
*/
public void setQueryEncoding(String queryEncoding)
{
@@ -2119,7 +2140,7 @@ public class Request implements HttpServletRequest
{
_remote = addr;
}
-
+
/* ------------------------------------------------------------ */
/**
* @param requestedSessionId
@@ -2165,7 +2186,7 @@ public class Request implements HttpServletRequest
*/
public void setAuthority(String host,int port)
{
- _metadata.getURI().setAuthority(host,port);;
+ _metadata.getURI().setAuthority(host,port);
}
/* ------------------------------------------------------------ */
@@ -2244,7 +2265,13 @@ public class Request implements HttpServletRequest
@Override
public String toString()
{
- return (_handled?"[":"(") + getMethod() + " " + _metadata.getURI() + (_handled?"]@":")@") + hashCode() + " " + super.toString();
+ return String.format("%s%s%s %s%s@%x",
+ getClass().getSimpleName(),
+ _handled ? "[" : "(",
+ getMethod(),
+ getHttpURI(),
+ _handled ? "]" : ")",
+ hashCode());
}
/* ------------------------------------------------------------ */
@@ -2286,14 +2313,14 @@ public class Request implements HttpServletRequest
if (_multiPartInputStream == null)
{
MultipartConfigElement config = (MultipartConfigElement)getAttribute(__MULTIPART_CONFIG_ELEMENT);
-
+
if (config == null)
throw new IllegalStateException("No multipart config for servlet");
-
+
_multiPartInputStream = new MultiPartInputStreamParser(getInputStream(),
- getContentType(), config,
+ getContentType(), config,
(_context != null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null));
-
+
setAttribute(__MULTIPART_INPUT_STREAM, _multiPartInputStream);
setAttribute(__MULTIPART_CONTEXT, _context);
Collection<Part> parts = _multiPartInputStream.getParts(); //causes parsing
@@ -2315,7 +2342,7 @@ public class Request implements HttpServletRequest
IO.copy(is, os);
String content=new String(os.toByteArray(),charset==null?StandardCharsets.UTF_8:Charset.forName(charset));
if (_contentParameters == null)
- _contentParameters = params == null ? new MultiMap<String>() : params;
+ _contentParameters = params == null ? new MultiMap<>() : params;
_contentParameters.add(mp.getName(), content);
}
os.reset();
@@ -2355,7 +2382,7 @@ public class Request implements HttpServletRequest
public void mergeQueryParameters(String oldQuery,String newQuery, boolean updateQueryString)
{
// TODO This is seriously ugly
-
+
MultiMap<String> newQueryParams = null;
// Have to assume ENCODING because we can't know otherwise.
if (newQuery!=null)
@@ -2388,24 +2415,32 @@ public class Request implements HttpServletRequest
if (updateQueryString)
{
- // Build the new merged query string, parameters in the
- // new query string hide parameters in the old query string.
- StringBuilder mergedQuery = new StringBuilder();
- if (newQuery!=null)
- mergedQuery.append(newQuery);
- for (Map.Entry<String, List<String>> entry : mergedQueryParams.entrySet())
+ if (newQuery==null)
+ setQueryString(oldQuery);
+ else if (oldQuery==null)
+ setQueryString(newQuery);
+ else
{
- if (newQueryParams!=null && newQueryParams.containsKey(entry.getKey()))
- continue;
- for (String value : entry.getValue())
+ // Build the new merged query string, parameters in the
+ // new query string hide parameters in the old query string.
+ StringBuilder mergedQuery = new StringBuilder();
+ if (newQuery!=null)
+ mergedQuery.append(newQuery);
+ for (Map.Entry<String, List<String>> entry : mergedQueryParams.entrySet())
{
- if (mergedQuery.length()>0)
- mergedQuery.append("&");
- mergedQuery.append(entry.getKey()).append("=").append(value);
+ if (newQueryParams!=null && newQueryParams.containsKey(entry.getKey()))
+ continue;
+ for (String value : entry.getValue())
+ {
+ if (mergedQuery.length()>0)
+ mergedQuery.append("&");
+ URIUtil.encodePath(mergedQuery,entry.getKey());
+ mergedQuery.append('=');
+ URIUtil.encodePath(mergedQuery,value);
+ }
}
+ setQueryString(mergedQuery.toString());
}
-
- setQueryString(mergedQuery.toString());
}
}
@@ -2415,25 +2450,6 @@ public class Request implements HttpServletRequest
@Override
public <T extends HttpUpgradeHandler> T upgrade(Class<T> handlerClass) throws IOException, ServletException
{
- if (getContext() == null)
- throw new ServletException ("Unable to instantiate "+handlerClass);
-
- try
- {
- //Instantiate an instance and inject it
- T h = getContext().createInstance(handlerClass);
-
- //TODO handle the rest of the upgrade process
-
- return h;
- }
- catch (Exception e)
- {
- if (e instanceof ServletException)
- throw (ServletException)e;
- throw new ServletException(e);
- }
+ throw new ServletException("HttpServletRequest.upgrade() not supported in Jetty");
}
-
-
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/RequestLogCollection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/RequestLogCollection.java
index 2359c32616..b6cfa1cea3 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/RequestLogCollection.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/RequestLogCollection.java
@@ -18,10 +18,10 @@
package org.eclipse.jetty.server;
-import java.util.ArrayList;
-
import static java.util.Arrays.asList;
+import java.util.ArrayList;
+
class RequestLogCollection
implements RequestLog
{
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
index b9e73bdd3f..a209bf109f 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java
@@ -32,6 +32,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.http.DateGenerator;
+import org.eclipse.jetty.http.GzipHttpContent;
import org.eclipse.jetty.http.HttpContent;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
@@ -45,8 +46,8 @@ import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
-
-public class ResourceCache
+// TODO rename to ContentCache
+public class ResourceCache implements HttpContent.Factory
{
private static final Logger LOG = Log.getLogger(ResourceCache.class);
@@ -56,7 +57,8 @@ public class ResourceCache
private final ResourceFactory _factory;
private final ResourceCache _parent;
private final MimeTypes _mimeTypes;
- private final boolean _etagSupported;
+ private final boolean _etags;
+ private final boolean _gzip;
private final boolean _useFileMappedBuffer;
private int _maxCachedFileSize =128*1024*1024;
@@ -70,8 +72,9 @@ public class ResourceCache
* @param mimeTypes Mimetype to use for meta data
* @param useFileMappedBuffer true to file memory mapped buffers
* @param etags true to support etags
+ * @param gzip true to support gzip
*/
- public ResourceCache(ResourceCache parent, ResourceFactory factory, MimeTypes mimeTypes,boolean useFileMappedBuffer,boolean etags)
+ public ResourceCache(ResourceCache parent, ResourceFactory factory, MimeTypes mimeTypes,boolean useFileMappedBuffer,boolean etags,boolean gzip)
{
_factory = factory;
_cache=new ConcurrentHashMap<String,CachedHttpContent>();
@@ -80,7 +83,8 @@ public class ResourceCache
_mimeTypes=mimeTypes;
_parent=parent;
_useFileMappedBuffer=useFileMappedBuffer;
- _etagSupported=etags;
+ _etags=etags;
+ _gzip=gzip;
}
/* ------------------------------------------------------------ */
@@ -164,6 +168,14 @@ public class ResourceCache
}
/* ------------------------------------------------------------ */
+ @Deprecated
+ public HttpContent lookup(String pathInContext)
+ throws IOException
+ {
+ return getContent(pathInContext);
+ }
+
+ /* ------------------------------------------------------------ */
/** Get a Entry from the cache.
* Get either a valid entry object or create a new one if possible.
*
@@ -174,7 +186,8 @@ public class ResourceCache
* the resource does not exist, then null is returned.
* @throws IOException Problem loading the resource
*/
- public HttpContent lookup(String pathInContext)
+ @Override
+ public HttpContent getContent(String pathInContext)
throws IOException
{
// Is the content in this cache?
@@ -206,6 +219,9 @@ public class ResourceCache
*/
protected boolean isCacheable(Resource resource)
{
+ if (_maxCachedFiles<=0)
+ return false;
+
long len = resource.length();
// Will it fit in the cache?
@@ -216,16 +232,43 @@ public class ResourceCache
private HttpContent load(String pathInContext, Resource resource)
throws IOException
{
- CachedHttpContent content=null;
if (resource==null || !resource.exists())
return null;
+ if (resource.isDirectory())
+ return new ResourceHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),getMaxCachedFileSize());
+
// Will it fit in the cache?
- if (!resource.isDirectory() && isCacheable(resource))
+ if (isCacheable(resource))
{
- // Create the Content (to increment the cache sizes before adding the content
- content = new CachedHttpContent(pathInContext,resource);
+ CachedHttpContent content=null;
+
+ // Look for a gzip resource
+ if (_gzip)
+ {
+ String pathInContextGz=pathInContext+".gz";
+ CachedHttpContent contentGz = _cache.get(pathInContextGz);
+ if (contentGz==null || !contentGz.isValid())
+ {
+ contentGz=null;
+ Resource resourceGz=_factory.getResource(pathInContextGz);
+ if (resourceGz.exists() && resourceGz.lastModified()>=resource.lastModified() && resourceGz.length()<resource.length())
+ {
+ contentGz = new CachedHttpContent(pathInContextGz,resourceGz,null);
+ shrinkCache();
+ CachedHttpContent added = _cache.putIfAbsent(pathInContextGz,contentGz);
+ if (added!=null)
+ {
+ contentGz.invalidate();
+ contentGz=added;
+ }
+ }
+ }
+ content = new CachedHttpContent(pathInContext,resource,contentGz);
+ }
+ else
+ content = new CachedHttpContent(pathInContext,resource,null);
// reduce the cache to an acceptable size.
shrinkCache();
@@ -237,12 +280,28 @@ public class ResourceCache
content.invalidate();
content=added;
}
-
+
return content;
}
- return new ResourceHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),getMaxCachedFileSize(),_etagSupported);
+ // Look for a gzip resource or content
+ String mt = _mimeTypes.getMimeByExtension(pathInContext);
+ if (_gzip)
+ {
+ // Is the gzip content cached?
+ String pathInContextGz=pathInContext+".gz";
+ CachedHttpContent contentGz = _cache.get(pathInContextGz);
+ if (contentGz!=null && contentGz.isValid() && contentGz.getResource().lastModified()>=resource.lastModified())
+ return new ResourceHttpContent(resource,mt,getMaxCachedFileSize(),contentGz);
+
+ // Is there a gzip resource?
+ Resource resourceGz=_factory.getResource(pathInContextGz);
+ if (resourceGz.exists() && resourceGz.lastModified()>=resource.lastModified() && resourceGz.length()<resource.length())
+ return new ResourceHttpContent(resource,mt,getMaxCachedFileSize(),
+ new ResourceHttpContent(resourceGz,_mimeTypes.getMimeByExtension(pathInContextGz),getMaxCachedFileSize()));
+ }
+ return new ResourceHttpContent(resource,mt,getMaxCachedFileSize());
}
/* ------------------------------------------------------------ */
@@ -337,13 +396,14 @@ public class ResourceCache
final HttpField _lastModified;
final long _lastModifiedValue;
final HttpField _etag;
+ final CachedGzipHttpContent _gzipped;
volatile long _lastAccessed;
AtomicReference<ByteBuffer> _indirectBuffer=new AtomicReference<ByteBuffer>();
AtomicReference<ByteBuffer> _directBuffer=new AtomicReference<ByteBuffer>();
/* ------------------------------------------------------------ */
- CachedHttpContent(String pathInContext,Resource resource)
+ CachedHttpContent(String pathInContext,Resource resource,CachedHttpContent gzipped)
{
_key=pathInContext;
_resource=resource;
@@ -365,9 +425,11 @@ public class ResourceCache
_cachedFiles.incrementAndGet();
_lastAccessed=System.currentTimeMillis();
- _etag=ResourceCache.this._etagSupported?new PreEncodedHttpField(HttpHeader.ETAG,resource.getWeakETag()):null;
+ _etag=ResourceCache.this._etags?new PreEncodedHttpField(HttpHeader.ETAG,resource.getWeakETag()):null;
+
+ _gzipped=gzipped==null?null:new CachedGzipHttpContent(this,gzipped);
}
-
+
/* ------------------------------------------------------------ */
public String getKey()
@@ -428,7 +490,7 @@ public class ResourceCache
// Invalidate it
_cachedSize.addAndGet(-_contentLengthValue);
_cachedFiles.decrementAndGet();
- _resource.close();
+ _resource.close();
}
/* ------------------------------------------------------------ */
@@ -459,6 +521,20 @@ public class ResourceCache
{
return _contentType==null?null:_contentType.getValue();
}
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public HttpField getContentEncoding()
+ {
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getContentEncodingValue()
+ {
+ return null;
+ }
/* ------------------------------------------------------------ */
@Override
@@ -479,7 +555,6 @@ public class ResourceCache
@Override
public void release()
{
- // don't release while cached. Release when invalidated.
}
/* ------------------------------------------------------------ */
@@ -557,12 +632,65 @@ public class ResourceCache
return _resource.getReadableByteChannel();
}
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return String.format("CachedContent@%x{r=%s,e=%b,lm=%s,ct=%s,gz=%b}",hashCode(),_resource,_resource.exists(),_lastModified,_contentType,_gzipped!=null);
+ }
/* ------------------------------------------------------------ */
@Override
+ public HttpContent getGzipContent()
+ {
+ return (_gzipped!=null && _gzipped.isValid())?_gzipped:null;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public class CachedGzipHttpContent extends GzipHttpContent
+ {
+ private final CachedHttpContent _content;
+ private final CachedHttpContent _contentGz;
+ private final HttpField _etag;
+
+ CachedGzipHttpContent(CachedHttpContent content, CachedHttpContent contentGz)
+ {
+ super(content,contentGz);
+ _content=content;
+ _contentGz=contentGz;
+
+ _etag=(ResourceCache.this._etags)?new PreEncodedHttpField(HttpHeader.ETAG,_content.getResource().getWeakETag("--gzip")):null;
+ }
+
+ public boolean isValid()
+ {
+ return _contentGz.isValid() && _content.isValid() && _content.getResource().lastModified() <= _contentGz.getResource().lastModified();
+ }
+
+ @Override
+ public HttpField getETag()
+ {
+ if (_etag!=null)
+ return _etag;
+ return super.getETag();
+ }
+
+ @Override
+ public String getETagValue()
+ {
+ if (_etag!=null)
+ return _etag.getValue();
+ return super.getETagValue();
+ }
+
+ @Override
public String toString()
{
- return String.format("CachedContent@%x{r=%s,e=%b,lm=%s,ct=%s}",hashCode(),_resource,_resource.exists(),_lastModified,_contentType);
- }
+ return "Cached"+super.toString();
+ }
}
+
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceContentFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceContentFactory.java
new file mode 100644
index 0000000000..2e0edde673
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceContentFactory.java
@@ -0,0 +1,104 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 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.http.HttpContent;
+import org.eclipse.jetty.http.HttpContent.Factory;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.http.ResourceHttpContent;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.resource.ResourceFactory;
+
+public class ResourceContentFactory implements Factory
+{
+ private final ResourceFactory _factory;
+ private final MimeTypes _mimeTypes;
+ private final int _maxBufferSize;
+ private final boolean _gzip;
+
+
+ /* ------------------------------------------------------------ */
+ public ResourceContentFactory(ResourceFactory factory, MimeTypes mimeTypes, int maxBufferSize, boolean gzip)
+ {
+ _factory=factory;
+ _mimeTypes=mimeTypes;
+ _maxBufferSize=maxBufferSize;
+ _gzip=gzip;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** 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
+ * @return The entry matching <code>pathInContext</code>, or a new entry
+ * if no matching entry was found. If the content exists but is not cachable,
+ * then a {@link ResourceHttpContent} instance is return. If
+ * the resource does not exist, then null is returned.
+ * @throws IOException Problem loading the resource
+ */
+ @Override
+ public HttpContent getContent(String pathInContext)
+ throws IOException
+ {
+
+ // try loading the content from our factory.
+ Resource resource=_factory.getResource(pathInContext);
+ HttpContent loaded = load(pathInContext,resource);
+ return loaded;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ private HttpContent load(String pathInContext, Resource resource)
+ throws IOException
+ {
+ if (resource==null || !resource.exists())
+ return null;
+
+ if (resource.isDirectory())
+ return new ResourceHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),_maxBufferSize);
+
+ // Look for a gzip resource or content
+ String mt = _mimeTypes.getMimeByExtension(pathInContext);
+ if (_gzip)
+ {
+ // Is there a gzip resource?
+ String pathInContextGz=pathInContext+".gz";
+ Resource resourceGz=_factory.getResource(pathInContextGz);
+ if (resourceGz.exists() && resourceGz.lastModified()>=resource.lastModified() && resourceGz.length()<resource.length())
+ return new ResourceHttpContent(resource,mt,_maxBufferSize,
+ new ResourceHttpContent(resourceGz,_mimeTypes.getMimeByExtension(pathInContextGz),_maxBufferSize));
+ }
+
+ return new ResourceHttpContent(resource,mt,_maxBufferSize);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return "ResourceContentFactory["+_factory+"]@"+hashCode();
+ }
+
+
+}
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
index 86d5341e51..2f5382bc4e 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java
@@ -51,9 +51,8 @@ import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.PreEncodedHttpField;
import org.eclipse.jetty.io.RuntimeIOException;
+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.Jetty;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
@@ -65,12 +64,12 @@ import org.eclipse.jetty.util.log.Logger;
*/
public class Response implements HttpServletResponse
{
- private static final Logger LOG = Log.getLogger(Response.class);
+ private static final Logger LOG = Log.getLogger(Response.class);
private static final String __COOKIE_DELIM="\",;\\ \t";
private final static String __01Jan1970_COOKIE = DateGenerator.formatCookieDate(0).trim();
private final static int __MIN_BUFFER_SIZE = 1;
private final static HttpField __EXPIRES_01JAN1970 = new PreEncodedHttpField(HttpHeader.EXPIRES,DateGenerator.__01Jan1970);
-
+
// Cookie building buffer. Reduce garbage for cookie using applications
private static final ThreadLocal<StringBuilder> __cookieBuilder = new ThreadLocal<StringBuilder>()
@@ -81,7 +80,7 @@ public class Response implements HttpServletResponse
return new StringBuilder(128);
}
};
-
+
public enum OutputType
{
NONE, STREAM, WRITER
@@ -114,7 +113,7 @@ public class Response implements HttpServletResponse
private OutputType _outputType = OutputType.NONE;
private ResponseWriter _writer;
private long _contentLength = -1;
-
+
public Response(HttpChannel channel, HttpOutput out)
{
@@ -141,7 +140,7 @@ public class Response implements HttpServletResponse
_fields.clear();
_explicitEncoding=false;
}
-
+
public HttpOutput getHttpOutput()
{
return _out;
@@ -178,7 +177,7 @@ public class Response implements HttpServletResponse
cookie.getComment(),
cookie.isSecure(),
cookie.isHttpOnly(),
- cookie.getVersion());;
+ cookie.getVersion());
}
@Override
@@ -241,13 +240,13 @@ public class Response implements HttpServletResponse
// Format value and params
StringBuilder buf = __cookieBuilder.get();
buf.setLength(0);
-
+
// Name is checked for legality by servlet spec, but can also be passed directly so check again for quoting
boolean quote_name=isQuoteNeededForCookie(name);
quoteOnlyOrAppend(buf,name,quote_name);
-
+
buf.append('=');
-
+
// Remember name= part to look for other matching set-cookie
String name_equals=buf.toString();
@@ -260,7 +259,7 @@ public class Response implements HttpServletResponse
boolean quote_domain = has_domain && isQuoteNeededForCookie(domain);
boolean has_path = path!=null && path.length()>0;
boolean quote_path = has_path && isQuoteNeededForCookie(path);
-
+
// Upgrade the version if we have a comment or we need to quote value/path/domain or if they were already quoted
if (version==0 && ( comment!=null || quote_name || quote_value || quote_domain || quote_path ||
QuotedStringTokenizer.isQuoted(name) || QuotedStringTokenizer.isQuoted(value) ||
@@ -272,14 +271,14 @@ public class Response implements HttpServletResponse
buf.append (";Version=1");
else if (version>1)
buf.append (";Version=").append(version);
-
+
// Append path
if (has_path)
{
buf.append(";Path=");
quoteOnlyOrAppend(buf,path,quote_path);
}
-
+
// Append domain
if (has_domain)
{
@@ -297,7 +296,7 @@ public class Response implements HttpServletResponse
buf.append(__01Jan1970_COOKIE);
else
DateGenerator.formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge);
-
+
// for v1 cookies, also send max-age
if (version>=1)
{
@@ -336,7 +335,7 @@ public class Response implements HttpServletResponse
}
}
}
-
+
// add the set cookie
_fields.add(HttpHeader.SET_COOKIE.toString(), buf.toString());
@@ -355,7 +354,7 @@ public class Response implements HttpServletResponse
{
if (s==null || s.length()==0)
return true;
-
+
if (QuotedStringTokenizer.isQuoted(s))
return false;
@@ -364,15 +363,15 @@ public class Response implements HttpServletResponse
char c = s.charAt(i);
if (__COOKIE_DELIM.indexOf(c)>=0)
return true;
-
+
if (c<0x20 || c>=0x7f)
throw new IllegalArgumentException("Illegal character in cookie value");
}
return false;
}
-
-
+
+
private static void quoteOnlyOrAppend(StringBuilder buf, String s, boolean quote)
{
if (quote)
@@ -380,7 +379,7 @@ public class Response implements HttpServletResponse
else
buf.append(s);
}
-
+
@Override
public boolean containsHeader(String name)
{
@@ -404,7 +403,7 @@ public class Response implements HttpServletResponse
int port = uri.getPort();
if (port < 0)
port = HttpScheme.HTTPS.asString().equalsIgnoreCase(uri.getScheme()) ? 443 : 80;
-
+
// Is it the same server?
if (!request.getServerName().equalsIgnoreCase(uri.getHost()))
return url;
@@ -422,7 +421,7 @@ public class Response implements HttpServletResponse
return null;
// should not encode if cookies in evidence
- if ((sessionManager.isUsingCookies() && request.isRequestedSessionIdFromCookie()) || !sessionManager.isUsingURLs())
+ if ((sessionManager.isUsingCookies() && request.isRequestedSessionIdFromCookie()) || !sessionManager.isUsingURLs())
{
int prefix = url.indexOf(sessionURLPrefix);
if (prefix != -1)
@@ -524,7 +523,7 @@ public class Response implements HttpServletResponse
LOG.debug("Aborting on sendError on committed response {} {}",code,message);
code=-1;
}
-
+
switch(code)
{
case -1:
@@ -534,91 +533,44 @@ public class Response implements HttpServletResponse
sendProcessing();
return;
default:
+ break;
}
- if (isCommitted())
- LOG.warn("Committed before "+code+" "+message);
-
resetBuffer();
+ _mimeType=null;
_characterEncoding=null;
+ _outputType = OutputType.NONE;
setHeader(HttpHeader.EXPIRES,null);
setHeader(HttpHeader.LAST_MODIFIED,null);
setHeader(HttpHeader.CACHE_CONTROL,null);
setHeader(HttpHeader.CONTENT_TYPE,null);
- setHeader(HttpHeader.CONTENT_LENGTH,null);
+ setHeader(HttpHeader.CONTENT_LENGTH, null);
- _outputType = OutputType.NONE;
setStatus(code);
- _reason=message;
Request request = _channel.getRequest();
Throwable cause = (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
if (message==null)
- message=cause==null?HttpStatus.getMessage(code):cause.toString();
+ {
+ _reason=HttpStatus.getMessage(code);
+ message=cause==null?_reason:cause.toString();
+ }
+ else
+ _reason=message;
- // If we are allowed to have a body
- if (code!=SC_NO_CONTENT &&
- code!=SC_NOT_MODIFIED &&
- code!=SC_PARTIAL_CONTENT &&
- code>=SC_OK)
+ // If we are allowed to have a body, then produce the error page.
+ if (code != SC_NO_CONTENT && code != SC_NOT_MODIFIED &&
+ code != SC_PARTIAL_CONTENT && code >= SC_OK)
{
- ErrorHandler error_handler = ErrorHandler.getErrorHandler(_channel.getServer(),request.getContext()==null?null:request.getContext().getContextHandler());
- if (error_handler!=null)
- {
- 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,_channel.getRequest(),_channel.getRequest(),this );
- }
- else
- {
- setHeader(HttpHeader.CACHE_CONTROL, "must-revalidate,no-cache,no-store");
- setContentType(MimeTypes.Type.TEXT_HTML_8859_1.toString());
- try (ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(2048);)
- {
- message=StringUtil.sanitizeXmlString(message);
- String uri= request.getRequestURI();
- uri=StringUtil.sanitizeXmlString(uri);
-
- 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)
- 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 />");
-
- getHttpChannel().getHttpConfiguration().writePoweredBy(writer,null,"<hr/>");
- writer.write("\n</body>\n</html>\n");
-
- writer.flush();
- setContentLength(writer.size());
- try (ServletOutputStream outputStream = getOutputStream())
- {
- writer.writeTo(outputStream);
- writer.destroy();
- }
- }
- }
+ ContextHandler.Context context = request.getContext();
+ ContextHandler contextHandler = context == null ? _channel.getState().getContextHandler() : context.getContextHandler();
+ ErrorHandler error_handler = ErrorHandler.getErrorHandler(_channel.getServer(), contextHandler);
+ request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, 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, request, request, this);
}
- else if (code!=SC_PARTIAL_CONTENT)
- {
- // TODO work out why this is required?
- _channel.getRequest().getHttpFields().remove(HttpHeader.CONTENT_TYPE);
- _channel.getRequest().getHttpFields().remove(HttpHeader.CONTENT_LENGTH);
- _characterEncoding=null;
- _mimeType=null;
- }
-
- closeOutput();
}
/**
@@ -637,7 +589,7 @@ public class Response implements HttpServletResponse
_channel.sendResponse(HttpGenerator.PROGRESS_102_INFO, null, true);
}
}
-
+
/**
* Sends a response with one of the 300 series redirection codes.
* @param code the redirect status code
@@ -648,7 +600,7 @@ public class Response implements HttpServletResponse
{
if ((code < HttpServletResponse.SC_MULTIPLE_CHOICES) || (code >= HttpServletResponse.SC_BAD_REQUEST))
throw new IllegalArgumentException("Not a 3xx redirect code");
-
+
if (isIncluding())
return;
@@ -672,11 +624,11 @@ public class Response implements HttpServletResponse
if (!location.startsWith("/"))
buf.append('/');
}
-
+
if(location==null)
throw new IllegalStateException("path cannot be above root");
buf.append(location);
-
+
location=buf.toString();
}
@@ -791,13 +743,13 @@ public class Response implements HttpServletResponse
setContentType(value);
return;
}
-
+
if (HttpHeader.CONTENT_LENGTH.is(name))
{
setHeader(name,value);
return;
}
-
+
_fields.add(name, value);
}
@@ -822,7 +774,7 @@ public class Response implements HttpServletResponse
_contentLength = value;
}
}
-
+
@Override
public void setStatus(int sc)
{
@@ -841,7 +793,7 @@ public class Response implements HttpServletResponse
{
setStatusWithReason(sc,sm);
}
-
+
public void setStatusWithReason(int sc, String sm)
{
if (sc <= 0)
@@ -903,9 +855,9 @@ public class Response implements HttpServletResponse
setCharacterEncoding(encoding,false);
}
}
-
+
Locale locale = getLocale();
-
+
if (_writer != null && _writer.isFor(locale,encoding))
_writer.reopen();
else
@@ -917,7 +869,7 @@ public class Response implements HttpServletResponse
else
_writer = new ResponseWriter(new EncodingHttpWriter(_out, encoding),locale,encoding);
}
-
+
// Set the output type at the end, because setCharacterEncoding() checks for it
_outputType = OutputType.WRITER;
}
@@ -939,7 +891,7 @@ public class Response implements HttpServletResponse
long written = _out.getWritten();
if (written > len)
throw new IllegalArgumentException("setContentLength(" + len + ") when already written " + written);
-
+
_fields.putLongField(HttpHeader.CONTENT_LENGTH, len);
if (isAllContentWritten(written))
{
@@ -963,7 +915,7 @@ public class Response implements HttpServletResponse
else
_fields.remove(HttpHeader.CONTENT_LENGTH);
}
-
+
public long getContentLength()
{
return _contentLength;
@@ -1006,7 +958,7 @@ public class Response implements HttpServletResponse
_contentLength = len;
_fields.putLongField(HttpHeader.CONTENT_LENGTH.toString(), len);
}
-
+
@Override
public void setContentLengthLong(long length)
{
@@ -1018,7 +970,7 @@ public class Response implements HttpServletResponse
{
setCharacterEncoding(encoding,true);
}
-
+
private void setCharacterEncoding(String encoding, boolean explicit)
{
if (isIncluding() || isWriting())
@@ -1029,12 +981,12 @@ public class Response implements HttpServletResponse
if (encoding == null)
{
_explicitEncoding=false;
-
+
// Clear any encoding.
if (_characterEncoding != null)
{
_characterEncoding = null;
-
+
if (_mimeType!=null)
{
_mimeType=_mimeType.getBaseType();
@@ -1070,7 +1022,7 @@ public class Response implements HttpServletResponse
}
}
}
-
+
@Override
public void setContentType(String contentType)
{
@@ -1092,7 +1044,7 @@ public class Response implements HttpServletResponse
{
_contentType = contentType;
_mimeType = MimeTypes.CACHE.get(contentType);
-
+
String charset;
if (_mimeType!=null && _mimeType.getCharset()!=null && !_mimeType.isCharsetAssumed())
charset=_mimeType.getCharsetString();
@@ -1129,7 +1081,7 @@ public class Response implements HttpServletResponse
_fields.put(_mimeType.getContentTypeField());
}
}
-
+
}
@Override
@@ -1165,7 +1117,7 @@ public class Response implements HttpServletResponse
_fields.clear();
String connection = _channel.getRequest().getHeader(HttpHeader.CONNECTION.asString());
-
+
if (connection != null)
{
for (String value: StringUtil.csvSplit(null,connection,0,connection.length()))
@@ -1195,12 +1147,12 @@ public class Response implements HttpServletResponse
}
public void reset(boolean preserveCookies)
- {
+ {
if (!preserveCookies)
reset();
else
{
- ArrayList<String> cookieValues = new ArrayList<String>(5);
+ ArrayList<String> cookieValues = new ArrayList<>(5);
Enumeration<String> vals = _fields.getValues(HttpHeader.SET_COOKIE.asString());
while (vals.hasMoreElements())
cookieValues.add(vals.nextElement());
@@ -1229,11 +1181,11 @@ public class Response implements HttpServletResponse
{
return new MetaData.Response(_channel.getRequest().getHttpVersion(), getStatus(), getReason(), _fields, getLongContentLength());
}
-
+
/** Get the MetaData.Response committed for this response.
- * This may differ from the meta data in this response for
+ * This may differ from the meta data in this response for
* exceptional responses (eg 4xx and 5xx responses generated
- * by the container) and the committedMetaData should be used
+ * by the container) and the committedMetaData should be used
* for logging purposes.
* @return The committed MetaData or a {@link #newResponseMetaData()}
* if not yet committed.
@@ -1307,11 +1259,10 @@ public class Response implements HttpServletResponse
{
return String.format("%s %d %s%n%s", _channel.getRequest().getHttpVersion(), _status, _reason == null ? "" : _reason, _fields);
}
-
+
public void putHeaders(HttpContent content,long contentLength, boolean etag)
{
-
HttpField lm = content.getLastModified();
if (lm!=null)
_fields.put(lm);
@@ -1335,7 +1286,11 @@ public class Response implements HttpServletResponse
_characterEncoding=content.getCharacterEncoding();
_mimeType=content.getMimeType();
}
-
+
+ HttpField ce=content.getContentEncoding();
+ if (ce!=null)
+ _fields.put(ce);
+
if (etag)
{
HttpField et = content.getETag();
@@ -1343,9 +1298,9 @@ public class Response implements HttpServletResponse
_fields.put(et);
}
}
-
+
public static void putHeaders(HttpServletResponse response, HttpContent content, long contentLength, boolean etag)
- {
+ {
long lml=content.getResource().lastModified();
if (lml>=0)
response.setDateHeader(HttpHeader.LAST_MODIFIED.asString(),lml);
@@ -1362,8 +1317,12 @@ public class Response implements HttpServletResponse
String ct=content.getContentTypeValue();
if (ct!=null && response.getContentType()==null)
- response.setContentType(content.getContentTypeValue());
-
+ response.setContentType(ct);
+
+ String ce=content.getContentEncodingValue();
+ if (ce!=null)
+ response.setHeader(HttpHeader.CONTENT_ENCODING.asString(),ce);
+
if (etag)
{
String et=content.getETagValue();
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java
index 77d4d9206c..aefb547f2a 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java
@@ -27,6 +27,7 @@ import javax.servlet.ServletRequest;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.io.ssl.SslConnection.DecryptedEndPoint;
import org.eclipse.jetty.util.TypeUtil;
@@ -66,15 +67,26 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
@Override
public void customize(Connector connector, HttpConfiguration channelConfig, Request request)
{
- if (request.getHttpChannel().getEndPoint() instanceof DecryptedEndPoint)
+ EndPoint endp = request.getHttpChannel().getEndPoint();
+ if (endp instanceof DecryptedEndPoint)
{
- request.setScheme(HttpScheme.HTTPS.asString());
- request.setSecure(true);
- SslConnection.DecryptedEndPoint ssl_endp = (DecryptedEndPoint)request.getHttpChannel().getEndPoint();
+ SslConnection.DecryptedEndPoint ssl_endp = (DecryptedEndPoint)endp;
SslConnection sslConnection = ssl_endp.getSslConnection();
SSLEngine sslEngine=sslConnection.getSSLEngine();
customize(sslEngine,request);
+
+ if (request.getHttpURI().getScheme()==null)
+ request.setScheme(HttpScheme.HTTPS.asString());
+ }
+ else if (endp instanceof ProxyConnectionFactory.ProxyEndPoint)
+ {
+ ProxyConnectionFactory.ProxyEndPoint proxy = (ProxyConnectionFactory.ProxyEndPoint)endp;
+ if (request.getHttpURI().getScheme()==null && proxy.getAttribute(ProxyConnectionFactory.TLS_VERSION)!=null)
+ request.setScheme(HttpScheme.HTTPS.asString());
}
+
+ if (HttpScheme.HTTPS.is(request.getScheme()))
+ request.setSecure(true);
}
/**
@@ -101,7 +113,6 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
*/
public void customize(SSLEngine sslEngine, Request request)
{
- request.setScheme(HttpScheme.HTTPS.asString());
SSLSession sslSession = sslEngine.getSession();
if (_sniHostCheck)
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java
index 1665a19b16..4cd668dd95 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java
@@ -24,6 +24,7 @@ import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.nio.channels.Channel;
+import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
@@ -32,6 +33,7 @@ import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.ChannelEndPoint;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ManagedSelector;
@@ -229,7 +231,6 @@ public class ServerConnector extends AbstractNetworkConnector
_manager = newSelectorManager(getExecutor(), getScheduler(),
selectors>0?selectors:Math.max(1,Math.min(4,Runtime.getRuntime().availableProcessors()/2)));
addBean(_manager, true);
- setSelectorPriorityDelta(-1);
setAcceptorPriorityDelta(-2);
}
@@ -426,7 +427,7 @@ public class ServerConnector extends AbstractNetworkConnector
return _localPort;
}
- protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
+ protected ChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
{
return new SelectChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout());
}
@@ -493,19 +494,19 @@ public class ServerConnector extends AbstractNetworkConnector
}
@Override
- protected void accepted(SocketChannel channel) throws IOException
+ protected void accepted(SelectableChannel channel) throws IOException
{
- ServerConnector.this.accepted(channel);
+ ServerConnector.this.accepted((SocketChannel)channel);
}
@Override
- protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey selectionKey) throws IOException
+ protected ChannelEndPoint newEndPoint(SelectableChannel channel, ManagedSelector selectSet, SelectionKey selectionKey) throws IOException
{
- return ServerConnector.this.newEndPoint(channel, selectSet, selectionKey);
+ return ServerConnector.this.newEndPoint((SocketChannel)channel, selectSet, selectionKey);
}
@Override
- public Connection newConnection(SocketChannel channel, EndPoint endpoint, Object attachment) throws IOException
+ public Connection newConnection(SelectableChannel channel, EndPoint endpoint, Object attachment) throws IOException
{
return getDefaultConnectionFactory().newConnection(ServerConnector.this, endpoint);
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java
index 198e5c4c25..20fc7db1ea 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java
@@ -345,14 +345,12 @@ public class ShutdownMonitor
*/
private ShutdownMonitor()
{
- Properties props = System.getProperties();
-
- this.DEBUG = props.containsKey("DEBUG");
+ this.DEBUG = System.getProperty("DEBUG") != null;
// Use values passed thru via /jetty-start/
- this.host = props.getProperty("STOP.HOST","127.0.0.1");
- this.port = Integer.parseInt(props.getProperty("STOP.PORT","-1"));
- this.key = props.getProperty("STOP.KEY",null);
+ this.host = System.getProperty("STOP.HOST","127.0.0.1");
+ this.port = Integer.parseInt(System.getProperty("STOP.PORT","-1"));
+ this.key = System.getProperty("STOP.KEY",null);
this.exitVm = true;
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/SocketCustomizationListener.java b/jetty-server/src/main/java/org/eclipse/jetty/server/SocketCustomizationListener.java
index cdc402120c..9ac93c7a52 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/SocketCustomizationListener.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/SocketCustomizationListener.java
@@ -20,12 +20,12 @@ package org.eclipse.jetty.server;
import java.net.Socket;
-import org.eclipse.jetty.io.ChannelEndPoint;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.Connection.Listener;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.SocketChannelEndPoint;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.io.ssl.SslConnection.DecryptedEndPoint;
-import org.eclipse.jetty.io.EndPoint;
/* ------------------------------------------------------------ */
@@ -70,9 +70,9 @@ public class SocketCustomizationListener implements Listener
ssl=true;
}
- if (endp instanceof ChannelEndPoint)
+ if (endp instanceof SocketChannelEndPoint)
{
- Socket socket = ((ChannelEndPoint)endp).getSocket();
+ Socket socket = ((SocketChannelEndPoint)endp).getSocket();
customize(socket,connection.getClass(),ssl);
}
}
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
index d5fca99b09..7501d84def 100644
--- 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
@@ -21,7 +21,14 @@ package org.eclipse.jetty.server.handler;
import java.io.IOException;
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+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.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
@@ -47,6 +54,31 @@ public abstract class AbstractHandler extends ContainerLifeCycle implements Hand
{
}
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ if (baseRequest.getDispatcherType()==DispatcherType.ERROR)
+ doError(target,baseRequest,request,response);
+ else
+ doHandle(target,baseRequest,request,response);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void doError(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ Object o = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
+ int code = (o instanceof Integer)?((Integer)o).intValue():(o!=null?Integer.valueOf(o.toString()):500);
+ o = request.getAttribute(RequestDispatcher.ERROR_MESSAGE);
+ String reason = o!=null?o.toString():null;
+
+ response.sendError(code,reason);
+ }
+
/* ------------------------------------------------------------ */
/*
* @see org.eclipse.thread.LifeCycle#start()
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AllowSymLinkAliasChecker.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AllowSymLinkAliasChecker.java
index f6dad19058..8f9d14c143 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AllowSymLinkAliasChecker.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AllowSymLinkAliasChecker.java
@@ -69,25 +69,42 @@ public class AllowSymLinkAliasChecker implements AliasCheck
return true;
}
}
-
+
// No, so let's check each element ourselves
- Path d = path.getRoot();
- for (Path e:path)
+ boolean linked=true;
+ Path target=path;
+ int loops=0;
+ while (linked)
{
- d=d.resolve(e);
-
- while (Files.exists(d) && Files.isSymbolicLink(d))
+ if (++loops>100)
{
- Path link=Files.readSymbolicLink(d);
- if (!link.isAbsolute())
- link=d.resolve(link);
- d=link;
+ if (LOG.isDebugEnabled())
+ LOG.debug("Too many symlinks {} --> {}",resource,target);
+ return false;
}
+ linked=false;
+ Path d = target.getRoot();
+ for (Path e:target)
+ {
+ Path r=d.resolve(e);
+ d=r;
+
+ while (Files.exists(d) && Files.isSymbolicLink(d))
+ {
+ Path link=Files.readSymbolicLink(d);
+ if (!link.isAbsolute())
+ link=d.getParent().resolve(link);
+ d=link;
+ linked=true;
+ }
+ }
+ target=d;
}
- if (pathResource.getAliasPath().equals(d))
+
+ if (pathResource.getAliasPath().equals(target))
{
if (LOG.isDebugEnabled())
- LOG.debug("Allow path symlink {} --> {}",resource,d);
+ LOG.debug("Allow path symlink {} --> {}",resource,target);
return true;
}
}
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
index cbdde7fae5..668df07f96 100644
--- 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
@@ -102,7 +102,7 @@ import org.eclipse.jetty.util.resource.Resource;
* <p>
* This servers executore is made available via a context attributed "org.eclipse.jetty.server.Executor".
* <p>
- * By default, the context is created with alias checkers for {@link AllowSymLinkAliasChecker} (unix only) and {@link ApproveNonExistentDirectoryAliases}.
+ * By default, the context is created with alias checkers for {@link AllowSymLinkAliasChecker} (unix only) and {@link ApproveNonExistentDirectoryAliases}.
* If these alias checkers are not required, then {@link #clearAliasChecks()} or {@link #setAliasChecks(List)} should be called.
*/
@ManagedObject("URI Context")
@@ -126,7 +126,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
private static final ThreadLocal<Context> __context = new ThreadLocal<Context>();
private static String __serverInfo = "jetty/" + Server.getVersion();
-
+
/**
* If a context attribute with this name is set, it is interpreted as a comma separated list of attribute name. Any other context attributes that are set
* with a name from this list will result in a call to {@link #setManagedAttribute(String, Object)}, which typically initiates the creation of a JMX MBean
@@ -603,7 +603,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
if (listener instanceof ContextScopeListener)
_contextListeners.add((ContextScopeListener)listener);
-
+
if (listener instanceof ServletContextListener)
_servletContextListeners.add((ServletContextListener)listener);
@@ -809,7 +809,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
}
}
-
+
/* ------------------------------------------------------------ */
protected void stopContext () throws Exception
{
@@ -824,9 +824,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
callContextDestroyed(_servletContextListeners.get(i),event);
}
}
-
-
-
+
+
+
/* ------------------------------------------------------------ */
protected void callContextInitialized (ServletContextListener l, ServletContextEvent e)
{
@@ -853,6 +853,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
_availability = Availability.UNAVAILABLE;
ClassLoader old_classloader = null;
+ ClassLoader old_webapploader = null;
Thread current_thread = null;
Context old_context = __context.get();
@@ -862,6 +863,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
// Set the classloader
if (_classLoader != null)
{
+ old_webapploader = _classLoader;
current_thread = Thread.currentThread();
old_classloader = current_thread.getContextClassLoader();
current_thread.setContextClassLoader(_classLoader);
@@ -885,14 +887,14 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
LOG.info("Stopped {}", this);
__context.set(old_context);
// reset the classloader
- if (_classLoader != null && current_thread!=null)
+ if ((old_classloader == null || (old_classloader != old_webapploader)) && current_thread != null)
current_thread.setContextClassLoader(old_classloader);
}
_scontext.clearAttributes();
}
-
-
+
+
/* ------------------------------------------------------------ */
public boolean checkVirtualHost(final Request baseRequest)
{
@@ -933,8 +935,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
}
return true;
}
-
-
+
+
/* ------------------------------------------------------------ */
public boolean checkContextPath(String uri)
{
@@ -1076,8 +1078,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
}
if (old_context != _scontext)
- enterScope(baseRequest);
-
+ enterScope(baseRequest,dispatch);
+
if (LOG.isDebugEnabled())
LOG.debug("context={}|{}|{} @ {}",baseRequest.getContextPath(),baseRequest.getServletPath(), baseRequest.getPathInfo(),this);
@@ -1097,7 +1099,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
if (old_context != _scontext)
{
exitScope(baseRequest);
-
+
// reset the classloader
if (_classLoader != null && current_thread!=null)
{
@@ -1141,11 +1143,30 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
}
}
- if (DispatcherType.REQUEST.equals(dispatch) && isProtectedTarget(target))
+ switch(dispatch)
{
- response.sendError(HttpServletResponse.SC_NOT_FOUND);
- baseRequest.setHandled(true);
- return;
+ case REQUEST:
+ if (isProtectedTarget(target))
+ {
+ response.sendError(HttpServletResponse.SC_NOT_FOUND);
+ baseRequest.setHandled(true);
+ return;
+ }
+ break;
+
+ case ERROR:
+ // If this is already a dispatch to an error page, proceed normally
+ if (Boolean.TRUE.equals(baseRequest.getAttribute(Dispatcher.__ERROR_DISPATCH)))
+ break;
+
+ Object error = request.getAttribute(Dispatcher.ERROR_STATUS_CODE);
+ // We can just call sendError here. If there is no error page, then one will
+ // be generated. If there is an error page, then a RequestDispatcher will be
+ // used to route the request through appropriate filters etc.
+ response.sendError((error instanceof Integer)?((Integer)error).intValue():500);
+ return;
+ default:
+ break;
}
// start manual inline of nextHandle(target,baseRequest,request,response);
@@ -1179,13 +1200,14 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
}
}
-
-
+
+
/**
* @param request A request that is applicable to the scope, or null
+ * @param reason An object that indicates the reason the scope is being entered.
*/
- protected void enterScope(Request request)
+ protected void enterScope(Request request, Object reason)
{
if (!_contextListeners.isEmpty())
{
@@ -1193,7 +1215,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
{
try
{
- listener.enterScope(_scontext,request);
+ listener.enterScope(_scontext,request,reason);
}
catch(Throwable e)
{
@@ -1202,8 +1224,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
}
}
}
-
-
+
+
/**
* @param request A request that is applicable to the scope, or null
*/
@@ -1224,7 +1246,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
}
}
}
-
+
/* ------------------------------------------------------------ */
/**
* Handle a runnable in the scope of this context and a particular request
@@ -1235,10 +1257,18 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
{
ClassLoader old_classloader = null;
Thread current_thread = null;
- Context old_context = null;
+ Context old_context = __context.get();
+
+ // Are we already in the scope?
+ if (old_context==_scontext)
+ {
+ runnable.run();
+ return;
+ }
+
+ // Nope, so enter the scope and then exit
try
{
- old_context = __context.get();
__context.set(_scontext);
// Set the classloader
@@ -1249,21 +1279,21 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
current_thread.setContextClassLoader(_classLoader);
}
- enterScope(request);
+ enterScope(request,runnable);
runnable.run();
}
finally
{
exitScope(request);
-
+
__context.set(old_context);
- if (old_classloader != null && current_thread!=null)
+ if (old_classloader != null)
{
current_thread.setContextClassLoader(old_classloader);
}
}
}
-
+
/* ------------------------------------------------------------ */
/*
* Handle a runnable in the scope of this context
@@ -1473,10 +1503,10 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
}
/* ------------------------------------------------------------ */
- /**
+ /**
* Set the base resource for this context.
- * @param resourceBase A string representing the base resource for the context. Any string accepted
- * by {@link Resource#newResource(String)} may be passed and the call is equivalent to
+ * @param resourceBase A string representing the base resource for the context. Any string accepted
+ * by {@link Resource#newResource(String)} may be passed and the call is equivalent to
* <code>setBaseResource(newResource(resourceBase));</code>
*/
public void setResourceBase(String resourceBase)
@@ -1643,7 +1673,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
return null;
if (_classLoader == null)
- return Loader.loadClass(this.getClass(),className);
+ return Loader.loadClass(className);
return _classLoader.loadClass(className);
}
@@ -1685,9 +1715,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
}
/* ------------------------------------------------------------ */
- /**
+ /**
* Get all of the locale encodings
- *
+ *
* @return a map of all the locale encodings: key is name of the locale and value is the char encoding
*/
public Map<String,String> getLocaleEncodings()
@@ -2287,7 +2317,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
try
{
@SuppressWarnings({ "unchecked", "rawtypes" })
- Class<? extends EventListener> clazz = _classLoader==null?Loader.loadClass(ContextHandler.class,className):(Class)_classLoader.loadClass(className);
+ Class<? extends EventListener> clazz = _classLoader==null?Loader.loadClass(className):(Class)_classLoader.loadClass(className);
addListener(clazz);
}
catch (ClassNotFoundException e)
@@ -2380,7 +2410,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
//classloader, or a parent of it
try
{
- Class<?> reflect = Loader.loadClass(getClass(), "sun.reflect.Reflection");
+ Class<?> reflect = Loader.loadClass("sun.reflect.Reflection");
Method getCallerClass = reflect.getMethod("getCallerClass", Integer.TYPE);
Class<?> caller = (Class<?>)getCallerClass.invoke(null, 2);
@@ -2388,16 +2418,16 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
ClassLoader callerLoader = caller.getClassLoader();
while (!ok && callerLoader != null)
{
- if (callerLoader == _classLoader)
+ if (callerLoader == _classLoader)
ok = true;
else
- callerLoader = callerLoader.getParent();
+ callerLoader = callerLoader.getParent();
}
if (ok)
return _classLoader;
}
- catch (Exception e)
+ catch (Exception e)
{
LOG.warn("Unable to check classloader of caller",e);
}
@@ -2846,11 +2876,11 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
if (a.length()<r.length())
return r.startsWith(a) && r.length()==a.length()+1 && r.endsWith("/");
- return a.equals(r);
+ return a.equals(r);
}
}
-
+
/** Listener for all threads entering context scope, including async IO callbacks
*/
public static interface ContextScopeListener extends EventListener
@@ -2858,14 +2888,15 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
/**
* @param context The context being entered
* @param request A request that is applicable to the scope, or null
+ * @param reason An object that indicates the reason the scope is being entered.
*/
- void enterScope(Context context, Request request);
-
-
+ void enterScope(Context context, Request request, Object reason);
+
+
/**
* @param context The context being exited
* @param request A request that is applicable to the scope, or null
*/
- void exitScope(Context context, Request request);
+ void exitScope(Context context, Request request);
}
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DebugHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DebugHandler.java
index ebb71641df..f8fd5a33e2 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DebugHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DebugHandler.java
@@ -30,6 +30,7 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.server.AbstractConnector;
import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.DebugListener;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.DateCache;
@@ -42,6 +43,7 @@ import org.eclipse.jetty.util.RolloverFileOutputStream;
* Details of the request and response are written to an output stream
* and the current thread name is updated with information that will link
* to the details in that output.
+ * @deprecated Use {@link DebugListener}
*/
public class DebugHandler extends HandlerWrapper implements Connection.Listener
{
@@ -64,8 +66,8 @@ public class DebugHandler extends HandlerWrapper implements Connection.Listener
boolean suspend=false;
boolean retry=false;
String name=(String)request.getAttribute("org.eclipse.jetty.thread.name");
- if (name==null)
- name=old_name+":"+baseRequest.getScheme()+"://"+baseRequest.getLocalAddr()+":"+baseRequest.getLocalPort()+baseRequest.getHttpURI();
+ if (name == null)
+ name = old_name + ":" + baseRequest.getHttpURI();
else
retry=true;
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
index a30a1e3149..97c29dac20 100644
--- 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
@@ -24,6 +24,7 @@ import java.io.StringWriter;
import java.io.Writer;
import java.nio.ByteBuffer;
+import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -33,38 +34,35 @@ import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.server.AsyncContextEvent;
import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.HttpOutput;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.ByteArrayISO8859Writer;
-import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-/* ------------------------------------------------------------ */
-/** Handler for Error pages
- * An ErrorHandler is registered with {@link ContextHandler#setErrorHandler(ErrorHandler)} or
- * {@link org.eclipse.jetty.server.Server#addBean(Object)}.
- * It is called by the HttpResponse.sendError method to write a error page via {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)}
- * or via {@link #badMessageError(int, String, HttpFields)} for bad requests for which a dispatch cannot be done.
- *
+/**
+ * <p>Component that handles Error Pages.</p>
+ * <p>An ErrorHandler is registered with {@link ContextHandler#setErrorHandler(ErrorHandler)} or
+ * {@link org.eclipse.jetty.server.Server#addBean(Object)}.</p>
+ * <p>It is called by {@link HttpServletResponse#sendError(int)} to write an error page via
+ * {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)}
+ * or via {@link #badMessageError(int, String, HttpFields)} for bad requests for which a
+ * dispatch cannot be done.</p>
*/
public class ErrorHandler extends AbstractHandler
-{
+{
private static final Logger LOG = Log.getLogger(ErrorHandler.class);
- public final static String ERROR_PAGE="org.eclipse.jetty.server.error_page";
-
- boolean _showStacks=true;
- boolean _showMessageInTitle=true;
- String _cacheControl="must-revalidate,no-cache,no-store";
- /* ------------------------------------------------------------ */
- /*
- * @see org.eclipse.jetty.server.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
- */
+ private boolean _showStacks = true;
+ private boolean _showMessageInTitle = true;
+ private String _cacheControl = "must-revalidate,no-cache,no-store";
+
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
{
@@ -74,139 +72,151 @@ public class ErrorHandler extends AbstractHandler
baseRequest.setHandled(true);
return;
}
-
+
if (this instanceof ErrorPageMapper)
{
- String error_page=((ErrorPageMapper)this).getErrorPage(request);
- if (error_page!=null && request.getServletContext()!=null)
+ String error_page = ((ErrorPageMapper)this).getErrorPage(request);
+
+ ServletContext context = request.getServletContext();
+ if (context == null)
{
- String old_error_page=(String)request.getAttribute(ERROR_PAGE);
- if (old_error_page==null || !old_error_page.equals(error_page))
- {
- request.setAttribute(ERROR_PAGE, error_page);
+ AsyncContextEvent event = baseRequest.getHttpChannelState().getAsyncContextEvent();
+ context = event == null ? null : event.getServletContext();
+ }
- Dispatcher dispatcher = (Dispatcher) request.getServletContext().getRequestDispatcher(error_page);
+ if (error_page != null && context != null)
+ {
+ Dispatcher dispatcher = (Dispatcher)context.getRequestDispatcher(error_page);
+ if (dispatcher != null)
+ {
try
{
- if(dispatcher!=null)
- {
- dispatcher.error(request, response);
- return;
- }
- LOG.warn("No error page "+error_page);
+ dispatcher.error(request, response);
+ return;
}
- catch (ServletException e)
+ catch (ServletException x)
{
- LOG.warn(Log.EXCEPTION, e);
- return;
+ throw new IOException(x);
}
}
+ else
+ {
+ LOG.warn("Could not dispatch to error page: {}", error_page);
+ // Fall through to provide the default error page.
+ }
}
}
-
+
baseRequest.setHandled(true);
- response.setContentType(MimeTypes.Type.TEXT_HTML_8859_1.asString());
- if (_cacheControl!=null)
- response.setHeader(HttpHeader.CACHE_CONTROL.asString(), _cacheControl);
- ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(4096);
- String reason=(response instanceof Response)?((Response)response).getReason():null;
- handleErrorPage(request, writer, response.getStatus(), reason);
- writer.flush();
- response.setContentLength(writer.size());
- writer.writeTo(response.getOutputStream());
- writer.destroy();
+
+ HttpOutput out = baseRequest.getResponse().getHttpOutput();
+ if (!out.isAsync())
+ {
+ response.setContentType(MimeTypes.Type.TEXT_HTML_8859_1.asString());
+ String cacheHeader = getCacheControl();
+ if (cacheHeader != null)
+ response.setHeader(HttpHeader.CACHE_CONTROL.asString(), cacheHeader);
+ ByteArrayISO8859Writer writer = new ByteArrayISO8859Writer(4096);
+ String reason = (response instanceof Response) ? ((Response)response).getReason() : null;
+ handleErrorPage(request, writer, response.getStatus(), reason);
+ 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
+ throws IOException
{
- writeErrorPage(request, writer, code, message, _showStacks);
+ writeErrorPage(request, writer, code, message, isShowStacks());
}
/* ------------------------------------------------------------ */
protected void writeErrorPage(HttpServletRequest request, Writer writer, int code, String message, boolean showStacks)
- throws IOException
+ throws IOException
{
if (message == null)
- message=HttpStatus.getMessage(code);
+ message = HttpStatus.getMessage(code);
writer.write("<html>\n<head>\n");
- writeErrorPageHead(request,writer,code,message);
+ writeErrorPageHead(request, writer, code, message);
writer.write("</head>\n<body>");
- writeErrorPageBody(request,writer,code,message,showStacks);
+ 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
- {
+ throws IOException
+ {
writer.write("<meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/>\n");
writer.write("<title>Error ");
writer.write(Integer.toString(code));
- if (_showMessageInTitle)
+ if (getShowMessageInTitle())
{
writer.write(' ');
- write(writer,message);
+ write(writer, message);
}
writer.write("</title>\n");
}
/* ------------------------------------------------------------ */
protected void writeErrorPageBody(HttpServletRequest request, Writer writer, int code, String message, boolean showStacks)
- throws IOException
+ throws IOException
{
- String uri= request.getRequestURI();
+ String uri = request.getRequestURI();
- writeErrorPageMessage(request,writer,code,message,uri);
+ writeErrorPageMessage(request, writer, code, message, uri);
if (showStacks)
- writeErrorPageStacks(request,writer);
+ writeErrorPageStacks(request, writer);
Request.getBaseRequest(request).getHttpChannel().getHttpConfiguration()
- .writePoweredBy(writer,"<hr>","<hr/>\n");
+ .writePoweredBy(writer, "<hr>", "<hr/>\n");
}
/* ------------------------------------------------------------ */
- protected void writeErrorPageMessage(HttpServletRequest request, Writer writer, int code, String message,String uri)
- throws IOException
+ 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 ");
- write(writer,uri);
+ write(writer, uri);
writer.write(". Reason:\n<pre> ");
- write(writer,message);
+ write(writer, message);
writer.write("</pre></p>");
}
/* ------------------------------------------------------------ */
protected void writeErrorPageStacks(HttpServletRequest request, Writer writer)
- throws IOException
+ throws IOException
{
Throwable th = (Throwable)request.getAttribute("javax.servlet.error.exception");
- while(th!=null)
+ while (th != null)
{
writer.write("<h3>Caused by:</h3><pre>");
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
th.printStackTrace(pw);
pw.flush();
- write(writer,sw.getBuffer().toString());
+ write(writer, sw.getBuffer().toString());
writer.write("</pre>\n");
- th =th.getCause();
+ th = th.getCause();
}
}
/* ------------------------------------------------------------ */
- /** Bad Message Error body
- * <p>Generate a error response body to be sent for a bad message.
- * In this case there is something wrong with the request, so either
+ /**
+ * <p>Generate a error response body to be sent for a bad message.</p>
+ * <p>In this case there is something wrong with the request, so either
* a request cannot be built, or it is not safe to build a request.
- * This method allows for a simple error page body to be returned
- * and some response headers to be set.
+ * This method allows for a simple error page body to be returned
+ * and some response headers to be set.</p>
+ *
* @param status The error code that will be sent
* @param reason The reason for the error code (may be null)
* @param fields The header fields that will be sent with the response.
@@ -214,14 +224,14 @@ public class ErrorHandler extends AbstractHandler
*/
public ByteBuffer badMessageError(int status, String reason, HttpFields fields)
{
- if (reason==null)
- reason=HttpStatus.getMessage(status);
- fields.put(HttpHeader.CONTENT_TYPE,MimeTypes.Type.TEXT_HTML_8859_1.asString());
+ if (reason == null)
+ reason = HttpStatus.getMessage(status);
+ fields.put(HttpHeader.CONTENT_TYPE, MimeTypes.Type.TEXT_HTML_8859_1.asString());
return BufferUtil.toBuffer("<h1>Bad Message " + status + "</h1><pre>reason: " + reason + "</pre>");
- }
-
+ }
+
/* ------------------------------------------------------------ */
- /** Get the cacheControl.
+ /**
* @return the cacheControl header to set on error responses.
*/
public String getCacheControl()
@@ -230,7 +240,7 @@ public class ErrorHandler extends AbstractHandler
}
/* ------------------------------------------------------------ */
- /** Set the cacheControl.
+ /**
* @param cacheControl the cacheControl header to set on error responses.
*/
public void setCacheControl(String cacheControl)
@@ -240,7 +250,7 @@ public class ErrorHandler extends AbstractHandler
/* ------------------------------------------------------------ */
/**
- * @return True if stack traces are shown in the error pages
+ * @return whether stack traces are shown in the error pages
*/
public boolean isShowStacks()
{
@@ -249,7 +259,7 @@ public class ErrorHandler extends AbstractHandler
/* ------------------------------------------------------------ */
/**
- * @param showStacks True if stack traces are shown in the error pages
+ * @param showStacks whether stack traces are shown in the error pages
*/
public void setShowStacks(boolean showStacks)
{
@@ -258,25 +268,27 @@ public class ErrorHandler extends AbstractHandler
/* ------------------------------------------------------------ */
/**
- * @param showMessageInTitle if true, the error message appears in page title
+ * @return whether the error message appears in page title
*/
- public void setShowMessageInTitle(boolean showMessageInTitle)
+ public boolean getShowMessageInTitle()
{
- _showMessageInTitle = showMessageInTitle;
+ return _showMessageInTitle;
}
-
/* ------------------------------------------------------------ */
- public boolean getShowMessageInTitle()
+ /**
+ * @param showMessageInTitle whether the error message appears in page title
+ */
+ public void setShowMessageInTitle(boolean showMessageInTitle)
{
- return _showMessageInTitle;
+ _showMessageInTitle = showMessageInTitle;
}
/* ------------------------------------------------------------ */
- protected void write(Writer writer,String string)
- throws IOException
+ protected void write(Writer writer, String string)
+ throws IOException
{
- if (string==null)
+ if (string == null)
return;
writer.write(StringUtil.sanitizeXmlString(string));
@@ -291,11 +303,22 @@ public class ErrorHandler extends AbstractHandler
/* ------------------------------------------------------------ */
public static ErrorHandler getErrorHandler(Server server, ContextHandler context)
{
- ErrorHandler error_handler=null;
- if (context!=null)
- error_handler=context.getErrorHandler();
- if (error_handler==null && server!=null)
- error_handler = server.getBean(ErrorHandler.class);
+ ErrorHandler error_handler = null;
+ if (context != null)
+ error_handler = context.getErrorHandler();
+ if (error_handler == null)
+ {
+ synchronized (ErrorHandler.class)
+ {
+ error_handler = server.getBean(ErrorHandler.class);
+ if (error_handler == null)
+ {
+ error_handler = new ErrorHandler();
+ error_handler.setServer(server);
+ server.addManaged(error_handler);
+ }
+ }
+ }
return error_handler;
}
}
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
index f799b3a4f8..2a1b771d6c 100644
--- 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
@@ -28,7 +28,6 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.ArrayUtil;
import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
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
index c8ef17b61c..bc60dc7c05 100644
--- 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
@@ -25,10 +25,8 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.LifeCycle;
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
index 6a38a710a8..9c2a72ab59 100644
--- 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
@@ -498,11 +498,12 @@ public class ResourceHandler extends HandlerWrapper
doResponseHeaders(response,resource,mime);
if (_etags)
baseRequest.getResponse().getHttpFields().put(HttpHeader.ETAG,etag);
+ if (last_modified>0)
+ response.setDateHeader(HttpHeader.LAST_MODIFIED.asString(),last_modified);
if(skipContentBody)
return;
-
// Send the content
OutputStream out =null;
try {out = response.getOutputStream();}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java
index ba0d6d05a0..8a14d13ab5 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java
@@ -18,6 +18,8 @@
package org.eclipse.jetty.server.handler.gzip;
+import static org.eclipse.jetty.http.GzipHttpContent.ETAG_GZIP_QUOTE;
+
import java.io.File;
import java.io.IOException;
import java.util.Set;
@@ -28,6 +30,7 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.eclipse.jetty.http.GzipHttpContent;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
@@ -61,10 +64,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
public final static String GZIP = "gzip";
public final static String DEFLATE = "deflate";
- public final static String ETAG_GZIP="--gzip";
- public final static String ETAG = "o.e.j.s.Gzip.ETag";
public final static int DEFAULT_MIN_GZIP_SIZE=16;
-
private int _minGzipSize=DEFAULT_MIN_GZIP_SIZE;
private int _compressionLevel=Deflater.DEFAULT_COMPRESSION;
private boolean _checkGzExists = true;
@@ -79,6 +79,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
private HttpField _vary;
+
/* ------------------------------------------------------------ */
/**
* Instantiates a new gzip handler.
@@ -415,11 +416,19 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
}
// Special handling for etags
- String etag = request.getHeader("If-None-Match");
+ String etag = baseRequest.getHttpFields().get(HttpHeader.IF_NONE_MATCH);
if (etag!=null)
{
- if (etag.contains(ETAG_GZIP))
- request.setAttribute(ETAG,etag.replace(ETAG_GZIP,""));
+ int i=etag.indexOf(ETAG_GZIP_QUOTE);
+ if (i>0)
+ {
+ while (i>=0)
+ {
+ etag=etag.substring(0,i)+etag.substring(i+GzipHttpContent.ETAG_GZIP.length());
+ i=etag.indexOf(ETAG_GZIP_QUOTE,i);
+ }
+ baseRequest.getHttpFields().put(new HttpField(HttpHeader.IF_NONE_MATCH,etag));
+ }
}
// install interceptor and handle
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java
index 41ba3b9c65..0aa28d786b 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java
@@ -24,6 +24,7 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.CRC32;
import java.util.zip.Deflater;
+import org.eclipse.jetty.http.GzipHttpContent;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
@@ -41,7 +42,6 @@ import org.eclipse.jetty.util.log.Logger;
public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
{
public static Logger LOG = Log.getLogger(GzipHttpOutputInterceptor.class);
- private final static PreEncodedHttpField CONTENT_ENCODING_GZIP=new PreEncodedHttpField(HttpHeader.CONTENT_ENCODING,"gzip");
private final static byte[] GZIP_HEADER = new byte[] { (byte)0x1f, (byte)0x8b, Deflater.DEFLATED, 0, 0, 0, 0, 0, 0, 0 };
public final static HttpField VARY_ACCEPT_ENCODING_USER_AGENT=new PreEncodedHttpField(HttpHeader.VARY,HttpHeader.ACCEPT_ENCODING+", "+HttpHeader.USER_AGENT);
@@ -202,7 +202,7 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
return;
}
- fields.put(CONTENT_ENCODING_GZIP);
+ fields.put(GzipHttpContent.CONTENT_ENCODING_GZIP);
_crc.reset();
_buffer=_channel.getByteBufferPool().acquire(_bufferSize,false);
BufferUtil.fill(_buffer,GZIP_HEADER,0,GZIP_HEADER.length);
@@ -213,7 +213,7 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
if (etag!=null)
{
int end = etag.length()-1;
- etag=(etag.charAt(end)=='"')?etag.substring(0,end)+GzipHandler.ETAG_GZIP+'"':etag+GzipHandler.ETAG_GZIP;
+ etag=(etag.charAt(end)=='"')?etag.substring(0,end)+GzipHttpContent.ETAG_GZIP+'"':etag+GzipHttpContent.ETAG_GZIP;
fields.put(HttpHeader.ETAG,etag);
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionDataStore.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionDataStore.java
index 2dc6aa8195..7130060dbb 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionDataStore.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionDataStore.java
@@ -34,13 +34,14 @@ public interface SessionDataStore extends LifeCycle
/**
* Initialize this session data store for the
* given context. A SessionDataStore can only
- * be used by one context.
+ * be used by one context(/session manager).
*
* @param contextId
*/
void initialize(ContextId contextId);
+
/**
* Read in session data from storage
* @param id

Back to the top