diff options
Diffstat (limited to 'jetty-server/src/main/java/org')
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 <=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 |