Mercurial Hosting > luan
changeset 802:3428c60d7cfc
replace jetty jars with source
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/continuation/Continuation.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,420 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.continuation; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.Servlet; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.ServletResponseWrapper; + +/* ------------------------------------------------------------ */ +/** + * Continuation. + * + * A continuation is a mechanism by which a HTTP Request can be suspended and + * restarted after a timeout or an asynchronous event has occurred. + * <p> + * The continuation mechanism is a portable mechanism that will work + * asynchronously without additional configuration of all jetty-7, + * jetty-8 and Servlet 3.0 containers. With the addition of + * the {@link ContinuationFilter}, the mechanism will also work + * asynchronously on jetty-6 and non-asynchronously on any + * servlet 2.5 container. + * <p> + * The Continuation API is a simplification of the richer async API + * provided by the servlet-3.0 and an enhancement of the continuation + * API that was introduced with jetty-6. + * </p> + * <h1>Continuation Usage</h1> + * <p> + * A continuation object is obtained for a request by calling the + * factory method {@link ContinuationSupport#getContinuation(ServletRequest)}. + * The continuation type returned will depend on the servlet container + * being used. + * </p> + * <p> + * There are two distinct style of operation of the continuation API. + * </p> + * <h3>Suspend/Resume Usage</h3> + * <p>The suspend/resume style is used when a servlet and/or + * filter is used to generate the response after a asynchronous wait that is + * terminated by an asynchronous handler. + * </p> + * <pre> + * <b>Filter/Servlet:</b> + * // if we need to get asynchronous results + * Object results = request.getAttribute("results); + * if (results==null) + * { + * Continuation continuation = ContinuationSupport.getContinuation(request); + * continuation.suspend(); + * myAsyncHandler.register(continuation); + * return; // or continuation.undispatch(); + * } + * + * async wait ... + * + * <b>Async Handler:</b> + * // when the waited for event happens + * continuation.setAttribute("results",event); + * continuation.resume(); + * + * <b>Filter/Servlet:</b> + * // when the request is redispatched + * if (results==null) + * { + * ... // see above + * } + * else + * { + * response.getOutputStream().write(process(results)); + * } + * </pre> + * <h3>Suspend/Complete Usage</h3> + * <p> + * The suspend/complete style is used when an asynchronous handler is used to + * generate the response: + * </p> + * <pre> + * <b>Filter/Servlet:</b> + * // when we want to enter asynchronous mode + * Continuation continuation = ContinuationSupport.getContinuation(request); + * continuation.suspend(response); // response may be wrapped + * myAsyncHandler.register(continuation); + * return; // or continuation.undispatch(); + * + * <b>Wrapping Filter:</b> + * // any filter that had wrapped the response should be implemented like: + * try + * { + * chain.doFilter(request,wrappedResponse); + * } + * finally + * { + * if (!continuation.isResponseWrapped()) + * wrappedResponse.finish() + * else + * continuation.addContinuationListener(myCompleteListener) + * } + * + * async wait ... + * + * <b>Async Handler:</b> + * // when the async event happens + * continuation.getServletResponse().getOutputStream().write(process(event)); + * continuation.complete() + * </pre> + * + * <h1>Continuation Timeout</h1> + * <p> + * If a continuation is suspended, but neither {@link #complete()} or {@link #resume()} is + * called during the period set by {@link #setTimeout(long)}, then the continuation will + * expire and {@link #isExpired()} will return true. + * </p> + * <p> + * When a continuation expires, the {@link ContinuationListener#onTimeout(Continuation)} + * method is called on any {@link ContinuationListener} that has been registered via the + * {@link #addContinuationListener(ContinuationListener)} method. The onTimeout handlers + * may write a response and call {@link #complete()}. If {@link #complete()} is not called, + * then the container will redispatch the request as if {@link #resume()} had been called, + * except that {@link #isExpired()} will be true and {@link #isResumed()} will be false. + * </p> + * + * @see ContinuationSupport + * @see ContinuationListener + * + */ +public interface Continuation +{ + public final static String ATTRIBUTE = "org.eclipse.jetty.continuation"; + + /* ------------------------------------------------------------ */ + /** + * Set the continuation timeout. + * + * @param timeoutMs + * The time in milliseconds to wait before expiring this + * continuation after a call to {@link #suspend()} or {@link #suspend(ServletResponse)}. + * A timeout of <=0 means the continuation will never expire. + */ + void setTimeout(long timeoutMs); + + /* ------------------------------------------------------------ */ + /** + * Suspend the processing of the request and associated + * {@link ServletResponse}. + * + * <p> + * After this method has been called, the lifecycle of the request will be + * extended beyond the return to the container from the + * {@link Servlet#service(ServletRequest, ServletResponse)} method and + * {@link Filter#doFilter(ServletRequest, ServletResponse, FilterChain)} + * calls. When a suspended request is returned to the container after + * a dispatch, then the container will not commit the associated response + * (unless an exception other than {@link ContinuationThrowable} is thrown). + * </p> + * + * <p> + * When the thread calling the filter chain and/or servlet has returned to + * the container with a suspended request, the thread is freed for other + * tasks and the request is held until either: + * <ul> + * <li>a call to {@link #resume()}.</li> + * <li>a call to {@link #complete()}.</li> + * <li>the timeout expires.</li> + * </ul> + * <p> + * Typically suspend with no arguments is uses when a call to {@link #resume()} + * is expected. If a call to {@link #complete()} is expected, then the + * {@link #suspend(ServletResponse)} method should be used instead of this method. + * </p> + * + * @exception IllegalStateException + * If the request cannot be suspended + */ + void suspend(); + + + /* ------------------------------------------------------------ */ + /** + * Suspend the processing of the request and associated + * {@link ServletResponse}. + * + * <p> + * After this method has been called, the lifecycle of the request will be + * extended beyond the return to the container from the + * {@link Servlet#service(ServletRequest, ServletResponse)} method and + * {@link Filter#doFilter(ServletRequest, ServletResponse, FilterChain)} + * calls. When a suspended request is returned to the container after + * a dispatch, then the container will not commit the associated response + * (unless an exception other than {@link ContinuationThrowable} is thrown). + * </p> + * <p> + * When the thread calling the filter chain and/or servlet has returned to + * the container with a suspended request, the thread is freed for other + * tasks and the request is held until either: + * <ul> + * <li>a call to {@link #resume()}.</li> + * <li>a call to {@link #complete()}.</li> + * <li>the timeout expires.</li> + * </ul> + * <p> + * Typically suspend with a response argument is uses when a call to {@link #complete()} + * is expected. If a call to {@link #resume()} is expected, then the + * {@link #suspend()} method should be used instead of this method. + * </p> + * <p> + * Filters that may wrap the response object should check {@link #isResponseWrapped()} + * to decide if they should destroy/finish the wrapper. If {@link #isResponseWrapped()} + * returns true, then the wrapped request has been passed to the asynchronous + * handler and the wrapper should not be destroyed/finished until after a call to + * {@link #complete()} (potentially using a {@link ContinuationListener#onComplete(Continuation)} + * listener). + * + * @param response The response to return via a call to {@link #getServletResponse()} + * @exception IllegalStateException + * If the request cannot be suspended + */ + void suspend(ServletResponse response); + + /* ------------------------------------------------------------ */ + /** + * Resume a suspended request. + * + * <p> + * This method can be called by any thread that has been passed a reference + * to a continuation. When called the request is redispatched to the + * normal filter chain and servlet processing with {@link #isInitial()} false. + * </p> + * <p> + * If resume is called before a suspended request is returned to the + * container (ie the thread that called {@link #suspend()} is still + * within the filter chain and/or servlet service method), then the resume + * does not take effect until the call to the filter chain and/or servlet + * returns to the container. In this case both {@link #isSuspended()} and + * {@link #isResumed()} return true. Multiple calls to resume are ignored. + * </p> + * <p> + * Typically resume() is used after a call to {@link #suspend()} with + * no arguments. The dispatch after a resume call will use the original + * request and response objects, even if {@link #suspend(ServletResponse)} + * had been passed a wrapped response. + * </p> + * + * @see #suspend() + * @exception IllegalStateException if the request is not suspended. + * + */ + void resume(); + + /* ------------------------------------------------------------ */ + /** + * Complete a suspended request. + * + * <p> + * This method can be called by any thread that has been passed a reference + * to a suspended request. When a request is completed, the associated + * response object committed and flushed. The request is not redispatched. + * </p> + * + * <p> + * If complete is called before a suspended request is returned to the + * container (ie the thread that called {@link #suspend()} is still + * within the filter chain and/or servlet service method), then the complete + * does not take effect until the call to the filter chain and/or servlet + * returns to the container. In this case both {@link #isSuspended()} and + * {@link #isResumed()} return true. + * </p> + * + * <p> + * Typically resume() is used after a call to {@link #suspend(ServletResponse)} with + * a possibly wrapped response. The async handler should use the response + * provided by {@link #getServletResponse()} to write the response before + * calling {@link #complete()}. If the request was suspended with a + * call to {@link #suspend()} then no response object will be available via + * {@link #getServletResponse()}. + * </p> + * + * <p> + * Once complete has been called and any thread calling the filter chain + * and/or servlet chain has returned to the container, the request lifecycle + * is complete. The container is able to recycle request objects, so it is + * not valid hold a request or continuation reference after the end of the + * life cycle. + * + * @see #suspend() + * @exception IllegalStateException + * if the request is not suspended. + * + */ + void complete(); + + /* ------------------------------------------------------------ */ + /** + * @return true after {@link #suspend()} has been called and before the + * request has been redispatched due to being resumed, completed or + * timed out. + */ + boolean isSuspended(); + + /* ------------------------------------------------------------ */ + /** + * @return true if the request has been redispatched by a call to + * {@link #resume()}. Returns false after any subsequent call to + * suspend + */ + boolean isResumed(); + + /* ------------------------------------------------------------ */ + /** + * @return true after a request has been redispatched as the result of a + * timeout. Returns false after any subsequent call to suspend. + */ + boolean isExpired(); + + /* ------------------------------------------------------------ */ + /** + * @return true while the request is within the initial dispatch to the + * filter chain and/or servlet. Will return false once the calling + * thread has returned to the container after suspend has been + * called and during any subsequent redispatch. + */ + boolean isInitial(); + + /* ------------------------------------------------------------ */ + /** + * Is the suspended response wrapped. + * <p> + * Filters that wrap the response object should check this method to + * determine if they should destroy/finish the wrapped response. If + * the request was suspended with a call to {@link #suspend(ServletResponse)} + * that passed the wrapped response, then the filter should register + * a {@link ContinuationListener} to destroy/finish the wrapped response + * during a call to {@link ContinuationListener#onComplete(Continuation)}. + * @return True if {@link #suspend(ServletResponse)} has been passed a + * {@link ServletResponseWrapper} instance. + */ + boolean isResponseWrapped(); + + + /* ------------------------------------------------------------ */ + /** + * Get the suspended response. + * @return the {@link ServletResponse} passed to {@link #suspend(ServletResponse)}. + */ + ServletResponse getServletResponse(); + + /* ------------------------------------------------------------ */ + /** + * Add a ContinuationListener. + * + * @param listener + */ + void addContinuationListener(ContinuationListener listener); + + /* ------------------------------------------------------------ */ + /** Set a request attribute. + * This method is a convenience method to call the {@link ServletRequest#setAttribute(String, Object)} + * method on the associated request object. + * This is a thread safe call and may be called by any thread. + * @param name the attribute name + * @param attribute the attribute value + */ + public void setAttribute(String name, Object attribute); + + /* ------------------------------------------------------------ */ + /** Get a request attribute. + * This method is a convenience method to call the {@link ServletRequest#getAttribute(String)} + * method on the associated request object. + * This is a thread safe call and may be called by any thread. + * @param name the attribute name + * @return the attribute value + */ + public Object getAttribute(String name); + + /* ------------------------------------------------------------ */ + /** Remove a request attribute. + * This method is a convenience method to call the {@link ServletRequest#removeAttribute(String)} + * method on the associated request object. + * This is a thread safe call and may be called by any thread. + * @param name the attribute name + */ + public void removeAttribute(String name); + + /* ------------------------------------------------------------ */ + /** + * Undispatch the request. + * <p> + * This method can be called on a suspended continuation in order + * to exit the dispatch to the filter/servlet by throwing a {@link ContinuationThrowable} + * which is caught either by the container or the {@link ContinuationFilter}. + * This is an alternative to simply returning from the dispatch in the case + * where filters in the filter chain may not be prepared to handle a suspended + * request. + * </p> + * This method should only be used as a last resort and a normal return is a prefereable + * solution if filters can be updated to handle that case. + * + * @throws ContinuationThrowable thrown if the request is suspended. The instance of the + * exception may be reused on subsequent calls, so the stack frame may not be accurate. + */ + public void undispatch() throws ContinuationThrowable; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/continuation/ContinuationFilter.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,174 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.continuation; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + + + +/* ------------------------------------------------------------ */ +/** + * <p>ContinuationFilter must be applied to servlet paths that make use of + * the asynchronous features provided by {@link Continuation} APIs, but that + * are deployed in servlet containers that are neither Jetty (>= 7) nor a + * compliant Servlet 3.0 container.</p> + * <p>The following init parameters may be used to configure the filter (these are mostly for testing):</p> + * <dl> + * <dt>debug</dt><dd>Boolean controlling debug output</dd> + * <dt>jetty6</dt><dd>Boolean to force use of Jetty 6 continuations</dd> + * <dt>faux</dt><dd>Boolean to force use of faux continuations</dd> + * </dl> + * <p>If the servlet container is not Jetty (either 6 or 7) nor a Servlet 3 + * container, then "faux" continuations will be used.</p> + * <p>Faux continuations will just put the thread that called {@link Continuation#suspend()} + * in wait, and will notify that thread when {@link Continuation#resume()} or + * {@link Continuation#complete()} is called.</p> + * <p>Faux continuations are not threadless continuations (they are "faux" - fake - for this reason) + * and as such they will scale less than proper continuations.</p> + */ +public class ContinuationFilter implements Filter +{ + static boolean _initialized; + static boolean __debug; // shared debug status + private boolean _faux; + private boolean _jetty6; + private boolean _filtered; + ServletContext _context; + private boolean _debug; + + public void init(FilterConfig filterConfig) throws ServletException + { + boolean jetty_7_or_greater="org.eclipse.jetty.servlet".equals(filterConfig.getClass().getPackage().getName()); + _context = filterConfig.getServletContext(); + + String param=filterConfig.getInitParameter("debug"); + _debug=param!=null&&Boolean.parseBoolean(param); + if (_debug) + __debug=true; + + param=filterConfig.getInitParameter("jetty6"); + if (param==null) + param=filterConfig.getInitParameter("partial"); + if (param!=null) + _jetty6=Boolean.parseBoolean(param); + else + _jetty6=ContinuationSupport.__jetty6 && !jetty_7_or_greater; + + param=filterConfig.getInitParameter("faux"); + if (param!=null) + _faux=Boolean.parseBoolean(param); + else + _faux=!(jetty_7_or_greater || _jetty6 || _context.getMajorVersion()>=3); + + _filtered=_faux||_jetty6; + if (_debug) + _context.log("ContinuationFilter "+ + " jetty="+jetty_7_or_greater+ + " jetty6="+_jetty6+ + " faux="+_faux+ + " filtered="+_filtered+ + " servlet3="+ContinuationSupport.__servlet3); + _initialized=true; + } + + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException + { + if (_filtered) + { + Continuation c = (Continuation) request.getAttribute(Continuation.ATTRIBUTE); + FilteredContinuation fc; + if (_faux && (c==null || !(c instanceof FauxContinuation))) + { + fc = new FauxContinuation(request); + request.setAttribute(Continuation.ATTRIBUTE,fc); + } + else + fc=(FilteredContinuation)c; + + boolean complete=false; + while (!complete) + { + try + { + if (fc==null || (fc).enter(response)) + chain.doFilter(request,response); + } + catch (ContinuationThrowable e) + { + debug("faux",e); + } + finally + { + if (fc==null) + fc = (FilteredContinuation) request.getAttribute(Continuation.ATTRIBUTE); + + complete=fc==null || (fc).exit(); + } + } + } + else + { + try + { + chain.doFilter(request,response); + } + catch (ContinuationThrowable e) + { + debug("caught",e); + } + } + } + + private void debug(String string) + { + if (_debug) + { + _context.log(string); + } + } + + private void debug(String string, Throwable th) + { + if (_debug) + { + if (th instanceof ContinuationThrowable) + _context.log(string+":"+th); + else + _context.log(string,th); + } + } + + public void destroy() + { + } + + public interface FilteredContinuation extends Continuation + { + boolean enter(ServletResponse response); + boolean exit(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/continuation/ContinuationListener.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,55 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.continuation; + +import java.util.EventListener; + +import javax.servlet.ServletRequestListener; + + +/* ------------------------------------------------------------ */ +/** A Continuation Listener + * <p> + * A ContinuationListener may be registered with a call to + * {@link Continuation#addContinuationListener(ContinuationListener)}. + * + */ +public interface ContinuationListener extends EventListener +{ + /* ------------------------------------------------------------ */ + /** + * Called when a continuation life cycle is complete and after + * any calls to {@link ServletRequestListener#requestDestroyed(javax.servlet.ServletRequestEvent)} + * The response may still be written to during the call. + * + * @param continuation + */ + public void onComplete(Continuation continuation); + + /* ------------------------------------------------------------ */ + /** + * Called when a suspended continuation has timed out. + * The response may be written to and the methods + * {@link Continuation#resume()} or {@link Continuation#complete()} + * may be called by a onTimeout implementation, + * @param continuation + */ + public void onTimeout(Continuation continuation); + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/continuation/ContinuationSupport.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,164 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.continuation; + +import java.lang.reflect.Constructor; +import javax.servlet.ServletRequest; +import javax.servlet.ServletRequestWrapper; +import javax.servlet.ServletResponse; + +/* ------------------------------------------------------------ */ +/** ContinuationSupport. + * + * Factory class for accessing Continuation instances, which with either be + * native to the container (jetty >= 6), a servlet 3.0 or a faux continuation. + * + */ +public class ContinuationSupport +{ + static final boolean __jetty6; + static final boolean __servlet3; + static final Class<?> __waitingContinuation; + static final Constructor<? extends Continuation> __newServlet3Continuation; + static final Constructor<? extends Continuation> __newJetty6Continuation; + static + { + boolean servlet3Support=false; + Constructor<? extends Continuation>s3cc=null; + try + { + boolean servlet3=ServletRequest.class.getMethod("startAsync")!=null; + if (servlet3) + { + Class<? extends Continuation> s3c = ContinuationSupport.class.getClassLoader().loadClass("org.eclipse.jetty.continuation.Servlet3Continuation").asSubclass(Continuation.class); + s3cc=s3c.getConstructor(ServletRequest.class); + servlet3Support=true; + } + } + catch (Exception e) + {} + finally + { + __servlet3=servlet3Support; + __newServlet3Continuation=s3cc; + } + + boolean jetty6Support=false; + Constructor<? extends Continuation>j6cc=null; + try + { + Class<?> jetty6ContinuationClass = ContinuationSupport.class.getClassLoader().loadClass("org.mortbay.util.ajax.Continuation"); + boolean jetty6=jetty6ContinuationClass!=null; + if (jetty6) + { + Class<? extends Continuation> j6c = ContinuationSupport.class.getClassLoader().loadClass("org.eclipse.jetty.continuation.Jetty6Continuation").asSubclass(Continuation.class); + j6cc=j6c.getConstructor(ServletRequest.class, jetty6ContinuationClass); + jetty6Support=true; + } + } + catch (Exception e) + {} + finally + { + __jetty6=jetty6Support; + __newJetty6Continuation=j6cc; + } + + Class<?> waiting=null; + try + { + waiting=ContinuationSupport.class.getClassLoader().loadClass("org.mortbay.util.ajax.WaitingContinuation"); + } + catch (Exception e) + { + } + finally + { + __waitingContinuation=waiting; + } + } + + /* ------------------------------------------------------------ */ + /** + * Get a Continuation. The type of the Continuation returned may + * vary depending on the container in which the application is + * deployed. It may be an implementation native to the container (eg + * org.eclipse.jetty.server.AsyncContinuation) or one of the utility + * implementations provided such as an internal <code>FauxContinuation</code> + * or a real implementation like {@link org.eclipse.jetty.continuation.Servlet3Continuation}. + * @param request The request + * @return a Continuation instance + */ + public static Continuation getContinuation(ServletRequest request) + { + Continuation continuation = (Continuation) request.getAttribute(Continuation.ATTRIBUTE); + if (continuation!=null) + return continuation; + + while (request instanceof ServletRequestWrapper) + request=((ServletRequestWrapper)request).getRequest(); + + if (__servlet3 ) + { + try + { + continuation=__newServlet3Continuation.newInstance(request); + request.setAttribute(Continuation.ATTRIBUTE,continuation); + return continuation; + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + + if (__jetty6) + { + Object c=request.getAttribute("org.mortbay.jetty.ajax.Continuation"); + try + { + if (c==null || __waitingContinuation==null || __waitingContinuation.isInstance(c)) + continuation=new FauxContinuation(request); + else + continuation= __newJetty6Continuation.newInstance(request,c); + request.setAttribute(Continuation.ATTRIBUTE,continuation); + return continuation; + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + + throw new IllegalStateException("!(Jetty || Servlet 3.0 || ContinuationFilter)"); + } + + /* ------------------------------------------------------------ */ + /** + * @param request the servlet request + * @param response the servlet response + * @deprecated use {@link #getContinuation(ServletRequest)} + * @return the continuation + */ + @Deprecated + public static Continuation getContinuation(final ServletRequest request, final ServletResponse response) + { + return getContinuation(request); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/continuation/ContinuationThrowable.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,46 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.continuation; + + +/* ------------------------------------------------------------ */ +/** ContinuationThrowable + * <p> + * A ContinuationThrowable is throw by {@link Continuation#undispatch()} + * in order to exit the dispatch to a Filter or Servlet. Use of + * ContinuationThrowable is discouraged and it is preferable to + * allow return to be used. ContinuationThrowables should only be + * used when there is a Filter/Servlet which cannot be modified + * to avoid committing a response when {@link Continuation#isSuspended()} + * is true. + * </p> + * <p> + * ContinuationThrowable instances are often reused so that the + * stack trace may be entirely unrelated to the calling stack. + * A real stack trace may be obtained by enabling debug. + * </p> + * <p> + * ContinuationThrowable extends Error as this is more likely + * to be uncaught (or rethrown) by a Filter/Servlet. A ContinuationThrowable + * does not represent and error condition. + * </p> + */ +public class ContinuationThrowable extends Error +{}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/continuation/FauxContinuation.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,508 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.continuation; + +import java.util.ArrayList; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.ServletResponseWrapper; + +import org.eclipse.jetty.continuation.ContinuationFilter.FilteredContinuation; + + +/* ------------------------------------------------------------ */ +/** + * A blocking implementation of Continuation. + * This implementation of Continuation is used by the {@link ContinuationFilter} + * when there are is no native or asynchronous continuation type available. + */ +class FauxContinuation implements FilteredContinuation +{ + // common exception used for all continuations. + // Turn on debug in ContinuationFilter to see real stack trace. + private final static ContinuationThrowable __exception = new ContinuationThrowable(); + + private static final int __HANDLING=1; // Request dispatched to filter/servlet + private static final int __SUSPENDING=2; // Suspend called, but not yet returned to container + private static final int __RESUMING=3; // resumed while suspending + private static final int __COMPLETING=4; // resumed while suspending or suspended + private static final int __SUSPENDED=5; // Suspended and parked + private static final int __UNSUSPENDING=6; + private static final int __COMPLETE=7; + + private final ServletRequest _request; + private ServletResponse _response; + + private int _state=__HANDLING; + private boolean _initial=true; + private boolean _resumed=false; + private boolean _timeout=false; + private boolean _responseWrapped=false; + private long _timeoutMs=30000; // TODO configure + + private ArrayList<ContinuationListener> _listeners; + + FauxContinuation(final ServletRequest request) + { + _request=request; + } + + /* ------------------------------------------------------------ */ + public void onComplete() + { + if (_listeners!=null) + for (ContinuationListener l:_listeners) + l.onComplete(this); + } + + /* ------------------------------------------------------------ */ + public void onTimeout() + { + if (_listeners!=null) + for (ContinuationListener l:_listeners) + l.onTimeout(this); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.continuation.Continuation#isResponseWrapped() + */ + public boolean isResponseWrapped() + { + return _responseWrapped; + } + + /* ------------------------------------------------------------ */ + public boolean isInitial() + { + synchronized(this) + { + return _initial; + } + } + + /* ------------------------------------------------------------ */ + public boolean isResumed() + { + synchronized(this) + { + return _resumed; + } + } + + /* ------------------------------------------------------------ */ + public boolean isSuspended() + { + synchronized(this) + { + switch(_state) + { + case __HANDLING: + return false; + case __SUSPENDING: + case __RESUMING: + case __COMPLETING: + case __SUSPENDED: + return true; + case __UNSUSPENDING: + default: + return false; + } + } + } + + /* ------------------------------------------------------------ */ + public boolean isExpired() + { + synchronized(this) + { + return _timeout; + } + } + + /* ------------------------------------------------------------ */ + public void setTimeout(long timeoutMs) + { + _timeoutMs = timeoutMs; + } + + /* ------------------------------------------------------------ */ + public void suspend(ServletResponse response) + { + _response=response; + _responseWrapped=response instanceof ServletResponseWrapper; + suspend(); + } + + /* ------------------------------------------------------------ */ + public void suspend() + { + synchronized (this) + { + switch(_state) + { + case __HANDLING: + _timeout=false; + _resumed=false; + _state=__SUSPENDING; + return; + + case __SUSPENDING: + case __RESUMING: + return; + + case __COMPLETING: + case __SUSPENDED: + case __UNSUSPENDING: + throw new IllegalStateException(this.getStatusString()); + + default: + throw new IllegalStateException(""+_state); + } + + } + } + + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see org.mortbay.jetty.Suspendor#resume() + */ + public void resume() + { + synchronized (this) + { + switch(_state) + { + case __HANDLING: + _resumed=true; + return; + + case __SUSPENDING: + _resumed=true; + _state=__RESUMING; + return; + + case __RESUMING: + case __COMPLETING: + return; + + case __SUSPENDED: + fauxResume(); + _resumed=true; + _state=__UNSUSPENDING; + break; + + case __UNSUSPENDING: + _resumed=true; + return; + + default: + throw new IllegalStateException(this.getStatusString()); + } + } + + } + + + /* ------------------------------------------------------------ */ + public void complete() + { + // just like resume, except don't set _resumed=true; + synchronized (this) + { + switch(_state) + { + case __HANDLING: + throw new IllegalStateException(this.getStatusString()); + + case __SUSPENDING: + _state=__COMPLETING; + break; + + case __RESUMING: + break; + + case __COMPLETING: + return; + + case __SUSPENDED: + _state=__COMPLETING; + fauxResume(); + break; + + case __UNSUSPENDING: + return; + + default: + throw new IllegalStateException(this.getStatusString()); + } + } + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.continuation.Continuation#getServletResponse() + */ + public boolean enter(ServletResponse response) + { + _response=response; + return true; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.continuation.Continuation#getServletResponse() + */ + public ServletResponse getServletResponse() + { + return _response; + } + + + /* ------------------------------------------------------------ */ + void handling() + { + synchronized (this) + { + _responseWrapped=false; + switch(_state) + { + case __HANDLING: + throw new IllegalStateException(this.getStatusString()); + + case __SUSPENDING: + case __RESUMING: + throw new IllegalStateException(this.getStatusString()); + + case __COMPLETING: + return; + + case __SUSPENDED: + fauxResume(); + case __UNSUSPENDING: + _state=__HANDLING; + return; + + default: + throw new IllegalStateException(""+_state); + } + + } + } + + /* ------------------------------------------------------------ */ + /** + * @return true if handling is complete + */ + public boolean exit() + { + synchronized (this) + { + switch(_state) + { + case __HANDLING: + _state=__COMPLETE; + onComplete(); + return true; + + case __SUSPENDING: + _initial=false; + _state=__SUSPENDED; + fauxSuspend(); // could block and change state. + if (_state==__SUSPENDED || _state==__COMPLETING) + { + onComplete(); + return true; + } + + _initial=false; + _state=__HANDLING; + return false; + + case __RESUMING: + _initial=false; + _state=__HANDLING; + return false; + + case __COMPLETING: + _initial=false; + _state=__COMPLETE; + onComplete(); + return true; + + case __SUSPENDED: + case __UNSUSPENDING: + default: + throw new IllegalStateException(this.getStatusString()); + } + } + } + + /* ------------------------------------------------------------ */ + protected void expire() + { + // just like resume, except don't set _resumed=true; + + synchronized (this) + { + _timeout=true; + } + + onTimeout(); + + synchronized (this) + { + switch(_state) + { + case __HANDLING: + return; + + case __SUSPENDING: + _timeout=true; + _state=__RESUMING; + fauxResume(); + return; + + case __RESUMING: + return; + + case __COMPLETING: + return; + + case __SUSPENDED: + _timeout=true; + _state=__UNSUSPENDING; + break; + + case __UNSUSPENDING: + _timeout=true; + return; + + default: + throw new IllegalStateException(this.getStatusString()); + } + } + } + + private void fauxSuspend() + { + long expire_at = System.currentTimeMillis()+_timeoutMs; + long wait=_timeoutMs; + while (_timeoutMs>0 && wait>0) + { + try + { + this.wait(wait); + } + catch (InterruptedException e) + { + break; + } + wait=expire_at-System.currentTimeMillis(); + } + + if (_timeoutMs>0 && wait<=0) + expire(); + } + + private void fauxResume() + { + _timeoutMs=0; + this.notifyAll(); + } + + @Override + public String toString() + { + return getStatusString(); + } + + String getStatusString() + { + synchronized (this) + { + return + ((_state==__HANDLING)?"HANDLING": + (_state==__SUSPENDING)?"SUSPENDING": + (_state==__SUSPENDED)?"SUSPENDED": + (_state==__RESUMING)?"RESUMING": + (_state==__UNSUSPENDING)?"UNSUSPENDING": + (_state==__COMPLETING)?"COMPLETING": + ("???"+_state))+ + (_initial?",initial":"")+ + (_resumed?",resumed":"")+ + (_timeout?",timeout":""); + } + } + + + public void addContinuationListener(ContinuationListener listener) + { + if (_listeners==null) + _listeners=new ArrayList<ContinuationListener>(); + _listeners.add(listener); + + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.continuation.Continuation#getAttribute(java.lang.String) + */ + public Object getAttribute(String name) + { + return _request.getAttribute(name); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.continuation.Continuation#removeAttribute(java.lang.String) + */ + public void removeAttribute(String name) + { + _request.removeAttribute(name); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.continuation.Continuation#setAttribute(java.lang.String, java.lang.Object) + */ + public void setAttribute(String name, Object attribute) + { + _request.setAttribute(name,attribute); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.continuation.Continuation#undispatch() + */ + public void undispatch() + { + if (isSuspended()) + { + if (ContinuationFilter.__debug) + throw new ContinuationThrowable(); + throw __exception; + } + throw new IllegalStateException("!suspended"); + + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/http/AbstractGenerator.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,532 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.http; + +import java.io.IOException; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.Buffers; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.io.View; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/* ------------------------------------------------------------ */ +/** + * Abstract Generator. Builds HTTP Messages. + * + * Currently this class uses a system parameter "jetty.direct.writers" to control + * two optional writer to byte conversions. buffer.writers=true will probably be + * faster, but will consume more memory. This option is just for testing and tuning. + * + */ +public abstract class AbstractGenerator implements Generator +{ + private static final Logger LOG = Log.getLogger(AbstractGenerator.class); + + // states + public final static int STATE_HEADER = 0; + public final static int STATE_CONTENT = 2; + public final static int STATE_FLUSHING = 3; + public final static int STATE_END = 4; + + public static final byte[] NO_BYTES = {}; + + // data + + protected final Buffers _buffers; // source of buffers + protected final EndPoint _endp; + + protected int _state = STATE_HEADER; + + protected int _status = 0; + protected int _version = HttpVersions.HTTP_1_1_ORDINAL; + protected Buffer _reason; + protected Buffer _method; + protected String _uri; + + protected long _contentWritten = 0; + protected long _contentLength = HttpTokens.UNKNOWN_CONTENT; + protected boolean _last = false; + protected boolean _head = false; + protected boolean _noContent = false; + protected Boolean _persistent = null; + + protected Buffer _header; // Buffer for HTTP header (and maybe small _content) + protected Buffer _buffer; // Buffer for copy of passed _content + protected Buffer _content; // Buffer passed to addContent + + protected Buffer _date; + + private boolean _sendServerVersion; + + + /* ------------------------------------------------------------------------------- */ + /** + * Constructor. + * + * @param buffers buffer pool + * @param io the end point + */ + public AbstractGenerator(Buffers buffers, EndPoint io) + { + this._buffers = buffers; + this._endp = io; + } + + /* ------------------------------------------------------------------------------- */ + public abstract boolean isRequest(); + + /* ------------------------------------------------------------------------------- */ + public abstract boolean isResponse(); + + /* ------------------------------------------------------------------------------- */ + public boolean isOpen() + { + return _endp.isOpen(); + } + + /* ------------------------------------------------------------------------------- */ + public void reset() + { + _state = STATE_HEADER; + _status = 0; + _version = HttpVersions.HTTP_1_1_ORDINAL; + _reason = null; + _last = false; + _head = false; + _noContent=false; + _persistent = null; + _contentWritten = 0; + _contentLength = HttpTokens.UNKNOWN_CONTENT; + _date = null; + + _content = null; + _method=null; + } + + /* ------------------------------------------------------------------------------- */ + public void returnBuffers() + { + if (_buffer!=null && _buffer.length()==0) + { + _buffers.returnBuffer(_buffer); + _buffer=null; + } + + if (_header!=null && _header.length()==0) + { + _buffers.returnBuffer(_header); + _header=null; + } + } + + /* ------------------------------------------------------------------------------- */ + public void resetBuffer() + { + if(_state>=STATE_FLUSHING) + throw new IllegalStateException("Flushed"); + + _last = false; + _persistent=null; + _contentWritten = 0; + _contentLength = HttpTokens.UNKNOWN_CONTENT; + _content=null; + if (_buffer!=null) + _buffer.clear(); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the contentBufferSize. + */ + public int getContentBufferSize() + { + if (_buffer==null) + _buffer=_buffers.getBuffer(); + return _buffer.capacity(); + } + + /* ------------------------------------------------------------ */ + /** + * @param contentBufferSize The contentBufferSize to set. + */ + public void increaseContentBufferSize(int contentBufferSize) + { + if (_buffer==null) + _buffer=_buffers.getBuffer(); + if (contentBufferSize > _buffer.capacity()) + { + Buffer nb = _buffers.getBuffer(contentBufferSize); + nb.put(_buffer); + _buffers.returnBuffer(_buffer); + _buffer = nb; + } + } + + /* ------------------------------------------------------------ */ + public Buffer getUncheckedBuffer() + { + return _buffer; + } + + /* ------------------------------------------------------------ */ + public boolean getSendServerVersion () + { + return _sendServerVersion; + } + + /* ------------------------------------------------------------ */ + public void setSendServerVersion (boolean sendServerVersion) + { + _sendServerVersion = sendServerVersion; + } + + /* ------------------------------------------------------------ */ + public int getState() + { + return _state; + } + + /* ------------------------------------------------------------ */ + public boolean isState(int state) + { + return _state == state; + } + + /* ------------------------------------------------------------ */ + public boolean isComplete() + { + return _state == STATE_END; + } + + /* ------------------------------------------------------------ */ + public boolean isIdle() + { + return _state == STATE_HEADER && _method==null && _status==0; + } + + /* ------------------------------------------------------------ */ + public boolean isCommitted() + { + return _state != STATE_HEADER; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the head. + */ + public boolean isHead() + { + return _head; + } + + /* ------------------------------------------------------------ */ + public void setContentLength(long value) + { + if (value<0) + _contentLength=HttpTokens.UNKNOWN_CONTENT; + else + _contentLength=value; + } + + /* ------------------------------------------------------------ */ + /** + * @param head The head to set. + */ + public void setHead(boolean head) + { + _head = head; + } + + /* ------------------------------------------------------------ */ + /** + * @return <code>false</code> if the connection should be closed after a request has been read, + * <code>true</code> if it should be used for additional requests. + */ + public boolean isPersistent() + { + return _persistent!=null + ?_persistent.booleanValue() + :(isRequest()?true:_version>HttpVersions.HTTP_1_0_ORDINAL); + } + + /* ------------------------------------------------------------ */ + public void setPersistent(boolean persistent) + { + _persistent=persistent; + } + + /* ------------------------------------------------------------ */ + /** + * @param version The version of the client the response is being sent to (NB. Not the version + * in the response, which is the version of the server). + */ + public void setVersion(int version) + { + if (_state != STATE_HEADER) + throw new IllegalStateException("STATE!=START "+_state); + _version = version; + if (_version==HttpVersions.HTTP_0_9_ORDINAL && _method!=null) + _noContent=true; + } + + /* ------------------------------------------------------------ */ + public int getVersion() + { + return _version; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.Generator#setDate(org.eclipse.jetty.io.Buffer) + */ + public void setDate(Buffer timeStampBuffer) + { + _date=timeStampBuffer; + } + + /* ------------------------------------------------------------ */ + /** + */ + public void setRequest(String method, String uri) + { + if (method==null || HttpMethods.GET.equals(method) ) + _method=HttpMethods.GET_BUFFER; + else + _method=HttpMethods.CACHE.lookup(method); + _uri=uri; + if (_version==HttpVersions.HTTP_0_9_ORDINAL) + _noContent=true; + } + + /* ------------------------------------------------------------ */ + /** + * @param status The status code to send. + * @param reason the status message to send. + */ + public void setResponse(int status, String reason) + { + if (_state != STATE_HEADER) throw new IllegalStateException("STATE!=START"); + _method=null; + _status = status; + if (reason!=null) + { + int len=reason.length(); + + // TODO don't hard code + if (len>1024) + len=1024; + _reason=new ByteArrayBuffer(len); + for (int i=0;i<len;i++) + { + char ch = reason.charAt(i); + if (ch!='\r'&&ch!='\n') + _reason.put((byte)ch); + else + _reason.put((byte)' '); + } + } + } + + /* ------------------------------------------------------------ */ + /** Prepare buffer for unchecked writes. + * Prepare the generator buffer to receive unchecked writes + * @return the available space in the buffer. + * @throws IOException + */ + public abstract int prepareUncheckedAddContent() throws IOException; + + /* ------------------------------------------------------------ */ + void uncheckedAddContent(int b) + { + _buffer.put((byte)b); + } + + /* ------------------------------------------------------------ */ + public void completeUncheckedAddContent() + { + if (_noContent) + { + if(_buffer!=null) + _buffer.clear(); + } + else + { + _contentWritten+=_buffer.length(); + if (_head) + _buffer.clear(); + } + } + + /* ------------------------------------------------------------ */ + public boolean isBufferFull() + { + if (_buffer != null && _buffer.space()==0) + { + if (_buffer.length()==0 && !_buffer.isImmutable()) + _buffer.compact(); + return _buffer.space()==0; + } + + return _content!=null && _content.length()>0; + } + + /* ------------------------------------------------------------ */ + public boolean isWritten() + { + return _contentWritten>0; + } + + /* ------------------------------------------------------------ */ + public boolean isAllContentWritten() + { + return _contentLength>=0 && _contentWritten>=_contentLength; + } + + /* ------------------------------------------------------------ */ + public abstract void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException; + + /* ------------------------------------------------------------ */ + /** + * Complete the message. + * + * @throws IOException + */ + public void complete() throws IOException + { + if (_state == STATE_HEADER) + { + throw new IllegalStateException("State==HEADER"); + } + + if (_contentLength >= 0 && _contentLength != _contentWritten && !_head) + { + if (LOG.isDebugEnabled()) + LOG.debug("ContentLength written=="+_contentWritten+" != contentLength=="+_contentLength); + _persistent = false; + } + } + + /* ------------------------------------------------------------ */ + public abstract int flushBuffer() throws IOException; + + + /* ------------------------------------------------------------ */ + public void flush(long maxIdleTime) throws IOException + { + // block until everything is flushed + long now=System.currentTimeMillis(); + long end=now+maxIdleTime; + Buffer content = _content; + Buffer buffer = _buffer; + if (content!=null && content.length()>0 || buffer!=null && buffer.length()>0 || isBufferFull()) + { + flushBuffer(); + + while (now<end && (content!=null && content.length()>0 ||buffer!=null && buffer.length()>0) && _endp.isOpen()&& !_endp.isOutputShutdown()) + { + blockForOutput(end-now); + now=System.currentTimeMillis(); + } + } + } + + /* ------------------------------------------------------------ */ + /** + * Utility method to send an error response. If the builder is not committed, this call is + * equivalent to a setResponse, addContent and complete call. + * + * @param code The error code + * @param reason The error reason + * @param content Contents of the error page + * @param close True if the connection should be closed + * @throws IOException if there is a problem flushing the response + */ + public void sendError(int code, String reason, String content, boolean close) throws IOException + { + if (close) + _persistent=false; + if (isCommitted()) + { + LOG.debug("sendError on committed: {} {}",code,reason); + } + else + { + LOG.debug("sendError: {} {}",code,reason); + setResponse(code, reason); + if (content != null) + { + completeHeader(null, false); + addContent(new View(new ByteArrayBuffer(content)), Generator.LAST); + } + else if (code>=400) + { + completeHeader(null, false); + addContent(new View(new ByteArrayBuffer("Error: "+(reason==null?(""+code):reason))), Generator.LAST); + } + else + { + completeHeader(null, true); + } + complete(); + } + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the contentWritten. + */ + public long getContentWritten() + { + return _contentWritten; + } + + + + /* ------------------------------------------------------------ */ + public void blockForOutput(long maxIdleTime) throws IOException + { + if (_endp.isBlocking()) + { + try + { + flushBuffer(); + } + catch(IOException e) + { + _endp.close(); + throw e; + } + } + else + { + if (!_endp.blockWritable(maxIdleTime)) + { + _endp.close(); + throw new EofException("timeout"); + } + + flushBuffer(); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/http/EncodedHttpURI.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,183 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.http; + +import java.io.UnsupportedEncodingException; + +import org.eclipse.jetty.util.MultiMap; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.UrlEncoded; +import org.eclipse.jetty.util.Utf8StringBuffer; + +public class EncodedHttpURI extends HttpURI +{ + private final String _encoding; + + public EncodedHttpURI(String encoding) + { + super(); + _encoding = encoding; + } + + + @Override + public String getScheme() + { + if (_scheme==_authority) + return null; + int l=_authority-_scheme; + if (l==5 && + _raw[_scheme]=='h' && + _raw[_scheme+1]=='t' && + _raw[_scheme+2]=='t' && + _raw[_scheme+3]=='p' ) + return HttpSchemes.HTTP; + if (l==6 && + _raw[_scheme]=='h' && + _raw[_scheme+1]=='t' && + _raw[_scheme+2]=='t' && + _raw[_scheme+3]=='p' && + _raw[_scheme+4]=='s' ) + return HttpSchemes.HTTPS; + + return StringUtil.toString(_raw,_scheme,_authority-_scheme-1,_encoding); + } + + @Override + public String getAuthority() + { + if (_authority==_path) + return null; + return StringUtil.toString(_raw,_authority,_path-_authority,_encoding); + } + + @Override + public String getHost() + { + if (_host==_port) + return null; + return StringUtil.toString(_raw,_host,_port-_host,_encoding); + } + + @Override + public int getPort() + { + if (_port==_path) + return -1; + return TypeUtil.parseInt(_raw, _port+1, _path-_port-1,10); + } + + @Override + public String getPath() + { + if (_path==_param) + return null; + return StringUtil.toString(_raw,_path,_param-_path,_encoding); + } + + @Override + public String getDecodedPath() + { + if (_path==_param) + return null; + return URIUtil.decodePath(_raw,_path,_param-_path); + } + + @Override + public String getPathAndParam() + { + if (_path==_query) + return null; + return StringUtil.toString(_raw,_path,_query-_path,_encoding); + } + + @Override + public String getCompletePath() + { + if (_path==_end) + return null; + return StringUtil.toString(_raw,_path,_end-_path,_encoding); + } + + @Override + public String getParam() + { + if (_param==_query) + return null; + return StringUtil.toString(_raw,_param+1,_query-_param-1,_encoding); + } + + @Override + public String getQuery() + { + if (_query==_fragment) + return null; + return StringUtil.toString(_raw,_query+1,_fragment-_query-1,_encoding); + } + + @Override + public boolean hasQuery() + { + return (_fragment>_query); + } + + @Override + public String getFragment() + { + if (_fragment==_end) + return null; + return StringUtil.toString(_raw,_fragment+1,_end-_fragment-1,_encoding); + } + + @Override + public void decodeQueryTo(MultiMap parameters) + { + if (_query==_fragment) + return; + UrlEncoded.decodeTo(StringUtil.toString(_raw,_query+1,_fragment-_query-1,_encoding),parameters,_encoding); + } + + @Override + public void decodeQueryTo(MultiMap parameters, String encoding) + throws UnsupportedEncodingException + { + if (_query==_fragment) + return; + + if (encoding==null) + encoding=_encoding; + UrlEncoded.decodeTo(StringUtil.toString(_raw,_query+1,_fragment-_query-1,encoding),parameters,encoding); + } + + @Override + public String toString() + { + if (_rawString==null) + _rawString= StringUtil.toString(_raw,_scheme,_end-_scheme,_encoding); + return _rawString; + } + + public void writeTo(Utf8StringBuffer buf) + { + buf.getStringBuffer().append(toString()); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/http/Generator.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,96 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.http; + +import java.io.IOException; + +import org.eclipse.jetty.io.Buffer; + +public interface Generator +{ + public static final boolean LAST=true; + public static final boolean MORE=false; + + /* ------------------------------------------------------------ */ + /** + * Add content. + * + * @param content + * @param last + * @throws IllegalArgumentException if <code>content</code> is {@link Buffer#isImmutable immutable}. + * @throws IllegalStateException If the request is not expecting any more content, + * or if the buffers are full and cannot be flushed. + * @throws IOException if there is a problem flushing the buffers. + */ + void addContent(Buffer content, boolean last) throws IOException; + + void complete() throws IOException; + + void completeHeader(HttpFields responseFields, boolean last) throws IOException; + + int flushBuffer() throws IOException; + + int getContentBufferSize(); + + long getContentWritten(); + + boolean isWritten(); + + boolean isAllContentWritten(); + + void increaseContentBufferSize(int size); + + boolean isBufferFull(); + + boolean isCommitted(); + + boolean isComplete(); + + boolean isPersistent(); + + void reset(); + + void resetBuffer(); + + void returnBuffers(); + + void sendError(int code, String reason, String content, boolean close) throws IOException; + + void setHead(boolean head); + + void setRequest(String method, String uri); + + void setResponse(int status, String reason); + + + void setSendServerVersion(boolean sendServerVersion); + + void setVersion(int version); + + boolean isIdle(); + + void setContentLength(long length); + + void setPersistent(boolean persistent); + + void setDate(Buffer timeStampBuffer); + + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/http/HttpBuffers.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,108 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.http; + +import org.eclipse.jetty.io.Buffers; +import org.eclipse.jetty.io.BuffersFactory; +import org.eclipse.jetty.util.component.AbstractLifeCycle; + +/* ------------------------------------------------------------ */ +/** Abstract Buffer pool. + */ +public interface HttpBuffers +{ + /** + * @return the requestBufferSize + */ + public int getRequestBufferSize(); + + /** + * @param requestBufferSize the requestBufferSize to set + */ + public void setRequestBufferSize(int requestBufferSize); + + /** + * @return the requestHeaderSize + */ + public int getRequestHeaderSize(); + + /** + * @param requestHeaderSize the requestHeaderSize to set + */ + public void setRequestHeaderSize(int requestHeaderSize); + + /** + * @return the responseBufferSize + */ + public int getResponseBufferSize(); + + /** + * @param responseBufferSize the responseBufferSize to set + */ + public void setResponseBufferSize(int responseBufferSize); + + /** + * @return the responseHeaderSize + */ + public int getResponseHeaderSize(); + + /** + * @param responseHeaderSize the responseHeaderSize to set + */ + public void setResponseHeaderSize(int responseHeaderSize); + + /** + * @return the requestBufferType + */ + public Buffers.Type getRequestBufferType(); + + /** + * @return the requestHeaderType + */ + public Buffers.Type getRequestHeaderType(); + + /** + * @return the responseBufferType + */ + public Buffers.Type getResponseBufferType(); + + /** + * @return the responseHeaderType + */ + public Buffers.Type getResponseHeaderType(); + + /** + * @param requestBuffers the requestBuffers to set + */ + public void setRequestBuffers(Buffers requestBuffers); + + /** + * @param responseBuffers the responseBuffers to set + */ + public void setResponseBuffers(Buffers responseBuffers); + + public Buffers getRequestBuffers(); + + public Buffers getResponseBuffers(); + + public void setMaxBuffers(int maxBuffers); + + public int getMaxBuffers(); + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/http/HttpBuffersImpl.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,238 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.http; + +import org.eclipse.jetty.io.Buffers; +import org.eclipse.jetty.io.BuffersFactory; +import org.eclipse.jetty.util.component.AbstractLifeCycle; + +/* ------------------------------------------------------------ */ +/** Abstract Buffer pool. + * simple unbounded pool of buffers for header, request and response sizes. + * + */ +public class HttpBuffersImpl extends AbstractLifeCycle implements HttpBuffers +{ + private int _requestBufferSize=16*1024; + private int _requestHeaderSize=6*1024; + private int _responseBufferSize=32*1024; + private int _responseHeaderSize=6*1024; + private int _maxBuffers=1024; + + private Buffers.Type _requestBufferType=Buffers.Type.BYTE_ARRAY; + private Buffers.Type _requestHeaderType=Buffers.Type.BYTE_ARRAY; + private Buffers.Type _responseBufferType=Buffers.Type.BYTE_ARRAY; + private Buffers.Type _responseHeaderType=Buffers.Type.BYTE_ARRAY; + + private Buffers _requestBuffers; + private Buffers _responseBuffers; + + + public HttpBuffersImpl() + { + super(); + } + + /** + * @return the requestBufferSize + */ + public int getRequestBufferSize() + { + return _requestBufferSize; + } + + /** + * @param requestBufferSize the requestBufferSize to set + */ + public void setRequestBufferSize(int requestBufferSize) + { + _requestBufferSize = requestBufferSize; + } + + /** + * @return the requestHeaderSize + */ + public int getRequestHeaderSize() + { + return _requestHeaderSize; + } + + /** + * @param requestHeaderSize the requestHeaderSize to set + */ + public void setRequestHeaderSize(int requestHeaderSize) + { + _requestHeaderSize = requestHeaderSize; + } + + /** + * @return the responseBufferSize + */ + public int getResponseBufferSize() + { + return _responseBufferSize; + } + + /** + * @param responseBufferSize the responseBufferSize to set + */ + public void setResponseBufferSize(int responseBufferSize) + { + _responseBufferSize = responseBufferSize; + } + + /** + * @return the responseHeaderSize + */ + public int getResponseHeaderSize() + { + return _responseHeaderSize; + } + + /** + * @param responseHeaderSize the responseHeaderSize to set + */ + public void setResponseHeaderSize(int responseHeaderSize) + { + _responseHeaderSize = responseHeaderSize; + } + + /** + * @return the requestBufferType + */ + public Buffers.Type getRequestBufferType() + { + return _requestBufferType; + } + + /** + * @param requestBufferType the requestBufferType to set + */ + public void setRequestBufferType(Buffers.Type requestBufferType) + { + _requestBufferType = requestBufferType; + } + + /** + * @return the requestHeaderType + */ + public Buffers.Type getRequestHeaderType() + { + return _requestHeaderType; + } + + /** + * @param requestHeaderType the requestHeaderType to set + */ + public void setRequestHeaderType(Buffers.Type requestHeaderType) + { + _requestHeaderType = requestHeaderType; + } + + /** + * @return the responseBufferType + */ + public Buffers.Type getResponseBufferType() + { + return _responseBufferType; + } + + /** + * @param responseBufferType the responseBufferType to set + */ + public void setResponseBufferType(Buffers.Type responseBufferType) + { + _responseBufferType = responseBufferType; + } + + /** + * @return the responseHeaderType + */ + public Buffers.Type getResponseHeaderType() + { + return _responseHeaderType; + } + + /** + * @param responseHeaderType the responseHeaderType to set + */ + public void setResponseHeaderType(Buffers.Type responseHeaderType) + { + _responseHeaderType = responseHeaderType; + } + + /** + * @param requestBuffers the requestBuffers to set + */ + public void setRequestBuffers(Buffers requestBuffers) + { + _requestBuffers = requestBuffers; + } + + /** + * @param responseBuffers the responseBuffers to set + */ + public void setResponseBuffers(Buffers responseBuffers) + { + _responseBuffers = responseBuffers; + } + + @Override + protected void doStart() + throws Exception + { + _requestBuffers=BuffersFactory.newBuffers(_requestHeaderType,_requestHeaderSize,_requestBufferType,_requestBufferSize,_requestBufferType,getMaxBuffers()); + _responseBuffers=BuffersFactory.newBuffers(_responseHeaderType,_responseHeaderSize,_responseBufferType,_responseBufferSize,_responseBufferType,getMaxBuffers()); + super.doStart(); + } + + @Override + protected void doStop() + throws Exception + { + _requestBuffers=null; + _responseBuffers=null; + } + + public Buffers getRequestBuffers() + { + return _requestBuffers; + } + + + public Buffers getResponseBuffers() + { + return _responseBuffers; + } + + public void setMaxBuffers(int maxBuffers) + { + _maxBuffers = maxBuffers; + } + + public int getMaxBuffers() + { + return _maxBuffers; + } + + public String toString() + { + return _requestBuffers+"/"+_responseBuffers; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/http/HttpContent.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,167 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.http; + +import java.io.IOException; +import java.io.InputStream; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.resource.Resource; + +/* ------------------------------------------------------------ */ +/** HttpContent. + * + * + */ +public interface HttpContent +{ + Buffer getContentType(); + Buffer getLastModified(); + Buffer getIndirectBuffer(); + Buffer getDirectBuffer(); + Buffer getETag(); + Resource getResource(); + long getContentLength(); + InputStream getInputStream() throws IOException; + void release(); + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + public class ResourceAsHttpContent implements HttpContent + { + private static final Logger LOG = Log.getLogger(ResourceAsHttpContent.class); + + final Resource _resource; + final Buffer _mimeType; + final int _maxBuffer; + final Buffer _etag; + + /* ------------------------------------------------------------ */ + public ResourceAsHttpContent(final Resource resource, final Buffer mimeType) + { + this(resource,mimeType,-1,false); + } + + /* ------------------------------------------------------------ */ + public ResourceAsHttpContent(final Resource resource, final Buffer mimeType, int maxBuffer) + { + this(resource,mimeType,maxBuffer,false); + } + + /* ------------------------------------------------------------ */ + public ResourceAsHttpContent(final Resource resource, final Buffer mimeType, boolean etag) + { + this(resource,mimeType,-1,etag); + } + + /* ------------------------------------------------------------ */ + public ResourceAsHttpContent(final Resource resource, final Buffer mimeType, int maxBuffer, boolean etag) + { + _resource=resource; + _mimeType=mimeType; + _maxBuffer=maxBuffer; + _etag=etag?new ByteArrayBuffer(resource.getWeakETag()):null; + } + + /* ------------------------------------------------------------ */ + public Buffer getContentType() + { + return _mimeType; + } + + /* ------------------------------------------------------------ */ + public Buffer getLastModified() + { + return null; + } + + /* ------------------------------------------------------------ */ + public Buffer getDirectBuffer() + { + return null; + } + + /* ------------------------------------------------------------ */ + public Buffer getETag() + { + return _etag; + } + + /* ------------------------------------------------------------ */ + public Buffer getIndirectBuffer() + { + InputStream inputStream = null; + try + { + if (_resource.length() <= 0 || _maxBuffer < _resource.length()) + return null; + ByteArrayBuffer buffer = new ByteArrayBuffer((int)_resource.length()); + inputStream = _resource.getInputStream(); + buffer.readFrom(inputStream,(int)_resource.length()); + return buffer; + } + catch (IOException e) + { + throw new RuntimeException(e); + } + finally + { + if (inputStream != null) + { + try + { + inputStream.close(); + } + catch (IOException e) + { + LOG.warn("Couldn't close inputStream. Possible file handle leak",e); + } + } + } + } + + /* ------------------------------------------------------------ */ + public long getContentLength() + { + return _resource.length(); + } + + /* ------------------------------------------------------------ */ + public InputStream getInputStream() throws IOException + { + return _resource.getInputStream(); + } + + /* ------------------------------------------------------------ */ + public Resource getResource() + { + return _resource; + } + + /* ------------------------------------------------------------ */ + public void release() + { + _resource.release(); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/http/HttpCookie.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,191 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.http; + +public class HttpCookie +{ + private final String _name; + private final String _value; + private final String _comment; + private final String _domain; + private final int _maxAge; + private final String _path; + private final boolean _secure; + private final int _version; + private final boolean _httpOnly; + + /* ------------------------------------------------------------ */ + public HttpCookie(String name, String value) + { + super(); + _name = name; + _value = value; + _comment = null; + _domain = null; + _httpOnly = false; + _maxAge = -1; + _path = null; + _secure = false; + _version = 0; + } + + /* ------------------------------------------------------------ */ + public HttpCookie(String name, String value, String domain, String path) + { + super(); + _name = name; + _value = value; + _comment = null; + _domain = domain; + _httpOnly = false; + _maxAge = -1; + _path = path; + _secure = false; + _version = 0; + + } + + /* ------------------------------------------------------------ */ + public HttpCookie(String name, String value, int maxAge) + { + super(); + _name = name; + _value = value; + _comment = null; + _domain = null; + _httpOnly = false; + _maxAge = maxAge; + _path = null; + _secure = false; + _version = 0; + } + + /* ------------------------------------------------------------ */ + public HttpCookie(String name, String value, String domain, String path, int maxAge, boolean httpOnly, boolean secure) + { + super(); + _comment = null; + _domain = domain; + _httpOnly = httpOnly; + _maxAge = maxAge; + _name = name; + _path = path; + _secure = secure; + _value = value; + _version = 0; + } + + /* ------------------------------------------------------------ */ + public HttpCookie(String name, String value, String domain, String path, int maxAge, boolean httpOnly, boolean secure, String comment, int version) + { + super(); + _comment = comment; + _domain = domain; + _httpOnly = httpOnly; + _maxAge = maxAge; + _name = name; + _path = path; + _secure = secure; + _value = value; + _version = version; + } + + /* ------------------------------------------------------------ */ + /** Get the name. + * @return the name + */ + public String getName() + { + return _name; + } + + /* ------------------------------------------------------------ */ + /** Get the value. + * @return the value + */ + public String getValue() + { + return _value; + } + + /* ------------------------------------------------------------ */ + /** Get the comment. + * @return the comment + */ + public String getComment() + { + return _comment; + } + + /* ------------------------------------------------------------ */ + /** Get the domain. + * @return the domain + */ + public String getDomain() + { + return _domain; + } + + /* ------------------------------------------------------------ */ + /** Get the maxAge. + * @return the maxAge + */ + public int getMaxAge() + { + return _maxAge; + } + + /* ------------------------------------------------------------ */ + /** Get the path. + * @return the path + */ + public String getPath() + { + return _path; + } + + /* ------------------------------------------------------------ */ + /** Get the secure. + * @return the secure + */ + public boolean isSecure() + { + return _secure; + } + + /* ------------------------------------------------------------ */ + /** Get the version. + * @return the version + */ + public int getVersion() + { + return _version; + } + + /* ------------------------------------------------------------ */ + /** Get the isHttpOnly. + * @return the isHttpOnly + */ + public boolean isHttpOnly() + { + return _httpOnly; + } + + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/http/HttpException.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,94 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.http; + +import java.io.IOException; + +public class HttpException extends IOException +{ + int _status; + String _reason; + + /* ------------------------------------------------------------ */ + public HttpException(int status) + { + _status=status; + _reason=null; + } + + /* ------------------------------------------------------------ */ + public HttpException(int status,String reason) + { + _status=status; + _reason=reason; + } + + /* ------------------------------------------------------------ */ + public HttpException(int status,String reason, Throwable rootCause) + { + _status=status; + _reason=reason; + initCause(rootCause); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the reason. + */ + public String getReason() + { + return _reason; + } + + /* ------------------------------------------------------------ */ + /** + * @param reason The reason to set. + */ + public void setReason(String reason) + { + _reason = reason; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the status. + */ + public int getStatus() + { + return _status; + } + + /* ------------------------------------------------------------ */ + /** + * @param status The status to set. + */ + public void setStatus(int status) + { + _status = status; + } + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + return ("HttpException("+_status+","+_reason+","+super.getCause()+")"); + } + + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/http/HttpFields.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,1406 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.http; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Collection; +import java.util.Date; +import java.util.Enumeration; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.StringTokenizer; +import java.util.TimeZone; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.BufferCache; +import org.eclipse.jetty.io.BufferCache.CachedBuffer; +import org.eclipse.jetty.io.BufferDateCache; +import org.eclipse.jetty.io.BufferUtil; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.QuotedStringTokenizer; +import org.eclipse.jetty.util.StringMap; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/* ------------------------------------------------------------ */ +/** + * HTTP Fields. A collection of HTTP header and or Trailer fields. + * + * <p>This class is not synchronized as it is expected that modifications will only be performed by a + * single thread. + * + * + */ +public class HttpFields +{ + private static final Logger LOG = Log.getLogger(HttpFields.class); + + /* ------------------------------------------------------------ */ + public static final String __COOKIE_DELIM="\"\\\n\r\t\f\b%+ ;="; + public static final TimeZone __GMT = TimeZone.getTimeZone("GMT"); + public static final BufferDateCache __dateCache = new BufferDateCache("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US); + + /* -------------------------------------------------------------- */ + static + { + __GMT.setID("GMT"); + __dateCache.setTimeZone(__GMT); + } + + /* ------------------------------------------------------------ */ + public final static String __separators = ", \t"; + + /* ------------------------------------------------------------ */ + private static final String[] DAYS = + { "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + private static final String[] MONTHS = + { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan"}; + + + /* ------------------------------------------------------------ */ + private static class DateGenerator + { + private final StringBuilder buf = new StringBuilder(32); + private final GregorianCalendar gc = new GregorianCalendar(__GMT); + + /** + * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'" + */ + public String formatDate(long date) + { + buf.setLength(0); + gc.setTimeInMillis(date); + + int day_of_week = gc.get(Calendar.DAY_OF_WEEK); + int day_of_month = gc.get(Calendar.DAY_OF_MONTH); + int month = gc.get(Calendar.MONTH); + int year = gc.get(Calendar.YEAR); + int century = year / 100; + year = year % 100; + + int hours = gc.get(Calendar.HOUR_OF_DAY); + int minutes = gc.get(Calendar.MINUTE); + int seconds = gc.get(Calendar.SECOND); + + buf.append(DAYS[day_of_week]); + buf.append(','); + buf.append(' '); + StringUtil.append2digits(buf, day_of_month); + + buf.append(' '); + buf.append(MONTHS[month]); + buf.append(' '); + StringUtil.append2digits(buf, century); + StringUtil.append2digits(buf, year); + + buf.append(' '); + StringUtil.append2digits(buf, hours); + buf.append(':'); + StringUtil.append2digits(buf, minutes); + buf.append(':'); + StringUtil.append2digits(buf, seconds); + buf.append(" GMT"); + return buf.toString(); + } + + /* ------------------------------------------------------------ */ + /** + * Format "EEE, dd-MMM-yy HH:mm:ss 'GMT'" for cookies + */ + public void formatCookieDate(StringBuilder buf, long date) + { + gc.setTimeInMillis(date); + + int day_of_week = gc.get(Calendar.DAY_OF_WEEK); + int day_of_month = gc.get(Calendar.DAY_OF_MONTH); + int month = gc.get(Calendar.MONTH); + int year = gc.get(Calendar.YEAR); + year = year % 10000; + + int epoch = (int) ((date / 1000) % (60 * 60 * 24)); + int seconds = epoch % 60; + epoch = epoch / 60; + int minutes = epoch % 60; + int hours = epoch / 60; + + buf.append(DAYS[day_of_week]); + buf.append(','); + buf.append(' '); + StringUtil.append2digits(buf, day_of_month); + + buf.append('-'); + buf.append(MONTHS[month]); + buf.append('-'); + StringUtil.append2digits(buf, year/100); + StringUtil.append2digits(buf, year%100); + + buf.append(' '); + StringUtil.append2digits(buf, hours); + buf.append(':'); + StringUtil.append2digits(buf, minutes); + buf.append(':'); + StringUtil.append2digits(buf, seconds); + buf.append(" GMT"); + } + } + + /* ------------------------------------------------------------ */ + private static final ThreadLocal<DateGenerator> __dateGenerator =new ThreadLocal<DateGenerator>() + { + @Override + protected DateGenerator initialValue() + { + return new DateGenerator(); + } + }; + + /* ------------------------------------------------------------ */ + /** + * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'" + */ + public static String formatDate(long date) + { + return __dateGenerator.get().formatDate(date); + } + + /* ------------------------------------------------------------ */ + /** + * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies + */ + public static void formatCookieDate(StringBuilder buf, long date) + { + __dateGenerator.get().formatCookieDate(buf,date); + } + + /* ------------------------------------------------------------ */ + /** + * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies + */ + public static String formatCookieDate(long date) + { + StringBuilder buf = new StringBuilder(28); + formatCookieDate(buf, date); + return buf.toString(); + } + + /* ------------------------------------------------------------ */ + private final static String __dateReceiveFmt[] = + { + "EEE, dd MMM yyyy HH:mm:ss zzz", + "EEE, dd-MMM-yy HH:mm:ss", + "EEE MMM dd HH:mm:ss yyyy", + + "EEE, dd MMM yyyy HH:mm:ss", "EEE dd MMM yyyy HH:mm:ss zzz", + "EEE dd MMM yyyy HH:mm:ss", "EEE MMM dd yyyy HH:mm:ss zzz", "EEE MMM dd yyyy HH:mm:ss", + "EEE MMM-dd-yyyy HH:mm:ss zzz", "EEE MMM-dd-yyyy HH:mm:ss", "dd MMM yyyy HH:mm:ss zzz", + "dd MMM yyyy HH:mm:ss", "dd-MMM-yy HH:mm:ss zzz", "dd-MMM-yy HH:mm:ss", "MMM dd HH:mm:ss yyyy zzz", + "MMM dd HH:mm:ss yyyy", "EEE MMM dd HH:mm:ss yyyy zzz", + "EEE, MMM dd HH:mm:ss yyyy zzz", "EEE, MMM dd HH:mm:ss yyyy", "EEE, dd-MMM-yy HH:mm:ss zzz", + "EEE dd-MMM-yy HH:mm:ss zzz", "EEE dd-MMM-yy HH:mm:ss", + }; + + /* ------------------------------------------------------------ */ + private static class DateParser + { + final SimpleDateFormat _dateReceive[]= new SimpleDateFormat[__dateReceiveFmt.length]; + + long parse(final String dateVal) + { + for (int i = 0; i < _dateReceive.length; i++) + { + if (_dateReceive[i] == null) + { + _dateReceive[i] = new SimpleDateFormat(__dateReceiveFmt[i], Locale.US); + _dateReceive[i].setTimeZone(__GMT); + } + + try + { + Date date = (Date) _dateReceive[i].parseObject(dateVal); + return date.getTime(); + } + catch (java.lang.Exception e) + { + // LOG.ignore(e); + } + } + + if (dateVal.endsWith(" GMT")) + { + final String val = dateVal.substring(0, dateVal.length() - 4); + + for (int i = 0; i < _dateReceive.length; i++) + { + try + { + Date date = (Date) _dateReceive[i].parseObject(val); + return date.getTime(); + } + catch (java.lang.Exception e) + { + // LOG.ignore(e); + } + } + } + return -1; + } + } + + /* ------------------------------------------------------------ */ + public static long parseDate(String date) + { + return __dateParser.get().parse(date); + } + + /* ------------------------------------------------------------ */ + private static final ThreadLocal<DateParser> __dateParser =new ThreadLocal<DateParser>() + { + @Override + protected DateParser initialValue() + { + return new DateParser(); + } + }; + + /* -------------------------------------------------------------- */ + public final static String __01Jan1970=formatDate(0); + public final static Buffer __01Jan1970_BUFFER=new ByteArrayBuffer(__01Jan1970); + public final static String __01Jan1970_COOKIE = formatCookieDate(0).trim(); + + /* -------------------------------------------------------------- */ + private final ArrayList<Field> _fields = new ArrayList<Field>(20); + private final HashMap<Buffer,Field> _names = new HashMap<Buffer,Field>(32); + + /* ------------------------------------------------------------ */ + /** + * Constructor. + */ + public HttpFields() + { + } + + // TODO externalize this cache so it can be configurable + private static ConcurrentMap<String, Buffer> __cache = new ConcurrentHashMap<String, Buffer>(); + private static int __cacheSize = Integer.getInteger("org.eclipse.jetty.http.HttpFields.CACHE",2000); + + /* -------------------------------------------------------------- */ + private Buffer convertValue(String value) + { + Buffer buffer = __cache.get(value); + if (buffer!=null) + return buffer; + + try + { + buffer = new ByteArrayBuffer(value,StringUtil.__ISO_8859_1); + + if (__cacheSize>0) + { + if (__cache.size()>__cacheSize) + __cache.clear(); + Buffer b=__cache.putIfAbsent(value,buffer); + if (b!=null) + buffer=b; + } + + return buffer; + } + catch (UnsupportedEncodingException e) + { + throw new RuntimeException(e); + } + } + + /* -------------------------------------------------------------- */ + /** + * Get Collection of header names. + */ + public Collection<String> getFieldNamesCollection() + { + final List<String> list = new ArrayList<String>(_fields.size()); + + for (Field f : _fields) + { + if (f!=null) + list.add(BufferUtil.to8859_1_String(f._name)); + } + return list; + } + + /* -------------------------------------------------------------- */ + /** + * Get enumeration of header _names. Returns an enumeration of strings representing the header + * _names for this request. + */ + public Enumeration<String> getFieldNames() + { + final Enumeration<?> buffers = Collections.enumeration(_names.keySet()); + return new Enumeration<String>() + { + public String nextElement() + { + return buffers.nextElement().toString(); + } + + public boolean hasMoreElements() + { + return buffers.hasMoreElements(); + } + }; + } + + /* ------------------------------------------------------------ */ + public int size() + { + return _fields.size(); + } + + /* ------------------------------------------------------------ */ + /** + * Get a Field by index. + * @return A Field value or null if the Field value has not been set + * + */ + public Field getField(int i) + { + return _fields.get(i); + } + + /* ------------------------------------------------------------ */ + private Field getField(String name) + { + return _names.get(HttpHeaders.CACHE.lookup(name)); + } + + /* ------------------------------------------------------------ */ + private Field getField(Buffer name) + { + return _names.get(HttpHeaders.CACHE.lookup(name)); + } + + /* ------------------------------------------------------------ */ + public boolean containsKey(Buffer name) + { + return _names.containsKey(HttpHeaders.CACHE.lookup(name)); + } + + /* ------------------------------------------------------------ */ + public boolean containsKey(String name) + { + return _names.containsKey(HttpHeaders.CACHE.lookup(name)); + } + + /* -------------------------------------------------------------- */ + /** + * @return the value of a field, or null if not found. For multiple fields of the same name, + * only the first is returned. + * @param name the case-insensitive field name + */ + public String getStringField(String name) + { + Field field = getField(name); + return field==null?null:field.getValue(); + } + + /* -------------------------------------------------------------- */ + /** + * @return the value of a field, or null if not found. For multiple fields of the same name, + * only the first is returned. + * @param name the case-insensitive field name + */ + public String getStringField(Buffer name) + { + Field field = getField(name); + return field==null?null:field.getValue(); + } + + /* -------------------------------------------------------------- */ + /** + * @return the value of a field, or null if not found. For multiple fields of the same name, + * only the first is returned. + * @param name the case-insensitive field name + */ + public Buffer get(Buffer name) + { + Field field = getField(name); + return field==null?null:field._value; + } + + + /* -------------------------------------------------------------- */ + /** + * Get multi headers + * + * @return Enumeration of the values, or null if no such header. + * @param name the case-insensitive field name + */ + public Collection<String> getValuesCollection(String name) + { + Field field = getField(name); + if (field==null) + return null; + + final List<String> list = new ArrayList<String>(); + + while(field!=null) + { + list.add(field.getValue()); + field=field._next; + } + return list; + } + + /* -------------------------------------------------------------- */ + /** + * Get multi headers + * + * @return Enumeration of the values + * @param name the case-insensitive field name + */ + public Enumeration<String> getValues(String name) + { + final Field field = getField(name); + if (field == null) + { + List<String> empty=Collections.emptyList(); + return Collections.enumeration(empty); + } + + return new Enumeration<String>() + { + Field f = field; + + public boolean hasMoreElements() + { + return f != null; + } + + public String nextElement() throws NoSuchElementException + { + if (f == null) throw new NoSuchElementException(); + Field n = f; + f = f._next; + return n.getValue(); + } + }; + } + + /* -------------------------------------------------------------- */ + /** + * Get multi headers + * + * @return Enumeration of the value Strings + * @param name the case-insensitive field name + */ + public Enumeration<String> getValues(Buffer name) + { + final Field field = getField(name); + if (field == null) + { + List<String> empty=Collections.emptyList(); + return Collections.enumeration(empty); + } + + return new Enumeration<String>() + { + Field f = field; + + public boolean hasMoreElements() + { + return f != null; + } + + public String nextElement() throws NoSuchElementException + { + if (f == null) throw new NoSuchElementException(); + Field n = f; + f = f._next; + return n.getValue(); + } + }; + } + + /* -------------------------------------------------------------- */ + /** + * Get multi field values with separator. The multiple values can be represented as separate + * headers of the same name, or by a single header using the separator(s), or a combination of + * both. Separators may be quoted. + * + * @param name the case-insensitive field name + * @param separators String of separators. + * @return Enumeration of the values, or null if no such header. + */ + public Enumeration<String> getValues(String name, final String separators) + { + final Enumeration<String> e = getValues(name); + if (e == null) + return null; + return new Enumeration<String>() + { + QuotedStringTokenizer tok = null; + + public boolean hasMoreElements() + { + if (tok != null && tok.hasMoreElements()) return true; + while (e.hasMoreElements()) + { + String value = e.nextElement(); + tok = new QuotedStringTokenizer(value, separators, false, false); + if (tok.hasMoreElements()) return true; + } + tok = null; + return false; + } + + public String nextElement() throws NoSuchElementException + { + if (!hasMoreElements()) throw new NoSuchElementException(); + String next = (String) tok.nextElement(); + if (next != null) next = next.trim(); + return next; + } + }; + } + + + /* -------------------------------------------------------------- */ + /** + * Set a field. + * + * @param name the name of the field + * @param value the value of the field. If null the field is cleared. + */ + public void put(String name, String value) + { + if (value==null) + remove(name); + else + { + Buffer n = HttpHeaders.CACHE.lookup(name); + Buffer v = convertValue(value); + put(n, v); + } + } + + /* -------------------------------------------------------------- */ + /** + * Set a field. + * + * @param name the name of the field + * @param value the value of the field. If null the field is cleared. + */ + public void put(Buffer name, String value) + { + Buffer n = HttpHeaders.CACHE.lookup(name); + Buffer v = convertValue(value); + put(n, v); + } + + /* -------------------------------------------------------------- */ + /** + * Set a field. + * + * @param name the name of the field + * @param value the value of the field. If null the field is cleared. + */ + public void put(Buffer name, Buffer value) + { + remove(name); + if (value == null) + return; + + if (!(name instanceof BufferCache.CachedBuffer)) + name = HttpHeaders.CACHE.lookup(name); + if (!(value instanceof CachedBuffer)) + value= HttpHeaderValues.CACHE.lookup(value).asImmutableBuffer(); + + // new value; + Field field = new Field(name, value); + _fields.add(field); + _names.put(name, field); + } + + /* -------------------------------------------------------------- */ + /** + * Set a field. + * + * @param name the name of the field + * @param list the List value of the field. If null the field is cleared. + */ + public void put(String name, List<?> list) + { + if (list == null || list.size() == 0) + { + remove(name); + return; + } + Buffer n = HttpHeaders.CACHE.lookup(name); + + Object v = list.get(0); + if (v != null) + put(n, HttpHeaderValues.CACHE.lookup(v.toString())); + else + remove(n); + + if (list.size() > 1) + { + java.util.Iterator<?> iter = list.iterator(); + iter.next(); + while (iter.hasNext()) + { + v = iter.next(); + if (v != null) put(n, HttpHeaderValues.CACHE.lookup(v.toString())); + } + } + } + + /* -------------------------------------------------------------- */ + /** + * Add to or set a field. If the field is allowed to have multiple values, add will add multiple + * headers of the same name. + * + * @param name the name of the field + * @param value the value of the field. + * @exception IllegalArgumentException If the name is a single valued field and already has a + * value. + */ + public void add(String name, String value) throws IllegalArgumentException + { + if (value==null) + return; + Buffer n = HttpHeaders.CACHE.lookup(name); + Buffer v = convertValue(value); + add(n, v); + } + + /* -------------------------------------------------------------- */ + /** + * Add to or set a field. If the field is allowed to have multiple values, add will add multiple + * headers of the same name. + * + * @param name the name of the field + * @param value the value of the field. + * @exception IllegalArgumentException If the name is a single valued field and already has a + * value. + */ + public void add(Buffer name, Buffer value) throws IllegalArgumentException + { + if (value == null) throw new IllegalArgumentException("null value"); + + if (!(name instanceof CachedBuffer)) + name = HttpHeaders.CACHE.lookup(name); + name=name.asImmutableBuffer(); + + if (!(value instanceof CachedBuffer) && HttpHeaderValues.hasKnownValues(HttpHeaders.CACHE.getOrdinal(name))) + value= HttpHeaderValues.CACHE.lookup(value); + value=value.asImmutableBuffer(); + + Field field = _names.get(name); + Field last = null; + while (field != null) + { + last = field; + field = field._next; + } + + // create the field + field = new Field(name, value); + _fields.add(field); + + // look for chain to add too + if (last != null) + last._next = field; + else + _names.put(name, field); + } + + /* ------------------------------------------------------------ */ + /** + * Remove a field. + * + * @param name + */ + public void remove(String name) + { + remove(HttpHeaders.CACHE.lookup(name)); + } + + /* ------------------------------------------------------------ */ + /** + * Remove a field. + * + * @param name + */ + public void remove(Buffer name) + { + if (!(name instanceof BufferCache.CachedBuffer)) + name = HttpHeaders.CACHE.lookup(name); + Field field = _names.remove(name); + while (field != null) + { + _fields.remove(field); + field = field._next; + } + } + + /* -------------------------------------------------------------- */ + /** + * Get a header as an long value. Returns the value of an integer field or -1 if not found. The + * case of the field name is ignored. + * + * @param name the case-insensitive field name + * @exception NumberFormatException If bad long found + */ + public long getLongField(String name) throws NumberFormatException + { + Field field = getField(name); + return field==null?-1L:field.getLongValue(); + } + + /* -------------------------------------------------------------- */ + /** + * Get a header as an long value. Returns the value of an integer field or -1 if not found. The + * case of the field name is ignored. + * + * @param name the case-insensitive field name + * @exception NumberFormatException If bad long found + */ + public long getLongField(Buffer name) throws NumberFormatException + { + Field field = getField(name); + return field==null?-1L:field.getLongValue(); + } + + /* -------------------------------------------------------------- */ + /** + * Get a header as a date value. Returns the value of a date field, or -1 if not found. The case + * of the field name is ignored. + * + * @param name the case-insensitive field name + */ + public long getDateField(String name) + { + Field field = getField(name); + if (field == null) + return -1; + + String val = valueParameters(BufferUtil.to8859_1_String(field._value), null); + if (val == null) + return -1; + + final long date = __dateParser.get().parse(val); + if (date==-1) + throw new IllegalArgumentException("Cannot convert date: " + val); + return date; + } + + /* -------------------------------------------------------------- */ + /** + * Sets the value of an long field. + * + * @param name the field name + * @param value the field long value + */ + public void putLongField(Buffer name, long value) + { + Buffer v = BufferUtil.toBuffer(value); + put(name, v); + } + + /* -------------------------------------------------------------- */ + /** + * Sets the value of an long field. + * + * @param name the field name + * @param value the field long value + */ + public void putLongField(String name, long value) + { + Buffer n = HttpHeaders.CACHE.lookup(name); + Buffer v = BufferUtil.toBuffer(value); + put(n, v); + } + + /* -------------------------------------------------------------- */ + /** + * Sets the value of an long field. + * + * @param name the field name + * @param value the field long value + */ + public void addLongField(String name, long value) + { + Buffer n = HttpHeaders.CACHE.lookup(name); + Buffer v = BufferUtil.toBuffer(value); + add(n, v); + } + + /* -------------------------------------------------------------- */ + /** + * Sets the value of an long field. + * + * @param name the field name + * @param value the field long value + */ + public void addLongField(Buffer name, long value) + { + Buffer v = BufferUtil.toBuffer(value); + add(name, v); + } + + /* -------------------------------------------------------------- */ + /** + * Sets the value of a date field. + * + * @param name the field name + * @param date the field date value + */ + public void putDateField(Buffer name, long date) + { + String d=formatDate(date); + Buffer v = new ByteArrayBuffer(d); + put(name, v); + } + + /* -------------------------------------------------------------- */ + /** + * Sets the value of a date field. + * + * @param name the field name + * @param date the field date value + */ + public void putDateField(String name, long date) + { + Buffer n = HttpHeaders.CACHE.lookup(name); + putDateField(n,date); + } + + /* -------------------------------------------------------------- */ + /** + * Sets the value of a date field. + * + * @param name the field name + * @param date the field date value + */ + public void addDateField(String name, long date) + { + String d=formatDate(date); + Buffer n = HttpHeaders.CACHE.lookup(name); + Buffer v = new ByteArrayBuffer(d); + add(n, v); + } + + /* ------------------------------------------------------------ */ + /** + * Format a set cookie value + * + * @param cookie The cookie. + */ + public void addSetCookie(HttpCookie cookie) + { + addSetCookie( + cookie.getName(), + cookie.getValue(), + cookie.getDomain(), + cookie.getPath(), + cookie.getMaxAge(), + cookie.getComment(), + cookie.isSecure(), + cookie.isHttpOnly(), + cookie.getVersion()); + } + + /** + * Format a set cookie value + * + * @param name the name + * @param value the value + * @param domain the domain + * @param path the path + * @param maxAge the maximum age + * @param comment the comment (only present on versions > 0) + * @param isSecure true if secure cookie + * @param isHttpOnly true if for http only + * @param version version of cookie logic to use (0 == default behavior) + */ + public void addSetCookie( + final String name, + final String value, + final String domain, + final String path, + final long maxAge, + final String comment, + final boolean isSecure, + final boolean isHttpOnly, + int version) + { + String delim=__COOKIE_DELIM; + + // Check arguments + if (name == null || name.length() == 0) + throw new IllegalArgumentException("Bad cookie name"); + + // Format value and params + StringBuilder buf = new StringBuilder(128); + String name_value_params; + QuotedStringTokenizer.quoteIfNeeded(buf, name, delim); + buf.append('='); + String start=buf.toString(); + boolean hasDomain = false; + boolean hasPath = false; + + if (value != null && value.length() > 0) + QuotedStringTokenizer.quoteIfNeeded(buf, value, delim); + + if (comment != null && comment.length() > 0) + { + buf.append(";Comment="); + QuotedStringTokenizer.quoteIfNeeded(buf, comment, delim); + } + + if (path != null && path.length() > 0) + { + hasPath = true; + buf.append(";Path="); + if (path.trim().startsWith("\"")) + buf.append(path); + else + QuotedStringTokenizer.quoteIfNeeded(buf,path,delim); + } + if (domain != null && domain.length() > 0) + { + hasDomain = true; + buf.append(";Domain="); + QuotedStringTokenizer.quoteIfNeeded(buf,domain.toLowerCase(Locale.ENGLISH),delim); + } + + if (maxAge >= 0) + { + // Always add the expires param as some browsers still don't handle max-age + buf.append(";Expires="); + if (maxAge == 0) + buf.append(__01Jan1970_COOKIE); + else + formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge); + + if (version >0) + { + buf.append(";Max-Age="); + buf.append(maxAge); + } + } + + if (isSecure) + buf.append(";Secure"); + if (isHttpOnly) + buf.append(";HttpOnly"); + + name_value_params = buf.toString(); + + // remove existing set-cookie of same name + Field field = getField(HttpHeaders.SET_COOKIE); + Field last=null; + while (field!=null) + { + String val = (field._value == null ? null : field._value.toString()); + if (val!=null && val.startsWith(start)) + { + //existing cookie has same name, does it also match domain and path? + if (((!hasDomain && !val.contains("Domain")) || (hasDomain && val.contains("Domain="+domain))) && + ((!hasPath && !val.contains("Path")) || (hasPath && val.contains("Path="+path)))) + { + _fields.remove(field); + if (last==null) + _names.put(HttpHeaders.SET_COOKIE_BUFFER,field._next); + else + last._next=field._next; + break; + } + } + last=field; + field=field._next; + } + + add(HttpHeaders.SET_COOKIE_BUFFER, new ByteArrayBuffer(name_value_params)); + + // Expire responses with set-cookie headers so they do not get cached. + put(HttpHeaders.EXPIRES_BUFFER, __01Jan1970_BUFFER); + } + + /* -------------------------------------------------------------- */ + public void putTo(Buffer buffer) throws IOException + { + for (int i = 0; i < _fields.size(); i++) + { + Field field = _fields.get(i); + if (field != null) + field.putTo(buffer); + } + BufferUtil.putCRLF(buffer); + } + + /* -------------------------------------------------------------- */ + public String toString() + { + try + { + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < _fields.size(); i++) + { + Field field = (Field) _fields.get(i); + if (field != null) + { + String tmp = field.getName(); + if (tmp != null) buffer.append(tmp); + buffer.append(": "); + tmp = field.getValue(); + if (tmp != null) buffer.append(tmp); + buffer.append("\r\n"); + } + } + buffer.append("\r\n"); + return buffer.toString(); + } + catch (Exception e) + { + LOG.warn(e); + return e.toString(); + } + } + + /* ------------------------------------------------------------ */ + /** + * Clear the header. + */ + public void clear() + { + _fields.clear(); + _names.clear(); + } + + /* ------------------------------------------------------------ */ + /** + * Add fields from another HttpFields instance. Single valued fields are replaced, while all + * others are added. + * + * @param fields + */ + public void add(HttpFields fields) + { + if (fields == null) return; + + Enumeration e = fields.getFieldNames(); + while (e.hasMoreElements()) + { + String name = (String) e.nextElement(); + Enumeration values = fields.getValues(name); + while (values.hasMoreElements()) + add(name, (String) values.nextElement()); + } + } + + /* ------------------------------------------------------------ */ + /** + * Get field value parameters. Some field values can have parameters. This method separates the + * value from the parameters and optionally populates a map with the parameters. For example: + * + * <PRE> + * + * FieldName : Value ; param1=val1 ; param2=val2 + * + * </PRE> + * + * @param value The Field value, possibly with parameteres. + * @param parameters A map to populate with the parameters, or null + * @return The value. + */ + public static String valueParameters(String value, Map<String,String> parameters) + { + if (value == null) return null; + + int i = value.indexOf(';'); + if (i < 0) return value; + if (parameters == null) return value.substring(0, i).trim(); + + StringTokenizer tok1 = new QuotedStringTokenizer(value.substring(i), ";", false, true); + while (tok1.hasMoreTokens()) + { + String token = tok1.nextToken(); + StringTokenizer tok2 = new QuotedStringTokenizer(token, "= "); + if (tok2.hasMoreTokens()) + { + String paramName = tok2.nextToken(); + String paramVal = null; + if (tok2.hasMoreTokens()) paramVal = tok2.nextToken(); + parameters.put(paramName, paramVal); + } + } + + return value.substring(0, i).trim(); + } + + /* ------------------------------------------------------------ */ + private static final Float __one = new Float("1.0"); + private static final Float __zero = new Float("0.0"); + private static final StringMap __qualities = new StringMap(); + static + { + __qualities.put(null, __one); + __qualities.put("1.0", __one); + __qualities.put("1", __one); + __qualities.put("0.9", new Float("0.9")); + __qualities.put("0.8", new Float("0.8")); + __qualities.put("0.7", new Float("0.7")); + __qualities.put("0.66", new Float("0.66")); + __qualities.put("0.6", new Float("0.6")); + __qualities.put("0.5", new Float("0.5")); + __qualities.put("0.4", new Float("0.4")); + __qualities.put("0.33", new Float("0.33")); + __qualities.put("0.3", new Float("0.3")); + __qualities.put("0.2", new Float("0.2")); + __qualities.put("0.1", new Float("0.1")); + __qualities.put("0", __zero); + __qualities.put("0.0", __zero); + } + + /* ------------------------------------------------------------ */ + public static Float getQuality(String value) + { + if (value == null) return __zero; + + int qe = value.indexOf(";"); + if (qe++ < 0 || qe == value.length()) return __one; + + if (value.charAt(qe++) == 'q') + { + qe++; + Map.Entry entry = __qualities.getEntry(value, qe, value.length() - qe); + if (entry != null) return (Float) entry.getValue(); + } + + HashMap params = new HashMap(3); + valueParameters(value, params); + String qs = (String) params.get("q"); + Float q = (Float) __qualities.get(qs); + if (q == null) + { + try + { + q = new Float(qs); + } + catch (Exception e) + { + q = __one; + } + } + return q; + } + + /* ------------------------------------------------------------ */ + /** + * List values in quality order. + * + * @param e Enumeration of values with quality parameters + * @return values in quality order. + */ + public static List qualityList(Enumeration e) + { + if (e == null || !e.hasMoreElements()) return Collections.EMPTY_LIST; + + Object list = null; + Object qual = null; + + // Assume list will be well ordered and just add nonzero + while (e.hasMoreElements()) + { + String v = e.nextElement().toString(); + Float q = getQuality(v); + + if (q.floatValue() >= 0.001) + { + list = LazyList.add(list, v); + qual = LazyList.add(qual, q); + } + } + + List vl = LazyList.getList(list, false); + if (vl.size() < 2) return vl; + + List ql = LazyList.getList(qual, false); + + // sort list with swaps + Float last = __zero; + for (int i = vl.size(); i-- > 0;) + { + Float q = (Float) ql.get(i); + if (last.compareTo(q) > 0) + { + Object tmp = vl.get(i); + vl.set(i, vl.get(i + 1)); + vl.set(i + 1, tmp); + ql.set(i, ql.get(i + 1)); + ql.set(i + 1, q); + last = __zero; + i = vl.size(); + continue; + } + last = q; + } + ql.clear(); + return vl; + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + public static final class Field + { + private Buffer _name; + private Buffer _value; + private Field _next; + + /* ------------------------------------------------------------ */ + private Field(Buffer name, Buffer value) + { + _name = name; + _value = value; + _next = null; + } + + /* ------------------------------------------------------------ */ + public void putTo(Buffer buffer) throws IOException + { + int o=(_name instanceof CachedBuffer)?((CachedBuffer)_name).getOrdinal():-1; + if (o>=0) + buffer.put(_name); + else + { + int s=_name.getIndex(); + int e=_name.putIndex(); + while (s<e) + { + byte b=_name.peek(s++); + switch(b) + { + case '\r': + case '\n': + case ':' : + continue; + default: + buffer.put(b); + } + } + } + + buffer.put((byte) ':'); + buffer.put((byte) ' '); + + o=(_value instanceof CachedBuffer)?((CachedBuffer)_value).getOrdinal():-1; + if (o>=0) + buffer.put(_value); + else + { + int s=_value.getIndex(); + int e=_value.putIndex(); + while (s<e) + { + byte b=_value.peek(s++); + switch(b) + { + case '\r': + case '\n': + continue; + default: + buffer.put(b); + } + } + } + + BufferUtil.putCRLF(buffer); + } + + /* ------------------------------------------------------------ */ + public String getName() + { + return BufferUtil.to8859_1_String(_name); + } + + /* ------------------------------------------------------------ */ + Buffer getNameBuffer() + { + return _name; + } + + /* ------------------------------------------------------------ */ + public int getNameOrdinal() + { + return HttpHeaders.CACHE.getOrdinal(_name); + } + + /* ------------------------------------------------------------ */ + public String getValue() + { + return BufferUtil.to8859_1_String(_value); + } + + /* ------------------------------------------------------------ */ + public Buffer getValueBuffer() + { + return _value; + } + + /* ------------------------------------------------------------ */ + public int getValueOrdinal() + { + return HttpHeaderValues.CACHE.getOrdinal(_value); + } + + /* ------------------------------------------------------------ */ + public int getIntValue() + { + return (int) getLongValue(); + } + + /* ------------------------------------------------------------ */ + public long getLongValue() + { + return BufferUtil.toLong(_value); + } + + /* ------------------------------------------------------------ */ + public String toString() + { + return ("[" + getName() + "=" + _value + (_next == null ? "" : "->") + "]"); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/http/HttpGenerator.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,1092 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.http; + +import java.io.IOException; +import java.io.InterruptedIOException; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.BufferCache.CachedBuffer; +import org.eclipse.jetty.io.BufferUtil; +import org.eclipse.jetty.io.Buffers; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/* ------------------------------------------------------------ */ +/** + * HttpGenerator. Builds HTTP Messages. + * + * + * + */ +public class HttpGenerator extends AbstractGenerator +{ + private static final Logger LOG = Log.getLogger(HttpGenerator.class); + + // Build cache of response lines for status + private static class Status + { + Buffer _reason; + Buffer _schemeCode; + Buffer _responseLine; + } + private static final Status[] __status = new Status[HttpStatus.MAX_CODE+1]; + static + { + int versionLength=HttpVersions.HTTP_1_1_BUFFER.length(); + + for (int i=0;i<__status.length;i++) + { + HttpStatus.Code code = HttpStatus.getCode(i); + if (code==null) + continue; + String reason=code.getMessage(); + byte[] bytes=new byte[versionLength+5+reason.length()+2]; + HttpVersions.HTTP_1_1_BUFFER.peek(0,bytes, 0, versionLength); + bytes[versionLength+0]=' '; + bytes[versionLength+1]=(byte)('0'+i/100); + bytes[versionLength+2]=(byte)('0'+(i%100)/10); + bytes[versionLength+3]=(byte)('0'+(i%10)); + bytes[versionLength+4]=' '; + for (int j=0;j<reason.length();j++) + bytes[versionLength+5+j]=(byte)reason.charAt(j); + bytes[versionLength+5+reason.length()]=HttpTokens.CARRIAGE_RETURN; + bytes[versionLength+6+reason.length()]=HttpTokens.LINE_FEED; + + __status[i] = new Status(); + __status[i]._reason=new ByteArrayBuffer(bytes,versionLength+5,bytes.length-versionLength-7,Buffer.IMMUTABLE); + __status[i]._schemeCode=new ByteArrayBuffer(bytes,0,versionLength+5,Buffer.IMMUTABLE); + __status[i]._responseLine=new ByteArrayBuffer(bytes,0,bytes.length,Buffer.IMMUTABLE); + } + } + + /* ------------------------------------------------------------------------------- */ + public static Buffer getReasonBuffer(int code) + { + Status status = code<__status.length?__status[code]:null; + if (status!=null) + return status._reason; + return null; + } + + + // common _content + private static final byte[] LAST_CHUNK = + { (byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012'}; + private static final byte[] CONTENT_LENGTH_0 = StringUtil.getBytes("Content-Length: 0\015\012"); + private static final byte[] CONNECTION_KEEP_ALIVE = StringUtil.getBytes("Connection: keep-alive\015\012"); + private static final byte[] CONNECTION_CLOSE = StringUtil.getBytes("Connection: close\015\012"); + private static final byte[] CONNECTION_ = StringUtil.getBytes("Connection: "); + private static final byte[] CRLF = StringUtil.getBytes("\015\012"); + private static final byte[] TRANSFER_ENCODING_CHUNKED = StringUtil.getBytes("Transfer-Encoding: chunked\015\012"); + private static byte[] SERVER = StringUtil.getBytes("Server: Jetty(7.0.x)\015\012"); + + // other statics + private static final int CHUNK_SPACE = 12; + + public static void setServerVersion(String version) + { + SERVER=StringUtil.getBytes("Server: Jetty("+version+")\015\012"); + } + + // data + protected boolean _bypass = false; // True if _content buffer can be written directly to endp and bypass the content buffer + private boolean _needCRLF = false; + private boolean _needEOC = false; + private boolean _bufferChunked = false; + + + /* ------------------------------------------------------------------------------- */ + /** + * Constructor. + * + * @param buffers buffer pool + * @param io the end point to use + */ + public HttpGenerator(Buffers buffers, EndPoint io) + { + super(buffers,io); + } + + /* ------------------------------------------------------------------------------- */ + @Override + public void reset() + { + if (_persistent!=null && !_persistent && _endp!=null && !_endp.isOutputShutdown()) + { + try + { + _endp.shutdownOutput(); + } + catch(IOException e) + { + LOG.ignore(e); + } + } + super.reset(); + if (_buffer!=null) + _buffer.clear(); + if (_header!=null) + _header.clear(); + if (_content!=null) + _content=null; + _bypass = false; + _needCRLF = false; + _needEOC = false; + _bufferChunked=false; + _method=null; + _uri=null; + _noContent=false; + } + + /* ------------------------------------------------------------ */ + /** + * Add content. + * + * @param content + * @param last + * @throws IllegalArgumentException if <code>content</code> is {@link Buffer#isImmutable immutable}. + * @throws IllegalStateException If the request is not expecting any more content, + * or if the buffers are full and cannot be flushed. + * @throws IOException if there is a problem flushing the buffers. + */ + public void addContent(Buffer content, boolean last) throws IOException + { + if (_noContent) + throw new IllegalStateException("NO CONTENT"); + + if (_last || _state==STATE_END) + { + LOG.warn("Ignoring extra content {}",content); + content.clear(); + return; + } + _last = last; + + // Handle any unfinished business? + if (_content!=null && _content.length()>0 || _bufferChunked) + { + if (_endp.isOutputShutdown()) + throw new EofException(); + flushBuffer(); + if (_content != null && _content.length()>0) + { + if (_bufferChunked) + { + Buffer nc=_buffers.getBuffer(_content.length()+CHUNK_SPACE+content.length()); + nc.put(_content); + nc.put(HttpTokens.CRLF); + BufferUtil.putHexInt(nc, content.length()); + nc.put(HttpTokens.CRLF); + nc.put(content); + content=nc; + } + else + { + Buffer nc=_buffers.getBuffer(_content.length()+content.length()); + nc.put(_content); + nc.put(content); + content=nc; + } + } + } + + _content = content; + _contentWritten += content.length(); + + // Handle the _content + if (_head) + { + content.clear(); + _content=null; + } + else if (_endp != null && (_buffer==null || _buffer.length()==0) && _content.length() > 0 && (_last || isCommitted() && _content.length()>1024)) + { + _bypass = true; + } + else if (!_bufferChunked) + { + // Yes - so we better check we have a buffer + if (_buffer == null) + _buffer = _buffers.getBuffer(); + + // Copy _content to buffer; + int len=_buffer.put(_content); + _content.skip(len); + if (_content.length() == 0) + _content = null; + } + } + + /* ------------------------------------------------------------ */ + /** + * send complete response. + * + * @param response + */ + public void sendResponse(Buffer response) throws IOException + { + if (_noContent || _state!=STATE_HEADER || _content!=null && _content.length()>0 || _bufferChunked || _head ) + throw new IllegalStateException(); + + _last = true; + + _content = response; + _bypass = true; + _state = STATE_FLUSHING; + + // TODO this is not exactly right, but should do. + _contentLength =_contentWritten = response.length(); + + } + + /* ------------------------------------------------------------ */ + /** Prepare buffer for unchecked writes. + * Prepare the generator buffer to receive unchecked writes + * @return the available space in the buffer. + * @throws IOException + */ + @Override + public int prepareUncheckedAddContent() throws IOException + { + if (_noContent) + return -1; + + if (_last || _state==STATE_END) + return -1; + + // Handle any unfinished business? + Buffer content = _content; + if (content != null && content.length()>0 || _bufferChunked) + { + flushBuffer(); + if (content != null && content.length()>0 || _bufferChunked) + throw new IllegalStateException("FULL"); + } + + // we better check we have a buffer + if (_buffer == null) + _buffer = _buffers.getBuffer(); + + _contentWritten-=_buffer.length(); + + // Handle the _content + if (_head) + return Integer.MAX_VALUE; + + return _buffer.space()-(_contentLength == HttpTokens.CHUNKED_CONTENT?CHUNK_SPACE:0); + } + + /* ------------------------------------------------------------ */ + @Override + public boolean isBufferFull() + { + // Should we flush the buffers? + return super.isBufferFull() || _bufferChunked || _bypass || (_contentLength == HttpTokens.CHUNKED_CONTENT && _buffer != null && _buffer.space() < CHUNK_SPACE); + } + + /* ------------------------------------------------------------ */ + public void send1xx(int code) throws IOException + { + if (_state != STATE_HEADER) + return; + + if (code<100||code>199) + throw new IllegalArgumentException("!1xx"); + Status status=__status[code]; + if (status==null) + throw new IllegalArgumentException(code+"?"); + + // get a header buffer + if (_header == null) + _header = _buffers.getHeader(); + + _header.put(status._responseLine); + _header.put(HttpTokens.CRLF); + + try + { + // nasty semi busy flush! + while(_header.length()>0) + { + int len = _endp.flush(_header); + if (len<0) + throw new EofException(); + if (len==0) + Thread.sleep(100); + } + } + catch(InterruptedException e) + { + LOG.debug(e); + throw new InterruptedIOException(e.toString()); + } + } + + /* ------------------------------------------------------------ */ + @Override + public boolean isRequest() + { + return _method!=null; + } + + /* ------------------------------------------------------------ */ + @Override + public boolean isResponse() + { + return _method==null; + } + + /* ------------------------------------------------------------ */ + @Override + public void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException + { + if (_state != STATE_HEADER) + return; + + // handle a reset + if (isResponse() && _status==0) + throw new EofException(); + + if (_last && !allContentAdded) + throw new IllegalStateException("last?"); + _last = _last | allContentAdded; + + // get a header buffer + if (_header == null) + _header = _buffers.getHeader(); + + boolean has_server = false; + + try + { + if (isRequest()) + { + _persistent=true; + + if (_version == HttpVersions.HTTP_0_9_ORDINAL) + { + _contentLength = HttpTokens.NO_CONTENT; + _header.put(_method); + _header.put((byte)' '); + _header.put(_uri.getBytes("UTF-8")); // TODO check + _header.put(HttpTokens.CRLF); + _state = STATE_FLUSHING; + _noContent=true; + return; + } + else + { + _header.put(_method); + _header.put((byte)' '); + _header.put(_uri.getBytes("UTF-8")); // TODO check + _header.put((byte)' '); + _header.put(_version==HttpVersions.HTTP_1_0_ORDINAL?HttpVersions.HTTP_1_0_BUFFER:HttpVersions.HTTP_1_1_BUFFER); + _header.put(HttpTokens.CRLF); + } + } + else + { + // Responses + if (_version == HttpVersions.HTTP_0_9_ORDINAL) + { + _persistent = false; + _contentLength = HttpTokens.EOF_CONTENT; + _state = STATE_CONTENT; + return; + } + else + { + if (_persistent==null) + _persistent= (_version > HttpVersions.HTTP_1_0_ORDINAL); + + // add response line + Status status = _status<__status.length?__status[_status]:null; + + if (status==null) + { + _header.put(HttpVersions.HTTP_1_1_BUFFER); + _header.put((byte) ' '); + _header.put((byte) ('0' + _status / 100)); + _header.put((byte) ('0' + (_status % 100) / 10)); + _header.put((byte) ('0' + (_status % 10))); + _header.put((byte) ' '); + if (_reason==null) + { + _header.put((byte) ('0' + _status / 100)); + _header.put((byte) ('0' + (_status % 100) / 10)); + _header.put((byte) ('0' + (_status % 10))); + } + else + _header.put(_reason); + _header.put(HttpTokens.CRLF); + } + else + { + if (_reason==null) + _header.put(status._responseLine); + else + { + _header.put(status._schemeCode); + _header.put(_reason); + _header.put(HttpTokens.CRLF); + } + } + + if (_status<200 && _status>=100 ) + { + _noContent=true; + _content=null; + if (_buffer!=null) + _buffer.clear(); + // end the header. + + if (_status!=101 ) + { + _header.put(HttpTokens.CRLF); + _state = STATE_CONTENT; + return; + } + } + else if (_status==204 || _status==304) + { + _noContent=true; + _content=null; + if (_buffer!=null) + _buffer.clear(); + } + } + } + + // Add headers + if (_status>=200 && _date!=null) + { + _header.put(HttpHeaders.DATE_BUFFER); + _header.put((byte)':'); + _header.put((byte)' '); + _header.put(_date); + _header.put(CRLF); + } + + // key field values + HttpFields.Field content_length = null; + HttpFields.Field transfer_encoding = null; + boolean keep_alive = false; + boolean close=false; + boolean content_type=false; + StringBuilder connection = null; + + if (fields != null) + { + int s=fields.size(); + for (int f=0;f<s;f++) + { + HttpFields.Field field = fields.getField(f); + if (field==null) + continue; + + switch (field.getNameOrdinal()) + { + case HttpHeaders.CONTENT_LENGTH_ORDINAL: + content_length = field; + _contentLength = field.getLongValue(); + + if (_contentLength < _contentWritten || _last && _contentLength != _contentWritten) + content_length = null; + + // write the field to the header buffer + field.putTo(_header); + break; + + case HttpHeaders.CONTENT_TYPE_ORDINAL: + if (BufferUtil.isPrefix(MimeTypes.MULTIPART_BYTERANGES_BUFFER, field.getValueBuffer())) _contentLength = HttpTokens.SELF_DEFINING_CONTENT; + + // write the field to the header buffer + content_type=true; + field.putTo(_header); + break; + + case HttpHeaders.TRANSFER_ENCODING_ORDINAL: + if (_version == HttpVersions.HTTP_1_1_ORDINAL) + transfer_encoding = field; + // Do NOT add yet! + break; + + case HttpHeaders.CONNECTION_ORDINAL: + if (isRequest()) + field.putTo(_header); + + int connection_value = field.getValueOrdinal(); + switch (connection_value) + { + case -1: + { + String[] values = field.getValue().split(","); + for (int i=0;values!=null && i<values.length;i++) + { + CachedBuffer cb = HttpHeaderValues.CACHE.get(values[i].trim()); + + if (cb!=null) + { + switch(cb.getOrdinal()) + { + case HttpHeaderValues.CLOSE_ORDINAL: + close=true; + if (isResponse()) + _persistent=false; + keep_alive=false; + if (!_persistent && isResponse() && _contentLength == HttpTokens.UNKNOWN_CONTENT) + _contentLength = HttpTokens.EOF_CONTENT; + break; + + case HttpHeaderValues.KEEP_ALIVE_ORDINAL: + if (_version == HttpVersions.HTTP_1_0_ORDINAL) + { + keep_alive = true; + if (isResponse()) + _persistent = true; + } + break; + + default: + if (connection==null) + connection=new StringBuilder(); + else + connection.append(','); + connection.append(values[i]); + } + } + else + { + if (connection==null) + connection=new StringBuilder(); + else + connection.append(','); + connection.append(values[i]); + } + } + + break; + } + case HttpHeaderValues.UPGRADE_ORDINAL: + { + // special case for websocket connection ordering + if (isResponse()) + { + field.putTo(_header); + continue; + } + } + case HttpHeaderValues.CLOSE_ORDINAL: + { + close=true; + if (isResponse()) + _persistent=false; + if (!_persistent && isResponse() && _contentLength == HttpTokens.UNKNOWN_CONTENT) + _contentLength = HttpTokens.EOF_CONTENT; + break; + } + case HttpHeaderValues.KEEP_ALIVE_ORDINAL: + { + if (_version == HttpVersions.HTTP_1_0_ORDINAL) + { + keep_alive = true; + if (isResponse()) + _persistent=true; + } + break; + } + default: + { + if (connection==null) + connection=new StringBuilder(); + else + connection.append(','); + connection.append(field.getValue()); + } + } + + // Do NOT add yet! + break; + + case HttpHeaders.SERVER_ORDINAL: + if (getSendServerVersion()) + { + has_server=true; + field.putTo(_header); + } + break; + + default: + // write the field to the header buffer + field.putTo(_header); + } + } + } + + // Calculate how to end _content and connection, _content length and transfer encoding + // settings. + // From RFC 2616 4.4: + // 1. No body for 1xx, 204, 304 & HEAD response + // 2. Force _content-length? + // 3. If Transfer-Encoding!=identity && HTTP/1.1 && !HttpConnection==close then chunk + // 4. Content-Length + // 5. multipart/byteranges + // 6. close + switch ((int) _contentLength) + { + case HttpTokens.UNKNOWN_CONTENT: + // It may be that we have no _content, or perhaps _content just has not been + // written yet? + + // Response known not to have a body + if (_contentWritten == 0 && isResponse() && (_status < 200 || _status == 204 || _status == 304)) + _contentLength = HttpTokens.NO_CONTENT; + else if (_last) + { + // we have seen all the _content there is + _contentLength = _contentWritten; + if (content_length == null && (isResponse() || _contentLength>0 || content_type ) && !_noContent) + { + // known length but not actually set. + _header.put(HttpHeaders.CONTENT_LENGTH_BUFFER); + _header.put(HttpTokens.COLON); + _header.put((byte) ' '); + BufferUtil.putDecLong(_header, _contentLength); + _header.put(HttpTokens.CRLF); + } + } + else + { + // No idea, so we must assume that a body is coming + _contentLength = (!_persistent || _version < HttpVersions.HTTP_1_1_ORDINAL ) ? HttpTokens.EOF_CONTENT : HttpTokens.CHUNKED_CONTENT; + if (isRequest() && _contentLength==HttpTokens.EOF_CONTENT) + { + _contentLength=HttpTokens.NO_CONTENT; + _noContent=true; + } + } + break; + + case HttpTokens.NO_CONTENT: + if (content_length == null && isResponse() && _status >= 200 && _status != 204 && _status != 304) + _header.put(CONTENT_LENGTH_0); + break; + + case HttpTokens.EOF_CONTENT: + _persistent = isRequest(); + break; + + case HttpTokens.CHUNKED_CONTENT: + break; + + default: + // TODO - maybe allow forced chunking by setting te ??? + break; + } + + // Add transfer_encoding if needed + if (_contentLength == HttpTokens.CHUNKED_CONTENT) + { + // try to use user supplied encoding as it may have other values. + if (transfer_encoding != null && HttpHeaderValues.CHUNKED_ORDINAL != transfer_encoding.getValueOrdinal()) + { + String c = transfer_encoding.getValue(); + if (c.endsWith(HttpHeaderValues.CHUNKED)) + transfer_encoding.putTo(_header); + else + throw new IllegalArgumentException("BAD TE"); + } + else + _header.put(TRANSFER_ENCODING_CHUNKED); + } + + // Handle connection if need be + if (_contentLength==HttpTokens.EOF_CONTENT) + { + keep_alive=false; + _persistent=false; + } + + if (isResponse()) + { + if (!_persistent && (close || _version > HttpVersions.HTTP_1_0_ORDINAL)) + { + _header.put(CONNECTION_CLOSE); + if (connection!=null) + { + _header.setPutIndex(_header.putIndex()-2); + _header.put((byte)','); + _header.put(connection.toString().getBytes()); + _header.put(CRLF); + } + } + else if (keep_alive) + { + _header.put(CONNECTION_KEEP_ALIVE); + if (connection!=null) + { + _header.setPutIndex(_header.putIndex()-2); + _header.put((byte)','); + _header.put(connection.toString().getBytes()); + _header.put(CRLF); + } + } + else if (connection!=null) + { + _header.put(CONNECTION_); + _header.put(connection.toString().getBytes()); + _header.put(CRLF); + } + } + + if (!has_server && _status>199 && getSendServerVersion()) + _header.put(SERVER); + + // end the header. + _header.put(HttpTokens.CRLF); + _state = STATE_CONTENT; + + } + catch(ArrayIndexOutOfBoundsException e) + { + throw new RuntimeException("Header>"+_header.capacity(),e); + } + } + + /* ------------------------------------------------------------ */ + /** + * Complete the message. + * + * @throws IOException + */ + @Override + public void complete() throws IOException + { + if (_state == STATE_END) + return; + + super.complete(); + + if (_state < STATE_FLUSHING) + { + _state = STATE_FLUSHING; + if (_contentLength == HttpTokens.CHUNKED_CONTENT) + _needEOC = true; + } + + flushBuffer(); + } + + /* ------------------------------------------------------------ */ + @Override + public int flushBuffer() throws IOException + { + try + { + + if (_state == STATE_HEADER) + throw new IllegalStateException("State==HEADER"); + + prepareBuffers(); + + if (_endp == null) + { + if (_needCRLF && _buffer!=null) + _buffer.put(HttpTokens.CRLF); + if (_needEOC && _buffer!=null && !_head) + _buffer.put(LAST_CHUNK); + _needCRLF=false; + _needEOC=false; + return 0; + } + + int total= 0; + + int len = -1; + int to_flush = flushMask(); + int last_flush; + + do + { + last_flush=to_flush; + switch (to_flush) + { + case 7: + throw new IllegalStateException(); // should never happen! + case 6: + len = _endp.flush(_header, _buffer, null); + break; + case 5: + len = _endp.flush(_header, _content, null); + break; + case 4: + len = _endp.flush(_header); + break; + case 3: + len = _endp.flush(_buffer, _content, null); + break; + case 2: + len = _endp.flush(_buffer); + break; + case 1: + len = _endp.flush(_content); + break; + case 0: + { + len=0; + // Nothing more we can write now. + if (_header != null) + _header.clear(); + + _bypass = false; + _bufferChunked = false; + + if (_buffer != null) + { + _buffer.clear(); + if (_contentLength == HttpTokens.CHUNKED_CONTENT) + { + // reserve some space for the chunk header + _buffer.setPutIndex(CHUNK_SPACE); + _buffer.setGetIndex(CHUNK_SPACE); + + // Special case handling for small left over buffer from + // an addContent that caused a buffer flush. + if (_content != null && _content.length() < _buffer.space() && _state != STATE_FLUSHING) + { + _buffer.put(_content); + _content.clear(); + _content=null; + } + } + } + + // Are we completely finished for now? + if (!_needCRLF && !_needEOC && (_content==null || _content.length()==0)) + { + if (_state == STATE_FLUSHING) + _state = STATE_END; + + if (_state==STATE_END && _persistent != null && !_persistent && _status!=100 && _method==null) + _endp.shutdownOutput(); + } + else + // Try to prepare more to write. + prepareBuffers(); + } + + } + + if (len > 0) + total+=len; + + to_flush = flushMask(); + } + // loop while progress is being made (OR we have prepared some buffers that might make progress) + while (len>0 || (to_flush!=0 && last_flush==0)); + + return total; + } + catch (IOException e) + { + LOG.ignore(e); + throw (e instanceof EofException) ? e:new EofException(e); + } + } + + /* ------------------------------------------------------------ */ + private int flushMask() + { + return ((_header != null && _header.length() > 0)?4:0) + | ((_buffer != null && _buffer.length() > 0)?2:0) + | ((_bypass && _content != null && _content.length() > 0)?1:0); + } + + /* ------------------------------------------------------------ */ + private void prepareBuffers() + { + // if we are not flushing an existing chunk + if (!_bufferChunked) + { + // Refill buffer if possible + if (!_bypass && _content != null && _content.length() > 0 && _buffer != null && _buffer.space() > 0) + { + int len = _buffer.put(_content); + _content.skip(len); + if (_content.length() == 0) + _content = null; + } + + // Chunk buffer if need be + if (_contentLength == HttpTokens.CHUNKED_CONTENT) + { + if (_bypass && (_buffer==null||_buffer.length()==0) && _content!=null) + { + // this is a bypass write + int size = _content.length(); + _bufferChunked = true; + + if (_header == null) + _header = _buffers.getHeader(); + + // if we need CRLF add this to header + if (_needCRLF) + { + if (_header.length() > 0) throw new IllegalStateException("EOC"); + _header.put(HttpTokens.CRLF); + _needCRLF = false; + } + // Add the chunk size to the header + BufferUtil.putHexInt(_header, size); + _header.put(HttpTokens.CRLF); + + // Need a CRLF after the content + _needCRLF=true; + } + else if (_buffer!=null) + { + int size = _buffer.length(); + if (size > 0) + { + // Prepare a chunk! + _bufferChunked = true; + + // Did we leave space at the start of the buffer. + //noinspection ConstantConditions + if (_buffer.getIndex() == CHUNK_SPACE) + { + // Oh yes, goodie! let's use it then! + _buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2); + _buffer.setGetIndex(_buffer.getIndex() - 2); + BufferUtil.prependHexInt(_buffer, size); + + if (_needCRLF) + { + _buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2); + _buffer.setGetIndex(_buffer.getIndex() - 2); + _needCRLF = false; + } + } + else + { + // No space so lets use a header buffer. + if (_header == null) + _header = _buffers.getHeader(); + + if (_needCRLF) + { + if (_header.length() > 0) throw new IllegalStateException("EOC"); + _header.put(HttpTokens.CRLF); + _needCRLF = false; + } + BufferUtil.putHexInt(_header, size); + _header.put(HttpTokens.CRLF); + } + + // Add end chunk trailer. + if (_buffer.space() >= 2) + _buffer.put(HttpTokens.CRLF); + else + _needCRLF = true; + } + } + + // If we need EOC and everything written + if (_needEOC && (_content == null || _content.length() == 0)) + { + if (_header == null && _buffer == null) + _header = _buffers.getHeader(); + + if (_needCRLF) + { + if (_buffer == null && _header != null && _header.space() >= HttpTokens.CRLF.length) + { + _header.put(HttpTokens.CRLF); + _needCRLF = false; + } + else if (_buffer!=null && _buffer.space() >= HttpTokens.CRLF.length) + { + _buffer.put(HttpTokens.CRLF); + _needCRLF = false; + } + } + + if (!_needCRLF && _needEOC) + { + if (_buffer == null && _header != null && _header.space() >= LAST_CHUNK.length) + { + if (!_head) + { + _header.put(LAST_CHUNK); + _bufferChunked=true; + } + _needEOC = false; + } + else if (_buffer!=null && _buffer.space() >= LAST_CHUNK.length) + { + if (!_head) + { + _buffer.put(LAST_CHUNK); + _bufferChunked=true; + } + _needEOC = false; + } + } + } + } + } + + if (_content != null && _content.length() == 0) + _content = null; + + } + + public int getBytesBuffered() + { + return(_header==null?0:_header.length())+ + (_buffer==null?0:_buffer.length())+ + (_content==null?0:_content.length()); + } + + public boolean isEmpty() + { + return (_header==null||_header.length()==0) && + (_buffer==null||_buffer.length()==0) && + (_content==null||_content.length()==0); + } + + @Override + public String toString() + { + Buffer header=_header; + Buffer buffer=_buffer; + Buffer content=_content; + return String.format("%s{s=%d,h=%d,b=%d,c=%d}", + getClass().getSimpleName(), + _state, + header == null ? -1 : header.length(), + buffer == null ? -1 : buffer.length(), + content == null ? -1 : content.length()); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/http/HttpHeaderValues.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,88 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.http; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.BufferCache; +import org.eclipse.jetty.io.ByteArrayBuffer; + +/** + * Cached HTTP Header values. + * This class caches the conversion of common HTTP Header values to and from {@link ByteArrayBuffer} instances. + * The resource "/org/eclipse/jetty/useragents" is checked for a list of common user agents, so that repeated + * creation of strings for these agents can be avoided. + * + * + */ +public class HttpHeaderValues extends BufferCache +{ + public final static String + CLOSE="close", + CHUNKED="chunked", + GZIP="gzip", + IDENTITY="identity", + KEEP_ALIVE="keep-alive", + CONTINUE="100-continue", + PROCESSING="102-processing", + TE="TE", + BYTES="bytes", + NO_CACHE="no-cache", + UPGRADE="Upgrade"; + + public final static int + CLOSE_ORDINAL=1, + CHUNKED_ORDINAL=2, + GZIP_ORDINAL=3, + IDENTITY_ORDINAL=4, + KEEP_ALIVE_ORDINAL=5, + CONTINUE_ORDINAL=6, + PROCESSING_ORDINAL=7, + TE_ORDINAL=8, + BYTES_ORDINAL=9, + NO_CACHE_ORDINAL=10, + UPGRADE_ORDINAL=11; + + public final static HttpHeaderValues CACHE= new HttpHeaderValues(); + + public final static Buffer + CLOSE_BUFFER=CACHE.add(CLOSE,CLOSE_ORDINAL), + CHUNKED_BUFFER=CACHE.add(CHUNKED,CHUNKED_ORDINAL), + GZIP_BUFFER=CACHE.add(GZIP,GZIP_ORDINAL), + IDENTITY_BUFFER=CACHE.add(IDENTITY,IDENTITY_ORDINAL), + KEEP_ALIVE_BUFFER=CACHE.add(KEEP_ALIVE,KEEP_ALIVE_ORDINAL), + CONTINUE_BUFFER=CACHE.add(CONTINUE, CONTINUE_ORDINAL), + PROCESSING_BUFFER=CACHE.add(PROCESSING, PROCESSING_ORDINAL), + TE_BUFFER=CACHE.add(TE,TE_ORDINAL), + BYTES_BUFFER=CACHE.add(BYTES,BYTES_ORDINAL), + NO_CACHE_BUFFER=CACHE.add(NO_CACHE,NO_CACHE_ORDINAL), + UPGRADE_BUFFER=CACHE.add(UPGRADE,UPGRADE_ORDINAL); + + + public static boolean hasKnownValues(int httpHeaderOrdinal) + { + switch(httpHeaderOrdinal) + { + case HttpHeaders.CONNECTION_ORDINAL: + case HttpHeaders.TRANSFER_ENCODING_ORDINAL: + case HttpHeaders.CONTENT_ENCODING_ORDINAL: + return true; + } + return false; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/http/HttpHeaders.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,241 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.http; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.BufferCache; + +/* ------------------------------------------------------------------------------- */ +/** + */ +public class HttpHeaders extends BufferCache +{ + /* ------------------------------------------------------------ */ + /** General Fields. + */ + public final static String + CONNECTION= "Connection", + CACHE_CONTROL= "Cache-Control", + DATE= "Date", + PRAGMA= "Pragma", + PROXY_CONNECTION = "Proxy-Connection", + TRAILER= "Trailer", + TRANSFER_ENCODING= "Transfer-Encoding", + UPGRADE= "Upgrade", + VIA= "Via", + WARNING= "Warning", + NEGOTIATE= "Negotiate"; + + /* ------------------------------------------------------------ */ + /** Entity Fields. + */ + public final static String ALLOW= "Allow", + CONTENT_ENCODING= "Content-Encoding", + CONTENT_LANGUAGE= "Content-Language", + CONTENT_LENGTH= "Content-Length", + CONTENT_LOCATION= "Content-Location", + CONTENT_MD5= "Content-MD5", + CONTENT_RANGE= "Content-Range", + CONTENT_TYPE= "Content-Type", + EXPIRES= "Expires", + LAST_MODIFIED= "Last-Modified"; + + /* ------------------------------------------------------------ */ + /** Request Fields. + */ + public final static String ACCEPT= "Accept", + ACCEPT_CHARSET= "Accept-Charset", + ACCEPT_ENCODING= "Accept-Encoding", + ACCEPT_LANGUAGE= "Accept-Language", + AUTHORIZATION= "Authorization", + EXPECT= "Expect", + FORWARDED= "Forwarded", + FROM= "From", + HOST= "Host", + IF_MATCH= "If-Match", + IF_MODIFIED_SINCE= "If-Modified-Since", + IF_NONE_MATCH= "If-None-Match", + IF_RANGE= "If-Range", + IF_UNMODIFIED_SINCE= "If-Unmodified-Since", + KEEP_ALIVE= "Keep-Alive", + MAX_FORWARDS= "Max-Forwards", + PROXY_AUTHORIZATION= "Proxy-Authorization", + RANGE= "Range", + REQUEST_RANGE= "Request-Range", + REFERER= "Referer", + TE= "TE", + USER_AGENT= "User-Agent", + X_FORWARDED_FOR= "X-Forwarded-For", + X_FORWARDED_PROTO= "X-Forwarded-Proto", + X_FORWARDED_SERVER= "X-Forwarded-Server", + X_FORWARDED_HOST= "X-Forwarded-Host"; + + /* ------------------------------------------------------------ */ + /** Response Fields. + */ + public final static String ACCEPT_RANGES= "Accept-Ranges", + AGE= "Age", + ETAG= "ETag", + LOCATION= "Location", + PROXY_AUTHENTICATE= "Proxy-Authenticate", + RETRY_AFTER= "Retry-After", + SERVER= "Server", + SERVLET_ENGINE= "Servlet-Engine", + VARY= "Vary", + WWW_AUTHENTICATE= "WWW-Authenticate"; + + /* ------------------------------------------------------------ */ + /** Other Fields. + */ + public final static String COOKIE= "Cookie", + SET_COOKIE= "Set-Cookie", + SET_COOKIE2= "Set-Cookie2", + MIME_VERSION= "MIME-Version", + IDENTITY= "identity"; + + public final static int CONNECTION_ORDINAL= 1, + DATE_ORDINAL= 2, + PRAGMA_ORDINAL= 3, + TRAILER_ORDINAL= 4, + TRANSFER_ENCODING_ORDINAL= 5, + UPGRADE_ORDINAL= 6, + VIA_ORDINAL= 7, + WARNING_ORDINAL= 8, + ALLOW_ORDINAL= 9, + CONTENT_ENCODING_ORDINAL= 10, + CONTENT_LANGUAGE_ORDINAL= 11, + CONTENT_LENGTH_ORDINAL= 12, + CONTENT_LOCATION_ORDINAL= 13, + CONTENT_MD5_ORDINAL= 14, + CONTENT_RANGE_ORDINAL= 15, + CONTENT_TYPE_ORDINAL= 16, + EXPIRES_ORDINAL= 17, + LAST_MODIFIED_ORDINAL= 18, + ACCEPT_ORDINAL= 19, + ACCEPT_CHARSET_ORDINAL= 20, + ACCEPT_ENCODING_ORDINAL= 21, + ACCEPT_LANGUAGE_ORDINAL= 22, + AUTHORIZATION_ORDINAL= 23, + EXPECT_ORDINAL= 24, + FORWARDED_ORDINAL= 25, + FROM_ORDINAL= 26, + HOST_ORDINAL= 27, + IF_MATCH_ORDINAL= 28, + IF_MODIFIED_SINCE_ORDINAL= 29, + IF_NONE_MATCH_ORDINAL= 30, + IF_RANGE_ORDINAL= 31, + IF_UNMODIFIED_SINCE_ORDINAL= 32, + KEEP_ALIVE_ORDINAL= 33, + MAX_FORWARDS_ORDINAL= 34, + PROXY_AUTHORIZATION_ORDINAL= 35, + RANGE_ORDINAL= 36, + REQUEST_RANGE_ORDINAL= 37, + REFERER_ORDINAL= 38, + TE_ORDINAL= 39, + USER_AGENT_ORDINAL= 40, + X_FORWARDED_FOR_ORDINAL= 41, + ACCEPT_RANGES_ORDINAL= 42, + AGE_ORDINAL= 43, + ETAG_ORDINAL= 44, + LOCATION_ORDINAL= 45, + PROXY_AUTHENTICATE_ORDINAL= 46, + RETRY_AFTER_ORDINAL= 47, + SERVER_ORDINAL= 48, + SERVLET_ENGINE_ORDINAL= 49, + VARY_ORDINAL= 50, + WWW_AUTHENTICATE_ORDINAL= 51, + COOKIE_ORDINAL= 52, + SET_COOKIE_ORDINAL= 53, + SET_COOKIE2_ORDINAL= 54, + MIME_VERSION_ORDINAL= 55, + IDENTITY_ORDINAL= 56, + CACHE_CONTROL_ORDINAL=57, + PROXY_CONNECTION_ORDINAL=58, + X_FORWARDED_PROTO_ORDINAL=59, + X_FORWARDED_SERVER_ORDINAL=60, + X_FORWARDED_HOST_ORDINAL=61; + + public final static HttpHeaders CACHE= new HttpHeaders(); + + public final static Buffer + HOST_BUFFER=CACHE.add(HOST,HOST_ORDINAL), + ACCEPT_BUFFER=CACHE.add(ACCEPT,ACCEPT_ORDINAL), + ACCEPT_CHARSET_BUFFER=CACHE.add(ACCEPT_CHARSET,ACCEPT_CHARSET_ORDINAL), + ACCEPT_ENCODING_BUFFER=CACHE.add(ACCEPT_ENCODING,ACCEPT_ENCODING_ORDINAL), + ACCEPT_LANGUAGE_BUFFER=CACHE.add(ACCEPT_LANGUAGE,ACCEPT_LANGUAGE_ORDINAL), + + CONTENT_LENGTH_BUFFER=CACHE.add(CONTENT_LENGTH,CONTENT_LENGTH_ORDINAL), + CONNECTION_BUFFER=CACHE.add(CONNECTION,CONNECTION_ORDINAL), + CACHE_CONTROL_BUFFER=CACHE.add(CACHE_CONTROL,CACHE_CONTROL_ORDINAL), + DATE_BUFFER=CACHE.add(DATE,DATE_ORDINAL), + PRAGMA_BUFFER=CACHE.add(PRAGMA,PRAGMA_ORDINAL), + TRAILER_BUFFER=CACHE.add(TRAILER,TRAILER_ORDINAL), + TRANSFER_ENCODING_BUFFER=CACHE.add(TRANSFER_ENCODING,TRANSFER_ENCODING_ORDINAL), + UPGRADE_BUFFER=CACHE.add(UPGRADE,UPGRADE_ORDINAL), + VIA_BUFFER=CACHE.add(VIA,VIA_ORDINAL), + WARNING_BUFFER=CACHE.add(WARNING,WARNING_ORDINAL), + ALLOW_BUFFER=CACHE.add(ALLOW,ALLOW_ORDINAL), + CONTENT_ENCODING_BUFFER=CACHE.add(CONTENT_ENCODING,CONTENT_ENCODING_ORDINAL), + CONTENT_LANGUAGE_BUFFER=CACHE.add(CONTENT_LANGUAGE,CONTENT_LANGUAGE_ORDINAL), + CONTENT_LOCATION_BUFFER=CACHE.add(CONTENT_LOCATION,CONTENT_LOCATION_ORDINAL), + CONTENT_MD5_BUFFER=CACHE.add(CONTENT_MD5,CONTENT_MD5_ORDINAL), + CONTENT_RANGE_BUFFER=CACHE.add(CONTENT_RANGE,CONTENT_RANGE_ORDINAL), + CONTENT_TYPE_BUFFER=CACHE.add(CONTENT_TYPE,CONTENT_TYPE_ORDINAL), + EXPIRES_BUFFER=CACHE.add(EXPIRES,EXPIRES_ORDINAL), + LAST_MODIFIED_BUFFER=CACHE.add(LAST_MODIFIED,LAST_MODIFIED_ORDINAL), + AUTHORIZATION_BUFFER=CACHE.add(AUTHORIZATION,AUTHORIZATION_ORDINAL), + EXPECT_BUFFER=CACHE.add(EXPECT,EXPECT_ORDINAL), + FORWARDED_BUFFER=CACHE.add(FORWARDED,FORWARDED_ORDINAL), + FROM_BUFFER=CACHE.add(FROM,FROM_ORDINAL), + IF_MATCH_BUFFER=CACHE.add(IF_MATCH,IF_MATCH_ORDINAL), + IF_MODIFIED_SINCE_BUFFER=CACHE.add(IF_MODIFIED_SINCE,IF_MODIFIED_SINCE_ORDINAL), + IF_NONE_MATCH_BUFFER=CACHE.add(IF_NONE_MATCH,IF_NONE_MATCH_ORDINAL), + IF_RANGE_BUFFER=CACHE.add(IF_RANGE,IF_RANGE_ORDINAL), + IF_UNMODIFIED_SINCE_BUFFER=CACHE.add(IF_UNMODIFIED_SINCE,IF_UNMODIFIED_SINCE_ORDINAL), + KEEP_ALIVE_BUFFER=CACHE.add(KEEP_ALIVE,KEEP_ALIVE_ORDINAL), + MAX_FORWARDS_BUFFER=CACHE.add(MAX_FORWARDS,MAX_FORWARDS_ORDINAL), + PROXY_AUTHORIZATION_BUFFER=CACHE.add(PROXY_AUTHORIZATION,PROXY_AUTHORIZATION_ORDINAL), + RANGE_BUFFER=CACHE.add(RANGE,RANGE_ORDINAL), + REQUEST_RANGE_BUFFER=CACHE.add(REQUEST_RANGE,REQUEST_RANGE_ORDINAL), + REFERER_BUFFER=CACHE.add(REFERER,REFERER_ORDINAL), + TE_BUFFER=CACHE.add(TE,TE_ORDINAL), + USER_AGENT_BUFFER=CACHE.add(USER_AGENT,USER_AGENT_ORDINAL), + X_FORWARDED_FOR_BUFFER=CACHE.add(X_FORWARDED_FOR,X_FORWARDED_FOR_ORDINAL), + X_FORWARDED_PROTO_BUFFER=CACHE.add(X_FORWARDED_PROTO,X_FORWARDED_PROTO_ORDINAL), + X_FORWARDED_SERVER_BUFFER=CACHE.add(X_FORWARDED_SERVER,X_FORWARDED_SERVER_ORDINAL), + X_FORWARDED_HOST_BUFFER=CACHE.add(X_FORWARDED_HOST,X_FORWARDED_HOST_ORDINAL), + ACCEPT_RANGES_BUFFER=CACHE.add(ACCEPT_RANGES,ACCEPT_RANGES_ORDINAL), + AGE_BUFFER=CACHE.add(AGE,AGE_ORDINAL), + ETAG_BUFFER=CACHE.add(ETAG,ETAG_ORDINAL), + LOCATION_BUFFER=CACHE.add(LOCATION,LOCATION_ORDINAL), + PROXY_AUTHENTICATE_BUFFER=CACHE.add(PROXY_AUTHENTICATE,PROXY_AUTHENTICATE_ORDINAL), + RETRY_AFTER_BUFFER=CACHE.add(RETRY_AFTER,RETRY_AFTER_ORDINAL), + SERVER_BUFFER=CACHE.add(SERVER,SERVER_ORDINAL), + SERVLET_ENGINE_BUFFER=CACHE.add(SERVLET_ENGINE,SERVLET_ENGINE_ORDINAL), + VARY_BUFFER=CACHE.add(VARY,VARY_ORDINAL), + WWW_AUTHENTICATE_BUFFER=CACHE.add(WWW_AUTHENTICATE,WWW_AUTHENTICATE_ORDINAL), + COOKIE_BUFFER=CACHE.add(COOKIE,COOKIE_ORDINAL), + SET_COOKIE_BUFFER=CACHE.add(SET_COOKIE,SET_COOKIE_ORDINAL), + SET_COOKIE2_BUFFER=CACHE.add(SET_COOKIE2,SET_COOKIE2_ORDINAL), + MIME_VERSION_BUFFER=CACHE.add(MIME_VERSION,MIME_VERSION_ORDINAL), + IDENTITY_BUFFER=CACHE.add(IDENTITY,IDENTITY_ORDINAL), + PROXY_CONNECTION_BUFFER=CACHE.add(PROXY_CONNECTION,PROXY_CONNECTION_ORDINAL); + + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/http/HttpMethods.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,64 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.http; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.BufferCache; + +/* ------------------------------------------------------------------------------- */ +/** + * + * + */ +public class HttpMethods +{ + public final static String GET= "GET", + POST= "POST", + HEAD= "HEAD", + PUT= "PUT", + OPTIONS= "OPTIONS", + DELETE= "DELETE", + TRACE= "TRACE", + CONNECT= "CONNECT", + MOVE= "MOVE"; + + public final static int GET_ORDINAL= 1, + POST_ORDINAL= 2, + HEAD_ORDINAL= 3, + PUT_ORDINAL= 4, + OPTIONS_ORDINAL= 5, + DELETE_ORDINAL= 6, + TRACE_ORDINAL= 7, + CONNECT_ORDINAL= 8, + MOVE_ORDINAL= 9; + + public final static BufferCache CACHE= new BufferCache(); + + public final static Buffer + GET_BUFFER= CACHE.add(GET, GET_ORDINAL), + POST_BUFFER= CACHE.add(POST, POST_ORDINAL), + HEAD_BUFFER= CACHE.add(HEAD, HEAD_ORDINAL), + PUT_BUFFER= CACHE.add(PUT, PUT_ORDINAL), + OPTIONS_BUFFER= CACHE.add(OPTIONS, OPTIONS_ORDINAL), + DELETE_BUFFER= CACHE.add(DELETE, DELETE_ORDINAL), + TRACE_BUFFER= CACHE.add(TRACE, TRACE_ORDINAL), + CONNECT_BUFFER= CACHE.add(CONNECT, CONNECT_ORDINAL), + MOVE_BUFFER= CACHE.add(MOVE, MOVE_ORDINAL); + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/http/HttpParser.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,1279 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.http; + +import java.io.IOException; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.BufferCache.CachedBuffer; +import org.eclipse.jetty.io.BufferUtil; +import org.eclipse.jetty.io.Buffers; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.io.View; +import org.eclipse.jetty.io.bio.StreamEndPoint; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +public class HttpParser implements Parser +{ + private static final Logger LOG = Log.getLogger(HttpParser.class); + + // States + public static final int STATE_START=-14; + public static final int STATE_FIELD0=-13; + public static final int STATE_SPACE1=-12; + public static final int STATE_STATUS=-11; + public static final int STATE_URI=-10; + public static final int STATE_SPACE2=-9; + public static final int STATE_END0=-8; + public static final int STATE_END1=-7; + public static final int STATE_FIELD2=-6; + public static final int STATE_HEADER=-5; + public static final int STATE_HEADER_NAME=-4; + public static final int STATE_HEADER_IN_NAME=-3; + public static final int STATE_HEADER_VALUE=-2; + public static final int STATE_HEADER_IN_VALUE=-1; + public static final int STATE_END=0; + public static final int STATE_EOF_CONTENT=1; + public static final int STATE_CONTENT=2; + public static final int STATE_CHUNKED_CONTENT=3; + public static final int STATE_CHUNK_SIZE=4; + public static final int STATE_CHUNK_PARAMS=5; + public static final int STATE_CHUNK=6; + public static final int STATE_SEEKING_EOF=7; + + private final EventHandler _handler; + private final Buffers _buffers; // source of buffers + private final EndPoint _endp; + private Buffer _header; // Buffer for header data (and small _content) + private Buffer _body; // Buffer for large content + private Buffer _buffer; // The current buffer in use (either _header or _content) + private CachedBuffer _cached; + private final View.CaseInsensitive _tok0; // Saved token: header name, request method or response version + private final View.CaseInsensitive _tok1; // Saved token: header value, request URI or response code + private String _multiLineValue; + private int _responseStatus; // If >0 then we are parsing a response + private boolean _forceContentBuffer; + private boolean _persistent; + + /* ------------------------------------------------------------------------------- */ + protected final View _contentView=new View(); // View of the content in the buffer for {@link Input} + protected int _state=STATE_START; + protected byte _eol; + protected int _length; + protected long _contentLength; + protected long _contentPosition; + protected int _chunkLength; + protected int _chunkPosition; + private boolean _headResponse; + + /* ------------------------------------------------------------------------------- */ + /** + * Constructor. + */ + public HttpParser(Buffer buffer, EventHandler handler) + { + _endp=null; + _buffers=null; + _header=buffer; + _buffer=buffer; + _handler=handler; + + _tok0=new View.CaseInsensitive(_header); + _tok1=new View.CaseInsensitive(_header); + } + + /* ------------------------------------------------------------------------------- */ + /** + * Constructor. + * @param buffers the buffers to use + * @param endp the endpoint + * @param handler the even handler + */ + public HttpParser(Buffers buffers, EndPoint endp, EventHandler handler) + { + _buffers=buffers; + _endp=endp; + _handler=handler; + _tok0=new View.CaseInsensitive(); + _tok1=new View.CaseInsensitive(); + } + + /* ------------------------------------------------------------------------------- */ + public long getContentLength() + { + return _contentLength; + } + + /* ------------------------------------------------------------ */ + public long getContentRead() + { + return _contentPosition; + } + + /* ------------------------------------------------------------ */ + /** Set if a HEAD response is expected + * @param head + */ + public void setHeadResponse(boolean head) + { + _headResponse=head; + } + + /* ------------------------------------------------------------------------------- */ + public int getState() + { + return _state; + } + + /* ------------------------------------------------------------------------------- */ + public boolean inContentState() + { + return _state > 0; + } + + /* ------------------------------------------------------------------------------- */ + public boolean inHeaderState() + { + return _state < 0; + } + + /* ------------------------------------------------------------------------------- */ + public boolean isChunking() + { + return _contentLength==HttpTokens.CHUNKED_CONTENT; + } + + /* ------------------------------------------------------------ */ + public boolean isIdle() + { + return isState(STATE_START); + } + + /* ------------------------------------------------------------ */ + public boolean isComplete() + { + return isState(STATE_END); + } + + /* ------------------------------------------------------------ */ + public boolean isMoreInBuffer() + throws IOException + { + return ( _header!=null && _header.hasContent() || + _body!=null && _body.hasContent()); + } + + /* ------------------------------------------------------------------------------- */ + public boolean isState(int state) + { + return _state == state; + } + + /* ------------------------------------------------------------------------------- */ + public boolean isPersistent() + { + return _persistent; + } + + /* ------------------------------------------------------------------------------- */ + public void setPersistent(boolean persistent) + { + _persistent = persistent; + if (!_persistent &&(_state==STATE_END || _state==STATE_START)) + _state=STATE_SEEKING_EOF; + } + + /* ------------------------------------------------------------------------------- */ + /** + * Parse until {@link #STATE_END END} state. + * If the parser is already in the END state, then it is {@link #reset reset} and re-parsed. + * @throws IllegalStateException If the buffers have already been partially parsed. + */ + public void parse() throws IOException + { + if (_state==STATE_END) + reset(); + if (_state!=STATE_START) + throw new IllegalStateException("!START"); + + // continue parsing + while (_state != STATE_END) + if (parseNext()<0) + return; + } + + /* ------------------------------------------------------------------------------- */ + /** + * Parse until END state. + * This method will parse any remaining content in the current buffer as long as there is + * no unconsumed content. It does not care about the {@link #getState current state} of the parser. + * @see #parse + * @see #parseNext + */ + public boolean parseAvailable() throws IOException + { + boolean progress=parseNext()>0; + + // continue parsing + while (!isComplete() && _buffer!=null && _buffer.length()>0 && !_contentView.hasContent()) + { + progress |= parseNext()>0; + } + return progress; + } + + + /* ------------------------------------------------------------------------------- */ + /** + * Parse until next Event. + * @return an indication of progress <0 EOF, 0 no progress, >0 progress. + */ + public int parseNext() throws IOException + { + try + { + int progress=0; + + if (_state == STATE_END) + return 0; + + if (_buffer==null) + _buffer=getHeaderBuffer(); + + + if (_state == STATE_CONTENT && _contentPosition == _contentLength) + { + _state=STATE_END; + _handler.messageComplete(_contentPosition); + return 1; + } + + int length=_buffer.length(); + + // Fill buffer if we can + if (length == 0) + { + int filled=-1; + IOException ex=null; + try + { + filled=fill(); + LOG.debug("filled {}/{}",filled,_buffer.length()); + } + catch(IOException e) + { + LOG.debug(this.toString(),e); + ex=e; + } + + if (filled > 0 ) + progress++; + else if (filled < 0 ) + { + _persistent=false; + + // do we have content to deliver? + if (_state>STATE_END) + { + if (_buffer.length()>0 && !_headResponse) + { + Buffer chunk=_buffer.get(_buffer.length()); + _contentPosition += chunk.length(); + _contentView.update(chunk); + _handler.content(chunk); // May recurse here + } + } + + // was this unexpected? + switch(_state) + { + case STATE_END: + case STATE_SEEKING_EOF: + _state=STATE_END; + break; + + case STATE_EOF_CONTENT: + _state=STATE_END; + _handler.messageComplete(_contentPosition); + break; + + default: + _state=STATE_END; + if (!_headResponse) + _handler.earlyEOF(); + _handler.messageComplete(_contentPosition); + } + + if (ex!=null) + throw ex; + + if (!isComplete() && !isIdle()) + throw new EofException(); + + return -1; + } + length=_buffer.length(); + } + + + // Handle header states + byte ch; + byte[] array=_buffer.array(); + int last=_state; + while (_state<STATE_END && length-->0) + { + if (last!=_state) + { + progress++; + last=_state; + } + + ch=_buffer.get(); + + if (_eol == HttpTokens.CARRIAGE_RETURN) + { + if (ch == HttpTokens.LINE_FEED) + { + _eol=HttpTokens.LINE_FEED; + continue; + } + throw new HttpException(HttpStatus.BAD_REQUEST_400); + } + _eol=0; + + switch (_state) + { + case STATE_START: + _contentLength=HttpTokens.UNKNOWN_CONTENT; + _cached=null; + if (ch > HttpTokens.SPACE || ch<0) + { + _buffer.mark(); + _state=STATE_FIELD0; + } + break; + + case STATE_FIELD0: + if (ch == HttpTokens.SPACE) + { + _tok0.update(_buffer.markIndex(), _buffer.getIndex() - 1); + _responseStatus=HttpVersions.CACHE.get(_tok0)==null?-1:0; + _state=STATE_SPACE1; + continue; + } + else if (ch < HttpTokens.SPACE && ch>=0) + { + throw new HttpException(HttpStatus.BAD_REQUEST_400); + } + break; + + case STATE_SPACE1: + if (ch > HttpTokens.SPACE || ch<0) + { + _buffer.mark(); + if (_responseStatus>=0) + { + _state=STATE_STATUS; + _responseStatus=ch-'0'; + } + else + _state=STATE_URI; + } + else if (ch < HttpTokens.SPACE) + { + throw new HttpException(HttpStatus.BAD_REQUEST_400); + } + break; + + case STATE_STATUS: + if (ch == HttpTokens.SPACE) + { + _tok1.update(_buffer.markIndex(), _buffer.getIndex() - 1); + _state=STATE_SPACE2; + continue; + } + else if (ch>='0' && ch<='9') + { + _responseStatus=_responseStatus*10+(ch-'0'); + continue; + } + else if (ch < HttpTokens.SPACE && ch>=0) + { + _handler.startResponse(HttpMethods.CACHE.lookup(_tok0), _responseStatus, null); + _eol=ch; + _state=STATE_HEADER; + _tok0.setPutIndex(_tok0.getIndex()); + _tok1.setPutIndex(_tok1.getIndex()); + _multiLineValue=null; + continue; + } + // not a digit, so must be a URI + _state=STATE_URI; + _responseStatus=-1; + break; + + case STATE_URI: + if (ch == HttpTokens.SPACE) + { + _tok1.update(_buffer.markIndex(), _buffer.getIndex() - 1); + _state=STATE_SPACE2; + continue; + } + else if (ch < HttpTokens.SPACE && ch>=0) + { + // HTTP/0.9 + _handler.startRequest(HttpMethods.CACHE.lookup(_tok0), _buffer.sliceFromMark(), null); + _persistent=false; + _state=STATE_SEEKING_EOF; + _handler.headerComplete(); + _handler.messageComplete(_contentPosition); + return 1; + } + break; + + case STATE_SPACE2: + if (ch > HttpTokens.SPACE || ch<0) + { + _buffer.mark(); + _state=STATE_FIELD2; + } + else if (ch < HttpTokens.SPACE) + { + if (_responseStatus>0) + { + _handler.startResponse(HttpMethods.CACHE.lookup(_tok0), _responseStatus, null); + _eol=ch; + _state=STATE_HEADER; + _tok0.setPutIndex(_tok0.getIndex()); + _tok1.setPutIndex(_tok1.getIndex()); + _multiLineValue=null; + } + else + { + // HTTP/0.9 + _handler.startRequest(HttpMethods.CACHE.lookup(_tok0), _tok1, null); + _persistent=false; + _state=STATE_SEEKING_EOF; + _handler.headerComplete(); + _handler.messageComplete(_contentPosition); + return 1; + } + } + break; + + case STATE_FIELD2: + if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) + { + Buffer version; + if (_responseStatus>0) + _handler.startResponse(version=HttpVersions.CACHE.lookup(_tok0), _responseStatus,_buffer.sliceFromMark()); + else + _handler.startRequest(HttpMethods.CACHE.lookup(_tok0), _tok1, version=HttpVersions.CACHE.lookup(_buffer.sliceFromMark())); + _eol=ch; + _persistent=HttpVersions.CACHE.getOrdinal(version)>=HttpVersions.HTTP_1_1_ORDINAL; + _state=STATE_HEADER; + _tok0.setPutIndex(_tok0.getIndex()); + _tok1.setPutIndex(_tok1.getIndex()); + _multiLineValue=null; + continue; + } + break; + + case STATE_HEADER: + switch(ch) + { + case HttpTokens.COLON: + case HttpTokens.SPACE: + case HttpTokens.TAB: + { + // header value without name - continuation? + _length=-1; + _state=STATE_HEADER_VALUE; + break; + } + + default: + { + // handler last header if any + if (_cached!=null || _tok0.length() > 0 || _tok1.length() > 0 || _multiLineValue != null) + { + Buffer header=_cached!=null?_cached:HttpHeaders.CACHE.lookup(_tok0); + _cached=null; + Buffer value=_multiLineValue == null ? _tok1 : new ByteArrayBuffer(_multiLineValue); + + int ho=HttpHeaders.CACHE.getOrdinal(header); + if (ho >= 0) + { + int vo; + + switch (ho) + { + case HttpHeaders.CONTENT_LENGTH_ORDINAL: + if (_contentLength != HttpTokens.CHUNKED_CONTENT ) + { + try + { + _contentLength=BufferUtil.toLong(value); + } + catch(NumberFormatException e) + { + LOG.ignore(e); + throw new HttpException(HttpStatus.BAD_REQUEST_400); + } + if (_contentLength <= 0) + _contentLength=HttpTokens.NO_CONTENT; + } + break; + + case HttpHeaders.TRANSFER_ENCODING_ORDINAL: + value=HttpHeaderValues.CACHE.lookup(value); + vo=HttpHeaderValues.CACHE.getOrdinal(value); + if (HttpHeaderValues.CHUNKED_ORDINAL == vo) + _contentLength=HttpTokens.CHUNKED_CONTENT; + else + { + String c=value.toString(StringUtil.__ISO_8859_1); + if (c.endsWith(HttpHeaderValues.CHUNKED)) + _contentLength=HttpTokens.CHUNKED_CONTENT; + + else if (c.indexOf(HttpHeaderValues.CHUNKED) >= 0) + throw new HttpException(400,null); + } + break; + + case HttpHeaders.CONNECTION_ORDINAL: + switch(HttpHeaderValues.CACHE.getOrdinal(value)) + { + case HttpHeaderValues.CLOSE_ORDINAL: + _persistent=false; + break; + + case HttpHeaderValues.KEEP_ALIVE_ORDINAL: + _persistent=true; + break; + + case -1: // No match, may be multi valued + { + for (String v : value.toString().split(",")) + { + switch(HttpHeaderValues.CACHE.getOrdinal(v.trim())) + { + case HttpHeaderValues.CLOSE_ORDINAL: + _persistent=false; + break; + + case HttpHeaderValues.KEEP_ALIVE_ORDINAL: + _persistent=true; + break; + } + } + break; + } + } + } + } + + _handler.parsedHeader(header, value); + _tok0.setPutIndex(_tok0.getIndex()); + _tok1.setPutIndex(_tok1.getIndex()); + _multiLineValue=null; + } + _buffer.setMarkIndex(-1); + + // now handle ch + if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) + { + // is it a response that cannot have a body? + if (_responseStatus > 0 && // response + (_responseStatus == 304 || // not-modified response + _responseStatus == 204 || // no-content response + _responseStatus < 200)) // 1xx response + _contentLength=HttpTokens.NO_CONTENT; // ignore any other headers set + // else if we don't know framing + else if (_contentLength == HttpTokens.UNKNOWN_CONTENT) + { + if (_responseStatus == 0 // request + || _responseStatus == 304 // not-modified response + || _responseStatus == 204 // no-content response + || _responseStatus < 200) // 1xx response + _contentLength=HttpTokens.NO_CONTENT; + else + _contentLength=HttpTokens.EOF_CONTENT; + } + + _contentPosition=0; + _eol=ch; + if (_eol==HttpTokens.CARRIAGE_RETURN && _buffer.hasContent() && _buffer.peek()==HttpTokens.LINE_FEED) + _eol=_buffer.get(); + + // We convert _contentLength to an int for this switch statement because + // we don't care about the amount of data available just whether there is some. + switch (_contentLength > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) _contentLength) + { + case HttpTokens.EOF_CONTENT: + _state=STATE_EOF_CONTENT; + _handler.headerComplete(); // May recurse here ! + break; + + case HttpTokens.CHUNKED_CONTENT: + _state=STATE_CHUNKED_CONTENT; + _handler.headerComplete(); // May recurse here ! + break; + + case HttpTokens.NO_CONTENT: + _handler.headerComplete(); + _state=_persistent||(_responseStatus>=100&&_responseStatus<200)?STATE_END:STATE_SEEKING_EOF; + _handler.messageComplete(_contentPosition); + return 1; + + default: + _state=STATE_CONTENT; + _handler.headerComplete(); // May recurse here ! + break; + } + return 1; + } + else + { + // New header + _length=1; + _buffer.mark(); + _state=STATE_HEADER_NAME; + + // try cached name! + if (array!=null) + { + _cached=HttpHeaders.CACHE.getBest(array, _buffer.markIndex(), length+1); + + if (_cached!=null) + { + _length=_cached.length(); + _buffer.setGetIndex(_buffer.markIndex()+_length); + length=_buffer.length(); + } + } + } + } + } + + break; + + case STATE_HEADER_NAME: + switch(ch) + { + case HttpTokens.CARRIAGE_RETURN: + case HttpTokens.LINE_FEED: + if (_length > 0) + _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length); + _eol=ch; + _state=STATE_HEADER; + break; + case HttpTokens.COLON: + if (_length > 0 && _cached==null) + _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length); + _length=-1; + _state=STATE_HEADER_VALUE; + break; + case HttpTokens.SPACE: + case HttpTokens.TAB: + break; + default: + { + _cached=null; + if (_length == -1) + _buffer.mark(); + _length=_buffer.getIndex() - _buffer.markIndex(); + _state=STATE_HEADER_IN_NAME; + } + } + + break; + + case STATE_HEADER_IN_NAME: + switch(ch) + { + case HttpTokens.CARRIAGE_RETURN: + case HttpTokens.LINE_FEED: + if (_length > 0) + _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length); + _eol=ch; + _state=STATE_HEADER; + break; + case HttpTokens.COLON: + if (_length > 0 && _cached==null) + _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length); + _length=-1; + _state=STATE_HEADER_VALUE; + break; + case HttpTokens.SPACE: + case HttpTokens.TAB: + _state=STATE_HEADER_NAME; + break; + default: + { + _cached=null; + _length++; + } + } + break; + + case STATE_HEADER_VALUE: + switch(ch) + { + case HttpTokens.CARRIAGE_RETURN: + case HttpTokens.LINE_FEED: + if (_length > 0) + { + if (_tok1.length() == 0) + _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length); + else + { + // Continuation line! + if (_multiLineValue == null) _multiLineValue=_tok1.toString(StringUtil.__ISO_8859_1); + _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length); + _multiLineValue += " " + _tok1.toString(StringUtil.__ISO_8859_1); + } + } + _eol=ch; + _state=STATE_HEADER; + break; + case HttpTokens.SPACE: + case HttpTokens.TAB: + break; + default: + { + if (_length == -1) + _buffer.mark(); + _length=_buffer.getIndex() - _buffer.markIndex(); + _state=STATE_HEADER_IN_VALUE; + } + } + break; + + case STATE_HEADER_IN_VALUE: + switch(ch) + { + case HttpTokens.CARRIAGE_RETURN: + case HttpTokens.LINE_FEED: + if (_length > 0) + { + if (_tok1.length() == 0) + _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length); + else + { + // Continuation line! + if (_multiLineValue == null) _multiLineValue=_tok1.toString(StringUtil.__ISO_8859_1); + _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length); + _multiLineValue += " " + _tok1.toString(StringUtil.__ISO_8859_1); + } + } + _eol=ch; + _state=STATE_HEADER; + break; + case HttpTokens.SPACE: + case HttpTokens.TAB: + _state=STATE_HEADER_VALUE; + break; + default: + _length++; + } + break; + } + } // end of HEADER states loop + + // ========================== + + // Handle HEAD response + if (_responseStatus>0 && _headResponse) + { + _state=_persistent||(_responseStatus>=100&&_responseStatus<200)?STATE_END:STATE_SEEKING_EOF; + _handler.messageComplete(_contentLength); + } + + + // ========================== + + // Handle _content + length=_buffer.length(); + Buffer chunk; + last=_state; + while (_state > STATE_END && length > 0) + { + if (last!=_state) + { + progress++; + last=_state; + } + + if (_eol == HttpTokens.CARRIAGE_RETURN && _buffer.peek() == HttpTokens.LINE_FEED) + { + _eol=_buffer.get(); + length=_buffer.length(); + continue; + } + _eol=0; + switch (_state) + { + case STATE_EOF_CONTENT: + chunk=_buffer.get(_buffer.length()); + _contentPosition += chunk.length(); + _contentView.update(chunk); + _handler.content(chunk); // May recurse here + // TODO adjust the _buffer to keep unconsumed content + return 1; + + case STATE_CONTENT: + { + long remaining=_contentLength - _contentPosition; + if (remaining == 0) + { + _state=_persistent?STATE_END:STATE_SEEKING_EOF; + _handler.messageComplete(_contentPosition); + return 1; + } + + if (length > remaining) + { + // We can cast reamining to an int as we know that it is smaller than + // or equal to length which is already an int. + length=(int)remaining; + } + + chunk=_buffer.get(length); + _contentPosition += chunk.length(); + _contentView.update(chunk); + _handler.content(chunk); // May recurse here + + if(_contentPosition == _contentLength) + { + _state=_persistent?STATE_END:STATE_SEEKING_EOF; + _handler.messageComplete(_contentPosition); + } + // TODO adjust the _buffer to keep unconsumed content + return 1; + } + + case STATE_CHUNKED_CONTENT: + { + ch=_buffer.peek(); + if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) + _eol=_buffer.get(); + else if (ch <= HttpTokens.SPACE) + _buffer.get(); + else + { + _chunkLength=0; + _chunkPosition=0; + _state=STATE_CHUNK_SIZE; + } + break; + } + + case STATE_CHUNK_SIZE: + { + ch=_buffer.get(); + if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) + { + _eol=ch; + + if (_chunkLength == 0) + { + if (_eol==HttpTokens.CARRIAGE_RETURN && _buffer.hasContent() && _buffer.peek()==HttpTokens.LINE_FEED) + _eol=_buffer.get(); + _state=_persistent?STATE_END:STATE_SEEKING_EOF; + _handler.messageComplete(_contentPosition); + return 1; + } + else + _state=STATE_CHUNK; + } + else if (ch <= HttpTokens.SPACE || ch == HttpTokens.SEMI_COLON) + _state=STATE_CHUNK_PARAMS; + else if (ch >= '0' && ch <= '9') + _chunkLength=_chunkLength * 16 + (ch - '0'); + else if (ch >= 'a' && ch <= 'f') + _chunkLength=_chunkLength * 16 + (10 + ch - 'a'); + else if (ch >= 'A' && ch <= 'F') + _chunkLength=_chunkLength * 16 + (10 + ch - 'A'); + else + throw new IOException("bad chunk char: " + ch); + break; + } + + case STATE_CHUNK_PARAMS: + { + ch=_buffer.get(); + if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) + { + _eol=ch; + if (_chunkLength == 0) + { + if (_eol==HttpTokens.CARRIAGE_RETURN && _buffer.hasContent() && _buffer.peek()==HttpTokens.LINE_FEED) + _eol=_buffer.get(); + _state=_persistent?STATE_END:STATE_SEEKING_EOF; + _handler.messageComplete(_contentPosition); + return 1; + } + else + _state=STATE_CHUNK; + } + break; + } + + case STATE_CHUNK: + { + int remaining=_chunkLength - _chunkPosition; + if (remaining == 0) + { + _state=STATE_CHUNKED_CONTENT; + break; + } + else if (length > remaining) + length=remaining; + chunk=_buffer.get(length); + _contentPosition += chunk.length(); + _chunkPosition += chunk.length(); + _contentView.update(chunk); + _handler.content(chunk); // May recurse here + // TODO adjust the _buffer to keep unconsumed content + return 1; + } + + case STATE_SEEKING_EOF: + { + // Close if there is more data than CRLF + if (_buffer.length()>2) + { + _state=STATE_END; + _endp.close(); + } + else + { + // or if the data is not white space + while (_buffer.length()>0) + if (!Character.isWhitespace(_buffer.get())) + { + _state=STATE_END; + _endp.close(); + _buffer.clear(); + } + } + + _buffer.clear(); + break; + } + } + + length=_buffer.length(); + } + + return progress; + } + catch(HttpException e) + { + _persistent=false; + _state=STATE_SEEKING_EOF; + throw e; + } + } + + /* ------------------------------------------------------------------------------- */ + /** fill the buffers from the endpoint + * + */ + protected int fill() throws IOException + { + // Do we have a buffer? + if (_buffer==null) + _buffer=getHeaderBuffer(); + + // Is there unconsumed content in body buffer + if (_state>STATE_END && _buffer==_header && _header!=null && !_header.hasContent() && _body!=null && _body.hasContent()) + { + _buffer=_body; + return _buffer.length(); + } + + // Shall we switch to a body buffer? + if (_buffer==_header && _state>STATE_END && _header.length()==0 && (_forceContentBuffer || (_contentLength-_contentPosition)>_header.capacity()) && (_body!=null||_buffers!=null)) + { + if (_body==null) + _body=_buffers.getBuffer(); + _buffer=_body; + } + + // Do we have somewhere to fill from? + if (_endp != null ) + { + // Shall we compact the body? + if (_buffer==_body || _state>STATE_END) + { + _buffer.compact(); + } + + // Are we full? + if (_buffer.space() == 0) + { + LOG.warn("HttpParser Full for {} ",_endp); + _buffer.clear(); + throw new HttpException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413, "Request Entity Too Large: "+(_buffer==_body?"body":"head")); + } + + try + { + int filled = _endp.fill(_buffer); + return filled; + } + catch(IOException e) + { + LOG.debug(e); + throw (e instanceof EofException) ? e:new EofException(e); + } + } + + return -1; + } + + /* ------------------------------------------------------------------------------- */ + public void reset() + { + // reset state + _contentView.setGetIndex(_contentView.putIndex()); + _state=_persistent?STATE_START:(_endp.isInputShutdown()?STATE_END:STATE_SEEKING_EOF); + _contentLength=HttpTokens.UNKNOWN_CONTENT; + _contentPosition=0; + _length=0; + _responseStatus=0; + + // Consume LF if CRLF + if (_eol == HttpTokens.CARRIAGE_RETURN && _buffer!=null && _buffer.hasContent() && _buffer.peek() == HttpTokens.LINE_FEED) + _eol=_buffer.get(); + + if (_body!=null && _body.hasContent()) + { + // There is content in the body after the end of the request. + // This is probably a pipelined header of the next request, so we need to + // copy it to the header buffer. + if (_header==null) + getHeaderBuffer(); + else + { + _header.setMarkIndex(-1); + _header.compact(); + } + int take=_header.space(); + if (take>_body.length()) + take=_body.length(); + _body.peek(_body.getIndex(),take); + _body.skip(_header.put(_body.peek(_body.getIndex(),take))); + } + + if (_header!=null) + { + _header.setMarkIndex(-1); + _header.compact(); + } + if (_body!=null) + _body.setMarkIndex(-1); + + _buffer=_header; + returnBuffers(); + } + + + /* ------------------------------------------------------------------------------- */ + public void returnBuffers() + { + if (_body!=null && !_body.hasContent() && _body.markIndex()==-1 && _buffers!=null) + { + if (_buffer==_body) + _buffer=_header; + if (_buffers!=null) + _buffers.returnBuffer(_body); + _body=null; + } + + if (_header!=null && !_header.hasContent() && _header.markIndex()==-1 && _buffers!=null) + { + if (_buffer==_header) + _buffer=null; + _buffers.returnBuffer(_header); + _header=null; + } + } + + /* ------------------------------------------------------------------------------- */ + public void setState(int state) + { + this._state=state; + _contentLength=HttpTokens.UNKNOWN_CONTENT; + } + + /* ------------------------------------------------------------------------------- */ + public String toString(Buffer buf) + { + return "state=" + _state + " length=" + _length + " buf=" + buf.hashCode(); + } + + /* ------------------------------------------------------------------------------- */ + @Override + public String toString() + { + return String.format("%s{s=%d,l=%d,c=%d}", + getClass().getSimpleName(), + _state, + _length, + _contentLength); + } + + /* ------------------------------------------------------------ */ + public Buffer getHeaderBuffer() + { + if (_header == null) + { + _header=_buffers.getHeader(); + _tok0.update(_header); + _tok1.update(_header); + } + return _header; + } + + /* ------------------------------------------------------------ */ + public Buffer getBodyBuffer() + { + return _body; + } + + /* ------------------------------------------------------------ */ + /** + * @param force True if a new buffer will be forced to be used for content and the header buffer will not be used. + */ + public void setForceContentBuffer(boolean force) + { + _forceContentBuffer=force; + } + + /* ------------------------------------------------------------ */ + public Buffer blockForContent(long maxIdleTime) throws IOException + { + if (_contentView.length()>0) + return _contentView; + + if (getState() <= STATE_END || isState(STATE_SEEKING_EOF)) + return null; + + try + { + parseNext(); + + // parse until some progress is made (or IOException thrown for timeout) + while(_contentView.length() == 0 && !(isState(HttpParser.STATE_END)||isState(HttpParser.STATE_SEEKING_EOF)) && _endp!=null && _endp.isOpen()) + { + if (!_endp.isBlocking()) + { + if (parseNext()>0) + continue; + + if (!_endp.blockReadable(maxIdleTime)) + { + _endp.close(); + throw new EofException("timeout"); + } + } + + parseNext(); + } + } + catch(IOException e) + { + // TODO is this needed? + _endp.close(); + throw e; + } + + return _contentView.length()>0?_contentView:null; + } + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see java.io.InputStream#available() + */ + public int available() throws IOException + { + if (_contentView!=null && _contentView.length()>0) + return _contentView.length(); + + if (_endp.isBlocking()) + { + if (_state>0 && _endp instanceof StreamEndPoint) + return ((StreamEndPoint)_endp).getInputStream().available()>0?1:0; + + return 0; + } + + parseNext(); + return _contentView==null?0:_contentView.length(); + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + public static abstract class EventHandler + { + public abstract void content(Buffer ref) throws IOException; + + public void headerComplete() throws IOException + { + } + + public void messageComplete(long contentLength) throws IOException + { + } + + /** + * This is the method called by parser when a HTTP Header name and value is found + */ + public void parsedHeader(Buffer name, Buffer value) throws IOException + { + } + + /** + * This is the method called by parser when the HTTP request line is parsed + */ + public abstract void startRequest(Buffer method, Buffer url, Buffer version) + throws IOException; + + /** + * This is the method called by parser when the HTTP request line is parsed + */ + public abstract void startResponse(Buffer version, int status, Buffer reason) + throws IOException; + + public void earlyEOF() + {} + } + + + + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/http/HttpSchemes.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,38 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.http; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.ByteArrayBuffer; + +/* ------------------------------------------------------------------------------- */ +/** + * + * + */ +public class HttpSchemes +{ + public final static String + HTTP ="http", + HTTPS="https"; + + public final static Buffer + HTTP_BUFFER = new ByteArrayBuffer(HTTP), + HTTPS_BUFFER = new ByteArrayBuffer(HTTPS); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/http/HttpStatus.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,1036 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.http; + +/** + * <p> + * HttpStatusCode enum class, for status codes based on various HTTP RFCs. (see + * table below) + * </p> + * + * <table border="1" cellpadding="5"> + * <tr> + * <th>Enum</th> + * <th>Code</th> + * <th>Message</th> + * <th> + * <a href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a></th> + * <th> + * <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a></th> + * <th> + * <a href="http://tools.ietf.org/html/rfc2518">RFC 2518 - WEBDAV</a></th> + * </tr> + * + * <tr> + * <td><strong><code>Informational - 1xx</code></strong></td> + * <td colspan="5">{@link #isInformational(int)}</td> + * </tr> + * + * <tr> + * <td>{@link #CONTINUE_100}</td> + * <td>100</td> + * <td>Continue</td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.1.1">Sec. 10.1.1</a></td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #SWITCHING_PROTOCOLS_101}</td> + * <td>101</td> + * <td>Switching Protocols</td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.1.2">Sec. 10.1.2</a></td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #PROCESSING_102}</td> + * <td>102</td> + * <td>Processing</td> + * <td> </td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2518#section-10.1">Sec. 10.1</a></td> + * </tr> + * + * <tr> + * <td><strong><code>Success - 2xx</code></strong></td> + * <td colspan="5">{@link #isSuccess(int)}</td> + * </tr> + * + * <tr> + * <td>{@link #OK_200}</td> + * <td>200</td> + * <td>OK</td> + * <td> + * <a href="http://tools.ietf.org/html/rfc1945#section-9.2">Sec. 9.2</a></td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.1">Sec. 10.2.1</a></td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #CREATED_201}</td> + * <td>201</td> + * <td>Created</td> + * <td> + * <a href="http://tools.ietf.org/html/rfc1945#section-9.2">Sec. 9.2</a></td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.2">Sec. 10.2.2</a></td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #ACCEPTED_202}</td> + * <td>202</td> + * <td>Accepted</td> + * <td> + * <a href="http://tools.ietf.org/html/rfc1945#section-9.2">Sec. 9.2</a></td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.3">Sec. 10.2.3</a></td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #NON_AUTHORITATIVE_INFORMATION_203}</td> + * <td>203</td> + * <td>Non Authoritative Information</td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.4">Sec. 10.2.4</a></td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #NO_CONTENT_204}</td> + * <td>204</td> + * <td>No Content</td> + * <td> + * <a href="http://tools.ietf.org/html/rfc1945#section-9.2">Sec. 9.2</a></td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.5">Sec. 10.2.5</a></td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #RESET_CONTENT_205}</td> + * <td>205</td> + * <td>Reset Content</td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.6">Sec. 10.2.6</a></td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #PARTIAL_CONTENT_206}</td> + * <td>206</td> + * <td>Partial Content</td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.7">Sec. 10.2.7</a></td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #MULTI_STATUS_207}</td> + * <td>207</td> + * <td>Multi-Status</td> + * <td> </td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2518#section-10.2">Sec. 10.2</a></td> + * </tr> + * <tr> + * <td> </td> + * <td><strike>207</strike></td> + * <td><strike>Partial Update OK</strike></td> + * <td> </td> + * <td> + * <a href= + * "http://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-rev-01.txt" + * >draft/01</a></td> + * <td> </td> + * </tr> + * + * <tr> + * <td><strong><code>Redirection - 3xx</code></strong></td> + * <td colspan="5">{@link #isRedirection(int)}</td> + * </tr> + * + * <tr> + * <td>{@link #MULTIPLE_CHOICES_300}</td> + * <td>300</td> + * <td>Multiple Choices</td> + * <td> + * <a href="http://tools.ietf.org/html/rfc1945#section-9.3">Sec. 9.3</a></td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.1">Sec. 10.3.1</a></td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #MOVED_PERMANENTLY_301}</td> + * <td>301</td> + * <td>Moved Permanently</td> + * <td> + * <a href="http://tools.ietf.org/html/rfc1945#section-9.3">Sec. 9.3</a></td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.2">Sec. 10.3.2</a></td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #MOVED_TEMPORARILY_302}</td> + * <td>302</td> + * <td>Moved Temporarily</td> + * <td> + * <a href="http://tools.ietf.org/html/rfc1945#section-9.3">Sec. 9.3</a></td> + * <td>(now "<code>302 Found</code>")</td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #FOUND_302}</td> + * <td>302</td> + * <td>Found</td> + * <td>(was "<code>302 Moved Temporarily</code>")</td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.3">Sec. 10.3.3</a></td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #SEE_OTHER_303}</td> + * <td>303</td> + * <td>See Other</td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.4">Sec. 10.3.4</a></td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #NOT_MODIFIED_304}</td> + * <td>304</td> + * <td>Not Modified</td> + * <td> + * <a href="http://tools.ietf.org/html/rfc1945#section-9.3">Sec. 9.3</a></td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.5">Sec. 10.3.5</a></td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #USE_PROXY_305}</td> + * <td>305</td> + * <td>Use Proxy</td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.6">Sec. 10.3.6</a></td> + * <td> </td> + * </tr> + * <tr> + * <td> </td> + * <td>306</td> + * <td><em>(Unused)</em></td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.7">Sec. 10.3.7</a></td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #TEMPORARY_REDIRECT_307}</td> + * <td>307</td> + * <td>Temporary Redirect</td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.8">Sec. 10.3.8</a></td> + * <td> </td> + * </tr> + * + * <tr> + * <td><strong><code>Client Error - 4xx</code></strong></td> + * <td colspan="5">{@link #isClientError(int)}</td> + * </tr> + * + * <tr> + * <td>{@link #BAD_REQUEST_400}</td> + * <td>400</td> + * <td>Bad Request</td> + * <td> + * <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.1">Sec. 10.4.1</a></td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #UNAUTHORIZED_401}</td> + * <td>401</td> + * <td>Unauthorized</td> + * <td> + * <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.2">Sec. 10.4.2</a></td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #PAYMENT_REQUIRED_402}</td> + * <td>402</td> + * <td>Payment Required</td> + * <td> + * <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.3">Sec. 10.4.3</a></td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #FORBIDDEN_403}</td> + * <td>403</td> + * <td>Forbidden</td> + * <td> + * <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.4">Sec. 10.4.4</a></td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #NOT_FOUND_404}</td> + * <td>404</td> + * <td>Not Found</td> + * <td> + * <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.5">Sec. 10.4.5</a></td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #METHOD_NOT_ALLOWED_405}</td> + * <td>405</td> + * <td>Method Not Allowed</td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.6">Sec. 10.4.6</a></td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #NOT_ACCEPTABLE_406}</td> + * <td>406</td> + * <td>Not Acceptable</td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.7">Sec. 10.4.7</a></td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #PROXY_AUTHENTICATION_REQUIRED_407}</td> + * <td>407</td> + * <td>Proxy Authentication Required</td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.8">Sec. 10.4.8</a></td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #REQUEST_TIMEOUT_408}</td> + * <td>408</td> + * <td>Request Timeout</td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.9">Sec. 10.4.9</a></td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #CONFLICT_409}</td> + * <td>409</td> + * <td>Conflict</td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.10">Sec. 10.4.10</a> + * </td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #GONE_410}</td> + * <td>410</td> + * <td>Gone</td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.11">Sec. 10.4.11</a> + * </td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #LENGTH_REQUIRED_411}</td> + * <td>411</td> + * <td>Length Required</td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.12">Sec. 10.4.12</a> + * </td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #PRECONDITION_FAILED_412}</td> + * <td>412</td> + * <td>Precondition Failed</td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.13">Sec. 10.4.13</a> + * </td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #REQUEST_ENTITY_TOO_LARGE_413}</td> + * <td>413</td> + * <td>Request Entity Too Large</td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.14">Sec. 10.4.14</a> + * </td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #REQUEST_URI_TOO_LONG_414}</td> + * <td>414</td> + * <td>Request-URI Too Long</td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.15">Sec. 10.4.15</a> + * </td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #UNSUPPORTED_MEDIA_TYPE_415}</td> + * <td>415</td> + * <td>Unsupported Media Type</td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.16">Sec. 10.4.16</a> + * </td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #REQUESTED_RANGE_NOT_SATISFIABLE_416}</td> + * <td>416</td> + * <td>Requested Range Not Satisfiable</td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.17">Sec. 10.4.17</a> + * </td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #EXPECTATION_FAILED_417}</td> + * <td>417</td> + * <td>Expectation Failed</td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.18">Sec. 10.4.18</a> + * </td> + * <td> </td> + * </tr> + * <tr> + * <td> </td> + * <td><strike>418</strike></td> + * <td><strike>Reauthentication Required</strike></td> + * <td> </td> + * <td> + * <a href= + * "http://tools.ietf.org/html/draft-ietf-http-v11-spec-rev-01#section-10.4.19" + * >draft/01</a></td> + * <td> </td> + * </tr> + * <tr> + * <td> </td> + * <td><strike>418</strike></td> + * <td><strike>Unprocessable Entity</strike></td> + * <td> </td> + * <td> </td> + * <td> + * <a href= + * "http://tools.ietf.org/html/draft-ietf-webdav-protocol-05#section-10.3" + * >draft/05</a></td> + * </tr> + * <tr> + * <td> </td> + * <td><strike>419</strike></td> + * <td><strike>Proxy Reauthentication Required</stike></td> + * <td> </td> + * <td> + * <a href= + * "http://tools.ietf.org/html/draft-ietf-http-v11-spec-rev-01#section-10.4.20" + * >draft/01</a></td> + * <td> </td> + * </tr> + * <tr> + * <td> </td> + * <td><strike>419</strike></td> + * <td><strike>Insufficient Space on Resource</stike></td> + * <td> </td> + * <td> </td> + * <td> + * <a href= + * "http://tools.ietf.org/html/draft-ietf-webdav-protocol-05#section-10.4" + * >draft/05</a></td> + * </tr> + * <tr> + * <td> </td> + * <td><strike>420</strike></td> + * <td><strike>Method Failure</strike></td> + * <td> </td> + * <td> </td> + * <td> + * <a href= + * "http://tools.ietf.org/html/draft-ietf-webdav-protocol-05#section-10.5" + * >draft/05</a></td> + * </tr> + * <tr> + * <td> </td> + * <td>421</td> + * <td><em>(Unused)</em></td> + * <td> </td> + * <td> </td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #UNPROCESSABLE_ENTITY_422}</td> + * <td>422</td> + * <td>Unprocessable Entity</td> + * <td> </td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2518#section-10.3">Sec. 10.3</a></td> + * </tr> + * <tr> + * <td>{@link #LOCKED_423}</td> + * <td>423</td> + * <td>Locked</td> + * <td> </td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2518#section-10.4">Sec. 10.4</a></td> + * </tr> + * <tr> + * <td>{@link #FAILED_DEPENDENCY_424}</td> + * <td>424</td> + * <td>Failed Dependency</td> + * <td> </td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2518#section-10.5">Sec. 10.5</a></td> + * </tr> + * + * <tr> + * <td><strong><code>Server Error - 5xx</code></strong></td> + * <td colspan="5">{@link #isServerError(int)}</td> + * </tr> + * + * <tr> + * <td>{@link #INTERNAL_SERVER_ERROR_500}</td> + * <td>500</td> + * <td>Internal Server Error</td> + * <td> + * <a href="http://tools.ietf.org/html/rfc1945#section-9.5">Sec. 9.5</a></td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.1">Sec. 10.5.1</a></td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #NOT_IMPLEMENTED_501}</td> + * <td>501</td> + * <td>Not Implemented</td> + * <td> + * <a href="http://tools.ietf.org/html/rfc1945#section-9.5">Sec. 9.5</a></td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.2">Sec. 10.5.2</a></td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #BAD_GATEWAY_502}</td> + * <td>502</td> + * <td>Bad Gateway</td> + * <td> + * <a href="http://tools.ietf.org/html/rfc1945#section-9.5">Sec. 9.5</a></td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.3">Sec. 10.5.3</a></td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #SERVICE_UNAVAILABLE_503}</td> + * <td>503</td> + * <td>Service Unavailable</td> + * <td> + * <a href="http://tools.ietf.org/html/rfc1945#section-9.5">Sec. 9.5</a></td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.4">Sec. 10.5.4</a></td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #GATEWAY_TIMEOUT_504}</td> + * <td>504</td> + * <td>Gateway Timeout</td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.5">Sec. 10.5.5</a></td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #HTTP_VERSION_NOT_SUPPORTED_505}</td> + * <td>505</td> + * <td>HTTP Version Not Supported</td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.6">Sec. 10.5.6</a></td> + * <td> </td> + * </tr> + * <tr> + * <td> </td> + * <td>506</td> + * <td><em>(Unused)</em></td> + * <td> </td> + * <td> </td> + * <td> </td> + * </tr> + * <tr> + * <td>{@link #INSUFFICIENT_STORAGE_507}</td> + * <td>507</td> + * <td>Insufficient Storage</td> + * <td> </td> + * <td> </td> + * <td> + * <a href="http://tools.ietf.org/html/rfc2518#section-10.6">Sec. 10.6</a></td> + * </tr> + * + * </table> + * + * @version $Id$ + */ +public class HttpStatus +{ + public final static int CONTINUE_100 = 100; + public final static int SWITCHING_PROTOCOLS_101 = 101; + public final static int PROCESSING_102 = 102; + + public final static int OK_200 = 200; + public final static int CREATED_201 = 201; + public final static int ACCEPTED_202 = 202; + public final static int NON_AUTHORITATIVE_INFORMATION_203 = 203; + public final static int NO_CONTENT_204 = 204; + public final static int RESET_CONTENT_205 = 205; + public final static int PARTIAL_CONTENT_206 = 206; + public final static int MULTI_STATUS_207 = 207; + + public final static int MULTIPLE_CHOICES_300 = 300; + public final static int MOVED_PERMANENTLY_301 = 301; + public final static int MOVED_TEMPORARILY_302 = 302; + public final static int FOUND_302 = 302; + public final static int SEE_OTHER_303 = 303; + public final static int NOT_MODIFIED_304 = 304; + public final static int USE_PROXY_305 = 305; + public final static int TEMPORARY_REDIRECT_307 = 307; + + public final static int BAD_REQUEST_400 = 400; + public final static int UNAUTHORIZED_401 = 401; + public final static int PAYMENT_REQUIRED_402 = 402; + public final static int FORBIDDEN_403 = 403; + public final static int NOT_FOUND_404 = 404; + public final static int METHOD_NOT_ALLOWED_405 = 405; + public final static int NOT_ACCEPTABLE_406 = 406; + public final static int PROXY_AUTHENTICATION_REQUIRED_407 = 407; + public final static int REQUEST_TIMEOUT_408 = 408; + public final static int CONFLICT_409 = 409; + public final static int GONE_410 = 410; + public final static int LENGTH_REQUIRED_411 = 411; + public final static int PRECONDITION_FAILED_412 = 412; + public final static int REQUEST_ENTITY_TOO_LARGE_413 = 413; + public final static int REQUEST_URI_TOO_LONG_414 = 414; + public final static int UNSUPPORTED_MEDIA_TYPE_415 = 415; + public final static int REQUESTED_RANGE_NOT_SATISFIABLE_416 = 416; + public final static int EXPECTATION_FAILED_417 = 417; + public final static int UNPROCESSABLE_ENTITY_422 = 422; + public final static int LOCKED_423 = 423; + public final static int FAILED_DEPENDENCY_424 = 424; + + public final static int INTERNAL_SERVER_ERROR_500 = 500; + public final static int NOT_IMPLEMENTED_501 = 501; + public final static int BAD_GATEWAY_502 = 502; + public final static int SERVICE_UNAVAILABLE_503 = 503; + public final static int GATEWAY_TIMEOUT_504 = 504; + public final static int HTTP_VERSION_NOT_SUPPORTED_505 = 505; + public final static int INSUFFICIENT_STORAGE_507 = 507; + + public static final int MAX_CODE = 507; + + + private static final Code[] codeMap = new Code[MAX_CODE+1]; + + static + { + for (Code code : Code.values()) + { + codeMap[code._code] = code; + } + } + + + public enum Code + { + /* + * -------------------------------------------------------------------- + * Informational messages in 1xx series. As defined by ... RFC 1945 - + * HTTP/1.0 RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV + */ + + /** <code>100 Continue</code> */ + CONTINUE(CONTINUE_100, "Continue"), + /** <code>101 Switching Protocols</code> */ + SWITCHING_PROTOCOLS(SWITCHING_PROTOCOLS_101, "Switching Protocols"), + /** <code>102 Processing</code> */ + PROCESSING(PROCESSING_102, "Processing"), + + /* + * -------------------------------------------------------------------- + * Success messages in 2xx series. As defined by ... RFC 1945 - HTTP/1.0 + * RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV + */ + + /** <code>200 OK</code> */ + OK(OK_200, "OK"), + /** <code>201 Created</code> */ + CREATED(CREATED_201, "Created"), + /** <code>202 Accepted</code> */ + ACCEPTED(ACCEPTED_202, "Accepted"), + /** <code>203 Non Authoritative Information</code> */ + NON_AUTHORITATIVE_INFORMATION(NON_AUTHORITATIVE_INFORMATION_203, "Non Authoritative Information"), + /** <code>204 No Content</code> */ + NO_CONTENT(NO_CONTENT_204, "No Content"), + /** <code>205 Reset Content</code> */ + RESET_CONTENT(RESET_CONTENT_205, "Reset Content"), + /** <code>206 Partial Content</code> */ + PARTIAL_CONTENT(PARTIAL_CONTENT_206, "Partial Content"), + /** <code>207 Multi-Status</code> */ + MULTI_STATUS(MULTI_STATUS_207, "Multi-Status"), + + /* + * -------------------------------------------------------------------- + * Redirection messages in 3xx series. As defined by ... RFC 1945 - + * HTTP/1.0 RFC 2616 - HTTP/1.1 + */ + + /** <code>300 Mutliple Choices</code> */ + MULTIPLE_CHOICES(MULTIPLE_CHOICES_300, "Multiple Choices"), + /** <code>301 Moved Permanently</code> */ + MOVED_PERMANENTLY(MOVED_PERMANENTLY_301, "Moved Permanently"), + /** <code>302 Moved Temporarily</code> */ + MOVED_TEMPORARILY(MOVED_TEMPORARILY_302, "Moved Temporarily"), + /** <code>302 Found</code> */ + FOUND(FOUND_302, "Found"), + /** <code>303 See Other</code> */ + SEE_OTHER(SEE_OTHER_303, "See Other"), + /** <code>304 Not Modified</code> */ + NOT_MODIFIED(NOT_MODIFIED_304, "Not Modified"), + /** <code>305 Use Proxy</code> */ + USE_PROXY(USE_PROXY_305, "Use Proxy"), + /** <code>307 Temporary Redirect</code> */ + TEMPORARY_REDIRECT(TEMPORARY_REDIRECT_307, "Temporary Redirect"), + + /* + * -------------------------------------------------------------------- + * Client Error messages in 4xx series. As defined by ... RFC 1945 - + * HTTP/1.0 RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV + */ + + /** <code>400 Bad Request</code> */ + BAD_REQUEST(BAD_REQUEST_400, "Bad Request"), + /** <code>401 Unauthorized</code> */ + UNAUTHORIZED(UNAUTHORIZED_401, "Unauthorized"), + /** <code>402 Payment Required</code> */ + PAYMENT_REQUIRED(PAYMENT_REQUIRED_402, "Payment Required"), + /** <code>403 Forbidden</code> */ + FORBIDDEN(FORBIDDEN_403, "Forbidden"), + /** <code>404 Not Found</code> */ + NOT_FOUND(NOT_FOUND_404, "Not Found"), + /** <code>405 Method Not Allowed</code> */ + METHOD_NOT_ALLOWED(METHOD_NOT_ALLOWED_405, "Method Not Allowed"), + /** <code>406 Not Acceptable</code> */ + NOT_ACCEPTABLE(NOT_ACCEPTABLE_406, "Not Acceptable"), + /** <code>407 Proxy Authentication Required</code> */ + PROXY_AUTHENTICATION_REQUIRED(PROXY_AUTHENTICATION_REQUIRED_407, "Proxy Authentication Required"), + /** <code>408 Request Timeout</code> */ + REQUEST_TIMEOUT(REQUEST_TIMEOUT_408, "Request Timeout"), + /** <code>409 Conflict</code> */ + CONFLICT(CONFLICT_409, "Conflict"), + /** <code>410 Gone</code> */ + GONE(GONE_410, "Gone"), + /** <code>411 Length Required</code> */ + LENGTH_REQUIRED(LENGTH_REQUIRED_411, "Length Required"), + /** <code>412 Precondition Failed</code> */ + PRECONDITION_FAILED(PRECONDITION_FAILED_412, "Precondition Failed"), + /** <code>413 Request Entity Too Large</code> */ + REQUEST_ENTITY_TOO_LARGE(REQUEST_ENTITY_TOO_LARGE_413, "Request Entity Too Large"), + /** <code>414 Request-URI Too Long</code> */ + REQUEST_URI_TOO_LONG(REQUEST_URI_TOO_LONG_414, "Request-URI Too Long"), + /** <code>415 Unsupported Media Type</code> */ + UNSUPPORTED_MEDIA_TYPE(UNSUPPORTED_MEDIA_TYPE_415, "Unsupported Media Type"), + /** <code>416 Requested Range Not Satisfiable</code> */ + REQUESTED_RANGE_NOT_SATISFIABLE(REQUESTED_RANGE_NOT_SATISFIABLE_416, "Requested Range Not Satisfiable"), + /** <code>417 Expectation Failed</code> */ + EXPECTATION_FAILED(EXPECTATION_FAILED_417, "Expectation Failed"), + /** <code>422 Unprocessable Entity</code> */ + UNPROCESSABLE_ENTITY(UNPROCESSABLE_ENTITY_422, "Unprocessable Entity"), + /** <code>423 Locked</code> */ + LOCKED(LOCKED_423, "Locked"), + /** <code>424 Failed Dependency</code> */ + FAILED_DEPENDENCY(FAILED_DEPENDENCY_424, "Failed Dependency"), + + /* + * -------------------------------------------------------------------- + * Server Error messages in 5xx series. As defined by ... RFC 1945 - + * HTTP/1.0 RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV + */ + + /** <code>500 Server Error</code> */ + INTERNAL_SERVER_ERROR(INTERNAL_SERVER_ERROR_500, "Server Error"), + /** <code>501 Not Implemented</code> */ + NOT_IMPLEMENTED(NOT_IMPLEMENTED_501, "Not Implemented"), + /** <code>502 Bad Gateway</code> */ + BAD_GATEWAY(BAD_GATEWAY_502, "Bad Gateway"), + /** <code>503 Service Unavailable</code> */ + SERVICE_UNAVAILABLE(SERVICE_UNAVAILABLE_503, "Service Unavailable"), + /** <code>504 Gateway Timeout</code> */ + GATEWAY_TIMEOUT(GATEWAY_TIMEOUT_504, "Gateway Timeout"), + /** <code>505 HTTP Version Not Supported</code> */ + HTTP_VERSION_NOT_SUPPORTED(HTTP_VERSION_NOT_SUPPORTED_505, "HTTP Version Not Supported"), + /** <code>507 Insufficient Storage</code> */ + INSUFFICIENT_STORAGE(INSUFFICIENT_STORAGE_507, "Insufficient Storage"); + + private final int _code; + private final String _message; + + private Code(int code, String message) + { + this._code = code; + _message=message; + } + + public int getCode() + { + return _code; + } + + public String getMessage() + { + return _message; + } + + + public boolean equals(int code) + { + return (this._code == code); + } + + @Override + public String toString() + { + return String.format("[%03d %s]",this._code,this.getMessage()); + } + + /** + * Simple test against an code to determine if it falls into the + * <code>Informational</code> message category as defined in the <a + * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, + * and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 - + * HTTP/1.1</a>. + * + * @return true if within range of codes that belongs to + * <code>Informational</code> messages. + */ + public boolean isInformational() + { + return HttpStatus.isInformational(this._code); + } + + /** + * Simple test against an code to determine if it falls into the + * <code>Success</code> message category as defined in the <a + * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, + * and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 - + * HTTP/1.1</a>. + * + * @return true if within range of codes that belongs to + * <code>Success</code> messages. + */ + public boolean isSuccess() + { + return HttpStatus.isSuccess(this._code); + } + + /** + * Simple test against an code to determine if it falls into the + * <code>Redirection</code> message category as defined in the <a + * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, + * and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 - + * HTTP/1.1</a>. + * + * @return true if within range of codes that belongs to + * <code>Redirection</code> messages. + */ + public boolean isRedirection() + { + return HttpStatus.isRedirection(this._code); + } + + /** + * Simple test against an code to determine if it falls into the + * <code>Client Error</code> message category as defined in the <a + * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, + * and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 - + * HTTP/1.1</a>. + * + * @return true if within range of codes that belongs to + * <code>Client Error</code> messages. + */ + public boolean isClientError() + { + return HttpStatus.isClientError(this._code); + } + + /** + * Simple test against an code to determine if it falls into the + * <code>Server Error</code> message category as defined in the <a + * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, + * and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 - + * HTTP/1.1</a>. + * + * @return true if within range of codes that belongs to + * <code>Server Error</code> messages. + */ + public boolean isServerError() + { + return HttpStatus.isServerError(this._code); + } + } + + + /** + * Get the HttpStatusCode for a specific code + * + * @param code + * the code to lookup. + * @return the {@link HttpStatus} if found, or null if not found. + */ + public static Code getCode(int code) + { + if (code <= MAX_CODE) + { + return codeMap[code]; + } + return null; + } + + /** + * Get the status message for a specific code. + * + * @param code + * the code to look up + * @return the specific message, or the code number itself if code + * does not match known list. + */ + public static String getMessage(int code) + { + Code codeEnum = getCode(code); + if (codeEnum != null) + { + return codeEnum.getMessage(); + } + else + { + return Integer.toString(code); + } + } + + /** + * Simple test against an code to determine if it falls into the + * <code>Informational</code> message category as defined in the <a + * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a + * href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>. + * + * @param code + * the code to test. + * @return true if within range of codes that belongs to + * <code>Informational</code> messages. + */ + public static boolean isInformational(int code) + { + return ((100 <= code) && (code <= 199)); + } + + /** + * Simple test against an code to determine if it falls into the + * <code>Success</code> message category as defined in the <a + * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a + * href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>. + * + * @param code + * the code to test. + * @return true if within range of codes that belongs to + * <code>Success</code> messages. + */ + public static boolean isSuccess(int code) + { + return ((200 <= code) && (code <= 299)); + } + + /** + * Simple test against an code to determine if it falls into the + * <code>Redirection</code> message category as defined in the <a + * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a + * href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>. + * + * @param code + * the code to test. + * @return true if within range of codes that belongs to + * <code>Redirection</code> messages. + */ + public static boolean isRedirection(int code) + { + return ((300 <= code) && (code <= 399)); + } + + /** + * Simple test against an code to determine if it falls into the + * <code>Client Error</code> message category as defined in the <a + * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a + * href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>. + * + * @param code + * the code to test. + * @return true if within range of codes that belongs to + * <code>Client Error</code> messages. + */ + public static boolean isClientError(int code) + { + return ((400 <= code) && (code <= 499)); + } + + /** + * Simple test against an code to determine if it falls into the + * <code>Server Error</code> message category as defined in the <a + * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a + * href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>. + * + * @param code + * the code to test. + * @return true if within range of codes that belongs to + * <code>Server Error</code> messages. + */ + public static boolean isServerError(int code) + { + return ((500 <= code) && (code <= 599)); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/http/HttpTokens.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,42 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.http; + +/** + * HTTP constants + */ +public interface HttpTokens +{ + // Terminal symbols. + static final byte COLON= (byte)':'; + static final byte SPACE= 0x20; + static final byte CARRIAGE_RETURN= 0x0D; + static final byte LINE_FEED= 0x0A; + static final byte[] CRLF = {CARRIAGE_RETURN,LINE_FEED}; + static final byte SEMI_COLON= (byte)';'; + static final byte TAB= 0x09; + + public static final int SELF_DEFINING_CONTENT= -4; + public static final int UNKNOWN_CONTENT= -3; + public static final int CHUNKED_CONTENT= -2; + public static final int EOF_CONTENT= -1; + public static final int NO_CONTENT= 0; + + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/http/HttpURI.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,771 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.http; + +import java.io.UnsupportedEncodingException; +import java.net.URI; + +import org.eclipse.jetty.util.MultiMap; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.UrlEncoded; +import org.eclipse.jetty.util.Utf8StringBuilder; + + +/* ------------------------------------------------------------ */ +/** Http URI. + * Parse a HTTP URI from a string or byte array. Given a URI + * <code>http://user@host:port/path/info;param?query#fragment</code> + * this class will split it into the following undecoded optional elements:<ul> + * <li>{@link #getScheme()} - http:</li> + * <li>{@link #getAuthority()} - //name@host:port</li> + * <li>{@link #getHost()} - host</li> + * <li>{@link #getPort()} - port</li> + * <li>{@link #getPath()} - /path/info</li> + * <li>{@link #getParam()} - param</li> + * <li>{@link #getQuery()} - query</li> + * <li>{@link #getFragment()} - fragment</li> + * </ul> + * + */ +public class HttpURI +{ + private static final byte[] __empty={}; + private final static int + START=0, + AUTH_OR_PATH=1, + SCHEME_OR_PATH=2, + AUTH=4, + IPV6=5, + PORT=6, + PATH=7, + PARAM=8, + QUERY=9, + ASTERISK=10; + + boolean _partial=false; + byte[] _raw=__empty; + String _rawString; + int _scheme; + int _authority; + int _host; + int _port; + int _portValue; + int _path; + int _param; + int _query; + int _fragment; + int _end; + boolean _encoded=false; + + final Utf8StringBuilder _utf8b = new Utf8StringBuilder(64); + + public HttpURI() + { + + } + + /* ------------------------------------------------------------ */ + /** + * @param parsePartialAuth If True, parse auth without prior scheme, else treat all URIs starting with / as paths + */ + public HttpURI(boolean parsePartialAuth) + { + _partial=parsePartialAuth; + } + + public HttpURI(String raw) + { + _rawString=raw; + byte[] b; + try + { + b = raw.getBytes(StringUtil.__UTF8); + } + catch (UnsupportedEncodingException e) + { + throw new RuntimeException(e.getMessage()); + } + parse(b,0,b.length); + } + + public HttpURI(byte[] raw,int offset, int length) + { + parse2(raw,offset,length); + } + + public HttpURI(URI uri) + { + parse(uri.toASCIIString()); + } + + public void parse(String raw) + { + byte[] b = raw.getBytes(); + parse2(b,0,b.length); + _rawString=raw; + } + + public void parse(byte[] raw,int offset, int length) + { + _rawString=null; + parse2(raw,offset,length); + } + + + public void parseConnect(byte[] raw,int offset, int length) + { + _rawString=null; + _encoded=false; + _raw=raw; + int i=offset; + int e=offset+length; + int state=AUTH; + _end=offset+length; + _scheme=offset; + _authority=offset; + _host=offset; + _port=_end; + _portValue=-1; + _path=_end; + _param=_end; + _query=_end; + _fragment=_end; + + loop: while (i<e) + { + char c=(char)(0xff&_raw[i]); + int s=i++; + + switch (state) + { + case AUTH: + { + switch (c) + { + case ':': + { + _port = s; + break loop; + } + case '[': + { + state = IPV6; + break; + } + } + continue; + } + + case IPV6: + { + switch (c) + { + case '/': + { + throw new IllegalArgumentException("No closing ']' for " + StringUtil.toString(_raw,offset,length,URIUtil.__CHARSET)); + } + case ']': + { + state = AUTH; + break; + } + } + + continue; + } + } + } + + if (_port<_path) + _portValue=TypeUtil.parseInt(_raw, _port+1, _path-_port-1,10); + else + throw new IllegalArgumentException("No port"); + _path=offset; + } + + + private void parse2(byte[] raw,int offset, int length) + { + _encoded=false; + _raw=raw; + int i=offset; + int e=offset+length; + int state=START; + int m=offset; + _end=offset+length; + _scheme=offset; + _authority=offset; + _host=offset; + _port=offset; + _portValue=-1; + _path=offset; + _param=_end; + _query=_end; + _fragment=_end; + while (i<e) + { + char c=(char)(0xff&_raw[i]); + int s=i++; + + state: switch (state) + { + case START: + { + m=s; + switch(c) + { + case '/': + state=AUTH_OR_PATH; + break; + case ';': + _param=s; + state=PARAM; + break; + case '?': + _param=s; + _query=s; + state=QUERY; + break; + case '#': + _param=s; + _query=s; + _fragment=s; + break; + case '*': + _path=s; + state=ASTERISK; + break; + + default: + state=SCHEME_OR_PATH; + } + + continue; + } + + case AUTH_OR_PATH: + { + if ((_partial||_scheme!=_authority) && c=='/') + { + _host=i; + _port=_end; + _path=_end; + state=AUTH; + } + else if (c==';' || c=='?' || c=='#') + { + i--; + state=PATH; + } + else + { + _host=m; + _port=m; + state=PATH; + } + continue; + } + + case SCHEME_OR_PATH: + { + // short cut for http and https + if (length>6 && c=='t') + { + if (_raw[offset+3]==':') + { + s=offset+3; + i=offset+4; + c=':'; + } + else if (_raw[offset+4]==':') + { + s=offset+4; + i=offset+5; + c=':'; + } + else if (_raw[offset+5]==':') + { + s=offset+5; + i=offset+6; + c=':'; + } + } + + switch (c) + { + case ':': + { + m = i++; + _authority = m; + _path = m; + c = (char)(0xff & _raw[i]); + if (c == '/') + state = AUTH_OR_PATH; + else + { + _host = m; + _port = m; + state = PATH; + } + break; + } + + case '/': + { + state = PATH; + break; + } + + case ';': + { + _param = s; + state = PARAM; + break; + } + + case '?': + { + _param = s; + _query = s; + state = QUERY; + break; + } + + case '#': + { + _param = s; + _query = s; + _fragment = s; + break; + } + } + continue; + } + + case AUTH: + { + switch (c) + { + + case '/': + { + m = s; + _path = m; + _port = _path; + state = PATH; + break; + } + case '@': + { + _host = i; + break; + } + case ':': + { + _port = s; + state = PORT; + break; + } + case '[': + { + state = IPV6; + break; + } + } + continue; + } + + case IPV6: + { + switch (c) + { + case '/': + { + throw new IllegalArgumentException("No closing ']' for " + StringUtil.toString(_raw,offset,length,URIUtil.__CHARSET)); + } + case ']': + { + state = AUTH; + break; + } + } + + continue; + } + + case PORT: + { + if (c=='/') + { + m=s; + _path=m; + if (_port<=_authority) + _port=_path; + state=PATH; + } + continue; + } + + case PATH: + { + switch (c) + { + case ';': + { + _param = s; + state = PARAM; + break; + } + case '?': + { + _param = s; + _query = s; + state = QUERY; + break; + } + case '#': + { + _param = s; + _query = s; + _fragment = s; + break state; + } + case '%': + { + _encoded=true; + } + } + continue; + } + + case PARAM: + { + switch (c) + { + case '?': + { + _query = s; + state = QUERY; + break; + } + case '#': + { + _query = s; + _fragment = s; + break state; + } + } + continue; + } + + case QUERY: + { + if (c=='#') + { + _fragment=s; + break state; + } + continue; + } + + case ASTERISK: + { + throw new IllegalArgumentException("only '*'"); + } + } + } + + if (_port<_path) + _portValue=TypeUtil.parseInt(_raw, _port+1, _path-_port-1,10); + } + + private String toUtf8String(int offset,int length) + { + _utf8b.reset(); + _utf8b.append(_raw,offset,length); + return _utf8b.toString(); + } + + public String getScheme() + { + if (_scheme==_authority) + return null; + int l=_authority-_scheme; + if (l==5 && + _raw[_scheme]=='h' && + _raw[_scheme+1]=='t' && + _raw[_scheme+2]=='t' && + _raw[_scheme+3]=='p' ) + return HttpSchemes.HTTP; + if (l==6 && + _raw[_scheme]=='h' && + _raw[_scheme+1]=='t' && + _raw[_scheme+2]=='t' && + _raw[_scheme+3]=='p' && + _raw[_scheme+4]=='s' ) + return HttpSchemes.HTTPS; + + return toUtf8String(_scheme,_authority-_scheme-1); + } + + public String getAuthority() + { + if (_authority==_path) + return null; + return toUtf8String(_authority,_path-_authority); + } + + public String getHost() + { + if (_host==_port) + return null; + return toUtf8String(_host,_port-_host); + } + + public int getPort() + { + return _portValue; + } + + public String getPath() + { + if (_path==_param) + return null; + return toUtf8String(_path,_param-_path); + } + + public String getDecodedPath() + { + if (_path==_param) + return null; + + int length = _param-_path; + boolean decoding=false; + + for (int i=_path;i<_param;i++) + { + byte b = _raw[i]; + + if (b=='%') + { + if (!decoding) + { + _utf8b.reset(); + _utf8b.append(_raw,_path,i-_path); + decoding=true; + } + + if ((i+2)>=_param) + throw new IllegalArgumentException("Bad % encoding: "+this); + if (_raw[i+1]=='u') + { + if ((i+5)>=_param) + throw new IllegalArgumentException("Bad %u encoding: "+this); + try + { + String unicode = new String(Character.toChars(TypeUtil.parseInt(_raw,i+2,4,16))); + _utf8b.getStringBuilder().append(unicode); + i+=5; + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + else + { + b=(byte)(0xff&TypeUtil.parseInt(_raw,i+1,2,16)); + _utf8b.append(b); + i+=2; + } + continue; + } + else if (decoding) + { + _utf8b.append(b); + } + } + + if (!decoding) + return toUtf8String(_path,length); + return _utf8b.toString(); + } + + public String getDecodedPath(String encoding) + { + if (_path==_param) + return null; + + int length = _param-_path; + byte[] bytes=null; + int n=0; + + for (int i=_path;i<_param;i++) + { + byte b = _raw[i]; + + if (b=='%') + { + if (bytes==null) + { + bytes=new byte[length]; + System.arraycopy(_raw,_path,bytes,0,n); + } + + if ((i+2)>=_param) + throw new IllegalArgumentException("Bad % encoding: "+this); + if (_raw[i+1]=='u') + { + if ((i+5)>=_param) + throw new IllegalArgumentException("Bad %u encoding: "+this); + + try + { + String unicode = new String(Character.toChars(TypeUtil.parseInt(_raw,i+2,4,16))); + byte[] encoded = unicode.getBytes(encoding); + System.arraycopy(encoded,0,bytes,n,encoded.length); + n+=encoded.length; + i+=5; + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + else + { + b=(byte)(0xff&TypeUtil.parseInt(_raw,i+1,2,16)); + bytes[n++]=b; + i+=2; + } + continue; + } + else if (bytes==null) + { + n++; + continue; + } + + bytes[n++]=b; + } + + + if (bytes==null) + return StringUtil.toString(_raw,_path,_param-_path,encoding); + + return StringUtil.toString(bytes,0,n,encoding); + } + + + + + + + + public String getPathAndParam() + { + if (_path==_query) + return null; + return toUtf8String(_path,_query-_path); + } + + public String getCompletePath() + { + if (_path==_end) + return null; + return toUtf8String(_path,_end-_path); + } + + public String getParam() + { + if (_param==_query) + return null; + return toUtf8String(_param+1,_query-_param-1); + } + + public String getQuery() + { + if (_query==_fragment) + return null; + return toUtf8String(_query+1,_fragment-_query-1); + } + + public String getQuery(String encoding) + { + if (_query==_fragment) + return null; + return StringUtil.toString(_raw,_query+1,_fragment-_query-1,encoding); + } + + public boolean hasQuery() + { + return (_fragment>_query); + } + + public String getFragment() + { + if (_fragment==_end) + return null; + return toUtf8String(_fragment+1,_end-_fragment-1); + } + + public void decodeQueryTo(MultiMap parameters) + { + if (_query==_fragment) + return; + _utf8b.reset(); + UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters,_utf8b); + } + + public void decodeQueryTo(MultiMap parameters, String encoding) + throws UnsupportedEncodingException + { + if (_query==_fragment) + return; + + if (encoding==null || StringUtil.isUTF8(encoding)) + UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters); + else + UrlEncoded.decodeTo(StringUtil.toString(_raw,_query+1,_fragment-_query-1,encoding),parameters,encoding); + } + + public void clear() + { + _scheme=_authority=_host=_port=_path=_param=_query=_fragment=_end=0; + _raw=__empty; + _rawString=""; + _encoded=false; + } + + @Override + public String toString() + { + if (_rawString==null) + _rawString=toUtf8String(_scheme,_end-_scheme); + return _rawString; + } + + public void writeTo(Utf8StringBuilder buf) + { + buf.append(_raw,_scheme,_end-_scheme); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/http/HttpVersions.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,47 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.http; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.BufferCache; + +/* ------------------------------------------------------------------------------- */ +/** + * + * + */ +public class HttpVersions +{ + public final static String + HTTP_0_9 = "", + HTTP_1_0 = "HTTP/1.0", + HTTP_1_1 = "HTTP/1.1"; + + public final static int + HTTP_0_9_ORDINAL=9, + HTTP_1_0_ORDINAL=10, + HTTP_1_1_ORDINAL=11; + + public final static BufferCache CACHE = new BufferCache(); + + public final static Buffer + HTTP_0_9_BUFFER=CACHE.add(HTTP_0_9,HTTP_0_9_ORDINAL), + HTTP_1_0_BUFFER=CACHE.add(HTTP_1_0,HTTP_1_0_ORDINAL), + HTTP_1_1_BUFFER=CACHE.add(HTTP_1_1,HTTP_1_1_ORDINAL); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/http/MimeTypes.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,376 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.http; + +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.BufferCache; +import org.eclipse.jetty.io.BufferCache.CachedBuffer; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + + +/* ------------------------------------------------------------ */ +/** + * + */ +public class MimeTypes +{ + private static final Logger LOG = Log.getLogger(MimeTypes.class); + + public final static String + FORM_ENCODED="application/x-www-form-urlencoded", + MESSAGE_HTTP="message/http", + MULTIPART_BYTERANGES="multipart/byteranges", + + TEXT_HTML="text/html", + TEXT_PLAIN="text/plain", + TEXT_XML="text/xml", + TEXT_JSON="text/json", + + TEXT_HTML_8859_1="text/html;charset=ISO-8859-1", + TEXT_PLAIN_8859_1="text/plain;charset=ISO-8859-1", + TEXT_XML_8859_1="text/xml;charset=ISO-8859-1", + + TEXT_HTML_UTF_8="text/html;charset=UTF-8", + TEXT_PLAIN_UTF_8="text/plain;charset=UTF-8", + TEXT_XML_UTF_8="text/xml;charset=UTF-8", + TEXT_JSON_UTF_8="text/json;charset=UTF-8"; + + private final static String + TEXT_HTML__8859_1="text/html; charset=ISO-8859-1", + TEXT_PLAIN__8859_1="text/plain; charset=ISO-8859-1", + TEXT_XML__8859_1="text/xml; charset=ISO-8859-1", + TEXT_HTML__UTF_8="text/html; charset=UTF-8", + TEXT_PLAIN__UTF_8="text/plain; charset=UTF-8", + TEXT_XML__UTF_8="text/xml; charset=UTF-8", + TEXT_JSON__UTF_8="text/json; charset=UTF-8"; + + private final static int + FORM_ENCODED_ORDINAL=1, + MESSAGE_HTTP_ORDINAL=2, + MULTIPART_BYTERANGES_ORDINAL=3, + + TEXT_HTML_ORDINAL=4, + TEXT_PLAIN_ORDINAL=5, + TEXT_XML_ORDINAL=6, + TEXT_JSON_ORDINAL=7, + + TEXT_HTML_8859_1_ORDINAL=8, + TEXT_PLAIN_8859_1_ORDINAL=9, + TEXT_XML_8859_1_ORDINAL=10, + + TEXT_HTML_UTF_8_ORDINAL=11, + TEXT_PLAIN_UTF_8_ORDINAL=12, + TEXT_XML_UTF_8_ORDINAL=13, + TEXT_JSON_UTF_8_ORDINAL=14; + + private static int __index=15; + + public final static BufferCache CACHE = new BufferCache(); + + public final static CachedBuffer + FORM_ENCODED_BUFFER=CACHE.add(FORM_ENCODED,FORM_ENCODED_ORDINAL), + MESSAGE_HTTP_BUFFER=CACHE.add(MESSAGE_HTTP, MESSAGE_HTTP_ORDINAL), + MULTIPART_BYTERANGES_BUFFER=CACHE.add(MULTIPART_BYTERANGES,MULTIPART_BYTERANGES_ORDINAL), + + TEXT_HTML_BUFFER=CACHE.add(TEXT_HTML,TEXT_HTML_ORDINAL), + TEXT_PLAIN_BUFFER=CACHE.add(TEXT_PLAIN,TEXT_PLAIN_ORDINAL), + TEXT_XML_BUFFER=CACHE.add(TEXT_XML,TEXT_XML_ORDINAL), + TEXT_JSON_BUFFER=CACHE.add(TEXT_JSON,TEXT_JSON_ORDINAL), + + TEXT_HTML_8859_1_BUFFER=CACHE.add(TEXT_HTML_8859_1,TEXT_HTML_8859_1_ORDINAL), + TEXT_PLAIN_8859_1_BUFFER=CACHE.add(TEXT_PLAIN_8859_1,TEXT_PLAIN_8859_1_ORDINAL), + TEXT_XML_8859_1_BUFFER=CACHE.add(TEXT_XML_8859_1,TEXT_XML_8859_1_ORDINAL), + + TEXT_HTML_UTF_8_BUFFER=CACHE.add(TEXT_HTML_UTF_8,TEXT_HTML_UTF_8_ORDINAL), + TEXT_PLAIN_UTF_8_BUFFER=CACHE.add(TEXT_PLAIN_UTF_8,TEXT_PLAIN_UTF_8_ORDINAL), + TEXT_XML_UTF_8_BUFFER=CACHE.add(TEXT_XML_UTF_8,TEXT_XML_UTF_8_ORDINAL), + TEXT_JSON_UTF_8_BUFFER=CACHE.add(TEXT_JSON_UTF_8,TEXT_JSON_UTF_8_ORDINAL), + + TEXT_HTML__8859_1_BUFFER=CACHE.add(TEXT_HTML__8859_1,TEXT_HTML_8859_1_ORDINAL), + TEXT_PLAIN__8859_1_BUFFER=CACHE.add(TEXT_PLAIN__8859_1,TEXT_PLAIN_8859_1_ORDINAL), + TEXT_XML__8859_1_BUFFER=CACHE.add(TEXT_XML__8859_1,TEXT_XML_8859_1_ORDINAL), + + TEXT_HTML__UTF_8_BUFFER=CACHE.add(TEXT_HTML__UTF_8,TEXT_HTML_UTF_8_ORDINAL), + TEXT_PLAIN__UTF_8_BUFFER=CACHE.add(TEXT_PLAIN__UTF_8,TEXT_PLAIN_UTF_8_ORDINAL), + TEXT_XML__UTF_8_BUFFER=CACHE.add(TEXT_XML__UTF_8,TEXT_XML_UTF_8_ORDINAL), + TEXT_JSON__UTF_8_BUFFER=CACHE.add(TEXT_JSON__UTF_8,TEXT_JSON_UTF_8_ORDINAL); + + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + private final static Map __dftMimeMap = new HashMap(); + private final static Map __encodings = new HashMap(); + static + { + try + { + ResourceBundle mime = ResourceBundle.getBundle("org/eclipse/jetty/http/mime"); + Enumeration i = mime.getKeys(); + while(i.hasMoreElements()) + { + String ext = (String)i.nextElement(); + String m = mime.getString(ext); + __dftMimeMap.put(StringUtil.asciiToLowerCase(ext),normalizeMimeType(m)); + } + } + catch(MissingResourceException e) + { + LOG.warn(e.toString()); + LOG.debug(e); + } + + try + { + ResourceBundle encoding = ResourceBundle.getBundle("org/eclipse/jetty/http/encoding"); + Enumeration i = encoding.getKeys(); + while(i.hasMoreElements()) + { + Buffer type = normalizeMimeType((String)i.nextElement()); + __encodings.put(type,encoding.getString(type.toString())); + } + } + catch(MissingResourceException e) + { + LOG.warn(e.toString()); + LOG.debug(e); + } + + + TEXT_HTML_BUFFER.setAssociate("ISO-8859-1",TEXT_HTML_8859_1_BUFFER); + TEXT_HTML_BUFFER.setAssociate("ISO_8859_1",TEXT_HTML_8859_1_BUFFER); + TEXT_HTML_BUFFER.setAssociate("iso-8859-1",TEXT_HTML_8859_1_BUFFER); + TEXT_PLAIN_BUFFER.setAssociate("ISO-8859-1",TEXT_PLAIN_8859_1_BUFFER); + TEXT_PLAIN_BUFFER.setAssociate("ISO_8859_1",TEXT_PLAIN_8859_1_BUFFER); + TEXT_PLAIN_BUFFER.setAssociate("iso-8859-1",TEXT_PLAIN_8859_1_BUFFER); + TEXT_XML_BUFFER.setAssociate("ISO-8859-1",TEXT_XML_8859_1_BUFFER); + TEXT_XML_BUFFER.setAssociate("ISO_8859_1",TEXT_XML_8859_1_BUFFER); + TEXT_XML_BUFFER.setAssociate("iso-8859-1",TEXT_XML_8859_1_BUFFER); + + TEXT_HTML_BUFFER.setAssociate("UTF-8",TEXT_HTML_UTF_8_BUFFER); + TEXT_HTML_BUFFER.setAssociate("UTF8",TEXT_HTML_UTF_8_BUFFER); + TEXT_HTML_BUFFER.setAssociate("utf8",TEXT_HTML_UTF_8_BUFFER); + TEXT_HTML_BUFFER.setAssociate("utf-8",TEXT_HTML_UTF_8_BUFFER); + TEXT_PLAIN_BUFFER.setAssociate("UTF-8",TEXT_PLAIN_UTF_8_BUFFER); + TEXT_PLAIN_BUFFER.setAssociate("UTF8",TEXT_PLAIN_UTF_8_BUFFER); + TEXT_PLAIN_BUFFER.setAssociate("utf8",TEXT_PLAIN_UTF_8_BUFFER); + TEXT_PLAIN_BUFFER.setAssociate("utf-8",TEXT_PLAIN_UTF_8_BUFFER); + TEXT_XML_BUFFER.setAssociate("UTF-8",TEXT_XML_UTF_8_BUFFER); + TEXT_XML_BUFFER.setAssociate("UTF8",TEXT_XML_UTF_8_BUFFER); + TEXT_XML_BUFFER.setAssociate("utf8",TEXT_XML_UTF_8_BUFFER); + TEXT_XML_BUFFER.setAssociate("utf-8",TEXT_XML_UTF_8_BUFFER); + TEXT_JSON_BUFFER.setAssociate("UTF-8",TEXT_JSON_UTF_8_BUFFER); + TEXT_JSON_BUFFER.setAssociate("UTF8",TEXT_JSON_UTF_8_BUFFER); + TEXT_JSON_BUFFER.setAssociate("utf8",TEXT_JSON_UTF_8_BUFFER); + TEXT_JSON_BUFFER.setAssociate("utf-8",TEXT_JSON_UTF_8_BUFFER); + } + + + /* ------------------------------------------------------------ */ + private Map _mimeMap; + + /* ------------------------------------------------------------ */ + /** Constructor. + */ + public MimeTypes() + { + } + + /* ------------------------------------------------------------ */ + public synchronized Map getMimeMap() + { + return _mimeMap; + } + + /* ------------------------------------------------------------ */ + /** + * @param mimeMap A Map of file extension to mime-type. + */ + public void setMimeMap(Map mimeMap) + { + if (mimeMap==null) + { + _mimeMap=null; + return; + } + + Map m=new HashMap(); + Iterator i=mimeMap.entrySet().iterator(); + while (i.hasNext()) + { + Map.Entry entry = (Map.Entry)i.next(); + m.put(entry.getKey(),normalizeMimeType(entry.getValue().toString())); + } + _mimeMap=m; + } + + /* ------------------------------------------------------------ */ + /** Get the MIME type by filename extension. + * @param filename A file name + * @return MIME type matching the longest dot extension of the + * file name. + */ + public Buffer getMimeByExtension(String filename) + { + Buffer type=null; + + if (filename!=null) + { + int i=-1; + while(type==null) + { + i=filename.indexOf(".",i+1); + + if (i<0 || i>=filename.length()) + break; + + String ext=StringUtil.asciiToLowerCase(filename.substring(i+1)); + if (_mimeMap!=null) + type = (Buffer)_mimeMap.get(ext); + if (type==null) + type=(Buffer)__dftMimeMap.get(ext); + } + } + + if (type==null) + { + if (_mimeMap!=null) + type=(Buffer)_mimeMap.get("*"); + if (type==null) + type=(Buffer)__dftMimeMap.get("*"); + } + + return type; + } + + /* ------------------------------------------------------------ */ + /** Set a mime mapping + * @param extension + * @param type + */ + public void addMimeMapping(String extension,String type) + { + if (_mimeMap==null) + _mimeMap=new HashMap(); + + _mimeMap.put(StringUtil.asciiToLowerCase(extension),normalizeMimeType(type)); + } + + /* ------------------------------------------------------------ */ + private static synchronized Buffer normalizeMimeType(String type) + { + Buffer b =CACHE.get(type); + if (b==null) + b=CACHE.add(type,__index++); + return b; + } + + /* ------------------------------------------------------------ */ + public static String getCharsetFromContentType(Buffer value) + { + if (value instanceof CachedBuffer) + { + switch(((CachedBuffer)value).getOrdinal()) + { + case TEXT_HTML_8859_1_ORDINAL: + case TEXT_PLAIN_8859_1_ORDINAL: + case TEXT_XML_8859_1_ORDINAL: + return StringUtil.__ISO_8859_1; + + case TEXT_HTML_UTF_8_ORDINAL: + case TEXT_PLAIN_UTF_8_ORDINAL: + case TEXT_XML_UTF_8_ORDINAL: + case TEXT_JSON_UTF_8_ORDINAL: + return StringUtil.__UTF8; + } + } + + int i=value.getIndex(); + int end=value.putIndex(); + int state=0; + int start=0; + boolean quote=false; + for (;i<end;i++) + { + byte b = value.peek(i); + + if (quote && state!=10) + { + if ('"'==b) + quote=false; + continue; + } + + switch(state) + { + case 0: + if ('"'==b) + { + quote=true; + break; + } + if (';'==b) + state=1; + break; + + case 1: if ('c'==b) state=2; else if (' '!=b) state=0; break; + case 2: if ('h'==b) state=3; else state=0;break; + case 3: if ('a'==b) state=4; else state=0;break; + case 4: if ('r'==b) state=5; else state=0;break; + case 5: if ('s'==b) state=6; else state=0;break; + case 6: if ('e'==b) state=7; else state=0;break; + case 7: if ('t'==b) state=8; else state=0;break; + + case 8: if ('='==b) state=9; else if (' '!=b) state=0; break; + + case 9: + if (' '==b) + break; + if ('"'==b) + { + quote=true; + start=i+1; + state=10; + break; + } + start=i; + state=10; + break; + + case 10: + if (!quote && (';'==b || ' '==b )|| + (quote && '"'==b )) + return CACHE.lookup(value.peek(start,i-start)).toString(StringUtil.__UTF8); + } + } + + if (state==10) + return CACHE.lookup(value.peek(start,i-start)).toString(StringUtil.__UTF8); + + return (String)__encodings.get(value); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/http/Parser.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,47 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.http; + +import java.io.IOException; + +/** + * Abstract interface for a connection Parser for use by Jetty. + */ +public interface Parser +{ + void returnBuffers(); + void reset(); + + boolean isComplete(); + + /** + * @return True if progress made + * @throws IOException + */ + boolean parseAvailable() throws IOException; + + boolean isMoreInBuffer() throws IOException; + + boolean isIdle(); + + boolean isPersistent(); + + void setPersistent(boolean persistent); + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/http/PathMap.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,589 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.http; + +import java.io.Externalizable; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringTokenizer; + +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.StringMap; +import org.eclipse.jetty.util.URIUtil; + +/* ------------------------------------------------------------ */ +/** URI path map to Object. + * This mapping implements the path specification recommended + * in the 2.2 Servlet API. + * + * Path specifications can be of the following forms:<PRE> + * /foo/bar - an exact path specification. + * /foo/* - a prefix path specification (must end '/*'). + * *.ext - a suffix path specification. + * / - the default path specification. + * </PRE> + * Matching is performed in the following order <NL> + * <LI>Exact match. + * <LI>Longest prefix match. + * <LI>Longest suffix match. + * <LI>default. + * </NL> + * Multiple path specifications can be mapped by providing a list of + * specifications. By default this class uses characters ":," as path + * separators, unless configured differently by calling the static + * method @see PathMap#setPathSpecSeparators(String) + * <P> + * Special characters within paths such as '?� and ';' are not treated specially + * as it is assumed they would have been either encoded in the original URL or + * stripped from the path. + * <P> + * This class is not synchronized. If concurrent modifications are + * possible then it should be synchronized at a higher level. + * + * + */ +public class PathMap extends HashMap implements Externalizable +{ + /* ------------------------------------------------------------ */ + private static String __pathSpecSeparators = ":,"; + + /* ------------------------------------------------------------ */ + /** Set the path spec separator. + * Multiple path specification may be included in a single string + * if they are separated by the characters set in this string. + * By default this class uses ":," characters as path separators. + * @param s separators + */ + public static void setPathSpecSeparators(String s) + { + __pathSpecSeparators=s; + } + + /* --------------------------------------------------------------- */ + final StringMap _prefixMap=new StringMap(); + final StringMap _suffixMap=new StringMap(); + final StringMap _exactMap=new StringMap(); + + List _defaultSingletonList=null; + Entry _prefixDefault=null; + Entry _default=null; + final Set _entrySet; + boolean _nodefault=false; + + /* --------------------------------------------------------------- */ + /** Construct empty PathMap. + */ + public PathMap() + { + super(11); + _entrySet=entrySet(); + } + + /* --------------------------------------------------------------- */ + /** Construct empty PathMap. + */ + public PathMap(boolean nodefault) + { + super(11); + _entrySet=entrySet(); + _nodefault=nodefault; + } + + /* --------------------------------------------------------------- */ + /** Construct empty PathMap. + */ + public PathMap(int capacity) + { + super (capacity); + _entrySet=entrySet(); + } + + /* --------------------------------------------------------------- */ + /** Construct from dictionary PathMap. + */ + public PathMap(Map m) + { + putAll(m); + _entrySet=entrySet(); + } + + /* ------------------------------------------------------------ */ + public void writeExternal(java.io.ObjectOutput out) + throws java.io.IOException + { + HashMap map = new HashMap(this); + out.writeObject(map); + } + + /* ------------------------------------------------------------ */ + public void readExternal(java.io.ObjectInput in) + throws java.io.IOException, ClassNotFoundException + { + HashMap map = (HashMap)in.readObject(); + this.putAll(map); + } + + /* --------------------------------------------------------------- */ + /** Add a single path match to the PathMap. + * @param pathSpec The path specification, or comma separated list of + * path specifications. + * @param object The object the path maps to + */ + @Override + public Object put(Object pathSpec, Object object) + { + String str = pathSpec.toString(); + if ("".equals(str.trim())) + { + Entry entry = new Entry("",object); + entry.setMapped(""); + _exactMap.put("", entry); + return super.put("", object); + } + + StringTokenizer tok = new StringTokenizer(str,__pathSpecSeparators); + Object old =null; + + while (tok.hasMoreTokens()) + { + String spec=tok.nextToken(); + + if (!spec.startsWith("/") && !spec.startsWith("*.")) + throw new IllegalArgumentException("PathSpec "+spec+". must start with '/' or '*.'"); + + old = super.put(spec,object); + + // Make entry that was just created. + Entry entry = new Entry(spec,object); + + if (entry.getKey().equals(spec)) + { + if (spec.equals("/*")) + _prefixDefault=entry; + else if (spec.endsWith("/*")) + { + String mapped=spec.substring(0,spec.length()-2); + entry.setMapped(mapped); + _prefixMap.put(mapped,entry); + _exactMap.put(mapped,entry); + _exactMap.put(spec.substring(0,spec.length()-1),entry); + } + else if (spec.startsWith("*.")) + _suffixMap.put(spec.substring(2),entry); + else if (spec.equals(URIUtil.SLASH)) + { + if (_nodefault) + _exactMap.put(spec,entry); + else + { + _default=entry; + _defaultSingletonList= + Collections.singletonList(_default); + } + } + else + { + entry.setMapped(spec); + _exactMap.put(spec,entry); + } + } + } + + return old; + } + + /* ------------------------------------------------------------ */ + /** Get object matched by the path. + * @param path the path. + * @return Best matched object or null. + */ + public Object match(String path) + { + Map.Entry entry = getMatch(path); + if (entry!=null) + return entry.getValue(); + return null; + } + + + /* --------------------------------------------------------------- */ + /** Get the entry mapped by the best specification. + * @param path the path. + * @return Map.Entry of the best matched or null. + */ + public Entry getMatch(String path) + { + Map.Entry entry=null; + + if (path==null) + return null; + + int l=path.length(); + + //special case + if (l == 1 && path.charAt(0)=='/') + { + entry = (Map.Entry)_exactMap.get(""); + if (entry != null) + return (Entry)entry; + } + + // try exact match + entry=_exactMap.getEntry(path,0,l); + if (entry!=null) + return (Entry) entry.getValue(); + + // prefix search + int i=l; + while((i=path.lastIndexOf('/',i-1))>=0) + { + entry=_prefixMap.getEntry(path,0,i); + if (entry!=null) + return (Entry) entry.getValue(); + } + + // Prefix Default + if (_prefixDefault!=null) + return _prefixDefault; + + // Extension search + i=0; + while ((i=path.indexOf('.',i+1))>0) + { + entry=_suffixMap.getEntry(path,i+1,l-i-1); + if (entry!=null) + return (Entry) entry.getValue(); + } + + // Default + return _default; + } + + /* --------------------------------------------------------------- */ + /** Get all entries matched by the path. + * Best match first. + * @param path Path to match + * @return LazyList of Map.Entry instances key=pathSpec + */ + public Object getLazyMatches(String path) + { + Map.Entry entry; + Object entries=null; + + if (path==null) + return LazyList.getList(entries); + + int l=path.length(); + + // try exact match + entry=_exactMap.getEntry(path,0,l); + if (entry!=null) + entries=LazyList.add(entries,entry.getValue()); + + // prefix search + int i=l-1; + while((i=path.lastIndexOf('/',i-1))>=0) + { + entry=_prefixMap.getEntry(path,0,i); + if (entry!=null) + entries=LazyList.add(entries,entry.getValue()); + } + + // Prefix Default + if (_prefixDefault!=null) + entries=LazyList.add(entries,_prefixDefault); + + // Extension search + i=0; + while ((i=path.indexOf('.',i+1))>0) + { + entry=_suffixMap.getEntry(path,i+1,l-i-1); + if (entry!=null) + entries=LazyList.add(entries,entry.getValue()); + } + + // Default + if (_default!=null) + { + // Optimization for just the default + if (entries==null) + return _defaultSingletonList; + + entries=LazyList.add(entries,_default); + } + + return entries; + } + + /* --------------------------------------------------------------- */ + /** Get all entries matched by the path. + * Best match first. + * @param path Path to match + * @return List of Map.Entry instances key=pathSpec + */ + public List getMatches(String path) + { + return LazyList.getList(getLazyMatches(path)); + } + + /* --------------------------------------------------------------- */ + /** Return whether the path matches any entries in the PathMap, + * excluding the default entry + * @param path Path to match + * @return Whether the PathMap contains any entries that match this + */ + public boolean containsMatch(String path) + { + Entry match = getMatch(path); + return match!=null && !match.equals(_default); + } + + /* --------------------------------------------------------------- */ + @Override + public Object remove(Object pathSpec) + { + if (pathSpec!=null) + { + String spec=(String) pathSpec; + if (spec.equals("/*")) + _prefixDefault=null; + else if (spec.endsWith("/*")) + { + _prefixMap.remove(spec.substring(0,spec.length()-2)); + _exactMap.remove(spec.substring(0,spec.length()-1)); + _exactMap.remove(spec.substring(0,spec.length()-2)); + } + else if (spec.startsWith("*.")) + _suffixMap.remove(spec.substring(2)); + else if (spec.equals(URIUtil.SLASH)) + { + _default=null; + _defaultSingletonList=null; + } + else + _exactMap.remove(spec); + } + return super.remove(pathSpec); + } + + /* --------------------------------------------------------------- */ + @Override + public void clear() + { + _exactMap.clear(); + _prefixMap.clear(); + _suffixMap.clear(); + _default=null; + _defaultSingletonList=null; + super.clear(); + } + + /* --------------------------------------------------------------- */ + /** + * @return true if match. + */ + public static boolean match(String pathSpec, String path) + throws IllegalArgumentException + { + return match(pathSpec, path, false); + } + + /* --------------------------------------------------------------- */ + /** + * @return true if match. + */ + public static boolean match(String pathSpec, String path, boolean noDefault) + throws IllegalArgumentException + { + if (pathSpec.length()==0) + return "/".equals(path); + + char c = pathSpec.charAt(0); + if (c=='/') + { + if (!noDefault && pathSpec.length()==1 || pathSpec.equals(path)) + return true; + + if(isPathWildcardMatch(pathSpec, path)) + return true; + } + else if (c=='*') + return path.regionMatches(path.length()-pathSpec.length()+1, + pathSpec,1,pathSpec.length()-1); + return false; + } + + /* --------------------------------------------------------------- */ + private static boolean isPathWildcardMatch(String pathSpec, String path) + { + // For a spec of "/foo/*" match "/foo" , "/foo/..." but not "/foobar" + int cpl=pathSpec.length()-2; + if (pathSpec.endsWith("/*") && path.regionMatches(0,pathSpec,0,cpl)) + { + if (path.length()==cpl || '/'==path.charAt(cpl)) + return true; + } + return false; + } + + + /* --------------------------------------------------------------- */ + /** Return the portion of a path that matches a path spec. + * @return null if no match at all. + */ + public static String pathMatch(String pathSpec, String path) + { + char c = pathSpec.charAt(0); + + if (c=='/') + { + if (pathSpec.length()==1) + return path; + + if (pathSpec.equals(path)) + return path; + + if (isPathWildcardMatch(pathSpec, path)) + return path.substring(0,pathSpec.length()-2); + } + else if (c=='*') + { + if (path.regionMatches(path.length()-(pathSpec.length()-1), + pathSpec,1,pathSpec.length()-1)) + return path; + } + return null; + } + + /* --------------------------------------------------------------- */ + /** Return the portion of a path that is after a path spec. + * @return The path info string + */ + public static String pathInfo(String pathSpec, String path) + { + if ("".equals(pathSpec)) + return path; //servlet 3 spec sec 12.2 will be '/' + + char c = pathSpec.charAt(0); + + if (c=='/') + { + if (pathSpec.length()==1) + return null; + + boolean wildcard = isPathWildcardMatch(pathSpec, path); + + // handle the case where pathSpec uses a wildcard and path info is "/*" + if (pathSpec.equals(path) && !wildcard) + return null; + + if (wildcard) + { + if (path.length()==pathSpec.length()-2) + return null; + return path.substring(pathSpec.length()-2); + } + } + return null; + } + + + /* ------------------------------------------------------------ */ + /** Relative path. + * @param base The base the path is relative to. + * @param pathSpec The spec of the path segment to ignore. + * @param path the additional path + * @return base plus path with pathspec removed + */ + public static String relativePath(String base, + String pathSpec, + String path ) + { + String info=pathInfo(pathSpec,path); + if (info==null) + info=path; + + if( info.startsWith( "./")) + info = info.substring( 2); + if( base.endsWith( URIUtil.SLASH)) + if( info.startsWith( URIUtil.SLASH)) + path = base + info.substring(1); + else + path = base + info; + else + if( info.startsWith( URIUtil.SLASH)) + path = base + info; + else + path = base + URIUtil.SLASH + info; + return path; + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + public static class Entry implements Map.Entry + { + private final Object key; + private final Object value; + private String mapped; + private transient String string; + + Entry(Object key, Object value) + { + this.key=key; + this.value=value; + } + + public Object getKey() + { + return key; + } + + public Object getValue() + { + return value; + } + + public Object setValue(Object o) + { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() + { + if (string==null) + string=key+"="+value; + return string; + } + + public String getMapped() + { + return mapped; + } + + void setMapped(String mapped) + { + this.mapped = mapped; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/http/encoding.properties Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,4 @@ +text/html = ISO-8859-1 +text/plain = ISO-8859-1 +text/xml = UTF-8 +text/json = UTF-8
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/http/gzip/AbstractCompressedStream.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,388 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.http.gzip; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.util.zip.DeflaterOutputStream; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.util.ByteArrayOutputStream2; + +/* ------------------------------------------------------------ */ +/** + * Skeletal implementation of a CompressedStream. This class adds compression features to a ServletOutputStream and takes care of setting response headers, etc. + * Major work and configuration is done here. Subclasses using different kinds of compression only have to implement the abstract methods doCompress() and + * setContentEncoding() using the desired compression and setting the appropriate Content-Encoding header string. + */ +public abstract class AbstractCompressedStream extends ServletOutputStream +{ + private final String _encoding; + protected final String _vary; + protected final CompressedResponseWrapper _wrapper; + protected final HttpServletResponse _response; + protected OutputStream _out; + protected ByteArrayOutputStream2 _bOut; + protected DeflaterOutputStream _compressedOutputStream; + protected boolean _closed; + protected boolean _doNotCompress; + + /** + * Instantiates a new compressed stream. + * + */ + public AbstractCompressedStream(String encoding,HttpServletRequest request, CompressedResponseWrapper wrapper,String vary) + throws IOException + { + _encoding=encoding; + _wrapper = wrapper; + _response = (HttpServletResponse)wrapper.getResponse(); + _vary=vary; + + if (_wrapper.getMinCompressSize()==0) + doCompress(); + } + + /* ------------------------------------------------------------ */ + /** + * Reset buffer. + */ + public void resetBuffer() + { + if (_response.isCommitted() || _compressedOutputStream!=null ) + throw new IllegalStateException("Committed"); + _closed = false; + _out = null; + _bOut = null; + _doNotCompress = false; + } + + /* ------------------------------------------------------------ */ + public void setBufferSize(int bufferSize) + { + if (_bOut!=null && _bOut.getBuf().length<bufferSize) + { + ByteArrayOutputStream2 b = new ByteArrayOutputStream2(bufferSize); + b.write(_bOut.getBuf(),0,_bOut.size()); + _bOut=b; + } + } + + /* ------------------------------------------------------------ */ + public void setContentLength() + { + if (_doNotCompress) + { + long length=_wrapper.getContentLength(); + if (length>=0) + { + if (length < Integer.MAX_VALUE) + _response.setContentLength((int)length); + else + _response.setHeader("Content-Length",Long.toString(length)); + } + } + } + + /* ------------------------------------------------------------ */ + /** + * @see java.io.OutputStream#flush() + */ + @Override + public void flush() throws IOException + { + if (_out == null || _bOut != null) + { + long length=_wrapper.getContentLength(); + if (length > 0 && length < _wrapper.getMinCompressSize()) + doNotCompress(false); + else + doCompress(); + } + + _out.flush(); + } + + /* ------------------------------------------------------------ */ + /** + * @see java.io.OutputStream#close() + */ + @Override + public void close() throws IOException + { + if (_closed) + return; + + if (_wrapper.getRequest().getAttribute("javax.servlet.include.request_uri") != null) + flush(); + else + { + if (_bOut != null) + { + long length=_wrapper.getContentLength(); + if (length < 0) + { + length = _bOut.getCount(); + _wrapper.setContentLength(length); + } + if (length < _wrapper.getMinCompressSize()) + doNotCompress(false); + else + doCompress(); + } + else if (_out == null) + { + // No output + doNotCompress(false); + } + + if (_compressedOutputStream != null) + _compressedOutputStream.close(); + else + _out.close(); + _closed = true; + } + } + + /** + * Finish. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + public void finish() throws IOException + { + if (!_closed) + { + if (_out == null || _bOut != null) + { + long length=_wrapper.getContentLength(); + if (length >= 0 && length < _wrapper.getMinCompressSize()) + doNotCompress(false); + else + doCompress(); + } + + if (_compressedOutputStream != null && !_closed) + { + _closed = true; + _compressedOutputStream.close(); + } + } + } + + /* ------------------------------------------------------------ */ + /** + * @see java.io.OutputStream#write(int) + */ + @Override + public void write(int b) throws IOException + { + checkOut(1); + _out.write(b); + } + + /* ------------------------------------------------------------ */ + /** + * @see java.io.OutputStream#write(byte[]) + */ + @Override + public void write(byte b[]) throws IOException + { + checkOut(b.length); + _out.write(b); + } + + /* ------------------------------------------------------------ */ + /** + * @see java.io.OutputStream#write(byte[], int, int) + */ + @Override + public void write(byte b[], int off, int len) throws IOException + { + checkOut(len); + _out.write(b,off,len); + } + + /** + * Do compress. + * + * @throws IOException Signals that an I/O exception has occurred. + */ + public void doCompress() throws IOException + { + if (_compressedOutputStream==null) + { + if (_response.isCommitted()) + throw new IllegalStateException(); + + if (_encoding!=null) + { + setHeader("Content-Encoding", _encoding); + if (_response.containsHeader("Content-Encoding")) + { + addHeader("Vary",_vary); + _out=_compressedOutputStream=createStream(); + if (_out!=null) + { + if (_bOut!=null) + { + _out.write(_bOut.getBuf(),0,_bOut.getCount()); + _bOut=null; + } + + String etag=_wrapper.getETag(); + if (etag!=null) + setHeader("ETag",etag.substring(0,etag.length()-1)+'-'+_encoding+'"'); + return; + } + } + } + + doNotCompress(true); // Send vary as it could have been compressed if encoding was present + } + } + + /** + * Do not compress. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + public void doNotCompress(boolean sendVary) throws IOException + { + if (_compressedOutputStream != null) + throw new IllegalStateException("Compressed output stream is already assigned."); + if (_out == null || _bOut != null) + { + if (sendVary) + addHeader("Vary",_vary); + if (_wrapper.getETag()!=null) + setHeader("ETag",_wrapper.getETag()); + + _doNotCompress = true; + + _out = _response.getOutputStream(); + setContentLength(); + + if (_bOut != null) + _out.write(_bOut.getBuf(),0,_bOut.getCount()); + _bOut = null; + } + } + + /** + * Check out. + * + * @param lengthToWrite + * the length + * @throws IOException + * Signals that an I/O exception has occurred. + */ + private void checkOut(int lengthToWrite) throws IOException + { + if (_closed) + throw new IOException("CLOSED"); + + if (_out == null) + { + // If this first write is larger than buffer size, then we are committing now + if (lengthToWrite>_wrapper.getBufferSize()) + { + // if we know this is all the content and it is less than minimum, then do not compress, otherwise do compress + long length=_wrapper.getContentLength(); + if (length>=0 && length<_wrapper.getMinCompressSize()) + doNotCompress(false); // Not compressing by size, so no vary on request headers + else + doCompress(); + } + else + { + // start aggregating writes into a buffered output stream + _out = _bOut = new ByteArrayOutputStream2(_wrapper.getBufferSize()); + } + } + // else are we aggregating writes? + else if (_bOut !=null) + { + // We are aggregating into the buffered output stream. + + // If this write fills the buffer, then we are committing + if (lengthToWrite>=(_bOut.getBuf().length - _bOut.getCount())) + { + // if we know this is all the content and it is less than minimum, then do not compress, otherwise do compress + long length=_wrapper.getContentLength(); + if (length>=0 && length<_wrapper.getMinCompressSize()) + doNotCompress(false); // Not compressing by size, so no vary on request headers + else + doCompress(); + } + } + } + + /** + * @see org.eclipse.jetty.http.gzip.CompressedStream#getOutputStream() + */ + public OutputStream getOutputStream() + { + return _out; + } + + /** + * @see org.eclipse.jetty.http.gzip.CompressedStream#isClosed() + */ + public boolean isClosed() + { + return _closed; + } + + /** + * Allows derived implementations to replace PrintWriter implementation. + */ + protected PrintWriter newWriter(OutputStream out, String encoding) throws UnsupportedEncodingException + { + return encoding == null?new PrintWriter(out):new PrintWriter(new OutputStreamWriter(out,encoding)); + } + + protected void addHeader(String name,String value) + { + _response.addHeader(name, value); + } + + protected void setHeader(String name,String value) + { + _response.setHeader(name, value); + } + + /** + * Create the stream fitting to the underlying compression type. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + protected abstract DeflaterOutputStream createStream() throws IOException; + + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/http/gzip/CompressedResponseWrapper.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,487 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.http.gzip; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.util.Set; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; + +import org.eclipse.jetty.util.StringUtil; + +/*------------------------------------------------------------ */ +/** + */ +public abstract class CompressedResponseWrapper extends HttpServletResponseWrapper +{ + + public static final int DEFAULT_BUFFER_SIZE = 8192; + public static final int DEFAULT_MIN_COMPRESS_SIZE = 256; + + private Set<String> _mimeTypes; + private int _bufferSize=DEFAULT_BUFFER_SIZE; + private int _minCompressSize=DEFAULT_MIN_COMPRESS_SIZE; + protected HttpServletRequest _request; + + private PrintWriter _writer; + private AbstractCompressedStream _compressedStream; + private String _etag; + private long _contentLength=-1; + private boolean _noCompression; + + /* ------------------------------------------------------------ */ + public CompressedResponseWrapper(HttpServletRequest request, HttpServletResponse response) + { + super(response); + _request = request; + } + + + /* ------------------------------------------------------------ */ + public long getContentLength() + { + return _contentLength; + } + + /* ------------------------------------------------------------ */ + public int getBufferSize() + { + return _bufferSize; + } + + /* ------------------------------------------------------------ */ + public int getMinCompressSize() + { + return _minCompressSize; + } + + /* ------------------------------------------------------------ */ + public String getETag() + { + return _etag; + } + + /* ------------------------------------------------------------ */ + public HttpServletRequest getRequest() + { + return _request; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setMimeTypes(java.util.Set) + */ + public void setMimeTypes(Set<String> mimeTypes) + { + _mimeTypes = mimeTypes; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setBufferSize(int) + */ + @Override + public void setBufferSize(int bufferSize) + { + _bufferSize = bufferSize; + if (_compressedStream!=null) + _compressedStream.setBufferSize(bufferSize); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setMinCompressSize(int) + */ + public void setMinCompressSize(int minCompressSize) + { + _minCompressSize = minCompressSize; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setContentType(java.lang.String) + */ + @Override + public void setContentType(String ct) + { + super.setContentType(ct); + + if (!_noCompression) + { + if (ct!=null) + { + int colon=ct.indexOf(";"); + if (colon>0) + ct=ct.substring(0,colon); + } + + if ((_compressedStream==null || _compressedStream.getOutputStream()==null) && + (_mimeTypes==null && ct!=null && ct.contains("gzip") || + _mimeTypes!=null && (ct==null||!_mimeTypes.contains(StringUtil.asciiToLowerCase(ct))))) + { + noCompression(); + } + } + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setStatus(int, java.lang.String) + */ + @Override + public void setStatus(int sc, String sm) + { + super.setStatus(sc,sm); + if (sc<200 || sc==204 || sc==205 || sc>=300) + noCompression(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setStatus(int) + */ + @Override + public void setStatus(int sc) + { + super.setStatus(sc); + if (sc<200 || sc==204 || sc==205 || sc>=300) + noCompression(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setContentLength(int) + */ + @Override + public void setContentLength(int length) + { + if (_noCompression) + super.setContentLength(length); + else + setContentLength((long)length); + } + + /* ------------------------------------------------------------ */ + protected void setContentLength(long length) + { + _contentLength=length; + if (_compressedStream!=null) + _compressedStream.setContentLength(); + else if (_noCompression && _contentLength>=0) + { + HttpServletResponse response = (HttpServletResponse)getResponse(); + if(_contentLength<Integer.MAX_VALUE) + { + response.setContentLength((int)_contentLength); + } + else + { + response.setHeader("Content-Length", Long.toString(_contentLength)); + } + } + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#addHeader(java.lang.String, java.lang.String) + */ + @Override + public void addHeader(String name, String value) + { + if ("content-length".equalsIgnoreCase(name)) + { + _contentLength=Long.parseLong(value); + if (_compressedStream!=null) + _compressedStream.setContentLength(); + } + else if ("content-type".equalsIgnoreCase(name)) + { + setContentType(value); + } + else if ("content-encoding".equalsIgnoreCase(name)) + { + super.addHeader(name,value); + if (!isCommitted()) + { + noCompression(); + } + } + else if ("etag".equalsIgnoreCase(name)) + _etag=value; + else + super.addHeader(name,value); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#flushBuffer() + */ + @Override + public void flushBuffer() throws IOException + { + if (_writer!=null) + _writer.flush(); + if (_compressedStream!=null) + _compressedStream.flush(); + else + getResponse().flushBuffer(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#reset() + */ + @Override + public void reset() + { + super.reset(); + if (_compressedStream!=null) + _compressedStream.resetBuffer(); + _writer=null; + _compressedStream=null; + _noCompression=false; + _contentLength=-1; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#resetBuffer() + */ + @Override + public void resetBuffer() + { + super.resetBuffer(); + if (_compressedStream!=null) + _compressedStream.resetBuffer(); + _writer=null; + _compressedStream=null; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#sendError(int, java.lang.String) + */ + @Override + public void sendError(int sc, String msg) throws IOException + { + resetBuffer(); + super.sendError(sc,msg); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#sendError(int) + */ + @Override + public void sendError(int sc) throws IOException + { + resetBuffer(); + super.sendError(sc); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#sendRedirect(java.lang.String) + */ + @Override + public void sendRedirect(String location) throws IOException + { + resetBuffer(); + super.sendRedirect(location); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#noCompression() + */ + public void noCompression() + { + if (!_noCompression) + setDeferredHeaders(); + _noCompression=true; + if (_compressedStream!=null) + { + try + { + _compressedStream.doNotCompress(false); + } + catch (IOException e) + { + throw new IllegalStateException(e); + } + } + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#finish() + */ + public void finish() throws IOException + { + if (_writer!=null && !_compressedStream.isClosed()) + _writer.flush(); + if (_compressedStream!=null) + _compressedStream.finish(); + else + setDeferredHeaders(); + } + + /* ------------------------------------------------------------ */ + private void setDeferredHeaders() + { + if (!isCommitted()) + { + if (_contentLength>=0) + { + if (_contentLength < Integer.MAX_VALUE) + super.setContentLength((int)_contentLength); + else + super.setHeader("Content-Length",Long.toString(_contentLength)); + } + if(_etag!=null) + super.setHeader("ETag",_etag); + } + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setHeader(java.lang.String, java.lang.String) + */ + @Override + public void setHeader(String name, String value) + { + if (_noCompression) + super.setHeader(name,value); + else if ("content-length".equalsIgnoreCase(name)) + { + setContentLength(Long.parseLong(value)); + } + else if ("content-type".equalsIgnoreCase(name)) + { + setContentType(value); + } + else if ("content-encoding".equalsIgnoreCase(name)) + { + super.setHeader(name,value); + if (!isCommitted()) + { + noCompression(); + } + } + else if ("etag".equalsIgnoreCase(name)) + _etag=value; + else + super.setHeader(name,value); + } + + /* ------------------------------------------------------------ */ + @Override + public boolean containsHeader(String name) + { + if (!_noCompression && "etag".equalsIgnoreCase(name) && _etag!=null) + return true; + return super.containsHeader(name); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#getOutputStream() + */ + @Override + public ServletOutputStream getOutputStream() throws IOException + { + if (_compressedStream==null) + { + if (getResponse().isCommitted() || _noCompression) + return getResponse().getOutputStream(); + + _compressedStream=newCompressedStream(_request,(HttpServletResponse)getResponse()); + } + else if (_writer!=null) + throw new IllegalStateException("getWriter() called"); + + return _compressedStream; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#getWriter() + */ + @Override + public PrintWriter getWriter() throws IOException + { + if (_writer==null) + { + if (_compressedStream!=null) + throw new IllegalStateException("getOutputStream() called"); + + if (getResponse().isCommitted() || _noCompression) + return getResponse().getWriter(); + + _compressedStream=newCompressedStream(_request,(HttpServletResponse)getResponse()); + _writer=newWriter(_compressedStream,getCharacterEncoding()); + } + return _writer; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setIntHeader(java.lang.String, int) + */ + @Override + public void setIntHeader(String name, int value) + { + if ("content-length".equalsIgnoreCase(name)) + { + _contentLength=value; + if (_compressedStream!=null) + _compressedStream.setContentLength(); + } + else + super.setIntHeader(name,value); + } + + /* ------------------------------------------------------------ */ + /** + * Allows derived implementations to replace PrintWriter implementation. + * + * @param out the out + * @param encoding the encoding + * @return the prints the writer + * @throws UnsupportedEncodingException the unsupported encoding exception + */ + protected PrintWriter newWriter(OutputStream out,String encoding) throws UnsupportedEncodingException + { + return encoding==null?new PrintWriter(out):new PrintWriter(new OutputStreamWriter(out,encoding)); + } + + /* ------------------------------------------------------------ */ + /** + *@return the underlying CompressedStream implementation + */ + protected abstract AbstractCompressedStream newCompressedStream(HttpServletRequest _request, HttpServletResponse response) throws IOException; + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/http/mime.properties Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,181 @@ +ai = application/postscript +aif = audio/x-aiff +aifc = audio/x-aiff +aiff = audio/x-aiff +apk = application/vnd.android.package-archive +asc = text/plain +asf = video/x.ms.asf +asx = video/x.ms.asx +au = audio/basic +avi = video/x-msvideo +bcpio = application/x-bcpio +bin = application/octet-stream +cab = application/x-cabinet +cdf = application/x-netcdf +class = application/java-vm +cpio = application/x-cpio +cpt = application/mac-compactpro +crt = application/x-x509-ca-cert +csh = application/x-csh +css = text/css +csv = text/comma-separated-values +dcr = application/x-director +dir = application/x-director +dll = application/x-msdownload +dms = application/octet-stream +doc = application/msword +dtd = application/xml-dtd +dvi = application/x-dvi +dxr = application/x-director +eps = application/postscript +etx = text/x-setext +exe = application/octet-stream +ez = application/andrew-inset +gif = image/gif +gtar = application/x-gtar +gz = application/gzip +gzip = application/gzip +hdf = application/x-hdf +hqx = application/mac-binhex40 +htc = text/x-component +htm = text/html +html = text/html +ice = x-conference/x-cooltalk +ico = image/x-icon +ief = image/ief +iges = model/iges +igs = model/iges +jad = text/vnd.sun.j2me.app-descriptor +jar = application/java-archive +java = text/plain +jnlp = application/x-java-jnlp-file +jpe = image/jpeg +jpeg = image/jpeg +jpg = image/jpeg +js = application/x-javascript +jsp = text/html +kar = audio/midi +latex = application/x-latex +lha = application/octet-stream +lzh = application/octet-stream +man = application/x-troff-man +mathml = application/mathml+xml +me = application/x-troff-me +mesh = model/mesh +mid = audio/midi +midi = audio/midi +mif = application/vnd.mif +mol = chemical/x-mdl-molfile +mov = video/quicktime +movie = video/x-sgi-movie +mp2 = audio/mpeg +mp3 = audio/mpeg +mpe = video/mpeg +mpeg = video/mpeg +mpg = video/mpeg +mpga = audio/mpeg +ms = application/x-troff-ms +msh = model/mesh +msi = application/octet-stream +nc = application/x-netcdf +oda = application/oda +odb = application/vnd.oasis.opendocument.database +odc = application/vnd.oasis.opendocument.chart +odf = application/vnd.oasis.opendocument.formula +odg = application/vnd.oasis.opendocument.graphics +odi = application/vnd.oasis.opendocument.image +odm = application/vnd.oasis.opendocument.text-master +odp = application/vnd.oasis.opendocument.presentation +ods = application/vnd.oasis.opendocument.spreadsheet +odt = application/vnd.oasis.opendocument.text +ogg = application/ogg +otc = application/vnd.oasis.opendocument.chart-template +otf = application/vnd.oasis.opendocument.formula-template +otg = application/vnd.oasis.opendocument.graphics-template +oth = application/vnd.oasis.opendocument.text-web +oti = application/vnd.oasis.opendocument.image-template +otp = application/vnd.oasis.opendocument.presentation-template +ots = application/vnd.oasis.opendocument.spreadsheet-template +ott = application/vnd.oasis.opendocument.text-template +pbm = image/x-portable-bitmap +pdb = chemical/x-pdb +pdf = application/pdf +pgm = image/x-portable-graymap +pgn = application/x-chess-pgn +png = image/png +pnm = image/x-portable-anymap +ppm = image/x-portable-pixmap +pps = application/vnd.ms-powerpoint +ppt = application/vnd.ms-powerpoint +ps = application/postscript +qt = video/quicktime +ra = audio/x-pn-realaudio +ram = audio/x-pn-realaudio +ras = image/x-cmu-raster +rdf = application/rdf+xml +rgb = image/x-rgb +rm = audio/x-pn-realaudio +roff = application/x-troff +rpm = application/x-rpm +rtf = application/rtf +rtx = text/richtext +rv = video/vnd.rn-realvideo +ser = application/java-serialized-object +sgm = text/sgml +sgml = text/sgml +sh = application/x-sh +shar = application/x-shar +silo = model/mesh +sit = application/x-stuffit +skd = application/x-koan +skm = application/x-koan +skp = application/x-koan +skt = application/x-koan +smi = application/smil +smil = application/smil +snd = audio/basic +spl = application/x-futuresplash +src = application/x-wais-source +sv4cpio = application/x-sv4cpio +sv4crc = application/x-sv4crc +svg = image/svg+xml +swf = application/x-shockwave-flash +t = application/x-troff +tar = application/x-tar +tar.gz = application/x-gtar +tcl = application/x-tcl +tex = application/x-tex +texi = application/x-texinfo +texinfo = application/x-texinfo +tgz = application/x-gtar +tif = image/tiff +tiff = image/tiff +tr = application/x-troff +tsv = text/tab-separated-values +txt = text/plain +ustar = application/x-ustar +vcd = application/x-cdlink +vrml = model/vrml +vxml = application/voicexml+xml +wav = audio/x-wav +wbmp = image/vnd.wap.wbmp +wml = text/vnd.wap.wml +wmlc = application/vnd.wap.wmlc +wmls = text/vnd.wap.wmlscript +wmlsc = application/vnd.wap.wmlscriptc +wrl = model/vrml +wtls-ca-certificate = application/vnd.wap.wtls-ca-certificate +xbm = image/x-xbitmap +xht = application/xhtml+xml +xhtml = application/xhtml+xml +xls = application/vnd.ms-excel +xml = application/xml +xpm = image/x-xpixmap +xsd = application/xml +xsl = application/xml +xslt = application/xslt+xml +xul = application/vnd.mozilla.xul+xml +xwd = image/x-xwindowdump +xyz = chemical/x-xyz +z = application/compress +zip = application/zip
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/http/ssl/SslContextFactory.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,41 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.http.ssl; + +/* ------------------------------------------------------------ */ +/** + * @deprecated Use org.eclipse.jetty.util.ssl.SslContextFactory + */ +public class SslContextFactory extends org.eclipse.jetty.util.ssl.SslContextFactory +{ + public SslContextFactory() + { + super(); + } + + public SslContextFactory(boolean trustAll) + { + super(trustAll); + } + + public SslContextFactory(String keyStorePath) + { + super(keyStorePath); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/AbstractBuffer.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,728 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; + +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + * + * + */ +public abstract class AbstractBuffer implements Buffer +{ + private static final Logger LOG = Log.getLogger(AbstractBuffer.class); + + private final static boolean __boundsChecking = Boolean.getBoolean("org.eclipse.jetty.io.AbstractBuffer.boundsChecking"); + + protected final static String + __IMMUTABLE = "IMMUTABLE", + __READONLY = "READONLY", + __READWRITE = "READWRITE", + __VOLATILE = "VOLATILE"; + + protected int _access; + protected boolean _volatile; + + protected int _get; + protected int _put; + protected int _hash; + protected int _hashGet; + protected int _hashPut; + protected int _mark; + protected String _string; + protected View _view; + + /** + * Constructor for BufferView + * + * @param access 0==IMMUTABLE, 1==READONLY, 2==READWRITE + */ + public AbstractBuffer(int access, boolean isVolatile) + { + if (access == IMMUTABLE && isVolatile) + throw new IllegalArgumentException("IMMUTABLE && VOLATILE"); + setMarkIndex(-1); + _access = access; + _volatile = isVolatile; + } + + /* + * @see org.eclipse.io.Buffer#toArray() + */ + public byte[] asArray() + { + byte[] bytes = new byte[length()]; + byte[] array = array(); + if (array != null) + System.arraycopy(array, getIndex(), bytes, 0, bytes.length); + else + peek(getIndex(), bytes, 0, length()); + return bytes; + } + + public ByteArrayBuffer duplicate(int access) + { + Buffer b=this.buffer(); + if (this instanceof Buffer.CaseInsensitve || b instanceof Buffer.CaseInsensitve) + return new ByteArrayBuffer.CaseInsensitive(asArray(), 0, length(),access); + else + return new ByteArrayBuffer(asArray(), 0, length(), access); + } + + /* + * @see org.eclipse.io.Buffer#asNonVolatile() + */ + public Buffer asNonVolatileBuffer() + { + if (!isVolatile()) return this; + return duplicate(_access); + } + + public Buffer asImmutableBuffer() + { + if (isImmutable()) return this; + return duplicate(IMMUTABLE); + } + + /* + * @see org.eclipse.util.Buffer#asReadOnlyBuffer() + */ + public Buffer asReadOnlyBuffer() + { + if (isReadOnly()) return this; + return new View(this, markIndex(), getIndex(), putIndex(), READONLY); + } + + public Buffer asMutableBuffer() + { + if (!isImmutable()) return this; + + Buffer b=this.buffer(); + if (b.isReadOnly()) + { + return duplicate(READWRITE); + } + return new View(b, markIndex(), getIndex(), putIndex(), _access); + } + + public Buffer buffer() + { + return this; + } + + public void clear() + { + setMarkIndex(-1); + setGetIndex(0); + setPutIndex(0); + } + + public void compact() + { + if (isReadOnly()) throw new IllegalStateException(__READONLY); + int s = markIndex() >= 0 ? markIndex() : getIndex(); + if (s > 0) + { + byte array[] = array(); + int length = putIndex() - s; + if (length > 0) + { + if (array != null) + System.arraycopy(array(), s, array(), 0, length); + else + poke(0, peek(s, length)); + } + if (markIndex() > 0) setMarkIndex(markIndex() - s); + setGetIndex(getIndex() - s); + setPutIndex(putIndex() - s); + } + } + + @Override + public boolean equals(Object obj) + { + if (obj==this) + return true; + + // reject non buffers; + if (obj == null || !(obj instanceof Buffer)) return false; + Buffer b = (Buffer) obj; + + if (this instanceof Buffer.CaseInsensitve || b instanceof Buffer.CaseInsensitve) + return equalsIgnoreCase(b); + + // reject different lengths + if (b.length() != length()) return false; + + // reject AbstractBuffer with different hash value + if (_hash != 0 && obj instanceof AbstractBuffer) + { + AbstractBuffer ab = (AbstractBuffer) obj; + if (ab._hash != 0 && _hash != ab._hash) return false; + } + + // Nothing for it but to do the hard grind. + int get=getIndex(); + int bi=b.putIndex(); + for (int i = putIndex(); i-->get;) + { + byte b1 = peek(i); + byte b2 = b.peek(--bi); + if (b1 != b2) return false; + } + return true; + } + + public boolean equalsIgnoreCase(Buffer b) + { + if (b==this) + return true; + + // reject different lengths + if (b.length() != length()) return false; + + // reject AbstractBuffer with different hash value + if (_hash != 0 && b instanceof AbstractBuffer) + { + AbstractBuffer ab = (AbstractBuffer) b; + if (ab._hash != 0 && _hash != ab._hash) return false; + } + + // Nothing for it but to do the hard grind. + int get=getIndex(); + int bi=b.putIndex(); + + byte[] array = array(); + byte[] barray= b.array(); + if (array!=null && barray!=null) + { + for (int i = putIndex(); i-->get;) + { + byte b1 = array[i]; + byte b2 = barray[--bi]; + if (b1 != b2) + { + if ('a' <= b1 && b1 <= 'z') b1 = (byte) (b1 - 'a' + 'A'); + if ('a' <= b2 && b2 <= 'z') b2 = (byte) (b2 - 'a' + 'A'); + if (b1 != b2) return false; + } + } + } + else + { + for (int i = putIndex(); i-->get;) + { + byte b1 = peek(i); + byte b2 = b.peek(--bi); + if (b1 != b2) + { + if ('a' <= b1 && b1 <= 'z') b1 = (byte) (b1 - 'a' + 'A'); + if ('a' <= b2 && b2 <= 'z') b2 = (byte) (b2 - 'a' + 'A'); + if (b1 != b2) return false; + } + } + } + return true; + } + + public byte get() + { + return peek(_get++); + } + + public int get(byte[] b, int offset, int length) + { + int gi = getIndex(); + int l=length(); + if (l==0) + return -1; + + if (length>l) + length=l; + + length = peek(gi, b, offset, length); + if (length>0) + setGetIndex(gi + length); + return length; + } + + public Buffer get(int length) + { + int gi = getIndex(); + Buffer view = peek(gi, length); + setGetIndex(gi + length); + return view; + } + + public final int getIndex() + { + return _get; + } + + public boolean hasContent() + { + return _put > _get; + } + + @Override + public int hashCode() + { + if (_hash == 0 || _hashGet!=_get || _hashPut!=_put) + { + int get=getIndex(); + byte[] array = array(); + if (array==null) + { + for (int i = putIndex(); i-- >get;) + { + byte b = peek(i); + if ('a' <= b && b <= 'z') + b = (byte) (b - 'a' + 'A'); + _hash = 31 * _hash + b; + } + } + else + { + for (int i = putIndex(); i-- >get;) + { + byte b = array[i]; + if ('a' <= b && b <= 'z') + b = (byte) (b - 'a' + 'A'); + _hash = 31 * _hash + b; + } + } + if (_hash == 0) + _hash = -1; + _hashGet=_get; + _hashPut=_put; + + } + return _hash; + } + + public boolean isImmutable() + { + return _access <= IMMUTABLE; + } + + public boolean isReadOnly() + { + return _access <= READONLY; + } + + public boolean isVolatile() + { + return _volatile; + } + + public int length() + { + return _put - _get; + } + + public void mark() + { + setMarkIndex(_get - 1); + } + + public void mark(int offset) + { + setMarkIndex(_get + offset); + } + + public int markIndex() + { + return _mark; + } + + public byte peek() + { + return peek(_get); + } + + public Buffer peek(int index, int length) + { + if (_view == null) + { + _view = new View(this, -1, index, index + length, isReadOnly() ? READONLY : READWRITE); + } + else + { + _view.update(this.buffer()); + _view.setMarkIndex(-1); + _view.setGetIndex(0); + _view.setPutIndex(index + length); + _view.setGetIndex(index); + + } + return _view; + } + + public int poke(int index, Buffer src) + { + _hash=0; + /* + if (isReadOnly()) + throw new IllegalStateException(__READONLY); + if (index < 0) + throw new IllegalArgumentException("index<0: " + index + "<0"); + */ + + int length=src.length(); + if (index + length > capacity()) + { + length=capacity()-index; + /* + if (length<0) + throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity()); + */ + } + + byte[] src_array = src.array(); + byte[] dst_array = array(); + if (src_array != null && dst_array != null) + System.arraycopy(src_array, src.getIndex(), dst_array, index, length); + else if (src_array != null) + { + int s=src.getIndex(); + for (int i=0;i<length;i++) + poke(index++,src_array[s++]); + } + else if (dst_array != null) + { + int s=src.getIndex(); + for (int i=0;i<length;i++) + dst_array[index++]=src.peek(s++); + } + else + { + int s=src.getIndex(); + for (int i=0;i<length;i++) + poke(index++,src.peek(s++)); + } + + return length; + } + + + public int poke(int index, byte[] b, int offset, int length) + { + _hash=0; + /* + if (isReadOnly()) + throw new IllegalStateException(__READONLY); + if (index < 0) + throw new IllegalArgumentException("index<0: " + index + "<0"); + */ + if (index + length > capacity()) + { + length=capacity()-index; + /* if (length<0) + throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity()); + */ + } + + byte[] dst_array = array(); + if (dst_array != null) + System.arraycopy(b, offset, dst_array, index, length); + else + { + int s=offset; + for (int i=0;i<length;i++) + poke(index++,b[s++]); + } + return length; + } + + public int put(Buffer src) + { + int pi = putIndex(); + int l=poke(pi, src); + setPutIndex(pi + l); + return l; + } + + public void put(byte b) + { + int pi = putIndex(); + poke(pi, b); + setPutIndex(pi + 1); + } + + public int put(byte[] b, int offset, int length) + { + int pi = putIndex(); + int l = poke(pi, b, offset, length); + setPutIndex(pi + l); + return l; + } + + public int put(byte[] b) + { + int pi = putIndex(); + int l = poke(pi, b, 0, b.length); + setPutIndex(pi + l); + return l; + } + + public final int putIndex() + { + return _put; + } + + public void reset() + { + if (markIndex() >= 0) setGetIndex(markIndex()); + } + + public void rewind() + { + setGetIndex(0); + setMarkIndex(-1); + } + + public void setGetIndex(int getIndex) + { + /* bounds checking + if (isImmutable()) + throw new IllegalStateException(__IMMUTABLE); + if (getIndex < 0) + throw new IllegalArgumentException("getIndex<0: " + getIndex + "<0"); + if (getIndex > putIndex()) + throw new IllegalArgumentException("getIndex>putIndex: " + getIndex + ">" + putIndex()); + */ + _get = getIndex; + _hash=0; + } + + public void setMarkIndex(int index) + { + /* + if (index>=0 && isImmutable()) + throw new IllegalStateException(__IMMUTABLE); + */ + _mark = index; + } + + public void setPutIndex(int putIndex) + { + /* bounds checking + if (isImmutable()) + throw new IllegalStateException(__IMMUTABLE); + if (putIndex > capacity()) + throw new IllegalArgumentException("putIndex>capacity: " + putIndex + ">" + capacity()); + if (getIndex() > putIndex) + throw new IllegalArgumentException("getIndex>putIndex: " + getIndex() + ">" + putIndex); + */ + _put = putIndex; + _hash=0; + } + + public int skip(int n) + { + if (length() < n) n = length(); + setGetIndex(getIndex() + n); + return n; + } + + public Buffer slice() + { + return peek(getIndex(), length()); + } + + public Buffer sliceFromMark() + { + return sliceFromMark(getIndex() - markIndex() - 1); + } + + public Buffer sliceFromMark(int length) + { + if (markIndex() < 0) return null; + Buffer view = peek(markIndex(), length); + setMarkIndex(-1); + return view; + } + + public int space() + { + return capacity() - _put; + } + + public String toDetailString() + { + StringBuilder buf = new StringBuilder(); + buf.append("["); + buf.append(super.hashCode()); + buf.append(","); + buf.append(this.buffer().hashCode()); + buf.append(",m="); + buf.append(markIndex()); + buf.append(",g="); + buf.append(getIndex()); + buf.append(",p="); + buf.append(putIndex()); + buf.append(",c="); + buf.append(capacity()); + buf.append("]={"); + if (markIndex() >= 0) + { + for (int i = markIndex(); i < getIndex(); i++) + { + byte b = peek(i); + TypeUtil.toHex(b,buf); + } + buf.append("}{"); + } + int count = 0; + for (int i = getIndex(); i < putIndex(); i++) + { + byte b = peek(i); + TypeUtil.toHex(b,buf); + if (count++ == 50) + { + if (putIndex() - i > 20) + { + buf.append(" ... "); + i = putIndex() - 20; + } + } + } + buf.append('}'); + return buf.toString(); + } + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + if (isImmutable()) + { + if (_string == null) + _string = new String(asArray(), 0, length()); + return _string; + } + return new String(asArray(), 0, length()); + } + + /* ------------------------------------------------------------ */ + public String toString(String charset) + { + try + { + byte[] bytes=array(); + if (bytes!=null) + return new String(bytes,getIndex(),length(),charset); + return new String(asArray(), 0, length(),charset); + + } + catch(Exception e) + { + LOG.warn(e); + return new String(asArray(), 0, length()); + } + } + + /* ------------------------------------------------------------ */ + public String toString(Charset charset) + { + try + { + byte[] bytes=array(); + if (bytes!=null) + return new String(bytes,getIndex(),length(),charset); + return new String(asArray(), 0, length(),charset); + } + catch(Exception e) + { + LOG.warn(e); + return new String(asArray(), 0, length()); + } + } + + /* ------------------------------------------------------------ */ + public String toDebugString() + { + return getClass()+"@"+super.hashCode(); + } + + /* ------------------------------------------------------------ */ + public void writeTo(OutputStream out) + throws IOException + { + byte[] array = array(); + + if (array!=null) + { + out.write(array,getIndex(),length()); + } + else + { + int len = this.length(); + byte[] buf=new byte[len>1024?1024:len]; + int offset=_get; + while (len>0) + { + int l=peek(offset,buf,0,len>buf.length?buf.length:len); + out.write(buf,0,l); + offset+=l; + len-=l; + } + } + clear(); + } + + /* ------------------------------------------------------------ */ + public int readFrom(InputStream in,int max) throws IOException + { + byte[] array = array(); + int s=space(); + if (s>max) + s=max; + + if (array!=null) + { + int l=in.read(array,_put,s); + if (l>0) + _put+=l; + return l; + } + else + { + byte[] buf=new byte[s>1024?1024:s]; + int total=0; + while (s>0) + { + int l=in.read(buf,0,buf.length); + if (l<0) + return total>0?total:-1; + int p=put(buf,0,l); + assert l==p; + s-=l; + } + return total; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/AbstractBuffers.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,168 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io; + +import org.eclipse.jetty.io.nio.DirectNIOBuffer; +import org.eclipse.jetty.io.nio.IndirectNIOBuffer; + +public abstract class AbstractBuffers implements Buffers +{ + protected final Buffers.Type _headerType; + protected final int _headerSize; + protected final Buffers.Type _bufferType; + protected final int _bufferSize; + protected final Buffers.Type _otherType; + + /* ------------------------------------------------------------ */ + public AbstractBuffers(Buffers.Type headerType, int headerSize, Buffers.Type bufferType, int bufferSize, Buffers.Type otherType) + { + _headerType=headerType; + _headerSize=headerSize; + _bufferType=bufferType; + _bufferSize=bufferSize; + _otherType=otherType; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the buffer size in bytes. + */ + public int getBufferSize() + { + return _bufferSize; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the header size in bytes. + */ + public int getHeaderSize() + { + return _headerSize; + } + + + /* ------------------------------------------------------------ */ + /** + * Create a new header Buffer + * @return new Buffer + */ + final protected Buffer newHeader() + { + switch(_headerType) + { + case BYTE_ARRAY: + return new ByteArrayBuffer(_headerSize); + case DIRECT: + return new DirectNIOBuffer(_headerSize); + case INDIRECT: + return new IndirectNIOBuffer(_headerSize); + } + throw new IllegalStateException(); + } + + /* ------------------------------------------------------------ */ + /** + * Create a new content Buffer + * @return new Buffer + */ + final protected Buffer newBuffer() + { + switch(_bufferType) + { + case BYTE_ARRAY: + return new ByteArrayBuffer(_bufferSize); + case DIRECT: + return new DirectNIOBuffer(_bufferSize); + case INDIRECT: + return new IndirectNIOBuffer(_bufferSize); + } + throw new IllegalStateException(); + } + + /* ------------------------------------------------------------ */ + /** + * Create a new content Buffer + * @param size + * @return new Buffer + */ + final protected Buffer newBuffer(int size) + { + switch(_otherType) + { + case BYTE_ARRAY: + return new ByteArrayBuffer(size); + case DIRECT: + return new DirectNIOBuffer(size); + case INDIRECT: + return new IndirectNIOBuffer(size); + } + throw new IllegalStateException(); + } + + /* ------------------------------------------------------------ */ + /** + * @param buffer + * @return True if the buffer is the correct type to be a Header buffer + */ + public final boolean isHeader(Buffer buffer) + { + if (buffer.capacity()==_headerSize) + { + switch(_headerType) + { + case BYTE_ARRAY: + return buffer instanceof ByteArrayBuffer && !(buffer instanceof IndirectNIOBuffer); + case DIRECT: + return buffer instanceof DirectNIOBuffer; + case INDIRECT: + return buffer instanceof IndirectNIOBuffer; + } + } + return false; + } + + /* ------------------------------------------------------------ */ + /** + * @param buffer + * @return True if the buffer is the correct type to be a Header buffer + */ + public final boolean isBuffer(Buffer buffer) + { + if (buffer.capacity()==_bufferSize) + { + switch(_bufferType) + { + case BYTE_ARRAY: + return buffer instanceof ByteArrayBuffer && !(buffer instanceof IndirectNIOBuffer); + case DIRECT: + return buffer instanceof DirectNIOBuffer; + case INDIRECT: + return buffer instanceof IndirectNIOBuffer; + } + } + return false; + } + + /* ------------------------------------------------------------ */ + public String toString() + { + return String.format("%s [%d,%d]", getClass().getSimpleName(), _headerSize, _bufferSize); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/AbstractConnection.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,85 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io; + +import java.io.IOException; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + + +public abstract class AbstractConnection implements Connection +{ + private static final Logger LOG = Log.getLogger(AbstractConnection.class); + + private final long _timeStamp; + protected final EndPoint _endp; + + public AbstractConnection(EndPoint endp) + { + _endp=(EndPoint)endp; + _timeStamp = System.currentTimeMillis(); + } + + public AbstractConnection(EndPoint endp,long timestamp) + { + _endp=(EndPoint)endp; + _timeStamp = timestamp; + } + + public long getTimeStamp() + { + return _timeStamp; + } + + public EndPoint getEndPoint() + { + return _endp; + } + + public void onIdleExpired(long idleForMs) + { + try + { + LOG.debug("onIdleExpired {}ms {} {}",idleForMs,this,_endp); + if (_endp.isInputShutdown() || _endp.isOutputShutdown()) + _endp.close(); + else + _endp.shutdownOutput(); + } + catch(IOException e) + { + LOG.ignore(e); + + try + { + _endp.close(); + } + catch(IOException e2) + { + LOG.ignore(e2); + } + } + } + + public String toString() + { + return String.format("%s@%x", getClass().getSimpleName(), hashCode()); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/AsyncEndPoint.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,83 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io; + +import org.eclipse.jetty.util.thread.Timeout; + +public interface AsyncEndPoint extends ConnectedEndPoint +{ + /* ------------------------------------------------------------ */ + /** + * Dispatch the endpoint if it is not already dispatched + * + */ + public void dispatch(); + + /* ------------------------------------------------------------ */ + /** + * Dispatch the endpoint. If it is already dispatched, schedule a redispatch + * + */ + public void asyncDispatch(); + + /* ------------------------------------------------------------ */ + /** Schedule a write dispatch. + * Set the endpoint to not be writable and schedule a dispatch when + * it becomes writable. + */ + public void scheduleWrite(); + + /* ------------------------------------------------------------ */ + /** Callback when idle. + * <p>An endpoint is idle if there has been no IO activity for + * {@link #getMaxIdleTime()} and {@link #isCheckForIdle()} is true. + * @param idleForMs TODO + */ + public void onIdleExpired(long idleForMs); + + /* ------------------------------------------------------------ */ + /** Set if the endpoint should be checked for idleness + */ + public void setCheckForIdle(boolean check); + + /* ------------------------------------------------------------ */ + /** Get if the endpoint should be checked for idleness + */ + public boolean isCheckForIdle(); + + + /* ------------------------------------------------------------ */ + public boolean isWritable(); + + /* ------------------------------------------------------------ */ + /** + * @return True if IO has been successfully performed since the last call to {@link #hasProgressed()} + */ + public boolean hasProgressed(); + + /* ------------------------------------------------------------ */ + /** + */ + public void scheduleTimeout(Timeout.Task task, long timeoutMs); + + /* ------------------------------------------------------------ */ + /** + */ + public void cancelTimeout(Timeout.Task task); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/Buffer.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,380 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; + + +/** + * Byte Buffer interface. + * + * This is a byte buffer that is designed to work like a FIFO for bytes. Puts and Gets operate on different + * pointers into the buffer and the valid _content of the buffer is always between the getIndex and the putIndex. + * + * This buffer interface is designed to be similar, but not dependent on the java.nio buffers, which may + * be used to back an implementation of this Buffer. The main difference is that NIO buffer after a put have + * their valid _content before the position and a flip is required to access that data. + * + * For this buffer it is always true that: + * markValue <= getIndex <= putIndex <= capacity + * + * + * @version 1.0 + */ +public interface Buffer extends Cloneable +{ + public final static int + IMMUTABLE=0, // neither indexes or contexts can be changed + READONLY=1, // indexes may be changed, but not content + READWRITE=2; // anything can be changed + public final boolean VOLATILE=true; // The buffer may change outside of current scope. + public final boolean NON_VOLATILE=false; + + /** + * Get the underlying array, if one exists. + * @return a <code>byte[]</code> backing this buffer or null if none exists. + */ + byte[] array(); + + /** + * + * @return a <code>byte[]</code> value of the bytes from the getIndex to the putIndex. + */ + byte[] asArray(); + + /** + * Get the underlying buffer. If this buffer wraps a backing buffer. + * @return The root backing buffer or this if there is no backing buffer; + */ + Buffer buffer(); + + /** + * + * @return a non volatile version of this <code>Buffer</code> value + */ + Buffer asNonVolatileBuffer(); + + /** + * + * @return a readonly version of this <code>Buffer</code>. + */ + Buffer asReadOnlyBuffer(); + + /** + * + * @return an immutable version of this <code>Buffer</code>. + */ + Buffer asImmutableBuffer(); + + /** + * + * @return an immutable version of this <code>Buffer</code>. + */ + Buffer asMutableBuffer(); + + /** + * + * The capacity of the buffer. This is the maximum putIndex that may be set. + * @return an <code>int</code> value + */ + int capacity(); + + /** + * the space remaining in the buffer. + * @return capacity - putIndex + */ + int space(); + + /** + * Clear the buffer. getIndex=0, putIndex=0. + */ + void clear(); + + /** + * Compact the buffer by discarding bytes before the postion (or mark if set). + * Bytes from the getIndex (or mark) to the putIndex are moved to the beginning of + * the buffer and the values adjusted accordingly. + */ + void compact(); + + /** + * Get the byte at the current getIndex and increment it. + * @return The <code>byte</code> value from the current getIndex. + */ + byte get(); + + /** + * Get bytes from the current postion and put them into the passed byte array. + * The getIndex is incremented by the number of bytes copied into the array. + * @param b The byte array to fill. + * @param offset Offset in the array. + * @param length The max number of bytes to read. + * @return The number of bytes actually read. + */ + int get(byte[] b, int offset, int length); + + /** + * + * @param length an <code>int</code> value + * @return a <code>Buffer</code> value + */ + Buffer get(int length); + + /** + * The index within the buffer that will next be read or written. + * @return an <code>int</code> value >=0 <= putIndex() + */ + int getIndex(); + + /** + * @return true of putIndex > getIndex + */ + boolean hasContent(); + + /** + * + * @return a <code>boolean</code> value true if case sensitive comparison on this buffer + */ + boolean equalsIgnoreCase(Buffer buffer); + + + /** + * + * @return a <code>boolean</code> value true if the buffer is immutable and that neither + * the buffer contents nor the indexes may be changed. + */ + boolean isImmutable(); + + /** + * + * @return a <code>boolean</code> value true if the buffer is readonly. The buffer indexes may + * be modified, but the buffer contents may not. For example a View onto an immutable Buffer will be + * read only. + */ + boolean isReadOnly(); + + /** + * + * @return a <code>boolean</code> value true if the buffer contents may change + * via alternate paths than this buffer. If the contents of this buffer are to be used outside of the + * current context, then a copy must be made. + */ + boolean isVolatile(); + + /** + * The number of bytes from the getIndex to the putIndex + * @return an <code>int</code> == putIndex()-getIndex() + */ + int length(); + + /** + * Set the mark to the current getIndex. + */ + void mark(); + + /** + * Set the mark relative to the current getIndex + * @param offset an <code>int</code> value to add to the current getIndex to obtain the mark value. + */ + void mark(int offset); + + /** + * The current index of the mark. + * @return an <code>int</code> index in the buffer or -1 if the mark is not set. + */ + int markIndex(); + + /** + * Get the byte at the current getIndex without incrementing the getIndex. + * @return The <code>byte</code> value from the current getIndex. + */ + byte peek(); + + /** + * Get the byte at a specific index in the buffer. + * @param index an <code>int</code> value + * @return a <code>byte</code> value + */ + byte peek(int index); + + /** + * + * @param index an <code>int</code> value + * @param length an <code>int</code> value + * @return The <code>Buffer</code> value from the requested getIndex. + */ + Buffer peek(int index, int length); + + /** + * + * @param index an <code>int</code> value + * @param b The byte array to peek into + * @param offset The offset into the array to start peeking + * @param length an <code>int</code> value + * @return The number of bytes actually peeked + */ + int peek(int index, byte[] b, int offset, int length); + + /** + * Put the contents of the buffer at the specific index. + * @param index an <code>int</code> value + * @param src a <code>Buffer</code>. If the source buffer is not modified + + * @return The number of bytes actually poked + */ + int poke(int index, Buffer src); + + /** + * Put a specific byte to a specific getIndex. + * @param index an <code>int</code> value + * @param b a <code>byte</code> value + */ + void poke(int index, byte b); + + /** + * Put a specific byte to a specific getIndex. + * @param index an <code>int</code> value + * @param b a <code>byte array</code> value + * @return The number of bytes actually poked + */ + int poke(int index, byte b[], int offset, int length); + + /** + * Write the bytes from the source buffer to the current getIndex. + * @param src The source <code>Buffer</code> it is not modified. + * @return The number of bytes actually poked + */ + int put(Buffer src); + + /** + * Put a byte to the current getIndex and increment the getIndex. + * @param b a <code>byte</code> value + */ + void put(byte b); + + /** + * Put a byte to the current getIndex and increment the getIndex. + * @param b a <code>byte</code> value + * @return The number of bytes actually poked + */ + int put(byte[] b,int offset, int length); + + /** + * Put a byte to the current getIndex and increment the getIndex. + * @param b a <code>byte</code> value + * @return The number of bytes actually poked + */ + int put(byte[] b); + + /** + * The index of the first element that should not be read. + * @return an <code>int</code> value >= getIndex() + */ + int putIndex(); + + /** + * Reset the current getIndex to the mark + */ + void reset(); + + /** + * Set the buffers start getIndex. + * @param newStart an <code>int</code> value + */ + void setGetIndex(int newStart); + + /** + * Set a specific value for the mark. + * @param newMark an <code>int</code> value + */ + void setMarkIndex(int newMark); + + /** + * + * @param newLimit an <code>int</code> value + */ + void setPutIndex(int newLimit); + + /** + * Skip _content. The getIndex is updated by min(remaining(), n) + * @param n The number of bytes to skip + * @return the number of bytes skipped. + */ + int skip(int n); + + /** + * + * @return a volitile <code>Buffer</code> from the postion to the putIndex. + */ + Buffer slice(); + + /** + * + * + * @return a volitile <code>Buffer</code> value from the mark to the putIndex + */ + Buffer sliceFromMark(); + + /** + * + * + * @param length an <code>int</code> value + * @return a valitile <code>Buffer</code> value from the mark of the length requested. + */ + Buffer sliceFromMark(int length); + + /** + * + * @return a <code>String</code> value describing the state and contents of the buffer. + */ + String toDetailString(); + + /* ------------------------------------------------------------ */ + /** Write the buffer's contents to the output stream + * @param out + */ + void writeTo(OutputStream out) throws IOException; + + /* ------------------------------------------------------------ */ + /** Read the buffer's contents from the input stream + * @param in input stream + * @param max maximum number of bytes that may be read + * @return actual number of bytes read or -1 for EOF + */ + int readFrom(InputStream in, int max) throws IOException; + + + /* ------------------------------------------------------------ */ + String toString(String charset); + + /* ------------------------------------------------------------ */ + String toString(Charset charset); + + /* + * Buffers implementing this interface should be compared with case insensitive equals + * + */ + public interface CaseInsensitve + {} + + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/BufferCache.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,169 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map.Entry; + +import org.eclipse.jetty.util.StringMap; + +/* ------------------------------------------------------------------------------- */ +/** + * Stores a collection of {@link Buffer} objects. + * Buffers are stored in an ordered collection and can retreived by index or value + * + */ +public class BufferCache +{ + private final HashMap _bufferMap=new HashMap(); + private final StringMap _stringMap=new StringMap(StringMap.CASE_INSENSTIVE); + private final ArrayList _index= new ArrayList(); + + /* ------------------------------------------------------------------------------- */ + /** Add a buffer to the cache at the specified index. + * @param value The content of the buffer. + */ + public CachedBuffer add(String value, int ordinal) + { + CachedBuffer buffer= new CachedBuffer(value, ordinal); + _bufferMap.put(buffer, buffer); + _stringMap.put(value, buffer); + while ((ordinal - _index.size()) >= 0) + _index.add(null); + if (_index.get(ordinal)==null) + _index.add(ordinal, buffer); + return buffer; + } + + public CachedBuffer get(int ordinal) + { + if (ordinal < 0 || ordinal >= _index.size()) + return null; + return (CachedBuffer)_index.get(ordinal); + } + + public CachedBuffer get(Buffer buffer) + { + return (CachedBuffer)_bufferMap.get(buffer); + } + + public CachedBuffer get(String value) + { + return (CachedBuffer)_stringMap.get(value); + } + + public Buffer lookup(Buffer buffer) + { + if (buffer instanceof CachedBuffer) + return buffer; + + Buffer b= get(buffer); + if (b == null) + { + if (buffer instanceof Buffer.CaseInsensitve) + return buffer; + return new ByteArrayBuffer.CaseInsensitive(buffer.asArray(),0,buffer.length(),Buffer.IMMUTABLE); + } + + return b; + } + + public CachedBuffer getBest(byte[] value, int offset, int maxLength) + { + Entry entry = _stringMap.getBestEntry(value, offset, maxLength); + if (entry!=null) + return (CachedBuffer)entry.getValue(); + return null; + } + + public Buffer lookup(String value) + { + Buffer b= get(value); + if (b == null) + { + return new CachedBuffer(value,-1); + } + return b; + } + + public String toString(Buffer buffer) + { + return lookup(buffer).toString(); + } + + public int getOrdinal(String value) + { + CachedBuffer buffer = (CachedBuffer)_stringMap.get(value); + return buffer==null?-1:buffer.getOrdinal(); + } + + public int getOrdinal(Buffer buffer) + { + if (buffer instanceof CachedBuffer) + return ((CachedBuffer)buffer).getOrdinal(); + buffer=lookup(buffer); + if (buffer!=null && buffer instanceof CachedBuffer) + return ((CachedBuffer)buffer).getOrdinal(); + return -1; + } + + public static class CachedBuffer extends ByteArrayBuffer.CaseInsensitive + { + private final int _ordinal; + private HashMap _associateMap=null; + + public CachedBuffer(String value, int ordinal) + { + super(value); + _ordinal= ordinal; + } + + public int getOrdinal() + { + return _ordinal; + } + + public CachedBuffer getAssociate(Object key) + { + if (_associateMap==null) + return null; + return (CachedBuffer)_associateMap.get(key); + } + + // TODO Replace Associate with a mime encoding specific solution + public void setAssociate(Object key, CachedBuffer associate) + { + if (_associateMap==null) + _associateMap=new HashMap(); + _associateMap.put(key,associate); + } + } + + + @Override + public String toString() + { + return "CACHE["+ + "bufferMap="+_bufferMap+ + ",stringMap="+_stringMap+ + ",index="+_index+ + "]"; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/BufferDateCache.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,61 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io; + +import java.text.DateFormatSymbols; +import java.util.Locale; + +import org.eclipse.jetty.util.DateCache; + +public class BufferDateCache extends DateCache +{ + Buffer _buffer; + String _last; + + public BufferDateCache() + { + super(); + } + + public BufferDateCache(String format, DateFormatSymbols s) + { + super(format,s); + } + + public BufferDateCache(String format, Locale l) + { + super(format,l); + } + + public BufferDateCache(String format) + { + super(format); + } + + public synchronized Buffer formatBuffer(long date) + { + String d = super.format(date); + if (d==_last) + return _buffer; + _last=d; + _buffer=new ByteArrayBuffer(d); + + return _buffer; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/BufferUtil.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,359 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io; + +import org.eclipse.jetty.io.BufferCache.CachedBuffer; +import org.eclipse.jetty.util.StringUtil; + +/* ------------------------------------------------------------------------------- */ +/** Buffer utility methods. + * + * + */ +public class BufferUtil +{ + static final byte SPACE= 0x20; + static final byte MINUS= '-'; + static final byte[] DIGIT= + {(byte)'0',(byte)'1',(byte)'2',(byte)'3',(byte)'4',(byte)'5',(byte)'6',(byte)'7',(byte)'8',(byte)'9',(byte)'A',(byte)'B',(byte)'C',(byte)'D',(byte)'E',(byte)'F'}; + + /** + * Convert buffer to an integer. + * Parses up to the first non-numeric character. If no number is found an + * IllegalArgumentException is thrown + * @param buffer A buffer containing an integer. The position is not changed. + * @return an int + */ + public static int toInt(Buffer buffer) + { + int val= 0; + boolean started= false; + boolean minus= false; + for (int i= buffer.getIndex(); i < buffer.putIndex(); i++) + { + byte b= buffer.peek(i); + if (b <= SPACE) + { + if (started) + break; + } + else if (b >= '0' && b <= '9') + { + val= val * 10 + (b - '0'); + started= true; + } + else if (b == MINUS && !started) + { + minus= true; + } + else + break; + } + + if (started) + return minus ? (-val) : val; + throw new NumberFormatException(buffer.toString()); + } + + /** + * Convert buffer to an long. + * Parses up to the first non-numeric character. If no number is found an + * IllegalArgumentException is thrown + * @param buffer A buffer containing an integer. The position is not changed. + * @return an int + */ + public static long toLong(Buffer buffer) + { + long val= 0; + boolean started= false; + boolean minus= false; + for (int i= buffer.getIndex(); i < buffer.putIndex(); i++) + { + byte b= buffer.peek(i); + if (b <= SPACE) + { + if (started) + break; + } + else if (b >= '0' && b <= '9') + { + val= val * 10L + (b - '0'); + started= true; + } + else if (b == MINUS && !started) + { + minus= true; + } + else + break; + } + + if (started) + return minus ? (-val) : val; + throw new NumberFormatException(buffer.toString()); + } + + public static void putHexInt(Buffer buffer, int n) + { + + if (n < 0) + { + buffer.put((byte)'-'); + + if (n == Integer.MIN_VALUE) + { + buffer.put((byte)(0x7f&'8')); + buffer.put((byte)(0x7f&'0')); + buffer.put((byte)(0x7f&'0')); + buffer.put((byte)(0x7f&'0')); + buffer.put((byte)(0x7f&'0')); + buffer.put((byte)(0x7f&'0')); + buffer.put((byte)(0x7f&'0')); + buffer.put((byte)(0x7f&'0')); + + return; + } + n= -n; + } + + if (n < 0x10) + { + buffer.put(DIGIT[n]); + } + else + { + boolean started= false; + // This assumes constant time int arithmatic + for (int i= 0; i < hexDivisors.length; i++) + { + if (n < hexDivisors[i]) + { + if (started) + buffer.put((byte)'0'); + continue; + } + + started= true; + int d= n / hexDivisors[i]; + buffer.put(DIGIT[d]); + n= n - d * hexDivisors[i]; + } + } + } + + /* ------------------------------------------------------------ */ + /** + * Add hex integer BEFORE current getIndex. + * @param buffer + * @param n + */ + public static void prependHexInt(Buffer buffer, int n) + { + if (n==0) + { + int gi=buffer.getIndex(); + buffer.poke(--gi,(byte)'0'); + buffer.setGetIndex(gi); + } + else + { + boolean minus=false; + if (n<0) + { + minus=true; + n=-n; + } + + int gi=buffer.getIndex(); + while(n>0) + { + int d = 0xf&n; + n=n>>4; + buffer.poke(--gi,DIGIT[d]); + } + + if (minus) + buffer.poke(--gi,(byte)'-'); + buffer.setGetIndex(gi); + } + } + + + /* ------------------------------------------------------------ */ + public static void putDecInt(Buffer buffer, int n) + { + if (n < 0) + { + buffer.put((byte)'-'); + + if (n == Integer.MIN_VALUE) + { + buffer.put((byte)'2'); + n= 147483648; + } + else + n= -n; + } + + if (n < 10) + { + buffer.put(DIGIT[n]); + } + else + { + boolean started= false; + // This assumes constant time int arithmatic + for (int i= 0; i < decDivisors.length; i++) + { + if (n < decDivisors[i]) + { + if (started) + buffer.put((byte)'0'); + continue; + } + + started= true; + int d= n / decDivisors[i]; + buffer.put(DIGIT[d]); + n= n - d * decDivisors[i]; + } + } + } + + public static void putDecLong(Buffer buffer, long n) + { + if (n < 0) + { + buffer.put((byte)'-'); + + if (n == Long.MIN_VALUE) + { + buffer.put((byte)'9'); + n= 223372036854775808L; + } + else + n= -n; + } + + if (n < 10) + { + buffer.put(DIGIT[(int)n]); + } + else + { + boolean started= false; + // This assumes constant time int arithmatic + for (int i= 0; i < decDivisorsL.length; i++) + { + if (n < decDivisorsL[i]) + { + if (started) + buffer.put((byte)'0'); + continue; + } + + started= true; + long d= n / decDivisorsL[i]; + buffer.put(DIGIT[(int)d]); + n= n - d * decDivisorsL[i]; + } + } + } + + public static Buffer toBuffer(long value) + { + ByteArrayBuffer buf=new ByteArrayBuffer(32); + putDecLong(buf, value); + return buf; + } + + private final static int[] decDivisors= + { + 1000000000, + 100000000, + 10000000, + 1000000, + 100000, + 10000, + 1000, + 100, + 10, + 1 + }; + + private final static int[] hexDivisors= + { + 0x10000000, + 0x1000000, + 0x100000, + 0x10000, + 0x1000, + 0x100, + 0x10, + 0x1 + }; + + private final static long[] decDivisorsL= + { + 1000000000000000000L, + 100000000000000000L, + 10000000000000000L, + 1000000000000000L, + 100000000000000L, + 10000000000000L, + 1000000000000L, + 100000000000L, + 10000000000L, + 1000000000L, + 100000000L, + 10000000L, + 1000000L, + 100000L, + 10000L, + 1000L, + 100L, + 10L, + 1L + }; + + + public static void putCRLF(Buffer buffer) + { + buffer.put((byte)13); + buffer.put((byte)10); + } + + public static boolean isPrefix(Buffer prefix,Buffer buffer) + { + if (prefix.length()>buffer.length()) + return false; + int bi=buffer.getIndex(); + for (int i=prefix.getIndex(); i<prefix.putIndex();i++) + if (prefix.peek(i)!=buffer.peek(bi++)) + return false; + return true; + } + + public static String to8859_1_String(Buffer buffer) + { + if (buffer instanceof CachedBuffer) + return buffer.toString(); + return buffer.toString(StringUtil.__ISO_8859_1_CHARSET); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/Buffers.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,38 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io; + + +/* ------------------------------------------------------------ */ +/** BufferSource. + * Represents a pool or other source of buffers and abstracts the creation + * of specific types of buffers (eg NIO). The concept of big and little buffers + * is supported, but these terms have no absolute meaning and must be determined by context. + * + */ +public interface Buffers +{ + enum Type { BYTE_ARRAY, DIRECT, INDIRECT } ; + + Buffer getHeader(); + Buffer getBuffer(); + Buffer getBuffer(int size); + + void returnBuffer(Buffer buffer); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/BuffersFactory.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,29 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io; + +public class BuffersFactory +{ + public static Buffers newBuffers(Buffers.Type headerType, int headerSize, Buffers.Type bufferType, int bufferSize, Buffers.Type otherType,int maxSize) + { + if (maxSize>=0) + return new PooledBuffers(headerType,headerSize,bufferType,bufferSize,otherType,maxSize); + return new ThreadLocalBuffers(headerType,headerSize,bufferType,bufferSize,otherType); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/ByteArrayBuffer.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,439 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +import org.eclipse.jetty.util.StringUtil; + +/* ------------------------------------------------------------------------------- */ +/** + * + */ +public class ByteArrayBuffer extends AbstractBuffer +{ + // Set a maximum size to a write for the writeTo method, to ensure that very large content is not + // written as a single write (which may fall foul to write timeouts if consumed slowly). + final static int MAX_WRITE=Integer.getInteger("org.eclipse.jetty.io.ByteArrayBuffer.MAX_WRITE",128*1024); + final protected byte[] _bytes; + + protected ByteArrayBuffer(int size, int access, boolean isVolatile) + { + this(new byte[size],0,0,access, isVolatile); + } + + public ByteArrayBuffer(byte[] bytes) + { + this(bytes, 0, bytes.length, READWRITE); + } + + public ByteArrayBuffer(byte[] bytes, int index, int length) + { + this(bytes, index, length, READWRITE); + } + + public ByteArrayBuffer(byte[] bytes, int index, int length, int access) + { + super(READWRITE, NON_VOLATILE); + _bytes = bytes; + setPutIndex(index + length); + setGetIndex(index); + _access = access; + } + + public ByteArrayBuffer(byte[] bytes, int index, int length, int access, boolean isVolatile) + { + super(READWRITE, isVolatile); + _bytes = bytes; + setPutIndex(index + length); + setGetIndex(index); + _access = access; + } + + public ByteArrayBuffer(int size) + { + this(new byte[size], 0, 0, READWRITE); + setPutIndex(0); + } + + public ByteArrayBuffer(String value) + { + super(READWRITE,NON_VOLATILE); + _bytes = StringUtil.getBytes(value); + setGetIndex(0); + setPutIndex(_bytes.length); + _access=IMMUTABLE; + _string = value; + } + + public ByteArrayBuffer(String value,boolean immutable) + { + super(READWRITE,NON_VOLATILE); + _bytes = StringUtil.getBytes(value); + setGetIndex(0); + setPutIndex(_bytes.length); + if (immutable) + { + _access=IMMUTABLE; + _string = value; + } + } + + public ByteArrayBuffer(String value,String encoding) throws UnsupportedEncodingException + { + super(READWRITE,NON_VOLATILE); + _bytes = value.getBytes(encoding); + setGetIndex(0); + setPutIndex(_bytes.length); + _access=IMMUTABLE; + _string = value; + } + + public byte[] array() + { + return _bytes; + } + + public int capacity() + { + return _bytes.length; + } + + @Override + public void compact() + { + if (isReadOnly()) + throw new IllegalStateException(__READONLY); + int s = markIndex() >= 0 ? markIndex() : getIndex(); + if (s > 0) + { + int length = putIndex() - s; + if (length > 0) + { + System.arraycopy(_bytes, s,_bytes, 0, length); + } + if (markIndex() > 0) setMarkIndex(markIndex() - s); + setGetIndex(getIndex() - s); + setPutIndex(putIndex() - s); + } + } + + + @Override + public boolean equals(Object obj) + { + if (obj==this) + return true; + + if (obj == null || !(obj instanceof Buffer)) + return false; + + if (obj instanceof Buffer.CaseInsensitve) + return equalsIgnoreCase((Buffer)obj); + + + Buffer b = (Buffer) obj; + + // reject different lengths + if (b.length() != length()) + return false; + + // reject AbstractBuffer with different hash value + if (_hash != 0 && obj instanceof AbstractBuffer) + { + AbstractBuffer ab = (AbstractBuffer) obj; + if (ab._hash != 0 && _hash != ab._hash) + return false; + } + + // Nothing for it but to do the hard grind. + int get=getIndex(); + int bi=b.putIndex(); + for (int i = putIndex(); i-->get;) + { + byte b1 = _bytes[i]; + byte b2 = b.peek(--bi); + if (b1 != b2) return false; + } + return true; + } + + + @Override + public boolean equalsIgnoreCase(Buffer b) + { + if (b==this) + return true; + + // reject different lengths + if (b==null || b.length() != length()) + return false; + + // reject AbstractBuffer with different hash value + if (_hash != 0 && b instanceof AbstractBuffer) + { + AbstractBuffer ab = (AbstractBuffer) b; + if (ab._hash != 0 && _hash != ab._hash) return false; + } + + // Nothing for it but to do the hard grind. + int get=getIndex(); + int bi=b.putIndex(); + byte[] barray=b.array(); + if (barray==null) + { + for (int i = putIndex(); i-->get;) + { + byte b1 = _bytes[i]; + byte b2 = b.peek(--bi); + if (b1 != b2) + { + if ('a' <= b1 && b1 <= 'z') b1 = (byte) (b1 - 'a' + 'A'); + if ('a' <= b2 && b2 <= 'z') b2 = (byte) (b2 - 'a' + 'A'); + if (b1 != b2) return false; + } + } + } + else + { + for (int i = putIndex(); i-->get;) + { + byte b1 = _bytes[i]; + byte b2 = barray[--bi]; + if (b1 != b2) + { + if ('a' <= b1 && b1 <= 'z') b1 = (byte) (b1 - 'a' + 'A'); + if ('a' <= b2 && b2 <= 'z') b2 = (byte) (b2 - 'a' + 'A'); + if (b1 != b2) return false; + } + } + } + return true; + } + + @Override + public byte get() + { + return _bytes[_get++]; + } + + @Override + public int hashCode() + { + if (_hash == 0 || _hashGet!=_get || _hashPut!=_put) + { + int get=getIndex(); + for (int i = putIndex(); i-- >get;) + { + byte b = _bytes[i]; + if ('a' <= b && b <= 'z') + b = (byte) (b - 'a' + 'A'); + _hash = 31 * _hash + b; + } + if (_hash == 0) + _hash = -1; + _hashGet=_get; + _hashPut=_put; + } + return _hash; + } + + + public byte peek(int index) + { + return _bytes[index]; + } + + public int peek(int index, byte[] b, int offset, int length) + { + int l = length; + if (index + l > capacity()) + { + l = capacity() - index; + if (l==0) + return -1; + } + + if (l < 0) + return -1; + + System.arraycopy(_bytes, index, b, offset, l); + return l; + } + + public void poke(int index, byte b) + { + /* + if (isReadOnly()) + throw new IllegalStateException(__READONLY); + + if (index < 0) + throw new IllegalArgumentException("index<0: " + index + "<0"); + if (index > capacity()) + throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity()); + */ + _bytes[index] = b; + } + + @Override + public int poke(int index, Buffer src) + { + _hash=0; + + /* + if (isReadOnly()) + throw new IllegalStateException(__READONLY); + if (index < 0) + throw new IllegalArgumentException("index<0: " + index + "<0"); + */ + + int length=src.length(); + if (index + length > capacity()) + { + length=capacity()-index; + /* + if (length<0) + throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity()); + */ + } + + byte[] src_array = src.array(); + if (src_array != null) + System.arraycopy(src_array, src.getIndex(), _bytes, index, length); + else + { + int s=src.getIndex(); + for (int i=0;i<length;i++) + _bytes[index++]=src.peek(s++); + } + + return length; + } + + + @Override + public int poke(int index, byte[] b, int offset, int length) + { + _hash=0; + /* + if (isReadOnly()) + throw new IllegalStateException(__READONLY); + if (index < 0) + throw new IllegalArgumentException("index<0: " + index + "<0"); + */ + + if (index + length > capacity()) + { + length=capacity()-index; + /* if (length<0) + throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity()); + */ + } + + System.arraycopy(b, offset, _bytes, index, length); + + return length; + } + + /* ------------------------------------------------------------ */ + @Override + public void writeTo(OutputStream out) + throws IOException + { + int len=length(); + if (MAX_WRITE>0 && len>MAX_WRITE) + { + int off=getIndex(); + while(len>0) + { + int c=len>MAX_WRITE?MAX_WRITE:len; + out.write(_bytes,off,c); + off+=c; + len-=c; + } + } + else + out.write(_bytes,getIndex(),len); + if (!isImmutable()) + clear(); + } + + /* ------------------------------------------------------------ */ + @Override + public int readFrom(InputStream in,int max) throws IOException + { + if (max<0||max>space()) + max=space(); + int p = putIndex(); + + int len=0, total=0, available=max; + while (total<max) + { + len=in.read(_bytes,p,available); + if (len<0) + break; + else if (len>0) + { + p += len; + total += len; + available -= len; + setPutIndex(p); + } + if (in.available()<=0) + break; + } + if (len<0 && total==0) + return -1; + return total; + } + + /* ------------------------------------------------------------ */ + @Override + public int space() + { + return _bytes.length - _put; + } + + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + public static class CaseInsensitive extends ByteArrayBuffer implements Buffer.CaseInsensitve + { + public CaseInsensitive(String s) + { + super(s); + } + + public CaseInsensitive(byte[] b, int o, int l, int rw) + { + super(b,o,l,rw); + } + + @Override + public boolean equals(Object obj) + { + return obj instanceof Buffer && equalsIgnoreCase((Buffer)obj); + } + + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/ByteArrayEndPoint.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,408 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io; + +import java.io.IOException; + + + +/* ------------------------------------------------------------ */ +/** ByteArrayEndPoint. + * + * + */ +public class ByteArrayEndPoint implements ConnectedEndPoint +{ + protected byte[] _inBytes; + protected ByteArrayBuffer _in; + protected ByteArrayBuffer _out; + protected boolean _closed; + protected boolean _nonBlocking; + protected boolean _growOutput; + protected Connection _connection; + protected int _maxIdleTime; + + + /* ------------------------------------------------------------ */ + /** + * + */ + public ByteArrayEndPoint() + { + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.io.ConnectedEndPoint#getConnection() + */ + public Connection getConnection() + { + return _connection; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.io.ConnectedEndPoint#setConnection(org.eclipse.jetty.io.Connection) + */ + public void setConnection(Connection connection) + { + _connection=connection; + } + + /* ------------------------------------------------------------ */ + /** + * @return the nonBlocking + */ + public boolean isNonBlocking() + { + return _nonBlocking; + } + + /* ------------------------------------------------------------ */ + /** + * @param nonBlocking the nonBlocking to set + */ + public void setNonBlocking(boolean nonBlocking) + { + _nonBlocking=nonBlocking; + } + + /* ------------------------------------------------------------ */ + /** + * + */ + public ByteArrayEndPoint(byte[] input, int outputSize) + { + _inBytes=input; + _in=new ByteArrayBuffer(input); + _out=new ByteArrayBuffer(outputSize); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the in. + */ + public ByteArrayBuffer getIn() + { + return _in; + } + /* ------------------------------------------------------------ */ + /** + * @param in The in to set. + */ + public void setIn(ByteArrayBuffer in) + { + _in = in; + } + /* ------------------------------------------------------------ */ + /** + * @return Returns the out. + */ + public ByteArrayBuffer getOut() + { + return _out; + } + /* ------------------------------------------------------------ */ + /** + * @param out The out to set. + */ + public void setOut(ByteArrayBuffer out) + { + _out = out; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#isOpen() + */ + public boolean isOpen() + { + return !_closed; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.io.EndPoint#isInputShutdown() + */ + public boolean isInputShutdown() + { + return _closed; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.io.EndPoint#isOutputShutdown() + */ + public boolean isOutputShutdown() + { + return _closed; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#isBlocking() + */ + public boolean isBlocking() + { + return !_nonBlocking; + } + + /* ------------------------------------------------------------ */ + public boolean blockReadable(long millisecs) + { + return true; + } + + /* ------------------------------------------------------------ */ + public boolean blockWritable(long millisecs) + { + return true; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#shutdownOutput() + */ + public void shutdownOutput() throws IOException + { + close(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#shutdownInput() + */ + public void shutdownInput() throws IOException + { + close(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#close() + */ + public void close() throws IOException + { + _closed=true; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#fill(org.eclipse.io.Buffer) + */ + public int fill(Buffer buffer) throws IOException + { + if (_closed) + throw new IOException("CLOSED"); + + if (_in!=null && _in.length()>0) + { + int len = buffer.put(_in); + _in.skip(len); + return len; + } + + if (_in!=null && _in.length()==0 && _nonBlocking) + return 0; + + close(); + return -1; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#flush(org.eclipse.io.Buffer) + */ + public int flush(Buffer buffer) throws IOException + { + if (_closed) + throw new IOException("CLOSED"); + if (_growOutput && buffer.length()>_out.space()) + { + _out.compact(); + + if (buffer.length()>_out.space()) + { + ByteArrayBuffer n = new ByteArrayBuffer(_out.putIndex()+buffer.length()); + + n.put(_out.peek(0,_out.putIndex())); + if (_out.getIndex()>0) + { + n.mark(); + n.setGetIndex(_out.getIndex()); + } + _out=n; + } + } + int len = _out.put(buffer); + if (!buffer.isImmutable()) + buffer.skip(len); + return len; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#flush(org.eclipse.io.Buffer, org.eclipse.io.Buffer, org.eclipse.io.Buffer) + */ + public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException + { + if (_closed) + throw new IOException("CLOSED"); + + int flushed=0; + + if (header!=null && header.length()>0) + flushed=flush(header); + + if (header==null || header.length()==0) + { + if (buffer!=null && buffer.length()>0) + flushed+=flush(buffer); + + if (buffer==null || buffer.length()==0) + { + if (trailer!=null && trailer.length()>0) + { + flushed+=flush(trailer); + } + } + } + + return flushed; + } + + /* ------------------------------------------------------------ */ + /** + * + */ + public void reset() + { + _closed=false; + _in.clear(); + _out.clear(); + if (_inBytes!=null) + _in.setPutIndex(_inBytes.length); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getLocalAddr() + */ + public String getLocalAddr() + { + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getLocalHost() + */ + public String getLocalHost() + { + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getLocalPort() + */ + public int getLocalPort() + { + return 0; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getRemoteAddr() + */ + public String getRemoteAddr() + { + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getRemoteHost() + */ + public String getRemoteHost() + { + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getRemotePort() + */ + public int getRemotePort() + { + return 0; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getConnection() + */ + public Object getTransport() + { + return _inBytes; + } + + /* ------------------------------------------------------------ */ + public void flush() throws IOException + { + } + + /* ------------------------------------------------------------ */ + /** + * @return the growOutput + */ + public boolean isGrowOutput() + { + return _growOutput; + } + + /* ------------------------------------------------------------ */ + /** + * @param growOutput the growOutput to set + */ + public void setGrowOutput(boolean growOutput) + { + _growOutput=growOutput; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.io.EndPoint#getMaxIdleTime() + */ + public int getMaxIdleTime() + { + return _maxIdleTime; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.io.EndPoint#setMaxIdleTime(int) + */ + public void setMaxIdleTime(int timeMs) throws IOException + { + _maxIdleTime=timeMs; + } + + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/ConnectedEndPoint.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,25 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io; + +public interface ConnectedEndPoint extends EndPoint +{ + Connection getConnection(); + void setConnection(Connection connection); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/Connection.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,77 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io; + +import java.io.IOException; + +/* ------------------------------------------------------------ */ +/** Abstract Connection used by Jetty Connectors. + * <p> + * Jetty will call the handle method of a connection when there is work + * to be done on the connection. For blocking connections, this is soon + * as the connection is open and handle will keep being called until the + * connection is closed. For non-blocking connections, handle will only + * be called if there are bytes to be read or the connection becomes writable + * after being write blocked. + * + * @see org.eclipse.jetty.io.nio.SelectorManager + */ +public interface Connection +{ + /* ------------------------------------------------------------ */ + /** + * Handle the connection. + * @return The Connection to use for the next handling of the connection. + * This allows protocol upgrades and support for CONNECT. + * @throws IOException if the handling of I/O operations fail + */ + Connection handle() throws IOException; + + /** + * @return the timestamp at which the connection was created + */ + long getTimeStamp(); + + /** + * @return whether this connection is idle, that is not parsing and not generating + * @see #onIdleExpired(long) + */ + boolean isIdle(); + + /** + * <p>The semantic of this method is to return true to indicate interest in further reads, + * or false otherwise, but it is misnamed and should be really called <code>isReadInterested()</code>.</p> + * + * @return true to indicate interest in further reads, false otherwise + */ + // TODO: rename to isReadInterested() in the next release + boolean isSuspended(); + + /** + * Called after the connection is closed + */ + void onClose(); + + /** + * Called when the connection idle timeout expires + * @param idleForMs how long the connection has been idle + * @see #isIdle() + */ + void onIdleExpired(long idleForMs); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/EndPoint.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,175 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io; + +import java.io.IOException; + + +/** + * + * A transport EndPoint + */ +public interface EndPoint +{ + /** + * Shutdown any backing output stream associated with the endpoint + */ + void shutdownOutput() throws IOException; + + boolean isOutputShutdown(); + + /** + * Shutdown any backing input stream associated with the endpoint + */ + void shutdownInput() throws IOException; + + boolean isInputShutdown(); + + /** + * Close any backing stream associated with the endpoint + */ + void close() throws IOException; + + /** + * Fill the buffer from the current putIndex to it's capacity from whatever + * byte source is backing the buffer. The putIndex is increased if bytes filled. + * The buffer may chose to do a compact before filling. + * @return an <code>int</code> value indicating the number of bytes + * filled or -1 if EOF is reached. + * @throws EofException If input is shutdown or the endpoint is closed. + */ + int fill(Buffer buffer) throws IOException; + + + /** + * Flush the buffer from the current getIndex to it's putIndex using whatever byte + * sink is backing the buffer. The getIndex is updated with the number of bytes flushed. + * Any mark set is cleared. + * If the entire contents of the buffer are flushed, then an implicit empty() is done. + * + * @param buffer The buffer to flush. This buffers getIndex is updated. + * @return the number of bytes written + * @throws EofException If the endpoint is closed or output is shutdown. + */ + int flush(Buffer buffer) throws IOException; + + /** + * Flush the buffer from the current getIndex to it's putIndex using whatever byte + * sink is backing the buffer. The getIndex is updated with the number of bytes flushed. + * Any mark set is cleared. + * If the entire contents of the buffer are flushed, then an implicit empty() is done. + * The passed header/trailer buffers are written before/after the contents of this buffer. This may be done + * either as gather writes, as a poke into this buffer or as several writes. The implementation is free to + * select the optimal mechanism. + * @param header A buffer to write before flushing this buffer. This buffers getIndex is updated. + * @param buffer The buffer to flush. This buffers getIndex is updated. + * @param trailer A buffer to write after flushing this buffer. This buffers getIndex is updated. + * @return the total number of bytes written. + */ + int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException; + + + /* ------------------------------------------------------------ */ + /** + * @return The local IP address to which this <code>EndPoint</code> is bound, or <code>null</code> + * if this <code>EndPoint</code> does not represent a network connection. + */ + public String getLocalAddr(); + + /* ------------------------------------------------------------ */ + /** + * @return The local host name to which this <code>EndPoint</code> is bound, or <code>null</code> + * if this <code>EndPoint</code> does not represent a network connection. + */ + public String getLocalHost(); + + /* ------------------------------------------------------------ */ + /** + * @return The local port number on which this <code>EndPoint</code> is listening, or <code>0</code> + * if this <code>EndPoint</code> does not represent a network connection. + */ + public int getLocalPort(); + + /* ------------------------------------------------------------ */ + /** + * @return The remote IP address to which this <code>EndPoint</code> is connected, or <code>null</code> + * if this <code>EndPoint</code> does not represent a network connection. + */ + public String getRemoteAddr(); + + /* ------------------------------------------------------------ */ + /** + * @return The host name of the remote machine to which this <code>EndPoint</code> is connected, or <code>null</code> + * if this <code>EndPoint</code> does not represent a network connection. + */ + public String getRemoteHost(); + + /* ------------------------------------------------------------ */ + /** + * @return The remote port number to which this <code>EndPoint</code> is connected, or <code>0</code> + * if this <code>EndPoint</code> does not represent a network connection. + */ + public int getRemotePort(); + + /* ------------------------------------------------------------ */ + public boolean isBlocking(); + + /* ------------------------------------------------------------ */ + public boolean blockReadable(long millisecs) throws IOException; + + /* ------------------------------------------------------------ */ + public boolean blockWritable(long millisecs) throws IOException; + + /* ------------------------------------------------------------ */ + public boolean isOpen(); + + /* ------------------------------------------------------------ */ + /** + * @return The underlying transport object (socket, channel, etc.) + */ + public Object getTransport(); + + /* ------------------------------------------------------------ */ + /** Flush any buffered output. + * May fail to write all data if endpoint is non-blocking + * @throws EofException If the endpoint is closed or output is shutdown. + */ + public void flush() throws IOException; + + /* ------------------------------------------------------------ */ + /** Get the max idle time in ms. + * <p>The max idle time is the time the endpoint can be idle before + * extraordinary handling takes place. This loosely corresponds to + * the {@link java.net.Socket#getSoTimeout()} for blocking connections, + * but {@link AsyncEndPoint} implementations must use other mechanisms + * to implement the max idle time. + * @return the max idle time in ms or if ms <= 0 implies an infinite timeout + */ + public int getMaxIdleTime(); + + /* ------------------------------------------------------------ */ + /** Set the max idle time. + * @param timeMs the max idle time in MS. Timeout <= 0 implies an infinite timeout + * @throws IOException if the timeout cannot be set. + */ + public void setMaxIdleTime(int timeMs) throws IOException; + + + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/EofException.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,46 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io; + +import java.io.EOFException; + + +/* ------------------------------------------------------------ */ +/** A Jetty specialization of EOFException. + * <p> This is thrown by Jetty to distinguish between EOF received from + * the connection, vs and EOF thrown by some application talking to some other file/socket etc. + * The only difference in handling is that Jetty EOFs are logged less verbosely. + */ +public class EofException extends EOFException +{ + public EofException() + { + } + + public EofException(String reason) + { + super(reason); + } + + public EofException(Throwable th) + { + if (th!=null) + initCause(th); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/NetworkTrafficListener.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,99 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io; + +import java.net.Socket; + +/** + * <p>A listener for raw network traffic within Jetty.</p> + * <p>{@link NetworkTrafficListener}s can be installed in a + * <code>org.eclipse.jetty.server.nio.NetworkTrafficSelectChannelConnector</code>, + * and are notified of the following network traffic events:</p> + * <ul> + * <li>Connection opened, when the server has accepted the connection from a remote client</li> + * <li>Incoming bytes, when the server receives bytes sent from a remote client</li> + * <li>Outgoing bytes, when the server sends bytes to a remote client</li> + * <li>Connection closed, when the server has closed the connection to a remote client</li> + * </ul> + * <p>{@link NetworkTrafficListener}s can be used to log the network traffic viewed by + * a Jetty server (for example logging to filesystem) for activities such as debugging + * or request/response cycles or for replaying request/response cycles to other servers.</p> + */ +public interface NetworkTrafficListener +{ + /** + * <p>Callback method invoked when a connection from a remote client has been accepted.</p> + * <p>The {@code socket} parameter can be used to extract socket address information of + * the remote client.</p> + * + * @param socket the socket associated with the remote client + */ + public void opened(Socket socket); + + /** + * <p>Callback method invoked when bytes sent by a remote client arrived on the server.</p> + * + * @param socket the socket associated with the remote client + * @param bytes the read-only buffer containing the incoming bytes + */ + public void incoming(Socket socket, Buffer bytes); + + /** + * <p>Callback method invoked when bytes are sent to a remote client from the server.</p> + * <p>This method is invoked after the bytes have been actually written to the remote client.</p> + * + * @param socket the socket associated with the remote client + * @param bytes the read-only buffer containing the outgoing bytes + */ + public void outgoing(Socket socket, Buffer bytes); + + /** + * <p>Callback method invoked when a connection to a remote client has been closed.</p> + * <p>The {@code socket} parameter is already closed when this method is called, so it + * cannot be queried for socket address information of the remote client.<br /> + * However, the {@code socket} parameter is the same object passed to {@link #opened(Socket)}, + * so it is possible to map socket information in {@link #opened(Socket)} and retrieve it + * in this method. + * + * @param socket the (closed) socket associated with the remote client + */ + public void closed(Socket socket); + + /** + * <p>A commodity class that implements {@link NetworkTrafficListener} with empty methods.</p> + */ + public static class Empty implements NetworkTrafficListener + { + public void opened(Socket socket) + { + } + + public void incoming(Socket socket, Buffer bytes) + { + } + + public void outgoing(Socket socket, Buffer bytes) + { + } + + public void closed(Socket socket) + { + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/PooledBuffers.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,122 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io; + +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicInteger; + +public class PooledBuffers extends AbstractBuffers +{ + private final Queue<Buffer> _headers; + private final Queue<Buffer> _buffers; + private final Queue<Buffer> _others; + private final AtomicInteger _size = new AtomicInteger(); + private final int _maxSize; + private final boolean _otherHeaders; + private final boolean _otherBuffers; + + /* ------------------------------------------------------------ */ + public PooledBuffers(Buffers.Type headerType, int headerSize, Buffers.Type bufferType, int bufferSize, Buffers.Type otherType,int maxSize) + { + super(headerType,headerSize,bufferType,bufferSize,otherType); + _headers=new ConcurrentLinkedQueue<Buffer>(); + _buffers=new ConcurrentLinkedQueue<Buffer>(); + _others=new ConcurrentLinkedQueue<Buffer>(); + _otherHeaders=headerType==otherType; + _otherBuffers=bufferType==otherType; + _maxSize=maxSize; + } + + /* ------------------------------------------------------------ */ + public Buffer getHeader() + { + Buffer buffer = _headers.poll(); + if (buffer==null) + buffer=newHeader(); + else + _size.decrementAndGet(); + return buffer; + } + + /* ------------------------------------------------------------ */ + public Buffer getBuffer() + { + Buffer buffer = _buffers.poll(); + if (buffer==null) + buffer=newBuffer(); + else + _size.decrementAndGet(); + return buffer; + } + + /* ------------------------------------------------------------ */ + public Buffer getBuffer(int size) + { + if (_otherHeaders && size==getHeaderSize()) + return getHeader(); + if (_otherBuffers && size==getBufferSize()) + return getBuffer(); + + // Look for an other buffer + Buffer buffer = _others.poll(); + + // consume all other buffers until one of the right size is found + while (buffer!=null && buffer.capacity()!=size) + { + _size.decrementAndGet(); + buffer = _others.poll(); + } + + if (buffer==null) + buffer=newBuffer(size); + else + _size.decrementAndGet(); + return buffer; + } + + /* ------------------------------------------------------------ */ + public void returnBuffer(Buffer buffer) + { + buffer.clear(); + if (buffer.isVolatile() || buffer.isImmutable()) + return; + + if (_size.incrementAndGet() > _maxSize) + _size.decrementAndGet(); + else + { + if (isHeader(buffer)) + _headers.add(buffer); + else if (isBuffer(buffer)) + _buffers.add(buffer); + else + _others.add(buffer); + } + } + + public String toString() + { + return String.format("%s [%d/%d@%d,%d/%d@%d,%d/%d@-]", + getClass().getSimpleName(), + _headers.size(),_maxSize,_headerSize, + _buffers.size(),_maxSize,_bufferSize, + _others.size(),_maxSize); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/RuntimeIOException.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,48 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io; + +/* ------------------------------------------------------------ */ +/** + * Subclass of {@link java.lang.RuntimeException} used to signal that there + * was an {@link java.io.IOException} thrown by underlying {@link java.io.Writer} + */ +public class RuntimeIOException extends RuntimeException +{ + public RuntimeIOException() + { + super(); + } + + public RuntimeIOException(String message) + { + super(message); + } + + public RuntimeIOException(Throwable cause) + { + super(cause); + } + + public RuntimeIOException(String message, Throwable cause) + { + super(message,cause); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/SimpleBuffers.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,117 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io; + +/* ------------------------------------------------------------ */ +/** SimpleBuffers. + * Simple implementation of Buffers holder. + * + * + */ +public class SimpleBuffers implements Buffers +{ + final Buffer _header; + final Buffer _buffer; + boolean _headerOut; + boolean _bufferOut; + + /* ------------------------------------------------------------ */ + /** + * + */ + public SimpleBuffers(Buffer header, Buffer buffer) + { + _header=header; + _buffer=buffer; + } + + /* ------------------------------------------------------------ */ + public Buffer getBuffer() + { + synchronized(this) + { + if (_buffer!=null && !_bufferOut) + { + _bufferOut=true; + return _buffer; + } + + if (_buffer!=null && _header!=null && _header.capacity()==_buffer.capacity() && !_headerOut) + { + _headerOut=true; + return _header; + } + + if (_buffer!=null) + return new ByteArrayBuffer(_buffer.capacity()); + return new ByteArrayBuffer(4096); + } + } + + /* ------------------------------------------------------------ */ + public Buffer getHeader() + { + synchronized(this) + { + if (_header!=null && !_headerOut) + { + _headerOut=true; + return _header; + } + + if (_buffer!=null && _header!=null && _header.capacity()==_buffer.capacity() && !_bufferOut) + { + _bufferOut=true; + return _buffer; + } + + if (_header!=null) + return new ByteArrayBuffer(_header.capacity()); + return new ByteArrayBuffer(4096); + } + } + + /* ------------------------------------------------------------ */ + public Buffer getBuffer(int size) + { + synchronized(this) + { + if (_header!=null && _header.capacity()==size) + return getHeader(); + if (_buffer!=null && _buffer.capacity()==size) + return getBuffer(); + return null; + } + } + + /* ------------------------------------------------------------ */ + public void returnBuffer(Buffer buffer) + { + synchronized(this) + { + buffer.clear(); + if (buffer==_header) + _headerOut=false; + if (buffer==_buffer) + _bufferOut=false; + } + } + + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/ThreadLocalBuffers.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,135 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io; + + + +/* ------------------------------------------------------------ */ +/** Abstract Buffer pool. + * simple unbounded pool of buffers for header, request and response sizes. + * + */ +public class ThreadLocalBuffers extends AbstractBuffers +{ + /* ------------------------------------------------------------ */ + private final ThreadLocal<ThreadBuffers> _buffers=new ThreadLocal<ThreadBuffers>() + { + @Override + protected ThreadBuffers initialValue() + { + return new ThreadBuffers(); + } + }; + + /* ------------------------------------------------------------ */ + public ThreadLocalBuffers(Buffers.Type headerType, int headerSize, Buffers.Type bufferType, int bufferSize, Buffers.Type otherType) + { + super(headerType,headerSize,bufferType,bufferSize,otherType); + } + + /* ------------------------------------------------------------ */ + public Buffer getBuffer() + { + ThreadBuffers buffers = _buffers.get(); + if (buffers._buffer!=null) + { + Buffer b=buffers._buffer; + buffers._buffer=null; + return b; + } + + if (buffers._other!=null && isBuffer(buffers._other)) + { + Buffer b=buffers._other; + buffers._other=null; + return b; + } + + return newBuffer(); + } + + /* ------------------------------------------------------------ */ + public Buffer getHeader() + { + ThreadBuffers buffers = _buffers.get(); + if (buffers._header!=null) + { + Buffer b=buffers._header; + buffers._header=null; + return b; + } + + if (buffers._other!=null && isHeader(buffers._other)) + { + Buffer b=buffers._other; + buffers._other=null; + return b; + } + + return newHeader(); + } + + /* ------------------------------------------------------------ */ + public Buffer getBuffer(int size) + { + ThreadBuffers buffers = _buffers.get(); + if (buffers._other!=null && buffers._other.capacity()==size) + { + Buffer b=buffers._other; + buffers._other=null; + return b; + } + + return newBuffer(size); + } + + /* ------------------------------------------------------------ */ + public void returnBuffer(Buffer buffer) + { + buffer.clear(); + if (buffer.isVolatile() || buffer.isImmutable()) + return; + + ThreadBuffers buffers = _buffers.get(); + + if (buffers._header==null && isHeader(buffer)) + buffers._header=buffer; + else if (buffers._buffer==null && isBuffer(buffer)) + buffers._buffer=buffer; + else + buffers._other=buffer; + } + + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + return "{{"+getHeaderSize()+","+getBufferSize()+"}}"; + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + protected static class ThreadBuffers + { + Buffer _buffer; + Buffer _header; + Buffer _other; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/UncheckedIOException.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,48 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io; + +/* ------------------------------------------------------------ */ +/** + * Subclass of {@link java.lang.RuntimeException} used to signal that there + * was an {@link java.io.IOException} thrown by underlying {@link UncheckedPrintWriter} + */ +public class UncheckedIOException extends RuntimeException +{ + public UncheckedIOException() + { + super(); + } + + public UncheckedIOException(String message) + { + super(message); + } + + public UncheckedIOException(Throwable cause) + { + super(cause); + } + + public UncheckedIOException(String message, Throwable cause) + { + super(message,cause); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/UncheckedPrintWriter.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,682 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.Writer; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/* ------------------------------------------------------------ */ +/** + * A wrapper for the {@link java.io.PrintWriter} that re-throws the instances of + * {@link java.io.IOException} thrown by the underlying implementation of + * {@link java.io.Writer} as {@link RuntimeIOException} instances. + */ +public class UncheckedPrintWriter extends PrintWriter +{ + private static final Logger LOG = Log.getLogger(UncheckedPrintWriter.class); + + private boolean _autoFlush = false; + private IOException _ioException; + private boolean _isClosed = false; + + /* ------------------------------------------------------------ */ + /** + * Line separator string. This is the value of the line.separator property + * at the moment that the stream was created. + */ + private String _lineSeparator; + + public UncheckedPrintWriter(Writer out) + { + this(out,false); + } + + /* ------------------------------------------------------------ */ + /** + * Create a new PrintWriter. + * + * @param out + * A character-output stream + * @param autoFlush + * A boolean; if true, the println() methods will flush the + * output buffer + */ + public UncheckedPrintWriter(Writer out, boolean autoFlush) + { + super(out,autoFlush); + this._autoFlush = autoFlush; + this._lineSeparator = System.getProperty("line.separator"); + } + + /* ------------------------------------------------------------ */ + /** + * Create a new PrintWriter, without automatic line flushing, from an + * existing OutputStream. This convenience constructor creates the necessary + * intermediate OutputStreamWriter, which will convert characters into bytes + * using the default character encoding. + * + * @param out + * An output stream + * + * @see java.io.OutputStreamWriter#OutputStreamWriter(java.io.OutputStream) + */ + public UncheckedPrintWriter(OutputStream out) + { + this(out,false); + } + + /* ------------------------------------------------------------ */ + /** + * Create a new PrintWriter from an existing OutputStream. This convenience + * constructor creates the necessary intermediate OutputStreamWriter, which + * will convert characters into bytes using the default character encoding. + * + * @param out + * An output stream + * @param autoFlush + * A boolean; if true, the println() methods will flush the + * output buffer + * + * @see java.io.OutputStreamWriter#OutputStreamWriter(java.io.OutputStream) + */ + public UncheckedPrintWriter(OutputStream out, boolean autoFlush) + { + this(new BufferedWriter(new OutputStreamWriter(out)),autoFlush); + } + + + /* ------------------------------------------------------------ */ + public boolean checkError() + { + return _ioException!=null || super.checkError(); + } + + /* ------------------------------------------------------------ */ + private void setError(Throwable th) + { + + super.setError(); + + if (th instanceof IOException) + _ioException=(IOException)th; + else + { + _ioException=new IOException(String.valueOf(th)); + _ioException.initCause(th); + } + + LOG.debug(th); + } + + + @Override + protected void setError() + { + setError(new IOException()); + } + + /* ------------------------------------------------------------ */ + /** Check to make sure that the stream has not been closed */ + private void isOpen() throws IOException + { + if (_ioException!=null) + throw new RuntimeIOException(_ioException); + + if (_isClosed) + throw new IOException("Stream closed"); + } + + /* ------------------------------------------------------------ */ + /** + * Flush the stream. + */ + @Override + public void flush() + { + try + { + synchronized (lock) + { + isOpen(); + out.flush(); + } + } + catch (IOException ex) + { + setError(ex); + } + } + + /* ------------------------------------------------------------ */ + /** + * Close the stream. + */ + @Override + public void close() + { + try + { + synchronized (lock) + { + out.close(); + _isClosed = true; + } + } + catch (IOException ex) + { + setError(ex); + } + } + + /* ------------------------------------------------------------ */ + /** + * Write a single character. + * + * @param c + * int specifying a character to be written. + */ + @Override + public void write(int c) + { + try + { + synchronized (lock) + { + isOpen(); + out.write(c); + } + } + catch (InterruptedIOException x) + { + Thread.currentThread().interrupt(); + } + catch (IOException ex) + { + setError(ex); + } + } + + /* ------------------------------------------------------------ */ + /** + * Write a portion of an array of characters. + * + * @param buf + * Array of characters + * @param off + * Offset from which to start writing characters + * @param len + * Number of characters to write + */ + @Override + public void write(char buf[], int off, int len) + { + try + { + synchronized (lock) + { + isOpen(); + out.write(buf,off,len); + } + } + catch (InterruptedIOException x) + { + Thread.currentThread().interrupt(); + } + catch (IOException ex) + { + setError(ex); + } + } + + /* ------------------------------------------------------------ */ + /** + * Write an array of characters. This method cannot be inherited from the + * Writer class because it must suppress I/O exceptions. + * + * @param buf + * Array of characters to be written + */ + @Override + public void write(char buf[]) + { + this.write(buf,0,buf.length); + } + + /* ------------------------------------------------------------ */ + /** + * Write a portion of a string. + * + * @param s + * A String + * @param off + * Offset from which to start writing characters + * @param len + * Number of characters to write + */ + @Override + public void write(String s, int off, int len) + { + try + { + synchronized (lock) + { + isOpen(); + out.write(s,off,len); + } + } + catch (InterruptedIOException x) + { + Thread.currentThread().interrupt(); + } + catch (IOException ex) + { + setError(ex); + } + } + + /* ------------------------------------------------------------ */ + /** + * Write a string. This method cannot be inherited from the Writer class + * because it must suppress I/O exceptions. + * + * @param s + * String to be written + */ + @Override + public void write(String s) + { + this.write(s,0,s.length()); + } + + private void newLine() + { + try + { + synchronized (lock) + { + isOpen(); + out.write(_lineSeparator); + if (_autoFlush) + out.flush(); + } + } + catch (InterruptedIOException x) + { + Thread.currentThread().interrupt(); + } + catch (IOException ex) + { + setError(ex); + } + } + + /* ------------------------------------------------------------ */ + /** + * Print a boolean value. The string produced by <code>{@link + * java.lang.String#valueOf(boolean)}</code> is translated into bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the <code>{@link + * #write(int)}</code> method. + * + * @param b + * The <code>boolean</code> to be printed + */ + @Override + public void print(boolean b) + { + this.write(b?"true":"false"); + } + + /* ------------------------------------------------------------ */ + /** + * Print a character. The character is translated into one or more bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the <code>{@link + * #write(int)}</code> method. + * + * @param c + * The <code>char</code> to be printed + */ + @Override + public void print(char c) + { + this.write(c); + } + + /* ------------------------------------------------------------ */ + /** + * Print an integer. The string produced by <code>{@link + * java.lang.String#valueOf(int)}</code> is translated into bytes according + * to the platform's default character encoding, and these bytes are written + * in exactly the manner of the <code>{@link #write(int)}</code> method. + * + * @param i + * The <code>int</code> to be printed + * @see java.lang.Integer#toString(int) + */ + @Override + public void print(int i) + { + this.write(String.valueOf(i)); + } + + /* ------------------------------------------------------------ */ + /** + * Print a long integer. The string produced by <code>{@link + * java.lang.String#valueOf(long)}</code> is translated into bytes according + * to the platform's default character encoding, and these bytes are written + * in exactly the manner of the <code>{@link #write(int)}</code> method. + * + * @param l + * The <code>long</code> to be printed + * @see java.lang.Long#toString(long) + */ + @Override + public void print(long l) + { + this.write(String.valueOf(l)); + } + + /* ------------------------------------------------------------ */ + /** + * Print a floating-point number. The string produced by <code>{@link + * java.lang.String#valueOf(float)}</code> is translated into bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the <code>{@link #write(int)}</code> + * method. + * + * @param f + * The <code>float</code> to be printed + * @see java.lang.Float#toString(float) + */ + @Override + public void print(float f) + { + this.write(String.valueOf(f)); + } + + /* ------------------------------------------------------------ */ + /** + * Print a double-precision floating-point number. The string produced by + * <code>{@link java.lang.String#valueOf(double)}</code> is translated into + * bytes according to the platform's default character encoding, and these + * bytes are written in exactly the manner of the <code>{@link + * #write(int)}</code> method. + * + * @param d + * The <code>double</code> to be printed + * @see java.lang.Double#toString(double) + */ + @Override + public void print(double d) + { + this.write(String.valueOf(d)); + } + + /* ------------------------------------------------------------ */ + /** + * Print an array of characters. The characters are converted into bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the <code>{@link #write(int)}</code> + * method. + * + * @param s + * The array of chars to be printed + * + * @throws NullPointerException + * If <code>s</code> is <code>null</code> + */ + @Override + public void print(char s[]) + { + this.write(s); + } + + /* ------------------------------------------------------------ */ + /** + * Print a string. If the argument is <code>null</code> then the string + * <code>"null"</code> is printed. Otherwise, the string's characters are + * converted into bytes according to the platform's default character + * encoding, and these bytes are written in exactly the manner of the + * <code>{@link #write(int)}</code> method. + * + * @param s + * The <code>String</code> to be printed + */ + @Override + public void print(String s) + { + if (s == null) + { + s = "null"; + } + this.write(s); + } + + /* ------------------------------------------------------------ */ + /** + * Print an object. The string produced by the <code>{@link + * java.lang.String#valueOf(Object)}</code> method is translated into bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the <code>{@link #write(int)}</code> + * method. + * + * @param obj + * The <code>Object</code> to be printed + * @see java.lang.Object#toString() + */ + @Override + public void print(Object obj) + { + this.write(String.valueOf(obj)); + } + + /* ------------------------------------------------------------ */ + /** + * Terminate the current line by writing the line separator string. The line + * separator string is defined by the system property + * <code>line.separator</code>, and is not necessarily a single newline + * character (<code>'\n'</code>). + */ + @Override + public void println() + { + this.newLine(); + } + + /* ------------------------------------------------------------ */ + /** + * Print a boolean value and then terminate the line. This method behaves as + * though it invokes <code>{@link #print(boolean)}</code> and then + * <code>{@link #println()}</code>. + * + * @param x + * the <code>boolean</code> value to be printed + */ + @Override + public void println(boolean x) + { + synchronized (lock) + { + this.print(x); + this.println(); + } + } + + /* ------------------------------------------------------------ */ + /** + * Print a character and then terminate the line. This method behaves as + * though it invokes <code>{@link #print(char)}</code> and then <code>{@link + * #println()}</code>. + * + * @param x + * the <code>char</code> value to be printed + */ + @Override + public void println(char x) + { + synchronized (lock) + { + this.print(x); + this.println(); + } + } + + /* ------------------------------------------------------------ */ + /** + * Print an integer and then terminate the line. This method behaves as + * though it invokes <code>{@link #print(int)}</code> and then <code>{@link + * #println()}</code>. + * + * @param x + * the <code>int</code> value to be printed + */ + @Override + public void println(int x) + { + synchronized (lock) + { + this.print(x); + this.println(); + } + } + + /* ------------------------------------------------------------ */ + /** + * Print a long integer and then terminate the line. This method behaves as + * though it invokes <code>{@link #print(long)}</code> and then + * <code>{@link #println()}</code>. + * + * @param x + * the <code>long</code> value to be printed + */ + @Override + public void println(long x) + { + synchronized (lock) + { + this.print(x); + this.println(); + } + } + + /* ------------------------------------------------------------ */ + /** + * Print a floating-point number and then terminate the line. This method + * behaves as though it invokes <code>{@link #print(float)}</code> and then + * <code>{@link #println()}</code>. + * + * @param x + * the <code>float</code> value to be printed + */ + @Override + public void println(float x) + { + synchronized (lock) + { + this.print(x); + this.println(); + } + } + + /* ------------------------------------------------------------ */ + /** + * Print a double-precision floating-point number and then terminate the + * line. This method behaves as though it invokes <code>{@link + * #print(double)}</code> and then <code>{@link #println()}</code>. + * + * @param x + * the <code>double</code> value to be printed + */ + /* ------------------------------------------------------------ */ + @Override + public void println(double x) + { + synchronized (lock) + { + this.print(x); + this.println(); + } + } + + /* ------------------------------------------------------------ */ + /** + * Print an array of characters and then terminate the line. This method + * behaves as though it invokes <code>{@link #print(char[])}</code> and then + * <code>{@link #println()}</code>. + * + * @param x + * the array of <code>char</code> values to be printed + */ + @Override + public void println(char x[]) + { + synchronized (lock) + { + this.print(x); + this.println(); + } + } + + /* ------------------------------------------------------------ */ + /** + * Print a String and then terminate the line. This method behaves as though + * it invokes <code>{@link #print(String)}</code> and then + * <code>{@link #println()}</code>. + * + * @param x + * the <code>String</code> value to be printed + */ + @Override + public void println(String x) + { + synchronized (lock) + { + this.print(x); + this.println(); + } + } + + /* ------------------------------------------------------------ */ + /** + * Print an Object and then terminate the line. This method behaves as + * though it invokes <code>{@link #print(Object)}</code> and then + * <code>{@link #println()}</code>. + * + * @param x + * the <code>Object</code> value to be printed + */ + @Override + public void println(Object x) + { + synchronized (lock) + { + this.print(x); + this.println(); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/View.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,251 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io; + +/** + * A View on another buffer. Allows operations that do not change the _content or + * indexes of the backing buffer. + * + * + * + */ +public class View extends AbstractBuffer +{ + Buffer _buffer; + + /** + * @param buffer The <code>Buffer</code> on which we are presenting a <code>View</code>. + * @param mark The initial value of the {@link Buffer#markIndex mark index} + * @param get The initial value of the {@link Buffer#getIndex get index} + * @param put The initial value of the {@link Buffer#putIndex put index} + * @param access The access level - one of the constants from {@link Buffer}. + */ + public View(Buffer buffer, int mark, int get, int put,int access) + { + super(READWRITE,!buffer.isImmutable()); + _buffer=buffer.buffer(); + setPutIndex(put); + setGetIndex(get); + setMarkIndex(mark); + _access=access; + } + + public View(Buffer buffer) + { + super(READWRITE,!buffer.isImmutable()); + _buffer=buffer.buffer(); + setPutIndex(buffer.putIndex()); + setGetIndex(buffer.getIndex()); + setMarkIndex(buffer.markIndex()); + _access=buffer.isReadOnly()?READONLY:READWRITE; + } + + public View() + { + super(READWRITE,true); + } + + /** + * Update view to buffer + */ + public void update(Buffer buffer) + { + _access=READWRITE; + _buffer=buffer.buffer(); + setGetIndex(0); + setPutIndex(buffer.putIndex()); + setGetIndex(buffer.getIndex()); + setMarkIndex(buffer.markIndex()); + _access=buffer.isReadOnly()?READONLY:READWRITE; + } + + public void update(int get, int put) + { + int a=_access; + _access=READWRITE; + setGetIndex(0); + setPutIndex(put); + setGetIndex(get); + setMarkIndex(-1); + _access=a; + } + + /** + * @return The {@link Buffer#array()} from the underlying buffer. + */ + public byte[] array() + { + return _buffer.array(); + } + + /** + * @return The {@link Buffer#buffer()} from the underlying buffer. + */ + @Override + public Buffer buffer() + { + return _buffer.buffer(); + } + + /** + * @return The {@link Buffer#capacity} of the underlying buffer. + */ + public int capacity() + { + return _buffer.capacity(); + } + + /** + * + */ + @Override + public void clear() + { + setMarkIndex(-1); + setGetIndex(0); + setPutIndex(_buffer.getIndex()); + setGetIndex(_buffer.getIndex()); + } + + /** + * + */ + @Override + public void compact() + { + // TODO + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) + { + return this==obj ||((obj instanceof Buffer)&& obj.equals(this)) || super.equals(obj); + } + + /** + * @return Whether the underlying buffer is {@link Buffer#isReadOnly read only} + */ + @Override + public boolean isReadOnly() + { + return _buffer.isReadOnly(); + } + + /** + * @return Whether the underlying buffer is {@link Buffer#isVolatile volatile} + */ + @Override + public boolean isVolatile() + { + return true; + } + + /** + * @return The result of calling {@link Buffer#peek(int)} on the underlying buffer + */ + public byte peek(int index) + { + return _buffer.peek(index); + } + + /** + * @return The result of calling {@link Buffer#peek(int, byte[], int, int)} on the underlying buffer + */ + public int peek(int index, byte[] b, int offset, int length) + { + return _buffer.peek(index,b,offset,length); + } + + /** + * @return The result of calling {@link Buffer#peek(int, int)} on the underlying buffer + */ + @Override + public Buffer peek(int index, int length) + { + return _buffer.peek(index, length); + } + + /** + * @param index + * @param src + */ + @Override + public int poke(int index, Buffer src) + { + return _buffer.poke(index,src); + } + + /** + * @param index + * @param b + */ + public void poke(int index, byte b) + { + _buffer.poke(index,b); + } + + /** + * @param index + * @param b + * @param offset + * @param length + */ + @Override + public int poke(int index, byte[] b, int offset, int length) + { + return _buffer.poke(index,b,offset,length); + } + + @Override + public String toString() + { + if (_buffer==null) + return "INVALID"; + return super.toString(); + } + + public static class CaseInsensitive extends View implements Buffer.CaseInsensitve + { + public CaseInsensitive() + { + super(); + } + + public CaseInsensitive(Buffer buffer, int mark, int get, int put, int access) + { + super(buffer,mark,get,put,access); + } + + public CaseInsensitive(Buffer buffer) + { + super(buffer); + } + + @Override + public boolean equals(Object obj) + { + return this==obj ||((obj instanceof Buffer)&&((Buffer)obj).equalsIgnoreCase(this)) || super.equals(obj); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/WriterOutputStream.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,100 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.Writer; + + +/* ------------------------------------------------------------ */ +/** Wrap a Writer as an OutputStream. + * When all you have is a Writer and only an OutputStream will do. + * Try not to use this as it indicates that your design is a dogs + * breakfast (JSP made me write it). + * + */ +public class WriterOutputStream extends OutputStream +{ + protected final Writer _writer; + protected final String _encoding; + private final byte[] _buf=new byte[1]; + + /* ------------------------------------------------------------ */ + public WriterOutputStream(Writer writer, String encoding) + { + _writer=writer; + _encoding=encoding; + } + + /* ------------------------------------------------------------ */ + public WriterOutputStream(Writer writer) + { + _writer=writer; + _encoding=null; + } + + /* ------------------------------------------------------------ */ + @Override + public void close() + throws IOException + { + _writer.close(); + } + + /* ------------------------------------------------------------ */ + @Override + public void flush() + throws IOException + { + _writer.flush(); + } + + /* ------------------------------------------------------------ */ + @Override + public void write(byte[] b) + throws IOException + { + if (_encoding==null) + _writer.write(new String(b)); + else + _writer.write(new String(b,_encoding)); + } + + /* ------------------------------------------------------------ */ + @Override + public void write(byte[] b, int off, int len) + throws IOException + { + if (_encoding==null) + _writer.write(new String(b,off,len)); + else + _writer.write(new String(b,off,len,_encoding)); + } + + /* ------------------------------------------------------------ */ + @Override + public synchronized void write(int b) + throws IOException + { + _buf[0]=(byte)b; + write(_buf); + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/bio/SocketEndPoint.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,286 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io.bio; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; + +import javax.net.ssl.SSLSocket; + +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +public class SocketEndPoint extends StreamEndPoint +{ + private static final Logger LOG = Log.getLogger(SocketEndPoint.class); + + final Socket _socket; + final InetSocketAddress _local; + final InetSocketAddress _remote; + + /* ------------------------------------------------------------ */ + /** + * + */ + public SocketEndPoint(Socket socket) + throws IOException + { + super(socket.getInputStream(),socket.getOutputStream()); + _socket=socket; + _local=(InetSocketAddress)_socket.getLocalSocketAddress(); + _remote=(InetSocketAddress)_socket.getRemoteSocketAddress(); + super.setMaxIdleTime(_socket.getSoTimeout()); + } + + /* ------------------------------------------------------------ */ + /** + * + */ + protected SocketEndPoint(Socket socket, int maxIdleTime) + throws IOException + { + super(socket.getInputStream(),socket.getOutputStream()); + _socket=socket; + _local=(InetSocketAddress)_socket.getLocalSocketAddress(); + _remote=(InetSocketAddress)_socket.getRemoteSocketAddress(); + _socket.setSoTimeout(maxIdleTime>0?maxIdleTime:0); + super.setMaxIdleTime(maxIdleTime); + } + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see org.eclipse.io.BufferIO#isClosed() + */ + @Override + public boolean isOpen() + { + return super.isOpen() && _socket!=null && !_socket.isClosed(); + } + + /* ------------------------------------------------------------ */ + @Override + public boolean isInputShutdown() + { + if (_socket instanceof SSLSocket) + return super.isInputShutdown(); + return _socket.isClosed() || _socket.isInputShutdown(); + } + + /* ------------------------------------------------------------ */ + @Override + public boolean isOutputShutdown() + { + if (_socket instanceof SSLSocket) + return super.isOutputShutdown(); + + return _socket.isClosed() || _socket.isOutputShutdown(); + } + + + /* ------------------------------------------------------------ */ + /* + */ + protected final void shutdownSocketOutput() throws IOException + { + if (!_socket.isClosed()) + { + if (!_socket.isOutputShutdown()) + _socket.shutdownOutput(); + if (_socket.isInputShutdown()) + _socket.close(); + } + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.io.bio.StreamEndPoint#shutdownOutput() + */ + @Override + public void shutdownOutput() throws IOException + { + if (_socket instanceof SSLSocket) + super.shutdownOutput(); + else + shutdownSocketOutput(); + } + + + /* ------------------------------------------------------------ */ + /* + */ + public void shutdownSocketInput() throws IOException + { + if (!_socket.isClosed()) + { + if (!_socket.isInputShutdown()) + _socket.shutdownInput(); + if (_socket.isOutputShutdown()) + _socket.close(); + } + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.io.bio.StreamEndPoint#shutdownOutput() + */ + @Override + public void shutdownInput() throws IOException + { + if (_socket instanceof SSLSocket) + super.shutdownInput(); + else + shutdownSocketInput(); + } + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see org.eclipse.io.BufferIO#close() + */ + @Override + public void close() throws IOException + { + _socket.close(); + _in=null; + _out=null; + } + + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getLocalAddr() + */ + @Override + public String getLocalAddr() + { + if (_local==null || _local.getAddress()==null || _local.getAddress().isAnyLocalAddress()) + return StringUtil.ALL_INTERFACES; + + return _local.getAddress().getHostAddress(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getLocalHost() + */ + @Override + public String getLocalHost() + { + if (_local==null || _local.getAddress()==null || _local.getAddress().isAnyLocalAddress()) + return StringUtil.ALL_INTERFACES; + + return _local.getAddress().getCanonicalHostName(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getLocalPort() + */ + @Override + public int getLocalPort() + { + if (_local==null) + return -1; + return _local.getPort(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getRemoteAddr() + */ + @Override + public String getRemoteAddr() + { + if (_remote==null) + return null; + InetAddress addr = _remote.getAddress(); + return ( addr == null ? null : addr.getHostAddress() ); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getRemoteHost() + */ + @Override + public String getRemoteHost() + { + if (_remote==null) + return null; + return _remote.getAddress().getCanonicalHostName(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getRemotePort() + */ + @Override + public int getRemotePort() + { + if (_remote==null) + return -1; + return _remote.getPort(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getConnection() + */ + @Override + public Object getTransport() + { + return _socket; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.io.bio.StreamEndPoint#setMaxIdleTime(int) + */ + @Override + public void setMaxIdleTime(int timeMs) throws IOException + { + if (timeMs!=getMaxIdleTime()) + _socket.setSoTimeout(timeMs>0?timeMs:0); + super.setMaxIdleTime(timeMs); + } + + + /* ------------------------------------------------------------ */ + @Override + protected void idleExpired() throws IOException + { + try + { + if (!isInputShutdown()) + shutdownInput(); + } + catch(IOException e) + { + LOG.ignore(e); + _socket.close(); + } + } + + @Override + public String toString() + { + return _local + " <--> " + _remote; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/bio/StreamEndPoint.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,325 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io.bio; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.SocketTimeoutException; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.EndPoint; + +public class StreamEndPoint implements EndPoint +{ + InputStream _in; + OutputStream _out; + int _maxIdleTime; + boolean _ishut; + boolean _oshut; + + /** + * + */ + public StreamEndPoint(InputStream in, OutputStream out) + { + _in=in; + _out=out; + } + + public boolean isBlocking() + { + return true; + } + + public boolean blockReadable(long millisecs) throws IOException + { + return true; + } + + public boolean blockWritable(long millisecs) throws IOException + { + return true; + } + + /* + * @see org.eclipse.io.BufferIO#isOpen() + */ + public boolean isOpen() + { + return _in!=null; + } + + /* + * @see org.eclipse.io.BufferIO#isOpen() + */ + public final boolean isClosed() + { + return !isOpen(); + } + + public void shutdownOutput() throws IOException + { + _oshut = true; + if (_ishut && _out!=null) + _out.close(); + } + + public boolean isInputShutdown() + { + return _ishut; + } + + public void shutdownInput() throws IOException + { + _ishut = true; + if (_oshut&&_in!=null) + _in.close(); + } + + public boolean isOutputShutdown() + { + return _oshut; + } + + /* + * @see org.eclipse.io.BufferIO#close() + */ + public void close() throws IOException + { + if (_in!=null) + _in.close(); + _in=null; + if (_out!=null) + _out.close(); + _out=null; + } + + protected void idleExpired() throws IOException + { + if (_in!=null) + _in.close(); + } + + /* (non-Javadoc) + * @see org.eclipse.io.BufferIO#fill(org.eclipse.io.Buffer) + */ + public int fill(Buffer buffer) throws IOException + { + if (_ishut) + return -1; + if (_in==null) + return 0; + + int space=buffer.space(); + if (space<=0) + { + if (buffer.hasContent()) + return 0; + throw new IOException("FULL"); + } + + try + { + int filled=buffer.readFrom(_in, space); + if (filled<0) + shutdownInput(); + return filled; + } + catch(SocketTimeoutException e) + { + idleExpired(); + return -1; + } + } + + /* (non-Javadoc) + * @see org.eclipse.io.BufferIO#flush(org.eclipse.io.Buffer) + */ + public int flush(Buffer buffer) throws IOException + { + if (_oshut) + return -1; + if (_out==null) + return 0; + int length=buffer.length(); + if (length>0) + buffer.writeTo(_out); + if (!buffer.isImmutable()) + buffer.clear(); + return length; + } + + /* (non-Javadoc) + * @see org.eclipse.io.BufferIO#flush(org.eclipse.io.Buffer, org.eclipse.io.Buffer, org.eclipse.io.Buffer) + */ + public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException + { + int len=0; + + if (header!=null) + { + int tw=header.length(); + if (tw>0) + { + int f=flush(header); + len=f; + if (f<tw) + return len; + } + } + + if (buffer!=null) + { + int tw=buffer.length(); + if (tw>0) + { + int f=flush(buffer); + if (f<0) + return len>0?len:f; + len+=f; + if (f<tw) + return len; + } + } + + if (trailer!=null) + { + int tw=trailer.length(); + if (tw>0) + { + int f=flush(trailer); + if (f<0) + return len>0?len:f; + len+=f; + } + } + return len; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getLocalAddr() + */ + public String getLocalAddr() + { + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getLocalHost() + */ + public String getLocalHost() + { + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getLocalPort() + */ + public int getLocalPort() + { + return 0; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getRemoteAddr() + */ + public String getRemoteAddr() + { + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getRemoteHost() + */ + public String getRemoteHost() + { + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getRemotePort() + */ + public int getRemotePort() + { + return 0; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getConnection() + */ + public Object getTransport() + { + return null; + } + + /* ------------------------------------------------------------ */ + public InputStream getInputStream() + { + return _in; + } + + /* ------------------------------------------------------------ */ + public void setInputStream(InputStream in) + { + _in=in; + } + + /* ------------------------------------------------------------ */ + public OutputStream getOutputStream() + { + return _out; + } + + /* ------------------------------------------------------------ */ + public void setOutputStream(OutputStream out) + { + _out=out; + } + + + /* ------------------------------------------------------------ */ + public void flush() + throws IOException + { + if (_out != null) + _out.flush(); + } + + /* ------------------------------------------------------------ */ + public int getMaxIdleTime() + { + return _maxIdleTime; + } + + /* ------------------------------------------------------------ */ + public void setMaxIdleTime(int timeMs) throws IOException + { + _maxIdleTime=timeMs; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/bio/StringEndPoint.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,94 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io.bio; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; + +import org.eclipse.jetty.util.StringUtil; + +/** + * + * + * To change the template for this generated type comment go to + * Window - Preferences - Java - Code Generation - Code and Comments + */ +public class StringEndPoint extends StreamEndPoint +{ + String _encoding=StringUtil.__UTF8; + ByteArrayInputStream _bin = new ByteArrayInputStream(new byte[0]); + ByteArrayOutputStream _bout = new ByteArrayOutputStream(); + + public StringEndPoint() + { + super(null,null); + _in=_bin; + _out=_bout; + } + + public StringEndPoint(String encoding) + { + this(); + if (encoding!=null) + _encoding=encoding; + } + + public void setInput(String s) + { + try + { + byte[] bytes = s.getBytes(_encoding); + _bin=new ByteArrayInputStream(bytes); + _in=_bin; + _bout = new ByteArrayOutputStream(); + _out=_bout; + _ishut=false; + _oshut=false; + } + catch(Exception e) + { + throw new IllegalStateException(e.toString()); + } + } + + public String getOutput() + { + try + { + String s = new String(_bout.toByteArray(),_encoding); + _bout.reset(); + return s; + } + catch(final Exception e) + { + throw new IllegalStateException(_encoding) + { + {initCause(e);} + }; + } + } + + /** + * @return <code>true</code> if there are bytes remaining to be read from the encoded input + */ + public boolean hasMore() + { + return _bin.available()>0; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/nio/AsyncConnection.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,28 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io.nio; + +import java.io.IOException; + +import org.eclipse.jetty.io.Connection; + +public interface AsyncConnection extends Connection +{ + void onInputShutdown() throws IOException; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/nio/ChannelEndPoint.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,509 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io.nio; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketException; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; +import java.nio.channels.GatheringByteChannel; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SocketChannel; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + * Channel End Point. + * <p>Holds the channel and socket for an NIO endpoint. + * + */ +public class ChannelEndPoint implements EndPoint +{ + private static final Logger LOG = Log.getLogger(ChannelEndPoint.class); + + protected final ByteChannel _channel; + protected final ByteBuffer[] _gather2=new ByteBuffer[2]; + protected final Socket _socket; + protected final InetSocketAddress _local; + protected final InetSocketAddress _remote; + protected volatile int _maxIdleTime; + private volatile boolean _ishut; + private volatile boolean _oshut; + + public ChannelEndPoint(ByteChannel channel) throws IOException + { + super(); + this._channel = channel; + _socket=(channel instanceof SocketChannel)?((SocketChannel)channel).socket():null; + if (_socket!=null) + { + _local=(InetSocketAddress)_socket.getLocalSocketAddress(); + _remote=(InetSocketAddress)_socket.getRemoteSocketAddress(); + _maxIdleTime=_socket.getSoTimeout(); + } + else + { + _local=_remote=null; + } + } + + protected ChannelEndPoint(ByteChannel channel, int maxIdleTime) throws IOException + { + this._channel = channel; + _maxIdleTime=maxIdleTime; + _socket=(channel instanceof SocketChannel)?((SocketChannel)channel).socket():null; + if (_socket!=null) + { + _local=(InetSocketAddress)_socket.getLocalSocketAddress(); + _remote=(InetSocketAddress)_socket.getRemoteSocketAddress(); + _socket.setSoTimeout(_maxIdleTime); + } + else + { + _local=_remote=null; + } + } + + public boolean isBlocking() + { + return !(_channel instanceof SelectableChannel) || ((SelectableChannel)_channel).isBlocking(); + } + + public boolean blockReadable(long millisecs) throws IOException + { + return true; + } + + public boolean blockWritable(long millisecs) throws IOException + { + return true; + } + + /* + * @see org.eclipse.io.EndPoint#isOpen() + */ + public boolean isOpen() + { + return _channel.isOpen(); + } + + /** Shutdown the channel Input. + * Cannot be overridden. To override, see {@link #shutdownInput()} + * @throws IOException + */ + protected final void shutdownChannelInput() throws IOException + { + LOG.debug("ishut {}", this); + _ishut = true; + if (_channel.isOpen()) + { + if (_socket != null) + { + try + { + if (!_socket.isInputShutdown()) + { + _socket.shutdownInput(); + } + } + catch (SocketException e) + { + LOG.debug(e.toString()); + LOG.ignore(e); + } + finally + { + if (_oshut) + { + close(); + } + } + } + } + } + + /* (non-Javadoc) + * @see org.eclipse.io.EndPoint#close() + */ + public void shutdownInput() throws IOException + { + shutdownChannelInput(); + } + + protected final void shutdownChannelOutput() throws IOException + { + LOG.debug("oshut {}",this); + _oshut = true; + if (_channel.isOpen()) + { + if (_socket != null) + { + try + { + if (!_socket.isOutputShutdown()) + { + _socket.shutdownOutput(); + } + } + catch (SocketException e) + { + LOG.debug(e.toString()); + LOG.ignore(e); + } + finally + { + if (_ishut) + { + close(); + } + } + } + } + } + + /* (non-Javadoc) + * @see org.eclipse.io.EndPoint#close() + */ + public void shutdownOutput() throws IOException + { + shutdownChannelOutput(); + } + + public boolean isOutputShutdown() + { + return _oshut || !_channel.isOpen() || _socket != null && _socket.isOutputShutdown(); + } + + public boolean isInputShutdown() + { + return _ishut || !_channel.isOpen() || _socket != null && _socket.isInputShutdown(); + } + + /* (non-Javadoc) + * @see org.eclipse.io.EndPoint#close() + */ + public void close() throws IOException + { + LOG.debug("close {}",this); + _channel.close(); + } + + /* (non-Javadoc) + * @see org.eclipse.io.EndPoint#fill(org.eclipse.io.Buffer) + */ + public int fill(Buffer buffer) throws IOException + { + if (_ishut) + return -1; + Buffer buf = buffer.buffer(); + int len=0; + if (buf instanceof NIOBuffer) + { + final NIOBuffer nbuf = (NIOBuffer)buf; + final ByteBuffer bbuf=nbuf.getByteBuffer(); + + //noinspection SynchronizationOnLocalVariableOrMethodParameter + try + { + synchronized(bbuf) + { + try + { + bbuf.position(buffer.putIndex()); + len=_channel.read(bbuf); + } + finally + { + buffer.setPutIndex(bbuf.position()); + bbuf.position(0); + } + } + + if (len<0 && isOpen()) + { + if (!isInputShutdown()) + shutdownInput(); + if (isOutputShutdown()) + _channel.close(); + } + } + catch (IOException x) + { + LOG.debug("Exception while filling", x); + try + { + if (_channel.isOpen()) + _channel.close(); + } + catch (Exception xx) + { + LOG.ignore(xx); + } + + if (len>0) + throw x; + len=-1; + } + } + else + { + throw new IOException("Not Implemented"); + } + + return len; + } + + /* (non-Javadoc) + * @see org.eclipse.io.EndPoint#flush(org.eclipse.io.Buffer) + */ + public int flush(Buffer buffer) throws IOException + { + Buffer buf = buffer.buffer(); + int len=0; + if (buf instanceof NIOBuffer) + { + final NIOBuffer nbuf = (NIOBuffer)buf; + final ByteBuffer bbuf=nbuf.getByteBuffer().asReadOnlyBuffer(); + try + { + bbuf.position(buffer.getIndex()); + bbuf.limit(buffer.putIndex()); + len=_channel.write(bbuf); + } + finally + { + if (len>0) + buffer.skip(len); + } + } + else if (buf instanceof RandomAccessFileBuffer) + { + len = ((RandomAccessFileBuffer)buf).writeTo(_channel,buffer.getIndex(),buffer.length()); + if (len>0) + buffer.skip(len); + } + else if (buffer.array()!=null) + { + ByteBuffer b = ByteBuffer.wrap(buffer.array(), buffer.getIndex(), buffer.length()); + len=_channel.write(b); + if (len>0) + buffer.skip(len); + } + else + { + throw new IOException("Not Implemented"); + } + return len; + } + + /* (non-Javadoc) + * @see org.eclipse.io.EndPoint#flush(org.eclipse.io.Buffer, org.eclipse.io.Buffer, org.eclipse.io.Buffer) + */ + public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException + { + int length=0; + + Buffer buf0 = header==null?null:header.buffer(); + Buffer buf1 = buffer==null?null:buffer.buffer(); + + if (_channel instanceof GatheringByteChannel && + header!=null && header.length()!=0 && buf0 instanceof NIOBuffer && + buffer!=null && buffer.length()!=0 && buf1 instanceof NIOBuffer) + { + length = gatheringFlush(header,((NIOBuffer)buf0).getByteBuffer(),buffer,((NIOBuffer)buf1).getByteBuffer()); + } + else + { + // flush header + if (header!=null && header.length()>0) + length=flush(header); + + // flush buffer + if ((header==null || header.length()==0) && + buffer!=null && buffer.length()>0) + length+=flush(buffer); + + // flush trailer + if ((header==null || header.length()==0) && + (buffer==null || buffer.length()==0) && + trailer!=null && trailer.length()>0) + length+=flush(trailer); + } + + return length; + } + + protected int gatheringFlush(Buffer header, ByteBuffer bbuf0, Buffer buffer, ByteBuffer bbuf1) throws IOException + { + int length; + + synchronized(this) + { + // Adjust position indexs of buf0 and buf1 + bbuf0=bbuf0.asReadOnlyBuffer(); + bbuf0.position(header.getIndex()); + bbuf0.limit(header.putIndex()); + bbuf1=bbuf1.asReadOnlyBuffer(); + bbuf1.position(buffer.getIndex()); + bbuf1.limit(buffer.putIndex()); + + _gather2[0]=bbuf0; + _gather2[1]=bbuf1; + + // do the gathering write. + length=(int)((GatheringByteChannel)_channel).write(_gather2); + + int hl=header.length(); + if (length>hl) + { + header.clear(); + buffer.skip(length-hl); + } + else if (length>0) + { + header.skip(length); + } + } + return length; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the channel. + */ + public ByteChannel getChannel() + { + return _channel; + } + + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getLocalAddr() + */ + public String getLocalAddr() + { + if (_socket==null) + return null; + if (_local==null || _local.getAddress()==null || _local.getAddress().isAnyLocalAddress()) + return StringUtil.ALL_INTERFACES; + return _local.getAddress().getHostAddress(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getLocalHost() + */ + public String getLocalHost() + { + if (_socket==null) + return null; + if (_local==null || _local.getAddress()==null || _local.getAddress().isAnyLocalAddress()) + return StringUtil.ALL_INTERFACES; + return _local.getAddress().getCanonicalHostName(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getLocalPort() + */ + public int getLocalPort() + { + if (_socket==null) + return 0; + if (_local==null) + return -1; + return _local.getPort(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getRemoteAddr() + */ + public String getRemoteAddr() + { + if (_socket==null) + return null; + if (_remote==null) + return null; + return _remote.getAddress().getHostAddress(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getRemoteHost() + */ + public String getRemoteHost() + { + if (_socket==null) + return null; + if (_remote==null) + return null; + return _remote.getAddress().getCanonicalHostName(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getRemotePort() + */ + public int getRemotePort() + { + if (_socket==null) + return 0; + return _remote==null?-1:_remote.getPort(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.EndPoint#getConnection() + */ + public Object getTransport() + { + return _channel; + } + + /* ------------------------------------------------------------ */ + public void flush() + throws IOException + { + } + + /* ------------------------------------------------------------ */ + public int getMaxIdleTime() + { + return _maxIdleTime; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.io.bio.StreamEndPoint#setMaxIdleTime(int) + */ + public void setMaxIdleTime(int timeMs) throws IOException + { + if (_socket!=null && timeMs!=_maxIdleTime) + _socket.setSoTimeout(timeMs>0?timeMs:0); + _maxIdleTime=timeMs; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/nio/DirectNIOBuffer.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,354 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io.nio; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; + +import org.eclipse.jetty.io.AbstractBuffer; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/* ------------------------------------------------------------------------------- */ +/** + * + * + */ +public class DirectNIOBuffer extends AbstractBuffer implements NIOBuffer +{ + private static final Logger LOG = Log.getLogger(DirectNIOBuffer.class); + + protected final ByteBuffer _buf; + private ReadableByteChannel _in; + private InputStream _inStream; + private WritableByteChannel _out; + private OutputStream _outStream; + + public DirectNIOBuffer(int size) + { + super(READWRITE,NON_VOLATILE); + _buf = ByteBuffer.allocateDirect(size); + _buf.position(0); + _buf.limit(_buf.capacity()); + } + + public DirectNIOBuffer(ByteBuffer buffer,boolean immutable) + { + super(immutable?IMMUTABLE:READWRITE,NON_VOLATILE); + if (!buffer.isDirect()) + throw new IllegalArgumentException(); + _buf = buffer; + setGetIndex(buffer.position()); + setPutIndex(buffer.limit()); + } + + /** + * @param file + */ + public DirectNIOBuffer(File file) throws IOException + { + super(READONLY,NON_VOLATILE); + FileInputStream fis = null; + FileChannel fc = null; + try + { + fis = new FileInputStream(file); + fc = fis.getChannel(); + _buf = fc.map(FileChannel.MapMode.READ_ONLY, 0, file.length()); + setGetIndex(0); + setPutIndex((int)file.length()); + _access=IMMUTABLE; + } + finally + { + if (fc != null) try {fc.close();} catch (IOException e){LOG.ignore(e);} + IO.close(fis); + } + } + + /* ------------------------------------------------------------ */ + public boolean isDirect() + { + return true; + } + + /* ------------------------------------------------------------ */ + public byte[] array() + { + return null; + } + + /* ------------------------------------------------------------ */ + public int capacity() + { + return _buf.capacity(); + } + + /* ------------------------------------------------------------ */ + public byte peek(int position) + { + return _buf.get(position); + } + + public int peek(int index, byte[] b, int offset, int length) + { + int l = length; + if (index+l > capacity()) + { + l=capacity()-index; + if (l==0) + return -1; + } + + if (l < 0) + return -1; + try + { + _buf.position(index); + _buf.get(b,offset,l); + } + finally + { + _buf.position(0); + } + + return l; + } + + public void poke(int index, byte b) + { + if (isReadOnly()) throw new IllegalStateException(__READONLY); + if (index < 0) throw new IllegalArgumentException("index<0: " + index + "<0"); + if (index > capacity()) + throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity()); + _buf.put(index,b); + } + + @Override + public int poke(int index, Buffer src) + { + if (isReadOnly()) throw new IllegalStateException(__READONLY); + + byte[] array=src.array(); + if (array!=null) + { + return poke(index,array,src.getIndex(),src.length()); + } + else + { + Buffer src_buf=src.buffer(); + if (src_buf instanceof DirectNIOBuffer) + { + ByteBuffer src_bytebuf = ((DirectNIOBuffer)src_buf)._buf; + if (src_bytebuf==_buf) + src_bytebuf=_buf.duplicate(); + try + { + _buf.position(index); + int space = _buf.remaining(); + + int length=src.length(); + if (length>space) + length=space; + + src_bytebuf.position(src.getIndex()); + src_bytebuf.limit(src.getIndex()+length); + + _buf.put(src_bytebuf); + return length; + } + finally + { + _buf.position(0); + src_bytebuf.limit(src_bytebuf.capacity()); + src_bytebuf.position(0); + } + } + else + return super.poke(index,src); + } + } + + @Override + public int poke(int index, byte[] b, int offset, int length) + { + if (isReadOnly()) throw new IllegalStateException(__READONLY); + + if (index < 0) throw new IllegalArgumentException("index<0: " + index + "<0"); + + if (index + length > capacity()) + { + length=capacity()-index; + if (length<0) + throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity()); + } + + try + { + _buf.position(index); + + int space=_buf.remaining(); + + if (length>space) + length=space; + if (length>0) + _buf.put(b,offset,length); + return length; + } + finally + { + _buf.position(0); + } + } + + /* ------------------------------------------------------------ */ + public ByteBuffer getByteBuffer() + { + return _buf; + } + + /* ------------------------------------------------------------ */ + @Override + public int readFrom(InputStream in, int max) throws IOException + { + if (_in==null || !_in.isOpen() || in!=_inStream) + { + _in=Channels.newChannel(in); + _inStream=in; + } + + if (max<0 || max>space()) + max=space(); + int p = putIndex(); + + try + { + int len=0, total=0, available=max; + int loop=0; + while (total<max) + { + _buf.position(p); + _buf.limit(p+available); + len=_in.read(_buf); + if (len<0) + { + _in=null; + _inStream=in; + break; + } + else if (len>0) + { + p += len; + total += len; + available -= len; + setPutIndex(p); + loop=0; + } + else if (loop++>1) + break; + if (in.available()<=0) + break; + } + if (len<0 && total==0) + return -1; + return total; + + } + catch(IOException e) + { + _in=null; + _inStream=in; + throw e; + } + finally + { + if (_in!=null && !_in.isOpen()) + { + _in=null; + _inStream=in; + } + _buf.position(0); + _buf.limit(_buf.capacity()); + } + } + + /* ------------------------------------------------------------ */ + @Override + public void writeTo(OutputStream out) throws IOException + { + if (_out==null || !_out.isOpen() || out!=_outStream) + { + _out=Channels.newChannel(out); + _outStream=out; + } + + synchronized (_buf) + { + try + { + int loop=0; + while(hasContent() && _out.isOpen()) + { + _buf.position(getIndex()); + _buf.limit(putIndex()); + int len=_out.write(_buf); + if (len<0) + break; + else if (len>0) + { + skip(len); + loop=0; + } + else if (loop++>1) + break; + } + + } + catch(IOException e) + { + _out=null; + _outStream=null; + throw e; + } + finally + { + if (_out!=null && !_out.isOpen()) + { + _out=null; + _outStream=null; + } + _buf.position(0); + _buf.limit(_buf.capacity()); + } + } + } + + + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/nio/IndirectNIOBuffer.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,62 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io.nio; + +import java.nio.ByteBuffer; + +import org.eclipse.jetty.io.ByteArrayBuffer; + +public class IndirectNIOBuffer extends ByteArrayBuffer implements NIOBuffer +{ + protected final ByteBuffer _buf; + + /* ------------------------------------------------------------ */ + public IndirectNIOBuffer(int size) + { + super(size,READWRITE,NON_VOLATILE); + _buf = ByteBuffer.wrap(_bytes); + _buf.position(0); + _buf.limit(_buf.capacity()); + } + + /* ------------------------------------------------------------ */ + public IndirectNIOBuffer(ByteBuffer buffer,boolean immutable) + { + super(buffer.array(),0,0, immutable?IMMUTABLE:READWRITE,NON_VOLATILE); + if (buffer.isDirect()) + throw new IllegalArgumentException(); + _buf = buffer; + _get=buffer.position(); + _put=buffer.limit(); + buffer.position(0); + buffer.limit(buffer.capacity()); + } + + /* ------------------------------------------------------------ */ + public ByteBuffer getByteBuffer() + { + return _buf; + } + + /* ------------------------------------------------------------ */ + public boolean isDirect() + { + return false; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/nio/NIOBuffer.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,37 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io.nio; + +import java.nio.ByteBuffer; + +import org.eclipse.jetty.io.Buffer; + +/* ------------------------------------------------------------------------------- */ +/** + * + * + */ +public interface NIOBuffer extends Buffer +{ + /* ------------------------------------------------------------ */ + public ByteBuffer getByteBuffer(); + + /* ------------------------------------------------------------ */ + public boolean isDirect(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/nio/NetworkTrafficSelectChannelEndPoint.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,148 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io.nio; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.util.List; + +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.NetworkTrafficListener; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +public class NetworkTrafficSelectChannelEndPoint extends SelectChannelEndPoint +{ + private static final Logger LOG = Log.getLogger(NetworkTrafficSelectChannelEndPoint.class); + + private final List<NetworkTrafficListener> listeners; + + public NetworkTrafficSelectChannelEndPoint(SocketChannel channel, SelectorManager.SelectSet selectSet, SelectionKey key, int maxIdleTime, List<NetworkTrafficListener> listeners) throws IOException + { + super(channel, selectSet, key, maxIdleTime); + this.listeners = listeners; + } + + @Override + public int fill(Buffer buffer) throws IOException + { + int read = super.fill(buffer); + notifyIncoming(buffer, read); + return read; + } + + @Override + public int flush(Buffer buffer) throws IOException + { + int position = buffer.getIndex(); + int written = super.flush(buffer); + notifyOutgoing(buffer, position, written); + return written; + } + + @Override + protected int gatheringFlush(Buffer header, ByteBuffer bbuf0, Buffer buffer, ByteBuffer bbuf1) throws IOException + { + int headerPosition = header.getIndex(); + int headerLength = header.length(); + int bufferPosition = buffer.getIndex(); + int written = super.gatheringFlush(header, bbuf0, buffer,bbuf1); + notifyOutgoing(header, headerPosition, written > headerLength ? headerLength : written); + notifyOutgoing(buffer, bufferPosition, written > headerLength ? written - headerLength : 0); + return written; + } + + public void notifyOpened() + { + if (listeners != null && !listeners.isEmpty()) + { + for (NetworkTrafficListener listener : listeners) + { + try + { + listener.opened(_socket); + } + catch (Exception x) + { + LOG.warn(x); + } + } + } + } + + public void notifyIncoming(Buffer buffer, int read) + { + if (listeners != null && !listeners.isEmpty() && read > 0) + { + for (NetworkTrafficListener listener : listeners) + { + try + { + Buffer view = buffer.asReadOnlyBuffer(); + listener.incoming(_socket, view); + } + catch (Exception x) + { + LOG.warn(x); + } + } + } + } + + public void notifyOutgoing(Buffer buffer, int position, int written) + { + if (listeners != null && !listeners.isEmpty() && written > 0) + { + for (NetworkTrafficListener listener : listeners) + { + try + { + Buffer view = buffer.asReadOnlyBuffer(); + view.setGetIndex(position); + view.setPutIndex(position + written); + listener.outgoing(_socket, view); + } + catch (Exception x) + { + LOG.warn(x); + } + } + } + } + + public void notifyClosed() + { + if (listeners != null && !listeners.isEmpty()) + { + for (NetworkTrafficListener listener : listeners) + { + try + { + listener.closed(_socket); + } + catch (Exception x) + { + LOG.warn(x); + } + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/nio/RandomAccessFileBuffer.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,196 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io.nio; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; +import java.nio.channels.WritableByteChannel; + +import org.eclipse.jetty.io.AbstractBuffer; +import org.eclipse.jetty.io.Buffer; + +public class RandomAccessFileBuffer extends AbstractBuffer implements Buffer +{ + final RandomAccessFile _file; + final FileChannel _channel; + final int _capacity; + + public RandomAccessFileBuffer(File file) + throws FileNotFoundException + { + super(READWRITE,true); + assert file.length()<=Integer.MAX_VALUE; + _file = new RandomAccessFile(file,"rw"); + _channel=_file.getChannel(); + _capacity=Integer.MAX_VALUE; + setGetIndex(0); + setPutIndex((int)file.length()); + } + + public RandomAccessFileBuffer(File file,int capacity) + throws FileNotFoundException + { + super(READWRITE,true); + assert capacity>=file.length(); + assert file.length()<=Integer.MAX_VALUE; + _capacity=capacity; + _file = new RandomAccessFile(file,"rw"); + _channel=_file.getChannel(); + setGetIndex(0); + setPutIndex((int)file.length()); + } + + public RandomAccessFileBuffer(File file,int capacity,int access) + throws FileNotFoundException + { + super(access,true); + assert capacity>=file.length(); + assert file.length()<=Integer.MAX_VALUE; + _capacity=capacity; + _file = new RandomAccessFile(file,access==READWRITE?"rw":"r"); + _channel=_file.getChannel(); + setGetIndex(0); + setPutIndex((int)file.length()); + } + + public byte[] array() + { + return null; + } + + public int capacity() + { + return _capacity; + } + + @Override + public void clear() + { + try + { + synchronized (_file) + { + super.clear(); + _file.setLength(0); + } + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + + + @Override + public byte peek() + { + synchronized (_file) + { + try + { + if (_get!=_file.getFilePointer()) + _file.seek(_get); + return _file.readByte(); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + } + + public byte peek(int index) + { + synchronized (_file) + { + try + { + _file.seek(index); + return _file.readByte(); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + } + + public int peek(int index, byte[] b, int offset, int length) + { + synchronized (_file) + { + try + { + _file.seek(index); + return _file.read(b,offset,length); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + } + + public void poke(int index, byte b) + { + synchronized (_file) + { + try + { + _file.seek(index); + _file.writeByte(b); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + } + + @Override + public int poke(int index, byte[] b, int offset, int length) + { + synchronized (_file) + { + try + { + _file.seek(index); + _file.write(b,offset,length); + return length; + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + } + + public int writeTo(WritableByteChannel channel,int index, int length) + throws IOException + { + synchronized (_file) + { + return (int)_channel.transferTo(index,length,channel); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,868 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io.nio; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.util.Locale; + +import org.eclipse.jetty.io.AsyncEndPoint; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.ConnectedEndPoint; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.io.nio.SelectorManager.SelectSet; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.thread.Timeout.Task; + +/* ------------------------------------------------------------ */ +/** + * An Endpoint that can be scheduled by {@link SelectorManager}. + */ +public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPoint, ConnectedEndPoint +{ + public static final Logger LOG=Log.getLogger("org.eclipse.jetty.io.nio"); + + private final boolean WORK_AROUND_JVM_BUG_6346658 = System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("win"); + private final SelectorManager.SelectSet _selectSet; + private final SelectorManager _manager; + private SelectionKey _key; + private final Runnable _handler = new Runnable() + { + public void run() { handle(); } + }; + + /** The desired value for {@link SelectionKey#interestOps()} */ + private int _interestOps; + + /** + * The connection instance is the handler for any IO activity on the endpoint. + * There is a different type of connection for HTTP, AJP, WebSocket and + * ProxyConnect. The connection may change for an SCEP as it is upgraded + * from HTTP to proxy connect or websocket. + */ + private volatile AsyncConnection _connection; + + private static final int STATE_NEEDS_DISPATCH=-1; + private static final int STATE_UNDISPATCHED=0; + private static final int STATE_DISPATCHED=1; + private static final int STATE_ASYNC=2; + private int _state; + + private boolean _onIdle; + + /** true if the last write operation succeed and wrote all offered bytes */ + private volatile boolean _writable = true; + + + /** True if a thread has is blocked in {@link #blockReadable(long)} */ + private boolean _readBlocked; + + /** True if a thread has is blocked in {@link #blockWritable(long)} */ + private boolean _writeBlocked; + + /** true if {@link SelectSet#destroyEndPoint(SelectChannelEndPoint)} has not been called */ + private boolean _open; + + private volatile long _idleTimestamp; + private volatile boolean _checkIdle; + + private boolean _interruptable; + + private boolean _ishut; + + /* ------------------------------------------------------------ */ + public SelectChannelEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key, int maxIdleTime) + throws IOException + { + super(channel, maxIdleTime); + + _manager = selectSet.getManager(); + _selectSet = selectSet; + _state=STATE_UNDISPATCHED; + _onIdle=false; + _open=true; + _key = key; + + setCheckForIdle(true); + } + + /* ------------------------------------------------------------ */ + public SelectionKey getSelectionKey() + { + synchronized (this) + { + return _key; + } + } + + /* ------------------------------------------------------------ */ + public SelectorManager getSelectManager() + { + return _manager; + } + + /* ------------------------------------------------------------ */ + public Connection getConnection() + { + return _connection; + } + + /* ------------------------------------------------------------ */ + public void setConnection(Connection connection) + { + Connection old=_connection; + _connection=(AsyncConnection)connection; + if (old!=null && old!=_connection) + _manager.endPointUpgraded(this,old); + } + + /* ------------------------------------------------------------ */ + public long getIdleTimestamp() + { + return _idleTimestamp; + } + + /* ------------------------------------------------------------ */ + /** Called by selectSet to schedule handling + * + */ + public void schedule() + { + synchronized (this) + { + // If there is no key, then do nothing + if (_key == null || !_key.isValid()) + { + _readBlocked=false; + _writeBlocked=false; + this.notifyAll(); + return; + } + + // If there are threads dispatched reading and writing + if (_readBlocked || _writeBlocked) + { + // assert _dispatched; + if (_readBlocked && _key.isReadable()) + _readBlocked=false; + if (_writeBlocked && _key.isWritable()) + _writeBlocked=false; + + // wake them up is as good as a dispatched. + this.notifyAll(); + + // we are not interested in further selecting + _key.interestOps(0); + if (_state<STATE_DISPATCHED) + updateKey(); + return; + } + + // Remove writeable op + if ((_key.readyOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE && (_key.interestOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) + { + // Remove writeable op + _interestOps = _key.interestOps() & ~SelectionKey.OP_WRITE; + _key.interestOps(_interestOps); + _writable = true; // Once writable is in ops, only removed with dispatch. + } + + // If dispatched, then deregister interest + if (_state>=STATE_DISPATCHED) + _key.interestOps(0); + else + { + // other wise do the dispatch + dispatch(); + if (_state>=STATE_DISPATCHED && !_selectSet.getManager().isDeferringInterestedOps0()) + { + _key.interestOps(0); + } + } + } + } + + /* ------------------------------------------------------------ */ + public void asyncDispatch() + { + synchronized(this) + { + switch(_state) + { + case STATE_NEEDS_DISPATCH: + case STATE_UNDISPATCHED: + dispatch(); + break; + + case STATE_DISPATCHED: + case STATE_ASYNC: + _state=STATE_ASYNC; + break; + } + } + } + + /* ------------------------------------------------------------ */ + public void dispatch() + { + synchronized(this) + { + if (_state<=STATE_UNDISPATCHED) + { + if (_onIdle) + _state = STATE_NEEDS_DISPATCH; + else + { + _state = STATE_DISPATCHED; + boolean dispatched = _manager.dispatch(_handler); + if(!dispatched) + { + _state = STATE_NEEDS_DISPATCH; + LOG.warn("Dispatched Failed! "+this+" to "+_manager); + updateKey(); + } + } + } + } + } + + /* ------------------------------------------------------------ */ + /** + * Called when a dispatched thread is no longer handling the endpoint. + * The selection key operations are updated. + * @return If false is returned, the endpoint has been redispatched and + * thread must keep handling the endpoint. + */ + protected boolean undispatch() + { + synchronized (this) + { + switch(_state) + { + case STATE_ASYNC: + _state=STATE_DISPATCHED; + return false; + + default: + _state=STATE_UNDISPATCHED; + updateKey(); + return true; + } + } + } + + /* ------------------------------------------------------------ */ + public void cancelTimeout(Task task) + { + getSelectSet().cancelTimeout(task); + } + + /* ------------------------------------------------------------ */ + public void scheduleTimeout(Task task, long timeoutMs) + { + getSelectSet().scheduleTimeout(task,timeoutMs); + } + + /* ------------------------------------------------------------ */ + public void setCheckForIdle(boolean check) + { + if (check) + { + _idleTimestamp=System.currentTimeMillis(); + _checkIdle=true; + } + else + _checkIdle=false; + } + + /* ------------------------------------------------------------ */ + public boolean isCheckForIdle() + { + return _checkIdle; + } + + /* ------------------------------------------------------------ */ + protected void notIdle() + { + _idleTimestamp=System.currentTimeMillis(); + } + + /* ------------------------------------------------------------ */ + public void checkIdleTimestamp(long now) + { + if (isCheckForIdle() && _maxIdleTime>0) + { + final long idleForMs=now-_idleTimestamp; + + if (idleForMs>_maxIdleTime) + { + // Don't idle out again until onIdleExpired task completes. + setCheckForIdle(false); + _manager.dispatch(new Runnable() + { + public void run() + { + try + { + onIdleExpired(idleForMs); + } + finally + { + setCheckForIdle(true); + } + } + }); + } + } + } + + /* ------------------------------------------------------------ */ + public void onIdleExpired(long idleForMs) + { + try + { + synchronized (this) + { + _onIdle=true; + } + + _connection.onIdleExpired(idleForMs); + } + finally + { + synchronized (this) + { + _onIdle=false; + if (_state==STATE_NEEDS_DISPATCH) + dispatch(); + } + } + } + + /* ------------------------------------------------------------ */ + @Override + public int fill(Buffer buffer) throws IOException + { + int fill=super.fill(buffer); + if (fill>0) + notIdle(); + return fill; + } + + /* ------------------------------------------------------------ */ + @Override + public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException + { + int l = super.flush(header, buffer, trailer); + + // If there was something to write and it wasn't written, then we are not writable. + if (l==0 && ( header!=null && header.hasContent() || buffer!=null && buffer.hasContent() || trailer!=null && trailer.hasContent())) + { + synchronized (this) + { + _writable=false; + if (_state<STATE_DISPATCHED) + updateKey(); + } + } + else if (l>0) + { + _writable=true; + notIdle(); + } + return l; + } + + /* ------------------------------------------------------------ */ + /* + */ + @Override + public int flush(Buffer buffer) throws IOException + { + int l = super.flush(buffer); + + // If there was something to write and it wasn't written, then we are not writable. + if (l==0 && buffer!=null && buffer.hasContent()) + { + synchronized (this) + { + _writable=false; + if (_state<STATE_DISPATCHED) + updateKey(); + } + } + else if (l>0) + { + _writable=true; + notIdle(); + } + + return l; + } + + /* ------------------------------------------------------------ */ + /* + * Allows thread to block waiting for further events. + */ + @Override + public boolean blockReadable(long timeoutMs) throws IOException + { + synchronized (this) + { + if (isInputShutdown()) + throw new EofException(); + + long now=_selectSet.getNow(); + long end=now+timeoutMs; + boolean check=isCheckForIdle(); + setCheckForIdle(true); + try + { + _readBlocked=true; + while (!isInputShutdown() && _readBlocked) + { + try + { + updateKey(); + this.wait(timeoutMs>0?(end-now):10000); + } + catch (final InterruptedException e) + { + LOG.warn(e); + if (_interruptable) + throw new InterruptedIOException(){{this.initCause(e);}}; + } + finally + { + now=_selectSet.getNow(); + } + + if (_readBlocked && timeoutMs>0 && now>=end) + return false; + } + } + finally + { + _readBlocked=false; + setCheckForIdle(check); + } + } + return true; + } + + /* ------------------------------------------------------------ */ + /* + * Allows thread to block waiting for further events. + */ + @Override + public boolean blockWritable(long timeoutMs) throws IOException + { + synchronized (this) + { + if (isOutputShutdown()) + throw new EofException(); + + long now=_selectSet.getNow(); + long end=now+timeoutMs; + boolean check=isCheckForIdle(); + setCheckForIdle(true); + try + { + _writeBlocked=true; + while (_writeBlocked && !isOutputShutdown()) + { + try + { + updateKey(); + this.wait(timeoutMs>0?(end-now):10000); + } + catch (final InterruptedException e) + { + LOG.warn(e); + if (_interruptable) + throw new InterruptedIOException(){{this.initCause(e);}}; + } + finally + { + now=_selectSet.getNow(); + } + if (_writeBlocked && timeoutMs>0 && now>=end) + return false; + } + } + finally + { + _writeBlocked=false; + setCheckForIdle(check); + } + } + return true; + } + + /* ------------------------------------------------------------ */ + /** Set the interruptable mode of the endpoint. + * If set to false (default), then interrupts are assumed to be spurious + * and blocking operations continue unless the endpoint has been closed. + * If true, then interrupts of blocking operations result in InterruptedIOExceptions + * being thrown. + * @param interupable + */ + public void setInterruptable(boolean interupable) + { + synchronized (this) + { + _interruptable=interupable; + } + } + + /* ------------------------------------------------------------ */ + public boolean isInterruptable() + { + return _interruptable; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.io.AsyncEndPoint#scheduleWrite() + */ + public void scheduleWrite() + { + if (_writable) + LOG.debug("Required scheduleWrite {}",this); + + _writable=false; + updateKey(); + } + + /* ------------------------------------------------------------ */ + public boolean isWritable() + { + return _writable; + } + + /* ------------------------------------------------------------ */ + public boolean hasProgressed() + { + return false; + } + + /* ------------------------------------------------------------ */ + /** + * Updates selection key. Adds operations types to the selection key as needed. No operations + * are removed as this is only done during dispatch. This method records the new key and + * schedules a call to doUpdateKey to do the keyChange + */ + private void updateKey() + { + final boolean changed; + synchronized (this) + { + int current_ops=-1; + if (getChannel().isOpen()) + { + boolean read_interest = _readBlocked || (_state<STATE_DISPATCHED && !_connection.isSuspended()); + boolean write_interest= _writeBlocked || (_state<STATE_DISPATCHED && !_writable); + + _interestOps = + ((!_socket.isInputShutdown() && read_interest ) ? SelectionKey.OP_READ : 0) + | ((!_socket.isOutputShutdown()&& write_interest) ? SelectionKey.OP_WRITE : 0); + try + { + current_ops = ((_key!=null && _key.isValid())?_key.interestOps():-1); + } + catch(Exception e) + { + _key=null; + LOG.ignore(e); + } + } + changed=_interestOps!=current_ops; + } + + if(changed) + { + _selectSet.addChange(this); + _selectSet.wakeup(); + } + } + + + /* ------------------------------------------------------------ */ + /** + * Synchronize the interestOps with the actual key. Call is scheduled by a call to updateKey + */ + void doUpdateKey() + { + synchronized (this) + { + if (getChannel().isOpen()) + { + if (_interestOps>0) + { + if (_key==null || !_key.isValid()) + { + SelectableChannel sc = (SelectableChannel)getChannel(); + if (sc.isRegistered()) + { + updateKey(); + } + else + { + try + { + _key=((SelectableChannel)getChannel()).register(_selectSet.getSelector(),_interestOps,this); + } + catch (Exception e) + { + LOG.ignore(e); + if (_key!=null && _key.isValid()) + { + _key.cancel(); + } + + if (_open) + { + _selectSet.destroyEndPoint(this); + } + _open=false; + _key = null; + } + } + } + else + { + _key.interestOps(_interestOps); + } + } + else + { + if (_key!=null && _key.isValid()) + _key.interestOps(0); + else + _key=null; + } + } + else + { + if (_key!=null && _key.isValid()) + _key.cancel(); + + if (_open) + { + _open=false; + _selectSet.destroyEndPoint(this); + } + _key = null; + } + } + } + + /* ------------------------------------------------------------ */ + /* + */ + protected void handle() + { + boolean dispatched=true; + try + { + while(dispatched) + { + try + { + while(true) + { + final AsyncConnection next = (AsyncConnection)_connection.handle(); + if (next!=_connection) + { + LOG.debug("{} replaced {}",next,_connection); + Connection old=_connection; + _connection=next; + _manager.endPointUpgraded(this,old); + continue; + } + break; + } + } + catch (ClosedChannelException e) + { + LOG.ignore(e); + } + catch (EofException e) + { + LOG.debug("EOF", e); + try{close();} + catch(IOException e2){LOG.ignore(e2);} + } + catch (IOException e) + { + LOG.warn(e.toString()); + try{close();} + catch(IOException e2){LOG.ignore(e2);} + } + catch (Throwable e) + { + LOG.warn("handle failed", e); + try{close();} + catch(IOException e2){LOG.ignore(e2);} + } + finally + { + if (!_ishut && isInputShutdown() && isOpen()) + { + _ishut=true; + try + { + _connection.onInputShutdown(); + } + catch(Throwable x) + { + LOG.warn("onInputShutdown failed", x); + try{close();} + catch(IOException e2){LOG.ignore(e2);} + } + finally + { + updateKey(); + } + } + dispatched=!undispatch(); + } + } + } + finally + { + if (dispatched) + { + dispatched=!undispatch(); + while (dispatched) + { + LOG.warn("SCEP.run() finally DISPATCHED"); + dispatched=!undispatch(); + } + } + } + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.io.nio.ChannelEndPoint#close() + */ + @Override + public void close() throws IOException + { + // On unix systems there is a JVM issue that if you cancel before closing, it can + // cause the selector to block waiting for a channel to close and that channel can + // block waiting for the remote end. But on windows, if you don't cancel before a + // close, then the selector can block anyway! + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=357318 + if (WORK_AROUND_JVM_BUG_6346658) + { + try + { + SelectionKey key = _key; + if (key!=null) + key.cancel(); + } + catch (Throwable e) + { + LOG.ignore(e); + } + } + + try + { + super.close(); + } + catch (IOException e) + { + LOG.ignore(e); + } + finally + { + updateKey(); + } + } + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + // Do NOT use synchronized (this) + // because it's very easy to deadlock when debugging is enabled. + // We do a best effort to print the right toString() and that's it. + SelectionKey key = _key; + String keyString = ""; + if (key != null) + { + if (key.isValid()) + { + if (key.isReadable()) + keyString += "r"; + if (key.isWritable()) + keyString += "w"; + } + else + { + keyString += "!"; + } + } + else + { + keyString += "-"; + } + return String.format("SCEP@%x{l(%s)<->r(%s),s=%d,open=%b,ishut=%b,oshut=%b,rb=%b,wb=%b,w=%b,i=%d%s}-{%s}", + hashCode(), + _socket.getRemoteSocketAddress(), + _socket.getLocalSocketAddress(), + _state, + isOpen(), + isInputShutdown(), + isOutputShutdown(), + _readBlocked, + _writeBlocked, + _writable, + _interestOps, + keyString, + _connection); + } + + /* ------------------------------------------------------------ */ + public SelectSet getSelectSet() + { + return _selectSet; + } + + /* ------------------------------------------------------------ */ + /** + * Don't set the SoTimeout + * @see org.eclipse.jetty.io.nio.ChannelEndPoint#setMaxIdleTime(int) + */ + @Override + public void setMaxIdleTime(int timeMs) throws IOException + { + _maxIdleTime=timeMs; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/nio/SelectorManager.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,1034 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io.nio; + +import java.io.IOException; +import java.nio.channels.CancelledKeyException; +import java.nio.channels.Channel; +import java.nio.channels.ClosedSelectorException; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.io.AsyncEndPoint; +import org.eclipse.jetty.io.ConnectedEndPoint; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.component.AggregateLifeCycle; +import org.eclipse.jetty.util.component.Dumpable; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.thread.Timeout; +import org.eclipse.jetty.util.thread.Timeout.Task; + + +/* ------------------------------------------------------------ */ +/** + * The Selector Manager manages and number of SelectSets to allow + * NIO scheduling to scale to large numbers of connections. + * <p> + */ +public abstract class SelectorManager extends AbstractLifeCycle implements Dumpable +{ + public static final Logger LOG=Log.getLogger("org.eclipse.jetty.io.nio"); + + private static final int __MONITOR_PERIOD=Integer.getInteger("org.eclipse.jetty.io.nio.MONITOR_PERIOD",1000).intValue(); + private static final int __MAX_SELECTS=Integer.getInteger("org.eclipse.jetty.io.nio.MAX_SELECTS",100000).intValue(); + private static final int __BUSY_PAUSE=Integer.getInteger("org.eclipse.jetty.io.nio.BUSY_PAUSE",50).intValue(); + private static final int __IDLE_TICK=Integer.getInteger("org.eclipse.jetty.io.nio.IDLE_TICK",400).intValue(); + + private int _maxIdleTime; + private int _lowResourcesMaxIdleTime; + private long _lowResourcesConnections; + private SelectSet[] _selectSet; + private int _selectSets=1; + private volatile int _set=0; + private boolean _deferringInterestedOps0=true; + private int _selectorPriorityDelta=0; + + /* ------------------------------------------------------------ */ + /** + * @param maxIdleTime The maximum period in milli seconds that a connection may be idle before it is closed. + * @see #setLowResourcesMaxIdleTime(long) + */ + public void setMaxIdleTime(long maxIdleTime) + { + _maxIdleTime=(int)maxIdleTime; + } + + /* ------------------------------------------------------------ */ + /** + * @param selectSets number of select sets to create + */ + public void setSelectSets(int selectSets) + { + long lrc = _lowResourcesConnections * _selectSets; + _selectSets=selectSets; + _lowResourcesConnections=lrc/_selectSets; + } + + /* ------------------------------------------------------------ */ + /** + * @return the max idle time + */ + public long getMaxIdleTime() + { + return _maxIdleTime; + } + + /* ------------------------------------------------------------ */ + /** + * @return the number of select sets in use + */ + public int getSelectSets() + { + return _selectSets; + } + + /* ------------------------------------------------------------ */ + /** + * @param i + * @return The select set + */ + public SelectSet getSelectSet(int i) + { + return _selectSet[i]; + } + + /* ------------------------------------------------------------ */ + /** Register a channel + * @param channel + * @param att Attached Object + */ + public void register(SocketChannel channel, Object att) + { + // The ++ increment here is not atomic, but it does not matter. + // so long as the value changes sometimes, then connections will + // be distributed over the available sets. + + int s=_set++; + if (s<0) + s=-s; + s=s%_selectSets; + SelectSet[] sets=_selectSet; + if (sets!=null) + { + SelectSet set=sets[s]; + set.addChange(channel,att); + set.wakeup(); + } + } + + + /* ------------------------------------------------------------ */ + /** Register a channel + * @param channel + */ + public void register(SocketChannel channel) + { + // The ++ increment here is not atomic, but it does not matter. + // so long as the value changes sometimes, then connections will + // be distributed over the available sets. + + int s=_set++; + if (s<0) + s=-s; + s=s%_selectSets; + SelectSet[] sets=_selectSet; + if (sets!=null) + { + SelectSet set=sets[s]; + set.addChange(channel); + set.wakeup(); + } + } + + /* ------------------------------------------------------------ */ + /** Register a {@link ServerSocketChannel} + * @param acceptChannel + */ + public void register(ServerSocketChannel acceptChannel) + { + int s=_set++; + if (s<0) + s=-s; + s=s%_selectSets; + SelectSet set=_selectSet[s]; + set.addChange(acceptChannel); + set.wakeup(); + } + + /* ------------------------------------------------------------ */ + /** + * @return delta The value to add to the selector thread priority. + */ + public int getSelectorPriorityDelta() + { + return _selectorPriorityDelta; + } + + /* ------------------------------------------------------------ */ + /** Set the selector thread priorty delta. + * @param delta The value to add to the selector thread priority. + */ + public void setSelectorPriorityDelta(int delta) + { + _selectorPriorityDelta=delta; + } + + + /* ------------------------------------------------------------ */ + /** + * @return the lowResourcesConnections + */ + public long getLowResourcesConnections() + { + return _lowResourcesConnections*_selectSets; + } + + /* ------------------------------------------------------------ */ + /** + * Set the number of connections, which if exceeded places this manager in low resources state. + * This is not an exact measure as the connection count is averaged over the select sets. + * @param lowResourcesConnections the number of connections + * @see #setLowResourcesMaxIdleTime(long) + */ + public void setLowResourcesConnections(long lowResourcesConnections) + { + _lowResourcesConnections=(lowResourcesConnections+_selectSets-1)/_selectSets; + } + + /* ------------------------------------------------------------ */ + /** + * @return the lowResourcesMaxIdleTime + */ + public long getLowResourcesMaxIdleTime() + { + return _lowResourcesMaxIdleTime; + } + + /* ------------------------------------------------------------ */ + /** + * @param lowResourcesMaxIdleTime the period in ms that a connection is allowed to be idle when this SelectSet has more connections than {@link #getLowResourcesConnections()} + * @see #setMaxIdleTime(long) + */ + public void setLowResourcesMaxIdleTime(long lowResourcesMaxIdleTime) + { + _lowResourcesMaxIdleTime=(int)lowResourcesMaxIdleTime; + } + + + /* ------------------------------------------------------------------------------- */ + public abstract boolean dispatch(Runnable task); + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see org.eclipse.component.AbstractLifeCycle#doStart() + */ + @Override + protected void doStart() throws Exception + { + _selectSet = new SelectSet[_selectSets]; + for (int i=0;i<_selectSet.length;i++) + _selectSet[i]= new SelectSet(i); + + super.doStart(); + + // start a thread to Select + for (int i=0;i<getSelectSets();i++) + { + final int id=i; + boolean selecting=dispatch(new Runnable() + { + public void run() + { + String name=Thread.currentThread().getName(); + int priority=Thread.currentThread().getPriority(); + try + { + SelectSet[] sets=_selectSet; + if (sets==null) + return; + SelectSet set=sets[id]; + + Thread.currentThread().setName(name+" Selector"+id); + if (getSelectorPriorityDelta()!=0) + Thread.currentThread().setPriority(Thread.currentThread().getPriority()+getSelectorPriorityDelta()); + LOG.debug("Starting {} on {}",Thread.currentThread(),this); + while (isRunning()) + { + try + { + set.doSelect(); + } + catch(IOException e) + { + LOG.ignore(e); + } + catch(Exception e) + { + LOG.warn(e); + } + } + } + finally + { + LOG.debug("Stopped {} on {}",Thread.currentThread(),this); + Thread.currentThread().setName(name); + if (getSelectorPriorityDelta()!=0) + Thread.currentThread().setPriority(priority); + } + } + + }); + + if (!selecting) + throw new IllegalStateException("!Selecting"); + } + } + + + /* ------------------------------------------------------------------------------- */ + @Override + protected void doStop() throws Exception + { + SelectSet[] sets= _selectSet; + _selectSet=null; + if (sets!=null) + { + for (SelectSet set : sets) + { + if (set!=null) + set.stop(); + } + } + super.doStop(); + } + + /* ------------------------------------------------------------ */ + /** + * @param endpoint + */ + protected abstract void endPointClosed(SelectChannelEndPoint endpoint); + + /* ------------------------------------------------------------ */ + /** + * @param endpoint + */ + protected abstract void endPointOpened(SelectChannelEndPoint endpoint); + + /* ------------------------------------------------------------ */ + protected abstract void endPointUpgraded(ConnectedEndPoint endpoint,Connection oldConnection); + + /* ------------------------------------------------------------------------------- */ + public abstract AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endpoint, Object attachment); + + /* ------------------------------------------------------------ */ + /** + * Create a new end point + * @param channel + * @param selectSet + * @param sKey the selection key + * @return the new endpoint {@link SelectChannelEndPoint} + * @throws IOException + */ + protected abstract SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectorManager.SelectSet selectSet, SelectionKey sKey) throws IOException; + + /* ------------------------------------------------------------------------------- */ + protected void connectionFailed(SocketChannel channel,Throwable ex,Object attachment) + { + LOG.warn(ex+","+channel+","+attachment); + LOG.debug(ex); + } + + /* ------------------------------------------------------------ */ + public String dump() + { + return AggregateLifeCycle.dump(this); + } + + /* ------------------------------------------------------------ */ + public void dump(Appendable out, String indent) throws IOException + { + AggregateLifeCycle.dumpObject(out,this); + AggregateLifeCycle.dump(out,indent,TypeUtil.asList(_selectSet)); + } + + + /* ------------------------------------------------------------------------------- */ + /* ------------------------------------------------------------------------------- */ + /* ------------------------------------------------------------------------------- */ + public class SelectSet implements Dumpable + { + private final int _setID; + private final Timeout _timeout; + + private final ConcurrentLinkedQueue<Object> _changes = new ConcurrentLinkedQueue<Object>(); + + private volatile Selector _selector; + + private volatile Thread _selecting; + private int _busySelects; + private long _monitorNext; + private boolean _pausing; + private boolean _paused; + private volatile long _idleTick; + private ConcurrentMap<SelectChannelEndPoint,Object> _endPoints = new ConcurrentHashMap<SelectChannelEndPoint, Object>(); + + /* ------------------------------------------------------------ */ + SelectSet(int acceptorID) throws Exception + { + _setID=acceptorID; + + _idleTick = System.currentTimeMillis(); + _timeout = new Timeout(this); + _timeout.setDuration(0L); + + // create a selector; + _selector = Selector.open(); + _monitorNext=System.currentTimeMillis()+__MONITOR_PERIOD; + } + + /* ------------------------------------------------------------ */ + public void addChange(Object change) + { + _changes.add(change); + } + + /* ------------------------------------------------------------ */ + public void addChange(SelectableChannel channel, Object att) + { + if (att==null) + addChange(channel); + else if (att instanceof EndPoint) + addChange(att); + else + addChange(new ChannelAndAttachment(channel,att)); + } + + /* ------------------------------------------------------------ */ + /** + * Select and dispatch tasks found from changes and the selector. + * + * @throws IOException + */ + public void doSelect() throws IOException + { + try + { + _selecting=Thread.currentThread(); + final Selector selector=_selector; + // Stopped concurrently ? + if (selector == null) + return; + + // Make any key changes required + Object change; + int changes=_changes.size(); + while (changes-->0 && (change=_changes.poll())!=null) + { + Channel ch=null; + SelectionKey key=null; + + try + { + if (change instanceof EndPoint) + { + // Update the operations for a key. + SelectChannelEndPoint endpoint = (SelectChannelEndPoint)change; + ch=endpoint.getChannel(); + endpoint.doUpdateKey(); + } + else if (change instanceof ChannelAndAttachment) + { + // finish accepting/connecting this connection + final ChannelAndAttachment asc = (ChannelAndAttachment)change; + final SelectableChannel channel=asc._channel; + ch=channel; + final Object att = asc._attachment; + + if ((channel instanceof SocketChannel) && ((SocketChannel)channel).isConnected()) + { + key = channel.register(selector,SelectionKey.OP_READ,att); + SelectChannelEndPoint endpoint = createEndPoint((SocketChannel)channel,key); + key.attach(endpoint); + endpoint.schedule(); + } + else if (channel.isOpen()) + { + key = channel.register(selector,SelectionKey.OP_CONNECT,att); + } + } + else if (change instanceof SocketChannel) + { + // Newly registered channel + final SocketChannel channel=(SocketChannel)change; + ch=channel; + key = channel.register(selector,SelectionKey.OP_READ,null); + SelectChannelEndPoint endpoint = createEndPoint(channel,key); + key.attach(endpoint); + endpoint.schedule(); + } + else if (change instanceof ChangeTask) + { + ((Runnable)change).run(); + } + else if (change instanceof Runnable) + { + dispatch((Runnable)change); + } + else + throw new IllegalArgumentException(change.toString()); + } + catch (CancelledKeyException e) + { + LOG.ignore(e); + } + catch (Throwable e) + { + if (isRunning()) + LOG.warn(e); + else + LOG.debug(e); + + try + { + if (ch!=null) + ch.close(); + } + catch(IOException e2) + { + LOG.debug(e2); + } + } + } + + + // Do and instant select to see if any connections can be handled. + int selected=selector.selectNow(); + + long now=System.currentTimeMillis(); + + // if no immediate things to do + if (selected==0 && selector.selectedKeys().isEmpty()) + { + // If we are in pausing mode + if (_pausing) + { + try + { + Thread.sleep(__BUSY_PAUSE); // pause to reduce impact of busy loop + } + catch(InterruptedException e) + { + LOG.ignore(e); + } + now=System.currentTimeMillis(); + } + + // workout how long to wait in select + _timeout.setNow(now); + long to_next_timeout=_timeout.getTimeToNext(); + + long wait = _changes.size()==0?__IDLE_TICK:0L; + if (wait > 0 && to_next_timeout >= 0 && wait > to_next_timeout) + wait = to_next_timeout; + + // If we should wait with a select + if (wait>0) + { + long before=now; + selector.select(wait); + now = System.currentTimeMillis(); + _timeout.setNow(now); + + // If we are monitoring for busy selector + // and this select did not wait more than 1ms + if (__MONITOR_PERIOD>0 && now-before <=1) + { + // count this as a busy select and if there have been too many this monitor cycle + if (++_busySelects>__MAX_SELECTS) + { + // Start injecting pauses + _pausing=true; + + // if this is the first pause + if (!_paused) + { + // Log and dump some status + _paused=true; + LOG.warn("Selector {} is too busy, pausing!",this); + } + } + } + } + } + + // have we been destroyed while sleeping + if (_selector==null || !selector.isOpen()) + return; + + // Look for things to do + for (SelectionKey key: selector.selectedKeys()) + { + SocketChannel channel=null; + + try + { + if (!key.isValid()) + { + key.cancel(); + SelectChannelEndPoint endpoint = (SelectChannelEndPoint)key.attachment(); + if (endpoint != null) + endpoint.doUpdateKey(); + continue; + } + + Object att = key.attachment(); + if (att instanceof SelectChannelEndPoint) + { + if (key.isReadable()||key.isWritable()) + ((SelectChannelEndPoint)att).schedule(); + } + else if (key.isConnectable()) + { + // Complete a connection of a registered channel + channel = (SocketChannel)key.channel(); + boolean connected=false; + try + { + connected=channel.finishConnect(); + } + catch(Exception e) + { + connectionFailed(channel,e,att); + } + finally + { + if (connected) + { + key.interestOps(SelectionKey.OP_READ); + SelectChannelEndPoint endpoint = createEndPoint(channel,key); + key.attach(endpoint); + endpoint.schedule(); + } + else + { + key.cancel(); + channel.close(); + } + } + } + else + { + // Wrap readable registered channel in an endpoint + channel = (SocketChannel)key.channel(); + SelectChannelEndPoint endpoint = createEndPoint(channel,key); + key.attach(endpoint); + if (key.isReadable()) + endpoint.schedule(); + } + key = null; + } + catch (CancelledKeyException e) + { + LOG.ignore(e); + } + catch (Exception e) + { + if (isRunning()) + LOG.warn(e); + else + LOG.ignore(e); + + try + { + if (channel!=null) + channel.close(); + } + catch(IOException e2) + { + LOG.debug(e2); + } + + if (key != null && !(key.channel() instanceof ServerSocketChannel) && key.isValid()) + key.cancel(); + } + } + + // Everything always handled + selector.selectedKeys().clear(); + + now=System.currentTimeMillis(); + _timeout.setNow(now); + Task task = _timeout.expired(); + while (task!=null) + { + if (task instanceof Runnable) + dispatch((Runnable)task); + task = _timeout.expired(); + } + + // Idle tick + if (now-_idleTick>__IDLE_TICK) + { + _idleTick=now; + + final long idle_now=((_lowResourcesConnections>0 && selector.keys().size()>_lowResourcesConnections)) + ?(now+_maxIdleTime-_lowResourcesMaxIdleTime) + :now; + + dispatch(new Runnable() + { + public void run() + { + for (SelectChannelEndPoint endp:_endPoints.keySet()) + { + endp.checkIdleTimestamp(idle_now); + } + } + public String toString() {return "Idle-"+super.toString();} + }); + + } + + // Reset busy select monitor counts + if (__MONITOR_PERIOD>0 && now>_monitorNext) + { + _busySelects=0; + _pausing=false; + _monitorNext=now+__MONITOR_PERIOD; + + } + } + catch (ClosedSelectorException e) + { + if (isRunning()) + LOG.warn(e); + else + LOG.ignore(e); + } + catch (CancelledKeyException e) + { + LOG.ignore(e); + } + finally + { + _selecting=null; + } + } + + + /* ------------------------------------------------------------ */ + private void renewSelector() + { + try + { + synchronized (this) + { + Selector selector=_selector; + if (selector==null) + return; + final Selector new_selector = Selector.open(); + for (SelectionKey k: selector.keys()) + { + if (!k.isValid() || k.interestOps()==0) + continue; + + final SelectableChannel channel = k.channel(); + final Object attachment = k.attachment(); + + if (attachment==null) + addChange(channel); + else + addChange(channel,attachment); + } + _selector.close(); + _selector=new_selector; + } + } + catch(IOException e) + { + throw new RuntimeException("recreating selector",e); + } + } + + /* ------------------------------------------------------------ */ + public SelectorManager getManager() + { + return SelectorManager.this; + } + + /* ------------------------------------------------------------ */ + public long getNow() + { + return _timeout.getNow(); + } + + /* ------------------------------------------------------------ */ + /** + * @param task The task to timeout. If it implements Runnable, then + * expired will be called from a dispatched thread. + * + * @param timeoutMs + */ + public void scheduleTimeout(Timeout.Task task, long timeoutMs) + { + if (!(task instanceof Runnable)) + throw new IllegalArgumentException("!Runnable"); + _timeout.schedule(task, timeoutMs); + } + + /* ------------------------------------------------------------ */ + public void cancelTimeout(Timeout.Task task) + { + task.cancel(); + } + + /* ------------------------------------------------------------ */ + public void wakeup() + { + try + { + Selector selector = _selector; + if (selector!=null) + selector.wakeup(); + } + catch(Exception e) + { + addChange(new ChangeTask() + { + public void run() + { + renewSelector(); + } + }); + + renewSelector(); + } + } + + /* ------------------------------------------------------------ */ + private SelectChannelEndPoint createEndPoint(SocketChannel channel, SelectionKey sKey) throws IOException + { + SelectChannelEndPoint endp = newEndPoint(channel,this,sKey); + LOG.debug("created {}",endp); + endPointOpened(endp); + _endPoints.put(endp,this); + return endp; + } + + /* ------------------------------------------------------------ */ + public void destroyEndPoint(SelectChannelEndPoint endp) + { + LOG.debug("destroyEndPoint {}",endp); + _endPoints.remove(endp); + endPointClosed(endp); + } + + /* ------------------------------------------------------------ */ + Selector getSelector() + { + return _selector; + } + + /* ------------------------------------------------------------ */ + void stop() throws Exception + { + // Spin for a while waiting for selector to complete + // to avoid unneccessary closed channel exceptions + try + { + for (int i=0;i<100 && _selecting!=null;i++) + { + wakeup(); + Thread.sleep(10); + } + } + catch(Exception e) + { + LOG.ignore(e); + } + + // close endpoints and selector + synchronized (this) + { + Selector selector=_selector; + for (SelectionKey key:selector.keys()) + { + if (key==null) + continue; + Object att=key.attachment(); + if (att instanceof EndPoint) + { + EndPoint endpoint = (EndPoint)att; + try + { + endpoint.close(); + } + catch(IOException e) + { + LOG.ignore(e); + } + } + } + + + _timeout.cancelAll(); + try + { + selector=_selector; + if (selector != null) + selector.close(); + } + catch (IOException e) + { + LOG.ignore(e); + } + _selector=null; + } + } + + /* ------------------------------------------------------------ */ + public String dump() + { + return AggregateLifeCycle.dump(this); + } + + /* ------------------------------------------------------------ */ + public void dump(Appendable out, String indent) throws IOException + { + out.append(String.valueOf(this)).append(" id=").append(String.valueOf(_setID)).append("\n"); + + Thread selecting = _selecting; + + Object where = "not selecting"; + StackTraceElement[] trace =selecting==null?null:selecting.getStackTrace(); + if (trace!=null) + { + for (StackTraceElement t:trace) + if (t.getClassName().startsWith("org.eclipse.jetty.")) + { + where=t; + break; + } + } + + Selector selector=_selector; + if (selector!=null) + { + final ArrayList<Object> dump = new ArrayList<Object>(selector.keys().size()*2); + dump.add(where); + + final CountDownLatch latch = new CountDownLatch(1); + + addChange(new ChangeTask() + { + public void run() + { + dumpKeyState(dump); + latch.countDown(); + } + }); + + try + { + latch.await(5,TimeUnit.SECONDS); + } + catch(InterruptedException e) + { + LOG.ignore(e); + } + + AggregateLifeCycle.dump(out,indent,dump); + } + } + + /* ------------------------------------------------------------ */ + public void dumpKeyState(List<Object> dumpto) + { + Selector selector=_selector; + Set<SelectionKey> keys = selector.keys(); + dumpto.add(selector + " keys=" + keys.size()); + for (SelectionKey key: keys) + { + if (key.isValid()) + dumpto.add(key.attachment()+" iOps="+key.interestOps()+" rOps="+key.readyOps()); + else + dumpto.add(key.attachment()+" iOps=-1 rOps=-1"); + } + } + + /* ------------------------------------------------------------ */ + public String toString() + { + Selector selector=_selector; + return String.format("%s keys=%d selected=%d", + super.toString(), + selector != null && selector.isOpen() ? selector.keys().size() : -1, + selector != null && selector.isOpen() ? selector.selectedKeys().size() : -1); + } + } + + /* ------------------------------------------------------------ */ + private static class ChannelAndAttachment + { + final SelectableChannel _channel; + final Object _attachment; + + public ChannelAndAttachment(SelectableChannel channel, Object attachment) + { + super(); + _channel = channel; + _attachment = attachment; + } + } + + /* ------------------------------------------------------------ */ + public boolean isDeferringInterestedOps0() + { + return _deferringInterestedOps0; + } + + /* ------------------------------------------------------------ */ + public void setDeferringInterestedOps0(boolean deferringInterestedOps0) + { + _deferringInterestedOps0 = deferringInterestedOps0; + } + + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + private interface ChangeTask extends Runnable + {} + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/io/nio/SslConnection.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,865 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.io.nio; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicBoolean; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; + +import org.eclipse.jetty.io.AbstractConnection; +import org.eclipse.jetty.io.AsyncEndPoint; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.thread.Timeout.Task; + +/* ------------------------------------------------------------ */ +/** SSL Connection. + * An AysyncConnection that acts as an interceptor between and EndPoint and another + * Connection, that implements TLS encryption using an {@link SSLEngine}. + * <p> + * The connector uses an {@link AsyncEndPoint} (like {@link SelectChannelEndPoint}) as + * it's source/sink of encrypted data. It then provides {@link #getSslEndPoint()} to + * expose a source/sink of unencrypted data to another connection (eg HttpConnection). + */ +public class SslConnection extends AbstractConnection implements AsyncConnection +{ + private final Logger _logger = Log.getLogger("org.eclipse.jetty.io.nio.ssl"); + + private static final NIOBuffer __ZERO_BUFFER=new IndirectNIOBuffer(0); + + private static final ThreadLocal<SslBuffers> __buffers = new ThreadLocal<SslBuffers>(); + private final SSLEngine _engine; + private final SSLSession _session; + private AsyncConnection _connection; + private final SslEndPoint _sslEndPoint; + private int _allocations; + private SslBuffers _buffers; + private NIOBuffer _inbound; + private NIOBuffer _unwrapBuf; + private NIOBuffer _outbound; + private AsyncEndPoint _aEndp; + private boolean _allowRenegotiate=true; + private boolean _handshook; + private boolean _ishut; + private boolean _oshut; + private final AtomicBoolean _progressed = new AtomicBoolean(); + + /* ------------------------------------------------------------ */ + /* this is a half baked buffer pool + */ + private static class SslBuffers + { + final NIOBuffer _in; + final NIOBuffer _out; + final NIOBuffer _unwrap; + + SslBuffers(int packetSize, int appSize) + { + _in=new IndirectNIOBuffer(packetSize); + _out=new IndirectNIOBuffer(packetSize); + _unwrap=new IndirectNIOBuffer(appSize); + } + } + + /* ------------------------------------------------------------ */ + public SslConnection(SSLEngine engine,EndPoint endp) + { + this(engine,endp,System.currentTimeMillis()); + } + + /* ------------------------------------------------------------ */ + public SslConnection(SSLEngine engine,EndPoint endp, long timeStamp) + { + super(endp,timeStamp); + _engine=engine; + _session=_engine.getSession(); + _aEndp=(AsyncEndPoint)endp; + _sslEndPoint = newSslEndPoint(); + } + + /* ------------------------------------------------------------ */ + protected SslEndPoint newSslEndPoint() + { + return new SslEndPoint(); + } + + /* ------------------------------------------------------------ */ + /** + * @return True if SSL re-negotiation is allowed (default false) + */ + public boolean isAllowRenegotiate() + { + return _allowRenegotiate; + } + + /* ------------------------------------------------------------ */ + /** + * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered + * a vulnerability in SSL/TLS with re-negotiation. If your JVM + * does not have CVE-2009-3555 fixed, then re-negotiation should + * not be allowed. CVE-2009-3555 was fixed in Sun java 1.6 with a ban + * of renegotiates in u19 and with RFC5746 in u22. + * + * @param allowRenegotiate + * true if re-negotiation is allowed (default false) + */ + public void setAllowRenegotiate(boolean allowRenegotiate) + { + _allowRenegotiate = allowRenegotiate; + } + + /* ------------------------------------------------------------ */ + private void allocateBuffers() + { + synchronized (this) + { + if (_allocations++==0) + { + if (_buffers==null) + { + _buffers=__buffers.get(); + if (_buffers==null) + _buffers=new SslBuffers(_session.getPacketBufferSize()*2,_session.getApplicationBufferSize()*2); + _inbound=_buffers._in; + _outbound=_buffers._out; + _unwrapBuf=_buffers._unwrap; + __buffers.set(null); + } + } + } + } + + /* ------------------------------------------------------------ */ + private void releaseBuffers() + { + synchronized (this) + { + if (--_allocations==0) + { + if (_buffers!=null && + _inbound.length()==0 && + _outbound.length()==0 && + _unwrapBuf.length()==0) + { + _inbound=null; + _outbound=null; + _unwrapBuf=null; + __buffers.set(_buffers); + _buffers=null; + } + } + } + } + + /* ------------------------------------------------------------ */ + public Connection handle() throws IOException + { + try + { + allocateBuffers(); + + boolean progress=true; + + while (progress) + { + progress=false; + + // If we are handshook let the delegate connection + if (_engine.getHandshakeStatus()!=HandshakeStatus.NOT_HANDSHAKING) + progress=process(null,null); + + // handle the delegate connection + AsyncConnection next = (AsyncConnection)_connection.handle(); + if (next!=_connection && next!=null) + { + _connection=next; + progress=true; + } + + _logger.debug("{} handle {} progress={}", _session, this, progress); + } + } + finally + { + releaseBuffers(); + + if (!_ishut && _sslEndPoint.isInputShutdown() && _sslEndPoint.isOpen()) + { + _ishut=true; + try + { + _connection.onInputShutdown(); + } + catch(Throwable x) + { + _logger.warn("onInputShutdown failed", x); + try{_sslEndPoint.close();} + catch(IOException e2){ + _logger.ignore(e2);} + } + } + } + + return this; + } + + /* ------------------------------------------------------------ */ + public boolean isIdle() + { + return false; + } + + /* ------------------------------------------------------------ */ + public boolean isSuspended() + { + return false; + } + + /* ------------------------------------------------------------ */ + public void onClose() + { + Connection connection = _sslEndPoint.getConnection(); + if (connection != null && connection != this) + connection.onClose(); + } + + /* ------------------------------------------------------------ */ + @Override + public void onIdleExpired(long idleForMs) + { + try + { + _logger.debug("onIdleExpired {}ms on {}",idleForMs,this); + if (_endp.isOutputShutdown()) + _sslEndPoint.close(); + else + _sslEndPoint.shutdownOutput(); + } + catch (IOException e) + { + _logger.warn(e); + super.onIdleExpired(idleForMs); + } + } + + /* ------------------------------------------------------------ */ + public void onInputShutdown() throws IOException + { + + } + + /* ------------------------------------------------------------ */ + private synchronized boolean process(Buffer toFill, Buffer toFlush) throws IOException + { + boolean some_progress=false; + try + { + // We need buffers to progress + allocateBuffers(); + + // if we don't have a buffer to put received data into + if (toFill==null) + { + // use the unwrapbuffer to hold received data. + _unwrapBuf.compact(); + toFill=_unwrapBuf; + } + // Else if the fill buffer is too small for the SSL session + else if (toFill.capacity()<_session.getApplicationBufferSize()) + { + // fill to the temporary unwrapBuffer + boolean progress=process(null,toFlush); + + // if we received any data, + if (_unwrapBuf!=null && _unwrapBuf.hasContent()) + { + // transfer from temp buffer to fill buffer + _unwrapBuf.skip(toFill.put(_unwrapBuf)); + return true; + } + else + // return progress from recursive call + return progress; + } + // Else if there is some temporary data + else if (_unwrapBuf!=null && _unwrapBuf.hasContent()) + { + // transfer from temp buffer to fill buffer + _unwrapBuf.skip(toFill.put(_unwrapBuf)); + return true; + } + + // If we are here, we have a buffer ready into which we can put some read data. + + // If we have no data to flush, flush the empty buffer + if (toFlush==null) + toFlush=__ZERO_BUFFER; + + // While we are making progress processing SSL engine + boolean progress=true; + while (progress) + { + progress=false; + + // Do any real IO + int filled=0,flushed=0; + try + { + // Read any available data + if (_inbound.space()>0 && (filled=_endp.fill(_inbound))>0) + progress = true; + + // flush any output data + if (_outbound.hasContent() && (flushed=_endp.flush(_outbound))>0) + progress = true; + } + catch (IOException e) + { + _endp.close(); + throw e; + } + finally + { + _logger.debug("{} {} {} filled={}/{} flushed={}/{}",_session,this,_engine.getHandshakeStatus(),filled,_inbound.length(),flushed,_outbound.length()); + } + + // handle the current hand share status + switch(_engine.getHandshakeStatus()) + { + case FINISHED: + throw new IllegalStateException(); + + case NOT_HANDSHAKING: + { + // Try unwrapping some application data + if (toFill.space()>0 && _inbound.hasContent() && unwrap(toFill)) + progress=true; + + // Try wrapping some application data + if (toFlush.hasContent() && _outbound.space()>0 && wrap(toFlush)) + progress=true; + } + break; + + case NEED_TASK: + { + // A task needs to be run, so run it! + Runnable task; + while ((task=_engine.getDelegatedTask())!=null) + { + progress=true; + task.run(); + } + + } + break; + + case NEED_WRAP: + { + // The SSL needs to send some handshake data to the other side + if (_handshook && !_allowRenegotiate) + _endp.close(); + else if (wrap(toFlush)) + progress=true; + } + break; + + case NEED_UNWRAP: + { + // The SSL needs to receive some handshake data from the other side + if (_handshook && !_allowRenegotiate) + _endp.close(); + else if (!_inbound.hasContent()&&filled==-1) + { + // No more input coming + _endp.shutdownInput(); + } + else if (unwrap(toFill)) + progress=true; + } + break; + } + + // pass on ishut/oshut state + if (_endp.isOpen() && _endp.isInputShutdown() && !_inbound.hasContent()) + closeInbound(); + + if (_endp.isOpen() && _engine.isOutboundDone() && !_outbound.hasContent()) + _endp.shutdownOutput(); + + // remember if any progress has been made + some_progress|=progress; + } + + // If we are reading into the temp buffer and it has some content, then we should be dispatched. + if (toFill==_unwrapBuf && _unwrapBuf.hasContent() && !_connection.isSuspended()) + _aEndp.dispatch(); + } + finally + { + releaseBuffers(); + if (some_progress) + _progressed.set(true); + } + return some_progress; + } + + private void closeInbound() + { + try + { + _engine.closeInbound(); + } + catch (SSLException x) + { + _logger.debug(x); + } + } + + private synchronized boolean wrap(final Buffer buffer) throws IOException + { + ByteBuffer bbuf=extractByteBuffer(buffer); + final SSLEngineResult result; + + synchronized(bbuf) + { + _outbound.compact(); + ByteBuffer out_buffer=_outbound.getByteBuffer(); + synchronized(out_buffer) + { + try + { + bbuf.position(buffer.getIndex()); + bbuf.limit(buffer.putIndex()); + out_buffer.position(_outbound.putIndex()); + out_buffer.limit(out_buffer.capacity()); + result=_engine.wrap(bbuf,out_buffer); + if (_logger.isDebugEnabled()) + _logger.debug("{} wrap {} {} consumed={} produced={}", + _session, + result.getStatus(), + result.getHandshakeStatus(), + result.bytesConsumed(), + result.bytesProduced()); + + + buffer.skip(result.bytesConsumed()); + _outbound.setPutIndex(_outbound.putIndex()+result.bytesProduced()); + } + catch(SSLException e) + { + _logger.debug(String.valueOf(_endp), e); + _endp.close(); + throw e; + } + finally + { + out_buffer.position(0); + out_buffer.limit(out_buffer.capacity()); + bbuf.position(0); + bbuf.limit(bbuf.capacity()); + } + } + } + + switch(result.getStatus()) + { + case BUFFER_UNDERFLOW: + throw new IllegalStateException(); + + case BUFFER_OVERFLOW: + break; + + case OK: + if (result.getHandshakeStatus()==HandshakeStatus.FINISHED) + _handshook=true; + break; + + case CLOSED: + _logger.debug("wrap CLOSE {} {}",this,result); + if (result.getHandshakeStatus()==HandshakeStatus.FINISHED) + _endp.close(); + break; + + default: + _logger.debug("{} wrap default {}",_session,result); + throw new IOException(result.toString()); + } + + return result.bytesConsumed()>0 || result.bytesProduced()>0; + } + + private synchronized boolean unwrap(final Buffer buffer) throws IOException + { + if (!_inbound.hasContent()) + return false; + + ByteBuffer bbuf=extractByteBuffer(buffer); + final SSLEngineResult result; + + synchronized(bbuf) + { + ByteBuffer in_buffer=_inbound.getByteBuffer(); + synchronized(in_buffer) + { + try + { + bbuf.position(buffer.putIndex()); + bbuf.limit(buffer.capacity()); + in_buffer.position(_inbound.getIndex()); + in_buffer.limit(_inbound.putIndex()); + + result=_engine.unwrap(in_buffer,bbuf); + if (_logger.isDebugEnabled()) + _logger.debug("{} unwrap {} {} consumed={} produced={}", + _session, + result.getStatus(), + result.getHandshakeStatus(), + result.bytesConsumed(), + result.bytesProduced()); + + _inbound.skip(result.bytesConsumed()); + _inbound.compact(); + buffer.setPutIndex(buffer.putIndex()+result.bytesProduced()); + } + catch(SSLException e) + { + _logger.debug(String.valueOf(_endp), e); + _endp.close(); + throw e; + } + finally + { + in_buffer.position(0); + in_buffer.limit(in_buffer.capacity()); + bbuf.position(0); + bbuf.limit(bbuf.capacity()); + } + } + } + + switch(result.getStatus()) + { + case BUFFER_UNDERFLOW: + if (_endp.isInputShutdown()) + _inbound.clear(); + break; + + case BUFFER_OVERFLOW: + if (_logger.isDebugEnabled()) _logger.debug("{} unwrap {} {}->{}",_session,result.getStatus(),_inbound.toDetailString(),buffer.toDetailString()); + break; + + case OK: + if (result.getHandshakeStatus()==HandshakeStatus.FINISHED) + _handshook=true; + break; + + case CLOSED: + _logger.debug("unwrap CLOSE {} {}",this,result); + if (result.getHandshakeStatus()==HandshakeStatus.FINISHED) + _endp.close(); + break; + + default: + _logger.debug("{} wrap default {}",_session,result); + throw new IOException(result.toString()); + } + + //if (LOG.isDebugEnabled() && result.bytesProduced()>0) + // LOG.debug("{} unwrapped '{}'",_session,buffer); + + return result.bytesConsumed()>0 || result.bytesProduced()>0; + } + + + /* ------------------------------------------------------------ */ + private ByteBuffer extractByteBuffer(Buffer buffer) + { + if (buffer.buffer() instanceof NIOBuffer) + return ((NIOBuffer)buffer.buffer()).getByteBuffer(); + return ByteBuffer.wrap(buffer.array()); + } + + /* ------------------------------------------------------------ */ + public AsyncEndPoint getSslEndPoint() + { + return _sslEndPoint; + } + + /* ------------------------------------------------------------ */ + public String toString() + { + return String.format("%s %s", super.toString(), _sslEndPoint); + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + public class SslEndPoint implements AsyncEndPoint + { + public SSLEngine getSslEngine() + { + return _engine; + } + + public AsyncEndPoint getEndpoint() + { + return _aEndp; + } + + public void shutdownOutput() throws IOException + { + synchronized (SslConnection.this) + { + _logger.debug("{} ssl endp.oshut {}",_session,this); + _engine.closeOutbound(); + _oshut=true; + } + flush(); + } + + public boolean isOutputShutdown() + { + synchronized (SslConnection.this) + { + return _oshut||!isOpen()||_engine.isOutboundDone(); + } + } + + public void shutdownInput() throws IOException + { + _logger.debug("{} ssl endp.ishut!",_session); + // We do not do a closeInput here, as SSL does not support half close. + // isInputShutdown works it out itself from buffer state and underlying endpoint state. + } + + public boolean isInputShutdown() + { + synchronized (SslConnection.this) + { + return _endp.isInputShutdown() && + !(_unwrapBuf!=null&&_unwrapBuf.hasContent()) && + !(_inbound!=null&&_inbound.hasContent()); + } + } + + public void close() throws IOException + { + _logger.debug("{} ssl endp.close",_session); + _endp.close(); + } + + public int fill(Buffer buffer) throws IOException + { + int size=buffer.length(); + process(buffer, null); + + int filled=buffer.length()-size; + + if (filled==0 && isInputShutdown()) + return -1; + return filled; + } + + public int flush(Buffer buffer) throws IOException + { + int size = buffer.length(); + process(null, buffer); + return size-buffer.length(); + } + + public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException + { + if (header!=null && header.hasContent()) + return flush(header); + if (buffer!=null && buffer.hasContent()) + return flush(buffer); + if (trailer!=null && trailer.hasContent()) + return flush(trailer); + return 0; + } + + public boolean blockReadable(long millisecs) throws IOException + { + long now = System.currentTimeMillis(); + long end=millisecs>0?(now+millisecs):Long.MAX_VALUE; + + while (now<end) + { + if (process(null,null)) + break; + _endp.blockReadable(end-now); + now = System.currentTimeMillis(); + } + + return now<end; + } + + public boolean blockWritable(long millisecs) throws IOException + { + return _endp.blockWritable(millisecs); + } + + public boolean isOpen() + { + return _endp.isOpen(); + } + + public Object getTransport() + { + return _endp; + } + + public void flush() throws IOException + { + process(null, null); + } + + public void dispatch() + { + _aEndp.dispatch(); + } + + public void asyncDispatch() + { + _aEndp.asyncDispatch(); + } + + public void scheduleWrite() + { + _aEndp.scheduleWrite(); + } + + public void onIdleExpired(long idleForMs) + { + _aEndp.onIdleExpired(idleForMs); + } + + public void setCheckForIdle(boolean check) + { + _aEndp.setCheckForIdle(check); + } + + public boolean isCheckForIdle() + { + return _aEndp.isCheckForIdle(); + } + + public void scheduleTimeout(Task task, long timeoutMs) + { + _aEndp.scheduleTimeout(task,timeoutMs); + } + + public void cancelTimeout(Task task) + { + _aEndp.cancelTimeout(task); + } + + public boolean isWritable() + { + return _aEndp.isWritable(); + } + + public boolean hasProgressed() + { + return _progressed.getAndSet(false); + } + + public String getLocalAddr() + { + return _aEndp.getLocalAddr(); + } + + public String getLocalHost() + { + return _aEndp.getLocalHost(); + } + + public int getLocalPort() + { + return _aEndp.getLocalPort(); + } + + public String getRemoteAddr() + { + return _aEndp.getRemoteAddr(); + } + + public String getRemoteHost() + { + return _aEndp.getRemoteHost(); + } + + public int getRemotePort() + { + return _aEndp.getRemotePort(); + } + + public boolean isBlocking() + { + return false; + } + + public int getMaxIdleTime() + { + return _aEndp.getMaxIdleTime(); + } + + public void setMaxIdleTime(int timeMs) throws IOException + { + _aEndp.setMaxIdleTime(timeMs); + } + + public Connection getConnection() + { + return _connection; + } + + public void setConnection(Connection connection) + { + _connection=(AsyncConnection)connection; + } + + public String toString() + { + // Do NOT use synchronized (SslConnection.this) + // because it's very easy to deadlock when debugging is enabled. + // We do a best effort to print the right toString() and that's it. + Buffer inbound = _inbound; + Buffer outbound = _outbound; + Buffer unwrap = _unwrapBuf; + int i = inbound == null? -1 : inbound.length(); + int o = outbound == null ? -1 : outbound.length(); + int u = unwrap == null ? -1 : unwrap.length(); + return String.format("SSL %s i/o/u=%d/%d/%d ishut=%b oshut=%b {%s}", + _engine.getHandshakeStatus(), + i, o, u, + _ishut, _oshut, + _connection); + } + + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/AbstractConnector.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,1222 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.util.concurrent.atomic.AtomicLong; + +import javax.servlet.ServletRequest; + +import org.eclipse.jetty.http.HttpBuffers; +import org.eclipse.jetty.http.HttpBuffersImpl; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.HttpSchemes; +import org.eclipse.jetty.io.Buffers; +import org.eclipse.jetty.io.Buffers.Type; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.util.component.AggregateLifeCycle; +import org.eclipse.jetty.util.component.Dumpable; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.statistic.CounterStatistic; +import org.eclipse.jetty.util.statistic.SampleStatistic; +import org.eclipse.jetty.util.thread.ThreadPool; + +/** + * Abstract Connector implementation. This abstract implementation of the Connector interface provides: + * <ul> + * <li>AbstractLifeCycle implementation</li> + * <li>Implementations for connector getters and setters</li> + * <li>Buffer management</li> + * <li>Socket configuration</li> + * <li>Base acceptor thread</li> + * <li>Optional reverse proxy headers checking</li> + * </ul> + */ +public abstract class AbstractConnector extends AggregateLifeCycle implements HttpBuffers, Connector, Dumpable +{ + private static final Logger LOG = Log.getLogger(AbstractConnector.class); + + private String _name; + + private Server _server; + private ThreadPool _threadPool; + private String _host; + private int _port = 0; + private String _integralScheme = HttpSchemes.HTTPS; + private int _integralPort = 0; + private String _confidentialScheme = HttpSchemes.HTTPS; + private int _confidentialPort = 0; + private int _acceptQueueSize = 0; + private int _acceptors = 1; + private int _acceptorPriorityOffset = 0; + private boolean _useDNS; + private boolean _forwarded; + private String _hostHeader; + + private String _forwardedHostHeader = HttpHeaders.X_FORWARDED_HOST; + private String _forwardedServerHeader = HttpHeaders.X_FORWARDED_SERVER; + private String _forwardedForHeader = HttpHeaders.X_FORWARDED_FOR; + private String _forwardedProtoHeader = HttpHeaders.X_FORWARDED_PROTO; + private String _forwardedCipherSuiteHeader; + private String _forwardedSslSessionIdHeader; + private boolean _reuseAddress = true; + + protected int _maxIdleTime = 200000; + protected int _lowResourceMaxIdleTime = -1; + protected int _soLingerTime = -1; + + private transient Thread[] _acceptorThreads; + + private final AtomicLong _statsStartedAt = new AtomicLong(-1L); + + /** connections to server */ + private final CounterStatistic _connectionStats = new CounterStatistic(); + /** requests per connection */ + private final SampleStatistic _requestStats = new SampleStatistic(); + /** duration of a connection */ + private final SampleStatistic _connectionDurationStats = new SampleStatistic(); + + protected final HttpBuffersImpl _buffers = new HttpBuffersImpl(); + + /* ------------------------------------------------------------ */ + /** + */ + public AbstractConnector() + { + addBean(_buffers); + } + + /* ------------------------------------------------------------ */ + /* + */ + public Server getServer() + { + return _server; + } + + /* ------------------------------------------------------------ */ + public void setServer(Server server) + { + _server = server; + } + + /* ------------------------------------------------------------ */ + public ThreadPool getThreadPool() + { + return _threadPool; + } + + /* ------------------------------------------------------------ */ + /** Set the ThreadPool. + * The threadpool passed is added via {@link #addBean(Object)} so that + * it's lifecycle may be managed as a {@link AggregateLifeCycle}. + * @param pool the threadPool to set + */ + public void setThreadPool(ThreadPool pool) + { + removeBean(_threadPool); + _threadPool = pool; + addBean(_threadPool); + } + + /* ------------------------------------------------------------ */ + /** + */ + public void setHost(String host) + { + _host = host; + } + + /* ------------------------------------------------------------ */ + /* + */ + public String getHost() + { + return _host; + } + + /* ------------------------------------------------------------ */ + public void setPort(int port) + { + _port = port; + } + + /* ------------------------------------------------------------ */ + public int getPort() + { + return _port; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the maxIdleTime. + */ + public int getMaxIdleTime() + { + return _maxIdleTime; + } + + /* ------------------------------------------------------------ */ + /** + * Set the maximum Idle time for a connection, which roughly translates to the {@link Socket#setSoTimeout(int)} call, although with NIO implementations + * other mechanisms may be used to implement the timeout. The max idle time is applied: + * <ul> + * <li>When waiting for a new request to be received on a connection</li> + * <li>When reading the headers and content of a request</li> + * <li>When writing the headers and content of a response</li> + * </ul> + * Jetty interprets this value as the maximum time between some progress being made on the connection. So if a single byte is read or written, then the + * timeout (if implemented by jetty) is reset. However, in many instances, the reading/writing is delegated to the JVM, and the semantic is more strictly + * enforced as the maximum time a single read/write operation can take. Note, that as Jetty supports writes of memory mapped file buffers, then a write may + * take many 10s of seconds for large content written to a slow device. + * <p> + * Previously, Jetty supported separate idle timeouts and IO operation timeouts, however the expense of changing the value of soTimeout was significant, so + * these timeouts were merged. With the advent of NIO, it may be possible to again differentiate these values (if there is demand). + * + * @param maxIdleTime + * The maxIdleTime to set. + */ + public void setMaxIdleTime(int maxIdleTime) + { + _maxIdleTime = maxIdleTime; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the maxIdleTime when resources are low. + */ + public int getLowResourcesMaxIdleTime() + { + return _lowResourceMaxIdleTime; + } + + /* ------------------------------------------------------------ */ + /** + * @param maxIdleTime + * The maxIdleTime to set when resources are low. + */ + public void setLowResourcesMaxIdleTime(int maxIdleTime) + { + _lowResourceMaxIdleTime = maxIdleTime; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the maxIdleTime when resources are low. + * @deprecated + */ + @Deprecated + public final int getLowResourceMaxIdleTime() + { + return getLowResourcesMaxIdleTime(); + } + + /* ------------------------------------------------------------ */ + /** + * @param maxIdleTime + * The maxIdleTime to set when resources are low. + * @deprecated + */ + @Deprecated + public final void setLowResourceMaxIdleTime(int maxIdleTime) + { + setLowResourcesMaxIdleTime(maxIdleTime); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the soLingerTime. + */ + public int getSoLingerTime() + { + return _soLingerTime; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the acceptQueueSize. + */ + public int getAcceptQueueSize() + { + return _acceptQueueSize; + } + + /* ------------------------------------------------------------ */ + /** + * @param acceptQueueSize + * The acceptQueueSize to set. + */ + public void setAcceptQueueSize(int acceptQueueSize) + { + _acceptQueueSize = acceptQueueSize; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the number of acceptor threads. + */ + public int getAcceptors() + { + return _acceptors; + } + + /* ------------------------------------------------------------ */ + /** + * @param acceptors + * The number of acceptor threads to set. + */ + public void setAcceptors(int acceptors) + { + if (acceptors > 2 * Runtime.getRuntime().availableProcessors()) + LOG.warn("Acceptors should be <=2*availableProcessors: " + this); + _acceptors = acceptors; + } + + /* ------------------------------------------------------------ */ + /** + * @param soLingerTime + * The soLingerTime to set or -1 to disable. + */ + public void setSoLingerTime(int soLingerTime) + { + _soLingerTime = soLingerTime; + } + + /* ------------------------------------------------------------ */ + @Override + protected void doStart() throws Exception + { + if (_server == null) + throw new IllegalStateException("No server"); + + // open listener port + open(); + + if (_threadPool == null) + { + _threadPool = _server.getThreadPool(); + addBean(_threadPool,false); + } + + super.doStart(); + + // Start selector thread + synchronized (this) + { + _acceptorThreads = new Thread[getAcceptors()]; + + for (int i = 0; i < _acceptorThreads.length; i++) + if (!_threadPool.dispatch(new Acceptor(i))) + throw new IllegalStateException("!accepting"); + if (_threadPool.isLowOnThreads()) + LOG.warn("insufficient threads configured for {}",this); + } + + LOG.info("Started {}",this); + } + + /* ------------------------------------------------------------ */ + @Override + protected void doStop() throws Exception + { + try + { + close(); + } + catch (IOException e) + { + LOG.warn(e); + } + + super.doStop(); + + Thread[] acceptors; + synchronized (this) + { + acceptors = _acceptorThreads; + _acceptorThreads = null; + } + if (acceptors != null) + { + for (Thread thread : acceptors) + { + if (thread != null) + thread.interrupt(); + } + } + } + + /* ------------------------------------------------------------ */ + public void join() throws InterruptedException + { + Thread[] threads; + synchronized(this) + { + threads=_acceptorThreads; + } + if (threads != null) + for (Thread thread : threads) + if (thread != null) + thread.join(); + } + + /* ------------------------------------------------------------ */ + protected void configure(Socket socket) throws IOException + { + try + { + socket.setTcpNoDelay(true); + if (_soLingerTime >= 0) + socket.setSoLinger(true,_soLingerTime / 1000); + else + socket.setSoLinger(false,0); + } + catch (Exception e) + { + LOG.ignore(e); + } + } + + /* ------------------------------------------------------------ */ + public void customize(EndPoint endpoint, Request request) throws IOException + { + if (isForwarded()) + checkForwardedHeaders(endpoint,request); + } + + /* ------------------------------------------------------------ */ + protected void checkForwardedHeaders(EndPoint endpoint, Request request) throws IOException + { + HttpFields httpFields = request.getConnection().getRequestFields(); + + // Do SSL first + if (getForwardedCipherSuiteHeader()!=null) + { + String cipher_suite=httpFields.getStringField(getForwardedCipherSuiteHeader()); + if (cipher_suite!=null) + request.setAttribute("javax.servlet.request.cipher_suite",cipher_suite); + } + if (getForwardedSslSessionIdHeader()!=null) + { + String ssl_session_id=httpFields.getStringField(getForwardedSslSessionIdHeader()); + if(ssl_session_id!=null) + { + request.setAttribute("javax.servlet.request.ssl_session_id", ssl_session_id); + request.setScheme(HttpSchemes.HTTPS); + } + } + + // Retrieving headers from the request + String forwardedHost = getLeftMostFieldValue(httpFields,getForwardedHostHeader()); + String forwardedServer = getLeftMostFieldValue(httpFields,getForwardedServerHeader()); + String forwardedFor = getLeftMostFieldValue(httpFields,getForwardedForHeader()); + String forwardedProto = getLeftMostFieldValue(httpFields,getForwardedProtoHeader()); + + if (_hostHeader != null) + { + // Update host header + httpFields.put(HttpHeaders.HOST_BUFFER,_hostHeader); + request.setServerName(null); + request.setServerPort(-1); + request.getServerName(); + } + else if (forwardedHost != null) + { + // Update host header + httpFields.put(HttpHeaders.HOST_BUFFER,forwardedHost); + request.setServerName(null); + request.setServerPort(-1); + request.getServerName(); + } + else if (forwardedServer != null) + { + // Use provided server name + request.setServerName(forwardedServer); + } + + if (forwardedFor != null) + { + request.setRemoteAddr(forwardedFor); + InetAddress inetAddress = null; + + if (_useDNS) + { + try + { + inetAddress = InetAddress.getByName(forwardedFor); + } + catch (UnknownHostException e) + { + LOG.ignore(e); + } + } + + request.setRemoteHost(inetAddress == null?forwardedFor:inetAddress.getHostName()); + } + + if (forwardedProto != null) + { + request.setScheme(forwardedProto); + } + } + + /* ------------------------------------------------------------ */ + protected String getLeftMostFieldValue(HttpFields fields, String header) + { + if (header == null) + return null; + + String headerValue = fields.getStringField(header); + + if (headerValue == null) + return null; + + int commaIndex = headerValue.indexOf(','); + + if (commaIndex == -1) + { + // Single value + return headerValue; + } + + // The left-most value is the farthest downstream client + return headerValue.substring(0,commaIndex); + } + + /* ------------------------------------------------------------ */ + public void persist(EndPoint endpoint) throws IOException + { + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.Connector#getConfidentialPort() + */ + public int getConfidentialPort() + { + return _confidentialPort; + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.Connector#getConfidentialScheme() + */ + public String getConfidentialScheme() + { + return _confidentialScheme; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.Connector#isConfidential(org.eclipse.jetty.server .Request) + */ + public boolean isIntegral(Request request) + { + return false; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.Connector#getConfidentialPort() + */ + public int getIntegralPort() + { + return _integralPort; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.Connector#getIntegralScheme() + */ + public String getIntegralScheme() + { + return _integralScheme; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.Connector#isConfidential(org.eclipse.jetty.server.Request) + */ + public boolean isConfidential(Request request) + { + return _forwarded && request.getScheme().equalsIgnoreCase(HttpSchemes.HTTPS); + } + + /* ------------------------------------------------------------ */ + /** + * @param confidentialPort + * The confidentialPort to set. + */ + public void setConfidentialPort(int confidentialPort) + { + _confidentialPort = confidentialPort; + } + + /* ------------------------------------------------------------ */ + /** + * @param confidentialScheme + * The confidentialScheme to set. + */ + public void setConfidentialScheme(String confidentialScheme) + { + _confidentialScheme = confidentialScheme; + } + + /* ------------------------------------------------------------ */ + /** + * @param integralPort + * The integralPort to set. + */ + public void setIntegralPort(int integralPort) + { + _integralPort = integralPort; + } + + /* ------------------------------------------------------------ */ + /** + * @param integralScheme + * The integralScheme to set. + */ + public void setIntegralScheme(String integralScheme) + { + _integralScheme = integralScheme; + } + + /* ------------------------------------------------------------ */ + protected abstract void accept(int acceptorID) throws IOException, InterruptedException; + + /* ------------------------------------------------------------ */ + public void stopAccept(int acceptorID) throws Exception + { + } + + /* ------------------------------------------------------------ */ + public boolean getResolveNames() + { + return _useDNS; + } + + /* ------------------------------------------------------------ */ + public void setResolveNames(boolean resolve) + { + _useDNS = resolve; + } + + /* ------------------------------------------------------------ */ + /** + * Is reverse proxy handling on? + * + * @return true if this connector is checking the x-forwarded-for/host/server headers + */ + public boolean isForwarded() + { + return _forwarded; + } + + /* ------------------------------------------------------------ */ + /** + * Set reverse proxy handling. If set to true, then the X-Forwarded headers (or the headers set in their place) are looked for to set the request protocol, + * host, server and client ip. + * + * @param check + * true if this connector is checking the x-forwarded-for/host/server headers + * @see #setForwardedForHeader(String) + * @see #setForwardedHostHeader(String) + * @see #setForwardedProtoHeader(String) + * @see #setForwardedServerHeader(String) + */ + public void setForwarded(boolean check) + { + if (check) + LOG.debug("{} is forwarded",this); + _forwarded = check; + } + + /* ------------------------------------------------------------ */ + public String getHostHeader() + { + return _hostHeader; + } + + /* ------------------------------------------------------------ */ + /** + * Set a forced valued for the host header to control what is returned by {@link ServletRequest#getServerName()} and {@link ServletRequest#getServerPort()}. + * This value is only used if {@link #isForwarded()} is true. + * + * @param hostHeader + * The value of the host header to force. + */ + public void setHostHeader(String hostHeader) + { + _hostHeader = hostHeader; + } + + /* ------------------------------------------------------------ */ + /* + * + * @see #setForwarded(boolean) + */ + public String getForwardedHostHeader() + { + return _forwardedHostHeader; + } + + /* ------------------------------------------------------------ */ + /** + * @param forwardedHostHeader + * The header name for forwarded hosts (default x-forwarded-host) + * @see #setForwarded(boolean) + */ + public void setForwardedHostHeader(String forwardedHostHeader) + { + _forwardedHostHeader = forwardedHostHeader; + } + + /* ------------------------------------------------------------ */ + /** + * @return the header name for forwarded server. + * @see #setForwarded(boolean) + */ + public String getForwardedServerHeader() + { + return _forwardedServerHeader; + } + + /* ------------------------------------------------------------ */ + /** + * @param forwardedServerHeader + * The header name for forwarded server (default x-forwarded-server) + * @see #setForwarded(boolean) + */ + public void setForwardedServerHeader(String forwardedServerHeader) + { + _forwardedServerHeader = forwardedServerHeader; + } + + /* ------------------------------------------------------------ */ + /** + * @see #setForwarded(boolean) + */ + public String getForwardedForHeader() + { + return _forwardedForHeader; + } + + /* ------------------------------------------------------------ */ + /** + * @param forwardedRemoteAddressHeader + * The header name for forwarded for (default x-forwarded-for) + * @see #setForwarded(boolean) + */ + public void setForwardedForHeader(String forwardedRemoteAddressHeader) + { + _forwardedForHeader = forwardedRemoteAddressHeader; + } + + /* ------------------------------------------------------------ */ + /** + * Get the forwardedProtoHeader. + * + * @return the forwardedProtoHeader (default X-Forwarded-For) + * @see #setForwarded(boolean) + */ + public String getForwardedProtoHeader() + { + return _forwardedProtoHeader; + } + + /* ------------------------------------------------------------ */ + /** + * Set the forwardedProtoHeader. + * + * @param forwardedProtoHeader + * the forwardedProtoHeader to set (default X-Forwarded-For) + * @see #setForwarded(boolean) + */ + public void setForwardedProtoHeader(String forwardedProtoHeader) + { + _forwardedProtoHeader = forwardedProtoHeader; + } + + /* ------------------------------------------------------------ */ + /** + * @return The header name holding a forwarded cipher suite (default null) + */ + public String getForwardedCipherSuiteHeader() + { + return _forwardedCipherSuiteHeader; + } + + /* ------------------------------------------------------------ */ + /** + * @param forwardedCipherSuite + * The header name holding a forwarded cipher suite (default null) + */ + public void setForwardedCipherSuiteHeader(String forwardedCipherSuite) + { + _forwardedCipherSuiteHeader = forwardedCipherSuite; + } + + /* ------------------------------------------------------------ */ + /** + * @return The header name holding a forwarded SSL Session ID (default null) + */ + public String getForwardedSslSessionIdHeader() + { + return _forwardedSslSessionIdHeader; + } + + /* ------------------------------------------------------------ */ + /** + * @param forwardedSslSessionId + * The header name holding a forwarded SSL Session ID (default null) + */ + public void setForwardedSslSessionIdHeader(String forwardedSslSessionId) + { + _forwardedSslSessionIdHeader = forwardedSslSessionId; + } + + public int getRequestBufferSize() + { + return _buffers.getRequestBufferSize(); + } + + public void setRequestBufferSize(int requestBufferSize) + { + _buffers.setRequestBufferSize(requestBufferSize); + } + + public int getRequestHeaderSize() + { + return _buffers.getRequestHeaderSize(); + } + + public void setRequestHeaderSize(int requestHeaderSize) + { + _buffers.setRequestHeaderSize(requestHeaderSize); + } + + public int getResponseBufferSize() + { + return _buffers.getResponseBufferSize(); + } + + public void setResponseBufferSize(int responseBufferSize) + { + _buffers.setResponseBufferSize(responseBufferSize); + } + + public int getResponseHeaderSize() + { + return _buffers.getResponseHeaderSize(); + } + + public void setResponseHeaderSize(int responseHeaderSize) + { + _buffers.setResponseHeaderSize(responseHeaderSize); + } + + public Type getRequestBufferType() + { + return _buffers.getRequestBufferType(); + } + + public Type getRequestHeaderType() + { + return _buffers.getRequestHeaderType(); + } + + public Type getResponseBufferType() + { + return _buffers.getResponseBufferType(); + } + + public Type getResponseHeaderType() + { + return _buffers.getResponseHeaderType(); + } + + public void setRequestBuffers(Buffers requestBuffers) + { + _buffers.setRequestBuffers(requestBuffers); + } + + public void setResponseBuffers(Buffers responseBuffers) + { + _buffers.setResponseBuffers(responseBuffers); + } + + public Buffers getRequestBuffers() + { + return _buffers.getRequestBuffers(); + } + + public Buffers getResponseBuffers() + { + return _buffers.getResponseBuffers(); + } + + public void setMaxBuffers(int maxBuffers) + { + _buffers.setMaxBuffers(maxBuffers); + } + + public int getMaxBuffers() + { + return _buffers.getMaxBuffers(); + } + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + return String.format("%s@%s:%d", + getClass().getSimpleName(), + getHost()==null?"0.0.0.0":getHost(), + getLocalPort()<=0?getPort():getLocalPort()); + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + private class Acceptor implements Runnable + { + int _acceptor = 0; + + Acceptor(int id) + { + _acceptor = id; + } + + /* ------------------------------------------------------------ */ + public void run() + { + Thread current = Thread.currentThread(); + String name; + synchronized (AbstractConnector.this) + { + if (_acceptorThreads == null) + return; + + _acceptorThreads[_acceptor] = current; + name = _acceptorThreads[_acceptor].getName(); + current.setName(name + " Acceptor" + _acceptor + " " + AbstractConnector.this); + } + int old_priority = current.getPriority(); + + try + { + current.setPriority(old_priority - _acceptorPriorityOffset); + while (isRunning() && getConnection() != null) + { + try + { + accept(_acceptor); + } + catch (EofException e) + { + LOG.ignore(e); + } + catch (IOException e) + { + LOG.ignore(e); + } + catch (InterruptedException x) + { + // Connector has been stopped + LOG.ignore(x); + } + catch (Throwable e) + { + LOG.warn(e); + } + } + } + finally + { + current.setPriority(old_priority); + current.setName(name); + + synchronized (AbstractConnector.this) + { + if (_acceptorThreads != null) + _acceptorThreads[_acceptor] = null; + } + } + } + } + + /* ------------------------------------------------------------ */ + public String getName() + { + if (_name == null) + _name = (getHost() == null?"0.0.0.0":getHost()) + ":" + (getLocalPort() <= 0?getPort():getLocalPort()); + return _name; + } + + /* ------------------------------------------------------------ */ + public void setName(String name) + { + _name = name; + } + + /* ------------------------------------------------------------ */ + /** + * @return Get the number of requests handled by this connector since last call of statsReset(). If setStatsOn(false) then this is undefined. + */ + public int getRequests() + { + return (int)_requestStats.getTotal(); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the connectionsDurationTotal. + */ + public long getConnectionsDurationTotal() + { + return _connectionDurationStats.getTotal(); + } + + /* ------------------------------------------------------------ */ + /** + * @return Number of connections accepted by the server since statsReset() called. Undefined if setStatsOn(false). + */ + public int getConnections() + { + return (int)_connectionStats.getTotal(); + } + + /* ------------------------------------------------------------ */ + /** + * @return Number of connections currently open that were opened since statsReset() called. Undefined if setStatsOn(false). + */ + public int getConnectionsOpen() + { + return (int)_connectionStats.getCurrent(); + } + + /* ------------------------------------------------------------ */ + /** + * @return Maximum number of connections opened simultaneously since statsReset() called. Undefined if setStatsOn(false). + */ + public int getConnectionsOpenMax() + { + return (int)_connectionStats.getMax(); + } + + /* ------------------------------------------------------------ */ + /** + * @return Mean duration in milliseconds of open connections since statsReset() called. Undefined if setStatsOn(false). + */ + public double getConnectionsDurationMean() + { + return _connectionDurationStats.getMean(); + } + + /* ------------------------------------------------------------ */ + /** + * @return Maximum duration in milliseconds of an open connection since statsReset() called. Undefined if setStatsOn(false). + */ + public long getConnectionsDurationMax() + { + return _connectionDurationStats.getMax(); + } + + /* ------------------------------------------------------------ */ + /** + * @return Standard deviation of duration in milliseconds of open connections since statsReset() called. Undefined if setStatsOn(false). + */ + public double getConnectionsDurationStdDev() + { + return _connectionDurationStats.getStdDev(); + } + + /* ------------------------------------------------------------ */ + /** + * @return Mean number of requests per connection since statsReset() called. Undefined if setStatsOn(false). + */ + public double getConnectionsRequestsMean() + { + return _requestStats.getMean(); + } + + /* ------------------------------------------------------------ */ + /** + * @return Maximum number of requests per connection since statsReset() called. Undefined if setStatsOn(false). + */ + public int getConnectionsRequestsMax() + { + return (int)_requestStats.getMax(); + } + + /* ------------------------------------------------------------ */ + /** + * @return Standard deviation of number of requests per connection since statsReset() called. Undefined if setStatsOn(false). + */ + public double getConnectionsRequestsStdDev() + { + return _requestStats.getStdDev(); + } + + /* ------------------------------------------------------------ */ + /** + * Reset statistics. + */ + public void statsReset() + { + updateNotEqual(_statsStartedAt,-1,System.currentTimeMillis()); + + _requestStats.reset(); + _connectionStats.reset(); + _connectionDurationStats.reset(); + } + + /* ------------------------------------------------------------ */ + public void setStatsOn(boolean on) + { + if (on && _statsStartedAt.get() != -1) + return; + + if (LOG.isDebugEnabled()) + LOG.debug("Statistics on = " + on + " for " + this); + + statsReset(); + _statsStartedAt.set(on?System.currentTimeMillis():-1); + } + + /* ------------------------------------------------------------ */ + /** + * @return True if statistics collection is turned on. + */ + public boolean getStatsOn() + { + return _statsStartedAt.get() != -1; + } + + /* ------------------------------------------------------------ */ + /** + * @return Timestamp stats were started at. + */ + public long getStatsOnMs() + { + long start = _statsStartedAt.get(); + + return (start != -1)?(System.currentTimeMillis() - start):0; + } + + /* ------------------------------------------------------------ */ + protected void connectionOpened(Connection connection) + { + if (_statsStartedAt.get() == -1) + return; + + _connectionStats.increment(); + } + + /* ------------------------------------------------------------ */ + protected void connectionUpgraded(Connection oldConnection, Connection newConnection) + { + _requestStats.set((oldConnection instanceof AbstractHttpConnection)?((AbstractHttpConnection)oldConnection).getRequests():0); + } + + /* ------------------------------------------------------------ */ + protected void connectionClosed(Connection connection) + { + connection.onClose(); + + if (_statsStartedAt.get() == -1) + return; + + long duration = System.currentTimeMillis() - connection.getTimeStamp(); + int requests = (connection instanceof AbstractHttpConnection)?((AbstractHttpConnection)connection).getRequests():0; + _requestStats.set(requests); + _connectionStats.decrement(); + _connectionDurationStats.set(duration); + } + + /* ------------------------------------------------------------ */ + /** + * @return the acceptorPriority + */ + public int getAcceptorPriorityOffset() + { + return _acceptorPriorityOffset; + } + + /* ------------------------------------------------------------ */ + /** + * Set the priority offset of the acceptor threads. The priority is adjusted by this amount (default 0) to either favour the acceptance of new threads and + * newly active connections or to favour the handling of already dispatched connections. + * + * @param offset + * the amount to alter the priority of the acceptor threads. + */ + public void setAcceptorPriorityOffset(int offset) + { + _acceptorPriorityOffset = offset; + } + + /* ------------------------------------------------------------ */ + /** + * @return True if the the server socket will be opened in SO_REUSEADDR mode. + */ + public boolean getReuseAddress() + { + return _reuseAddress; + } + + /* ------------------------------------------------------------ */ + /** + * @param reuseAddress + * True if the the server socket will be opened in SO_REUSEADDR mode. + */ + public void setReuseAddress(boolean reuseAddress) + { + _reuseAddress = reuseAddress; + } + + /* ------------------------------------------------------------ */ + public boolean isLowResources() + { + if (_threadPool != null) + return _threadPool.isLowOnThreads(); + return _server.getThreadPool().isLowOnThreads(); + } + + /* ------------------------------------------------------------ */ + private void updateNotEqual(AtomicLong valueHolder, long compare, long value) + { + long oldValue = valueHolder.get(); + while (compare != oldValue) + { + if (valueHolder.compareAndSet(oldValue,value)) + break; + oldValue = valueHolder.get(); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/AbstractHttpConnection.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,1273 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; + +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.continuation.ContinuationThrowable; +import org.eclipse.jetty.http.EncodedHttpURI; +import org.eclipse.jetty.http.Generator; +import org.eclipse.jetty.http.HttpBuffers; +import org.eclipse.jetty.http.HttpContent; +import org.eclipse.jetty.http.HttpException; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpGenerator; +import org.eclipse.jetty.http.HttpHeaderValues; +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.HttpMethods; +import org.eclipse.jetty.http.HttpParser; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.http.HttpVersions; +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.http.Parser; +import org.eclipse.jetty.http.PathMap; +import org.eclipse.jetty.io.AbstractConnection; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.BufferCache.CachedBuffer; +import org.eclipse.jetty.io.Buffers; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.io.RuntimeIOException; +import org.eclipse.jetty.io.UncheckedPrintWriter; +import org.eclipse.jetty.server.handler.ErrorHandler; +import org.eclipse.jetty.server.nio.NIOConnector; +import org.eclipse.jetty.server.ssl.SslConnector; +import org.eclipse.jetty.util.QuotedStringTokenizer; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.resource.Resource; + +/** + * <p>A HttpConnection represents the connection of a HTTP client to the server + * and is created by an instance of a {@link Connector}. It's prime function is + * to associate {@link Request} and {@link Response} instances with a {@link EndPoint}. + * </p> + * <p> + * A connection is also the prime mechanism used by jetty to recycle objects without + * pooling. The {@link Request}, {@link Response}, {@link HttpParser}, {@link HttpGenerator} + * and {@link HttpFields} instances are all recycled for the duraction of + * a connection. Where appropriate, allocated buffers are also kept associated + * with the connection via the parser and/or generator. + * </p> + * <p> + * The connection state is held by 3 separate state machines: The request state, the + * response state and the continuation state. All three state machines must be driven + * to completion for every request, and all three can complete in any order. + * </p> + * <p> + * The HttpConnection support protocol upgrade. If on completion of a request, the + * response code is 101 (switch protocols), then the org.eclipse.jetty.io.Connection + * request attribute is checked to see if there is a new Connection instance. If so, + * the new connection is returned from {@link #handle()} and is used for future + * handling of the underlying connection. Note that for switching protocols that + * don't use 101 responses (eg CONNECT), the response should be sent and then the + * status code changed to 101 before returning from the handler. Implementors + * of new Connection types should be careful to extract any buffered data from + * (HttpParser)http.getParser()).getHeaderBuffer() and + * (HttpParser)http.getParser()).getBodyBuffer() to initialise their new connection. + * </p> + * + */ +public abstract class AbstractHttpConnection extends AbstractConnection +{ + private static final Logger LOG = Log.getLogger(AbstractHttpConnection.class); + + private static final int UNKNOWN = -2; + private static final ThreadLocal<AbstractHttpConnection> __currentConnection = new ThreadLocal<AbstractHttpConnection>(); + + private int _requests; + + protected final Connector _connector; + protected final Server _server; + protected final HttpURI _uri; + + protected final Parser _parser; + protected final HttpFields _requestFields; + protected final Request _request; + protected volatile ServletInputStream _in; + + protected final Generator _generator; + protected final HttpFields _responseFields; + protected final Response _response; + protected volatile Output _out; + protected volatile OutputWriter _writer; + protected volatile PrintWriter _printWriter; + + int _include; + + private Object _associatedObject; // associated object + + private int _version = UNKNOWN; + + private String _charset; + private boolean _expect = false; + private boolean _expect100Continue = false; + private boolean _expect102Processing = false; + private boolean _head = false; + private boolean _host = false; + private boolean _delayedHandling=false; + private boolean _earlyEOF = false; + + /* ------------------------------------------------------------ */ + public static AbstractHttpConnection getCurrentConnection() + { + return __currentConnection.get(); + } + + /* ------------------------------------------------------------ */ + protected static void setCurrentConnection(AbstractHttpConnection connection) + { + __currentConnection.set(connection); + } + + /* ------------------------------------------------------------ */ + public AbstractHttpConnection(Connector connector, EndPoint endpoint, Server server) + { + super(endpoint); + _uri = StringUtil.__UTF8.equals(URIUtil.__CHARSET)?new HttpURI():new EncodedHttpURI(URIUtil.__CHARSET); + _connector = connector; + HttpBuffers ab = (HttpBuffers)_connector; + _parser = newHttpParser(ab.getRequestBuffers(), endpoint, new RequestHandler()); + _requestFields = new HttpFields(); + _responseFields = new HttpFields(); + _request = new Request(this); + _response = new Response(this); + _generator = newHttpGenerator(ab.getResponseBuffers(), endpoint); + _generator.setSendServerVersion(server.getSendServerVersion()); + _server = server; + } + + /* ------------------------------------------------------------ */ + protected AbstractHttpConnection(Connector connector, EndPoint endpoint, Server server, + Parser parser, Generator generator, Request request) + { + super(endpoint); + + _uri = URIUtil.__CHARSET.equals(StringUtil.__UTF8)?new HttpURI():new EncodedHttpURI(URIUtil.__CHARSET); + _connector = connector; + _parser = parser; + _requestFields = new HttpFields(); + _responseFields = new HttpFields(); + _request = request; + _response = new Response(this); + _generator = generator; + _generator.setSendServerVersion(server.getSendServerVersion()); + _server = server; + } + + protected HttpParser newHttpParser(Buffers requestBuffers, EndPoint endpoint, HttpParser.EventHandler requestHandler) + { + return new HttpParser(requestBuffers, endpoint, requestHandler); + } + + protected HttpGenerator newHttpGenerator(Buffers responseBuffers, EndPoint endPoint) + { + return new HttpGenerator(responseBuffers, endPoint); + } + + /* ------------------------------------------------------------ */ + /** + * @return the parser used by this connection + */ + public Parser getParser() + { + return _parser; + } + + /* ------------------------------------------------------------ */ + /** + * @return the number of requests handled by this connection + */ + public int getRequests() + { + return _requests; + } + + /* ------------------------------------------------------------ */ + public Server getServer() + { + return _server; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the associatedObject. + */ + public Object getAssociatedObject() + { + return _associatedObject; + } + + /* ------------------------------------------------------------ */ + /** + * @param associatedObject The associatedObject to set. + */ + public void setAssociatedObject(Object associatedObject) + { + _associatedObject = associatedObject; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the connector. + */ + public Connector getConnector() + { + return _connector; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the requestFields. + */ + public HttpFields getRequestFields() + { + return _requestFields; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the responseFields. + */ + public HttpFields getResponseFields() + { + return _responseFields; + } + + /* ------------------------------------------------------------ */ + /** + * Find out if the request supports CONFIDENTIAL security. + * @param request the incoming HTTP request + * @return the result of calling {@link Connector#isConfidential(Request)}, or false + * if there is no connector + */ + public boolean isConfidential(Request request) + { + return _connector != null && _connector.isConfidential(request); + } + + /* ------------------------------------------------------------ */ + /** + * Find out if the request supports INTEGRAL security. + * @param request the incoming HTTP request + * @return the result of calling {@link Connector#isIntegral(Request)}, or false + * if there is no connector + */ + public boolean isIntegral(Request request) + { + return _connector != null && _connector.isIntegral(request); + } + + /* ------------------------------------------------------------ */ + /** + * @return <code>false</code> (this method is not yet implemented) + */ + public boolean getResolveNames() + { + return _connector.getResolveNames(); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the request. + */ + public Request getRequest() + { + return _request; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the response. + */ + public Response getResponse() + { + return _response; + } + + /* ------------------------------------------------------------ */ + /** + * Get the inputStream from the connection. + * <p> + * If the associated response has the Expect header set to 100 Continue, + * then accessing the input stream indicates that the handler/servlet + * is ready for the request body and thus a 100 Continue response is sent. + * + * @return The input stream for this connection. + * The stream will be created if it does not already exist. + * @throws IOException if the input stream cannot be retrieved + */ + public ServletInputStream getInputStream() throws IOException + { + // If the client is expecting 100 CONTINUE, then send it now. + if (_expect100Continue) + { + // is content missing? + if (((HttpParser)_parser).getHeaderBuffer()==null || ((HttpParser)_parser).getHeaderBuffer().length()<2) + { + if (_generator.isCommitted()) + throw new IllegalStateException("Committed before 100 Continues"); + + ((HttpGenerator)_generator).send1xx(HttpStatus.CONTINUE_100); + } + _expect100Continue=false; + } + + if (_in == null) + _in = new HttpInput(AbstractHttpConnection.this); + return _in; + } + + /* ------------------------------------------------------------ */ + /** + * @return The output stream for this connection. The stream will be created if it does not already exist. + */ + public ServletOutputStream getOutputStream() + { + if (_out == null) + _out = new Output(); + return _out; + } + + /* ------------------------------------------------------------ */ + /** + * @param encoding the PrintWriter encoding + * @return A {@link PrintWriter} wrapping the {@link #getOutputStream output stream}. The writer is created if it + * does not already exist. + */ + public PrintWriter getPrintWriter(String encoding) + { + getOutputStream(); + if (_writer==null) + { + _writer=new OutputWriter(); + if (_server.isUncheckedPrintWriter()) + _printWriter=new UncheckedPrintWriter(_writer); + else + _printWriter = new PrintWriter(_writer) + { + public void close() + { + synchronized (lock) + { + try + { + out.close(); + } + catch (IOException e) + { + setError(); + } + } + } + }; + } + _writer.setCharacterEncoding(encoding); + return _printWriter; + } + + /* ------------------------------------------------------------ */ + public boolean isResponseCommitted() + { + return _generator.isCommitted(); + } + + /* ------------------------------------------------------------ */ + public boolean isEarlyEOF() + { + return _earlyEOF; + } + + /* ------------------------------------------------------------ */ + public void reset() + { + _parser.reset(); + _parser.returnBuffers(); // TODO maybe only on unhandle + _requestFields.clear(); + _request.recycle(); + _generator.reset(); + _generator.returnBuffers();// TODO maybe only on unhandle + _responseFields.clear(); + _response.recycle(); + _uri.clear(); + _writer=null; + _earlyEOF = false; + } + + /* ------------------------------------------------------------ */ + protected void handleRequest() throws IOException + { + boolean error = false; + + String threadName=null; + Throwable async_exception=null; + try + { + if (LOG.isDebugEnabled()) + { + threadName=Thread.currentThread().getName(); + Thread.currentThread().setName(threadName+" - "+_uri); + } + + + // Loop here to handle async request redispatches. + // The loop is controlled by the call to async.unhandle in the + // finally block below. If call is from a non-blocking connector, + // then the unhandle will return false only if an async dispatch has + // already happened when unhandle is called. For a blocking connector, + // the wait for the asynchronous dispatch or timeout actually happens + // within the call to unhandle(). + + final Server server=_server; + boolean was_continuation=_request._async.isContinuation(); + boolean handling=_request._async.handling() && server!=null && server.isRunning(); + while (handling) + { + _request.setHandled(false); + + String info=null; + try + { + _uri.getPort(); + String path = null; + + try + { + path = _uri.getDecodedPath(); + } + catch (Exception e) + { + LOG.warn("Failed UTF-8 decode for request path, trying ISO-8859-1"); + LOG.ignore(e); + path = _uri.getDecodedPath(StringUtil.__ISO_8859_1); + } + + info=URIUtil.canonicalPath(path); + if (info==null && !_request.getMethod().equals(HttpMethods.CONNECT)) + { + if (path==null && _uri.getScheme()!=null && _uri.getHost()!=null) + { + info="/"; + _request.setRequestURI(""); + } + else + throw new HttpException(400); + } + _request.setPathInfo(info); + + if (_out!=null) + _out.reopen(); + + if (_request._async.isInitial()) + { + _request.setDispatcherType(DispatcherType.REQUEST); + _connector.customize(_endp, _request); + server.handle(this); + } + else + { + if (_request._async.isExpired()&&!was_continuation) + { + async_exception = (Throwable)_request.getAttribute(RequestDispatcher.ERROR_EXCEPTION); + _response.setStatus(500,async_exception==null?"Async Timeout":"Async Exception"); + _request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,new Integer(500)); + _request.setAttribute(RequestDispatcher.ERROR_MESSAGE, _response.getReason()); + _request.setDispatcherType(DispatcherType.ERROR); + + ErrorHandler eh = _request._async.getContextHandler().getErrorHandler(); + if (eh instanceof ErrorHandler.ErrorPageMapper) + { + String error_page=((ErrorHandler.ErrorPageMapper)eh).getErrorPage((HttpServletRequest)_request._async.getRequest()); + if (error_page!=null) + { + AsyncContinuation.AsyncEventState state = _request._async.getAsyncEventState(); + state.setPath(error_page); + } + } + } + else + _request.setDispatcherType(DispatcherType.ASYNC); + server.handleAsync(this); + } + } + catch (ContinuationThrowable e) + { + LOG.ignore(e); + } + catch (EofException e) + { + async_exception=e; + LOG.debug(e); + error=true; + _request.setHandled(true); + if (!_response.isCommitted()) + _generator.sendError(500, null, null, true); + } + catch (RuntimeIOException e) + { + async_exception=e; + LOG.debug(e); + error=true; + _request.setHandled(true); + } + catch (HttpException e) + { + LOG.debug(e); + error=true; + _request.setHandled(true); + _response.sendError(e.getStatus(), e.getReason()); + } + catch (Throwable e) + { + async_exception=e; + LOG.warn(String.valueOf(_uri),e); + error=true; + _request.setHandled(true); + _generator.sendError(info==null?400:500, null, null, true); + + } + finally + { + // Complete async requests + if (error && _request.isAsyncStarted()) + _request.getAsyncContinuation().errorComplete(); + + was_continuation=_request._async.isContinuation(); + handling = !_request._async.unhandle() && server.isRunning() && _server!=null; + } + } + } + finally + { + if (threadName!=null) + Thread.currentThread().setName(threadName); + + if (_request._async.isUncompleted()) + { + + _request._async.doComplete(async_exception); + + if (_expect100Continue) + { + LOG.debug("100 continues not sent"); + // We didn't send 100 continues, but the latest interpretation + // of the spec (see httpbis) is that the client will either + // send the body anyway, or close. So we no longer need to + // do anything special here other than make the connection not persistent + _expect100Continue = false; + if (!_response.isCommitted()) + _generator.setPersistent(false); + } + + if(_endp.isOpen()) + { + if (error) + { + _endp.shutdownOutput(); + _generator.setPersistent(false); + if (!_generator.isComplete()) + _response.complete(); + } + else + { + if (!_response.isCommitted() && !_request.isHandled()) + _response.sendError(HttpServletResponse.SC_NOT_FOUND); + _response.complete(); + if (_generator.isPersistent()) + _connector.persist(_endp); + } + } + else + { + _response.complete(); + } + + _request.setHandled(true); + } + } + } + + /* ------------------------------------------------------------ */ + public abstract Connection handle() throws IOException; + + /* ------------------------------------------------------------ */ + public void commitResponse(boolean last) throws IOException + { + if (!_generator.isCommitted()) + { + _generator.setResponse(_response.getStatus(), _response.getReason()); + try + { + // If the client was expecting 100 continues, but we sent something + // else, then we need to close the connection + if (_expect100Continue && _response.getStatus()!=100) + _generator.setPersistent(false); + _generator.completeHeader(_responseFields, last); + } + catch(RuntimeException e) + { + LOG.warn("header full: " + e); + + _response.reset(); + _generator.reset(); + _generator.setResponse(HttpStatus.INTERNAL_SERVER_ERROR_500,null); + _generator.completeHeader(_responseFields,Generator.LAST); + _generator.complete(); + throw new HttpException(HttpStatus.INTERNAL_SERVER_ERROR_500); + } + + } + if (last) + _generator.complete(); + } + + /* ------------------------------------------------------------ */ + public void completeResponse() throws IOException + { + if (!_generator.isCommitted()) + { + _generator.setResponse(_response.getStatus(), _response.getReason()); + try + { + _generator.completeHeader(_responseFields, Generator.LAST); + } + catch(RuntimeException e) + { + LOG.warn("header full: "+e); + LOG.debug(e); + + _response.reset(); + _generator.reset(); + _generator.setResponse(HttpStatus.INTERNAL_SERVER_ERROR_500,null); + _generator.completeHeader(_responseFields,Generator.LAST); + _generator.complete(); + throw new HttpException(HttpStatus.INTERNAL_SERVER_ERROR_500); + } + } + + _generator.complete(); + } + + /* ------------------------------------------------------------ */ + public void flushResponse() throws IOException + { + try + { + commitResponse(Generator.MORE); + _generator.flushBuffer(); + } + catch(IOException e) + { + throw (e instanceof EofException) ? e:new EofException(e); + } + } + + /* ------------------------------------------------------------ */ + public Generator getGenerator() + { + return _generator; + } + + /* ------------------------------------------------------------ */ + public boolean isIncluding() + { + return _include>0; + } + + /* ------------------------------------------------------------ */ + public void include() + { + _include++; + } + + /* ------------------------------------------------------------ */ + public void included() + { + _include--; + if (_out!=null) + _out.reopen(); + } + + /* ------------------------------------------------------------ */ + public boolean isIdle() + { + return _generator.isIdle() && (_parser.isIdle() || _delayedHandling); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.io.Connection#isSuspended() + */ + public boolean isSuspended() + { + return _request.getAsyncContinuation().isSuspended(); + } + + /* ------------------------------------------------------------ */ + public void onClose() + { + LOG.debug("closed {}",this); + } + + /* ------------------------------------------------------------ */ + public boolean isExpecting100Continues() + { + return _expect100Continue; + } + + /* ------------------------------------------------------------ */ + public boolean isExpecting102Processing() + { + return _expect102Processing; + } + + /* ------------------------------------------------------------ */ + public int getMaxIdleTime() + { + if (_connector.isLowResources() && _endp.getMaxIdleTime()==_connector.getMaxIdleTime()) + return _connector.getLowResourceMaxIdleTime(); + if (_endp.getMaxIdleTime()>0) + return _endp.getMaxIdleTime(); + return _connector.getMaxIdleTime(); + } + + /* ------------------------------------------------------------ */ + public String toString() + { + return String.format("%s,g=%s,p=%s,r=%d", + super.toString(), + _generator, + _parser, + _requests); + } + + /* ------------------------------------------------------------ */ + protected void startRequest(Buffer method, Buffer uri, Buffer version) throws IOException + { + uri=uri.asImmutableBuffer(); + + _host = false; + _expect = false; + _expect100Continue=false; + _expect102Processing=false; + _delayedHandling=false; + _charset=null; + + if(_request.getTimeStamp()==0) + _request.setTimeStamp(System.currentTimeMillis()); + _request.setMethod(method.toString()); + + try + { + _head=false; + switch (HttpMethods.CACHE.getOrdinal(method)) + { + case HttpMethods.CONNECT_ORDINAL: + _uri.parseConnect(uri.array(), uri.getIndex(), uri.length()); + break; + + case HttpMethods.HEAD_ORDINAL: + _head=true; + _uri.parse(uri.array(), uri.getIndex(), uri.length()); + break; + + default: + _uri.parse(uri.array(), uri.getIndex(), uri.length()); + } + + _request.setUri(_uri); + + if (version==null) + { + _request.setProtocol(HttpVersions.HTTP_0_9); + _version=HttpVersions.HTTP_0_9_ORDINAL; + } + else + { + version= HttpVersions.CACHE.get(version); + if (version==null) + throw new HttpException(HttpStatus.BAD_REQUEST_400,null); + _version = HttpVersions.CACHE.getOrdinal(version); + if (_version <= 0) _version = HttpVersions.HTTP_1_0_ORDINAL; + _request.setProtocol(version.toString()); + } + } + catch (Exception e) + { + LOG.debug(e); + if (e instanceof HttpException) + throw (HttpException)e; + throw new HttpException(HttpStatus.BAD_REQUEST_400,null,e); + } + } + + /* ------------------------------------------------------------ */ + protected void parsedHeader(Buffer name, Buffer value) throws IOException + { + int ho = HttpHeaders.CACHE.getOrdinal(name); + switch (ho) + { + case HttpHeaders.HOST_ORDINAL: + // TODO check if host matched a host in the URI. + _host = true; + break; + + case HttpHeaders.EXPECT_ORDINAL: + if (_version>=HttpVersions.HTTP_1_1_ORDINAL) + { + value = HttpHeaderValues.CACHE.lookup(value); + switch(HttpHeaderValues.CACHE.getOrdinal(value)) + { + case HttpHeaderValues.CONTINUE_ORDINAL: + _expect100Continue=_generator instanceof HttpGenerator; + break; + + case HttpHeaderValues.PROCESSING_ORDINAL: + _expect102Processing=_generator instanceof HttpGenerator; + break; + + default: + String[] values = value.toString().split(","); + for (int i=0;values!=null && i<values.length;i++) + { + CachedBuffer cb=HttpHeaderValues.CACHE.get(values[i].trim()); + if (cb==null) + _expect=true; + else + { + switch(cb.getOrdinal()) + { + case HttpHeaderValues.CONTINUE_ORDINAL: + _expect100Continue=_generator instanceof HttpGenerator; + break; + case HttpHeaderValues.PROCESSING_ORDINAL: + _expect102Processing=_generator instanceof HttpGenerator; + break; + default: + _expect=true; + } + } + } + } + } + break; + + case HttpHeaders.ACCEPT_ENCODING_ORDINAL: + case HttpHeaders.USER_AGENT_ORDINAL: + value = HttpHeaderValues.CACHE.lookup(value); + break; + + case HttpHeaders.CONTENT_TYPE_ORDINAL: + value = MimeTypes.CACHE.lookup(value); + _charset=MimeTypes.getCharsetFromContentType(value); + break; + } + + _requestFields.add(name, value); + } + + /* ------------------------------------------------------------ */ + protected void headerComplete() throws IOException + { + // Handle idle race + if (_endp.isOutputShutdown()) + { + _endp.close(); + return; + } + + _requests++; + _generator.setVersion(_version); + switch (_version) + { + case HttpVersions.HTTP_0_9_ORDINAL: + break; + case HttpVersions.HTTP_1_0_ORDINAL: + _generator.setHead(_head); + if (_parser.isPersistent()) + { + _responseFields.add(HttpHeaders.CONNECTION_BUFFER, HttpHeaderValues.KEEP_ALIVE_BUFFER); + _generator.setPersistent(true); + } + else if (HttpMethods.CONNECT.equals(_request.getMethod())) + { + _generator.setPersistent(true); + _parser.setPersistent(true); + } + + if (_server.getSendDateHeader()) + _generator.setDate(_request.getTimeStampBuffer()); + break; + + case HttpVersions.HTTP_1_1_ORDINAL: + _generator.setHead(_head); + + if (!_parser.isPersistent()) + { + _responseFields.add(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.CLOSE_BUFFER); + _generator.setPersistent(false); + } + if (_server.getSendDateHeader()) + _generator.setDate(_request.getTimeStampBuffer()); + + if (!_host) + { + LOG.debug("!host {}",this); + _generator.setResponse(HttpStatus.BAD_REQUEST_400, null); + _responseFields.put(HttpHeaders.CONNECTION_BUFFER, HttpHeaderValues.CLOSE_BUFFER); + _generator.completeHeader(_responseFields, true); + _generator.complete(); + return; + } + + if (_expect) + { + LOG.debug("!expectation {}",this); + _generator.setResponse(HttpStatus.EXPECTATION_FAILED_417, null); + _responseFields.put(HttpHeaders.CONNECTION_BUFFER, HttpHeaderValues.CLOSE_BUFFER); + _generator.completeHeader(_responseFields, true); + _generator.complete(); + return; + } + + break; + default: + } + + if(_charset!=null) + _request.setCharacterEncodingUnchecked(_charset); + + // Either handle now or wait for first content + if ((((HttpParser)_parser).getContentLength()<=0 && !((HttpParser)_parser).isChunking())||_expect100Continue) + handleRequest(); + else + _delayedHandling=true; + } + + /* ------------------------------------------------------------ */ + protected void content(Buffer buffer) throws IOException + { + if (_delayedHandling) + { + _delayedHandling=false; + handleRequest(); + } + } + + /* ------------------------------------------------------------ */ + public void messageComplete(long contentLength) throws IOException + { + if (_delayedHandling) + { + _delayedHandling=false; + handleRequest(); + } + } + + /* ------------------------------------------------------------ */ + public void earlyEOF() + { + _earlyEOF = true; + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + private class RequestHandler extends HttpParser.EventHandler + { + /* + * + * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#startRequest(org.eclipse.io.Buffer, + * org.eclipse.io.Buffer, org.eclipse.io.Buffer) + */ + @Override + public void startRequest(Buffer method, Buffer uri, Buffer version) throws IOException + { + AbstractHttpConnection.this.startRequest(method, uri, version); + } + + /* + * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#parsedHeaderValue(org.eclipse.io.Buffer) + */ + @Override + public void parsedHeader(Buffer name, Buffer value) throws IOException + { + AbstractHttpConnection.this.parsedHeader(name, value); + } + + /* + * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#headerComplete() + */ + @Override + public void headerComplete() throws IOException + { + AbstractHttpConnection.this.headerComplete(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#content(int, org.eclipse.io.Buffer) + */ + @Override + public void content(Buffer ref) throws IOException + { + AbstractHttpConnection.this.content(ref); + } + + /* ------------------------------------------------------------ */ + /* + * (non-Javadoc) + * + * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#messageComplete(int) + */ + @Override + public void messageComplete(long contentLength) throws IOException + { + AbstractHttpConnection.this.messageComplete(contentLength); + } + + /* ------------------------------------------------------------ */ + /* + * (non-Javadoc) + * + * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#startResponse(org.eclipse.io.Buffer, int, + * org.eclipse.io.Buffer) + */ + @Override + public void startResponse(Buffer version, int status, Buffer reason) + { + if (LOG.isDebugEnabled()) + LOG.debug("Bad request!: "+version+" "+status+" "+reason); + } + + /* ------------------------------------------------------------ */ + /* + * (non-Javadoc) + * + * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#earlyEOF() + */ + @Override + public void earlyEOF() + { + AbstractHttpConnection.this.earlyEOF(); + } + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + public class Output extends HttpOutput + { + Output() + { + super(AbstractHttpConnection.this); + } + + /* ------------------------------------------------------------ */ + /* + * @see java.io.OutputStream#close() + */ + @Override + public void close() throws IOException + { + if (isClosed()) + return; + + if (!isIncluding() && !super._generator.isCommitted()) + commitResponse(Generator.LAST); + else + flushResponse(); + + super.close(); + } + + + /* ------------------------------------------------------------ */ + /* + * @see java.io.OutputStream#flush() + */ + @Override + public void flush() throws IOException + { + if (!super._generator.isCommitted()) + commitResponse(Generator.MORE); + super.flush(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletOutputStream#print(java.lang.String) + */ + @Override + public void print(String s) throws IOException + { + if (isClosed()) + throw new IOException("Closed"); + PrintWriter writer=getPrintWriter(null); + writer.print(s); + } + + /* ------------------------------------------------------------ */ + public void sendResponse(Buffer response) throws IOException + { + ((HttpGenerator)super._generator).sendResponse(response); + } + + /* ------------------------------------------------------------ */ + public void sendContent(Object content) throws IOException + { + Resource resource=null; + + if (isClosed()) + throw new IOException("Closed"); + + if (super._generator.isWritten()) + throw new IllegalStateException("!empty"); + + // Convert HTTP content to content + if (content instanceof HttpContent) + { + HttpContent httpContent = (HttpContent) content; + Buffer contentType = httpContent.getContentType(); + if (contentType != null && !_responseFields.containsKey(HttpHeaders.CONTENT_TYPE_BUFFER)) + { + String enc = _response.getSetCharacterEncoding(); + if(enc==null) + _responseFields.add(HttpHeaders.CONTENT_TYPE_BUFFER, contentType); + else + { + if(contentType instanceof CachedBuffer) + { + CachedBuffer content_type = ((CachedBuffer)contentType).getAssociate(enc); + if(content_type!=null) + _responseFields.put(HttpHeaders.CONTENT_TYPE_BUFFER, content_type); + else + { + _responseFields.put(HttpHeaders.CONTENT_TYPE_BUFFER, + contentType+";charset="+QuotedStringTokenizer.quoteIfNeeded(enc,";= ")); + } + } + else + { + _responseFields.put(HttpHeaders.CONTENT_TYPE_BUFFER, + contentType+";charset="+QuotedStringTokenizer.quoteIfNeeded(enc,";= ")); + } + } + } + if (httpContent.getContentLength() > 0) + _responseFields.putLongField(HttpHeaders.CONTENT_LENGTH_BUFFER, httpContent.getContentLength()); + Buffer lm = httpContent.getLastModified(); + long lml=httpContent.getResource().lastModified(); + if (lm != null) + { + _responseFields.put(HttpHeaders.LAST_MODIFIED_BUFFER, lm); + } + else if (httpContent.getResource()!=null) + { + if (lml!=-1) + _responseFields.putDateField(HttpHeaders.LAST_MODIFIED_BUFFER, lml); + } + + Buffer etag=httpContent.getETag(); + if (etag!=null) + _responseFields.put(HttpHeaders.ETAG_BUFFER,etag); + + + boolean direct=_connector instanceof NIOConnector && ((NIOConnector)_connector).getUseDirectBuffers() && !(_connector instanceof SslConnector); + content = direct?httpContent.getDirectBuffer():httpContent.getIndirectBuffer(); + if (content==null) + content=httpContent.getInputStream(); + } + else if (content instanceof Resource) + { + resource=(Resource)content; + _responseFields.putDateField(HttpHeaders.LAST_MODIFIED_BUFFER, resource.lastModified()); + content=resource.getInputStream(); + } + + // Process content. + if (content instanceof Buffer) + { + super._generator.addContent((Buffer) content, Generator.LAST); + commitResponse(Generator.LAST); + } + else if (content instanceof InputStream) + { + InputStream in = (InputStream)content; + + try + { + int max = super._generator.prepareUncheckedAddContent(); + Buffer buffer = super._generator.getUncheckedBuffer(); + + int len=buffer.readFrom(in,max); + + while (len>=0) + { + super._generator.completeUncheckedAddContent(); + _out.flush(); + + max = super._generator.prepareUncheckedAddContent(); + buffer = super._generator.getUncheckedBuffer(); + len=buffer.readFrom(in,max); + } + super._generator.completeUncheckedAddContent(); + _out.flush(); + } + finally + { + if (resource!=null) + resource.release(); + else + in.close(); + } + } + else + throw new IllegalArgumentException("unknown content type?"); + + + } + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + public class OutputWriter extends HttpWriter + { + OutputWriter() + { + super(AbstractHttpConnection.this._out); + } + } + + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/AsyncContinuation.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,1160 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import javax.servlet.AsyncContext; +import javax.servlet.AsyncEvent; +import javax.servlet.AsyncListener; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletException; + +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.ServletContext; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; + +import org.eclipse.jetty.continuation.Continuation; +import org.eclipse.jetty.continuation.ContinuationThrowable; +import org.eclipse.jetty.continuation.ContinuationListener; +import org.eclipse.jetty.io.AsyncEndPoint; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.ContextHandler.Context; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.thread.Timeout; + +/* ------------------------------------------------------------ */ +/** Implementation of Continuation and AsyncContext interfaces + * + */ +public class AsyncContinuation implements AsyncContext, Continuation +{ + private static final Logger LOG = Log.getLogger(AsyncContinuation.class); + + private final static long DEFAULT_TIMEOUT=30000L; + + private final static ContinuationThrowable __exception = new ContinuationThrowable(); + + // STATES: + // handling() suspend() unhandle() resume() complete() doComplete() + // startAsync() dispatch() + // IDLE DISPATCHED + // DISPATCHED ASYNCSTARTED UNCOMPLETED + // ASYNCSTARTED ASYNCWAIT REDISPATCHING COMPLETING + // REDISPATCHING REDISPATCHED + // ASYNCWAIT REDISPATCH COMPLETING + // REDISPATCH REDISPATCHED + // REDISPATCHED ASYNCSTARTED UNCOMPLETED + // COMPLETING UNCOMPLETED UNCOMPLETED + // UNCOMPLETED COMPLETED + // COMPLETED + private static final int __IDLE=0; // Idle request + private static final int __DISPATCHED=1; // Request dispatched to filter/servlet + private static final int __ASYNCSTARTED=2; // Suspend called, but not yet returned to container + private static final int __REDISPATCHING=3;// resumed while dispatched + private static final int __ASYNCWAIT=4; // Suspended and parked + private static final int __REDISPATCH=5; // Has been scheduled + private static final int __REDISPATCHED=6; // Request redispatched to filter/servlet + private static final int __COMPLETING=7; // complete while dispatched + private static final int __UNCOMPLETED=8; // Request is completable + private static final int __COMPLETED=9; // Request is complete + + /* ------------------------------------------------------------ */ + protected AbstractHttpConnection _connection; + private List<AsyncListener> _lastAsyncListeners; + private List<AsyncListener> _asyncListeners; + private List<ContinuationListener> _continuationListeners; + + /* ------------------------------------------------------------ */ + private int _state; + private boolean _initial; + private boolean _resumed; + private boolean _expired; + private volatile boolean _responseWrapped; + private long _timeoutMs=DEFAULT_TIMEOUT; + private AsyncEventState _event; + private volatile long _expireAt; + private volatile boolean _continuation; + + /* ------------------------------------------------------------ */ + protected AsyncContinuation() + { + _state=__IDLE; + _initial=true; + } + + /* ------------------------------------------------------------ */ + protected void setConnection(final AbstractHttpConnection connection) + { + synchronized(this) + { + _connection=connection; + } + } + + /* ------------------------------------------------------------ */ + public void addListener(AsyncListener listener) + { + synchronized(this) + { + if (_asyncListeners==null) + _asyncListeners=new ArrayList<AsyncListener>(); + _asyncListeners.add(listener); + } + } + + /* ------------------------------------------------------------ */ + public void addListener(AsyncListener listener,ServletRequest request, ServletResponse response) + { + synchronized(this) + { + // TODO handle the request/response ??? + if (_asyncListeners==null) + _asyncListeners=new ArrayList<AsyncListener>(); + _asyncListeners.add(listener); + } + } + + /* ------------------------------------------------------------ */ + public void addContinuationListener(ContinuationListener listener) + { + synchronized(this) + { + if (_continuationListeners==null) + _continuationListeners=new ArrayList<ContinuationListener>(); + _continuationListeners.add(listener); + } + } + + /* ------------------------------------------------------------ */ + public void setTimeout(long ms) + { + synchronized(this) + { + _timeoutMs=ms; + } + } + + /* ------------------------------------------------------------ */ + public long getTimeout() + { + synchronized(this) + { + return _timeoutMs; + } + } + + /* ------------------------------------------------------------ */ + public AsyncEventState getAsyncEventState() + { + synchronized(this) + { + return _event; + } + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.continuation.Continuation#keepWrappers() + */ + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.continuation.Continuation#isResponseWrapped() + */ + public boolean isResponseWrapped() + { + return _responseWrapped; + } + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see javax.servlet.ServletRequest#isInitial() + */ + public boolean isInitial() + { + synchronized(this) + { + return _initial; + } + } + + public boolean isContinuation() + { + return _continuation; + } + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see javax.servlet.ServletRequest#isSuspended() + */ + public boolean isSuspended() + { + synchronized(this) + { + switch(_state) + { + case __ASYNCSTARTED: + case __REDISPATCHING: + case __COMPLETING: + case __ASYNCWAIT: + return true; + + default: + return false; + } + } + } + + /* ------------------------------------------------------------ */ + public boolean isSuspending() + { + synchronized(this) + { + switch(_state) + { + case __ASYNCSTARTED: + case __ASYNCWAIT: + return true; + + default: + return false; + } + } + } + + /* ------------------------------------------------------------ */ + public boolean isDispatchable() + { + synchronized(this) + { + switch(_state) + { + case __REDISPATCH: + case __REDISPATCHED: + case __REDISPATCHING: + case __COMPLETING: + return true; + + default: + return false; + } + } + } + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + synchronized (this) + { + return super.toString()+"@"+getStatusString(); + } + } + + /* ------------------------------------------------------------ */ + public String getStatusString() + { + synchronized (this) + { + return + ((_state==__IDLE)?"IDLE": + (_state==__DISPATCHED)?"DISPATCHED": + (_state==__ASYNCSTARTED)?"ASYNCSTARTED": + (_state==__ASYNCWAIT)?"ASYNCWAIT": + (_state==__REDISPATCHING)?"REDISPATCHING": + (_state==__REDISPATCH)?"REDISPATCH": + (_state==__REDISPATCHED)?"REDISPATCHED": + (_state==__COMPLETING)?"COMPLETING": + (_state==__UNCOMPLETED)?"UNCOMPLETED": + (_state==__COMPLETED)?"COMPLETE": + ("UNKNOWN?"+_state))+ + (_initial?",initial":"")+ + (_resumed?",resumed":"")+ + (_expired?",expired":""); + } + } + + /* ------------------------------------------------------------ */ + /** + * @return false if the handling of the request should not proceed + */ + protected boolean handling() + { + synchronized (this) + { + _continuation=false; + + switch(_state) + { + case __IDLE: + _initial=true; + _state=__DISPATCHED; + if (_lastAsyncListeners!=null) + _lastAsyncListeners.clear(); + if (_asyncListeners!=null) + _asyncListeners.clear(); + else + { + _asyncListeners=_lastAsyncListeners; + _lastAsyncListeners=null; + } + return true; + + case __COMPLETING: + _state=__UNCOMPLETED; + return false; + + case __ASYNCWAIT: + return false; + + case __REDISPATCH: + _state=__REDISPATCHED; + return true; + + default: + throw new IllegalStateException(this.getStatusString()); + } + } + } + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see javax.servlet.ServletRequest#suspend(long) + */ + private void doSuspend(final ServletContext context, + final ServletRequest request, + final ServletResponse response) + { + synchronized (this) + { + switch(_state) + { + case __DISPATCHED: + case __REDISPATCHED: + _resumed=false; + _expired=false; + + if (_event==null || request!=_event.getSuppliedRequest() || response != _event.getSuppliedResponse() || context != _event.getServletContext()) + _event=new AsyncEventState(context,request,response); + else + { + _event._dispatchContext=null; + _event._pathInContext=null; + } + _state=__ASYNCSTARTED; + List<AsyncListener> recycle=_lastAsyncListeners; + _lastAsyncListeners=_asyncListeners; + _asyncListeners=recycle; + if (_asyncListeners!=null) + _asyncListeners.clear(); + break; + + default: + throw new IllegalStateException(this.getStatusString()); + } + } + + if (_lastAsyncListeners!=null) + { + for (AsyncListener listener : _lastAsyncListeners) + { + try + { + listener.onStartAsync(_event); + } + catch(Exception e) + { + LOG.warn(e); + } + } + } + } + + /* ------------------------------------------------------------ */ + /** + * Signal that the HttpConnection has finished handling the request. + * For blocking connectors, this call may block if the request has + * been suspended (startAsync called). + * @return true if handling is complete, false if the request should + * be handled again (eg because of a resume that happened before unhandle was called) + */ + protected boolean unhandle() + { + synchronized (this) + { + switch(_state) + { + case __REDISPATCHED: + case __DISPATCHED: + _state=__UNCOMPLETED; + return true; + + case __IDLE: + throw new IllegalStateException(this.getStatusString()); + + case __ASYNCSTARTED: + _initial=false; + _state=__ASYNCWAIT; + scheduleTimeout(); // could block and change state. + if (_state==__ASYNCWAIT) + return true; + else if (_state==__COMPLETING) + { + _state=__UNCOMPLETED; + return true; + } + _initial=false; + _state=__REDISPATCHED; + return false; + + case __REDISPATCHING: + _initial=false; + _state=__REDISPATCHED; + return false; + + case __COMPLETING: + _initial=false; + _state=__UNCOMPLETED; + return true; + + default: + throw new IllegalStateException(this.getStatusString()); + } + } + } + + /* ------------------------------------------------------------ */ + public void dispatch() + { + boolean dispatch=false; + synchronized (this) + { + switch(_state) + { + case __ASYNCSTARTED: + _state=__REDISPATCHING; + _resumed=true; + return; + + case __ASYNCWAIT: + dispatch=!_expired; + _state=__REDISPATCH; + _resumed=true; + break; + + case __REDISPATCH: + return; + + default: + throw new IllegalStateException(this.getStatusString()); + } + } + + if (dispatch) + { + cancelTimeout(); + scheduleDispatch(); + } + } + + /* ------------------------------------------------------------ */ + protected void expired() + { + final List<ContinuationListener> cListeners; + final List<AsyncListener> aListeners; + synchronized (this) + { + switch(_state) + { + case __ASYNCSTARTED: + case __ASYNCWAIT: + cListeners=_continuationListeners; + aListeners=_asyncListeners; + break; + default: + cListeners=null; + aListeners=null; + return; + } + _expired=true; + } + + if (aListeners!=null) + { + for (AsyncListener listener : aListeners) + { + try + { + listener.onTimeout(_event); + } + catch(Exception e) + { + LOG.debug(e); + _connection.getRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,e); + break; + } + } + } + if (cListeners!=null) + { + for (ContinuationListener listener : cListeners) + { + try + { + listener.onTimeout(this); + } + catch(Exception e) + { + LOG.warn(e); + } + } + } + + synchronized (this) + { + switch(_state) + { + case __ASYNCSTARTED: + case __ASYNCWAIT: + dispatch(); + break; + + default: + if (!_continuation) + _expired=false; + } + } + + scheduleDispatch(); + } + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see javax.servlet.ServletRequest#complete() + */ + public void complete() + { + // just like resume, except don't set _resumed=true; + boolean dispatch=false; + synchronized (this) + { + switch(_state) + { + case __DISPATCHED: + case __REDISPATCHED: + throw new IllegalStateException(this.getStatusString()); + + case __ASYNCSTARTED: + _state=__COMPLETING; + return; + + case __ASYNCWAIT: + _state=__COMPLETING; + dispatch=!_expired; + break; + + default: + throw new IllegalStateException(this.getStatusString()); + } + } + + if (dispatch) + { + cancelTimeout(); + scheduleDispatch(); + } + } + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see javax.servlet.ServletRequest#complete() + */ + public void errorComplete() + { + // just like complete except can overrule a prior dispatch call; + synchronized (this) + { + switch(_state) + { + case __REDISPATCHING: + case __ASYNCSTARTED: + _state=__COMPLETING; + _resumed=false; + return; + + case __COMPLETING: + return; + + default: + throw new IllegalStateException(this.getStatusString()); + } + } + } + + /* ------------------------------------------------------------ */ + @Override + public <T extends AsyncListener> T createListener(Class<T> clazz) throws ServletException + { + try + { + // TODO inject + return clazz.newInstance(); + } + catch(Exception e) + { + throw new ServletException(e); + } + } + + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see javax.servlet.ServletRequest#complete() + */ + protected void doComplete(Throwable ex) + { + final List<ContinuationListener> cListeners; + final List<AsyncListener> aListeners; + synchronized (this) + { + switch(_state) + { + case __UNCOMPLETED: + _state=__COMPLETED; + cListeners=_continuationListeners; + aListeners=_asyncListeners; + break; + + default: + cListeners=null; + aListeners=null; + throw new IllegalStateException(this.getStatusString()); + } + } + + if (aListeners!=null) + { + for (AsyncListener listener : aListeners) + { + try + { + if (ex!=null) + { + _event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,ex); + _event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_MESSAGE,ex.getMessage()); + listener.onError(_event); + } + else + listener.onComplete(_event); + } + catch(Exception e) + { + LOG.warn(e); + } + } + } + if (cListeners!=null) + { + for (ContinuationListener listener : cListeners) + { + try + { + listener.onComplete(this); + } + catch(Exception e) + { + LOG.warn(e); + } + } + } + } + + /* ------------------------------------------------------------ */ + protected void recycle() + { + synchronized (this) + { + switch(_state) + { + case __DISPATCHED: + case __REDISPATCHED: + throw new IllegalStateException(getStatusString()); + default: + _state=__IDLE; + } + _initial = true; + _resumed=false; + _expired=false; + _responseWrapped=false; + cancelTimeout(); + _timeoutMs=DEFAULT_TIMEOUT; + _continuationListeners=null; + } + } + + /* ------------------------------------------------------------ */ + public void cancel() + { + synchronized (this) + { + cancelTimeout(); + _continuationListeners=null; + } + } + + /* ------------------------------------------------------------ */ + protected void scheduleDispatch() + { + EndPoint endp=_connection.getEndPoint(); + if (!endp.isBlocking()) + { + ((AsyncEndPoint)endp).asyncDispatch(); + } + } + + /* ------------------------------------------------------------ */ + protected void scheduleTimeout() + { + EndPoint endp=_connection.getEndPoint(); + if (_timeoutMs>0) + { + if (endp.isBlocking()) + { + synchronized(this) + { + _expireAt = System.currentTimeMillis()+_timeoutMs; + long wait=_timeoutMs; + while (_expireAt>0 && wait>0 && _connection.getServer().isRunning()) + { + try + { + this.wait(wait); + } + catch (InterruptedException e) + { + LOG.ignore(e); + } + wait=_expireAt-System.currentTimeMillis(); + } + + if (_expireAt>0 && wait<=0 && _connection.getServer().isRunning()) + { + expired(); + } + } + } + else + { + ((AsyncEndPoint)endp).scheduleTimeout(_event._timeout,_timeoutMs); + } + } + } + + /* ------------------------------------------------------------ */ + protected void cancelTimeout() + { + EndPoint endp=_connection.getEndPoint(); + if (endp.isBlocking()) + { + synchronized(this) + { + _expireAt=0; + this.notifyAll(); + } + } + else + { + final AsyncEventState event=_event; + if (event!=null) + { + ((AsyncEndPoint)endp).cancelTimeout(event._timeout); + } + } + } + + /* ------------------------------------------------------------ */ + public boolean isCompleting() + { + synchronized (this) + { + return _state==__COMPLETING; + } + } + + /* ------------------------------------------------------------ */ + boolean isUncompleted() + { + synchronized (this) + { + return _state==__UNCOMPLETED; + } + } + + /* ------------------------------------------------------------ */ + public boolean isComplete() + { + synchronized (this) + { + return _state==__COMPLETED; + } + } + + + /* ------------------------------------------------------------ */ + public boolean isAsyncStarted() + { + synchronized (this) + { + switch(_state) + { + case __ASYNCSTARTED: + case __REDISPATCHING: + case __REDISPATCH: + case __ASYNCWAIT: + return true; + + default: + return false; + } + } + } + + + /* ------------------------------------------------------------ */ + public boolean isAsync() + { + synchronized (this) + { + switch(_state) + { + case __IDLE: + case __DISPATCHED: + case __UNCOMPLETED: + case __COMPLETED: + return false; + + default: + return true; + } + } + } + + /* ------------------------------------------------------------ */ + public void dispatch(ServletContext context, String path) + { + _event._dispatchContext=context; + _event.setPath(path); + dispatch(); + } + + /* ------------------------------------------------------------ */ + public void dispatch(String path) + { + _event.setPath(path); + dispatch(); + } + + /* ------------------------------------------------------------ */ + public Request getBaseRequest() + { + return _connection.getRequest(); + } + + /* ------------------------------------------------------------ */ + public ServletRequest getRequest() + { + if (_event!=null) + return _event.getSuppliedRequest(); + return _connection.getRequest(); + } + + /* ------------------------------------------------------------ */ + public ServletResponse getResponse() + { + if (_responseWrapped && _event!=null && _event.getSuppliedResponse()!=null) + return _event.getSuppliedResponse(); + return _connection.getResponse(); + } + + /* ------------------------------------------------------------ */ + public void start(final Runnable run) + { + final AsyncEventState event=_event; + if (event!=null) + { + _connection.getServer().getThreadPool().dispatch(new Runnable() + { + public void run() + { + ((Context)event.getServletContext()).getContextHandler().handle(run); + } + }); + } + } + + /* ------------------------------------------------------------ */ + public boolean hasOriginalRequestAndResponse() + { + synchronized (this) + { + return (_event!=null && _event.getSuppliedRequest()==_connection._request && _event.getSuppliedResponse()==_connection._response); + } + } + + /* ------------------------------------------------------------ */ + public ContextHandler getContextHandler() + { + final AsyncEventState event=_event; + if (event!=null) + return ((Context)event.getServletContext()).getContextHandler(); + return null; + } + + + /* ------------------------------------------------------------ */ + /** + * @see Continuation#isResumed() + */ + public boolean isResumed() + { + synchronized (this) + { + return _resumed; + } + } + /* ------------------------------------------------------------ */ + /** + * @see Continuation#isExpired() + */ + public boolean isExpired() + { + synchronized (this) + { + return _expired; + } + } + + /* ------------------------------------------------------------ */ + /** + * @see Continuation#resume() + */ + public void resume() + { + dispatch(); + } + + + + /* ------------------------------------------------------------ */ + protected void startAsync(final ServletContext context, + final ServletRequest request, + final ServletResponse response) + { + synchronized (this) + { + _responseWrapped=!(response instanceof Response); + doSuspend(context,request,response); + if (request instanceof HttpServletRequest) + { + _event._pathInContext = URIUtil.addPaths(((HttpServletRequest)request).getServletPath(),((HttpServletRequest)request).getPathInfo()); + } + } + } + + /* ------------------------------------------------------------ */ + protected void startAsync() + { + _responseWrapped=false; + _continuation=false; + doSuspend(_connection.getRequest().getServletContext(),_connection.getRequest(),_connection.getResponse()); + } + + + /* ------------------------------------------------------------ */ + /** + * @see Continuation#suspend() + */ + public void suspend(ServletResponse response) + { + _continuation=true; + _responseWrapped=!(response instanceof Response); + doSuspend(_connection.getRequest().getServletContext(),_connection.getRequest(),response); + } + + /* ------------------------------------------------------------ */ + /** + * @see Continuation#suspend() + */ + public void suspend() + { + _responseWrapped=false; + _continuation=true; + doSuspend(_connection.getRequest().getServletContext(),_connection.getRequest(),_connection.getResponse()); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.continuation.Continuation#getServletResponse() + */ + public ServletResponse getServletResponse() + { + if (_responseWrapped && _event!=null && _event.getSuppliedResponse()!=null) + return _event.getSuppliedResponse(); + return _connection.getResponse(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.continuation.Continuation#getAttribute(java.lang.String) + */ + public Object getAttribute(String name) + { + return _connection.getRequest().getAttribute(name); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.continuation.Continuation#removeAttribute(java.lang.String) + */ + public void removeAttribute(String name) + { + _connection.getRequest().removeAttribute(name); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.continuation.Continuation#setAttribute(java.lang.String, java.lang.Object) + */ + public void setAttribute(String name, Object attribute) + { + _connection.getRequest().setAttribute(name,attribute); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.continuation.Continuation#undispatch() + */ + public void undispatch() + { + if (isSuspended()) + { + if (LOG.isDebugEnabled()) + throw new ContinuationThrowable(); + else + throw __exception; + } + throw new IllegalStateException("!suspended"); + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + public class AsyncTimeout extends Timeout.Task implements Runnable + { + @Override + public void expired() + { + AsyncContinuation.this.expired(); + } + + @Override + public void run() + { + AsyncContinuation.this.expired(); + } + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + public class AsyncEventState extends AsyncEvent + { + private final ServletContext _suspendedContext; + private ServletContext _dispatchContext; + private String _pathInContext; + private Timeout.Task _timeout= new AsyncTimeout(); + + public AsyncEventState(ServletContext context, ServletRequest request, ServletResponse response) + { + super(AsyncContinuation.this, request,response); + _suspendedContext=context; + // Get the base request So we can remember the initial paths + Request r=_connection.getRequest(); + + // If we haven't been async dispatched before + if (r.getAttribute(AsyncContext.ASYNC_REQUEST_URI)==null) + { + // We are setting these attributes during startAsync, when the spec implies that + // they are only available after a call to AsyncContext.dispatch(...); + + // have we been forwarded before? + String uri=(String)r.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI); + if (uri!=null) + { + r.setAttribute(AsyncContext.ASYNC_REQUEST_URI,uri); + r.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,r.getAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH)); + r.setAttribute(AsyncContext.ASYNC_SERVLET_PATH,r.getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH)); + r.setAttribute(AsyncContext.ASYNC_PATH_INFO,r.getAttribute(RequestDispatcher.FORWARD_PATH_INFO)); + r.setAttribute(AsyncContext.ASYNC_QUERY_STRING,r.getAttribute(RequestDispatcher.FORWARD_QUERY_STRING)); + } + else + { + r.setAttribute(AsyncContext.ASYNC_REQUEST_URI,r.getRequestURI()); + r.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,r.getContextPath()); + r.setAttribute(AsyncContext.ASYNC_SERVLET_PATH,r.getServletPath()); + r.setAttribute(AsyncContext.ASYNC_PATH_INFO,r.getPathInfo()); + r.setAttribute(AsyncContext.ASYNC_QUERY_STRING,r.getQueryString()); + } + } + } + + public ServletContext getSuspendedContext() + { + return _suspendedContext; + } + + public ServletContext getDispatchContext() + { + return _dispatchContext; + } + + public ServletContext getServletContext() + { + return _dispatchContext==null?_suspendedContext:_dispatchContext; + } + + public void setPath(String path) + { + _pathInContext=path; + } + + /* ------------------------------------------------------------ */ + /** + * @return The path in the context + */ + public String getPath() + { + return _pathInContext; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/AsyncHttpConnection.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,220 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.HttpException; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.io.AsyncEndPoint; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.nio.AsyncConnection; +import org.eclipse.jetty.io.nio.SelectChannelEndPoint; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + + +/* ------------------------------------------------------------ */ +/** Asychronous Server HTTP connection + * + */ +public class AsyncHttpConnection extends AbstractHttpConnection implements AsyncConnection +{ + private final static int NO_PROGRESS_INFO = Integer.getInteger("org.mortbay.jetty.NO_PROGRESS_INFO",100); + private final static int NO_PROGRESS_CLOSE = Integer.getInteger("org.mortbay.jetty.NO_PROGRESS_CLOSE",200); + + private static final Logger LOG = Log.getLogger(AsyncHttpConnection.class); + private int _total_no_progress; + private final AsyncEndPoint _asyncEndp; + private boolean _readInterested = true; + + public AsyncHttpConnection(Connector connector, EndPoint endpoint, Server server) + { + super(connector,endpoint,server); + _asyncEndp=(AsyncEndPoint)endpoint; + } + + @Override + public Connection handle() throws IOException + { + Connection connection = this; + boolean some_progress=false; + boolean progress=true; + + try + { + setCurrentConnection(this); + + // don't check for idle while dispatched (unless blocking IO is done). + _asyncEndp.setCheckForIdle(false); + + + // While progress and the connection has not changed + while (progress && connection==this) + { + progress=false; + try + { + // Handle resumed request + if (_request._async.isAsync()) + { + if (_request._async.isDispatchable()) + handleRequest(); + } + // else Parse more input + else if (!_parser.isComplete() && _parser.parseAvailable()) + progress=true; + + // Generate more output + if (_generator.isCommitted() && !_generator.isComplete() && !_endp.isOutputShutdown() && !_request.getAsyncContinuation().isAsyncStarted()) + if (_generator.flushBuffer()>0) + progress=true; + + // Flush output + _endp.flush(); + + // Has any IO been done by the endpoint itself since last loop + if (_asyncEndp.hasProgressed()) + progress=true; + } + catch (HttpException e) + { + if (LOG.isDebugEnabled()) + { + LOG.debug("uri="+_uri); + LOG.debug("fields="+_requestFields); + LOG.debug(e); + } + progress=true; + _generator.sendError(e.getStatus(), e.getReason(), null, true); + } + finally + { + some_progress|=progress; + // Is this request/response round complete and are fully flushed? + boolean parserComplete = _parser.isComplete(); + boolean generatorComplete = _generator.isComplete(); + boolean complete = parserComplete && generatorComplete; + if (parserComplete) + { + if (generatorComplete) + { + // Reset the parser/generator + progress=true; + + // look for a switched connection instance? + if (_response.getStatus()==HttpStatus.SWITCHING_PROTOCOLS_101) + { + Connection switched=(Connection)_request.getAttribute("org.eclipse.jetty.io.Connection"); + if (switched!=null) + connection=switched; + } + + reset(); + + // TODO Is this still required? + if (!_generator.isPersistent() && !_endp.isOutputShutdown()) + { + LOG.warn("Safety net oshut!!! IF YOU SEE THIS, PLEASE RAISE BUGZILLA"); + _endp.shutdownOutput(); + } + } + else + { + // We have finished parsing, but not generating so + // we must not be interested in reading until we + // have finished generating and we reset the generator + _readInterested = false; + LOG.debug("Disabled read interest while writing response {}", _endp); + } + } + + if (!complete && _request.getAsyncContinuation().isAsyncStarted()) + { + // The request is suspended, so even though progress has been made, + // exit the while loop by setting progress to false + LOG.debug("suspended {}",this); + progress=false; + } + } + } + } + finally + { + setCurrentConnection(null); + + // If we are not suspended + if (!_request.getAsyncContinuation().isAsyncStarted()) + { + // return buffers + _parser.returnBuffers(); + _generator.returnBuffers(); + + // reenable idle checking unless request is suspended + _asyncEndp.setCheckForIdle(true); + } + + // Safety net to catch spinning + if (some_progress) + _total_no_progress=0; + else + { + _total_no_progress++; + if (NO_PROGRESS_INFO>0 && _total_no_progress%NO_PROGRESS_INFO==0 && (NO_PROGRESS_CLOSE<=0 || _total_no_progress< NO_PROGRESS_CLOSE)) + LOG.info("EndPoint making no progress: "+_total_no_progress+" "+_endp+" "+this); + if (NO_PROGRESS_CLOSE>0 && _total_no_progress==NO_PROGRESS_CLOSE) + { + LOG.warn("Closing EndPoint making no progress: "+_total_no_progress+" "+_endp+" "+this); + if (_endp instanceof SelectChannelEndPoint) + ((SelectChannelEndPoint)_endp).getChannel().close(); + } + } + } + return connection; + } + + public void onInputShutdown() throws IOException + { + // If we don't have a committed response and we are not suspended + if (_generator.isIdle() && !_request.getAsyncContinuation().isSuspended()) + { + // then no more can happen, so close. + _endp.close(); + } + + // Make idle parser seek EOF + if (_parser.isIdle()) + _parser.setPersistent(false); + } + + @Override + public void reset() + { + _readInterested = true; + LOG.debug("Enabled read interest {}", _endp); + super.reset(); + } + + @Override + public boolean isSuspended() + { + return !_readInterested || super.isSuspended(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/AsyncNCSARequestLog.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,130 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import org.eclipse.jetty.util.BlockingArrayQueue; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +import java.io.IOException; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + + +/* ------------------------------------------------------------ */ +/** + * An asynchronously writing NCSA Request Log + */ +public class AsyncNCSARequestLog extends NCSARequestLog +{ + private static final Logger LOG = Log.getLogger(AsyncNCSARequestLog.class); + private final BlockingQueue<String> _queue; + private transient WriterThread _thread; + private boolean _warnedFull; + + public AsyncNCSARequestLog() + { + this(null,null); + } + + public AsyncNCSARequestLog(BlockingQueue<String> queue) + { + this(null,queue); + } + + public AsyncNCSARequestLog(String filename) + { + this(filename,null); + } + + public AsyncNCSARequestLog(String filename,BlockingQueue<String> queue) + { + super(filename); + if (queue==null) + queue=new BlockingArrayQueue<String>(1024); + _queue=queue; + } + + private class WriterThread extends Thread + { + WriterThread() + { + setName("AsyncNCSARequestLog@"+Integer.toString(AsyncNCSARequestLog.this.hashCode(),16)); + } + + @Override + public void run() + { + while (isRunning()) + { + try + { + String log = _queue.poll(10,TimeUnit.SECONDS); + if (log!=null) + AsyncNCSARequestLog.super.write(log); + + while(!_queue.isEmpty()) + { + log=_queue.poll(); + if (log!=null) + AsyncNCSARequestLog.super.write(log); + } + } + catch (IOException e) + { + LOG.warn(e); + } + catch (InterruptedException e) + { + LOG.ignore(e); + } + } + } + } + + @Override + protected synchronized void doStart() throws Exception + { + super.doStart(); + _thread = new WriterThread(); + _thread.start(); + } + + @Override + protected void doStop() throws Exception + { + _thread.interrupt(); + _thread.join(); + super.doStop(); + _thread=null; + } + + @Override + protected void write(String log) throws IOException + { + if (!_queue.offer(log)) + { + if (_warnedFull) + LOG.warn("Log Queue overflow"); + _warnedFull=true; + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/Authentication.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,155 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +/* ------------------------------------------------------------ */ +/** The Authentication state of a request. + * <p> + * The Authentication state can be one of several sub-types that + * reflects where the request is in the many different authentication + * cycles. Authentication might not yet be checked or it might be checked + * and failed, checked and deferred or succeeded. + * + */ +public interface Authentication +{ + /* ------------------------------------------------------------ */ + /** A successful Authentication with User information. + */ + public interface User extends Authentication + { + String getAuthMethod(); + UserIdentity getUserIdentity(); + boolean isUserInRole(UserIdentity.Scope scope,String role); + void logout(); + } + + /* ------------------------------------------------------------ */ + /** A wrapped authentication with methods provide the + * wrapped request/response for use by the application + */ + public interface Wrapped extends Authentication + { + HttpServletRequest getHttpServletRequest(); + HttpServletResponse getHttpServletResponse(); + } + + /* ------------------------------------------------------------ */ + /** A deferred authentication with methods to progress + * the authentication process. + */ + public interface Deferred extends Authentication + { + /* ------------------------------------------------------------ */ + /** Authenticate if possible without sending a challenge. + * This is used to check credentials that have been sent for + * non-manditory authentication. + * @return The new Authentication state. + */ + Authentication authenticate(ServletRequest request); + + /* ------------------------------------------------------------ */ + /** Authenticate and possibly send a challenge. + * This is used to initiate authentication for previously + * non-manditory authentication. + * @return The new Authentication state. + */ + Authentication authenticate(ServletRequest request,ServletResponse response); + + + /* ------------------------------------------------------------ */ + /** Login with the LOGIN authenticator + * @param username + * @param password + * @return The new Authentication state + */ + Authentication login(String username,Object password,ServletRequest request); + } + + + /* ------------------------------------------------------------ */ + /** Authentication Response sent state. + * Responses are sent by authenticators either to issue an + * authentication challenge or on successful authentication in + * order to redirect the user to the original URL. + */ + public interface ResponseSent extends Authentication + { + } + + /* ------------------------------------------------------------ */ + /** An Authentication Challenge has been sent. + */ + public interface Challenge extends ResponseSent + { + } + + /* ------------------------------------------------------------ */ + /** An Authentication Failure has been sent. + */ + public interface Failure extends ResponseSent + { + } + + public interface SendSuccess extends ResponseSent + { + } + + /* ------------------------------------------------------------ */ + /** Unauthenticated state. + * <p> + * This convenience instance is for non mandatory authentication where credentials + * have been presented and checked, but failed authentication. + */ + public final static Authentication UNAUTHENTICATED = new Authentication(){@Override + public String toString(){return "UNAUTHENTICATED";}}; + + /* ------------------------------------------------------------ */ + /** Authentication not checked + * <p> + * This convenience instance us for non mandatory authentication when no + * credentials are present to be checked. + */ + public final static Authentication NOT_CHECKED = new Authentication(){@Override + public String toString(){return "NOT CHECKED";}}; + + /* ------------------------------------------------------------ */ + /** Authentication challenge sent. + * <p> + * This convenience instance is for when an authentication challenge has been sent. + */ + public final static Authentication SEND_CONTINUE = new Authentication.Challenge(){@Override + public String toString(){return "CHALLENGE";}}; + + /* ------------------------------------------------------------ */ + /** Authentication failure sent. + * <p> + * This convenience instance is for when an authentication failure has been sent. + */ + public final static Authentication SEND_FAILURE = new Authentication.Failure(){@Override + public String toString(){return "FAILURE";}}; + public final static Authentication SEND_SUCCESS = new SendSuccess(){@Override + public String toString(){return "SEND_SUCCESS";}}; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/BlockingHttpConnection.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,138 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.Generator; +import org.eclipse.jetty.http.HttpException; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.Parser; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + + +/* ------------------------------------------------------------ */ +/** Blocking Server HTTP Connection + */ +public class BlockingHttpConnection extends AbstractHttpConnection +{ + private static final Logger LOG = Log.getLogger(BlockingHttpConnection.class); + + public BlockingHttpConnection(Connector connector, EndPoint endpoint, Server server) + { + super(connector,endpoint,server); + } + + public BlockingHttpConnection(Connector connector, EndPoint endpoint, Server server, Parser parser, Generator generator, Request request) + { + super(connector,endpoint,server,parser,generator,request); + } + + @Override + protected void handleRequest() throws IOException + { + super.handleRequest(); + } + + public Connection handle() throws IOException + { + Connection connection = this; + + try + { + setCurrentConnection(this); + + // do while the endpoint is open + // AND the connection has not changed + while (_endp.isOpen() && connection==this) + { + try + { + // If we are not ended then parse available + if (!_parser.isComplete() && !_endp.isInputShutdown()) + _parser.parseAvailable(); + + // Do we have more generating to do? + // Loop here because some writes may take multiple steps and + // we need to flush them all before potentially blocking in the + // next loop. + if (_generator.isCommitted() && !_generator.isComplete() && !_endp.isOutputShutdown()) + _generator.flushBuffer(); + + // Flush buffers + _endp.flush(); + } + catch (HttpException e) + { + if (LOG.isDebugEnabled()) + { + LOG.debug("uri="+_uri); + LOG.debug("fields="+_requestFields); + LOG.debug(e); + } + _generator.sendError(e.getStatus(), e.getReason(), null, true); + _parser.reset(); + _endp.shutdownOutput(); + } + finally + { + // Is this request/response round complete and are fully flushed? + if (_parser.isComplete() && _generator.isComplete()) + { + // Reset the parser/generator + reset(); + + // look for a switched connection instance? + if (_response.getStatus()==HttpStatus.SWITCHING_PROTOCOLS_101) + { + Connection switched=(Connection)_request.getAttribute("org.eclipse.jetty.io.Connection"); + if (switched!=null) + connection=switched; + } + + // TODO Is this required? + if (!_generator.isPersistent() && !_endp.isOutputShutdown()) + { + LOG.warn("Safety net oshut!!! Please open a bugzilla"); + _endp.shutdownOutput(); + } + } + + // If we don't have a committed response and we are not suspended + if (_endp.isInputShutdown() && _generator.isIdle() && !_request.getAsyncContinuation().isSuspended()) + { + // then no more can happen, so close. + _endp.close(); + } + } + } + + return connection; + } + finally + { + setCurrentConnection(null); + _parser.returnBuffers(); + _generator.returnBuffers(); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/Connector.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,387 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.io.IOException; + +import org.eclipse.jetty.io.Buffers; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.util.thread.ThreadPool; + +/** HTTP Connector. + * Implementations of this interface provide connectors for the HTTP protocol. + * A connector receives requests (normally from a socket) and calls the + * handle method of the Handler object. These operations are performed using + * threads from the ThreadPool set on the connector. + * + * When a connector is registered with an instance of Server, then the server + * will set itself as both the ThreadPool and the Handler. Note that a connector + * can be used without a Server if a thread pool and handler are directly provided. + * + * + * + */ +/** + * @author gregw + * + */ +public interface Connector extends LifeCycle +{ + /* ------------------------------------------------------------ */ + /** + * @return the name of the connector. Defaults to the HostName:port + */ + String getName(); + + /* ------------------------------------------------------------ */ + /** + * Opens the connector + * @throws IOException + */ + void open() throws IOException; + + /* ------------------------------------------------------------ */ + void close() throws IOException; + + /* ------------------------------------------------------------ */ + void setServer(Server server); + + /* ------------------------------------------------------------ */ + Server getServer(); + + /* ------------------------------------------------------------ */ + /** + * @return Returns the request header buffer size in bytes. + */ + int getRequestHeaderSize(); + + /* ------------------------------------------------------------ */ + /** + * Set the size of the buffer to be used for request headers. + * @param size The size in bytes. + */ + void setRequestHeaderSize(int size); + + /* ------------------------------------------------------------ */ + /** + * @return Returns the response header buffer size in bytes. + */ + int getResponseHeaderSize(); + + /* ------------------------------------------------------------ */ + /** + * Set the size of the buffer to be used for request headers. + * @param size The size in bytes. + */ + void setResponseHeaderSize(int size); + + + /* ------------------------------------------------------------ */ + /** + * @return factory for request buffers + */ + Buffers getRequestBuffers(); + + /* ------------------------------------------------------------ */ + /** + * @return factory for response buffers + */ + Buffers getResponseBuffers(); + + + /* ------------------------------------------------------------ */ + /** + * @return Returns the requestBufferSize. + */ + int getRequestBufferSize(); + + /* ------------------------------------------------------------ */ + /** + * Set the size of the content buffer for receiving requests. + * These buffers are only used for active connections that have + * requests with bodies that will not fit within the header buffer. + * @param requestBufferSize The requestBufferSize to set. + */ + void setRequestBufferSize(int requestBufferSize); + + /* ------------------------------------------------------------ */ + /** + * @return Returns the responseBufferSize. + */ + int getResponseBufferSize(); + + /* ------------------------------------------------------------ */ + /** + * Set the size of the content buffer for sending responses. + * These buffers are only used for active connections that are sending + * responses with bodies that will not fit within the header buffer. + * @param responseBufferSize The responseBufferSize to set. + */ + void setResponseBufferSize(int responseBufferSize); + + + /* ------------------------------------------------------------ */ + /** + * @return The port to use when redirecting a request if a data constraint of integral is + * required. See {@link org.eclipse.jetty.util.security.Constraint#getDataConstraint()} + */ + int getIntegralPort(); + + /* ------------------------------------------------------------ */ + /** + * @return The schema to use when redirecting a request if a data constraint of integral is + * required. See {@link org.eclipse.jetty.util.security.Constraint#getDataConstraint()} + */ + String getIntegralScheme(); + + /* ------------------------------------------------------------ */ + /** + * @param request A request + * @return true if the request is integral. This normally means the https schema has been used. + */ + boolean isIntegral(Request request); + + /* ------------------------------------------------------------ */ + /** + * @return The port to use when redirecting a request if a data constraint of confidential is + * required. See {@link org.eclipse.jetty.util.security.Constraint#getDataConstraint()} + */ + int getConfidentialPort(); + + + /* ------------------------------------------------------------ */ + /** + * @return The schema to use when redirecting a request if a data constraint of confidential is + * required. See {@link org.eclipse.jetty.util.security.Constraint#getDataConstraint()} + */ + String getConfidentialScheme(); + + /* ------------------------------------------------------------ */ + /** + * @param request A request + * @return true if the request is confidential. This normally means the https schema has been used. + */ + boolean isConfidential(Request request); + + /* ------------------------------------------------------------ */ + /** Customize a request for an endpoint. + * Called on every request to allow customization of the request for + * the particular endpoint (eg security properties from a SSL connection). + * @param endpoint + * @param request + * @throws IOException + */ + void customize(EndPoint endpoint, Request request) throws IOException; + + /* ------------------------------------------------------------ */ + /** Persist an endpoint. + * Called after every request if the connection is to remain open. + * @param endpoint + * @throws IOException + */ + void persist(EndPoint endpoint) throws IOException; + + /* ------------------------------------------------------------ */ + /** + * @return The hostname representing the interface to which + * this connector will bind, or null for all interfaces. + */ + String getHost(); + + /* ------------------------------------------------------------ */ + /** + * Set the hostname of the interface to bind to. + * @param hostname The hostname representing the interface to which + * this connector will bind, or null for all interfaces. + */ + void setHost(String hostname); + + /* ------------------------------------------------------------ */ + /** + * @param port The port to listen of for connections or 0 if any available + * port may be used. + */ + void setPort(int port); + + /* ------------------------------------------------------------ */ + /** + * @return The configured port for the connector or 0 if any available + * port may be used. + */ + int getPort(); + + /* ------------------------------------------------------------ */ + /** + * @return The actual port the connector is listening on or + * -1 if it has not been opened, or -2 if it has been closed. + */ + int getLocalPort(); + + /* ------------------------------------------------------------ */ + /** + * @return Max Idle time for connections in milliseconds + */ + int getMaxIdleTime(); + + /** + * @param ms Max Idle time for connections in milliseconds + */ + void setMaxIdleTime(int ms); + + /* ------------------------------------------------------------ */ + int getLowResourceMaxIdleTime(); + void setLowResourceMaxIdleTime(int ms); + + /* ------------------------------------------------------------ */ + /** + * @return the underlying socket, channel, buffer etc. for the connector. + */ + Object getConnection(); + + + /* ------------------------------------------------------------ */ + /** + * @return true if names resolution should be done. + */ + boolean getResolveNames(); + + + + /* ------------------------------------------------------------ */ + /** + * @return Get the number of requests handled by this connector + * since last call of statsReset(). If setStatsOn(false) then this + * is undefined. + */ + public int getRequests(); + + /* ------------------------------------------------------------ */ + /** + * @return Returns the connectionsDurationTotal. + */ + public long getConnectionsDurationTotal(); + + /* ------------------------------------------------------------ */ + /** + * @return Number of connections accepted by the server since + * statsReset() called. Undefined if setStatsOn(false). + */ + public int getConnections() ; + + /* ------------------------------------------------------------ */ + /** + * @return Number of connections currently open that were opened + * since statsReset() called. Undefined if setStatsOn(false). + */ + public int getConnectionsOpen() ; + + /* ------------------------------------------------------------ */ + /** + * @return Maximum number of connections opened simultaneously + * since statsReset() called. Undefined if setStatsOn(false). + */ + public int getConnectionsOpenMax() ; + + /* ------------------------------------------------------------ */ + /** + * @return Maximum duration in milliseconds of an open connection + * since statsReset() called. Undefined if setStatsOn(false). + */ + public long getConnectionsDurationMax(); + + /* ------------------------------------------------------------ */ + /** + * @return Mean duration in milliseconds of open connections + * since statsReset() called. Undefined if setStatsOn(false). + */ + public double getConnectionsDurationMean() ; + + /* ------------------------------------------------------------ */ + /** + * @return Standard deviation of duration in milliseconds of + * open connections since statsReset() called. Undefined if + * setStatsOn(false). + */ + public double getConnectionsDurationStdDev() ; + + /* ------------------------------------------------------------ */ + /** + * @return Mean number of requests per connection + * since statsReset() called. Undefined if setStatsOn(false). + */ + public double getConnectionsRequestsMean() ; + + /* ------------------------------------------------------------ */ + /** + * @return Standard Deviation of number of requests per connection + * since statsReset() called. Undefined if setStatsOn(false). + */ + public double getConnectionsRequestsStdDev() ; + + /* ------------------------------------------------------------ */ + /** + * @return Maximum number of requests per connection + * since statsReset() called. Undefined if setStatsOn(false). + */ + public int getConnectionsRequestsMax(); + + /* ------------------------------------------------------------ */ + /** Reset statistics. + */ + public void statsReset(); + + /* ------------------------------------------------------------ */ + public void setStatsOn(boolean on); + + /* ------------------------------------------------------------ */ + /** + * @return True if statistics collection is turned on. + */ + public boolean getStatsOn(); + + /* ------------------------------------------------------------ */ + /** + * @return Timestamp stats were started at. + */ + public long getStatsOnMs(); + + + /* ------------------------------------------------------------ */ + /** Check if low on resources. + * For most connectors, low resources is measured by calling + * {@link ThreadPool#isLowOnThreads()} on the connector threadpool + * or the server threadpool if there is no connector threadpool. + * <p> + * For blocking connectors, low resources is used to trigger + * usage of {@link #getLowResourceMaxIdleTime()} for the timeout + * of an idle connection. + * <p> + * for non-blocking connectors, the number of connections is + * used instead of this method, to select the timeout of an + * idle connection. + * <p> + * For all connectors, low resources is used to trigger the + * usage of {@link #getLowResourceMaxIdleTime()} for read and + * write operations. + * + * @return true if this connector is low on resources. + */ + public boolean isLowResources(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/CookieCutter.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,335 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server; +import java.util.Locale; + +import javax.servlet.http.Cookie; + +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.QuotedStringTokenizer; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + + +/* ------------------------------------------------------------ */ +/** Cookie parser + * <p>Optimized stateful cookie parser. Cookies fields are added with the + * {@link #addCookieField(String)} method and parsed on the next subsequent + * call to {@link #getCookies()}. + * If the added fields are identical to those last added (as strings), then the + * cookies are not re parsed. + * + * + */ +public class CookieCutter +{ + private static final Logger LOG = Log.getLogger(CookieCutter.class); + + + private Cookie[] _cookies; + private Cookie[] _lastCookies; + Object _lazyFields; + int _fields; + + public CookieCutter() + { + } + + public Cookie[] getCookies() + { + if (_cookies!=null) + return _cookies; + + if (_lastCookies!=null && + _lazyFields!=null && + _fields==LazyList.size(_lazyFields)) + _cookies=_lastCookies; + else + parseFields(); + _lastCookies=_cookies; + return _cookies; + } + + public void setCookies(Cookie[] cookies) + { + _cookies=cookies; + _lastCookies=null; + _lazyFields=null; + _fields=0; + } + + public void reset() + { + _cookies=null; + _fields=0; + } + + public void addCookieField(String f) + { + if (f==null) + return; + f=f.trim(); + if (f.length()==0) + return; + + if (LazyList.size(_lazyFields)>_fields) + { + if (f.equals(LazyList.get(_lazyFields,_fields))) + { + _fields++; + return; + } + + while (LazyList.size(_lazyFields)>_fields) + _lazyFields=LazyList.remove(_lazyFields,_fields); + } + _cookies=null; + _lastCookies=null; + _lazyFields=LazyList.add(_lazyFields,_fields++,f); + } + + + protected void parseFields() + { + _lastCookies=null; + _cookies=null; + + Object cookies = null; + + int version = 0; + + // delete excess fields + while (LazyList.size(_lazyFields)>_fields) + _lazyFields=LazyList.remove(_lazyFields,_fields); + + // For each cookie field + for (int f=0;f<_fields;f++) + { + String hdr = LazyList.get(_lazyFields,f); + + // Parse the header + String name = null; + String value = null; + + Cookie cookie = null; + + boolean invalue=false; + boolean quoted=false; + boolean escaped=false; + int tokenstart=-1; + int tokenend=-1; + for (int i = 0, length = hdr.length(), last=length-1; i < length; i++) + { + char c = hdr.charAt(i); + + // Handle quoted values for name or value + if (quoted) + { + if (escaped) + { + escaped=false; + continue; + } + + switch (c) + { + case '"': + tokenend=i; + quoted=false; + + // handle quote as last character specially + if (i==last) + { + if (invalue) + value = hdr.substring(tokenstart, tokenend+1); + else + { + name = hdr.substring(tokenstart, tokenend+1); + value = ""; + } + } + break; + + case '\\': + escaped=true; + continue; + default: + continue; + } + } + else + { + // Handle name and value state machines + if (invalue) + { + // parse the value + switch (c) + { + case ' ': + case '\t': + continue; + + case '"': + if (tokenstart<0) + { + quoted=true; + tokenstart=i; + } + tokenend=i; + if (i==last) + { + value = hdr.substring(tokenstart, tokenend+1); + break; + } + continue; + + case ';': + // case ',': + if (tokenstart>=0) + value = hdr.substring(tokenstart, tokenend+1); + else + value=""; + tokenstart = -1; + invalue=false; + break; + + default: + if (tokenstart<0) + tokenstart=i; + tokenend=i; + if (i==last) + { + value = hdr.substring(tokenstart, tokenend+1); + break; + } + continue; + } + } + else + { + // parse the name + switch (c) + { + case ' ': + case '\t': + continue; + + case '"': + if (tokenstart<0) + { + quoted=true; + tokenstart=i; + } + tokenend=i; + if (i==last) + { + name = hdr.substring(tokenstart, tokenend+1); + value = ""; + break; + } + continue; + + case ';': + // case ',': + if (tokenstart>=0) + { + name = hdr.substring(tokenstart, tokenend+1); + value = ""; + } + tokenstart = -1; + break; + + case '=': + if (tokenstart>=0) + name = hdr.substring(tokenstart, tokenend+1); + tokenstart = -1; + invalue=true; + continue; + + default: + if (tokenstart<0) + tokenstart=i; + tokenend=i; + if (i==last) + { + name = hdr.substring(tokenstart, tokenend+1); + value = ""; + break; + } + continue; + } + } + } + + // If after processing the current character we have a value and a name, then it is a cookie + if (value!=null && name!=null) + { + // TODO handle unquoting during parsing! But quoting is uncommon + name=QuotedStringTokenizer.unquoteOnly(name); + value=QuotedStringTokenizer.unquoteOnly(value); + + try + { + if (name.startsWith("$")) + { + String lowercaseName = name.toLowerCase(Locale.ENGLISH); + if ("$path".equals(lowercaseName)) + { + if (cookie!=null) + cookie.setPath(value); + } + else if ("$domain".equals(lowercaseName)) + { + if (cookie!=null) + cookie.setDomain(value); + } + else if ("$port".equals(lowercaseName)) + { + if (cookie!=null) + cookie.setComment("$port="+value); + } + else if ("$version".equals(lowercaseName)) + { + version = Integer.parseInt(value); + } + } + else + { + cookie = new Cookie(name, value); + if (version > 0) + cookie.setVersion(version); + cookies = LazyList.add(cookies, cookie); + } + } + catch (Exception e) + { + LOG.debug(e); + } + + name = null; + value = null; + } + } + } + + _cookies = (Cookie[]) LazyList.toArray(cookies,Cookie.class); + _lastCookies=_cookies; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/Dispatcher.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,550 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.io.IOException; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; + +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.Attributes; +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.MultiMap; +import org.eclipse.jetty.util.UrlEncoded; + +/* ------------------------------------------------------------ */ +/** Servlet RequestDispatcher. + * + * + */ +public class Dispatcher implements RequestDispatcher +{ + /** Dispatch include attribute names */ + public final static String __INCLUDE_PREFIX="javax.servlet.include."; + + /** Dispatch include attribute names */ + public final static String __FORWARD_PREFIX="javax.servlet.forward."; + + /** JSP attributes */ + public final static String __JSP_FILE="org.apache.catalina.jsp_file"; + + /* ------------------------------------------------------------ */ + private final ContextHandler _contextHandler; + private final String _uri; + private final String _path; + private final String _dQuery; + private final String _named; + + /* ------------------------------------------------------------ */ + /** + * @param contextHandler + * @param uri + * @param pathInContext + * @param query + */ + public Dispatcher(ContextHandler contextHandler, String uri, String pathInContext, String query) + { + _contextHandler=contextHandler; + _uri=uri; + _path=pathInContext; + _dQuery=query; + _named=null; + } + + + /* ------------------------------------------------------------ */ + /** Constructor. + * @param contextHandler + * @param name + */ + public Dispatcher(ContextHandler contextHandler,String name) + throws IllegalStateException + { + _contextHandler=contextHandler; + _named=name; + _uri=null; + _path=null; + _dQuery=null; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.RequestDispatcher#forward(javax.servlet.ServletRequest, javax.servlet.ServletResponse) + */ + public void forward(ServletRequest request, ServletResponse response) throws ServletException, IOException + { + forward(request, response, DispatcherType.FORWARD); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.RequestDispatcher#forward(javax.servlet.ServletRequest, javax.servlet.ServletResponse) + */ + public void error(ServletRequest request, ServletResponse response) throws ServletException, IOException + { + forward(request, response, DispatcherType.ERROR); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.RequestDispatcher#include(javax.servlet.ServletRequest, javax.servlet.ServletResponse) + */ + public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException + { + Request baseRequest=(request instanceof Request)?((Request)request):AbstractHttpConnection.getCurrentConnection().getRequest(); + + + if (!(request instanceof HttpServletRequest)) + request = new ServletRequestHttpWrapper(request); + if (!(response instanceof HttpServletResponse)) + response = new ServletResponseHttpWrapper(response); + + + // TODO - allow stream or writer???? + + final DispatcherType old_type = baseRequest.getDispatcherType(); + final Attributes old_attr=baseRequest.getAttributes(); + MultiMap old_params=baseRequest.getParameters(); + try + { + baseRequest.setDispatcherType(DispatcherType.INCLUDE); + baseRequest.getConnection().include(); + if (_named!=null) + _contextHandler.handle(_named,baseRequest, (HttpServletRequest)request, (HttpServletResponse)response); + else + { + String query=_dQuery; + + if (query!=null) + { + // force parameter extraction + if (old_params==null) + { + baseRequest.extractParameters(); + old_params=baseRequest.getParameters(); + } + + MultiMap parameters=new MultiMap(); + UrlEncoded.decodeTo(query,parameters,baseRequest.getCharacterEncoding()); + + if (old_params!=null && old_params.size()>0) + { + // Merge parameters. + Iterator iter = old_params.entrySet().iterator(); + while (iter.hasNext()) + { + Map.Entry entry = (Map.Entry)iter.next(); + String name=(String)entry.getKey(); + Object values=entry.getValue(); + for (int i=0;i<LazyList.size(values);i++) + parameters.add(name, LazyList.get(values, i)); + } + } + baseRequest.setParameters(parameters); + } + + IncludeAttributes attr = new IncludeAttributes(old_attr); + + attr._requestURI=_uri; + attr._contextPath=_contextHandler.getContextPath(); + attr._servletPath=null; // set by ServletHandler + attr._pathInfo=_path; + attr._query=query; + + baseRequest.setAttributes(attr); + + _contextHandler.handle(_path,baseRequest, (HttpServletRequest)request, (HttpServletResponse)response); + } + } + finally + { + baseRequest.setAttributes(old_attr); + baseRequest.getConnection().included(); + baseRequest.setParameters(old_params); + baseRequest.setDispatcherType(old_type); + } + } + + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.RequestDispatcher#forward(javax.servlet.ServletRequest, javax.servlet.ServletResponse) + */ + protected void forward(ServletRequest request, ServletResponse response, DispatcherType dispatch) throws ServletException, IOException + { + Request baseRequest=(request instanceof Request)?((Request)request):AbstractHttpConnection.getCurrentConnection().getRequest(); + Response base_response=baseRequest.getResponse(); + response.resetBuffer(); + base_response.fwdReset(); + + + if (!(request instanceof HttpServletRequest)) + request = new ServletRequestHttpWrapper(request); + if (!(response instanceof HttpServletResponse)) + response = new ServletResponseHttpWrapper(response); + + final boolean old_handled=baseRequest.isHandled(); + final String old_uri=baseRequest.getRequestURI(); + final String old_context_path=baseRequest.getContextPath(); + final String old_servlet_path=baseRequest.getServletPath(); + final String old_path_info=baseRequest.getPathInfo(); + final String old_query=baseRequest.getQueryString(); + final Attributes old_attr=baseRequest.getAttributes(); + final DispatcherType old_type=baseRequest.getDispatcherType(); + MultiMap<String> old_params=baseRequest.getParameters(); + + try + { + baseRequest.setHandled(false); + baseRequest.setDispatcherType(dispatch); + + if (_named!=null) + _contextHandler.handle(_named,baseRequest, (HttpServletRequest)request, (HttpServletResponse)response); + else + { + + // process any query string from the dispatch URL + String query=_dQuery; + if (query!=null) + { + // force parameter extraction + if (old_params==null) + { + baseRequest.extractParameters(); + old_params=baseRequest.getParameters(); + } + + baseRequest.mergeQueryString(query); + } + + ForwardAttributes attr = new ForwardAttributes(old_attr); + + //If we have already been forwarded previously, then keep using the established + //original value. Otherwise, this is the first forward and we need to establish the values. + //Note: the established value on the original request for pathInfo and + //for queryString is allowed to be null, but cannot be null for the other values. + if (old_attr.getAttribute(FORWARD_REQUEST_URI) != null) + { + attr._pathInfo=(String)old_attr.getAttribute(FORWARD_PATH_INFO); + attr._query=(String)old_attr.getAttribute(FORWARD_QUERY_STRING); + attr._requestURI=(String)old_attr.getAttribute(FORWARD_REQUEST_URI); + attr._contextPath=(String)old_attr.getAttribute(FORWARD_CONTEXT_PATH); + attr._servletPath=(String)old_attr.getAttribute(FORWARD_SERVLET_PATH); + } + else + { + attr._pathInfo=old_path_info; + attr._query=old_query; + attr._requestURI=old_uri; + attr._contextPath=old_context_path; + attr._servletPath=old_servlet_path; + } + + baseRequest.setRequestURI(_uri); + baseRequest.setContextPath(_contextHandler.getContextPath()); + baseRequest.setServletPath(null); + baseRequest.setPathInfo(_uri); + baseRequest.setAttributes(attr); + + _contextHandler.handle(_path,baseRequest, (HttpServletRequest)request, (HttpServletResponse)response); + + if (!baseRequest.getAsyncContinuation().isAsyncStarted()) + commitResponse(response,baseRequest); + } + } + finally + { + baseRequest.setHandled(old_handled); + baseRequest.setRequestURI(old_uri); + baseRequest.setContextPath(old_context_path); + baseRequest.setServletPath(old_servlet_path); + baseRequest.setPathInfo(old_path_info); + baseRequest.setAttributes(old_attr); + baseRequest.setParameters(old_params); + baseRequest.setQueryString(old_query); + baseRequest.setDispatcherType(old_type); + } + } + + + /* ------------------------------------------------------------ */ + private void commitResponse(ServletResponse response, Request baseRequest) throws IOException + { + if (baseRequest.getResponse().isWriting()) + { + try + { + response.getWriter().close(); + } + catch (IllegalStateException e) + { + response.getOutputStream().close(); + } + } + else + { + try + { + response.getOutputStream().close(); + } + catch (IllegalStateException e) + { + response.getWriter().close(); + } + } + } + + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + private class ForwardAttributes implements Attributes + { + final Attributes _attr; + + String _requestURI; + String _contextPath; + String _servletPath; + String _pathInfo; + String _query; + + ForwardAttributes(Attributes attributes) + { + _attr=attributes; + } + + /* ------------------------------------------------------------ */ + public Object getAttribute(String key) + { + if (Dispatcher.this._named==null) + { + if (key.equals(FORWARD_PATH_INFO)) + return _pathInfo; + if (key.equals(FORWARD_REQUEST_URI)) + return _requestURI; + if (key.equals(FORWARD_SERVLET_PATH)) + return _servletPath; + if (key.equals(FORWARD_CONTEXT_PATH)) + return _contextPath; + if (key.equals(FORWARD_QUERY_STRING)) + return _query; + } + + if (key.startsWith(__INCLUDE_PREFIX)) + return null; + + return _attr.getAttribute(key); + } + + /* ------------------------------------------------------------ */ + public Enumeration getAttributeNames() + { + HashSet set=new HashSet(); + Enumeration e=_attr.getAttributeNames(); + while(e.hasMoreElements()) + { + String name=(String)e.nextElement(); + if (!name.startsWith(__INCLUDE_PREFIX) && + !name.startsWith(__FORWARD_PREFIX)) + set.add(name); + } + + if (_named==null) + { + if (_pathInfo!=null) + set.add(FORWARD_PATH_INFO); + else + set.remove(FORWARD_PATH_INFO); + set.add(FORWARD_REQUEST_URI); + set.add(FORWARD_SERVLET_PATH); + set.add(FORWARD_CONTEXT_PATH); + if (_query!=null) + set.add(FORWARD_QUERY_STRING); + else + set.remove(FORWARD_QUERY_STRING); + } + + return Collections.enumeration(set); + } + + /* ------------------------------------------------------------ */ + public void setAttribute(String key, Object value) + { + if (_named==null && key.startsWith("javax.servlet.")) + { + if (key.equals(FORWARD_PATH_INFO)) + _pathInfo=(String)value; + else if (key.equals(FORWARD_REQUEST_URI)) + _requestURI=(String)value; + else if (key.equals(FORWARD_SERVLET_PATH)) + _servletPath=(String)value; + else if (key.equals(FORWARD_CONTEXT_PATH)) + _contextPath=(String)value; + else if (key.equals(FORWARD_QUERY_STRING)) + _query=(String)value; + + else if (value==null) + _attr.removeAttribute(key); + else + _attr.setAttribute(key,value); + } + else if (value==null) + _attr.removeAttribute(key); + else + _attr.setAttribute(key,value); + } + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + return "FORWARD+"+_attr.toString(); + } + + /* ------------------------------------------------------------ */ + public void clearAttributes() + { + throw new IllegalStateException(); + } + + /* ------------------------------------------------------------ */ + public void removeAttribute(String name) + { + setAttribute(name,null); + } + } + + /* ------------------------------------------------------------ */ + private class IncludeAttributes implements Attributes + { + final Attributes _attr; + + String _requestURI; + String _contextPath; + String _servletPath; + String _pathInfo; + String _query; + + IncludeAttributes(Attributes attributes) + { + _attr=attributes; + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + public Object getAttribute(String key) + { + if (Dispatcher.this._named==null) + { + if (key.equals(INCLUDE_PATH_INFO)) return _pathInfo; + if (key.equals(INCLUDE_SERVLET_PATH)) return _servletPath; + if (key.equals(INCLUDE_CONTEXT_PATH)) return _contextPath; + if (key.equals(INCLUDE_QUERY_STRING)) return _query; + if (key.equals(INCLUDE_REQUEST_URI)) return _requestURI; + } + else if (key.startsWith(__INCLUDE_PREFIX)) + return null; + + + return _attr.getAttribute(key); + } + + /* ------------------------------------------------------------ */ + public Enumeration getAttributeNames() + { + HashSet set=new HashSet(); + Enumeration e=_attr.getAttributeNames(); + while(e.hasMoreElements()) + { + String name=(String)e.nextElement(); + if (!name.startsWith(__INCLUDE_PREFIX)) + set.add(name); + } + + if (_named==null) + { + if (_pathInfo!=null) + set.add(INCLUDE_PATH_INFO); + else + set.remove(INCLUDE_PATH_INFO); + set.add(INCLUDE_REQUEST_URI); + set.add(INCLUDE_SERVLET_PATH); + set.add(INCLUDE_CONTEXT_PATH); + if (_query!=null) + set.add(INCLUDE_QUERY_STRING); + else + set.remove(INCLUDE_QUERY_STRING); + } + + return Collections.enumeration(set); + } + + /* ------------------------------------------------------------ */ + public void setAttribute(String key, Object value) + { + if (_named==null && key.startsWith("javax.servlet.")) + { + if (key.equals(INCLUDE_PATH_INFO)) _pathInfo=(String)value; + else if (key.equals(INCLUDE_REQUEST_URI)) _requestURI=(String)value; + else if (key.equals(INCLUDE_SERVLET_PATH)) _servletPath=(String)value; + else if (key.equals(INCLUDE_CONTEXT_PATH)) _contextPath=(String)value; + else if (key.equals(INCLUDE_QUERY_STRING)) _query=(String)value; + else if (value==null) + _attr.removeAttribute(key); + else + _attr.setAttribute(key,value); + } + else if (value==null) + _attr.removeAttribute(key); + else + _attr.setAttribute(key,value); + } + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + return "INCLUDE+"+_attr.toString(); + } + + /* ------------------------------------------------------------ */ + public void clearAttributes() + { + throw new IllegalStateException(); + } + + /* ------------------------------------------------------------ */ + public void removeAttribute(String name) + { + setAttribute(name,null); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/Handler.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,72 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.server.handler.HandlerWrapper; +import org.eclipse.jetty.util.component.Destroyable; +import org.eclipse.jetty.util.component.LifeCycle; + +/* ------------------------------------------------------------ */ +/** A Jetty Server Handler. + * + * A Handler instance is required by a {@link Server} to handle incoming + * HTTP requests. A Handler may: <ul> + * <li>Completely generate the HTTP Response</li> + * <li>Examine/modify the request and call another Handler (see {@link HandlerWrapper}). + * <li>Pass the request to one or more other Handlers (see {@link HandlerCollection}). + * </ul> + * + * Handlers are passed the servlet API request and response object, but are + * not Servlets. The servlet container is implemented by handlers for + * context, security, session and servlet that modify the request object + * before passing it to the next stage of handling. + * + */ +public interface Handler extends LifeCycle, Destroyable +{ + /* ------------------------------------------------------------ */ + /** Handle a request. + * @param target The target of the request - either a URI or a name. + * @param baseRequest The original unwrapped request object. + * @param request The request either as the {@link Request} + * object or a wrapper of that request. The {@link AbstractHttpConnection#getCurrentConnection()} + * method can be used access the Request object if required. + * @param response The response as the {@link Response} + * object or a wrapper of that request. The {@link AbstractHttpConnection#getCurrentConnection()} + * method can be used access the Response object if required. + * @throws IOException + * @throws ServletException + */ + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException; + + public void setServer(Server server); + public Server getServer(); + + public void destroy(); + +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/HandlerContainer.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,57 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import org.eclipse.jetty.util.component.LifeCycle; + +/** + * A Handler that contains other Handlers. + * <p> + * The contained handlers may be one (see @{link {@link org.eclipse.jetty.server.handler.HandlerWrapper}) + * or many (see {@link org.eclipse.jetty.server.handler.HandlerList} or {@link org.eclipse.jetty.server.handler.HandlerCollection}. + * + */ +public interface HandlerContainer extends LifeCycle +{ + /* ------------------------------------------------------------ */ + /** + * @return array of handlers directly contained by this handler. + */ + public Handler[] getHandlers(); + + /* ------------------------------------------------------------ */ + /** + * @return array of all handlers contained by this handler and it's children + */ + public Handler[] getChildHandlers(); + + /* ------------------------------------------------------------ */ + /** + * @param byclass + * @return array of all handlers contained by this handler and it's children of the passed type. + */ + public Handler[] getChildHandlersByClass(Class<?> byclass); + + /* ------------------------------------------------------------ */ + /** + * @param byclass + * @return first handler of all handlers contained by this handler and it's children of the passed type. + */ + public <T extends Handler> T getChildHandlerByClass(Class<T> byclass); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/HttpInput.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,75 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.io.IOException; + +import javax.servlet.ServletInputStream; + +import org.eclipse.jetty.http.HttpParser; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.EofException; + +public class HttpInput extends ServletInputStream +{ + protected final AbstractHttpConnection _connection; + protected final HttpParser _parser; + + /* ------------------------------------------------------------ */ + public HttpInput(AbstractHttpConnection connection) + { + _connection=connection; + _parser=(HttpParser)connection.getParser(); + } + + /* ------------------------------------------------------------ */ + /* + * @see java.io.InputStream#read() + */ + @Override + public int read() throws IOException + { + byte[] bytes = new byte[1]; + int read = read(bytes, 0, 1); + return read < 0 ? -1 : 0xff & bytes[0]; + } + + /* ------------------------------------------------------------ */ + /* + * @see java.io.InputStream#read(byte[], int, int) + */ + @Override + public int read(byte[] b, int off, int len) throws IOException + { + int l=-1; + Buffer content=_parser.blockForContent(_connection.getMaxIdleTime()); + if (content!=null) + l= content.get(b, off, len); + else if (_connection.isEarlyEOF()) + throw new EofException("early EOF"); + return l; + } + + /* ------------------------------------------------------------ */ + @Override + public int available() throws IOException + { + return _parser.available(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/HttpOutput.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,183 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.io.IOException; +import java.io.Writer; + +import javax.servlet.ServletOutputStream; + +import org.eclipse.jetty.http.AbstractGenerator; +import org.eclipse.jetty.http.Generator; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.util.ByteArrayOutputStream2; + +/** Output. + * + * <p> + * Implements {@link javax.servlet.ServletOutputStream} from the <code>javax.servlet</code> package. + * </p> + * A {@link ServletOutputStream} implementation that writes content + * to a {@link AbstractGenerator}. The class is designed to be reused + * and can be reopened after a close. + */ +public class HttpOutput extends ServletOutputStream +{ + protected final AbstractHttpConnection _connection; + protected final AbstractGenerator _generator; + private boolean _closed; + private ByteArrayBuffer _onebyte; + + // These are held here for reuse by Writer + String _characterEncoding; + Writer _converter; + char[] _chars; + ByteArrayOutputStream2 _bytes; + + /* ------------------------------------------------------------ */ + public HttpOutput(AbstractHttpConnection connection) + { + _connection=connection; + _generator=(AbstractGenerator)connection.getGenerator(); + } + + /* ------------------------------------------------------------ */ + public int getMaxIdleTime() + { + return _connection.getMaxIdleTime(); + } + + /* ------------------------------------------------------------ */ + public boolean isWritten() + { + return _generator.getContentWritten()>0; + } + + /* ------------------------------------------------------------ */ + /* + * @see java.io.OutputStream#close() + */ + @Override + public void close() throws IOException + { + _closed=true; + } + + /* ------------------------------------------------------------ */ + public boolean isClosed() + { + return _closed; + } + + /* ------------------------------------------------------------ */ + public void reopen() + { + _closed=false; + } + + /* ------------------------------------------------------------ */ + @Override + public void flush() throws IOException + { + _generator.flush(getMaxIdleTime()); + } + + /* ------------------------------------------------------------ */ + @Override + public void write(byte[] b, int off, int len) throws IOException + { + write(new ByteArrayBuffer(b,off,len)); + } + + /* ------------------------------------------------------------ */ + /* + * @see java.io.OutputStream#write(byte[]) + */ + @Override + public void write(byte[] b) throws IOException + { + write(new ByteArrayBuffer(b)); + } + + + /* ------------------------------------------------------------ */ + /* + * @see java.io.OutputStream#write(int) + */ + @Override + public void write(int b) throws IOException + { + if (_onebyte==null) + _onebyte=new ByteArrayBuffer(1); + else + _onebyte.clear(); + _onebyte.put((byte)b); + write(_onebyte); + } + + /* ------------------------------------------------------------ */ + private void write(Buffer buffer) throws IOException + { + if (_closed) + throw new IOException("Closed"); + if (!_generator.isOpen()) + throw new EofException(); + + // Block until we can add _content. + while (_generator.isBufferFull()) + { + _generator.blockForOutput(getMaxIdleTime()); + if (_closed) + throw new IOException("Closed"); + if (!_generator.isOpen()) + throw new EofException(); + } + + // Add the _content + _generator.addContent(buffer, Generator.MORE); + + // Have to flush and complete headers? + + if (_generator.isAllContentWritten()) + { + flush(); + close(); + } + else if (_generator.isBufferFull()) + _connection.commitResponse(Generator.MORE); + + // Block until our buffer is free + while (buffer.length() > 0 && _generator.isOpen()) + { + _generator.blockForOutput(getMaxIdleTime()); + } + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletOutputStream#print(java.lang.String) + */ + @Override + public void print(String s) throws IOException + { + write(s.getBytes()); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/HttpWriter.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,302 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; + +import org.eclipse.jetty.http.AbstractGenerator; +import org.eclipse.jetty.util.ByteArrayOutputStream2; +import org.eclipse.jetty.util.StringUtil; + +/** OutputWriter. + * A writer that can wrap a {@link HttpOutput} stream and provide + * character encodings. + * + * The UTF-8 encoding is done by this class and no additional + * buffers or Writers are used. + * The UTF-8 code was inspired by http://javolution.org + */ +public class HttpWriter extends Writer +{ + public static final int MAX_OUTPUT_CHARS = 512; + + private static final int WRITE_CONV = 0; + private static final int WRITE_ISO1 = 1; + private static final int WRITE_UTF8 = 2; + + final HttpOutput _out; + final AbstractGenerator _generator; + int _writeMode; + int _surrogate; + + /* ------------------------------------------------------------ */ + public HttpWriter(HttpOutput out) + { + _out=out; + _generator=_out._generator; + _surrogate=0; // AS lastUTF16CodePoint + } + + /* ------------------------------------------------------------ */ + public void setCharacterEncoding(String encoding) + { + if (encoding == null || StringUtil.__ISO_8859_1.equalsIgnoreCase(encoding)) + { + _writeMode = WRITE_ISO1; + } + else if (StringUtil.__UTF8.equalsIgnoreCase(encoding)) + { + _writeMode = WRITE_UTF8; + } + else + { + _writeMode = WRITE_CONV; + if (_out._characterEncoding == null || !_out._characterEncoding.equalsIgnoreCase(encoding)) + _out._converter = null; // Set lazily in getConverter() + } + + _out._characterEncoding = encoding; + if (_out._bytes==null) + _out._bytes = new ByteArrayOutputStream2(MAX_OUTPUT_CHARS); + } + + /* ------------------------------------------------------------ */ + @Override + public void close() throws IOException + { + _out.close(); + } + + /* ------------------------------------------------------------ */ + @Override + public void flush() throws IOException + { + _out.flush(); + } + + /* ------------------------------------------------------------ */ + @Override + public void write (String s,int offset, int length) throws IOException + { + while (length > MAX_OUTPUT_CHARS) + { + write(s, offset, MAX_OUTPUT_CHARS); + offset += MAX_OUTPUT_CHARS; + length -= MAX_OUTPUT_CHARS; + } + + if (_out._chars==null) + { + _out._chars = new char[MAX_OUTPUT_CHARS]; + } + char[] chars = _out._chars; + s.getChars(offset, offset + length, chars, 0); + write(chars, 0, length); + } + + /* ------------------------------------------------------------ */ + @Override + public void write (char[] s,int offset, int length) throws IOException + { + HttpOutput out = _out; + + while (length > 0) + { + out._bytes.reset(); + int chars = length>MAX_OUTPUT_CHARS?MAX_OUTPUT_CHARS:length; + + switch (_writeMode) + { + case WRITE_CONV: + { + Writer converter=getConverter(); + converter.write(s, offset, chars); + converter.flush(); + } + break; + + case WRITE_ISO1: + { + byte[] buffer=out._bytes.getBuf(); + int bytes=out._bytes.getCount(); + + if (chars>buffer.length-bytes) + chars=buffer.length-bytes; + + for (int i = 0; i < chars; i++) + { + int c = s[offset+i]; + buffer[bytes++]=(byte)(c<256?c:'?'); // ISO-1 and UTF-8 match for 0 - 255 + } + if (bytes>=0) + out._bytes.setCount(bytes); + + break; + } + + case WRITE_UTF8: + { + byte[] buffer=out._bytes.getBuf(); + int bytes=out._bytes.getCount(); + + if (bytes+chars>buffer.length) + chars=buffer.length-bytes; + + for (int i = 0; i < chars; i++) + { + int code = s[offset+i]; + + // Do we already have a surrogate? + if(_surrogate==0) + { + // No - is this char code a surrogate? + if(Character.isHighSurrogate((char)code)) + { + _surrogate=code; // UCS-? + continue; + } + } + // else handle a low surrogate + else if(Character.isLowSurrogate((char)code)) + { + code = Character.toCodePoint((char)_surrogate, (char)code); // UCS-4 + } + // else UCS-2 + else + { + code=_surrogate; // UCS-2 + _surrogate=0; // USED + i--; + } + + if ((code & 0xffffff80) == 0) + { + // 1b + if (bytes>=buffer.length) + { + chars=i; + break; + } + buffer[bytes++]=(byte)(code); + } + else + { + if((code&0xfffff800)==0) + { + // 2b + if (bytes+2>buffer.length) + { + chars=i; + break; + } + buffer[bytes++]=(byte)(0xc0|(code>>6)); + buffer[bytes++]=(byte)(0x80|(code&0x3f)); + } + else if((code&0xffff0000)==0) + { + // 3b + if (bytes+3>buffer.length) + { + chars=i; + break; + } + buffer[bytes++]=(byte)(0xe0|(code>>12)); + buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f)); + buffer[bytes++]=(byte)(0x80|(code&0x3f)); + } + else if((code&0xff200000)==0) + { + // 4b + if (bytes+4>buffer.length) + { + chars=i; + break; + } + buffer[bytes++]=(byte)(0xf0|(code>>18)); + buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f)); + buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f)); + buffer[bytes++]=(byte)(0x80|(code&0x3f)); + } + else if((code&0xf4000000)==0) + { + // 5b + if (bytes+5>buffer.length) + { + chars=i; + break; + } + buffer[bytes++]=(byte)(0xf8|(code>>24)); + buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f)); + buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f)); + buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f)); + buffer[bytes++]=(byte)(0x80|(code&0x3f)); + } + else if((code&0x80000000)==0) + { + // 6b + if (bytes+6>buffer.length) + { + chars=i; + break; + } + buffer[bytes++]=(byte)(0xfc|(code>>30)); + buffer[bytes++]=(byte)(0x80|((code>>24)&0x3f)); + buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f)); + buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f)); + buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f)); + buffer[bytes++]=(byte)(0x80|(code&0x3f)); + } + else + { + buffer[bytes++]=(byte)('?'); + } + + _surrogate=0; // USED + + if (bytes==buffer.length) + { + chars=i+1; + break; + } + } + } + out._bytes.setCount(bytes); + break; + } + default: + throw new IllegalStateException(); + } + + out._bytes.writeTo(out); + length-=chars; + offset+=chars; + } + } + + + /* ------------------------------------------------------------ */ + private Writer getConverter() throws IOException + { + if (_out._converter == null) + _out._converter = new OutputStreamWriter(_out._bytes, _out._characterEncoding); + return _out._converter; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/InclusiveByteRange.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,227 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.util.Enumeration; +import java.util.List; +import java.util.StringTokenizer; + +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/* ------------------------------------------------------------ */ +/** Byte range inclusive of end points. + * <PRE> + * + * parses the following types of byte ranges: + * + * bytes=100-499 + * bytes=-300 + * bytes=100- + * bytes=1-2,2-3,6-,-2 + * + * given an entity length, converts range to string + * + * bytes 100-499/500 + * + * </PRE> + * + * Based on RFC2616 3.12, 14.16, 14.35.1, 14.35.2 + * @version $version$ + * + */ +public class InclusiveByteRange +{ + private static final Logger LOG = Log.getLogger(InclusiveByteRange.class); + + long first = 0; + long last = 0; + + public InclusiveByteRange(long first, long last) + { + this.first = first; + this.last = last; + } + + public long getFirst() + { + return first; + } + + public long getLast() + { + return last; + } + + + + /* ------------------------------------------------------------ */ + /** + * @param headers Enumeration of Range header fields. + * @param size Size of the resource. + * @return LazyList of satisfiable ranges + */ + public static List satisfiableRanges(Enumeration headers, long size) + { + Object satRanges=null; + + // walk through all Range headers + headers: + while (headers.hasMoreElements()) + { + String header = (String) headers.nextElement(); + StringTokenizer tok = new StringTokenizer(header,"=,",false); + String t=null; + try + { + // read all byte ranges for this header + while (tok.hasMoreTokens()) + { + try + { + t = tok.nextToken().trim(); + + long first = -1; + long last = -1; + int d = t.indexOf('-'); + if (d < 0 || t.indexOf("-",d + 1) >= 0) + { + if ("bytes".equals(t)) + continue; + LOG.warn("Bad range format: {}",t); + continue headers; + } + else if (d == 0) + { + if (d + 1 < t.length()) + last = Long.parseLong(t.substring(d + 1).trim()); + else + { + LOG.warn("Bad range format: {}",t); + continue; + } + } + else if (d + 1 < t.length()) + { + first = Long.parseLong(t.substring(0,d).trim()); + last = Long.parseLong(t.substring(d + 1).trim()); + } + else + first = Long.parseLong(t.substring(0,d).trim()); + + if (first == -1 && last == -1) + continue headers; + + if (first != -1 && last != -1 && (first > last)) + continue headers; + + if (first < size) + { + InclusiveByteRange range = new InclusiveByteRange(first,last); + satRanges = LazyList.add(satRanges,range); + } + } + catch (NumberFormatException e) + { + LOG.warn("Bad range format: {}",t); + LOG.ignore(e); + continue; + } + } + } + catch(Exception e) + { + LOG.warn("Bad range format: {}",t); + LOG.ignore(e); + } + } + return LazyList.getList(satRanges,true); + } + + /* ------------------------------------------------------------ */ + public long getFirst(long size) + { + if (first<0) + { + long tf=size-last; + if (tf<0) + tf=0; + return tf; + } + return first; + } + + /* ------------------------------------------------------------ */ + public long getLast(long size) + { + if (first<0) + return size-1; + + if (last<0 ||last>=size) + return size-1; + return last; + } + + /* ------------------------------------------------------------ */ + public long getSize(long size) + { + return getLast(size)-getFirst(size)+1; + } + + + /* ------------------------------------------------------------ */ + public String toHeaderRangeString(long size) + { + StringBuilder sb = new StringBuilder(40); + sb.append("bytes "); + sb.append(getFirst(size)); + sb.append('-'); + sb.append(getLast(size)); + sb.append("/"); + sb.append(size); + return sb.toString(); + } + + /* ------------------------------------------------------------ */ + public static String to416HeaderRangeString(long size) + { + StringBuilder sb = new StringBuilder(40); + sb.append("bytes */"); + sb.append(size); + return sb.toString(); + } + + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(60); + sb.append(Long.toString(first)); + sb.append(":"); + sb.append(Long.toString(last)); + return sb.toString(); + } + + +} + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/LocalConnector.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,176 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.io.IOException; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.ByteArrayEndPoint; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +public class LocalConnector extends AbstractConnector +{ + private static final Logger LOG = Log.getLogger(LocalConnector.class); + private final BlockingQueue<Request> _requests = new LinkedBlockingQueue<Request>(); + + public LocalConnector() + { + setMaxIdleTime(30000); + } + + public Object getConnection() + { + return this; + } + + public String getResponses(String requests) throws Exception + { + return getResponses(requests, false); + } + + public String getResponses(String requests, boolean keepOpen) throws Exception + { + ByteArrayBuffer result = getResponses(new ByteArrayBuffer(requests, StringUtil.__ISO_8859_1), keepOpen); + return result==null?null:result.toString(StringUtil.__ISO_8859_1); + } + + public ByteArrayBuffer getResponses(ByteArrayBuffer requestsBuffer, boolean keepOpen) throws Exception + { + CountDownLatch latch = new CountDownLatch(1); + Request request = new Request(requestsBuffer, keepOpen, latch); + _requests.add(request); + latch.await(getMaxIdleTime(),TimeUnit.MILLISECONDS); + return request.getResponsesBuffer(); + } + + @Override + protected void accept(int acceptorID) throws IOException, InterruptedException + { + Request request = _requests.take(); + getThreadPool().dispatch(request); + } + + public void open() throws IOException + { + } + + public void close() throws IOException + { + } + + public int getLocalPort() + { + return -1; + } + + public void executeRequest(String rawRequest) throws IOException + { + Request request = new Request(new ByteArrayBuffer(rawRequest, "UTF-8"), true, null); + _requests.add(request); + } + + private class Request implements Runnable + { + private final ByteArrayBuffer _requestsBuffer; + private final boolean _keepOpen; + private final CountDownLatch _latch; + private volatile ByteArrayBuffer _responsesBuffer; + + private Request(ByteArrayBuffer requestsBuffer, boolean keepOpen, CountDownLatch latch) + { + _requestsBuffer = requestsBuffer; + _keepOpen = keepOpen; + _latch = latch; + } + + public void run() + { + try + { + ByteArrayEndPoint endPoint = new ByteArrayEndPoint(_requestsBuffer.asArray(), 1024) + { + @Override + public void setConnection(Connection connection) + { + if (getConnection()!=null && connection!=getConnection()) + connectionUpgraded(getConnection(),connection); + super.setConnection(connection); + } + }; + + endPoint.setGrowOutput(true); + AbstractHttpConnection connection = new BlockingHttpConnection(LocalConnector.this, endPoint, getServer()); + endPoint.setConnection(connection); + connectionOpened(connection); + + boolean leaveOpen = _keepOpen; + try + { + while (endPoint.getIn().length() > 0 && endPoint.isOpen()) + { + while (true) + { + final Connection con = endPoint.getConnection(); + final Connection next = con.handle(); + if (next!=con) + { + endPoint.setConnection(next); + continue; + } + break; + } + } + } + catch (IOException x) + { + LOG.debug(x); + leaveOpen = false; + } + catch (Exception x) + { + LOG.warn(x); + leaveOpen = false; + } + finally + { + if (!leaveOpen) + connectionClosed(connection); + _responsesBuffer = endPoint.getOut(); + } + } + finally + { + if (_latch != null) + _latch.countDown(); + } + } + + public ByteArrayBuffer getResponsesBuffer() + { + return _responsesBuffer; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/NCSARequestLog.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,726 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.Locale; +import java.util.TimeZone; + +import javax.servlet.http.Cookie; + +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.PathMap; +import org.eclipse.jetty.util.DateCache; +import org.eclipse.jetty.util.RolloverFileOutputStream; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + * This {@link RequestLog} implementation outputs logs in the pseudo-standard + * NCSA common log format. Configuration options allow a choice between the + * standard Common Log Format (as used in the 3 log format) and the Combined Log + * Format (single log format). This log format can be output by most web + * servers, and almost all web log analysis software can understand these + * formats. + * + * @org.apache.xbean.XBean element="ncsaLog" + */ + +/* ------------------------------------------------------------ */ +/** + */ +public class NCSARequestLog extends AbstractLifeCycle implements RequestLog +{ + private static final Logger LOG = Log.getLogger(NCSARequestLog.class); + private static ThreadLocal<StringBuilder> _buffers = new ThreadLocal<StringBuilder>() + { + @Override + protected StringBuilder initialValue() + { + return new StringBuilder(256); + } + }; + + private String _filename; + private boolean _extended; + private boolean _append; + private int _retainDays; + private boolean _closeOut; + private boolean _preferProxiedForAddress; + private String _logDateFormat = "dd/MMM/yyyy:HH:mm:ss Z"; + private String _filenameDateFormat = null; + private Locale _logLocale = Locale.getDefault(); + private String _logTimeZone = "GMT"; + private String[] _ignorePaths; + private boolean _logLatency = false; + private boolean _logCookies = false; + private boolean _logServer = false; + private boolean _logDispatch = false; + + private transient OutputStream _out; + private transient OutputStream _fileOut; + private transient DateCache _logDateCache; + private transient PathMap _ignorePathMap; + private transient Writer _writer; + + /* ------------------------------------------------------------ */ + /** + * Create request log object with default settings. + */ + public NCSARequestLog() + { + _extended = true; + _append = true; + _retainDays = 31; + } + + /* ------------------------------------------------------------ */ + /** + * Create request log object with specified output file name. + * + * @param filename the file name for the request log. + * This may be in the format expected + * by {@link RolloverFileOutputStream} + */ + public NCSARequestLog(String filename) + { + _extended = true; + _append = true; + _retainDays = 31; + setFilename(filename); + } + + /* ------------------------------------------------------------ */ + /** + * Set the output file name of the request log. + * The file name may be in the format expected by + * {@link RolloverFileOutputStream}. + * + * @param filename file name of the request log + * + */ + public void setFilename(String filename) + { + if (filename != null) + { + filename = filename.trim(); + if (filename.length() == 0) + filename = null; + } + _filename = filename; + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve the output file name of the request log. + * + * @return file name of the request log + */ + public String getFilename() + { + return _filename; + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve the file name of the request log with the expanded + * date wildcard if the output is written to the disk using + * {@link RolloverFileOutputStream}. + * + * @return file name of the request log, or null if not applicable + */ + public String getDatedFilename() + { + if (_fileOut instanceof RolloverFileOutputStream) + return ((RolloverFileOutputStream)_fileOut).getDatedFilename(); + return null; + } + + /* ------------------------------------------------------------ */ + /** + * Set the timestamp format for request log entries in the file. + * If this is not set, the pre-formated request timestamp is used. + * + * @param format timestamp format string + */ + public void setLogDateFormat(String format) + { + _logDateFormat = format; + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve the timestamp format string for request log entries. + * + * @return timestamp format string. + */ + public String getLogDateFormat() + { + return _logDateFormat; + } + + /* ------------------------------------------------------------ */ + /** + * Set the locale of the request log. + * + * @param logLocale locale object + */ + public void setLogLocale(Locale logLocale) + { + _logLocale = logLocale; + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve the locale of the request log. + * + * @return locale object + */ + public Locale getLogLocale() + { + return _logLocale; + } + + /* ------------------------------------------------------------ */ + /** + * Set the timezone of the request log. + * + * @param tz timezone string + */ + public void setLogTimeZone(String tz) + { + _logTimeZone = tz; + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve the timezone of the request log. + * + * @return timezone string + */ + public String getLogTimeZone() + { + return _logTimeZone; + } + + /* ------------------------------------------------------------ */ + /** + * Set the number of days before rotated log files are deleted. + * + * @param retainDays number of days to keep a log file + */ + public void setRetainDays(int retainDays) + { + _retainDays = retainDays; + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve the number of days before rotated log files are deleted. + * + * @return number of days to keep a log file + */ + public int getRetainDays() + { + return _retainDays; + } + + /* ------------------------------------------------------------ */ + /** + * Set the extended request log format flag. + * + * @param extended true - log the extended request information, + * false - do not log the extended request information + */ + public void setExtended(boolean extended) + { + _extended = extended; + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve the extended request log format flag. + * + * @return value of the flag + */ + public boolean isExtended() + { + return _extended; + } + + /* ------------------------------------------------------------ */ + /** + * Set append to log flag. + * + * @param append true - request log file will be appended after restart, + * false - request log file will be overwritten after restart + */ + public void setAppend(boolean append) + { + _append = append; + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve append to log flag. + * + * @return value of the flag + */ + public boolean isAppend() + { + return _append; + } + + /* ------------------------------------------------------------ */ + /** + * Set request paths that will not be logged. + * + * @param ignorePaths array of request paths + */ + public void setIgnorePaths(String[] ignorePaths) + { + _ignorePaths = ignorePaths; + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve the request paths that will not be logged. + * + * @return array of request paths + */ + public String[] getIgnorePaths() + { + return _ignorePaths; + } + + /* ------------------------------------------------------------ */ + /** + * Controls logging of the request cookies. + * + * @param logCookies true - values of request cookies will be logged, + * false - values of request cookies will not be logged + */ + public void setLogCookies(boolean logCookies) + { + _logCookies = logCookies; + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve log cookies flag + * + * @return value of the flag + */ + public boolean getLogCookies() + { + return _logCookies; + } + + /* ------------------------------------------------------------ */ + /** + * Controls logging of the request hostname. + * + * @param logServer true - request hostname will be logged, + * false - request hostname will not be logged + */ + public void setLogServer(boolean logServer) + { + _logServer = logServer; + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve log hostname flag. + * + * @return value of the flag + */ + public boolean getLogServer() + { + return _logServer; + } + + /* ------------------------------------------------------------ */ + /** + * Controls logging of request processing time. + * + * @param logLatency true - request processing time will be logged + * false - request processing time will not be logged + */ + public void setLogLatency(boolean logLatency) + { + _logLatency = logLatency; + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve log request processing time flag. + * + * @return value of the flag + */ + public boolean getLogLatency() + { + return _logLatency; + } + + /* ------------------------------------------------------------ */ + /** + * Controls whether the actual IP address of the connection or + * the IP address from the X-Forwarded-For header will be logged. + * + * @param preferProxiedForAddress true - IP address from header will be logged, + * false - IP address from the connection will be logged + */ + public void setPreferProxiedForAddress(boolean preferProxiedForAddress) + { + _preferProxiedForAddress = preferProxiedForAddress; + } + + /* ------------------------------------------------------------ */ + /** + * Retrieved log X-Forwarded-For IP address flag. + * + * @return value of the flag + */ + public boolean getPreferProxiedForAddress() + { + return _preferProxiedForAddress; + } + + /* ------------------------------------------------------------ */ + /** + * Set the log file name date format. + * @see RolloverFileOutputStream#RolloverFileOutputStream(String, boolean, int, TimeZone, String, String) + * + * @param logFileDateFormat format string that is passed to {@link RolloverFileOutputStream} + */ + public void setFilenameDateFormat(String logFileDateFormat) + { + _filenameDateFormat = logFileDateFormat; + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve the file name date format string. + * + * @return the log File Date Format + */ + public String getFilenameDateFormat() + { + return _filenameDateFormat; + } + + /* ------------------------------------------------------------ */ + /** + * Controls logging of the request dispatch time + * + * @param value true - request dispatch time will be logged + * false - request dispatch time will not be logged + */ + public void setLogDispatch(boolean value) + { + _logDispatch = value; + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve request dispatch time logging flag + * + * @return value of the flag + */ + public boolean isLogDispatch() + { + return _logDispatch; + } + + /* ------------------------------------------------------------ */ + /** + * Writes the request and response information to the output stream. + * + * @see org.eclipse.jetty.server.RequestLog#log(org.eclipse.jetty.server.Request, org.eclipse.jetty.server.Response) + */ + public void log(Request request, Response response) + { + try + { + if (_ignorePathMap != null && _ignorePathMap.getMatch(request.getRequestURI()) != null) + return; + + if (_fileOut == null) + return; + + StringBuilder buf= _buffers.get(); + buf.setLength(0); + + if (_logServer) + { + buf.append(request.getServerName()); + buf.append(' '); + } + + String addr = null; + if (_preferProxiedForAddress) + { + addr = request.getHeader(HttpHeaders.X_FORWARDED_FOR); + } + + if (addr == null) + addr = request.getRemoteAddr(); + + buf.append(addr); + buf.append(" - "); + Authentication authentication=request.getAuthentication(); + if (authentication instanceof Authentication.User) + buf.append(((Authentication.User)authentication).getUserIdentity().getUserPrincipal().getName()); + else + buf.append(" - "); + + buf.append(" ["); + if (_logDateCache != null) + buf.append(_logDateCache.format(request.getTimeStamp())); + else + buf.append(request.getTimeStampBuffer().toString()); + + buf.append("] \""); + buf.append(request.getMethod()); + buf.append(' '); + buf.append(request.getUri().toString()); + buf.append(' '); + buf.append(request.getProtocol()); + buf.append("\" "); + if (request.getAsyncContinuation().isInitial()) + { + int status = response.getStatus(); + if (status <= 0) + status = 404; + buf.append((char)('0' + ((status / 100) % 10))); + buf.append((char)('0' + ((status / 10) % 10))); + buf.append((char)('0' + (status % 10))); + } + else + buf.append("Async"); + + long responseLength = response.getContentCount(); + if (responseLength >= 0) + { + buf.append(' '); + if (responseLength > 99999) + buf.append(responseLength); + else + { + if (responseLength > 9999) + buf.append((char)('0' + ((responseLength / 10000) % 10))); + if (responseLength > 999) + buf.append((char)('0' + ((responseLength / 1000) % 10))); + if (responseLength > 99) + buf.append((char)('0' + ((responseLength / 100) % 10))); + if (responseLength > 9) + buf.append((char)('0' + ((responseLength / 10) % 10))); + buf.append((char)('0' + (responseLength) % 10)); + } + buf.append(' '); + } + else + buf.append(" - "); + + + if (_extended) + logExtended(request, response, buf); + + if (_logCookies) + { + Cookie[] cookies = request.getCookies(); + if (cookies == null || cookies.length == 0) + buf.append(" -"); + else + { + buf.append(" \""); + for (int i = 0; i < cookies.length; i++) + { + if (i != 0) + buf.append(';'); + buf.append(cookies[i].getName()); + buf.append('='); + buf.append(cookies[i].getValue()); + } + buf.append('\"'); + } + } + + if (_logDispatch || _logLatency) + { + long now = System.currentTimeMillis(); + + if (_logDispatch) + { + long d = request.getDispatchTime(); + buf.append(' '); + buf.append(now - (d==0 ? request.getTimeStamp():d)); + } + + if (_logLatency) + { + buf.append(' '); + buf.append(now - request.getTimeStamp()); + } + } + + buf.append(StringUtil.__LINE_SEPARATOR); + + String log = buf.toString(); + write(log); + } + catch (IOException e) + { + LOG.warn(e); + } + } + + /* ------------------------------------------------------------ */ + protected void write(String log) throws IOException + { + synchronized(this) + { + if (_writer==null) + return; + _writer.write(log); + _writer.flush(); + } + } + + + /* ------------------------------------------------------------ */ + /** + * Writes extended request and response information to the output stream. + * + * @param request request object + * @param response response object + * @param b StringBuilder to write to + * @throws IOException + */ + protected void logExtended(Request request, + Response response, + StringBuilder b) throws IOException + { + String referer = request.getHeader(HttpHeaders.REFERER); + if (referer == null) + b.append("\"-\" "); + else + { + b.append('"'); + b.append(referer); + b.append("\" "); + } + + String agent = request.getHeader(HttpHeaders.USER_AGENT); + if (agent == null) + b.append("\"-\" "); + else + { + b.append('"'); + b.append(agent); + b.append('"'); + } + } + + /* ------------------------------------------------------------ */ + /** + * Set up request logging and open log file. + * + * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart() + */ + @Override + protected synchronized void doStart() throws Exception + { + if (_logDateFormat != null) + { + _logDateCache = new DateCache(_logDateFormat,_logLocale); + _logDateCache.setTimeZoneID(_logTimeZone); + } + + if (_filename != null) + { + _fileOut = new RolloverFileOutputStream(_filename,_append,_retainDays,TimeZone.getTimeZone(_logTimeZone),_filenameDateFormat,null); + _closeOut = true; + LOG.info("Opened " + getDatedFilename()); + } + else + _fileOut = System.err; + + _out = _fileOut; + + if (_ignorePaths != null && _ignorePaths.length > 0) + { + _ignorePathMap = new PathMap(); + for (int i = 0; i < _ignorePaths.length; i++) + _ignorePathMap.put(_ignorePaths[i],_ignorePaths[i]); + } + else + _ignorePathMap = null; + + synchronized(this) + { + _writer = new OutputStreamWriter(_out); + } + super.doStart(); + } + + /* ------------------------------------------------------------ */ + /** + * Close the log file and perform cleanup. + * + * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop() + */ + @Override + protected void doStop() throws Exception + { + synchronized (this) + { + super.doStop(); + try + { + if (_writer != null) + _writer.flush(); + } + catch (IOException e) + { + LOG.ignore(e); + } + if (_out != null && _closeOut) + try + { + _out.close(); + } + catch (IOException e) + { + LOG.ignore(e); + } + + _out = null; + _fileOut = null; + _closeOut = false; + _logDateCache = null; + _writer = null; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/Request.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,2204 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.security.Principal; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.EventListener; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; + +import javax.servlet.AsyncContext; +import javax.servlet.AsyncEvent; +import javax.servlet.AsyncListener; +import javax.servlet.DispatcherType; +import javax.servlet.MultipartConfigElement; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletRequest; +import javax.servlet.ServletRequestAttributeEvent; +import javax.servlet.ServletRequestAttributeListener; +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import javax.servlet.http.Part; + +import org.eclipse.jetty.continuation.Continuation; +import org.eclipse.jetty.continuation.ContinuationListener; +import org.eclipse.jetty.http.HttpCookie; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.HttpMethods; +import org.eclipse.jetty.http.HttpParser; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.http.HttpVersions; +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.BufferUtil; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.nio.DirectNIOBuffer; +import org.eclipse.jetty.io.nio.IndirectNIOBuffer; +import org.eclipse.jetty.io.nio.NIOBuffer; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.ContextHandler.Context; +import org.eclipse.jetty.util.Attributes; +import org.eclipse.jetty.util.AttributesMap; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.MultiException; +import org.eclipse.jetty.util.MultiMap; +import org.eclipse.jetty.util.MultiPartInputStream; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.UrlEncoded; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/* ------------------------------------------------------------ */ +/** + * Jetty Request. + * <p> + * Implements {@link javax.servlet.http.HttpServletRequest} from the <code>javax.servlet.http</code> package. + * </p> + * <p> + * The standard interface of mostly getters, is extended with setters so that the request is mutable by the handlers that it is passed to. This allows the + * request object to be as lightweight as possible and not actually implement any significant behavior. For example + * <ul> + * + * <li>The {@link Request#getContextPath()} method will return null, until the request has been passed to a {@link ContextHandler} which matches the + * {@link Request#getPathInfo()} with a context path and calls {@link Request#setContextPath(String)} as a result.</li> + * + * <li>the HTTP session methods will all return null sessions until such time as a request has been passed to a + * {@link org.eclipse.jetty.server.session.SessionHandler} which checks for session cookies and enables the ability to create new sessions.</li> + * + * <li>The {@link Request#getServletPath()} method will return null until the request has been passed to a <code>org.eclipse.jetty.servlet.ServletHandler</code> + * and the pathInfo matched against the servlet URL patterns and {@link Request#setServletPath(String)} called as a result.</li> + * </ul> + * + * A request instance is created for each {@link AbstractHttpConnection} accepted by the server and recycled for each HTTP request received via that connection. + * An effort is made to avoid reparsing headers and cookies that are likely to be the same for requests from the same connection. + * + * <p> + * The form content that a request can process is limited to protect from Denial of Service attacks. The size in bytes is limited by + * {@link ContextHandler#getMaxFormContentSize()} or if there is no context then the "org.eclipse.jetty.server.Request.maxFormContentSize" {@link Server} + * attribute. The number of parameters keys is limited by {@link ContextHandler#getMaxFormKeys()} or if there is no context then the + * "org.eclipse.jetty.server.Request.maxFormKeys" {@link Server} attribute. + * + * + */ +public class Request implements HttpServletRequest +{ + public static final String __MULTIPART_CONFIG_ELEMENT = "org.eclipse.multipartConfig"; + public static final String __MULTIPART_INPUT_STREAM = "org.eclipse.multiPartInputStream"; + public static final String __MULTIPART_CONTEXT = "org.eclipse.multiPartContext"; + private static final Logger LOG = Log.getLogger(Request.class); + + private static final String __ASYNC_FWD = "org.eclipse.asyncfwd"; + private static final Collection __defaultLocale = Collections.singleton(Locale.getDefault()); + private static final int __NONE = 0, _STREAM = 1, __READER = 2; + + public static class MultiPartCleanerListener implements ServletRequestListener + { + + @Override + public void requestDestroyed(ServletRequestEvent sre) + { + //Clean up any tmp files created by MultiPartInputStream + MultiPartInputStream mpis = (MultiPartInputStream)sre.getServletRequest().getAttribute(__MULTIPART_INPUT_STREAM); + if (mpis != null) + { + ContextHandler.Context context = (ContextHandler.Context)sre.getServletRequest().getAttribute(__MULTIPART_CONTEXT); + + //Only do the cleanup if we are exiting from the context in which a servlet parsed the multipart files + if (context == sre.getServletContext()) + { + try + { + mpis.deleteParts(); + } + catch (MultiException e) + { + sre.getServletContext().log("Errors deleting multipart tmp files", e); + } + } + } + } + + @Override + public void requestInitialized(ServletRequestEvent sre) + { + //nothing to do, multipart config set up by ServletHolder.handle() + } + + } + + + /* ------------------------------------------------------------ */ + public static Request getRequest(HttpServletRequest request) + { + if (request instanceof Request) + return (Request)request; + + return AbstractHttpConnection.getCurrentConnection().getRequest(); + } + protected final AsyncContinuation _async = new AsyncContinuation(); + private boolean _asyncSupported = true; + private volatile Attributes _attributes; + private Authentication _authentication; + private MultiMap<String> _baseParameters; + private String _characterEncoding; + protected AbstractHttpConnection _connection; + private ContextHandler.Context _context; + private boolean _newContext; + private String _contextPath; + private CookieCutter _cookies; + private boolean _cookiesExtracted = false; + private DispatcherType _dispatcherType; + private boolean _dns = false; + private EndPoint _endp; + private boolean _handled = false; + private int _inputState = __NONE; + private String _method; + private MultiMap<String> _parameters; + private boolean _paramsExtracted; + private String _pathInfo; + private int _port; + private String _protocol = HttpVersions.HTTP_1_1; + private String _queryEncoding; + private String _queryString; + private BufferedReader _reader; + private String _readerEncoding; + private String _remoteAddr; + private String _remoteHost; + private Object _requestAttributeListeners; + private String _requestedSessionId; + private boolean _requestedSessionIdFromCookie = false; + private String _requestURI; + private Map<Object, HttpSession> _savedNewSessions; + private String _scheme = URIUtil.HTTP; + private UserIdentity.Scope _scope; + private String _serverName; + private String _servletPath; + private HttpSession _session; + private SessionManager _sessionManager; + private long _timeStamp; + private long _dispatchTime; + + private Buffer _timeStampBuffer; + private HttpURI _uri; + + private MultiPartInputStream _multiPartInputStream; //if the request is a multi-part mime + + /* ------------------------------------------------------------ */ + public Request() + { + } + + /* ------------------------------------------------------------ */ + public Request(AbstractHttpConnection connection) + { + setConnection(connection); + } + + /* ------------------------------------------------------------ */ + public void addEventListener(final EventListener listener) + { + if (listener instanceof ServletRequestAttributeListener) + _requestAttributeListeners = LazyList.add(_requestAttributeListeners,listener); + if (listener instanceof ContinuationListener) + throw new IllegalArgumentException(listener.getClass().toString()); + if (listener instanceof AsyncListener) + throw new IllegalArgumentException(listener.getClass().toString()); + } + + /* ------------------------------------------------------------ */ + /** + * Extract Parameters from query string and/or form _content. + */ + public void extractParameters() + { + if (_baseParameters == null) + _baseParameters = new MultiMap(16); + + if (_paramsExtracted) + { + if (_parameters == null) + _parameters = _baseParameters; + return; + } + + _paramsExtracted = true; + + try + { + // Handle query string + if (_uri != null && _uri.hasQuery()) + { + if (_queryEncoding == null) + _uri.decodeQueryTo(_baseParameters); + else + { + try + { + _uri.decodeQueryTo(_baseParameters,_queryEncoding); + } + catch (UnsupportedEncodingException e) + { + if (LOG.isDebugEnabled()) + LOG.warn(e); + else + LOG.warn(e.toString()); + } + } + } + + // handle any _content. + String encoding = getCharacterEncoding(); + String content_type = getContentType(); + if (content_type != null && content_type.length() > 0) + { + content_type = HttpFields.valueParameters(content_type,null); + + if (MimeTypes.FORM_ENCODED.equalsIgnoreCase(content_type) && _inputState == __NONE + && (HttpMethods.POST.equals(getMethod()) || HttpMethods.PUT.equals(getMethod()))) + { + int content_length = getContentLength(); + if (content_length != 0) + { + try + { + int maxFormContentSize = -1; + int maxFormKeys = -1; + + if (_context != null) + { + maxFormContentSize = _context.getContextHandler().getMaxFormContentSize(); + maxFormKeys = _context.getContextHandler().getMaxFormKeys(); + } + + if (maxFormContentSize < 0) + { + Object obj = _connection.getConnector().getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormContentSize"); + if (obj == null) + maxFormContentSize = 200000; + else if (obj instanceof Number) + { + Number size = (Number)obj; + maxFormContentSize = size.intValue(); + } + else if (obj instanceof String) + { + maxFormContentSize = Integer.valueOf((String)obj); + } + } + + if (maxFormKeys < 0) + { + Object obj = _connection.getConnector().getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormKeys"); + if (obj == null) + maxFormKeys = 1000; + else if (obj instanceof Number) + { + Number keys = (Number)obj; + maxFormKeys = keys.intValue(); + } + else if (obj instanceof String) + { + maxFormKeys = Integer.valueOf((String)obj); + } + } + + if (content_length > maxFormContentSize && maxFormContentSize > 0) + { + throw new IllegalStateException("Form too large " + content_length + ">" + maxFormContentSize); + } + InputStream in = getInputStream(); + + // Add form params to query params + UrlEncoded.decodeTo(in,_baseParameters,encoding,content_length < 0?maxFormContentSize:-1,maxFormKeys); + } + catch (IOException e) + { + if (LOG.isDebugEnabled()) + LOG.warn(e); + else + LOG.warn(e.toString()); + } + } + } + + } + + if (_parameters == null) + _parameters = _baseParameters; + else if (_parameters != _baseParameters) + { + // Merge parameters (needed if parameters extracted after a forward). + Iterator iter = _baseParameters.entrySet().iterator(); + while (iter.hasNext()) + { + Map.Entry entry = (Map.Entry)iter.next(); + String name = (String)entry.getKey(); + Object values = entry.getValue(); + for (int i = 0; i < LazyList.size(values); i++) + _parameters.add(name,LazyList.get(values,i)); + } + } + + if (content_type != null && content_type.length()>0 && content_type.startsWith("multipart/form-data") && getAttribute(__MULTIPART_CONFIG_ELEMENT)!=null) + { + try + { + getParts(); + } + catch (IOException e) + { + if (LOG.isDebugEnabled()) + LOG.warn(e); + else + LOG.warn(e.toString()); + } + catch (ServletException e) + { + if (LOG.isDebugEnabled()) + LOG.warn(e); + else + LOG.warn(e.toString()); + } + } + } + finally + { + // ensure params always set (even if empty) after extraction + if (_parameters == null) + _parameters = _baseParameters; + } + } + + /* ------------------------------------------------------------ */ + public AsyncContext getAsyncContext() + { + if (_async.isInitial() && !_async.isAsyncStarted()) + throw new IllegalStateException(_async.getStatusString()); + return _async; + } + + /* ------------------------------------------------------------ */ + public AsyncContinuation getAsyncContinuation() + { + return _async; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getAttribute(java.lang.String) + */ + public Object getAttribute(String name) + { + if ("org.eclipse.jetty.io.EndPoint.maxIdleTime".equalsIgnoreCase(name)) + return new Long(getConnection().getEndPoint().getMaxIdleTime()); + + Object attr = (_attributes == null)?null:_attributes.getAttribute(name); + if (attr == null && Continuation.ATTRIBUTE.equals(name)) + return _async; + return attr; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getAttributeNames() + */ + public Enumeration getAttributeNames() + { + if (_attributes == null) + return Collections.enumeration(Collections.EMPTY_LIST); + + return AttributesMap.getAttributeNamesCopy(_attributes); + } + + /* ------------------------------------------------------------ */ + /* + */ + public Attributes getAttributes() + { + if (_attributes == null) + _attributes = new AttributesMap(); + return _attributes; + } + + /* ------------------------------------------------------------ */ + /** + * Get the authentication. + * + * @return the authentication + */ + public Authentication getAuthentication() + { + return _authentication; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getAuthType() + */ + public String getAuthType() + { + if (_authentication instanceof Authentication.Deferred) + setAuthentication(((Authentication.Deferred)_authentication).authenticate(this)); + + if (_authentication instanceof Authentication.User) + return ((Authentication.User)_authentication).getAuthMethod(); + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getCharacterEncoding() + */ + public String getCharacterEncoding() + { + return _characterEncoding; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the connection. + */ + public AbstractHttpConnection getConnection() + { + return _connection; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getContentLength() + */ + public int getContentLength() + { + return (int)_connection.getRequestFields().getLongField(HttpHeaders.CONTENT_LENGTH_BUFFER); + } + + public long getContentRead() + { + if (_connection == null || _connection.getParser() == null) + return -1; + + return ((HttpParser)_connection.getParser()).getContentRead(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getContentType() + */ + public String getContentType() + { + return _connection.getRequestFields().getStringField(HttpHeaders.CONTENT_TYPE_BUFFER); + } + + /* ------------------------------------------------------------ */ + /** + * @return The current {@link Context context} used for this request, or <code>null</code> if {@link #setContext} has not yet been called. + */ + public Context getContext() + { + return _context; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getContextPath() + */ + public String getContextPath() + { + return _contextPath; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getCookies() + */ + public Cookie[] getCookies() + { + if (_cookiesExtracted) + return _cookies == null?null:_cookies.getCookies(); + + _cookiesExtracted = true; + + Enumeration enm = _connection.getRequestFields().getValues(HttpHeaders.COOKIE_BUFFER); + + // Handle no cookies + if (enm != null) + { + if (_cookies == null) + _cookies = new CookieCutter(); + + while (enm.hasMoreElements()) + { + String c = (String)enm.nextElement(); + _cookies.addCookieField(c); + } + } + + return _cookies == null?null:_cookies.getCookies(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getDateHeader(java.lang.String) + */ + public long getDateHeader(String name) + { + return _connection.getRequestFields().getDateField(name); + } + + /* ------------------------------------------------------------ */ + public DispatcherType getDispatcherType() + { + return _dispatcherType; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getHeader(java.lang.String) + */ + public String getHeader(String name) + { + return _connection.getRequestFields().getStringField(name); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getHeaderNames() + */ + public Enumeration getHeaderNames() + { + return _connection.getRequestFields().getFieldNames(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getHeaders(java.lang.String) + */ + public Enumeration getHeaders(String name) + { + Enumeration e = _connection.getRequestFields().getValues(name); + if (e == null) + return Collections.enumeration(Collections.EMPTY_LIST); + return e; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the inputState. + */ + public int getInputState() + { + return _inputState; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getInputStream() + */ + public ServletInputStream getInputStream() throws IOException + { + if (_inputState != __NONE && _inputState != _STREAM) + throw new IllegalStateException("READER"); + _inputState = _STREAM; + return _connection.getInputStream(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getIntHeader(java.lang.String) + */ + public int getIntHeader(String name) + { + return (int)_connection.getRequestFields().getLongField(name); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getLocalAddr() + */ + public String getLocalAddr() + { + return _endp == null?null:_endp.getLocalAddr(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getLocale() + */ + public Locale getLocale() + { + Enumeration enm = _connection.getRequestFields().getValues(HttpHeaders.ACCEPT_LANGUAGE,HttpFields.__separators); + + // handle no locale + if (enm == null || !enm.hasMoreElements()) + return Locale.getDefault(); + + // sort the list in quality order + List acceptLanguage = HttpFields.qualityList(enm); + if (acceptLanguage.size() == 0) + return Locale.getDefault(); + + int size = acceptLanguage.size(); + + if (size > 0) + { + String language = (String)acceptLanguage.get(0); + language = HttpFields.valueParameters(language,null); + String country = ""; + int dash = language.indexOf('-'); + if (dash > -1) + { + country = language.substring(dash + 1).trim(); + language = language.substring(0,dash).trim(); + } + return new Locale(language,country); + } + + return Locale.getDefault(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getLocales() + */ + public Enumeration getLocales() + { + + Enumeration enm = _connection.getRequestFields().getValues(HttpHeaders.ACCEPT_LANGUAGE,HttpFields.__separators); + + // handle no locale + if (enm == null || !enm.hasMoreElements()) + return Collections.enumeration(__defaultLocale); + + // sort the list in quality order + List acceptLanguage = HttpFields.qualityList(enm); + + if (acceptLanguage.size() == 0) + return Collections.enumeration(__defaultLocale); + + Object langs = null; + int size = acceptLanguage.size(); + + // convert to locals + for (int i = 0; i < size; i++) + { + String language = (String)acceptLanguage.get(i); + language = HttpFields.valueParameters(language,null); + String country = ""; + int dash = language.indexOf('-'); + if (dash > -1) + { + country = language.substring(dash + 1).trim(); + language = language.substring(0,dash).trim(); + } + langs = LazyList.ensureSize(langs,size); + langs = LazyList.add(langs,new Locale(language,country)); + } + + if (LazyList.size(langs) == 0) + return Collections.enumeration(__defaultLocale); + + return Collections.enumeration(LazyList.getList(langs)); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getLocalName() + */ + public String getLocalName() + { + if (_endp == null) + return null; + if (_dns) + return _endp.getLocalHost(); + + String local = _endp.getLocalAddr(); + if (local != null && local.indexOf(':') >= 0) + local = "[" + local + "]"; + return local; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getLocalPort() + */ + public int getLocalPort() + { + return _endp == null?0:_endp.getLocalPort(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getMethod() + */ + public String getMethod() + { + return _method; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getParameter(java.lang.String) + */ + public String getParameter(String name) + { + if (!_paramsExtracted) + extractParameters(); + return (String)_parameters.getValue(name,0); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getParameterMap() + */ + public Map getParameterMap() + { + if (!_paramsExtracted) + extractParameters(); + + return Collections.unmodifiableMap(_parameters.toStringArrayMap()); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getParameterNames() + */ + public Enumeration getParameterNames() + { + if (!_paramsExtracted) + extractParameters(); + return Collections.enumeration(_parameters.keySet()); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the parameters. + */ + public MultiMap<String> getParameters() + { + return _parameters; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getParameterValues(java.lang.String) + */ + public String[] getParameterValues(String name) + { + if (!_paramsExtracted) + extractParameters(); + List<Object> vals = _parameters.getValues(name); + if (vals == null) + return null; + return vals.toArray(new String[vals.size()]); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getPathInfo() + */ + public String getPathInfo() + { + return _pathInfo; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getPathTranslated() + */ + public String getPathTranslated() + { + if (_pathInfo == null || _context == null) + return null; + return _context.getRealPath(_pathInfo); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getProtocol() + */ + public String getProtocol() + { + return _protocol; + } + + /* ------------------------------------------------------------ */ + public String getQueryEncoding() + { + return _queryEncoding; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getQueryString() + */ + public String getQueryString() + { + if (_queryString == null && _uri != null) + { + if (_queryEncoding == null) + _queryString = _uri.getQuery(); + else + _queryString = _uri.getQuery(_queryEncoding); + } + return _queryString; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getReader() + */ + public BufferedReader getReader() throws IOException + { + if (_inputState != __NONE && _inputState != __READER) + throw new IllegalStateException("STREAMED"); + + if (_inputState == __READER) + return _reader; + + String encoding = getCharacterEncoding(); + if (encoding == null) + encoding = StringUtil.__ISO_8859_1; + + if (_reader == null || !encoding.equalsIgnoreCase(_readerEncoding)) + { + final ServletInputStream in = getInputStream(); + _readerEncoding = encoding; + _reader = new BufferedReader(new InputStreamReader(in,encoding)) + { + @Override + public void close() throws IOException + { + in.close(); + } + }; + } + _inputState = __READER; + return _reader; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getRealPath(java.lang.String) + */ + public String getRealPath(String path) + { + if (_context == null) + return null; + return _context.getRealPath(path); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getRemoteAddr() + */ + public String getRemoteAddr() + { + if (_remoteAddr != null) + return _remoteAddr; + return _endp == null?null:_endp.getRemoteAddr(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getRemoteHost() + */ + public String getRemoteHost() + { + if (_dns) + { + if (_remoteHost != null) + { + return _remoteHost; + } + return _endp == null?null:_endp.getRemoteHost(); + } + return getRemoteAddr(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getRemotePort() + */ + public int getRemotePort() + { + return _endp == null?0:_endp.getRemotePort(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getRemoteUser() + */ + public String getRemoteUser() + { + Principal p = getUserPrincipal(); + if (p == null) + return null; + return p.getName(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getRequestDispatcher(java.lang.String) + */ + public RequestDispatcher getRequestDispatcher(String path) + { + if (path == null || _context == null) + return null; + + // handle relative path + if (!path.startsWith("/")) + { + String relTo = URIUtil.addPaths(_servletPath,_pathInfo); + int slash = relTo.lastIndexOf("/"); + if (slash > 1) + relTo = relTo.substring(0,slash + 1); + else + relTo = "/"; + path = URIUtil.addPaths(relTo,path); + } + + return _context.getRequestDispatcher(path); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getRequestedSessionId() + */ + public String getRequestedSessionId() + { + return _requestedSessionId; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getRequestURI() + */ + public String getRequestURI() + { + if (_requestURI == null && _uri != null) + _requestURI = _uri.getPathAndParam(); + return _requestURI; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getRequestURL() + */ + public StringBuffer getRequestURL() + { + final StringBuffer url = new StringBuffer(48); + synchronized (url) + { + String scheme = getScheme(); + int port = getServerPort(); + + url.append(scheme); + url.append("://"); + url.append(getServerName()); + if (_port > 0 && ((scheme.equalsIgnoreCase(URIUtil.HTTP) && port != 80) || (scheme.equalsIgnoreCase(URIUtil.HTTPS) && port != 443))) + { + url.append(':'); + url.append(_port); + } + + url.append(getRequestURI()); + return url; + } + } + + /* ------------------------------------------------------------ */ + public Response getResponse() + { + return _connection._response; + } + + /* ------------------------------------------------------------ */ + /** + * Reconstructs the URL the client used to make the request. The returned URL contains a protocol, server name, port number, and, but it does not include a + * path. + * <p> + * Because this method returns a <code>StringBuffer</code>, not a string, you can modify the URL easily, for example, to append path and query parameters. + * + * This method is useful for creating redirect messages and for reporting errors. + * + * @return "scheme://host:port" + */ + public StringBuilder getRootURL() + { + StringBuilder url = new StringBuilder(48); + String scheme = getScheme(); + int port = getServerPort(); + + url.append(scheme); + url.append("://"); + url.append(getServerName()); + + if (port > 0 && ((scheme.equalsIgnoreCase("http") && port != 80) || (scheme.equalsIgnoreCase("https") && port != 443))) + { + url.append(':'); + url.append(port); + } + return url; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getScheme() + */ + public String getScheme() + { + return _scheme; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getServerName() + */ + public String getServerName() + { + // Return already determined host + if (_serverName != null) + return _serverName; + + if (_uri == null) + throw new IllegalStateException("No uri"); + + // Return host from absolute URI + _serverName = _uri.getHost(); + _port = _uri.getPort(); + if (_serverName != null) + return _serverName; + + // Return host from header field + Buffer hostPort = _connection.getRequestFields().get(HttpHeaders.HOST_BUFFER); + if (hostPort != null) + { + loop: for (int i = hostPort.putIndex(); i-- > hostPort.getIndex();) + { + char ch = (char)(0xff & hostPort.peek(i)); + switch (ch) + { + case ']': + break loop; + + case ':': + _serverName = BufferUtil.to8859_1_String(hostPort.peek(hostPort.getIndex(),i - hostPort.getIndex())); + try + { + _port = BufferUtil.toInt(hostPort.peek(i + 1,hostPort.putIndex() - i - 1)); + } + catch (NumberFormatException e) + { + try + { + if (_connection != null) + _connection._generator.sendError(HttpStatus.BAD_REQUEST_400,"Bad Host header",null,true); + } + catch (IOException e1) + { + throw new RuntimeException(e1); + } + } + return _serverName; + } + } + if (_serverName == null || _port < 0) + { + _serverName = BufferUtil.to8859_1_String(hostPort); + _port = 0; + } + + return _serverName; + } + + // Return host from connection + if (_connection != null) + { + _serverName = getLocalName(); + _port = getLocalPort(); + if (_serverName != null && !StringUtil.ALL_INTERFACES.equals(_serverName)) + return _serverName; + } + + // Return the local host + try + { + _serverName = InetAddress.getLocalHost().getHostAddress(); + } + catch (java.net.UnknownHostException e) + { + LOG.ignore(e); + } + return _serverName; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getServerPort() + */ + public int getServerPort() + { + if (_port <= 0) + { + if (_serverName == null) + getServerName(); + + if (_port <= 0) + { + if (_serverName != null && _uri != null) + _port = _uri.getPort(); + else + _port = _endp == null?0:_endp.getLocalPort(); + } + } + + if (_port <= 0) + { + if (getScheme().equalsIgnoreCase(URIUtil.HTTPS)) + return 443; + return 80; + } + return _port; + } + + /* ------------------------------------------------------------ */ + public ServletContext getServletContext() + { + return _context; + } + + /* ------------------------------------------------------------ */ + /* + */ + public String getServletName() + { + if (_scope != null) + return _scope.getName(); + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getServletPath() + */ + public String getServletPath() + { + if (_servletPath == null) + _servletPath = ""; + return _servletPath; + } + + /* ------------------------------------------------------------ */ + public ServletResponse getServletResponse() + { + return _connection.getResponse(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getSession() + */ + public HttpSession getSession() + { + return getSession(true); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getSession(boolean) + */ + public HttpSession getSession(boolean create) + { + if (_session != null) + { + if (_sessionManager != null && !_sessionManager.isValid(_session)) + _session = null; + else + return _session; + } + + if (!create) + return null; + + if (_sessionManager == null) + throw new IllegalStateException("No SessionManager"); + + _session = _sessionManager.newHttpSession(this); + HttpCookie cookie = _sessionManager.getSessionCookie(_session,getContextPath(),isSecure()); + if (cookie != null) + _connection.getResponse().addCookie(cookie); + + return _session; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the sessionManager. + */ + public SessionManager getSessionManager() + { + return _sessionManager; + } + + /* ------------------------------------------------------------ */ + /** + * Get Request TimeStamp + * + * @return The time that the request was received. + */ + public long getTimeStamp() + { + return _timeStamp; + } + + /* ------------------------------------------------------------ */ + /** + * Get Request TimeStamp + * + * @return The time that the request was received. + */ + public Buffer getTimeStampBuffer() + { + if (_timeStampBuffer == null && _timeStamp > 0) + _timeStampBuffer = HttpFields.__dateCache.formatBuffer(_timeStamp); + return _timeStampBuffer; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the uri. + */ + public HttpURI getUri() + { + return _uri; + } + + /* ------------------------------------------------------------ */ + public UserIdentity getUserIdentity() + { + if (_authentication instanceof Authentication.Deferred) + setAuthentication(((Authentication.Deferred)_authentication).authenticate(this)); + + if (_authentication instanceof Authentication.User) + return ((Authentication.User)_authentication).getUserIdentity(); + return null; + } + + /* ------------------------------------------------------------ */ + /** + * @return The resolved user Identity, which may be null if the {@link Authentication} is not {@link Authentication.User} (eg. + * {@link Authentication.Deferred}). + */ + public UserIdentity getResolvedUserIdentity() + { + if (_authentication instanceof Authentication.User) + return ((Authentication.User)_authentication).getUserIdentity(); + return null; + } + + /* ------------------------------------------------------------ */ + public UserIdentity.Scope getUserIdentityScope() + { + return _scope; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getUserPrincipal() + */ + public Principal getUserPrincipal() + { + if (_authentication instanceof Authentication.Deferred) + setAuthentication(((Authentication.Deferred)_authentication).authenticate(this)); + + if (_authentication instanceof Authentication.User) + { + UserIdentity user = ((Authentication.User)_authentication).getUserIdentity(); + return user.getUserPrincipal(); + } + + return null; + } + + /* ------------------------------------------------------------ */ + /** + * Get timestamp of the request dispatch + * + * @return timestamp + */ + public long getDispatchTime() + { + return _dispatchTime; + } + + /* ------------------------------------------------------------ */ + public boolean isHandled() + { + return _handled; + } + + public boolean isAsyncStarted() + { + return _async.isAsyncStarted(); + } + + + /* ------------------------------------------------------------ */ + public boolean isAsyncSupported() + { + return _asyncSupported; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromCookie() + */ + public boolean isRequestedSessionIdFromCookie() + { + return _requestedSessionId != null && _requestedSessionIdFromCookie; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromUrl() + */ + public boolean isRequestedSessionIdFromUrl() + { + return _requestedSessionId != null && !_requestedSessionIdFromCookie; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromURL() + */ + public boolean isRequestedSessionIdFromURL() + { + return _requestedSessionId != null && !_requestedSessionIdFromCookie; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdValid() + */ + public boolean isRequestedSessionIdValid() + { + if (_requestedSessionId == null) + return false; + + HttpSession session = getSession(false); + return (session != null && _sessionManager.getSessionIdManager().getClusterId(_requestedSessionId).equals(_sessionManager.getClusterId(session))); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#isSecure() + */ + public boolean isSecure() + { + return _connection.isConfidential(this); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#isUserInRole(java.lang.String) + */ + public boolean isUserInRole(String role) + { + if (_authentication instanceof Authentication.Deferred) + setAuthentication(((Authentication.Deferred)_authentication).authenticate(this)); + + if (_authentication instanceof Authentication.User) + return ((Authentication.User)_authentication).isUserInRole(_scope,role); + return false; + } + + /* ------------------------------------------------------------ */ + public HttpSession recoverNewSession(Object key) + { + if (_savedNewSessions == null) + return null; + return _savedNewSessions.get(key); + } + + /* ------------------------------------------------------------ */ + protected void recycle() + { + if (_inputState == __READER) + { + try + { + int r = _reader.read(); + while (r != -1) + r = _reader.read(); + } + catch (Exception e) + { + LOG.ignore(e); + _reader = null; + } + } + + setAuthentication(Authentication.NOT_CHECKED); + _async.recycle(); + _asyncSupported = true; + _handled = false; + if (_context != null) + throw new IllegalStateException("Request in context!"); + if (_attributes != null) + _attributes.clearAttributes(); + _characterEncoding = null; + _contextPath = null; + if (_cookies != null) + _cookies.reset(); + _cookiesExtracted = false; + _context = null; + _serverName = null; + _method = null; + _pathInfo = null; + _port = 0; + _protocol = HttpVersions.HTTP_1_1; + _queryEncoding = null; + _queryString = null; + _requestedSessionId = null; + _requestedSessionIdFromCookie = false; + _session = null; + _sessionManager = null; + _requestURI = null; + _scope = null; + _scheme = URIUtil.HTTP; + _servletPath = null; + _timeStamp = 0; + _timeStampBuffer = null; + _uri = null; + if (_baseParameters != null) + _baseParameters.clear(); + _parameters = null; + _paramsExtracted = false; + _inputState = __NONE; + + if (_savedNewSessions != null) + _savedNewSessions.clear(); + _savedNewSessions=null; + _multiPartInputStream = null; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#removeAttribute(java.lang.String) + */ + public void removeAttribute(String name) + { + Object old_value = _attributes == null?null:_attributes.getAttribute(name); + + if (_attributes != null) + _attributes.removeAttribute(name); + + if (old_value != null) + { + if (_requestAttributeListeners != null) + { + final ServletRequestAttributeEvent event = new ServletRequestAttributeEvent(_context,this,name,old_value); + final int size = LazyList.size(_requestAttributeListeners); + for (int i = 0; i < size; i++) + { + final EventListener listener = (ServletRequestAttributeListener)LazyList.get(_requestAttributeListeners,i); + if (listener instanceof ServletRequestAttributeListener) + { + final ServletRequestAttributeListener l = (ServletRequestAttributeListener)listener; + l.attributeRemoved(event); + } + } + } + } + } + + /* ------------------------------------------------------------ */ + public void removeEventListener(final EventListener listener) + { + _requestAttributeListeners = LazyList.remove(_requestAttributeListeners,listener); + } + + /* ------------------------------------------------------------ */ + public void saveNewSession(Object key, HttpSession session) + { + if (_savedNewSessions == null) + _savedNewSessions = new HashMap<Object, HttpSession>(); + _savedNewSessions.put(key,session); + } + + /* ------------------------------------------------------------ */ + public void setAsyncSupported(boolean supported) + { + _asyncSupported = supported; + } + + /* ------------------------------------------------------------ */ + /* + * Set a request attribute. if the attribute name is "org.eclipse.jetty.server.server.Request.queryEncoding" then the value is also passed in a call to + * {@link #setQueryEncoding}. <p> if the attribute name is "org.eclipse.jetty.server.server.ResponseBuffer", then the response buffer is flushed with @{link + * #flushResponseBuffer} <p> if the attribute name is "org.eclipse.jetty.io.EndPoint.maxIdleTime", then the value is passed to the associated {@link + * EndPoint#setMaxIdleTime}. + * + * @see javax.servlet.ServletRequest#setAttribute(java.lang.String, java.lang.Object) + */ + public void setAttribute(String name, Object value) + { + Object old_value = _attributes == null?null:_attributes.getAttribute(name); + + if (name.startsWith("org.eclipse.jetty.")) + { + if ("org.eclipse.jetty.server.Request.queryEncoding".equals(name)) + setQueryEncoding(value == null?null:value.toString()); + else if ("org.eclipse.jetty.server.sendContent".equals(name)) + { + try + { + ((AbstractHttpConnection.Output)getServletResponse().getOutputStream()).sendContent(value); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + else if ("org.eclipse.jetty.server.ResponseBuffer".equals(name)) + { + try + { + final ByteBuffer byteBuffer = (ByteBuffer)value; + synchronized (byteBuffer) + { + NIOBuffer buffer = byteBuffer.isDirect()?new DirectNIOBuffer(byteBuffer,true):new IndirectNIOBuffer(byteBuffer,true); + ((AbstractHttpConnection.Output)getServletResponse().getOutputStream()).sendResponse(buffer); + } + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + else if ("org.eclipse.jetty.io.EndPoint.maxIdleTime".equalsIgnoreCase(name)) + { + try + { + getConnection().getEndPoint().setMaxIdleTime(Integer.valueOf(value.toString())); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + } + + if (_attributes == null) + _attributes = new AttributesMap(); + _attributes.setAttribute(name,value); + + if (_requestAttributeListeners != null) + { + final ServletRequestAttributeEvent event = new ServletRequestAttributeEvent(_context,this,name,old_value == null?value:old_value); + final int size = LazyList.size(_requestAttributeListeners); + for (int i = 0; i < size; i++) + { + final EventListener listener = (ServletRequestAttributeListener)LazyList.get(_requestAttributeListeners,i); + if (listener instanceof ServletRequestAttributeListener) + { + final ServletRequestAttributeListener l = (ServletRequestAttributeListener)listener; + + if (old_value == null) + l.attributeAdded(event); + else if (value == null) + l.attributeRemoved(event); + else + l.attributeReplaced(event); + } + } + } + } + + /* ------------------------------------------------------------ */ + /* + */ + public void setAttributes(Attributes attributes) + { + _attributes = attributes; + } + + /* ------------------------------------------------------------ */ + + /* ------------------------------------------------------------ */ + /** + * Set the authentication. + * + * @param authentication + * the authentication to set + */ + public void setAuthentication(Authentication authentication) + { + _authentication = authentication; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#setCharacterEncoding(java.lang.String) + */ + public void setCharacterEncoding(String encoding) throws UnsupportedEncodingException + { + if (_inputState != __NONE) + return; + + _characterEncoding = encoding; + + // check encoding is supported + if (!StringUtil.isUTF8(encoding)) + // noinspection ResultOfMethodCallIgnored + "".getBytes(encoding); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#setCharacterEncoding(java.lang.String) + */ + public void setCharacterEncodingUnchecked(String encoding) + { + _characterEncoding = encoding; + } + + /* ------------------------------------------------------------ */ + // final so we can safely call this from constructor + protected final void setConnection(AbstractHttpConnection connection) + { + _connection = connection; + _async.setConnection(connection); + _endp = connection.getEndPoint(); + _dns = connection.getResolveNames(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getContentType() + */ + public void setContentType(String contentType) + { + _connection.getRequestFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,contentType); + + } + + /* ------------------------------------------------------------ */ + /** + * Set request context + * + * @param context + * context object + */ + public void setContext(Context context) + { + _newContext = _context != context; + _context = context; + } + + /* ------------------------------------------------------------ */ + /** + * @return True if this is the first call of {@link #takeNewContext()} since the last + * {@link #setContext(org.eclipse.jetty.server.handler.ContextHandler.Context)} call. + */ + public boolean takeNewContext() + { + boolean nc = _newContext; + _newContext = false; + return nc; + } + + /* ------------------------------------------------------------ */ + /** + * Sets the "context path" for this request + * + * @see HttpServletRequest#getContextPath() + */ + public void setContextPath(String contextPath) + { + _contextPath = contextPath; + } + + /* ------------------------------------------------------------ */ + /** + * @param cookies + * The cookies to set. + */ + public void setCookies(Cookie[] cookies) + { + if (_cookies == null) + _cookies = new CookieCutter(); + _cookies.setCookies(cookies); + } + + /* ------------------------------------------------------------ */ + public void setDispatcherType(DispatcherType type) + { + _dispatcherType = type; + } + + /* ------------------------------------------------------------ */ + public void setHandled(boolean h) + { + _handled = h; + } + + /* ------------------------------------------------------------ */ + /** + * @param method + * The method to set. + */ + public void setMethod(String method) + { + _method = method; + } + + /* ------------------------------------------------------------ */ + /** + * @param parameters + * The parameters to set. + */ + public void setParameters(MultiMap<String> parameters) + { + _parameters = (parameters == null)?_baseParameters:parameters; + if (_paramsExtracted && _parameters == null) + throw new IllegalStateException(); + } + + /* ------------------------------------------------------------ */ + /** + * @param pathInfo + * The pathInfo to set. + */ + public void setPathInfo(String pathInfo) + { + _pathInfo = pathInfo; + } + + /* ------------------------------------------------------------ */ + /** + * @param protocol + * The protocol to set. + */ + public void setProtocol(String protocol) + { + _protocol = protocol; + } + + /* ------------------------------------------------------------ */ + /** + * Set the character encoding used for the query string. This call will effect the return of getQueryString and getParamaters. It must be called before any + * geParameter methods. + * + * The request attribute "org.eclipse.jetty.server.server.Request.queryEncoding" may be set as an alternate method of calling setQueryEncoding. + * + * @param queryEncoding + */ + public void setQueryEncoding(String queryEncoding) + { + _queryEncoding = queryEncoding; + _queryString = null; + } + + /* ------------------------------------------------------------ */ + /** + * @param queryString + * The queryString to set. + */ + public void setQueryString(String queryString) + { + _queryString = queryString; + _queryEncoding = null; //assume utf-8 + } + + /* ------------------------------------------------------------ */ + /** + * @param addr + * The address to set. + */ + public void setRemoteAddr(String addr) + { + _remoteAddr = addr; + } + + /* ------------------------------------------------------------ */ + /** + * @param host + * The host to set. + */ + public void setRemoteHost(String host) + { + _remoteHost = host; + } + + /* ------------------------------------------------------------ */ + /** + * @param requestedSessionId + * The requestedSessionId to set. + */ + public void setRequestedSessionId(String requestedSessionId) + { + _requestedSessionId = requestedSessionId; + } + + /* ------------------------------------------------------------ */ + /** + * @param requestedSessionIdCookie + * The requestedSessionIdCookie to set. + */ + public void setRequestedSessionIdFromCookie(boolean requestedSessionIdCookie) + { + _requestedSessionIdFromCookie = requestedSessionIdCookie; + } + + /* ------------------------------------------------------------ */ + /** + * @param requestURI + * The requestURI to set. + */ + public void setRequestURI(String requestURI) + { + _requestURI = requestURI; + } + + /* ------------------------------------------------------------ */ + /** + * @param scheme + * The scheme to set. + */ + public void setScheme(String scheme) + { + _scheme = scheme; + } + + /* ------------------------------------------------------------ */ + /** + * @param host + * The host to set. + */ + public void setServerName(String host) + { + _serverName = host; + } + + /* ------------------------------------------------------------ */ + /** + * @param port + * The port to set. + */ + public void setServerPort(int port) + { + _port = port; + } + + /* ------------------------------------------------------------ */ + /** + * @param servletPath + * The servletPath to set. + */ + public void setServletPath(String servletPath) + { + _servletPath = servletPath; + } + + /* ------------------------------------------------------------ */ + /** + * @param session + * The session to set. + */ + public void setSession(HttpSession session) + { + _session = session; + } + + /* ------------------------------------------------------------ */ + /** + * @param sessionManager + * The sessionManager to set. + */ + public void setSessionManager(SessionManager sessionManager) + { + _sessionManager = sessionManager; + } + + /* ------------------------------------------------------------ */ + public void setTimeStamp(long ts) + { + _timeStamp = ts; + } + + /* ------------------------------------------------------------ */ + /** + * @param uri + * The uri to set. + */ + public void setUri(HttpURI uri) + { + _uri = uri; + } + + /* ------------------------------------------------------------ */ + public void setUserIdentityScope(UserIdentity.Scope scope) + { + _scope = scope; + } + + /* ------------------------------------------------------------ */ + /** + * Set timetstamp of request dispatch + * + * @param value + * timestamp + */ + public void setDispatchTime(long value) + { + _dispatchTime = value; + } + + /* ------------------------------------------------------------ */ + public AsyncContext startAsync() throws IllegalStateException + { + if (!_asyncSupported) + throw new IllegalStateException("!asyncSupported"); + _async.startAsync(); + return _async; + } + + /* ------------------------------------------------------------ */ + public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException + { + if (!_asyncSupported) + throw new IllegalStateException("!asyncSupported"); + _async.startAsync(_context,servletRequest,servletResponse); + return _async; + } + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + return (_handled?"[":"(") + getMethod() + " " + _uri + (_handled?"]@":")@") + hashCode() + " " + super.toString(); + } + + /* ------------------------------------------------------------ */ + public boolean authenticate(HttpServletResponse response) throws IOException, ServletException + { + if (_authentication instanceof Authentication.Deferred) + { + setAuthentication(((Authentication.Deferred)_authentication).authenticate(this,response)); + return !(_authentication instanceof Authentication.ResponseSent); + } + response.sendError(HttpStatus.UNAUTHORIZED_401); + return false; + } + + /* ------------------------------------------------------------ */ + public Part getPart(String name) throws IOException, ServletException + { + getParts(); + return _multiPartInputStream.getPart(name); + } + + /* ------------------------------------------------------------ */ + public Collection<Part> getParts() throws IOException, ServletException + { + if (getContentType() == null || !getContentType().startsWith("multipart/form-data")) + throw new ServletException("Content-Type != multipart/form-data"); + + if (_multiPartInputStream == null) + _multiPartInputStream = (MultiPartInputStream)getAttribute(__MULTIPART_INPUT_STREAM); + + if (_multiPartInputStream == null) + { + MultipartConfigElement config = (MultipartConfigElement)getAttribute(__MULTIPART_CONFIG_ELEMENT); + + if (config == null) + throw new IllegalStateException("No multipart config for servlet"); + + _multiPartInputStream = new MultiPartInputStream(getInputStream(), + 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 + for (Part p:parts) + { + MultiPartInputStream.MultiPart mp = (MultiPartInputStream.MultiPart)p; + if (mp.getContentDispositionFilename() == null) + { + //Servlet Spec 3.0 pg 23, parts without filenames must be put into init params + String charset = null; + if (mp.getContentType() != null) + charset = MimeTypes.getCharsetFromContentType(new ByteArrayBuffer(mp.getContentType())); + + ByteArrayOutputStream os = null; + InputStream is = mp.getInputStream(); //get the bytes regardless of being in memory or in temp file + try + { + os = new ByteArrayOutputStream(); + IO.copy(is, os); + String content=new String(os.toByteArray(),charset==null?StringUtil.__UTF8:charset); + getParameter(""); //cause params to be evaluated + getParameters().add(mp.getName(), content); + } + finally + { + IO.close(os); + IO.close(is); + } + } + } + } + + return _multiPartInputStream.getParts(); + } + + /* ------------------------------------------------------------ */ + public void login(String username, String password) throws ServletException + { + if (_authentication instanceof Authentication.Deferred) + { + _authentication=((Authentication.Deferred)_authentication).login(username,password,this); + if (_authentication == null) + throw new ServletException(); + } + else + { + throw new ServletException("Authenticated as "+_authentication); + } + } + + /* ------------------------------------------------------------ */ + public void logout() throws ServletException + { + if (_authentication instanceof Authentication.User) + ((Authentication.User)_authentication).logout(); + _authentication=Authentication.UNAUTHENTICATED; + } + + /* ------------------------------------------------------------ */ + /** + * Merge in a new query string. The query string is merged with the existing parameters and {@link #setParameters(MultiMap)} and + * {@link #setQueryString(String)} are called with the result. The merge is according to the rules of the servlet dispatch forward method. + * + * @param query + * The query string to merge into the request. + */ + public void mergeQueryString(String query) + { + // extract parameters from dispatch query + MultiMap<String> parameters = new MultiMap<String>(); + UrlEncoded.decodeTo(query,parameters, StringUtil.__UTF8); //have to assume UTF-8 because we can't know otherwise + + boolean merge_old_query = false; + + // Have we evaluated parameters + if (!_paramsExtracted) + extractParameters(); + + // Are there any existing parameters? + if (_parameters != null && _parameters.size() > 0) + { + // Merge parameters; new parameters of the same name take precedence. + Iterator<Entry<String, Object>> iter = _parameters.entrySet().iterator(); + while (iter.hasNext()) + { + Map.Entry<String, Object> entry = iter.next(); + String name = entry.getKey(); + + // If the names match, we will need to remake the query string + if (parameters.containsKey(name)) + merge_old_query = true; + + // Add the old values to the new parameter map + Object values = entry.getValue(); + for (int i = 0; i < LazyList.size(values); i++) + parameters.add(name,LazyList.get(values,i)); + } + } + + if (_queryString != null && _queryString.length() > 0) + { + if (merge_old_query) + { + StringBuilder overridden_query_string = new StringBuilder(); + MultiMap<String> overridden_old_query = new MultiMap<String>(); + UrlEncoded.decodeTo(_queryString,overridden_old_query,getQueryEncoding());//decode using any queryencoding set for the request + + + MultiMap<String> overridden_new_query = new MultiMap<String>(); + UrlEncoded.decodeTo(query,overridden_new_query,StringUtil.__UTF8); //have to assume utf8 as we cannot know otherwise + + Iterator<Entry<String, Object>> iter = overridden_old_query.entrySet().iterator(); + while (iter.hasNext()) + { + Map.Entry<String, Object> entry = iter.next(); + String name = entry.getKey(); + if (!overridden_new_query.containsKey(name)) + { + Object values = entry.getValue(); + for (int i = 0; i < LazyList.size(values); i++) + { + overridden_query_string.append("&").append(name).append("=").append((Object)LazyList.get(values,i)); + } + } + } + + query = query + overridden_query_string; + } + else + { + query = query + "&" + _queryString; + } + } + + setParameters(parameters); + setQueryString(query); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/RequestLog.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,30 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import org.eclipse.jetty.util.component.LifeCycle; + +/** + * A <code>RequestLog</code> can be attached to a {@link org.eclipse.jetty.server.handler.RequestLogHandler} to enable + * logging of requests/responses. + */ +public interface RequestLog extends LifeCycle +{ + public void log(Request request, Response response); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/ResourceCache.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,522 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Comparator; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.jetty.http.HttpContent; +import org.eclipse.jetty.http.HttpContent.ResourceAsHttpContent; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.View; +import org.eclipse.jetty.io.nio.DirectNIOBuffer; +import org.eclipse.jetty.io.nio.IndirectNIOBuffer; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.resource.ResourceFactory; + + +/* ------------------------------------------------------------ */ +/** + * + */ +public class ResourceCache +{ + private static final Logger LOG = Log.getLogger(ResourceCache.class); + + private final ConcurrentMap<String,Content> _cache; + private final AtomicInteger _cachedSize; + private final AtomicInteger _cachedFiles; + private final ResourceFactory _factory; + private final ResourceCache _parent; + private final MimeTypes _mimeTypes; + private final boolean _etags; + + private boolean _useFileMappedBuffer=true; + private int _maxCachedFileSize =4*1024*1024; + private int _maxCachedFiles=2048; + private int _maxCacheSize =32*1024*1024; + + /* ------------------------------------------------------------ */ + /** Constructor. + * @param mimeTypes Mimetype to use for meta data + */ + public ResourceCache(ResourceCache parent, ResourceFactory factory, MimeTypes mimeTypes,boolean useFileMappedBuffer,boolean etags) + { + _factory = factory; + _cache=new ConcurrentHashMap<String,Content>(); + _cachedSize=new AtomicInteger(); + _cachedFiles=new AtomicInteger(); + _mimeTypes=mimeTypes; + _parent=parent; + _etags=etags; + _useFileMappedBuffer=useFileMappedBuffer; + } + + /* ------------------------------------------------------------ */ + public int getCachedSize() + { + return _cachedSize.get(); + } + + /* ------------------------------------------------------------ */ + public int getCachedFiles() + { + return _cachedFiles.get(); + } + + /* ------------------------------------------------------------ */ + public int getMaxCachedFileSize() + { + return _maxCachedFileSize; + } + + /* ------------------------------------------------------------ */ + public void setMaxCachedFileSize(int maxCachedFileSize) + { + _maxCachedFileSize = maxCachedFileSize; + shrinkCache(); + } + + /* ------------------------------------------------------------ */ + public int getMaxCacheSize() + { + return _maxCacheSize; + } + + /* ------------------------------------------------------------ */ + public void setMaxCacheSize(int maxCacheSize) + { + _maxCacheSize = maxCacheSize; + shrinkCache(); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the maxCachedFiles. + */ + public int getMaxCachedFiles() + { + return _maxCachedFiles; + } + + /* ------------------------------------------------------------ */ + /** + * @param maxCachedFiles The maxCachedFiles to set. + */ + public void setMaxCachedFiles(int maxCachedFiles) + { + _maxCachedFiles = maxCachedFiles; + shrinkCache(); + } + + /* ------------------------------------------------------------ */ + public boolean isUseFileMappedBuffer() + { + return _useFileMappedBuffer; + } + + /* ------------------------------------------------------------ */ + public void setUseFileMappedBuffer(boolean useFileMappedBuffer) + { + _useFileMappedBuffer = useFileMappedBuffer; + } + + /* ------------------------------------------------------------ */ + public void flushCache() + { + if (_cache!=null) + { + while (_cache.size()>0) + { + for (String path : _cache.keySet()) + { + Content content = _cache.remove(path); + if (content!=null) + content.invalidate(); + } + } + } + } + + /* ------------------------------------------------------------ */ + /** 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 ResourceAsHttpContent} instance is return. If + * the resource does not exist, then null is returned. + * @throws IOException Problem loading the resource + */ + public HttpContent lookup(String pathInContext) + throws IOException + { + // Is the content in this cache? + Content content =_cache.get(pathInContext); + if (content!=null && (content).isValid()) + return content; + + // try loading the content from our factory. + Resource resource=_factory.getResource(pathInContext); + HttpContent loaded = load(pathInContext,resource); + if (loaded!=null) + return loaded; + + // Is the content in the parent cache? + if (_parent!=null) + { + HttpContent httpContent=_parent.lookup(pathInContext); + if (httpContent!=null) + return httpContent; + } + + return null; + } + + /* ------------------------------------------------------------ */ + /** + * @param resource + * @return True if the resource is cacheable. The default implementation tests the cache sizes. + */ + protected boolean isCacheable(Resource resource) + { + long len = resource.length(); + + // Will it fit in the cache? + return (len>0 && len<_maxCachedFileSize && len<_maxCacheSize); + } + + /* ------------------------------------------------------------ */ + private HttpContent load(String pathInContext, Resource resource) + throws IOException + { + Content content=null; + + if (resource==null || !resource.exists()) + return null; + + // Will it fit in the cache? + if (!resource.isDirectory() && isCacheable(resource)) + { + // Create the Content (to increment the cache sizes before adding the content + content = new Content(pathInContext,resource); + + // reduce the cache to an acceptable size. + shrinkCache(); + + // Add it to the cache. + Content added = _cache.putIfAbsent(pathInContext,content); + if (added!=null) + { + content.invalidate(); + content=added; + } + + return content; + } + + return new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),getMaxCachedFileSize(),_etags); + + } + + /* ------------------------------------------------------------ */ + private void shrinkCache() + { + // While we need to shrink + while (_cache.size()>0 && (_cachedFiles.get()>_maxCachedFiles || _cachedSize.get()>_maxCacheSize)) + { + // Scan the entire cache and generate an ordered list by last accessed time. + SortedSet<Content> sorted= new TreeSet<Content>( + new Comparator<Content>() + { + public int compare(Content c1, Content c2) + { + if (c1._lastAccessed<c2._lastAccessed) + return -1; + + if (c1._lastAccessed>c2._lastAccessed) + return 1; + + if (c1._length<c2._length) + return -1; + + return c1._key.compareTo(c2._key); + } + }); + for (Content content : _cache.values()) + sorted.add(content); + + // Invalidate least recently used first + for (Content content : sorted) + { + if (_cachedFiles.get()<=_maxCachedFiles && _cachedSize.get()<=_maxCacheSize) + break; + if (content==_cache.remove(content.getKey())) + content.invalidate(); + } + } + } + + /* ------------------------------------------------------------ */ + protected Buffer getIndirectBuffer(Resource resource) + { + try + { + int len=(int)resource.length(); + if (len<0) + { + LOG.warn("invalid resource: "+String.valueOf(resource)+" "+len); + return null; + } + Buffer buffer = new IndirectNIOBuffer(len); + InputStream is = resource.getInputStream(); + buffer.readFrom(is,len); + is.close(); + return buffer; + } + catch(IOException e) + { + LOG.warn(e); + return null; + } + } + + /* ------------------------------------------------------------ */ + protected Buffer getDirectBuffer(Resource resource) + { + try + { + if (_useFileMappedBuffer && resource.getFile()!=null) + return new DirectNIOBuffer(resource.getFile()); + + int len=(int)resource.length(); + if (len<0) + { + LOG.warn("invalid resource: "+String.valueOf(resource)+" "+len); + return null; + } + Buffer buffer = new DirectNIOBuffer(len); + InputStream is = resource.getInputStream(); + buffer.readFrom(is,len); + is.close(); + return buffer; + } + catch(IOException e) + { + LOG.warn(e); + return null; + } + } + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + return "ResourceCache["+_parent+","+_factory+"]@"+hashCode(); + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /** MetaData associated with a context Resource. + */ + public class Content implements HttpContent + { + final Resource _resource; + final int _length; + final String _key; + final long _lastModified; + final Buffer _lastModifiedBytes; + final Buffer _contentType; + final Buffer _etagBuffer; + + volatile long _lastAccessed; + AtomicReference<Buffer> _indirectBuffer=new AtomicReference<Buffer>(); + AtomicReference<Buffer> _directBuffer=new AtomicReference<Buffer>(); + + /* ------------------------------------------------------------ */ + Content(String pathInContext,Resource resource) + { + _key=pathInContext; + _resource=resource; + + _contentType=_mimeTypes.getMimeByExtension(_resource.toString()); + boolean exists=resource.exists(); + _lastModified=exists?resource.lastModified():-1; + _lastModifiedBytes=_lastModified<0?null:new ByteArrayBuffer(HttpFields.formatDate(_lastModified)); + + _length=exists?(int)resource.length():0; + _cachedSize.addAndGet(_length); + _cachedFiles.incrementAndGet(); + _lastAccessed=System.currentTimeMillis(); + + _etagBuffer=_etags?new ByteArrayBuffer(resource.getWeakETag()):null; + } + + + /* ------------------------------------------------------------ */ + public String getKey() + { + return _key; + } + + /* ------------------------------------------------------------ */ + public boolean isCached() + { + return _key!=null; + } + + /* ------------------------------------------------------------ */ + public boolean isMiss() + { + return false; + } + + /* ------------------------------------------------------------ */ + public Resource getResource() + { + return _resource; + } + + /* ------------------------------------------------------------ */ + public Buffer getETag() + { + return _etagBuffer; + } + + /* ------------------------------------------------------------ */ + boolean isValid() + { + if (_lastModified==_resource.lastModified() && _length==_resource.length()) + { + _lastAccessed=System.currentTimeMillis(); + return true; + } + + if (this==_cache.remove(_key)) + invalidate(); + return false; + } + + /* ------------------------------------------------------------ */ + protected void invalidate() + { + // Invalidate it + _cachedSize.addAndGet(-_length); + _cachedFiles.decrementAndGet(); + _resource.release(); + } + + /* ------------------------------------------------------------ */ + public Buffer getLastModified() + { + return _lastModifiedBytes; + } + + /* ------------------------------------------------------------ */ + public Buffer getContentType() + { + return _contentType; + } + + /* ------------------------------------------------------------ */ + public void release() + { + // don't release while cached. Release when invalidated. + } + + /* ------------------------------------------------------------ */ + public Buffer getIndirectBuffer() + { + Buffer buffer = _indirectBuffer.get(); + if (buffer==null) + { + Buffer buffer2=ResourceCache.this.getIndirectBuffer(_resource); + + if (buffer2==null) + LOG.warn("Could not load "+this); + else if (_indirectBuffer.compareAndSet(null,buffer2)) + buffer=buffer2; + else + buffer=_indirectBuffer.get(); + } + if (buffer==null) + return null; + return new View(buffer); + } + + + /* ------------------------------------------------------------ */ + public Buffer getDirectBuffer() + { + Buffer buffer = _directBuffer.get(); + if (buffer==null) + { + Buffer buffer2=ResourceCache.this.getDirectBuffer(_resource); + + if (buffer2==null) + LOG.warn("Could not load "+this); + else if (_directBuffer.compareAndSet(null,buffer2)) + buffer=buffer2; + else + buffer=_directBuffer.get(); + } + if (buffer==null) + return null; + + return new View(buffer); + } + + /* ------------------------------------------------------------ */ + public long getContentLength() + { + return _length; + } + + /* ------------------------------------------------------------ */ + public InputStream getInputStream() throws IOException + { + Buffer indirect = getIndirectBuffer(); + if (indirect!=null && indirect.array()!=null) + return new ByteArrayInputStream(indirect.array(),indirect.getIndex(),indirect.length()); + + return _resource.getInputStream(); + } + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + return String.format("%s %s %d %s %s",_resource,_resource.exists(),_resource.lastModified(),_contentType,_lastModifiedBytes); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/Response.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,1307 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Collection; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Enumeration; +import java.util.Locale; + +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletOutputStream; +import javax.servlet.SessionTrackingMode; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.eclipse.jetty.http.HttpCookie; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpGenerator; +import org.eclipse.jetty.http.HttpHeaderValues; +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.HttpSchemes; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.http.HttpVersions; +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.io.BufferCache.CachedBuffer; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.ErrorHandler; +import org.eclipse.jetty.util.ByteArrayISO8859Writer; +import org.eclipse.jetty.util.QuotedStringTokenizer; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** Response. + * <p> + * Implements {@link javax.servlet.http.HttpServletResponse} from the <code>javax.servlet.http</code> package. + * </p> + */ +public class Response implements HttpServletResponse +{ + private static final Logger LOG = Log.getLogger(Response.class); + + + public static final int + NONE=0, + STREAM=1, + WRITER=2; + + /** + * If a header name starts with this string, the header (stripped of the prefix) + * can be set during include using only {@link #setHeader(String, String)} or + * {@link #addHeader(String, String)}. + */ + public final static String SET_INCLUDE_HEADER_PREFIX = "org.eclipse.jetty.server.include."; + + /** + * If this string is found within the comment of a cookie added with {@link #addCookie(Cookie)}, then the cookie + * will be set as HTTP ONLY. + */ + public final static String HTTP_ONLY_COMMENT="__HTTP_ONLY__"; + + + /* ------------------------------------------------------------ */ + public static Response getResponse(HttpServletResponse response) + { + if (response instanceof Response) + return (Response)response; + + return AbstractHttpConnection.getCurrentConnection().getResponse(); + } + + private final AbstractHttpConnection _connection; + private int _status=SC_OK; + private String _reason; + private Locale _locale; + private String _mimeType; + private CachedBuffer _cachedMimeType; + private String _characterEncoding; + private boolean _explicitEncoding; + private String _contentType; + private volatile int _outputState; + private PrintWriter _writer; + + /* ------------------------------------------------------------ */ + /** + * + */ + public Response(AbstractHttpConnection connection) + { + _connection=connection; + } + + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#reset() + */ + protected void recycle() + { + _status=SC_OK; + _reason=null; + _locale=null; + _mimeType=null; + _cachedMimeType=null; + _characterEncoding=null; + _explicitEncoding=false; + _contentType=null; + _writer=null; + _outputState=NONE; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#addCookie(javax.servlet.http.Cookie) + */ + public void addCookie(HttpCookie cookie) + { + _connection.getResponseFields().addSetCookie(cookie); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#addCookie(javax.servlet.http.Cookie) + */ + public void addCookie(Cookie cookie) + { + String comment=cookie.getComment(); + boolean http_only=false; + + if (comment!=null) + { + int i=comment.indexOf(HTTP_ONLY_COMMENT); + if (i>=0) + { + http_only=true; + comment=comment.replace(HTTP_ONLY_COMMENT,"").trim(); + if (comment.length()==0) + comment=null; + } + } + _connection.getResponseFields().addSetCookie(cookie.getName(), + cookie.getValue(), + cookie.getDomain(), + cookie.getPath(), + cookie.getMaxAge(), + comment, + cookie.getSecure(), + http_only || cookie.isHttpOnly(), + cookie.getVersion()); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#containsHeader(java.lang.String) + */ + public boolean containsHeader(String name) + { + return _connection.getResponseFields().containsKey(name); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#encodeURL(java.lang.String) + */ + public String encodeURL(String url) + { + final Request request=_connection.getRequest(); + SessionManager sessionManager = request.getSessionManager(); + if (sessionManager==null) + return url; + + HttpURI uri = null; + if (sessionManager.isCheckingRemoteSessionIdEncoding() && URIUtil.hasScheme(url)) + { + uri = new HttpURI(url); + String path = uri.getPath(); + path = (path == null?"":path); + int port=uri.getPort(); + if (port<0) + port = HttpSchemes.HTTPS.equalsIgnoreCase(uri.getScheme())?443:80; + if (!request.getServerName().equalsIgnoreCase(uri.getHost()) || + request.getServerPort()!=port || + !path.startsWith(request.getContextPath())) //TODO the root context path is "", with which every non null string starts + return url; + } + + String sessionURLPrefix = sessionManager.getSessionIdPathParameterNamePrefix(); + if (sessionURLPrefix==null) + return url; + + if (url==null) + return null; + + // should not encode if cookies in evidence + if ((sessionManager.isUsingCookies() && request.isRequestedSessionIdFromCookie()) || !sessionManager.isUsingURLs()) + { + int prefix=url.indexOf(sessionURLPrefix); + if (prefix!=-1) + { + int suffix=url.indexOf("?",prefix); + if (suffix<0) + suffix=url.indexOf("#",prefix); + + if (suffix<=prefix) + return url.substring(0,prefix); + return url.substring(0,prefix)+url.substring(suffix); + } + return url; + } + + // get session; + HttpSession session=request.getSession(false); + + // no session + if (session == null) + return url; + + // invalid session + if (!sessionManager.isValid(session)) + return url; + + String id=sessionManager.getNodeId(session); + + if (uri == null) + uri = new HttpURI(url); + + + // Already encoded + int prefix=url.indexOf(sessionURLPrefix); + if (prefix!=-1) + { + int suffix=url.indexOf("?",prefix); + if (suffix<0) + suffix=url.indexOf("#",prefix); + + if (suffix<=prefix) + return url.substring(0,prefix+sessionURLPrefix.length())+id; + return url.substring(0,prefix+sessionURLPrefix.length())+id+ + url.substring(suffix); + } + + // edit the session + int suffix=url.indexOf('?'); + if (suffix<0) + suffix=url.indexOf('#'); + if (suffix<0) + { + return url+ + ((HttpSchemes.HTTPS.equalsIgnoreCase(uri.getScheme()) || HttpSchemes.HTTP.equalsIgnoreCase(uri.getScheme())) && uri.getPath()==null?"/":"") + //if no path, insert the root path + sessionURLPrefix+id; + } + + + return url.substring(0,suffix)+ + ((HttpSchemes.HTTPS.equalsIgnoreCase(uri.getScheme()) || HttpSchemes.HTTP.equalsIgnoreCase(uri.getScheme())) && uri.getPath()==null?"/":"")+ //if no path so insert the root path + sessionURLPrefix+id+url.substring(suffix); + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.http.HttpServletResponse#encodeRedirectURL(java.lang.String) + */ + public String encodeRedirectURL(String url) + { + return encodeURL(url); + } + + /* ------------------------------------------------------------ */ + @Deprecated + public String encodeUrl(String url) + { + return encodeURL(url); + } + + /* ------------------------------------------------------------ */ + @Deprecated + public String encodeRedirectUrl(String url) + { + return encodeRedirectURL(url); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#sendError(int, java.lang.String) + */ + public void sendError(int code, String message) throws IOException + { + if (_connection.isIncluding()) + return; + + if (isCommitted()) + LOG.warn("Committed before "+code+" "+message); + + resetBuffer(); + _characterEncoding=null; + setHeader(HttpHeaders.EXPIRES,null); + setHeader(HttpHeaders.LAST_MODIFIED,null); + setHeader(HttpHeaders.CACHE_CONTROL,null); + setHeader(HttpHeaders.CONTENT_TYPE,null); + setHeader(HttpHeaders.CONTENT_LENGTH,null); + + _outputState=NONE; + setStatus(code,message); + + if (message==null) + message=HttpStatus.getMessage(code); + + // If we are allowed to have a body + if (code!=SC_NO_CONTENT && + code!=SC_NOT_MODIFIED && + code!=SC_PARTIAL_CONTENT && + code>=SC_OK) + { + Request request = _connection.getRequest(); + + ErrorHandler error_handler = null; + ContextHandler.Context context = request.getContext(); + if (context!=null) + error_handler=context.getContextHandler().getErrorHandler(); + if (error_handler==null) + error_handler = _connection.getConnector().getServer().getBean(ErrorHandler.class); + 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,_connection.getRequest(),_connection.getRequest(),this ); + } + else + { + setHeader(HttpHeaders.CACHE_CONTROL, "must-revalidate,no-cache,no-store"); + setContentType(MimeTypes.TEXT_HTML_8859_1); + ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(2048); + if (message != null) + { + message= StringUtil.replace(message, "&", "&"); + message= StringUtil.replace(message, "<", "<"); + message= StringUtil.replace(message, ">", ">"); + } + String uri= request.getRequestURI(); + if (uri!=null) + { + uri= StringUtil.replace(uri, "&", "&"); + uri= StringUtil.replace(uri, "<", "<"); + uri= StringUtil.replace(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) + message=HttpStatus.getMessage(code); + writer.write(message); + writer.write("</title>\n</head>\n<body>\n<h2>HTTP ERROR: "); + writer.write(Integer.toString(code)); + writer.write("</h2>\n<p>Problem accessing "); + writer.write(uri); + writer.write(". Reason:\n<pre> "); + writer.write(message); + writer.write("</pre>"); + writer.write("</p>\n<hr /><i><small>Powered by Jetty://</small></i>"); + + for (int i= 0; i < 20; i++) + writer.write("\n "); + writer.write("\n</body>\n</html>\n"); + + writer.flush(); + setContentLength(writer.size()); + writer.writeTo(getOutputStream()); + writer.destroy(); + } + } + else if (code!=SC_PARTIAL_CONTENT) + { + _connection.getRequestFields().remove(HttpHeaders.CONTENT_TYPE_BUFFER); + _connection.getRequestFields().remove(HttpHeaders.CONTENT_LENGTH_BUFFER); + _characterEncoding=null; + _mimeType=null; + _cachedMimeType=null; + } + + complete(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#sendError(int) + */ + public void sendError(int sc) throws IOException + { + if (sc==102) + sendProcessing(); + else + sendError(sc,null); + } + + /* ------------------------------------------------------------ */ + /* Send a 102-Processing response. + * If the connection is a HTTP connection, the version is 1.1 and the + * request has a Expect header starting with 102, then a 102 response is + * sent. This indicates that the request still be processed and real response + * can still be sent. This method is called by sendError if it is passed 102. + * @see javax.servlet.http.HttpServletResponse#sendError(int) + */ + public void sendProcessing() throws IOException + { + if (_connection.isExpecting102Processing() && !isCommitted()) + ((HttpGenerator)_connection.getGenerator()).send1xx(HttpStatus.PROCESSING_102); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#sendRedirect(java.lang.String) + */ + public void sendRedirect(String location) throws IOException + { + if (_connection.isIncluding()) + return; + + if (location==null) + throw new IllegalArgumentException(); + + if (!URIUtil.hasScheme(location)) + { + StringBuilder buf = _connection.getRequest().getRootURL(); + if (location.startsWith("/")) + buf.append(location); + else + { + String path=_connection.getRequest().getRequestURI(); + String parent=(path.endsWith("/"))?path:URIUtil.parentPath(path); + location=URIUtil.addPaths(parent,location); + if(location==null) + throw new IllegalStateException("path cannot be above root"); + if (!location.startsWith("/")) + buf.append('/'); + buf.append(location); + } + + location=buf.toString(); + HttpURI uri = new HttpURI(location); + String path=uri.getDecodedPath(); + String canonical=URIUtil.canonicalPath(path); + if (canonical==null) + throw new IllegalArgumentException(); + if (!canonical.equals(path)) + { + buf = _connection.getRequest().getRootURL(); + buf.append(URIUtil.encodePath(canonical)); + String param=uri.getParam(); + if (param!=null) + { + buf.append(';'); + buf.append(param); + } + String query=uri.getQuery(); + if (query!=null) + { + buf.append('?'); + buf.append(query); + } + String fragment=uri.getFragment(); + if (fragment!=null) + { + buf.append('#'); + buf.append(fragment); + } + location=buf.toString(); + } + } + + resetBuffer(); + setHeader(HttpHeaders.LOCATION,location); + setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); + complete(); + + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#setDateHeader(java.lang.String, long) + */ + public void setDateHeader(String name, long date) + { + if (!_connection.isIncluding()) + _connection.getResponseFields().putDateField(name, date); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#addDateHeader(java.lang.String, long) + */ + public void addDateHeader(String name, long date) + { + if (!_connection.isIncluding()) + _connection.getResponseFields().addDateField(name, date); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#setHeader(java.lang.String, java.lang.String) + */ + public void setHeader(String name, String value) + { + if (HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(name)) + setContentType(value); + else + { + if (_connection.isIncluding()) + { + if (name.startsWith(SET_INCLUDE_HEADER_PREFIX)) + name=name.substring(SET_INCLUDE_HEADER_PREFIX.length()); + else + return; + } + _connection.getResponseFields().put(name, value); + if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name)) + { + if (value==null) + _connection._generator.setContentLength(-1); + else + _connection._generator.setContentLength(Long.parseLong(value)); + } + } + } + + + /* ------------------------------------------------------------ */ + public Collection<String> getHeaderNames() + { + final HttpFields fields=_connection.getResponseFields(); + return fields.getFieldNamesCollection(); + } + + /* ------------------------------------------------------------ */ + /* + */ + public String getHeader(String name) + { + return _connection.getResponseFields().getStringField(name); + } + + /* ------------------------------------------------------------ */ + /* + */ + public Collection<String> getHeaders(String name) + { + final HttpFields fields=_connection.getResponseFields(); + Collection<String> i = fields.getValuesCollection(name); + if (i==null) + return Collections.EMPTY_LIST; + return i; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#addHeader(java.lang.String, java.lang.String) + */ + public void addHeader(String name, String value) + { + + if (_connection.isIncluding()) + { + if (name.startsWith(SET_INCLUDE_HEADER_PREFIX)) + name=name.substring(SET_INCLUDE_HEADER_PREFIX.length()); + else + return; + } + + if (HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(name)) + { + setContentType(value); + return; + } + + _connection.getResponseFields().add(name, value); + if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name)) + _connection._generator.setContentLength(Long.parseLong(value)); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#setIntHeader(java.lang.String, int) + */ + public void setIntHeader(String name, int value) + { + if (!_connection.isIncluding()) + { + _connection.getResponseFields().putLongField(name, value); + if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name)) + _connection._generator.setContentLength(value); + } + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#addIntHeader(java.lang.String, int) + */ + public void addIntHeader(String name, int value) + { + if (!_connection.isIncluding()) + { + _connection.getResponseFields().addLongField(name, value); + if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name)) + _connection._generator.setContentLength(value); + } + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#setStatus(int) + */ + public void setStatus(int sc) + { + setStatus(sc,null); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletResponse#setStatus(int, java.lang.String) + */ + public void setStatus(int sc, String sm) + { + if (sc<=0) + throw new IllegalArgumentException(); + if (!_connection.isIncluding()) + { + _status=sc; + _reason=sm; + } + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#getCharacterEncoding() + */ + public String getCharacterEncoding() + { + if (_characterEncoding==null) + _characterEncoding=StringUtil.__ISO_8859_1; + return _characterEncoding; + } + + /* ------------------------------------------------------------ */ + String getSetCharacterEncoding() + { + return _characterEncoding; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#getContentType() + */ + public String getContentType() + { + return _contentType; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#getOutputStream() + */ + public ServletOutputStream getOutputStream() throws IOException + { + if (_outputState!=NONE && _outputState!=STREAM) + throw new IllegalStateException("WRITER"); + + ServletOutputStream out = _connection.getOutputStream(); + _outputState=STREAM; + return out; + } + + /* ------------------------------------------------------------ */ + public boolean isWriting() + { + return _outputState==WRITER; + } + + /* ------------------------------------------------------------ */ + public boolean isOutputing() + { + return _outputState!=NONE; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#getWriter() + */ + public PrintWriter getWriter() throws IOException + { + if (_outputState!=NONE && _outputState!=WRITER) + throw new IllegalStateException("STREAM"); + + /* if there is no writer yet */ + if (_writer==null) + { + /* get encoding from Content-Type header */ + String encoding = _characterEncoding; + + if (encoding==null) + { + /* implementation of educated defaults */ + if(_cachedMimeType != null) + encoding = MimeTypes.getCharsetFromContentType(_cachedMimeType); + + if (encoding==null) + encoding = StringUtil.__ISO_8859_1; + + setCharacterEncoding(encoding); + } + + /* construct Writer using correct encoding */ + _writer = _connection.getPrintWriter(encoding); + } + _outputState=WRITER; + return _writer; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#setCharacterEncoding(java.lang.String) + */ + public void setCharacterEncoding(String encoding) + { + if (_connection.isIncluding()) + return; + + if (this._outputState==0 && !isCommitted()) + { + _explicitEncoding=true; + + if (encoding==null) + { + // Clear any encoding. + if (_characterEncoding!=null) + { + _characterEncoding=null; + if (_cachedMimeType!=null) + _contentType=_cachedMimeType.toString(); + else if (_mimeType!=null) + _contentType=_mimeType; + else + _contentType=null; + + if (_contentType==null) + _connection.getResponseFields().remove(HttpHeaders.CONTENT_TYPE_BUFFER); + else + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + } + else + { + // No, so just add this one to the mimetype + _characterEncoding=encoding; + if (_contentType!=null) + { + int i0=_contentType.indexOf(';'); + if (i0<0) + { + _contentType=null; + if(_cachedMimeType!=null) + { + CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding); + if (content_type!=null) + { + _contentType=content_type.toString(); + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,content_type); + } + } + + if (_contentType==null) + { + _contentType = _mimeType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= "); + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + } + else + { + int i1=_contentType.indexOf("charset=",i0); + if (i1<0) + { + _contentType = _contentType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= "); + } + else + { + int i8=i1+8; + int i2=_contentType.indexOf(" ",i8); + if (i2<0) + _contentType=_contentType.substring(0,i8)+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= "); + else + _contentType=_contentType.substring(0,i8)+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ")+_contentType.substring(i2); + } + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + } + } + } + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#setContentLength(int) + */ + public void setContentLength(int len) + { + // Protect from setting after committed as default handling + // of a servlet HEAD request ALWAYS sets _content length, even + // if the getHandling committed the response! + if (isCommitted() || _connection.isIncluding()) + return; + _connection._generator.setContentLength(len); + if (len>0) + { + _connection.getResponseFields().putLongField(HttpHeaders.CONTENT_LENGTH, len); + if (_connection._generator.isAllContentWritten()) + { + if (_outputState==WRITER) + _writer.close(); + else if (_outputState==STREAM) + { + try + { + getOutputStream().close(); + } + catch(IOException e) + { + throw new RuntimeException(e); + } + } + } + } + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#setContentLength(int) + */ + public void setLongContentLength(long len) + { + // Protect from setting after committed as default handling + // of a servlet HEAD request ALWAYS sets _content length, even + // if the getHandling committed the response! + if (isCommitted() || _connection.isIncluding()) + return; + _connection._generator.setContentLength(len); + _connection.getResponseFields().putLongField(HttpHeaders.CONTENT_LENGTH, len); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#setContentType(java.lang.String) + */ + public void setContentType(String contentType) + { + if (isCommitted() || _connection.isIncluding()) + return; + + // Yes this method is horribly complex.... but there are lots of special cases and + // as this method is called on every request, it is worth trying to save string creation. + // + + if (contentType==null) + { + if (_locale==null) + _characterEncoding=null; + _mimeType=null; + _cachedMimeType=null; + _contentType=null; + _connection.getResponseFields().remove(HttpHeaders.CONTENT_TYPE_BUFFER); + } + else + { + // Look for encoding in contentType + int i0=contentType.indexOf(';'); + + if (i0>0) + { + // we have content type parameters + + // Extract params off mimetype + _mimeType=contentType.substring(0,i0).trim(); + _cachedMimeType=MimeTypes.CACHE.get(_mimeType); + + // Look for charset + int i1=contentType.indexOf("charset=",i0+1); + if (i1>=0) + { + _explicitEncoding=true; + int i8=i1+8; + int i2 = contentType.indexOf(' ',i8); + + if (_outputState==WRITER) + { + // strip the charset and ignore; + if ((i1==i0+1 && i2<0) || (i1==i0+2 && i2<0 && contentType.charAt(i0+1)==' ')) + { + if (_cachedMimeType!=null) + { + CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding); + if (content_type!=null) + { + _contentType=content_type.toString(); + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,content_type); + } + else + { + _contentType=_mimeType+";charset="+_characterEncoding; + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + } + else + { + _contentType=_mimeType+";charset="+_characterEncoding; + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + } + else if (i2<0) + { + _contentType=contentType.substring(0,i1)+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= "); + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + else + { + _contentType=contentType.substring(0,i1)+contentType.substring(i2)+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= "); + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + } + else if ((i1==i0+1 && i2<0) || (i1==i0+2 && i2<0 && contentType.charAt(i0+1)==' ')) + { + // The params are just the char encoding + _cachedMimeType=MimeTypes.CACHE.get(_mimeType); + _characterEncoding = QuotedStringTokenizer.unquote(contentType.substring(i8)); + + if (_cachedMimeType!=null) + { + CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding); + if (content_type!=null) + { + _contentType=content_type.toString(); + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,content_type); + } + else + { + _contentType=contentType; + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + } + else + { + _contentType=contentType; + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + } + else if (i2>0) + { + _characterEncoding = QuotedStringTokenizer.unquote(contentType.substring(i8,i2)); + _contentType=contentType; + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + else + { + _characterEncoding = QuotedStringTokenizer.unquote(contentType.substring(i8)); + _contentType=contentType; + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + } + else // No encoding in the params. + { + _cachedMimeType=null; + _contentType=_characterEncoding==null?contentType:contentType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= "); + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + } + else // No params at all + { + _mimeType=contentType; + _cachedMimeType=MimeTypes.CACHE.get(_mimeType); + + if (_characterEncoding!=null) + { + if (_cachedMimeType!=null) + { + CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding); + if (content_type!=null) + { + _contentType=content_type.toString(); + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,content_type); + } + else + { + _contentType=_mimeType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= "); + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + } + else + { + _contentType=contentType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= "); + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + } + else if (_cachedMimeType!=null) + { + _contentType=_cachedMimeType.toString(); + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_cachedMimeType); + } + else + { + _contentType=contentType; + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + } + } + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#setBufferSize(int) + */ + public void setBufferSize(int size) + { + if (isCommitted() || getContentCount()>0) + throw new IllegalStateException("Committed or content written"); + _connection.getGenerator().increaseContentBufferSize(size); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#getBufferSize() + */ + public int getBufferSize() + { + return _connection.getGenerator().getContentBufferSize(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#flushBuffer() + */ + public void flushBuffer() throws IOException + { + _connection.flushResponse(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#reset() + */ + public void reset() + { + resetBuffer(); + fwdReset(); + _status=200; + _reason=null; + + HttpFields response_fields=_connection.getResponseFields(); + + response_fields.clear(); + String connection=_connection.getRequestFields().getStringField(HttpHeaders.CONNECTION_BUFFER); + if (connection!=null) + { + String[] values = connection.split(","); + for (int i=0;values!=null && i<values.length;i++) + { + CachedBuffer cb = HttpHeaderValues.CACHE.get(values[0].trim()); + + if (cb!=null) + { + switch(cb.getOrdinal()) + { + case HttpHeaderValues.CLOSE_ORDINAL: + response_fields.put(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.CLOSE_BUFFER); + break; + + case HttpHeaderValues.KEEP_ALIVE_ORDINAL: + if (HttpVersions.HTTP_1_0.equalsIgnoreCase(_connection.getRequest().getProtocol())) + response_fields.put(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.KEEP_ALIVE); + break; + case HttpHeaderValues.TE_ORDINAL: + response_fields.put(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.TE); + break; + } + } + } + } + } + + + public void reset(boolean preserveCookies) + { + if (!preserveCookies) + reset(); + else + { + HttpFields response_fields=_connection.getResponseFields(); + + ArrayList<String> cookieValues = new ArrayList<String>(5); + Enumeration<String> vals = response_fields.getValues(HttpHeaders.SET_COOKIE); + while (vals.hasMoreElements()) + cookieValues.add((String)vals.nextElement()); + + reset(); + + for (String v:cookieValues) + response_fields.add(HttpHeaders.SET_COOKIE, v); + } + } + + + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#reset() + */ + public void fwdReset() + { + resetBuffer(); + + _writer=null; + _outputState=NONE; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#resetBuffer() + */ + public void resetBuffer() + { + if (isCommitted()) + throw new IllegalStateException("Committed"); + _connection.getGenerator().resetBuffer(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#isCommitted() + */ + public boolean isCommitted() + { + return _connection.isResponseCommitted(); + } + + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#setLocale(java.util.Locale) + */ + public void setLocale(Locale locale) + { + if (locale == null || isCommitted() ||_connection.isIncluding()) + return; + + _locale = locale; + _connection.getResponseFields().put(HttpHeaders.CONTENT_LANGUAGE_BUFFER,locale.toString().replace('_','-')); + + if (_explicitEncoding || _outputState!=0 ) + return; + + if (_connection.getRequest().getContext()==null) + return; + + String charset = _connection.getRequest().getContext().getContextHandler().getLocaleEncoding(locale); + + if (charset!=null && charset.length()>0) + { + _characterEncoding=charset; + + /* get current MIME type from Content-Type header */ + String type=getContentType(); + if (type!=null) + { + _characterEncoding=charset; + int semi=type.indexOf(';'); + if (semi<0) + { + _mimeType=type; + _contentType= type += ";charset="+charset; + } + else + { + _mimeType=type.substring(0,semi); + _contentType= _mimeType += ";charset="+charset; + } + + _cachedMimeType=MimeTypes.CACHE.get(_mimeType); + _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); + } + } + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletResponse#getLocale() + */ + public Locale getLocale() + { + if (_locale==null) + return Locale.getDefault(); + return _locale; + } + + /* ------------------------------------------------------------ */ + /** + * @return The HTTP status code that has been set for this request. This will be <code>200<code> + * ({@link HttpServletResponse#SC_OK}), unless explicitly set through one of the <code>setStatus</code> methods. + */ + public int getStatus() + { + return _status; + } + + /* ------------------------------------------------------------ */ + /** + * @return The reason associated with the current {@link #getStatus() status}. This will be <code>null</code>, + * unless one of the <code>setStatus</code> methods have been called. + */ + public String getReason() + { + return _reason; + } + + /* ------------------------------------------------------------ */ + /** + */ + public void complete() + throws IOException + { + _connection.completeResponse(); + } + + /* ------------------------------------------------------------- */ + /** + * @return the number of bytes actually written in response body + */ + public long getContentCount() + { + if (_connection==null || _connection.getGenerator()==null) + return -1; + return _connection.getGenerator().getContentWritten(); + } + + /* ------------------------------------------------------------ */ + public HttpFields getHttpFields() + { + return _connection.getResponseFields(); + } + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + return "HTTP/1.1 "+_status+" "+ (_reason==null?"":_reason) +System.getProperty("line.separator")+ + _connection.getResponseFields().toString(); + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + private static class NullOutput extends ServletOutputStream + { + @Override + public void write(int b) throws IOException + { + } + + @Override + public void print(String s) throws IOException + { + } + + @Override + public void println(String s) throws IOException + { + } + + @Override + public void write(byte[] b, int off, int len) throws IOException + { + } + + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/Server.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,662 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Enumeration; + +import javax.servlet.AsyncContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpGenerator; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.server.handler.HandlerWrapper; +import org.eclipse.jetty.server.nio.SelectChannelConnector; +import org.eclipse.jetty.util.Attributes; +import org.eclipse.jetty.util.AttributesMap; +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.MultiException; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.component.Container; +import org.eclipse.jetty.util.component.Destroyable; +import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.eclipse.jetty.util.thread.ShutdownThread; +import org.eclipse.jetty.util.thread.ThreadPool; + +/* ------------------------------------------------------------ */ +/** Jetty HTTP Servlet Server. + * This class is the main class for the Jetty HTTP Servlet server. + * It aggregates Connectors (HTTP request receivers) and request Handlers. + * The server is itself a handler and a ThreadPool. Connectors use the ThreadPool methods + * to run jobs that will eventually call the handle method. + * + * @org.apache.xbean.XBean description="Creates an embedded Jetty web server" + */ +public class Server extends HandlerWrapper implements Attributes +{ + private static final Logger LOG = Log.getLogger(Server.class); + + private static final String __version; + static + { + if (Server.class.getPackage()!=null && + "Eclipse.org - Jetty".equals(Server.class.getPackage().getImplementationVendor()) && + Server.class.getPackage().getImplementationVersion()!=null) + __version=Server.class.getPackage().getImplementationVersion(); + else + __version=System.getProperty("jetty.version","8.y.z-SNAPSHOT"); + } + + private final Container _container=new Container(); + private final AttributesMap _attributes = new AttributesMap(); + private ThreadPool _threadPool; + private Connector[] _connectors; + private SessionIdManager _sessionIdManager; + private boolean _sendServerVersion = true; //send Server: header + private boolean _sendDateHeader = false; //send Date: header + private int _graceful=0; + private boolean _stopAtShutdown; + private boolean _dumpAfterStart=false; + private boolean _dumpBeforeStop=false; + private boolean _uncheckedPrintWriter=false; + + + /* ------------------------------------------------------------ */ + public Server() + { + setServer(this); + } + + /* ------------------------------------------------------------ */ + /** Convenience constructor + * Creates server and a {@link SelectChannelConnector} at the passed port. + */ + public Server(int port) + { + setServer(this); + + Connector connector=new SelectChannelConnector(); + connector.setPort(port); + setConnectors(new Connector[]{connector}); + } + + /* ------------------------------------------------------------ */ + /** Convenience constructor + * Creates server and a {@link SelectChannelConnector} at the passed address. + */ + public Server(InetSocketAddress addr) + { + setServer(this); + + Connector connector=new SelectChannelConnector(); + connector.setHost(addr.getHostName()); + connector.setPort(addr.getPort()); + setConnectors(new Connector[]{connector}); + } + + + /* ------------------------------------------------------------ */ + public static String getVersion() + { + return __version; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the container. + */ + public Container getContainer() + { + return _container; + } + + /* ------------------------------------------------------------ */ + public boolean getStopAtShutdown() + { + return _stopAtShutdown; + } + + /* ------------------------------------------------------------ */ + public void setStopAtShutdown(boolean stop) + { + //if we now want to stop + if (stop) + { + //and we weren't stopping before + if (!_stopAtShutdown) + { + //only register to stop if we're already started (otherwise we'll do it in doStart()) + if (isStarted()) + ShutdownThread.register(this); + } + } + else + ShutdownThread.deregister(this); + + _stopAtShutdown=stop; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the connectors. + */ + public Connector[] getConnectors() + { + return _connectors; + } + + /* ------------------------------------------------------------ */ + public void addConnector(Connector connector) + { + setConnectors((Connector[])LazyList.addToArray(getConnectors(), connector, Connector.class)); + } + + /* ------------------------------------------------------------ */ + /** + * Conveniance method which calls {@link #getConnectors()} and {@link #setConnectors(Connector[])} to + * remove a connector. + * @param connector The connector to remove. + */ + public void removeConnector(Connector connector) { + setConnectors((Connector[])LazyList.removeFromArray (getConnectors(), connector)); + } + + /* ------------------------------------------------------------ */ + /** Set the connectors for this server. + * Each connector has this server set as it's ThreadPool and its Handler. + * @param connectors The connectors to set. + */ + public void setConnectors(Connector[] connectors) + { + if (connectors!=null) + { + for (int i=0;i<connectors.length;i++) + connectors[i].setServer(this); + } + + _container.update(this, _connectors, connectors, "connector"); + _connectors = connectors; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the threadPool. + */ + public ThreadPool getThreadPool() + { + return _threadPool; + } + + /* ------------------------------------------------------------ */ + /** + * @param threadPool The threadPool to set. + */ + public void setThreadPool(ThreadPool threadPool) + { + if (_threadPool!=null) + removeBean(_threadPool); + _container.update(this, _threadPool, threadPool, "threadpool",false); + _threadPool = threadPool; + if (_threadPool!=null) + addBean(_threadPool); + } + + /** + * @return true if {@link #dumpStdErr()} is called after starting + */ + public boolean isDumpAfterStart() + { + return _dumpAfterStart; + } + + /** + * @param dumpAfterStart true if {@link #dumpStdErr()} is called after starting + */ + public void setDumpAfterStart(boolean dumpAfterStart) + { + _dumpAfterStart = dumpAfterStart; + } + + /** + * @return true if {@link #dumpStdErr()} is called before stopping + */ + public boolean isDumpBeforeStop() + { + return _dumpBeforeStop; + } + + /** + * @param dumpBeforeStop true if {@link #dumpStdErr()} is called before stopping + */ + public void setDumpBeforeStop(boolean dumpBeforeStop) + { + _dumpBeforeStop = dumpBeforeStop; + } + + + + /* ------------------------------------------------------------ */ + @Override + protected void doStart() throws Exception + { + if (getStopAtShutdown()) + { + ShutdownThread.register(this); + } + + ShutdownMonitor.getInstance().start(); // initialize + + LOG.info("jetty-"+__version); + HttpGenerator.setServerVersion(__version); + + MultiException mex=new MultiException(); + + if (_threadPool==null) + setThreadPool(new QueuedThreadPool()); + + try + { + super.doStart(); + } + catch(Throwable e) + { + mex.add(e); + } + + if (_connectors!=null && mex.size()==0) + { + for (int i=0;i<_connectors.length;i++) + { + try{_connectors[i].start();} + catch(Throwable e) + { + mex.add(e); + } + } + } + + if (isDumpAfterStart()) + dumpStdErr(); + + mex.ifExceptionThrow(); + } + + /* ------------------------------------------------------------ */ + @Override + protected void doStop() throws Exception + { + if (isDumpBeforeStop()) + dumpStdErr(); + + MultiException mex=new MultiException(); + + if (_graceful>0) + { + if (_connectors!=null) + { + for (int i=_connectors.length;i-->0;) + { + LOG.info("Graceful shutdown {}",_connectors[i]); + try{_connectors[i].close();}catch(Throwable e){mex.add(e);} + } + } + + Handler[] contexts = getChildHandlersByClass(Graceful.class); + for (int c=0;c<contexts.length;c++) + { + Graceful context=(Graceful)contexts[c]; + LOG.info("Graceful shutdown {}",context); + context.setShutdown(true); + } + Thread.sleep(_graceful); + } + + if (_connectors!=null) + { + for (int i=_connectors.length;i-->0;) + try{_connectors[i].stop();}catch(Throwable e){mex.add(e);} + } + + try {super.doStop(); } catch(Throwable e) { mex.add(e);} + + mex.ifExceptionThrow(); + + if (getStopAtShutdown()) + ShutdownThread.deregister(this); + } + + /* ------------------------------------------------------------ */ + /* Handle a request from a connection. + * Called to handle a request on the connection when either the header has been received, + * or after the entire request has been received (for short requests of known length), or + * on the dispatch of an async request. + */ + public void handle(AbstractHttpConnection connection) throws IOException, ServletException + { + final String target=connection.getRequest().getPathInfo(); + final Request request=connection.getRequest(); + final Response response=connection.getResponse(); + + if (LOG.isDebugEnabled()) + { + LOG.debug("REQUEST "+target+" on "+connection); + handle(target, request, request, response); + LOG.debug("RESPONSE "+target+" "+connection.getResponse().getStatus()+" handled="+request.isHandled()); + } + else + handle(target, request, request, response); + } + + /* ------------------------------------------------------------ */ + /* Handle a request from a connection. + * Called to handle a request on the connection when either the header has been received, + * or after the entire request has been received (for short requests of known length), or + * on the dispatch of an async request. + */ + public void handleAsync(AbstractHttpConnection connection) throws IOException, ServletException + { + final AsyncContinuation async = connection.getRequest().getAsyncContinuation(); + final AsyncContinuation.AsyncEventState state = async.getAsyncEventState(); + + final Request baseRequest=connection.getRequest(); + final String path=state.getPath(); + + if (path!=null) + { + // this is a dispatch with a path + final String contextPath=state.getServletContext().getContextPath(); + HttpURI uri = new HttpURI(URIUtil.addPaths(contextPath,path)); + baseRequest.setUri(uri); + baseRequest.setRequestURI(null); + baseRequest.setPathInfo(baseRequest.getRequestURI()); + if (uri.getQuery()!=null) + baseRequest.mergeQueryString(uri.getQuery()); //we have to assume dispatch path and query are UTF8 + } + + final String target=baseRequest.getPathInfo(); + final HttpServletRequest request=(HttpServletRequest)async.getRequest(); + final HttpServletResponse response=(HttpServletResponse)async.getResponse(); + + if (LOG.isDebugEnabled()) + { + LOG.debug("REQUEST "+target+" on "+connection); + handle(target, baseRequest, request, response); + LOG.debug("RESPONSE "+target+" "+connection.getResponse().getStatus()); + } + else + handle(target, baseRequest, request, response); + + } + + + /* ------------------------------------------------------------ */ + public void join() throws InterruptedException + { + getThreadPool().join(); + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /** + * @return Returns the sessionIdManager. + */ + public SessionIdManager getSessionIdManager() + { + return _sessionIdManager; + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /** + * @param sessionIdManager The sessionIdManager to set. + */ + public void setSessionIdManager(SessionIdManager sessionIdManager) + { + if (_sessionIdManager!=null) + removeBean(_sessionIdManager); + _container.update(this, _sessionIdManager, sessionIdManager, "sessionIdManager",false); + _sessionIdManager = sessionIdManager; + if (_sessionIdManager!=null) + addBean(_sessionIdManager); + } + + /* ------------------------------------------------------------ */ + public void setSendServerVersion (boolean sendServerVersion) + { + _sendServerVersion = sendServerVersion; + } + + /* ------------------------------------------------------------ */ + public boolean getSendServerVersion() + { + return _sendServerVersion; + } + + /* ------------------------------------------------------------ */ + /** + * @param sendDateHeader + */ + public void setSendDateHeader(boolean sendDateHeader) + { + _sendDateHeader = sendDateHeader; + } + + /* ------------------------------------------------------------ */ + public boolean getSendDateHeader() + { + return _sendDateHeader; + } + + /* ------------------------------------------------------------ */ + /** + */ + @Deprecated + public int getMaxCookieVersion() + { + return 1; + } + + /* ------------------------------------------------------------ */ + /** + */ + @Deprecated + public void setMaxCookieVersion(int maxCookieVersion) + { + } + + /* ------------------------------------------------------------ */ + /** + * Add a LifeCycle object to be started/stopped + * along with the Server. + * @deprecated Use {@link #addBean(Object)} + * @param c + */ + @Deprecated + public void addLifeCycle (LifeCycle c) + { + addBean(c); + } + + /* ------------------------------------------------------------ */ + /** + * Add an associated bean. + * The bean will be added to the servers {@link Container} + * and if it is a {@link LifeCycle} instance, it will be + * started/stopped along with the Server. Any beans that are also + * {@link Destroyable}, will be destroyed with the server. + * @param o the bean object to add + */ + @Override + public boolean addBean(Object o) + { + if (super.addBean(o)) + { + _container.addBean(o); + return true; + } + return false; + } + + /** + * Remove a LifeCycle object to be started/stopped + * along with the Server + * @deprecated Use {@link #removeBean(Object)} + */ + @Deprecated + public void removeLifeCycle (LifeCycle c) + { + removeBean(c); + } + + /* ------------------------------------------------------------ */ + /** + * Remove an associated bean. + */ + @Override + public boolean removeBean (Object o) + { + if (super.removeBean(o)) + { + _container.removeBean(o); + return true; + } + return false; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.util.AttributesMap#clearAttributes() + */ + public void clearAttributes() + { + _attributes.clearAttributes(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.util.AttributesMap#getAttribute(java.lang.String) + */ + public Object getAttribute(String name) + { + return _attributes.getAttribute(name); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.util.AttributesMap#getAttributeNames() + */ + public Enumeration getAttributeNames() + { + return AttributesMap.getAttributeNamesCopy(_attributes); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.util.AttributesMap#removeAttribute(java.lang.String) + */ + public void removeAttribute(String name) + { + _attributes.removeAttribute(name); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.util.AttributesMap#setAttribute(java.lang.String, java.lang.Object) + */ + public void setAttribute(String name, Object attribute) + { + _attributes.setAttribute(name, attribute); + } + + /* ------------------------------------------------------------ */ + /** + * @return the graceful + */ + public int getGracefulShutdown() + { + return _graceful; + } + + /* ------------------------------------------------------------ */ + /** + * Set graceful shutdown timeout. If set, the internal <code>doStop()</code> method will not immediately stop the + * server. Instead, all {@link Connector}s will be closed so that new connections will not be accepted + * and all handlers that implement {@link Graceful} will be put into the shutdown mode so that no new requests + * will be accepted, but existing requests can complete. The server will then wait the configured timeout + * before stopping. + * @param timeoutMS the milliseconds to wait for existing request to complete before stopping the server. + * + */ + public void setGracefulShutdown(int timeoutMS) + { + _graceful=timeoutMS; + } + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + return this.getClass().getName()+"@"+Integer.toHexString(hashCode()); + } + + /* ------------------------------------------------------------ */ + @Override + public void dump(Appendable out,String indent) throws IOException + { + dumpThis(out); + dump(out,indent,TypeUtil.asList(getHandlers()),getBeans(),TypeUtil.asList(_connectors)); + } + + + /* ------------------------------------------------------------ */ + public boolean isUncheckedPrintWriter() + { + return _uncheckedPrintWriter; + } + + /* ------------------------------------------------------------ */ + public void setUncheckedPrintWriter(boolean unchecked) + { + _uncheckedPrintWriter=unchecked; + } + + + /* ------------------------------------------------------------ */ + /* A handler that can be gracefully shutdown. + * Called by doStop if a {@link #setGracefulShutdown} period is set. + * TODO move this somewhere better + */ + public interface Graceful extends Handler + { + public void setShutdown(boolean shutdown); + } + + /* ------------------------------------------------------------ */ + public static void main(String...args) throws Exception + { + System.err.println(getVersion()); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/ServletRequestHttpWrapper.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,212 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.security.Principal; +import java.util.Collection; +import java.util.Enumeration; + +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletRequestWrapper; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import javax.servlet.http.Part; + +/* ------------------------------------------------------------ */ +/** Class to tunnel a ServletRequest via a HttpServletRequest + */ +public class ServletRequestHttpWrapper extends ServletRequestWrapper implements HttpServletRequest +{ + public ServletRequestHttpWrapper(ServletRequest request) + { + super(request); + } + + public String getAuthType() + { + return null; + } + + public Cookie[] getCookies() + { + return null; + } + + public long getDateHeader(String name) + { + return 0; + } + + public String getHeader(String name) + { + return null; + } + + public Enumeration getHeaders(String name) + { + return null; + } + + public Enumeration getHeaderNames() + { + return null; + } + + public int getIntHeader(String name) + { + return 0; + } + + public String getMethod() + { + return null; + } + + public String getPathInfo() + { + return null; + } + + public String getPathTranslated() + { + return null; + } + + public String getContextPath() + { + return null; + } + + public String getQueryString() + { + return null; + } + + public String getRemoteUser() + { + return null; + } + + public boolean isUserInRole(String role) + { + return false; + } + + public Principal getUserPrincipal() + { + return null; + } + + public String getRequestedSessionId() + { + return null; + } + + public String getRequestURI() + { + return null; + } + + public StringBuffer getRequestURL() + { + return null; + } + + public String getServletPath() + { + return null; + } + + public HttpSession getSession(boolean create) + { + return null; + } + + public HttpSession getSession() + { + return null; + } + + public boolean isRequestedSessionIdValid() + { + return false; + } + + public boolean isRequestedSessionIdFromCookie() + { + return false; + } + + public boolean isRequestedSessionIdFromURL() + { + return false; + } + + public boolean isRequestedSessionIdFromUrl() + { + return false; + } + + /** + * @see javax.servlet.http.HttpServletRequest#authenticate(javax.servlet.http.HttpServletResponse) + */ + public boolean authenticate(HttpServletResponse response) throws IOException, ServletException + { + return false; + } + + /** + * @see javax.servlet.http.HttpServletRequest#getPart(java.lang.String) + */ + public Part getPart(String name) throws IOException, ServletException + { + return null; + } + + /** + * @see javax.servlet.http.HttpServletRequest#getParts() + */ + public Collection<Part> getParts() throws IOException, ServletException + { + return null; + } + + /** + * @see javax.servlet.http.HttpServletRequest#login(java.lang.String, java.lang.String) + */ + public void login(String username, String password) throws ServletException + { + + } + + /** + * @see javax.servlet.http.HttpServletRequest#logout() + */ + public void logout() throws ServletException + { + + } + + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/ServletResponseHttpWrapper.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,145 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.io.IOException; +import java.util.Collection; + +import javax.servlet.ServletResponse; +import javax.servlet.ServletResponseWrapper; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; + + +/* ------------------------------------------------------------ */ +/** Wrapper to tunnel a ServletResponse via a HttpServletResponse + */ +public class ServletResponseHttpWrapper extends ServletResponseWrapper implements HttpServletResponse +{ + public ServletResponseHttpWrapper(ServletResponse response) + { + super(response); + } + + public void addCookie(Cookie cookie) + { + } + + public boolean containsHeader(String name) + { + return false; + } + + public String encodeURL(String url) + { + return null; + } + + public String encodeRedirectURL(String url) + { + return null; + } + + public String encodeUrl(String url) + { + return null; + } + + public String encodeRedirectUrl(String url) + { + return null; + } + + public void sendError(int sc, String msg) throws IOException + { + } + + public void sendError(int sc) throws IOException + { + } + + public void sendRedirect(String location) throws IOException + { + } + + public void setDateHeader(String name, long date) + { + } + + public void addDateHeader(String name, long date) + { + } + + public void setHeader(String name, String value) + { + } + + public void addHeader(String name, String value) + { + } + + public void setIntHeader(String name, int value) + { + } + + public void addIntHeader(String name, int value) + { + } + + public void setStatus(int sc) + { + } + + public void setStatus(int sc, String sm) + { + } + + /** + * @see javax.servlet.http.HttpServletResponse#getHeader(java.lang.String) + */ + public String getHeader(String name) + { + return null; + } + + /** + * @see javax.servlet.http.HttpServletResponse#getHeaderNames() + */ + public Collection<String> getHeaderNames() + { + return null; + } + + /** + * @see javax.servlet.http.HttpServletResponse#getHeaders(java.lang.String) + */ + public Collection<String> getHeaders(String name) + { + return null; + } + + /** + * @see javax.servlet.http.HttpServletResponse#getStatus() + */ + public int getStatus() + { + return 0; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/SessionIdManager.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,81 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +import org.eclipse.jetty.util.component.LifeCycle; + +/** Session ID Manager. + * Manages session IDs across multiple contexts. + */ +public interface SessionIdManager extends LifeCycle +{ + /** + * @param id The session ID without any cluster node extension + * @return True if the session ID is in use by at least one context. + */ + public boolean idInUse(String id); + + /** + * Add a session to the list of known sessions for a given ID. + * @param session The session + */ + public void addSession(HttpSession session); + + /** + * Remove session from the list of known sessions for a given ID. + * @param session + */ + public void removeSession(HttpSession session); + + /** + * Call {@link HttpSession#invalidate()} on all known sessions for the given id. + * @param id The session ID without any cluster node extension + */ + public void invalidateAll(String id); + + /** + * @param request + * @param created + * @return the new session id + */ + public String newSessionId(HttpServletRequest request,long created); + + public String getWorkerName(); + + + /* ------------------------------------------------------------ */ + /** Get a cluster ID from a node ID. + * Strip node identifier from a located session ID. + * @param nodeId + * @return the cluster id + */ + public String getClusterId(String nodeId); + + /* ------------------------------------------------------------ */ + /** Get a node ID from a cluster ID and a request + * @param clusterId The ID of the session + * @param request The request that for the session (or null) + * @return The session ID qualified with the node ID. + */ + public String getNodeId(String clusterId,HttpServletRequest request); + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/SessionManager.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,307 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.util.EventListener; +import java.util.Set; + +import javax.servlet.SessionCookieConfig; +import javax.servlet.SessionTrackingMode; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +import org.eclipse.jetty.http.HttpCookie; +import org.eclipse.jetty.server.session.SessionHandler; +import org.eclipse.jetty.util.component.LifeCycle; + +/* --------------------------------------------------------------------- */ +/** + * Session Manager. + * The API required to manage sessions for a servlet context. + * + */ + +/* ------------------------------------------------------------ */ +/** + */ +public interface SessionManager extends LifeCycle +{ + /* ------------------------------------------------------------ */ + /** + * Session cookie name. + * Defaults to <code>JSESSIONID</code>, but can be set with the + * <code>org.eclipse.jetty.servlet.SessionCookie</code> context init parameter. + */ + public final static String __SessionCookieProperty = "org.eclipse.jetty.servlet.SessionCookie"; + public final static String __DefaultSessionCookie = "JSESSIONID"; + + + /* ------------------------------------------------------------ */ + /** + * Session id path parameter name. + * Defaults to <code>jsessionid</code>, but can be set with the + * <code>org.eclipse.jetty.servlet.SessionIdPathParameterName</code> context init parameter. + * If set to null or "none" no URL rewriting will be done. + */ + public final static String __SessionIdPathParameterNameProperty = "org.eclipse.jetty.servlet.SessionIdPathParameterName"; + public final static String __DefaultSessionIdPathParameterName = "jsessionid"; + public final static String __CheckRemoteSessionEncoding = "org.eclipse.jetty.servlet.CheckingRemoteSessionIdEncoding"; + + + /* ------------------------------------------------------------ */ + /** + * Session Domain. + * If this property is set as a ServletContext InitParam, then it is + * used as the domain for session cookies. If it is not set, then + * no domain is specified for the session cookie. + */ + public final static String __SessionDomainProperty = "org.eclipse.jetty.servlet.SessionDomain"; + public final static String __DefaultSessionDomain = null; + + + /* ------------------------------------------------------------ */ + /** + * Session Path. + * If this property is set as a ServletContext InitParam, then it is + * used as the path for the session cookie. If it is not set, then + * the context path is used as the path for the cookie. + */ + public final static String __SessionPathProperty = "org.eclipse.jetty.servlet.SessionPath"; + + /* ------------------------------------------------------------ */ + /** + * Session Max Age. + * If this property is set as a ServletContext InitParam, then it is + * used as the max age for the session cookie. If it is not set, then + * a max age of -1 is used. + */ + public final static String __MaxAgeProperty = "org.eclipse.jetty.servlet.MaxAge"; + + /* ------------------------------------------------------------ */ + /** + * Returns the <code>HttpSession</code> with the given session id + * + * @param id the session id + * @return the <code>HttpSession</code> with the corresponding id or null if no session with the given id exists + */ + public HttpSession getHttpSession(String id); + + /* ------------------------------------------------------------ */ + /** + * Creates a new <code>HttpSession</code>. + * + * @param request the HttpServletRequest containing the requested session id + * @return the new <code>HttpSession</code> + */ + public HttpSession newHttpSession(HttpServletRequest request); + + + /* ------------------------------------------------------------ */ + /** + * @return true if session cookies should be HTTP-only (Microsoft extension) + * @see org.eclipse.jetty.http.HttpCookie#isHttpOnly() + */ + public boolean getHttpOnly(); + + /* ------------------------------------------------------------ */ + /** + * @return the max period of inactivity, after which the session is invalidated, in seconds. + * @see #setMaxInactiveInterval(int) + */ + public int getMaxInactiveInterval(); + + /* ------------------------------------------------------------ */ + /** + * Sets the max period of inactivity, after which the session is invalidated, in seconds. + * + * @param seconds the max inactivity period, in seconds. + * @see #getMaxInactiveInterval() + */ + public void setMaxInactiveInterval(int seconds); + + /* ------------------------------------------------------------ */ + /** + * Sets the {@link SessionHandler}. + * + * @param handler the <code>SessionHandler</code> object + */ + public void setSessionHandler(SessionHandler handler); + + /* ------------------------------------------------------------ */ + /** + * Adds an event listener for session-related events. + * + * @param listener the session event listener to add + * Individual SessionManagers implementations may accept arbitrary listener types, + * but they are expected to at least handle HttpSessionActivationListener, + * HttpSessionAttributeListener, HttpSessionBindingListener and HttpSessionListener. + * @see #removeEventListener(EventListener) + */ + public void addEventListener(EventListener listener); + + /* ------------------------------------------------------------ */ + /** + * Removes an event listener for for session-related events. + * + * @param listener the session event listener to remove + * @see #addEventListener(EventListener) + */ + public void removeEventListener(EventListener listener); + + /* ------------------------------------------------------------ */ + /** + * Removes all event listeners for session-related events. + * + * @see #removeEventListener(EventListener) + */ + public void clearEventListeners(); + + /* ------------------------------------------------------------ */ + /** + * Gets a Cookie for a session. + * + * @param session the session to which the cookie should refer. + * @param contextPath the context to which the cookie should be linked. + * The client will only send the cookie value when requesting resources under this path. + * @param requestIsSecure whether the client is accessing the server over a secure protocol (i.e. HTTPS). + * @return if this <code>SessionManager</code> uses cookies, then this method will return a new + * {@link Cookie cookie object} that should be set on the client in order to link future HTTP requests + * with the <code>session</code>. If cookies are not in use, this method returns <code>null</code>. + */ + public HttpCookie getSessionCookie(HttpSession session, String contextPath, boolean requestIsSecure); + + /* ------------------------------------------------------------ */ + /** + * @return the cross context session id manager. + * @see #setSessionIdManager(SessionIdManager) + */ + public SessionIdManager getSessionIdManager(); + + /* ------------------------------------------------------------ */ + /** + * @return the cross context session id manager. + * @deprecated use {@link #getSessionIdManager()} + */ + @Deprecated + public SessionIdManager getMetaManager(); + + /* ------------------------------------------------------------ */ + /** + * Sets the cross context session id manager + * + * @param idManager the cross context session id manager. + * @see #getSessionIdManager() + */ + public void setSessionIdManager(SessionIdManager idManager); + + /* ------------------------------------------------------------ */ + /** + * @param session the session to test for validity + * @return whether the given session is valid, that is, it has not been invalidated. + */ + public boolean isValid(HttpSession session); + + /* ------------------------------------------------------------ */ + /** + * @param session the session object + * @return the unique id of the session within the cluster, extended with an optional node id. + * @see #getClusterId(HttpSession) + */ + public String getNodeId(HttpSession session); + + /* ------------------------------------------------------------ */ + /** + * @param session the session object + * @return the unique id of the session within the cluster (without a node id extension) + * @see #getNodeId(HttpSession) + */ + public String getClusterId(HttpSession session); + + /* ------------------------------------------------------------ */ + /** + * Called by the {@link SessionHandler} when a session is first accessed by a request. + * + * @param session the session object + * @param secure whether the request is secure or not + * @return the session cookie. If not null, this cookie should be set on the response to either migrate + * the session or to refresh a session cookie that may expire. + * @see #complete(HttpSession) + */ + public HttpCookie access(HttpSession session, boolean secure); + + /* ------------------------------------------------------------ */ + /** + * Called by the {@link SessionHandler} when a session is last accessed by a request. + * + * @param session the session object + * @see #access(HttpSession, boolean) + */ + public void complete(HttpSession session); + + /** + * Sets the session id URL path parameter name. + * + * @param parameterName the URL path parameter name for session id URL rewriting (null or "none" for no rewriting). + * @see #getSessionIdPathParameterName() + * @see #getSessionIdPathParameterNamePrefix() + */ + public void setSessionIdPathParameterName(String parameterName); + + /** + * @return the URL path parameter name for session id URL rewriting, by default "jsessionid". + * @see #setSessionIdPathParameterName(String) + */ + public String getSessionIdPathParameterName(); + + /** + * @return a formatted version of {@link #getSessionIdPathParameterName()}, by default + * ";" + sessionIdParameterName + "=", for easier lookup in URL strings. + * @see #getSessionIdPathParameterName() + */ + public String getSessionIdPathParameterNamePrefix(); + + /** + * @return whether the session management is handled via cookies. + */ + public boolean isUsingCookies(); + + /** + * @return whether the session management is handled via URLs. + */ + public boolean isUsingURLs(); + + public Set<SessionTrackingMode> getDefaultSessionTrackingModes(); + + public Set<SessionTrackingMode> getEffectiveSessionTrackingModes(); + + public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes); + + public SessionCookieConfig getSessionCookieConfig(); + + /** + * @return True if absolute URLs are check for remoteness before being session encoded. + */ + public boolean isCheckingRemoteSessionIdEncoding(); + + /** + * @param remote True if absolute URLs are check for remoteness before being session encoded. + */ + public void setCheckingRemoteSessionIdEncoding(boolean remote); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/ShutdownMonitor.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,384 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.InputStreamReader; +import java.io.LineNumberReader; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.Properties; + +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.thread.ShutdownThread; + +/** + * Shutdown/Stop Monitor thread. + * <p> + * This thread listens on the port specified by the STOP.PORT system parameter (defaults to -1 for not listening) for request authenticated with the key given + * by the STOP.KEY system parameter (defaults to "eclipse") for admin requests. + * <p> + * If the stop port is set to zero, then a random port is assigned and the port number is printed to stdout. + * <p> + * Commands "stop" and "status" are currently supported. + */ +public class ShutdownMonitor +{ + // Implementation of safe lazy init, using Initialization on Demand Holder technique. + static class Holder + { + static ShutdownMonitor instance = new ShutdownMonitor(); + } + + public static ShutdownMonitor getInstance() + { + return Holder.instance; + } + + /** + * ShutdownMonitorThread + * + * Thread for listening to STOP.PORT for command to stop Jetty. + * If ShowndownMonitor.exitVm is true, then Sytem.exit will also be + * called after the stop. + * + */ + public class ShutdownMonitorThread extends Thread + { + + public ShutdownMonitorThread () + { + setDaemon(true); + setName("ShutdownMonitor"); + } + + @Override + public void run() + { + if (serverSocket == null) + { + return; + } + + while (serverSocket != null) + { + Socket socket = null; + try + { + socket = serverSocket.accept(); + + LineNumberReader lin = new LineNumberReader(new InputStreamReader(socket.getInputStream())); + String receivedKey = lin.readLine(); + if (!key.equals(receivedKey)) + { + System.err.println("Ignoring command with incorrect key"); + continue; + } + + OutputStream out = socket.getOutputStream(); + + String cmd = lin.readLine(); + debug("command=%s",cmd); + if ("stop".equals(cmd)) + { + // Graceful Shutdown + debug("Issuing graceful shutdown.."); + ShutdownThread.getInstance().run(); + + // Reply to client + debug("Informing client that we are stopped."); + out.write("Stopped\r\n".getBytes(StringUtil.__UTF8)); + out.flush(); + + // Shutdown Monitor + debug("Shutting down monitor"); + close(socket); + socket = null; + close(serverSocket); + serverSocket = null; + + if (exitVm) + { + // Kill JVM + debug("Killing JVM"); + System.exit(0); + } + } + else if ("status".equals(cmd)) + { + // Reply to client + out.write("OK\r\n".getBytes(StringUtil.__UTF8)); + out.flush(); + } + } + catch (Exception e) + { + debug(e); + System.err.println(e.toString()); + } + finally + { + close(socket); + socket = null; + } + } + } + + public void start() + { + if (isAlive()) + { + System.err.printf("ShutdownMonitorThread already started"); + return; // cannot start it again + } + + startListenSocket(); + + if (serverSocket == null) + { + return; + } + if (DEBUG) + System.err.println("Starting ShutdownMonitorThread"); + super.start(); + } + + private void startListenSocket() + { + if (port < 0) + { + if (DEBUG) + System.err.println("ShutdownMonitor not in use (port < 0): " + port); + return; + } + + try + { + serverSocket = new ServerSocket(port,1,InetAddress.getByName("127.0.0.1")); + if (port == 0) + { + // server assigned port in use + port = serverSocket.getLocalPort(); + System.out.printf("STOP.PORT=%d%n",port); + } + + if (key == null) + { + // create random key + key = Long.toString((long)(Long.MAX_VALUE * Math.random() + this.hashCode() + System.currentTimeMillis()),36); + System.out.printf("STOP.KEY=%s%n",key); + } + } + catch (Exception e) + { + debug(e); + System.err.println("Error binding monitor port " + port + ": " + e.toString()); + serverSocket = null; + } + finally + { + // establish the port and key that are in use + debug("STOP.PORT=%d",port); + debug("STOP.KEY=%s",key); + debug("%s",serverSocket); + } + } + + } + + private boolean DEBUG; + private int port; + private String key; + private boolean exitVm; + private ServerSocket serverSocket; + private ShutdownMonitorThread thread; + + + + /** + * Create a ShutdownMonitor using configuration from the System properties. + * <p> + * <code>STOP.PORT</code> = the port to listen on (empty, null, or values less than 0 disable the stop ability)<br> + * <code>STOP.KEY</code> = the magic key/passphrase to allow the stop (defaults to "eclipse")<br> + * <p> + * Note: server socket will only listen on localhost, and a successful stop will issue a System.exit() call. + */ + private ShutdownMonitor() + { + Properties props = System.getProperties(); + + this.DEBUG = props.containsKey("DEBUG"); + + // Use values passed thru via /jetty-start/ + this.port = Integer.parseInt(props.getProperty("STOP.PORT","-1")); + this.key = props.getProperty("STOP.KEY",null); + this.exitVm = true; + } + + private void close(ServerSocket server) + { + if (server == null) + { + return; + } + + try + { + server.close(); + } + catch (IOException ignore) + { + /* ignore */ + } + } + + private void close(Socket socket) + { + if (socket == null) + { + return; + } + + try + { + socket.close(); + } + catch (IOException ignore) + { + /* ignore */ + } + } + + private void debug(String format, Object... args) + { + if (DEBUG) + { + System.err.printf("[ShutdownMonitor] " + format + "%n",args); + } + } + + private void debug(Throwable t) + { + if (DEBUG) + { + t.printStackTrace(System.err); + } + } + + public String getKey() + { + return key; + } + + public int getPort() + { + return port; + } + + public ServerSocket getServerSocket() + { + return serverSocket; + } + + public boolean isExitVm() + { + return exitVm; + } + + + public void setDebug(boolean flag) + { + this.DEBUG = flag; + } + + public void setExitVm(boolean exitVm) + { + synchronized (this) + { + if (thread != null && thread.isAlive()) + { + throw new IllegalStateException("ShutdownMonitorThread already started"); + } + this.exitVm = exitVm; + } + } + + public void setKey(String key) + { + synchronized (this) + { + if (thread != null && thread.isAlive()) + { + throw new IllegalStateException("ShutdownMonitorThread already started"); + } + this.key = key; + } + } + + public void setPort(int port) + { + synchronized (this) + { + if (thread != null && thread.isAlive()) + { + throw new IllegalStateException("ShutdownMonitorThread already started"); + } + this.port = port; + } + } + + protected void start() throws Exception + { + ShutdownMonitorThread t = null; + synchronized (this) + { + if (thread != null && thread.isAlive()) + { + System.err.printf("ShutdownMonitorThread already started"); + return; // cannot start it again + } + + thread = new ShutdownMonitorThread(); + t = thread; + } + + if (t != null) + t.start(); + } + + + protected boolean isAlive () + { + boolean result = false; + synchronized (this) + { + result = (thread != null && thread.isAlive()); + } + return result; + } + + + @Override + public String toString() + { + return String.format("%s[port=%d]",this.getClass().getName(),port); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/UserIdentity.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,116 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server; +import java.security.Principal; +import java.util.Map; + +import javax.security.auth.Subject; + +/* ------------------------------------------------------------ */ +/** User object that encapsulates user identity and operations such as run-as-role actions, + * checking isUserInRole and getUserPrincipal. + * + * Implementations of UserIdentity should be immutable so that they may be + * cached by Authenticators and LoginServices. + * + */ +public interface UserIdentity +{ + /* ------------------------------------------------------------ */ + /** + * @return The user subject + */ + Subject getSubject(); + + /* ------------------------------------------------------------ */ + /** + * @return The user principal + */ + Principal getUserPrincipal(); + + /* ------------------------------------------------------------ */ + /** Check if the user is in a role. + * This call is used to satisfy authorization calls from + * container code which will be using translated role names. + * @param role A role name. + * @param scope + * @return True if the user can act in that role. + */ + boolean isUserInRole(String role, Scope scope); + + + /* ------------------------------------------------------------ */ + /** + * A UserIdentity Scope. + * A scope is the environment in which a User Identity is to + * be interpreted. Typically it is set by the target servlet of + * a request. + */ + interface Scope + { + /* ------------------------------------------------------------ */ + /** + * @return The context path that the identity is being considered within + */ + String getContextPath(); + + /* ------------------------------------------------------------ */ + /** + * @return The name of the identity context. Typically this is the servlet name. + */ + String getName(); + + /* ------------------------------------------------------------ */ + /** + * @return A map of role reference names that converts from names used by application code + * to names used by the context deployment. + */ + Map<String,String> getRoleRefMap(); + } + + /* ------------------------------------------------------------ */ + public interface UnauthenticatedUserIdentity extends UserIdentity + { + } + + /* ------------------------------------------------------------ */ + public static final UserIdentity UNAUTHENTICATED_IDENTITY = new UnauthenticatedUserIdentity() + { + public Subject getSubject() + { + return null; + } + + public Principal getUserPrincipal() + { + return null; + } + + public boolean isUserInRole(String role, Scope scope) + { + return false; + } + + @Override + public String toString() + { + return "UNAUTHENTICATED"; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/bio/SocketConnector.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,325 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.bio; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.jetty.http.HttpException; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.ConnectedEndPoint; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.io.bio.SocketEndPoint; +import org.eclipse.jetty.server.AbstractConnector; +import org.eclipse.jetty.server.AbstractHttpConnection; +import org.eclipse.jetty.server.BlockingHttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.component.AggregateLifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + + +/* ------------------------------------------------------------------------------- */ +/** Socket Connector. + * This connector implements a traditional blocking IO and threading model. + * Normal JRE sockets are used and a thread is allocated per connection. + * Buffers are managed so that large buffers are only allocated to active connections. + * + * This Connector should only be used if NIO is not available. + * + * @org.apache.xbean.XBean element="bioConnector" description="Creates a BIO based socket connector" + * + * + */ +public class SocketConnector extends AbstractConnector +{ + private static final Logger LOG = Log.getLogger(SocketConnector.class); + + protected ServerSocket _serverSocket; + protected final Set<EndPoint> _connections; + protected volatile int _localPort=-1; + + /* ------------------------------------------------------------ */ + /** Constructor. + * + */ + public SocketConnector() + { + _connections=new HashSet<EndPoint>(); + } + + /* ------------------------------------------------------------ */ + public Object getConnection() + { + return _serverSocket; + } + + /* ------------------------------------------------------------ */ + public void open() throws IOException + { + // Create a new server socket and set to non blocking mode + if (_serverSocket==null || _serverSocket.isClosed()) + _serverSocket= newServerSocket(getHost(),getPort(),getAcceptQueueSize()); + _serverSocket.setReuseAddress(getReuseAddress()); + _localPort=_serverSocket.getLocalPort(); + if (_localPort<=0) + throw new IllegalStateException("port not allocated for "+this); + + } + + /* ------------------------------------------------------------ */ + protected ServerSocket newServerSocket(String host, int port,int backlog) throws IOException + { + ServerSocket ss= host==null? + new ServerSocket(port,backlog): + new ServerSocket(port,backlog,InetAddress.getByName(host)); + + return ss; + } + + /* ------------------------------------------------------------ */ + public void close() throws IOException + { + if (_serverSocket!=null) + _serverSocket.close(); + _serverSocket=null; + _localPort=-2; + } + + /* ------------------------------------------------------------ */ + @Override + public void accept(int acceptorID) + throws IOException, InterruptedException + { + Socket socket = _serverSocket.accept(); + configure(socket); + + ConnectorEndPoint connection=new ConnectorEndPoint(socket); + connection.dispatch(); + } + + /* ------------------------------------------------------------------------------- */ + /** + * Allows subclass to override Conection if required. + */ + protected Connection newConnection(EndPoint endpoint) + { + return new BlockingHttpConnection(this, endpoint, getServer()); + } + + /* ------------------------------------------------------------------------------- */ + @Override + public void customize(EndPoint endpoint, Request request) + throws IOException + { + ConnectorEndPoint connection = (ConnectorEndPoint)endpoint; + int lrmit = isLowResources()?_lowResourceMaxIdleTime:_maxIdleTime; + connection.setMaxIdleTime(lrmit); + + super.customize(endpoint, request); + } + + /* ------------------------------------------------------------------------------- */ + public int getLocalPort() + { + return _localPort; + } + + /* ------------------------------------------------------------------------------- */ + @Override + protected void doStart() throws Exception + { + _connections.clear(); + super.doStart(); + } + + /* ------------------------------------------------------------------------------- */ + @Override + protected void doStop() throws Exception + { + super.doStop(); + Set<EndPoint> set = new HashSet<EndPoint>(); + synchronized(_connections) + { + set.addAll(_connections); + } + for (EndPoint endPoint : set) + { + ConnectorEndPoint connection = (ConnectorEndPoint)endPoint; + connection.close(); + } + } + + @Override + public void dump(Appendable out, String indent) throws IOException + { + super.dump(out, indent); + Set<EndPoint> connections = new HashSet<EndPoint>(); + synchronized (_connections) + { + connections.addAll(_connections); + } + AggregateLifeCycle.dump(out, indent, connections); + } + + /* ------------------------------------------------------------------------------- */ + /* ------------------------------------------------------------------------------- */ + /* ------------------------------------------------------------------------------- */ + protected class ConnectorEndPoint extends SocketEndPoint implements Runnable, ConnectedEndPoint + { + volatile Connection _connection; + protected final Socket _socket; + + public ConnectorEndPoint(Socket socket) throws IOException + { + super(socket,_maxIdleTime); + _connection = newConnection(this); + _socket=socket; + } + + public Connection getConnection() + { + return _connection; + } + + public void setConnection(Connection connection) + { + if (_connection!=connection && _connection!=null) + connectionUpgraded(_connection,connection); + _connection=connection; + } + + public void dispatch() throws IOException + { + if (getThreadPool()==null || !getThreadPool().dispatch(this)) + { + LOG.warn("dispatch failed for {}",_connection); + close(); + } + } + + @Override + public int fill(Buffer buffer) throws IOException + { + int l = super.fill(buffer); + if (l<0) + { + if (!isInputShutdown()) + shutdownInput(); + if (isOutputShutdown()) + close(); + } + return l; + } + + @Override + public void close() throws IOException + { + if (_connection instanceof AbstractHttpConnection) + ((AbstractHttpConnection)_connection).getRequest().getAsyncContinuation().cancel(); + super.close(); + } + + public void run() + { + try + { + connectionOpened(_connection); + synchronized(_connections) + { + _connections.add(this); + } + + while (isStarted() && !isClosed()) + { + if (_connection.isIdle()) + { + if (isLowResources()) + setMaxIdleTime(getLowResourcesMaxIdleTime()); + } + + _connection=_connection.handle(); + } + } + catch (EofException e) + { + LOG.debug("EOF", e); + try{close();} + catch(IOException e2){LOG.ignore(e2);} + } + catch (SocketException e) + { + LOG.debug("EOF", e); + try{close();} + catch(IOException e2){LOG.ignore(e2);} + } + catch (HttpException e) + { + LOG.debug("BAD", e); + try{close();} + catch(IOException e2){LOG.ignore(e2);} + } + catch(Exception e) + { + LOG.warn("handle failed?",e); + try{close();} + catch(IOException e2){LOG.ignore(e2);} + } + finally + { + connectionClosed(_connection); + synchronized(_connections) + { + _connections.remove(this); + } + + // wait for client to close, but if not, close ourselves. + try + { + if (!_socket.isClosed()) + { + long timestamp=System.currentTimeMillis(); + int max_idle=getMaxIdleTime(); + + _socket.setSoTimeout(getMaxIdleTime()); + int c=0; + do + { + c = _socket.getInputStream().read(); + } + while (c>=0 && (System.currentTimeMillis()-timestamp)<max_idle); + if (!_socket.isClosed()) + _socket.close(); + } + } + catch(IOException e) + { + LOG.ignore(e); + } + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/handler/AbstractHandler.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,105 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + + +import java.io.IOException; + +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.component.AggregateLifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + + +/* ------------------------------------------------------------ */ +/** AbstractHandler. + * + * + */ +public abstract class AbstractHandler extends AggregateLifeCycle implements Handler +{ + private static final Logger LOG = Log.getLogger(AbstractHandler.class); + + private Server _server; + + /* ------------------------------------------------------------ */ + /** + * + */ + public AbstractHandler() + { + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.thread.LifeCycle#start() + */ + @Override + protected void doStart() throws Exception + { + LOG.debug("starting {}",this); + super.doStart(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.thread.LifeCycle#stop() + */ + @Override + protected void doStop() throws Exception + { + LOG.debug("stopping {}",this); + super.doStop(); + } + + /* ------------------------------------------------------------ */ + public void setServer(Server server) + { + Server old_server=_server; + if (old_server!=null && old_server!=server) + old_server.getContainer().removeBean(this); + _server=server; + if (_server!=null && _server!=old_server) + _server.getContainer().addBean(this); + } + + /* ------------------------------------------------------------ */ + public Server getServer() + { + return _server; + } + + /* ------------------------------------------------------------ */ + public void destroy() + { + if (!isStopped()) + throw new IllegalStateException("!STOPPED"); + super.destroy(); + if (_server!=null) + _server.getContainer().removeBean(this); + } + + /* ------------------------------------------------------------ */ + public void dumpThis(Appendable out) throws IOException + { + out.append(toString()).append(" - ").append(getState()).append('\n'); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,123 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + + +import java.io.IOException; + +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HandlerContainer; +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.TypeUtil; + + +/* ------------------------------------------------------------ */ +/** Abstract Handler Container. + * This is the base class for handlers that may contain other handlers. + * + */ +public abstract class AbstractHandlerContainer extends AbstractHandler implements HandlerContainer +{ + /* ------------------------------------------------------------ */ + public AbstractHandlerContainer() + { + } + + /* ------------------------------------------------------------ */ + public Handler[] getChildHandlers() + { + Object list = expandChildren(null,null); + return (Handler[])LazyList.toArray(list, Handler.class); + } + + /* ------------------------------------------------------------ */ + public Handler[] getChildHandlersByClass(Class<?> byclass) + { + Object list = expandChildren(null,byclass); + return (Handler[])LazyList.toArray(list, byclass); + } + + /* ------------------------------------------------------------ */ + public <T extends Handler> T getChildHandlerByClass(Class<T> byclass) + { + // TODO this can be more efficient? + Object list = expandChildren(null,byclass); + if (list==null) + return null; + return (T)LazyList.get(list, 0); + } + + /* ------------------------------------------------------------ */ + protected Object expandChildren(Object list, Class<?> byClass) + { + return list; + } + + /* ------------------------------------------------------------ */ + protected Object expandHandler(Handler handler, Object list, Class<Handler> byClass) + { + if (handler==null) + return list; + + if (byClass==null || byClass.isAssignableFrom(handler.getClass())) + list=LazyList.add(list, handler); + + if (handler instanceof AbstractHandlerContainer) + list=((AbstractHandlerContainer)handler).expandChildren(list, byClass); + else if (handler instanceof HandlerContainer) + { + HandlerContainer container = (HandlerContainer)handler; + Handler[] handlers=byClass==null?container.getChildHandlers():container.getChildHandlersByClass(byClass); + list=LazyList.addArray(list, handlers); + } + + return list; + } + + /* ------------------------------------------------------------ */ + public static <T extends HandlerContainer> T findContainerOf(HandlerContainer root,Class<T>type, Handler handler) + { + if (root==null || handler==null) + return null; + + Handler[] branches=root.getChildHandlersByClass(type); + if (branches!=null) + { + for (Handler h:branches) + { + T container = (T)h; + Handler[] candidates = container.getChildHandlersByClass(handler.getClass()); + if (candidates!=null) + { + for (Handler c:candidates) + if (c==handler) + return container; + } + } + } + return null; + } + + /* ------------------------------------------------------------ */ + public void dump(Appendable out,String indent) throws IOException + { + dumpThis(out); + dump(out,indent,getBeans(),TypeUtil.asList(getHandlers())); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/handler/ConnectHandler.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,1025 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.util.Arrays; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpMethods; +import org.eclipse.jetty.http.HttpParser; +import org.eclipse.jetty.io.AsyncEndPoint; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.ConnectedEndPoint; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.nio.AsyncConnection; +import org.eclipse.jetty.io.nio.IndirectNIOBuffer; +import org.eclipse.jetty.io.nio.SelectChannelEndPoint; +import org.eclipse.jetty.io.nio.SelectorManager; +import org.eclipse.jetty.server.AbstractHttpConnection; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.HostMap; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.thread.ThreadPool; + +/** + * <p>Implementation of a tunneling proxy that supports HTTP CONNECT.</p> + * <p>To work as CONNECT proxy, objects of this class must be instantiated using the no-arguments + * constructor, since the remote server information will be present in the CONNECT URI.</p> + */ +public class ConnectHandler extends HandlerWrapper +{ + private static final Logger LOG = Log.getLogger(ConnectHandler.class); + private final SelectorManager _selectorManager = new Manager(); + private volatile int _connectTimeout = 5000; + private volatile int _writeTimeout = 30000; + private volatile ThreadPool _threadPool; + private volatile boolean _privateThreadPool; + private HostMap<String> _white = new HostMap<String>(); + private HostMap<String> _black = new HostMap<String>(); + + public ConnectHandler() + { + this(null); + } + + public ConnectHandler(String[] white, String[] black) + { + this(null, white, black); + } + + public ConnectHandler(Handler handler) + { + setHandler(handler); + } + + public ConnectHandler(Handler handler, String[] white, String[] black) + { + setHandler(handler); + set(white, _white); + set(black, _black); + } + + /** + * @return the timeout, in milliseconds, to connect to the remote server + */ + public int getConnectTimeout() + { + return _connectTimeout; + } + + /** + * @param connectTimeout the timeout, in milliseconds, to connect to the remote server + */ + public void setConnectTimeout(int connectTimeout) + { + _connectTimeout = connectTimeout; + } + + /** + * @return the timeout, in milliseconds, to write data to a peer + */ + public int getWriteTimeout() + { + return _writeTimeout; + } + + /** + * @param writeTimeout the timeout, in milliseconds, to write data to a peer + */ + public void setWriteTimeout(int writeTimeout) + { + _writeTimeout = writeTimeout; + } + + @Override + public void setServer(Server server) + { + super.setServer(server); + + server.getContainer().update(this, null, _selectorManager, "selectManager"); + + if (_privateThreadPool) + server.getContainer().update(this, null, _privateThreadPool, "threadpool", true); + else + _threadPool = server.getThreadPool(); + } + + /** + * @return the thread pool + */ + public ThreadPool getThreadPool() + { + return _threadPool; + } + + /** + * @param threadPool the thread pool + */ + public void setThreadPool(ThreadPool threadPool) + { + if (getServer() != null) + getServer().getContainer().update(this, _privateThreadPool ? _threadPool : null, threadPool, "threadpool", true); + _privateThreadPool = threadPool != null; + _threadPool = threadPool; + } + + @Override + protected void doStart() throws Exception + { + super.doStart(); + + if (_threadPool == null) + { + _threadPool = getServer().getThreadPool(); + _privateThreadPool = false; + } + if (_threadPool instanceof LifeCycle && !((LifeCycle)_threadPool).isRunning()) + ((LifeCycle)_threadPool).start(); + + _selectorManager.start(); + } + + @Override + protected void doStop() throws Exception + { + _selectorManager.stop(); + + ThreadPool threadPool = _threadPool; + if (_privateThreadPool && _threadPool != null && threadPool instanceof LifeCycle) + ((LifeCycle)threadPool).stop(); + + super.doStop(); + } + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + if (HttpMethods.CONNECT.equalsIgnoreCase(request.getMethod())) + { + LOG.debug("CONNECT request for {}", request.getRequestURI()); + try + { + handleConnect(baseRequest, request, response, request.getRequestURI()); + } + catch(Exception e) + { + LOG.warn("ConnectHandler "+baseRequest.getUri()+" "+ e); + LOG.debug(e); + } + } + else + { + super.handle(target, baseRequest, request, response); + } + } + + /** + * <p>Handles a CONNECT request.</p> + * <p>CONNECT requests may have authentication headers such as <code>Proxy-Authorization</code> + * that authenticate the client with the proxy.</p> + * + * @param baseRequest Jetty-specific http request + * @param request the http request + * @param response the http response + * @param serverAddress the remote server address in the form {@code host:port} + * @throws ServletException if an application error occurs + * @throws IOException if an I/O error occurs + */ + protected void handleConnect(Request baseRequest, HttpServletRequest request, HttpServletResponse response, String serverAddress) throws ServletException, IOException + { + boolean proceed = handleAuthentication(request, response, serverAddress); + if (!proceed) + return; + + String host = serverAddress; + int port = 80; + int colon = serverAddress.indexOf(':'); + if (colon > 0) + { + host = serverAddress.substring(0, colon); + port = Integer.parseInt(serverAddress.substring(colon + 1)); + } + + if (!validateDestination(host)) + { + LOG.info("ProxyHandler: Forbidden destination " + host); + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + baseRequest.setHandled(true); + return; + } + + SocketChannel channel; + + try + { + channel = connectToServer(request,host,port); + } + catch (SocketException se) + { + LOG.info("ConnectHandler: SocketException " + se.getMessage()); + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + baseRequest.setHandled(true); + return; + } + catch (SocketTimeoutException ste) + { + LOG.info("ConnectHandler: SocketTimeoutException" + ste.getMessage()); + response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT); + baseRequest.setHandled(true); + return; + } + catch (IOException ioe) + { + LOG.info("ConnectHandler: IOException" + ioe.getMessage()); + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + baseRequest.setHandled(true); + return; + } + + // Transfer unread data from old connection to new connection + // We need to copy the data to avoid races: + // 1. when this unread data is written and the server replies before the clientToProxy + // connection is installed (it is only installed after returning from this method) + // 2. when the client sends data before this unread data has been written. + AbstractHttpConnection httpConnection = AbstractHttpConnection.getCurrentConnection(); + Buffer headerBuffer = ((HttpParser)httpConnection.getParser()).getHeaderBuffer(); + Buffer bodyBuffer = ((HttpParser)httpConnection.getParser()).getBodyBuffer(); + int length = headerBuffer == null ? 0 : headerBuffer.length(); + length += bodyBuffer == null ? 0 : bodyBuffer.length(); + IndirectNIOBuffer buffer = null; + if (length > 0) + { + buffer = new IndirectNIOBuffer(length); + if (headerBuffer != null) + { + buffer.put(headerBuffer); + headerBuffer.clear(); + } + if (bodyBuffer != null) + { + buffer.put(bodyBuffer); + bodyBuffer.clear(); + } + } + + ConcurrentMap<String, Object> context = new ConcurrentHashMap<String, Object>(); + prepareContext(request, context); + + ClientToProxyConnection clientToProxy = prepareConnections(context, channel, buffer); + + // CONNECT expects a 200 response + response.setStatus(HttpServletResponse.SC_OK); + + // Prevent close + baseRequest.getConnection().getGenerator().setPersistent(true); + + // Close to force last flush it so that the client receives it + response.getOutputStream().close(); + + upgradeConnection(request, response, clientToProxy); + } + + private ClientToProxyConnection prepareConnections(ConcurrentMap<String, Object> context, SocketChannel channel, Buffer buffer) + { + AbstractHttpConnection httpConnection = AbstractHttpConnection.getCurrentConnection(); + ProxyToServerConnection proxyToServer = newProxyToServerConnection(context, buffer); + ClientToProxyConnection clientToProxy = newClientToProxyConnection(context, channel, httpConnection.getEndPoint(), httpConnection.getTimeStamp()); + clientToProxy.setConnection(proxyToServer); + proxyToServer.setConnection(clientToProxy); + return clientToProxy; + } + + /** + * <p>Handles the authentication before setting up the tunnel to the remote server.</p> + * <p>The default implementation returns true.</p> + * + * @param request the HTTP request + * @param response the HTTP response + * @param address the address of the remote server in the form {@code host:port}. + * @return true to allow to connect to the remote host, false otherwise + * @throws ServletException to report a server error to the caller + * @throws IOException to report a server error to the caller + */ + protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String address) throws ServletException, IOException + { + return true; + } + + protected ClientToProxyConnection newClientToProxyConnection(ConcurrentMap<String, Object> context, SocketChannel channel, EndPoint endPoint, long timeStamp) + { + return new ClientToProxyConnection(context, channel, endPoint, timeStamp); + } + + protected ProxyToServerConnection newProxyToServerConnection(ConcurrentMap<String, Object> context, Buffer buffer) + { + return new ProxyToServerConnection(context, buffer); + } + + // may return null + private SocketChannel connectToServer(HttpServletRequest request, String host, int port) throws IOException + { + SocketChannel channel = connect(request, host, port); + channel.configureBlocking(false); + return channel; + } + + /** + * <p>Establishes a connection to the remote server.</p> + * + * @param request the HTTP request that initiated the tunnel + * @param host the host to connect to + * @param port the port to connect to + * @return a {@link SocketChannel} connected to the remote server + * @throws IOException if the connection cannot be established + */ + protected SocketChannel connect(HttpServletRequest request, String host, int port) throws IOException + { + SocketChannel channel = SocketChannel.open(); + + if (channel == null) + { + throw new IOException("unable to connect to " + host + ":" + port); + } + + try + { + // Connect to remote server + LOG.debug("Establishing connection to {}:{}", host, port); + channel.socket().setTcpNoDelay(true); + channel.socket().connect(new InetSocketAddress(host, port), getConnectTimeout()); + LOG.debug("Established connection to {}:{}", host, port); + return channel; + } + catch (IOException x) + { + LOG.debug("Failed to establish connection to " + host + ":" + port, x); + try + { + channel.close(); + } + catch (IOException xx) + { + LOG.ignore(xx); + } + throw x; + } + } + + protected void prepareContext(HttpServletRequest request, ConcurrentMap<String, Object> context) + { + } + + private void upgradeConnection(HttpServletRequest request, HttpServletResponse response, Connection connection) throws IOException + { + // Set the new connection as request attribute and change the status to 101 + // so that Jetty understands that it has to upgrade the connection + request.setAttribute("org.eclipse.jetty.io.Connection", connection); + response.setStatus(HttpServletResponse.SC_SWITCHING_PROTOCOLS); + LOG.debug("Upgraded connection to {}", connection); + } + + private void register(SocketChannel channel, ProxyToServerConnection proxyToServer) throws IOException + { + _selectorManager.register(channel, proxyToServer); + proxyToServer.waitReady(_connectTimeout); + } + + /** + * <p>Reads (with non-blocking semantic) into the given {@code buffer} from the given {@code endPoint}.</p> + * + * @param endPoint the endPoint to read from + * @param buffer the buffer to read data into + * @param context the context information related to the connection + * @return the number of bytes read (possibly 0 since the read is non-blocking) + * or -1 if the channel has been closed remotely + * @throws IOException if the endPoint cannot be read + */ + protected int read(EndPoint endPoint, Buffer buffer, ConcurrentMap<String, Object> context) throws IOException + { + return endPoint.fill(buffer); + } + + /** + * <p>Writes (with blocking semantic) the given buffer of data onto the given endPoint.</p> + * + * @param endPoint the endPoint to write to + * @param buffer the buffer to write + * @param context the context information related to the connection + * @throws IOException if the buffer cannot be written + * @return the number of bytes written + */ + protected int write(EndPoint endPoint, Buffer buffer, ConcurrentMap<String, Object> context) throws IOException + { + if (buffer == null) + return 0; + + int length = buffer.length(); + final StringBuilder debug = LOG.isDebugEnabled()?new StringBuilder():null; + int flushed = endPoint.flush(buffer); + if (debug!=null) + debug.append(flushed); + + // Loop until all written + while (buffer.length()>0 && !endPoint.isOutputShutdown()) + { + if (!endPoint.isBlocking()) + { + boolean ready = endPoint.blockWritable(getWriteTimeout()); + if (!ready) + throw new IOException("Write timeout"); + } + flushed = endPoint.flush(buffer); + if (debug!=null) + debug.append("+").append(flushed); + } + + LOG.debug("Written {}/{} bytes {}", debug, length, endPoint); + buffer.compact(); + return length; + } + + private class Manager extends SelectorManager + { + @Override + protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException + { + SelectChannelEndPoint endp = new SelectChannelEndPoint(channel, selectSet, key, channel.socket().getSoTimeout()); + endp.setConnection(selectSet.getManager().newConnection(channel,endp, key.attachment())); + endp.setMaxIdleTime(_writeTimeout); + return endp; + } + + @Override + public AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endpoint, Object attachment) + { + ProxyToServerConnection proxyToServer = (ProxyToServerConnection)attachment; + proxyToServer.setTimeStamp(System.currentTimeMillis()); + proxyToServer.setEndPoint(endpoint); + return proxyToServer; + } + + @Override + protected void endPointOpened(SelectChannelEndPoint endpoint) + { + ProxyToServerConnection proxyToServer = (ProxyToServerConnection)endpoint.getSelectionKey().attachment(); + proxyToServer.ready(); + } + + @Override + public boolean dispatch(Runnable task) + { + return _threadPool.dispatch(task); + } + + @Override + protected void endPointClosed(SelectChannelEndPoint endpoint) + { + } + + @Override + protected void endPointUpgraded(ConnectedEndPoint endpoint, Connection oldConnection) + { + } + } + + public class ProxyToServerConnection implements AsyncConnection + { + private final CountDownLatch _ready = new CountDownLatch(1); + private final Buffer _buffer = new IndirectNIOBuffer(4096); + private final ConcurrentMap<String, Object> _context; + private volatile Buffer _data; + private volatile ClientToProxyConnection _toClient; + private volatile long _timestamp; + private volatile AsyncEndPoint _endPoint; + + public ProxyToServerConnection(ConcurrentMap<String, Object> context, Buffer data) + { + _context = context; + _data = data; + } + + @Override + public String toString() + { + StringBuilder builder = new StringBuilder("ProxyToServer"); + builder.append("(:").append(_endPoint.getLocalPort()); + builder.append("<=>:").append(_endPoint.getRemotePort()); + return builder.append(")").toString(); + } + + public Connection handle() throws IOException + { + LOG.debug("{}: begin reading from server", this); + try + { + writeData(); + + while (true) + { + int read = read(_endPoint, _buffer, _context); + + if (read == -1) + { + LOG.debug("{}: server closed connection {}", this, _endPoint); + + if (_endPoint.isOutputShutdown() || !_endPoint.isOpen()) + closeClient(); + else + _toClient.shutdownOutput(); + + break; + } + + if (read == 0) + break; + + LOG.debug("{}: read from server {} bytes {}", this, read, _endPoint); + int written = write(_toClient._endPoint, _buffer, _context); + LOG.debug("{}: written to {} {} bytes", this, _toClient, written); + } + return this; + } + catch (ClosedChannelException x) + { + LOG.debug(x); + throw x; + } + catch (IOException x) + { + LOG.warn(this + ": unexpected exception", x); + close(); + throw x; + } + catch (RuntimeException x) + { + LOG.warn(this + ": unexpected exception", x); + close(); + throw x; + } + finally + { + LOG.debug("{}: end reading from server", this); + } + } + + public void onInputShutdown() throws IOException + { + } + + private void writeData() throws IOException + { + // This method is called from handle() and closeServer() + // which may happen concurrently (e.g. a client closing + // while reading from the server), so needs synchronization + synchronized (this) + { + if (_data != null) + { + try + { + int written = write(_endPoint, _data, _context); + LOG.debug("{}: written to server {} bytes", this, written); + } + finally + { + // Attempt once to write the data; if the write fails (for example + // because the connection is already closed), clear the data and + // give up to avoid to continue to write data to a closed connection + _data = null; + } + } + } + } + + public void setConnection(ClientToProxyConnection connection) + { + _toClient = connection; + } + + public long getTimeStamp() + { + return _timestamp; + } + + public void setTimeStamp(long timestamp) + { + _timestamp = timestamp; + } + + public void setEndPoint(AsyncEndPoint endpoint) + { + _endPoint = endpoint; + } + + public boolean isIdle() + { + return false; + } + + public boolean isSuspended() + { + return false; + } + + public void onClose() + { + } + + public void ready() + { + _ready.countDown(); + } + + public void waitReady(long timeout) throws IOException + { + try + { + _ready.await(timeout, TimeUnit.MILLISECONDS); + } + catch (final InterruptedException x) + { + throw new IOException() + {{ + initCause(x); + }}; + } + } + + public void closeClient() throws IOException + { + _toClient.closeClient(); + } + + public void closeServer() throws IOException + { + _endPoint.close(); + } + + public void close() + { + try + { + closeClient(); + } + catch (IOException x) + { + LOG.debug(this + ": unexpected exception closing the client", x); + } + + try + { + closeServer(); + } + catch (IOException x) + { + LOG.debug(this + ": unexpected exception closing the server", x); + } + } + + public void shutdownOutput() throws IOException + { + writeData(); + _endPoint.shutdownOutput(); + } + + public void onIdleExpired(long idleForMs) + { + try + { + LOG.debug("{} idle expired", this); + if (_endPoint.isOutputShutdown()) + close(); + else + shutdownOutput(); + } + catch(Exception e) + { + LOG.debug(e); + close(); + } + } + } + + public class ClientToProxyConnection implements AsyncConnection + { + private final Buffer _buffer = new IndirectNIOBuffer(4096); + private final ConcurrentMap<String, Object> _context; + private final SocketChannel _channel; + private final EndPoint _endPoint; + private final long _timestamp; + private volatile ProxyToServerConnection _toServer; + private boolean _firstTime = true; + + public ClientToProxyConnection(ConcurrentMap<String, Object> context, SocketChannel channel, EndPoint endPoint, long timestamp) + { + _context = context; + _channel = channel; + _endPoint = endPoint; + _timestamp = timestamp; + } + + @Override + public String toString() + { + StringBuilder builder = new StringBuilder("ClientToProxy"); + builder.append("(:").append(_endPoint.getLocalPort()); + builder.append("<=>:").append(_endPoint.getRemotePort()); + return builder.append(")").toString(); + } + + public Connection handle() throws IOException + { + LOG.debug("{}: begin reading from client", this); + try + { + if (_firstTime) + { + _firstTime = false; + register(_channel, _toServer); + LOG.debug("{}: registered channel {} with connection {}", this, _channel, _toServer); + } + + while (true) + { + int read = read(_endPoint, _buffer, _context); + + if (read == -1) + { + LOG.debug("{}: client closed connection {}", this, _endPoint); + + if (_endPoint.isOutputShutdown() || !_endPoint.isOpen()) + closeServer(); + else + _toServer.shutdownOutput(); + + break; + } + + if (read == 0) + break; + + LOG.debug("{}: read from client {} bytes {}", this, read, _endPoint); + int written = write(_toServer._endPoint, _buffer, _context); + LOG.debug("{}: written to {} {} bytes", this, _toServer, written); + } + return this; + } + catch (ClosedChannelException x) + { + LOG.debug(x); + closeServer(); + throw x; + } + catch (IOException x) + { + LOG.warn(this + ": unexpected exception", x); + close(); + throw x; + } + catch (RuntimeException x) + { + LOG.warn(this + ": unexpected exception", x); + close(); + throw x; + } + finally + { + LOG.debug("{}: end reading from client", this); + } + } + + public void onInputShutdown() throws IOException + { + } + + public long getTimeStamp() + { + return _timestamp; + } + + public boolean isIdle() + { + return false; + } + + public boolean isSuspended() + { + return false; + } + + public void onClose() + { + } + + public void setConnection(ProxyToServerConnection connection) + { + _toServer = connection; + } + + public void closeClient() throws IOException + { + _endPoint.close(); + } + + public void closeServer() throws IOException + { + _toServer.closeServer(); + } + + public void close() + { + try + { + closeClient(); + } + catch (IOException x) + { + LOG.debug(this + ": unexpected exception closing the client", x); + } + + try + { + closeServer(); + } + catch (IOException x) + { + LOG.debug(this + ": unexpected exception closing the server", x); + } + } + + public void shutdownOutput() throws IOException + { + _endPoint.shutdownOutput(); + } + + public void onIdleExpired(long idleForMs) + { + try + { + LOG.debug("{} idle expired", this); + if (_endPoint.isOutputShutdown()) + close(); + else + shutdownOutput(); + } + catch(Exception e) + { + LOG.debug(e); + close(); + } + } + } + + /** + * Add a whitelist entry to an existing handler configuration + * + * @param entry new whitelist entry + */ + public void addWhite(String entry) + { + add(entry, _white); + } + + /** + * Add a blacklist entry to an existing handler configuration + * + * @param entry new blacklist entry + */ + public void addBlack(String entry) + { + add(entry, _black); + } + + /** + * Re-initialize the whitelist of existing handler object + * + * @param entries array of whitelist entries + */ + public void setWhite(String[] entries) + { + set(entries, _white); + } + + /** + * Re-initialize the blacklist of existing handler object + * + * @param entries array of blacklist entries + */ + public void setBlack(String[] entries) + { + set(entries, _black); + } + + /** + * Helper method to process a list of new entries and replace + * the content of the specified host map + * + * @param entries new entries + * @param hostMap target host map + */ + protected void set(String[] entries, HostMap<String> hostMap) + { + hostMap.clear(); + + if (entries != null && entries.length > 0) + { + for (String addrPath : entries) + { + add(addrPath, hostMap); + } + } + } + + /** + * Helper method to process the new entry and add it to + * the specified host map. + * + * @param entry new entry + * @param hostMap target host map + */ + private void add(String entry, HostMap<String> hostMap) + { + if (entry != null && entry.length() > 0) + { + entry = entry.trim(); + if (hostMap.get(entry) == null) + { + hostMap.put(entry, entry); + } + } + } + + /** + * Check the request hostname against white- and blacklist. + * + * @param host hostname to check + * @return true if hostname is allowed to be proxied + */ + public boolean validateDestination(String host) + { + if (_white.size() > 0) + { + Object whiteObj = _white.getLazyMatches(host); + if (whiteObj == null) + { + return false; + } + } + + if (_black.size() > 0) + { + Object blackObj = _black.getLazyMatches(host); + if (blackObj != null) + { + return false; + } + } + + return true; + } + + @Override + public void dump(Appendable out, String indent) throws IOException + { + dumpThis(out); + if (_privateThreadPool) + dump(out, indent, Arrays.asList(_threadPool, _selectorManager), TypeUtil.asList(getHandlers()), getBeans()); + else + dump(out, indent, Arrays.asList(_selectorManager), TypeUtil.asList(getHandlers()), getBeans()); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/handler/ContextHandler.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,2570 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.security.AccessController; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.EventListener; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; + +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.Servlet; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextAttributeEvent; +import javax.servlet.ServletContextAttributeListener; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.ServletException; +import javax.servlet.ServletRegistration; +import javax.servlet.ServletRequestAttributeListener; +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.SessionCookieConfig; +import javax.servlet.SessionTrackingMode; +import javax.servlet.Filter; +import javax.servlet.FilterRegistration; +import javax.servlet.FilterRegistration.Dynamic; +import javax.servlet.descriptor.JspConfigDescriptor; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpException; +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.server.AbstractHttpConnection; +import org.eclipse.jetty.server.Dispatcher; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HandlerContainer; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.Attributes; +import org.eclipse.jetty.util.AttributesMap; +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.Loader; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.component.AggregateLifeCycle; +import org.eclipse.jetty.util.component.Dumpable; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.resource.Resource; + +/* ------------------------------------------------------------ */ +/** + * ContextHandler. + * + * This handler wraps a call to handle by setting the context and servlet path, plus setting the context classloader. + * + * <p> + * If the context init parameter "org.eclipse.jetty.server.context.ManagedAttributes" is set to a comma separated list of names, then they are treated as + * context attribute names, which if set as attributes are passed to the servers Container so that they may be managed with JMX. + * <p> + * The maximum size of a form that can be processed by this context is controlled by the system properties org.eclipse.jetty.server.Request.maxFormKeys + * and org.eclipse.jetty.server.Request.maxFormContentSize. These can also be configured with {@link #setMaxFormContentSize(int)} and {@link #setMaxFormKeys(int)} + * + * @org.apache.xbean.XBean description="Creates a basic HTTP context" + */ +public class ContextHandler extends ScopedHandler implements Attributes, Server.Graceful +{ + private static final Logger LOG = Log.getLogger(ContextHandler.class); + + private static final ThreadLocal<Context> __context = new ThreadLocal<Context>(); + + /** + * 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 + * for the attribute value. + */ + public static final String MANAGED_ATTRIBUTES = "org.eclipse.jetty.server.context.ManagedAttributes"; + + /* ------------------------------------------------------------ */ + /** + * Get the current ServletContext implementation. + * + * @return ServletContext implementation + */ + public static Context getCurrentContext() + { + return __context.get(); + } + + protected Context _scontext; + + private final AttributesMap _attributes; + private final AttributesMap _contextAttributes; + private final Map<String, String> _initParams; + private ClassLoader _classLoader; + private String _contextPath = "/"; + private String _displayName; + private Resource _baseResource; + private MimeTypes _mimeTypes; + private Map<String, String> _localeEncodingMap; + private String[] _welcomeFiles; + private ErrorHandler _errorHandler; + private String[] _vhosts; + private Set<String> _connectors; + private EventListener[] _eventListeners; + private Logger _logger; + private boolean _allowNullPathInfo; + private int _maxFormKeys = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormKeys",-1).intValue(); + private int _maxFormContentSize = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormContentSize",-1).intValue(); + private boolean _compactPath = false; + private boolean _aliasesAllowed = false; + + private Object _contextListeners; + private Object _contextAttributeListeners; + private Object _requestListeners; + private Object _requestAttributeListeners; + private Object _durableListeners; + private Map<String, Object> _managedAttributes; + private String[] _protectedTargets; + private final CopyOnWriteArrayList<AliasCheck> _aliasChecks = new CopyOnWriteArrayList<ContextHandler.AliasCheck>(); + + private boolean _shutdown = false; + private boolean _available = true; + private volatile int _availability; // 0=STOPPED, 1=AVAILABLE, 2=SHUTDOWN, 3=UNAVAILABLE + + private final static int __STOPPED = 0, __AVAILABLE = 1, __SHUTDOWN = 2, __UNAVAILABLE = 3; + + /* ------------------------------------------------------------ */ + /** + * + */ + public ContextHandler() + { + super(); + _scontext = new Context(); + _attributes = new AttributesMap(); + _contextAttributes = new AttributesMap(); + _initParams = new HashMap<String, String>(); + addAliasCheck(new ApproveNonExistentDirectoryAliases()); + } + + /* ------------------------------------------------------------ */ + /** + * + */ + protected ContextHandler(Context context) + { + super(); + _scontext = context; + _attributes = new AttributesMap(); + _contextAttributes = new AttributesMap(); + _initParams = new HashMap<String, String>(); + addAliasCheck(new ApproveNonExistentDirectoryAliases()); + } + + /* ------------------------------------------------------------ */ + /** + * + */ + public ContextHandler(String contextPath) + { + this(); + setContextPath(contextPath); + } + + /* ------------------------------------------------------------ */ + /** + * + */ + public ContextHandler(HandlerContainer parent, String contextPath) + { + this(); + setContextPath(contextPath); + if (parent instanceof HandlerWrapper) + ((HandlerWrapper)parent).setHandler(this); + else if (parent instanceof HandlerCollection) + ((HandlerCollection)parent).addHandler(this); + } + + /* ------------------------------------------------------------ */ + @Override + public void dump(Appendable out, String indent) throws IOException + { + dumpThis(out); + dump(out,indent,Collections.singletonList(new CLDump(getClassLoader())),TypeUtil.asList(getHandlers()),getBeans(),_initParams.entrySet(), + _attributes.getAttributeEntrySet(),_contextAttributes.getAttributeEntrySet()); + } + + /* ------------------------------------------------------------ */ + public Context getServletContext() + { + return _scontext; + } + + /* ------------------------------------------------------------ */ + /** + * @return the allowNullPathInfo true if /context is not redirected to /context/ + */ + public boolean getAllowNullPathInfo() + { + return _allowNullPathInfo; + } + + /* ------------------------------------------------------------ */ + /** + * @param allowNullPathInfo + * true if /context is not redirected to /context/ + */ + public void setAllowNullPathInfo(boolean allowNullPathInfo) + { + _allowNullPathInfo = allowNullPathInfo; + } + + /* ------------------------------------------------------------ */ + @Override + public void setServer(Server server) + { + if (_errorHandler != null) + { + Server old_server = getServer(); + if (old_server != null && old_server != server) + old_server.getContainer().update(this,_errorHandler,null,"error",true); + super.setServer(server); + if (server != null && server != old_server) + server.getContainer().update(this,null,_errorHandler,"error",true); + _errorHandler.setServer(server); + } + else + super.setServer(server); + } + + /* ------------------------------------------------------------ */ + /** + * Set the virtual hosts for the context. Only requests that have a matching host header or fully qualified URL will be passed to that context with a + * virtual host name. A context with no virtual host names or a null virtual host name is available to all requests that are not served by a context with a + * matching virtual host name. + * + * @param vhosts + * Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be + * String representation of IP addresses. Host names may start with '*.' to wildcard one level of names. + */ + public void setVirtualHosts(String[] vhosts) + { + if (vhosts == null) + { + _vhosts = vhosts; + } + else + { + _vhosts = new String[vhosts.length]; + for (int i = 0; i < vhosts.length; i++) + _vhosts[i] = normalizeHostname(vhosts[i]); + } + } + + /* ------------------------------------------------------------ */ + /** Either set virtual hosts or add to an existing set of virtual hosts. + * + * @param virtualHosts + * Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be + * String representation of IP addresses. Host names may start with '*.' to wildcard one level of names. + */ + public void addVirtualHosts(String[] virtualHosts) + { + if (virtualHosts == null) // since this is add, we don't null the old ones + { + return; + } + else + { + List<String> currentVirtualHosts = null; + if (_vhosts != null) + { + currentVirtualHosts = new ArrayList<String>(Arrays.asList(_vhosts)); + } + else + { + currentVirtualHosts = new ArrayList<String>(); + } + + for (int i = 0; i < virtualHosts.length; i++) + { + String normVhost = normalizeHostname(virtualHosts[i]); + if (!currentVirtualHosts.contains(normVhost)) + { + currentVirtualHosts.add(normVhost); + } + } + _vhosts = currentVirtualHosts.toArray(new String[0]); + } + } + + /* ------------------------------------------------------------ */ + /** + * Removes an array of virtual host entries, if this removes all entries the _vhosts will be set to null + * + * @param virtualHosts + * Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be + * String representation of IP addresses. Host names may start with '*.' to wildcard one level of names. + */ + public void removeVirtualHosts(String[] virtualHosts) + { + if (virtualHosts == null) + { + return; // do nothing + } + else if ( _vhosts == null || _vhosts.length == 0) + { + return; // do nothing + } + else + { + List<String> existingVirtualHosts = new ArrayList<String>(Arrays.asList(_vhosts)); + + for (int i = 0; i < virtualHosts.length; i++) + { + String toRemoveVirtualHost = normalizeHostname(virtualHosts[i]); + if (existingVirtualHosts.contains(toRemoveVirtualHost)) + { + existingVirtualHosts.remove(toRemoveVirtualHost); + } + } + + if (existingVirtualHosts.isEmpty()) + { + _vhosts = null; // if we ended up removing them all, just null out _vhosts + } + else + { + _vhosts = existingVirtualHosts.toArray(new String[0]); + } + } + } + + /* ------------------------------------------------------------ */ + /** + * Get the virtual hosts for the context. Only requests that have a matching host header or fully qualified URL will be passed to that context with a + * virtual host name. A context with no virtual host names or a null virtual host name is available to all requests that are not served by a context with a + * matching virtual host name. + * + * @return Array of virtual hosts that this context responds to. A null host name or empty array means any hostname is acceptable. Host names may be String + * representation of IP addresses. Host names may start with '*.' to wildcard one level of names. + */ + public String[] getVirtualHosts() + { + return _vhosts; + } + + /* ------------------------------------------------------------ */ + /** + * @return an array of connector names that this context will accept a request from. + */ + public String[] getConnectorNames() + { + if (_connectors == null || _connectors.size() == 0) + return null; + + return _connectors.toArray(new String[_connectors.size()]); + } + + /* ------------------------------------------------------------ */ + /** + * Set the names of accepted connectors. + * + * Names are either "host:port" or a specific configured name for a connector. + * + * @param connectors + * If non null, an array of connector names that this context will accept a request from. + */ + public void setConnectorNames(String[] connectors) + { + if (connectors == null || connectors.length == 0) + _connectors = null; + else + _connectors = new HashSet<String>(Arrays.asList(connectors)); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getAttribute(java.lang.String) + */ + public Object getAttribute(String name) + { + return _attributes.getAttribute(name); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getAttributeNames() + */ + @SuppressWarnings("unchecked") + public Enumeration getAttributeNames() + { + return AttributesMap.getAttributeNamesCopy(_attributes); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the attributes. + */ + public Attributes getAttributes() + { + return _attributes; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the classLoader. + */ + public ClassLoader getClassLoader() + { + return _classLoader; + } + + /* ------------------------------------------------------------ */ + /** + * Make best effort to extract a file classpath from the context classloader + * + * @return Returns the classLoader. + */ + public String getClassPath() + { + if (_classLoader == null || !(_classLoader instanceof URLClassLoader)) + return null; + URLClassLoader loader = (URLClassLoader)_classLoader; + URL[] urls = loader.getURLs(); + StringBuilder classpath = new StringBuilder(); + for (int i = 0; i < urls.length; i++) + { + try + { + Resource resource = newResource(urls[i]); + File file = resource.getFile(); + if (file != null && file.exists()) + { + if (classpath.length() > 0) + classpath.append(File.pathSeparatorChar); + classpath.append(file.getAbsolutePath()); + } + } + catch (IOException e) + { + LOG.debug(e); + } + } + if (classpath.length() == 0) + return null; + return classpath.toString(); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the _contextPath. + */ + public String getContextPath() + { + return _contextPath; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getInitParameter(java.lang.String) + */ + public String getInitParameter(String name) + { + return _initParams.get(name); + } + + /* ------------------------------------------------------------ */ + /* + */ + public String setInitParameter(String name, String value) + { + return _initParams.put(name,value); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getInitParameterNames() + */ + @SuppressWarnings("rawtypes") + public Enumeration getInitParameterNames() + { + return Collections.enumeration(_initParams.keySet()); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the initParams. + */ + public Map<String, String> getInitParams() + { + return _initParams; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getServletContextName() + */ + public String getDisplayName() + { + return _displayName; + } + + /* ------------------------------------------------------------ */ + public EventListener[] getEventListeners() + { + return _eventListeners; + } + + /* ------------------------------------------------------------ */ + /** + * Set the context event listeners. + * + * @param eventListeners + * the event listeners + * @see ServletContextListener + * @see ServletContextAttributeListener + * @see ServletRequestListener + * @see ServletRequestAttributeListener + */ + public void setEventListeners(EventListener[] eventListeners) + { + _contextListeners = null; + _contextAttributeListeners = null; + _requestListeners = null; + _requestAttributeListeners = null; + + _eventListeners = eventListeners; + + for (int i = 0; eventListeners != null && i < eventListeners.length; i++) + { + EventListener listener = _eventListeners[i]; + + if (listener instanceof ServletContextListener) + _contextListeners = LazyList.add(_contextListeners,listener); + + if (listener instanceof ServletContextAttributeListener) + _contextAttributeListeners = LazyList.add(_contextAttributeListeners,listener); + + if (listener instanceof ServletRequestListener) + _requestListeners = LazyList.add(_requestListeners,listener); + + if (listener instanceof ServletRequestAttributeListener) + _requestAttributeListeners = LazyList.add(_requestAttributeListeners,listener); + } + } + + /* ------------------------------------------------------------ */ + /** + * Add a context event listeners. + * + * @see ServletContextListener + * @see ServletContextAttributeListener + * @see ServletRequestListener + * @see ServletRequestAttributeListener + */ + public void addEventListener(EventListener listener) + { + //Only listeners added before the context starts last through a stop/start cycle + if (!(isStarted() || isStarting())) + _durableListeners = LazyList.add(_durableListeners, listener); + + setEventListeners((EventListener[])LazyList.addToArray(getEventListeners(),listener,EventListener.class)); + } + + + /** + * Apply any necessary restrictions on a programmatically added + * listener. + * + * Superclasses should implement. + * + * @param listener + */ + public void restrictEventListener (EventListener listener) + { + } + + + + /* ------------------------------------------------------------ */ + /** + * @return true if this context is accepting new requests + */ + public boolean isShutdown() + { + synchronized (this) + { + return !_shutdown; + } + } + + /* ------------------------------------------------------------ */ + /** + * Set shutdown status. This field allows for graceful shutdown of a context. A started context may be put into non accepting state so that existing + * requests can complete, but no new requests are accepted. + * + * @param shutdown + * true if this context is (not?) accepting new requests + */ + public void setShutdown(boolean shutdown) + { + synchronized (this) + { + _shutdown = shutdown; + _availability = isRunning()?(_shutdown?__SHUTDOWN:_available?__AVAILABLE:__UNAVAILABLE):__STOPPED; + } + } + + /* ------------------------------------------------------------ */ + /** + * @return false if this context is unavailable (sends 503) + */ + public boolean isAvailable() + { + synchronized (this) + { + return _available; + } + } + + /* ------------------------------------------------------------ */ + /** + * Set Available status. + */ + public void setAvailable(boolean available) + { + synchronized (this) + { + _available = available; + _availability = isRunning()?(_shutdown?__SHUTDOWN:_available?__AVAILABLE:__UNAVAILABLE):__STOPPED; + } + } + + /* ------------------------------------------------------------ */ + public Logger getLogger() + { + return _logger; + } + + /* ------------------------------------------------------------ */ + public void setLogger(Logger logger) + { + _logger = logger; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.thread.AbstractLifeCycle#doStart() + */ + @Override + protected void doStart() throws Exception + { + _availability = __STOPPED; + + if (_contextPath == null) + throw new IllegalStateException("Null contextPath"); + + _logger = Log.getLogger(getDisplayName() == null?getContextPath():getDisplayName()); + ClassLoader old_classloader = null; + Thread current_thread = null; + Context old_context = null; + + try + { + // Set the classloader + if (_classLoader != null) + { + current_thread = Thread.currentThread(); + old_classloader = current_thread.getContextClassLoader(); + current_thread.setContextClassLoader(_classLoader); + } + + if (_mimeTypes == null) + _mimeTypes = new MimeTypes(); + + old_context = __context.get(); + __context.set(_scontext); + + // defers the calling of super.doStart() + startContext(); + + synchronized(this) + { + _availability = _shutdown?__SHUTDOWN:_available?__AVAILABLE:__UNAVAILABLE; + } + } + finally + { + __context.set(old_context); + + // reset the classloader + if (_classLoader != null) + { + current_thread.setContextClassLoader(old_classloader); + } + + } + } + + /* ------------------------------------------------------------ */ + /** + * Extensible startContext. this method is called from {@link ContextHandler#doStart()} instead of a call to super.doStart(). This allows derived classes to + * insert additional handling (Eg configuration) before the call to super.doStart by this method will start contained handlers. + * + * @see org.eclipse.jetty.server.handler.ContextHandler.Context + */ + protected void startContext() throws Exception + { + String managedAttributes = _initParams.get(MANAGED_ATTRIBUTES); + if (managedAttributes != null) + { + _managedAttributes = new HashMap<String, Object>(); + String[] attributes = managedAttributes.split(","); + for (String attribute : attributes) + _managedAttributes.put(attribute,null); + + Enumeration e = _scontext.getAttributeNames(); + while (e.hasMoreElements()) + { + String name = (String)e.nextElement(); + Object value = _scontext.getAttribute(name); + checkManagedAttribute(name,value); + } + } + + super.doStart(); + + if (_errorHandler != null) + _errorHandler.start(); + + // Context listeners + if (_contextListeners != null) + { + ServletContextEvent event = new ServletContextEvent(_scontext); + for (int i = 0; i < LazyList.size(_contextListeners); i++) + { + callContextInitialized(((ServletContextListener)LazyList.get(_contextListeners, i)), event); + } + } + } + + /* ------------------------------------------------------------ */ + public void callContextInitialized (ServletContextListener l, ServletContextEvent e) + { + l.contextInitialized(e); + } + + /* ------------------------------------------------------------ */ + public void callContextDestroyed (ServletContextListener l, ServletContextEvent e) + { + l.contextDestroyed(e); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.thread.AbstractLifeCycle#doStop() + */ + @Override + protected void doStop() throws Exception + { + _availability = __STOPPED; + + ClassLoader old_classloader = null; + Thread current_thread = null; + + Context old_context = __context.get(); + __context.set(_scontext); + try + { + // Set the classloader + if (_classLoader != null) + { + current_thread = Thread.currentThread(); + old_classloader = current_thread.getContextClassLoader(); + current_thread.setContextClassLoader(_classLoader); + } + + super.doStop(); + + // Context listeners + if (_contextListeners != null) + { + ServletContextEvent event = new ServletContextEvent(_scontext); + for (int i = LazyList.size(_contextListeners); i-- > 0;) + { + ((ServletContextListener)LazyList.get(_contextListeners,i)).contextDestroyed(event); + } + } + + //remove all non-durable listeners + setEventListeners((EventListener[])LazyList.toArray(_durableListeners, EventListener.class)); + _durableListeners = null; + + if (_errorHandler != null) + _errorHandler.stop(); + + Enumeration e = _scontext.getAttributeNames(); + while (e.hasMoreElements()) + { + String name = (String)e.nextElement(); + checkManagedAttribute(name,null); + } + } + finally + { + LOG.info("stopped {}",this); + __context.set(old_context); + // reset the classloader + if (_classLoader != null) + current_thread.setContextClassLoader(old_classloader); + } + + _contextAttributes.clearAttributes(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + public boolean checkContext(final String target, final Request baseRequest, final HttpServletResponse response) throws IOException, ServletException + { + DispatcherType dispatch = baseRequest.getDispatcherType(); + + switch (_availability) + { + case __STOPPED: + case __SHUTDOWN: + return false; + case __UNAVAILABLE: + baseRequest.setHandled(true); + response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); + return false; + default: + if ((DispatcherType.REQUEST.equals(dispatch) && baseRequest.isHandled())) + return false; + } + + // Check the vhosts + if (_vhosts != null && _vhosts.length > 0) + { + String vhost = normalizeHostname(baseRequest.getServerName()); + + boolean match = false; + + // TODO non-linear lookup + for (int i = 0; !match && i < _vhosts.length; i++) + { + String contextVhost = _vhosts[i]; + if (contextVhost == null) + continue; + if (contextVhost.startsWith("*.")) + { + // wildcard only at the beginning, and only for one additional subdomain level + match = contextVhost.regionMatches(true,2,vhost,vhost.indexOf(".") + 1,contextVhost.length() - 2); + } + else + match = contextVhost.equalsIgnoreCase(vhost); + } + if (!match) + return false; + } + + // Check the connector + if (_connectors != null && _connectors.size() > 0) + { + String connector = AbstractHttpConnection.getCurrentConnection().getConnector().getName(); + if (connector == null || !_connectors.contains(connector)) + return false; + } + + // Are we not the root context? + if (_contextPath.length() > 1) + { + // reject requests that are not for us + if (!target.startsWith(_contextPath)) + return false; + if (target.length() > _contextPath.length() && target.charAt(_contextPath.length()) != '/') + return false; + + // redirect null path infos + if (!_allowNullPathInfo && _contextPath.length() == target.length()) + { + // context request must end with / + baseRequest.setHandled(true); + if (baseRequest.getQueryString() != null) + response.sendRedirect(URIUtil.addPaths(baseRequest.getRequestURI(),URIUtil.SLASH) + "?" + baseRequest.getQueryString()); + else + response.sendRedirect(URIUtil.addPaths(baseRequest.getRequestURI(),URIUtil.SLASH)); + return false; + } + } + + return true; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.handler.ScopedHandler#doScope(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest, + * javax.servlet.http.HttpServletResponse) + */ + @Override + public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + if (LOG.isDebugEnabled()) + LOG.debug("scope {}|{}|{} @ {}",baseRequest.getContextPath(),baseRequest.getServletPath(),baseRequest.getPathInfo(),this); + + Context old_context = null; + String old_context_path = null; + String old_servlet_path = null; + String old_path_info = null; + ClassLoader old_classloader = null; + Thread current_thread = null; + String pathInfo = target; + + DispatcherType dispatch = baseRequest.getDispatcherType(); + + old_context = baseRequest.getContext(); + + // Are we already in this context? + if (old_context != _scontext) + { + // check the target. + if (DispatcherType.REQUEST.equals(dispatch) || DispatcherType.ASYNC.equals(dispatch) || (DispatcherType.ERROR.equals(dispatch) && baseRequest.getAsyncContinuation().isExpired())) + { + if (_compactPath) + target = URIUtil.compactPath(target); + if (!checkContext(target,baseRequest,response)) + return; + + if (target.length() > _contextPath.length()) + { + if (_contextPath.length() > 1) + target = target.substring(_contextPath.length()); + pathInfo = target; + } + else if (_contextPath.length() == 1) + { + target = URIUtil.SLASH; + pathInfo = URIUtil.SLASH; + } + else + { + target = URIUtil.SLASH; + pathInfo = null; + } + } + + // Set the classloader + if (_classLoader != null) + { + current_thread = Thread.currentThread(); + old_classloader = current_thread.getContextClassLoader(); + current_thread.setContextClassLoader(_classLoader); + } + } + + try + { + old_context_path = baseRequest.getContextPath(); + old_servlet_path = baseRequest.getServletPath(); + old_path_info = baseRequest.getPathInfo(); + + // Update the paths + baseRequest.setContext(_scontext); + __context.set(_scontext); + if (!DispatcherType.INCLUDE.equals(dispatch) && target.startsWith("/")) + { + if (_contextPath.length() == 1) + baseRequest.setContextPath(""); + else + baseRequest.setContextPath(_contextPath); + baseRequest.setServletPath(null); + baseRequest.setPathInfo(pathInfo); + } + + if (LOG.isDebugEnabled()) + LOG.debug("context={}|{}|{} @ {}",baseRequest.getContextPath(),baseRequest.getServletPath(), baseRequest.getPathInfo(),this); + + // start manual inline of nextScope(target,baseRequest,request,response); + if (never()) + nextScope(target,baseRequest,request,response); + else if (_nextScope != null) + _nextScope.doScope(target,baseRequest,request,response); + else if (_outerScope != null) + _outerScope.doHandle(target,baseRequest,request,response); + else + doHandle(target,baseRequest,request,response); + // end manual inline (pathentic attempt to reduce stack depth) + } + finally + { + if (old_context != _scontext) + { + // reset the classloader + if (_classLoader != null) + { + current_thread.setContextClassLoader(old_classloader); + } + + // reset the context and servlet path. + baseRequest.setContext(old_context); + __context.set(old_context); + baseRequest.setContextPath(old_context_path); + baseRequest.setServletPath(old_servlet_path); + baseRequest.setPathInfo(old_path_info); + } + } + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.handler.ScopedHandler#doHandle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest, + * javax.servlet.http.HttpServletResponse) + */ + @Override + public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + final DispatcherType dispatch = baseRequest.getDispatcherType(); + final boolean new_context = baseRequest.takeNewContext(); + try + { + if (new_context) + { + // Handle the REALLY SILLY request events! + if (_requestAttributeListeners != null) + { + final int s = LazyList.size(_requestAttributeListeners); + for (int i = 0; i < s; i++) + baseRequest.addEventListener(((EventListener)LazyList.get(_requestAttributeListeners,i))); + } + + if (_requestListeners != null) + { + final int s = LazyList.size(_requestListeners); + final ServletRequestEvent sre = new ServletRequestEvent(_scontext,request); + for (int i = 0; i < s; i++) + ((ServletRequestListener)LazyList.get(_requestListeners,i)).requestInitialized(sre); + } + } + + if (DispatcherType.REQUEST.equals(dispatch) && isProtectedTarget(target)) + throw new HttpException(HttpServletResponse.SC_NOT_FOUND); + + // start manual inline of nextHandle(target,baseRequest,request,response); + // noinspection ConstantIfStatement + if (never()) + nextHandle(target,baseRequest,request,response); + else if (_nextScope != null && _nextScope == _handler) + _nextScope.doHandle(target,baseRequest,request,response); + else if (_handler != null) + _handler.handle(target,baseRequest,request,response); + // end manual inline + } + catch (HttpException e) + { + LOG.debug(e); + baseRequest.setHandled(true); + response.sendError(e.getStatus(),e.getReason()); + } + finally + { + // Handle more REALLY SILLY request events! + if (new_context) + { + if (_requestListeners != null) + { + final ServletRequestEvent sre = new ServletRequestEvent(_scontext,request); + for (int i = LazyList.size(_requestListeners); i-- > 0;) + ((ServletRequestListener)LazyList.get(_requestListeners,i)).requestDestroyed(sre); + } + + if (_requestAttributeListeners != null) + { + for (int i = LazyList.size(_requestAttributeListeners); i-- > 0;) + baseRequest.removeEventListener(((EventListener)LazyList.get(_requestAttributeListeners,i))); + } + } + } + } + + /* ------------------------------------------------------------ */ + /* + * Handle a runnable in this context + */ + public void handle(Runnable runnable) + { + ClassLoader old_classloader = null; + Thread current_thread = null; + Context old_context = null; + try + { + old_context = __context.get(); + __context.set(_scontext); + + // Set the classloader + if (_classLoader != null) + { + current_thread = Thread.currentThread(); + old_classloader = current_thread.getContextClassLoader(); + current_thread.setContextClassLoader(_classLoader); + } + + runnable.run(); + } + finally + { + __context.set(old_context); + if (old_classloader != null) + { + current_thread.setContextClassLoader(old_classloader); + } + } + } + + /* ------------------------------------------------------------ */ + /** + * Check the target. Called by {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)} when a target within a context is determined. If + * the target is protected, 404 is returned. + */ + /* ------------------------------------------------------------ */ + public boolean isProtectedTarget(String target) + { + if (target == null || _protectedTargets == null) + return false; + + while (target.startsWith("//")) + target=URIUtil.compactPath(target); + + boolean isProtected = false; + int i=0; + while (!isProtected && i<_protectedTargets.length) + { + isProtected = StringUtil.startsWithIgnoreCase(target, _protectedTargets[i++]); + } + return isProtected; + } + + + public void setProtectedTargets (String[] targets) + { + if (targets == null) + { + _protectedTargets = null; + return; + } + + _protectedTargets = new String[targets.length]; + System.arraycopy(targets, 0, _protectedTargets, 0, targets.length); + } + + public String[] getProtectedTargets () + { + if (_protectedTargets == null) + return null; + + String[] tmp = new String[_protectedTargets.length]; + System.arraycopy(_protectedTargets, 0, tmp, 0, _protectedTargets.length); + return tmp; + } + + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#removeAttribute(java.lang.String) + */ + public void removeAttribute(String name) + { + checkManagedAttribute(name,null); + _attributes.removeAttribute(name); + } + + /* ------------------------------------------------------------ */ + /* + * Set a context attribute. Attributes set via this API cannot be overriden by the ServletContext.setAttribute API. Their lifecycle spans the stop/start of + * a context. No attribute listener events are triggered by this API. + * + * @see javax.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object) + */ + public void setAttribute(String name, Object value) + { + checkManagedAttribute(name,value); + _attributes.setAttribute(name,value); + } + + /* ------------------------------------------------------------ */ + /** + * @param attributes + * The attributes to set. + */ + public void setAttributes(Attributes attributes) + { + _attributes.clearAttributes(); + _attributes.addAll(attributes); + Enumeration e = _attributes.getAttributeNames(); + while (e.hasMoreElements()) + { + String name = (String)e.nextElement(); + checkManagedAttribute(name,attributes.getAttribute(name)); + } + } + + /* ------------------------------------------------------------ */ + public void clearAttributes() + { + Enumeration e = _attributes.getAttributeNames(); + while (e.hasMoreElements()) + { + String name = (String)e.nextElement(); + checkManagedAttribute(name,null); + } + _attributes.clearAttributes(); + } + + /* ------------------------------------------------------------ */ + public void checkManagedAttribute(String name, Object value) + { + if (_managedAttributes != null && _managedAttributes.containsKey(name)) + { + setManagedAttribute(name,value); + } + } + + /* ------------------------------------------------------------ */ + public void setManagedAttribute(String name, Object value) + { + Object old = _managedAttributes.put(name,value); + getServer().getContainer().update(this,old,value,name,true); + } + + /* ------------------------------------------------------------ */ + /** + * @param classLoader + * The classLoader to set. + */ + public void setClassLoader(ClassLoader classLoader) + { + _classLoader = classLoader; + } + + /* ------------------------------------------------------------ */ + /** + * @param contextPath + * The _contextPath to set. + */ + public void setContextPath(String contextPath) + { + if (contextPath != null && contextPath.length() > 1 && contextPath.endsWith("/")) + throw new IllegalArgumentException("ends with /"); + _contextPath = contextPath; + + if (getServer() != null && (getServer().isStarting() || getServer().isStarted())) + { + Handler[] contextCollections = getServer().getChildHandlersByClass(ContextHandlerCollection.class); + for (int h = 0; contextCollections != null && h < contextCollections.length; h++) + ((ContextHandlerCollection)contextCollections[h]).mapContexts(); + } + } + + /* ------------------------------------------------------------ */ + /** + * @param servletContextName + * The servletContextName to set. + */ + public void setDisplayName(String servletContextName) + { + _displayName = servletContextName; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the resourceBase. + */ + public Resource getBaseResource() + { + if (_baseResource == null) + return null; + return _baseResource; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the base resource as a string. + */ + public String getResourceBase() + { + if (_baseResource == null) + return null; + return _baseResource.toString(); + } + + /* ------------------------------------------------------------ */ + /** + * @param base + * The resourceBase to set. + */ + public void setBaseResource(Resource base) + { + _baseResource = base; + } + + /* ------------------------------------------------------------ */ + /** + * @param resourceBase + * The base resource as a string. + */ + public void setResourceBase(String resourceBase) + { + try + { + setBaseResource(newResource(resourceBase)); + } + catch (Exception e) + { + LOG.warn(e.toString()); + LOG.debug(e); + throw new IllegalArgumentException(resourceBase); + } + } + + /* ------------------------------------------------------------ */ + /** + * @return True if aliases are allowed + */ + public boolean isAliases() + { + return _aliasesAllowed; + } + + /* ------------------------------------------------------------ */ + /** + * @param aliases + * aliases are allowed + */ + public void setAliases(boolean aliases) + { + _aliasesAllowed = aliases; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the mimeTypes. + */ + public MimeTypes getMimeTypes() + { + if (_mimeTypes == null) + _mimeTypes = new MimeTypes(); + return _mimeTypes; + } + + /* ------------------------------------------------------------ */ + /** + * @param mimeTypes + * The mimeTypes to set. + */ + public void setMimeTypes(MimeTypes mimeTypes) + { + _mimeTypes = mimeTypes; + } + + /* ------------------------------------------------------------ */ + /** + */ + public void setWelcomeFiles(String[] files) + { + _welcomeFiles = files; + } + + /* ------------------------------------------------------------ */ + /** + * @return The names of the files which the server should consider to be welcome files in this context. + * @see <a href="http://jcp.org/aboutJava/communityprocess/final/jsr154/index.html">The Servlet Specification</a> + * @see #setWelcomeFiles + */ + public String[] getWelcomeFiles() + { + return _welcomeFiles; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the errorHandler. + */ + public ErrorHandler getErrorHandler() + { + return _errorHandler; + } + + /* ------------------------------------------------------------ */ + /** + * @param errorHandler + * The errorHandler to set. + */ + public void setErrorHandler(ErrorHandler errorHandler) + { + if (errorHandler != null) + errorHandler.setServer(getServer()); + if (getServer() != null) + getServer().getContainer().update(this,_errorHandler,errorHandler,"errorHandler",true); + _errorHandler = errorHandler; + } + + /* ------------------------------------------------------------ */ + public int getMaxFormContentSize() + { + return _maxFormContentSize; + } + + /* ------------------------------------------------------------ */ + /** + * Set the maximum size of a form post, to protect against DOS attacks from large forms. + * @param maxSize + */ + public void setMaxFormContentSize(int maxSize) + { + _maxFormContentSize = maxSize; + } + + /* ------------------------------------------------------------ */ + public int getMaxFormKeys() + { + return _maxFormKeys; + } + + /* ------------------------------------------------------------ */ + /** + * Set the maximum number of form Keys to protect against DOS attack from crafted hash keys. + * @param max + */ + public void setMaxFormKeys(int max) + { + _maxFormKeys = max; + } + + /* ------------------------------------------------------------ */ + /** + * @return True if URLs are compacted to replace multiple '/'s with a single '/' + */ + public boolean isCompactPath() + { + return _compactPath; + } + + /* ------------------------------------------------------------ */ + /** + * @param compactPath + * True if URLs are compacted to replace multiple '/'s with a single '/' + */ + public void setCompactPath(boolean compactPath) + { + _compactPath = compactPath; + } + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + String[] vhosts = getVirtualHosts(); + + StringBuilder b = new StringBuilder(); + + Package pkg = getClass().getPackage(); + if (pkg != null) + { + String p = pkg.getName(); + if (p != null && p.length() > 0) + { + String[] ss = p.split("\\."); + for (String s : ss) + b.append(s.charAt(0)).append('.'); + } + } + b.append(getClass().getSimpleName()); + b.append('{').append(getContextPath()).append(',').append(getBaseResource()); + + if (vhosts != null && vhosts.length > 0) + b.append(',').append(vhosts[0]); + b.append('}'); + + return b.toString(); + } + + /* ------------------------------------------------------------ */ + public synchronized Class<?> loadClass(String className) throws ClassNotFoundException + { + if (className == null) + return null; + + if (_classLoader == null) + return Loader.loadClass(this.getClass(),className); + + return _classLoader.loadClass(className); + } + + /* ------------------------------------------------------------ */ + public void addLocaleEncoding(String locale, String encoding) + { + if (_localeEncodingMap == null) + _localeEncodingMap = new HashMap<String, String>(); + _localeEncodingMap.put(locale,encoding); + } + + /* ------------------------------------------------------------ */ + public String getLocaleEncoding(String locale) + { + if (_localeEncodingMap == null) + return null; + String encoding = _localeEncodingMap.get(locale); + return encoding; + } + + /* ------------------------------------------------------------ */ + /** + * Get the character encoding for a locale. The full locale name is first looked up in the map of encodings. If no encoding is found, then the locale + * language is looked up. + * + * @param locale + * a <code>Locale</code> value + * @return a <code>String</code> representing the character encoding for the locale or null if none found. + */ + public String getLocaleEncoding(Locale locale) + { + if (_localeEncodingMap == null) + return null; + String encoding = _localeEncodingMap.get(locale.toString()); + if (encoding == null) + encoding = _localeEncodingMap.get(locale.getLanguage()); + return encoding; + } + + /* ------------------------------------------------------------ */ + /* + */ + public Resource getResource(String path) throws MalformedURLException + { + if (path == null || !path.startsWith(URIUtil.SLASH)) + throw new MalformedURLException(path); + + if (_baseResource == null) + return null; + + try + { + path = URIUtil.canonicalPath(path); + Resource resource = _baseResource.addPath(path); + + if (checkAlias(path,resource)) + return resource; + return null; + } + catch (Exception e) + { + LOG.ignore(e); + } + + return null; + } + + /* ------------------------------------------------------------ */ + public boolean checkAlias(String path, Resource resource) + { + // Is the resource aliased? + if (!_aliasesAllowed && resource.getAlias() != null) + { + if (LOG.isDebugEnabled()) + LOG.debug("Aliased resource: " + resource + "~=" + resource.getAlias()); + + // alias checks + for (Iterator<AliasCheck> i=_aliasChecks.iterator();i.hasNext();) + { + AliasCheck check = i.next(); + if (check.check(path,resource)) + { + if (LOG.isDebugEnabled()) + LOG.debug("Aliased resource: " + resource + " approved by " + check); + return true; + } + } + return false; + } + return true; + } + + /* ------------------------------------------------------------ */ + /** + * Convert URL to Resource wrapper for {@link Resource#newResource(URL)} enables extensions to provide alternate resource implementations. + */ + public Resource newResource(URL url) throws IOException + { + return Resource.newResource(url); + } + + /* ------------------------------------------------------------ */ + /** + * Convert a URL or path to a Resource. The default implementation is a wrapper for {@link Resource#newResource(String)}. + * + * @param urlOrPath + * The URL or path to convert + * @return The Resource for the URL/path + * @throws IOException + * The Resource could not be created. + */ + public Resource newResource(String urlOrPath) throws IOException + { + return Resource.newResource(urlOrPath); + } + + /* ------------------------------------------------------------ */ + /* + */ + public Set<String> getResourcePaths(String path) + { + try + { + path = URIUtil.canonicalPath(path); + Resource resource = getResource(path); + + if (resource != null && resource.exists()) + { + if (!path.endsWith(URIUtil.SLASH)) + path = path + URIUtil.SLASH; + + String[] l = resource.list(); + if (l != null) + { + HashSet<String> set = new HashSet<String>(); + for (int i = 0; i < l.length; i++) + set.add(path + l[i]); + return set; + } + } + } + catch (Exception e) + { + LOG.ignore(e); + } + return Collections.emptySet(); + } + + /* ------------------------------------------------------------ */ + private String normalizeHostname(String host) + { + if (host == null) + return null; + + if (host.endsWith(".")) + return host.substring(0,host.length() - 1); + + return host; + } + + /* ------------------------------------------------------------ */ + /** + * Add an AliasCheck instance to possibly permit aliased resources + * @param check The alias checker + */ + public void addAliasCheck(AliasCheck check) + { + _aliasChecks.add(check); + } + + /* ------------------------------------------------------------ */ + /** + * @return Mutable list of Alias checks + */ + public List<AliasCheck> getAliasChecks() + { + return _aliasChecks; + } + + /* ------------------------------------------------------------ */ + /** + * Context. + * <p> + * A partial implementation of {@link javax.servlet.ServletContext}. A complete implementation is provided by the derived {@link ContextHandler}. + * </p> + * + * + */ + public class Context implements ServletContext + { + protected int _majorVersion = 3; + protected int _minorVersion = 0; + protected boolean _enabled = true; //whether or not the dynamic API is enabled for callers + + /* ------------------------------------------------------------ */ + protected Context() + { + } + + /* ------------------------------------------------------------ */ + public ContextHandler getContextHandler() + { + // TODO reduce visibility of this method + return ContextHandler.this; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getContext(java.lang.String) + */ + @Override + public ServletContext getContext(String uripath) + { + List<ContextHandler> contexts = new ArrayList<ContextHandler>(); + Handler[] handlers = getServer().getChildHandlersByClass(ContextHandler.class); + String matched_path = null; + + for (Handler handler : handlers) + { + if (handler == null) + continue; + ContextHandler ch = (ContextHandler)handler; + String context_path = ch.getContextPath(); + + if (uripath.equals(context_path) || (uripath.startsWith(context_path) && uripath.charAt(context_path.length()) == '/') + || "/".equals(context_path)) + { + // look first for vhost matching context only + if (getVirtualHosts() != null && getVirtualHosts().length > 0) + { + if (ch.getVirtualHosts() != null && ch.getVirtualHosts().length > 0) + { + for (String h1 : getVirtualHosts()) + for (String h2 : ch.getVirtualHosts()) + if (h1.equals(h2)) + { + if (matched_path == null || context_path.length() > matched_path.length()) + { + contexts.clear(); + matched_path = context_path; + } + + if (matched_path.equals(context_path)) + contexts.add(ch); + } + } + } + else + { + if (matched_path == null || context_path.length() > matched_path.length()) + { + contexts.clear(); + matched_path = context_path; + } + + if (matched_path.equals(context_path)) + contexts.add(ch); + } + } + } + + if (contexts.size() > 0) + return contexts.get(0)._scontext; + + // try again ignoring virtual hosts + matched_path = null; + for (Handler handler : handlers) + { + if (handler == null) + continue; + ContextHandler ch = (ContextHandler)handler; + String context_path = ch.getContextPath(); + + if (uripath.equals(context_path) || (uripath.startsWith(context_path) && uripath.charAt(context_path.length()) == '/') + || "/".equals(context_path)) + { + if (matched_path == null || context_path.length() > matched_path.length()) + { + contexts.clear(); + matched_path = context_path; + } + + if (matched_path.equals(context_path)) + contexts.add(ch); + } + } + + if (contexts.size() > 0) + return contexts.get(0)._scontext; + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getMajorVersion() + */ + @Override + public int getMajorVersion() + { + return 3; + } + + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getMimeType(java.lang.String) + */ + @Override + public String getMimeType(String file) + { + if (_mimeTypes == null) + return null; + Buffer mime = _mimeTypes.getMimeByExtension(file); + if (mime != null) + return mime.toString(); + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getMinorVersion() + */ + @Override + public int getMinorVersion() + { + return 0; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getNamedDispatcher(java.lang.String) + */ + @Override + public RequestDispatcher getNamedDispatcher(String name) + { + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getRequestDispatcher(java.lang.String) + */ + @Override + public RequestDispatcher getRequestDispatcher(String uriInContext) + { + if (uriInContext == null) + return null; + + if (!uriInContext.startsWith("/")) + return null; + + try + { + String query = null; + int q = 0; + if ((q = uriInContext.indexOf('?')) > 0) + { + query = uriInContext.substring(q + 1); + uriInContext = uriInContext.substring(0,q); + } + + String pathInContext = URIUtil.canonicalPath(URIUtil.decodePath(uriInContext)); + if (pathInContext!=null) + { + String uri = URIUtil.addPaths(getContextPath(),uriInContext); + ContextHandler context = ContextHandler.this; + return new Dispatcher(context,uri,pathInContext,query); + } + } + catch (Exception e) + { + LOG.ignore(e); + } + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getRealPath(java.lang.String) + */ + @Override + public String getRealPath(String path) + { + if (path == null) + return null; + if (path.length() == 0) + path = URIUtil.SLASH; + else if (path.charAt(0) != '/') + path = URIUtil.SLASH + path; + + try + { + Resource resource = ContextHandler.this.getResource(path); + if (resource != null) + { + File file = resource.getFile(); + if (file != null) + return file.getCanonicalPath(); + } + } + catch (Exception e) + { + LOG.ignore(e); + } + + return null; + } + + /* ------------------------------------------------------------ */ + @Override + public URL getResource(String path) throws MalformedURLException + { + Resource resource = ContextHandler.this.getResource(path); + if (resource != null && resource.exists()) + return resource.getURL(); + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getResourceAsStream(java.lang.String) + */ + @Override + public InputStream getResourceAsStream(String path) + { + try + { + URL url = getResource(path); + if (url == null) + return null; + Resource r = Resource.newResource(url); + return r.getInputStream(); + } + catch (Exception e) + { + LOG.ignore(e); + return null; + } + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getResourcePaths(java.lang.String) + */ + @Override + public Set getResourcePaths(String path) + { + return ContextHandler.this.getResourcePaths(path); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getServerInfo() + */ + @Override + public String getServerInfo() + { + return "jetty/" + Server.getVersion(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getServlet(java.lang.String) + */ + @Override + @Deprecated + public Servlet getServlet(String name) throws ServletException + { + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getServletNames() + */ + @SuppressWarnings("unchecked") + @Override + @Deprecated + public Enumeration getServletNames() + { + return Collections.enumeration(Collections.EMPTY_LIST); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getServlets() + */ + @SuppressWarnings("unchecked") + @Override + @Deprecated + public Enumeration getServlets() + { + return Collections.enumeration(Collections.EMPTY_LIST); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#log(java.lang.Exception, java.lang.String) + */ + @Override + public void log(Exception exception, String msg) + { + _logger.warn(msg,exception); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#log(java.lang.String) + */ + @Override + public void log(String msg) + { + _logger.info(msg); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#log(java.lang.String, java.lang.Throwable) + */ + @Override + public void log(String message, Throwable throwable) + { + _logger.warn(message,throwable); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getInitParameter(java.lang.String) + */ + @Override + public String getInitParameter(String name) + { + return ContextHandler.this.getInitParameter(name); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getInitParameterNames() + */ + @SuppressWarnings("unchecked") + @Override + public Enumeration getInitParameterNames() + { + return ContextHandler.this.getInitParameterNames(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getAttribute(java.lang.String) + */ + @Override + public synchronized Object getAttribute(String name) + { + Object o = ContextHandler.this.getAttribute(name); + if (o == null && _contextAttributes != null) + o = _contextAttributes.getAttribute(name); + return o; + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getAttributeNames() + */ + @SuppressWarnings("unchecked") + @Override + public synchronized Enumeration getAttributeNames() + { + HashSet<String> set = new HashSet<String>(); + if (_contextAttributes != null) + { + Enumeration<String> e = _contextAttributes.getAttributeNames(); + while (e.hasMoreElements()) + set.add(e.nextElement()); + } + Enumeration<String> e = _attributes.getAttributeNames(); + while (e.hasMoreElements()) + set.add(e.nextElement()); + + return Collections.enumeration(set); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object) + */ + @Override + public synchronized void setAttribute(String name, Object value) + { + checkManagedAttribute(name,value); + Object old_value = _contextAttributes.getAttribute(name); + + if (value == null) + _contextAttributes.removeAttribute(name); + else + _contextAttributes.setAttribute(name,value); + + if (_contextAttributeListeners != null) + { + ServletContextAttributeEvent event = new ServletContextAttributeEvent(_scontext,name,old_value == null?value:old_value); + + for (int i = 0; i < LazyList.size(_contextAttributeListeners); i++) + { + ServletContextAttributeListener l = (ServletContextAttributeListener)LazyList.get(_contextAttributeListeners,i); + + if (old_value == null) + l.attributeAdded(event); + else if (value == null) + l.attributeRemoved(event); + else + l.attributeReplaced(event); + } + } + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#removeAttribute(java.lang.String) + */ + @Override + public synchronized void removeAttribute(String name) + { + checkManagedAttribute(name,null); + + if (_contextAttributes == null) + { + // Set it on the handler + _attributes.removeAttribute(name); + return; + } + + Object old_value = _contextAttributes.getAttribute(name); + _contextAttributes.removeAttribute(name); + if (old_value != null) + { + if (_contextAttributeListeners != null) + { + ServletContextAttributeEvent event = new ServletContextAttributeEvent(_scontext,name,old_value); + + for (int i = 0; i < LazyList.size(_contextAttributeListeners); i++) + ((ServletContextAttributeListener)LazyList.get(_contextAttributeListeners,i)).attributeRemoved(event); + } + } + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getServletContextName() + */ + @Override + public String getServletContextName() + { + String name = ContextHandler.this.getDisplayName(); + if (name == null) + name = ContextHandler.this.getContextPath(); + return name; + } + + /* ------------------------------------------------------------ */ + @Override + public String getContextPath() + { + if ((_contextPath != null) && _contextPath.equals(URIUtil.SLASH)) + return ""; + + return _contextPath; + } + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + return "ServletContext@" + ContextHandler.this.toString(); + } + + /* ------------------------------------------------------------ */ + @Override + public boolean setInitParameter(String name, String value) + { + if (ContextHandler.this.getInitParameter(name) != null) + return false; + ContextHandler.this.getInitParams().put(name,value); + return true; + } + + /* ------------------------------------------------------------ */ + final private static String __unimplmented="Unimplemented - use org.eclipse.jetty.servlet.ServletContextHandler"; + + @Override + public Dynamic addFilter(String filterName, Class<? extends Filter> filterClass) + { + LOG.warn(__unimplmented); + return null; + } + + @Override + public Dynamic addFilter(String filterName, Filter filter) + { + LOG.warn(__unimplmented); + return null; + } + + @Override + public Dynamic addFilter(String filterName, String className) + { + LOG.warn(__unimplmented); + return null; + } + + @Override + public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, Class<? extends Servlet> servletClass) + { + LOG.warn(__unimplmented); + return null; + } + + @Override + public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet) + { + LOG.warn(__unimplmented); + return null; + } + + @Override + public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, String className) + { + LOG.warn(__unimplmented); + return null; + } + + @Override + public <T extends Filter> T createFilter(Class<T> c) throws ServletException + { + LOG.warn(__unimplmented); + return null; + } + + @Override + public <T extends Servlet> T createServlet(Class<T> c) throws ServletException + { + LOG.warn(__unimplmented); + return null; + } + + @Override + public Set<SessionTrackingMode> getDefaultSessionTrackingModes() + { + LOG.warn(__unimplmented); + return null; + } + + @Override + public Set<SessionTrackingMode> getEffectiveSessionTrackingModes() + { + LOG.warn(__unimplmented); + return null; + } + + @Override + public FilterRegistration getFilterRegistration(String filterName) + { + LOG.warn(__unimplmented); + return null; + } + + @Override + public Map<String, ? extends FilterRegistration> getFilterRegistrations() + { + LOG.warn(__unimplmented); + return null; + } + + @Override + public ServletRegistration getServletRegistration(String servletName) + { + LOG.warn(__unimplmented); + return null; + } + + @Override + public Map<String, ? extends ServletRegistration> getServletRegistrations() + { + LOG.warn(__unimplmented); + return null; + } + + @Override + public SessionCookieConfig getSessionCookieConfig() + { + LOG.warn(__unimplmented); + return null; + } + + @Override + public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes) + { + LOG.warn(__unimplmented); + } + + @Override + public void addListener(String className) + { + if (!_enabled) + throw new UnsupportedOperationException(); + + try + { +// Class<? extends EventListener> clazz = _classLoader==null?Loader.loadClass(ContextHandler.class,className):_classLoader.loadClass(className); + Class clazz = _classLoader==null?Loader.loadClass(ContextHandler.class,className):_classLoader.loadClass(className); + addListener(clazz); + } + catch (ClassNotFoundException e) + { + throw new IllegalArgumentException(e); + } + } + + @Override + public <T extends EventListener> void addListener(T t) + { + if (!_enabled) + throw new UnsupportedOperationException(); + ContextHandler.this.addEventListener(t); + ContextHandler.this.restrictEventListener(t); + } + + @Override + public void addListener(Class<? extends EventListener> listenerClass) + { + if (!_enabled) + throw new UnsupportedOperationException(); + + try + { + EventListener e = createListener(listenerClass); + ContextHandler.this.addEventListener(e); + ContextHandler.this.restrictEventListener(e); + } + catch (ServletException e) + { + throw new IllegalArgumentException(e); + } + } + + @Override + public <T extends EventListener> T createListener(Class<T> clazz) throws ServletException + { + try + { + return clazz.newInstance(); + } + catch (InstantiationException e) + { + throw new ServletException(e); + } + catch (IllegalAccessException e) + { + throw new ServletException(e); + } + } + + @Override + public ClassLoader getClassLoader() + { + AccessController.checkPermission(new RuntimePermission("getClassLoader")); + return _classLoader; + } + + @Override + public int getEffectiveMajorVersion() + { + return _majorVersion; + } + + @Override + public int getEffectiveMinorVersion() + { + return _minorVersion; + } + + public void setEffectiveMajorVersion (int v) + { + _majorVersion = v; + } + + public void setEffectiveMinorVersion (int v) + { + _minorVersion = v; + } + + @Override + public JspConfigDescriptor getJspConfigDescriptor() + { + LOG.warn(__unimplmented); + return null; + } + + public void setJspConfigDescriptor(JspConfigDescriptor d) + { + + } + + @Override + public void declareRoles(String... roleNames) + { + if (!isStarting()) + throw new IllegalStateException (); + if (!_enabled) + throw new UnsupportedOperationException(); + + // TODO Auto-generated method stub + + } + + public void setEnabled(boolean enabled) + { + _enabled = enabled; + } + + public boolean isEnabled() + { + return _enabled; + } + } + + private static class CLDump implements Dumpable + { + final ClassLoader _loader; + + CLDump(ClassLoader loader) + { + _loader = loader; + } + + public String dump() + { + return AggregateLifeCycle.dump(this); + } + + public void dump(Appendable out, String indent) throws IOException + { + out.append(String.valueOf(_loader)).append("\n"); + + if (_loader != null) + { + Object parent = _loader.getParent(); + if (parent != null) + { + if (!(parent instanceof Dumpable)) + parent = new CLDump((ClassLoader)parent); + + if (_loader instanceof URLClassLoader) + AggregateLifeCycle.dump(out,indent,TypeUtil.asList(((URLClassLoader)_loader).getURLs()),Collections.singleton(parent)); + else + AggregateLifeCycle.dump(out,indent,Collections.singleton(parent)); + } + } + } + } + + + /* ------------------------------------------------------------ */ + /** Interface to check aliases + */ + public interface AliasCheck + { + /* ------------------------------------------------------------ */ + /** Check an alias + * @param path The path the aliased resource was created for + * @param resource The aliased resourced + * @return True if the resource is OK to be served. + */ + boolean check(String path, Resource resource); + } + + + /* ------------------------------------------------------------ */ + /** Approve Aliases with same suffix. + * Eg. a symbolic link from /foobar.html to /somewhere/wibble.html would be + * approved because both the resource and alias end with ".html". + */ + @Deprecated + public static class ApproveSameSuffixAliases implements AliasCheck + { + { + LOG.warn("ApproveSameSuffixAlias is not safe for production"); + } + + public boolean check(String path, Resource resource) + { + int dot = path.lastIndexOf('.'); + if (dot<0) + return false; + String suffix=path.substring(dot); + return resource.toString().endsWith(suffix); + } + } + + + /* ------------------------------------------------------------ */ + /** Approve Aliases with a path prefix. + * Eg. a symbolic link from /dirA/foobar.html to /dirB/foobar.html would be + * approved because both the resource and alias end with "/foobar.html". + */ + @Deprecated + public static class ApprovePathPrefixAliases implements AliasCheck + { + { + LOG.warn("ApprovePathPrefixAliases is not safe for production"); + } + + public boolean check(String path, Resource resource) + { + int slash = path.lastIndexOf('/'); + if (slash<0 || slash==path.length()-1) + return false; + String suffix=path.substring(slash); + return resource.toString().endsWith(suffix); + } + } + + /* ------------------------------------------------------------ */ + /** Approve Aliases of a non existent directory. + * If a directory "/foobar/" does not exist, then the resource is + * aliased to "/foobar". Accept such aliases. + */ + public static class ApproveNonExistentDirectoryAliases implements AliasCheck + { + public boolean check(String path, Resource resource) + { + if (resource.exists()) + return false; + + String a=resource.getAlias().toString(); + String r=resource.getURL().toString(); + + if (a.length()>r.length()) + return a.startsWith(r) && a.length()==r.length()+1 && a.endsWith("/"); + else + return r.startsWith(a) && r.length()==a.length()+1 && r.endsWith("/"); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/handler/ContextHandlerCollection.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,332 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.PathMap; +import org.eclipse.jetty.server.AsyncContinuation; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HandlerContainer; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/* ------------------------------------------------------------ */ +/** ContextHandlerCollection. + * + * This {@link org.eclipse.jetty.server.handler.HandlerCollection} is creates a + * {@link org.eclipse.jetty.http.PathMap} to it's contained handlers based + * on the context path and virtual hosts of any contained {@link org.eclipse.jetty.server.handler.ContextHandler}s. + * The contexts do not need to be directly contained, only children of the contained handlers. + * Multiple contexts may have the same context path and they are called in order until one + * handles the request. + * + * @org.apache.xbean.XBean element="contexts" + */ +public class ContextHandlerCollection extends HandlerCollection +{ + private static final Logger LOG = Log.getLogger(ContextHandlerCollection.class); + + private volatile PathMap _contextMap; + private Class<? extends ContextHandler> _contextClass = ContextHandler.class; + + /* ------------------------------------------------------------ */ + public ContextHandlerCollection() + { + super(true); + } + + + /* ------------------------------------------------------------ */ + /** + * Remap the context paths. + */ + public void mapContexts() + { + PathMap contextMap = new PathMap(); + Handler[] branches = getHandlers(); + + + for (int b=0;branches!=null && b<branches.length;b++) + { + Handler[] handlers=null; + + if (branches[b] instanceof ContextHandler) + { + handlers = new Handler[]{ branches[b] }; + } + else if (branches[b] instanceof HandlerContainer) + { + handlers = ((HandlerContainer)branches[b]).getChildHandlersByClass(ContextHandler.class); + } + else + continue; + + for (int i=0;i<handlers.length;i++) + { + ContextHandler handler=(ContextHandler)handlers[i]; + + String contextPath=handler.getContextPath(); + + if (contextPath==null || contextPath.indexOf(',')>=0 || contextPath.startsWith("*")) + throw new IllegalArgumentException ("Illegal context spec:"+contextPath); + + if(!contextPath.startsWith("/")) + contextPath='/'+contextPath; + + if (contextPath.length()>1) + { + if (contextPath.endsWith("/")) + contextPath+="*"; + else if (!contextPath.endsWith("/*")) + contextPath+="/*"; + } + + Object contexts=contextMap.get(contextPath); + String[] vhosts=handler.getVirtualHosts(); + + + if (vhosts!=null && vhosts.length>0) + { + Map hosts; + + if (contexts instanceof Map) + hosts=(Map)contexts; + else + { + hosts=new HashMap(); + hosts.put("*",contexts); + contextMap.put(contextPath, hosts); + } + + for (int j=0;j<vhosts.length;j++) + { + String vhost=vhosts[j]; + contexts=hosts.get(vhost); + contexts=LazyList.add(contexts,branches[b]); + hosts.put(vhost,contexts); + } + } + else if (contexts instanceof Map) + { + Map hosts=(Map)contexts; + contexts=hosts.get("*"); + contexts= LazyList.add(contexts, branches[b]); + hosts.put("*",contexts); + } + else + { + contexts= LazyList.add(contexts, branches[b]); + contextMap.put(contextPath, contexts); + } + } + } + _contextMap=contextMap; + + } + + + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.server.handler.HandlerCollection#setHandlers(org.eclipse.jetty.server.server.Handler[]) + */ + @Override + public void setHandlers(Handler[] handlers) + { + _contextMap=null; + super.setHandlers(handlers); + if (isStarted()) + mapContexts(); + } + + /* ------------------------------------------------------------ */ + @Override + protected void doStart() throws Exception + { + mapContexts(); + super.doStart(); + } + + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.server.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int) + */ + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + Handler[] handlers = getHandlers(); + if (handlers==null || handlers.length==0) + return; + + AsyncContinuation async = baseRequest.getAsyncContinuation(); + if (async.isAsync()) + { + ContextHandler context=async.getContextHandler(); + if (context!=null) + { + context.handle(target,baseRequest,request, response); + return; + } + } + + // data structure which maps a request to a context; first-best match wins + // { context path => + // { virtual host => context } + // } + PathMap map = _contextMap; + if (map!=null && target!=null && target.startsWith("/")) + { + // first, get all contexts matched by context path + Object contexts = map.getLazyMatches(target); + + for (int i=0; i<LazyList.size(contexts); i++) + { + // then, match against the virtualhost of each context + Map.Entry entry = (Map.Entry)LazyList.get(contexts, i); + Object list = entry.getValue(); + + if (list instanceof Map) + { + Map hosts = (Map)list; + String host = normalizeHostname(request.getServerName()); + + // explicitly-defined virtual hosts, most specific + list=hosts.get(host); + for (int j=0; j<LazyList.size(list); j++) + { + Handler handler = (Handler)LazyList.get(list,j); + handler.handle(target,baseRequest, request, response); + if (baseRequest.isHandled()) + return; + } + + // wildcard for one level of names + list=hosts.get("*."+host.substring(host.indexOf(".")+1)); + for (int j=0; j<LazyList.size(list); j++) + { + Handler handler = (Handler)LazyList.get(list,j); + handler.handle(target,baseRequest, request, response); + if (baseRequest.isHandled()) + return; + } + + // no virtualhosts defined for the context, least specific + // will handle any request that does not match to a specific virtual host above + list=hosts.get("*"); + for (int j=0; j<LazyList.size(list); j++) + { + Handler handler = (Handler)LazyList.get(list,j); + handler.handle(target,baseRequest, request, response); + if (baseRequest.isHandled()) + return; + } + } + else + { + for (int j=0; j<LazyList.size(list); j++) + { + Handler handler = (Handler)LazyList.get(list,j); + handler.handle(target,baseRequest, request, response); + if (baseRequest.isHandled()) + return; + } + } + } + } + else + { + // This may not work in all circumstances... but then I think it should never be called + for (int i=0;i<handlers.length;i++) + { + handlers[i].handle(target,baseRequest, request, response); + if ( baseRequest.isHandled()) + return; + } + } + } + + + /* ------------------------------------------------------------ */ + /** Add a context handler. + * @param contextPath The context path to add + * @return the ContextHandler just added + */ + public ContextHandler addContext(String contextPath,String resourceBase) + { + try + { + ContextHandler context = _contextClass.newInstance(); + context.setContextPath(contextPath); + context.setResourceBase(resourceBase); + addHandler(context); + return context; + } + catch (Exception e) + { + LOG.debug(e); + throw new Error(e); + } + } + + + + /* ------------------------------------------------------------ */ + /** + * @return The class to use to add new Contexts + */ + public Class getContextClass() + { + return _contextClass; + } + + + /* ------------------------------------------------------------ */ + /** + * @param contextClass The class to use to add new Contexts + */ + public void setContextClass(Class contextClass) + { + if (contextClass ==null || !(ContextHandler.class.isAssignableFrom(contextClass))) + throw new IllegalArgumentException(); + _contextClass = contextClass; + } + + /* ------------------------------------------------------------ */ + private String normalizeHostname( String host ) + { + if ( host == null ) + return null; + + if ( host.endsWith( "." ) ) + return host.substring( 0, host.length() -1); + + return host; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/handler/DebugHandler.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,158 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.Locale; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.DateCache; +import org.eclipse.jetty.util.RolloverFileOutputStream; + + +/** + * Debug Handler. + * A lightweight debug handler that can be used in production code. + * 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. + */ +public class DebugHandler extends HandlerWrapper +{ + private DateCache _date=new DateCache("HH:mm:ss", Locale.US); + private OutputStream _out; + private PrintStream _print; + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int) + */ + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + final Response base_response = baseRequest.getResponse(); + final Thread thread=Thread.currentThread(); + final String old_name=thread.getName(); + + 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.getUri(); + else + retry=true; + + String ex=null; + try + { + final String d=_date.now(); + final int ms=_date.lastMs(); + + if (retry) + _print.println(d+(ms>99?".":(ms>9?".0":".00"))+ms+":"+name+" RETRY"); + else + _print.println(d+(ms>99?".":(ms>9?".0":".00"))+ms+":"+name+" "+baseRequest.getRemoteAddr()+" "+request.getMethod()+" "+baseRequest.getHeader("Cookie")+"; "+baseRequest.getHeader("User-Agent")); + thread.setName(name); + + getHandler().handle(target,baseRequest,request,response); + } + catch(IOException ioe) + { + ex=ioe.toString(); + throw ioe; + } + catch(ServletException se) + { + ex=se.toString()+":"+se.getCause(); + throw se; + } + catch(RuntimeException rte) + { + ex=rte.toString(); + throw rte; + } + catch(Error e) + { + ex=e.toString(); + throw e; + } + finally + { + thread.setName(old_name); + final String d=_date.now(); + final int ms=_date.lastMs(); + suspend=baseRequest.getAsyncContinuation().isSuspended(); + if (suspend) + { + request.setAttribute("org.eclipse.jetty.thread.name",name); + _print.println(d+(ms>99?".":(ms>9?".0":".00"))+ms+":"+name+" SUSPEND"); + } + else + _print.println(d+(ms>99?".":(ms>9?".0":".00"))+ms+":"+name+" "+base_response.getStatus()+ + (ex==null?"":("/"+ex))+ + " "+base_response.getContentType()+" "+base_response.getContentCount()); + } + } + + /* (non-Javadoc) + * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStart() + */ + @Override + protected void doStart() throws Exception + { + if (_out==null) + _out=new RolloverFileOutputStream("./logs/yyyy_mm_dd.debug.log",true); + _print=new PrintStream(_out); + super.doStart(); + } + + /* (non-Javadoc) + * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStop() + */ + @Override + protected void doStop() throws Exception + { + super.doStop(); + _print.close(); + } + + /** + * @return the out + */ + public OutputStream getOutputStream() + { + return _out; + } + + /** + * @param out the out to set + */ + public void setOutputStream(OutputStream out) + { + _out = out; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/handler/DefaultHandler.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,208 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.URL; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.HttpMethods; +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.ByteArrayISO8859Writer; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.resource.Resource; + + +/* ------------------------------------------------------------ */ +/** Default Handler. + * + * This handle will deal with unhandled requests in the server. + * For requests for favicon.ico, the Jetty icon is served. + * For reqests to '/' a 404 with a list of known contexts is served. + * For all other requests a normal 404 is served. + * TODO Implement OPTIONS and TRACE methods for the server. + * + * + * @org.apache.xbean.XBean + */ +public class DefaultHandler extends AbstractHandler +{ + private static final Logger LOG = Log.getLogger(DefaultHandler.class); + + final long _faviconModified=(System.currentTimeMillis()/1000)*1000L; + byte[] _favicon; + boolean _serveIcon=true; + boolean _showContexts=true; + + public DefaultHandler() + { + try + { + URL fav = this.getClass().getClassLoader().getResource("org/eclipse/jetty/favicon.ico"); + if (fav!=null) + { + Resource r = Resource.newResource(fav); + _favicon=IO.readBytes(r.getInputStream()); + } + } + catch(Exception e) + { + LOG.warn(e); + } + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int) + */ + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + if (response.isCommitted() || baseRequest.isHandled()) + return; + + baseRequest.setHandled(true); + + String method=request.getMethod(); + + // little cheat for common request + if (_serveIcon && _favicon!=null && method.equals(HttpMethods.GET) && request.getRequestURI().equals("/favicon.ico")) + { + if (request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE)==_faviconModified) + response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); + else + { + response.setStatus(HttpServletResponse.SC_OK); + response.setContentType("image/x-icon"); + response.setContentLength(_favicon.length); + response.setDateHeader(HttpHeaders.LAST_MODIFIED, _faviconModified); + response.setHeader(HttpHeaders.CACHE_CONTROL,"max-age=360000,public"); + response.getOutputStream().write(_favicon); + } + return; + } + + + if (!method.equals(HttpMethods.GET) || !request.getRequestURI().equals("/")) + { + response.sendError(HttpServletResponse.SC_NOT_FOUND); + return; + } + + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + response.setContentType(MimeTypes.TEXT_HTML); + + ByteArrayISO8859Writer writer = new ByteArrayISO8859Writer(1500); + + writer.write("<HTML>\n<HEAD>\n<TITLE>Error 404 - Not Found"); + writer.write("</TITLE>\n<BODY>\n<H2>Error 404 - Not Found.</H2>\n"); + writer.write("No context on this server matched or handled this request.<BR>"); + + if (_showContexts) + { + writer.write("Contexts known to this server are: <ul>"); + + Server server = getServer(); + Handler[] handlers = server==null?null:server.getChildHandlersByClass(ContextHandler.class); + + for (int i=0;handlers!=null && i<handlers.length;i++) + { + ContextHandler context = (ContextHandler)handlers[i]; + if (context.isRunning()) + { + writer.write("<li><a href=\""); + if (context.getVirtualHosts()!=null && context.getVirtualHosts().length>0) + writer.write("http://"+context.getVirtualHosts()[0]+":"+request.getLocalPort()); + writer.write(context.getContextPath()); + if (context.getContextPath().length()>1 && context.getContextPath().endsWith("/")) + writer.write("/"); + writer.write("\">"); + writer.write(context.getContextPath()); + if (context.getVirtualHosts()!=null && context.getVirtualHosts().length>0) + writer.write(" @ "+context.getVirtualHosts()[0]+":"+request.getLocalPort()); + writer.write(" ---> "); + writer.write(context.toString()); + writer.write("</a></li>\n"); + } + else + { + writer.write("<li>"); + writer.write(context.getContextPath()); + if (context.getVirtualHosts()!=null && context.getVirtualHosts().length>0) + writer.write(" @ "+context.getVirtualHosts()[0]+":"+request.getLocalPort()); + writer.write(" ---> "); + writer.write(context.toString()); + if (context.isFailed()) + writer.write(" [failed]"); + if (context.isStopped()) + writer.write(" [stopped]"); + writer.write("</li>\n"); + } + } + } + + for (int i=0;i<10;i++) + writer.write("\n<!-- Padding for IE -->"); + + writer.write("\n</BODY>\n</HTML>\n"); + writer.flush(); + response.setContentLength(writer.size()); + OutputStream out=response.getOutputStream(); + writer.writeTo(out); + out.close(); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns true if the handle can server the jetty favicon.ico + */ + public boolean getServeIcon() + { + return _serveIcon; + } + + /* ------------------------------------------------------------ */ + /** + * @param serveIcon true if the handle can server the jetty favicon.ico + */ + public void setServeIcon(boolean serveIcon) + { + _serveIcon = serveIcon; + } + + public boolean getShowContexts() + { + return _showContexts; + } + + public void setShowContexts(boolean show) + { + _showContexts = show; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/handler/ErrorHandler.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,285 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.HttpMethods; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.server.AbstractHttpConnection; +import org.eclipse.jetty.server.Dispatcher; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.ByteArrayISO8859Writer; +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. + * + */ +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) + */ + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + { + AbstractHttpConnection connection = AbstractHttpConnection.getCurrentConnection(); + String method = request.getMethod(); + if(!method.equals(HttpMethods.GET) && !method.equals(HttpMethods.POST) && !method.equals(HttpMethods.HEAD)) + { + connection.getRequest().setHandled(true); + return; + } + + if (this instanceof ErrorPageMapper) + { + String error_page=((ErrorPageMapper)this).getErrorPage(request); + if (error_page!=null && request.getServletContext()!=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); + + Dispatcher dispatcher = (Dispatcher) request.getServletContext().getRequestDispatcher(error_page); + try + { + if(dispatcher!=null) + { + dispatcher.error(request, response); + return; + } + LOG.warn("No error page "+error_page); + } + catch (ServletException e) + { + LOG.warn(Log.EXCEPTION, e); + return; + } + } + } + } + + connection.getRequest().setHandled(true); + response.setContentType(MimeTypes.TEXT_HTML_8859_1); + if (_cacheControl!=null) + response.setHeader(HttpHeaders.CACHE_CONTROL, _cacheControl); + ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(4096); + handleErrorPage(request, writer, connection.getResponse().getStatus(), connection.getResponse().getReason()); + writer.flush(); + response.setContentLength(writer.size()); + writer.writeTo(response.getOutputStream()); + writer.destroy(); + } + + /* ------------------------------------------------------------ */ + protected void handleErrorPage(HttpServletRequest request, Writer writer, int code, String message) + throws IOException + { + writeErrorPage(request, writer, code, message, _showStacks); + } + + /* ------------------------------------------------------------ */ + protected void writeErrorPage(HttpServletRequest request, Writer writer, int code, String message, boolean showStacks) + throws IOException + { + if (message == null) + message=HttpStatus.getMessage(code); + + writer.write("<html>\n<head>\n"); + writeErrorPageHead(request,writer,code,message); + writer.write("</head>\n<body>"); + writeErrorPageBody(request,writer,code,message,showStacks); + writer.write("\n</body>\n</html>\n"); + } + + /* ------------------------------------------------------------ */ + protected void writeErrorPageHead(HttpServletRequest request, Writer writer, int code, String message) + throws IOException + { + writer.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\"/>\n"); + writer.write("<title>Error "); + writer.write(Integer.toString(code)); + + if (_showMessageInTitle) + { + writer.write(' '); + write(writer,message); + } + writer.write("</title>\n"); + } + + /* ------------------------------------------------------------ */ + protected void writeErrorPageBody(HttpServletRequest request, Writer writer, int code, String message, boolean showStacks) + throws IOException + { + String uri= request.getRequestURI(); + + writeErrorPageMessage(request,writer,code,message,uri); + if (showStacks) + writeErrorPageStacks(request,writer); + writer.write("<hr /><i><small>Powered by Jetty://</small></i>"); + for (int i= 0; i < 20; i++) + writer.write("<br/> \n"); + } + + /* ------------------------------------------------------------ */ + protected void writeErrorPageMessage(HttpServletRequest request, Writer writer, int code, String message,String uri) + throws IOException + { + writer.write("<h2>HTTP ERROR "); + writer.write(Integer.toString(code)); + writer.write("</h2>\n<p>Problem accessing "); + write(writer,uri); + writer.write(". Reason:\n<pre> "); + write(writer,message); + writer.write("</pre></p>"); + } + + /* ------------------------------------------------------------ */ + protected void writeErrorPageStacks(HttpServletRequest request, Writer writer) + throws IOException + { + Throwable th = (Throwable)request.getAttribute("javax.servlet.error.exception"); + while(th!=null) + { + writer.write("<h3>Caused by:</h3><pre>"); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + th.printStackTrace(pw); + pw.flush(); + write(writer,sw.getBuffer().toString()); + writer.write("</pre>\n"); + + th =th.getCause(); + } + } + + + /* ------------------------------------------------------------ */ + /** Get the cacheControl. + * @return the cacheControl header to set on error responses. + */ + public String getCacheControl() + { + return _cacheControl; + } + + /* ------------------------------------------------------------ */ + /** Set the cacheControl. + * @param cacheControl the cacheControl header to set on error responses. + */ + public void setCacheControl(String cacheControl) + { + _cacheControl = cacheControl; + } + + /* ------------------------------------------------------------ */ + /** + * @return True if stack traces are shown in the error pages + */ + public boolean isShowStacks() + { + return _showStacks; + } + + /* ------------------------------------------------------------ */ + /** + * @param showStacks True if stack traces are shown in the error pages + */ + public void setShowStacks(boolean showStacks) + { + _showStacks = showStacks; + } + + /* ------------------------------------------------------------ */ + /** + * @param showMessageInTitle if true, the error message appears in page title + */ + public void setShowMessageInTitle(boolean showMessageInTitle) + { + _showMessageInTitle = showMessageInTitle; + } + + + /* ------------------------------------------------------------ */ + public boolean getShowMessageInTitle() + { + return _showMessageInTitle; + } + + /* ------------------------------------------------------------ */ + protected void write(Writer writer,String string) + throws IOException + { + if (string==null) + return; + + for (int i=0;i<string.length();i++) + { + char c=string.charAt(i); + + switch(c) + { + case '&' : + writer.write("&"); + break; + case '<' : + writer.write("<"); + break; + case '>' : + writer.write(">"); + break; + + default: + if (Character.isISOControl(c) && !Character.isWhitespace(c)) + writer.write('?'); + else + writer.write(c); + } + } + } + + /* ------------------------------------------------------------ */ + public interface ErrorPageMapper + { + String getErrorPage(HttpServletRequest request); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/handler/GzipHandler.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,356 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.util.HashSet; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.GZIPOutputStream; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.continuation.Continuation; +import org.eclipse.jetty.continuation.ContinuationListener; +import org.eclipse.jetty.continuation.ContinuationSupport; +import org.eclipse.jetty.http.HttpMethods; +import org.eclipse.jetty.http.gzip.CompressedResponseWrapper; +import org.eclipse.jetty.http.gzip.AbstractCompressedStream; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/* ------------------------------------------------------------ */ +/** + * GZIP Handler This handler will gzip the content of a response if: + * <ul> + * <li>The filter is mapped to a matching path</li> + * <li>The response status code is >=200 and <300 + * <li>The content length is unknown or more than the <code>minGzipSize</code> initParameter or the minGzipSize is 0(default)</li> + * <li>The content-type is in the comma separated list of mimeTypes set in the <code>mimeTypes</code> initParameter or if no mimeTypes are defined the + * content-type is not "application/gzip"</li> + * <li>No content-encoding is specified by the resource</li> + * </ul> + * + * <p> + * Compressing the content can greatly improve the network bandwidth usage, but at a cost of memory and CPU cycles. If this handler is used for static content, + * then use of efficient direct NIO may be prevented, thus use of the gzip mechanism of the <code>org.eclipse.jetty.servlet.DefaultServlet</code> is advised instead. + * </p> + */ +public class GzipHandler extends HandlerWrapper +{ + private static final Logger LOG = Log.getLogger(GzipHandler.class); + + protected Set<String> _mimeTypes; + protected Set<String> _excluded; + protected int _bufferSize = 8192; + protected int _minGzipSize = 256; + protected String _vary = "Accept-Encoding, User-Agent"; + + /* ------------------------------------------------------------ */ + /** + * Instantiates a new gzip handler. + */ + public GzipHandler() + { + } + + /* ------------------------------------------------------------ */ + /** + * Get the mime types. + * + * @return mime types to set + */ + public Set<String> getMimeTypes() + { + return _mimeTypes; + } + + /* ------------------------------------------------------------ */ + /** + * Set the mime types. + * + * @param mimeTypes + * the mime types to set + */ + public void setMimeTypes(Set<String> mimeTypes) + { + _mimeTypes = mimeTypes; + } + + /* ------------------------------------------------------------ */ + /** + * Set the mime types. + * + * @param mimeTypes + * the mime types to set + */ + public void setMimeTypes(String mimeTypes) + { + if (mimeTypes != null) + { + _mimeTypes = new HashSet<String>(); + StringTokenizer tok = new StringTokenizer(mimeTypes,",",false); + while (tok.hasMoreTokens()) + { + _mimeTypes.add(tok.nextToken()); + } + } + } + + /* ------------------------------------------------------------ */ + /** + * Get the excluded user agents. + * + * @return excluded user agents + */ + public Set<String> getExcluded() + { + return _excluded; + } + + /* ------------------------------------------------------------ */ + /** + * Set the excluded user agents. + * + * @param excluded + * excluded user agents to set + */ + public void setExcluded(Set<String> excluded) + { + _excluded = excluded; + } + + /* ------------------------------------------------------------ */ + /** + * Set the excluded user agents. + * + * @param excluded + * excluded user agents to set + */ + public void setExcluded(String excluded) + { + if (excluded != null) + { + _excluded = new HashSet<String>(); + StringTokenizer tok = new StringTokenizer(excluded,",",false); + while (tok.hasMoreTokens()) + _excluded.add(tok.nextToken()); + } + } + + /* ------------------------------------------------------------ */ + /** + * @return The value of the Vary header set if a response can be compressed. + */ + public String getVary() + { + return _vary; + } + + /* ------------------------------------------------------------ */ + /** + * Set the value of the Vary header sent with responses that could be compressed. + * <p> + * By default it is set to 'Accept-Encoding, User-Agent' since IE6 is excluded by + * default from the excludedAgents. If user-agents are not to be excluded, then + * this can be set to 'Accept-Encoding'. Note also that shared caches may cache + * many copies of a resource that is varied by User-Agent - one per variation of the + * User-Agent, unless the cache does some normalization of the UA string. + * @param vary The value of the Vary header set if a response can be compressed. + */ + public void setVary(String vary) + { + _vary = vary; + } + + /* ------------------------------------------------------------ */ + /** + * Get the buffer size. + * + * @return the buffer size + */ + public int getBufferSize() + { + return _bufferSize; + } + + /* ------------------------------------------------------------ */ + /** + * Set the buffer size. + * + * @param bufferSize + * buffer size to set + */ + public void setBufferSize(int bufferSize) + { + _bufferSize = bufferSize; + } + + /* ------------------------------------------------------------ */ + /** + * Get the minimum reponse size. + * + * @return minimum reponse size + */ + public int getMinGzipSize() + { + return _minGzipSize; + } + + /* ------------------------------------------------------------ */ + /** + * Set the minimum reponse size. + * + * @param minGzipSize + * minimum reponse size + */ + public void setMinGzipSize(int minGzipSize) + { + _minGzipSize = minGzipSize; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.handler.HandlerWrapper#handle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + if (_handler!=null && isStarted()) + { + String ae = request.getHeader("accept-encoding"); + if (ae != null && ae.indexOf("gzip")>=0 && !response.containsHeader("Content-Encoding") + && !HttpMethods.HEAD.equalsIgnoreCase(request.getMethod())) + { + if (_excluded!=null) + { + String ua = request.getHeader("User-Agent"); + if (_excluded.contains(ua)) + { + _handler.handle(target,baseRequest, request, response); + return; + } + } + + final CompressedResponseWrapper wrappedResponse = newGzipResponseWrapper(request,response); + + boolean exceptional=true; + try + { + _handler.handle(target, baseRequest, request, wrappedResponse); + exceptional=false; + } + finally + { + Continuation continuation = ContinuationSupport.getContinuation(request); + if (continuation.isSuspended() && continuation.isResponseWrapped()) + { + continuation.addContinuationListener(new ContinuationListener() + { + public void onComplete(Continuation continuation) + { + try + { + wrappedResponse.finish(); + } + catch(IOException e) + { + LOG.warn(e); + } + } + + public void onTimeout(Continuation continuation) + {} + }); + } + else if (exceptional && !response.isCommitted()) + { + wrappedResponse.resetBuffer(); + wrappedResponse.noCompression(); + } + else + wrappedResponse.finish(); + } + } + else + { + _handler.handle(target,baseRequest, request, response); + } + } + } + + /** + * Allows derived implementations to replace ResponseWrapper implementation. + * + * @param request the request + * @param response the response + * @return the gzip response wrapper + */ + protected CompressedResponseWrapper newGzipResponseWrapper(HttpServletRequest request, HttpServletResponse response) + { + return new CompressedResponseWrapper(request,response) + { + { + super.setMimeTypes(GzipHandler.this._mimeTypes); + super.setBufferSize(GzipHandler.this._bufferSize); + super.setMinCompressSize(GzipHandler.this._minGzipSize); + } + + @Override + protected AbstractCompressedStream newCompressedStream(HttpServletRequest request,HttpServletResponse response) throws IOException + { + return new AbstractCompressedStream("gzip",request,this,_vary) + { + @Override + protected DeflaterOutputStream createStream() throws IOException + { + return new GZIPOutputStream(_response.getOutputStream(),_bufferSize); + } + }; + } + + @Override + protected PrintWriter newWriter(OutputStream out,String encoding) throws UnsupportedEncodingException + { + return GzipHandler.this.newWriter(out,encoding); + } + }; + } + + /** + * Allows derived implementations to replace PrintWriter implementation. + * + * @param out the out + * @param encoding the encoding + * @return the prints the writer + * @throws UnsupportedEncodingException + */ + protected PrintWriter newWriter(OutputStream out,String encoding) throws UnsupportedEncodingException + { + return encoding==null?new PrintWriter(out):new PrintWriter(new OutputStreamWriter(out,encoding)); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/handler/HandlerCollection.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,316 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; + +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.LazyList; +import org.eclipse.jetty.util.MultiException; + +/* ------------------------------------------------------------ */ +/** A collection of handlers. + * <p> + * The default implementations calls all handlers in list order, + * regardless of the response status or exceptions. Derived implementation + * may alter the order or the conditions of calling the contained + * handlers. + * <p> + * + * @org.apache.xbean.XBean + */ +public class HandlerCollection extends AbstractHandlerContainer +{ + private final boolean _mutableWhenRunning; + private volatile Handler[] _handlers; + private boolean _parallelStart=false; + + /* ------------------------------------------------------------ */ + public HandlerCollection() + { + _mutableWhenRunning=false; + } + + /* ------------------------------------------------------------ */ + public HandlerCollection(boolean mutableWhenRunning) + { + _mutableWhenRunning=mutableWhenRunning; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the handlers. + */ + public Handler[] getHandlers() + { + return _handlers; + } + + /* ------------------------------------------------------------ */ + /** + * + * @param handlers The handlers to set. + */ + public void setHandlers(Handler[] handlers) + { + if (!_mutableWhenRunning && isStarted()) + throw new IllegalStateException(STARTED); + + Handler [] old_handlers = _handlers==null?null:_handlers.clone(); + _handlers = handlers; + + Server server = getServer(); + MultiException mex = new MultiException(); + for (int i=0;handlers!=null && i<handlers.length;i++) + { + if (handlers[i].getServer()!=server) + handlers[i].setServer(server); + } + + if (getServer()!=null) + getServer().getContainer().update(this, old_handlers, handlers, "handler"); + + // stop old handlers + for (int i=0;old_handlers!=null && i<old_handlers.length;i++) + { + if (old_handlers[i]!=null) + { + try + { + if (old_handlers[i].isStarted()) + old_handlers[i].stop(); + } + catch (Throwable e) + { + mex.add(e); + } + } + } + + mex.ifExceptionThrowRuntime(); + } + + + + /* ------------------------------------------------------------ */ + /** Get the parrallelStart. + * @return true if the contained handlers are started in parallel. + */ + public boolean isParallelStart() + { + return _parallelStart; + } + + + + /* ------------------------------------------------------------ */ + /** Set the parallelStart. + * @param parallelStart If true, contained handlers are started in parallel. + */ + public void setParallelStart(boolean parallelStart) + { + this._parallelStart = parallelStart; + } + + + /* ------------------------------------------------------------ */ + /** + * @see Handler#handle(String, Request, HttpServletRequest, HttpServletResponse) + */ + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + if (_handlers!=null && isStarted()) + { + MultiException mex=null; + + for (int i=0;i<_handlers.length;i++) + { + try + { + _handlers[i].handle(target,baseRequest, request, response); + } + catch(IOException e) + { + throw e; + } + catch(RuntimeException e) + { + throw e; + } + catch(Exception e) + { + if (mex==null) + mex=new MultiException(); + mex.add(e); + } + } + if (mex!=null) + { + if (mex.size()==1) + throw new ServletException(mex.getThrowable(0)); + else + throw new ServletException(mex); + } + + } + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.server.handler.AbstractHandler#doStart() + */ + @Override + protected void doStart() throws Exception + { + final MultiException mex=new MultiException(); + if (_handlers!=null) + { + if (_parallelStart) + { + final CountDownLatch latch = new CountDownLatch(_handlers.length); + final ClassLoader loader = Thread.currentThread().getContextClassLoader(); + for (int i=0;i<_handlers.length;i++) + { + final int h=i; + getServer().getThreadPool().dispatch( + new Runnable() + { + public void run() + { + ClassLoader orig = Thread.currentThread().getContextClassLoader(); + try + { + Thread.currentThread().setContextClassLoader(loader); + _handlers[h].start(); + } + catch(Throwable e) + { + mex.add(e); + } + finally + { + Thread.currentThread().setContextClassLoader(orig); + latch.countDown(); + } + } + } + ); + } + latch.await(); + } + else + { + for (int i=0;i<_handlers.length;i++) + { + try{_handlers[i].start();} + catch(Throwable e){mex.add(e);} + } + } + } + super.doStart(); + mex.ifExceptionThrow(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.server.handler.AbstractHandler#doStop() + */ + @Override + protected void doStop() throws Exception + { + MultiException mex=new MultiException(); + try { super.doStop(); } catch(Throwable e){mex.add(e);} + if (_handlers!=null) + { + for (int i=_handlers.length;i-->0;) + try{_handlers[i].stop();}catch(Throwable e){mex.add(e);} + } + mex.ifExceptionThrow(); + } + + /* ------------------------------------------------------------ */ + @Override + public void setServer(Server server) + { + if (isStarted()) + throw new IllegalStateException(STARTED); + + Server old_server=getServer(); + + super.setServer(server); + + Handler[] h=getHandlers(); + for (int i=0;h!=null && i<h.length;i++) + h[i].setServer(server); + + if (server!=null && server!=old_server) + server.getContainer().update(this, null,_handlers, "handler"); + + } + + /* ------------------------------------------------------------ */ + /* Add a handler. + * This implementation adds the passed handler to the end of the existing collection of handlers. + * @see org.eclipse.jetty.server.server.HandlerContainer#addHandler(org.eclipse.jetty.server.server.Handler) + */ + public void addHandler(Handler handler) + { + setHandlers((Handler[])LazyList.addToArray(getHandlers(), handler, Handler.class)); + } + + /* ------------------------------------------------------------ */ + public void removeHandler(Handler handler) + { + Handler[] handlers = getHandlers(); + + if (handlers!=null && handlers.length>0 ) + setHandlers((Handler[])LazyList.removeFromArray(handlers, handler)); + } + + /* ------------------------------------------------------------ */ + @Override + protected Object expandChildren(Object list, Class byClass) + { + Handler[] handlers = getHandlers(); + for (int i=0;handlers!=null && i<handlers.length;i++) + list=expandHandler(handlers[i], list, byClass); + return list; + } + + /* ------------------------------------------------------------ */ + @Override + public void destroy() + { + if (!isStopped()) + throw new IllegalStateException("!STOPPED"); + Handler[] children=getChildHandlers(); + setHandlers(null); + for (Handler child: children) + child.destroy(); + super.destroy(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/handler/HandlerList.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,58 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; + +/* ------------------------------------------------------------ */ +/** HandlerList. + * This extension of {@link HandlerCollection} will call + * each contained handler in turn until either an exception is thrown, the response + * is committed or a positive response status is set. + */ +public class HandlerList extends HandlerCollection +{ + /* ------------------------------------------------------------ */ + /** + * @see Handler#handle(String, Request, HttpServletRequest, HttpServletResponse) + */ + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + Handler[] handlers = getHandlers(); + + if (handlers!=null && isStarted()) + { + for (int i=0;i<handlers.length;i++) + { + handlers[i].handle(target,baseRequest, request, response); + if ( baseRequest.isHandled()) + return; + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/handler/HandlerWrapper.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,182 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.component.LifeCycle; + +/* ------------------------------------------------------------ */ +/** A <code>HandlerWrapper</code> acts as a {@link Handler} but delegates the {@link Handler#handle handle} method and + * {@link LifeCycle life cycle} events to a delegate. This is primarily used to implement the <i>Decorator</i> pattern. + * + */ +public class HandlerWrapper extends AbstractHandlerContainer +{ + protected Handler _handler; + + /* ------------------------------------------------------------ */ + /** + * + */ + public HandlerWrapper() + { + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the handlers. + */ + public Handler getHandler() + { + return _handler; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the handlers. + */ + public Handler[] getHandlers() + { + if (_handler==null) + return new Handler[0]; + return new Handler[] {_handler}; + } + + /* ------------------------------------------------------------ */ + /** + * @param handler Set the {@link Handler} which should be wrapped. + */ + public void setHandler(Handler handler) + { + if (isStarted()) + throw new IllegalStateException(STARTED); + + Handler old_handler = _handler; + _handler = handler; + if (handler!=null) + handler.setServer(getServer()); + + if (getServer()!=null) + getServer().getContainer().update(this, old_handler, handler, "handler"); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.thread.AbstractLifeCycle#doStart() + */ + @Override + protected void doStart() throws Exception + { + if (_handler!=null) + _handler.start(); + super.doStart(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.thread.AbstractLifeCycle#doStop() + */ + @Override + protected void doStop() throws Exception + { + if (_handler!=null) + _handler.stop(); + super.doStop(); + } + + /* ------------------------------------------------------------ */ + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + if (_handler!=null && isStarted()) + { + _handler.handle(target,baseRequest, request, response); + } + } + + + /* ------------------------------------------------------------ */ + @Override + public void setServer(Server server) + { + Server old_server=getServer(); + if (server==old_server) + return; + + if (isStarted()) + throw new IllegalStateException(STARTED); + + super.setServer(server); + + Handler h=getHandler(); + if (h!=null) + h.setServer(server); + + if (server!=null && server!=old_server) + server.getContainer().update(this, null,_handler, "handler"); + } + + + /* ------------------------------------------------------------ */ + @Override + protected Object expandChildren(Object list, Class byClass) + { + return expandHandler(_handler,list,byClass); + } + + /* ------------------------------------------------------------ */ + public <H extends Handler> H getNestedHandlerByClass(Class<H> byclass) + { + HandlerWrapper h=this; + while (h!=null) + { + if (byclass.isInstance(h)) + return (H)h; + Handler w = h.getHandler(); + if (w instanceof HandlerWrapper) + h=(HandlerWrapper)w; + else break; + } + return null; + + } + + /* ------------------------------------------------------------ */ + @Override + public void destroy() + { + if (!isStopped()) + throw new IllegalStateException("!STOPPED"); + Handler child=getHandler(); + if (child!=null) + { + setHandler(null); + child.destroy(); + } + super.destroy(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/handler/HotSwapHandler.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,176 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; + +/* ------------------------------------------------------------ */ +/** + * A <code>HandlerContainer</code> that allows a hot swap of a wrapped handler. + * + */ +public class HotSwapHandler extends AbstractHandlerContainer +{ + private volatile Handler _handler; + + /* ------------------------------------------------------------ */ + /** + * + */ + public HotSwapHandler() + { + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the handlers. + */ + public Handler getHandler() + { + return _handler; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the handlers. + */ + public Handler[] getHandlers() + { + return new Handler[] + { _handler }; + } + + /* ------------------------------------------------------------ */ + /** + * @param handler + * Set the {@link Handler} which should be wrapped. + */ + public void setHandler(Handler handler) + { + if (handler == null) + throw new IllegalArgumentException("Parameter handler is null."); + try + { + Handler old_handler = _handler; + _handler = handler; + Server server = getServer(); + handler.setServer(server); + addBean(handler); + + if (server != null) + server.getContainer().update(this,old_handler,handler,"handler"); + + // if there is an old handler and it was started, stop it + if (old_handler != null) + { + removeBean(old_handler); + } + + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.thread.AbstractLifeCycle#doStart() + */ + @Override + protected void doStart() throws Exception + { + super.doStart(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.thread.AbstractLifeCycle#doStop() + */ + @Override + protected void doStop() throws Exception + { + super.doStop(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.server.EventHandler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + if (_handler != null && isStarted()) + { + _handler.handle(target,baseRequest,request,response); + } + } + + /* ------------------------------------------------------------ */ + @Override + public void setServer(Server server) + { + Server old_server = getServer(); + if (server == old_server) + return; + + if (isRunning()) + throw new IllegalStateException(RUNNING); + + super.setServer(server); + + Handler h = getHandler(); + if (h != null) + h.setServer(server); + + if (server != null && server != old_server) + server.getContainer().update(this,null,_handler,"handler"); + } + + /* ------------------------------------------------------------ */ + @SuppressWarnings( + { "rawtypes", "unchecked" }) + @Override + protected Object expandChildren(Object list, Class byClass) + { + return expandHandler(_handler,list,byClass); + } + + /* ------------------------------------------------------------ */ + @Override + public void destroy() + { + if (!isStopped()) + throw new IllegalStateException("!STOPPED"); + Handler child = getHandler(); + if (child != null) + { + setHandler(null); + child.destroy(); + } + super.destroy(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/handler/IPAccessHandler.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,382 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.PathMap; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.server.AbstractHttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.IPAddressMap; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + + +/** + * IP Access Handler + * <p> + * Controls access to the wrapped handler by the real remote IP. Control is provided + * by white/black lists that include both internet addresses and URIs. This handler + * uses the real internet address of the connection, not one reported in the forwarded + * for headers, as this cannot be as easily forged. + * <p> + * Typically, the black/white lists will be used in one of three modes: + * <ul> + * <li>Blocking a few specific IPs/URLs by specifying several black list entries. + * <li>Allowing only some specific IPs/URLs by specifying several white lists entries. + * <li>Allowing a general range of IPs/URLs by specifying several general white list + * entries, that are then further refined by several specific black list exceptions + * </ul> + * <p> + * An empty white list is treated as match all. If there is at least one entry in + * the white list, then a request must match a white list entry. Black list entries + * are always applied, so that even if an entry matches the white list, a black list + * entry will override it. + * <p> + * Internet addresses may be specified as absolute address or as a combination of + * four octet wildcard specifications (a.b.c.d) that are defined as follows. + * </p> + * <pre> + * nnn - an absolute value (0-255) + * mmm-nnn - an inclusive range of absolute values, + * with following shorthand notations: + * nnn- => nnn-255 + * -nnn => 0-nnn + * - => 0-255 + * a,b,... - a list of wildcard specifications + * </pre> + * <p> + * Internet address specification is separated from the URI pattern using the "|" (pipe) + * character. URI patterns follow the servlet specification for simple * prefix and + * suffix wild cards (e.g. /, /foo, /foo/bar, /foo/bar/*, *.baz). + * <p> + * Earlier versions of the handler used internet address prefix wildcard specification + * to define a range of the internet addresses (e.g. 127., 10.10., 172.16.1.). + * They also used the first "/" character of the URI pattern to separate it from the + * internet address. Both of these features have been deprecated in the current version. + * <p> + * Examples of the entry specifications are: + * <ul> + * <li>10.10.1.2 - all requests from IP 10.10.1.2 + * <li>10.10.1.2|/foo/bar - all requests from IP 10.10.1.2 to URI /foo/bar + * <li>10.10.1.2|/foo/* - all requests from IP 10.10.1.2 to URIs starting with /foo/ + * <li>10.10.1.2|*.html - all requests from IP 10.10.1.2 to URIs ending with .html + * <li>10.10.0-255.0-255 - all requests from IPs within 10.10.0.0/16 subnet + * <li>10.10.0-.-255|/foo/bar - all requests from IPs within 10.10.0.0/16 subnet to URI /foo/bar + * <li>10.10.0-3,1,3,7,15|/foo/* - all requests from IPs addresses with last octet equal + * to 1,3,7,15 in subnet 10.10.0.0/22 to URIs starting with /foo/ + * </ul> + * <p> + * Earlier versions of the handler used internet address prefix wildcard specification + * to define a range of the internet addresses (e.g. 127., 10.10., 172.16.1.). + * They also used the first "/" character of the URI pattern to separate it from the + * internet address. Both of these features have been deprecated in the current version. + */ +public class IPAccessHandler extends HandlerWrapper +{ + private static final Logger LOG = Log.getLogger(IPAccessHandler.class); + + IPAddressMap<PathMap> _white = new IPAddressMap<PathMap>(); + IPAddressMap<PathMap> _black = new IPAddressMap<PathMap>(); + + /* ------------------------------------------------------------ */ + /** + * Creates new handler object + */ + public IPAccessHandler() + { + super(); + } + + /* ------------------------------------------------------------ */ + /** + * Creates new handler object and initializes white- and black-list + * + * @param white array of whitelist entries + * @param black array of blacklist entries + */ + public IPAccessHandler(String[] white, String []black) + { + super(); + + if (white != null && white.length > 0) + setWhite(white); + if (black != null && black.length > 0) + setBlack(black); + } + + /* ------------------------------------------------------------ */ + /** + * Add a whitelist entry to an existing handler configuration + * + * @param entry new whitelist entry + */ + public void addWhite(String entry) + { + add(entry, _white); + } + + /* ------------------------------------------------------------ */ + /** + * Add a blacklist entry to an existing handler configuration + * + * @param entry new blacklist entry + */ + public void addBlack(String entry) + { + add(entry, _black); + } + + /* ------------------------------------------------------------ */ + /** + * Re-initialize the whitelist of existing handler object + * + * @param entries array of whitelist entries + */ + public void setWhite(String[] entries) + { + set(entries, _white); + } + + /* ------------------------------------------------------------ */ + /** + * Re-initialize the blacklist of existing handler object + * + * @param entries array of blacklist entries + */ + public void setBlack(String[] entries) + { + set(entries, _black); + } + + /* ------------------------------------------------------------ */ + /** + * Checks the incoming request against the whitelist and blacklist + * + * @see org.eclipse.jetty.server.handler.HandlerWrapper#handle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + // Get the real remote IP (not the one set by the forwarded headers (which may be forged)) + AbstractHttpConnection connection = baseRequest.getConnection(); + if (connection!=null) + { + EndPoint endp=connection.getEndPoint(); + if (endp!=null) + { + String addr = endp.getRemoteAddr(); + if (addr!=null && !isAddrUriAllowed(addr,baseRequest.getPathInfo())) + { + response.sendError(HttpStatus.FORBIDDEN_403); + baseRequest.setHandled(true); + return; + } + } + } + + getHandler().handle(target,baseRequest, request, response); + } + + + /* ------------------------------------------------------------ */ + /** + * Helper method to parse the new entry and add it to + * the specified address pattern map. + * + * @param entry new entry + * @param patternMap target address pattern map + */ + protected void add(String entry, IPAddressMap<PathMap> patternMap) + { + if (entry != null && entry.length() > 0) + { + boolean deprecated = false; + int idx; + if (entry.indexOf('|') > 0 ) + { + idx = entry.indexOf('|'); + } + else + { + idx = entry.indexOf('/'); + deprecated = (idx >= 0); + } + + String addr = idx > 0 ? entry.substring(0,idx) : entry; + String path = idx > 0 ? entry.substring(idx) : "/*"; + + if (addr.endsWith(".")) + deprecated = true; + if (path!=null && (path.startsWith("|") || path.startsWith("/*."))) + path=path.substring(1); + + PathMap pathMap = patternMap.get(addr); + if (pathMap == null) + { + pathMap = new PathMap(true); + patternMap.put(addr,pathMap); + } + if (path != null && !"".equals(path)) + pathMap.put(path,path); + + if (deprecated) + LOG.debug(toString() +" - deprecated specification syntax: "+entry); + } + } + + /* ------------------------------------------------------------ */ + /** + * Helper method to process a list of new entries and replace + * the content of the specified address pattern map + * + * @param entries new entries + * @param patternMap target address pattern map + */ + protected void set(String[] entries, IPAddressMap<PathMap> patternMap) + { + patternMap.clear(); + + if (entries != null && entries.length > 0) + { + for (String addrPath:entries) + { + add(addrPath, patternMap); + } + } + } + + /* ------------------------------------------------------------ */ + /** + * Check if specified request is allowed by current IPAccess rules. + * + * @param addr internet address + * @param path context path + * @return true if request is allowed + * + */ + protected boolean isAddrUriAllowed(String addr, String path) + { + if (_white.size()>0) + { + boolean match = false; + + Object whiteObj = _white.getLazyMatches(addr); + if (whiteObj != null) + { + List whiteList = (whiteObj instanceof List) ? (List)whiteObj : Collections.singletonList(whiteObj); + + for (Object entry: whiteList) + { + PathMap pathMap = ((Map.Entry<String,PathMap>)entry).getValue(); + if (match = (pathMap!=null && (pathMap.size()==0 || pathMap.match(path)!=null))) + break; + } + } + + if (!match) + return false; + } + + if (_black.size() > 0) + { + Object blackObj = _black.getLazyMatches(addr); + if (blackObj != null) + { + List blackList = (blackObj instanceof List) ? (List)blackObj : Collections.singletonList(blackObj); + + for (Object entry: blackList) + { + PathMap pathMap = ((Map.Entry<String,PathMap>)entry).getValue(); + if (pathMap!=null && (pathMap.size()==0 || pathMap.match(path)!=null)) + return false; + } + } + } + + return true; + } + + /* ------------------------------------------------------------ */ + /** + * Dump the white- and black-list configurations when started + * + * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStart() + */ + @Override + protected void doStart() + throws Exception + { + super.doStart(); + + if (LOG.isDebugEnabled()) + { + System.err.println(dump()); + } + } + + /* ------------------------------------------------------------ */ + /** + * Dump the handler configuration + */ + public String dump() + { + StringBuilder buf = new StringBuilder(); + + buf.append(toString()); + buf.append(" WHITELIST:\n"); + dump(buf, _white); + buf.append(toString()); + buf.append(" BLACKLIST:\n"); + dump(buf, _black); + + return buf.toString(); + } + + /* ------------------------------------------------------------ */ + /** + * Dump a pattern map into a StringBuilder buffer + * + * @param buf buffer + * @param patternMap pattern map to dump + */ + protected void dump(StringBuilder buf, IPAddressMap<PathMap> patternMap) + { + for (String addr: patternMap.keySet()) + { + for (Object path: ((PathMap)patternMap.get(addr)).values()) + { + buf.append("# "); + buf.append(addr); + buf.append("|"); + buf.append(path); + buf.append("\n"); + } + } + } + }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/handler/IdleTimeoutHandler.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,138 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + +import java.io.IOException; + +import javax.servlet.AsyncEvent; +import javax.servlet.AsyncListener; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.server.AbstractHttpConnection; +import org.eclipse.jetty.server.Request; + +/** + * Handler to adjust the idle timeout of requests while dispatched. + * + * <p>Can be applied in jetty.xml with + * <pre> + * <Get id='handler' name='Handler'/> + * <Set name='Handler'> + * <New id='idleTimeoutHandler' class='org.eclipse.jetty.server.handler.IdleTimeoutHandler'> + * <Set name='Handler'><Ref id='handler'/></Set> + * <Set name='IdleTimeoutMs'>5000</Set> + * </New> + * </Set> + * </pre> + */ +public class IdleTimeoutHandler extends HandlerWrapper +{ + private int _idleTimeoutMs = 1000; + private boolean _applyToAsync = false; + + + public boolean isApplyToAsync() + { + return _applyToAsync; + } + + /** + * Should the adjusted idle time be maintained for asynchronous requests + * @param applyToAsync true if alternate idle timeout is applied to asynchronous requests + */ + public void setApplyToAsync(boolean applyToAsync) + { + _applyToAsync = applyToAsync; + } + + public long getIdleTimeoutMs() + { + return _idleTimeoutMs; + } + + /** + * @param idleTimeoutMs The idle timeout in MS to apply while dispatched or async + */ + public void setIdleTimeoutMs(int _idleTimeoutMs) + { + this._idleTimeoutMs = _idleTimeoutMs; + } + + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + AbstractHttpConnection connection = AbstractHttpConnection.getCurrentConnection(); + final EndPoint endp = connection==null?null:connection.getEndPoint(); + + final int idle_timeout; + if (endp==null) + idle_timeout=-1; + else + { + idle_timeout=endp.getMaxIdleTime(); + endp.setMaxIdleTime(_idleTimeoutMs); + } + + try + { + super.handle(target,baseRequest,request,response); + } + finally + { + if (endp!=null) + { + if (_applyToAsync && request.isAsyncStarted()) + { + request.getAsyncContext().addListener(new AsyncListener() + { + @Override + public void onTimeout(AsyncEvent event) throws IOException + { + } + + @Override + public void onStartAsync(AsyncEvent event) throws IOException + { + } + + @Override + public void onError(AsyncEvent event) throws IOException + { + endp.setMaxIdleTime(idle_timeout); + } + + @Override + public void onComplete(AsyncEvent event) throws IOException + { + endp.setMaxIdleTime(idle_timeout); + } + }); + } + else + { + endp.setMaxIdleTime(idle_timeout); + } + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/handler/MovedContextHandler.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,154 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.server.HandlerContainer; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.URIUtil; + +/* ------------------------------------------------------------ */ +/** Moved ContextHandler. + * This context can be used to replace a context that has changed + * location. Requests are redirected (either to a fixed URL or to a + * new context base). + */ +public class MovedContextHandler extends ContextHandler +{ + final Redirector _redirector; + String _newContextURL; + boolean _discardPathInfo; + boolean _discardQuery; + boolean _permanent; + String _expires; + + public MovedContextHandler() + { + _redirector=new Redirector(); + setHandler(_redirector); + setAllowNullPathInfo(true); + } + + public MovedContextHandler(HandlerContainer parent, String contextPath, String newContextURL) + { + super(parent,contextPath); + _newContextURL=newContextURL; + _redirector=new Redirector(); + setHandler(_redirector); + } + + public boolean isDiscardPathInfo() + { + return _discardPathInfo; + } + + public void setDiscardPathInfo(boolean discardPathInfo) + { + _discardPathInfo = discardPathInfo; + } + + public String getNewContextURL() + { + return _newContextURL; + } + + public void setNewContextURL(String newContextURL) + { + _newContextURL = newContextURL; + } + + public boolean isPermanent() + { + return _permanent; + } + + public void setPermanent(boolean permanent) + { + _permanent = permanent; + } + + public boolean isDiscardQuery() + { + return _discardQuery; + } + + public void setDiscardQuery(boolean discardQuery) + { + _discardQuery = discardQuery; + } + + private class Redirector extends AbstractHandler + { + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + if (_newContextURL==null) + return; + + String path=_newContextURL; + if (!_discardPathInfo && request.getPathInfo()!=null) + path=URIUtil.addPaths(path, request.getPathInfo()); + + StringBuilder location = URIUtil.hasScheme(path)?new StringBuilder():baseRequest.getRootURL(); + + location.append(path); + if (!_discardQuery && request.getQueryString()!=null) + { + location.append('?'); + String q=request.getQueryString(); + q=q.replaceAll("\r\n?&=","!"); + location.append(q); + } + + response.setHeader(HttpHeaders.LOCATION,location.toString()); + + if (_expires!=null) + response.setHeader(HttpHeaders.EXPIRES,_expires); + + response.setStatus(_permanent?HttpServletResponse.SC_MOVED_PERMANENTLY:HttpServletResponse.SC_FOUND); + response.setContentLength(0); + baseRequest.setHandled(true); + } + + } + + /* ------------------------------------------------------------ */ + /** + * @return the expires header value or null if no expires header + */ + public String getExpires() + { + return _expires; + } + + /* ------------------------------------------------------------ */ + /** + * @param expires the expires header value or null if no expires header + */ + public void setExpires(String expires) + { + _expires = expires; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/handler/ProxyHandler.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,51 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + +import org.eclipse.jetty.server.Handler; + + +/* ------------------------------------------------------------ */ +/** ProxyHandler. + * <p>This class has been renamed to ConnectHandler, as it only implements + * the CONNECT method (and a ProxyServlet must be used for full proxy handling). + * @deprecated Use {@link ConnectHandler} + */ +public class ProxyHandler extends ConnectHandler +{ + public ProxyHandler() + { + super(); + } + + public ProxyHandler(Handler handler, String[] white, String[] black) + { + super(handler,white,black); + } + + public ProxyHandler(Handler handler) + { + super(handler); + } + + public ProxyHandler(String[] white, String[] black) + { + super(white,black); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/handler/RequestLogHandler.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,192 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + +import java.io.IOException; + +import javax.servlet.DispatcherType; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.continuation.Continuation; +import org.eclipse.jetty.continuation.ContinuationListener; +import org.eclipse.jetty.server.AsyncContinuation; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.RequestLog; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + + +/** + * RequestLogHandler. + * This handler can be used to wrap an individual context for context logging. + * + * @org.apache.xbean.XBean + */ +public class RequestLogHandler extends HandlerWrapper +{ + private static final Logger LOG = Log.getLogger(RequestLogHandler.class); + + private RequestLog _requestLog; + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.server.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int) + */ + @Override + public void handle(String target, final Request baseRequest, HttpServletRequest request, final HttpServletResponse response) + throws IOException, ServletException + { + AsyncContinuation continuation = baseRequest.getAsyncContinuation(); + if (!continuation.isInitial()) + { + baseRequest.setDispatchTime(System.currentTimeMillis()); + } + + try + { + super.handle(target, baseRequest, request, response); + } + finally + { + if (_requestLog != null && baseRequest.getDispatcherType().equals(DispatcherType.REQUEST)) + { + if (continuation.isAsync()) + { + if (continuation.isInitial()) + continuation.addContinuationListener(new ContinuationListener() + { + + public void onTimeout(Continuation continuation) + { + + } + + public void onComplete(Continuation continuation) + { + _requestLog.log(baseRequest, (Response)response); + } + }); + } + else + _requestLog.log(baseRequest, (Response)response); + } + } + } + + /* ------------------------------------------------------------ */ + public void setRequestLog(RequestLog requestLog) + { + //are we changing the request log impl? + try + { + if (_requestLog != null) + _requestLog.stop(); + } + catch (Exception e) + { + LOG.warn (e); + } + + if (getServer()!=null) + getServer().getContainer().update(this, _requestLog, requestLog, "logimpl",true); + + _requestLog = requestLog; + + //if we're already started, then start our request log + try + { + if (isStarted() && (_requestLog != null)) + _requestLog.start(); + } + catch (Exception e) + { + throw new RuntimeException (e); + } + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.server.handler.HandlerWrapper#setServer(org.eclipse.jetty.server.server.Server) + */ + @Override + public void setServer(Server server) + { + if (_requestLog!=null) + { + if (getServer()!=null && getServer()!=server) + getServer().getContainer().update(this, _requestLog, null, "logimpl",true); + super.setServer(server); + if (server!=null && server!=getServer()) + server.getContainer().update(this, null,_requestLog, "logimpl",true); + } + else + super.setServer(server); + } + + /* ------------------------------------------------------------ */ + public RequestLog getRequestLog() + { + return _requestLog; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.server.handler.HandlerWrapper#doStart() + */ + @Override + protected void doStart() throws Exception + { + if (_requestLog==null) + { + LOG.warn("!RequestLog"); + _requestLog=new NullRequestLog(); + } + super.doStart(); + _requestLog.start(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.server.handler.HandlerWrapper#doStop() + */ + @Override + protected void doStop() throws Exception + { + super.doStop(); + _requestLog.stop(); + if (_requestLog instanceof NullRequestLog) + _requestLog=null; + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + private static class NullRequestLog extends AbstractLifeCycle implements RequestLog + { + public void log(Request request, Response response) + { + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/handler/ResourceHandler.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,545 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.MalformedURLException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.HttpMethods; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.WriterOutputStream; +import org.eclipse.jetty.server.AbstractHttpConnection; +import org.eclipse.jetty.server.Dispatcher; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.server.handler.ContextHandler.Context; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.resource.FileResource; +import org.eclipse.jetty.util.resource.Resource; + + +/* ------------------------------------------------------------ */ +/** Resource Handler. + * + * This handle will serve static content and handle If-Modified-Since headers. + * No caching is done. + * Requests for resources that do not exist are let pass (Eg no 404's). + * + * + * @org.apache.xbean.XBean + */ +public class ResourceHandler extends HandlerWrapper +{ + private static final Logger LOG = Log.getLogger(ResourceHandler.class); + + ContextHandler _context; + Resource _baseResource; + Resource _defaultStylesheet; + Resource _stylesheet; + String[] _welcomeFiles={"index.html"}; + MimeTypes _mimeTypes = new MimeTypes(); + ByteArrayBuffer _cacheControl; + boolean _aliases; + boolean _directory; + boolean _etags; + + /* ------------------------------------------------------------ */ + public ResourceHandler() + { + + } + + /* ------------------------------------------------------------ */ + public MimeTypes getMimeTypes() + { + return _mimeTypes; + } + + /* ------------------------------------------------------------ */ + public void setMimeTypes(MimeTypes mimeTypes) + { + _mimeTypes = mimeTypes; + } + + /* ------------------------------------------------------------ */ + /** + * @return True if resource aliases are allowed. + */ + public boolean isAliases() + { + return _aliases; + } + + /* ------------------------------------------------------------ */ + /** + * Set if resource aliases (eg symlink, 8.3 names, case insensitivity) are allowed. + * Allowing aliases can significantly increase security vulnerabilities. + * If this handler is deployed inside a ContextHandler, then the + * {@link ContextHandler#isAliases()} takes precedent. + * @param aliases True if aliases are supported. + */ + public void setAliases(boolean aliases) + { + _aliases = aliases; + } + + /* ------------------------------------------------------------ */ + /** Get the directory option. + * @return true if directories are listed. + */ + public boolean isDirectoriesListed() + { + return _directory; + } + + /* ------------------------------------------------------------ */ + /** Set the directory. + * @param directory true if directories are listed. + */ + public void setDirectoriesListed(boolean directory) + { + _directory = directory; + } + + /* ------------------------------------------------------------ */ + /** + * @return True if ETag processing is done + */ + public boolean isEtags() + { + return _etags; + } + + /* ------------------------------------------------------------ */ + /** + * @param etags True if ETag processing is done + */ + public void setEtags(boolean etags) + { + _etags = etags; + } + + /* ------------------------------------------------------------ */ + @Override + public void doStart() + throws Exception + { + Context scontext = ContextHandler.getCurrentContext(); + _context = (scontext==null?null:scontext.getContextHandler()); + + if (_context!=null) + _aliases=_context.isAliases(); + + if (!_aliases && !FileResource.getCheckAliases()) + throw new IllegalStateException("Alias checking disabled"); + + super.doStart(); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the resourceBase. + */ + public Resource getBaseResource() + { + if (_baseResource==null) + return null; + return _baseResource; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the base resource as a string. + */ + public String getResourceBase() + { + if (_baseResource==null) + return null; + return _baseResource.toString(); + } + + + /* ------------------------------------------------------------ */ + /** + * @param base The resourceBase to set. + */ + public void setBaseResource(Resource base) + { + _baseResource=base; + } + + /* ------------------------------------------------------------ */ + /** + * @param resourceBase The base resource as a string. + */ + public void setResourceBase(String resourceBase) + { + try + { + setBaseResource(Resource.newResource(resourceBase)); + } + catch (Exception e) + { + LOG.warn(e.toString()); + LOG.debug(e); + throw new IllegalArgumentException(resourceBase); + } + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the stylesheet as a Resource. + */ + public Resource getStylesheet() + { + if(_stylesheet != null) + { + return _stylesheet; + } + else + { + if(_defaultStylesheet == null) + { + try + { + _defaultStylesheet = Resource.newResource(this.getClass().getResource("/jetty-dir.css")); + } + catch(IOException e) + { + LOG.warn(e.toString()); + LOG.debug(e); + } + } + return _defaultStylesheet; + } + } + + /* ------------------------------------------------------------ */ + /** + * @param stylesheet The location of the stylesheet to be used as a String. + */ + public void setStylesheet(String stylesheet) + { + try + { + _stylesheet = Resource.newResource(stylesheet); + if(!_stylesheet.exists()) + { + LOG.warn("unable to find custom stylesheet: " + stylesheet); + _stylesheet = null; + } + } + catch(Exception e) + { + LOG.warn(e.toString()); + LOG.debug(e); + throw new IllegalArgumentException(stylesheet.toString()); + } + } + + /* ------------------------------------------------------------ */ + /** + * @return the cacheControl header to set on all static content. + */ + public String getCacheControl() + { + return _cacheControl.toString(); + } + + /* ------------------------------------------------------------ */ + /** + * @param cacheControl the cacheControl header to set on all static content. + */ + public void setCacheControl(String cacheControl) + { + _cacheControl=cacheControl==null?null:new ByteArrayBuffer(cacheControl); + } + + /* ------------------------------------------------------------ */ + /* + */ + public Resource getResource(String path) throws MalformedURLException + { + if (path==null || !path.startsWith("/")) + throw new MalformedURLException(path); + + Resource base = _baseResource; + if (base==null) + { + if (_context==null) + return null; + base=_context.getBaseResource(); + if (base==null) + return null; + } + + try + { + path=URIUtil.canonicalPath(path); + return base.addPath(path); + } + catch(Exception e) + { + LOG.ignore(e); + } + + return null; + } + + /* ------------------------------------------------------------ */ + protected Resource getResource(HttpServletRequest request) throws MalformedURLException + { + String servletPath; + String pathInfo; + Boolean included = request.getAttribute(Dispatcher.INCLUDE_REQUEST_URI) != null; + if (included != null && included.booleanValue()) + { + servletPath = (String)request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH); + pathInfo = (String)request.getAttribute(Dispatcher.INCLUDE_PATH_INFO); + + if (servletPath == null && pathInfo == null) + { + servletPath = request.getServletPath(); + pathInfo = request.getPathInfo(); + } + } + else + { + servletPath = request.getServletPath(); + pathInfo = request.getPathInfo(); + } + + String pathInContext=URIUtil.addPaths(servletPath,pathInfo); + return getResource(pathInContext); + } + + + /* ------------------------------------------------------------ */ + public String[] getWelcomeFiles() + { + return _welcomeFiles; + } + + /* ------------------------------------------------------------ */ + public void setWelcomeFiles(String[] welcomeFiles) + { + _welcomeFiles=welcomeFiles; + } + + /* ------------------------------------------------------------ */ + protected Resource getWelcome(Resource directory) throws MalformedURLException, IOException + { + for (int i=0;i<_welcomeFiles.length;i++) + { + Resource welcome=directory.addPath(_welcomeFiles[i]); + if (welcome.exists() && !welcome.isDirectory()) + return welcome; + } + + return null; + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int) + */ + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + if (baseRequest.isHandled()) + return; + + boolean skipContentBody = false; + + if(!HttpMethods.GET.equals(request.getMethod())) + { + if(!HttpMethods.HEAD.equals(request.getMethod())) + { + //try another handler + super.handle(target, baseRequest, request, response); + return; + } + skipContentBody = true; + } + + Resource resource = getResource(request); + + if (resource==null || !resource.exists()) + { + if (target.endsWith("/jetty-dir.css")) + { + resource = getStylesheet(); + if (resource==null) + return; + response.setContentType("text/css"); + } + else + { + //no resource - try other handlers + super.handle(target, baseRequest, request, response); + return; + } + } + + if (!_aliases && resource.getAlias()!=null) + { + LOG.info(resource+" aliased to "+resource.getAlias()); + return; + } + + // We are going to serve something + baseRequest.setHandled(true); + + if (resource.isDirectory()) + { + if (!request.getPathInfo().endsWith(URIUtil.SLASH)) + { + response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH))); + return; + } + + Resource welcome=getWelcome(resource); + if (welcome!=null && welcome.exists()) + resource=welcome; + else + { + doDirectory(request,response,resource); + baseRequest.setHandled(true); + return; + } + } + + // set some headers + long last_modified=resource.lastModified(); + String etag=null; + if (_etags) + { + // simple handling of only a single etag + String ifnm = request.getHeader(HttpHeaders.IF_NONE_MATCH); + etag=resource.getWeakETag(); + if (ifnm!=null && resource!=null && ifnm.equals(etag)) + { + response.setStatus(HttpStatus.NOT_MODIFIED_304); + baseRequest.getResponse().getHttpFields().put(HttpHeaders.ETAG_BUFFER,etag); + return; + } + } + + + if (last_modified>0) + { + long if_modified=request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE); + if (if_modified>0 && last_modified/1000<=if_modified/1000) + { + response.setStatus(HttpStatus.NOT_MODIFIED_304); + return; + } + } + + Buffer mime=_mimeTypes.getMimeByExtension(resource.toString()); + if (mime==null) + mime=_mimeTypes.getMimeByExtension(request.getPathInfo()); + + // set the headers + doResponseHeaders(response,resource,mime!=null?mime.toString():null); + response.setDateHeader(HttpHeaders.LAST_MODIFIED,last_modified); + if (_etags) + baseRequest.getResponse().getHttpFields().put(HttpHeaders.ETAG_BUFFER,etag); + + if(skipContentBody) + return; + // Send the content + OutputStream out =null; + try {out = response.getOutputStream();} + catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());} + + // See if a short direct method can be used? + if (out instanceof AbstractHttpConnection.Output) + { + // TODO file mapped buffers + ((AbstractHttpConnection.Output)out).sendContent(resource.getInputStream()); + } + else + { + // Write content normally + resource.writeTo(out,0,resource.length()); + } + } + + /* ------------------------------------------------------------ */ + protected void doDirectory(HttpServletRequest request,HttpServletResponse response, Resource resource) + throws IOException + { + if (_directory) + { + String listing = resource.getListHTML(request.getRequestURI(),request.getPathInfo().lastIndexOf("/") > 0); + response.setContentType("text/html; charset=UTF-8"); + response.getWriter().println(listing); + } + else + response.sendError(HttpStatus.FORBIDDEN_403); + } + + /* ------------------------------------------------------------ */ + /** Set the response headers. + * This method is called to set the response headers such as content type and content length. + * May be extended to add additional headers. + * @param response + * @param resource + * @param mimeType + */ + protected void doResponseHeaders(HttpServletResponse response, Resource resource, String mimeType) + { + if (mimeType!=null) + response.setContentType(mimeType); + + long length=resource.length(); + + if (response instanceof Response) + { + HttpFields fields = ((Response)response).getHttpFields(); + + if (length>0) + fields.putLongField(HttpHeaders.CONTENT_LENGTH_BUFFER,length); + + if (_cacheControl!=null) + fields.put(HttpHeaders.CACHE_CONTROL_BUFFER,_cacheControl); + } + else + { + if (length>0) + response.setHeader(HttpHeaders.CONTENT_LENGTH,Long.toString(length)); + + if (_cacheControl!=null) + response.setHeader(HttpHeaders.CACHE_CONTROL,_cacheControl.toString()); + } + + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/handler/ScopedHandler.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,193 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.Request; + + +/* ------------------------------------------------------------ */ +/** ScopedHandler. + * + * A ScopedHandler is a HandlerWrapper where the wrapped handlers + * each define a scope. When {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)} + * is called on the first ScopedHandler in a chain of HandlerWrappers, + * the {@link #doScope(String, Request, HttpServletRequest, HttpServletResponse)} method is + * called on all contained ScopedHandlers, before the + * {@link #doHandle(String, Request, HttpServletRequest, HttpServletResponse)} method + * is called on all contained handlers. + * + * <p>For example if Scoped handlers A, B & C were chained together, then + * the calling order would be:<pre> + * A.handle(...) + * A.doScope(...) + * B.doScope(...) + * C.doScope(...) + * A.doHandle(...) + * B.doHandle(...) + * C.doHandle(...) + * <pre> + * + * <p>If non scoped handler X was in the chained A, B, X & C, then + * the calling order would be:<pre> + * A.handle(...) + * A.doScope(...) + * B.doScope(...) + * C.doScope(...) + * A.doHandle(...) + * B.doHandle(...) + * X.handle(...) + * C.handle(...) + * C.doHandle(...) + * <pre> + * + * <p>A typical usage pattern is:<pre> + * private static class MyHandler extends ScopedHandler + * { + * public void doScope(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + * { + * try + * { + * setUpMyScope(); + * super.doScope(target,request,response); + * } + * finally + * { + * tearDownMyScope(); + * } + * } + * + * public void doHandle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + * { + * try + * { + * doMyHandling(); + * super.doHandle(target,request,response); + * } + * finally + * { + * cleanupMyHandling(); + * } + * } + * } + * </pre> + */ +public abstract class ScopedHandler extends HandlerWrapper +{ + private static final ThreadLocal<ScopedHandler> __outerScope= new ThreadLocal<ScopedHandler>(); + protected ScopedHandler _outerScope; + protected ScopedHandler _nextScope; + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStart() + */ + @Override + protected void doStart() throws Exception + { + try + { + _outerScope=__outerScope.get(); + if (_outerScope==null) + __outerScope.set(this); + + super.doStart(); + + _nextScope= (ScopedHandler)getChildHandlerByClass(ScopedHandler.class); + + } + finally + { + if (_outerScope==null) + __outerScope.set(null); + } + } + + + /* ------------------------------------------------------------ */ + /* + */ + @Override + public final void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + if (_outerScope==null) + doScope(target,baseRequest,request, response); + else + doHandle(target,baseRequest,request, response); + } + + /* ------------------------------------------------------------ */ + /* + * Scope the handler + */ + public abstract void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException; + + /* ------------------------------------------------------------ */ + /* + * Scope the handler + */ + public final void nextScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + // this method has been manually inlined in several locations, but + // is called protected by an if(never()), so your IDE can find those + // locations if this code is changed. + if (_nextScope!=null) + _nextScope.doScope(target,baseRequest,request, response); + else if (_outerScope!=null) + _outerScope.doHandle(target,baseRequest,request, response); + else + doHandle(target,baseRequest,request, response); + } + + /* ------------------------------------------------------------ */ + /* + * Do the handler work within the scope. + */ + public abstract void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException; + + /* ------------------------------------------------------------ */ + /* + * Do the handler work within the scope. + */ + public final void nextHandle(String target, final Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + // this method has been manually inlined in several locations, but + // is called protected by an if(never()), so your IDE can find those + // locations if this code is changed. + if (_nextScope!=null && _nextScope==_handler) + _nextScope.doHandle(target,baseRequest,request, response); + else if (_handler!=null) + _handler.handle(target,baseRequest, request, response); + } + + /* ------------------------------------------------------------ */ + protected boolean never() + { + return false; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/handler/ShutdownHandler.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,170 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/* ------------------------------------------------------------ */ +/** + * A handler that shuts the server down on a valid request. Used to do "soft" restarts from Java. If _exitJvm ist set to true a hard System.exit() call is being + * made. + * + * This handler is a contribution from Johannes Brodwall: https://bugs.eclipse.org/bugs/show_bug.cgi?id=357687 + * + * Usage: + * + * <pre> + Server server = new Server(8080); + HandlerList handlers = new HandlerList(); + handlers.setHandlers(new Handler[] + { someOtherHandler, new ShutdownHandler(server,"secret password") }); + server.setHandler(handlers); + server.start(); + </pre> + * + <pre> + public static void attemptShutdown(int port, String shutdownCookie) { + try { + URL url = new URL("http://localhost:" + port + "/shutdown?token=" + shutdownCookie); + HttpURLConnection connection = (HttpURLConnection)url.openConnection(); + connection.setRequestMethod("POST"); + connection.getResponseCode(); + logger.info("Shutting down " + url + ": " + connection.getResponseMessage()); + } catch (SocketException e) { + logger.debug("Not running"); + // Okay - the server is not running + } catch (IOException e) { + throw new RuntimeException(e); + } + } + </pre> + */ +public class ShutdownHandler extends AbstractHandler +{ + private static final Logger LOG = Log.getLogger(ShutdownHandler.class); + + private final String _shutdownToken; + + private final Server _server; + + private boolean _exitJvm = false; + + + + /** + * Creates a listener that lets the server be shut down remotely (but only from localhost). + * + * @param server + * the Jetty instance that should be shut down + * @param shutdownToken + * a secret password to avoid unauthorized shutdown attempts + */ + public ShutdownHandler(Server server, String shutdownToken) + { + this._server = server; + this._shutdownToken = shutdownToken; + } + + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + if (!target.equals("/shutdown")) + { + return; + } + + if (!request.getMethod().equals("POST")) + { + response.sendError(HttpServletResponse.SC_BAD_REQUEST); + return; + } + if (!hasCorrectSecurityToken(request)) + { + LOG.warn("Unauthorized shutdown attempt from " + getRemoteAddr(request)); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + return; + } + if (!requestFromLocalhost(request)) + { + LOG.warn("Unauthorized shutdown attempt from " + getRemoteAddr(request)); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + return; + } + + LOG.info("Shutting down by request from " + getRemoteAddr(request)); + + new Thread() + { + public void run () + { + try + { + shutdownServer(); + } + catch (InterruptedException e) + { + LOG.ignore(e); + } + catch (Exception e) + { + throw new RuntimeException("Shutting down server",e); + } + } + }.start(); + } + + private boolean requestFromLocalhost(HttpServletRequest request) + { + return "127.0.0.1".equals(getRemoteAddr(request)); + } + + protected String getRemoteAddr(HttpServletRequest request) + { + return request.getRemoteAddr(); + } + + private boolean hasCorrectSecurityToken(HttpServletRequest request) + { + return _shutdownToken.equals(request.getParameter("token")); + } + + private void shutdownServer() throws Exception + { + _server.stop(); + + if (_exitJvm) + { + System.exit(0); + } + } + + public void setExitJvm(boolean exitJvm) + { + this._exitJvm = exitJvm; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/handler/StatisticsHandler.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,474 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.continuation.Continuation; +import org.eclipse.jetty.continuation.ContinuationListener; +import org.eclipse.jetty.server.AsyncContinuation; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.statistic.CounterStatistic; +import org.eclipse.jetty.util.statistic.SampleStatistic; + +public class StatisticsHandler extends HandlerWrapper +{ + private final AtomicLong _statsStartedAt = new AtomicLong(); + + private final CounterStatistic _requestStats = new CounterStatistic(); + private final SampleStatistic _requestTimeStats = new SampleStatistic(); + private final CounterStatistic _dispatchedStats = new CounterStatistic(); + private final SampleStatistic _dispatchedTimeStats = new SampleStatistic(); + private final CounterStatistic _suspendStats = new CounterStatistic(); + + private final AtomicInteger _resumes = new AtomicInteger(); + private final AtomicInteger _expires = new AtomicInteger(); + + private final AtomicInteger _responses1xx = new AtomicInteger(); + private final AtomicInteger _responses2xx = new AtomicInteger(); + private final AtomicInteger _responses3xx = new AtomicInteger(); + private final AtomicInteger _responses4xx = new AtomicInteger(); + private final AtomicInteger _responses5xx = new AtomicInteger(); + private final AtomicLong _responsesTotalBytes = new AtomicLong(); + + private final ContinuationListener _onCompletion = new ContinuationListener() + { + public void onComplete(Continuation continuation) + { + final Request request = ((AsyncContinuation)continuation).getBaseRequest(); + final long elapsed = System.currentTimeMillis()-request.getTimeStamp(); + + _requestStats.decrement(); + _requestTimeStats.set(elapsed); + + updateResponse(request); + + if (!continuation.isResumed()) + _suspendStats.decrement(); + } + + public void onTimeout(Continuation continuation) + { + _expires.incrementAndGet(); + } + }; + + /** + * Resets the current request statistics. + */ + public void statsReset() + { + _statsStartedAt.set(System.currentTimeMillis()); + + _requestStats.reset(); + _requestTimeStats.reset(); + _dispatchedStats.reset(); + _dispatchedTimeStats.reset(); + _suspendStats.reset(); + + _resumes.set(0); + _expires.set(0); + _responses1xx.set(0); + _responses2xx.set(0); + _responses3xx.set(0); + _responses4xx.set(0); + _responses5xx.set(0); + _responsesTotalBytes.set(0L); + } + + @Override + public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException + { + _dispatchedStats.increment(); + + final long start; + AsyncContinuation continuation = request.getAsyncContinuation(); + if (continuation.isInitial()) + { + // new request + _requestStats.increment(); + start = request.getTimeStamp(); + } + else + { + // resumed request + start = System.currentTimeMillis(); + _suspendStats.decrement(); + if (continuation.isResumed()) + _resumes.incrementAndGet(); + } + + try + { + super.handle(path, request, httpRequest, httpResponse); + } + finally + { + final long now = System.currentTimeMillis(); + final long dispatched=now-start; + + _dispatchedStats.decrement(); + _dispatchedTimeStats.set(dispatched); + + if (continuation.isSuspended()) + { + if (continuation.isInitial()) + continuation.addContinuationListener(_onCompletion); + _suspendStats.increment(); + } + else if (continuation.isInitial()) + { + _requestStats.decrement(); + _requestTimeStats.set(dispatched); + updateResponse(request); + } + // else onCompletion will handle it. + } + } + + private void updateResponse(Request request) + { + Response response = request.getResponse(); + switch (response.getStatus() / 100) + { + case 1: + _responses1xx.incrementAndGet(); + break; + case 2: + _responses2xx.incrementAndGet(); + break; + case 3: + _responses3xx.incrementAndGet(); + break; + case 4: + _responses4xx.incrementAndGet(); + break; + case 5: + _responses5xx.incrementAndGet(); + break; + default: + break; + } + _responsesTotalBytes.addAndGet(response.getContentCount()); + } + + @Override + protected void doStart() throws Exception + { + super.doStart(); + statsReset(); + } + + /** + * @return the number of requests handled by this handler + * since {@link #statsReset()} was last called, excluding + * active requests + * @see #getResumes() + */ + public int getRequests() + { + return (int)_requestStats.getTotal(); + } + + /** + * @return the number of requests currently active. + * since {@link #statsReset()} was last called. + */ + public int getRequestsActive() + { + return (int)_requestStats.getCurrent(); + } + + /** + * @return the maximum number of active requests + * since {@link #statsReset()} was last called. + */ + public int getRequestsActiveMax() + { + return (int)_requestStats.getMax(); + } + + /** + * @return the maximum time (in milliseconds) of request handling + * since {@link #statsReset()} was last called. + */ + public long getRequestTimeMax() + { + return _requestTimeStats.getMax(); + } + + /** + * @return the total time (in milliseconds) of requests handling + * since {@link #statsReset()} was last called. + */ + public long getRequestTimeTotal() + { + return _requestTimeStats.getTotal(); + } + + /** + * @return the mean time (in milliseconds) of request handling + * since {@link #statsReset()} was last called. + * @see #getRequestTimeTotal() + * @see #getRequests() + */ + public double getRequestTimeMean() + { + return _requestTimeStats.getMean(); + } + + /** + * @return the standard deviation of time (in milliseconds) of request handling + * since {@link #statsReset()} was last called. + * @see #getRequestTimeTotal() + * @see #getRequests() + */ + public double getRequestTimeStdDev() + { + return _requestTimeStats.getStdDev(); + } + + /** + * @return the number of dispatches seen by this handler + * since {@link #statsReset()} was last called, excluding + * active dispatches + */ + public int getDispatched() + { + return (int)_dispatchedStats.getTotal(); + } + + /** + * @return the number of dispatches currently in this handler + * since {@link #statsReset()} was last called, including + * resumed requests + */ + public int getDispatchedActive() + { + return (int)_dispatchedStats.getCurrent(); + } + + /** + * @return the max number of dispatches currently in this handler + * since {@link #statsReset()} was last called, including + * resumed requests + */ + public int getDispatchedActiveMax() + { + return (int)_dispatchedStats.getMax(); + } + + /** + * @return the maximum time (in milliseconds) of request dispatch + * since {@link #statsReset()} was last called. + */ + public long getDispatchedTimeMax() + { + return _dispatchedTimeStats.getMax(); + } + + /** + * @return the total time (in milliseconds) of requests handling + * since {@link #statsReset()} was last called. + */ + public long getDispatchedTimeTotal() + { + return _dispatchedTimeStats.getTotal(); + } + + /** + * @return the mean time (in milliseconds) of request handling + * since {@link #statsReset()} was last called. + * @see #getRequestTimeTotal() + * @see #getRequests() + */ + public double getDispatchedTimeMean() + { + return _dispatchedTimeStats.getMean(); + } + + /** + * @return the standard deviation of time (in milliseconds) of request handling + * since {@link #statsReset()} was last called. + * @see #getRequestTimeTotal() + * @see #getRequests() + */ + public double getDispatchedTimeStdDev() + { + return _dispatchedTimeStats.getStdDev(); + } + + /** + * @return the number of requests handled by this handler + * since {@link #statsReset()} was last called, including + * resumed requests + * @see #getResumes() + */ + public int getSuspends() + { + return (int)_suspendStats.getTotal(); + } + + /** + * @return the number of requests currently suspended. + * since {@link #statsReset()} was last called. + */ + public int getSuspendsActive() + { + return (int)_suspendStats.getCurrent(); + } + + /** + * @return the maximum number of current suspended requests + * since {@link #statsReset()} was last called. + */ + public int getSuspendsActiveMax() + { + return (int)_suspendStats.getMax(); + } + + /** + * @return the number of requests that have been resumed + * @see #getExpires() + */ + public int getResumes() + { + return _resumes.get(); + } + + /** + * @return the number of requests that expired while suspended. + * @see #getResumes() + */ + public int getExpires() + { + return _expires.get(); + } + + /** + * @return the number of responses with a 1xx status returned by this context + * since {@link #statsReset()} was last called. + */ + public int getResponses1xx() + { + return _responses1xx.get(); + } + + /** + * @return the number of responses with a 2xx status returned by this context + * since {@link #statsReset()} was last called. + */ + public int getResponses2xx() + { + return _responses2xx.get(); + } + + /** + * @return the number of responses with a 3xx status returned by this context + * since {@link #statsReset()} was last called. + */ + public int getResponses3xx() + { + return _responses3xx.get(); + } + + /** + * @return the number of responses with a 4xx status returned by this context + * since {@link #statsReset()} was last called. + */ + public int getResponses4xx() + { + return _responses4xx.get(); + } + + /** + * @return the number of responses with a 5xx status returned by this context + * since {@link #statsReset()} was last called. + */ + public int getResponses5xx() + { + return _responses5xx.get(); + } + + /** + * @return the milliseconds since the statistics were started with {@link #statsReset()}. + */ + public long getStatsOnMs() + { + return System.currentTimeMillis() - _statsStartedAt.get(); + } + + /** + * @return the total bytes of content sent in responses + */ + public long getResponsesBytesTotal() + { + return _responsesTotalBytes.get(); + } + + public String toStatsHTML() + { + StringBuilder sb = new StringBuilder(); + + sb.append("<h1>Statistics:</h1>\n"); + sb.append("Statistics gathering started ").append(getStatsOnMs()).append("ms ago").append("<br />\n"); + + sb.append("<h2>Requests:</h2>\n"); + sb.append("Total requests: ").append(getRequests()).append("<br />\n"); + sb.append("Active requests: ").append(getRequestsActive()).append("<br />\n"); + sb.append("Max active requests: ").append(getRequestsActiveMax()).append("<br />\n"); + sb.append("Total requests time: ").append(getRequestTimeTotal()).append("<br />\n"); + sb.append("Mean request time: ").append(getRequestTimeMean()).append("<br />\n"); + sb.append("Max request time: ").append(getRequestTimeMax()).append("<br />\n"); + sb.append("Request time standard deviation: ").append(getRequestTimeStdDev()).append("<br />\n"); + + + sb.append("<h2>Dispatches:</h2>\n"); + sb.append("Total dispatched: ").append(getDispatched()).append("<br />\n"); + sb.append("Active dispatched: ").append(getDispatchedActive()).append("<br />\n"); + sb.append("Max active dispatched: ").append(getDispatchedActiveMax()).append("<br />\n"); + sb.append("Total dispatched time: ").append(getDispatchedTimeTotal()).append("<br />\n"); + sb.append("Mean dispatched time: ").append(getDispatchedTimeMean()).append("<br />\n"); + sb.append("Max dispatched time: ").append(getDispatchedTimeMax()).append("<br />\n"); + sb.append("Dispatched time standard deviation: ").append(getDispatchedTimeStdDev()).append("<br />\n"); + + + sb.append("Total requests suspended: ").append(getSuspends()).append("<br />\n"); + sb.append("Total requests expired: ").append(getExpires()).append("<br />\n"); + sb.append("Total requests resumed: ").append(getResumes()).append("<br />\n"); + + sb.append("<h2>Responses:</h2>\n"); + sb.append("1xx responses: ").append(getResponses1xx()).append("<br />\n"); + sb.append("2xx responses: ").append(getResponses2xx()).append("<br />\n"); + sb.append("3xx responses: ").append(getResponses3xx()).append("<br />\n"); + sb.append("4xx responses: ").append(getResponses4xx()).append("<br />\n"); + sb.append("5xx responses: ").append(getResponses5xx()).append("<br />\n"); + sb.append("Bytes sent total: ").append(getResponsesBytesTotal()).append("<br />\n"); + + return sb.toString(); + + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/nio/AbstractNIOConnector.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,53 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +/** + * + */ +package org.eclipse.jetty.server.nio; + +import org.eclipse.jetty.io.Buffers.Type; +import org.eclipse.jetty.server.AbstractConnector; + +public abstract class AbstractNIOConnector extends AbstractConnector implements NIOConnector +{ + public AbstractNIOConnector() + { + _buffers.setRequestBufferType(Type.DIRECT); + _buffers.setRequestHeaderType(Type.INDIRECT); + _buffers.setResponseBufferType(Type.DIRECT); + _buffers.setResponseHeaderType(Type.INDIRECT); + } + + /* ------------------------------------------------------------------------------- */ + public boolean getUseDirectBuffers() + { + return getRequestBufferType()==Type.DIRECT; + } + + /* ------------------------------------------------------------------------------- */ + /** + * @param direct If True (the default), the connector can use NIO direct buffers. + * Some JVMs have memory management issues (bugs) with direct buffers. + */ + public void setUseDirectBuffers(boolean direct) + { + _buffers.setRequestBufferType(direct?Type.DIRECT:Type.INDIRECT); + _buffers.setResponseBufferType(direct?Type.DIRECT:Type.INDIRECT); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/nio/BlockingChannelConnector.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,366 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.nio; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.channels.ByteChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.util.Set; + +import org.eclipse.jetty.http.HttpException; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.ConnectedEndPoint; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.io.nio.ChannelEndPoint; +import org.eclipse.jetty.server.BlockingHttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.ConcurrentHashSet; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + + +/* ------------------------------------------------------------------------------- */ +/** Blocking NIO connector. + * This connector uses efficient NIO buffers with a traditional blocking thread model. + * Direct NIO buffers are used and a thread is allocated per connections. + * + * This connector is best used when there are a few very active connections. + * + * @org.apache.xbean.XBean element="blockingNioConnector" description="Creates a blocking NIO based socket connector" + * + * + * + */ +public class BlockingChannelConnector extends AbstractNIOConnector +{ + private static final Logger LOG = Log.getLogger(BlockingChannelConnector.class); + + private transient ServerSocketChannel _acceptChannel; + private final Set<BlockingChannelEndPoint> _endpoints = new ConcurrentHashSet<BlockingChannelEndPoint>(); + + + /* ------------------------------------------------------------ */ + /** Constructor. + * + */ + public BlockingChannelConnector() + { + } + + /* ------------------------------------------------------------ */ + public Object getConnection() + { + return _acceptChannel; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.AbstractConnector#doStart() + */ + @Override + protected void doStart() throws Exception + { + super.doStart(); + getThreadPool().dispatch(new Runnable() + { + + public void run() + { + while (isRunning()) + { + try + { + Thread.sleep(400); + long now=System.currentTimeMillis(); + for (BlockingChannelEndPoint endp : _endpoints) + { + endp.checkIdleTimestamp(now); + } + } + catch(InterruptedException e) + { + LOG.ignore(e); + } + catch(Exception e) + { + LOG.warn(e); + } + } + } + + }); + + } + + + /* ------------------------------------------------------------ */ + public void open() throws IOException + { + // Create a new server socket and set to non blocking mode + _acceptChannel= ServerSocketChannel.open(); + _acceptChannel.configureBlocking(true); + + // Bind the server socket to the local host and port + InetSocketAddress addr = getHost()==null?new InetSocketAddress(getPort()):new InetSocketAddress(getHost(),getPort()); + _acceptChannel.socket().bind(addr,getAcceptQueueSize()); + } + + /* ------------------------------------------------------------ */ + public void close() throws IOException + { + if (_acceptChannel != null) + _acceptChannel.close(); + _acceptChannel=null; + } + + /* ------------------------------------------------------------ */ + @Override + public void accept(int acceptorID) + throws IOException, InterruptedException + { + SocketChannel channel = _acceptChannel.accept(); + channel.configureBlocking(true); + Socket socket=channel.socket(); + configure(socket); + + BlockingChannelEndPoint connection=new BlockingChannelEndPoint(channel); + connection.dispatch(); + } + + /* ------------------------------------------------------------------------------- */ + @Override + public void customize(EndPoint endpoint, Request request) + throws IOException + { + super.customize(endpoint, request); + endpoint.setMaxIdleTime(_maxIdleTime); + configure(((SocketChannel)endpoint.getTransport()).socket()); + } + + + /* ------------------------------------------------------------------------------- */ + public int getLocalPort() + { + if (_acceptChannel==null || !_acceptChannel.isOpen()) + return -1; + return _acceptChannel.socket().getLocalPort(); + } + + /* ------------------------------------------------------------------------------- */ + /* ------------------------------------------------------------------------------- */ + /* ------------------------------------------------------------------------------- */ + private class BlockingChannelEndPoint extends ChannelEndPoint implements Runnable, ConnectedEndPoint + { + private Connection _connection; + private int _timeout; + private volatile long _idleTimestamp; + + BlockingChannelEndPoint(ByteChannel channel) + throws IOException + { + super(channel,BlockingChannelConnector.this._maxIdleTime); + _connection = new BlockingHttpConnection(BlockingChannelConnector.this,this,getServer()); + } + + /* ------------------------------------------------------------ */ + /** Get the connection. + * @return the connection + */ + public Connection getConnection() + { + return _connection; + } + + /* ------------------------------------------------------------ */ + public void setConnection(Connection connection) + { + _connection=connection; + } + + /* ------------------------------------------------------------ */ + public void checkIdleTimestamp(long now) + { + if (_idleTimestamp!=0 && _timeout>0 && now>(_idleTimestamp+_timeout)) + { + idleExpired(); + } + } + + /* ------------------------------------------------------------ */ + protected void idleExpired() + { + try + { + super.close(); + } + catch (IOException e) + { + LOG.ignore(e); + } + } + + /* ------------------------------------------------------------ */ + void dispatch() throws IOException + { + if (!getThreadPool().dispatch(this)) + { + LOG.warn("dispatch failed for {}",_connection); + super.close(); + } + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.io.nio.ChannelEndPoint#fill(org.eclipse.jetty.io.Buffer) + */ + @Override + public int fill(Buffer buffer) throws IOException + { + _idleTimestamp=System.currentTimeMillis(); + return super.fill(buffer); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.io.nio.ChannelEndPoint#flush(org.eclipse.jetty.io.Buffer) + */ + @Override + public int flush(Buffer buffer) throws IOException + { + _idleTimestamp=System.currentTimeMillis(); + return super.flush(buffer); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.io.nio.ChannelEndPoint#flush(org.eclipse.jetty.io.Buffer, org.eclipse.jetty.io.Buffer, org.eclipse.jetty.io.Buffer) + */ + @Override + public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException + { + _idleTimestamp=System.currentTimeMillis(); + return super.flush(header,buffer,trailer); + } + + /* ------------------------------------------------------------ */ + public void run() + { + try + { + _timeout=getMaxIdleTime(); + connectionOpened(_connection); + _endpoints.add(this); + + while (isOpen()) + { + _idleTimestamp=System.currentTimeMillis(); + if (_connection.isIdle()) + { + if (getServer().getThreadPool().isLowOnThreads()) + { + int lrmit = getLowResourcesMaxIdleTime(); + if (lrmit>=0 && _timeout!= lrmit) + { + _timeout=lrmit; + } + } + } + else + { + if (_timeout!=getMaxIdleTime()) + { + _timeout=getMaxIdleTime(); + } + } + + _connection = _connection.handle(); + + } + } + catch (EofException e) + { + LOG.debug("EOF", e); + try{BlockingChannelEndPoint.this.close();} + catch(IOException e2){LOG.ignore(e2);} + } + catch (HttpException e) + { + LOG.debug("BAD", e); + try{super.close();} + catch(IOException e2){LOG.ignore(e2);} + } + catch(Throwable e) + { + LOG.warn("handle failed",e); + try{super.close();} + catch(IOException e2){LOG.ignore(e2);} + } + finally + { + connectionClosed(_connection); + _endpoints.remove(this); + + // wait for client to close, but if not, close ourselves. + try + { + if (!_socket.isClosed()) + { + long timestamp=System.currentTimeMillis(); + int max_idle=getMaxIdleTime(); + + _socket.setSoTimeout(getMaxIdleTime()); + int c=0; + do + { + c = _socket.getInputStream().read(); + } + while (c>=0 && (System.currentTimeMillis()-timestamp)<max_idle); + if (!_socket.isClosed()) + _socket.close(); + } + } + catch(IOException e) + { + LOG.ignore(e); + } + } + } + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + return String.format("BCEP@%x{l(%s)<->r(%s),open=%b,ishut=%b,oshut=%b}-{%s}", + hashCode(), + _socket.getRemoteSocketAddress(), + _socket.getLocalSocketAddress(), + isOpen(), + isInputShutdown(), + isOutputShutdown(), + _connection); + } + + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/nio/InheritedChannelConnector.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,75 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.nio; + +import java.io.IOException; +import java.nio.channels.Channel; +import java.nio.channels.ServerSocketChannel; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + * An implementation of the SelectChannelConnector which first tries to + * inherit from a channel provided by the system. If there is no inherited + * channel available, or if the inherited channel provided not usable, then + * it will fall back upon normal ServerSocketChannel creation. + * <p> + * Note that System.inheritedChannel() is only available from Java 1.5 onwards. + * Trying to use this class under Java 1.4 will be the same as using a normal + * SelectChannelConnector. + * <p> + * Use it with xinetd/inetd, to launch an instance of Jetty on demand. The port + * used to access pages on the Jetty instance is the same as the port used to + * launch Jetty. + * + * @author athena + */ +public class InheritedChannelConnector extends SelectChannelConnector +{ + private static final Logger LOG = Log.getLogger(InheritedChannelConnector.class); + + /* ------------------------------------------------------------ */ + @Override + public void open() throws IOException + { + synchronized(this) + { + try + { + Channel channel = System.inheritedChannel(); + if ( channel instanceof ServerSocketChannel ) + _acceptChannel = (ServerSocketChannel)channel; + else + LOG.warn("Unable to use System.inheritedChannel() [" +channel+ "]. Trying a new ServerSocketChannel at " + getHost() + ":" + getPort()); + + if ( _acceptChannel != null ) + _acceptChannel.configureBlocking(true); + } + catch(NoSuchMethodError e) + { + LOG.warn("Need at least Java 5 to use socket inherited from xinetd/inetd."); + } + + if (_acceptChannel == null) + super.open(); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/nio/NIOConnector.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,31 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.nio; + +/** + * NIOConnector. + * A marker interface that indicates that NIOBuffers can be handled (efficiently) by this Connector. + * + * + * + */ +public interface NIOConnector +{ + boolean getUseDirectBuffers(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/nio/NetworkTrafficSelectChannelConnector.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,73 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.nio; + +import java.io.IOException; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.util.ConcurrentModificationException; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.eclipse.jetty.io.NetworkTrafficListener; +import org.eclipse.jetty.io.nio.NetworkTrafficSelectChannelEndPoint; +import org.eclipse.jetty.io.nio.SelectChannelEndPoint; +import org.eclipse.jetty.io.nio.SelectorManager; + +/** + * <p>A specialized version of {@link SelectChannelConnector} that supports {@link NetworkTrafficListener}s.</p> + * <p>{@link NetworkTrafficListener}s can be added and removed dynamically before and after this connector has + * been started without causing {@link ConcurrentModificationException}s.</p> + */ +public class NetworkTrafficSelectChannelConnector extends SelectChannelConnector +{ + private final List<NetworkTrafficListener> listeners = new CopyOnWriteArrayList<NetworkTrafficListener>(); + + /** + * @param listener the listener to add + */ + public void addNetworkTrafficListener(NetworkTrafficListener listener) + { + listeners.add(listener); + } + + /** + * @param listener the listener to remove + */ + public void removeNetworkTrafficListener(NetworkTrafficListener listener) + { + listeners.remove(listener); + } + + @Override + protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectorManager.SelectSet selectSet, SelectionKey key) throws IOException + { + NetworkTrafficSelectChannelEndPoint endPoint = new NetworkTrafficSelectChannelEndPoint(channel, selectSet, key, _maxIdleTime, listeners); + endPoint.setConnection(selectSet.getManager().newConnection(channel,endPoint, key.attachment())); + endPoint.notifyOpened(); + return endPoint; + } + + @Override + protected void endPointClosed(SelectChannelEndPoint endpoint) + { + super.endPointClosed(endpoint); + ((NetworkTrafficSelectChannelEndPoint)endpoint).notifyClosed(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/nio/SelectChannelConnector.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,334 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.nio; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.channels.SelectionKey; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; + +import org.eclipse.jetty.continuation.Continuation; +import org.eclipse.jetty.io.AsyncEndPoint; +import org.eclipse.jetty.io.ConnectedEndPoint; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.nio.AsyncConnection; +import org.eclipse.jetty.io.nio.SelectChannelEndPoint; +import org.eclipse.jetty.io.nio.SelectorManager; +import org.eclipse.jetty.io.nio.SelectorManager.SelectSet; +import org.eclipse.jetty.server.AsyncHttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.thread.ThreadPool; + +/* ------------------------------------------------------------------------------- */ +/** + * Selecting NIO connector. + * <p> + * This connector uses efficient NIO buffers with a non blocking threading model. Direct NIO buffers + * are used and threads are only allocated to connections with requests. Synchronization is used to + * simulate blocking for the servlet API, and any unflushed content at the end of request handling + * is written asynchronously. + * </p> + * <p> + * This connector is best used when there are a many connections that have idle periods. + * </p> + * <p> + * When used with {@link org.eclipse.jetty.continuation.Continuation}, threadless waits are supported. + * If a filter or servlet returns after calling {@link Continuation#suspend()} or when a + * runtime exception is thrown from a call to {@link Continuation#undispatch()}, Jetty will + * will not send a response to the client. Instead the thread is released and the Continuation is + * placed on the timer queue. If the Continuation timeout expires, or it's + * resume method is called, then the request is again allocated a thread and the request is retried. + * The limitation of this approach is that request content is not available on the retried request, + * thus if possible it should be read after the continuation or saved as a request attribute or as the + * associated object of the Continuation instance. + * </p> + * + * @org.apache.xbean.XBean element="nioConnector" description="Creates an NIO based socket connector" + */ +public class SelectChannelConnector extends AbstractNIOConnector +{ + protected ServerSocketChannel _acceptChannel; + private int _lowResourcesConnections; + private int _lowResourcesMaxIdleTime; + private int _localPort=-1; + + private final SelectorManager _manager = new ConnectorSelectorManager(); + + /* ------------------------------------------------------------------------------- */ + /** + * Constructor. + * + */ + public SelectChannelConnector() + { + _manager.setMaxIdleTime(getMaxIdleTime()); + addBean(_manager,true); + setAcceptors(Math.max(1,(Runtime.getRuntime().availableProcessors()+3)/4)); + } + + @Override + public void setThreadPool(ThreadPool pool) + { + super.setThreadPool(pool); + // preserve start order + removeBean(_manager); + addBean(_manager,true); + } + + /* ------------------------------------------------------------ */ + @Override + public void accept(int acceptorID) throws IOException + { + ServerSocketChannel server; + synchronized(this) + { + server = _acceptChannel; + } + + if (server!=null && server.isOpen() && _manager.isStarted()) + { + SocketChannel channel = server.accept(); + channel.configureBlocking(false); + Socket socket = channel.socket(); + configure(socket); + _manager.register(channel); + } + } + + /* ------------------------------------------------------------ */ + public void close() throws IOException + { + synchronized(this) + { + if (_acceptChannel != null) + { + removeBean(_acceptChannel); + if (_acceptChannel.isOpen()) + _acceptChannel.close(); + } + _acceptChannel = null; + _localPort=-2; + } + } + + /* ------------------------------------------------------------------------------- */ + @Override + public void customize(EndPoint endpoint, Request request) throws IOException + { + request.setTimeStamp(System.currentTimeMillis()); + endpoint.setMaxIdleTime(_maxIdleTime); + super.customize(endpoint, request); + } + + /* ------------------------------------------------------------------------------- */ + @Override + public void persist(EndPoint endpoint) throws IOException + { + AsyncEndPoint aEndp = ((AsyncEndPoint)endpoint); + aEndp.setCheckForIdle(true); + super.persist(endpoint); + } + + /* ------------------------------------------------------------ */ + public SelectorManager getSelectorManager() + { + return _manager; + } + + /* ------------------------------------------------------------ */ + public synchronized Object getConnection() + { + return _acceptChannel; + } + + /* ------------------------------------------------------------------------------- */ + public int getLocalPort() + { + synchronized(this) + { + return _localPort; + } + } + + /* ------------------------------------------------------------ */ + public void open() throws IOException + { + synchronized(this) + { + if (_acceptChannel == null) + { + // Create a new server socket + _acceptChannel = ServerSocketChannel.open(); + // Set to blocking mode + _acceptChannel.configureBlocking(true); + + // Bind the server socket to the local host and port + _acceptChannel.socket().setReuseAddress(getReuseAddress()); + InetSocketAddress addr = getHost()==null?new InetSocketAddress(getPort()):new InetSocketAddress(getHost(),getPort()); + _acceptChannel.socket().bind(addr,getAcceptQueueSize()); + + _localPort=_acceptChannel.socket().getLocalPort(); + if (_localPort<=0) + throw new IOException("Server channel not bound"); + + addBean(_acceptChannel); + } + } + } + + /* ------------------------------------------------------------ */ + @Override + public void setMaxIdleTime(int maxIdleTime) + { + _manager.setMaxIdleTime(maxIdleTime); + super.setMaxIdleTime(maxIdleTime); + } + + /* ------------------------------------------------------------ */ + /** + * @return the lowResourcesConnections + */ + public int getLowResourcesConnections() + { + return _lowResourcesConnections; + } + + /* ------------------------------------------------------------ */ + /** + * Set the number of connections, which if exceeded places this manager in low resources state. + * This is not an exact measure as the connection count is averaged over the select sets. + * @param lowResourcesConnections the number of connections + * @see #setLowResourcesMaxIdleTime(int) + */ + public void setLowResourcesConnections(int lowResourcesConnections) + { + _lowResourcesConnections=lowResourcesConnections; + } + + /* ------------------------------------------------------------ */ + /** + * @return the lowResourcesMaxIdleTime + */ + @Override + public int getLowResourcesMaxIdleTime() + { + return _lowResourcesMaxIdleTime; + } + + /* ------------------------------------------------------------ */ + /** + * Set the period in ms that a connection is allowed to be idle when this there are more + * than {@link #getLowResourcesConnections()} connections. This allows the server to rapidly close idle connections + * in order to gracefully handle high load situations. + * @param lowResourcesMaxIdleTime the period in ms that a connection is allowed to be idle when resources are low. + * @see #setMaxIdleTime(int) + */ + @Override + public void setLowResourcesMaxIdleTime(int lowResourcesMaxIdleTime) + { + _lowResourcesMaxIdleTime=lowResourcesMaxIdleTime; + super.setLowResourcesMaxIdleTime(lowResourcesMaxIdleTime); + } + + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.server.AbstractConnector#doStart() + */ + @Override + protected void doStart() throws Exception + { + _manager.setSelectSets(getAcceptors()); + _manager.setMaxIdleTime(getMaxIdleTime()); + _manager.setLowResourcesConnections(getLowResourcesConnections()); + _manager.setLowResourcesMaxIdleTime(getLowResourcesMaxIdleTime()); + + super.doStart(); + } + + /* ------------------------------------------------------------ */ + protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException + { + SelectChannelEndPoint endp= new SelectChannelEndPoint(channel,selectSet,key, SelectChannelConnector.this._maxIdleTime); + endp.setConnection(selectSet.getManager().newConnection(channel,endp, key.attachment())); + return endp; + } + + /* ------------------------------------------------------------------------------- */ + protected void endPointClosed(SelectChannelEndPoint endpoint) + { + connectionClosed(endpoint.getConnection()); + } + + /* ------------------------------------------------------------------------------- */ + protected AsyncConnection newConnection(SocketChannel channel,final AsyncEndPoint endpoint) + { + return new AsyncHttpConnection(SelectChannelConnector.this,endpoint,getServer()); + } + + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + private final class ConnectorSelectorManager extends SelectorManager + { + @Override + public boolean dispatch(Runnable task) + { + ThreadPool pool=getThreadPool(); + if (pool==null) + pool=getServer().getThreadPool(); + return pool.dispatch(task); + } + + @Override + protected void endPointClosed(final SelectChannelEndPoint endpoint) + { + SelectChannelConnector.this.endPointClosed(endpoint); + } + + @Override + protected void endPointOpened(SelectChannelEndPoint endpoint) + { + // TODO handle max connections and low resources + connectionOpened(endpoint.getConnection()); + } + + @Override + protected void endPointUpgraded(ConnectedEndPoint endpoint, Connection oldConnection) + { + connectionUpgraded(oldConnection,endpoint.getConnection()); + } + + @Override + public AsyncConnection newConnection(SocketChannel channel,AsyncEndPoint endpoint, Object attachment) + { + return SelectChannelConnector.this.newConnection(channel,endpoint); + } + + @Override + protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey sKey) throws IOException + { + return SelectChannelConnector.this.newEndPoint(channel,selectSet,sKey); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/nio/jmx/SelectChannelConnector-mbean.properties Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,2 @@ +SelectChannelConnector: HTTP connector using NIO ByteChannels and Selectors +lowResourcesConnections: The number of connections, which if exceeded represents low resources
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/session/AbstractSession.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,568 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.session; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSessionActivationListener; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionBindingListener; +import javax.servlet.http.HttpSessionContext; +import javax.servlet.http.HttpSessionEvent; + +import org.eclipse.jetty.util.log.Logger; + +/** + * + * <p> + * Implements {@link javax.servlet.http.HttpSession} from the <code>javax.servlet</code> package. + * </p> + * + */ +@SuppressWarnings("deprecation") +public abstract class AbstractSession implements AbstractSessionManager.SessionIf +{ + final static Logger LOG = SessionHandler.LOG; + + private final AbstractSessionManager _manager; + private final String _clusterId; // ID unique within cluster + private final String _nodeId; // ID unique within node + private final Map<String,Object> _attributes=new HashMap<String, Object>(); + private boolean _idChanged; + private final long _created; + private long _cookieSet; + private long _accessed; // the time of the last access + private long _lastAccessed; // the time of the last access excluding this one + private boolean _invalid; + private boolean _doInvalidate; + private long _maxIdleMs; + private boolean _newSession; + private int _requests; + + + + /* ------------------------------------------------------------- */ + protected AbstractSession(AbstractSessionManager abstractSessionManager, HttpServletRequest request) + { + _manager = abstractSessionManager; + + _newSession=true; + _created=System.currentTimeMillis(); + _clusterId=_manager._sessionIdManager.newSessionId(request,_created); + _nodeId=_manager._sessionIdManager.getNodeId(_clusterId,request); + _accessed=_created; + _lastAccessed=_created; + _requests=1; + _maxIdleMs=_manager._dftMaxIdleSecs>0?_manager._dftMaxIdleSecs*1000L:-1; + if (LOG.isDebugEnabled()) + LOG.debug("new session & id "+_nodeId+" "+_clusterId); + } + + /* ------------------------------------------------------------- */ + protected AbstractSession(AbstractSessionManager abstractSessionManager, long created, long accessed, String clusterId) + { + _manager = abstractSessionManager; + _created=created; + _clusterId=clusterId; + _nodeId=_manager._sessionIdManager.getNodeId(_clusterId,null); + _accessed=accessed; + _lastAccessed=accessed; + _requests=1; + _maxIdleMs=_manager._dftMaxIdleSecs>0?_manager._dftMaxIdleSecs*1000L:-1; + if (LOG.isDebugEnabled()) + LOG.debug("new session "+_nodeId+" "+_clusterId); + } + + /* ------------------------------------------------------------- */ + /** + * asserts that the session is valid + */ + protected void checkValid() throws IllegalStateException + { + if (_invalid) + throw new IllegalStateException(); + } + + /* ------------------------------------------------------------- */ + public AbstractSession getSession() + { + return this; + } + + /* ------------------------------------------------------------- */ + public long getAccessed() + { + synchronized (this) + { + return _accessed; + } + } + + /* ------------------------------------------------------------ */ + public Object getAttribute(String name) + { + synchronized (this) + { + checkValid(); + return _attributes.get(name); + } + } + + /* ------------------------------------------------------------ */ + public int getAttributes() + { + synchronized (this) + { + checkValid(); + return _attributes.size(); + } + } + + /* ------------------------------------------------------------ */ + @SuppressWarnings({ "unchecked" }) + public Enumeration<String> getAttributeNames() + { + synchronized (this) + { + checkValid(); + List<String> names=_attributes==null?Collections.EMPTY_LIST:new ArrayList<String>(_attributes.keySet()); + return Collections.enumeration(names); + } + } + + /* ------------------------------------------------------------ */ + public Set<String> getNames() + { + synchronized (this) + { + return new HashSet<String>(_attributes.keySet()); + } + } + + /* ------------------------------------------------------------- */ + public long getCookieSetTime() + { + return _cookieSet; + } + + /* ------------------------------------------------------------- */ + public long getCreationTime() throws IllegalStateException + { + return _created; + } + + /* ------------------------------------------------------------ */ + public String getId() throws IllegalStateException + { + return _manager._nodeIdInSessionId?_nodeId:_clusterId; + } + + /* ------------------------------------------------------------- */ + public String getNodeId() + { + return _nodeId; + } + + /* ------------------------------------------------------------- */ + public String getClusterId() + { + return _clusterId; + } + + /* ------------------------------------------------------------- */ + public long getLastAccessedTime() throws IllegalStateException + { + checkValid(); + return _lastAccessed; + } + + /* ------------------------------------------------------------- */ + public void setLastAccessedTime(long time) + { + _lastAccessed = time; + } + + /* ------------------------------------------------------------- */ + public int getMaxInactiveInterval() + { + checkValid(); + return (int)(_maxIdleMs/1000); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpSession#getServletContext() + */ + public ServletContext getServletContext() + { + return _manager._context; + } + + /* ------------------------------------------------------------- */ + @Deprecated + public HttpSessionContext getSessionContext() throws IllegalStateException + { + checkValid(); + return AbstractSessionManager.__nullSessionContext; + } + + /* ------------------------------------------------------------- */ + /** + * @deprecated As of Version 2.2, this method is replaced by + * {@link #getAttribute} + */ + @Deprecated + public Object getValue(String name) throws IllegalStateException + { + return getAttribute(name); + } + + /* ------------------------------------------------------------- */ + /** + * @deprecated As of Version 2.2, this method is replaced by + * {@link #getAttributeNames} + */ + @Deprecated + public String[] getValueNames() throws IllegalStateException + { + synchronized(this) + { + checkValid(); + if (_attributes==null) + return new String[0]; + String[] a=new String[_attributes.size()]; + return (String[])_attributes.keySet().toArray(a); + } + } + + /* ------------------------------------------------------------ */ + protected Map<String,Object> getAttributeMap () + { + return _attributes; + } + + /* ------------------------------------------------------------ */ + protected void addAttributes(Map<String,Object> map) + { + _attributes.putAll(map); + } + + /* ------------------------------------------------------------ */ + protected boolean access(long time) + { + synchronized(this) + { + if (_invalid) + return false; + _newSession=false; + _lastAccessed=_accessed; + _accessed=time; + + if (_maxIdleMs>0 && _lastAccessed>0 && _lastAccessed + _maxIdleMs < time) + { + invalidate(); + return false; + } + _requests++; + return true; + } + } + + /* ------------------------------------------------------------ */ + protected void complete() + { + synchronized(this) + { + _requests--; + if (_doInvalidate && _requests<=0 ) + doInvalidate(); + } + } + + + /* ------------------------------------------------------------- */ + protected void timeout() throws IllegalStateException + { + // remove session from context and invalidate other sessions with same ID. + _manager.removeSession(this,true); + + // Notify listeners and unbind values + boolean do_invalidate=false; + synchronized (this) + { + if (!_invalid) + { + if (_requests<=0) + do_invalidate=true; + else + _doInvalidate=true; + } + } + if (do_invalidate) + doInvalidate(); + } + + /* ------------------------------------------------------------- */ + public void invalidate() throws IllegalStateException + { + // remove session from context and invalidate other sessions with same ID. + _manager.removeSession(this,true); + doInvalidate(); + } + + /* ------------------------------------------------------------- */ + protected void doInvalidate() throws IllegalStateException + { + try + { + LOG.debug("invalidate {}",_clusterId); + if (isValid()) + clearAttributes(); + } + finally + { + synchronized (this) + { + // mark as invalid + _invalid=true; + } + } + } + + /* ------------------------------------------------------------- */ + public void clearAttributes() + { + while (_attributes!=null && _attributes.size()>0) + { + ArrayList<String> keys; + synchronized(this) + { + keys=new ArrayList<String>(_attributes.keySet()); + } + + Iterator<String> iter=keys.iterator(); + while (iter.hasNext()) + { + String key=(String)iter.next(); + + Object value; + synchronized(this) + { + value=doPutOrRemove(key,null); + } + unbindValue(key,value); + + _manager.doSessionAttributeListeners(this,key,value,null); + } + } + if (_attributes!=null) + _attributes.clear(); + } + + /* ------------------------------------------------------------- */ + public boolean isIdChanged() + { + return _idChanged; + } + + /* ------------------------------------------------------------- */ + public boolean isNew() throws IllegalStateException + { + checkValid(); + return _newSession; + } + + /* ------------------------------------------------------------- */ + /** + * @deprecated As of Version 2.2, this method is replaced by + * {@link #setAttribute} + */ + @Deprecated + public void putValue(java.lang.String name, java.lang.Object value) throws IllegalStateException + { + setAttribute(name,value); + } + + /* ------------------------------------------------------------ */ + public void removeAttribute(String name) + { + setAttribute(name,null); + } + + /* ------------------------------------------------------------- */ + /** + * @deprecated As of Version 2.2, this method is replaced by + * {@link #removeAttribute} + */ + @Deprecated + public void removeValue(java.lang.String name) throws IllegalStateException + { + removeAttribute(name); + } + + /* ------------------------------------------------------------ */ + protected Object doPutOrRemove(String name, Object value) + { + return value==null?_attributes.remove(name):_attributes.put(name,value); + } + + /* ------------------------------------------------------------ */ + protected Object doGet(String name) + { + return _attributes.get(name); + } + + /* ------------------------------------------------------------ */ + public void setAttribute(String name, Object value) + { + Object old=null; + synchronized (this) + { + checkValid(); + old=doPutOrRemove(name,value); + } + + if (value==null || !value.equals(old)) + { + if (old!=null) + unbindValue(name,old); + if (value!=null) + bindValue(name,value); + + _manager.doSessionAttributeListeners(this,name,old,value); + + } + } + + /* ------------------------------------------------------------- */ + public void setIdChanged(boolean changed) + { + _idChanged=changed; + } + + /* ------------------------------------------------------------- */ + public void setMaxInactiveInterval(int secs) + { + _maxIdleMs=(long)secs*1000L; + } + + /* ------------------------------------------------------------- */ + @Override + public String toString() + { + return this.getClass().getName()+":"+getId()+"@"+hashCode(); + } + + /* ------------------------------------------------------------- */ + /** If value implements HttpSessionBindingListener, call valueBound() */ + public void bindValue(java.lang.String name, Object value) + { + if (value!=null&&value instanceof HttpSessionBindingListener) + ((HttpSessionBindingListener)value).valueBound(new HttpSessionBindingEvent(this,name)); + } + + /* ------------------------------------------------------------ */ + public boolean isValid() + { + return !_invalid; + } + + /* ------------------------------------------------------------- */ + protected void cookieSet() + { + synchronized (this) + { + _cookieSet=_accessed; + } + } + + /* ------------------------------------------------------------ */ + public int getRequests() + { + synchronized (this) + { + return _requests; + } + } + + /* ------------------------------------------------------------ */ + public void setRequests(int requests) + { + synchronized (this) + { + _requests=requests; + } + } + + /* ------------------------------------------------------------- */ + /** If value implements HttpSessionBindingListener, call valueUnbound() */ + public void unbindValue(java.lang.String name, Object value) + { + if (value!=null&&value instanceof HttpSessionBindingListener) + ((HttpSessionBindingListener)value).valueUnbound(new HttpSessionBindingEvent(this,name)); + } + + /* ------------------------------------------------------------- */ + public void willPassivate() + { + synchronized(this) + { + HttpSessionEvent event = new HttpSessionEvent(this); + for (Iterator<Object> iter = _attributes.values().iterator(); iter.hasNext();) + { + Object value = iter.next(); + if (value instanceof HttpSessionActivationListener) + { + HttpSessionActivationListener listener = (HttpSessionActivationListener) value; + listener.sessionWillPassivate(event); + } + } + } + } + + /* ------------------------------------------------------------- */ + public void didActivate() + { + synchronized(this) + { + HttpSessionEvent event = new HttpSessionEvent(this); + for (Iterator<Object> iter = _attributes.values().iterator(); iter.hasNext();) + { + Object value = iter.next(); + if (value instanceof HttpSessionActivationListener) + { + HttpSessionActivationListener listener = (HttpSessionActivationListener) value; + listener.sessionDidActivate(event); + } + } + } + } + + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/session/AbstractSessionIdManager.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,220 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.session; + +import java.security.SecureRandom; +import java.util.Random; + +import javax.servlet.http.HttpServletRequest; + +import org.eclipse.jetty.server.SessionIdManager; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +public abstract class AbstractSessionIdManager extends AbstractLifeCycle implements SessionIdManager +{ + private static final Logger LOG = Log.getLogger(AbstractSessionIdManager.class); + + private final static String __NEW_SESSION_ID="org.eclipse.jetty.server.newSessionId"; + + protected Random _random; + protected boolean _weakRandom; + protected String _workerName; + protected long _reseed=100000L; + + /* ------------------------------------------------------------ */ + public AbstractSessionIdManager() + { + } + + /* ------------------------------------------------------------ */ + public AbstractSessionIdManager(Random random) + { + _random=random; + } + + + /* ------------------------------------------------------------ */ + /** + * @return the reseed probability + */ + public long getReseed() + { + return _reseed; + } + + /* ------------------------------------------------------------ */ + /** Set the reseed probability. + * @param reseed If non zero then when a random long modulo the reseed value == 1, the {@link SecureRandom} will be reseeded. + */ + public void setReseed(long reseed) + { + _reseed = reseed; + } + + /* ------------------------------------------------------------ */ + /** + * Get the workname. If set, the workername is dot appended to the session + * ID and can be used to assist session affinity in a load balancer. + * + * @return String or null + */ + public String getWorkerName() + { + return _workerName; + } + + /* ------------------------------------------------------------ */ + /** + * Set the workname. If set, the workername is dot appended to the session + * ID and can be used to assist session affinity in a load balancer. + * + * @param workerName + */ + public void setWorkerName(String workerName) + { + if (workerName.contains(".")) + throw new IllegalArgumentException("Name cannot contain '.'"); + _workerName=workerName; + } + + /* ------------------------------------------------------------ */ + public Random getRandom() + { + return _random; + } + + /* ------------------------------------------------------------ */ + public synchronized void setRandom(Random random) + { + _random=random; + _weakRandom=false; + } + + /* ------------------------------------------------------------ */ + /** + * Create a new session id if necessary. + * + * @see org.eclipse.jetty.server.SessionIdManager#newSessionId(javax.servlet.http.HttpServletRequest, long) + */ + public String newSessionId(HttpServletRequest request, long created) + { + synchronized (this) + { + if (request!=null) + { + // A requested session ID can only be used if it is in use already. + String requested_id=request.getRequestedSessionId(); + if (requested_id!=null) + { + String cluster_id=getClusterId(requested_id); + if (idInUse(cluster_id)) + return cluster_id; + } + + // Else reuse any new session ID already defined for this request. + String new_id=(String)request.getAttribute(__NEW_SESSION_ID); + if (new_id!=null&&idInUse(new_id)) + return new_id; + } + + // pick a new unique ID! + String id=null; + while (id==null||id.length()==0||idInUse(id)) + { + long r0=_weakRandom + ?(hashCode()^Runtime.getRuntime().freeMemory()^_random.nextInt()^(((long)request.hashCode())<<32)) + :_random.nextLong(); + if (r0<0) + r0=-r0; + + // random chance to reseed + if (_reseed>0 && (r0%_reseed)== 1L) + { + LOG.debug("Reseeding {}",this); + if (_random instanceof SecureRandom) + { + SecureRandom secure = (SecureRandom)_random; + secure.setSeed(secure.generateSeed(8)); + } + else + { + _random.setSeed(_random.nextLong()^System.currentTimeMillis()^request.hashCode()^Runtime.getRuntime().freeMemory()); + } + } + + long r1=_weakRandom + ?(hashCode()^Runtime.getRuntime().freeMemory()^_random.nextInt()^(((long)request.hashCode())<<32)) + :_random.nextLong(); + if (r1<0) + r1=-r1; + id=Long.toString(r0,36)+Long.toString(r1,36); + + //add in the id of the node to ensure unique id across cluster + //NOTE this is different to the node suffix which denotes which node the request was received on + if (_workerName!=null) + id=_workerName + id; + } + + request.setAttribute(__NEW_SESSION_ID,id); + return id; + } + } + + /* ------------------------------------------------------------ */ + @Override + protected void doStart() throws Exception + { + initRandom(); + } + + /* ------------------------------------------------------------ */ + @Override + protected void doStop() throws Exception + { + } + + /* ------------------------------------------------------------ */ + /** + * Set up a random number generator for the sessionids. + * + * By preference, use a SecureRandom but allow to be injected. + */ + public void initRandom () + { + if (_random==null) + { + try + { + _random=new SecureRandom(); + } + catch (Exception e) + { + LOG.warn("Could not generate SecureRandom for session-id randomness",e); + _random=new Random(); + _weakRandom=true; + } + } + else + _random.setSeed(_random.nextLong()^System.currentTimeMillis()^hashCode()^Runtime.getRuntime().freeMemory()); + } + + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/session/AbstractSessionManager.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,1026 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.session; + +import static java.lang.Math.round; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.EventListener; +import java.util.HashSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; + +import javax.servlet.ServletRequest; +import javax.servlet.SessionCookieConfig; +import javax.servlet.SessionTrackingMode; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionAttributeListener; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionContext; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionListener; + +import org.eclipse.jetty.http.HttpCookie; +import org.eclipse.jetty.server.AbstractConnector; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.SessionIdManager; +import org.eclipse.jetty.server.SessionManager; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.statistic.CounterStatistic; +import org.eclipse.jetty.util.statistic.SampleStatistic; + +/* ------------------------------------------------------------ */ +/** + * An Abstract implementation of SessionManager. The partial implementation of + * SessionManager interface provides the majority of the handling required to + * implement a SessionManager. Concrete implementations of SessionManager based + * on AbstractSessionManager need only implement the newSession method to return + * a specialised version of the Session inner class that provides an attribute + * Map. + * <p> + */ +@SuppressWarnings("deprecation") +public abstract class AbstractSessionManager extends AbstractLifeCycle implements SessionManager +{ + final static Logger __log = SessionHandler.LOG; + + public Set<SessionTrackingMode> __defaultSessionTrackingModes = + Collections.unmodifiableSet( + new HashSet<SessionTrackingMode>( + Arrays.asList(new SessionTrackingMode[]{SessionTrackingMode.COOKIE,SessionTrackingMode.URL}))); + + public final static String SESSION_KNOWN_ONLY_TO_AUTHENTICATED="org.eclipse.jetty.security.sessionKnownOnlytoAuthenticated"; + + /* ------------------------------------------------------------ */ + public final static int __distantFuture=60*60*24*7*52*20; + + static final HttpSessionContext __nullSessionContext=new HttpSessionContext() + { + public HttpSession getSession(String sessionId) + { + return null; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public Enumeration getIds() + { + return Collections.enumeration(Collections.EMPTY_LIST); + } + }; + + private boolean _usingCookies=true; + + /* ------------------------------------------------------------ */ + // Setting of max inactive interval for new sessions + // -1 means no timeout + protected int _dftMaxIdleSecs=-1; + protected SessionHandler _sessionHandler; + protected boolean _httpOnly=false; + protected SessionIdManager _sessionIdManager; + protected boolean _secureCookies=false; + protected boolean _secureRequestOnly=true; + + protected final List<HttpSessionAttributeListener> _sessionAttributeListeners = new CopyOnWriteArrayList<HttpSessionAttributeListener>(); + protected final List<HttpSessionListener> _sessionListeners= new CopyOnWriteArrayList<HttpSessionListener>(); + + protected ClassLoader _loader; + protected ContextHandler.Context _context; + protected String _sessionCookie=__DefaultSessionCookie; + protected String _sessionIdPathParameterName = __DefaultSessionIdPathParameterName; + protected String _sessionIdPathParameterNamePrefix =";"+ _sessionIdPathParameterName +"="; + protected String _sessionDomain; + protected String _sessionPath; + protected int _maxCookieAge=-1; + protected int _refreshCookieAge; + protected boolean _nodeIdInSessionId; + protected boolean _checkingRemoteSessionIdEncoding; + protected String _sessionComment; + + public Set<SessionTrackingMode> _sessionTrackingModes; + + private boolean _usingURLs; + + protected final CounterStatistic _sessionsStats = new CounterStatistic(); + protected final SampleStatistic _sessionTimeStats = new SampleStatistic(); + + + /* ------------------------------------------------------------ */ + public static HttpSession renewSession (HttpServletRequest request, HttpSession httpSession, boolean authenticated) + { + Map<String,Object> attributes = new HashMap<String, Object>(); + + for (Enumeration<String> e=httpSession.getAttributeNames();e.hasMoreElements();) + { + String name=e.nextElement(); + attributes.put(name,httpSession.getAttribute(name)); + httpSession.removeAttribute(name); + } + + httpSession.invalidate(); + httpSession = request.getSession(true); + if (authenticated) + httpSession.setAttribute(SESSION_KNOWN_ONLY_TO_AUTHENTICATED, Boolean.TRUE); + for (Map.Entry<String, Object> entry: attributes.entrySet()) + httpSession.setAttribute(entry.getKey(),entry.getValue()); + return httpSession; + } + + /* ------------------------------------------------------------ */ + public AbstractSessionManager() + { + setSessionTrackingModes(__defaultSessionTrackingModes); + } + + /* ------------------------------------------------------------ */ + public ContextHandler.Context getContext() + { + return _context; + } + + /* ------------------------------------------------------------ */ + public ContextHandler getContextHandler() + { + return _context.getContextHandler(); + } + + public String getSessionPath() + { + return _sessionPath; + } + + public int getMaxCookieAge() + { + return _maxCookieAge; + } + + /* ------------------------------------------------------------ */ + public HttpCookie access(HttpSession session,boolean secure) + { + long now=System.currentTimeMillis(); + + AbstractSession s = ((SessionIf)session).getSession(); + + if (s.access(now)) + { + // Do we need to refresh the cookie? + if (isUsingCookies() && + (s.isIdChanged() || + (getSessionCookieConfig().getMaxAge()>0 && getRefreshCookieAge()>0 && ((now-s.getCookieSetTime())/1000>getRefreshCookieAge())) + ) + ) + { + HttpCookie cookie=getSessionCookie(session,_context==null?"/":(_context.getContextPath()),secure); + s.cookieSet(); + s.setIdChanged(false); + return cookie; + } + } + return null; + } + + /* ------------------------------------------------------------ */ + public void addEventListener(EventListener listener) + { + if (listener instanceof HttpSessionAttributeListener) + _sessionAttributeListeners.add((HttpSessionAttributeListener)listener); + if (listener instanceof HttpSessionListener) + _sessionListeners.add((HttpSessionListener)listener); + } + + /* ------------------------------------------------------------ */ + public void clearEventListeners() + { + _sessionAttributeListeners.clear(); + _sessionListeners.clear(); + } + + /* ------------------------------------------------------------ */ + public void complete(HttpSession session) + { + AbstractSession s = ((SessionIf)session).getSession(); + s.complete(); + } + + /* ------------------------------------------------------------ */ + @Override + public void doStart() throws Exception + { + _context=ContextHandler.getCurrentContext(); + _loader=Thread.currentThread().getContextClassLoader(); + + if (_sessionIdManager==null) + { + final Server server=getSessionHandler().getServer(); + synchronized (server) + { + _sessionIdManager=server.getSessionIdManager(); + if (_sessionIdManager==null) + { + _sessionIdManager=new HashSessionIdManager(); + server.setSessionIdManager(_sessionIdManager); + } + } + } + if (!_sessionIdManager.isStarted()) + _sessionIdManager.start(); + + // Look for a session cookie name + if (_context!=null) + { + String tmp=_context.getInitParameter(SessionManager.__SessionCookieProperty); + if (tmp!=null) + _sessionCookie=tmp; + + tmp=_context.getInitParameter(SessionManager.__SessionIdPathParameterNameProperty); + if (tmp!=null) + setSessionIdPathParameterName(tmp); + + // set up the max session cookie age if it isn't already + if (_maxCookieAge==-1) + { + tmp=_context.getInitParameter(SessionManager.__MaxAgeProperty); + if (tmp!=null) + _maxCookieAge=Integer.parseInt(tmp.trim()); + } + + // set up the session domain if it isn't already + if (_sessionDomain==null) + _sessionDomain=_context.getInitParameter(SessionManager.__SessionDomainProperty); + + // set up the sessionPath if it isn't already + if (_sessionPath==null) + _sessionPath=_context.getInitParameter(SessionManager.__SessionPathProperty); + + tmp=_context.getInitParameter(SessionManager.__CheckRemoteSessionEncoding); + if (tmp!=null) + _checkingRemoteSessionIdEncoding=Boolean.parseBoolean(tmp); + } + + super.doStart(); + } + + /* ------------------------------------------------------------ */ + @Override + public void doStop() throws Exception + { + super.doStop(); + + invalidateSessions(); + + _loader=null; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the httpOnly. + */ + public boolean getHttpOnly() + { + return _httpOnly; + } + + /* ------------------------------------------------------------ */ + public HttpSession getHttpSession(String nodeId) + { + String cluster_id = getSessionIdManager().getClusterId(nodeId); + + AbstractSession session = getSession(cluster_id); + if (session!=null && !session.getNodeId().equals(nodeId)) + session.setIdChanged(true); + return session; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the metaManager used for cross context session management + * @deprecated Use {@link #getSessionIdManager()} + */ + public SessionIdManager getIdManager() + { + return getSessionIdManager(); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the SessionIdManager used for cross context session management + */ + public SessionIdManager getSessionIdManager() + { + return _sessionIdManager; + } + + + /* ------------------------------------------------------------ */ + /** + * @return seconds + */ + @Override + public int getMaxInactiveInterval() + { + return _dftMaxIdleSecs; + } + + /* ------------------------------------------------------------ */ + /** + * @see #getSessionsMax() + */ + @Deprecated + public int getMaxSessions() + { + return getSessionsMax(); + } + + /* ------------------------------------------------------------ */ + /** + * @return maximum number of sessions + */ + public int getSessionsMax() + { + return (int)_sessionsStats.getMax(); + } + + /* ------------------------------------------------------------ */ + /** + * @return total number of sessions + */ + public int getSessionsTotal() + { + return (int)_sessionsStats.getTotal(); + } + + /* ------------------------------------------------------------ */ + /** + * @deprecated use {@link #getSessionIdManager()} + */ + @Deprecated + public SessionIdManager getMetaManager() + { + return getSessionIdManager(); + } + + /* ------------------------------------------------------------ */ + /** + * @deprecated always returns 0. no replacement available. + */ + @Deprecated + public int getMinSessions() + { + return 0; + } + + /* ------------------------------------------------------------ */ + public int getRefreshCookieAge() + { + return _refreshCookieAge; + } + + + /* ------------------------------------------------------------ */ + /** + * @return same as SessionCookieConfig.getSecure(). If true, session + * cookies are ALWAYS marked as secure. If false, a session cookie is + * ONLY marked as secure if _secureRequestOnly == true and it is a HTTPS request. + */ + public boolean getSecureCookies() + { + return _secureCookies; + } + + /* ------------------------------------------------------------ */ + /** + * @return true if session cookie is to be marked as secure only on HTTPS requests + */ + public boolean isSecureRequestOnly() + { + return _secureRequestOnly; + } + + + /* ------------------------------------------------------------ */ + /** + * @return if true, session cookie will be marked as secure only iff + * HTTPS request. Can be overridden by setting SessionCookieConfig.setSecure(true), + * in which case the session cookie will be marked as secure on both HTTPS and HTTP. + */ + public void setSecureRequestOnly(boolean secureRequestOnly) + { + _secureRequestOnly = secureRequestOnly; + } + + + + /* ------------------------------------------------------------ */ + public String getSessionCookie() + { + return _sessionCookie; + } + + /* ------------------------------------------------------------ */ + /** + * A sessioncookie is marked as secure IFF any of the following conditions are true: + * <ol> + * <li>SessionCookieConfig.setSecure == true</li> + * <li>SessionCookieConfig.setSecure == false && _secureRequestOnly==true && request is HTTPS</li> + * </ol> + * According to SessionCookieConfig javadoc, case 1 can be used when: + * "... even though the request that initiated the session came over HTTP, + * is to support a topology where the web container is front-ended by an + * SSL offloading load balancer. In this case, the traffic between the client + * and the load balancer will be over HTTPS, whereas the traffic between the + * load balancer and the web container will be over HTTP." + * + * For case 2, you can use _secureRequestOnly to determine if you want the + * Servlet Spec 3.0 default behaviour when SessionCookieConfig.setSecure==false, + * which is: + * "they shall be marked as secure only if the request that initiated the + * corresponding session was also secure" + * + * The default for _secureRequestOnly is true, which gives the above behaviour. If + * you set it to false, then a session cookie is NEVER marked as secure, even if + * the initiating request was secure. + * + * @see org.eclipse.jetty.server.SessionManager#getSessionCookie(javax.servlet.http.HttpSession, java.lang.String, boolean) + */ + public HttpCookie getSessionCookie(HttpSession session, String contextPath, boolean requestIsSecure) + { + if (isUsingCookies()) + { + String sessionPath = (_sessionPath==null) ? contextPath : _sessionPath; + sessionPath = (sessionPath==null||sessionPath.length()==0) ? "/" : sessionPath; + String id = getNodeId(session); + HttpCookie cookie = null; + if (_sessionComment == null) + { + cookie = new HttpCookie( + _sessionCookie, + id, + _sessionDomain, + sessionPath, + _cookieConfig.getMaxAge(), + _cookieConfig.isHttpOnly(), + _cookieConfig.isSecure() || (isSecureRequestOnly() && requestIsSecure)); + } + else + { + cookie = new HttpCookie( + _sessionCookie, + id, + _sessionDomain, + sessionPath, + _cookieConfig.getMaxAge(), + _cookieConfig.isHttpOnly(), + _cookieConfig.isSecure() || (isSecureRequestOnly() && requestIsSecure), + _sessionComment, + 1); + } + + return cookie; + } + return null; + } + + public String getSessionDomain() + { + return _sessionDomain; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the sessionHandler. + */ + public SessionHandler getSessionHandler() + { + return _sessionHandler; + } + + /* ------------------------------------------------------------ */ + /** + * @deprecated Need to review if it is needed. + */ + @SuppressWarnings("rawtypes") + public Map getSessionMap() + { + throw new UnsupportedOperationException(); + } + + + + /* ------------------------------------------------------------ */ + public int getSessions() + { + return (int)_sessionsStats.getCurrent(); + } + + /* ------------------------------------------------------------ */ + public String getSessionIdPathParameterName() + { + return _sessionIdPathParameterName; + } + + /* ------------------------------------------------------------ */ + public String getSessionIdPathParameterNamePrefix() + { + return _sessionIdPathParameterNamePrefix; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the usingCookies. + */ + public boolean isUsingCookies() + { + return _usingCookies; + } + + /* ------------------------------------------------------------ */ + public boolean isValid(HttpSession session) + { + AbstractSession s = ((SessionIf)session).getSession(); + return s.isValid(); + } + + /* ------------------------------------------------------------ */ + public String getClusterId(HttpSession session) + { + AbstractSession s = ((SessionIf)session).getSession(); + return s.getClusterId(); + } + + /* ------------------------------------------------------------ */ + public String getNodeId(HttpSession session) + { + AbstractSession s = ((SessionIf)session).getSession(); + return s.getNodeId(); + } + + /* ------------------------------------------------------------ */ + /** + * Create a new HttpSession for a request + */ + public HttpSession newHttpSession(HttpServletRequest request) + { + AbstractSession session=newSession(request); + session.setMaxInactiveInterval(_dftMaxIdleSecs); + addSession(session,true); + return session; + } + + /* ------------------------------------------------------------ */ + public void removeEventListener(EventListener listener) + { + if (listener instanceof HttpSessionAttributeListener) + _sessionAttributeListeners.remove(listener); + if (listener instanceof HttpSessionListener) + _sessionListeners.remove(listener); + } + + /* ------------------------------------------------------------ */ + /** + * @see #statsReset() + */ + @Deprecated + public void resetStats() + { + statsReset(); + } + + /* ------------------------------------------------------------ */ + /** + * Reset statistics values + */ + public void statsReset() + { + _sessionsStats.reset(getSessions()); + _sessionTimeStats.reset(); + } + + /* ------------------------------------------------------------ */ + /** + * @param httpOnly + * The httpOnly to set. + */ + public void setHttpOnly(boolean httpOnly) + { + _httpOnly=httpOnly; + } + + /* ------------------------------------------------------------ */ + /** + * @param metaManager The metaManager used for cross context session management. + * @deprecated use {@link #setSessionIdManager(SessionIdManager)} + */ + public void setIdManager(SessionIdManager metaManager) + { + setSessionIdManager(metaManager); + } + + /* ------------------------------------------------------------ */ + /** + * @param metaManager The metaManager used for cross context session management. + */ + public void setSessionIdManager(SessionIdManager metaManager) + { + _sessionIdManager=metaManager; + } + + + + /* ------------------------------------------------------------ */ + /** + * @param seconds + */ + public void setMaxInactiveInterval(int seconds) + { + _dftMaxIdleSecs=seconds; + } + + + /* ------------------------------------------------------------ */ + public void setRefreshCookieAge(int ageInSeconds) + { + _refreshCookieAge=ageInSeconds; + } + + + + public void setSessionCookie(String cookieName) + { + _sessionCookie=cookieName; + } + + + + /* ------------------------------------------------------------ */ + /** + * @param sessionHandler + * The sessionHandler to set. + */ + public void setSessionHandler(SessionHandler sessionHandler) + { + _sessionHandler=sessionHandler; + } + + + /* ------------------------------------------------------------ */ + public void setSessionIdPathParameterName(String param) + { + _sessionIdPathParameterName =(param==null||"none".equals(param))?null:param; + _sessionIdPathParameterNamePrefix =(param==null||"none".equals(param))?null:(";"+ _sessionIdPathParameterName +"="); + } + /* ------------------------------------------------------------ */ + /** + * @param usingCookies + * The usingCookies to set. + */ + public void setUsingCookies(boolean usingCookies) + { + _usingCookies=usingCookies; + } + + + protected abstract void addSession(AbstractSession session); + + /* ------------------------------------------------------------ */ + /** + * Add the session Registers the session with this manager and registers the + * session ID with the sessionIDManager; + */ + protected void addSession(AbstractSession session, boolean created) + { + synchronized (_sessionIdManager) + { + _sessionIdManager.addSession(session); + addSession(session); + } + + if (created) + { + _sessionsStats.increment(); + if (_sessionListeners!=null) + { + HttpSessionEvent event=new HttpSessionEvent(session); + for (HttpSessionListener listener : _sessionListeners) + listener.sessionCreated(event); + } + } + } + + /* ------------------------------------------------------------ */ + /** + * Get a known existing session + * @param idInCluster The session ID in the cluster, stripped of any worker name. + * @return A Session or null if none exists. + */ + public abstract AbstractSession getSession(String idInCluster); + + protected abstract void invalidateSessions() throws Exception; + + + /* ------------------------------------------------------------ */ + /** + * Create a new session instance + * @param request + * @return the new session + */ + protected abstract AbstractSession newSession(HttpServletRequest request); + + + /* ------------------------------------------------------------ */ + /** + * @return true if the cluster node id (worker id) is returned as part of the session id by {@link HttpSession#getId()}. Default is false. + */ + public boolean isNodeIdInSessionId() + { + return _nodeIdInSessionId; + } + + /* ------------------------------------------------------------ */ + /** + * @param nodeIdInSessionId true if the cluster node id (worker id) will be returned as part of the session id by {@link HttpSession#getId()}. Default is false. + */ + public void setNodeIdInSessionId(boolean nodeIdInSessionId) + { + _nodeIdInSessionId=nodeIdInSessionId; + } + + /* ------------------------------------------------------------ */ + /** Remove session from manager + * @param session The session to remove + * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and + * {@link SessionIdManager#invalidateAll(String)} should be called. + */ + public void removeSession(HttpSession session, boolean invalidate) + { + AbstractSession s = ((SessionIf)session).getSession(); + removeSession(s,invalidate); + } + + /* ------------------------------------------------------------ */ + /** Remove session from manager + * @param session The session to remove + * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and + * {@link SessionIdManager#invalidateAll(String)} should be called. + */ + public void removeSession(AbstractSession session, boolean invalidate) + { + // Remove session from context and global maps + boolean removed = removeSession(session.getClusterId()); + + if (removed) + { + _sessionsStats.decrement(); + _sessionTimeStats.set(round((System.currentTimeMillis() - session.getCreationTime())/1000.0)); + + // Remove session from all context and global id maps + _sessionIdManager.removeSession(session); + if (invalidate) + _sessionIdManager.invalidateAll(session.getClusterId()); + + if (invalidate && _sessionListeners!=null) + { + HttpSessionEvent event=new HttpSessionEvent(session); + for (HttpSessionListener listener : _sessionListeners) + listener.sessionDestroyed(event); + } + } + } + + /* ------------------------------------------------------------ */ + protected abstract boolean removeSession(String idInCluster); + + /* ------------------------------------------------------------ */ + /** + * @return maximum amount of time session remained valid + */ + public long getSessionTimeMax() + { + return _sessionTimeStats.getMax(); + } + + /* ------------------------------------------------------------ */ + public Set<SessionTrackingMode> getDefaultSessionTrackingModes() + { + return __defaultSessionTrackingModes; + } + + /* ------------------------------------------------------------ */ + public Set<SessionTrackingMode> getEffectiveSessionTrackingModes() + { + return Collections.unmodifiableSet(_sessionTrackingModes); + } + + /* ------------------------------------------------------------ */ + @Override + public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes) + { + _sessionTrackingModes=new HashSet<SessionTrackingMode>(sessionTrackingModes); + _usingCookies=_sessionTrackingModes.contains(SessionTrackingMode.COOKIE); + _usingURLs=_sessionTrackingModes.contains(SessionTrackingMode.URL); + } + + /* ------------------------------------------------------------ */ + @Override + public boolean isUsingURLs() + { + return _usingURLs; + } + + + /* ------------------------------------------------------------ */ + public SessionCookieConfig getSessionCookieConfig() + { + return _cookieConfig; + } + + /* ------------------------------------------------------------ */ + private SessionCookieConfig _cookieConfig = + new SessionCookieConfig() + { + @Override + public String getComment() + { + return _sessionComment; + } + + @Override + public String getDomain() + { + return _sessionDomain; + } + + @Override + public int getMaxAge() + { + return _maxCookieAge; + } + + @Override + public String getName() + { + return _sessionCookie; + } + + @Override + public String getPath() + { + return _sessionPath; + } + + @Override + public boolean isHttpOnly() + { + return _httpOnly; + } + + @Override + public boolean isSecure() + { + return _secureCookies; + } + + @Override + public void setComment(String comment) + { + _sessionComment = comment; + } + + @Override + public void setDomain(String domain) + { + _sessionDomain=domain; + } + + @Override + public void setHttpOnly(boolean httpOnly) + { + _httpOnly=httpOnly; + } + + @Override + public void setMaxAge(int maxAge) + { + _maxCookieAge=maxAge; + } + + @Override + public void setName(String name) + { + _sessionCookie=name; + } + + @Override + public void setPath(String path) + { + _sessionPath=path; + } + + @Override + public void setSecure(boolean secure) + { + _secureCookies=secure; + } + + }; + + + /* ------------------------------------------------------------ */ + /** + * @return total amount of time all sessions remained valid + */ + public long getSessionTimeTotal() + { + return _sessionTimeStats.getTotal(); + } + + /* ------------------------------------------------------------ */ + /** + * @return mean amount of time session remained valid + */ + public double getSessionTimeMean() + { + return _sessionTimeStats.getMean(); + } + + /* ------------------------------------------------------------ */ + /** + * @return standard deviation of amount of time session remained valid + */ + public double getSessionTimeStdDev() + { + return _sessionTimeStats.getStdDev(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.SessionManager#isCheckingRemoteSessionIdEncoding() + */ + public boolean isCheckingRemoteSessionIdEncoding() + { + return _checkingRemoteSessionIdEncoding; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.SessionManager#setCheckingRemoteSessionIdEncoding(boolean) + */ + public void setCheckingRemoteSessionIdEncoding(boolean remote) + { + _checkingRemoteSessionIdEncoding=remote; + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /** + * Interface that any session wrapper should implement so that + * SessionManager may access the Jetty session implementation. + * + */ + public interface SessionIf extends HttpSession + { + public AbstractSession getSession(); + } + + public void doSessionAttributeListeners(AbstractSession session, String name, Object old, Object value) + { + if (!_sessionAttributeListeners.isEmpty()) + { + HttpSessionBindingEvent event=new HttpSessionBindingEvent(session,name,old==null?value:old); + + for (HttpSessionAttributeListener l : _sessionAttributeListeners) + { + if (old==null) + l.attributeAdded(event); + else if (value==null) + l.attributeRemoved(event); + else + l.attributeReplaced(event); + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/session/HashSessionIdManager.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,222 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.session; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Random; +import java.util.Set; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +import org.eclipse.jetty.server.SessionIdManager; + +/* ------------------------------------------------------------ */ +/** + * HashSessionIdManager. An in-memory implementation of the session ID manager. + */ +public class HashSessionIdManager extends AbstractSessionIdManager +{ + private final Map<String, Set<WeakReference<HttpSession>>> _sessions = new HashMap<String, Set<WeakReference<HttpSession>>>(); + + /* ------------------------------------------------------------ */ + public HashSessionIdManager() + { + } + + /* ------------------------------------------------------------ */ + public HashSessionIdManager(Random random) + { + super(random); + } + + /* ------------------------------------------------------------ */ + /** + * @return Collection of String session IDs + */ + public Collection<String> getSessions() + { + return Collections.unmodifiableCollection(_sessions.keySet()); + } + + /* ------------------------------------------------------------ */ + /** + * @return Collection of Sessions for the passed session ID + */ + public Collection<HttpSession> getSession(String id) + { + ArrayList<HttpSession> sessions = new ArrayList<HttpSession>(); + Set<WeakReference<HttpSession>> refs =_sessions.get(id); + if (refs!=null) + { + for (WeakReference<HttpSession> ref: refs) + { + HttpSession session = ref.get(); + if (session!=null) + sessions.add(session); + } + } + return sessions; + } + /* ------------------------------------------------------------ */ + /** Get the session ID with any worker ID. + * + * @param clusterId + * @param request + * @return sessionId plus any worker ID. + */ + public String getNodeId(String clusterId,HttpServletRequest request) + { + // used in Ajp13Parser + String worker=request==null?null:(String)request.getAttribute("org.eclipse.jetty.ajp.JVMRoute"); + if (worker!=null) + return clusterId+'.'+worker; + + if (_workerName!=null) + return clusterId+'.'+_workerName; + + return clusterId; + } + + /* ------------------------------------------------------------ */ + /** Get the session ID without any worker ID. + * + * @param nodeId the node id + * @return sessionId without any worker ID. + */ + public String getClusterId(String nodeId) + { + int dot=nodeId.lastIndexOf('.'); + return (dot>0)?nodeId.substring(0,dot):nodeId; + } + + /* ------------------------------------------------------------ */ + @Override + protected void doStart() throws Exception + { + super.doStart(); + } + + /* ------------------------------------------------------------ */ + @Override + protected void doStop() throws Exception + { + _sessions.clear(); + super.doStop(); + } + + /* ------------------------------------------------------------ */ + /** + * @see SessionIdManager#idInUse(String) + */ + public boolean idInUse(String id) + { + synchronized (this) + { + return _sessions.containsKey(id); + } + } + + /* ------------------------------------------------------------ */ + /** + * @see SessionIdManager#addSession(HttpSession) + */ + public void addSession(HttpSession session) + { + String id = getClusterId(session.getId()); + WeakReference<HttpSession> ref = new WeakReference<HttpSession>(session); + + synchronized (this) + { + Set<WeakReference<HttpSession>> sessions = _sessions.get(id); + if (sessions==null) + { + sessions=new HashSet<WeakReference<HttpSession>>(); + _sessions.put(id,sessions); + } + sessions.add(ref); + } + } + + /* ------------------------------------------------------------ */ + /** + * @see SessionIdManager#removeSession(HttpSession) + */ + public void removeSession(HttpSession session) + { + String id = getClusterId(session.getId()); + + synchronized (this) + { + Collection<WeakReference<HttpSession>> sessions = _sessions.get(id); + if (sessions!=null) + { + for (Iterator<WeakReference<HttpSession>> iter = sessions.iterator(); iter.hasNext();) + { + WeakReference<HttpSession> ref = iter.next(); + HttpSession s=ref.get(); + if (s==null) + { + iter.remove(); + continue; + } + if (s==session) + { + iter.remove(); + break; + } + } + if (sessions.isEmpty()) + _sessions.remove(id); + } + } + } + + /* ------------------------------------------------------------ */ + /** + * @see SessionIdManager#invalidateAll(String) + */ + public void invalidateAll(String id) + { + Collection<WeakReference<HttpSession>> sessions; + synchronized (this) + { + sessions = _sessions.remove(id); + } + + if (sessions!=null) + { + for (WeakReference<HttpSession> ref: sessions) + { + AbstractSession session=(AbstractSession)ref.get(); + if (session!=null && session.isValid()) + session.invalidate(); + } + sessions.clear(); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/session/HashSessionManager.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,640 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.session; + +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.net.URI; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; + +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.log.Logger; + + +/* ------------------------------------------------------------ */ +/** + * HashSessionManager + * + * An in-memory implementation of SessionManager. + * <p> + * This manager supports saving sessions to disk, either periodically or at shutdown. + * Sessions can also have their content idle saved to disk to reduce the memory overheads of large idle sessions. + * <p> + * This manager will create it's own Timer instance to scavenge threads, unless it discovers a shared Timer instance + * set as the "org.eclipse.jetty.server.session.timer" attribute of the ContextHandler. + * + */ +public class HashSessionManager extends AbstractSessionManager +{ + final static Logger __log = SessionHandler.LOG; + + protected final ConcurrentMap<String,HashedSession> _sessions=new ConcurrentHashMap<String,HashedSession>(); + private static int __id; + private Timer _timer; + private boolean _timerStop=false; + private TimerTask _task; + long _scavengePeriodMs=30000; + long _savePeriodMs=0; //don't do period saves by default + long _idleSavePeriodMs = 0; // don't idle save sessions by default. + private TimerTask _saveTask; + File _storeDir; + private boolean _lazyLoad=false; + private volatile boolean _sessionsLoaded=false; + private boolean _deleteUnrestorableSessions=false; + + + + + /* ------------------------------------------------------------ */ + public HashSessionManager() + { + super(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.servlet.AbstractSessionManager#doStart() + */ + @Override + public void doStart() throws Exception + { + super.doStart(); + + _timerStop=false; + ServletContext context = ContextHandler.getCurrentContext(); + if (context!=null) + _timer=(Timer)context.getAttribute("org.eclipse.jetty.server.session.timer"); + if (_timer==null) + { + _timerStop=true; + _timer=new Timer("HashSessionScavenger-"+__id++, true); + } + + setScavengePeriod(getScavengePeriod()); + + if (_storeDir!=null) + { + if (!_storeDir.exists()) + _storeDir.mkdirs(); + + if (!_lazyLoad) + restoreSessions(); + } + + setSavePeriod(getSavePeriod()); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.servlet.AbstractSessionManager#doStop() + */ + @Override + public void doStop() throws Exception + { + // stop the scavengers + synchronized(this) + { + if (_saveTask!=null) + _saveTask.cancel(); + _saveTask=null; + if (_task!=null) + _task.cancel(); + _task=null; + if (_timer!=null && _timerStop) + _timer.cancel(); + _timer=null; + } + + // This will callback invalidate sessions - where we decide if we will save + super.doStop(); + + _sessions.clear(); + + } + + /* ------------------------------------------------------------ */ + /** + * @return the period in seconds at which a check is made for sessions to be invalidated. + */ + public int getScavengePeriod() + { + return (int)(_scavengePeriodMs/1000); + } + + + /* ------------------------------------------------------------ */ + @Override + public int getSessions() + { + int sessions=super.getSessions(); + if (__log.isDebugEnabled()) + { + if (_sessions.size()!=sessions) + __log.warn("sessions: "+_sessions.size()+"!="+sessions); + } + return sessions; + } + + /* ------------------------------------------------------------ */ + /** + * @return seconds Idle period after which a session is saved + */ + public int getIdleSavePeriod() + { + if (_idleSavePeriodMs <= 0) + return 0; + + return (int)(_idleSavePeriodMs / 1000); + } + + /* ------------------------------------------------------------ */ + /** + * Configures the period in seconds after which a session is deemed idle and saved + * to save on session memory. + * + * The session is persisted, the values attribute map is cleared and the session set to idled. + * + * @param seconds Idle period after which a session is saved + */ + public void setIdleSavePeriod(int seconds) + { + _idleSavePeriodMs = seconds * 1000L; + } + + /* ------------------------------------------------------------ */ + @Override + public void setMaxInactiveInterval(int seconds) + { + super.setMaxInactiveInterval(seconds); + if (_dftMaxIdleSecs>0&&_scavengePeriodMs>_dftMaxIdleSecs*1000L) + setScavengePeriod((_dftMaxIdleSecs+9)/10); + } + + /* ------------------------------------------------------------ */ + /** + * @param seconds the period is seconds at which sessions are periodically saved to disk + */ + public void setSavePeriod (int seconds) + { + long period = (seconds * 1000L); + if (period < 0) + period=0; + _savePeriodMs=period; + + if (_timer!=null) + { + synchronized (this) + { + if (_saveTask!=null) + _saveTask.cancel(); + if (_savePeriodMs > 0 && _storeDir!=null) //only save if we have a directory configured + { + _saveTask = new TimerTask() + { + @Override + public void run() + { + try + { + saveSessions(true); + } + catch (Exception e) + { + __log.warn(e); + } + } + }; + _timer.schedule(_saveTask,_savePeriodMs,_savePeriodMs); + } + } + } + } + + /* ------------------------------------------------------------ */ + /** + * @return the period in seconds at which sessions are periodically saved to disk + */ + public int getSavePeriod () + { + if (_savePeriodMs<=0) + return 0; + + return (int)(_savePeriodMs/1000); + } + + /* ------------------------------------------------------------ */ + /** + * @param seconds the period in seconds at which a check is made for sessions to be invalidated. + */ + public void setScavengePeriod(int seconds) + { + if (seconds==0) + seconds=60; + + long old_period=_scavengePeriodMs; + long period=seconds*1000L; + if (period>60000) + period=60000; + if (period<1000) + period=1000; + + _scavengePeriodMs=period; + + if (_timer!=null && (period!=old_period || _task==null)) + { + synchronized (this) + { + if (_task!=null) + _task.cancel(); + _task = new TimerTask() + { + @Override + public void run() + { + scavenge(); + } + }; + _timer.schedule(_task,_scavengePeriodMs,_scavengePeriodMs); + } + } + } + + /* -------------------------------------------------------------- */ + /** + * Find sessions that have timed out and invalidate them. This runs in the + * SessionScavenger thread. + */ + protected void scavenge() + { + //don't attempt to scavenge if we are shutting down + if (isStopping() || isStopped()) + return; + + Thread thread=Thread.currentThread(); + ClassLoader old_loader=thread.getContextClassLoader(); + try + { + if (_loader!=null) + thread.setContextClassLoader(_loader); + + // For each session + long now=System.currentTimeMillis(); + + for (Iterator<HashedSession> i=_sessions.values().iterator(); i.hasNext();) + { + HashedSession session=i.next(); + long idleTime=session.getMaxInactiveInterval()*1000L; + if (idleTime>0&&session.getAccessed()+idleTime<now) + { + // Found a stale session, add it to the list + try + { + session.timeout(); + } + catch (Exception e) + { + __log.warn("Problem scavenging sessions", e); + } + } + else if (_idleSavePeriodMs > 0 && session.getAccessed()+_idleSavePeriodMs < now) + { + try + { + session.idle(); + } + catch (Exception e) + { + __log.warn("Problem idling session "+ session.getId(), e); + } + } + } + } + finally + { + thread.setContextClassLoader(old_loader); + } + } + + /* ------------------------------------------------------------ */ + @Override + protected void addSession(AbstractSession session) + { + if (isRunning()) + _sessions.put(session.getClusterId(),(HashedSession)session); + } + + /* ------------------------------------------------------------ */ + @Override + public AbstractSession getSession(String idInCluster) + { + if ( _lazyLoad && !_sessionsLoaded) + { + try + { + restoreSessions(); + } + catch(Exception e) + { + __log.warn(e); + } + } + + Map<String,HashedSession> sessions=_sessions; + if (sessions==null) + return null; + + HashedSession session = sessions.get(idInCluster); + + if (session == null && _lazyLoad) + session=restoreSession(idInCluster); + if (session == null) + return null; + + if (_idleSavePeriodMs!=0) + session.deIdle(); + + return session; + } + + /* ------------------------------------------------------------ */ + @Override + protected void invalidateSessions() throws Exception + { + // Invalidate all sessions to cause unbind events + ArrayList<HashedSession> sessions=new ArrayList<HashedSession>(_sessions.values()); + int loop=100; + while (sessions.size()>0 && loop-->0) + { + // If we are called from doStop + if (isStopping() && _storeDir != null && _storeDir.exists() && _storeDir.canWrite()) + { + // Then we only save and remove the session - it is not invalidated. + for (HashedSession session : sessions) + { + session.save(false); + removeSession(session,false); + } + } + else + { + for (HashedSession session : sessions) + session.invalidate(); + } + + // check that no new sessions were created while we were iterating + sessions=new ArrayList<HashedSession>(_sessions.values()); + } + } + + /* ------------------------------------------------------------ */ + @Override + protected AbstractSession newSession(HttpServletRequest request) + { + return new HashedSession(this, request); + } + + /* ------------------------------------------------------------ */ + protected AbstractSession newSession(long created, long accessed, String clusterId) + { + return new HashedSession(this, created,accessed, clusterId); + } + + /* ------------------------------------------------------------ */ + @Override + protected boolean removeSession(String clusterId) + { + return _sessions.remove(clusterId)!=null; + } + + /* ------------------------------------------------------------ */ + public void setStoreDirectory (File dir) throws IOException + { + // CanonicalFile is used to capture the base store directory in a way that will + // work on Windows. Case differences may through off later checks using this directory. + _storeDir=dir.getCanonicalFile(); + } + + /* ------------------------------------------------------------ */ + public File getStoreDirectory () + { + return _storeDir; + } + + /* ------------------------------------------------------------ */ + public void setLazyLoad(boolean lazyLoad) + { + _lazyLoad = lazyLoad; + } + + /* ------------------------------------------------------------ */ + public boolean isLazyLoad() + { + return _lazyLoad; + } + + /* ------------------------------------------------------------ */ + public boolean isDeleteUnrestorableSessions() + { + return _deleteUnrestorableSessions; + } + + /* ------------------------------------------------------------ */ + public void setDeleteUnrestorableSessions(boolean deleteUnrestorableSessions) + { + _deleteUnrestorableSessions = deleteUnrestorableSessions; + } + + /* ------------------------------------------------------------ */ + public void restoreSessions () throws Exception + { + _sessionsLoaded = true; + + if (_storeDir==null || !_storeDir.exists()) + { + return; + } + + if (!_storeDir.canRead()) + { + __log.warn ("Unable to restore Sessions: Cannot read from Session storage directory "+_storeDir.getAbsolutePath()); + return; + } + + String[] files = _storeDir.list(); + for (int i=0;files!=null&&i<files.length;i++) + { + restoreSession(files[i]); + } + } + + /* ------------------------------------------------------------ */ + protected synchronized HashedSession restoreSession(String idInCuster) + { + File file = new File(_storeDir,idInCuster); + + FileInputStream in = null; + Exception error = null; + try + { + if (file.exists()) + { + in = new FileInputStream(file); + HashedSession session = restoreSession(in, null); + addSession(session, false); + session.didActivate(); + return session; + } + } + catch (Exception e) + { + error = e; + } + finally + { + if (in != null) IO.close(in); + + if (error != null) + { + if (isDeleteUnrestorableSessions() && file.exists() && file.getParentFile().equals(_storeDir) ) + { + file.delete(); + __log.warn("Deleting file for unrestorable session "+idInCuster, error); + } + else + { + __log.warn("Problem restoring session "+idInCuster, error); + } + } + else + file.delete(); //delete successfully restored file + + } + return null; + } + + /* ------------------------------------------------------------ */ + public void saveSessions(boolean reactivate) throws Exception + { + if (_storeDir==null || !_storeDir.exists()) + { + return; + } + + if (!_storeDir.canWrite()) + { + __log.warn ("Unable to save Sessions: Session persistence storage directory "+_storeDir.getAbsolutePath()+ " is not writeable"); + return; + } + + for (HashedSession session : _sessions.values()) + session.save(true); + } + + /* ------------------------------------------------------------ */ + public HashedSession restoreSession (InputStream is, HashedSession session) throws Exception + { + /* + * Take care of this class's fields first by calling + * defaultReadObject + */ + DataInputStream in = new DataInputStream(is); + try + { + String clusterId = in.readUTF(); + in.readUTF(); // nodeId + long created = in.readLong(); + long accessed = in.readLong(); + int requests = in.readInt(); + + if (session == null) + session = (HashedSession)newSession(created, accessed, clusterId); + session.setRequests(requests); + int size = in.readInt(); + if (size>0) + { + ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(in); + try + { + for (int i=0; i<size;i++) + { + String key = ois.readUTF(); + Object value = ois.readObject(); + session.setAttribute(key,value); + } + } + finally + { + IO.close(ois); + } + } + return session; + } + finally + { + IO.close(in); + } + } + + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + protected class ClassLoadingObjectInputStream extends ObjectInputStream + { + /* ------------------------------------------------------------ */ + public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException + { + super(in); + } + + /* ------------------------------------------------------------ */ + public ClassLoadingObjectInputStream () throws IOException + { + super(); + } + + /* ------------------------------------------------------------ */ + @Override + public Class<?> resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException + { + try + { + return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader()); + } + catch (ClassNotFoundException e) + { + return super.resolveClass(cl); + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/session/HashedSession.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,241 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.session; + +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.util.Enumeration; + +import javax.servlet.http.HttpServletRequest; + +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +public class HashedSession extends AbstractSession +{ + private static final Logger LOG = Log.getLogger(HashedSession.class); + + private final HashSessionManager _hashSessionManager; + + /** Whether the session has been saved because it has been deemed idle; + * in which case its attribute map will have been saved and cleared. */ + private transient boolean _idled = false; + + /** Whether there has already been an attempt to save this session + * which has failed. If there has, there will be no more save attempts + * for this session. This is to stop the logs being flooded with errors + * due to serialization failures that are most likely caused by user + * data stored in the session that is not serializable. */ + private transient boolean _saveFailed = false; + + /* ------------------------------------------------------------- */ + protected HashedSession(HashSessionManager hashSessionManager, HttpServletRequest request) + { + super(hashSessionManager,request); + _hashSessionManager = hashSessionManager; + } + + /* ------------------------------------------------------------- */ + protected HashedSession(HashSessionManager hashSessionManager, long created, long accessed, String clusterId) + { + super(hashSessionManager,created, accessed, clusterId); + _hashSessionManager = hashSessionManager; + } + + /* ------------------------------------------------------------- */ + protected void checkValid() + { + if (_hashSessionManager._idleSavePeriodMs!=0) + deIdle(); + super.checkValid(); + } + + /* ------------------------------------------------------------- */ + @Override + public void setMaxInactiveInterval(int secs) + { + super.setMaxInactiveInterval(secs); + if (getMaxInactiveInterval()>0&&(getMaxInactiveInterval()*1000L/10)<_hashSessionManager._scavengePeriodMs) + _hashSessionManager.setScavengePeriod((secs+9)/10); + } + + /* ------------------------------------------------------------ */ + @Override + protected void doInvalidate() + throws IllegalStateException + { + super.doInvalidate(); + + // Remove from the disk + if (_hashSessionManager._storeDir!=null && getId()!=null) + { + String id=getId(); + File f = new File(_hashSessionManager._storeDir, id); + f.delete(); + } + } + + /* ------------------------------------------------------------ */ + synchronized void save(boolean reactivate) + throws Exception + { + // Only idle the session if not already idled and no previous save/idle has failed + if (!isIdled() && !_saveFailed) + { + if (LOG.isDebugEnabled()) + LOG.debug("Saving {} {}",super.getId(),reactivate); + + File file = null; + FileOutputStream fos = null; + + try + { + file = new File(_hashSessionManager._storeDir, super.getId()); + + if (file.exists()) + file.delete(); + file.createNewFile(); + fos = new FileOutputStream(file); + willPassivate(); + save(fos); + IO.close(fos); + if (reactivate) + didActivate(); + else + clearAttributes(); + } + catch (Exception e) + { + saveFailed(); // We won't try again for this session + if (fos != null) IO.close(fos); + if (file != null) file.delete(); // No point keeping the file if we didn't save the whole session + throw e; + } + } + } + /* ------------------------------------------------------------ */ + public synchronized void save(OutputStream os) throws IOException + { + DataOutputStream out = new DataOutputStream(os); + out.writeUTF(getClusterId()); + out.writeUTF(getNodeId()); + out.writeLong(getCreationTime()); + out.writeLong(getAccessed()); + + /* Don't write these out, as they don't make sense to store because they + * either they cannot be true or their value will be restored in the + * Session constructor. + */ + //out.writeBoolean(_invalid); + //out.writeBoolean(_doInvalidate); + //out.writeLong(_maxIdleMs); + //out.writeBoolean( _newSession); + out.writeInt(getRequests()); + out.writeInt(getAttributes()); + ObjectOutputStream oos = new ObjectOutputStream(out); + Enumeration<String> e=getAttributeNames(); + while(e.hasMoreElements()) + { + String key=e.nextElement(); + oos.writeUTF(key); + oos.writeObject(doGet(key)); + } + oos.close(); + } + + /* ------------------------------------------------------------ */ + public synchronized void deIdle() + { + if (isIdled()) + { + // Access now to prevent race with idling period + access(System.currentTimeMillis()); + + if (LOG.isDebugEnabled()) + LOG.debug("De-idling " + super.getId()); + + FileInputStream fis = null; + + try + { + File file = new File(_hashSessionManager._storeDir, super.getId()); + if (!file.exists() || !file.canRead()) + throw new FileNotFoundException(file.getName()); + + fis = new FileInputStream(file); + _idled = false; + _hashSessionManager.restoreSession(fis, this); + IO.close(fis); + + didActivate(); + + // If we are doing period saves, then there is no point deleting at this point + if (_hashSessionManager._savePeriodMs == 0) + file.delete(); + } + catch (Exception e) + { + LOG.warn("Problem de-idling session " + super.getId(), e); + if (fis != null) IO.close(fis);//Must ensure closed before invalidate + invalidate(); + } + } + } + + + /* ------------------------------------------------------------ */ + /** + * Idle the session to reduce session memory footprint. + * + * The session is idled by persisting it, then clearing the session values attribute map and finally setting + * it to an idled state. + */ + public synchronized void idle() + throws Exception + { + save(false); + _idled = true; + } + + /* ------------------------------------------------------------ */ + public synchronized boolean isIdled() + { + return _idled; + } + + /* ------------------------------------------------------------ */ + public synchronized boolean isSaveFailed() + { + return _saveFailed; + } + + /* ------------------------------------------------------------ */ + public synchronized void saveFailed() + { + _saveFailed = true; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/session/JDBCSessionIdManager.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,1040 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.session; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.sql.Blob; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Random; +import java.util.Timer; +import java.util.TimerTask; + +import javax.naming.InitialContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import javax.sql.DataSource; + +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.SessionManager; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.log.Logger; + + + +/** + * JDBCSessionIdManager + * + * SessionIdManager implementation that uses a database to store in-use session ids, + * to support distributed sessions. + * + */ +public class JDBCSessionIdManager extends AbstractSessionIdManager +{ + final static Logger LOG = SessionHandler.LOG; + + protected final HashSet<String> _sessionIds = new HashSet<String>(); + protected Server _server; + protected Driver _driver; + protected String _driverClassName; + protected String _connectionUrl; + protected DataSource _datasource; + protected String _jndiName; + protected String _sessionIdTable = "JettySessionIds"; + protected String _sessionTable = "JettySessions"; + protected String _sessionTableRowId = "rowId"; + + protected Timer _timer; //scavenge timer + protected TimerTask _task; //scavenge task + protected long _lastScavengeTime; + protected long _scavengeIntervalMs = 1000L * 60 * 10; //10mins + protected String _blobType; //if not set, is deduced from the type of the database at runtime + protected String _longType; //if not set, is deduced from the type of the database at runtime + + protected String _createSessionIdTable; + protected String _createSessionTable; + + protected String _selectBoundedExpiredSessions; + protected String _deleteOldExpiredSessions; + + protected String _insertId; + protected String _deleteId; + protected String _queryId; + + protected String _insertSession; + protected String _deleteSession; + protected String _updateSession; + protected String _updateSessionNode; + protected String _updateSessionAccessTime; + + protected DatabaseAdaptor _dbAdaptor; + + private String _selectExpiredSessions; + + + /** + * DatabaseAdaptor + * + * Handles differences between databases. + * + * Postgres uses the getBytes and setBinaryStream methods to access + * a "bytea" datatype, which can be up to 1Gb of binary data. MySQL + * is happy to use the "blob" type and getBlob() methods instead. + * + * TODO if the differences become more major it would be worthwhile + * refactoring this class. + */ + public class DatabaseAdaptor + { + String _dbName; + boolean _isLower; + boolean _isUpper; + + + + public DatabaseAdaptor (DatabaseMetaData dbMeta) + throws SQLException + { + _dbName = dbMeta.getDatabaseProductName().toLowerCase(Locale.ENGLISH); + LOG.debug ("Using database {}",_dbName); + _isLower = dbMeta.storesLowerCaseIdentifiers(); + _isUpper = dbMeta.storesUpperCaseIdentifiers(); + } + + /** + * Convert a camel case identifier into either upper or lower + * depending on the way the db stores identifiers. + * + * @param identifier + * @return the converted identifier + */ + public String convertIdentifier (String identifier) + { + if (_isLower) + return identifier.toLowerCase(Locale.ENGLISH); + if (_isUpper) + return identifier.toUpperCase(Locale.ENGLISH); + + return identifier; + } + + public String getDBName () + { + return _dbName; + } + + public String getBlobType () + { + if (_blobType != null) + return _blobType; + + if (_dbName.startsWith("postgres")) + return "bytea"; + + return "blob"; + } + + public String getLongType () + { + if (_longType != null) + return _longType; + + if (_dbName.startsWith("oracle")) + return "number(20)"; + + return "bigint"; + } + + public InputStream getBlobInputStream (ResultSet result, String columnName) + throws SQLException + { + if (_dbName.startsWith("postgres")) + { + byte[] bytes = result.getBytes(columnName); + return new ByteArrayInputStream(bytes); + } + + Blob blob = result.getBlob(columnName); + return blob.getBinaryStream(); + } + + /** + * rowId is a reserved word for Oracle, so change the name of this column + * @return + */ + public String getRowIdColumnName () + { + if (_dbName != null && _dbName.startsWith("oracle")) + return "srowId"; + + return "rowId"; + } + + + public boolean isEmptyStringNull () + { + return (_dbName.startsWith("oracle")); + } + + public PreparedStatement getLoadStatement (Connection connection, String rowId, String contextPath, String virtualHosts) + throws SQLException + { + if (contextPath == null || "".equals(contextPath)) + { + if (isEmptyStringNull()) + { + PreparedStatement statement = connection.prepareStatement("select * from "+_sessionTable+ + " where sessionId = ? and contextPath is null and virtualHost = ?"); + statement.setString(1, rowId); + statement.setString(2, virtualHosts); + + return statement; + } + } + + + + PreparedStatement statement = connection.prepareStatement("select * from "+_sessionTable+ + " where sessionId = ? and contextPath = ? and virtualHost = ?"); + statement.setString(1, rowId); + statement.setString(2, contextPath); + statement.setString(3, virtualHosts); + + return statement; + } + } + + + + public JDBCSessionIdManager(Server server) + { + super(); + _server=server; + } + + public JDBCSessionIdManager(Server server, Random random) + { + super(random); + _server=server; + } + + /** + * Configure jdbc connection information via a jdbc Driver + * + * @param driverClassName + * @param connectionUrl + */ + public void setDriverInfo (String driverClassName, String connectionUrl) + { + _driverClassName=driverClassName; + _connectionUrl=connectionUrl; + } + + /** + * Configure jdbc connection information via a jdbc Driver + * + * @param driverClass + * @param connectionUrl + */ + public void setDriverInfo (Driver driverClass, String connectionUrl) + { + _driver=driverClass; + _connectionUrl=connectionUrl; + } + + + public void setDatasource (DataSource ds) + { + _datasource = ds; + } + + public DataSource getDataSource () + { + return _datasource; + } + + public String getDriverClassName() + { + return _driverClassName; + } + + public String getConnectionUrl () + { + return _connectionUrl; + } + + public void setDatasourceName (String jndi) + { + _jndiName=jndi; + } + + public String getDatasourceName () + { + return _jndiName; + } + + public void setBlobType (String name) + { + _blobType = name; + } + + public String getBlobType () + { + return _blobType; + } + + + + public String getLongType() + { + return _longType; + } + + public void setLongType(String longType) + { + this._longType = longType; + } + + public void setScavengeInterval (long sec) + { + if (sec<=0) + sec=60; + + long old_period=_scavengeIntervalMs; + long period=sec*1000L; + + _scavengeIntervalMs=period; + + //add a bit of variability into the scavenge time so that not all + //nodes with the same scavenge time sync up + long tenPercent = _scavengeIntervalMs/10; + if ((System.currentTimeMillis()%2) == 0) + _scavengeIntervalMs += tenPercent; + + if (LOG.isDebugEnabled()) + LOG.debug("Scavenging every "+_scavengeIntervalMs+" ms"); + if (_timer!=null && (period!=old_period || _task==null)) + { + synchronized (this) + { + if (_task!=null) + _task.cancel(); + _task = new TimerTask() + { + @Override + public void run() + { + scavenge(); + } + }; + _timer.schedule(_task,_scavengeIntervalMs,_scavengeIntervalMs); + } + } + } + + public long getScavengeInterval () + { + return _scavengeIntervalMs/1000; + } + + + public void addSession(HttpSession session) + { + if (session == null) + return; + + synchronized (_sessionIds) + { + String id = ((JDBCSessionManager.Session)session).getClusterId(); + try + { + insert(id); + _sessionIds.add(id); + } + catch (Exception e) + { + LOG.warn("Problem storing session id="+id, e); + } + } + } + + public void removeSession(HttpSession session) + { + if (session == null) + return; + + removeSession(((JDBCSessionManager.Session)session).getClusterId()); + } + + + + public void removeSession (String id) + { + + if (id == null) + return; + + synchronized (_sessionIds) + { + if (LOG.isDebugEnabled()) + LOG.debug("Removing session id="+id); + try + { + _sessionIds.remove(id); + delete(id); + } + catch (Exception e) + { + LOG.warn("Problem removing session id="+id, e); + } + } + + } + + + /** + * Get the session id without any node identifier suffix. + * + * @see org.eclipse.jetty.server.SessionIdManager#getClusterId(java.lang.String) + */ + public String getClusterId(String nodeId) + { + int dot=nodeId.lastIndexOf('.'); + return (dot>0)?nodeId.substring(0,dot):nodeId; + } + + + /** + * Get the session id, including this node's id as a suffix. + * + * @see org.eclipse.jetty.server.SessionIdManager#getNodeId(java.lang.String, javax.servlet.http.HttpServletRequest) + */ + public String getNodeId(String clusterId, HttpServletRequest request) + { + if (_workerName!=null) + return clusterId+'.'+_workerName; + + return clusterId; + } + + + public boolean idInUse(String id) + { + if (id == null) + return false; + + String clusterId = getClusterId(id); + boolean inUse = false; + synchronized (_sessionIds) + { + inUse = _sessionIds.contains(clusterId); + } + + + if (inUse) + return true; //optimisation - if this session is one we've been managing, we can check locally + + //otherwise, we need to go to the database to check + try + { + return exists(clusterId); + } + catch (Exception e) + { + LOG.warn("Problem checking inUse for id="+clusterId, e); + return false; + } + } + + /** + * Invalidate the session matching the id on all contexts. + * + * @see org.eclipse.jetty.server.SessionIdManager#invalidateAll(java.lang.String) + */ + public void invalidateAll(String id) + { + //take the id out of the list of known sessionids for this node + removeSession(id); + + synchronized (_sessionIds) + { + //tell all contexts that may have a session object with this id to + //get rid of them + Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class); + for (int i=0; contexts!=null && i<contexts.length; i++) + { + SessionHandler sessionHandler = (SessionHandler)((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class); + if (sessionHandler != null) + { + SessionManager manager = sessionHandler.getSessionManager(); + + if (manager != null && manager instanceof JDBCSessionManager) + { + ((JDBCSessionManager)manager).invalidateSession(id); + } + } + } + } + } + + + /** + * Start up the id manager. + * + * Makes necessary database tables and starts a Session + * scavenger thread. + */ + @Override + public void doStart() + throws Exception + { + initializeDatabase(); + prepareTables(); + cleanExpiredSessions(); + super.doStart(); + if (LOG.isDebugEnabled()) + LOG.debug("Scavenging interval = "+getScavengeInterval()+" sec"); + _timer=new Timer("JDBCSessionScavenger", true); + setScavengeInterval(getScavengeInterval()); + } + + /** + * Stop the scavenger. + */ + @Override + public void doStop () + throws Exception + { + synchronized(this) + { + if (_task!=null) + _task.cancel(); + if (_timer!=null) + _timer.cancel(); + _timer=null; + } + _sessionIds.clear(); + super.doStop(); + } + + /** + * Get a connection from the driver or datasource. + * + * @return the connection for the datasource + * @throws SQLException + */ + protected Connection getConnection () + throws SQLException + { + if (_datasource != null) + return _datasource.getConnection(); + else + return DriverManager.getConnection(_connectionUrl); + } + + + + + + /** + * Set up the tables in the database + * @throws SQLException + */ + private void prepareTables() + throws SQLException + { + _createSessionIdTable = "create table "+_sessionIdTable+" (id varchar(120), primary key(id))"; + _selectBoundedExpiredSessions = "select * from "+_sessionTable+" where expiryTime >= ? and expiryTime <= ?"; + _selectExpiredSessions = "select * from "+_sessionTable+" where expiryTime >0 and expiryTime <= ?"; + _deleteOldExpiredSessions = "delete from "+_sessionTable+" where expiryTime >0 and expiryTime <= ?"; + + _insertId = "insert into "+_sessionIdTable+" (id) values (?)"; + _deleteId = "delete from "+_sessionIdTable+" where id = ?"; + _queryId = "select * from "+_sessionIdTable+" where id = ?"; + + Connection connection = null; + try + { + //make the id table + connection = getConnection(); + connection.setAutoCommit(true); + DatabaseMetaData metaData = connection.getMetaData(); + _dbAdaptor = new DatabaseAdaptor(metaData); + _sessionTableRowId = _dbAdaptor.getRowIdColumnName(); + + //checking for table existence is case-sensitive, but table creation is not + String tableName = _dbAdaptor.convertIdentifier(_sessionIdTable); + ResultSet result = metaData.getTables(null, null, tableName, null); + if (!result.next()) + { + //table does not exist, so create it + connection.createStatement().executeUpdate(_createSessionIdTable); + } + + //make the session table if necessary + tableName = _dbAdaptor.convertIdentifier(_sessionTable); + result = metaData.getTables(null, null, tableName, null); + if (!result.next()) + { + //table does not exist, so create it + String blobType = _dbAdaptor.getBlobType(); + String longType = _dbAdaptor.getLongType(); + _createSessionTable = "create table "+_sessionTable+" ("+_sessionTableRowId+" varchar(120), sessionId varchar(120), "+ + " contextPath varchar(60), virtualHost varchar(60), lastNode varchar(60), accessTime "+longType+", "+ + " lastAccessTime "+longType+", createTime "+longType+", cookieTime "+longType+", "+ + " lastSavedTime "+longType+", expiryTime "+longType+", map "+blobType+", primary key("+_sessionTableRowId+"))"; + connection.createStatement().executeUpdate(_createSessionTable); + } + + //make some indexes on the JettySessions table + String index1 = "idx_"+_sessionTable+"_expiry"; + String index2 = "idx_"+_sessionTable+"_session"; + + result = metaData.getIndexInfo(null, null, tableName, false, false); + boolean index1Exists = false; + boolean index2Exists = false; + while (result.next()) + { + String idxName = result.getString("INDEX_NAME"); + if (index1.equalsIgnoreCase(idxName)) + index1Exists = true; + else if (index2.equalsIgnoreCase(idxName)) + index2Exists = true; + } + if (!(index1Exists && index2Exists)) + { + Statement statement = connection.createStatement(); + try + { + if (!index1Exists) + statement.executeUpdate("create index "+index1+" on "+_sessionTable+" (expiryTime)"); + if (!index2Exists) + statement.executeUpdate("create index "+index2+" on "+_sessionTable+" (sessionId, contextPath)"); + } + finally + { + if (statement!=null) + { + try { statement.close(); } + catch(Exception e) { LOG.warn(e); } + } + } + } + + //set up some strings representing the statements for session manipulation + _insertSession = "insert into "+_sessionTable+ + " ("+_sessionTableRowId+", sessionId, contextPath, virtualHost, lastNode, accessTime, lastAccessTime, createTime, cookieTime, lastSavedTime, expiryTime, map) "+ + " values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + _deleteSession = "delete from "+_sessionTable+ + " where "+_sessionTableRowId+" = ?"; + + _updateSession = "update "+_sessionTable+ + " set lastNode = ?, accessTime = ?, lastAccessTime = ?, lastSavedTime = ?, expiryTime = ?, map = ? where "+_sessionTableRowId+" = ?"; + + _updateSessionNode = "update "+_sessionTable+ + " set lastNode = ? where "+_sessionTableRowId+" = ?"; + + _updateSessionAccessTime = "update "+_sessionTable+ + " set lastNode = ?, accessTime = ?, lastAccessTime = ?, lastSavedTime = ?, expiryTime = ? where "+_sessionTableRowId+" = ?"; + + + } + finally + { + if (connection != null) + connection.close(); + } + } + + /** + * Insert a new used session id into the table. + * + * @param id + * @throws SQLException + */ + private void insert (String id) + throws SQLException + { + Connection connection = null; + PreparedStatement statement = null; + PreparedStatement query = null; + try + { + connection = getConnection(); + connection.setAutoCommit(true); + query = connection.prepareStatement(_queryId); + query.setString(1, id); + ResultSet result = query.executeQuery(); + //only insert the id if it isn't in the db already + if (!result.next()) + { + statement = connection.prepareStatement(_insertId); + statement.setString(1, id); + statement.executeUpdate(); + } + } + finally + { + if (query!=null) + { + try { query.close(); } + catch(Exception e) { LOG.warn(e); } + } + + if (statement!=null) + { + try { statement.close(); } + catch(Exception e) { LOG.warn(e); } + } + + if (connection != null) + connection.close(); + } + } + + /** + * Remove a session id from the table. + * + * @param id + * @throws SQLException + */ + private void delete (String id) + throws SQLException + { + Connection connection = null; + PreparedStatement statement = null; + try + { + connection = getConnection(); + connection.setAutoCommit(true); + statement = connection.prepareStatement(_deleteId); + statement.setString(1, id); + statement.executeUpdate(); + } + finally + { + if (statement!=null) + { + try { statement.close(); } + catch(Exception e) { LOG.warn(e); } + } + + if (connection != null) + connection.close(); + } + } + + + /** + * Check if a session id exists. + * + * @param id + * @return + * @throws SQLException + */ + private boolean exists (String id) + throws SQLException + { + Connection connection = null; + PreparedStatement statement = null; + try + { + connection = getConnection(); + connection.setAutoCommit(true); + statement = connection.prepareStatement(_queryId); + statement.setString(1, id); + ResultSet result = statement.executeQuery(); + return result.next(); + } + finally + { + if (statement!=null) + { + try { statement.close(); } + catch(Exception e) { LOG.warn(e); } + } + + if (connection != null) + connection.close(); + } + } + + /** + * Look for sessions in the database that have expired. + * + * We do this in the SessionIdManager and not the SessionManager so + * that we only have 1 scavenger, otherwise if there are n SessionManagers + * there would be n scavengers, all contending for the database. + * + * We look first for sessions that expired in the previous interval, then + * for sessions that expired previously - these are old sessions that no + * node is managing any more and have become stuck in the database. + */ + private void scavenge () + { + Connection connection = null; + PreparedStatement statement = null; + List<String> expiredSessionIds = new ArrayList<String>(); + try + { + if (LOG.isDebugEnabled()) + LOG.debug("Scavenge sweep started at "+System.currentTimeMillis()); + if (_lastScavengeTime > 0) + { + connection = getConnection(); + connection.setAutoCommit(true); + //"select sessionId from JettySessions where expiryTime > (lastScavengeTime - scanInterval) and expiryTime < lastScavengeTime"; + statement = connection.prepareStatement(_selectBoundedExpiredSessions); + long lowerBound = (_lastScavengeTime - _scavengeIntervalMs); + long upperBound = _lastScavengeTime; + if (LOG.isDebugEnabled()) + LOG.debug (" Searching for sessions expired between "+lowerBound + " and "+upperBound); + + statement.setLong(1, lowerBound); + statement.setLong(2, upperBound); + ResultSet result = statement.executeQuery(); + while (result.next()) + { + String sessionId = result.getString("sessionId"); + expiredSessionIds.add(sessionId); + if (LOG.isDebugEnabled()) LOG.debug (" Found expired sessionId="+sessionId); + } + + //tell the SessionManagers to expire any sessions with a matching sessionId in memory + Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class); + for (int i=0; contexts!=null && i<contexts.length; i++) + { + + SessionHandler sessionHandler = (SessionHandler)((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class); + if (sessionHandler != null) + { + SessionManager manager = sessionHandler.getSessionManager(); + if (manager != null && manager instanceof JDBCSessionManager) + { + ((JDBCSessionManager)manager).expire(expiredSessionIds); + } + } + } + + //find all sessions that have expired at least a couple of scanIntervals ago and just delete them + upperBound = _lastScavengeTime - (2 * _scavengeIntervalMs); + if (upperBound > 0) + { + if (LOG.isDebugEnabled()) LOG.debug("Deleting old expired sessions expired before "+upperBound); + try + { + statement = connection.prepareStatement(_deleteOldExpiredSessions); + statement.setLong(1, upperBound); + int rows = statement.executeUpdate(); + if (LOG.isDebugEnabled()) LOG.debug("Deleted "+rows+" rows of old sessions expired before "+upperBound); + } + finally + { + if (statement!=null) + { + try { statement.close(); } + catch(Exception e) { LOG.warn(e); } + } + } + } + } + } + catch (Exception e) + { + if (isRunning()) + LOG.warn("Problem selecting expired sessions", e); + else + LOG.ignore(e); + } + finally + { + _lastScavengeTime=System.currentTimeMillis(); + if (LOG.isDebugEnabled()) LOG.debug("Scavenge sweep ended at "+_lastScavengeTime); + if (connection != null) + { + try + { + connection.close(); + } + catch (SQLException e) + { + LOG.warn(e); + } + } + } + } + + /** + * Get rid of sessions and sessionids from sessions that have already expired + * @throws Exception + */ + private void cleanExpiredSessions () + { + Connection connection = null; + PreparedStatement statement = null; + Statement sessionsTableStatement = null; + Statement sessionIdsTableStatement = null; + List<String> expiredSessionIds = new ArrayList<String>(); + try + { + connection = getConnection(); + connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); + connection.setAutoCommit(false); + + statement = connection.prepareStatement(_selectExpiredSessions); + long now = System.currentTimeMillis(); + if (LOG.isDebugEnabled()) LOG.debug ("Searching for sessions expired before {}", now); + + statement.setLong(1, now); + ResultSet result = statement.executeQuery(); + while (result.next()) + { + String sessionId = result.getString("sessionId"); + expiredSessionIds.add(sessionId); + if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId={}", sessionId); + } + + sessionsTableStatement = null; + sessionIdsTableStatement = null; + + if (!expiredSessionIds.isEmpty()) + { + sessionsTableStatement = connection.createStatement(); + sessionsTableStatement.executeUpdate(createCleanExpiredSessionsSql("delete from "+_sessionTable+" where sessionId in ", expiredSessionIds)); + sessionIdsTableStatement = connection.createStatement(); + sessionIdsTableStatement.executeUpdate(createCleanExpiredSessionsSql("delete from "+_sessionIdTable+" where id in ", expiredSessionIds)); + } + connection.commit(); + + synchronized (_sessionIds) + { + _sessionIds.removeAll(expiredSessionIds); //in case they were in our local cache of session ids + } + } + catch (Exception e) + { + if (connection != null) + { + try + { + LOG.warn("Rolling back clean of expired sessions", e); + connection.rollback(); + } + catch (Exception x) { LOG.warn("Rollback of expired sessions failed", x);} + } + } + finally + { + if (sessionIdsTableStatement!=null) + { + try { sessionIdsTableStatement.close(); } + catch(Exception e) { LOG.warn(e); } + } + + if (sessionsTableStatement!=null) + { + try { sessionsTableStatement.close(); } + catch(Exception e) { LOG.warn(e); } + } + + if (statement!=null) + { + try { statement.close(); } + catch(Exception e) { LOG.warn(e); } + } + + try + { + if (connection != null) + connection.close(); + } + catch (SQLException e) + { + LOG.warn(e); + } + } + } + + + /** + * + * @param sql + * @param connection + * @param expiredSessionIds + * @throws Exception + */ + private String createCleanExpiredSessionsSql (String sql,Collection<String> expiredSessionIds) + throws Exception + { + StringBuffer buff = new StringBuffer(); + buff.append(sql); + buff.append("("); + Iterator<String> itor = expiredSessionIds.iterator(); + while (itor.hasNext()) + { + buff.append("'"+(itor.next())+"'"); + if (itor.hasNext()) + buff.append(","); + } + buff.append(")"); + + if (LOG.isDebugEnabled()) LOG.debug("Cleaning expired sessions with: {}", buff); + return buff.toString(); + } + + private void initializeDatabase () + throws Exception + { + if (_datasource != null) + return; //already set up + + if (_jndiName!=null) + { + InitialContext ic = new InitialContext(); + _datasource = (DataSource)ic.lookup(_jndiName); + } + else if ( _driver != null && _connectionUrl != null ) + { + DriverManager.registerDriver(_driver); + } + else if (_driverClassName != null && _connectionUrl != null) + { + Class.forName(_driverClassName); + } + else + throw new IllegalStateException("No database configured for sessions"); + } + + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/session/JDBCSessionManager.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,1174 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + + +package org.eclipse.jetty.server.session; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; + +import javax.servlet.SessionTrackingMode; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionListener; + +import org.eclipse.jetty.server.SessionIdManager; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + * JDBCSessionManager + * + * SessionManager that persists sessions to a database to enable clustering. + * + * Session data is persisted to the JettySessions table: + * + * rowId (unique in cluster: webapp name/path + virtualhost + sessionId) + * contextPath (of the context owning the session) + * sessionId (unique in a context) + * lastNode (name of node last handled session) + * accessTime (time in milliseconds session was accessed) + * lastAccessTime (previous time in milliseconds session was accessed) + * createTime (time in milliseconds session created) + * cookieTime (time in milliseconds session cookie created) + * lastSavedTime (last time in milliseconds session access times were saved) + * expiryTime (time in milliseconds that the session is due to expire) + * map (attribute map) + * + * As an optimization, to prevent thrashing the database, we do not persist + * the accessTime and lastAccessTime every time the session is accessed. Rather, + * we write it out every so often. The frequency is controlled by the saveIntervalSec + * field. + */ +public class JDBCSessionManager extends AbstractSessionManager +{ + private static final Logger LOG = Log.getLogger(JDBCSessionManager.class); + + private ConcurrentHashMap<String, AbstractSession> _sessions; + protected JDBCSessionIdManager _jdbcSessionIdMgr = null; + protected long _saveIntervalSec = 60; //only persist changes to session access times every 60 secs + + + + + /** + * Session + * + * Session instance. + */ + public class Session extends AbstractSession + { + private static final long serialVersionUID = 5208464051134226143L; + + /** + * If dirty, session needs to be (re)persisted + */ + private boolean _dirty=false; + + + /** + * Time in msec since the epoch that a session cookie was set for this session + */ + private long _cookieSet; + + + /** + * Time in msec since the epoch that the session will expire + */ + private long _expiryTime; + + + /** + * Time in msec since the epoch that the session was last persisted + */ + private long _lastSaved; + + + /** + * Unique identifier of the last node to host the session + */ + private String _lastNode; + + + /** + * Virtual host for context (used to help distinguish 2 sessions with same id on different contexts) + */ + private String _virtualHost; + + + /** + * Unique row in db for session + */ + private String _rowId; + + + /** + * Mangled context name (used to help distinguish 2 sessions with same id on different contexts) + */ + private String _canonicalContext; + + + /** + * Session from a request. + * + * @param request + */ + protected Session (HttpServletRequest request) + { + super(JDBCSessionManager.this,request); + int maxInterval=getMaxInactiveInterval(); + _expiryTime = (maxInterval <= 0 ? 0 : (System.currentTimeMillis() + maxInterval*1000L)); + _virtualHost = JDBCSessionManager.getVirtualHost(_context); + _canonicalContext = canonicalize(_context.getContextPath()); + _lastNode = getSessionIdManager().getWorkerName(); + } + + + /** + * Session restored from database + * @param sessionId + * @param rowId + * @param created + * @param accessed + */ + protected Session (String sessionId, String rowId, long created, long accessed) + { + super(JDBCSessionManager.this, created, accessed, sessionId); + _rowId = rowId; + } + + + protected synchronized String getRowId() + { + return _rowId; + } + + protected synchronized void setRowId(String rowId) + { + _rowId = rowId; + } + + public synchronized void setVirtualHost (String vhost) + { + _virtualHost=vhost; + } + + public synchronized String getVirtualHost () + { + return _virtualHost; + } + + public synchronized long getLastSaved () + { + return _lastSaved; + } + + public synchronized void setLastSaved (long time) + { + _lastSaved=time; + } + + public synchronized void setExpiryTime (long time) + { + _expiryTime=time; + } + + public synchronized long getExpiryTime () + { + return _expiryTime; + } + + + public synchronized void setCanonicalContext(String str) + { + _canonicalContext=str; + } + + public synchronized String getCanonicalContext () + { + return _canonicalContext; + } + + public void setCookieSet (long ms) + { + _cookieSet = ms; + } + + public synchronized long getCookieSet () + { + return _cookieSet; + } + + public synchronized void setLastNode (String node) + { + _lastNode=node; + } + + public synchronized String getLastNode () + { + return _lastNode; + } + + @Override + public void setAttribute (String name, Object value) + { + super.setAttribute(name, value); + _dirty=true; + } + + @Override + public void removeAttribute (String name) + { + super.removeAttribute(name); + _dirty=true; + } + + @Override + protected void cookieSet() + { + _cookieSet = getAccessed(); + } + + /** + * Entry to session. + * Called by SessionHandler on inbound request and the session already exists in this node's memory. + * + * @see org.eclipse.jetty.server.session.AbstractSession#access(long) + */ + @Override + protected boolean access(long time) + { + synchronized (this) + { + if (super.access(time)) + { + int maxInterval=getMaxInactiveInterval(); + _expiryTime = (maxInterval <= 0 ? 0 : (time + maxInterval*1000L)); + return true; + } + return false; + } + } + + + + /** + * Exit from session + * @see org.eclipse.jetty.server.session.AbstractSession#complete() + */ + @Override + protected void complete() + { + synchronized (this) + { + super.complete(); + try + { + if (isValid()) + { + if (_dirty) + { + //The session attributes have changed, write to the db, ensuring + //http passivation/activation listeners called + willPassivate(); + updateSession(this); + didActivate(); + } + else if ((getAccessed() - _lastSaved) >= (getSaveInterval() * 1000L)) + { + updateSessionAccessTime(this); + } + } + } + catch (Exception e) + { + LOG.warn("Problem persisting changed session data id="+getId(), e); + } + finally + { + _dirty=false; + } + } + } + + @Override + protected void timeout() throws IllegalStateException + { + if (LOG.isDebugEnabled()) + LOG.debug("Timing out session id="+getClusterId()); + super.timeout(); + } + + @Override + public String toString () + { + return "Session rowId="+_rowId+",id="+getId()+",lastNode="+_lastNode+ + ",created="+getCreationTime()+",accessed="+getAccessed()+ + ",lastAccessed="+getLastAccessedTime()+",cookieSet="+_cookieSet+ + ",lastSaved="+_lastSaved+",expiry="+_expiryTime; + } + } + + + + + /** + * ClassLoadingObjectInputStream + * + * Used to persist the session attribute map + */ + protected class ClassLoadingObjectInputStream extends ObjectInputStream + { + public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException + { + super(in); + } + + public ClassLoadingObjectInputStream () throws IOException + { + super(); + } + + @Override + public Class<?> resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException + { + try + { + return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader()); + } + catch (ClassNotFoundException e) + { + return super.resolveClass(cl); + } + } + } + + + /** + * Set the time in seconds which is the interval between + * saving the session access time to the database. + * + * This is an optimization that prevents the database from + * being overloaded when a session is accessed very frequently. + * + * On session exit, if the session attributes have NOT changed, + * the time at which we last saved the accessed + * time is compared to the current accessed time. If the interval + * is at least saveIntervalSecs, then the access time will be + * persisted to the database. + * + * If any session attribute does change, then the attributes and + * the accessed time are persisted. + * + * @param sec + */ + public void setSaveInterval (long sec) + { + _saveIntervalSec=sec; + } + + public long getSaveInterval () + { + return _saveIntervalSec; + } + + + + /** + * A method that can be implemented in subclasses to support + * distributed caching of sessions. This method will be + * called whenever the session is written to the database + * because the session data has changed. + * + * This could be used eg with a JMS backplane to notify nodes + * that the session has changed and to delete the session from + * the node's cache, and re-read it from the database. + * @param session + */ + public void cacheInvalidate (Session session) + { + + } + + + /** + * A session has been requested by its id on this node. + * + * Load the session by id AND context path from the database. + * Multiple contexts may share the same session id (due to dispatching) + * but they CANNOT share the same contents. + * + * Check if last node id is my node id, if so, then the session we have + * in memory cannot be stale. If another node used the session last, then + * we need to refresh from the db. + * + * NOTE: this method will go to the database, so if you only want to check + * for the existence of a Session in memory, use _sessions.get(id) instead. + * + * @see org.eclipse.jetty.server.session.AbstractSessionManager#getSession(java.lang.String) + */ + @Override + public Session getSession(String idInCluster) + { + Session session = null; + Session memSession = (Session)_sessions.get(idInCluster); + + synchronized (this) + { + //check if we need to reload the session - + //as an optimization, don't reload on every access + //to reduce the load on the database. This introduces a window of + //possibility that the node may decide that the session is local to it, + //when the session has actually been live on another node, and then + //re-migrated to this node. This should be an extremely rare occurrence, + //as load-balancers are generally well-behaved and consistently send + //sessions to the same node, changing only iff that node fails. + //Session data = null; + long now = System.currentTimeMillis(); + if (LOG.isDebugEnabled()) + { + if (memSession==null) + LOG.debug("getSession("+idInCluster+"): not in session map,"+ + " now="+now+ + " lastSaved="+(memSession==null?0:memSession._lastSaved)+ + " interval="+(_saveIntervalSec * 1000L)); + else + LOG.debug("getSession("+idInCluster+"): in session map, "+ + " now="+now+ + " lastSaved="+(memSession==null?0:memSession._lastSaved)+ + " interval="+(_saveIntervalSec * 1000L)+ + " lastNode="+memSession._lastNode+ + " thisNode="+getSessionIdManager().getWorkerName()+ + " difference="+(now - memSession._lastSaved)); + } + + try + { + if (memSession==null) + { + LOG.debug("getSession("+idInCluster+"): no session in session map. Reloading session data from db."); + session = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context)); + } + else if ((now - memSession._lastSaved) >= (_saveIntervalSec * 1000L)) + { + LOG.debug("getSession("+idInCluster+"): stale session. Reloading session data from db."); + session = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context)); + } + else + { + LOG.debug("getSession("+idInCluster+"): session in session map"); + session = memSession; + } + } + catch (Exception e) + { + LOG.warn("Unable to load session "+idInCluster, e); + return null; + } + + + //If we have a session + if (session != null) + { + //If the session was last used on a different node, or session doesn't exist on this node + if (!session.getLastNode().equals(getSessionIdManager().getWorkerName()) || memSession==null) + { + //if session doesn't expire, or has not already expired, update it and put it in this nodes' memory + if (session._expiryTime <= 0 || session._expiryTime > now) + { + if (LOG.isDebugEnabled()) + LOG.debug("getSession("+idInCluster+"): lastNode="+session.getLastNode()+" thisNode="+getSessionIdManager().getWorkerName()); + + session.setLastNode(getSessionIdManager().getWorkerName()); + _sessions.put(idInCluster, session); + + //update in db: if unable to update, session will be scavenged later + try + { + updateSessionNode(session); + session.didActivate(); + } + catch (Exception e) + { + LOG.warn("Unable to update freshly loaded session "+idInCluster, e); + return null; + } + } + else + { + LOG.debug("getSession ({}): Session has expired", idInCluster); + session=null; + } + + } + else + { + //the session loaded from the db and the one in memory are the same, so keep using the one in memory + session = memSession; + LOG.debug("getSession({}): Session not stale {}", idInCluster,session); + } + } + else + { + //No session in db with matching id and context path. + LOG.debug("getSession({}): No session in database matching id={}",idInCluster,idInCluster); + } + + return session; + } + } + + /** + * Get the number of sessions. + * + * @see org.eclipse.jetty.server.session.AbstractSessionManager#getSessions() + */ + @Override + public int getSessions() + { + int size = 0; + synchronized (this) + { + size = _sessions.size(); + } + return size; + } + + + /** + * Start the session manager. + * + * @see org.eclipse.jetty.server.session.AbstractSessionManager#doStart() + */ + @Override + public void doStart() throws Exception + { + if (_sessionIdManager==null) + throw new IllegalStateException("No session id manager defined"); + + _jdbcSessionIdMgr = (JDBCSessionIdManager)_sessionIdManager; + + _sessions = new ConcurrentHashMap<String, AbstractSession>(); + + super.doStart(); + } + + + /** + * Stop the session manager. + * + * @see org.eclipse.jetty.server.session.AbstractSessionManager#doStop() + */ + @Override + public void doStop() throws Exception + { + _sessions.clear(); + _sessions = null; + + super.doStop(); + } + + @Override + protected void invalidateSessions() + { + //Do nothing - we don't want to remove and + //invalidate all the sessions because this + //method is called from doStop(), and just + //because this context is stopping does not + //mean that we should remove the session from + //any other nodes + } + + + /** + * Invalidate a session. + * + * @param idInCluster + */ + protected void invalidateSession (String idInCluster) + { + Session session = null; + synchronized (this) + { + session = (Session)_sessions.get(idInCluster); + } + + if (session != null) + { + session.invalidate(); + } + } + + /** + * Delete an existing session, both from the in-memory map and + * the database. + * + * @see org.eclipse.jetty.server.session.AbstractSessionManager#removeSession(java.lang.String) + */ + @Override + protected boolean removeSession(String idInCluster) + { + synchronized (this) + { + Session session = (Session)_sessions.remove(idInCluster); + try + { + if (session != null) + deleteSession(session); + } + catch (Exception e) + { + LOG.warn("Problem deleting session id="+idInCluster, e); + } + return session!=null; + } + } + + + /** + * Add a newly created session to our in-memory list for this node and persist it. + * + * @see org.eclipse.jetty.server.session.AbstractSessionManager#addSession(org.eclipse.jetty.server.session.AbstractSession) + */ + @Override + protected void addSession(AbstractSession session) + { + if (session==null) + return; + + synchronized (this) + { + _sessions.put(session.getClusterId(), session); + } + + //TODO or delay the store until exit out of session? If we crash before we store it + //then session data will be lost. + try + { + synchronized (session) + { + session.willPassivate(); + storeSession(((JDBCSessionManager.Session)session)); + session.didActivate(); + } + } + catch (Exception e) + { + LOG.warn("Unable to store new session id="+session.getId() , e); + } + } + + + /** + * Make a new Session. + * + * @see org.eclipse.jetty.server.session.AbstractSessionManager#newSession(javax.servlet.http.HttpServletRequest) + */ + @Override + protected AbstractSession newSession(HttpServletRequest request) + { + return new Session(request); + } + + /* ------------------------------------------------------------ */ + /** Remove session from manager + * @param session The session to remove + * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and + * {@link SessionIdManager#invalidateAll(String)} should be called. + */ + @Override + public void removeSession(AbstractSession session, boolean invalidate) + { + // Remove session from context and global maps + boolean removed = false; + + synchronized (this) + { + //take this session out of the map of sessions for this context + if (getSession(session.getClusterId()) != null) + { + removed = true; + removeSession(session.getClusterId()); + } + } + + if (removed) + { + // Remove session from all context and global id maps + _sessionIdManager.removeSession(session); + + if (invalidate) + _sessionIdManager.invalidateAll(session.getClusterId()); + + if (invalidate && !_sessionListeners.isEmpty()) + { + HttpSessionEvent event=new HttpSessionEvent(session); + for (HttpSessionListener l : _sessionListeners) + l.sessionDestroyed(event); + } + if (!invalidate) + { + session.willPassivate(); + } + } + } + + + /** + * Expire any Sessions we have in memory matching the list of + * expired Session ids. + * + * @param sessionIds + */ + protected void expire (List<?> sessionIds) + { + //don't attempt to scavenge if we are shutting down + if (isStopping() || isStopped()) + return; + + //Remove any sessions we already have in memory that match the ids + Thread thread=Thread.currentThread(); + ClassLoader old_loader=thread.getContextClassLoader(); + ListIterator<?> itor = sessionIds.listIterator(); + + try + { + while (itor.hasNext()) + { + String sessionId = (String)itor.next(); + if (LOG.isDebugEnabled()) + LOG.debug("Expiring session id "+sessionId); + + Session session = (Session)_sessions.get(sessionId); + if (session != null) + { + session.timeout(); + itor.remove(); + } + else + { + if (LOG.isDebugEnabled()) + LOG.debug("Unrecognized session id="+sessionId); + } + } + } + catch (Throwable t) + { + LOG.warn("Problem expiring sessions", t); + } + finally + { + thread.setContextClassLoader(old_loader); + } + } + + + /** + * Load a session from the database + * @param id + * @return the session data that was loaded + * @throws Exception + */ + protected Session loadSession (final String id, final String canonicalContextPath, final String vhost) + throws Exception + { + final AtomicReference<Session> _reference = new AtomicReference<Session>(); + final AtomicReference<Exception> _exception = new AtomicReference<Exception>(); + Runnable load = new Runnable() + { + @SuppressWarnings("unchecked") + public void run() + { + Session session = null; + Connection connection=null; + PreparedStatement statement = null; + try + { + connection = getConnection(); + statement = _jdbcSessionIdMgr._dbAdaptor.getLoadStatement(connection, id, canonicalContextPath, vhost); + ResultSet result = statement.executeQuery(); + if (result.next()) + { + session = new Session(id, result.getString(_jdbcSessionIdMgr._sessionTableRowId), result.getLong("createTime"), result.getLong("accessTime")); + session.setCookieSet(result.getLong("cookieTime")); + session.setLastAccessedTime(result.getLong("lastAccessTime")); + session.setLastNode(result.getString("lastNode")); + session.setLastSaved(result.getLong("lastSavedTime")); + session.setExpiryTime(result.getLong("expiryTime")); + session.setCanonicalContext(result.getString("contextPath")); + session.setVirtualHost(result.getString("virtualHost")); + + InputStream is = ((JDBCSessionIdManager)getSessionIdManager())._dbAdaptor.getBlobInputStream(result, "map"); + ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream (is); + Object o = ois.readObject(); + session.addAttributes((Map<String,Object>)o); + ois.close(); + + if (LOG.isDebugEnabled()) + LOG.debug("LOADED session "+session); + } + _reference.set(session); + } + catch (Exception e) + { + _exception.set(e); + } + finally + { + if (statement!=null) + { + try { statement.close(); } + catch(Exception e) { LOG.warn(e); } + } + + if (connection!=null) + { + try { connection.close();} + catch(Exception e) { LOG.warn(e); } + } + } + } + }; + + if (_context==null) + load.run(); + else + _context.getContextHandler().handle(load); + + if (_exception.get()!=null) + { + //if the session could not be restored, take its id out of the pool of currently-in-use + //session ids + _jdbcSessionIdMgr.removeSession(id); + throw _exception.get(); + } + + return _reference.get(); + } + + /** + * Insert a session into the database. + * + * @param data + * @throws Exception + */ + protected void storeSession (Session session) + throws Exception + { + if (session==null) + return; + + //put into the database + Connection connection = getConnection(); + PreparedStatement statement = null; + try + { + String rowId = calculateRowId(session); + + long now = System.currentTimeMillis(); + connection.setAutoCommit(true); + statement = connection.prepareStatement(_jdbcSessionIdMgr._insertSession); + statement.setString(1, rowId); //rowId + statement.setString(2, session.getId()); //session id + statement.setString(3, session.getCanonicalContext()); //context path + statement.setString(4, session.getVirtualHost()); //first vhost + statement.setString(5, getSessionIdManager().getWorkerName());//my node id + statement.setLong(6, session.getAccessed());//accessTime + statement.setLong(7, session.getLastAccessedTime()); //lastAccessTime + statement.setLong(8, session.getCreationTime()); //time created + statement.setLong(9, session.getCookieSet());//time cookie was set + statement.setLong(10, now); //last saved time + statement.setLong(11, session.getExpiryTime()); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(session.getAttributeMap()); + byte[] bytes = baos.toByteArray(); + + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + statement.setBinaryStream(12, bais, bytes.length);//attribute map as blob + + statement.executeUpdate(); + session.setRowId(rowId); //set it on the in-memory data as well as in db + session.setLastSaved(now); + + + if (LOG.isDebugEnabled()) + LOG.debug("Stored session "+session); + } + finally + { + if (statement!=null) + { + try { statement.close(); } + catch(Exception e) { LOG.warn(e); } + } + + if (connection!=null) + connection.close(); + } + } + + + /** + * Update data on an existing persisted session. + * + * @param data the session + * @throws Exception + */ + protected void updateSession (Session data) + throws Exception + { + if (data==null) + return; + + Connection connection = getConnection(); + PreparedStatement statement = null; + try + { + long now = System.currentTimeMillis(); + connection.setAutoCommit(true); + statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSession); + statement.setString(1, getSessionIdManager().getWorkerName());//my node id + statement.setLong(2, data.getAccessed());//accessTime + statement.setLong(3, data.getLastAccessedTime()); //lastAccessTime + statement.setLong(4, now); //last saved time + statement.setLong(5, data.getExpiryTime()); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(data.getAttributeMap()); + byte[] bytes = baos.toByteArray(); + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + + statement.setBinaryStream(6, bais, bytes.length);//attribute map as blob + statement.setString(7, data.getRowId()); //rowId + statement.executeUpdate(); + + data.setLastSaved(now); + if (LOG.isDebugEnabled()) + LOG.debug("Updated session "+data); + } + finally + { + if (statement!=null) + { + try { statement.close(); } + catch(Exception e) { LOG.warn(e); } + } + + if (connection!=null) + connection.close(); + } + } + + + /** + * Update the node on which the session was last seen to be my node. + * + * @param data the session + * @throws Exception + */ + protected void updateSessionNode (Session data) + throws Exception + { + String nodeId = getSessionIdManager().getWorkerName(); + Connection connection = getConnection(); + PreparedStatement statement = null; + try + { + connection.setAutoCommit(true); + statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSessionNode); + statement.setString(1, nodeId); + statement.setString(2, data.getRowId()); + statement.executeUpdate(); + statement.close(); + if (LOG.isDebugEnabled()) + LOG.debug("Updated last node for session id="+data.getId()+", lastNode = "+nodeId); + } + finally + { + if (statement!=null) + { + try { statement.close(); } + catch(Exception e) { LOG.warn(e); } + } + + if (connection!=null) + connection.close(); + } + } + + /** + * Persist the time the session was last accessed. + * + * @param data the session + * @throws Exception + */ + private void updateSessionAccessTime (Session data) + throws Exception + { + Connection connection = getConnection(); + PreparedStatement statement = null; + try + { + long now = System.currentTimeMillis(); + connection.setAutoCommit(true); + statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSessionAccessTime); + statement.setString(1, getSessionIdManager().getWorkerName()); + statement.setLong(2, data.getAccessed()); + statement.setLong(3, data.getLastAccessedTime()); + statement.setLong(4, now); + statement.setLong(5, data.getExpiryTime()); + statement.setString(6, data.getRowId()); + statement.executeUpdate(); + data.setLastSaved(now); + statement.close(); + if (LOG.isDebugEnabled()) + LOG.debug("Updated access time session id="+data.getId()); + } + finally + { + if (statement!=null) + { + try { statement.close(); } + catch(Exception e) { LOG.warn(e); } + } + + if (connection!=null) + connection.close(); + } + } + + + + + /** + * Delete a session from the database. Should only be called + * when the session has been invalidated. + * + * @param data + * @throws Exception + */ + protected void deleteSession (Session data) + throws Exception + { + Connection connection = getConnection(); + PreparedStatement statement = null; + try + { + connection.setAutoCommit(true); + statement = connection.prepareStatement(_jdbcSessionIdMgr._deleteSession); + statement.setString(1, data.getRowId()); + statement.executeUpdate(); + if (LOG.isDebugEnabled()) + LOG.debug("Deleted Session "+data); + } + finally + { + if (statement!=null) + { + try { statement.close(); } + catch(Exception e) { LOG.warn(e); } + } + + if (connection!=null) + connection.close(); + } + } + + + + /** + * Get a connection from the driver. + * @return + * @throws SQLException + */ + private Connection getConnection () + throws SQLException + { + return ((JDBCSessionIdManager)getSessionIdManager()).getConnection(); + } + + /** + * Calculate a unique id for this session across the cluster. + * + * Unique id is composed of: contextpath_virtualhost0_sessionid + * @param data + * @return + */ + private String calculateRowId (Session data) + { + String rowId = canonicalize(_context.getContextPath()); + rowId = rowId + "_" + getVirtualHost(_context); + rowId = rowId+"_"+data.getId(); + return rowId; + } + + /** + * Get the first virtual host for the context. + * + * Used to help identify the exact session/contextPath. + * + * @return 0.0.0.0 if no virtual host is defined + */ + private static String getVirtualHost (ContextHandler.Context context) + { + String vhost = "0.0.0.0"; + + if (context==null) + return vhost; + + String [] vhosts = context.getContextHandler().getVirtualHosts(); + if (vhosts==null || vhosts.length==0 || vhosts[0]==null) + return vhost; + + return vhosts[0]; + } + + /** + * Make an acceptable file name from a context path. + * + * @param path + * @return + */ + private static String canonicalize (String path) + { + if (path==null) + return ""; + + return path.replace('/', '_').replace('.','_').replace('\\','_'); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/session/SessionHandler.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,346 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.session; + +import java.io.IOException; +import java.util.EnumSet; +import java.util.EventListener; +import javax.servlet.DispatcherType; +import javax.servlet.ServletException; +import javax.servlet.SessionTrackingMode; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.eclipse.jetty.http.HttpCookie; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.SessionManager; +import org.eclipse.jetty.server.handler.ScopedHandler; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/* ------------------------------------------------------------ */ +/** + * SessionHandler. + */ +public class SessionHandler extends ScopedHandler +{ + final static Logger LOG = Log.getLogger("org.eclipse.jetty.server.session"); + + public final static EnumSet<SessionTrackingMode> DEFAULT_TRACKING = EnumSet.of(SessionTrackingMode.COOKIE,SessionTrackingMode.URL); + + /* -------------------------------------------------------------- */ + private SessionManager _sessionManager; + + /* ------------------------------------------------------------ */ + /** + * Constructor. Construct a SessionHandler witha a HashSessionManager with a standard java.util.Random generator is created. + */ + public SessionHandler() + { + this(new HashSessionManager()); + } + + /* ------------------------------------------------------------ */ + /** + * @param manager + * The session manager + */ + public SessionHandler(SessionManager manager) + { + setSessionManager(manager); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the sessionManager. + */ + public SessionManager getSessionManager() + { + return _sessionManager; + } + + /* ------------------------------------------------------------ */ + /** + * @param sessionManager + * The sessionManager to set. + */ + public void setSessionManager(SessionManager sessionManager) + { + if (isStarted()) + throw new IllegalStateException(); + SessionManager old_session_manager = _sessionManager; + + if (getServer() != null) + getServer().getContainer().update(this,old_session_manager,sessionManager,"sessionManager",true); + + if (sessionManager != null) + sessionManager.setSessionHandler(this); + + _sessionManager = sessionManager; + + if (old_session_manager != null) + old_session_manager.setSessionHandler(null); + } + + /* ------------------------------------------------------------ */ + @Override + public void setServer(Server server) + { + Server old_server = getServer(); + if (old_server != null && old_server != server) + old_server.getContainer().update(this,_sessionManager,null,"sessionManager",true); + super.setServer(server); + if (server != null && server != old_server) + server.getContainer().update(this,null,_sessionManager,"sessionManager",true); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.thread.AbstractLifeCycle#doStart() + */ + @Override + protected void doStart() throws Exception + { + _sessionManager.start(); + super.doStart(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.thread.AbstractLifeCycle#doStop() + */ + @Override + protected void doStop() throws Exception + { + // Destroy sessions before destroying servlets/filters see JETTY-1266 + _sessionManager.stop(); + super.doStop(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int) + */ + @Override + public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + SessionManager old_session_manager = null; + HttpSession old_session = null; + HttpSession access = null; + try + { + old_session_manager = baseRequest.getSessionManager(); + old_session = baseRequest.getSession(false); + + if (old_session_manager != _sessionManager) + { + // new session context + baseRequest.setSessionManager(_sessionManager); + baseRequest.setSession(null); + checkRequestedSessionId(baseRequest,request); + } + + // access any existing session + HttpSession session = null; + if (_sessionManager != null) + { + session = baseRequest.getSession(false); + if (session != null) + { + if (session != old_session) + { + access = session; + HttpCookie cookie = _sessionManager.access(session,request.isSecure()); + if (cookie != null) // Handle changed ID or max-age refresh + baseRequest.getResponse().addCookie(cookie); + } + } + else + { + session = baseRequest.recoverNewSession(_sessionManager); + if (session != null) + baseRequest.setSession(session); + } + } + + if (LOG.isDebugEnabled()) + { + LOG.debug("sessionManager=" + _sessionManager); + LOG.debug("session=" + session); + } + + // start manual inline of nextScope(target,baseRequest,request,response); + if (_nextScope != null) + _nextScope.doScope(target,baseRequest,request,response); + else if (_outerScope != null) + _outerScope.doHandle(target,baseRequest,request,response); + else + doHandle(target,baseRequest,request,response); + // end manual inline (pathentic attempt to reduce stack depth) + + } + finally + { + if (access != null) + _sessionManager.complete(access); + + HttpSession session = baseRequest.getSession(false); + if (session != null && old_session == null && session != access) + _sessionManager.complete(session); + + if (old_session_manager != null && old_session_manager != _sessionManager) + { + baseRequest.setSessionManager(old_session_manager); + baseRequest.setSession(old_session); + } + } + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int) + */ + @Override + public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + // start manual inline of nextHandle(target,baseRequest,request,response); + if (never()) + nextHandle(target,baseRequest,request,response); + else if (_nextScope != null && _nextScope == _handler) + _nextScope.doHandle(target,baseRequest,request,response); + else if (_handler != null) + _handler.handle(target,baseRequest,request,response); + // end manual inline + } + + /* ------------------------------------------------------------ */ + /** + * Look for a requested session ID in cookies and URI parameters + * + * @param baseRequest + * @param request + */ + protected void checkRequestedSessionId(Request baseRequest, HttpServletRequest request) + { + String requested_session_id = request.getRequestedSessionId(); + + SessionManager sessionManager = getSessionManager(); + + if (requested_session_id != null && sessionManager != null) + { + HttpSession session = sessionManager.getHttpSession(requested_session_id); + if (session != null && sessionManager.isValid(session)) + baseRequest.setSession(session); + return; + } + else if (!DispatcherType.REQUEST.equals(baseRequest.getDispatcherType())) + return; + + boolean requested_session_id_from_cookie = false; + HttpSession session = null; + + // Look for session id cookie + if (_sessionManager.isUsingCookies()) + { + Cookie[] cookies = request.getCookies(); + if (cookies != null && cookies.length > 0) + { + final String sessionCookie=sessionManager.getSessionCookieConfig().getName(); + for (int i = 0; i < cookies.length; i++) + { + if (sessionCookie.equalsIgnoreCase(cookies[i].getName())) + { + requested_session_id = cookies[i].getValue(); + requested_session_id_from_cookie = true; + + LOG.debug("Got Session ID {} from cookie",requested_session_id); + + if (requested_session_id != null) + { + session = sessionManager.getHttpSession(requested_session_id); + + if (session != null && sessionManager.isValid(session)) + { + break; + } + } + else + { + LOG.warn("null session id from cookie"); + } + } + } + } + } + + if (requested_session_id == null || session == null) + { + String uri = request.getRequestURI(); + + String prefix = sessionManager.getSessionIdPathParameterNamePrefix(); + if (prefix != null) + { + int s = uri.indexOf(prefix); + if (s >= 0) + { + s += prefix.length(); + int i = s; + while (i < uri.length()) + { + char c = uri.charAt(i); + if (c == ';' || c == '#' || c == '?' || c == '/') + break; + i++; + } + + requested_session_id = uri.substring(s,i); + requested_session_id_from_cookie = false; + session = sessionManager.getHttpSession(requested_session_id); + if (LOG.isDebugEnabled()) + LOG.debug("Got Session ID {} from URL",requested_session_id); + } + } + } + + baseRequest.setRequestedSessionId(requested_session_id); + baseRequest.setRequestedSessionIdFromCookie(requested_session_id != null && requested_session_id_from_cookie); + if (session != null && sessionManager.isValid(session)) + baseRequest.setSession(session); + } + + /* ------------------------------------------------------------ */ + /** + * @param listener + */ + public void addEventListener(EventListener listener) + { + if (_sessionManager != null) + _sessionManager.addEventListener(listener); + } + + /* ------------------------------------------------------------ */ + public void clearEventListeners() + { + if (_sessionManager != null) + _sessionManager.clearEventListeners(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/ssl/ServletSSL.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,88 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.ssl; + +/* --------------------------------------------------------------------- */ +/** + * Jetty Servlet SSL support utilities. + * <p> + * A collection of utilities required to support the SSL requirements of the Servlet 2.2 and 2.3 + * specs. + * + * <p> + * Used by the SSL listener classes. + * + * + */ +public class ServletSSL +{ + /* ------------------------------------------------------------ */ + /** + * Given the name of a TLS/SSL cipher suite, return an int representing it effective stream + * cipher key strength. i.e. How much entropy material is in the key material being fed into the + * encryption routines. + * + * <p> + * This is based on the information on effective key lengths in RFC 2246 - The TLS Protocol + * Version 1.0, Appendix C. CipherSuite definitions: + * + * <pre> + * Effective + * Cipher Type Key Bits + * + * NULL * Stream 0 + * IDEA_CBC Block 128 + * RC2_CBC_40 * Block 40 + * RC4_40 * Stream 40 + * RC4_128 Stream 128 + * DES40_CBC * Block 40 + * DES_CBC Block 56 + * 3DES_EDE_CBC Block 168 + * </pre> + * + * @param cipherSuite String name of the TLS cipher suite. + * @return int indicating the effective key entropy bit-length. + */ + public static int deduceKeyLength(String cipherSuite) + { + // Roughly ordered from most common to least common. + if (cipherSuite == null) + return 0; + else if (cipherSuite.indexOf("WITH_AES_256_") >= 0) + return 256; + else if (cipherSuite.indexOf("WITH_RC4_128_") >= 0) + return 128; + else if (cipherSuite.indexOf("WITH_AES_128_") >= 0) + return 128; + else if (cipherSuite.indexOf("WITH_RC4_40_") >= 0) + return 40; + else if (cipherSuite.indexOf("WITH_3DES_EDE_CBC_") >= 0) + return 168; + else if (cipherSuite.indexOf("WITH_IDEA_CBC_") >= 0) + return 128; + else if (cipherSuite.indexOf("WITH_RC2_CBC_40_") >= 0) + return 40; + else if (cipherSuite.indexOf("WITH_DES40_CBC_") >= 0) + return 40; + else if (cipherSuite.indexOf("WITH_DES_CBC_") >= 0) + return 56; + else + return 0; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/ssl/SslCertificates.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,182 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.ssl; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; + +import org.eclipse.jetty.http.HttpSchemes; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.bio.SocketEndPoint; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +public class SslCertificates +{ + private static final Logger LOG = Log.getLogger(SslCertificates.class); + + /** + * The name of the SSLSession attribute that will contain any cached information. + */ + static final String CACHED_INFO_ATTR = CachedInfo.class.getName(); + + public static X509Certificate[] getCertChain(SSLSession sslSession) + { + try + { + javax.security.cert.X509Certificate javaxCerts[]=sslSession.getPeerCertificateChain(); + if (javaxCerts==null||javaxCerts.length==0) + return null; + + int length=javaxCerts.length; + X509Certificate[] javaCerts=new X509Certificate[length]; + + java.security.cert.CertificateFactory cf=java.security.cert.CertificateFactory.getInstance("X.509"); + for (int i=0; i<length; i++) + { + byte bytes[]=javaxCerts[i].getEncoded(); + ByteArrayInputStream stream=new ByteArrayInputStream(bytes); + javaCerts[i]=(X509Certificate)cf.generateCertificate(stream); + } + + return javaCerts; + } + catch (SSLPeerUnverifiedException pue) + { + return null; + } + catch (Exception e) + { + LOG.warn(Log.EXCEPTION,e); + return null; + } + } + + + /* ------------------------------------------------------------ */ + /** + * Allow the Listener a chance to customise the request. before the server + * does its stuff. <br> + * This allows the required attributes to be set for SSL requests. <br> + * The requirements of the Servlet specs are: + * <ul> + * <li> an attribute named "javax.servlet.request.ssl_session_id" of type + * String (since Servlet Spec 3.0).</li> + * <li> an attribute named "javax.servlet.request.cipher_suite" of type + * String.</li> + * <li> an attribute named "javax.servlet.request.key_size" of type Integer.</li> + * <li> an attribute named "javax.servlet.request.X509Certificate" of type + * java.security.cert.X509Certificate[]. This is an array of objects of type + * X509Certificate, the order of this array is defined as being in ascending + * order of trust. The first certificate in the chain is the one set by the + * client, the next is the one used to authenticate the first, and so on. + * </li> + * </ul> + * + * @param endpoint + * The Socket the request arrived on. This should be a + * {@link SocketEndPoint} wrapping a {@link SSLSocket}. + * @param request + * HttpRequest to be customised. + */ + public static void customize(SSLSession sslSession, EndPoint endpoint, Request request) throws IOException + { + request.setScheme(HttpSchemes.HTTPS); + + try + { + String cipherSuite=sslSession.getCipherSuite(); + Integer keySize; + X509Certificate[] certs; + String idStr; + + CachedInfo cachedInfo=(CachedInfo)sslSession.getValue(CACHED_INFO_ATTR); + if (cachedInfo!=null) + { + keySize=cachedInfo.getKeySize(); + certs=cachedInfo.getCerts(); + idStr=cachedInfo.getIdStr(); + } + else + { + keySize=new Integer(ServletSSL.deduceKeyLength(cipherSuite)); + certs=SslCertificates.getCertChain(sslSession); + byte[] bytes = sslSession.getId(); + idStr = TypeUtil.toHexString(bytes); + cachedInfo=new CachedInfo(keySize,certs,idStr); + sslSession.putValue(CACHED_INFO_ATTR,cachedInfo); + } + + if (certs!=null) + request.setAttribute("javax.servlet.request.X509Certificate",certs); + + request.setAttribute("javax.servlet.request.cipher_suite",cipherSuite); + request.setAttribute("javax.servlet.request.key_size",keySize); + request.setAttribute("javax.servlet.request.ssl_session_id", idStr); + } + catch (Exception e) + { + LOG.warn(Log.EXCEPTION,e); + } + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /** + * Simple bundle of information that is cached in the SSLSession. Stores the + * effective keySize and the client certificate chain. + */ + private static class CachedInfo + { + private final X509Certificate[] _certs; + private final Integer _keySize; + private final String _idStr; + + CachedInfo(Integer keySize, X509Certificate[] certs,String idStr) + { + this._keySize=keySize; + this._certs=certs; + this._idStr=idStr; + } + + X509Certificate[] getCerts() + { + return _certs; + } + + Integer getKeySize() + { + return _keySize; + } + + String getIdStr() + { + return _idStr; + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/ssl/SslConnector.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,348 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.ssl; + +import java.io.File; +import java.security.SecureRandom; +import java.security.Security; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.TrustManagerFactory; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.util.ssl.SslContextFactory; + + +/* ------------------------------------------------------------ */ +/** The interface for SSL connectors and their configuration methods. + * + */ +public interface SslConnector extends Connector +{ + @Deprecated + public static final String DEFAULT_KEYSTORE_ALGORITHM=(Security.getProperty("ssl.KeyManagerFactory.algorithm")==null?"SunX509":Security.getProperty("ssl.KeyManagerFactory.algorithm")); + @Deprecated + public static final String DEFAULT_TRUSTSTORE_ALGORITHM=(Security.getProperty("ssl.TrustManagerFactory.algorithm")==null?"SunX509":Security.getProperty("ssl.TrustManagerFactory.algorithm")); + + /** Default value for the keystore location path. @deprecated */ + @Deprecated + public static final String DEFAULT_KEYSTORE = System.getProperty("user.home") + File.separator + ".keystore"; + + /** String name of key password property. @deprecated */ + @Deprecated + public static final String KEYPASSWORD_PROPERTY = "org.eclipse.jetty.ssl.keypassword"; + + /** String name of keystore password property. @deprecated */ + @Deprecated + public static final String PASSWORD_PROPERTY = "org.eclipse.jetty.ssl.password"; + + + /* ------------------------------------------------------------ */ + /** + * @return the instance of SslContextFactory associated with the connector + */ + public SslContextFactory getSslContextFactory(); + + /* ------------------------------------------------------------ */ + /** + * @return The array of Ciphersuite names to exclude from + * {@link SSLEngine#setEnabledCipherSuites(String[])} + * @deprecated + */ + @Deprecated + public abstract String[] getExcludeCipherSuites(); + + /* ------------------------------------------------------------ */ + /** + * @param cipherSuites The array of Ciphersuite names to exclude from + * {@link SSLEngine#setEnabledCipherSuites(String[])} + * @deprecated + */ + @Deprecated + public abstract void setExcludeCipherSuites(String[] cipherSuites); + + /* ------------------------------------------------------------ */ + /** + * @return The array of Ciphersuite names to include in + * {@link SSLEngine#setEnabledCipherSuites(String[])} + * @deprecated + */ + @Deprecated + public abstract String[] getIncludeCipherSuites(); + + /* ------------------------------------------------------------ */ + /** + * @param cipherSuites The array of Ciphersuite names to include in + * {@link SSLEngine#setEnabledCipherSuites(String[])} + * @deprecated + */ + @Deprecated + public abstract void setIncludeCipherSuites(String[] cipherSuites); + + /* ------------------------------------------------------------ */ + /** + * @param password The password for the key store + * @deprecated + */ + @Deprecated + public abstract void setPassword(String password); + + /* ------------------------------------------------------------ */ + /** + * @param password The password for the trust store + * @deprecated + */ + @Deprecated + public abstract void setTrustPassword(String password); + + /* ------------------------------------------------------------ */ + /** + * @param password The password (if any) for the specific key within + * the key store + * @deprecated + */ + @Deprecated + public abstract void setKeyPassword(String password); + + /* ------------------------------------------------------------ */ + /** + * @return The SSL protocol (default "TLS") passed to {@link SSLContext#getInstance(String, String)} + * @deprecated + */ + @Deprecated + public abstract String getProtocol(); + + /* ------------------------------------------------------------ */ + /** + * @param protocol The SSL protocol (default "TLS") passed to {@link SSLContext#getInstance(String, String)} + * @deprecated + */ + @Deprecated + public abstract void setProtocol(String protocol); + + /* ------------------------------------------------------------ */ + /** + * @param keystore The file or URL of the SSL Key store. + * @deprecated + */ + @Deprecated + public abstract void setKeystore(String keystore); + + /* ------------------------------------------------------------ */ + /** + * @return The file or URL of the SSL Key store. + * @deprecated + */ + @Deprecated + public abstract String getKeystore(); + + /* ------------------------------------------------------------ */ + /** + * @return The type of the key store (default "JKS") + * @deprecated + */ + @Deprecated + public abstract String getKeystoreType(); + + /* ------------------------------------------------------------ */ + /** + * @return True if SSL needs client authentication. + * @see SSLEngine#getNeedClientAuth() + * @deprecated + */ + @Deprecated + public abstract boolean getNeedClientAuth(); + + /* ------------------------------------------------------------ */ + /** + * @return True if SSL wants client authentication. + * @see SSLEngine#getWantClientAuth() + * @deprecated + */ + @Deprecated + public abstract boolean getWantClientAuth(); + + /* ------------------------------------------------------------ */ + /** + * @param needClientAuth True if SSL needs client authentication. + * @see SSLEngine#getNeedClientAuth() + * @deprecated + */ + @Deprecated + public abstract void setNeedClientAuth(boolean needClientAuth); + + /* ------------------------------------------------------------ */ + /** + * @param wantClientAuth True if SSL wants client authentication. + * @see SSLEngine#getWantClientAuth() + * @deprecated + */ + @Deprecated + public abstract void setWantClientAuth(boolean wantClientAuth); + + /* ------------------------------------------------------------ */ + /** + * @param keystoreType The type of the key store (default "JKS") + * @deprecated + */ + @Deprecated + public abstract void setKeystoreType(String keystoreType); + + /* ------------------------------------------------------------ */ + /** + * @return The SSL provider name, which if set is passed to + * {@link SSLContext#getInstance(String, String)} + * @deprecated + */ + @Deprecated + public abstract String getProvider(); + + /* ------------------------------------------------------------ */ + /** + * @return The algorithm name, which if set is passed to + * {@link SecureRandom#getInstance(String)} to obtain the {@link SecureRandom} + * instance passed to {@link SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], SecureRandom)} + * @deprecated + */ + @Deprecated + public abstract String getSecureRandomAlgorithm(); + + /* ------------------------------------------------------------ */ + /** + * @return The algorithm name (default "SunX509") used by the {@link KeyManagerFactory} + * @deprecated + */ + @Deprecated + public abstract String getSslKeyManagerFactoryAlgorithm(); + + /* ------------------------------------------------------------ */ + /** + * @return The algorithm name (default "SunX509") used by the {@link TrustManagerFactory} + * @deprecated + */ + @Deprecated + public abstract String getSslTrustManagerFactoryAlgorithm(); + + /* ------------------------------------------------------------ */ + /** + * @return The file name or URL of the trust store location + * @deprecated + */ + @Deprecated + public abstract String getTruststore(); + + /* ------------------------------------------------------------ */ + /** + * @return The type of the trust store (default "JKS") + * @deprecated + */ + @Deprecated + public abstract String getTruststoreType(); + + /* ------------------------------------------------------------ */ + /** + * @param provider The SSL provider name, which if set is passed to + * {@link SSLContext#getInstance(String, String)} + * @deprecated + */ + @Deprecated + public abstract void setProvider(String provider); + + /* ------------------------------------------------------------ */ + /** + * @param algorithm The algorithm name, which if set is passed to + * {@link SecureRandom#getInstance(String)} to obtain the {@link SecureRandom} + * instance passed to {@link SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], SecureRandom)} + * @deprecated + */ + @Deprecated + public abstract void setSecureRandomAlgorithm(String algorithm); + + /* ------------------------------------------------------------ */ + /** + * @param algorithm The algorithm name (default "SunX509") used by + * the {@link KeyManagerFactory} + * @deprecated + */ + @Deprecated + public abstract void setSslKeyManagerFactoryAlgorithm(String algorithm); + + /* ------------------------------------------------------------ */ + /** + * @param algorithm The algorithm name (default "SunX509") used by the {@link TrustManagerFactory} + * @deprecated + */ + @Deprecated + public abstract void setSslTrustManagerFactoryAlgorithm(String algorithm); + + /* ------------------------------------------------------------ */ + /** + * @param truststore The file name or URL of the trust store location + * @deprecated + */ + @Deprecated + public abstract void setTruststore(String truststore); + + /* ------------------------------------------------------------ */ + /** + * @param truststoreType The type of the trust store (default "JKS") + * @deprecated + */ + @Deprecated + public abstract void setTruststoreType(String truststoreType); + + /* ------------------------------------------------------------ */ + /** + * @param sslContext Set a preconfigured SSLContext + * @deprecated + */ + @Deprecated + public abstract void setSslContext(SSLContext sslContext); + + /* ------------------------------------------------------------ */ + /** + * @return The SSLContext + * @deprecated + */ + @Deprecated + public abstract SSLContext getSslContext(); + + + /* ------------------------------------------------------------ */ + /** + * @return True if SSL re-negotiation is allowed (default false) + * @deprecated + */ + @Deprecated + public boolean isAllowRenegotiate(); + + /* ------------------------------------------------------------ */ + /** + * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered + * a vulnerability in SSL/TLS with re-negotiation. If your JVM + * does not have CVE-2009-3555 fixed, then re-negotiation should + * not be allowed. + * @param allowRenegotiate true if re-negotiation is allowed (default false) + * @deprecated + */ + @Deprecated + public void setAllowRenegotiate(boolean allowRenegotiate); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/ssl/SslSelectChannelConnector.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,653 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.ssl; + +import java.io.IOException; +import java.nio.channels.SocketChannel; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; + +import org.eclipse.jetty.http.HttpSchemes; +import org.eclipse.jetty.io.AsyncEndPoint; +import org.eclipse.jetty.io.Buffers; +import org.eclipse.jetty.io.Buffers.Type; +import org.eclipse.jetty.io.BuffersFactory; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.RuntimeIOException; +import org.eclipse.jetty.io.bio.SocketEndPoint; +import org.eclipse.jetty.io.nio.AsyncConnection; +import org.eclipse.jetty.io.nio.SslConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.nio.SelectChannelConnector; +import org.eclipse.jetty.util.component.AggregateLifeCycle; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +/* ------------------------------------------------------------ */ +/** + * SslSelectChannelConnector. + * + * @org.apache.xbean.XBean element="sslConnector" description="Creates an NIO ssl connector" + */ +public class SslSelectChannelConnector extends SelectChannelConnector implements SslConnector +{ + private final SslContextFactory _sslContextFactory; + private Buffers _sslBuffers; + + /* ------------------------------------------------------------ */ + public SslSelectChannelConnector() + { + this(new SslContextFactory(SslContextFactory.DEFAULT_KEYSTORE_PATH)); + setSoLingerTime(30000); + } + + /* ------------------------------------------------------------ */ + /** Construct with explicit SslContextFactory. + * The SslContextFactory passed is added via {@link #addBean(Object)} so that + * it's lifecycle may be managed with {@link AggregateLifeCycle}. + * @param sslContextFactory + */ + public SslSelectChannelConnector(SslContextFactory sslContextFactory) + { + _sslContextFactory = sslContextFactory; + addBean(_sslContextFactory); + setUseDirectBuffers(false); + setSoLingerTime(30000); + } + + /* ------------------------------------------------------------ */ + /** + * Allow the Listener a chance to customise the request. before the server + * does its stuff. <br> + * This allows the required attributes to be set for SSL requests. <br> + * The requirements of the Servlet specs are: + * <ul> + * <li> an attribute named "javax.servlet.request.ssl_session_id" of type + * String (since Servlet Spec 3.0).</li> + * <li> an attribute named "javax.servlet.request.cipher_suite" of type + * String.</li> + * <li> an attribute named "javax.servlet.request.key_size" of type Integer.</li> + * <li> an attribute named "javax.servlet.request.X509Certificate" of type + * java.security.cert.X509Certificate[]. This is an array of objects of type + * X509Certificate, the order of this array is defined as being in ascending + * order of trust. The first certificate in the chain is the one set by the + * client, the next is the one used to authenticate the first, and so on. + * </li> + * </ul> + * + * @param endpoint + * The Socket the request arrived on. This should be a + * {@link SocketEndPoint} wrapping a {@link SSLSocket}. + * @param request + * HttpRequest to be customised. + */ + @Override + public void customize(EndPoint endpoint, Request request) throws IOException + { + request.setScheme(HttpSchemes.HTTPS); + super.customize(endpoint,request); + + SslConnection.SslEndPoint sslEndpoint=(SslConnection.SslEndPoint)endpoint; + SSLEngine sslEngine=sslEndpoint.getSslEngine(); + SSLSession sslSession=sslEngine.getSession(); + + SslCertificates.customize(sslSession,endpoint,request); + } + + /* ------------------------------------------------------------ */ + /** + * @return True if SSL re-negotiation is allowed (default false) + * @deprecated + */ + @Deprecated + public boolean isAllowRenegotiate() + { + return _sslContextFactory.isAllowRenegotiate(); + } + + /* ------------------------------------------------------------ */ + /** + * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered + * a vulnerability in SSL/TLS with re-negotiation. If your JVM + * does not have CVE-2009-3555 fixed, then re-negotiation should + * not be allowed. CVE-2009-3555 was fixed in Sun java 1.6 with a ban + * of renegotiate in u19 and with RFC5746 in u22. + * @param allowRenegotiate true if re-negotiation is allowed (default false) + * @deprecated + */ + @Deprecated + public void setAllowRenegotiate(boolean allowRenegotiate) + { + _sslContextFactory.setAllowRenegotiate(allowRenegotiate); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#getExcludeCipherSuites() + * @deprecated + */ + @Deprecated + public String[] getExcludeCipherSuites() + { + return _sslContextFactory.getExcludeCipherSuites(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setExcludeCipherSuites(java.lang.String[]) + * @deprecated + */ + @Deprecated + public void setExcludeCipherSuites(String[] cipherSuites) + { + _sslContextFactory.setExcludeCipherSuites(cipherSuites); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#getExcludeCipherSuites() + * @deprecated + */ + @Deprecated + public String[] getIncludeCipherSuites() + { + return _sslContextFactory.getIncludeCipherSuites(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setExcludeCipherSuites(java.lang.String[]) + * @deprecated + */ + @Deprecated + public void setIncludeCipherSuites(String[] cipherSuites) + { + _sslContextFactory.setIncludeCipherSuites(cipherSuites); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setPassword(java.lang.String) + * @deprecated + */ + @Deprecated + public void setPassword(String password) + { + _sslContextFactory.setKeyStorePassword(password); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setTrustPassword(java.lang.String) + * @deprecated + */ + @Deprecated + public void setTrustPassword(String password) + { + _sslContextFactory.setTrustStorePassword(password); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setKeyPassword(java.lang.String) + * @deprecated + */ + @Deprecated + public void setKeyPassword(String password) + { + _sslContextFactory.setKeyManagerPassword(password); + } + + /* ------------------------------------------------------------ */ + /** + * Unsupported. + * + * TODO: we should remove this as it is no longer an overridden method from SslConnector (like it was in the past) + * @deprecated + */ + @Deprecated + public String getAlgorithm() + { + throw new UnsupportedOperationException(); + } + + /* ------------------------------------------------------------ */ + /** + * Unsupported. + * + * TODO: we should remove this as it is no longer an overridden method from SslConnector (like it was in the past) + * @deprecated + */ + @Deprecated + public void setAlgorithm(String algorithm) + { + throw new UnsupportedOperationException(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#getProtocol() + * @deprecated + */ + @Deprecated + public String getProtocol() + { + return _sslContextFactory.getProtocol(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setProtocol(java.lang.String) + * @deprecated + */ + @Deprecated + public void setProtocol(String protocol) + { + _sslContextFactory.setProtocol(protocol); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setKeystore(java.lang.String) + * @deprecated + */ + @Deprecated + public void setKeystore(String keystore) + { + _sslContextFactory.setKeyStorePath(keystore); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#getKeystore() + * @deprecated + */ + @Deprecated + public String getKeystore() + { + return _sslContextFactory.getKeyStorePath(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#getKeystoreType() + * @deprecated + */ + @Deprecated + public String getKeystoreType() + { + return _sslContextFactory.getKeyStoreType(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#getNeedClientAuth() + * @deprecated + */ + @Deprecated + public boolean getNeedClientAuth() + { + return _sslContextFactory.getNeedClientAuth(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#getWantClientAuth() + * @deprecated + */ + @Deprecated + public boolean getWantClientAuth() + { + return _sslContextFactory.getWantClientAuth(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setNeedClientAuth(boolean) + * @deprecated + */ + @Deprecated + public void setNeedClientAuth(boolean needClientAuth) + { + _sslContextFactory.setNeedClientAuth(needClientAuth); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setWantClientAuth(boolean) + * @deprecated + */ + @Deprecated + public void setWantClientAuth(boolean wantClientAuth) + { + _sslContextFactory.setWantClientAuth(wantClientAuth); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setKeystoreType(java.lang.String) + * @deprecated + */ + @Deprecated + public void setKeystoreType(String keystoreType) + { + _sslContextFactory.setKeyStoreType(keystoreType); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#getProvider() + * @deprecated + */ + @Deprecated + public String getProvider() + { + return _sslContextFactory.getProvider(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#getSecureRandomAlgorithm() + * @deprecated + */ + @Deprecated + public String getSecureRandomAlgorithm() + { + return _sslContextFactory.getSecureRandomAlgorithm(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#getSslKeyManagerFactoryAlgorithm() + * @deprecated + */ + @Deprecated + public String getSslKeyManagerFactoryAlgorithm() + { + return _sslContextFactory.getSslKeyManagerFactoryAlgorithm(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#getSslTrustManagerFactoryAlgorithm() + * @deprecated + */ + @Deprecated + public String getSslTrustManagerFactoryAlgorithm() + { + return _sslContextFactory.getTrustManagerFactoryAlgorithm(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#getTruststore() + * @deprecated + */ + @Deprecated + public String getTruststore() + { + return _sslContextFactory.getTrustStore(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#getTruststoreType() + * @deprecated + */ + @Deprecated + public String getTruststoreType() + { + return _sslContextFactory.getTrustStoreType(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setProvider(java.lang.String) + * @deprecated + */ + @Deprecated + public void setProvider(String provider) + { + _sslContextFactory.setProvider(provider); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setSecureRandomAlgorithm(java.lang.String) + * @deprecated + */ + @Deprecated + public void setSecureRandomAlgorithm(String algorithm) + { + _sslContextFactory.setSecureRandomAlgorithm(algorithm); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setSslKeyManagerFactoryAlgorithm(java.lang.String) + * @deprecated + */ + @Deprecated + public void setSslKeyManagerFactoryAlgorithm(String algorithm) + { + _sslContextFactory.setSslKeyManagerFactoryAlgorithm(algorithm); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setSslTrustManagerFactoryAlgorithm(java.lang.String) + * @deprecated + */ + @Deprecated + public void setSslTrustManagerFactoryAlgorithm(String algorithm) + { + _sslContextFactory.setTrustManagerFactoryAlgorithm(algorithm); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setTruststore(java.lang.String) + * @deprecated + */ + @Deprecated + public void setTruststore(String truststore) + { + _sslContextFactory.setTrustStore(truststore); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setTruststoreType(java.lang.String) + * @deprecated + */ + @Deprecated + public void setTruststoreType(String truststoreType) + { + _sslContextFactory.setTrustStoreType(truststoreType); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setSslContext(javax.net.ssl.SSLContext) + * @deprecated + */ + @Deprecated + public void setSslContext(SSLContext sslContext) + { + _sslContextFactory.setSslContext(sslContext); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setSslContext(javax.net.ssl.SSLContext) + * @deprecated + */ + @Deprecated + public SSLContext getSslContext() + { + return _sslContextFactory.getSslContext(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#getSslContextFactory() + */ + public SslContextFactory getSslContextFactory() + { + return _sslContextFactory; + } + + /* ------------------------------------------------------------ */ + /** + * By default, we're confidential, given we speak SSL. But, if we've been + * told about an confidential port, and said port is not our port, then + * we're not. This allows separation of listeners providing INTEGRAL versus + * CONFIDENTIAL constraints, such as one SSL listener configured to require + * client certs providing CONFIDENTIAL, whereas another SSL listener not + * requiring client certs providing mere INTEGRAL constraints. + */ + @Override + public boolean isConfidential(Request request) + { + final int confidentialPort=getConfidentialPort(); + return confidentialPort==0||confidentialPort==request.getServerPort(); + } + + /* ------------------------------------------------------------ */ + /** + * By default, we're integral, given we speak SSL. But, if we've been told + * about an integral port, and said port is not our port, then we're not. + * This allows separation of listeners providing INTEGRAL versus + * CONFIDENTIAL constraints, such as one SSL listener configured to require + * client certs providing CONFIDENTIAL, whereas another SSL listener not + * requiring client certs providing mere INTEGRAL constraints. + */ + @Override + public boolean isIntegral(Request request) + { + final int integralPort=getIntegralPort(); + return integralPort==0||integralPort==request.getServerPort(); + } + + /* ------------------------------------------------------------------------------- */ + @Override + protected AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endpoint) + { + try + { + SSLEngine engine = createSSLEngine(channel); + SslConnection connection = newSslConnection(endpoint, engine); + AsyncConnection delegate = newPlainConnection(channel, connection.getSslEndPoint()); + connection.getSslEndPoint().setConnection(delegate); + connection.setAllowRenegotiate(_sslContextFactory.isAllowRenegotiate()); + return connection; + } + catch (IOException e) + { + throw new RuntimeIOException(e); + } + } + + protected AsyncConnection newPlainConnection(SocketChannel channel, AsyncEndPoint endPoint) + { + return super.newConnection(channel, endPoint); + } + + protected SslConnection newSslConnection(AsyncEndPoint endpoint, SSLEngine engine) + { + return new SslConnection(engine, endpoint); + } + + /* ------------------------------------------------------------ */ + /** + * @param channel A channel which if passed is used as to extract remote + * host and port for the purposes of SSL session caching + * @return A SSLEngine for a new or cached SSL Session + * @throws IOException if the SSLEngine cannot be created + */ + protected SSLEngine createSSLEngine(SocketChannel channel) throws IOException + { + SSLEngine engine; + if (channel != null) + { + String peerHost = channel.socket().getInetAddress().getHostAddress(); + int peerPort = channel.socket().getPort(); + engine = _sslContextFactory.newSslEngine(peerHost, peerPort); + } + else + { + engine = _sslContextFactory.newSslEngine(); + } + + engine.setUseClientMode(false); + return engine; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.nio.SelectChannelConnector#doStart() + */ + @Override + protected void doStart() throws Exception + { + _sslContextFactory.checkKeyStore(); + _sslContextFactory.start(); + + SSLEngine sslEngine = _sslContextFactory.newSslEngine(); + + sslEngine.setUseClientMode(false); + + SSLSession sslSession = sslEngine.getSession(); + + _sslBuffers = BuffersFactory.newBuffers( + getUseDirectBuffers()?Type.DIRECT:Type.INDIRECT,sslSession.getApplicationBufferSize(), + getUseDirectBuffers()?Type.DIRECT:Type.INDIRECT,sslSession.getApplicationBufferSize(), + getUseDirectBuffers()?Type.DIRECT:Type.INDIRECT,getMaxBuffers() + ); + + if (getRequestHeaderSize()<sslSession.getApplicationBufferSize()) + setRequestHeaderSize(sslSession.getApplicationBufferSize()); + if (getRequestBufferSize()<sslSession.getApplicationBufferSize()) + setRequestBufferSize(sslSession.getApplicationBufferSize()); + + super.doStart(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.nio.SelectChannelConnector#doStop() + */ + @Override + protected void doStop() throws Exception + { + _sslBuffers=null; + super.doStop(); + } + + /* ------------------------------------------------------------ */ + /** + * @return SSL buffers + */ + public Buffers getSslBuffers() + { + return _sslBuffers; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/server/ssl/SslSocketConnector.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,712 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.ssl; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; + +import javax.net.ssl.HandshakeCompletedEvent; +import javax.net.ssl.HandshakeCompletedListener; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; + +import org.eclipse.jetty.http.HttpSchemes; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.RuntimeIOException; +import org.eclipse.jetty.io.bio.SocketEndPoint; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.bio.SocketConnector; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +/* ------------------------------------------------------------ */ +/** + * SSL Socket Connector. + * + * This specialization of SocketConnector is an abstract listener that can be used as the basis for a + * specific JSSE listener. + * + * The original of this class was heavily based on the work from Court Demas, which in turn is + * based on the work from Forge Research. Since JSSE, this class has evolved significantly from + * that early work. + * + * @org.apache.xbean.XBean element="sslSocketConnector" description="Creates an ssl socket connector" + * + * + */ +public class SslSocketConnector extends SocketConnector implements SslConnector +{ + private static final Logger LOG = Log.getLogger(SslSocketConnector.class); + + private final SslContextFactory _sslContextFactory; + private int _handshakeTimeout = 0; //0 means use maxIdleTime + + /* ------------------------------------------------------------ */ + /** + * Constructor. + */ + public SslSocketConnector() + { + this(new SslContextFactory(SslContextFactory.DEFAULT_KEYSTORE_PATH)); + setSoLingerTime(30000); + } + + /* ------------------------------------------------------------ */ + public SslSocketConnector(SslContextFactory sslContextFactory) + { + _sslContextFactory = sslContextFactory; + } + + /* ------------------------------------------------------------ */ + /** + * @return True if SSL re-negotiation is allowed (default false) + */ + public boolean isAllowRenegotiate() + { + return _sslContextFactory.isAllowRenegotiate(); + } + + /* ------------------------------------------------------------ */ + /** + * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered + * a vulnerability in SSL/TLS with re-negotiation. If your JVM + * does not have CVE-2009-3555 fixed, then re-negotiation should + * not be allowed. + * @param allowRenegotiate true if re-negotiation is allowed (default false) + */ + public void setAllowRenegotiate(boolean allowRenegotiate) + { + _sslContextFactory.setAllowRenegotiate(allowRenegotiate); + } + + /* ------------------------------------------------------------ */ + @Override + public void accept(int acceptorID) + throws IOException, InterruptedException + { + Socket socket = _serverSocket.accept(); + configure(socket); + + ConnectorEndPoint connection=new SslConnectorEndPoint(socket); + connection.dispatch(); + } + + /* ------------------------------------------------------------ */ + @Override + protected void configure(Socket socket) + throws IOException + { + super.configure(socket); + } + + /* ------------------------------------------------------------ */ + /** + * Allow the Listener a chance to customise the request. before the server does its stuff. <br> + * This allows the required attributes to be set for SSL requests. <br> + * The requirements of the Servlet specs are: + * <ul> + * <li> an attribute named "javax.servlet.request.ssl_id" of type String (since Spec 3.0).</li> + * <li> an attribute named "javax.servlet.request.cipher_suite" of type String.</li> + * <li> an attribute named "javax.servlet.request.key_size" of type Integer.</li> + * <li> an attribute named "javax.servlet.request.X509Certificate" of type + * java.security.cert.X509Certificate[]. This is an array of objects of type X509Certificate, + * the order of this array is defined as being in ascending order of trust. The first + * certificate in the chain is the one set by the client, the next is the one used to + * authenticate the first, and so on. </li> + * </ul> + * + * @param endpoint The Socket the request arrived on. + * This should be a {@link SocketEndPoint} wrapping a {@link SSLSocket}. + * @param request HttpRequest to be customised. + */ + @Override + public void customize(EndPoint endpoint, Request request) + throws IOException + { + super.customize(endpoint, request); + request.setScheme(HttpSchemes.HTTPS); + + SocketEndPoint socket_end_point = (SocketEndPoint)endpoint; + SSLSocket sslSocket = (SSLSocket)socket_end_point.getTransport(); + SSLSession sslSession = sslSocket.getSession(); + + SslCertificates.customize(sslSession,endpoint,request); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#getExcludeCipherSuites() + * @deprecated + */ + @Deprecated + public String[] getExcludeCipherSuites() { + return _sslContextFactory.getExcludeCipherSuites(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#getIncludeCipherSuites() + * @deprecated + */ + @Deprecated + public String[] getIncludeCipherSuites() + { + return _sslContextFactory.getIncludeCipherSuites(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#getKeystore() + * @deprecated + */ + @Deprecated + public String getKeystore() + { + return _sslContextFactory.getKeyStorePath(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#getKeystoreType() + * @deprecated + */ + @Deprecated + public String getKeystoreType() + { + return _sslContextFactory.getKeyStoreType(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#getNeedClientAuth() + * @deprecated + */ + @Deprecated + public boolean getNeedClientAuth() + { + return _sslContextFactory.getNeedClientAuth(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#getProtocol() + * @deprecated + */ + @Deprecated + public String getProtocol() + { + return _sslContextFactory.getProtocol(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#getProvider() + * @deprecated + */ + @Deprecated + public String getProvider() { + return _sslContextFactory.getProvider(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#getSecureRandomAlgorithm() + * @deprecated + */ + @Deprecated + public String getSecureRandomAlgorithm() + { + return _sslContextFactory.getSecureRandomAlgorithm(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#getSslKeyManagerFactoryAlgorithm() + * @deprecated + */ + @Deprecated + public String getSslKeyManagerFactoryAlgorithm() + { + return _sslContextFactory.getSslKeyManagerFactoryAlgorithm(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#getSslTrustManagerFactoryAlgorithm() + * @deprecated + */ + @Deprecated + public String getSslTrustManagerFactoryAlgorithm() + { + return _sslContextFactory.getTrustManagerFactoryAlgorithm(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#getTruststore() + * @deprecated + */ + @Deprecated + public String getTruststore() + { + return _sslContextFactory.getTrustStore(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#getSslContextFactory() + */ +// @Override + public SslContextFactory getSslContextFactory() + { + return _sslContextFactory; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#getTruststoreType() + * @deprecated + */ + @Deprecated + public String getTruststoreType() + { + return _sslContextFactory.getTrustStoreType(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#getWantClientAuth() + * @deprecated + */ + @Deprecated + public boolean getWantClientAuth() + { + return _sslContextFactory.getWantClientAuth(); + } + + /* ------------------------------------------------------------ */ + /** + * By default, we're confidential, given we speak SSL. But, if we've been told about an + * confidential port, and said port is not our port, then we're not. This allows separation of + * listeners providing INTEGRAL versus CONFIDENTIAL constraints, such as one SSL listener + * configured to require client certs providing CONFIDENTIAL, whereas another SSL listener not + * requiring client certs providing mere INTEGRAL constraints. + */ + @Override + public boolean isConfidential(Request request) + { + final int confidentialPort = getConfidentialPort(); + return confidentialPort == 0 || confidentialPort == request.getServerPort(); + } + + /* ------------------------------------------------------------ */ + /** + * By default, we're integral, given we speak SSL. But, if we've been told about an integral + * port, and said port is not our port, then we're not. This allows separation of listeners + * providing INTEGRAL versus CONFIDENTIAL constraints, such as one SSL listener configured to + * require client certs providing CONFIDENTIAL, whereas another SSL listener not requiring + * client certs providing mere INTEGRAL constraints. + */ + @Override + public boolean isIntegral(Request request) + { + final int integralPort = getIntegralPort(); + return integralPort == 0 || integralPort == request.getServerPort(); + } + + /* ------------------------------------------------------------ */ + @Override + public void open() throws IOException + { + _sslContextFactory.checkKeyStore(); + try + { + _sslContextFactory.start(); + } + catch(Exception e) + { + throw new RuntimeIOException(e); + } + super.open(); + } + + /* ------------------------------------------------------------ */ + /** + * {@inheritDoc} + */ + @Override + protected void doStart() throws Exception + { + _sslContextFactory.checkKeyStore(); + _sslContextFactory.start(); + + super.doStart(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.bio.SocketConnector#doStop() + */ + @Override + protected void doStop() throws Exception + { + _sslContextFactory.stop(); + + super.doStop(); + } + + /* ------------------------------------------------------------ */ + /** + * @param host The host name that this server should listen on + * @param port the port that this server should listen on + * @param backlog See {@link ServerSocket#bind(java.net.SocketAddress, int)} + * @return A new {@link ServerSocket socket object} bound to the supplied address with all other + * settings as per the current configuration of this connector. + * @see #setWantClientAuth(boolean) + * @see #setNeedClientAuth(boolean) + * @exception IOException + */ + @Override + protected ServerSocket newServerSocket(String host, int port,int backlog) throws IOException + { + return _sslContextFactory.newSslServerSocket(host,port,backlog); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setExcludeCipherSuites(java.lang.String[]) + * @deprecated + */ + @Deprecated + public void setExcludeCipherSuites(String[] cipherSuites) + { + _sslContextFactory.setExcludeCipherSuites(cipherSuites); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setIncludeCipherSuites(java.lang.String[]) + * @deprecated + */ + @Deprecated + public void setIncludeCipherSuites(String[] cipherSuites) + { + _sslContextFactory.setIncludeCipherSuites(cipherSuites); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setKeyPassword(java.lang.String) + * @deprecated + */ + @Deprecated + public void setKeyPassword(String password) + { + _sslContextFactory.setKeyManagerPassword(password); + } + + /* ------------------------------------------------------------ */ + /** + * @param keystore The resource path to the keystore, or null for built in keystores. + * @deprecated + */ + @Deprecated + public void setKeystore(String keystore) + { + _sslContextFactory.setKeyStorePath(keystore); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setKeystoreType(java.lang.String) + * @deprecated + */ + @Deprecated + public void setKeystoreType(String keystoreType) + { + _sslContextFactory.setKeyStoreType(keystoreType); + } + + /* ------------------------------------------------------------ */ + /** + * Set the value of the needClientAuth property + * + * @param needClientAuth true iff we require client certificate authentication. + * @deprecated + */ + @Deprecated + public void setNeedClientAuth(boolean needClientAuth) + { + _sslContextFactory.setNeedClientAuth(needClientAuth); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setPassword(java.lang.String) + * @deprecated + */ + @Deprecated + public void setPassword(String password) + { + _sslContextFactory.setKeyStorePassword(password); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setTrustPassword(java.lang.String) + * @deprecated + */ + @Deprecated + public void setTrustPassword(String password) + { + _sslContextFactory.setTrustStorePassword(password); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setProtocol(java.lang.String) + * @deprecated + */ + @Deprecated + public void setProtocol(String protocol) + { + _sslContextFactory.setProtocol(protocol); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setProvider(java.lang.String) + * @deprecated + */ + @Deprecated + public void setProvider(String provider) { + _sslContextFactory.setProvider(provider); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setSecureRandomAlgorithm(java.lang.String) + * @deprecated + */ + @Deprecated + public void setSecureRandomAlgorithm(String algorithm) + { + _sslContextFactory.setSecureRandomAlgorithm(algorithm); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setSslKeyManagerFactoryAlgorithm(java.lang.String) + * @deprecated + */ + @Deprecated + public void setSslKeyManagerFactoryAlgorithm(String algorithm) + { + _sslContextFactory.setSslKeyManagerFactoryAlgorithm(algorithm); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setSslTrustManagerFactoryAlgorithm(java.lang.String) + * @deprecated + */ + @Deprecated + public void setSslTrustManagerFactoryAlgorithm(String algorithm) + { + _sslContextFactory.setTrustManagerFactoryAlgorithm(algorithm); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setTruststore(java.lang.String) + * @deprecated + */ + @Deprecated + public void setTruststore(String truststore) + { + _sslContextFactory.setTrustStore(truststore); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setTruststoreType(java.lang.String) + * @deprecated + */ + @Deprecated + public void setTruststoreType(String truststoreType) + { + _sslContextFactory.setTrustStoreType(truststoreType); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setSslContext(javax.net.ssl.SSLContext) + * @deprecated + */ + @Deprecated + public void setSslContext(SSLContext sslContext) + { + _sslContextFactory.setSslContext(sslContext); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.ssl.SslConnector#setSslContext(javax.net.ssl.SSLContext) + * @deprecated + */ + @Deprecated + public SSLContext getSslContext() + { + return _sslContextFactory.getSslContext(); + } + + /* ------------------------------------------------------------ */ + /** + * Set the value of the _wantClientAuth property. This property is used + * internally when opening server sockets. + * + * @param wantClientAuth true if we want client certificate authentication. + * @see SSLServerSocket#setWantClientAuth + * @deprecated + */ + @Deprecated + public void setWantClientAuth(boolean wantClientAuth) + { + _sslContextFactory.setWantClientAuth(wantClientAuth); + } + + /* ------------------------------------------------------------ */ + /** + * Set the time in milliseconds for so_timeout during ssl handshaking + * @param msec a non-zero value will be used to set so_timeout during + * ssl handshakes. A zero value means the maxIdleTime is used instead. + */ + public void setHandshakeTimeout (int msec) + { + _handshakeTimeout = msec; + } + + + /* ------------------------------------------------------------ */ + public int getHandshakeTimeout () + { + return _handshakeTimeout; + } + + /* ------------------------------------------------------------ */ + public class SslConnectorEndPoint extends ConnectorEndPoint + { + public SslConnectorEndPoint(Socket socket) throws IOException + { + super(socket); + } + + @Override + public void shutdownOutput() throws IOException + { + close(); + } + + @Override + public void shutdownInput() throws IOException + { + close(); + } + + @Override + public void run() + { + try + { + int handshakeTimeout = getHandshakeTimeout(); + int oldTimeout = _socket.getSoTimeout(); + if (handshakeTimeout > 0) + _socket.setSoTimeout(handshakeTimeout); + + final SSLSocket ssl=(SSLSocket)_socket; + ssl.addHandshakeCompletedListener(new HandshakeCompletedListener() + { + boolean handshook=false; + public void handshakeCompleted(HandshakeCompletedEvent event) + { + if (handshook) + { + if (!_sslContextFactory.isAllowRenegotiate()) + { + LOG.warn("SSL renegotiate denied: "+ssl); + try{ssl.close();}catch(IOException e){LOG.warn(e);} + } + } + else + handshook=true; + } + }); + ssl.startHandshake(); + + if (handshakeTimeout>0) + _socket.setSoTimeout(oldTimeout); + + super.run(); + } + catch (SSLException e) + { + LOG.debug(e); + try{close();} + catch(IOException e2){LOG.ignore(e2);} + } + catch (IOException e) + { + LOG.debug(e); + try{close();} + catch(IOException e2){LOG.ignore(e2);} + } + } + } + + /* ------------------------------------------------------------ */ + /** + * Unsupported. + * + * TODO: we should remove this as it is no longer an overridden method from SslConnector (like it was in the past) + * @deprecated + */ + @Deprecated + public String getAlgorithm() + { + throw new UnsupportedOperationException(); + } + + /* ------------------------------------------------------------ */ + /** + * Unsupported. + * + * TODO: we should remove this as it is no longer an overridden method from SslConnector (like it was in the past) + * @deprecated + */ + @Deprecated + public void setAlgorithm(String algorithm) + { + throw new UnsupportedOperationException(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/ArrayQueue.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,379 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import java.util.AbstractList; +import java.util.NoSuchElementException; +import java.util.Queue; + +/* ------------------------------------------------------------ */ +/** + * Queue backed by circular array. + * <p/> + * This partial Queue implementation (also with {@link #remove()} for stack operation) + * is backed by a growable circular array. + * + * @param <E> + */ +public class ArrayQueue<E> extends AbstractList<E> implements Queue<E> +{ + public static final int DEFAULT_CAPACITY = 64; + public static final int DEFAULT_GROWTH = 32; + + protected final Object _lock; + protected final int _growCapacity; + protected Object[] _elements; + protected int _nextE; + protected int _nextSlot; + protected int _size; + + /* ------------------------------------------------------------ */ + public ArrayQueue() + { + this(DEFAULT_CAPACITY, -1); + } + + /* ------------------------------------------------------------ */ + public ArrayQueue(int capacity) + { + this(capacity, -1); + } + + /* ------------------------------------------------------------ */ + public ArrayQueue(int initCapacity, int growBy) + { + this(initCapacity, growBy, null); + } + + /* ------------------------------------------------------------ */ + public ArrayQueue(int initCapacity, int growBy, Object lock) + { + _lock = lock == null ? this : lock; + _growCapacity = growBy; + _elements = new Object[initCapacity]; + } + + /* ------------------------------------------------------------ */ + public int getCapacity() + { + synchronized (_lock) + { + return _elements.length; + } + } + + /* ------------------------------------------------------------ */ + @Override + public boolean add(E e) + { + if (!offer(e)) + throw new IllegalStateException("Full"); + return true; + } + + /* ------------------------------------------------------------ */ + public boolean offer(E e) + { + synchronized (_lock) + { + return enqueue(e); + } + } + + /* ------------------------------------------------------------ */ + private boolean enqueue(E e) + { + if (_size == _elements.length && !grow()) + return false; + + _size++; + _elements[_nextSlot++] = e; + if (_nextSlot == _elements.length) + _nextSlot = 0; + + return true; + } + + /* ------------------------------------------------------------ */ + /** + * Add without synchronization or bounds checking + * + * @param e the element to add + * @see #add(Object) + */ + public void addUnsafe(E e) + { + if (!enqueue(e)) + throw new IllegalStateException("Full"); + } + + /* ------------------------------------------------------------ */ + public E element() + { + synchronized (_lock) + { + if (isEmpty()) + throw new NoSuchElementException(); + return at(_nextE); + } + } + + @SuppressWarnings("unchecked") + private E at(int index) + { + return (E)_elements[index]; + } + + /* ------------------------------------------------------------ */ + public E peek() + { + synchronized (_lock) + { + if (isEmpty()) + return null; + return at(_nextE); + } + } + + /* ------------------------------------------------------------ */ + public E poll() + { + synchronized (_lock) + { + if (_size == 0) + return null; + return dequeue(); + } + } + + /* ------------------------------------------------------------ */ + private E dequeue() + { + E e = at(_nextE); + _elements[_nextE] = null; + _size--; + if (++_nextE == _elements.length) + _nextE = 0; + return e; + } + + /* ------------------------------------------------------------ */ + public E remove() + { + synchronized (_lock) + { + if (_size == 0) + throw new NoSuchElementException(); + return dequeue(); + } + } + + /* ------------------------------------------------------------ */ + @Override + public void clear() + { + synchronized (_lock) + { + _size = 0; + _nextE = 0; + _nextSlot = 0; + } + } + + /* ------------------------------------------------------------ */ + @Override + public boolean isEmpty() + { + synchronized (_lock) + { + return _size == 0; + } + } + + /* ------------------------------------------------------------ */ + @Override + public int size() + { + synchronized (_lock) + { + return _size; + } + } + + /* ------------------------------------------------------------ */ + @Override + public E get(int index) + { + synchronized (_lock) + { + if (index < 0 || index >= _size) + throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")"); + return getUnsafe(index); + } + } + + /* ------------------------------------------------------------ */ + /** + * Get without synchronization or bounds checking. + * + * @param index index of the element to return + * @return the element at the specified index + * @see #get(int) + */ + public E getUnsafe(int index) + { + int i = (_nextE + index) % _elements.length; + return at(i); + } + + /* ------------------------------------------------------------ */ + @Override + public E remove(int index) + { + synchronized (_lock) + { + if (index < 0 || index >= _size) + throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")"); + + int i = (_nextE + index) % _elements.length; + E old = at(i); + + if (i < _nextSlot) + { + // 0 _elements.length + // _nextE........._nextSlot + System.arraycopy(_elements, i + 1, _elements, i, _nextSlot - i); + _nextSlot--; + _size--; + } + else + { + // 0 _elements.length + // ......_nextSlot _nextE.......... + System.arraycopy(_elements, i + 1, _elements, i, _elements.length - i - 1); + if (_nextSlot > 0) + { + _elements[_elements.length - 1] = _elements[0]; + System.arraycopy(_elements, 1, _elements, 0, _nextSlot - 1); + _nextSlot--; + } + else + _nextSlot = _elements.length - 1; + + _size--; + } + + return old; + } + } + + /* ------------------------------------------------------------ */ + @Override + public E set(int index, E element) + { + synchronized (_lock) + { + if (index < 0 || index >= _size) + throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")"); + + int i = _nextE + index; + if (i >= _elements.length) + i -= _elements.length; + E old = at(i); + _elements[i] = element; + return old; + } + } + + /* ------------------------------------------------------------ */ + @Override + public void add(int index, E element) + { + synchronized (_lock) + { + if (index < 0 || index > _size) + throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")"); + + if (_size == _elements.length && !grow()) + throw new IllegalStateException("Full"); + + if (index == _size) + { + add(element); + } + else + { + int i = _nextE + index; + if (i >= _elements.length) + i -= _elements.length; + + _size++; + _nextSlot++; + if (_nextSlot == _elements.length) + _nextSlot = 0; + + if (i < _nextSlot) + { + // 0 _elements.length + // _nextE.....i..._nextSlot + // 0 _elements.length + // ..i..._nextSlot _nextE.......... + System.arraycopy(_elements, i, _elements, i + 1, _nextSlot - i); + _elements[i] = element; + } + else + { + // 0 _elements.length + // ......_nextSlot _nextE.....i.... + if (_nextSlot > 0) + { + System.arraycopy(_elements, 0, _elements, 1, _nextSlot); + _elements[0] = _elements[_elements.length - 1]; + } + + System.arraycopy(_elements, i, _elements, i + 1, _elements.length - i - 1); + _elements[i] = element; + } + } + } + } + + /* ------------------------------------------------------------ */ + protected boolean grow() + { + synchronized (_lock) + { + if (_growCapacity <= 0) + return false; + + Object[] elements = new Object[_elements.length + _growCapacity]; + + int split = _elements.length - _nextE; + if (split > 0) + System.arraycopy(_elements, _nextE, elements, 0, split); + if (_nextE != 0) + System.arraycopy(_elements, 0, elements, split, _nextSlot); + + _elements = elements; + _nextE = 0; + _nextSlot = _size; + return true; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/Atomics.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,73 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +public class Atomics +{ + private Atomics() + { + } + + public static void updateMin(AtomicLong currentMin, long newValue) + { + long oldValue = currentMin.get(); + while (newValue < oldValue) + { + if (currentMin.compareAndSet(oldValue, newValue)) + break; + oldValue = currentMin.get(); + } + } + + public static void updateMax(AtomicLong currentMax, long newValue) + { + long oldValue = currentMax.get(); + while (newValue > oldValue) + { + if (currentMax.compareAndSet(oldValue, newValue)) + break; + oldValue = currentMax.get(); + } + } + + public static void updateMin(AtomicInteger currentMin, int newValue) + { + int oldValue = currentMin.get(); + while (newValue < oldValue) + { + if (currentMin.compareAndSet(oldValue, newValue)) + break; + oldValue = currentMin.get(); + } + } + + public static void updateMax(AtomicInteger currentMax, int newValue) + { + int oldValue = currentMax.get(); + while (newValue > oldValue) + { + if (currentMax.compareAndSet(oldValue, newValue)) + break; + oldValue = currentMax.get(); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/Attributes.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,36 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import java.util.Enumeration; + +/* ------------------------------------------------------------ */ +/** Attributes. + * Interface commonly used for storing attributes. + * + * + */ +public interface Attributes +{ + public void removeAttribute(String name); + public void setAttribute(String name, Object attribute); + public Object getAttribute(String name); + public Enumeration<String> getAttributeNames(); + public void clearAttributes(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/AttributesMap.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,163 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/* ------------------------------------------------------------ */ +/** AttributesMap. + * + * + */ +public class AttributesMap implements Attributes +{ + protected final Map<String,Object> _map; + + /* ------------------------------------------------------------ */ + public AttributesMap() + { + _map=new HashMap<String,Object>(); + } + + /* ------------------------------------------------------------ */ + public AttributesMap(Map<String,Object> map) + { + _map=map; + } + + /* ------------------------------------------------------------ */ + public AttributesMap(AttributesMap map) + { + _map=new HashMap<String,Object>(map._map); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.util.Attributes#removeAttribute(java.lang.String) + */ + public void removeAttribute(String name) + { + _map.remove(name); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.util.Attributes#setAttribute(java.lang.String, java.lang.Object) + */ + public void setAttribute(String name, Object attribute) + { + if (attribute==null) + _map.remove(name); + else + _map.put(name, attribute); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.util.Attributes#getAttribute(java.lang.String) + */ + public Object getAttribute(String name) + { + return _map.get(name); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.util.Attributes#getAttributeNames() + */ + public Enumeration<String> getAttributeNames() + { + return Collections.enumeration(_map.keySet()); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.util.Attributes#getAttributeNames() + */ + public Set<String> getAttributeNameSet() + { + return _map.keySet(); + } + + /* ------------------------------------------------------------ */ + public Set<Map.Entry<String, Object>> getAttributeEntrySet() + { + return _map.entrySet(); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.util.Attributes#getAttributeNames() + */ + public static Enumeration<String> getAttributeNamesCopy(Attributes attrs) + { + if (attrs instanceof AttributesMap) + return Collections.enumeration(((AttributesMap)attrs)._map.keySet()); + + List<String> names = new ArrayList<String>(); + names.addAll(Collections.list(attrs.getAttributeNames())); + return Collections.enumeration(names); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.util.Attributes#clear() + */ + public void clearAttributes() + { + _map.clear(); + } + + /* ------------------------------------------------------------ */ + public int size() + { + return _map.size(); + } + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + return _map.toString(); + } + + /* ------------------------------------------------------------ */ + public Set<String> keySet() + { + return _map.keySet(); + } + + /* ------------------------------------------------------------ */ + public void addAll(Attributes attributes) + { + Enumeration<String> e = attributes.getAttributeNames(); + while (e.hasMoreElements()) + { + String name=e.nextElement(); + setAttribute(name,attributes.getAttribute(name)); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/B64Code.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,450 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; + + +/* ------------------------------------------------------------ */ +/** Fast B64 Encoder/Decoder as described in RFC 1421. + * <p>Does not insert or interpret whitespace as described in RFC + * 1521. If you require this you must pre/post process your data. + * <p> Note that in a web context the usual case is to not want + * linebreaks or other white space in the encoded output. + * + */ +public class B64Code +{ + // ------------------------------------------------------------------ + static final char __pad='='; + static final char[] __rfc1421alphabet= + { + 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', + 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f', + 'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v', + 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/' + }; + + static final byte[] __rfc1421nibbles; + + static + { + __rfc1421nibbles=new byte[256]; + for (int i=0;i<256;i++) + __rfc1421nibbles[i]=-1; + for (byte b=0;b<64;b++) + __rfc1421nibbles[(byte)__rfc1421alphabet[b]]=b; + __rfc1421nibbles[(byte)__pad]=0; + } + + // ------------------------------------------------------------------ + /** + * Base 64 encode as described in RFC 1421. + * <p>Does not insert whitespace as described in RFC 1521. + * @param s String to encode. + * @return String containing the encoded form of the input. + */ + static public String encode(String s) + { + try + { + return encode(s,null); + } + catch (UnsupportedEncodingException e) + { + throw new IllegalArgumentException(e.toString()); + } + } + + // ------------------------------------------------------------------ + /** + * Base 64 encode as described in RFC 1421. + * <p>Does not insert whitespace as described in RFC 1521. + * @param s String to encode. + * @param charEncoding String representing the name of + * the character encoding of the provided input String. + * @return String containing the encoded form of the input. + */ + static public String encode(String s,String charEncoding) + throws UnsupportedEncodingException + { + byte[] bytes; + if (charEncoding==null) + bytes=s.getBytes(StringUtil.__ISO_8859_1); + else + bytes=s.getBytes(charEncoding); + + return new String(encode(bytes)); + } + + // ------------------------------------------------------------------ + /** + * Fast Base 64 encode as described in RFC 1421. + * <p>Does not insert whitespace as described in RFC 1521. + * <p> Avoids creating extra copies of the input/output. + * @param b byte array to encode. + * @return char array containing the encoded form of the input. + */ + static public char[] encode(byte[] b) + { + if (b==null) + return null; + + int bLen=b.length; + int cLen=((bLen+2)/3)*4; + char c[]=new char[cLen]; + int ci=0; + int bi=0; + byte b0, b1, b2; + int stop=(bLen/3)*3; + while (bi<stop) + { + b0=b[bi++]; + b1=b[bi++]; + b2=b[bi++]; + c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f]; + c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f]; + c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f|(b2>>>6)&0x03]; + c[ci++]=__rfc1421alphabet[b2&077]; + } + + if (bLen!=bi) + { + switch (bLen%3) + { + case 2: + b0=b[bi++]; + b1=b[bi++]; + c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f]; + c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f]; + c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f]; + c[ci++]=__pad; + break; + + case 1: + b0=b[bi++]; + c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f]; + c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f]; + c[ci++]=__pad; + c[ci++]=__pad; + break; + + default: + break; + } + } + + return c; + } + + // ------------------------------------------------------------------ + /** + * Fast Base 64 encode as described in RFC 1421 and RFC2045 + * <p>Does not insert whitespace as described in RFC 1521, unless rfc2045 is passed as true. + * <p> Avoids creating extra copies of the input/output. + * @param b byte array to encode. + * @param rfc2045 If true, break lines at 76 characters with CRLF + * @return char array containing the encoded form of the input. + */ + static public char[] encode(byte[] b, boolean rfc2045) + { + if (b==null) + return null; + if (!rfc2045) + return encode(b); + + int bLen=b.length; + int cLen=((bLen+2)/3)*4; + cLen+=2+2*(cLen/76); + char c[]=new char[cLen]; + int ci=0; + int bi=0; + byte b0, b1, b2; + int stop=(bLen/3)*3; + int l=0; + while (bi<stop) + { + b0=b[bi++]; + b1=b[bi++]; + b2=b[bi++]; + c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f]; + c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f]; + c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f|(b2>>>6)&0x03]; + c[ci++]=__rfc1421alphabet[b2&077]; + l+=4; + if (l%76==0) + { + c[ci++]=13; + c[ci++]=10; + } + } + + if (bLen!=bi) + { + switch (bLen%3) + { + case 2: + b0=b[bi++]; + b1=b[bi++]; + c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f]; + c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f]; + c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f]; + c[ci++]=__pad; + break; + + case 1: + b0=b[bi++]; + c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f]; + c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f]; + c[ci++]=__pad; + c[ci++]=__pad; + break; + + default: + break; + } + } + + c[ci++]=13; + c[ci++]=10; + return c; + } + + // ------------------------------------------------------------------ + /** + * Base 64 decode as described in RFC 2045. + * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored. + * @param encoded String to decode. + * @param charEncoding String representing the character encoding + * used to map the decoded bytes into a String. + * @return String decoded byte array. + * @throws UnsupportedEncodingException if the encoding is not supported + * @throws IllegalArgumentException if the input is not a valid + * B64 encoding. + */ + static public String decode(String encoded,String charEncoding) + throws UnsupportedEncodingException + { + byte[] decoded=decode(encoded); + if (charEncoding==null) + return new String(decoded); + return new String(decoded,charEncoding); + } + + /* ------------------------------------------------------------ */ + /** + * Fast Base 64 decode as described in RFC 1421. + * + * <p>Unlike other decode methods, this does not attempt to + * cope with extra whitespace as described in RFC 1521/2045. + * <p> Avoids creating extra copies of the input/output. + * <p> Note this code has been flattened for performance. + * @param b char array to decode. + * @return byte array containing the decoded form of the input. + * @throws IllegalArgumentException if the input is not a valid + * B64 encoding. + */ + static public byte[] decode(char[] b) + { + if (b==null) + return null; + + int bLen=b.length; + if (bLen%4!=0) + throw new IllegalArgumentException("Input block size is not 4"); + + int li=bLen-1; + while (li>=0 && b[li]==(byte)__pad) + li--; + + if (li<0) + return new byte[0]; + + // Create result array of exact required size. + int rLen=((li+1)*3)/4; + byte r[]=new byte[rLen]; + int ri=0; + int bi=0; + int stop=(rLen/3)*3; + byte b0,b1,b2,b3; + try + { + while (ri<stop) + { + b0=__rfc1421nibbles[b[bi++]]; + b1=__rfc1421nibbles[b[bi++]]; + b2=__rfc1421nibbles[b[bi++]]; + b3=__rfc1421nibbles[b[bi++]]; + if (b0<0 || b1<0 || b2<0 || b3<0) + throw new IllegalArgumentException("Not B64 encoded"); + + r[ri++]=(byte)(b0<<2|b1>>>4); + r[ri++]=(byte)(b1<<4|b2>>>2); + r[ri++]=(byte)(b2<<6|b3); + } + + if (rLen!=ri) + { + switch (rLen%3) + { + case 2: + b0=__rfc1421nibbles[b[bi++]]; + b1=__rfc1421nibbles[b[bi++]]; + b2=__rfc1421nibbles[b[bi++]]; + if (b0<0 || b1<0 || b2<0) + throw new IllegalArgumentException("Not B64 encoded"); + r[ri++]=(byte)(b0<<2|b1>>>4); + r[ri++]=(byte)(b1<<4|b2>>>2); + break; + + case 1: + b0=__rfc1421nibbles[b[bi++]]; + b1=__rfc1421nibbles[b[bi++]]; + if (b0<0 || b1<0) + throw new IllegalArgumentException("Not B64 encoded"); + r[ri++]=(byte)(b0<<2|b1>>>4); + break; + + default: + break; + } + } + } + catch (IndexOutOfBoundsException e) + { + throw new IllegalArgumentException("char "+bi + +" was not B64 encoded"); + } + + return r; + } + + /* ------------------------------------------------------------ */ + /** + * Base 64 decode as described in RFC 2045. + * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored. + * @param encoded String to decode. + * @return byte array containing the decoded form of the input. + * @throws IllegalArgumentException if the input is not a valid + * B64 encoding. + */ + static public byte[] decode(String encoded) + { + if (encoded==null) + return null; + + ByteArrayOutputStream bout = new ByteArrayOutputStream(4*encoded.length()/3); + decode(encoded, bout); + return bout.toByteArray(); + } + + /* ------------------------------------------------------------ */ + /** + * Base 64 decode as described in RFC 2045. + * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored. + * @param encoded String to decode. + * @param output stream for decoded bytes + * @return byte array containing the decoded form of the input. + * @throws IllegalArgumentException if the input is not a valid + * B64 encoding. + */ + static public void decode (String encoded, ByteArrayOutputStream bout) + { + if (encoded==null) + return; + + if (bout == null) + throw new IllegalArgumentException("No outputstream for decoded bytes"); + + int ci=0; + byte nibbles[] = new byte[4]; + int s=0; + + while (ci<encoded.length()) + { + char c=encoded.charAt(ci++); + + if (c==__pad) + break; + + if (Character.isWhitespace(c)) + continue; + + byte nibble=__rfc1421nibbles[c]; + if (nibble<0) + throw new IllegalArgumentException("Not B64 encoded"); + + nibbles[s++]=__rfc1421nibbles[c]; + + switch(s) + { + case 1: + break; + case 2: + bout.write(nibbles[0]<<2|nibbles[1]>>>4); + break; + case 3: + bout.write(nibbles[1]<<4|nibbles[2]>>>2); + break; + case 4: + bout.write(nibbles[2]<<6|nibbles[3]); + s=0; + break; + } + + } + + return; + } + + + /* ------------------------------------------------------------ */ + public static void encode(int value,Appendable buf) throws IOException + { + buf.append(__rfc1421alphabet[0x3f&((0xFC000000&value)>>26)]); + buf.append(__rfc1421alphabet[0x3f&((0x03F00000&value)>>20)]); + buf.append(__rfc1421alphabet[0x3f&((0x000FC000&value)>>14)]); + buf.append(__rfc1421alphabet[0x3f&((0x00003F00&value)>>8)]); + buf.append(__rfc1421alphabet[0x3f&((0x000000FC&value)>>2)]); + buf.append(__rfc1421alphabet[0x3f&((0x00000003&value)<<4)]); + buf.append('='); + } + + /* ------------------------------------------------------------ */ + public static void encode(long lvalue,Appendable buf) throws IOException + { + int value=(int)(0xFFFFFFFC&(lvalue>>32)); + buf.append(__rfc1421alphabet[0x3f&((0xFC000000&value)>>26)]); + buf.append(__rfc1421alphabet[0x3f&((0x03F00000&value)>>20)]); + buf.append(__rfc1421alphabet[0x3f&((0x000FC000&value)>>14)]); + buf.append(__rfc1421alphabet[0x3f&((0x00003F00&value)>>8)]); + buf.append(__rfc1421alphabet[0x3f&((0x000000FC&value)>>2)]); + + buf.append(__rfc1421alphabet[0x3f&((0x00000003&value)<<4) + (0xf&(int)(lvalue>>28))]); + + value=0x0FFFFFFF&(int)lvalue; + buf.append(__rfc1421alphabet[0x3f&((0x0FC00000&value)>>22)]); + buf.append(__rfc1421alphabet[0x3f&((0x003F0000&value)>>16)]); + buf.append(__rfc1421alphabet[0x3f&((0x0000FC00&value)>>10)]); + buf.append(__rfc1421alphabet[0x3f&((0x000003F0&value)>>4)]); + buf.append(__rfc1421alphabet[0x3f&((0x0000000F&value)<<2)]); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/BlockingArrayQueue.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,704 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import java.util.AbstractList; +import java.util.Collection; +import java.util.NoSuchElementException; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + + +/* ------------------------------------------------------------ */ +/** Queue backed by a circular array. + * + * This queue is uses a variant of the two lock queue algorithm to + * provide an efficient queue or list backed by a growable circular + * array. This queue also has a partial implementation of + * {@link java.util.concurrent.BlockingQueue}, specifically the {@link #take()} and + * {@link #poll(long, TimeUnit)} methods. + * Unlike {@link java.util.concurrent.ArrayBlockingQueue}, this class is + * able to grow and provides a blocking put call. + * <p> + * The queue has both a capacity (the size of the array currently allocated) + * and a limit (the maximum size that may be allocated), which defaults to + * {@link Integer#MAX_VALUE}. + * + * @param <E> The element type + */ +public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQueue<E> +{ + public final int DEFAULT_CAPACITY=128; + public final int DEFAULT_GROWTH=64; + private final int _limit; + private final AtomicInteger _size=new AtomicInteger(); + private final int _growCapacity; + + private volatile int _capacity; + private Object[] _elements; + + private final ReentrantLock _headLock = new ReentrantLock(); + private final Condition _notEmpty = _headLock.newCondition(); + private int _head; + + // spacers created to prevent false sharing between head and tail http://en.wikipedia.org/wiki/False_sharing + // TODO verify this has benefits + private long _space0; + private long _space1; + private long _space2; + private long _space3; + private long _space4; + private long _space5; + private long _space6; + private long _space7; + + private final ReentrantLock _tailLock = new ReentrantLock(); + private int _tail; + + + /* ------------------------------------------------------------ */ + /** Create a growing partially blocking Queue + * + */ + public BlockingArrayQueue() + { + _elements=new Object[DEFAULT_CAPACITY]; + _growCapacity=DEFAULT_GROWTH; + _capacity=_elements.length; + _limit=Integer.MAX_VALUE; + } + + /* ------------------------------------------------------------ */ + /** Create a fixed size partially blocking Queue + * @param limit The initial capacity and the limit. + */ + public BlockingArrayQueue(int limit) + { + _elements=new Object[limit]; + _capacity=_elements.length; + _growCapacity=-1; + _limit=limit; + } + + /* ------------------------------------------------------------ */ + /** Create a growing partially blocking Queue. + * @param capacity Initial capacity + * @param growBy Incremental capacity. + */ + public BlockingArrayQueue(int capacity,int growBy) + { + _elements=new Object[capacity]; + _capacity=_elements.length; + _growCapacity=growBy; + _limit=Integer.MAX_VALUE; + } + + /* ------------------------------------------------------------ */ + /** Create a growing limited partially blocking Queue. + * @param capacity Initial capacity + * @param growBy Incremental capacity. + * @param limit maximum capacity. + */ + public BlockingArrayQueue(int capacity,int growBy,int limit) + { + if (capacity>limit) + throw new IllegalArgumentException(); + + _elements=new Object[capacity]; + _capacity=_elements.length; + _growCapacity=growBy; + _limit=limit; + } + + /* ------------------------------------------------------------ */ + public int getCapacity() + { + return _capacity; + } + + /* ------------------------------------------------------------ */ + public int getLimit() + { + return _limit; + } + + /* ------------------------------------------------------------ */ + @Override + public boolean add(E e) + { + return offer(e); + } + + /* ------------------------------------------------------------ */ + public E element() + { + E e = peek(); + if (e==null) + throw new NoSuchElementException(); + return e; + } + + /* ------------------------------------------------------------ */ + @SuppressWarnings("unchecked") + public E peek() + { + if (_size.get() == 0) + return null; + + E e = null; + _headLock.lock(); // Size cannot shrink + try + { + if (_size.get() > 0) + e = (E)_elements[_head]; + } + finally + { + _headLock.unlock(); + } + + return e; + } + + /* ------------------------------------------------------------ */ + public boolean offer(E e) + { + if (e == null) + throw new NullPointerException(); + + boolean not_empty=false; + _tailLock.lock(); // size cannot grow... only shrink + try + { + if (_size.get() >= _limit) + return false; + + // should we expand array? + if (_size.get()==_capacity) + { + _headLock.lock(); // Need to grow array + try + { + if (!grow()) + return false; + } + finally + { + _headLock.unlock(); + } + } + + // add the element + _elements[_tail]=e; + _tail=(_tail+1)%_capacity; + + not_empty=0==_size.getAndIncrement(); + + } + finally + { + _tailLock.unlock(); + } + + if (not_empty) + { + _headLock.lock(); + try + { + _notEmpty.signal(); + } + finally + { + _headLock.unlock(); + } + } + + return true; + } + + + /* ------------------------------------------------------------ */ + @SuppressWarnings("unchecked") + public E poll() + { + if (_size.get() == 0) + return null; + + E e = null; + _headLock.lock(); // Size cannot shrink + try + { + if (_size.get() > 0) + { + final int head=_head; + e = (E)_elements[head]; + _elements[head]=null; + _head=(head+1)%_capacity; + + if (_size.decrementAndGet()>0) + _notEmpty.signal(); + } + } + finally + { + _headLock.unlock(); + } + + return e; + } + + /* ------------------------------------------------------------ */ + /** + * Retrieves and removes the head of this queue, waiting + * if no elements are present on this queue. + * @return the head of this queue + * @throws InterruptedException if interrupted while waiting. + */ + @SuppressWarnings("unchecked") + public E take() throws InterruptedException + { + E e = null; + _headLock.lockInterruptibly(); // Size cannot shrink + try + { + try + { + while (_size.get() == 0) + { + _notEmpty.await(); + } + } + catch (InterruptedException ie) + { + _notEmpty.signal(); + throw ie; + } + + final int head=_head; + e = (E)_elements[head]; + _elements[head]=null; + _head=(head+1)%_capacity; + + if (_size.decrementAndGet()>0) + _notEmpty.signal(); + } + finally + { + _headLock.unlock(); + } + + return e; + } + + /* ------------------------------------------------------------ */ + /** + * Retrieves and removes the head of this queue, waiting + * if necessary up to the specified wait time if no elements are + * present on this queue. + * @param time how long to wait before giving up, in units of + * <tt>unit</tt> + * @param unit a <tt>TimeUnit</tt> determining how to interpret the + * <tt>timeout</tt> parameter + * @return the head of this queue, or <tt>null</tt> if the + * specified waiting time elapses before an element is present. + * @throws InterruptedException if interrupted while waiting. + */ + @SuppressWarnings("unchecked") + public E poll(long time, TimeUnit unit) throws InterruptedException + { + + E e = null; + + long nanos = unit.toNanos(time); + + _headLock.lockInterruptibly(); // Size cannot shrink + try + { + try + { + while (_size.get() == 0) + { + if (nanos<=0) + return null; + nanos = _notEmpty.awaitNanos(nanos); + } + } + catch (InterruptedException ie) + { + _notEmpty.signal(); + throw ie; + } + + e = (E)_elements[_head]; + _elements[_head]=null; + _head=(_head+1)%_capacity; + + if (_size.decrementAndGet()>0) + _notEmpty.signal(); + } + finally + { + _headLock.unlock(); + } + + return e; + } + + /* ------------------------------------------------------------ */ + public E remove() + { + E e=poll(); + if (e==null) + throw new NoSuchElementException(); + return e; + } + + /* ------------------------------------------------------------ */ + @Override + public void clear() + { + _tailLock.lock(); + try + { + _headLock.lock(); + try + { + _head=0; + _tail=0; + _size.set(0); + } + finally + { + _headLock.unlock(); + } + } + finally + { + _tailLock.unlock(); + } + } + + /* ------------------------------------------------------------ */ + @Override + public boolean isEmpty() + { + return _size.get()==0; + } + + /* ------------------------------------------------------------ */ + @Override + public int size() + { + return _size.get(); + } + + /* ------------------------------------------------------------ */ + @SuppressWarnings("unchecked") + @Override + public E get(int index) + { + _tailLock.lock(); + try + { + _headLock.lock(); + try + { + if (index<0 || index>=_size.get()) + throw new IndexOutOfBoundsException("!("+0+"<"+index+"<="+_size+")"); + int i = _head+index; + if (i>=_capacity) + i-=_capacity; + return (E)_elements[i]; + } + finally + { + _headLock.unlock(); + } + } + finally + { + _tailLock.unlock(); + } + } + + /* ------------------------------------------------------------ */ + @Override + public E remove(int index) + { + _tailLock.lock(); + try + { + _headLock.lock(); + try + { + + if (index<0 || index>=_size.get()) + throw new IndexOutOfBoundsException("!("+0+"<"+index+"<="+_size+")"); + + int i = _head+index; + if (i>=_capacity) + i-=_capacity; + @SuppressWarnings("unchecked") + E old=(E)_elements[i]; + + if (i<_tail) + { + System.arraycopy(_elements,i+1,_elements,i,_tail-i); + _tail--; + _size.decrementAndGet(); + } + else + { + System.arraycopy(_elements,i+1,_elements,i,_capacity-i-1); + if (_tail>0) + { + _elements[_capacity]=_elements[0]; + System.arraycopy(_elements,1,_elements,0,_tail-1); + _tail--; + } + else + _tail=_capacity-1; + + _size.decrementAndGet(); + } + + return old; + } + finally + { + _headLock.unlock(); + } + } + finally + { + _tailLock.unlock(); + } + } + + /* ------------------------------------------------------------ */ + @Override + public E set(int index, E e) + { + if (e == null) + throw new NullPointerException(); + + _tailLock.lock(); + try + { + _headLock.lock(); + try + { + + if (index<0 || index>=_size.get()) + throw new IndexOutOfBoundsException("!("+0+"<"+index+"<="+_size+")"); + + int i = _head+index; + if (i>=_capacity) + i-=_capacity; + @SuppressWarnings("unchecked") + E old=(E)_elements[i]; + _elements[i]=e; + return old; + } + finally + { + _headLock.unlock(); + } + } + finally + { + _tailLock.unlock(); + } + } + + /* ------------------------------------------------------------ */ + @Override + public void add(int index, E e) + { + if (e == null) + throw new NullPointerException(); + + _tailLock.lock(); + try + { + _headLock.lock(); + try + { + + if (index<0 || index>_size.get()) + throw new IndexOutOfBoundsException("!("+0+"<"+index+"<="+_size+")"); + + if (index==_size.get()) + { + add(e); + } + else + { + if (_tail==_head) + if (!grow()) + throw new IllegalStateException("full"); + + int i = _head+index; + if (i>=_capacity) + i-=_capacity; + + _size.incrementAndGet(); + _tail=(_tail+1)%_capacity; + + + if (i<_tail) + { + System.arraycopy(_elements,i,_elements,i+1,_tail-i); + _elements[i]=e; + } + else + { + if (_tail>0) + { + System.arraycopy(_elements,0,_elements,1,_tail); + _elements[0]=_elements[_capacity-1]; + } + + System.arraycopy(_elements,i,_elements,i+1,_capacity-i-1); + _elements[i]=e; + } + } + } + finally + { + _headLock.unlock(); + } + } + finally + { + _tailLock.unlock(); + } + } + + /* ------------------------------------------------------------ */ + private boolean grow() + { + if (_growCapacity<=0) + return false; + + _tailLock.lock(); + try + { + _headLock.lock(); + try + { + final int head=_head; + final int tail=_tail; + final int new_tail; + + Object[] elements=new Object[_capacity+_growCapacity]; + + if (head<tail) + { + new_tail=tail-head; + System.arraycopy(_elements,head,elements,0,new_tail); + } + else if (head>tail || _size.get()>0) + { + new_tail=_capacity+tail-head; + int cut=_capacity-head; + System.arraycopy(_elements,head,elements,0,cut); + System.arraycopy(_elements,0,elements,cut,tail); + } + else + { + new_tail=0; + } + + _elements=elements; + _capacity=_elements.length; + _head=0; + _tail=new_tail; + return true; + } + finally + { + _headLock.unlock(); + } + } + finally + { + _tailLock.unlock(); + } + + } + + /* ------------------------------------------------------------ */ + public int drainTo(Collection<? super E> c) + { + throw new UnsupportedOperationException(); + } + + /* ------------------------------------------------------------ */ + public int drainTo(Collection<? super E> c, int maxElements) + { + throw new UnsupportedOperationException(); + } + + /* ------------------------------------------------------------ */ + public boolean offer(E o, long timeout, TimeUnit unit) throws InterruptedException + { + throw new UnsupportedOperationException(); + } + + /* ------------------------------------------------------------ */ + public void put(E o) throws InterruptedException + { + if (!add(o)) + throw new IllegalStateException("full"); + } + + /* ------------------------------------------------------------ */ + public int remainingCapacity() + { + _tailLock.lock(); + try + { + _headLock.lock(); + try + { + return getCapacity()-size(); + } + finally + { + _headLock.unlock(); + } + } + finally + { + _tailLock.unlock(); + } + } + + + /* ------------------------------------------------------------ */ + long sumOfSpace() + { + // this method exists to stop clever optimisers removing the spacers + return _space0++ +_space1++ +_space2++ +_space3++ +_space4++ +_space5++ +_space6++ +_space7++; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/ByteArrayISO8859Writer.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,272 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; + + +/* ------------------------------------------------------------ */ +/** Byte Array ISO 8859 writer. + * This class combines the features of a OutputStreamWriter for + * ISO8859 encoding with that of a ByteArrayOutputStream. It avoids + * many inefficiencies associated with these standard library classes. + * It has been optimized for standard ASCII characters. + * + * + */ +public class ByteArrayISO8859Writer extends Writer +{ + private byte[] _buf; + private int _size; + private ByteArrayOutputStream2 _bout=null; + private OutputStreamWriter _writer=null; + private boolean _fixed=false; + + /* ------------------------------------------------------------ */ + /** Constructor. + */ + public ByteArrayISO8859Writer() + { + _buf=new byte[2048]; + } + + /* ------------------------------------------------------------ */ + /** Constructor. + * @param capacity Buffer capacity + */ + public ByteArrayISO8859Writer(int capacity) + { + _buf=new byte[capacity]; + } + + /* ------------------------------------------------------------ */ + public ByteArrayISO8859Writer(byte[] buf) + { + _buf=buf; + _fixed=true; + } + + /* ------------------------------------------------------------ */ + public Object getLock() + { + return lock; + } + + /* ------------------------------------------------------------ */ + public int size() + { + return _size; + } + + /* ------------------------------------------------------------ */ + public int capacity() + { + return _buf.length; + } + + /* ------------------------------------------------------------ */ + public int spareCapacity() + { + return _buf.length-_size; + } + + /* ------------------------------------------------------------ */ + public void setLength(int l) + { + _size=l; + } + + /* ------------------------------------------------------------ */ + public byte[] getBuf() + { + return _buf; + } + + /* ------------------------------------------------------------ */ + public void writeTo(OutputStream out) + throws IOException + { + out.write(_buf,0,_size); + } + + /* ------------------------------------------------------------ */ + public void write(char c) + throws IOException + { + ensureSpareCapacity(1); + if (c>=0&&c<=0x7f) + _buf[_size++]=(byte)c; + else + { + char[] ca ={c}; + writeEncoded(ca,0,1); + } + } + + /* ------------------------------------------------------------ */ + @Override + public void write(char[] ca) + throws IOException + { + ensureSpareCapacity(ca.length); + for (int i=0;i<ca.length;i++) + { + char c=ca[i]; + if (c>=0&&c<=0x7f) + _buf[_size++]=(byte)c; + else + { + writeEncoded(ca,i,ca.length-i); + break; + } + } + } + + /* ------------------------------------------------------------ */ + @Override + public void write(char[] ca,int offset, int length) + throws IOException + { + ensureSpareCapacity(length); + for (int i=0;i<length;i++) + { + char c=ca[offset+i]; + if (c>=0&&c<=0x7f) + _buf[_size++]=(byte)c; + else + { + writeEncoded(ca,offset+i,length-i); + break; + } + } + } + + /* ------------------------------------------------------------ */ + @Override + public void write(String s) + throws IOException + { + if (s==null) + { + write("null",0,4); + return; + } + + int length=s.length(); + ensureSpareCapacity(length); + for (int i=0;i<length;i++) + { + char c=s.charAt(i); + if (c>=0x0&&c<=0x7f) + _buf[_size++]=(byte)c; + else + { + writeEncoded(s.toCharArray(),i,length-i); + break; + } + } + } + + /* ------------------------------------------------------------ */ + @Override + public void write(String s,int offset, int length) + throws IOException + { + ensureSpareCapacity(length); + for (int i=0;i<length;i++) + { + char c=s.charAt(offset+i); + if (c>=0&&c<=0x7f) + _buf[_size++]=(byte)c; + else + { + writeEncoded(s.toCharArray(),offset+i,length-i); + break; + } + } + } + + /* ------------------------------------------------------------ */ + private void writeEncoded(char[] ca,int offset, int length) + throws IOException + { + if (_bout==null) + { + _bout = new ByteArrayOutputStream2(2*length); + _writer = new OutputStreamWriter(_bout,StringUtil.__ISO_8859_1); + } + else + _bout.reset(); + _writer.write(ca,offset,length); + _writer.flush(); + ensureSpareCapacity(_bout.getCount()); + System.arraycopy(_bout.getBuf(),0,_buf,_size,_bout.getCount()); + _size+=_bout.getCount(); + } + + /* ------------------------------------------------------------ */ + @Override + public void flush() + {} + + /* ------------------------------------------------------------ */ + public void resetWriter() + { + _size=0; + } + + /* ------------------------------------------------------------ */ + @Override + public void close() + {} + + /* ------------------------------------------------------------ */ + public void destroy() + { + _buf=null; + } + + /* ------------------------------------------------------------ */ + public void ensureSpareCapacity(int n) + throws IOException + { + if (_size+n>_buf.length) + { + if (_fixed) + throw new IOException("Buffer overflow: "+_buf.length); + byte[] buf = new byte[(_buf.length+n)*4/3]; + System.arraycopy(_buf,0,buf,0,_size); + _buf=buf; + } + } + + + /* ------------------------------------------------------------ */ + public byte[] getByteArray() + { + byte[] data=new byte[_size]; + System.arraycopy(_buf,0,data,0,_size); + return data; + } + +} + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/ByteArrayOutputStream2.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,49 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; +import java.io.ByteArrayOutputStream; + +/* ------------------------------------------------------------ */ +/** ByteArrayOutputStream with public internals + + * + */ +public class ByteArrayOutputStream2 extends ByteArrayOutputStream +{ + public ByteArrayOutputStream2(){super();} + public ByteArrayOutputStream2(int size){super(size);} + public byte[] getBuf(){return buf;} + public int getCount(){return count;} + public void setCount(int count){this.count = count;} + + public void reset(int minSize) + { + reset(); + if (buf.length<minSize) + { + buf=new byte[minSize]; + } + } + + public void writeUnchecked(int b) + { + buf[count++]=(byte)b; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/ConcurrentHashSet.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,126 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public class ConcurrentHashSet<E> extends AbstractSet<E> implements Set<E> +{ + private final Map<E, Boolean> _map = new ConcurrentHashMap<E, Boolean>(); + private transient Set<E> _keys = _map.keySet(); + + public ConcurrentHashSet() + { + } + + @Override + public boolean add(E e) + { + return _map.put(e,Boolean.TRUE) == null; + } + + @Override + public void clear() + { + _map.clear(); + } + + @Override + public boolean contains(Object o) + { + return _map.containsKey(o); + } + + @Override + public boolean containsAll(Collection<?> c) + { + return _keys.containsAll(c); + } + + @Override + public boolean equals(Object o) + { + return o == this || _keys.equals(o); + } + + @Override + public int hashCode() + { + return _keys.hashCode(); + } + + @Override + public boolean isEmpty() + { + return _map.isEmpty(); + } + + @Override + public Iterator<E> iterator() + { + return _keys.iterator(); + } + + @Override + public boolean remove(Object o) + { + return _map.remove(o) != null; + } + + @Override + public boolean removeAll(Collection<?> c) + { + return _keys.removeAll(c); + } + + @Override + public boolean retainAll(Collection<?> c) + { + return _keys.retainAll(c); + } + + @Override + public int size() + { + return _map.size(); + } + + @Override + public Object[] toArray() + { + return _keys.toArray(); + } + + @Override + public <T> T[] toArray(T[] a) + { + return _keys.toArray(a); + } + + @Override + public String toString() + { + return _keys.toString(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/DateCache.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,311 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import java.text.DateFormatSymbols; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +/* ------------------------------------------------------------ */ +/** Date Format Cache. + * Computes String representations of Dates and caches + * the results so that subsequent requests within the same minute + * will be fast. + * + * Only format strings that contain either "ss" or "ss.SSS" are + * handled. + * + * The timezone of the date may be included as an ID with the "zzz" + * format string or as an offset with the "ZZZ" format string. + * + * If consecutive calls are frequently very different, then this + * may be a little slower than a normal DateFormat. + * + * + * + */ + +public class DateCache +{ + public static String DEFAULT_FORMAT="EEE MMM dd HH:mm:ss zzz yyyy"; + private static long __hitWindow=60*60; + + private String _formatString; + private String _tzFormatString; + private SimpleDateFormat _tzFormat; + + private String _minFormatString; + private SimpleDateFormat _minFormat; + + private String _secFormatString; + private String _secFormatString0; + private String _secFormatString1; + + private long _lastMinutes = -1; + private long _lastSeconds = -1; + private int _lastMs = -1; + private String _lastResult = null; + + private Locale _locale = null; + private DateFormatSymbols _dfs = null; + + /* ------------------------------------------------------------ */ + /** Constructor. + * Make a DateCache that will use a default format. The default format + * generates the same results as Date.toString(). + */ + public DateCache() + { + this(DEFAULT_FORMAT); + getFormat().setTimeZone(TimeZone.getDefault()); + } + + /* ------------------------------------------------------------ */ + /** Constructor. + * Make a DateCache that will use the given format + */ + public DateCache(String format) + { + _formatString=format; + setTimeZone(TimeZone.getDefault()); + + } + + /* ------------------------------------------------------------ */ + public DateCache(String format,Locale l) + { + _formatString=format; + _locale = l; + setTimeZone(TimeZone.getDefault()); + } + + /* ------------------------------------------------------------ */ + public DateCache(String format,DateFormatSymbols s) + { + _formatString=format; + _dfs = s; + setTimeZone(TimeZone.getDefault()); + } + + /* ------------------------------------------------------------ */ + /** Set the timezone. + * @param tz TimeZone + */ + public synchronized void setTimeZone(TimeZone tz) + { + setTzFormatString(tz); + if( _locale != null ) + { + _tzFormat=new SimpleDateFormat(_tzFormatString,_locale); + _minFormat=new SimpleDateFormat(_minFormatString,_locale); + } + else if( _dfs != null ) + { + _tzFormat=new SimpleDateFormat(_tzFormatString,_dfs); + _minFormat=new SimpleDateFormat(_minFormatString,_dfs); + } + else + { + _tzFormat=new SimpleDateFormat(_tzFormatString); + _minFormat=new SimpleDateFormat(_minFormatString); + } + _tzFormat.setTimeZone(tz); + _minFormat.setTimeZone(tz); + _lastSeconds=-1; + _lastMinutes=-1; + } + + /* ------------------------------------------------------------ */ + public TimeZone getTimeZone() + { + return _tzFormat.getTimeZone(); + } + + /* ------------------------------------------------------------ */ + /** Set the timezone. + * @param timeZoneId TimeZoneId the ID of the zone as used by + * TimeZone.getTimeZone(id) + */ + public void setTimeZoneID(String timeZoneId) + { + setTimeZone(TimeZone.getTimeZone(timeZoneId)); + } + + /* ------------------------------------------------------------ */ + private synchronized void setTzFormatString(final TimeZone tz ) + { + int zIndex = _formatString.indexOf( "ZZZ" ); + if( zIndex >= 0 ) + { + String ss1 = _formatString.substring( 0, zIndex ); + String ss2 = _formatString.substring( zIndex+3 ); + int tzOffset = tz.getRawOffset(); + + StringBuilder sb = new StringBuilder(_formatString.length()+10); + sb.append(ss1); + sb.append("'"); + if( tzOffset >= 0 ) + sb.append( '+' ); + else + { + tzOffset = -tzOffset; + sb.append( '-' ); + } + + int raw = tzOffset / (1000*60); // Convert to seconds + int hr = raw / 60; + int min = raw % 60; + + if( hr < 10 ) + sb.append( '0' ); + sb.append( hr ); + if( min < 10 ) + sb.append( '0' ); + sb.append( min ); + sb.append( '\'' ); + + sb.append(ss2); + _tzFormatString=sb.toString(); + } + else + _tzFormatString=_formatString; + setMinFormatString(); + } + + + /* ------------------------------------------------------------ */ + private void setMinFormatString() + { + int i = _tzFormatString.indexOf("ss.SSS"); + int l = 6; + if (i>=0) + throw new IllegalStateException("ms not supported"); + i = _tzFormatString.indexOf("ss"); + l=2; + + // Build a formatter that formats a second format string + String ss1=_tzFormatString.substring(0,i); + String ss2=_tzFormatString.substring(i+l); + _minFormatString =ss1+"'ss'"+ss2; + } + + /* ------------------------------------------------------------ */ + /** Format a date according to our stored formatter. + * @param inDate + * @return Formatted date + */ + public synchronized String format(Date inDate) + { + return format(inDate.getTime()); + } + + /* ------------------------------------------------------------ */ + /** Format a date according to our stored formatter. + * @param inDate + * @return Formatted date + */ + public synchronized String format(long inDate) + { + long seconds = inDate / 1000; + + // Is it not suitable to cache? + if (seconds<_lastSeconds || + _lastSeconds>0 && seconds>_lastSeconds+__hitWindow) + { + // It's a cache miss + Date d = new Date(inDate); + return _tzFormat.format(d); + + } + + // Check if we are in the same second + // and don't care about millis + if (_lastSeconds==seconds ) + return _lastResult; + + Date d = new Date(inDate); + + // Check if we need a new format string + long minutes = seconds/60; + if (_lastMinutes != minutes) + { + _lastMinutes = minutes; + _secFormatString=_minFormat.format(d); + + int i=_secFormatString.indexOf("ss"); + int l=2; + _secFormatString0=_secFormatString.substring(0,i); + _secFormatString1=_secFormatString.substring(i+l); + } + + // Always format if we get here + _lastSeconds = seconds; + StringBuilder sb=new StringBuilder(_secFormatString.length()); + sb.append(_secFormatString0); + int s=(int)(seconds%60); + if (s<10) + sb.append('0'); + sb.append(s); + sb.append(_secFormatString1); + _lastResult=sb.toString(); + + + return _lastResult; + } + + /* ------------------------------------------------------------ */ + /** Format to string buffer. + * @param inDate Date the format + * @param buffer StringBuilder + */ + public void format(long inDate, StringBuilder buffer) + { + buffer.append(format(inDate)); + } + + /* ------------------------------------------------------------ */ + /** Get the format. + */ + public SimpleDateFormat getFormat() + { + return _minFormat; + } + + /* ------------------------------------------------------------ */ + public String getFormatString() + { + return _formatString; + } + + /* ------------------------------------------------------------ */ + public String now() + { + long now=System.currentTimeMillis(); + _lastMs=(int)(now%1000); + return format(now); + } + + /* ------------------------------------------------------------ */ + public int lastMs() + { + return _lastMs; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/HostMap.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,108 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +/* ------------------------------------------------------------ */ +/** + */ +@SuppressWarnings("serial") +public class HostMap<TYPE> extends HashMap<String, TYPE> +{ + + /* --------------------------------------------------------------- */ + /** Construct empty HostMap. + */ + public HostMap() + { + super(11); + } + + /* --------------------------------------------------------------- */ + /** Construct empty HostMap. + * + * @param capacity initial capacity + */ + public HostMap(int capacity) + { + super (capacity); + } + + /* ------------------------------------------------------------ */ + /** + * @see java.util.HashMap#put(java.lang.Object, java.lang.Object) + */ + @Override + public TYPE put(String host, TYPE object) + throws IllegalArgumentException + { + return super.put(host, object); + } + + /* ------------------------------------------------------------ */ + /** + * @see java.util.HashMap#get(java.lang.Object) + */ + @Override + public TYPE get(Object key) + { + return super.get(key); + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve a lazy list of map entries associated with specified + * hostname by taking into account the domain suffix matches. + * + * @param host hostname + * @return lazy list of map entries + */ + public Object getLazyMatches(String host) + { + if (host == null) + return LazyList.getList(super.entrySet()); + + int idx = 0; + String domain = host.trim(); + HashSet<String> domains = new HashSet<String>(); + do { + domains.add(domain); + if ((idx = domain.indexOf('.')) > 0) + { + domain = domain.substring(idx+1); + } + } while (idx > 0); + + Object entries = null; + for(Map.Entry<String, TYPE> entry: super.entrySet()) + { + if (domains.contains(entry.getKey())) + { + entries = LazyList.add(entries,entry); + } + } + + return entries; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/IO.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,556 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.StringWriter; +import java.io.Writer; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.thread.QueuedThreadPool; + +/* ======================================================================== */ +/** IO Utilities. + * Provides stream handling utilities in + * singleton Threadpool implementation accessed by static members. + */ +public class IO +{ + private static final Logger LOG = Log.getLogger(IO.class); + + /* ------------------------------------------------------------------- */ + public final static String + CRLF = "\015\012"; + + /* ------------------------------------------------------------------- */ + public final static byte[] + CRLF_BYTES = {(byte)'\015',(byte)'\012'}; + + /* ------------------------------------------------------------------- */ + public static int bufferSize = 64*1024; + + /* ------------------------------------------------------------------- */ + // TODO get rid of this singleton! + private static class Singleton { + static final QueuedThreadPool __pool=new QueuedThreadPool(); + static + { + try{__pool.start();} + catch(Exception e){LOG.warn(e); System.exit(1);} + } + } + + /* ------------------------------------------------------------------- */ + static class Job implements Runnable + { + InputStream in; + OutputStream out; + Reader read; + Writer write; + + Job(InputStream in,OutputStream out) + { + this.in=in; + this.out=out; + this.read=null; + this.write=null; + } + Job(Reader read,Writer write) + { + this.in=null; + this.out=null; + this.read=read; + this.write=write; + } + + /* ------------------------------------------------------------ */ + /* + * @see java.lang.Runnable#run() + */ + public void run() + { + try { + if (in!=null) + copy(in,out,-1); + else + copy(read,write,-1); + } + catch(IOException e) + { + LOG.ignore(e); + try{ + if (out!=null) + out.close(); + if (write!=null) + write.close(); + } + catch(IOException e2) + { + LOG.ignore(e2); + } + } + } + } + + /* ------------------------------------------------------------------- */ + /** Copy Stream in to Stream out until EOF or exception. + * in own thread + */ + public static void copyThread(InputStream in, OutputStream out) + { + try{ + Job job=new Job(in,out); + if (!Singleton.__pool.dispatch(job)) + job.run(); + } + catch(Exception e) + { + LOG.warn(e); + } + } + + /* ------------------------------------------------------------------- */ + /** Copy Stream in to Stream out until EOF or exception. + */ + public static void copy(InputStream in, OutputStream out) + throws IOException + { + copy(in,out,-1); + } + + /* ------------------------------------------------------------------- */ + /** Copy Stream in to Stream out until EOF or exception + * in own thread + */ + public static void copyThread(Reader in, Writer out) + { + try + { + Job job=new Job(in,out); + if (!Singleton.__pool.dispatch(job)) + job.run(); + } + catch(Exception e) + { + LOG.warn(e); + } + } + + /* ------------------------------------------------------------------- */ + /** Copy Reader to Writer out until EOF or exception. + */ + public static void copy(Reader in, Writer out) + throws IOException + { + copy(in,out,-1); + } + + /* ------------------------------------------------------------------- */ + /** Copy Stream in to Stream for byteCount bytes or until EOF or exception. + */ + public static void copy(InputStream in, + OutputStream out, + long byteCount) + throws IOException + { + byte buffer[] = new byte[bufferSize]; + int len=bufferSize; + + if (byteCount>=0) + { + while (byteCount>0) + { + int max = byteCount<bufferSize?(int)byteCount:bufferSize; + len=in.read(buffer,0,max); + + if (len==-1) + break; + + byteCount -= len; + out.write(buffer,0,len); + } + } + else + { + while (true) + { + len=in.read(buffer,0,bufferSize); + if (len<0 ) + break; + out.write(buffer,0,len); + } + } + } + + /* ------------------------------------------------------------------- */ + /** Copy Reader to Writer for byteCount bytes or until EOF or exception. + */ + public static void copy(Reader in, + Writer out, + long byteCount) + throws IOException + { + char buffer[] = new char[bufferSize]; + int len=bufferSize; + + if (byteCount>=0) + { + while (byteCount>0) + { + if (byteCount<bufferSize) + len=in.read(buffer,0,(int)byteCount); + else + len=in.read(buffer,0,bufferSize); + + if (len==-1) + break; + + byteCount -= len; + out.write(buffer,0,len); + } + } + else if (out instanceof PrintWriter) + { + PrintWriter pout=(PrintWriter)out; + while (!pout.checkError()) + { + len=in.read(buffer,0,bufferSize); + if (len==-1) + break; + out.write(buffer,0,len); + } + } + else + { + while (true) + { + len=in.read(buffer,0,bufferSize); + if (len==-1) + break; + out.write(buffer,0,len); + } + } + } + + /* ------------------------------------------------------------ */ + /** Copy files or directories + * @param from + * @param to + * @throws IOException + */ + public static void copy(File from,File to) throws IOException + { + if (from.isDirectory()) + copyDir(from,to); + else + copyFile(from,to); + } + + /* ------------------------------------------------------------ */ + public static void copyDir(File from,File to) throws IOException + { + if (to.exists()) + { + if (!to.isDirectory()) + throw new IllegalArgumentException(to.toString()); + } + else + to.mkdirs(); + + File[] files = from.listFiles(); + if (files!=null) + { + for (int i=0;i<files.length;i++) + { + String name = files[i].getName(); + if (".".equals(name) || "..".equals(name)) + continue; + copy(files[i],new File(to,name)); + } + } + } + + /* ------------------------------------------------------------ */ + public static void copyFile(File from,File to) throws IOException + { + FileInputStream in=new FileInputStream(from); + FileOutputStream out=new FileOutputStream(to); + copy(in,out); + in.close(); + out.close(); + } + + /* ------------------------------------------------------------ */ + /** Read input stream to string. + */ + public static String toString(InputStream in) + throws IOException + { + return toString(in,null); + } + + /* ------------------------------------------------------------ */ + /** Read input stream to string. + */ + public static String toString(InputStream in,String encoding) + throws IOException + { + StringWriter writer=new StringWriter(); + InputStreamReader reader = encoding==null?new InputStreamReader(in):new InputStreamReader(in,encoding); + + copy(reader,writer); + return writer.toString(); + } + + /* ------------------------------------------------------------ */ + /** Read input stream to string. + */ + public static String toString(Reader in) + throws IOException + { + StringWriter writer=new StringWriter(); + copy(in,writer); + return writer.toString(); + } + + + /* ------------------------------------------------------------ */ + /** Delete File. + * This delete will recursively delete directories - BE CAREFULL + * @param file The file to be deleted. + */ + public static boolean delete(File file) + { + if (!file.exists()) + return false; + if (file.isDirectory()) + { + File[] files = file.listFiles(); + for (int i=0;files!=null && i<files.length;i++) + delete(files[i]); + } + return file.delete(); + } + + /* ------------------------------------------------------------ */ + /** + * closes any {@link Closeable} + * + * @param c the closeable to close + */ + public static void close(Closeable c) + { + try + { + if (c != null) + c.close(); + } + catch (IOException e) + { + LOG.ignore(e); + } + } + + /** + * closes an input stream, and logs exceptions + * + * @param is the input stream to close + */ + public static void close(InputStream is) + { + try + { + if (is != null) + is.close(); + } + catch (IOException e) + { + LOG.ignore(e); + } + } + + /** + * closes a reader, and logs exceptions + * + * @param reader the reader to close + */ + public static void close(Reader reader) + { + try + { + if (reader != null) + reader.close(); + } catch (IOException e) + { + LOG.ignore(e); + } + } + + /** + * closes a writer, and logs exceptions + * + * @param writer the writer to close + */ + public static void close(Writer writer) + { + try + { + if (writer != null) + writer.close(); + } catch (IOException e) + { + LOG.ignore(e); + } + } + + /* ------------------------------------------------------------ */ + public static byte[] readBytes(InputStream in) + throws IOException + { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + copy(in,bout); + return bout.toByteArray(); + } + + /* ------------------------------------------------------------ */ + /** + * closes an output stream, and logs exceptions + * + * @param os the output stream to close + */ + public static void close(OutputStream os) + { + try + { + if (os != null) + os.close(); + } + catch (IOException e) + { + LOG.ignore(e); + } + } + + /* ------------------------------------------------------------ */ + /** + * @return An outputstream to nowhere + */ + public static OutputStream getNullStream() + { + return __nullStream; + } + + /* ------------------------------------------------------------ */ + /** + * @return An outputstream to nowhere + */ + public static InputStream getClosedStream() + { + return __closedStream; + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + private static class NullOS extends OutputStream + { + @Override + public void close(){} + @Override + public void flush(){} + @Override + public void write(byte[]b){} + @Override + public void write(byte[]b,int i,int l){} + @Override + public void write(int b){} + } + private static NullOS __nullStream = new NullOS(); + + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + private static class ClosedIS extends InputStream + { + @Override + public int read() throws IOException + { + return -1; + } + } + private static ClosedIS __closedStream = new ClosedIS(); + + /* ------------------------------------------------------------ */ + /** + * @return An writer to nowhere + */ + public static Writer getNullWriter() + { + return __nullWriter; + } + + /* ------------------------------------------------------------ */ + /** + * @return An writer to nowhere + */ + public static PrintWriter getNullPrintWriter() + { + return __nullPrintWriter; + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + private static class NullWrite extends Writer + { + @Override + public void close(){} + @Override + public void flush(){} + @Override + public void write(char[]b){} + @Override + public void write(char[]b,int o,int l){} + @Override + public void write(int b){} + @Override + public void write(String s){} + @Override + public void write(String s,int o,int l){} + } + private static NullWrite __nullWriter = new NullWrite(); + private static PrintWriter __nullPrintWriter = new PrintWriter(__nullWriter); +} + + + + + + + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/IPAddressMap.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,364 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import java.util.BitSet; +import java.util.HashMap; +import java.util.Map; +import java.util.StringTokenizer; + + +/* ------------------------------------------------------------ */ +/** + * Internet address map to object + * <p> + * Internet addresses may be specified as absolute address or as a combination of + * four octet wildcard specifications (a.b.c.d) that are defined as follows. + * </p> + * <pre> + * nnn - an absolute value (0-255) + * mmm-nnn - an inclusive range of absolute values, + * with following shorthand notations: + * nnn- => nnn-255 + * -nnn => 0-nnn + * - => 0-255 + * a,b,... - a list of wildcard specifications + * </pre> + */ +@SuppressWarnings("serial") +public class IPAddressMap<TYPE> extends HashMap<String, TYPE> +{ + private final HashMap<String,IPAddrPattern> _patterns = new HashMap<String,IPAddrPattern>(); + + /* --------------------------------------------------------------- */ + /** Construct empty IPAddressMap. + */ + public IPAddressMap() + { + super(11); + } + + /* --------------------------------------------------------------- */ + /** Construct empty IPAddressMap. + * + * @param capacity initial capacity + */ + public IPAddressMap(int capacity) + { + super (capacity); + } + + /* ------------------------------------------------------------ */ + /** + * Insert a new internet address into map + * + * @see java.util.HashMap#put(java.lang.Object, java.lang.Object) + */ + @Override + public TYPE put(String addrSpec, TYPE object) + throws IllegalArgumentException + { + if (addrSpec == null || addrSpec.trim().length() == 0) + throw new IllegalArgumentException("Invalid IP address pattern: "+addrSpec); + + String spec = addrSpec.trim(); + if (_patterns.get(spec) == null) + _patterns.put(spec,new IPAddrPattern(spec)); + + return super.put(spec, object); + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve the object mapped to the specified internet address literal + * + * @see java.util.HashMap#get(java.lang.Object) + */ + @Override + public TYPE get(Object key) + { + return super.get(key); + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve the first object that is associated with the specified + * internet address by taking into account the wildcard specifications. + * + * @param addr internet address + * @return associated object + */ + public TYPE match(String addr) + { + Map.Entry<String, TYPE> entry = getMatch(addr); + return entry==null ? null : entry.getValue(); + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve the first map entry that is associated with the specified + * internet address by taking into account the wildcard specifications. + * + * @param addr internet address + * @return map entry associated + */ + public Map.Entry<String, TYPE> getMatch(String addr) + { + if (addr != null) + { + for(Map.Entry<String, TYPE> entry: super.entrySet()) + { + if (_patterns.get(entry.getKey()).match(addr)) + { + return entry; + } + } + } + return null; + } + + /* ------------------------------------------------------------ */ + /** + * Retrieve a lazy list of map entries associated with specified + * internet address by taking into account the wildcard specifications. + * + * @param addr internet address + * @return lazy list of map entries + */ + public Object getLazyMatches(String addr) + { + if (addr == null) + return LazyList.getList(super.entrySet()); + + Object entries = null; + for(Map.Entry<String, TYPE> entry: super.entrySet()) + { + if (_patterns.get(entry.getKey()).match(addr)) + { + entries = LazyList.add(entries,entry); + } + } + return entries; + } + + /* ------------------------------------------------------------ */ + /** + * IPAddrPattern + * + * Represents internet address wildcard. + * Matches the wildcard to provided internet address. + */ + private static class IPAddrPattern + { + private final OctetPattern[] _octets = new OctetPattern[4]; + /* ------------------------------------------------------------ */ + /** + * Create new IPAddrPattern + * + * @param value internet address wildcard specification + * @throws IllegalArgumentException if wildcard specification is invalid + */ + public IPAddrPattern(String value) + throws IllegalArgumentException + { + if (value == null || value.trim().length() == 0) + throw new IllegalArgumentException("Invalid IP address pattern: "+value); + + try + { + StringTokenizer parts = new StringTokenizer(value, "."); + + String part; + for (int idx=0; idx<4; idx++) + { + part = parts.hasMoreTokens() ? parts.nextToken().trim() : "0-255"; + + int len = part.length(); + if (len == 0 && parts.hasMoreTokens()) + throw new IllegalArgumentException("Invalid IP address pattern: "+value); + + _octets[idx] = new OctetPattern(len==0 ? "0-255" : part); + } + } + catch (IllegalArgumentException ex) + { + throw new IllegalArgumentException("Invalid IP address pattern: "+value, ex); + } + } + + /* ------------------------------------------------------------ */ + /** + * Match the specified internet address against the wildcard + * + * @param value internet address + * @return true if specified internet address matches wildcard specification + * + * @throws IllegalArgumentException if specified internet address is invalid + */ + public boolean match(String value) + throws IllegalArgumentException + { + if (value == null || value.trim().length() == 0) + throw new IllegalArgumentException("Invalid IP address: "+value); + + try + { + StringTokenizer parts = new StringTokenizer(value, "."); + + boolean result = true; + for (int idx=0; idx<4; idx++) + { + if (!parts.hasMoreTokens()) + throw new IllegalArgumentException("Invalid IP address: "+value); + + if (!(result &= _octets[idx].match(parts.nextToken()))) + break; + } + return result; + } + catch (IllegalArgumentException ex) + { + throw new IllegalArgumentException("Invalid IP address: "+value, ex); + } + } + } + + /* ------------------------------------------------------------ */ + /** + * OctetPattern + * + * Represents a single octet wildcard. + * Matches the wildcard to the specified octet value. + */ + private static class OctetPattern extends BitSet + { + private final BitSet _mask = new BitSet(256); + + /* ------------------------------------------------------------ */ + /** + * Create new OctetPattern + * + * @param octetSpec octet wildcard specification + * @throws IllegalArgumentException if wildcard specification is invalid + */ + public OctetPattern(String octetSpec) + throws IllegalArgumentException + { + try + { + if (octetSpec != null) + { + String spec = octetSpec.trim(); + if(spec.length() == 0) + { + _mask.set(0,255); + } + else + { + StringTokenizer parts = new StringTokenizer(spec,","); + while (parts.hasMoreTokens()) + { + String part = parts.nextToken().trim(); + if (part.length() > 0) + { + if (part.indexOf('-') < 0) + { + Integer value = Integer.valueOf(part); + _mask.set(value); + } + else + { + int low = 0, high = 255; + + String[] bounds = part.split("-",-2); + if (bounds.length != 2) + { + throw new IllegalArgumentException("Invalid octet spec: "+octetSpec); + } + + if (bounds[0].length() > 0) + { + low = Integer.parseInt(bounds[0]); + } + if (bounds[1].length() > 0) + { + high = Integer.parseInt(bounds[1]); + } + + if (low > high) + { + throw new IllegalArgumentException("Invalid octet spec: "+octetSpec); + } + + _mask.set(low, high+1); + } + } + } + } + } + } + catch (NumberFormatException ex) + { + throw new IllegalArgumentException("Invalid octet spec: "+octetSpec, ex); + } + } + + /* ------------------------------------------------------------ */ + /** + * Match specified octet value against the wildcard + * + * @param value octet value + * @return true if specified octet value matches the wildcard + * @throws IllegalArgumentException if specified octet value is invalid + */ + public boolean match(String value) + throws IllegalArgumentException + { + if (value == null || value.trim().length() == 0) + throw new IllegalArgumentException("Invalid octet: "+value); + + try + { + int number = Integer.parseInt(value); + return match(number); + } + catch (NumberFormatException ex) + { + throw new IllegalArgumentException("Invalid octet: "+value); + } + } + + /* ------------------------------------------------------------ */ + /** + * Match specified octet value against the wildcard + * + * @param number octet value + * @return true if specified octet value matches the wildcard + * @throws IllegalArgumentException if specified octet value is invalid + */ + public boolean match(int number) + throws IllegalArgumentException + { + if (number < 0 || number > 255) + throw new IllegalArgumentException("Invalid octet: "+number); + + return _mask.get(number); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/IntrospectionUtil.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,300 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.List; + +/** + * IntrospectionUtil + * + * + */ +public class IntrospectionUtil +{ + + public static boolean isJavaBeanCompliantSetter (Method method) + { + if (method == null) + return false; + + if (method.getReturnType() != Void.TYPE) + return false; + + if (!method.getName().startsWith("set")) + return false; + + if (method.getParameterTypes().length != 1) + return false; + + return true; + } + + public static Method findMethod (Class<?> clazz, String methodName, Class<?>[] args, boolean checkInheritance, boolean strictArgs) + throws NoSuchMethodException + { + if (clazz == null) + throw new NoSuchMethodException("No class"); + if (methodName==null || methodName.trim().equals("")) + throw new NoSuchMethodException("No method name"); + + Method method = null; + Method[] methods = clazz.getDeclaredMethods(); + for (int i=0;i<methods.length && method==null;i++) + { + if (methods[i].getName().equals(methodName) && checkParams(methods[i].getParameterTypes(), (args==null?new Class[] {}:args), strictArgs)) + { + method = methods[i]; + } + + } + if (method!=null) + { + return method; + } + else if (checkInheritance) + return findInheritedMethod(clazz.getPackage(), clazz.getSuperclass(), methodName, args, strictArgs); + else + throw new NoSuchMethodException("No such method "+methodName+" on class "+clazz.getName()); + + } + + + + + + public static Field findField (Class<?> clazz, String targetName, Class<?> targetType, boolean checkInheritance, boolean strictType) + throws NoSuchFieldException + { + if (clazz == null) + throw new NoSuchFieldException("No class"); + if (targetName==null) + throw new NoSuchFieldException("No field name"); + + try + { + Field field = clazz.getDeclaredField(targetName); + if (strictType) + { + if (field.getType().equals(targetType)) + return field; + } + else + { + if (field.getType().isAssignableFrom(targetType)) + return field; + } + if (checkInheritance) + { + return findInheritedField(clazz.getPackage(), clazz.getSuperclass(), targetName, targetType, strictType); + } + else + throw new NoSuchFieldException("No field with name "+targetName+" in class "+clazz.getName()+" of type "+targetType); + } + catch (NoSuchFieldException e) + { + return findInheritedField(clazz.getPackage(),clazz.getSuperclass(), targetName,targetType,strictType); + } + } + + + + + + public static boolean isInheritable (Package pack, Member member) + { + if (pack==null) + return false; + if (member==null) + return false; + + int modifiers = member.getModifiers(); + if (Modifier.isPublic(modifiers)) + return true; + if (Modifier.isProtected(modifiers)) + return true; + if (!Modifier.isPrivate(modifiers) && pack.equals(member.getDeclaringClass().getPackage())) + return true; + + return false; + } + + + + + public static boolean checkParams (Class<?>[] formalParams, Class<?>[] actualParams, boolean strict) + { + if (formalParams==null) + return actualParams==null; + if (actualParams==null) + return false; + + if (formalParams.length!=actualParams.length) + return false; + + if (formalParams.length==0) + return true; + + int j=0; + if (strict) + { + while (j<formalParams.length && formalParams[j].equals(actualParams[j])) + j++; + } + else + { + while ((j<formalParams.length) && (formalParams[j].isAssignableFrom(actualParams[j]))) + { + j++; + } + } + + if (j!=formalParams.length) + { + return false; + } + + return true; + } + + + public static boolean isSameSignature (Method methodA, Method methodB) + { + if (methodA==null) + return false; + if (methodB==null) + return false; + + List<Class<?>> parameterTypesA = Arrays.asList(methodA.getParameterTypes()); + List<Class<?>> parameterTypesB = Arrays.asList(methodB.getParameterTypes()); + + if (methodA.getName().equals(methodB.getName()) + && + parameterTypesA.containsAll(parameterTypesB)) + return true; + + return false; + } + + public static boolean isTypeCompatible (Class<?> formalType, Class<?> actualType, boolean strict) + { + if (formalType==null) + return actualType==null; + if (actualType==null) + return false; + + if (strict) + return formalType.equals(actualType); + else + return formalType.isAssignableFrom(actualType); + } + + + + + public static boolean containsSameMethodSignature (Method method, Class<?> c, boolean checkPackage) + { + if (checkPackage) + { + if (!c.getPackage().equals(method.getDeclaringClass().getPackage())) + return false; + } + + boolean samesig = false; + Method[] methods = c.getDeclaredMethods(); + for (int i=0; i<methods.length && !samesig; i++) + { + if (IntrospectionUtil.isSameSignature(method, methods[i])) + samesig = true; + } + return samesig; + } + + + public static boolean containsSameFieldName(Field field, Class<?> c, boolean checkPackage) + { + if (checkPackage) + { + if (!c.getPackage().equals(field.getDeclaringClass().getPackage())) + return false; + } + + boolean sameName = false; + Field[] fields = c.getDeclaredFields(); + for (int i=0;i<fields.length && !sameName; i++) + { + if (fields[i].getName().equals(field.getName())) + sameName = true; + } + return sameName; + } + + + + protected static Method findInheritedMethod (Package pack, Class<?> clazz, String methodName, Class<?>[] args, boolean strictArgs) + throws NoSuchMethodException + { + if (clazz==null) + throw new NoSuchMethodException("No class"); + if (methodName==null) + throw new NoSuchMethodException("No method name"); + + Method method = null; + Method[] methods = clazz.getDeclaredMethods(); + for (int i=0;i<methods.length && method==null;i++) + { + if (methods[i].getName().equals(methodName) + && isInheritable(pack,methods[i]) + && checkParams(methods[i].getParameterTypes(), args, strictArgs)) + method = methods[i]; + } + if (method!=null) + { + return method; + } + else + return findInheritedMethod(clazz.getPackage(), clazz.getSuperclass(), methodName, args, strictArgs); + } + + protected static Field findInheritedField (Package pack, Class<?> clazz, String fieldName, Class<?> fieldType, boolean strictType) + throws NoSuchFieldException + { + if (clazz==null) + throw new NoSuchFieldException ("No class"); + if (fieldName==null) + throw new NoSuchFieldException ("No field name"); + try + { + Field field = clazz.getDeclaredField(fieldName); + if (isInheritable(pack, field) && isTypeCompatible(fieldType, field.getType(), strictType)) + return field; + else + return findInheritedField(clazz.getPackage(), clazz.getSuperclass(),fieldName, fieldType, strictType); + } + catch (NoSuchFieldException e) + { + return findInheritedField(clazz.getPackage(), clazz.getSuperclass(),fieldName, fieldType, strictType); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/LazyList.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,483 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import java.io.Serializable; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +/* ------------------------------------------------------------ */ +/** Lazy List creation. + * A List helper class that attempts to avoid unnecessary List + * creation. If a method needs to create a List to return, but it is + * expected that this will either be empty or frequently contain a + * single item, then using LazyList will avoid additional object + * creations by using {@link Collections#EMPTY_LIST} or + * {@link Collections#singletonList(Object)} where possible. + * <p> + * LazyList works by passing an opaque representation of the list in + * and out of all the LazyList methods. This opaque object is either + * null for an empty list, an Object for a list with a single entry + * or an {@link ArrayList} for a list of items. + * + * <p><h4>Usage</h4> + * <pre> + * Object lazylist =null; + * while(loopCondition) + * { + * Object item = getItem(); + * if (item.isToBeAdded()) + * lazylist = LazyList.add(lazylist,item); + * } + * return LazyList.getList(lazylist); + * </pre> + * + * An ArrayList of default size is used as the initial LazyList. + * + * @see java.util.List + */ +public class LazyList + implements Cloneable, Serializable +{ + private static final String[] __EMTPY_STRING_ARRAY = new String[0]; + + /* ------------------------------------------------------------ */ + private LazyList() + {} + + /* ------------------------------------------------------------ */ + /** Add an item to a LazyList + * @param list The list to add to or null if none yet created. + * @param item The item to add. + * @return The lazylist created or added to. + */ + @SuppressWarnings("unchecked") + public static Object add(Object list, Object item) + { + if (list==null) + { + if (item instanceof List || item==null) + { + List<Object> l = new ArrayList<Object>(); + l.add(item); + return l; + } + + return item; + } + + if (list instanceof List) + { + ((List<Object>)list).add(item); + return list; + } + + List<Object> l=new ArrayList<Object>(); + l.add(list); + l.add(item); + return l; + } + + /* ------------------------------------------------------------ */ + /** Add an item to a LazyList + * @param list The list to add to or null if none yet created. + * @param index The index to add the item at. + * @param item The item to add. + * @return The lazylist created or added to. + */ + @SuppressWarnings("unchecked") + public static Object add(Object list, int index, Object item) + { + if (list==null) + { + if (index>0 || item instanceof List || item==null) + { + List<Object> l = new ArrayList<Object>(); + l.add(index,item); + return l; + } + return item; + } + + if (list instanceof List) + { + ((List<Object>)list).add(index,item); + return list; + } + + List<Object> l=new ArrayList<Object>(); + l.add(list); + l.add(index,item); + return l; + } + + /* ------------------------------------------------------------ */ + /** Add the contents of a Collection to a LazyList + * @param list The list to add to or null if none yet created. + * @param collection The Collection whose contents should be added. + * @return The lazylist created or added to. + */ + public static Object addCollection(Object list, Collection<?> collection) + { + Iterator<?> i=collection.iterator(); + while(i.hasNext()) + list=LazyList.add(list,i.next()); + return list; + } + + /* ------------------------------------------------------------ */ + /** Add the contents of an array to a LazyList + * @param list The list to add to or null if none yet created. + * @param array The array whose contents should be added. + * @return The lazylist created or added to. + */ + public static Object addArray(Object list, Object[] array) + { + for(int i=0;array!=null && i<array.length;i++) + list=LazyList.add(list,array[i]); + return list; + } + + /* ------------------------------------------------------------ */ + /** Ensure the capacity of the underlying list. + * + */ + public static Object ensureSize(Object list, int initialSize) + { + if (list==null) + return new ArrayList<Object>(initialSize); + if (list instanceof ArrayList) + { + ArrayList<?> ol=(ArrayList<?>)list; + if (ol.size()>initialSize) + return ol; + ArrayList<Object> nl = new ArrayList<Object>(initialSize); + nl.addAll(ol); + return nl; + } + List<Object> l= new ArrayList<Object>(initialSize); + l.add(list); + return l; + } + + /* ------------------------------------------------------------ */ + public static Object remove(Object list, Object o) + { + if (list==null) + return null; + + if (list instanceof List) + { + List<?> l = (List<?>)list; + l.remove(o); + if (l.size()==0) + return null; + return list; + } + + if (list.equals(o)) + return null; + return list; + } + + /* ------------------------------------------------------------ */ + public static Object remove(Object list, int i) + { + if (list==null) + return null; + + if (list instanceof List) + { + List<?> l = (List<?>)list; + l.remove(i); + if (l.size()==0) + return null; + return list; + } + + if (i==0) + return null; + return list; + } + + + + /* ------------------------------------------------------------ */ + /** Get the real List from a LazyList. + * + * @param list A LazyList returned from LazyList.add(Object) + * @return The List of added items, which may be an EMPTY_LIST + * or a SingletonList. + */ + public static<E> List<E> getList(Object list) + { + return getList(list,false); + } + + + /* ------------------------------------------------------------ */ + /** Get the real List from a LazyList. + * + * @param list A LazyList returned from LazyList.add(Object) or null + * @param nullForEmpty If true, null is returned instead of an + * empty list. + * @return The List of added items, which may be null, an EMPTY_LIST + * or a SingletonList. + */ + @SuppressWarnings("unchecked") + public static<E> List<E> getList(Object list, boolean nullForEmpty) + { + if (list==null) + { + if (nullForEmpty) + return null; + return Collections.emptyList(); + } + if (list instanceof List) + return (List<E>)list; + + return (List<E>)Collections.singletonList(list); + } + + + /* ------------------------------------------------------------ */ + public static String[] toStringArray(Object list) + { + if (list==null) + return __EMTPY_STRING_ARRAY; + + if (list instanceof List) + { + List<?> l = (List<?>)list; + String[] a = new String[l.size()]; + for (int i=l.size();i-->0;) + { + Object o=l.get(i); + if (o!=null) + a[i]=o.toString(); + } + return a; + } + + return new String[] {list.toString()}; + } + + /* ------------------------------------------------------------ */ + /** Convert a lazylist to an array + * @param list The list to convert + * @param clazz The class of the array, which may be a primitive type + * @return array of the lazylist entries passed in + */ + public static Object toArray(Object list,Class<?> clazz) + { + if (list==null) + return Array.newInstance(clazz,0); + + if (list instanceof List) + { + List<?> l = (List<?>)list; + if (clazz.isPrimitive()) + { + Object a = Array.newInstance(clazz,l.size()); + for (int i=0;i<l.size();i++) + Array.set(a,i,l.get(i)); + return a; + } + return l.toArray((Object[])Array.newInstance(clazz,l.size())); + + } + + Object a = Array.newInstance(clazz,1); + Array.set(a,0,list); + return a; + } + + /* ------------------------------------------------------------ */ + /** The size of a lazy List + * @param list A LazyList returned from LazyList.add(Object) or null + * @return the size of the list. + */ + public static int size(Object list) + { + if (list==null) + return 0; + if (list instanceof List) + return ((List<?>)list).size(); + return 1; + } + + /* ------------------------------------------------------------ */ + /** Get item from the list + * @param list A LazyList returned from LazyList.add(Object) or null + * @param i int index + * @return the item from the list. + */ + @SuppressWarnings("unchecked") + public static <E> E get(Object list, int i) + { + if (list==null) + throw new IndexOutOfBoundsException(); + + if (list instanceof List) + return (E)((List<?>)list).get(i); + + if (i==0) + return (E)list; + + throw new IndexOutOfBoundsException(); + } + + /* ------------------------------------------------------------ */ + public static boolean contains(Object list,Object item) + { + if (list==null) + return false; + + if (list instanceof List) + return ((List<?>)list).contains(item); + + return list.equals(item); + } + + + /* ------------------------------------------------------------ */ + public static Object clone(Object list) + { + if (list==null) + return null; + if (list instanceof List) + return new ArrayList<Object>((List<?>)list); + return list; + } + + /* ------------------------------------------------------------ */ + public static String toString(Object list) + { + if (list==null) + return "[]"; + if (list instanceof List) + return list.toString(); + return "["+list+"]"; + } + + /* ------------------------------------------------------------ */ + @SuppressWarnings("unchecked") + public static<E> Iterator<E> iterator(Object list) + { + if (list==null) + { + List<E> empty=Collections.emptyList(); + return empty.iterator(); + } + if (list instanceof List) + { + return ((List<E>)list).iterator(); + } + List<E> l=getList(list); + return l.iterator(); + } + + /* ------------------------------------------------------------ */ + @SuppressWarnings("unchecked") + public static<E> ListIterator<E> listIterator(Object list) + { + if (list==null) + { + List<E> empty=Collections.emptyList(); + return empty.listIterator(); + } + if (list instanceof List) + return ((List<E>)list).listIterator(); + + List<E> l=getList(list); + return l.listIterator(); + } + + /* ------------------------------------------------------------ */ + /** + * @param array Any array of object + * @return A new <i>modifiable</i> list initialised with the elements from <code>array</code>. + */ + public static<E> List<E> array2List(E[] array) + { + if (array==null || array.length==0) + return new ArrayList<E>(); + return new ArrayList<E>(Arrays.asList(array)); + } + + /* ------------------------------------------------------------ */ + /** Add element to an array + * @param array The array to add to (or null) + * @param item The item to add + * @param type The type of the array (in case of null array) + * @return new array with contents of array plus item + */ + public static<T> T[] addToArray(T[] array, T item, Class<?> type) + { + if (array==null) + { + if (type==null && item!=null) + type= item.getClass(); + @SuppressWarnings("unchecked") + T[] na = (T[])Array.newInstance(type, 1); + na[0]=item; + return na; + } + else + { + // TODO: Replace with Arrays.copyOf(T[] original, int newLength) from Java 1.6+ + Class<?> c = array.getClass().getComponentType(); + @SuppressWarnings("unchecked") + T[] na = (T[])Array.newInstance(c, Array.getLength(array)+1); + System.arraycopy(array, 0, na, 0, array.length); + na[array.length]=item; + return na; + } + } + + /* ------------------------------------------------------------ */ + public static<T> T[] removeFromArray(T[] array, Object item) + { + if (item==null || array==null) + return array; + for (int i=array.length;i-->0;) + { + if (item.equals(array[i])) + { + Class<?> c = array==null?item.getClass():array.getClass().getComponentType(); + @SuppressWarnings("unchecked") + T[] na = (T[])Array.newInstance(c, Array.getLength(array)-1); + if (i>0) + System.arraycopy(array, 0, na, 0, i); + if (i+1<array.length) + System.arraycopy(array, i+1, na, i, array.length-(i+1)); + return na; + } + } + return array; + } + +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/Loader.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,193 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import java.io.File; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +import org.eclipse.jetty.util.resource.Resource; + +/* ------------------------------------------------------------ */ +/** ClassLoader Helper. + * This helper class allows classes to be loaded either from the + * Thread's ContextClassLoader, the classloader of the derived class + * or the system ClassLoader. + * + * <B>Usage:</B><PRE> + * public class MyClass { + * void myMethod() { + * ... + * Class c=Loader.loadClass(this.getClass(),classname); + * ... + * } + * </PRE> + * + */ +public class Loader +{ + /* ------------------------------------------------------------ */ + public static URL getResource(Class<?> loadClass,String name, boolean checkParents) + { + URL url =null; + ClassLoader loader=Thread.currentThread().getContextClassLoader(); + while (url==null && loader!=null ) + { + url=loader.getResource(name); + loader=(url==null&&checkParents)?loader.getParent():null; + } + + loader=loadClass==null?null:loadClass.getClassLoader(); + while (url==null && loader!=null ) + { + url=loader.getResource(name); + loader=(url==null&&checkParents)?loader.getParent():null; + } + + if (url==null) + { + url=ClassLoader.getSystemResource(name); + } + + return url; + } + + /* ------------------------------------------------------------ */ + @SuppressWarnings("rawtypes") + public static Class loadClass(Class loadClass,String name) + throws ClassNotFoundException + { + return loadClass(loadClass,name,false); + } + + /* ------------------------------------------------------------ */ + /** Load a class. + * + * @param loadClass + * @param name + * @param checkParents If true, try loading directly from parent classloaders. + * @return Class + * @throws ClassNotFoundException + */ + @SuppressWarnings("rawtypes") + public static Class loadClass(Class loadClass,String name,boolean checkParents) + throws ClassNotFoundException + { + ClassNotFoundException ex=null; + Class<?> c =null; + ClassLoader loader=Thread.currentThread().getContextClassLoader(); + while (c==null && loader!=null ) + { + try { c=loader.loadClass(name); } + catch (ClassNotFoundException e) {if(ex==null)ex=e;} + loader=(c==null&&checkParents)?loader.getParent():null; + } + + loader=loadClass==null?null:loadClass.getClassLoader(); + while (c==null && loader!=null ) + { + try { c=loader.loadClass(name); } + catch (ClassNotFoundException e) {if(ex==null)ex=e;} + loader=(c==null&&checkParents)?loader.getParent():null; + } + + if (c==null) + { + try { c=Class.forName(name); } + catch (ClassNotFoundException e) {if(ex==null)ex=e;} + } + + if (c!=null) + return c; + throw ex; + } + + + + /* ------------------------------------------------------------ */ + public static ResourceBundle getResourceBundle(Class<?> loadClass,String name,boolean checkParents, Locale locale) + throws MissingResourceException + { + MissingResourceException ex=null; + ResourceBundle bundle =null; + ClassLoader loader=Thread.currentThread().getContextClassLoader(); + while (bundle==null && loader!=null ) + { + try { bundle=ResourceBundle.getBundle(name, locale, loader); } + catch (MissingResourceException e) {if(ex==null)ex=e;} + loader=(bundle==null&&checkParents)?loader.getParent():null; + } + + loader=loadClass==null?null:loadClass.getClassLoader(); + while (bundle==null && loader!=null ) + { + try { bundle=ResourceBundle.getBundle(name, locale, loader); } + catch (MissingResourceException e) {if(ex==null)ex=e;} + loader=(bundle==null&&checkParents)?loader.getParent():null; + } + + if (bundle==null) + { + try { bundle=ResourceBundle.getBundle(name, locale); } + catch (MissingResourceException e) {if(ex==null)ex=e;} + } + + if (bundle!=null) + return bundle; + throw ex; + } + + + /* ------------------------------------------------------------ */ + /** + * Generate the classpath (as a string) of all classloaders + * above the given classloader. + * + * This is primarily used for jasper. + * @return the system class path + */ + public static String getClassPath(ClassLoader loader) throws Exception + { + StringBuilder classpath=new StringBuilder(); + while (loader != null && (loader instanceof URLClassLoader)) + { + URL[] urls = ((URLClassLoader)loader).getURLs(); + if (urls != null) + { + for (int i=0;i<urls.length;i++) + { + Resource resource = Resource.newResource(urls[i]); + File file=resource.getFile(); + if (file!=null && file.exists()) + { + if (classpath.length()>0) + classpath.append(File.pathSeparatorChar); + classpath.append(file.getAbsolutePath()); + } + } + } + loader = loader.getParent(); + } + return classpath.toString(); + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/MultiException.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,185 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.util.List; + + +/* ------------------------------------------------------------ */ +/** Wraps multiple exceptions. + * + * Allows multiple exceptions to be thrown as a single exception. + * + * + */ +@SuppressWarnings("serial") +public class MultiException extends Exception +{ + private Object nested; + + /* ------------------------------------------------------------ */ + public MultiException() + { + super("Multiple exceptions"); + } + + /* ------------------------------------------------------------ */ + public void add(Throwable e) + { + if (e instanceof MultiException) + { + MultiException me = (MultiException)e; + for (int i=0;i<LazyList.size(me.nested);i++) + nested=LazyList.add(nested,LazyList.get(me.nested,i)); + } + else + nested=LazyList.add(nested,e); + } + + /* ------------------------------------------------------------ */ + public int size() + { + return LazyList.size(nested); + } + + /* ------------------------------------------------------------ */ + public List<Throwable> getThrowables() + { + return LazyList.getList(nested); + } + + /* ------------------------------------------------------------ */ + public Throwable getThrowable(int i) + { + return (Throwable) LazyList.get(nested,i); + } + + /* ------------------------------------------------------------ */ + /** Throw a multiexception. + * If this multi exception is empty then no action is taken. If it + * contains a single exception that is thrown, otherwise the this + * multi exception is thrown. + * @exception Exception + */ + public void ifExceptionThrow() + throws Exception + { + switch (LazyList.size(nested)) + { + case 0: + break; + case 1: + Throwable th=(Throwable)LazyList.get(nested,0); + if (th instanceof Error) + throw (Error)th; + if (th instanceof Exception) + throw (Exception)th; + default: + throw this; + } + } + + /* ------------------------------------------------------------ */ + /** Throw a Runtime exception. + * If this multi exception is empty then no action is taken. If it + * contains a single error or runtime exception that is thrown, otherwise the this + * multi exception is thrown, wrapped in a runtime exception. + * @exception Error If this exception contains exactly 1 {@link Error} + * @exception RuntimeException If this exception contains 1 {@link Throwable} but it is not an error, + * or it contains more than 1 {@link Throwable} of any type. + */ + public void ifExceptionThrowRuntime() + throws Error + { + switch (LazyList.size(nested)) + { + case 0: + break; + case 1: + Throwable th=(Throwable)LazyList.get(nested,0); + if (th instanceof Error) + throw (Error)th; + else if (th instanceof RuntimeException) + throw (RuntimeException)th; + else + throw new RuntimeException(th); + default: + throw new RuntimeException(this); + } + } + + /* ------------------------------------------------------------ */ + /** Throw a multiexception. + * If this multi exception is empty then no action is taken. If it + * contains a any exceptions then this + * multi exception is thrown. + */ + public void ifExceptionThrowMulti() + throws MultiException + { + if (LazyList.size(nested)>0) + throw this; + } + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + if (LazyList.size(nested)>0) + return MultiException.class.getSimpleName()+ + LazyList.getList(nested); + return MultiException.class.getSimpleName()+"[]"; + } + + /* ------------------------------------------------------------ */ + @Override + public void printStackTrace() + { + super.printStackTrace(); + for (int i=0;i<LazyList.size(nested);i++) + ((Throwable)LazyList.get(nested,i)).printStackTrace(); + } + + + /* ------------------------------------------------------------------------------- */ + /** + * @see java.lang.Throwable#printStackTrace(java.io.PrintStream) + */ + @Override + public void printStackTrace(PrintStream out) + { + super.printStackTrace(out); + for (int i=0;i<LazyList.size(nested);i++) + ((Throwable)LazyList.get(nested,i)).printStackTrace(out); + } + + /* ------------------------------------------------------------------------------- */ + /** + * @see java.lang.Throwable#printStackTrace(java.io.PrintWriter) + */ + @Override + public void printStackTrace(PrintWriter out) + { + super.printStackTrace(out); + for (int i=0;i<LazyList.size(nested);i++) + ((Throwable)LazyList.get(nested,i)).printStackTrace(out); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/MultiMap.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,415 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/* ------------------------------------------------------------ */ +/** A multi valued Map. + * This Map specializes HashMap and provides methods + * that operate on multi valued items. + * <P> + * Implemented as a map of LazyList values + * @param <K> The key type of the map. + * + * @see LazyList + * + */ +public class MultiMap<K> implements ConcurrentMap<K,Object>, Serializable +{ + private static final long serialVersionUID = -6878723138353851005L; + Map<K,Object> _map; + ConcurrentMap<K, Object> _cmap; + + public MultiMap() + { + _map=new HashMap<K, Object>(); + } + + public MultiMap(Map<K,Object> map) + { + if (map instanceof ConcurrentMap) + _map=_cmap=new ConcurrentHashMap<K, Object>(map); + else + _map=new HashMap<K, Object>(map); + } + + public MultiMap(MultiMap<K> map) + { + if (map._cmap!=null) + _map=_cmap=new ConcurrentHashMap<K, Object>(map._cmap); + else + _map=new HashMap<K,Object>(map._map); + } + + public MultiMap(int capacity) + { + _map=new HashMap<K, Object>(capacity); + } + + public MultiMap(boolean concurrent) + { + if (concurrent) + _map=_cmap=new ConcurrentHashMap<K, Object>(); + else + _map=new HashMap<K, Object>(); + } + + + /* ------------------------------------------------------------ */ + /** Get multiple values. + * Single valued entries are converted to singleton lists. + * @param name The entry key. + * @return Unmodifieable List of values. + */ + public List getValues(Object name) + { + return LazyList.getList(_map.get(name),true); + } + + /* ------------------------------------------------------------ */ + /** Get a value from a multiple value. + * If the value is not a multivalue, then index 0 retrieves the + * value or null. + * @param name The entry key. + * @param i Index of element to get. + * @return Unmodifieable List of values. + */ + public Object getValue(Object name,int i) + { + Object l=_map.get(name); + if (i==0 && LazyList.size(l)==0) + return null; + return LazyList.get(l,i); + } + + + /* ------------------------------------------------------------ */ + /** Get value as String. + * Single valued items are converted to a String with the toString() + * Object method. Multi valued entries are converted to a comma separated + * List. No quoting of commas within values is performed. + * @param name The entry key. + * @return String value. + */ + public String getString(Object name) + { + Object l=_map.get(name); + switch(LazyList.size(l)) + { + case 0: + return null; + case 1: + Object o=LazyList.get(l,0); + return o==null?null:o.toString(); + default: + { + StringBuilder values=new StringBuilder(128); + for (int i=0; i<LazyList.size(l); i++) + { + Object e=LazyList.get(l,i); + if (e!=null) + { + if (values.length()>0) + values.append(','); + values.append(e.toString()); + } + } + return values.toString(); + } + } + } + + /* ------------------------------------------------------------ */ + public Object get(Object name) + { + Object l=_map.get(name); + switch(LazyList.size(l)) + { + case 0: + return null; + case 1: + Object o=LazyList.get(l,0); + return o; + default: + return LazyList.getList(l,true); + } + } + + /* ------------------------------------------------------------ */ + /** Put and entry into the map. + * @param name The entry key. + * @param value The entry value. + * @return The previous value or null. + */ + public Object put(K name, Object value) + { + return _map.put(name,LazyList.add(null,value)); + } + + /* ------------------------------------------------------------ */ + /** Put multi valued entry. + * @param name The entry key. + * @param values The List of multiple values. + * @return The previous value or null. + */ + public Object putValues(K name, List<? extends Object> values) + { + return _map.put(name,values); + } + + /* ------------------------------------------------------------ */ + /** Put multi valued entry. + * @param name The entry key. + * @param values The String array of multiple values. + * @return The previous value or null. + */ + public Object putValues(K name, String... values) + { + Object list=null; + for (int i=0;i<values.length;i++) + list=LazyList.add(list,values[i]); + return _map.put(name,list); + } + + + /* ------------------------------------------------------------ */ + /** Add value to multi valued entry. + * If the entry is single valued, it is converted to the first + * value of a multi valued entry. + * @param name The entry key. + * @param value The entry value. + */ + public void add(K name, Object value) + { + Object lo = _map.get(name); + Object ln = LazyList.add(lo,value); + if (lo!=ln) + _map.put(name,ln); + } + + /* ------------------------------------------------------------ */ + /** Add values to multi valued entry. + * If the entry is single valued, it is converted to the first + * value of a multi valued entry. + * @param name The entry key. + * @param values The List of multiple values. + */ + public void addValues(K name, List<? extends Object> values) + { + Object lo = _map.get(name); + Object ln = LazyList.addCollection(lo,values); + if (lo!=ln) + _map.put(name,ln); + } + + /* ------------------------------------------------------------ */ + /** Add values to multi valued entry. + * If the entry is single valued, it is converted to the first + * value of a multi valued entry. + * @param name The entry key. + * @param values The String array of multiple values. + */ + public void addValues(K name, String[] values) + { + Object lo = _map.get(name); + Object ln = LazyList.addCollection(lo,Arrays.asList(values)); + if (lo!=ln) + _map.put(name,ln); + } + + /* ------------------------------------------------------------ */ + /** Remove value. + * @param name The entry key. + * @param value The entry value. + * @return true if it was removed. + */ + public boolean removeValue(K name,Object value) + { + Object lo = _map.get(name); + Object ln=lo; + int s=LazyList.size(lo); + if (s>0) + { + ln=LazyList.remove(lo,value); + if (ln==null) + _map.remove(name); + else + _map.put(name, ln); + } + return LazyList.size(ln)!=s; + } + + + /* ------------------------------------------------------------ */ + /** Put all contents of map. + * @param m Map + */ + public void putAll(Map<? extends K, ? extends Object> m) + { + boolean multi = (m instanceof MultiMap); + + if (multi) + { + for (Map.Entry<? extends K, ? extends Object> entry : m.entrySet()) + { + _map.put(entry.getKey(),LazyList.clone(entry.getValue())); + } + } + else + { + _map.putAll(m); + } + } + + /* ------------------------------------------------------------ */ + /** + * @return Map of String arrays + */ + public Map<K,String[]> toStringArrayMap() + { + HashMap<K,String[]> map = new HashMap<K,String[]>(_map.size()*3/2) + { + public String toString() + { + StringBuilder b=new StringBuilder(); + b.append('{'); + for (K k:keySet()) + { + if(b.length()>1) + b.append(','); + b.append(k); + b.append('='); + b.append(Arrays.asList(get(k))); + } + + b.append('}'); + return b.toString(); + } + }; + + for(Map.Entry<K,Object> entry: _map.entrySet()) + { + String[] a = LazyList.toStringArray(entry.getValue()); + map.put(entry.getKey(),a); + } + return map; + } + + @Override + public String toString() + { + return _cmap==null?_map.toString():_cmap.toString(); + } + + public void clear() + { + _map.clear(); + } + + public boolean containsKey(Object key) + { + return _map.containsKey(key); + } + + public boolean containsValue(Object value) + { + return _map.containsValue(value); + } + + public Set<Entry<K, Object>> entrySet() + { + return _map.entrySet(); + } + + @Override + public boolean equals(Object o) + { + return _map.equals(o); + } + + @Override + public int hashCode() + { + return _map.hashCode(); + } + + public boolean isEmpty() + { + return _map.isEmpty(); + } + + public Set<K> keySet() + { + return _map.keySet(); + } + + public Object remove(Object key) + { + return _map.remove(key); + } + + public int size() + { + return _map.size(); + } + + public Collection<Object> values() + { + return _map.values(); + } + + + + public Object putIfAbsent(K key, Object value) + { + if (_cmap==null) + throw new UnsupportedOperationException(); + return _cmap.putIfAbsent(key,value); + } + + public boolean remove(Object key, Object value) + { + if (_cmap==null) + throw new UnsupportedOperationException(); + return _cmap.remove(key,value); + } + + public boolean replace(K key, Object oldValue, Object newValue) + { + if (_cmap==null) + throw new UnsupportedOperationException(); + return _cmap.replace(key,oldValue,newValue); + } + + public Object replace(K key, Object value) + { + if (_cmap==null) + throw new UnsupportedOperationException(); + return _cmap.replace(key,value); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/MultiPartInputStream.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,851 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.StringTokenizer; + +import javax.servlet.MultipartConfigElement; +import javax.servlet.ServletException; +import javax.servlet.http.Part; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + + + +/** + * MultiPartInputStream + * + * Handle a MultiPart Mime input stream, breaking it up on the boundary into files and strings. + */ +public class MultiPartInputStream +{ + private static final Logger LOG = Log.getLogger(MultiPartInputStream.class); + + public static final MultipartConfigElement __DEFAULT_MULTIPART_CONFIG = new MultipartConfigElement(System.getProperty("java.io.tmpdir")); + protected InputStream _in; + protected MultipartConfigElement _config; + protected String _contentType; + protected MultiMap<String> _parts; + protected File _tmpDir; + protected File _contextTmpDir; + protected boolean _deleteOnExit; + + + + public class MultiPart implements Part + { + protected String _name; + protected String _filename; + protected File _file; + protected OutputStream _out; + protected ByteArrayOutputStream2 _bout; + protected String _contentType; + protected MultiMap<String> _headers; + protected long _size = 0; + protected boolean _temporary = true; + + public MultiPart (String name, String filename) + throws IOException + { + _name = name; + _filename = filename; + } + + protected void setContentType (String contentType) + { + _contentType = contentType; + } + + + protected void open() + throws IOException + { + //We will either be writing to a file, if it has a filename on the content-disposition + //and otherwise a byte-array-input-stream, OR if we exceed the getFileSizeThreshold, we + //will need to change to write to a file. + if (_filename != null && _filename.trim().length() > 0) + { + createFile(); + } + else + { + //Write to a buffer in memory until we discover we've exceed the + //MultipartConfig fileSizeThreshold + _out = _bout= new ByteArrayOutputStream2(); + } + } + + protected void close() + throws IOException + { + _out.close(); + } + + + protected void write (int b) + throws IOException + { + if (MultiPartInputStream.this._config.getMaxFileSize() > 0 && _size + 1 > MultiPartInputStream.this._config.getMaxFileSize()) + throw new IllegalStateException ("Multipart Mime part "+_name+" exceeds max filesize"); + + if (MultiPartInputStream.this._config.getFileSizeThreshold() > 0 && _size + 1 > MultiPartInputStream.this._config.getFileSizeThreshold() && _file==null) + createFile(); + _out.write(b); + _size ++; + } + + protected void write (byte[] bytes, int offset, int length) + throws IOException + { + if (MultiPartInputStream.this._config.getMaxFileSize() > 0 && _size + length > MultiPartInputStream.this._config.getMaxFileSize()) + throw new IllegalStateException ("Multipart Mime part "+_name+" exceeds max filesize"); + + if (MultiPartInputStream.this._config.getFileSizeThreshold() > 0 && _size + length > MultiPartInputStream.this._config.getFileSizeThreshold() && _file==null) + createFile(); + + _out.write(bytes, offset, length); + _size += length; + } + + protected void createFile () + throws IOException + { + _file = File.createTempFile("MultiPart", "", MultiPartInputStream.this._tmpDir); + if (_deleteOnExit) + _file.deleteOnExit(); + FileOutputStream fos = new FileOutputStream(_file); + BufferedOutputStream bos = new BufferedOutputStream(fos); + + if (_size > 0 && _out != null) + { + //already written some bytes, so need to copy them into the file + _out.flush(); + _bout.writeTo(bos); + _out.close(); + _bout = null; + } + _out = bos; + } + + + + protected void setHeaders(MultiMap<String> headers) + { + _headers = headers; + } + + /** + * @see javax.servlet.http.Part#getContentType() + */ + public String getContentType() + { + return _contentType; + } + + /** + * @see javax.servlet.http.Part#getHeader(java.lang.String) + */ + public String getHeader(String name) + { + if (name == null) + return null; + return (String)_headers.getValue(name.toLowerCase(Locale.ENGLISH), 0); + } + + /** + * @see javax.servlet.http.Part#getHeaderNames() + */ + public Collection<String> getHeaderNames() + { + return _headers.keySet(); + } + + /** + * @see javax.servlet.http.Part#getHeaders(java.lang.String) + */ + public Collection<String> getHeaders(String name) + { + return _headers.getValues(name); + } + + /** + * @see javax.servlet.http.Part#getInputStream() + */ + public InputStream getInputStream() throws IOException + { + if (_file != null) + { + //written to a file, whether temporary or not + return new BufferedInputStream (new FileInputStream(_file)); + } + else + { + //part content is in memory + return new ByteArrayInputStream(_bout.getBuf(),0,_bout.size()); + } + } + + public byte[] getBytes() + { + if (_bout!=null) + return _bout.toByteArray(); + return null; + } + + /** + * @see javax.servlet.http.Part#getName() + */ + public String getName() + { + return _name; + } + + /** + * @see javax.servlet.http.Part#getSize() + */ + public long getSize() + { + return _size; + } + + /** + * @see javax.servlet.http.Part#write(java.lang.String) + */ + public void write(String fileName) throws IOException + { + if (_file == null) + { + _temporary = false; + + //part data is only in the ByteArrayOutputStream and never been written to disk + _file = new File (_tmpDir, fileName); + + BufferedOutputStream bos = null; + try + { + bos = new BufferedOutputStream(new FileOutputStream(_file)); + _bout.writeTo(bos); + bos.flush(); + } + finally + { + if (bos != null) + bos.close(); + _bout = null; + } + } + else + { + //the part data is already written to a temporary file, just rename it + _temporary = false; + + File f = new File(_tmpDir, fileName); + if (_file.renameTo(f)) + _file = f; + } + } + + /** + * Remove the file, whether or not Part.write() was called on it + * (ie no longer temporary) + * @see javax.servlet.http.Part#delete() + */ + public void delete() throws IOException + { + if (_file != null && _file.exists()) + _file.delete(); + } + + /** + * Only remove tmp files. + * + * @throws IOException + */ + public void cleanUp() throws IOException + { + if (_temporary && _file != null && _file.exists()) + _file.delete(); + } + + + /** + * Get the file, if any, the data has been written to. + * @return + */ + public File getFile () + { + return _file; + } + + + /** + * Get the filename from the content-disposition. + * @return null or the filename + */ + public String getContentDispositionFilename () + { + return _filename; + } + } + + + + + /** + * @param in Request input stream + * @param contentType Content-Type header + * @param config MultipartConfigElement + * @param contextTmpDir javax.servlet.context.tempdir + */ + public MultiPartInputStream (InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir) + { + _in = new ReadLineInputStream(in); + _contentType = contentType; + _config = config; + _contextTmpDir = contextTmpDir; + if (_contextTmpDir == null) + _contextTmpDir = new File (System.getProperty("java.io.tmpdir")); + + if (_config == null) + _config = new MultipartConfigElement(_contextTmpDir.getAbsolutePath()); + } + + /** + * Get the already parsed parts. + * + * @return + */ + public Collection<Part> getParsedParts() + { + if (_parts == null) + return Collections.emptyList(); + + Collection<Object> values = _parts.values(); + List<Part> parts = new ArrayList<Part>(); + for (Object o: values) + { + List<Part> asList = LazyList.getList(o, false); + parts.addAll(asList); + } + return parts; + } + + /** + * Delete any tmp storage for parts, and clear out the parts list. + * + * @throws MultiException + */ + public void deleteParts () + throws MultiException + { + Collection<Part> parts = getParsedParts(); + MultiException err = new MultiException(); + for (Part p:parts) + { + try + { + ((MultiPartInputStream.MultiPart)p).cleanUp(); + } + catch(Exception e) + { + err.add(e); + } + } + _parts.clear(); + + err.ifExceptionThrowMulti(); + } + + + /** + * Parse, if necessary, the multipart data and return the list of Parts. + * + * @return + * @throws IOException + * @throws ServletException + */ + public Collection<Part> getParts() + throws IOException, ServletException + { + parse(); + Collection<Object> values = _parts.values(); + List<Part> parts = new ArrayList<Part>(); + for (Object o: values) + { + List<Part> asList = LazyList.getList(o, false); + parts.addAll(asList); + } + return parts; + } + + + /** + * Get the named Part. + * + * @param name + * @return + * @throws IOException + * @throws ServletException + */ + public Part getPart(String name) + throws IOException, ServletException + { + parse(); + return (Part)_parts.getValue(name, 0); + } + + + /** + * Parse, if necessary, the multipart stream. + * + * @throws IOException + * @throws ServletException + */ + protected void parse () + throws IOException, ServletException + { + //have we already parsed the input? + if (_parts != null) + return; + + //initialize + long total = 0; //keep running total of size of bytes read from input and throw an exception if exceeds MultipartConfigElement._maxRequestSize + _parts = new MultiMap<String>(); + + //if its not a multipart request, don't parse it + if (_contentType == null || !_contentType.startsWith("multipart/form-data")) + return; + + //sort out the location to which to write the files + + if (_config.getLocation() == null) + _tmpDir = _contextTmpDir; + else if ("".equals(_config.getLocation())) + _tmpDir = _contextTmpDir; + else + { + File f = new File (_config.getLocation()); + if (f.isAbsolute()) + _tmpDir = f; + else + _tmpDir = new File (_contextTmpDir, _config.getLocation()); + } + + if (!_tmpDir.exists()) + _tmpDir.mkdirs(); + + String contentTypeBoundary = ""; + int bstart = _contentType.indexOf("boundary="); + if (bstart >= 0) + { + int bend = _contentType.indexOf(";", bstart); + bend = (bend < 0? _contentType.length(): bend); + contentTypeBoundary = QuotedStringTokenizer.unquote(value(_contentType.substring(bstart,bend), true).trim()); + } + + String boundary="--"+contentTypeBoundary; + byte[] byteBoundary=(boundary+"--").getBytes(StringUtil.__ISO_8859_1); + + // Get first boundary + String line = null; + try + { + line=((ReadLineInputStream)_in).readLine(); + } + catch (IOException e) + { + LOG.warn("Badly formatted multipart request"); + throw e; + } + + if (line == null) + throw new IOException("Missing content for multipart request"); + + boolean badFormatLogged = false; + line=line.trim(); + while (line != null && !line.equals(boundary)) + { + if (!badFormatLogged) + { + LOG.warn("Badly formatted multipart request"); + badFormatLogged = true; + } + line=((ReadLineInputStream)_in).readLine(); + line=(line==null?line:line.trim()); + } + + if (line == null) + throw new IOException("Missing initial multi part boundary"); + + // Read each part + boolean lastPart=false; + + outer:while(!lastPart) + { + String contentDisposition=null; + String contentType=null; + String contentTransferEncoding=null; + + MultiMap<String> headers = new MultiMap<String>(); + while(true) + { + line=((ReadLineInputStream)_in).readLine(); + + //No more input + if(line==null) + break outer; + + // If blank line, end of part headers + if("".equals(line)) + break; + + total += line.length(); + if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize()) + throw new IllegalStateException ("Request exceeds maxRequestSize ("+_config.getMaxRequestSize()+")"); + + //get content-disposition and content-type + int c=line.indexOf(':',0); + if(c>0) + { + String key=line.substring(0,c).trim().toLowerCase(Locale.ENGLISH); + String value=line.substring(c+1,line.length()).trim(); + headers.put(key, value); + if (key.equalsIgnoreCase("content-disposition")) + contentDisposition=value; + if (key.equalsIgnoreCase("content-type")) + contentType = value; + if(key.equals("content-transfer-encoding")) + contentTransferEncoding=value; + + } + } + + // Extract content-disposition + boolean form_data=false; + if(contentDisposition==null) + { + throw new IOException("Missing content-disposition"); + } + + QuotedStringTokenizer tok=new QuotedStringTokenizer(contentDisposition,";", false, true); + String name=null; + String filename=null; + while(tok.hasMoreTokens()) + { + String t=tok.nextToken().trim(); + String tl=t.toLowerCase(Locale.ENGLISH); + if(t.startsWith("form-data")) + form_data=true; + else if(tl.startsWith("name=")) + name=value(t, true); + else if(tl.startsWith("filename=")) + filename=filenameValue(t); + } + + // Check disposition + if(!form_data) + { + continue; + } + //It is valid for reset and submit buttons to have an empty name. + //If no name is supplied, the browser skips sending the info for that field. + //However, if you supply the empty string as the name, the browser sends the + //field, with name as the empty string. So, only continue this loop if we + //have not yet seen a name field. + if(name==null) + { + continue; + } + + //Have a new Part + MultiPart part = new MultiPart(name, filename); + part.setHeaders(headers); + part.setContentType(contentType); + _parts.add(name, part); + part.open(); + + InputStream partInput = null; + if ("base64".equalsIgnoreCase(contentTransferEncoding)) + { + partInput = new Base64InputStream((ReadLineInputStream)_in); + } + else if ("quoted-printable".equalsIgnoreCase(contentTransferEncoding)) + { + partInput = new FilterInputStream(_in) + { + @Override + public int read() throws IOException + { + int c = in.read(); + if (c >= 0 && c == '=') + { + int hi = in.read(); + int lo = in.read(); + if (hi < 0 || lo < 0) + { + throw new IOException("Unexpected end to quoted-printable byte"); + } + char[] chars = new char[] { (char)hi, (char)lo }; + c = Integer.parseInt(new String(chars),16); + } + return c; + } + }; + } + else + partInput = _in; + + try + { + int state=-2; + int c; + boolean cr=false; + boolean lf=false; + + // loop for all lines + while(true) + { + int b=0; + while((c=(state!=-2)?state:partInput.read())!=-1) + { + total ++; + if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize()) + throw new IllegalStateException("Request exceeds maxRequestSize ("+_config.getMaxRequestSize()+")"); + + state=-2; + + // look for CR and/or LF + if(c==13||c==10) + { + if(c==13) + { + partInput.mark(1); + int tmp=partInput.read(); + if (tmp!=10) + partInput.reset(); + else + state=tmp; + } + break; + } + + // Look for boundary + if(b>=0&&b<byteBoundary.length&&c==byteBoundary[b]) + { + b++; + } + else + { + // Got a character not part of the boundary, so we don't have the boundary marker. + // Write out as many chars as we matched, then the char we're looking at. + if(cr) + part.write(13); + + if(lf) + part.write(10); + + cr=lf=false; + if(b>0) + part.write(byteBoundary,0,b); + + b=-1; + part.write(c); + } + } + + // Check for incomplete boundary match, writing out the chars we matched along the way + if((b>0&&b<byteBoundary.length-2)||(b==byteBoundary.length-1)) + { + if(cr) + part.write(13); + + if(lf) + part.write(10); + + cr=lf=false; + part.write(byteBoundary,0,b); + b=-1; + } + + // Boundary match. If we've run out of input or we matched the entire final boundary marker, then this is the last part. + if(b>0||c==-1) + { + + if(b==byteBoundary.length) + lastPart=true; + if(state==10) + state=-2; + break; + } + + // handle CR LF + if(cr) + part.write(13); + + if(lf) + part.write(10); + + cr=(c==13); + lf=(c==10||state==10); + if(state==10) + state=-2; + } + } + finally + { + part.close(); + } + } + if (!lastPart) + throw new IOException("Incomplete parts"); + } + + public void setDeleteOnExit(boolean deleteOnExit) + { + _deleteOnExit = deleteOnExit; + } + + + public boolean isDeleteOnExit() + { + return _deleteOnExit; + } + + + /* ------------------------------------------------------------ */ + private String value(String nameEqualsValue, boolean splitAfterSpace) + { + /* + String value=nameEqualsValue.substring(nameEqualsValue.indexOf('=')+1).trim(); + int i=value.indexOf(';'); + if(i>0) + value=value.substring(0,i); + if(value.startsWith("\"")) + { + value=value.substring(1,value.indexOf('"',1)); + } + else if (splitAfterSpace) + { + i=value.indexOf(' '); + if(i>0) + value=value.substring(0,i); + } + return value; + */ + int idx = nameEqualsValue.indexOf('='); + String value = nameEqualsValue.substring(idx+1).trim(); + return QuotedStringTokenizer.unquoteOnly(value); + } + + + /* ------------------------------------------------------------ */ + private String filenameValue(String nameEqualsValue) + { + int idx = nameEqualsValue.indexOf('='); + String value = nameEqualsValue.substring(idx+1).trim(); + + if (value.matches(".??[a-z,A-Z]\\:\\\\[^\\\\].*")) + { + //incorrectly escaped IE filenames that have the whole path + //we just strip any leading & trailing quotes and leave it as is + char first=value.charAt(0); + if (first=='"' || first=='\'') + value=value.substring(1); + char last=value.charAt(value.length()-1); + if (last=='"' || last=='\'') + value = value.substring(0,value.length()-1); + + return value; + } + else + //unquote the string, but allow any backslashes that don't + //form a valid escape sequence to remain as many browsers + //even on *nix systems will not escape a filename containing + //backslashes + return QuotedStringTokenizer.unquoteOnly(value, true); + } + + private static class Base64InputStream extends InputStream + { + ReadLineInputStream _in; + String _line; + byte[] _buffer; + int _pos; + + + public Base64InputStream(ReadLineInputStream rlis) + { + _in = rlis; + } + + @Override + public int read() throws IOException + { + if (_buffer==null || _pos>= _buffer.length) + { + //Any CR and LF will be consumed by the readLine() call. + //We need to put them back into the bytes returned from this + //method because the parsing of the multipart content uses them + //as markers to determine when we've reached the end of a part. + _line = _in.readLine(); + if (_line==null) + return -1; //nothing left + if (_line.startsWith("--")) + _buffer=(_line+"\r\n").getBytes(); //boundary marking end of part + else if (_line.length()==0) + _buffer="\r\n".getBytes(); //blank line + else + { + ByteArrayOutputStream baos = new ByteArrayOutputStream((4*_line.length()/3)+2); + B64Code.decode(_line, baos); + baos.write(13); + baos.write(10); + _buffer = baos.toByteArray(); + } + + _pos=0; + } + + return _buffer[_pos++]; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/MultiPartOutputStream.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,140 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + + +/* ================================================================ */ +/** Handle a multipart MIME response. + * + * + * +*/ +public class MultiPartOutputStream extends FilterOutputStream +{ + /* ------------------------------------------------------------ */ + private static final byte[] __CRLF={'\r','\n'}; + private static final byte[] __DASHDASH={'-','-'}; + + public static String MULTIPART_MIXED="multipart/mixed"; + public static String MULTIPART_X_MIXED_REPLACE="multipart/x-mixed-replace"; + + /* ------------------------------------------------------------ */ + private String boundary; + private byte[] boundaryBytes; + + /* ------------------------------------------------------------ */ + private boolean inPart=false; + + /* ------------------------------------------------------------ */ + public MultiPartOutputStream(OutputStream out) + throws IOException + { + super(out); + + boundary = "jetty"+System.identityHashCode(this)+ + Long.toString(System.currentTimeMillis(),36); + boundaryBytes=boundary.getBytes(StringUtil.__ISO_8859_1); + + inPart=false; + } + + + + /* ------------------------------------------------------------ */ + /** End the current part. + * @exception IOException IOException + */ + @Override + public void close() + throws IOException + { + if (inPart) + out.write(__CRLF); + out.write(__DASHDASH); + out.write(boundaryBytes); + out.write(__DASHDASH); + out.write(__CRLF); + inPart=false; + super.close(); + } + + /* ------------------------------------------------------------ */ + public String getBoundary() + { + return boundary; + } + + public OutputStream getOut() {return out;} + + /* ------------------------------------------------------------ */ + /** Start creation of the next Content. + */ + public void startPart(String contentType) + throws IOException + { + if (inPart) + out.write(__CRLF); + inPart=true; + out.write(__DASHDASH); + out.write(boundaryBytes); + out.write(__CRLF); + if (contentType != null) + out.write(("Content-Type: "+contentType).getBytes(StringUtil.__ISO_8859_1)); + out.write(__CRLF); + out.write(__CRLF); + } + + /* ------------------------------------------------------------ */ + /** Start creation of the next Content. + */ + public void startPart(String contentType, String[] headers) + throws IOException + { + if (inPart) + out.write(__CRLF); + inPart=true; + out.write(__DASHDASH); + out.write(boundaryBytes); + out.write(__CRLF); + if (contentType != null) + out.write(("Content-Type: "+contentType).getBytes(StringUtil.__ISO_8859_1)); + out.write(__CRLF); + for (int i=0;headers!=null && i<headers.length;i++) + { + out.write(headers[i].getBytes(StringUtil.__ISO_8859_1)); + out.write(__CRLF); + } + out.write(__CRLF); + } + + /* ------------------------------------------------------------ */ + @Override + public void write(byte[] b, int off, int len) throws IOException + { + out.write(b,off,len); + } +} + + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/MultiPartWriter.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,138 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import java.io.FilterWriter; +import java.io.IOException; +import java.io.Writer; + + +/* ================================================================ */ +/** Handle a multipart MIME response. + * + * + * +*/ +public class MultiPartWriter extends FilterWriter +{ + /* ------------------------------------------------------------ */ + private final static String __CRLF="\015\012"; + private final static String __DASHDASH="--"; + + public static String MULTIPART_MIXED=MultiPartOutputStream.MULTIPART_MIXED; + public static String MULTIPART_X_MIXED_REPLACE=MultiPartOutputStream.MULTIPART_X_MIXED_REPLACE; + + /* ------------------------------------------------------------ */ + private String boundary; + + /* ------------------------------------------------------------ */ + private boolean inPart=false; + + /* ------------------------------------------------------------ */ + public MultiPartWriter(Writer out) + throws IOException + { + super(out); + boundary = "jetty"+System.identityHashCode(this)+ + Long.toString(System.currentTimeMillis(),36); + + inPart=false; + } + + /* ------------------------------------------------------------ */ + /** End the current part. + * @exception IOException IOException + */ + @Override + public void close() + throws IOException + { + if (inPart) + out.write(__CRLF); + out.write(__DASHDASH); + out.write(boundary); + out.write(__DASHDASH); + out.write(__CRLF); + inPart=false; + super.close(); + } + + /* ------------------------------------------------------------ */ + public String getBoundary() + { + return boundary; + } + + /* ------------------------------------------------------------ */ + /** Start creation of the next Content. + */ + public void startPart(String contentType) + throws IOException + { + if (inPart) + out.write(__CRLF); + out.write(__DASHDASH); + out.write(boundary); + out.write(__CRLF); + out.write("Content-Type: "); + out.write(contentType); + out.write(__CRLF); + out.write(__CRLF); + inPart=true; + } + + /* ------------------------------------------------------------ */ + /** end creation of the next Content. + */ + public void endPart() + throws IOException + { + if (inPart) + out.write(__CRLF); + inPart=false; + } + + /* ------------------------------------------------------------ */ + /** Start creation of the next Content. + */ + public void startPart(String contentType, String[] headers) + throws IOException + { + if (inPart) + out.write(__CRLF); + out.write(__DASHDASH); + out.write(boundary); + out.write(__CRLF); + out.write("Content-Type: "); + out.write(contentType); + out.write(__CRLF); + for (int i=0;headers!=null && i<headers.length;i++) + { + out.write(headers[i]); + out.write(__CRLF); + } + out.write(__CRLF); + inPart=true; + } + +} + + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/PatternMatcher.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,104 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +public abstract class PatternMatcher +{ + public abstract void matched (URI uri) throws Exception; + + + /** + * Find jar names from the provided list matching a pattern. + * + * If the pattern is null and isNullInclusive is true, then + * all jar names will match. + * + * A pattern is a set of acceptable jar names. Each acceptable + * jar name is a regex. Each regex can be separated by either a + * "," or a "|". If you use a "|" this or's together the jar + * name patterns. This means that ordering of the matches is + * unimportant to you. If instead, you want to match particular + * jar names, and you want to match them in order, you should + * separate the regexs with "," instead. + * + * Eg "aaa-.*\\.jar|bbb-.*\\.jar" + * Will iterate over the jar names and match + * in any order. + * + * Eg "aaa-*\\.jar,bbb-.*\\.jar" + * Will iterate over the jar names, matching + * all those starting with "aaa-" first, then "bbb-". + * + * @param pattern the pattern + * @param uris the uris to test the pattern against + * @param isNullInclusive if true, an empty pattern means all names match, if false, none match + * @throws Exception + */ + public void match (Pattern pattern, URI[] uris, boolean isNullInclusive) + throws Exception + { + if (uris!=null) + { + String[] patterns = (pattern==null?null:pattern.pattern().split(",")); + + List<Pattern> subPatterns = new ArrayList<Pattern>(); + for (int i=0; patterns!=null && i<patterns.length;i++) + { + subPatterns.add(Pattern.compile(patterns[i])); + } + if (subPatterns.isEmpty()) + subPatterns.add(pattern); + + if (subPatterns.isEmpty()) + { + matchPatterns(null, uris, isNullInclusive); + } + else + { + //for each subpattern, iterate over all the urls, processing those that match + for (Pattern p : subPatterns) + { + matchPatterns(p, uris, isNullInclusive); + } + } + } + } + + + public void matchPatterns (Pattern pattern, URI[] uris, boolean isNullInclusive) + throws Exception + { + for (int i=0; i<uris.length;i++) + { + URI uri = uris[i]; + String s = uri.toString(); + if ((pattern == null && isNullInclusive) + || + (pattern!=null && pattern.matcher(s).matches())) + { + matched(uris[i]); + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/QuotedStringTokenizer.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,603 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import java.io.IOException; +import java.util.Arrays; +import java.util.NoSuchElementException; +import java.util.StringTokenizer; + +/* ------------------------------------------------------------ */ +/** StringTokenizer with Quoting support. + * + * This class is a copy of the java.util.StringTokenizer API and + * the behaviour is the same, except that single and double quoted + * string values are recognised. + * Delimiters within quotes are not considered delimiters. + * Quotes can be escaped with '\'. + * + * @see java.util.StringTokenizer + * + */ +public class QuotedStringTokenizer + extends StringTokenizer +{ + private final static String __delim="\t\n\r"; + private String _string; + private String _delim = __delim; + private boolean _returnQuotes=false; + private boolean _returnDelimiters=false; + private StringBuffer _token; + private boolean _hasToken=false; + private int _i=0; + private int _lastStart=0; + private boolean _double=true; + private boolean _single=true; + + /* ------------------------------------------------------------ */ + public QuotedStringTokenizer(String str, + String delim, + boolean returnDelimiters, + boolean returnQuotes) + { + super(""); + _string=str; + if (delim!=null) + _delim=delim; + _returnDelimiters=returnDelimiters; + _returnQuotes=returnQuotes; + + if (_delim.indexOf('\'')>=0 || + _delim.indexOf('"')>=0) + throw new Error("Can't use quotes as delimiters: "+_delim); + + _token=new StringBuffer(_string.length()>1024?512:_string.length()/2); + } + + /* ------------------------------------------------------------ */ + public QuotedStringTokenizer(String str, + String delim, + boolean returnDelimiters) + { + this(str,delim,returnDelimiters,false); + } + + /* ------------------------------------------------------------ */ + public QuotedStringTokenizer(String str, + String delim) + { + this(str,delim,false,false); + } + + /* ------------------------------------------------------------ */ + public QuotedStringTokenizer(String str) + { + this(str,null,false,false); + } + + /* ------------------------------------------------------------ */ + @Override + public boolean hasMoreTokens() + { + // Already found a token + if (_hasToken) + return true; + + _lastStart=_i; + + int state=0; + boolean escape=false; + while (_i<_string.length()) + { + char c=_string.charAt(_i++); + + switch (state) + { + case 0: // Start + if(_delim.indexOf(c)>=0) + { + if (_returnDelimiters) + { + _token.append(c); + return _hasToken=true; + } + } + else if (c=='\'' && _single) + { + if (_returnQuotes) + _token.append(c); + state=2; + } + else if (c=='\"' && _double) + { + if (_returnQuotes) + _token.append(c); + state=3; + } + else + { + _token.append(c); + _hasToken=true; + state=1; + } + break; + + case 1: // Token + _hasToken=true; + if(_delim.indexOf(c)>=0) + { + if (_returnDelimiters) + _i--; + return _hasToken; + } + else if (c=='\'' && _single) + { + if (_returnQuotes) + _token.append(c); + state=2; + } + else if (c=='\"' && _double) + { + if (_returnQuotes) + _token.append(c); + state=3; + } + else + { + _token.append(c); + } + break; + + case 2: // Single Quote + _hasToken=true; + if (escape) + { + escape=false; + _token.append(c); + } + else if (c=='\'') + { + if (_returnQuotes) + _token.append(c); + state=1; + } + else if (c=='\\') + { + if (_returnQuotes) + _token.append(c); + escape=true; + } + else + { + _token.append(c); + } + break; + + case 3: // Double Quote + _hasToken=true; + if (escape) + { + escape=false; + _token.append(c); + } + else if (c=='\"') + { + if (_returnQuotes) + _token.append(c); + state=1; + } + else if (c=='\\') + { + if (_returnQuotes) + _token.append(c); + escape=true; + } + else + { + _token.append(c); + } + break; + } + } + + return _hasToken; + } + + /* ------------------------------------------------------------ */ + @Override + public String nextToken() + throws NoSuchElementException + { + if (!hasMoreTokens() || _token==null) + throw new NoSuchElementException(); + String t=_token.toString(); + _token.setLength(0); + _hasToken=false; + return t; + } + + /* ------------------------------------------------------------ */ + @Override + public String nextToken(String delim) + throws NoSuchElementException + { + _delim=delim; + _i=_lastStart; + _token.setLength(0); + _hasToken=false; + return nextToken(); + } + + /* ------------------------------------------------------------ */ + @Override + public boolean hasMoreElements() + { + return hasMoreTokens(); + } + + /* ------------------------------------------------------------ */ + @Override + public Object nextElement() + throws NoSuchElementException + { + return nextToken(); + } + + /* ------------------------------------------------------------ */ + /** Not implemented. + */ + @Override + public int countTokens() + { + return -1; + } + + + /* ------------------------------------------------------------ */ + /** Quote a string. + * The string is quoted only if quoting is required due to + * embedded delimiters, quote characters or the + * empty string. + * @param s The string to quote. + * @param delim the delimiter to use to quote the string + * @return quoted string + */ + public static String quoteIfNeeded(String s, String delim) + { + if (s==null) + return null; + if (s.length()==0) + return "\"\""; + + + for (int i=0;i<s.length();i++) + { + char c = s.charAt(i); + if (c=='\\' || c=='"' || c=='\'' || Character.isWhitespace(c) || delim.indexOf(c)>=0) + { + StringBuffer b=new StringBuffer(s.length()+8); + quote(b,s); + return b.toString(); + } + } + + return s; + } + + /* ------------------------------------------------------------ */ + /** Quote a string. + * The string is quoted only if quoting is required due to + * embeded delimiters, quote characters or the + * empty string. + * @param s The string to quote. + * @return quoted string + */ + public static String quote(String s) + { + if (s==null) + return null; + if (s.length()==0) + return "\"\""; + + StringBuffer b=new StringBuffer(s.length()+8); + quote(b,s); + return b.toString(); + + } + + private static final char[] escapes = new char[32]; + static + { + Arrays.fill(escapes, (char)0xFFFF); + escapes['\b'] = 'b'; + escapes['\t'] = 't'; + escapes['\n'] = 'n'; + escapes['\f'] = 'f'; + escapes['\r'] = 'r'; + } + + /* ------------------------------------------------------------ */ + /** Quote a string into an Appendable. + * The characters ", \, \n, \r, \t, \f and \b are escaped + * @param buffer The Appendable + * @param input The String to quote. + */ + public static void quote(Appendable buffer, String input) + { + try + { + buffer.append('"'); + for (int i = 0; i < input.length(); ++i) + { + char c = input.charAt(i); + if (c >= 32) + { + if (c == '"' || c == '\\') + buffer.append('\\'); + buffer.append(c); + } + else + { + char escape = escapes[c]; + if (escape == 0xFFFF) + { + // Unicode escape + buffer.append('\\').append('u').append('0').append('0'); + if (c < 0x10) + buffer.append('0'); + buffer.append(Integer.toString(c, 16)); + } + else + { + buffer.append('\\').append(escape); + } + } + } + buffer.append('"'); + } + catch (IOException x) + { + throw new RuntimeException(x); + } + } + + /* ------------------------------------------------------------ */ + /** Quote a string into a StringBuffer only if needed. + * Quotes are forced if any delim characters are present. + * + * @param buf The StringBuffer + * @param s The String to quote. + * @param delim String of characters that must be quoted. + * @return true if quoted; + */ + public static boolean quoteIfNeeded(Appendable buf, String s,String delim) + { + for (int i=0;i<s.length();i++) + { + char c = s.charAt(i); + if (delim.indexOf(c)>=0) + { + quote(buf,s); + return true; + } + } + + try + { + buf.append(s); + return false; + } + catch(IOException e) + { + throw new RuntimeException(e); + } + } + + + /* ------------------------------------------------------------ */ + public static String unquoteOnly(String s) + { + return unquoteOnly(s, false); + } + + + /* ------------------------------------------------------------ */ + /** Unquote a string, NOT converting unicode sequences + * @param s The string to unquote. + * @param lenient if true, will leave in backslashes that aren't valid escapes + * @return quoted string + */ + public static String unquoteOnly(String s, boolean lenient) + { + if (s==null) + return null; + if (s.length()<2) + return s; + + char first=s.charAt(0); + char last=s.charAt(s.length()-1); + if (first!=last || (first!='"' && first!='\'')) + return s; + + StringBuilder b = new StringBuilder(s.length() - 2); + boolean escape=false; + for (int i=1;i<s.length()-1;i++) + { + char c = s.charAt(i); + + if (escape) + { + escape=false; + if (lenient && !isValidEscaping(c)) + { + b.append('\\'); + } + b.append(c); + } + else if (c=='\\') + { + escape=true; + } + else + { + b.append(c); + } + } + + return b.toString(); + } + + /* ------------------------------------------------------------ */ + public static String unquote(String s) + { + return unquote(s,false); + } + + /* ------------------------------------------------------------ */ + /** Unquote a string. + * @param s The string to unquote. + * @return quoted string + */ + public static String unquote(String s, boolean lenient) + { + if (s==null) + return null; + if (s.length()<2) + return s; + + char first=s.charAt(0); + char last=s.charAt(s.length()-1); + if (first!=last || (first!='"' && first!='\'')) + return s; + + StringBuilder b = new StringBuilder(s.length() - 2); + boolean escape=false; + for (int i=1;i<s.length()-1;i++) + { + char c = s.charAt(i); + + if (escape) + { + escape=false; + switch (c) + { + case 'n': + b.append('\n'); + break; + case 'r': + b.append('\r'); + break; + case 't': + b.append('\t'); + break; + case 'f': + b.append('\f'); + break; + case 'b': + b.append('\b'); + break; + case '\\': + b.append('\\'); + break; + case '/': + b.append('/'); + break; + case '"': + b.append('"'); + break; + case 'u': + b.append((char)( + (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<24)+ + (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<16)+ + (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<8)+ + (TypeUtil.convertHexDigit((byte)s.charAt(i++))) + ) + ); + break; + default: + if (lenient && !isValidEscaping(c)) + { + b.append('\\'); + } + b.append(c); + } + } + else if (c=='\\') + { + escape=true; + } + else + { + b.append(c); + } + } + + return b.toString(); + } + + + /* ------------------------------------------------------------ */ + /** Check that char c (which is preceded by a backslash) is a valid + * escape sequence. + * @param c + * @return + */ + private static boolean isValidEscaping(char c) + { + return ((c == 'n') || (c == 'r') || (c == 't') || + (c == 'f') || (c == 'b') || (c == '\\') || + (c == '/') || (c == '"') || (c == 'u')); + } + + /* ------------------------------------------------------------ */ + /** + * @return handle double quotes if true + */ + public boolean getDouble() + { + return _double; + } + + /* ------------------------------------------------------------ */ + /** + * @param d handle double quotes if true + */ + public void setDouble(boolean d) + { + _double=d; + } + + /* ------------------------------------------------------------ */ + /** + * @return handle single quotes if true + */ + public boolean getSingle() + { + return _single; + } + + /* ------------------------------------------------------------ */ + /** + * @param single handle single quotes if true + */ + public void setSingle(boolean single) + { + _single=single; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/ReadLineInputStream.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,136 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * ReadLineInputStream + * + * Read from an input stream, accepting CR/LF, LF or just CR. + */ +public class ReadLineInputStream extends BufferedInputStream +{ + boolean _seenCRLF; + boolean _skipLF; + + public ReadLineInputStream(InputStream in) + { + super(in); + } + + public ReadLineInputStream(InputStream in, int size) + { + super(in,size); + } + + public String readLine() throws IOException + { + mark(buf.length); + + while (true) + { + int b=super.read(); + + if (markpos < 0) + throw new IOException("Buffer size exceeded: no line terminator"); + + if (b==-1) + { + int m=markpos; + markpos=-1; + if (pos>m) + return new String(buf,m,pos-m,StringUtil.__UTF8_CHARSET); + + return null; + } + + if (b=='\r') + { + int p=pos; + + // if we have seen CRLF before, hungrily consume LF + if (_seenCRLF && pos<count) + { + if (buf[pos]=='\n') + pos+=1; + } + else + _skipLF=true; + int m=markpos; + markpos=-1; + return new String(buf,m,p-m-1,StringUtil.__UTF8_CHARSET); + } + + if (b=='\n') + { + if (_skipLF) + { + _skipLF=false; + _seenCRLF=true; + markpos++; + continue; + } + int m=markpos; + markpos=-1; + return new String(buf,m,pos-m-1,StringUtil.__UTF8_CHARSET); + } + } + } + + @Override + public synchronized int read() throws IOException + { + int b = super.read(); + if (_skipLF) + { + _skipLF=false; + if (_seenCRLF && b=='\n') + b=super.read(); + } + return b; + } + + @Override + public synchronized int read(byte[] buf, int off, int len) throws IOException + { + if (_skipLF && len>0) + { + _skipLF=false; + if (_seenCRLF) + { + int b = super.read(); + if (b==-1) + return -1; + + if (b!='\n') + { + buf[off]=(byte)(0xff&b); + return 1+super.read(buf,off+1,len-1); + } + } + } + + return super.read(buf,off,len); + } + + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/RolloverFileOutputStream.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,340 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Locale; +import java.util.TimeZone; +import java.util.Timer; +import java.util.TimerTask; + +/** + * RolloverFileOutputStream + * + * This output stream puts content in a file that is rolled over every 24 hours. + * The filename must include the string "yyyy_mm_dd", which is replaced with the + * actual date when creating and rolling over the file. + * + * Old files are retained for a number of days before being deleted. + * + * + */ +public class RolloverFileOutputStream extends FilterOutputStream +{ + private static Timer __rollover; + + final static String YYYY_MM_DD="yyyy_mm_dd"; + final static String ROLLOVER_FILE_DATE_FORMAT = "yyyy_MM_dd"; + final static String ROLLOVER_FILE_BACKUP_FORMAT = "HHmmssSSS"; + final static int ROLLOVER_FILE_RETAIN_DAYS = 31; + + private RollTask _rollTask; + private SimpleDateFormat _fileBackupFormat; + private SimpleDateFormat _fileDateFormat; + + private String _filename; + private File _file; + private boolean _append; + private int _retainDays; + + /* ------------------------------------------------------------ */ + /** + * @param filename The filename must include the string "yyyy_mm_dd", + * which is replaced with the actual date when creating and rolling over the file. + * @throws IOException + */ + public RolloverFileOutputStream(String filename) + throws IOException + { + this(filename,true,ROLLOVER_FILE_RETAIN_DAYS); + } + + /* ------------------------------------------------------------ */ + /** + * @param filename The filename must include the string "yyyy_mm_dd", + * which is replaced with the actual date when creating and rolling over the file. + * @param append If true, existing files will be appended to. + * @throws IOException + */ + public RolloverFileOutputStream(String filename, boolean append) + throws IOException + { + this(filename,append,ROLLOVER_FILE_RETAIN_DAYS); + } + + /* ------------------------------------------------------------ */ + /** + * @param filename The filename must include the string "yyyy_mm_dd", + * which is replaced with the actual date when creating and rolling over the file. + * @param append If true, existing files will be appended to. + * @param retainDays The number of days to retain files before deleting them. 0 to retain forever. + * @throws IOException + */ + public RolloverFileOutputStream(String filename, + boolean append, + int retainDays) + throws IOException + { + this(filename,append,retainDays,TimeZone.getDefault()); + } + + /* ------------------------------------------------------------ */ + /** + * @param filename The filename must include the string "yyyy_mm_dd", + * which is replaced with the actual date when creating and rolling over the file. + * @param append If true, existing files will be appended to. + * @param retainDays The number of days to retain files before deleting them. 0 to retain forever. + * @throws IOException + */ + public RolloverFileOutputStream(String filename, + boolean append, + int retainDays, + TimeZone zone) + throws IOException + { + + this(filename,append,retainDays,zone,null,null); + } + + /* ------------------------------------------------------------ */ + /** + * @param filename The filename must include the string "yyyy_mm_dd", + * which is replaced with the actual date when creating and rolling over the file. + * @param append If true, existing files will be appended to. + * @param retainDays The number of days to retain files before deleting them. 0 to retain forever. + * @param dateFormat The format for the date file substitution. The default is "yyyy_MM_dd". + * @param backupFormat The format for the file extension of backup files. The default is "HHmmssSSS". + * @throws IOException + */ + public RolloverFileOutputStream(String filename, + boolean append, + int retainDays, + TimeZone zone, + String dateFormat, + String backupFormat) + throws IOException + { + super(null); + + if (dateFormat==null) + dateFormat=ROLLOVER_FILE_DATE_FORMAT; + _fileDateFormat = new SimpleDateFormat(dateFormat); + + if (backupFormat==null) + backupFormat=ROLLOVER_FILE_BACKUP_FORMAT; + _fileBackupFormat = new SimpleDateFormat(backupFormat); + + _fileBackupFormat.setTimeZone(zone); + _fileDateFormat.setTimeZone(zone); + + if (filename!=null) + { + filename=filename.trim(); + if (filename.length()==0) + filename=null; + } + if (filename==null) + throw new IllegalArgumentException("Invalid filename"); + + _filename=filename; + _append=append; + _retainDays=retainDays; + setFile(); + + synchronized(RolloverFileOutputStream.class) + { + if (__rollover==null) + __rollover=new Timer(RolloverFileOutputStream.class.getName(),true); + + _rollTask=new RollTask(); + + Calendar now = Calendar.getInstance(); + now.setTimeZone(zone); + + GregorianCalendar midnight = + new GregorianCalendar(now.get(Calendar.YEAR), + now.get(Calendar.MONTH), + now.get(Calendar.DAY_OF_MONTH), + 23,0); + midnight.setTimeZone(zone); + midnight.add(Calendar.HOUR,1); + __rollover.scheduleAtFixedRate(_rollTask,midnight.getTime(),1000L*60*60*24); + } + } + + /* ------------------------------------------------------------ */ + public String getFilename() + { + return _filename; + } + + /* ------------------------------------------------------------ */ + public String getDatedFilename() + { + if (_file==null) + return null; + return _file.toString(); + } + + /* ------------------------------------------------------------ */ + public int getRetainDays() + { + return _retainDays; + } + + /* ------------------------------------------------------------ */ + private synchronized void setFile() + throws IOException + { + // Check directory + File file = new File(_filename); + _filename=file.getCanonicalPath(); + file=new File(_filename); + File dir= new File(file.getParent()); + if (!dir.isDirectory() || !dir.canWrite()) + throw new IOException("Cannot write log directory "+dir); + + Date now=new Date(); + + // Is this a rollover file? + String filename=file.getName(); + int i=filename.toLowerCase(Locale.ENGLISH).indexOf(YYYY_MM_DD); + if (i>=0) + { + file=new File(dir, + filename.substring(0,i)+ + _fileDateFormat.format(now)+ + filename.substring(i+YYYY_MM_DD.length())); + } + + if (file.exists()&&!file.canWrite()) + throw new IOException("Cannot write log file "+file); + + // Do we need to change the output stream? + if (out==null || !file.equals(_file)) + { + // Yep + _file=file; + if (!_append && file.exists()) + file.renameTo(new File(file.toString()+"."+_fileBackupFormat.format(now))); + OutputStream oldOut=out; + out=new FileOutputStream(file.toString(),_append); + if (oldOut!=null) + oldOut.close(); + //if(log.isDebugEnabled())log.debug("Opened "+_file); + } + } + + /* ------------------------------------------------------------ */ + private void removeOldFiles() + { + if (_retainDays>0) + { + long now = System.currentTimeMillis(); + + File file= new File(_filename); + File dir = new File(file.getParent()); + String fn=file.getName(); + int s=fn.toLowerCase(Locale.ENGLISH).indexOf(YYYY_MM_DD); + if (s<0) + return; + String prefix=fn.substring(0,s); + String suffix=fn.substring(s+YYYY_MM_DD.length()); + + String[] logList=dir.list(); + for (int i=0;i<logList.length;i++) + { + fn = logList[i]; + if(fn.startsWith(prefix)&&fn.indexOf(suffix,prefix.length())>=0) + { + File f = new File(dir,fn); + long date = f.lastModified(); + if ( ((now-date)/(1000*60*60*24))>_retainDays) + f.delete(); + } + } + } + } + + /* ------------------------------------------------------------ */ + @Override + public void write (byte[] buf) + throws IOException + { + out.write (buf); + } + + /* ------------------------------------------------------------ */ + @Override + public void write (byte[] buf, int off, int len) + throws IOException + { + out.write (buf, off, len); + } + + /* ------------------------------------------------------------ */ + /** + */ + @Override + public void close() + throws IOException + { + synchronized(RolloverFileOutputStream.class) + { + try{super.close();} + finally + { + out=null; + _file=null; + } + + _rollTask.cancel(); + } + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + private class RollTask extends TimerTask + { + @Override + public void run() + { + try + { + RolloverFileOutputStream.this.setFile(); + RolloverFileOutputStream.this.removeOldFiles(); + + } + catch(IOException e) + { + // Cannot log this exception to a LOG, as RolloverFOS can be used by logging + e.printStackTrace(); + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/Scanner.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,746 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; + +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + + +/** + * Scanner + * + * Utility for scanning a directory for added, removed and changed + * files and reporting these events via registered Listeners. + * + */ +public class Scanner extends AbstractLifeCycle +{ + private static final Logger LOG = Log.getLogger(Scanner.class); + private static int __scannerId=0; + private int _scanInterval; + private int _scanCount = 0; + private final List<Listener> _listeners = new ArrayList<Listener>(); + private final Map<String,TimeNSize> _prevScan = new HashMap<String,TimeNSize> (); + private final Map<String,TimeNSize> _currentScan = new HashMap<String,TimeNSize> (); + private FilenameFilter _filter; + private final List<File> _scanDirs = new ArrayList<File>(); + private volatile boolean _running = false; + private boolean _reportExisting = true; + private boolean _reportDirs = true; + private Timer _timer; + private TimerTask _task; + private int _scanDepth=0; + + public enum Notification { ADDED, CHANGED, REMOVED }; + private final Map<String,Notification> _notifications = new HashMap<String,Notification>(); + + static class TimeNSize + { + final long _lastModified; + final long _size; + + public TimeNSize(long lastModified, long size) + { + _lastModified = lastModified; + _size = size; + } + + @Override + public int hashCode() + { + return (int)_lastModified^(int)_size; + } + + @Override + public boolean equals(Object o) + { + if (o instanceof TimeNSize) + { + TimeNSize tns = (TimeNSize)o; + return tns._lastModified==_lastModified && tns._size==_size; + } + return false; + } + + @Override + public String toString() + { + return "[lm="+_lastModified+",s="+_size+"]"; + } + } + + /** + * Listener + * + * Marker for notifications re file changes. + */ + public interface Listener + { + } + + public interface ScanListener extends Listener + { + public void scan(); + } + + public interface DiscreteListener extends Listener + { + public void fileChanged (String filename) throws Exception; + public void fileAdded (String filename) throws Exception; + public void fileRemoved (String filename) throws Exception; + } + + + public interface BulkListener extends Listener + { + public void filesChanged (List<String> filenames) throws Exception; + } + + /** + * Listener that notifies when a scan has started and when it has ended. + */ + public interface ScanCycleListener extends Listener + { + public void scanStarted(int cycle) throws Exception; + public void scanEnded(int cycle) throws Exception; + } + + /** + * + */ + public Scanner () + { + } + + /** + * Get the scan interval + * @return interval between scans in seconds + */ + public int getScanInterval() + { + return _scanInterval; + } + + /** + * Set the scan interval + * @param scanInterval pause between scans in seconds, or 0 for no scan after the initial scan. + */ + public synchronized void setScanInterval(int scanInterval) + { + _scanInterval = scanInterval; + schedule(); + } + + /** + * Set the location of the directory to scan. + * @param dir + * @deprecated use setScanDirs(List dirs) instead + */ + @Deprecated + public void setScanDir (File dir) + { + _scanDirs.clear(); + _scanDirs.add(dir); + } + + /** + * Get the location of the directory to scan + * @return the first directory (of {@link #getScanDirs()} being scanned) + * @deprecated use getScanDirs() instead + */ + @Deprecated + public File getScanDir () + { + return (_scanDirs==null?null:(File)_scanDirs.get(0)); + } + + public void setScanDirs (List<File> dirs) + { + _scanDirs.clear(); + _scanDirs.addAll(dirs); + } + + public synchronized void addScanDir( File dir ) + { + _scanDirs.add( dir ); + } + + public List<File> getScanDirs () + { + return Collections.unmodifiableList(_scanDirs); + } + + /* ------------------------------------------------------------ */ + /** + * @param recursive True if scanning is recursive + * @see #setScanDepth(int) + */ + public void setRecursive (boolean recursive) + { + _scanDepth=recursive?-1:0; + } + + /* ------------------------------------------------------------ */ + /** + * @return True if scanning is fully recursive (scandepth==-1) + * @see #getScanDepth() + */ + public boolean getRecursive () + { + return _scanDepth==-1; + } + + /* ------------------------------------------------------------ */ + /** Get the scanDepth. + * @return the scanDepth + */ + public int getScanDepth() + { + return _scanDepth; + } + + /* ------------------------------------------------------------ */ + /** Set the scanDepth. + * @param scanDepth the scanDepth to set + */ + public void setScanDepth(int scanDepth) + { + _scanDepth = scanDepth; + } + + /** + * Apply a filter to files found in the scan directory. + * Only files matching the filter will be reported as added/changed/removed. + * @param filter + */ + public void setFilenameFilter (FilenameFilter filter) + { + _filter = filter; + } + + /** + * Get any filter applied to files in the scan dir. + * @return the filename filter + */ + public FilenameFilter getFilenameFilter () + { + return _filter; + } + + /* ------------------------------------------------------------ */ + /** + * Whether or not an initial scan will report all files as being + * added. + * @param reportExisting if true, all files found on initial scan will be + * reported as being added, otherwise not + */ + public void setReportExistingFilesOnStartup (boolean reportExisting) + { + _reportExisting = reportExisting; + } + + /* ------------------------------------------------------------ */ + public boolean getReportExistingFilesOnStartup() + { + return _reportExisting; + } + + /* ------------------------------------------------------------ */ + /** Set if found directories should be reported. + * @param dirs + */ + public void setReportDirs(boolean dirs) + { + _reportDirs=dirs; + } + + /* ------------------------------------------------------------ */ + public boolean getReportDirs() + { + return _reportDirs; + } + + /* ------------------------------------------------------------ */ + /** + * Add an added/removed/changed listener + * @param listener + */ + public synchronized void addListener (Listener listener) + { + if (listener == null) + return; + _listeners.add(listener); + } + + + + /** + * Remove a registered listener + * @param listener the Listener to be removed + */ + public synchronized void removeListener (Listener listener) + { + if (listener == null) + return; + _listeners.remove(listener); + } + + + /** + * Start the scanning action. + */ + @Override + public synchronized void doStart() + { + if (_running) + return; + + _running = true; + + if (_reportExisting) + { + // if files exist at startup, report them + scan(); + scan(); // scan twice so files reported as stable + } + else + { + //just register the list of existing files and only report changes + scanFiles(); + _prevScan.putAll(_currentScan); + } + schedule(); + } + + public TimerTask newTimerTask () + { + return new TimerTask() + { + @Override + public void run() { scan(); } + }; + } + + public Timer newTimer () + { + return new Timer("Scanner-"+__scannerId++, true); + } + + public void schedule () + { + if (_running) + { + if (_timer!=null) + _timer.cancel(); + if (_task!=null) + _task.cancel(); + if (getScanInterval() > 0) + { + _timer = newTimer(); + _task = newTimerTask(); + _timer.schedule(_task, 1010L*getScanInterval(),1010L*getScanInterval()); + } + } + } + /** + * Stop the scanning. + */ + @Override + public synchronized void doStop() + { + if (_running) + { + _running = false; + if (_timer!=null) + _timer.cancel(); + if (_task!=null) + _task.cancel(); + _task=null; + _timer=null; + } + } + + /** + * Perform a pass of the scanner and report changes + */ + public synchronized void scan () + { + reportScanStart(++_scanCount); + scanFiles(); + reportDifferences(_currentScan, _prevScan); + _prevScan.clear(); + _prevScan.putAll(_currentScan); + reportScanEnd(_scanCount); + + for (Listener l : _listeners) + { + try + { + if (l instanceof ScanListener) + ((ScanListener)l).scan(); + } + catch (Exception e) + { + LOG.warn(e); + } + catch (Error e) + { + LOG.warn(e); + } + } + } + + /** + * Recursively scan all files in the designated directories. + */ + public synchronized void scanFiles () + { + if (_scanDirs==null) + return; + + _currentScan.clear(); + Iterator<File> itor = _scanDirs.iterator(); + while (itor.hasNext()) + { + File dir = itor.next(); + + if ((dir != null) && (dir.exists())) + try + { + scanFile(dir.getCanonicalFile(), _currentScan,0); + } + catch (IOException e) + { + LOG.warn("Error scanning files.", e); + } + } + } + + + /** + * Report the adds/changes/removes to the registered listeners + * + * @param currentScan the info from the most recent pass + * @param oldScan info from the previous pass + */ + public synchronized void reportDifferences (Map<String,TimeNSize> currentScan, Map<String,TimeNSize> oldScan) + { + // scan the differences and add what was found to the map of notifications: + + Set<String> oldScanKeys = new HashSet<String>(oldScan.keySet()); + + // Look for new and changed files + for (Map.Entry<String, TimeNSize> entry: currentScan.entrySet()) + { + String file = entry.getKey(); + if (!oldScanKeys.contains(file)) + { + Notification old=_notifications.put(file,Notification.ADDED); + if (old!=null) + { + switch(old) + { + case REMOVED: + case CHANGED: + _notifications.put(file,Notification.CHANGED); + } + } + } + else if (!oldScan.get(file).equals(currentScan.get(file))) + { + Notification old=_notifications.put(file,Notification.CHANGED); + if (old!=null) + { + switch(old) + { + case ADDED: + _notifications.put(file,Notification.ADDED); + } + } + } + } + + // Look for deleted files + for (String file : oldScan.keySet()) + { + if (!currentScan.containsKey(file)) + { + Notification old=_notifications.put(file,Notification.REMOVED); + if (old!=null) + { + switch(old) + { + case ADDED: + _notifications.remove(file); + } + } + } + } + + if (LOG.isDebugEnabled()) + LOG.debug("scanned "+_scanDirs+": "+_notifications); + + // Process notifications + // Only process notifications that are for stable files (ie same in old and current scan). + List<String> bulkChanges = new ArrayList<String>(); + for (Iterator<Entry<String,Notification>> iter = _notifications.entrySet().iterator();iter.hasNext();) + { + Entry<String,Notification> entry=iter.next(); + String file=entry.getKey(); + + // Is the file stable? + if (oldScan.containsKey(file)) + { + if (!oldScan.get(file).equals(currentScan.get(file))) + continue; + } + else if (currentScan.containsKey(file)) + continue; + + // File is stable so notify + Notification notification=entry.getValue(); + iter.remove(); + bulkChanges.add(file); + switch(notification) + { + case ADDED: + reportAddition(file); + break; + case CHANGED: + reportChange(file); + break; + case REMOVED: + reportRemoval(file); + break; + } + } + if (!bulkChanges.isEmpty()) + reportBulkChanges(bulkChanges); + } + + + /** + * Get last modified time on a single file or recurse if + * the file is a directory. + * @param f file or directory + * @param scanInfoMap map of filenames to last modified times + */ + private void scanFile (File f, Map<String,TimeNSize> scanInfoMap, int depth) + { + try + { + if (!f.exists()) + return; + + if (f.isFile() || depth>0&& _reportDirs && f.isDirectory()) + { + if ((_filter == null) || ((_filter != null) && _filter.accept(f.getParentFile(), f.getName()))) + { + String name = f.getCanonicalPath(); + scanInfoMap.put(name, new TimeNSize(f.lastModified(),f.length())); + } + } + + // If it is a directory, scan if it is a known directory or the depth is OK. + if (f.isDirectory() && (depth<_scanDepth || _scanDepth==-1 || _scanDirs.contains(f))) + { + File[] files = f.listFiles(); + if (files != null) + { + for (int i=0;i<files.length;i++) + scanFile(files[i], scanInfoMap,depth+1); + } + else + LOG.warn("Error listing files in directory {}", f); + + } + } + catch (IOException e) + { + LOG.warn("Error scanning watched files", e); + } + } + + private void warn(Object listener,String filename,Throwable th) + { + LOG.warn(listener+" failed on '"+filename, th); + } + + /** + * Report a file addition to the registered FileAddedListeners + * @param filename + */ + private void reportAddition (String filename) + { + Iterator<Listener> itor = _listeners.iterator(); + while (itor.hasNext()) + { + Listener l = itor.next(); + try + { + if (l instanceof DiscreteListener) + ((DiscreteListener)l).fileAdded(filename); + } + catch (Exception e) + { + warn(l,filename,e); + } + catch (Error e) + { + warn(l,filename,e); + } + } + } + + + /** + * Report a file removal to the FileRemovedListeners + * @param filename + */ + private void reportRemoval (String filename) + { + Iterator<Listener> itor = _listeners.iterator(); + while (itor.hasNext()) + { + Object l = itor.next(); + try + { + if (l instanceof DiscreteListener) + ((DiscreteListener)l).fileRemoved(filename); + } + catch (Exception e) + { + warn(l,filename,e); + } + catch (Error e) + { + warn(l,filename,e); + } + } + } + + + /** + * Report a file change to the FileChangedListeners + * @param filename + */ + private void reportChange (String filename) + { + Iterator<Listener> itor = _listeners.iterator(); + while (itor.hasNext()) + { + Listener l = itor.next(); + try + { + if (l instanceof DiscreteListener) + ((DiscreteListener)l).fileChanged(filename); + } + catch (Exception e) + { + warn(l,filename,e); + } + catch (Error e) + { + warn(l,filename,e); + } + } + } + + private void reportBulkChanges (List<String> filenames) + { + Iterator<Listener> itor = _listeners.iterator(); + while (itor.hasNext()) + { + Listener l = itor.next(); + try + { + if (l instanceof BulkListener) + ((BulkListener)l).filesChanged(filenames); + } + catch (Exception e) + { + warn(l,filenames.toString(),e); + } + catch (Error e) + { + warn(l,filenames.toString(),e); + } + } + } + + /** + * signal any scan cycle listeners that a scan has started + */ + private void reportScanStart(int cycle) + { + for (Listener listener : _listeners) + { + try + { + if (listener instanceof ScanCycleListener) + { + ((ScanCycleListener)listener).scanStarted(cycle); + } + } + catch (Exception e) + { + LOG.warn(listener + " failed on scan start for cycle " + cycle, e); + } + } + } + + /** + * sign + */ + private void reportScanEnd(int cycle) + { + for (Listener listener : _listeners) + { + try + { + if (listener instanceof ScanCycleListener) + { + ((ScanCycleListener)listener).scanEnded(cycle); + } + } + catch (Exception e) + { + LOG.warn(listener + " failed on scan end for cycle " + cycle, e); + } + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/StringMap.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,695 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import java.io.Externalizable; +import java.util.AbstractMap; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/* ------------------------------------------------------------ */ +/** Map implementation Optimized for Strings keys.. + * This String Map has been optimized for mapping small sets of + * Strings where the most frequently accessed Strings have been put to + * the map first. + * + * It also has the benefit that it can look up entries by substring or + * sections of char and byte arrays. This can prevent many String + * objects from being created just to look up in the map. + * + * This map is NOT synchronized. + */ +public class StringMap extends AbstractMap implements Externalizable +{ + public static final boolean CASE_INSENSTIVE=true; + protected static final int __HASH_WIDTH=17; + + /* ------------------------------------------------------------ */ + protected int _width=__HASH_WIDTH; + protected Node _root=new Node(); + protected boolean _ignoreCase=false; + protected NullEntry _nullEntry=null; + protected Object _nullValue=null; + protected HashSet _entrySet=new HashSet(3); + protected Set _umEntrySet=Collections.unmodifiableSet(_entrySet); + + /* ------------------------------------------------------------ */ + /** Constructor. + */ + public StringMap() + {} + + /* ------------------------------------------------------------ */ + /** Constructor. + * @param ignoreCase + */ + public StringMap(boolean ignoreCase) + { + this(); + _ignoreCase=ignoreCase; + } + + /* ------------------------------------------------------------ */ + /** Constructor. + * @param ignoreCase + * @param width Width of hash tables, larger values are faster but + * use more memory. + */ + public StringMap(boolean ignoreCase,int width) + { + this(); + _ignoreCase=ignoreCase; + _width=width; + } + + /* ------------------------------------------------------------ */ + /** Set the ignoreCase attribute. + * @param ic If true, the map is case insensitive for keys. + */ + public void setIgnoreCase(boolean ic) + { + if (_root._children!=null) + throw new IllegalStateException("Must be set before first put"); + _ignoreCase=ic; + } + + /* ------------------------------------------------------------ */ + public boolean isIgnoreCase() + { + return _ignoreCase; + } + + /* ------------------------------------------------------------ */ + /** Set the hash width. + * @param width Width of hash tables, larger values are faster but + * use more memory. + */ + public void setWidth(int width) + { + _width=width; + } + + /* ------------------------------------------------------------ */ + public int getWidth() + { + return _width; + } + + /* ------------------------------------------------------------ */ + @Override + public Object put(Object key, Object value) + { + if (key==null) + return put(null,value); + return put(key.toString(),value); + } + + /* ------------------------------------------------------------ */ + public Object put(String key, Object value) + { + if (key==null) + { + Object oldValue=_nullValue; + _nullValue=value; + if (_nullEntry==null) + { + _nullEntry=new NullEntry(); + _entrySet.add(_nullEntry); + } + return oldValue; + } + + Node node = _root; + int ni=-1; + Node prev = null; + Node parent = null; + + // look for best match + charLoop: + for (int i=0;i<key.length();i++) + { + char c=key.charAt(i); + + // Advance node + if (ni==-1) + { + parent=node; + prev=null; + ni=0; + node=(node._children==null)?null:node._children[c%_width]; + } + + // Loop through a node chain at the same level + while (node!=null) + { + // If it is a matching node, goto next char + if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c) + { + prev=null; + ni++; + if (ni==node._char.length) + ni=-1; + continue charLoop; + } + + // no char match + // if the first char, + if (ni==0) + { + // look along the chain for a char match + prev=node; + node=node._next; + } + else + { + // Split the current node! + node.split(this,ni); + i--; + ni=-1; + continue charLoop; + } + } + + // We have run out of nodes, so as this is a put, make one + node = new Node(_ignoreCase,key,i); + + if (prev!=null) // add to end of chain + prev._next=node; + else if (parent!=null) // add new child + { + if (parent._children==null) + parent._children=new Node[_width]; + parent._children[c%_width]=node; + int oi=node._ochar[0]%_width; + if (node._ochar!=null && node._char[0]%_width!=oi) + { + if (parent._children[oi]==null) + parent._children[oi]=node; + else + { + Node n=parent._children[oi]; + while(n._next!=null) + n=n._next; + n._next=node; + } + } + } + else // this is the root. + _root=node; + break; + } + + // Do we have a node + if (node!=null) + { + // Split it if we are in the middle + if(ni>0) + node.split(this,ni); + + Object old = node._value; + node._key=key; + node._value=value; + _entrySet.add(node); + return old; + } + return null; + } + + /* ------------------------------------------------------------ */ + @Override + public Object get(Object key) + { + if (key==null) + return _nullValue; + if (key instanceof String) + return get((String)key); + return get(key.toString()); + } + + /* ------------------------------------------------------------ */ + public Object get(String key) + { + if (key==null) + return _nullValue; + + Map.Entry entry = getEntry(key,0,key.length()); + if (entry==null) + return null; + return entry.getValue(); + } + + /* ------------------------------------------------------------ */ + /** Get a map entry by substring key. + * @param key String containing the key + * @param offset Offset of the key within the String. + * @param length The length of the key + * @return The Map.Entry for the key or null if the key is not in + * the map. + */ + public Map.Entry getEntry(String key,int offset, int length) + { + if (key==null) + return _nullEntry; + + Node node = _root; + int ni=-1; + + // look for best match + charLoop: + for (int i=0;i<length;i++) + { + char c=key.charAt(offset+i); + + // Advance node + if (ni==-1) + { + ni=0; + node=(node._children==null)?null:node._children[c%_width]; + } + + // Look through the node chain + while (node!=null) + { + // If it is a matching node, goto next char + if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c) + { + ni++; + if (ni==node._char.length) + ni=-1; + continue charLoop; + } + + // No char match, so if mid node then no match at all. + if (ni>0) return null; + + // try next in chain + node=node._next; + } + return null; + } + + if (ni>0) return null; + if (node!=null && node._key==null) + return null; + return node; + } + + /* ------------------------------------------------------------ */ + /** Get a map entry by char array key. + * @param key char array containing the key + * @param offset Offset of the key within the array. + * @param length The length of the key + * @return The Map.Entry for the key or null if the key is not in + * the map. + */ + public Map.Entry getEntry(char[] key,int offset, int length) + { + if (key==null) + return _nullEntry; + + Node node = _root; + int ni=-1; + + // look for best match + charLoop: + for (int i=0;i<length;i++) + { + char c=key[offset+i]; + + // Advance node + if (ni==-1) + { + ni=0; + node=(node._children==null)?null:node._children[c%_width]; + } + + // While we have a node to try + while (node!=null) + { + // If it is a matching node, goto next char + if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c) + { + ni++; + if (ni==node._char.length) + ni=-1; + continue charLoop; + } + + // No char match, so if mid node then no match at all. + if (ni>0) return null; + + // try next in chain + node=node._next; + } + return null; + } + + if (ni>0) return null; + if (node!=null && node._key==null) + return null; + return node; + } + + /* ------------------------------------------------------------ */ + /** Get a map entry by byte array key, using as much of the passed key as needed for a match. + * A simple 8859-1 byte to char mapping is assumed. + * @param key char array containing the key + * @param offset Offset of the key within the array. + * @param maxLength The length of the key + * @return The Map.Entry for the key or null if the key is not in + * the map. + */ + public Map.Entry getBestEntry(byte[] key,int offset, int maxLength) + { + if (key==null) + return _nullEntry; + + Node node = _root; + int ni=-1; + + // look for best match + charLoop: + for (int i=0;i<maxLength;i++) + { + char c=(char)key[offset+i]; + + // Advance node + if (ni==-1) + { + ni=0; + + Node child = (node._children==null)?null:node._children[c%_width]; + + if (child==null && i>0) + return node; // This is the best match + node=child; + } + + // While we have a node to try + while (node!=null) + { + // If it is a matching node, goto next char + if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c) + { + ni++; + if (ni==node._char.length) + ni=-1; + continue charLoop; + } + + // No char match, so if mid node then no match at all. + if (ni>0) return null; + + // try next in chain + node=node._next; + } + return null; + } + + if (ni>0) return null; + if (node!=null && node._key==null) + return null; + return node; + } + + + /* ------------------------------------------------------------ */ + @Override + public Object remove(Object key) + { + if (key==null) + return remove(null); + return remove(key.toString()); + } + + /* ------------------------------------------------------------ */ + public Object remove(String key) + { + if (key==null) + { + Object oldValue=_nullValue; + if (_nullEntry!=null) + { + _entrySet.remove(_nullEntry); + _nullEntry=null; + _nullValue=null; + } + return oldValue; + } + + Node node = _root; + int ni=-1; + + // look for best match + charLoop: + for (int i=0;i<key.length();i++) + { + char c=key.charAt(i); + + // Advance node + if (ni==-1) + { + ni=0; + node=(node._children==null)?null:node._children[c%_width]; + } + + // While we have a node to try + while (node!=null) + { + // If it is a matching node, goto next char + if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c) + { + ni++; + if (ni==node._char.length) + ni=-1; + continue charLoop; + } + + // No char match, so if mid node then no match at all. + if (ni>0) return null; + + // try next in chain + node=node._next; + } + return null; + } + + if (ni>0) return null; + if (node!=null && node._key==null) + return null; + + Object old = node._value; + _entrySet.remove(node); + node._value=null; + node._key=null; + + return old; + } + + /* ------------------------------------------------------------ */ + @Override + public Set entrySet() + { + return _umEntrySet; + } + + /* ------------------------------------------------------------ */ + @Override + public int size() + { + return _entrySet.size(); + } + + /* ------------------------------------------------------------ */ + @Override + public boolean isEmpty() + { + return _entrySet.isEmpty(); + } + + /* ------------------------------------------------------------ */ + @Override + public boolean containsKey(Object key) + { + if (key==null) + return _nullEntry!=null; + return + getEntry(key.toString(),0,key==null?0:key.toString().length())!=null; + } + + /* ------------------------------------------------------------ */ + @Override + public void clear() + { + _root=new Node(); + _nullEntry=null; + _nullValue=null; + _entrySet.clear(); + } + + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + private static class Node implements Map.Entry + { + char[] _char; + char[] _ochar; + Node _next; + Node[] _children; + String _key; + Object _value; + + Node(){} + + Node(boolean ignoreCase,String s, int offset) + { + int l=s.length()-offset; + _char=new char[l]; + _ochar=new char[l]; + for (int i=0;i<l;i++) + { + char c=s.charAt(offset+i); + _char[i]=c; + if (ignoreCase) + { + char o=c; + if (Character.isUpperCase(c)) + o=Character.toLowerCase(c); + else if (Character.isLowerCase(c)) + o=Character.toUpperCase(c); + _ochar[i]=o; + } + } + } + + Node split(StringMap map,int offset) + { + Node split = new Node(); + int sl=_char.length-offset; + + char[] tmp=this._char; + this._char=new char[offset]; + split._char = new char[sl]; + System.arraycopy(tmp,0,this._char,0,offset); + System.arraycopy(tmp,offset,split._char,0,sl); + + if (this._ochar!=null) + { + tmp=this._ochar; + this._ochar=new char[offset]; + split._ochar = new char[sl]; + System.arraycopy(tmp,0,this._ochar,0,offset); + System.arraycopy(tmp,offset,split._ochar,0,sl); + } + + split._key=this._key; + split._value=this._value; + this._key=null; + this._value=null; + if (map._entrySet.remove(this)) + map._entrySet.add(split); + + split._children=this._children; + this._children=new Node[map._width]; + this._children[split._char[0]%map._width]=split; + if (split._ochar!=null && this._children[split._ochar[0]%map._width]!=split) + this._children[split._ochar[0]%map._width]=split; + + return split; + } + + public Object getKey(){return _key;} + public Object getValue(){return _value;} + public Object setValue(Object o){Object old=_value;_value=o;return old;} + @Override + public String toString() + { + StringBuilder buf=new StringBuilder(); + toString(buf); + return buf.toString(); + } + + private void toString(StringBuilder buf) + { + buf.append("{["); + if (_char==null) + buf.append('-'); + else + for (int i=0;i<_char.length;i++) + buf.append(_char[i]); + buf.append(':'); + buf.append(_key); + buf.append('='); + buf.append(_value); + buf.append(']'); + if (_children!=null) + { + for (int i=0;i<_children.length;i++) + { + buf.append('|'); + if (_children[i]!=null) + _children[i].toString(buf); + else + buf.append("-"); + } + } + buf.append('}'); + if (_next!=null) + { + buf.append(",\n"); + _next.toString(buf); + } + } + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + private class NullEntry implements Map.Entry + { + public Object getKey(){return null;} + public Object getValue(){return _nullValue;} + public Object setValue(Object o) + {Object old=_nullValue;_nullValue=o;return old;} + @Override + public String toString(){return "[:null="+_nullValue+"]";} + } + + /* ------------------------------------------------------------ */ + public void writeExternal(java.io.ObjectOutput out) + throws java.io.IOException + { + HashMap map = new HashMap(this); + out.writeBoolean(_ignoreCase); + out.writeObject(map); + } + + /* ------------------------------------------------------------ */ + public void readExternal(java.io.ObjectInput in) + throws java.io.IOException, ClassNotFoundException + { + boolean ic=in.readBoolean(); + HashMap map = (HashMap)in.readObject(); + setIgnoreCase(ic); + this.putAll(map); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/StringUtil.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,504 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** Fast String Utilities. + * + * These string utilities provide both conveniance methods and + * performance improvements over most standard library versions. The + * main aim of the optimizations is to avoid object creation unless + * absolutely required. + * + * + */ +public class StringUtil +{ + private static final Logger LOG = Log.getLogger(StringUtil.class); + + public static final String ALL_INTERFACES="0.0.0.0"; + public static final String CRLF="\015\012"; + public static final String __LINE_SEPARATOR= + System.getProperty("line.separator","\n"); + + public static final String __ISO_8859_1="ISO-8859-1"; + public final static String __UTF8="UTF-8"; + public final static String __UTF8Alt="UTF8"; + public final static String __UTF16="UTF-16"; + + public final static Charset __UTF8_CHARSET; + public final static Charset __ISO_8859_1_CHARSET; + + static + { + __UTF8_CHARSET=Charset.forName(__UTF8); + __ISO_8859_1_CHARSET=Charset.forName(__ISO_8859_1); + } + + private static char[] lowercases = { + '\000','\001','\002','\003','\004','\005','\006','\007', + '\010','\011','\012','\013','\014','\015','\016','\017', + '\020','\021','\022','\023','\024','\025','\026','\027', + '\030','\031','\032','\033','\034','\035','\036','\037', + '\040','\041','\042','\043','\044','\045','\046','\047', + '\050','\051','\052','\053','\054','\055','\056','\057', + '\060','\061','\062','\063','\064','\065','\066','\067', + '\070','\071','\072','\073','\074','\075','\076','\077', + '\100','\141','\142','\143','\144','\145','\146','\147', + '\150','\151','\152','\153','\154','\155','\156','\157', + '\160','\161','\162','\163','\164','\165','\166','\167', + '\170','\171','\172','\133','\134','\135','\136','\137', + '\140','\141','\142','\143','\144','\145','\146','\147', + '\150','\151','\152','\153','\154','\155','\156','\157', + '\160','\161','\162','\163','\164','\165','\166','\167', + '\170','\171','\172','\173','\174','\175','\176','\177' }; + + /* ------------------------------------------------------------ */ + /** + * fast lower case conversion. Only works on ascii (not unicode) + * @param s the string to convert + * @return a lower case version of s + */ + public static String asciiToLowerCase(String s) + { + char[] c = null; + int i=s.length(); + + // look for first conversion + while (i-->0) + { + char c1=s.charAt(i); + if (c1<=127) + { + char c2=lowercases[c1]; + if (c1!=c2) + { + c=s.toCharArray(); + c[i]=c2; + break; + } + } + } + + while (i-->0) + { + if(c[i]<=127) + c[i] = lowercases[c[i]]; + } + + return c==null?s:new String(c); + } + + + /* ------------------------------------------------------------ */ + public static boolean startsWithIgnoreCase(String s,String w) + { + if (w==null) + return true; + + if (s==null || s.length()<w.length()) + return false; + + for (int i=0;i<w.length();i++) + { + char c1=s.charAt(i); + char c2=w.charAt(i); + if (c1!=c2) + { + if (c1<=127) + c1=lowercases[c1]; + if (c2<=127) + c2=lowercases[c2]; + if (c1!=c2) + return false; + } + } + return true; + } + + /* ------------------------------------------------------------ */ + public static boolean endsWithIgnoreCase(String s,String w) + { + if (w==null) + return true; + + if (s==null) + return false; + + int sl=s.length(); + int wl=w.length(); + + if (sl<wl) + return false; + + for (int i=wl;i-->0;) + { + char c1=s.charAt(--sl); + char c2=w.charAt(i); + if (c1!=c2) + { + if (c1<=127) + c1=lowercases[c1]; + if (c2<=127) + c2=lowercases[c2]; + if (c1!=c2) + return false; + } + } + return true; + } + + /* ------------------------------------------------------------ */ + /** + * returns the next index of a character from the chars string + */ + public static int indexFrom(String s,String chars) + { + for (int i=0;i<s.length();i++) + if (chars.indexOf(s.charAt(i))>=0) + return i; + return -1; + } + + /* ------------------------------------------------------------ */ + /** + * replace substrings within string. + */ + public static String replace(String s, String sub, String with) + { + int c=0; + int i=s.indexOf(sub,c); + if (i == -1) + return s; + + StringBuilder buf = new StringBuilder(s.length()+with.length()); + + do + { + buf.append(s.substring(c,i)); + buf.append(with); + c=i+sub.length(); + } while ((i=s.indexOf(sub,c))!=-1); + + if (c<s.length()) + buf.append(s.substring(c,s.length())); + + return buf.toString(); + + } + + + /* ------------------------------------------------------------ */ + /** Remove single or double quotes. + */ + public static String unquote(String s) + { + return QuotedStringTokenizer.unquote(s); + } + + + /* ------------------------------------------------------------ */ + /** Append substring to StringBuilder + * @param buf StringBuilder to append to + * @param s String to append from + * @param offset The offset of the substring + * @param length The length of the substring + */ + public static void append(StringBuilder buf, + String s, + int offset, + int length) + { + synchronized(buf) + { + int end=offset+length; + for (int i=offset; i<end;i++) + { + if (i>=s.length()) + break; + buf.append(s.charAt(i)); + } + } + } + + + /* ------------------------------------------------------------ */ + /** + * append hex digit + * + */ + public static void append(StringBuilder buf,byte b,int base) + { + int bi=0xff&b; + int c='0'+(bi/base)%base; + if (c>'9') + c= 'a'+(c-'0'-10); + buf.append((char)c); + c='0'+bi%base; + if (c>'9') + c= 'a'+(c-'0'-10); + buf.append((char)c); + } + + /* ------------------------------------------------------------ */ + public static void append2digits(StringBuffer buf,int i) + { + if (i<100) + { + buf.append((char)(i/10+'0')); + buf.append((char)(i%10+'0')); + } + } + + /* ------------------------------------------------------------ */ + public static void append2digits(StringBuilder buf,int i) + { + if (i<100) + { + buf.append((char)(i/10+'0')); + buf.append((char)(i%10+'0')); + } + } + + /* ------------------------------------------------------------ */ + /** Return a non null string. + * @param s String + * @return The string passed in or empty string if it is null. + */ + public static String nonNull(String s) + { + if (s==null) + return ""; + return s; + } + + /* ------------------------------------------------------------ */ + public static boolean equals(String s,char[] buf, int offset, int length) + { + if (s.length()!=length) + return false; + for (int i=0;i<length;i++) + if (buf[offset+i]!=s.charAt(i)) + return false; + return true; + } + + /* ------------------------------------------------------------ */ + public static String toUTF8String(byte[] b,int offset,int length) + { + try + { + return new String(b,offset,length,__UTF8); + } + catch (UnsupportedEncodingException e) + { + throw new IllegalArgumentException(e); + } + } + + /* ------------------------------------------------------------ */ + public static String toString(byte[] b,int offset,int length,String charset) + { + try + { + return new String(b,offset,length,charset); + } + catch (UnsupportedEncodingException e) + { + throw new IllegalArgumentException(e); + } + } + + + /* ------------------------------------------------------------ */ + public static boolean isUTF8(String charset) + { + return __UTF8.equalsIgnoreCase(charset)||__UTF8Alt.equalsIgnoreCase(charset); + } + + + /* ------------------------------------------------------------ */ + public static String printable(String name) + { + if (name==null) + return null; + StringBuilder buf = new StringBuilder(name.length()); + for (int i=0;i<name.length();i++) + { + char c=name.charAt(i); + if (!Character.isISOControl(c)) + buf.append(c); + } + return buf.toString(); + } + + /* ------------------------------------------------------------ */ + public static String printable(byte[] b) + { + StringBuilder buf = new StringBuilder(); + for (int i=0;i<b.length;i++) + { + char c=(char)b[i]; + if (Character.isWhitespace(c)|| c>' ' && c<0x7f) + buf.append(c); + else + { + buf.append("0x"); + TypeUtil.toHex(b[i],buf); + } + } + return buf.toString(); + } + + public static byte[] getBytes(String s) + { + try + { + return s.getBytes(__ISO_8859_1); + } + catch(Exception e) + { + LOG.warn(e); + return s.getBytes(); + } + } + + public static byte[] getBytes(String s,String charset) + { + try + { + return s.getBytes(charset); + } + catch(Exception e) + { + LOG.warn(e); + return s.getBytes(); + } + } + + + + /** + * Converts a binary SID to a string SID + * + * http://en.wikipedia.org/wiki/Security_Identifier + * + * S-1-IdentifierAuthority-SubAuthority1-SubAuthority2-...-SubAuthorityn + */ + public static String sidBytesToString(byte[] sidBytes) + { + StringBuilder sidString = new StringBuilder(); + + // Identify this as a SID + sidString.append("S-"); + + // Add SID revision level (expect 1 but may change someday) + sidString.append(Byte.toString(sidBytes[0])).append('-'); + + StringBuilder tmpBuilder = new StringBuilder(); + + // crunch the six bytes of issuing authority value + for (int i = 2; i <= 7; ++i) + { + tmpBuilder.append(Integer.toHexString(sidBytes[i] & 0xFF)); + } + + sidString.append(Long.parseLong(tmpBuilder.toString(), 16)); // '-' is in the subauth loop + + // the number of subAuthorities we need to attach + int subAuthorityCount = sidBytes[1]; + + // attach each of the subAuthorities + for (int i = 0; i < subAuthorityCount; ++i) + { + int offset = i * 4; + tmpBuilder.setLength(0); + // these need to be zero padded hex and little endian + tmpBuilder.append(String.format("%02X%02X%02X%02X", + (sidBytes[11 + offset] & 0xFF), + (sidBytes[10 + offset] & 0xFF), + (sidBytes[9 + offset] & 0xFF), + (sidBytes[8 + offset] & 0xFF))); + sidString.append('-').append(Long.parseLong(tmpBuilder.toString(), 16)); + } + + return sidString.toString(); + } + + /** + * Converts a string SID to a binary SID + * + * http://en.wikipedia.org/wiki/Security_Identifier + * + * S-1-IdentifierAuthority-SubAuthority1-SubAuthority2-...-SubAuthorityn + */ + public static byte[] sidStringToBytes( String sidString ) + { + String[] sidTokens = sidString.split("-"); + + int subAuthorityCount = sidTokens.length - 3; // S-Rev-IdAuth- + + int byteCount = 0; + byte[] sidBytes = new byte[1 + 1 + 6 + (4 * subAuthorityCount)]; + + // the revision byte + sidBytes[byteCount++] = (byte)Integer.parseInt(sidTokens[1]); + + // the # of sub authorities byte + sidBytes[byteCount++] = (byte)subAuthorityCount; + + // the certAuthority + String hexStr = Long.toHexString(Long.parseLong(sidTokens[2])); + + while( hexStr.length() < 12) // pad to 12 characters + { + hexStr = "0" + hexStr; + } + + // place the certAuthority 6 bytes + for ( int i = 0 ; i < hexStr.length(); i = i + 2) + { + sidBytes[byteCount++] = (byte)Integer.parseInt(hexStr.substring(i, i + 2),16); + } + + + for ( int i = 3; i < sidTokens.length ; ++i) + { + hexStr = Long.toHexString(Long.parseLong(sidTokens[i])); + + while( hexStr.length() < 8) // pad to 8 characters + { + hexStr = "0" + hexStr; + } + + // place the inverted sub authorities, 4 bytes each + for ( int j = hexStr.length(); j > 0; j = j - 2) + { + sidBytes[byteCount++] = (byte)Integer.parseInt(hexStr.substring(j-2, j),16); + } + } + + return sidBytes; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/TypeUtil.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,593 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + + +/* ------------------------------------------------------------ */ +/** + * TYPE Utilities. + * Provides various static utiltiy methods for manipulating types and their + * string representations. + * + * @since Jetty 4.1 + */ +public class TypeUtil +{ + private static final Logger LOG = Log.getLogger(TypeUtil.class); + public static int CR = '\015'; + public static int LF = '\012'; + + /* ------------------------------------------------------------ */ + private static final HashMap<String, Class<?>> name2Class=new HashMap<String, Class<?>>(); + static + { + name2Class.put("boolean",java.lang.Boolean.TYPE); + name2Class.put("byte",java.lang.Byte.TYPE); + name2Class.put("char",java.lang.Character.TYPE); + name2Class.put("double",java.lang.Double.TYPE); + name2Class.put("float",java.lang.Float.TYPE); + name2Class.put("int",java.lang.Integer.TYPE); + name2Class.put("long",java.lang.Long.TYPE); + name2Class.put("short",java.lang.Short.TYPE); + name2Class.put("void",java.lang.Void.TYPE); + + name2Class.put("java.lang.Boolean.TYPE",java.lang.Boolean.TYPE); + name2Class.put("java.lang.Byte.TYPE",java.lang.Byte.TYPE); + name2Class.put("java.lang.Character.TYPE",java.lang.Character.TYPE); + name2Class.put("java.lang.Double.TYPE",java.lang.Double.TYPE); + name2Class.put("java.lang.Float.TYPE",java.lang.Float.TYPE); + name2Class.put("java.lang.Integer.TYPE",java.lang.Integer.TYPE); + name2Class.put("java.lang.Long.TYPE",java.lang.Long.TYPE); + name2Class.put("java.lang.Short.TYPE",java.lang.Short.TYPE); + name2Class.put("java.lang.Void.TYPE",java.lang.Void.TYPE); + + name2Class.put("java.lang.Boolean",java.lang.Boolean.class); + name2Class.put("java.lang.Byte",java.lang.Byte.class); + name2Class.put("java.lang.Character",java.lang.Character.class); + name2Class.put("java.lang.Double",java.lang.Double.class); + name2Class.put("java.lang.Float",java.lang.Float.class); + name2Class.put("java.lang.Integer",java.lang.Integer.class); + name2Class.put("java.lang.Long",java.lang.Long.class); + name2Class.put("java.lang.Short",java.lang.Short.class); + + name2Class.put("Boolean",java.lang.Boolean.class); + name2Class.put("Byte",java.lang.Byte.class); + name2Class.put("Character",java.lang.Character.class); + name2Class.put("Double",java.lang.Double.class); + name2Class.put("Float",java.lang.Float.class); + name2Class.put("Integer",java.lang.Integer.class); + name2Class.put("Long",java.lang.Long.class); + name2Class.put("Short",java.lang.Short.class); + + name2Class.put(null,java.lang.Void.TYPE); + name2Class.put("string",java.lang.String.class); + name2Class.put("String",java.lang.String.class); + name2Class.put("java.lang.String",java.lang.String.class); + } + + /* ------------------------------------------------------------ */ + private static final HashMap<Class<?>, String> class2Name=new HashMap<Class<?>, String>(); + static + { + class2Name.put(java.lang.Boolean.TYPE,"boolean"); + class2Name.put(java.lang.Byte.TYPE,"byte"); + class2Name.put(java.lang.Character.TYPE,"char"); + class2Name.put(java.lang.Double.TYPE,"double"); + class2Name.put(java.lang.Float.TYPE,"float"); + class2Name.put(java.lang.Integer.TYPE,"int"); + class2Name.put(java.lang.Long.TYPE,"long"); + class2Name.put(java.lang.Short.TYPE,"short"); + class2Name.put(java.lang.Void.TYPE,"void"); + + class2Name.put(java.lang.Boolean.class,"java.lang.Boolean"); + class2Name.put(java.lang.Byte.class,"java.lang.Byte"); + class2Name.put(java.lang.Character.class,"java.lang.Character"); + class2Name.put(java.lang.Double.class,"java.lang.Double"); + class2Name.put(java.lang.Float.class,"java.lang.Float"); + class2Name.put(java.lang.Integer.class,"java.lang.Integer"); + class2Name.put(java.lang.Long.class,"java.lang.Long"); + class2Name.put(java.lang.Short.class,"java.lang.Short"); + + class2Name.put(null,"void"); + class2Name.put(java.lang.String.class,"java.lang.String"); + } + + /* ------------------------------------------------------------ */ + private static final HashMap<Class<?>, Method> class2Value=new HashMap<Class<?>, Method>(); + static + { + try + { + Class<?>[] s ={java.lang.String.class}; + + class2Value.put(java.lang.Boolean.TYPE, + java.lang.Boolean.class.getMethod("valueOf",s)); + class2Value.put(java.lang.Byte.TYPE, + java.lang.Byte.class.getMethod("valueOf",s)); + class2Value.put(java.lang.Double.TYPE, + java.lang.Double.class.getMethod("valueOf",s)); + class2Value.put(java.lang.Float.TYPE, + java.lang.Float.class.getMethod("valueOf",s)); + class2Value.put(java.lang.Integer.TYPE, + java.lang.Integer.class.getMethod("valueOf",s)); + class2Value.put(java.lang.Long.TYPE, + java.lang.Long.class.getMethod("valueOf",s)); + class2Value.put(java.lang.Short.TYPE, + java.lang.Short.class.getMethod("valueOf",s)); + + class2Value.put(java.lang.Boolean.class, + java.lang.Boolean.class.getMethod("valueOf",s)); + class2Value.put(java.lang.Byte.class, + java.lang.Byte.class.getMethod("valueOf",s)); + class2Value.put(java.lang.Double.class, + java.lang.Double.class.getMethod("valueOf",s)); + class2Value.put(java.lang.Float.class, + java.lang.Float.class.getMethod("valueOf",s)); + class2Value.put(java.lang.Integer.class, + java.lang.Integer.class.getMethod("valueOf",s)); + class2Value.put(java.lang.Long.class, + java.lang.Long.class.getMethod("valueOf",s)); + class2Value.put(java.lang.Short.class, + java.lang.Short.class.getMethod("valueOf",s)); + } + catch(Exception e) + { + throw new Error(e); + } + } + + /* ------------------------------------------------------------ */ + /** Array to List. + * <p> + * Works like {@link Arrays#asList(Object...)}, but handles null arrays. + * @return a list backed by the array. + */ + public static <T> List<T> asList(T[] a) + { + if (a==null) + return Collections.emptyList(); + return Arrays.asList(a); + } + + /* ------------------------------------------------------------ */ + /** Class from a canonical name for a type. + * @param name A class or type name. + * @return A class , which may be a primitive TYPE field.. + */ + public static Class<?> fromName(String name) + { + return name2Class.get(name); + } + + /* ------------------------------------------------------------ */ + /** Canonical name for a type. + * @param type A class , which may be a primitive TYPE field. + * @return Canonical name. + */ + public static String toName(Class<?> type) + { + return class2Name.get(type); + } + + /* ------------------------------------------------------------ */ + /** Convert String value to instance. + * @param type The class of the instance, which may be a primitive TYPE field. + * @param value The value as a string. + * @return The value as an Object. + */ + public static Object valueOf(Class<?> type, String value) + { + try + { + if (type.equals(java.lang.String.class)) + return value; + + Method m = class2Value.get(type); + if (m!=null) + return m.invoke(null, value); + + if (type.equals(java.lang.Character.TYPE) || + type.equals(java.lang.Character.class)) + return new Character(value.charAt(0)); + + Constructor<?> c = type.getConstructor(java.lang.String.class); + return c.newInstance(value); + } + catch(NoSuchMethodException e) + { + // LogSupport.ignore(log,e); + } + catch(IllegalAccessException e) + { + // LogSupport.ignore(log,e); + } + catch(InstantiationException e) + { + // LogSupport.ignore(log,e); + } + catch(InvocationTargetException e) + { + if (e.getTargetException() instanceof Error) + throw (Error)(e.getTargetException()); + // LogSupport.ignore(log,e); + } + return null; + } + + /* ------------------------------------------------------------ */ + /** Convert String value to instance. + * @param type classname or type (eg int) + * @param value The value as a string. + * @return The value as an Object. + */ + public static Object valueOf(String type, String value) + { + return valueOf(fromName(type),value); + } + + /* ------------------------------------------------------------ */ + /** Parse an int from a substring. + * Negative numbers are not handled. + * @param s String + * @param offset Offset within string + * @param length Length of integer or -1 for remainder of string + * @param base base of the integer + * @return the parsed integer + * @throws NumberFormatException if the string cannot be parsed + */ + public static int parseInt(String s, int offset, int length, int base) + throws NumberFormatException + { + int value=0; + + if (length<0) + length=s.length()-offset; + + for (int i=0;i<length;i++) + { + char c=s.charAt(offset+i); + + int digit=convertHexDigit((int)c); + if (digit<0 || digit>=base) + throw new NumberFormatException(s.substring(offset,offset+length)); + value=value*base+digit; + } + return value; + } + + /* ------------------------------------------------------------ */ + /** Parse an int from a byte array of ascii characters. + * Negative numbers are not handled. + * @param b byte array + * @param offset Offset within string + * @param length Length of integer or -1 for remainder of string + * @param base base of the integer + * @return the parsed integer + * @throws NumberFormatException if the array cannot be parsed into an integer + */ + public static int parseInt(byte[] b, int offset, int length, int base) + throws NumberFormatException + { + int value=0; + + if (length<0) + length=b.length-offset; + + for (int i=0;i<length;i++) + { + char c=(char)(0xff&b[offset+i]); + + int digit=c-'0'; + if (digit<0 || digit>=base || digit>=10) + { + digit=10+c-'A'; + if (digit<10 || digit>=base) + digit=10+c-'a'; + } + if (digit<0 || digit>=base) + throw new NumberFormatException(new String(b,offset,length)); + value=value*base+digit; + } + return value; + } + + /* ------------------------------------------------------------ */ + public static byte[] parseBytes(String s, int base) + { + byte[] bytes=new byte[s.length()/2]; + for (int i=0;i<s.length();i+=2) + bytes[i/2]=(byte)TypeUtil.parseInt(s,i,2,base); + return bytes; + } + + /* ------------------------------------------------------------ */ + public static String toString(byte[] bytes, int base) + { + StringBuilder buf = new StringBuilder(); + for (byte b : bytes) + { + int bi=0xff&b; + int c='0'+(bi/base)%base; + if (c>'9') + c= 'a'+(c-'0'-10); + buf.append((char)c); + c='0'+bi%base; + if (c>'9') + c= 'a'+(c-'0'-10); + buf.append((char)c); + } + return buf.toString(); + } + + /* ------------------------------------------------------------ */ + /** + * @param c An ASCII encoded character 0-9 a-f A-F + * @return The byte value of the character 0-16. + */ + public static byte convertHexDigit( byte c ) + { + byte b = (byte)((c & 0x1f) + ((c >> 6) * 0x19) - 0x10); + if (b<0 || b>15) + throw new IllegalArgumentException("!hex "+c); + return b; + } + + /* ------------------------------------------------------------ */ + /** + * @param c An ASCII encoded character 0-9 a-f A-F + * @return The byte value of the character 0-16. + */ + public static int convertHexDigit( int c ) + { + int d= ((c & 0x1f) + ((c >> 6) * 0x19) - 0x10); + if (d<0 || d>15) + throw new NumberFormatException("!hex "+c); + return d; + } + + /* ------------------------------------------------------------ */ + public static void toHex(byte b,Appendable buf) + { + try + { + int d=0xf&((0xF0&b)>>4); + buf.append((char)((d>9?('A'-10):'0')+d)); + d=0xf&b; + buf.append((char)((d>9?('A'-10):'0')+d)); + } + catch(IOException e) + { + throw new RuntimeException(e); + } + } + + /* ------------------------------------------------------------ */ + public static void toHex(int value,Appendable buf) throws IOException + { + int d=0xf&((0xF0000000&value)>>28); + buf.append((char)((d>9?('A'-10):'0')+d)); + d=0xf&((0x0F000000&value)>>24); + buf.append((char)((d>9?('A'-10):'0')+d)); + d=0xf&((0x00F00000&value)>>20); + buf.append((char)((d>9?('A'-10):'0')+d)); + d=0xf&((0x000F0000&value)>>16); + buf.append((char)((d>9?('A'-10):'0')+d)); + d=0xf&((0x0000F000&value)>>12); + buf.append((char)((d>9?('A'-10):'0')+d)); + d=0xf&((0x00000F00&value)>>8); + buf.append((char)((d>9?('A'-10):'0')+d)); + d=0xf&((0x000000F0&value)>>4); + buf.append((char)((d>9?('A'-10):'0')+d)); + d=0xf&value; + buf.append((char)((d>9?('A'-10):'0')+d)); + + Integer.toString(0,36); + } + + + /* ------------------------------------------------------------ */ + public static void toHex(long value,Appendable buf) throws IOException + { + toHex((int)(value>>32),buf); + toHex((int)value,buf); + } + + /* ------------------------------------------------------------ */ + public static String toHexString(byte b) + { + return toHexString(new byte[]{b}, 0, 1); + } + + /* ------------------------------------------------------------ */ + public static String toHexString(byte[] b) + { + return toHexString(b, 0, b.length); + } + + /* ------------------------------------------------------------ */ + public static String toHexString(byte[] b,int offset,int length) + { + StringBuilder buf = new StringBuilder(); + for (int i=offset;i<offset+length;i++) + { + int bi=0xff&b[i]; + int c='0'+(bi/16)%16; + if (c>'9') + c= 'A'+(c-'0'-10); + buf.append((char)c); + c='0'+bi%16; + if (c>'9') + c= 'a'+(c-'0'-10); + buf.append((char)c); + } + return buf.toString(); + } + + /* ------------------------------------------------------------ */ + public static byte[] fromHexString(String s) + { + if (s.length()%2!=0) + throw new IllegalArgumentException(s); + byte[] array = new byte[s.length()/2]; + for (int i=0;i<array.length;i++) + { + int b = Integer.parseInt(s.substring(i*2,i*2+2),16); + array[i]=(byte)(0xff&b); + } + return array; + } + + + public static void dump(Class<?> c) + { + System.err.println("Dump: "+c); + dump(c.getClassLoader()); + } + + public static void dump(ClassLoader cl) + { + System.err.println("Dump Loaders:"); + while(cl!=null) + { + System.err.println(" loader "+cl); + cl = cl.getParent(); + } + } + + + /* ------------------------------------------------------------ */ + /** + * @deprecated + */ + public static byte[] readLine(InputStream in) throws IOException + { + byte[] buf = new byte[256]; + + int i=0; + int loops=0; + int ch=0; + + while (true) + { + ch=in.read(); + if (ch<0) + break; + loops++; + + // skip a leading LF's + if (loops==1 && ch==LF) + continue; + + if (ch==CR || ch==LF) + break; + + if (i>=buf.length) + { + byte[] old_buf=buf; + buf=new byte[old_buf.length+256]; + System.arraycopy(old_buf, 0, buf, 0, old_buf.length); + } + buf[i++]=(byte)ch; + } + + if (ch==-1 && i==0) + return null; + + // skip a trailing LF if it exists + if (ch==CR && in.available()>=1 && in.markSupported()) + { + in.mark(1); + ch=in.read(); + if (ch!=LF) + in.reset(); + } + + byte[] old_buf=buf; + buf=new byte[i]; + System.arraycopy(old_buf, 0, buf, 0, i); + + return buf; + } + + public static URL jarFor(String className) + { + try + { + className=className.replace('.','/')+".class"; + // hack to discover jstl libraries + URL url = Loader.getResource(null,className,false); + String s=url.toString(); + if (s.startsWith("jar:file:")) + return new URL(s.substring(4,s.indexOf("!/"))); + } + catch(Exception e) + { + LOG.ignore(e); + } + return null; + } + + public static Object call(Class<?> oClass, String method, Object obj, Object[] arg) + throws InvocationTargetException, NoSuchMethodException + { + // Lets just try all methods for now + Method[] methods = oClass.getMethods(); + for (int c = 0; methods != null && c < methods.length; c++) + { + if (!methods[c].getName().equals(method)) + continue; + if (methods[c].getParameterTypes().length != arg.length) + continue; + if (Modifier.isStatic(methods[c].getModifiers()) != (obj == null)) + continue; + if ((obj == null) && methods[c].getDeclaringClass() != oClass) + continue; + + try + { + return methods[c].invoke(obj,arg); + } + catch (IllegalAccessException e) + { + LOG.ignore(e); + } + catch (IllegalArgumentException e) + { + LOG.ignore(e); + } + } + + throw new NoSuchMethodException(method); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/URIUtil.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,692 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLEncoder; + +import org.eclipse.jetty.util.log.Log; + + + +/* ------------------------------------------------------------ */ +/** URI Holder. + * This class assists with the decoding and encoding or HTTP URI's. + * It differs from the java.net.URL class as it does not provide + * communications ability, but it does assist with query string + * formatting. + * <P>UTF-8 encoding is used by default for % encoded characters. This + * may be overridden with the org.eclipse.jetty.util.URI.charset system property. + * @see UrlEncoded + * + */ +public class URIUtil + implements Cloneable +{ + public static final String SLASH="/"; + public static final String HTTP="http"; + public static final String HTTP_COLON="http:"; + public static final String HTTPS="https"; + public static final String HTTPS_COLON="https:"; + + // Use UTF-8 as per http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars + public static final String __CHARSET=System.getProperty("org.eclipse.jetty.util.URI.charset",StringUtil.__UTF8); + + private URIUtil() + {} + + /* ------------------------------------------------------------ */ + /** Encode a URI path. + * This is the same encoding offered by URLEncoder, except that + * the '/' character is not encoded. + * @param path The path the encode + * @return The encoded path + */ + public static String encodePath(String path) + { + if (path==null || path.length()==0) + return path; + + StringBuilder buf = encodePath(null,path); + return buf==null?path:buf.toString(); + } + + /* ------------------------------------------------------------ */ + /** Encode a URI path. + * @param path The path the encode + * @param buf StringBuilder to encode path into (or null) + * @return The StringBuilder or null if no substitutions required. + */ + public static StringBuilder encodePath(StringBuilder buf, String path) + { + byte[] bytes=null; + if (buf==null) + { + loop: + for (int i=0;i<path.length();i++) + { + char c=path.charAt(i); + switch(c) + { + case '%': + case '?': + case ';': + case '#': + case '\'': + case '"': + case '<': + case '>': + case ' ': + buf=new StringBuilder(path.length()*2); + break loop; + default: + if (c>127) + { + try + { + bytes=path.getBytes(URIUtil.__CHARSET); + } + catch (UnsupportedEncodingException e) + { + throw new IllegalStateException(e); + } + buf=new StringBuilder(path.length()*2); + break loop; + } + + } + } + if (buf==null) + return null; + } + + synchronized(buf) + { + if (bytes!=null) + { + for (int i=0;i<bytes.length;i++) + { + byte c=bytes[i]; + switch(c) + { + case '%': + buf.append("%25"); + continue; + case '?': + buf.append("%3F"); + continue; + case ';': + buf.append("%3B"); + continue; + case '#': + buf.append("%23"); + continue; + case '"': + buf.append("%22"); + continue; + case '\'': + buf.append("%27"); + continue; + case '<': + buf.append("%3C"); + continue; + case '>': + buf.append("%3E"); + continue; + case ' ': + buf.append("%20"); + continue; + default: + if (c<0) + { + buf.append('%'); + TypeUtil.toHex(c,buf); + } + else + buf.append((char)c); + continue; + } + } + + } + else + { + for (int i=0;i<path.length();i++) + { + char c=path.charAt(i); + switch(c) + { + case '%': + buf.append("%25"); + continue; + case '?': + buf.append("%3F"); + continue; + case ';': + buf.append("%3B"); + continue; + case '#': + buf.append("%23"); + continue; + case '"': + buf.append("%22"); + continue; + case '\'': + buf.append("%27"); + continue; + case '<': + buf.append("%3C"); + continue; + case '>': + buf.append("%3E"); + continue; + case ' ': + buf.append("%20"); + continue; + default: + buf.append(c); + continue; + } + } + } + } + + return buf; + } + + /* ------------------------------------------------------------ */ + /** Encode a URI path. + * @param path The path the encode + * @param buf StringBuilder to encode path into (or null) + * @param encode String of characters to encode. % is always encoded. + * @return The StringBuilder or null if no substitutions required. + */ + public static StringBuilder encodeString(StringBuilder buf, + String path, + String encode) + { + if (buf==null) + { + loop: + for (int i=0;i<path.length();i++) + { + char c=path.charAt(i); + if (c=='%' || encode.indexOf(c)>=0) + { + buf=new StringBuilder(path.length()<<1); + break loop; + } + } + if (buf==null) + return null; + } + + synchronized(buf) + { + for (int i=0;i<path.length();i++) + { + char c=path.charAt(i); + if (c=='%' || encode.indexOf(c)>=0) + { + buf.append('%'); + StringUtil.append(buf,(byte)(0xff&c),16); + } + else + buf.append(c); + } + } + + return buf; + } + + /* ------------------------------------------------------------ */ + /* Decode a URI path and strip parameters + * @param path The path the encode + * @param buf StringBuilder to encode path into + */ + public static String decodePath(String path) + { + if (path==null) + return null; + // Array to hold all converted characters + char[] chars=null; + int n=0; + // Array to hold a sequence of %encodings + byte[] bytes=null; + int b=0; + + int len=path.length(); + + for (int i=0;i<len;i++) + { + char c = path.charAt(i); + + if (c=='%' && (i+2)<len) + { + if (chars==null) + { + chars=new char[len]; + bytes=new byte[len]; + path.getChars(0,i,chars,0); + } + bytes[b++]=(byte)(0xff&TypeUtil.parseInt(path,i+1,2,16)); + i+=2; + continue; + } + else if (c==';') + { + if (chars==null) + { + chars=new char[len]; + path.getChars(0,i,chars,0); + n=i; + } + break; + } + else if (bytes==null) + { + n++; + continue; + } + + // Do we have some bytes to convert? + if (b>0) + { + // convert series of bytes and add to chars + String s; + try + { + s=new String(bytes,0,b,__CHARSET); + } + catch (UnsupportedEncodingException e) + { + s=new String(bytes,0,b); + } + s.getChars(0,s.length(),chars,n); + n+=s.length(); + b=0; + } + + chars[n++]=c; + } + + if (chars==null) + return path; + + // if we have a remaining sequence of bytes + if (b>0) + { + // convert series of bytes and add to chars + String s; + try + { + s=new String(bytes,0,b,__CHARSET); + } + catch (UnsupportedEncodingException e) + { + s=new String(bytes,0,b); + } + s.getChars(0,s.length(),chars,n); + n+=s.length(); + } + + return new String(chars,0,n); + } + + /* ------------------------------------------------------------ */ + /* Decode a URI path and strip parameters. + * @param path The path the encode + * @param buf StringBuilder to encode path into + */ + public static String decodePath(byte[] buf, int offset, int length) + { + byte[] bytes=null; + int n=0; + + for (int i=0;i<length;i++) + { + byte b = buf[i + offset]; + + if (b=='%' && (i+2)<length) + { + b=(byte)(0xff&TypeUtil.parseInt(buf,i+offset+1,2,16)); + i+=2; + } + else if (b==';') + { + length=i; + break; + } + else if (bytes==null) + { + n++; + continue; + } + + if (bytes==null) + { + bytes=new byte[length]; + for (int j=0;j<n;j++) + bytes[j]=buf[j + offset]; + } + + bytes[n++]=b; + } + + if (bytes==null) + return StringUtil.toString(buf,offset,length,__CHARSET); + return StringUtil.toString(bytes,0,n,__CHARSET); + } + + + /* ------------------------------------------------------------ */ + /** Add two URI path segments. + * Handles null and empty paths, path and query params (eg ?a=b or + * ;JSESSIONID=xxx) and avoids duplicate '/' + * @param p1 URI path segment (should be encoded) + * @param p2 URI path segment (should be encoded) + * @return Legally combined path segments. + */ + public static String addPaths(String p1, String p2) + { + if (p1==null || p1.length()==0) + { + if (p1!=null && p2==null) + return p1; + return p2; + } + if (p2==null || p2.length()==0) + return p1; + + int split=p1.indexOf(';'); + if (split<0) + split=p1.indexOf('?'); + if (split==0) + return p2+p1; + if (split<0) + split=p1.length(); + + StringBuilder buf = new StringBuilder(p1.length()+p2.length()+2); + buf.append(p1); + + if (buf.charAt(split-1)=='/') + { + if (p2.startsWith(URIUtil.SLASH)) + { + buf.deleteCharAt(split-1); + buf.insert(split-1,p2); + } + else + buf.insert(split,p2); + } + else + { + if (p2.startsWith(URIUtil.SLASH)) + buf.insert(split,p2); + else + { + buf.insert(split,'/'); + buf.insert(split+1,p2); + } + } + + return buf.toString(); + } + + /* ------------------------------------------------------------ */ + /** Return the parent Path. + * Treat a URI like a directory path and return the parent directory. + */ + public static String parentPath(String p) + { + if (p==null || URIUtil.SLASH.equals(p)) + return null; + int slash=p.lastIndexOf('/',p.length()-2); + if (slash>=0) + return p.substring(0,slash+1); + return null; + } + + /* ------------------------------------------------------------ */ + /** Convert a path to a cananonical form. + * All instances of "." and ".." are factored out. Null is returned + * if the path tries to .. above its root. + * @param path + * @return path or null. + */ + public static String canonicalPath(String path) + { + if (path==null || path.length()==0) + return path; + + int end=path.length(); + int start = path.lastIndexOf('/', end); + + search: + while (end>0) + { + switch(end-start) + { + case 2: // possible single dot + if (path.charAt(start+1)!='.') + break; + break search; + case 3: // possible double dot + if (path.charAt(start+1)!='.' || path.charAt(start+2)!='.') + break; + break search; + } + + end=start; + start=path.lastIndexOf('/',end-1); + } + + // If we have checked the entire string + if (start>=end) + return path; + + StringBuilder buf = new StringBuilder(path); + int delStart=-1; + int delEnd=-1; + int skip=0; + + while (end>0) + { + switch(end-start) + { + case 2: // possible single dot + if (buf.charAt(start+1)!='.') + { + if (skip>0 && --skip==0) + { + delStart=start>=0?start:0; + if(delStart>0 && delEnd==buf.length() && buf.charAt(delEnd-1)=='.') + delStart++; + } + break; + } + + if(start<0 && buf.length()>2 && buf.charAt(1)=='/' && buf.charAt(2)=='/') + break; + + if(delEnd<0) + delEnd=end; + delStart=start; + if (delStart<0 || delStart==0&&buf.charAt(delStart)=='/') + { + delStart++; + if (delEnd<buf.length() && buf.charAt(delEnd)=='/') + delEnd++; + break; + } + if (end==buf.length()) + delStart++; + + end=start--; + while (start>=0 && buf.charAt(start)!='/') + start--; + continue; + + case 3: // possible double dot + if (buf.charAt(start+1)!='.' || buf.charAt(start+2)!='.') + { + if (skip>0 && --skip==0) + { delStart=start>=0?start:0; + if(delStart>0 && delEnd==buf.length() && buf.charAt(delEnd-1)=='.') + delStart++; + } + break; + } + + delStart=start; + if (delEnd<0) + delEnd=end; + + skip++; + end=start--; + while (start>=0 && buf.charAt(start)!='/') + start--; + continue; + + default: + if (skip>0 && --skip==0) + { + delStart=start>=0?start:0; + if(delEnd==buf.length() && buf.charAt(delEnd-1)=='.') + delStart++; + } + } + + // Do the delete + if (skip<=0 && delStart>=0 && delEnd>=delStart) + { + buf.delete(delStart,delEnd); + delStart=delEnd=-1; + if (skip>0) + delEnd=end; + } + + end=start--; + while (start>=0 && buf.charAt(start)!='/') + start--; + } + + // Too many .. + if (skip>0) + return null; + + // Do the delete + if (delEnd>=0) + buf.delete(delStart,delEnd); + + return buf.toString(); + } + + /* ------------------------------------------------------------ */ + /** Convert a path to a compact form. + * All instances of "//" and "///" etc. are factored out to single "/" + * @param path + * @return path + */ + public static String compactPath(String path) + { + if (path==null || path.length()==0) + return path; + + int state=0; + int end=path.length(); + int i=0; + + loop: + while (i<end) + { + char c=path.charAt(i); + switch(c) + { + case '?': + return path; + case '/': + state++; + if (state==2) + break loop; + break; + default: + state=0; + } + i++; + } + + if (state<2) + return path; + + StringBuffer buf = new StringBuffer(path.length()); + buf.append(path,0,i); + + loop2: + while (i<end) + { + char c=path.charAt(i); + switch(c) + { + case '?': + buf.append(path,i,end); + break loop2; + case '/': + if (state++==0) + buf.append(c); + break; + default: + state=0; + buf.append(c); + } + i++; + } + + return buf.toString(); + } + + /* ------------------------------------------------------------ */ + /** + * @param uri URI + * @return True if the uri has a scheme + */ + public static boolean hasScheme(String uri) + { + for (int i=0;i<uri.length();i++) + { + char c=uri.charAt(i); + if (c==':') + return true; + if (!(c>='a'&&c<='z' || + c>='A'&&c<='Z' || + (i>0 &&(c>='0'&&c<='9' || + c=='.' || + c=='+' || + c=='-')) + )) + break; + } + return false; + } + +} + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/UrlEncoded.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,1034 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import static org.eclipse.jetty.util.TypeUtil.convertHexDigit; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.util.Iterator; +import java.util.Map; + +import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/* ------------------------------------------------------------ */ +/** Handles coding of MIME "x-www-form-urlencoded". + * <p> + * This class handles the encoding and decoding for either + * the query string of a URL or the _content of a POST HTTP request. + * + * <h4>Notes</h4> + * The UTF-8 charset is assumed, unless otherwise defined by either + * passing a parameter or setting the "org.eclipse.jetty.util.UrlEncoding.charset" + * System property. + * <p> + * The hashtable either contains String single values, vectors + * of String or arrays of Strings. + * <p> + * This class is only partially synchronised. In particular, simple + * get operations are not protected from concurrent updates. + * + * @see java.net.URLEncoder + */ +public class UrlEncoded extends MultiMap implements Cloneable +{ + private static final Logger LOG = Log.getLogger(UrlEncoded.class); + + public static final String ENCODING = System.getProperty("org.eclipse.jetty.util.UrlEncoding.charset",StringUtil.__UTF8); + + /* ----------------------------------------------------------------- */ + public UrlEncoded(UrlEncoded url) + { + super(url); + } + + /* ----------------------------------------------------------------- */ + public UrlEncoded() + { + super(6); + } + + /* ----------------------------------------------------------------- */ + public UrlEncoded(String s) + { + super(6); + decode(s,ENCODING); + } + + /* ----------------------------------------------------------------- */ + public UrlEncoded(String s, String charset) + { + super(6); + decode(s,charset); + } + + /* ----------------------------------------------------------------- */ + public void decode(String query) + { + decodeTo(query,this,ENCODING,-1); + } + + /* ----------------------------------------------------------------- */ + public void decode(String query,String charset) + { + decodeTo(query,this,charset,-1); + } + + /* -------------------------------------------------------------- */ + /** Encode Hashtable with % encoding. + */ + public String encode() + { + return encode(ENCODING,false); + } + + /* -------------------------------------------------------------- */ + /** Encode Hashtable with % encoding. + */ + public String encode(String charset) + { + return encode(charset,false); + } + + /* -------------------------------------------------------------- */ + /** Encode Hashtable with % encoding. + * @param equalsForNullValue if True, then an '=' is always used, even + * for parameters without a value. e.g. "blah?a=&b=&c=". + */ + public synchronized String encode(String charset, boolean equalsForNullValue) + { + return encode(this,charset,equalsForNullValue); + } + + /* -------------------------------------------------------------- */ + /** Encode Hashtable with % encoding. + * @param equalsForNullValue if True, then an '=' is always used, even + * for parameters without a value. e.g. "blah?a=&b=&c=". + */ + public static String encode(MultiMap map, String charset, boolean equalsForNullValue) + { + if (charset==null) + charset=ENCODING; + + StringBuilder result = new StringBuilder(128); + + Iterator iter = map.entrySet().iterator(); + while(iter.hasNext()) + { + Map.Entry entry = (Map.Entry)iter.next(); + + String key = entry.getKey().toString(); + Object list = entry.getValue(); + int s=LazyList.size(list); + + if (s==0) + { + result.append(encodeString(key,charset)); + if(equalsForNullValue) + result.append('='); + } + else + { + for (int i=0;i<s;i++) + { + if (i>0) + result.append('&'); + Object val=LazyList.get(list,i); + result.append(encodeString(key,charset)); + + if (val!=null) + { + String str=val.toString(); + if (str.length()>0) + { + result.append('='); + result.append(encodeString(str,charset)); + } + else if (equalsForNullValue) + result.append('='); + } + else if (equalsForNullValue) + result.append('='); + } + } + if (iter.hasNext()) + result.append('&'); + } + return result.toString(); + } + + + + /* -------------------------------------------------------------- */ + /** Decoded parameters to Map. + * @param content the string containing the encoded parameters + */ + public static void decodeTo(String content, MultiMap map, String charset) + { + decodeTo(content,map,charset,-1); + } + + /* -------------------------------------------------------------- */ + /** Decoded parameters to Map. + * @param content the string containing the encoded parameters + */ + public static void decodeTo(String content, MultiMap map, String charset, int maxKeys) + { + if (charset==null) + charset=ENCODING; + + synchronized(map) + { + String key = null; + String value = null; + int mark=-1; + boolean encoded=false; + for (int i=0;i<content.length();i++) + { + char c = content.charAt(i); + switch (c) + { + case '&': + int l=i-mark-1; + value = l==0?"": + (encoded?decodeString(content,mark+1,l,charset):content.substring(mark+1,i)); + mark=i; + encoded=false; + if (key != null) + { + map.add(key,value); + } + else if (value!=null&&value.length()>0) + { + map.add(value,""); + } + key = null; + value=null; + if (maxKeys>0 && map.size()>maxKeys) + throw new IllegalStateException("Form too many keys"); + break; + case '=': + if (key!=null) + break; + key = encoded?decodeString(content,mark+1,i-mark-1,charset):content.substring(mark+1,i); + mark=i; + encoded=false; + break; + case '+': + encoded=true; + break; + case '%': + encoded=true; + break; + } + } + + if (key != null) + { + int l=content.length()-mark-1; + value = l==0?"":(encoded?decodeString(content,mark+1,l,charset):content.substring(mark+1)); + map.add(key,value); + } + else if (mark<content.length()) + { + key = encoded + ?decodeString(content,mark+1,content.length()-mark-1,charset) + :content.substring(mark+1); + if (key != null && key.length() > 0) + { + map.add(key,""); + } + } + } + } + + /* -------------------------------------------------------------- */ + /** Decoded parameters to Map. + * @param raw the byte[] containing the encoded parameters + * @param offset the offset within raw to decode from + * @param length the length of the section to decode + * @param map the {@link MultiMap} to populate + */ + public static void decodeUtf8To(byte[] raw,int offset, int length, MultiMap map) + { + decodeUtf8To(raw,offset,length,map,new Utf8StringBuilder()); + } + + /* -------------------------------------------------------------- */ + /** Decoded parameters to Map. + * @param raw the byte[] containing the encoded parameters + * @param offset the offset within raw to decode from + * @param length the length of the section to decode + * @param map the {@link MultiMap} to populate + * @param buffer the buffer to decode into + */ + public static void decodeUtf8To(byte[] raw,int offset, int length, MultiMap map,Utf8StringBuilder buffer) + { + synchronized(map) + { + String key = null; + String value = null; + + // TODO cache of parameter names ??? + int end=offset+length; + for (int i=offset;i<end;i++) + { + byte b=raw[i]; + try + { + switch ((char)(0xff&b)) + { + case '&': + value = buffer.length()==0?"":buffer.toString(); + buffer.reset(); + if (key != null) + { + map.add(key,value); + } + else if (value!=null&&value.length()>0) + { + map.add(value,""); + } + key = null; + value=null; + break; + + case '=': + if (key!=null) + { + buffer.append(b); + break; + } + key = buffer.toString(); + buffer.reset(); + break; + + case '+': + buffer.append((byte)' '); + break; + + case '%': + if (i+2<end) + { + if ('u'==raw[i+1]) + { + i++; + if (i+4<end) + buffer.getStringBuilder().append(Character.toChars((convertHexDigit(raw[++i])<<12) +(convertHexDigit(raw[++i])<<8) + (convertHexDigit(raw[++i])<<4) +convertHexDigit(raw[++i]))); + else + { + buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT); + i=end; + } + } + else + buffer.append((byte)((convertHexDigit(raw[++i])<<4) + convertHexDigit(raw[++i]))); + } + else + { + buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT); + i=end; + } + break; + + default: + buffer.append(b); + break; + } + } + catch(NotUtf8Exception e) + { + LOG.warn(e.toString()); + LOG.debug(e); + } + } + + if (key != null) + { + value = buffer.length()==0?"":buffer.toReplacedString(); + buffer.reset(); + map.add(key,value); + } + else if (buffer.length()>0) + { + map.add(buffer.toReplacedString(),""); + } + } + } + + /* -------------------------------------------------------------- */ + /** Decoded parameters to Map. + * @param in InputSteam to read + * @param map MultiMap to add parameters to + * @param maxLength maximum number of keys to read or -1 for no limit + */ + public static void decode88591To(InputStream in, MultiMap map, int maxLength, int maxKeys) + throws IOException + { + synchronized(map) + { + StringBuffer buffer = new StringBuffer(); + String key = null; + String value = null; + + int b; + + // TODO cache of parameter names ??? + int totalLength=0; + while ((b=in.read())>=0) + { + switch ((char) b) + { + case '&': + value = buffer.length()==0?"":buffer.toString(); + buffer.setLength(0); + if (key != null) + { + map.add(key,value); + } + else if (value!=null&&value.length()>0) + { + map.add(value,""); + } + key = null; + value=null; + if (maxKeys>0 && map.size()>maxKeys) + throw new IllegalStateException("Form too many keys"); + break; + + case '=': + if (key!=null) + { + buffer.append((char)b); + break; + } + key = buffer.toString(); + buffer.setLength(0); + break; + + case '+': + buffer.append(' '); + break; + + case '%': + int code0=in.read(); + if ('u'==code0) + { + int code1=in.read(); + if (code1>=0) + { + int code2=in.read(); + if (code2>=0) + { + int code3=in.read(); + if (code3>=0) + buffer.append(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3))); + } + } + } + else if (code0>=0) + { + int code1=in.read(); + if (code1>=0) + buffer.append((char)((convertHexDigit(code0)<<4)+convertHexDigit(code1))); + } + break; + + default: + buffer.append((char)b); + break; + } + if (maxLength>=0 && (++totalLength > maxLength)) + throw new IllegalStateException("Form too large"); + } + + if (key != null) + { + value = buffer.length()==0?"":buffer.toString(); + buffer.setLength(0); + map.add(key,value); + } + else if (buffer.length()>0) + { + map.add(buffer.toString(), ""); + } + } + } + + /* -------------------------------------------------------------- */ + /** Decoded parameters to Map. + * @param in InputSteam to read + * @param map MultiMap to add parameters to + * @param maxLength maximum number of keys to read or -1 for no limit + */ + public static void decodeUtf8To(InputStream in, MultiMap map, int maxLength, int maxKeys) + throws IOException + { + synchronized(map) + { + Utf8StringBuilder buffer = new Utf8StringBuilder(); + String key = null; + String value = null; + + int b; + + // TODO cache of parameter names ??? + int totalLength=0; + while ((b=in.read())>=0) + { + try + { + switch ((char) b) + { + case '&': + value = buffer.length()==0?"":buffer.toString(); + buffer.reset(); + if (key != null) + { + map.add(key,value); + } + else if (value!=null&&value.length()>0) + { + map.add(value,""); + } + key = null; + value=null; + if (maxKeys>0 && map.size()>maxKeys) + throw new IllegalStateException("Form too many keys"); + break; + + case '=': + if (key!=null) + { + buffer.append((byte)b); + break; + } + key = buffer.toString(); + buffer.reset(); + break; + + case '+': + buffer.append((byte)' '); + break; + + case '%': + int code0=in.read(); + if ('u'==code0) + { + int code1=in.read(); + if (code1>=0) + { + int code2=in.read(); + if (code2>=0) + { + int code3=in.read(); + if (code3>=0) + buffer.getStringBuilder().append(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3))); + } + } + } + else if (code0>=0) + { + int code1=in.read(); + if (code1>=0) + buffer.append((byte)((convertHexDigit(code0)<<4)+convertHexDigit(code1))); + } + break; + + default: + buffer.append((byte)b); + break; + } + } + catch(NotUtf8Exception e) + { + LOG.warn(e.toString()); + LOG.debug(e); + } + if (maxLength>=0 && (++totalLength > maxLength)) + throw new IllegalStateException("Form too large"); + } + + if (key != null) + { + value = buffer.length()==0?"":buffer.toString(); + buffer.reset(); + map.add(key,value); + } + else if (buffer.length()>0) + { + map.add(buffer.toString(), ""); + } + } + } + + /* -------------------------------------------------------------- */ + public static void decodeUtf16To(InputStream in, MultiMap map, int maxLength, int maxKeys) throws IOException + { + InputStreamReader input = new InputStreamReader(in,StringUtil.__UTF16); + StringWriter buf = new StringWriter(8192); + IO.copy(input,buf,maxLength); + + decodeTo(buf.getBuffer().toString(),map,StringUtil.__UTF16,maxKeys); + } + + /* -------------------------------------------------------------- */ + /** Decoded parameters to Map. + * @param in the stream containing the encoded parameters + */ + public static void decodeTo(InputStream in, MultiMap map, String charset, int maxLength, int maxKeys) + throws IOException + { + //no charset present, use the configured default + if (charset==null) + { + charset=ENCODING; + } + + if (StringUtil.__UTF8.equalsIgnoreCase(charset)) + { + decodeUtf8To(in,map,maxLength,maxKeys); + return; + } + + if (StringUtil.__ISO_8859_1.equals(charset)) + { + decode88591To(in,map,maxLength,maxKeys); + return; + } + + if (StringUtil.__UTF16.equalsIgnoreCase(charset)) // Should be all 2 byte encodings + { + decodeUtf16To(in,map,maxLength,maxKeys); + return; + } + + + synchronized(map) + { + String key = null; + String value = null; + + int c; + + int totalLength = 0; + ByteArrayOutputStream2 output = new ByteArrayOutputStream2(); + + int size=0; + + while ((c=in.read())>0) + { + switch ((char) c) + { + case '&': + size=output.size(); + value = size==0?"":output.toString(charset); + output.setCount(0); + if (key != null) + { + map.add(key,value); + } + else if (value!=null&&value.length()>0) + { + map.add(value,""); + } + key = null; + value=null; + if (maxKeys>0 && map.size()>maxKeys) + throw new IllegalStateException("Form too many keys"); + break; + case '=': + if (key!=null) + { + output.write(c); + break; + } + size=output.size(); + key = size==0?"":output.toString(charset); + output.setCount(0); + break; + case '+': + output.write(' '); + break; + case '%': + int code0=in.read(); + if ('u'==code0) + { + int code1=in.read(); + if (code1>=0) + { + int code2=in.read(); + if (code2>=0) + { + int code3=in.read(); + if (code3>=0) + output.write(new String(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3))).getBytes(charset)); + } + } + + } + else if (code0>=0) + { + int code1=in.read(); + if (code1>=0) + output.write((convertHexDigit(code0)<<4)+convertHexDigit(code1)); + } + break; + default: + output.write(c); + break; + } + + totalLength++; + if (maxLength>=0 && totalLength > maxLength) + throw new IllegalStateException("Form too large"); + } + + size=output.size(); + if (key != null) + { + value = size==0?"":output.toString(charset); + output.setCount(0); + map.add(key,value); + } + else if (size>0) + map.add(output.toString(charset),""); + } + } + + /* -------------------------------------------------------------- */ + /** Decode String with % encoding. + * This method makes the assumption that the majority of calls + * will need no decoding. + */ + public static String decodeString(String encoded,int offset,int length,String charset) + { + if (charset==null || StringUtil.isUTF8(charset)) + { + Utf8StringBuffer buffer=null; + + for (int i=0;i<length;i++) + { + char c = encoded.charAt(offset+i); + if (c<0||c>0xff) + { + if (buffer==null) + { + buffer=new Utf8StringBuffer(length); + buffer.getStringBuffer().append(encoded,offset,offset+i+1); + } + else + buffer.getStringBuffer().append(c); + } + else if (c=='+') + { + if (buffer==null) + { + buffer=new Utf8StringBuffer(length); + buffer.getStringBuffer().append(encoded,offset,offset+i); + } + + buffer.getStringBuffer().append(' '); + } + else if (c=='%') + { + if (buffer==null) + { + buffer=new Utf8StringBuffer(length); + buffer.getStringBuffer().append(encoded,offset,offset+i); + } + + if ((i+2)<length) + { + try + { + if ('u'==encoded.charAt(offset+i+1)) + { + if((i+5)<length) + { + int o=offset+i+2; + i+=5; + String unicode = new String(Character.toChars(TypeUtil.parseInt(encoded,o,4,16))); + buffer.getStringBuffer().append(unicode); + } + else + { + i=length; + buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT); + } + } + else + { + int o=offset+i+1; + i+=2; + byte b=(byte)TypeUtil.parseInt(encoded,o,2,16); + buffer.append(b); + } + } + catch(NotUtf8Exception e) + { + LOG.warn(e.toString()); + LOG.debug(e); + } + catch(NumberFormatException nfe) + { + LOG.debug(nfe); + buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT); + } + } + else + { + buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT); + i=length; + } + } + else if (buffer!=null) + buffer.getStringBuffer().append(c); + } + + if (buffer==null) + { + if (offset==0 && encoded.length()==length) + return encoded; + return encoded.substring(offset,offset+length); + } + + return buffer.toReplacedString(); + } + else + { + StringBuffer buffer=null; + + try + { + for (int i=0;i<length;i++) + { + char c = encoded.charAt(offset+i); + if (c<0||c>0xff) + { + if (buffer==null) + { + buffer=new StringBuffer(length); + buffer.append(encoded,offset,offset+i+1); + } + else + buffer.append(c); + } + else if (c=='+') + { + if (buffer==null) + { + buffer=new StringBuffer(length); + buffer.append(encoded,offset,offset+i); + } + + buffer.append(' '); + } + else if (c=='%') + { + if (buffer==null) + { + buffer=new StringBuffer(length); + buffer.append(encoded,offset,offset+i); + } + + byte[] ba=new byte[length]; + int n=0; + while(c>=0 && c<=0xff) + { + if (c=='%') + { + if(i+2<length) + { + try + { + if ('u'==encoded.charAt(offset+i+1)) + { + if (i+6<length) + { + int o=offset+i+2; + i+=6; + String unicode = new String(Character.toChars(TypeUtil.parseInt(encoded,o,4,16))); + byte[] reencoded = unicode.getBytes(charset); + System.arraycopy(reencoded,0,ba,n,reencoded.length); + n+=reencoded.length; + } + else + { + ba[n++] = (byte)'?'; + i=length; + } + } + else + { + int o=offset+i+1; + i+=3; + ba[n]=(byte)TypeUtil.parseInt(encoded,o,2,16); + n++; + } + } + catch(NumberFormatException nfe) + { + LOG.ignore(nfe); + ba[n++] = (byte)'?'; + } + } + else + { + ba[n++] = (byte)'?'; + i=length; + } + } + else if (c=='+') + { + ba[n++]=(byte)' '; + i++; + } + else + { + ba[n++]=(byte)c; + i++; + } + + if (i>=length) + break; + c = encoded.charAt(offset+i); + } + + i--; + buffer.append(new String(ba,0,n,charset)); + + } + else if (buffer!=null) + buffer.append(c); + } + + if (buffer==null) + { + if (offset==0 && encoded.length()==length) + return encoded; + return encoded.substring(offset,offset+length); + } + + return buffer.toString(); + } + catch (UnsupportedEncodingException e) + { + throw new RuntimeException(e); + } + } + + } + + /* ------------------------------------------------------------ */ + /** Perform URL encoding. + * @param string + * @return encoded string. + */ + public static String encodeString(String string) + { + return encodeString(string,ENCODING); + } + + /* ------------------------------------------------------------ */ + /** Perform URL encoding. + * @param string + * @return encoded string. + */ + public static String encodeString(String string,String charset) + { + if (charset==null) + charset=ENCODING; + byte[] bytes=null; + try + { + bytes=string.getBytes(charset); + } + catch(UnsupportedEncodingException e) + { + // LOG.warn(LogSupport.EXCEPTION,e); + bytes=string.getBytes(); + } + + int len=bytes.length; + byte[] encoded= new byte[bytes.length*3]; + int n=0; + boolean noEncode=true; + + for (int i=0;i<len;i++) + { + byte b = bytes[i]; + + if (b==' ') + { + noEncode=false; + encoded[n++]=(byte)'+'; + } + else if (b>='a' && b<='z' || + b>='A' && b<='Z' || + b>='0' && b<='9') + { + encoded[n++]=b; + } + else + { + noEncode=false; + encoded[n++]=(byte)'%'; + byte nibble= (byte) ((b&0xf0)>>4); + if (nibble>=10) + encoded[n++]=(byte)('A'+nibble-10); + else + encoded[n++]=(byte)('0'+nibble); + nibble= (byte) (b&0xf); + if (nibble>=10) + encoded[n++]=(byte)('A'+nibble-10); + else + encoded[n++]=(byte)('0'+nibble); + } + } + + if (noEncode) + return string; + + try + { + return new String(encoded,0,n,charset); + } + catch(UnsupportedEncodingException e) + { + // LOG.warn(LogSupport.EXCEPTION,e); + return new String(encoded,0,n); + } + } + + + /* ------------------------------------------------------------ */ + /** + */ + @Override + public Object clone() + { + return new UrlEncoded(this); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/Utf8Appendable.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,238 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import java.io.IOException; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/* ------------------------------------------------------------ */ +/** + * Utf8 Appendable abstract base class + * + * This abstract class wraps a standard {@link java.lang.Appendable} and provides methods to append UTF-8 encoded bytes, that are converted into characters. + * + * This class is stateful and up to 4 calls to {@link #append(byte)} may be needed before state a character is appended to the string buffer. + * + * The UTF-8 decoding is done by this class and no additional buffers or Readers are used. The UTF-8 code was inspired by + * http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + * + * License information for Bjoern Hoehrmann's code: + * + * Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de> + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + **/ +public abstract class Utf8Appendable +{ + protected static final Logger LOG = Log.getLogger(Utf8Appendable.class); + public static final char REPLACEMENT = '\ufffd'; + private static final int UTF8_ACCEPT = 0; + private static final int UTF8_REJECT = 12; + + protected final Appendable _appendable; + protected int _state = UTF8_ACCEPT; + + private static final byte[] BYTE_TABLE = + { + // The first part of the table maps bytes to character classes that + // to reduce the size of the transition table and create bitmasks. + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8 + }; + + private static final byte[] TRANS_TABLE = + { + // The second part is a transition table that maps a combination + // of a state of the automaton and a character class to a state. + 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, + 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, + 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, + 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, + 12,36,12,12,12,12,12,12,12,12,12,12 + }; + + private int _codep; + + public Utf8Appendable(Appendable appendable) + { + _appendable = appendable; + } + + public abstract int length(); + + protected void reset() + { + _state = UTF8_ACCEPT; + } + + public void append(byte b) + { + try + { + appendByte(b); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + public void append(byte[] b, int offset, int length) + { + try + { + int end = offset + length; + for (int i = offset; i < end; i++) + appendByte(b[i]); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + public boolean append(byte[] b, int offset, int length, int maxChars) + { + try + { + int end = offset + length; + for (int i = offset; i < end; i++) + { + if (length() > maxChars) + return false; + appendByte(b[i]); + } + return true; + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + protected void appendByte(byte b) throws IOException + { + + if (b > 0 && _state == UTF8_ACCEPT) + { + _appendable.append((char)(b & 0xFF)); + } + else + { + int i = b & 0xFF; + int type = BYTE_TABLE[i]; + _codep = _state == UTF8_ACCEPT ? (0xFF >> type) & i : (i & 0x3F) | (_codep << 6); + int next = TRANS_TABLE[_state + type]; + + switch(next) + { + case UTF8_ACCEPT: + _state=next; + if (_codep < Character.MIN_HIGH_SURROGATE) + { + _appendable.append((char)_codep); + } + else + { + for (char c : Character.toChars(_codep)) + _appendable.append(c); + } + break; + + case UTF8_REJECT: + String reason = "byte "+TypeUtil.toHexString(b)+" in state "+(_state/12); + _codep=0; + _state = UTF8_ACCEPT; + _appendable.append(REPLACEMENT); + throw new NotUtf8Exception(reason); + + default: + _state=next; + + } + } + } + + public boolean isUtf8SequenceComplete() + { + return _state == UTF8_ACCEPT; + } + + public static class NotUtf8Exception extends IllegalArgumentException + { + public NotUtf8Exception(String reason) + { + super("Not valid UTF8! "+reason); + } + } + + protected void checkState() + { + if (!isUtf8SequenceComplete()) + { + _codep=0; + _state = UTF8_ACCEPT; + try + { + _appendable.append(REPLACEMENT); + } + catch(IOException e) + { + throw new RuntimeException(e); + } + throw new NotUtf8Exception("incomplete UTF8 sequence"); + } + } + + public String toReplacedString() + { + if (!isUtf8SequenceComplete()) + { + _codep=0; + _state = UTF8_ACCEPT; + try + { + _appendable.append(REPLACEMENT); + } + catch(IOException e) + { + throw new RuntimeException(e); + } + Throwable th= new NotUtf8Exception("incomplete UTF8 sequence"); + LOG.warn(th.toString()); + LOG.debug(th); + } + return _appendable.toString(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/Utf8StringBuffer.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,75 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +/* ------------------------------------------------------------ */ +/** + * UTF-8 StringBuffer. + * + * This class wraps a standard {@link java.lang.StringBuffer} and provides methods to append + * UTF-8 encoded bytes, that are converted into characters. + * + * This class is stateful and up to 4 calls to {@link #append(byte)} may be needed before + * state a character is appended to the string buffer. + * + * The UTF-8 decoding is done by this class and no additional buffers or Readers are used. + * The UTF-8 code was inspired by http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + */ +public class Utf8StringBuffer extends Utf8Appendable +{ + final StringBuffer _buffer; + + public Utf8StringBuffer() + { + super(new StringBuffer()); + _buffer = (StringBuffer)_appendable; + } + + public Utf8StringBuffer(int capacity) + { + super(new StringBuffer(capacity)); + _buffer = (StringBuffer)_appendable; + } + + @Override + public int length() + { + return _buffer.length(); + } + + @Override + public void reset() + { + super.reset(); + _buffer.setLength(0); + } + + public StringBuffer getStringBuffer() + { + checkState(); + return _buffer; + } + + @Override + public String toString() + { + checkState(); + return _buffer.toString(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/Utf8StringBuilder.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,78 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + + +/* ------------------------------------------------------------ */ +/** UTF-8 StringBuilder. + * + * This class wraps a standard {@link java.lang.StringBuilder} and provides methods to append + * UTF-8 encoded bytes, that are converted into characters. + * + * This class is stateful and up to 4 calls to {@link #append(byte)} may be needed before + * state a character is appended to the string buffer. + * + * The UTF-8 decoding is done by this class and no additional buffers or Readers are used. + * The UTF-8 code was inspired by http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + * + */ +public class Utf8StringBuilder extends Utf8Appendable +{ + final StringBuilder _buffer; + + public Utf8StringBuilder() + { + super(new StringBuilder()); + _buffer=(StringBuilder)_appendable; + } + + public Utf8StringBuilder(int capacity) + { + super(new StringBuilder(capacity)); + _buffer=(StringBuilder)_appendable; + } + + @Override + public int length() + { + return _buffer.length(); + } + + @Override + public void reset() + { + super.reset(); + _buffer.setLength(0); + } + + public StringBuilder getStringBuilder() + { + checkState(); + return _buffer; + } + + @Override + public String toString() + { + checkState(); + return _buffer.toString(); + } + + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/ajax/JSON.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,1640 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.ajax; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.Loader; +import org.eclipse.jetty.util.QuotedStringTokenizer; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + * JSON Parser and Generator. + * <p /> + * This class provides some static methods to convert POJOs to and from JSON + * notation. The mapping from JSON to java is: + * + * <pre> + * object ==> Map + * array ==> Object[] + * number ==> Double or Long + * string ==> String + * null ==> null + * bool ==> Boolean + * </pre> + + * The java to JSON mapping is: + * + * <pre> + * String --> string + * Number --> number + * Map --> object + * List --> array + * Array --> array + * null --> null + * Boolean--> boolean + * Object --> string (dubious!) + * </pre> + * + * The interface {@link JSON.Convertible} may be implemented by classes that + * wish to externalize and initialize specific fields to and from JSON objects. + * Only directed acyclic graphs of objects are supported. + * <p /> + * The interface {@link JSON.Generator} may be implemented by classes that know + * how to render themselves as JSON and the {@link #toString(Object)} method + * will use {@link JSON.Generator#addJSON(Appendable)} to generate the JSON. + * The class {@link JSON.Literal} may be used to hold pre-generated JSON object. + * <p /> + * The interface {@link JSON.Convertor} may be implemented to provide static + * converters for objects that may be registered with + * {@link #registerConvertor(Class, Convertor)}. + * These converters are looked up by class, interface and super class by + * {@link #getConvertor(Class)}. + * <p /> + * If a JSON object has a "class" field, then a java class for that name is + * loaded and the method {@link #convertTo(Class,Map)} is used to find a + * {@link JSON.Convertor} for that class. + * <p /> + * If a JSON object has a "x-class" field then a direct lookup for a + * {@link JSON.Convertor} for that class name is done (without loading the class). + */ +public class JSON +{ + static final Logger LOG = Log.getLogger(JSON.class); + public final static JSON DEFAULT = new JSON(); + + private Map<String, Convertor> _convertors = new ConcurrentHashMap<String, Convertor>(); + private int _stringBufferSize = 1024; + + public JSON() + { + } + + /** + * @return the initial stringBuffer size to use when creating JSON strings + * (default 1024) + */ + public int getStringBufferSize() + { + return _stringBufferSize; + } + + /** + * @param stringBufferSize + * the initial stringBuffer size to use when creating JSON + * strings (default 1024) + */ + public void setStringBufferSize(int stringBufferSize) + { + _stringBufferSize = stringBufferSize; + } + + /** + * Register a {@link Convertor} for a class or interface. + * + * @param forClass + * The class or interface that the convertor applies to + * @param convertor + * the convertor + */ + public static void registerConvertor(Class forClass, Convertor convertor) + { + DEFAULT.addConvertor(forClass,convertor); + } + + public static JSON getDefault() + { + return DEFAULT; + } + + @Deprecated + public static void setDefault(JSON json) + { + } + + public static String toString(Object object) + { + StringBuilder buffer = new StringBuilder(DEFAULT.getStringBufferSize()); + DEFAULT.append(buffer,object); + return buffer.toString(); + } + + public static String toString(Map object) + { + StringBuilder buffer = new StringBuilder(DEFAULT.getStringBufferSize()); + DEFAULT.appendMap(buffer,object); + return buffer.toString(); + } + + public static String toString(Object[] array) + { + StringBuilder buffer = new StringBuilder(DEFAULT.getStringBufferSize()); + DEFAULT.appendArray(buffer,array); + return buffer.toString(); + } + + /** + * @param s + * String containing JSON object or array. + * @return A Map, Object array or primitive array parsed from the JSON. + */ + public static Object parse(String s) + { + return DEFAULT.parse(new StringSource(s),false); + } + + /** + * @param s + * String containing JSON object or array. + * @param stripOuterComment + * If true, an outer comment around the JSON is ignored. + * @return A Map, Object array or primitive array parsed from the JSON. + */ + public static Object parse(String s, boolean stripOuterComment) + { + return DEFAULT.parse(new StringSource(s),stripOuterComment); + } + + /** + * @param in + * Reader containing JSON object or array. + * @return A Map, Object array or primitive array parsed from the JSON. + */ + public static Object parse(Reader in) throws IOException + { + return DEFAULT.parse(new ReaderSource(in),false); + } + + /** + * @param in + * Reader containing JSON object or array. + * @param stripOuterComment + * If true, an outer comment around the JSON is ignored. + * @return A Map, Object array or primitive array parsed from the JSON. + */ + public static Object parse(Reader in, boolean stripOuterComment) throws IOException + { + return DEFAULT.parse(new ReaderSource(in),stripOuterComment); + } + + /** + * @deprecated use {@link #parse(Reader)} + * @param in + * Reader containing JSON object or array. + * @return A Map, Object array or primitive array parsed from the JSON. + */ + @Deprecated + public static Object parse(InputStream in) throws IOException + { + return DEFAULT.parse(new StringSource(IO.toString(in)),false); + } + + /** + * @deprecated use {@link #parse(Reader, boolean)} + * @param in + * Stream containing JSON object or array. + * @param stripOuterComment + * If true, an outer comment around the JSON is ignored. + * @return A Map, Object array or primitive array parsed from the JSON. + */ + @Deprecated + public static Object parse(InputStream in, boolean stripOuterComment) throws IOException + { + return DEFAULT.parse(new StringSource(IO.toString(in)),stripOuterComment); + } + + /** + * Convert Object to JSON + * + * @param object + * The object to convert + * @return The JSON String + */ + public String toJSON(Object object) + { + StringBuilder buffer = new StringBuilder(getStringBufferSize()); + append(buffer,object); + return buffer.toString(); + } + + /** + * Convert JSON to Object + * + * @param json + * The json to convert + * @return The object + */ + public Object fromJSON(String json) + { + Source source = new StringSource(json); + return parse(source); + } + + @Deprecated + public void append(StringBuffer buffer, Object object) + { + append((Appendable)buffer,object); + } + + /** + * Append object as JSON to string buffer. + * + * @param buffer + * the buffer to append to + * @param object + * the object to append + */ + public void append(Appendable buffer, Object object) + { + try + { + if (object == null) + { + buffer.append("null"); + } + // Most likely first + else if (object instanceof Map) + { + appendMap(buffer,(Map)object); + } + else if (object instanceof String) + { + appendString(buffer,(String)object); + } + else if (object instanceof Number) + { + appendNumber(buffer,(Number)object); + } + else if (object instanceof Boolean) + { + appendBoolean(buffer,(Boolean)object); + } + else if (object.getClass().isArray()) + { + appendArray(buffer,object); + } + else if (object instanceof Character) + { + appendString(buffer,object.toString()); + } + else if (object instanceof Convertible) + { + appendJSON(buffer,(Convertible)object); + } + else if (object instanceof Generator) + { + appendJSON(buffer,(Generator)object); + } + else + { + // Check Convertor before Collection to support JSONCollectionConvertor + Convertor convertor = getConvertor(object.getClass()); + if (convertor != null) + { + appendJSON(buffer,convertor,object); + } + else if (object instanceof Collection) + { + appendArray(buffer,(Collection)object); + } + else + { + appendString(buffer,object.toString()); + } + } + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + @Deprecated + public void appendNull(StringBuffer buffer) + { + appendNull((Appendable)buffer); + } + + public void appendNull(Appendable buffer) + { + try + { + buffer.append("null"); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + @Deprecated + public void appendJSON(final StringBuffer buffer, final Convertor convertor, final Object object) + { + appendJSON((Appendable)buffer,convertor,object); + } + + public void appendJSON(final Appendable buffer, final Convertor convertor, final Object object) + { + appendJSON(buffer,new Convertible() + { + public void fromJSON(Map object) + { + } + + public void toJSON(Output out) + { + convertor.toJSON(object,out); + } + }); + } + + @Deprecated + public void appendJSON(final StringBuffer buffer, Convertible converter) + { + appendJSON((Appendable)buffer,converter); + } + + public void appendJSON(final Appendable buffer, Convertible converter) + { + ConvertableOutput out=new ConvertableOutput(buffer); + converter.toJSON(out); + out.complete(); + } + + @Deprecated + public void appendJSON(StringBuffer buffer, Generator generator) + { + generator.addJSON(buffer); + } + + public void appendJSON(Appendable buffer, Generator generator) + { + generator.addJSON(buffer); + } + + @Deprecated + public void appendMap(StringBuffer buffer, Map<?,?> map) + { + appendMap((Appendable)buffer,map); + } + + public void appendMap(Appendable buffer, Map<?,?> map) + { + try + { + if (map == null) + { + appendNull(buffer); + return; + } + + buffer.append('{'); + Iterator<?> iter = map.entrySet().iterator(); + while (iter.hasNext()) + { + Map.Entry<?,?> entry = (Map.Entry<?,?>)iter.next(); + QuotedStringTokenizer.quote(buffer,entry.getKey().toString()); + buffer.append(':'); + append(buffer,entry.getValue()); + if (iter.hasNext()) + buffer.append(','); + } + + buffer.append('}'); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + @Deprecated + public void appendArray(StringBuffer buffer, Collection collection) + { + appendArray((Appendable)buffer,collection); + } + + public void appendArray(Appendable buffer, Collection collection) + { + try + { + if (collection == null) + { + appendNull(buffer); + return; + } + + buffer.append('['); + Iterator iter = collection.iterator(); + boolean first = true; + while (iter.hasNext()) + { + if (!first) + buffer.append(','); + + first = false; + append(buffer,iter.next()); + } + + buffer.append(']'); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + @Deprecated + public void appendArray(StringBuffer buffer, Object array) + { + appendArray((Appendable)buffer,array); + } + + public void appendArray(Appendable buffer, Object array) + { + try + { + if (array == null) + { + appendNull(buffer); + return; + } + + buffer.append('['); + int length = Array.getLength(array); + + for (int i = 0; i < length; i++) + { + if (i != 0) + buffer.append(','); + append(buffer,Array.get(array,i)); + } + + buffer.append(']'); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + @Deprecated + public void appendBoolean(StringBuffer buffer, Boolean b) + { + appendBoolean((Appendable)buffer,b); + } + + public void appendBoolean(Appendable buffer, Boolean b) + { + try + { + if (b == null) + { + appendNull(buffer); + return; + } + buffer.append(b?"true":"false"); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + @Deprecated + public void appendNumber(StringBuffer buffer, Number number) + { + appendNumber((Appendable)buffer,number); + } + + public void appendNumber(Appendable buffer, Number number) + { + try + { + if (number == null) + { + appendNull(buffer); + return; + } + buffer.append(String.valueOf(number)); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + @Deprecated + public void appendString(StringBuffer buffer, String string) + { + appendString((Appendable)buffer,string); + } + + public void appendString(Appendable buffer, String string) + { + if (string == null) + { + appendNull(buffer); + return; + } + + QuotedStringTokenizer.quote(buffer,string); + } + + // Parsing utilities + + protected String toString(char[] buffer, int offset, int length) + { + return new String(buffer,offset,length); + } + + protected Map<String, Object> newMap() + { + return new HashMap<String, Object>(); + } + + protected Object[] newArray(int size) + { + return new Object[size]; + } + + protected JSON contextForArray() + { + return this; + } + + protected JSON contextFor(String field) + { + return this; + } + + protected Object convertTo(Class type, Map map) + { + if (type != null && Convertible.class.isAssignableFrom(type)) + { + try + { + Convertible conv = (Convertible)type.newInstance(); + conv.fromJSON(map); + return conv; + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + Convertor convertor = getConvertor(type); + if (convertor != null) + { + return convertor.fromJSON(map); + } + return map; + } + + /** + * Register a {@link Convertor} for a class or interface. + * + * @param forClass + * The class or interface that the convertor applies to + * @param convertor + * the convertor + */ + public void addConvertor(Class forClass, Convertor convertor) + { + _convertors.put(forClass.getName(),convertor); + } + + /** + * Lookup a convertor for a class. + * <p> + * If no match is found for the class, then the interfaces for the class are + * tried. If still no match is found, then the super class and it's + * interfaces are tried recursively. + * + * @param forClass + * The class + * @return a {@link JSON.Convertor} or null if none were found. + */ + protected Convertor getConvertor(Class forClass) + { + Class cls = forClass; + Convertor convertor = _convertors.get(cls.getName()); + if (convertor == null && this != DEFAULT) + convertor = DEFAULT.getConvertor(cls); + + while (convertor == null && cls != Object.class) + { + Class[] ifs = cls.getInterfaces(); + int i = 0; + while (convertor == null && ifs != null && i < ifs.length) + convertor = _convertors.get(ifs[i++].getName()); + if (convertor == null) + { + cls = cls.getSuperclass(); + convertor = _convertors.get(cls.getName()); + } + } + return convertor; + } + + /** + * Register a {@link JSON.Convertor} for a named class or interface. + * + * @param name + * name of a class or an interface that the convertor applies to + * @param convertor + * the convertor + */ + public void addConvertorFor(String name, Convertor convertor) + { + _convertors.put(name,convertor); + } + + /** + * Lookup a convertor for a named class. + * + * @param name + * name of the class + * @return a {@link JSON.Convertor} or null if none were found. + */ + public Convertor getConvertorFor(String name) + { + Convertor convertor = _convertors.get(name); + if (convertor == null && this != DEFAULT) + convertor = DEFAULT.getConvertorFor(name); + return convertor; + } + + public Object parse(Source source, boolean stripOuterComment) + { + int comment_state = 0; // 0=no comment, 1="/", 2="/*", 3="/* *" -1="//" + if (!stripOuterComment) + return parse(source); + + int strip_state = 1; // 0=no strip, 1=wait for /*, 2= wait for */ + + Object o = null; + while (source.hasNext()) + { + char c = source.peek(); + + // handle // or /* comment + if (comment_state == 1) + { + switch (c) + { + case '/': + comment_state = -1; + break; + case '*': + comment_state = 2; + if (strip_state == 1) + { + comment_state = 0; + strip_state = 2; + } + } + } + // handle /* */ comment + else if (comment_state > 1) + { + switch (c) + { + case '*': + comment_state = 3; + break; + case '/': + if (comment_state == 3) + { + comment_state = 0; + if (strip_state == 2) + return o; + } + else + comment_state = 2; + break; + default: + comment_state = 2; + } + } + // handle // comment + else if (comment_state < 0) + { + switch (c) + { + case '\r': + case '\n': + comment_state = 0; + default: + break; + } + } + // handle unknown + else + { + if (!Character.isWhitespace(c)) + { + if (c == '/') + comment_state = 1; + else if (c == '*') + comment_state = 3; + else if (o == null) + { + o = parse(source); + continue; + } + } + } + + source.next(); + } + + return o; + } + + public Object parse(Source source) + { + int comment_state = 0; // 0=no comment, 1="/", 2="/*", 3="/* *" -1="//" + + while (source.hasNext()) + { + char c = source.peek(); + + // handle // or /* comment + if (comment_state == 1) + { + switch (c) + { + case '/': + comment_state = -1; + break; + case '*': + comment_state = 2; + } + } + // handle /* */ comment + else if (comment_state > 1) + { + switch (c) + { + case '*': + comment_state = 3; + break; + case '/': + if (comment_state == 3) + comment_state = 0; + else + comment_state = 2; + break; + default: + comment_state = 2; + } + } + // handle // comment + else if (comment_state < 0) + { + switch (c) + { + case '\r': + case '\n': + comment_state = 0; + break; + default: + break; + } + } + // handle unknown + else + { + switch (c) + { + case '{': + return parseObject(source); + case '[': + return parseArray(source); + case '"': + return parseString(source); + case '-': + return parseNumber(source); + + case 'n': + complete("null",source); + return null; + case 't': + complete("true",source); + return Boolean.TRUE; + case 'f': + complete("false",source); + return Boolean.FALSE; + case 'u': + complete("undefined",source); + return null; + case 'N': + complete("NaN",source); + return null; + + case '/': + comment_state = 1; + break; + + default: + if (Character.isDigit(c)) + return parseNumber(source); + else if (Character.isWhitespace(c)) + break; + return handleUnknown(source,c); + } + } + source.next(); + } + + return null; + } + + protected Object handleUnknown(Source source, char c) + { + throw new IllegalStateException("unknown char '" + c + "'(" + (int)c + ") in " + source); + } + + protected Object parseObject(Source source) + { + if (source.next() != '{') + throw new IllegalStateException(); + Map<String, Object> map = newMap(); + + char next = seekTo("\"}",source); + + while (source.hasNext()) + { + if (next == '}') + { + source.next(); + break; + } + + String name = parseString(source); + seekTo(':',source); + source.next(); + + Object value = contextFor(name).parse(source); + map.put(name,value); + + seekTo(",}",source); + next = source.next(); + if (next == '}') + break; + else + next = seekTo("\"}",source); + } + + String xclassname = (String)map.get("x-class"); + if (xclassname != null) + { + Convertor c = getConvertorFor(xclassname); + if (c != null) + return c.fromJSON(map); + LOG.warn("No Convertor for x-class '{}'", xclassname); + } + + String classname = (String)map.get("class"); + if (classname != null) + { + try + { + Class c = Loader.loadClass(JSON.class,classname); + return convertTo(c,map); + } + catch (ClassNotFoundException e) + { + LOG.warn("No Class for '{}'", classname); + } + } + + return map; + } + + protected Object parseArray(Source source) + { + if (source.next() != '[') + throw new IllegalStateException(); + + int size = 0; + ArrayList list = null; + Object item = null; + boolean coma = true; + + while (source.hasNext()) + { + char c = source.peek(); + switch (c) + { + case ']': + source.next(); + switch (size) + { + case 0: + return newArray(0); + case 1: + Object array = newArray(1); + Array.set(array,0,item); + return array; + default: + return list.toArray(newArray(list.size())); + } + + case ',': + if (coma) + throw new IllegalStateException(); + coma = true; + source.next(); + break; + + default: + if (Character.isWhitespace(c)) + source.next(); + else + { + coma = false; + if (size++ == 0) + item = contextForArray().parse(source); + else if (list == null) + { + list = new ArrayList(); + list.add(item); + item = contextForArray().parse(source); + list.add(item); + item = null; + } + else + { + item = contextForArray().parse(source); + list.add(item); + item = null; + } + } + } + + } + + throw new IllegalStateException("unexpected end of array"); + } + + protected String parseString(Source source) + { + if (source.next() != '"') + throw new IllegalStateException(); + + boolean escape = false; + + StringBuilder b = null; + final char[] scratch = source.scratchBuffer(); + + if (scratch != null) + { + int i = 0; + while (source.hasNext()) + { + if (i >= scratch.length) + { + // we have filled the scratch buffer, so we must + // use the StringBuffer for a large string + b = new StringBuilder(scratch.length * 2); + b.append(scratch,0,i); + break; + } + + char c = source.next(); + + if (escape) + { + escape = false; + switch (c) + { + case '"': + scratch[i++] = '"'; + break; + case '\\': + scratch[i++] = '\\'; + break; + case '/': + scratch[i++] = '/'; + break; + case 'b': + scratch[i++] = '\b'; + break; + case 'f': + scratch[i++] = '\f'; + break; + case 'n': + scratch[i++] = '\n'; + break; + case 'r': + scratch[i++] = '\r'; + break; + case 't': + scratch[i++] = '\t'; + break; + case 'u': + char uc = (char)((TypeUtil.convertHexDigit((byte)source.next()) << 12) + (TypeUtil.convertHexDigit((byte)source.next()) << 8) + + (TypeUtil.convertHexDigit((byte)source.next()) << 4) + (TypeUtil.convertHexDigit((byte)source.next()))); + scratch[i++] = uc; + break; + default: + scratch[i++] = c; + } + } + else if (c == '\\') + { + escape = true; + } + else if (c == '\"') + { + // Return string that fits within scratch buffer + return toString(scratch,0,i); + } + else + { + scratch[i++] = c; + } + } + + // Missing end quote, but return string anyway ? + if (b == null) + return toString(scratch,0,i); + } + else + b = new StringBuilder(getStringBufferSize()); + + // parse large string into string buffer + final StringBuilder builder=b; + while (source.hasNext()) + { + char c = source.next(); + + if (escape) + { + escape = false; + switch (c) + { + case '"': + builder.append('"'); + break; + case '\\': + builder.append('\\'); + break; + case '/': + builder.append('/'); + break; + case 'b': + builder.append('\b'); + break; + case 'f': + builder.append('\f'); + break; + case 'n': + builder.append('\n'); + break; + case 'r': + builder.append('\r'); + break; + case 't': + builder.append('\t'); + break; + case 'u': + char uc = (char)((TypeUtil.convertHexDigit((byte)source.next()) << 12) + (TypeUtil.convertHexDigit((byte)source.next()) << 8) + + (TypeUtil.convertHexDigit((byte)source.next()) << 4) + (TypeUtil.convertHexDigit((byte)source.next()))); + builder.append(uc); + break; + default: + builder.append(c); + } + } + else if (c == '\\') + { + escape = true; + } + else if (c == '\"') + { + break; + } + else + { + builder.append(c); + } + } + return builder.toString(); + } + + public Number parseNumber(Source source) + { + boolean minus = false; + long number = 0; + StringBuilder buffer = null; + + longLoop: while (source.hasNext()) + { + char c = source.peek(); + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + number = number * 10 + (c - '0'); + source.next(); + break; + + case '-': + case '+': + if (number != 0) + throw new IllegalStateException("bad number"); + minus = true; + source.next(); + break; + + case '.': + case 'e': + case 'E': + buffer = new StringBuilder(16); + if (minus) + buffer.append('-'); + buffer.append(number); + buffer.append(c); + source.next(); + break longLoop; + + default: + break longLoop; + } + } + + if (buffer == null) + return minus ? -1 * number : number; + + doubleLoop: while (source.hasNext()) + { + char c = source.peek(); + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + case '.': + case '+': + case 'e': + case 'E': + buffer.append(c); + source.next(); + break; + + default: + break doubleLoop; + } + } + return new Double(buffer.toString()); + + } + + protected void seekTo(char seek, Source source) + { + while (source.hasNext()) + { + char c = source.peek(); + if (c == seek) + return; + + if (!Character.isWhitespace(c)) + throw new IllegalStateException("Unexpected '" + c + " while seeking '" + seek + "'"); + source.next(); + } + + throw new IllegalStateException("Expected '" + seek + "'"); + } + + protected char seekTo(String seek, Source source) + { + while (source.hasNext()) + { + char c = source.peek(); + if (seek.indexOf(c) >= 0) + { + return c; + } + + if (!Character.isWhitespace(c)) + throw new IllegalStateException("Unexpected '" + c + "' while seeking one of '" + seek + "'"); + source.next(); + } + + throw new IllegalStateException("Expected one of '" + seek + "'"); + } + + protected static void complete(String seek, Source source) + { + int i = 0; + while (source.hasNext() && i < seek.length()) + { + char c = source.next(); + if (c != seek.charAt(i++)) + throw new IllegalStateException("Unexpected '" + c + " while seeking \"" + seek + "\""); + } + + if (i < seek.length()) + throw new IllegalStateException("Expected \"" + seek + "\""); + } + + private final class ConvertableOutput implements Output + { + private final Appendable _buffer; + char c = '{'; + + private ConvertableOutput(Appendable buffer) + { + _buffer = buffer; + } + + public void complete() + { + try + { + if (c == '{') + _buffer.append("{}"); + else if (c != 0) + _buffer.append("}"); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + public void add(Object obj) + { + if (c == 0) + throw new IllegalStateException(); + append(_buffer,obj); + c = 0; + } + + public void addClass(Class type) + { + try + { + if (c == 0) + throw new IllegalStateException(); + _buffer.append(c); + _buffer.append("\"class\":"); + append(_buffer,type.getName()); + c = ','; + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + public void add(String name, Object value) + { + try + { + if (c == 0) + throw new IllegalStateException(); + _buffer.append(c); + QuotedStringTokenizer.quote(_buffer,name); + _buffer.append(':'); + append(_buffer,value); + c = ','; + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + public void add(String name, double value) + { + try + { + if (c == 0) + throw new IllegalStateException(); + _buffer.append(c); + QuotedStringTokenizer.quote(_buffer,name); + _buffer.append(':'); + appendNumber(_buffer, value); + c = ','; + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + public void add(String name, long value) + { + try + { + if (c == 0) + throw new IllegalStateException(); + _buffer.append(c); + QuotedStringTokenizer.quote(_buffer,name); + _buffer.append(':'); + appendNumber(_buffer, value); + c = ','; + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + public void add(String name, boolean value) + { + try + { + if (c == 0) + throw new IllegalStateException(); + _buffer.append(c); + QuotedStringTokenizer.quote(_buffer,name); + _buffer.append(':'); + appendBoolean(_buffer,value?Boolean.TRUE:Boolean.FALSE); + c = ','; + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + } + + public interface Source + { + boolean hasNext(); + + char next(); + + char peek(); + + char[] scratchBuffer(); + } + + public static class StringSource implements Source + { + private final String string; + private int index; + private char[] scratch; + + public StringSource(String s) + { + string = s; + } + + public boolean hasNext() + { + if (index < string.length()) + return true; + scratch = null; + return false; + } + + public char next() + { + return string.charAt(index++); + } + + public char peek() + { + return string.charAt(index); + } + + @Override + public String toString() + { + return string.substring(0,index) + "|||" + string.substring(index); + } + + public char[] scratchBuffer() + { + if (scratch == null) + scratch = new char[string.length()]; + return scratch; + } + } + + public static class ReaderSource implements Source + { + private Reader _reader; + private int _next = -1; + private char[] scratch; + + public ReaderSource(Reader r) + { + _reader = r; + } + + public void setReader(Reader reader) + { + _reader = reader; + _next = -1; + } + + public boolean hasNext() + { + getNext(); + if (_next < 0) + { + scratch = null; + return false; + } + return true; + } + + public char next() + { + getNext(); + char c = (char)_next; + _next = -1; + return c; + } + + public char peek() + { + getNext(); + return (char)_next; + } + + private void getNext() + { + if (_next < 0) + { + try + { + _next = _reader.read(); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + } + + public char[] scratchBuffer() + { + if (scratch == null) + scratch = new char[1024]; + return scratch; + } + + } + + /** + * JSON Output class for use by {@link Convertible}. + */ + public interface Output + { + public void addClass(Class c); + + public void add(Object obj); + + public void add(String name, Object value); + + public void add(String name, double value); + + public void add(String name, long value); + + public void add(String name, boolean value); + } + + /* ------------------------------------------------------------ */ + /** + * JSON Convertible object. Object can implement this interface in a similar + * way to the {@link Externalizable} interface is used to allow classes to + * provide their own serialization mechanism. + * <p> + * A JSON.Convertible object may be written to a JSONObject or initialized + * from a Map of field names to values. + * <p> + * If the JSON is to be convertible back to an Object, then the method + * {@link Output#addClass(Class)} must be called from within toJSON() + * + */ + public interface Convertible + { + public void toJSON(Output out); + + public void fromJSON(Map object); + } + + /** + * Static JSON Convertor. + * <p> + * may be implemented to provide static convertors for objects that may be + * registered with + * {@link JSON#registerConvertor(Class, org.eclipse.jetty.util.ajax.JSON.Convertor)} + * . These convertors are looked up by class, interface and super class by + * {@link JSON#getConvertor(Class)}. Convertors should be used when the + * classes to be converted cannot implement {@link Convertible} or + * {@link Generator}. + */ + public interface Convertor + { + public void toJSON(Object obj, Output out); + + public Object fromJSON(Map object); + } + + /** + * JSON Generator. A class that can add it's JSON representation directly to + * a StringBuffer. This is useful for object instances that are frequently + * converted and wish to avoid multiple Conversions + */ + public interface Generator + { + public void addJSON(Appendable buffer); + } + + /** + * A Literal JSON generator A utility instance of {@link JSON.Generator} + * that holds a pre-generated string on JSON text. + */ + public static class Literal implements Generator + { + private String _json; + + /** + * Construct a literal JSON instance for use by + * {@link JSON#toString(Object)}. If {@link Log#isDebugEnabled()} is + * true, the JSON will be parsed to check validity + * + * @param json + * A literal JSON string. + */ + public Literal(String json) + { + if (LOG.isDebugEnabled()) // TODO: Make this a configurable option on JSON instead! + parse(json); + _json = json; + } + + @Override + public String toString() + { + return _json; + } + + public void addJSON(Appendable buffer) + { + try + { + buffer.append(_json); + } + catch(IOException e) + { + throw new RuntimeException(e); + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/ajax/JSONCollectionConvertor.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,50 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.ajax; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import org.eclipse.jetty.util.Loader; + +public class JSONCollectionConvertor implements JSON.Convertor +{ + public void toJSON(Object obj, JSON.Output out) + { + out.addClass(obj.getClass()); + out.add("list", ((Collection)obj).toArray()); + } + + public Object fromJSON(Map object) + { + try + { + Collection result = (Collection)Loader.loadClass(getClass(), (String)object.get("class")).newInstance(); + Collections.addAll(result, (Object[])object.get("list")); + return result; + } + catch (Exception x) + { + if (x instanceof RuntimeException) + throw (RuntimeException)x; + throw new RuntimeException(x); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/ajax/JSONDateConvertor.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,107 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.ajax; + +import java.text.DateFormatSymbols; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; + +import org.eclipse.jetty.util.DateCache; +import org.eclipse.jetty.util.ajax.JSON.Output; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/* ------------------------------------------------------------ */ +/** +* Convert a {@link Date} to JSON. +* If fromJSON is true in the constructor, the JSON generated will +* be of the form {class="java.util.Date",value="1/1/1970 12:00 GMT"} +* If fromJSON is false, then only the string value of the date is generated. +*/ +public class JSONDateConvertor implements JSON.Convertor +{ + private static final Logger LOG = Log.getLogger(JSONDateConvertor.class); + + private final boolean _fromJSON; + private final DateCache _dateCache; + private final SimpleDateFormat _format; + + public JSONDateConvertor() + { + this(false); + } + + public JSONDateConvertor(boolean fromJSON) + { + this(DateCache.DEFAULT_FORMAT,TimeZone.getTimeZone("GMT"),fromJSON); + } + + public JSONDateConvertor(String format,TimeZone zone,boolean fromJSON) + { + _dateCache=new DateCache(format); + _dateCache.setTimeZone(zone); + _fromJSON=fromJSON; + _format=new SimpleDateFormat(format); + _format.setTimeZone(zone); + } + + public JSONDateConvertor(String format, TimeZone zone, boolean fromJSON, Locale locale) + { + _dateCache = new DateCache(format, locale); + _dateCache.setTimeZone(zone); + _fromJSON = fromJSON; + _format = new SimpleDateFormat(format, new DateFormatSymbols(locale)); + _format.setTimeZone(zone); + } + + public Object fromJSON(Map map) + { + if (!_fromJSON) + throw new UnsupportedOperationException(); + try + { + synchronized(_format) + { + return _format.parseObject((String)map.get("value")); + } + } + catch(Exception e) + { + LOG.warn(e); + } + return null; + } + + public void toJSON(Object obj, Output out) + { + String date = _dateCache.format((Date)obj); + if (_fromJSON) + { + out.addClass(obj.getClass()); + out.add("value",date); + } + else + { + out.add(date); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/ajax/JSONEnumConvertor.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,93 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.ajax; + +import java.lang.reflect.Method; +import java.util.Map; + +import org.eclipse.jetty.util.Loader; +import org.eclipse.jetty.util.ajax.JSON.Output; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/* ------------------------------------------------------------ */ +/** + * Convert an {@link Enum} to JSON. + * If fromJSON is true in the constructor, the JSON generated will + * be of the form {class="com.acme.TrafficLight",value="Green"} + * If fromJSON is false, then only the string value of the enum is generated. + * + * + */ +public class JSONEnumConvertor implements JSON.Convertor +{ + private static final Logger LOG = Log.getLogger(JSONEnumConvertor.class); + private boolean _fromJSON; + private Method _valueOf; + { + try + { + Class<?> e = Loader.loadClass(getClass(),"java.lang.Enum"); + _valueOf=e.getMethod("valueOf",Class.class,String.class); + } + catch(Exception e) + { + throw new RuntimeException("!Enums",e); + } + } + + public JSONEnumConvertor() + { + this(false); + } + + public JSONEnumConvertor(boolean fromJSON) + { + _fromJSON=fromJSON; + } + + public Object fromJSON(Map map) + { + if (!_fromJSON) + throw new UnsupportedOperationException(); + try + { + Class c=Loader.loadClass(getClass(),(String)map.get("class")); + return _valueOf.invoke(null,c,map.get("value")); + } + catch(Exception e) + { + LOG.warn(e); + } + return null; + } + + public void toJSON(Object obj, Output out) + { + if (_fromJSON) + { + out.addClass(obj.getClass()); + out.add("value",((Enum)obj).name()); + } + else + { + out.add(((Enum)obj).name()); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/ajax/JSONObjectConvertor.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,115 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.ajax; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jetty.util.ajax.JSON.Output; + +/* ------------------------------------------------------------ */ +/** + * Convert an Object to JSON using reflection on getters methods. + * + * + * + */ +public class JSONObjectConvertor implements JSON.Convertor +{ + private boolean _fromJSON; + private Set _excluded=null; + + public JSONObjectConvertor() + { + _fromJSON=false; + } + + public JSONObjectConvertor(boolean fromJSON) + { + _fromJSON=fromJSON; + } + + /* ------------------------------------------------------------ */ + /** + * @param fromJSON + * @param excluded An array of field names to exclude from the conversion + */ + public JSONObjectConvertor(boolean fromJSON,String[] excluded) + { + _fromJSON=fromJSON; + if (excluded!=null) + _excluded=new HashSet(Arrays.asList(excluded)); + } + + public Object fromJSON(Map map) + { + if (_fromJSON) + throw new UnsupportedOperationException(); + return map; + } + + public void toJSON(Object obj, Output out) + { + try + { + Class c=obj.getClass(); + + if (_fromJSON) + out.addClass(obj.getClass()); + + Method[] methods = obj.getClass().getMethods(); + + for (int i=0;i<methods.length;i++) + { + Method m=methods[i]; + if (!Modifier.isStatic(m.getModifiers()) && + m.getParameterTypes().length==0 && + m.getReturnType()!=null && + m.getDeclaringClass()!=Object.class) + { + String name=m.getName(); + if (name.startsWith("is")) + name=name.substring(2,3).toLowerCase(Locale.ENGLISH)+name.substring(3); + else if (name.startsWith("get")) + name=name.substring(3,4).toLowerCase(Locale.ENGLISH)+name.substring(4); + else + continue; + + if (includeField(name,obj,m)) + out.add(name, m.invoke(obj,(Object[])null)); + } + } + } + catch (Throwable e) + { + throw new IllegalArgumentException(e); + } + } + + protected boolean includeField(String name, Object o, Method m) + { + return _excluded==null || !_excluded.contains(name); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/ajax/JSONPojoConvertor.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,431 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.ajax; + +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jetty.util.ajax.JSON.Output; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +/** + * Converts POJOs to JSON and vice versa. + * The key difference: + * - returns the actual object from Convertor.fromJSON (JSONObjectConverter returns a Map) + * - the getters/setters are resolved at initialization (JSONObjectConverter resolves it at runtime) + * - correctly sets the number fields + * + */ +public class JSONPojoConvertor implements JSON.Convertor +{ + private static final Logger LOG = Log.getLogger(JSONPojoConvertor.class); + public static final Object[] GETTER_ARG = new Object[]{}, NULL_ARG = new Object[]{null}; + private static final Map<Class<?>, NumberType> __numberTypes = new HashMap<Class<?>, NumberType>(); + + public static NumberType getNumberType(Class<?> clazz) + { + return __numberTypes.get(clazz); + } + + protected boolean _fromJSON; + protected Class<?> _pojoClass; + protected Map<String,Method> _getters = new HashMap<String,Method>(); + protected Map<String,Setter> _setters = new HashMap<String,Setter>(); + protected Set<String> _excluded; + + /** + * @param pojoClass The class to convert + */ + public JSONPojoConvertor(Class<?> pojoClass) + { + this(pojoClass, (Set<String>)null, true); + } + + /** + * @param pojoClass The class to convert + * @param excluded The fields to exclude + */ + public JSONPojoConvertor(Class<?> pojoClass, String[] excluded) + { + this(pojoClass, new HashSet<String>(Arrays.asList(excluded)), true); + } + + /** + * @param pojoClass The class to convert + * @param excluded The fields to exclude + */ + public JSONPojoConvertor(Class<?> pojoClass, Set<String> excluded) + { + this(pojoClass, excluded, true); + } + + /** + * @param pojoClass The class to convert + * @param excluded The fields to exclude + * @param fromJSON If true, add a class field to the JSON + */ + public JSONPojoConvertor(Class<?> pojoClass, Set<String> excluded, boolean fromJSON) + { + _pojoClass = pojoClass; + _excluded = excluded; + _fromJSON = fromJSON; + init(); + } + + /** + * @param pojoClass The class to convert + * @param fromJSON If true, add a class field to the JSON + */ + public JSONPojoConvertor(Class<?> pojoClass, boolean fromJSON) + { + this(pojoClass, (Set<String>)null, fromJSON); + } + + /* ------------------------------------------------------------ */ + protected void init() + { + Method[] methods = _pojoClass.getMethods(); + for (int i=0;i<methods.length;i++) + { + Method m=methods[i]; + if (!Modifier.isStatic(m.getModifiers()) && m.getDeclaringClass()!=Object.class) + { + String name=m.getName(); + switch(m.getParameterTypes().length) + { + case 0: + + if(m.getReturnType()!=null) + { + if (name.startsWith("is") && name.length()>2) + name=name.substring(2,3).toLowerCase(Locale.ENGLISH)+name.substring(3); + else if (name.startsWith("get") && name.length()>3) + name=name.substring(3,4).toLowerCase(Locale.ENGLISH)+name.substring(4); + else + break; + if(includeField(name, m)) + addGetter(name, m); + } + break; + case 1: + if (name.startsWith("set") && name.length()>3) + { + name=name.substring(3,4).toLowerCase(Locale.ENGLISH)+name.substring(4); + if(includeField(name, m)) + addSetter(name, m); + } + break; + } + } + } + } + + /* ------------------------------------------------------------ */ + protected void addGetter(String name, Method method) + { + _getters.put(name, method); + } + + /* ------------------------------------------------------------ */ + protected void addSetter(String name, Method method) + { + _setters.put(name, new Setter(name, method)); + } + + /* ------------------------------------------------------------ */ + protected Setter getSetter(String name) + { + return _setters.get(name); + } + + /* ------------------------------------------------------------ */ + protected boolean includeField(String name, Method m) + { + return _excluded==null || !_excluded.contains(name); + } + + /* ------------------------------------------------------------ */ + protected int getExcludedCount() + { + return _excluded==null ? 0 : _excluded.size(); + } + + /* ------------------------------------------------------------ */ + public Object fromJSON(Map object) + { + Object obj = null; + try + { + obj = _pojoClass.newInstance(); + } + catch(Exception e) + { + // TODO return Map instead? + throw new RuntimeException(e); + } + + setProps(obj, object); + return obj; + } + + /* ------------------------------------------------------------ */ + public int setProps(Object obj, Map<?,?> props) + { + int count = 0; + for(Iterator<?> iterator = props.entrySet().iterator(); iterator.hasNext();) + { + Map.Entry<?, ?> entry = (Map.Entry<?,?>) iterator.next(); + Setter setter = getSetter((String)entry.getKey()); + if(setter!=null) + { + try + { + setter.invoke(obj, entry.getValue()); + count++; + } + catch(Exception e) + { + // TODO throw exception? + LOG.warn(_pojoClass.getName()+"#"+setter.getPropertyName()+" not set from "+ + (entry.getValue().getClass().getName())+"="+entry.getValue().toString()); + log(e); + } + } + } + return count; + } + + /* ------------------------------------------------------------ */ + public void toJSON(Object obj, Output out) + { + if(_fromJSON) + out.addClass(_pojoClass); + for(Map.Entry<String,Method> entry : _getters.entrySet()) + { + try + { + out.add(entry.getKey(), entry.getValue().invoke(obj, GETTER_ARG)); + } + catch(Exception e) + { + // TODO throw exception? + LOG.warn("{} property '{}' excluded. (errors)", _pojoClass.getName(), + entry.getKey()); + log(e); + } + } + } + + /* ------------------------------------------------------------ */ + protected void log(Throwable t) + { + LOG.ignore(t); + } + + /* ------------------------------------------------------------ */ + public static class Setter + { + protected String _propertyName; + protected Method _setter; + protected NumberType _numberType; + protected Class<?> _type; + protected Class<?> _componentType; + + public Setter(String propertyName, Method method) + { + _propertyName = propertyName; + _setter = method; + _type = method.getParameterTypes()[0]; + _numberType = __numberTypes.get(_type); + if(_numberType==null && _type.isArray()) + { + _componentType = _type.getComponentType(); + _numberType = __numberTypes.get(_componentType); + } + } + + public String getPropertyName() + { + return _propertyName; + } + + public Method getMethod() + { + return _setter; + } + + public NumberType getNumberType() + { + return _numberType; + } + + public Class<?> getType() + { + return _type; + } + + public Class<?> getComponentType() + { + return _componentType; + } + + public boolean isPropertyNumber() + { + return _numberType!=null; + } + + public void invoke(Object obj, Object value) throws IllegalArgumentException, + IllegalAccessException, InvocationTargetException + { + if(value==null) + _setter.invoke(obj, NULL_ARG); + else + invokeObject(obj, value); + } + + protected void invokeObject(Object obj, Object value) throws IllegalArgumentException, + IllegalAccessException, InvocationTargetException + { + + if (_type.isEnum()) + { + if (value instanceof Enum) + _setter.invoke(obj, new Object[]{value}); + else + _setter.invoke(obj, new Object[]{Enum.valueOf((Class<? extends Enum>)_type,value.toString())}); + } + else if(_numberType!=null && value instanceof Number) + { + _setter.invoke(obj, new Object[]{_numberType.getActualValue((Number)value)}); + } + else if (Character.TYPE.equals(_type) || Character.class.equals(_type)) + { + _setter.invoke(obj, new Object[]{String.valueOf(value).charAt(0)}); + } + else if(_componentType!=null && value.getClass().isArray()) + { + if(_numberType==null) + { + int len = Array.getLength(value); + Object array = Array.newInstance(_componentType, len); + try + { + System.arraycopy(value, 0, array, 0, len); + } + catch(Exception e) + { + // unusual array with multiple types + LOG.ignore(e); + _setter.invoke(obj, new Object[]{value}); + return; + } + _setter.invoke(obj, new Object[]{array}); + } + else + { + Object[] old = (Object[])value; + Object array = Array.newInstance(_componentType, old.length); + try + { + for(int i=0; i<old.length; i++) + Array.set(array, i, _numberType.getActualValue((Number)old[i])); + } + catch(Exception e) + { + // unusual array with multiple types + LOG.ignore(e); + _setter.invoke(obj, new Object[]{value}); + return; + } + _setter.invoke(obj, new Object[]{array}); + } + } + else + _setter.invoke(obj, new Object[]{value}); + } + } + + public interface NumberType + { + public Object getActualValue(Number number); + } + + public static final NumberType SHORT = new NumberType() + { + public Object getActualValue(Number number) + { + return new Short(number.shortValue()); + } + }; + + public static final NumberType INTEGER = new NumberType() + { + public Object getActualValue(Number number) + { + return new Integer(number.intValue()); + } + }; + + public static final NumberType FLOAT = new NumberType() + { + public Object getActualValue(Number number) + { + return new Float(number.floatValue()); + } + }; + + public static final NumberType LONG = new NumberType() + { + public Object getActualValue(Number number) + { + return number instanceof Long ? number : new Long(number.longValue()); + } + }; + + public static final NumberType DOUBLE = new NumberType() + { + public Object getActualValue(Number number) + { + return number instanceof Double ? number : new Double(number.doubleValue()); + } + }; + + static + { + __numberTypes.put(Short.class, SHORT); + __numberTypes.put(Short.TYPE, SHORT); + __numberTypes.put(Integer.class, INTEGER); + __numberTypes.put(Integer.TYPE, INTEGER); + __numberTypes.put(Long.class, LONG); + __numberTypes.put(Long.TYPE, LONG); + __numberTypes.put(Float.class, FLOAT); + __numberTypes.put(Float.TYPE, FLOAT); + __numberTypes.put(Double.class, DOUBLE); + __numberTypes.put(Double.TYPE, DOUBLE); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/ajax/JSONPojoConvertorFactory.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,110 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.ajax; + +import java.util.Map; + +import org.eclipse.jetty.util.Loader; +import org.eclipse.jetty.util.ajax.JSON.Convertor; +import org.eclipse.jetty.util.ajax.JSON.Output; + +public class JSONPojoConvertorFactory implements JSON.Convertor +{ + private final JSON _json; + private final boolean _fromJson; + + public JSONPojoConvertorFactory(JSON json) + { + if (json==null) + { + throw new IllegalArgumentException(); + } + _json=json; + _fromJson=true; + } + + /* ------------------------------------------------------------ */ + /** + * @param json The JSON instance to use + * @param fromJSON If true, the class name of the objects is included + * in the generated JSON and is used to instantiate the object when + * JSON is parsed (otherwise a Map is used). + */ + public JSONPojoConvertorFactory(JSON json,boolean fromJSON) + { + if (json==null) + { + throw new IllegalArgumentException(); + } + _json=json; + _fromJson=fromJSON; + } + + /* ------------------------------------------------------------ */ + public void toJSON(Object obj, Output out) + { + String clsName=obj.getClass().getName(); + Convertor convertor=_json.getConvertorFor(clsName); + if (convertor==null) + { + try + { + Class cls=Loader.loadClass(JSON.class,clsName); + convertor=new JSONPojoConvertor(cls,_fromJson); + _json.addConvertorFor(clsName, convertor); + } + catch (ClassNotFoundException e) + { + JSON.LOG.warn(e); + } + } + if (convertor!=null) + { + convertor.toJSON(obj, out); + } + } + + public Object fromJSON(Map object) + { + Map map=object; + String clsName=(String)map.get("class"); + if (clsName!=null) + { + Convertor convertor=_json.getConvertorFor(clsName); + if (convertor==null) + { + try + { + Class cls=Loader.loadClass(JSON.class,clsName); + convertor=new JSONPojoConvertor(cls,_fromJson); + _json.addConvertorFor(clsName, convertor); + } + catch (ClassNotFoundException e) + { + JSON.LOG.warn(e); + } + } + if (convertor!=null) + { + return convertor.fromJSON(object); + } + } + return map; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/component/AbstractLifeCycle.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,217 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.component; + +import java.util.concurrent.CopyOnWriteArrayList; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + * Basic implementation of the life cycle interface for components. + * + * + */ +public abstract class AbstractLifeCycle implements LifeCycle +{ + private static final Logger LOG = Log.getLogger(AbstractLifeCycle.class); + public static final String STOPPED="STOPPED"; + public static final String FAILED="FAILED"; + public static final String STARTING="STARTING"; + public static final String STARTED="STARTED"; + public static final String STOPPING="STOPPING"; + public static final String RUNNING="RUNNING"; + + private final Object _lock = new Object(); + private final int __FAILED = -1, __STOPPED = 0, __STARTING = 1, __STARTED = 2, __STOPPING = 3; + private volatile int _state = __STOPPED; + + protected final CopyOnWriteArrayList<LifeCycle.Listener> _listeners=new CopyOnWriteArrayList<LifeCycle.Listener>(); + + protected void doStart() throws Exception + { + } + + protected void doStop() throws Exception + { + } + + public final void start() throws Exception + { + synchronized (_lock) + { + try + { + if (_state == __STARTED || _state == __STARTING) + return; + setStarting(); + doStart(); + setStarted(); + } + catch (Exception e) + { + setFailed(e); + throw e; + } + catch (Error e) + { + setFailed(e); + throw e; + } + } + } + + public final void stop() throws Exception + { + synchronized (_lock) + { + try + { + if (_state == __STOPPING || _state == __STOPPED) + return; + setStopping(); + doStop(); + setStopped(); + } + catch (Exception e) + { + setFailed(e); + throw e; + } + catch (Error e) + { + setFailed(e); + throw e; + } + } + } + + public boolean isRunning() + { + final int state = _state; + + return state == __STARTED || state == __STARTING; + } + + public boolean isStarted() + { + return _state == __STARTED; + } + + public boolean isStarting() + { + return _state == __STARTING; + } + + public boolean isStopping() + { + return _state == __STOPPING; + } + + public boolean isStopped() + { + return _state == __STOPPED; + } + + public boolean isFailed() + { + return _state == __FAILED; + } + + public void addLifeCycleListener(LifeCycle.Listener listener) + { + _listeners.add(listener); + } + + public void removeLifeCycleListener(LifeCycle.Listener listener) + { + _listeners.remove(listener); + } + + public String getState() + { + switch(_state) + { + case __FAILED: return FAILED; + case __STARTING: return STARTING; + case __STARTED: return STARTED; + case __STOPPING: return STOPPING; + case __STOPPED: return STOPPED; + } + return null; + } + + public static String getState(LifeCycle lc) + { + if (lc.isStarting()) return STARTING; + if (lc.isStarted()) return STARTED; + if (lc.isStopping()) return STOPPING; + if (lc.isStopped()) return STOPPED; + return FAILED; + } + + private void setStarted() + { + _state = __STARTED; + LOG.debug(STARTED+" {}",this); + for (Listener listener : _listeners) + listener.lifeCycleStarted(this); + } + + private void setStarting() + { + LOG.debug("starting {}",this); + _state = __STARTING; + for (Listener listener : _listeners) + listener.lifeCycleStarting(this); + } + + private void setStopping() + { + LOG.debug("stopping {}",this); + _state = __STOPPING; + for (Listener listener : _listeners) + listener.lifeCycleStopping(this); + } + + private void setStopped() + { + _state = __STOPPED; + LOG.debug("{} {}",STOPPED,this); + for (Listener listener : _listeners) + listener.lifeCycleStopped(this); + } + + private void setFailed(Throwable th) + { + _state = __FAILED; + LOG.warn(FAILED+" " + this+": "+th,th); + for (Listener listener : _listeners) + listener.lifeCycleFailure(this,th); + } + + public static abstract class AbstractLifeCycleListener implements LifeCycle.Listener + { + public void lifeCycleFailure(LifeCycle event, Throwable cause) {} + public void lifeCycleStarted(LifeCycle event) {} + public void lifeCycleStarting(LifeCycle event) {} + public void lifeCycleStopped(LifeCycle event) {} + public void lifeCycleStopping(LifeCycle event) {} + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/component/AggregateLifeCycle.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,441 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.component; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + * An AggregateLifeCycle is an {@link LifeCycle} implementation for a collection of contained beans. + * <p> + * Beans can be added the AggregateLifeCycle either as managed beans or as unmanaged beans. A managed bean is started, stopped and destroyed with the aggregate. + * An unmanaged bean is associated with the aggregate for the purposes of {@link #dump()}, but it's lifecycle must be managed externally. + * <p> + * When a bean is added, if it is a {@link LifeCycle} and it is already started, then it is assumed to be an unmanaged bean. + * Otherwise the methods {@link #addBean(Object, boolean)}, {@link #manage(Object)} and {@link #unmanage(Object)} can be used to + * explicitly control the life cycle relationship. + * <p> + * If adding a bean that is shared between multiple {@link AggregateLifeCycle} instances, then it should be started before being added, so it is unmanaged, or + * the API must be used to explicitly set it as unmanaged. + * <p> + */ +public class AggregateLifeCycle extends AbstractLifeCycle implements Destroyable, Dumpable +{ + private static final Logger LOG = Log.getLogger(AggregateLifeCycle.class); + private final List<Bean> _beans=new CopyOnWriteArrayList<Bean>(); + private boolean _started=false; + + private class Bean + { + Bean(Object b) + { + _bean=b; + } + final Object _bean; + volatile boolean _managed=true; + + public String toString() + { + return "{"+_bean+","+_managed+"}"; + } + } + + /* ------------------------------------------------------------ */ + /** + * Start the managed lifecycle beans in the order they were added. + * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart() + */ + @Override + protected void doStart() throws Exception + { + for (Bean b:_beans) + { + if (b._managed && b._bean instanceof LifeCycle) + { + LifeCycle l=(LifeCycle)b._bean; + if (!l.isRunning()) + l.start(); + } + } + // indicate that we are started, so that addBean will start other beans added. + _started=true; + super.doStart(); + } + + /* ------------------------------------------------------------ */ + /** + * Stop the joined lifecycle beans in the reverse order they were added. + * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart() + */ + @Override + protected void doStop() throws Exception + { + _started=false; + super.doStop(); + List<Bean> reverse = new ArrayList<Bean>(_beans); + Collections.reverse(reverse); + for (Bean b:reverse) + { + if (b._managed && b._bean instanceof LifeCycle) + { + LifeCycle l=(LifeCycle)b._bean; + if (l.isRunning()) + l.stop(); + } + } + } + + + /* ------------------------------------------------------------ */ + /** + * Destroy the joined Destroyable beans in the reverse order they were added. + * @see org.eclipse.jetty.util.component.Destroyable#destroy() + */ + public void destroy() + { + List<Bean> reverse = new ArrayList<Bean>(_beans); + Collections.reverse(reverse); + for (Bean b:reverse) + { + if (b._bean instanceof Destroyable && b._managed) + { + Destroyable d=(Destroyable)b._bean; + d.destroy(); + } + } + _beans.clear(); + } + + + /* ------------------------------------------------------------ */ + /** Is the bean contained in the aggregate. + * @param bean + * @return True if the aggregate contains the bean + */ + public boolean contains(Object bean) + { + for (Bean b:_beans) + if (b._bean==bean) + return true; + return false; + } + + /* ------------------------------------------------------------ */ + /** Is the bean joined to the aggregate. + * @param bean + * @return True if the aggregate contains the bean and it is joined + */ + public boolean isManaged(Object bean) + { + for (Bean b:_beans) + if (b._bean==bean) + return b._managed; + return false; + } + + /* ------------------------------------------------------------ */ + /** + * Add an associated bean. + * If the bean is a {@link LifeCycle}, then it will be managed if it is not + * already started and umanaged if it is already started. The {@link #addBean(Object, boolean)} + * method should be used if this is not correct, or the {@link #manage(Object)} and {@link #unmanage(Object)} + * methods may be used after an add to change the status. + * @param o the bean object to add + * @return true if the bean was added or false if it has already been added. + */ + public boolean addBean(Object o) + { + // beans are joined unless they are started lifecycles + return addBean(o,!((o instanceof LifeCycle)&&((LifeCycle)o).isStarted())); + } + + /* ------------------------------------------------------------ */ + /** Add an associated lifecycle. + * @param o The lifecycle to add + * @param managed True if the LifeCycle is to be joined, otherwise it will be disjoint. + * @return true if bean was added, false if already present. + */ + public boolean addBean(Object o, boolean managed) + { + if (contains(o)) + return false; + + Bean b = new Bean(o); + b._managed=managed; + _beans.add(b); + + if (o instanceof LifeCycle) + { + LifeCycle l=(LifeCycle)o; + + // Start the bean if we are started + if (managed && _started) + { + try + { + l.start(); + } + catch(Exception e) + { + throw new RuntimeException (e); + } + } + } + return true; + } + + /* ------------------------------------------------------------ */ + /** + * Manage a bean by this aggregate, so that it is started/stopped/destroyed with the + * aggregate lifecycle. + * @param bean The bean to manage (must already have been added). + */ + public void manage(Object bean) + { + for (Bean b :_beans) + { + if (b._bean==bean) + { + b._managed=true; + return; + } + } + throw new IllegalArgumentException(); + } + + /* ------------------------------------------------------------ */ + /** + * Unmanage a bean by this aggregate, so that it is not started/stopped/destroyed with the + * aggregate lifecycle. + * @param bean The bean to manage (must already have been added). + */ + public void unmanage(Object bean) + { + for (Bean b :_beans) + { + if (b._bean==bean) + { + b._managed=false; + return; + } + } + throw new IllegalArgumentException(); + } + + /* ------------------------------------------------------------ */ + /** Get dependent beans + * @return List of beans. + */ + public Collection<Object> getBeans() + { + return getBeans(Object.class); + } + + /* ------------------------------------------------------------ */ + /** Get dependent beans of a specific class + * @see #addBean(Object) + * @param clazz + * @return List of beans. + */ + public <T> List<T> getBeans(Class<T> clazz) + { + ArrayList<T> beans = new ArrayList<T>(); + for (Bean b:_beans) + { + if (clazz.isInstance(b._bean)) + beans.add((T)(b._bean)); + } + return beans; + } + + + /* ------------------------------------------------------------ */ + /** Get dependent beans of a specific class. + * If more than one bean of the type exist, the first is returned. + * @see #addBean(Object) + * @param clazz + * @return bean or null + */ + public <T> T getBean(Class<T> clazz) + { + for (Bean b:_beans) + { + if (clazz.isInstance(b._bean)) + return (T)b._bean; + } + + return null; + } + + /* ------------------------------------------------------------ */ + /** + * Remove all associated bean. + */ + public void removeBeans () + { + _beans.clear(); + } + + /* ------------------------------------------------------------ */ + /** + * Remove an associated bean. + */ + public boolean removeBean (Object o) + { + Iterator<Bean> i = _beans.iterator(); + while(i.hasNext()) + { + Bean b=i.next(); + if (b._bean==o) + { + _beans.remove(b); + return true; + } + } + return false; + } + + /* ------------------------------------------------------------ */ + public void dumpStdErr() + { + try + { + dump(System.err,""); + } + catch (IOException e) + { + LOG.warn(e); + } + } + + /* ------------------------------------------------------------ */ + public String dump() + { + return dump(this); + } + + /* ------------------------------------------------------------ */ + public static String dump(Dumpable dumpable) + { + StringBuilder b = new StringBuilder(); + try + { + dumpable.dump(b,""); + } + catch (IOException e) + { + LOG.warn(e); + } + return b.toString(); + } + + /* ------------------------------------------------------------ */ + public void dump(Appendable out) throws IOException + { + dump(out,""); + } + + /* ------------------------------------------------------------ */ + protected void dumpThis(Appendable out) throws IOException + { + out.append(String.valueOf(this)).append(" - ").append(getState()).append("\n"); + } + + /* ------------------------------------------------------------ */ + public static void dumpObject(Appendable out,Object o) throws IOException + { + try + { + if (o instanceof LifeCycle) + out.append(String.valueOf(o)).append(" - ").append((AbstractLifeCycle.getState((LifeCycle)o))).append("\n"); + else + out.append(String.valueOf(o)).append("\n"); + } + catch(Throwable th) + { + out.append(" => ").append(th.toString()).append('\n'); + } + } + + /* ------------------------------------------------------------ */ + public void dump(Appendable out,String indent) throws IOException + { + dumpThis(out); + int size=_beans.size(); + if (size==0) + return; + int i=0; + for (Bean b : _beans) + { + i++; + + out.append(indent).append(" +- "); + if (b._managed) + { + if (b._bean instanceof Dumpable) + ((Dumpable)b._bean).dump(out,indent+(i==size?" ":" | ")); + else + dumpObject(out,b._bean); + } + else + dumpObject(out,b._bean); + } + + if (i!=size) + out.append(indent).append(" |\n"); + } + + /* ------------------------------------------------------------ */ + public static void dump(Appendable out,String indent,Collection<?>... collections) throws IOException + { + if (collections.length==0) + return; + int size=0; + for (Collection<?> c : collections) + size+=c.size(); + if (size==0) + return; + + int i=0; + for (Collection<?> c : collections) + { + for (Object o : c) + { + i++; + out.append(indent).append(" +- "); + + if (o instanceof Dumpable) + ((Dumpable)o).dump(out,indent+(i==size?" ":" | ")); + else + dumpObject(out,o); + } + + if (i!=size) + out.append(indent).append(" |\n"); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/component/Container.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,305 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.component; +import java.lang.ref.WeakReference; +import java.util.EventListener; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/* ------------------------------------------------------------ */ +/** Container. + * This class allows a containment events to be generated from update methods. + * + * The style of usage is: <pre> + * public void setFoo(Foo foo) + * { + * getContainer().update(this,this.foo,foo,"foo"); + * this.foo=foo; + * } + * + * public void setBars(Bar[] bars) + * { + * getContainer().update(this,this.bars,bars,"bar"); + * this.bars=bars; + * } + * </pre> + */ +public class Container +{ + private static final Logger LOG = Log.getLogger(Container.class); + private final CopyOnWriteArrayList<Container.Listener> _listeners=new CopyOnWriteArrayList<Container.Listener>(); + + public void addEventListener(Container.Listener listener) + { + _listeners.add(listener); + } + + public void removeEventListener(Container.Listener listener) + { + _listeners.remove(listener); + } + + /* ------------------------------------------------------------ */ + /** Update single parent to child relationship. + * @param parent The parent of the child. + * @param oldChild The previous value of the child. If this is non null and differs from <code>child</code>, then a remove event is generated. + * @param child The current child. If this is non null and differs from <code>oldChild</code>, then an add event is generated. + * @param relationship The name of the relationship + */ + public void update(Object parent, Object oldChild, final Object child, String relationship) + { + if (oldChild!=null && !oldChild.equals(child)) + remove(parent,oldChild,relationship); + if (child!=null && !child.equals(oldChild)) + add(parent,child,relationship); + } + + /* ------------------------------------------------------------ */ + /** Update single parent to child relationship. + * @param parent The parent of the child. + * @param oldChild The previous value of the child. If this is non null and differs from <code>child</code>, then a remove event is generated. + * @param child The current child. If this is non null and differs from <code>oldChild</code>, then an add event is generated. + * @param relationship The name of the relationship + * @param addRemove If true add/remove is called for the new/old children as well as the relationships + */ + public void update(Object parent, Object oldChild, final Object child, String relationship,boolean addRemove) + { + if (oldChild!=null && !oldChild.equals(child)) + { + remove(parent,oldChild,relationship); + if (addRemove) + removeBean(oldChild); + } + + if (child!=null && !child.equals(oldChild)) + { + if (addRemove) + addBean(child); + add(parent,child,relationship); + } + } + + /* ------------------------------------------------------------ */ + /** Update multiple parent to child relationship. + * @param parent The parent of the child. + * @param oldChildren The previous array of children. A remove event is generated for any child in this array but not in the <code>children</code> array. + * This array is modified and children that remain in the new children array are nulled out of the old children array. + * @param children The current array of children. An add event is generated for any child in this array but not in the <code>oldChildren</code> array. + * @param relationship The name of the relationship + */ + public void update(Object parent, Object[] oldChildren, final Object[] children, String relationship) + { + update(parent,oldChildren,children,relationship,false); + } + + /* ------------------------------------------------------------ */ + /** Update multiple parent to child relationship. + * @param parent The parent of the child. + * @param oldChildren The previous array of children. A remove event is generated for any child in this array but not in the <code>children</code> array. + * This array is modified and children that remain in the new children array are nulled out of the old children array. + * @param children The current array of children. An add event is generated for any child in this array but not in the <code>oldChildren</code> array. + * @param relationship The name of the relationship + * @param addRemove If true add/remove is called for the new/old children as well as the relationships + */ + public void update(Object parent, Object[] oldChildren, final Object[] children, String relationship, boolean addRemove) + { + Object[] newChildren = null; + if (children!=null) + { + newChildren = new Object[children.length]; + + for (int i=children.length;i-->0;) + { + boolean new_child=true; + if (oldChildren!=null) + { + for (int j=oldChildren.length;j-->0;) + { + if (children[i]!=null && children[i].equals(oldChildren[j])) + { + oldChildren[j]=null; + new_child=false; + } + } + } + if (new_child) + newChildren[i]=children[i]; + } + } + + if (oldChildren!=null) + { + for (int i=oldChildren.length;i-->0;) + { + if (oldChildren[i]!=null) + { + remove(parent,oldChildren[i],relationship); + if (addRemove) + removeBean(oldChildren[i]); + } + } + } + + if (newChildren!=null) + { + for (int i=0;i<newChildren.length;i++) + if (newChildren[i]!=null) + { + if (addRemove) + addBean(newChildren[i]); + add(parent,newChildren[i],relationship); + } + } + } + + /* ------------------------------------------------------------ */ + public void addBean(Object obj) + { + if (_listeners!=null) + { + for (int i=0; i<LazyList.size(_listeners); i++) + { + Listener listener=(Listener)LazyList.get(_listeners, i); + listener.addBean(obj); + } + } + } + + /* ------------------------------------------------------------ */ + public void removeBean(Object obj) + { + if (_listeners!=null) + { + for (int i=0; i<LazyList.size(_listeners); i++) + ((Listener)LazyList.get(_listeners, i)).removeBean(obj); + } + } + + /* ------------------------------------------------------------ */ + /** Add a parent child relationship + * @param parent + * @param child + * @param relationship + */ + private void add(Object parent, Object child, String relationship) + { + if (LOG.isDebugEnabled()) + LOG.debug("Container "+parent+" + "+child+" as "+relationship); + if (_listeners!=null) + { + Relationship event=new Relationship(this,parent,child,relationship); + for (int i=0; i<LazyList.size(_listeners); i++) + ((Listener)LazyList.get(_listeners, i)).add(event); + } + } + + /* ------------------------------------------------------------ */ + /** remove a parent child relationship + * @param parent + * @param child + * @param relationship + */ + private void remove(Object parent, Object child, String relationship) + { + if (LOG.isDebugEnabled()) + LOG.debug("Container "+parent+" - "+child+" as "+relationship); + if (_listeners!=null) + { + Relationship event=new Relationship(this,parent,child,relationship); + for (int i=0; i<LazyList.size(_listeners); i++) + ((Listener)LazyList.get(_listeners, i)).remove(event); + } + } + + /* ------------------------------------------------------------ */ + /** A Container event. + * @see Listener + */ + public static class Relationship + { + private final WeakReference<Object> _parent; + private final WeakReference<Object> _child; + private String _relationship; + private Container _container; + + private Relationship(Container container, Object parent,Object child, String relationship) + { + _container=container; + _parent=new WeakReference<Object>(parent); + _child=new WeakReference<Object>(child); + _relationship=relationship; + } + + public Container getContainer() + { + return _container; + } + + public Object getChild() + { + return _child.get(); + } + + public Object getParent() + { + return _parent.get(); + } + + public String getRelationship() + { + return _relationship; + } + + @Override + public String toString() + { + return _parent+"---"+_relationship+"-->"+_child; + } + + @Override + public int hashCode() + { + return _parent.hashCode()+_child.hashCode()+_relationship.hashCode(); + } + + @Override + public boolean equals(Object o) + { + if (o==null || !(o instanceof Relationship)) + return false; + Relationship r = (Relationship)o; + return r._parent.get()==_parent.get() && r._child.get()==_child.get() && r._relationship.equals(_relationship); + } + } + + /* ------------------------------------------------------------ */ + /** Listener. + * A listener for Container events. + */ + public interface Listener extends EventListener + { + public void addBean(Object bean); + public void removeBean(Object bean); + public void add(Container.Relationship relationship); + public void remove(Container.Relationship relationship); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/component/Destroyable.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,31 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.component; + + +/** + * <p>A Destroyable is an object which can be destroyed.</p> + * <p>Typically a Destroyable is a {@link LifeCycle} component that can hold onto + * resources over multiple start/stop cycles. A call to destroy will release all + * resources and will prevent any further start/stop cycles from being successful.</p> + */ +public interface Destroyable +{ + void destroy(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/component/Dumpable.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,27 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.component; + +import java.io.IOException; + +public interface Dumpable +{ + String dump(); + void dump(Appendable out,String indent) throws IOException; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/component/FileDestroyable.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,88 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.component; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.resource.Resource; + +public class FileDestroyable implements Destroyable +{ + private static final Logger LOG = Log.getLogger(FileDestroyable.class); + final List<File> _files = new ArrayList<File>(); + + public FileDestroyable() + { + } + + public FileDestroyable(String file) throws IOException + { + _files.add(Resource.newResource(file).getFile()); + } + + public FileDestroyable(File file) + { + _files.add(file); + } + + public void addFile(String file) throws IOException + { + _files.add(Resource.newResource(file).getFile()); + } + + public void addFile(File file) + { + _files.add(file); + } + + public void addFiles(Collection<File> files) + { + _files.addAll(files); + } + + public void removeFile(String file) throws IOException + { + _files.remove(Resource.newResource(file).getFile()); + } + + public void removeFile(File file) + { + _files.remove(file); + } + + public void destroy() + { + for (File file : _files) + { + if (file.exists()) + { + LOG.debug("Destroy {}",file); + IO.delete(file); + } + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/component/FileNoticeLifeCycleListener.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,80 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.component; + +import java.io.FileWriter; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + + +/* ------------------------------------------------------------ */ +/** A LifeCycle Listener that writes state changes to a file. + * <p>This can be used with the jetty.sh script to wait for successful startup. + */ +public class FileNoticeLifeCycleListener implements LifeCycle.Listener +{ + Logger LOG = Log.getLogger(FileNoticeLifeCycleListener.class); + + private final String _filename; + + public FileNoticeLifeCycleListener(String filename) + { + _filename=filename; + } + + private void writeState(String action, LifeCycle lifecycle) + { + try + { + FileWriter out = new FileWriter(_filename,true); + out.append(action).append(" ").append(lifecycle.toString()).append("\n"); + out.close(); + } + catch(Exception e) + { + LOG.warn(e); + } + } + + public void lifeCycleStarting(LifeCycle event) + { + writeState("STARTING",event); + } + + public void lifeCycleStarted(LifeCycle event) + { + writeState("STARTED",event); + } + + public void lifeCycleFailure(LifeCycle event, Throwable cause) + { + writeState("FAILED",event); + } + + public void lifeCycleStopping(LifeCycle event) + { + writeState("STOPPING",event); + } + + public void lifeCycleStopped(LifeCycle event) + { + writeState("STOPPED",event); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/component/LifeCycle.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,119 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.component; + +import java.util.EventListener; + +/* ------------------------------------------------------------ */ +/** + * The lifecycle interface for generic components. + * <br /> + * Classes implementing this interface have a defined life cycle + * defined by the methods of this interface. + * + * + */ +public interface LifeCycle +{ + /* ------------------------------------------------------------ */ + /** + * Starts the component. + * @throws Exception If the component fails to start + * @see #isStarted() + * @see #stop() + * @see #isFailed() + */ + public void start() + throws Exception; + + /* ------------------------------------------------------------ */ + /** + * Stops the component. + * The component may wait for current activities to complete + * normally, but it can be interrupted. + * @exception Exception If the component fails to stop + * @see #isStopped() + * @see #start() + * @see #isFailed() + */ + public void stop() + throws Exception; + + /* ------------------------------------------------------------ */ + /** + * @return true if the component is starting or has been started. + */ + public boolean isRunning(); + + /* ------------------------------------------------------------ */ + /** + * @return true if the component has been started. + * @see #start() + * @see #isStarting() + */ + public boolean isStarted(); + + /* ------------------------------------------------------------ */ + /** + * @return true if the component is starting. + * @see #isStarted() + */ + public boolean isStarting(); + + /* ------------------------------------------------------------ */ + /** + * @return true if the component is stopping. + * @see #isStopped() + */ + public boolean isStopping(); + + /* ------------------------------------------------------------ */ + /** + * @return true if the component has been stopped. + * @see #stop() + * @see #isStopping() + */ + public boolean isStopped(); + + /* ------------------------------------------------------------ */ + /** + * @return true if the component has failed to start or has failed to stop. + */ + public boolean isFailed(); + + /* ------------------------------------------------------------ */ + public void addLifeCycleListener(LifeCycle.Listener listener); + + /* ------------------------------------------------------------ */ + public void removeLifeCycleListener(LifeCycle.Listener listener); + + + /* ------------------------------------------------------------ */ + /** Listener. + * A listener for Lifecycle events. + */ + public interface Listener extends EventListener + { + public void lifeCycleStarting(LifeCycle event); + public void lifeCycleStarted(LifeCycle event); + public void lifeCycleFailure(LifeCycle event,Throwable cause); + public void lifeCycleStopping(LifeCycle event); + public void lifeCycleStopped(LifeCycle event); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/component/jmx/AggregateLifeCycle-mbean.properties Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,2 @@ +AggregateLifeCycle: A LifeCycle holding other LifeCycles +dumpStdErr():Object:INFO:Dump the nested Object state to StdErr \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/component/jmx/Dumpable-mbean.properties Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,2 @@ +Dumpable: Dumpable Object +dump():Object:INFO:Dump the nested Object state as a String \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/component/jmx/LifeCycle-mbean.properties Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,9 @@ +LifeCycle: Startable object +start(): Starts the instance +stop(): Stops the instance +running: Instance is started or starting +started: Instance is started +starting: Instance is starting +stopping: Instance is stopping +stopped: Instance is stopped +failed: Instance is failed \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/log/AbstractLogger.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,77 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.log; + + +/* ------------------------------------------------------------ */ +/** Abstract Logger. + * Manages the atomic registration of the logger by name. + */ +public abstract class AbstractLogger implements Logger +{ + public final Logger getLogger(String name) + { + if (isBlank(name)) + return this; + + final String basename = getName(); + final String fullname = (isBlank(basename) || Log.getRootLogger()==this)?name:(basename + "." + name); + + Logger logger = Log.getLoggers().get(fullname); + if (logger == null) + { + Logger newlog = newLogger(fullname); + + logger = Log.getMutableLoggers().putIfAbsent(fullname,newlog); + if (logger == null) + logger=newlog; + } + + return logger; + } + + + protected abstract Logger newLogger(String fullname); + + /** + * A more robust form of name blank test. Will return true for null names, and names that have only whitespace + * + * @param name + * the name to test + * @return true for null or blank name, false if any non-whitespace character is found. + */ + private static boolean isBlank(String name) + { + if (name == null) + { + return true; + } + int size = name.length(); + char c; + for (int i = 0; i < size; i++) + { + c = name.charAt(i); + if (!Character.isWhitespace(c)) + { + return false; + } + } + return true; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/log/JavaUtilLog.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,163 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.log; + +import java.util.logging.Level; + +/** + * <p> + * Implementation of Jetty {@link Logger} based on {@link java.util.logging.Logger}. + * </p> + * + * <p> + * You can also set the logger level using <a href="http://java.sun.com/j2se/1.5.0/docs/guide/logging/overview.html"> + * standard java.util.logging configuration</a>. + * </p> + */ +public class JavaUtilLog extends AbstractLogger +{ + private Level configuredLevel; + private java.util.logging.Logger _logger; + + public JavaUtilLog() + { + this("org.eclipse.jetty.util.log"); + } + + public JavaUtilLog(String name) + { + _logger = java.util.logging.Logger.getLogger(name); + if (Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.DEBUG", "false"))) + { + _logger.setLevel(Level.FINE); + } + configuredLevel = _logger.getLevel(); + } + + public String getName() + { + return _logger.getName(); + } + + public void warn(String msg, Object... args) + { + _logger.log(Level.WARNING, format(msg, args)); + } + + public void warn(Throwable thrown) + { + warn("", thrown); + } + + public void warn(String msg, Throwable thrown) + { + _logger.log(Level.WARNING, msg, thrown); + } + + public void info(String msg, Object... args) + { + _logger.log(Level.INFO, format(msg, args)); + } + + public void info(Throwable thrown) + { + info("", thrown); + } + + public void info(String msg, Throwable thrown) + { + _logger.log(Level.INFO, msg, thrown); + } + + public boolean isDebugEnabled() + { + return _logger.isLoggable(Level.FINE); + } + + public void setDebugEnabled(boolean enabled) + { + if (enabled) + { + configuredLevel = _logger.getLevel(); + _logger.setLevel(Level.FINE); + } + else + { + _logger.setLevel(configuredLevel); + } + } + + public void debug(String msg, Object... args) + { + _logger.log(Level.FINE, format(msg, args)); + } + + public void debug(Throwable thrown) + { + debug("", thrown); + } + + public void debug(String msg, Throwable thrown) + { + _logger.log(Level.FINE, msg, thrown); + } + + /** + * Create a Child Logger of this Logger. + */ + protected Logger newLogger(String fullname) + { + return new JavaUtilLog(fullname); + } + + public void ignore(Throwable ignored) + { + if (Log.isIgnored()) + { + warn(Log.IGNORED, ignored); + } + } + + private String format(String msg, Object... args) + { + msg = String.valueOf(msg); // Avoids NPE + String braces = "{}"; + StringBuilder builder = new StringBuilder(); + int start = 0; + for (Object arg : args) + { + int bracesIndex = msg.indexOf(braces, start); + if (bracesIndex < 0) + { + builder.append(msg.substring(start)); + builder.append(" "); + builder.append(arg); + start = msg.length(); + } + else + { + builder.append(msg.substring(start, bracesIndex)); + builder.append(String.valueOf(arg)); + start = bracesIndex + braces.length(); + } + } + builder.append(msg.substring(start)); + return builder.toString(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/log/JettyAwareLogger.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,624 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.log; + +import org.slf4j.Marker; +import org.slf4j.helpers.FormattingTuple; +import org.slf4j.helpers.MessageFormatter; + +/** + * JettyAwareLogger is used to fix a FQCN bug that arises from how Jetty + * Log uses an indirect slf4j implementation. + * + * https://bugs.eclipse.org/bugs/show_bug.cgi?id=276670 + */ +class JettyAwareLogger implements org.slf4j.Logger +{ + private static final int DEBUG = org.slf4j.spi.LocationAwareLogger.DEBUG_INT; + private static final int ERROR = org.slf4j.spi.LocationAwareLogger.ERROR_INT; + private static final int INFO = org.slf4j.spi.LocationAwareLogger.INFO_INT; + private static final int TRACE = org.slf4j.spi.LocationAwareLogger.TRACE_INT; + private static final int WARN = org.slf4j.spi.LocationAwareLogger.WARN_INT; + + private static final String FQCN = Slf4jLog.class.getName(); + private final org.slf4j.spi.LocationAwareLogger _logger; + + public JettyAwareLogger(org.slf4j.spi.LocationAwareLogger logger) + { + _logger = logger; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#getName() + */ + public String getName() + { + return _logger.getName(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#isTraceEnabled() + */ + public boolean isTraceEnabled() + { + return _logger.isTraceEnabled(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#trace(java.lang.String) + */ + public void trace(String msg) + { + log(null, TRACE, msg, null, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#trace(java.lang.String, java.lang.Object) + */ + public void trace(String format, Object arg) + { + log(null, TRACE, format, new Object[]{arg}, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#trace(java.lang.String, java.lang.Object, java.lang.Object) + */ + public void trace(String format, Object arg1, Object arg2) + { + log(null, TRACE, format, new Object[]{arg1,arg2}, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#trace(java.lang.String, java.lang.Object[]) + */ + public void trace(String format, Object[] argArray) + { + log(null, TRACE, format, argArray, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#trace(java.lang.String, java.lang.Throwable) + */ + public void trace(String msg, Throwable t) + { + log(null, TRACE, msg, null, t); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#isTraceEnabled(org.slf4j.Marker) + */ + public boolean isTraceEnabled(Marker marker) + { + return _logger.isTraceEnabled(marker); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#trace(org.slf4j.Marker, java.lang.String) + */ + public void trace(Marker marker, String msg) + { + log(marker, TRACE, msg, null, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#trace(org.slf4j.Marker, java.lang.String, java.lang.Object) + */ + public void trace(Marker marker, String format, Object arg) + { + log(marker, TRACE, format, new Object[]{arg}, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#trace(org.slf4j.Marker, java.lang.String, java.lang.Object, java.lang.Object) + */ + public void trace(Marker marker, String format, Object arg1, Object arg2) + { + log(marker, TRACE, format, new Object[]{arg1,arg2}, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#trace(org.slf4j.Marker, java.lang.String, java.lang.Object[]) + */ + public void trace(Marker marker, String format, Object[] argArray) + { + log(marker, TRACE, format, argArray, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#trace(org.slf4j.Marker, java.lang.String, java.lang.Throwable) + */ + public void trace(Marker marker, String msg, Throwable t) + { + log(marker, TRACE, msg, null, t); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#isDebugEnabled() + */ + public boolean isDebugEnabled() + { + return _logger.isDebugEnabled(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#debug(java.lang.String) + */ + public void debug(String msg) + { + log(null, DEBUG, msg, null, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#debug(java.lang.String, java.lang.Object) + */ + public void debug(String format, Object arg) + { + log(null, DEBUG, format, new Object[]{arg}, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#debug(java.lang.String, java.lang.Object, java.lang.Object) + */ + public void debug(String format, Object arg1, Object arg2) + { + log(null, DEBUG, format, new Object[]{arg1,arg2}, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#debug(java.lang.String, java.lang.Object[]) + */ + public void debug(String format, Object[] argArray) + { + log(null, DEBUG, format, argArray, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#debug(java.lang.String, java.lang.Throwable) + */ + public void debug(String msg, Throwable t) + { + log(null, DEBUG, msg, null, t); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#isDebugEnabled(org.slf4j.Marker) + */ + public boolean isDebugEnabled(Marker marker) + { + return _logger.isDebugEnabled(marker); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#debug(org.slf4j.Marker, java.lang.String) + */ + public void debug(Marker marker, String msg) + { + log(marker, DEBUG, msg, null, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#debug(org.slf4j.Marker, java.lang.String, java.lang.Object) + */ + public void debug(Marker marker, String format, Object arg) + { + log(marker, DEBUG, format, new Object[]{arg}, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#debug(org.slf4j.Marker, java.lang.String, java.lang.Object, java.lang.Object) + */ + public void debug(Marker marker, String format, Object arg1, Object arg2) + { + log(marker, DEBUG, format, new Object[]{arg1,arg2}, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#debug(org.slf4j.Marker, java.lang.String, java.lang.Object[]) + */ + public void debug(Marker marker, String format, Object[] argArray) + { + log(marker, DEBUG, format, argArray, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#debug(org.slf4j.Marker, java.lang.String, java.lang.Throwable) + */ + public void debug(Marker marker, String msg, Throwable t) + { + log(marker, DEBUG, msg, null, t); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#isInfoEnabled() + */ + public boolean isInfoEnabled() + { + return _logger.isInfoEnabled(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#info(java.lang.String) + */ + public void info(String msg) + { + log(null, INFO, msg, null, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#info(java.lang.String, java.lang.Object) + */ + public void info(String format, Object arg) + { + log(null, INFO, format, new Object[]{arg}, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#info(java.lang.String, java.lang.Object, java.lang.Object) + */ + public void info(String format, Object arg1, Object arg2) + { + log(null, INFO, format, new Object[]{arg1,arg2}, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#info(java.lang.String, java.lang.Object[]) + */ + public void info(String format, Object[] argArray) + { + log(null, INFO, format, argArray, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#info(java.lang.String, java.lang.Throwable) + */ + public void info(String msg, Throwable t) + { + log(null, INFO, msg, null, t); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#isInfoEnabled(org.slf4j.Marker) + */ + public boolean isInfoEnabled(Marker marker) + { + return _logger.isInfoEnabled(marker); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#info(org.slf4j.Marker, java.lang.String) + */ + public void info(Marker marker, String msg) + { + log(marker, INFO, msg, null, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#info(org.slf4j.Marker, java.lang.String, java.lang.Object) + */ + public void info(Marker marker, String format, Object arg) + { + log(marker, INFO, format, new Object[]{arg}, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#info(org.slf4j.Marker, java.lang.String, java.lang.Object, java.lang.Object) + */ + public void info(Marker marker, String format, Object arg1, Object arg2) + { + log(marker, INFO, format, new Object[]{arg1,arg2}, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#info(org.slf4j.Marker, java.lang.String, java.lang.Object[]) + */ + public void info(Marker marker, String format, Object[] argArray) + { + log(marker, INFO, format, argArray, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#info(org.slf4j.Marker, java.lang.String, java.lang.Throwable) + */ + public void info(Marker marker, String msg, Throwable t) + { + log(marker, INFO, msg, null, t); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#isWarnEnabled() + */ + public boolean isWarnEnabled() + { + return _logger.isWarnEnabled(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#warn(java.lang.String) + */ + public void warn(String msg) + { + log(null, WARN, msg, null, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#warn(java.lang.String, java.lang.Object) + */ + public void warn(String format, Object arg) + { + log(null, WARN, format, new Object[]{arg}, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#warn(java.lang.String, java.lang.Object[]) + */ + public void warn(String format, Object[] argArray) + { + log(null, WARN, format, argArray, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#warn(java.lang.String, java.lang.Object, java.lang.Object) + */ + public void warn(String format, Object arg1, Object arg2) + { + log(null, WARN, format, new Object[]{arg1,arg2}, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#warn(java.lang.String, java.lang.Throwable) + */ + public void warn(String msg, Throwable t) + { + log(null, WARN, msg, null, t); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#isWarnEnabled(org.slf4j.Marker) + */ + public boolean isWarnEnabled(Marker marker) + { + return _logger.isWarnEnabled(marker); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#warn(org.slf4j.Marker, java.lang.String) + */ + public void warn(Marker marker, String msg) + { + log(marker, WARN, msg, null, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#warn(org.slf4j.Marker, java.lang.String, java.lang.Object) + */ + public void warn(Marker marker, String format, Object arg) + { + log(marker, WARN, format, new Object[]{arg}, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#warn(org.slf4j.Marker, java.lang.String, java.lang.Object, java.lang.Object) + */ + public void warn(Marker marker, String format, Object arg1, Object arg2) + { + log(marker, WARN, format, new Object[]{arg1,arg2}, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#warn(org.slf4j.Marker, java.lang.String, java.lang.Object[]) + */ + public void warn(Marker marker, String format, Object[] argArray) + { + log(marker, WARN, format, argArray, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#warn(org.slf4j.Marker, java.lang.String, java.lang.Throwable) + */ + public void warn(Marker marker, String msg, Throwable t) + { + log(marker, WARN, msg, null, t); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#isErrorEnabled() + */ + public boolean isErrorEnabled() + { + return _logger.isErrorEnabled(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#error(java.lang.String) + */ + public void error(String msg) + { + log(null, ERROR, msg, null, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#error(java.lang.String, java.lang.Object) + */ + public void error(String format, Object arg) + { + log(null, ERROR, format, new Object[]{arg}, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#error(java.lang.String, java.lang.Object, java.lang.Object) + */ + public void error(String format, Object arg1, Object arg2) + { + log(null, ERROR, format, new Object[]{arg1,arg2}, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#error(java.lang.String, java.lang.Object[]) + */ + public void error(String format, Object[] argArray) + { + log(null, ERROR, format, argArray, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#error(java.lang.String, java.lang.Throwable) + */ + public void error(String msg, Throwable t) + { + log(null, ERROR, msg, null, t); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#isErrorEnabled(org.slf4j.Marker) + */ + public boolean isErrorEnabled(Marker marker) + { + return _logger.isErrorEnabled(marker); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#error(org.slf4j.Marker, java.lang.String) + */ + public void error(Marker marker, String msg) + { + log(marker, ERROR, msg, null, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#error(org.slf4j.Marker, java.lang.String, java.lang.Object) + */ + public void error(Marker marker, String format, Object arg) + { + log(marker, ERROR, format, new Object[]{arg}, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#error(org.slf4j.Marker, java.lang.String, java.lang.Object, java.lang.Object) + */ + public void error(Marker marker, String format, Object arg1, Object arg2) + { + log(marker, ERROR, format, new Object[]{arg1,arg2}, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#error(org.slf4j.Marker, java.lang.String, java.lang.Object[]) + */ + public void error(Marker marker, String format, Object[] argArray) + { + log(marker, ERROR, format, argArray, null); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.slf4j.Logger#error(org.slf4j.Marker, java.lang.String, java.lang.Throwable) + */ + public void error(Marker marker, String msg, Throwable t) + { + log(marker, ERROR, msg, null, t); + } + + @Override + public String toString() + { + return _logger.toString(); + } + + private void log(Marker marker, int level, String msg, Object[] argArray, Throwable t) + { + if (argArray == null) + { + // Simple SLF4J Message (no args) + _logger.log(marker, FQCN, level, msg, null, t); + } + else + { + int loggerLevel = _logger.isTraceEnabled() ? TRACE : + _logger.isDebugEnabled() ? DEBUG : + _logger.isInfoEnabled() ? INFO : + _logger.isWarnEnabled() ? WARN : ERROR; + if (loggerLevel <= level) + { + // Don't assume downstream handles argArray properly. + // Do it the SLF4J way here to eliminate that as a bug. + FormattingTuple ft = MessageFormatter.arrayFormat(msg, argArray); + _logger.log(marker, FQCN, level, ft.getMessage(), null, t); + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/log/Log.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,462 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.log; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.net.URL; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.Loader; + +/** + * Logging. + * This class provides a static logging interface. If an instance of the + * org.slf4j.Logger class is found on the classpath, the static log methods + * are directed to a slf4j logger for "org.eclipse.log". Otherwise the logs + * are directed to stderr. + * <p> + * The "org.eclipse.jetty.util.log.class" system property can be used + * to select a specific logging implementation. + * <p> + * If the system property org.eclipse.jetty.util.log.IGNORED is set, + * then ignored exceptions are logged in detail. + * + * @see StdErrLog + * @see Slf4jLog + */ +public class Log +{ + public final static String EXCEPTION= "EXCEPTION "; + public final static String IGNORED= "IGNORED "; + + /** + * Logging Configuration Properties + */ + protected static Properties __props; + /** + * The {@link Logger} implementation class name + */ + public static String __logClass; + /** + * Legacy flag indicating if {@link Log#ignore(Throwable)} methods produce any output in the {@link Logger}s + */ + public static boolean __ignored; + + /** + * Hold loggers only. + */ + private final static ConcurrentMap<String, Logger> __loggers = new ConcurrentHashMap<String, Logger>(); + + + static + { + /* Instantiate a default configuration properties (empty) + */ + __props = new Properties(); + + AccessController.doPrivileged(new PrivilegedAction<Object>() + { + public Object run() + { + /* First see if the jetty-logging.properties object exists in the classpath. + * This is an optional feature used by embedded mode use, and test cases to allow for early + * configuration of the Log class in situations where access to the System.properties are + * either too late or just impossible. + */ + URL testProps = Loader.getResource(Log.class,"jetty-logging.properties",true); + if (testProps != null) + { + InputStream in = null; + try + { + in = testProps.openStream(); + __props.load(in); + } + catch (IOException e) + { + System.err.println("Unable to load " + testProps); + e.printStackTrace(System.err); + } + finally + { + IO.close(in); + } + } + + /* Now load the System.properties as-is into the __props, these values will override + * any key conflicts in __props. + */ + @SuppressWarnings("unchecked") + Enumeration<String> systemKeyEnum = (Enumeration<String>)System.getProperties().propertyNames(); + while (systemKeyEnum.hasMoreElements()) + { + String key = systemKeyEnum.nextElement(); + String val = System.getProperty(key); + //protect against application code insertion of non-String values (returned as null) + if (val != null) + __props.setProperty(key,val); + } + + /* Now use the configuration properties to configure the Log statics + */ + __logClass = __props.getProperty("org.eclipse.jetty.util.log.class","org.eclipse.jetty.util.log.Slf4jLog"); + __ignored = Boolean.parseBoolean(__props.getProperty("org.eclipse.jetty.util.log.IGNORED","false")); + return null; + } + }); + } + + private static Logger LOG; + private static boolean __initialized; + + public static boolean initialized() + { + if (LOG != null) + { + return true; + } + + synchronized (Log.class) + { + if (__initialized) + { + return LOG != null; + } + __initialized = true; + } + + try + { + Class<?> log_class = Loader.loadClass(Log.class, __logClass); + if (LOG == null || !LOG.getClass().equals(log_class)) + { + LOG = (Logger)log_class.newInstance(); + LOG.debug("Logging to {} via {}", LOG, log_class.getName()); + } + } + catch(Throwable e) + { + // Unable to load specified Logger implementation, default to standard logging. + initStandardLogging(e); + } + + return LOG != null; + } + + private static void initStandardLogging(Throwable e) + { + Class<?> log_class; + if(e != null && __ignored) + { + e.printStackTrace(); + } + + if (LOG == null) + { + log_class = StdErrLog.class; + LOG = new StdErrLog(); + LOG.debug("Logging to {} via {}", LOG, log_class.getName()); + } + } + + public static void setLog(Logger log) + { + Log.LOG = log; + } + + /** + * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)} + */ + @Deprecated + public static Logger getLog() + { + initialized(); + return LOG; + } + + /** + * Get the root logger. + * @return the root logger + */ + public static Logger getRootLogger() { + initialized(); + return LOG; + } + + static boolean isIgnored() + { + return __ignored; + } + + /** + * Set Log to parent Logger. + * <p> + * If there is a different Log class available from a parent classloader, + * call {@link #getLogger(String)} on it and construct a {@link LoggerLog} instance + * as this Log's Logger, so that logging is delegated to the parent Log. + * <p> + * This should be used if a webapp is using Log, but wishes the logging to be + * directed to the containers log. + * <p> + * If there is not parent Log, then this call is equivalent to<pre> + * Log.setLog(Log.getLogger(name)); + * </pre> + * @param name Logger name + */ + public static void setLogToParent(String name) + { + ClassLoader loader = Log.class.getClassLoader(); + if (loader!=null && loader.getParent()!=null) + { + try + { + Class<?> uberlog = loader.getParent().loadClass("org.eclipse.jetty.util.log.Log"); + Method getLogger = uberlog.getMethod("getLogger", new Class[]{String.class}); + Object logger = getLogger.invoke(null,name); + setLog(new LoggerLog(logger)); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + else + { + setLog(getLogger(name)); + } + } + + /** + * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)} + */ + @Deprecated + public static void debug(Throwable th) + { + if (!isDebugEnabled()) + return; + LOG.debug(EXCEPTION, th); + } + + /** + * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)} + */ + @Deprecated + public static void debug(String msg) + { + if (!initialized()) + return; + LOG.debug(msg); + } + + /** + * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)} + */ + @Deprecated + public static void debug(String msg, Object arg) + { + if (!initialized()) + return; + LOG.debug(msg, arg); + } + + /** + * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)} + */ + @Deprecated + public static void debug(String msg, Object arg0, Object arg1) + { + if (!initialized()) + return; + LOG.debug(msg, arg0, arg1); + } + + /** + * Ignore an exception unless trace is enabled. + * This works around the problem that log4j does not support the trace level. + * @param thrown the Throwable to ignore + */ + /** + * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)} + */ + @Deprecated + public static void ignore(Throwable thrown) + { + if (!initialized()) + return; + LOG.ignore(thrown); + } + + /** + * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)} + */ + @Deprecated + public static void info(String msg) + { + if (!initialized()) + return; + LOG.info(msg); + } + + /** + * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)} + */ + @Deprecated + public static void info(String msg, Object arg) + { + if (!initialized()) + return; + LOG.info(msg, arg); + } + + /** + * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)} + */ + @Deprecated + public static void info(String msg, Object arg0, Object arg1) + { + if (!initialized()) + return; + LOG.info(msg, arg0, arg1); + } + + /** + * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)} + */ + @Deprecated + public static boolean isDebugEnabled() + { + if (!initialized()) + return false; + return LOG.isDebugEnabled(); + } + + /** + * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)} + */ + @Deprecated + public static void warn(String msg) + { + if (!initialized()) + return; + LOG.warn(msg); + } + + /** + * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)} + */ + @Deprecated + public static void warn(String msg, Object arg) + { + if (!initialized()) + return; + LOG.warn(msg, arg); + } + + /** + * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)} + */ + @Deprecated + public static void warn(String msg, Object arg0, Object arg1) + { + if (!initialized()) + return; + LOG.warn(msg, arg0, arg1); + } + + /** + * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)} + */ + @Deprecated + public static void warn(String msg, Throwable th) + { + if (!initialized()) + return; + LOG.warn(msg, th); + } + + /** + * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)} + */ + @Deprecated + public static void warn(Throwable th) + { + if (!initialized()) + return; + LOG.warn(EXCEPTION, th); + } + + /** + * Obtain a named Logger based on the fully qualified class name. + * + * @param clazz + * the class to base the Logger name off of + * @return the Logger with the given name + */ + public static Logger getLogger(Class<?> clazz) + { + return getLogger(clazz.getName()); + } + + /** + * Obtain a named Logger or the default Logger if null is passed. + * @param name the Logger name + * @return the Logger with the given name + */ + public static Logger getLogger(String name) + { + if (!initialized()) + return null; + + if(name==null) + return LOG; + + Logger logger = __loggers.get(name); + if(logger==null) + logger = LOG.getLogger(name); + + return logger; + } + + static ConcurrentMap<String, Logger> getMutableLoggers() + { + return __loggers; + } + + /** + * Get a map of all configured {@link Logger} instances. + * + * @return a map of all configured {@link Logger} instances + */ + public static Map<String, Logger> getLoggers() + { + return Collections.unmodifiableMap(__loggers); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/log/Logger.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,113 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.log; + +/** + * A simple logging facade that is intended simply to capture the style of logging as used by Jetty. + */ +public interface Logger +{ + /** + * @return the name of this logger + */ + public String getName(); + + /** + * Formats and logs at warn level. + * @param msg the formatting string + * @param args the optional arguments + */ + public void warn(String msg, Object... args); + + /** + * Logs the given Throwable information at warn level + * @param thrown the Throwable to log + */ + public void warn(Throwable thrown); + + /** + * Logs the given message at warn level, with Throwable information. + * @param msg the message to log + * @param thrown the Throwable to log + */ + public void warn(String msg, Throwable thrown); + + /** + * Formats and logs at info level. + * @param msg the formatting string + * @param args the optional arguments + */ + public void info(String msg, Object... args); + + /** + * Logs the given Throwable information at info level + * @param thrown the Throwable to log + */ + public void info(Throwable thrown); + + /** + * Logs the given message at info level, with Throwable information. + * @param msg the message to log + * @param thrown the Throwable to log + */ + public void info(String msg, Throwable thrown); + + /** + * @return whether the debug level is enabled + */ + public boolean isDebugEnabled(); + + /** + * Mutator used to turn debug on programmatically. + * @param enabled whether to enable the debug level + */ + public void setDebugEnabled(boolean enabled); + + /** + * Formats and logs at debug level. + * @param msg the formatting string + * @param args the optional arguments + */ + public void debug(String msg, Object... args); + + /** + * Logs the given Throwable information at debug level + * @param thrown the Throwable to log + */ + public void debug(Throwable thrown); + + /** + * Logs the given message at debug level, with Throwable information. + * @param msg the message to log + * @param thrown the Throwable to log + */ + public void debug(String msg, Throwable thrown); + + /** + * @param name the name of the logger + * @return a logger with the given name + */ + public Logger getLogger(String name); + + /** + * Ignore an exception. + * <p>This should be used rather than an empty catch block. + */ + public void ignore(Throwable ignored); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/log/LoggerLog.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,213 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.log; + +import java.lang.reflect.Method; + +/** + * + */ +public class LoggerLog extends AbstractLogger +{ + private final Object _logger; + private final Method _debugMT; + private final Method _debugMAA; + private final Method _infoMT; + private final Method _infoMAA; + private final Method _warnMT; + private final Method _warnMAA; + private final Method _setDebugEnabledE; + private final Method _getLoggerN; + private final Method _getName; + private volatile boolean _debug; + + public LoggerLog(Object logger) + { + try + { + _logger = logger; + Class<?> lc = logger.getClass(); + _debugMT = lc.getMethod("debug", new Class[]{String.class, Throwable.class}); + _debugMAA = lc.getMethod("debug", new Class[]{String.class, Object[].class}); + _infoMT = lc.getMethod("info", new Class[]{String.class, Throwable.class}); + _infoMAA = lc.getMethod("info", new Class[]{String.class, Object[].class}); + _warnMT = lc.getMethod("warn", new Class[]{String.class, Throwable.class}); + _warnMAA = lc.getMethod("warn", new Class[]{String.class, Object[].class}); + Method _isDebugEnabled = lc.getMethod("isDebugEnabled"); + _setDebugEnabledE = lc.getMethod("setDebugEnabled", new Class[]{Boolean.TYPE}); + _getLoggerN = lc.getMethod("getLogger", new Class[]{String.class}); + _getName = lc.getMethod("getName"); + + _debug = (Boolean)_isDebugEnabled.invoke(_logger); + } + catch(Exception x) + { + throw new IllegalStateException(x); + } + } + + public String getName() + { + try + { + return (String)_getName.invoke(_logger); + } + catch (Exception e) + { + e.printStackTrace(); + return null; + } + } + + public void warn(String msg, Object... args) + { + try + { + _warnMAA.invoke(_logger, args); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + public void warn(Throwable thrown) + { + warn("", thrown); + } + + public void warn(String msg, Throwable thrown) + { + try + { + _warnMT.invoke(_logger, msg, thrown); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + public void info(String msg, Object... args) + { + try + { + _infoMAA.invoke(_logger, args); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + public void info(Throwable thrown) + { + info("", thrown); + } + + public void info(String msg, Throwable thrown) + { + try + { + _infoMT.invoke(_logger, msg, thrown); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + public boolean isDebugEnabled() + { + return _debug; + } + + public void setDebugEnabled(boolean enabled) + { + try + { + _setDebugEnabledE.invoke(_logger, enabled); + _debug = enabled; + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + public void debug(String msg, Object... args) + { + if (!_debug) + return; + + try + { + _debugMAA.invoke(_logger, args); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + public void debug(Throwable thrown) + { + debug("", thrown); + } + + public void debug(String msg, Throwable th) + { + if (!_debug) + return; + + try + { + _debugMT.invoke(_logger, msg, th); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + public void ignore(Throwable ignored) + { + if (Log.isIgnored()) + { + warn(Log.IGNORED, ignored); + } + } + + /** + * Create a Child Logger of this Logger. + */ + protected Logger newLogger(String fullname) + { + try + { + Object logger=_getLoggerN.invoke(_logger, fullname); + return new LoggerLog(logger); + } + catch (Exception e) + { + e.printStackTrace(); + return this; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/log/Slf4jLog.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,133 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.log; + + + +/** + * Slf4jLog Logger + */ +public class Slf4jLog extends AbstractLogger +{ + private final org.slf4j.Logger _logger; + + public Slf4jLog() throws Exception + { + this("org.eclipse.jetty.util.log"); + } + + public Slf4jLog(String name) + { + //NOTE: if only an slf4j-api jar is on the classpath, slf4j will use a NOPLogger + org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger( name ); + + // Fix LocationAwareLogger use to indicate FQCN of this class - + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=276670 + if (logger instanceof org.slf4j.spi.LocationAwareLogger) + { + _logger = new JettyAwareLogger((org.slf4j.spi.LocationAwareLogger)logger); + } + else + { + _logger = logger; + } + } + + public String getName() + { + return _logger.getName(); + } + + public void warn(String msg, Object... args) + { + _logger.warn(msg, args); + } + + public void warn(Throwable thrown) + { + warn("", thrown); + } + + public void warn(String msg, Throwable thrown) + { + _logger.warn(msg, thrown); + } + + public void info(String msg, Object... args) + { + _logger.info(msg, args); + } + + public void info(Throwable thrown) + { + info("", thrown); + } + + public void info(String msg, Throwable thrown) + { + _logger.info(msg, thrown); + } + + public void debug(String msg, Object... args) + { + _logger.debug(msg, args); + } + + public void debug(Throwable thrown) + { + debug("", thrown); + } + + public void debug(String msg, Throwable thrown) + { + _logger.debug(msg, thrown); + } + + public boolean isDebugEnabled() + { + return _logger.isDebugEnabled(); + } + + public void setDebugEnabled(boolean enabled) + { + warn("setDebugEnabled not implemented",null,null); + } + + /** + * Create a Child Logger of this Logger. + */ + protected Logger newLogger(String fullname) + { + return new Slf4jLog(fullname); + } + + public void ignore(Throwable ignored) + { + if (Log.isIgnored()) + { + warn(Log.IGNORED, ignored); + } + } + + @Override + public String toString() + { + return _logger.toString(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/log/StdErrLog.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,630 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.log; + +import java.io.PrintStream; +import java.security.AccessControlException; +import java.util.Properties; + +import org.eclipse.jetty.util.DateCache; + +/** + * StdErr Logging. This implementation of the Logging facade sends all logs to StdErr with minimal formatting. + * <p> + * If the system property "org.eclipse.jetty.LEVEL" is set to one of the following (ALL, DEBUG, INFO, WARN), then set + * the eclipse jetty root level logger level to that specified level. (Default level is INFO) + * <p> + * If the system property "org.eclipse.jetty.util.log.SOURCE" is set, then the source method/file of a log is logged. + * For named debuggers, the system property name+".SOURCE" is checked, eg "org.eclipse.jetty.util.log.stderr.SOURCE". + * If it is not not set, then "org.eclipse.jetty.util.log.SOURCE" is used as the default. + * <p> + * If the system property "org.eclipse.jetty.util.log.stderr.LONG" is set, then the full, unabbreviated name of the logger is + * used for logging. + */ +public class StdErrLog extends AbstractLogger +{ + private static final String EOL = System.getProperty("line.separator"); + private static DateCache _dateCache; + private static final Properties __props = new Properties(); + + private final static boolean __source = Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.SOURCE", + Log.__props.getProperty("org.eclipse.jetty.util.log.stderr.SOURCE","false"))); + private final static boolean __long = Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.stderr.LONG","false")); + + static + { + __props.putAll(Log.__props); + + String deprecatedProperties[] = + { "DEBUG", "org.eclipse.jetty.util.log.DEBUG", "org.eclipse.jetty.util.log.stderr.DEBUG" }; + + // Toss a message to users about deprecated system properties + for (String deprecatedProp : deprecatedProperties) + { + if (System.getProperty(deprecatedProp) != null) + { + System.err.printf("System Property [%s] has been deprecated! (Use org.eclipse.jetty.LEVEL=DEBUG instead)%n",deprecatedProp); + } + } + + try + { + _dateCache = new DateCache("yyyy-MM-dd HH:mm:ss"); + } + catch (Exception x) + { + x.printStackTrace(System.err); + } + } + + public static final int LEVEL_ALL = 0; + public static final int LEVEL_DEBUG = 1; + public static final int LEVEL_INFO = 2; + public static final int LEVEL_WARN = 3; + + private int _level = LEVEL_INFO; + // Level that this Logger was configured as (remembered in special case of .setDebugEnabled()) + private int _configuredLevel; + private PrintStream _stderr = null; + private boolean _source = __source; + // Print the long form names, otherwise use abbreviated + private boolean _printLongNames = __long; + // The full log name, as provided by the system. + private final String _name; + // The abbreviated log name (used by default, unless _long is specified) + private final String _abbrevname; + private boolean _hideStacks = false; + + public StdErrLog() + { + this(null); + } + + public StdErrLog(String name) + { + this(name,__props); + } + + public StdErrLog(String name, Properties props) + { + if (props!=null && props!=__props) + __props.putAll(props); + this._name = name == null?"":name; + this._abbrevname = condensePackageString(this._name); + this._level = getLoggingLevel(props,this._name); + this._configuredLevel = this._level; + + try + { + _source = Boolean.parseBoolean(props.getProperty(_name + ".SOURCE",Boolean.toString(_source))); + } + catch (AccessControlException ace) + { + _source = __source; + } + } + + /** + * Get the Logging Level for the provided log name. Using the FQCN first, then each package segment from longest to + * shortest. + * + * @param props + * the properties to check + * @param name + * the name to get log for + * @return the logging level + */ + public static int getLoggingLevel(Properties props, final String name) + { + // Calculate the level this named logger should operate under. + // Checking with FQCN first, then each package segment from longest to shortest. + String nameSegment = name; + + while ((nameSegment != null) && (nameSegment.length() > 0)) + { + String levelStr = props.getProperty(nameSegment + ".LEVEL"); + // System.err.printf("[StdErrLog.CONFIG] Checking for property [%s.LEVEL] = %s%n",nameSegment,levelStr); + int level = getLevelId(nameSegment + ".LEVEL",levelStr); + if (level != (-1)) + { + return level; + } + + // Trim and try again. + int idx = nameSegment.lastIndexOf('.'); + if (idx >= 0) + { + nameSegment = nameSegment.substring(0,idx); + } + else + { + nameSegment = null; + } + } + + // Default Logging Level + return getLevelId("log.LEVEL",props.getProperty("log.LEVEL","INFO")); + } + + protected static int getLevelId(String levelSegment, String levelName) + { + if (levelName == null) + { + return -1; + } + String levelStr = levelName.trim(); + if ("ALL".equalsIgnoreCase(levelStr)) + { + return LEVEL_ALL; + } + else if ("DEBUG".equalsIgnoreCase(levelStr)) + { + return LEVEL_DEBUG; + } + else if ("INFO".equalsIgnoreCase(levelStr)) + { + return LEVEL_INFO; + } + else if ("WARN".equalsIgnoreCase(levelStr)) + { + return LEVEL_WARN; + } + + System.err.println("Unknown StdErrLog level [" + levelSegment + "]=[" + levelStr + "], expecting only [ALL, DEBUG, INFO, WARN] as values."); + return -1; + } + + /** + * Condenses a classname by stripping down the package name to just the first character of each package name + * segment.Configured + * <p> + * + * <pre> + * Examples: + * "org.eclipse.jetty.test.FooTest" = "oejt.FooTest" + * "org.eclipse.jetty.server.logging.LogTest" = "orjsl.LogTest" + * </pre> + * + * @param classname + * the fully qualified class name + * @return the condensed name + */ + protected static String condensePackageString(String classname) + { + String parts[] = classname.split("\\."); + StringBuilder dense = new StringBuilder(); + for (int i = 0; i < (parts.length - 1); i++) + { + dense.append(parts[i].charAt(0)); + } + if (dense.length() > 0) + { + dense.append('.'); + } + dense.append(parts[parts.length - 1]); + return dense.toString(); + } + + public String getName() + { + return _name; + } + + public void setPrintLongNames(boolean printLongNames) + { + this._printLongNames = printLongNames; + } + + public boolean isPrintLongNames() + { + return this._printLongNames; + } + + public boolean isHideStacks() + { + return _hideStacks; + } + + public void setHideStacks(boolean hideStacks) + { + _hideStacks = hideStacks; + } + + /* ------------------------------------------------------------ */ + /** + * Is the source of a log, logged + * + * @return true if the class, method, file and line number of a log is logged. + */ + public boolean isSource() + { + return _source; + } + + /* ------------------------------------------------------------ */ + /** + * Set if a log source is logged. + * + * @param source + * true if the class, method, file and line number of a log is logged. + */ + public void setSource(boolean source) + { + _source = source; + } + + public void warn(String msg, Object... args) + { + if (_level <= LEVEL_WARN) + { + StringBuilder buffer = new StringBuilder(64); + format(buffer,":WARN:",msg,args); + (_stderr==null?System.err:_stderr).println(buffer); + } + } + + public void warn(Throwable thrown) + { + warn("",thrown); + } + + public void warn(String msg, Throwable thrown) + { + if (_level <= LEVEL_WARN) + { + StringBuilder buffer = new StringBuilder(64); + format(buffer,":WARN:",msg,thrown); + (_stderr==null?System.err:_stderr).println(buffer); + } + } + + public void info(String msg, Object... args) + { + if (_level <= LEVEL_INFO) + { + StringBuilder buffer = new StringBuilder(64); + format(buffer,":INFO:",msg,args); + (_stderr==null?System.err:_stderr).println(buffer); + } + } + + public void info(Throwable thrown) + { + info("",thrown); + } + + public void info(String msg, Throwable thrown) + { + if (_level <= LEVEL_INFO) + { + StringBuilder buffer = new StringBuilder(64); + format(buffer,":INFO:",msg,thrown); + (_stderr==null?System.err:_stderr).println(buffer); + } + } + + public boolean isDebugEnabled() + { + return (_level <= LEVEL_DEBUG); + } + + /** + * Legacy interface where a programmatic configuration of the logger level + * is done as a wholesale approach. + */ + public void setDebugEnabled(boolean enabled) + { + if (enabled) + { + this._level = LEVEL_DEBUG; + + for (Logger log : Log.getLoggers().values()) + { + if (log.getName().startsWith(getName()) && log instanceof StdErrLog) + ((StdErrLog)log).setLevel(LEVEL_DEBUG); + } + } + else + { + this._level = this._configuredLevel; + + for (Logger log : Log.getLoggers().values()) + { + if (log.getName().startsWith(getName()) && log instanceof StdErrLog) + ((StdErrLog)log).setLevel(((StdErrLog)log)._configuredLevel); + } + } + } + + public int getLevel() + { + return _level; + } + + /** + * Set the level for this logger. + * <p> + * Available values ({@link StdErrLog#LEVEL_ALL}, {@link StdErrLog#LEVEL_DEBUG}, {@link StdErrLog#LEVEL_INFO}, + * {@link StdErrLog#LEVEL_WARN}) + * + * @param level + * the level to set the logger to + */ + public void setLevel(int level) + { + this._level = level; + } + + public void setStdErrStream(PrintStream stream) + { + this._stderr = stream==System.err?null:stream; + } + + public void debug(String msg, Object... args) + { + if (_level <= LEVEL_DEBUG) + { + StringBuilder buffer = new StringBuilder(64); + format(buffer,":DBUG:",msg,args); + (_stderr==null?System.err:_stderr).println(buffer); + } + } + + public void debug(Throwable thrown) + { + debug("",thrown); + } + + public void debug(String msg, Throwable thrown) + { + if (_level <= LEVEL_DEBUG) + { + StringBuilder buffer = new StringBuilder(64); + format(buffer,":DBUG:",msg,thrown); + (_stderr==null?System.err:_stderr).println(buffer); + } + } + + private void format(StringBuilder buffer, String level, String msg, Object... args) + { + String d = _dateCache.now(); + int ms = _dateCache.lastMs(); + tag(buffer,d,ms,level); + format(buffer,msg,args); + } + + private void format(StringBuilder buffer, String level, String msg, Throwable thrown) + { + format(buffer,level,msg); + if (isHideStacks()) + { + format(buffer,String.valueOf(thrown)); + } + else + { + format(buffer,thrown); + } + } + + private void tag(StringBuilder buffer, String d, int ms, String tag) + { + buffer.setLength(0); + buffer.append(d); + if (ms > 99) + { + buffer.append('.'); + } + else if (ms > 9) + { + buffer.append(".0"); + } + else + { + buffer.append(".00"); + } + buffer.append(ms).append(tag); + if (_printLongNames) + { + buffer.append(_name); + } + else + { + buffer.append(_abbrevname); + } + buffer.append(':'); + if (_source) + { + Throwable source = new Throwable(); + StackTraceElement[] frames = source.getStackTrace(); + for (int i = 0; i < frames.length; i++) + { + final StackTraceElement frame = frames[i]; + String clazz = frame.getClassName(); + if (clazz.equals(StdErrLog.class.getName()) || clazz.equals(Log.class.getName())) + { + continue; + } + if (!_printLongNames && clazz.startsWith("org.eclipse.jetty.")) + { + buffer.append(condensePackageString(clazz)); + } + else + { + buffer.append(clazz); + } + buffer.append('#').append(frame.getMethodName()); + if (frame.getFileName() != null) + { + buffer.append('(').append(frame.getFileName()).append(':').append(frame.getLineNumber()).append(')'); + } + buffer.append(':'); + break; + } + } + } + + private void format(StringBuilder builder, String msg, Object... args) + { + if (msg == null) + { + msg = ""; + for (int i = 0; i < args.length; i++) + { + msg += "{} "; + } + } + String braces = "{}"; + int start = 0; + for (Object arg : args) + { + int bracesIndex = msg.indexOf(braces,start); + if (bracesIndex < 0) + { + escape(builder,msg.substring(start)); + builder.append(" "); + builder.append(arg); + start = msg.length(); + } + else + { + escape(builder,msg.substring(start,bracesIndex)); + builder.append(String.valueOf(arg)); + start = bracesIndex + braces.length(); + } + } + escape(builder,msg.substring(start)); + } + + private void escape(StringBuilder builder, String string) + { + for (int i = 0; i < string.length(); ++i) + { + char c = string.charAt(i); + if (Character.isISOControl(c)) + { + if (c == '\n') + { + builder.append('|'); + } + else if (c == '\r') + { + builder.append('<'); + } + else + { + builder.append('?'); + } + } + else + { + builder.append(c); + } + } + } + + private void format(StringBuilder buffer, Throwable thrown) + { + if (thrown == null) + { + buffer.append("null"); + } + else + { + buffer.append(EOL); + format(buffer,thrown.toString()); + StackTraceElement[] elements = thrown.getStackTrace(); + for (int i = 0; elements != null && i < elements.length; i++) + { + buffer.append(EOL).append("\tat "); + format(buffer,elements[i].toString()); + } + + Throwable cause = thrown.getCause(); + if (cause != null && cause != thrown) + { + buffer.append(EOL).append("Caused by: "); + format(buffer,cause); + } + } + } + + + /** + * Create a Child Logger of this Logger. + */ + protected Logger newLogger(String fullname) + { + StdErrLog logger = new StdErrLog(fullname); + // Preserve configuration for new loggers configuration + logger.setPrintLongNames(_printLongNames); + // Let Level come from configured Properties instead - sel.setLevel(_level); + logger.setSource(_source); + logger._stderr = this._stderr; + + // Force the child to have any programmatic configuration + if (_level!=_configuredLevel) + logger._level=_level; + + return logger; + } + + @Override + public String toString() + { + StringBuilder s = new StringBuilder(); + s.append("StdErrLog:"); + s.append(_name); + s.append(":LEVEL="); + switch (_level) + { + case LEVEL_ALL: + s.append("ALL"); + break; + case LEVEL_DEBUG: + s.append("DEBUG"); + break; + case LEVEL_INFO: + s.append("INFO"); + break; + case LEVEL_WARN: + s.append("WARN"); + break; + default: + s.append("?"); + break; + } + return s.toString(); + } + + public static void setProperties(Properties props) + { + __props.clear(); + __props.putAll(props); + } + + public void ignore(Throwable ignored) + { + if (_level <= LEVEL_ALL) + { + StringBuilder buffer = new StringBuilder(64); + format(buffer,":IGNORED:","",ignored); + (_stderr==null?System.err:_stderr).println(buffer); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/preventers/AWTLeakPreventer.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,47 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.preventers; + +import java.awt.Toolkit; + +/** + * AWTLeakPreventer + * + * See https://issues.jboss.org/browse/AS7-3733 + * + * The java.awt.Toolkit class has a static field that is the default toolkit. + * Creating the default toolkit causes the creation of an EventQueue, which has a + * classloader field initialized by the thread context class loader. + * + */ +public class AWTLeakPreventer extends AbstractLeakPreventer +{ + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader) + */ + @Override + public void prevent(ClassLoader loader) + { + LOG.debug("Pinning classloader for java.awt.EventQueue using "+loader); + Toolkit.getDefaultToolkit(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/preventers/AbstractLeakPreventer.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,62 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.preventers; + +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + * AbstractLeakPreventer + * + * Abstract base class for code that seeks to avoid pinning of webapp classloaders by using the jetty classloader to + * proactively call the code that pins them (generally pinned as static data members, or as static + * data members that are daemon threads (which use the context classloader)). + * + * Instances of subclasses of this class should be set with Server.addBean(), which will + * ensure that they are called when the Server instance starts up, which will have the jetty + * classloader in scope. + * + */ +public abstract class AbstractLeakPreventer extends AbstractLifeCycle +{ + protected static final Logger LOG = Log.getLogger(AbstractLeakPreventer.class); + + /* ------------------------------------------------------------ */ + abstract public void prevent(ClassLoader loader); + + + /* ------------------------------------------------------------ */ + @Override + protected void doStart() throws Exception + { + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + try + { + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + prevent(getClass().getClassLoader()); + super.doStart(); + } + finally + { + Thread.currentThread().setContextClassLoader(loader); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/preventers/AppContextLeakPreventer.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,41 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.preventers; + +import javax.imageio.ImageIO; + +/** + * AppContextLeakPreventer + * + * Cause the classloader that is pinned by AppContext.getAppContext() to be + * a container or system classloader, not a webapp classloader. + * + * Inspired by Tomcat JreMemoryLeakPrevention. + */ +public class AppContextLeakPreventer extends AbstractLeakPreventer +{ + /* ------------------------------------------------------------ */ + @Override + public void prevent(ClassLoader loader) + { + LOG.debug("Pinning classloader for AppContext.getContext() with "+loader); + ImageIO.getUseCache(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/preventers/DOMLeakPreventer.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,56 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.preventers; + +import javax.xml.parsers.DocumentBuilderFactory; + +/** + * DOMLeakPreventer + * + * See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6916498 + * + * Prevent the RuntimeException that is a static member of AbstractDOMParser + * from pinning a webapp classloader by causing it to be set here by a non-webapp classloader. + * + * Note that according to the bug report, a heap dump may not identify the GCRoot, making + * it difficult to identify the cause of the leak. + * + */ +public class DOMLeakPreventer extends AbstractLeakPreventer +{ + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader) + */ + @Override + public void prevent(ClassLoader loader) + { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + try + { + factory.newDocumentBuilder(); + } + catch (Exception e) + { + LOG.warn(e); + } + + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/preventers/DriverManagerLeakPreventer.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,42 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.preventers; + +import java.sql.DriverManager; + + +/** + * DriverManagerLeakPreventer + * + * Cause DriverManager.getCallerClassLoader() to be called, which will pin the classloader. + * + * Inspired by Tomcat JreMemoryLeakPrevention. + */ +public class DriverManagerLeakPreventer extends AbstractLeakPreventer +{ + /* ------------------------------------------------------------ */ + @Override + public void prevent(ClassLoader loader) + { + LOG.debug("Pinning DriverManager classloader with "+loader); + DriverManager.getDrivers(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/preventers/GCThreadLeakPreventer.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,64 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.preventers; + +import java.lang.reflect.Method; + +/** + * GCThreadLeakPreventer + * + * Prevents a call to sun.misc.GC.requestLatency pinning a webapp classloader + * by calling it with a non-webapp classloader. The problem appears to be that + * when this method is called, a daemon thread is created which takes the + * context classloader. A known caller of this method is the RMI impl. See + * http://stackoverflow.com/questions/6626680/does-java-garbage-collection-log-entry-full-gc-system-mean-some-class-called + * + * This preventer will start the thread with the longest possible interval, although + * subsequent calls can vary that. Recommend to only use this class if you're doing + * RMI. + * + * Inspired by Tomcat JreMemoryLeakPrevention. + * + */ +public class GCThreadLeakPreventer extends AbstractLeakPreventer +{ + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader) + */ + @Override + public void prevent(ClassLoader loader) + { + try + { + Class clazz = Class.forName("sun.misc.GC"); + Method requestLatency = clazz.getMethod("requestLatency", new Class[] {long.class}); + requestLatency.invoke(null, Long.valueOf(Long.MAX_VALUE-1)); + } + catch (ClassNotFoundException e) + { + LOG.ignore(e); + } + catch (Exception e) + { + LOG.warn(e); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/preventers/Java2DLeakPreventer.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,49 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.preventers; + +/** + * Java2DLeakPreventer + * + * Prevent pinning of webapp classloader by pre-loading sun.java2d.Disposer class + * before webapp classloaders are created. + * + * See https://issues.apache.org/bugzilla/show_bug.cgi?id=51687 + * + */ +public class Java2DLeakPreventer extends AbstractLeakPreventer +{ + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader) + */ + @Override + public void prevent(ClassLoader loader) + { + try + { + Class.forName("sun.java2d.Disposer", true, loader); + } + catch (ClassNotFoundException e) + { + LOG.ignore(e); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/preventers/LDAPLeakPreventer.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,51 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.preventers; + +/** + * LDAPLeakPreventer + * + * If com.sun.jndi.LdapPoolManager class is loaded and the system property + * com.sun.jndi.ldap.connect.pool.timeout is set to a nonzero value, a daemon + * thread is started which can pin a webapp classloader if it is the first to + * load the LdapPoolManager. + * + * Inspired by Tomcat JreMemoryLeakPrevention + * + */ +public class LDAPLeakPreventer extends AbstractLeakPreventer +{ + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader) + */ + @Override + public void prevent(ClassLoader loader) + { + try + { + Class.forName("com.sun.jndi.LdapPoolManager", true, loader); + } + catch (ClassNotFoundException e) + { + LOG.ignore(e); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/preventers/LoginConfigurationLeakPreventer.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,49 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.preventers; + +/** + * LoginConfigurationLeakPreventer + * + * The javax.security.auth.login.Configuration class keeps a static reference to the + * thread context classloader. We prevent a webapp context classloader being used for + * that by invoking the classloading here. + * + * Inspired by Tomcat JreMemoryLeakPrevention + */ +public class LoginConfigurationLeakPreventer extends AbstractLeakPreventer +{ + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader) + */ + @Override + public void prevent(ClassLoader loader) + { + try + { + Class.forName("javax.security.auth.login.Configuration", true, loader); + } + catch (ClassNotFoundException e) + { + LOG.warn(e); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/preventers/SecurityProviderLeakPreventer.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,44 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.preventers; + +import java.security.Security; + +/** + * SecurityProviderLeakPreventer + * + * Some security providers, such as sun.security.pkcs11.SunPKCS11 start a deamon thread, + * which will use the thread context classloader. Load them here to ensure the classloader + * is not a webapp classloader. + * + * Inspired by Tomcat JreMemoryLeakPrevention + */ +public class SecurityProviderLeakPreventer extends AbstractLeakPreventer +{ + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader) + */ + @Override + public void prevent(ClassLoader loader) + { + Security.getProviders(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/resource/BadResource.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,139 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.resource; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; + + +/* ------------------------------------------------------------ */ +/** Bad Resource. + * + * A Resource that is returned for a bade URL. Acts as a resource + * that does not exist and throws appropriate exceptions. + * + * + */ +class BadResource extends URLResource +{ + /* ------------------------------------------------------------ */ + private String _message=null; + + /* -------------------------------------------------------- */ + BadResource(URL url, String message) + { + super(url,null); + _message=message; + } + + + /* -------------------------------------------------------- */ + @Override + public boolean exists() + { + return false; + } + + /* -------------------------------------------------------- */ + @Override + public long lastModified() + { + return -1; + } + + /* -------------------------------------------------------- */ + @Override + public boolean isDirectory() + { + return false; + } + + /* --------------------------------------------------------- */ + @Override + public long length() + { + return -1; + } + + + /* ------------------------------------------------------------ */ + @Override + public File getFile() + { + return null; + } + + /* --------------------------------------------------------- */ + @Override + public InputStream getInputStream() throws IOException + { + throw new FileNotFoundException(_message); + } + + /* --------------------------------------------------------- */ + @Override + public OutputStream getOutputStream() + throws java.io.IOException, SecurityException + { + throw new FileNotFoundException(_message); + } + + /* --------------------------------------------------------- */ + @Override + public boolean delete() + throws SecurityException + { + throw new SecurityException(_message); + } + + /* --------------------------------------------------------- */ + @Override + public boolean renameTo( Resource dest) + throws SecurityException + { + throw new SecurityException(_message); + } + + /* --------------------------------------------------------- */ + @Override + public String[] list() + { + return null; + } + + /* ------------------------------------------------------------ */ + @Override + public void copyTo(File destination) + throws IOException + { + throw new SecurityException(_message); + } + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + return super.toString()+"; BadResource="+_message; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/resource/FileResource.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,400 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.resource; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.security.Permission; + +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + + +/* ------------------------------------------------------------ */ +/** File Resource. + * + * Handle resources of implied or explicit file type. + * This class can check for aliasing in the filesystem (eg case + * insensitivity). By default this is turned on, or it can be controlled + * by calling the static method @see FileResource#setCheckAliases(boolean) + * + */ +public class FileResource extends URLResource +{ + private static final Logger LOG = Log.getLogger(FileResource.class); + private static boolean __checkAliases = true; + + /* ------------------------------------------------------------ */ + private File _file; + private transient URL _alias=null; + private transient boolean _aliasChecked=false; + + /* ------------------------------------------------------------------------------- */ + /** setCheckAliases. + * @param checkAliases True of resource aliases are to be checked for (eg case insensitivity or 8.3 short names) and treated as not found. + */ + public static void setCheckAliases(boolean checkAliases) + { + __checkAliases=checkAliases; + } + + /* ------------------------------------------------------------------------------- */ + /** getCheckAliases. + * @return True of resource aliases are to be checked for (eg case insensitivity or 8.3 short names) and treated as not found. + */ + public static boolean getCheckAliases() + { + return __checkAliases; + } + + /* -------------------------------------------------------- */ + public FileResource(URL url) + throws IOException, URISyntaxException + { + super(url,null); + + try + { + // Try standard API to convert URL to file. + _file =new File(new URI(url.toString())); + } + catch (URISyntaxException e) + { + throw e; + } + catch (Exception e) + { + LOG.ignore(e); + try + { + // Assume that File.toURL produced unencoded chars. So try + // encoding them. + String file_url="file:"+URIUtil.encodePath(url.toString().substring(5)); + URI uri = new URI(file_url); + if (uri.getAuthority()==null) + _file = new File(uri); + else + _file = new File("//"+uri.getAuthority()+URIUtil.decodePath(url.getFile())); + } + catch (Exception e2) + { + LOG.ignore(e2); + + // Still can't get the file. Doh! try good old hack! + checkConnection(); + Permission perm = _connection.getPermission(); + _file = new File(perm==null?url.getFile():perm.getName()); + } + } + if (_file.isDirectory()) + { + if (!_urlString.endsWith("/")) + _urlString=_urlString+"/"; + } + else + { + if (_urlString.endsWith("/")) + _urlString=_urlString.substring(0,_urlString.length()-1); + } + + } + + /* -------------------------------------------------------- */ + FileResource(URL url, URLConnection connection, File file) + { + super(url,connection); + _file=file; + if (_file.isDirectory() && !_urlString.endsWith("/")) + _urlString=_urlString+"/"; + } + + /* -------------------------------------------------------- */ + @Override + public Resource addPath(String path) + throws IOException,MalformedURLException + { + URLResource r=null; + String url=null; + + path = org.eclipse.jetty.util.URIUtil.canonicalPath(path); + + if ("/".equals(path)) + return this; + else if (!isDirectory()) + { + r=(FileResource)super.addPath(path); + url=r._urlString; + } + else + { + if (path==null) + throw new MalformedURLException(); + + // treat all paths being added as relative + String rel=path; + if (path.startsWith("/")) + rel = path.substring(1); + + url=URIUtil.addPaths(_urlString,URIUtil.encodePath(rel)); + r=(URLResource)Resource.newResource(url); + } + + // Check for encoding aliases + // The encoded path should be a suffix of the resource (give or take a directory / ) + String encoded=URIUtil.encodePath(path); + int expected=r.toString().length()-encoded.length(); + int index = r._urlString.lastIndexOf(encoded, expected); + if (expected!=index && ((expected-1)!=index || path.endsWith("/") || !r.isDirectory())) + { + if (r instanceof FileResource) + { + ((FileResource)r)._alias=((FileResource)r)._file.getCanonicalFile().toURI().toURL(); + ((FileResource)r)._aliasChecked=true; + } + } + return r; + } + + + /* ------------------------------------------------------------ */ + @Override + public URL getAlias() + { + if (__checkAliases && !_aliasChecked) + { + try + { + String abs=_file.getAbsolutePath(); + String can=_file.getCanonicalPath(); + + if (abs.length()!=can.length() || !abs.equals(can)) + _alias=Resource.toURL(new File(can)); + + _aliasChecked=true; + + if (_alias!=null && LOG.isDebugEnabled()) + { + LOG.debug("ALIAS abs="+abs); + LOG.debug("ALIAS can="+can); + } + } + catch(Exception e) + { + LOG.warn(Log.EXCEPTION,e); + return getURL(); + } + } + return _alias; + } + + /* -------------------------------------------------------- */ + /** + * Returns true if the resource exists. + */ + @Override + public boolean exists() + { + return _file.exists(); + } + + /* -------------------------------------------------------- */ + /** + * Returns the last modified time + */ + @Override + public long lastModified() + { + return _file.lastModified(); + } + + /* -------------------------------------------------------- */ + /** + * Returns true if the respresenetd resource is a container/directory. + */ + @Override + public boolean isDirectory() + { + return _file.isDirectory(); + } + + /* --------------------------------------------------------- */ + /** + * Return the length of the resource + */ + @Override + public long length() + { + return _file.length(); + } + + + /* --------------------------------------------------------- */ + /** + * Returns the name of the resource + */ + @Override + public String getName() + { + return _file.getAbsolutePath(); + } + + /* ------------------------------------------------------------ */ + /** + * Returns an File representing the given resource or NULL if this + * is not possible. + */ + @Override + public File getFile() + { + return _file; + } + + /* --------------------------------------------------------- */ + /** + * Returns an input stream to the resource + */ + @Override + public InputStream getInputStream() throws IOException + { + return new FileInputStream(_file); + } + + /* --------------------------------------------------------- */ + /** + * Returns an output stream to the resource + */ + @Override + public OutputStream getOutputStream() + throws java.io.IOException, SecurityException + { + return new FileOutputStream(_file); + } + + /* --------------------------------------------------------- */ + /** + * Deletes the given resource + */ + @Override + public boolean delete() + throws SecurityException + { + return _file.delete(); + } + + /* --------------------------------------------------------- */ + /** + * Rename the given resource + */ + @Override + public boolean renameTo( Resource dest) + throws SecurityException + { + if( dest instanceof FileResource) + return _file.renameTo( ((FileResource)dest)._file); + else + return false; + } + + /* --------------------------------------------------------- */ + /** + * Returns a list of resources contained in the given resource + */ + @Override + public String[] list() + { + String[] list =_file.list(); + if (list==null) + return null; + for (int i=list.length;i-->0;) + { + if (new File(_file,list[i]).isDirectory() && + !list[i].endsWith("/")) + list[i]+="/"; + } + return list; + } + + /* ------------------------------------------------------------ */ + /** Encode according to this resource type. + * File URIs are encoded. + * @param uri URI to encode. + * @return The uri unchanged. + */ + @Override + public String encode(String uri) + { + return uri; + } + + /* ------------------------------------------------------------ */ + /** + * @param o + * @return <code>true</code> of the object <code>o</code> is a {@link FileResource} pointing to the same file as this resource. + */ + @Override + public boolean equals( Object o) + { + if (this == o) + return true; + + if (null == o || ! (o instanceof FileResource)) + return false; + + FileResource f=(FileResource)o; + return f._file == _file || (null != _file && _file.equals(f._file)); + } + + /* ------------------------------------------------------------ */ + /** + * @return the hashcode. + */ + @Override + public int hashCode() + { + return null == _file ? super.hashCode() : _file.hashCode(); + } + + /* ------------------------------------------------------------ */ + @Override + public void copyTo(File destination) + throws IOException + { + if (isDirectory()) + { + IO.copyDir(getFile(),destination); + } + else + { + if (destination.exists()) + throw new IllegalArgumentException(destination+" exists"); + IO.copy(getFile(),destination); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/resource/JarFileResource.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,435 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.resource; + +import java.io.File; +import java.io.IOException; +import java.net.JarURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/* ------------------------------------------------------------ */ +class JarFileResource extends JarResource +{ + private static final Logger LOG = Log.getLogger(JarFileResource.class); + private JarFile _jarFile; + private File _file; + private String[] _list; + private JarEntry _entry; + private boolean _directory; + private String _jarUrl; + private String _path; + private boolean _exists; + + /* -------------------------------------------------------- */ + JarFileResource(URL url) + { + super(url); + } + + /* ------------------------------------------------------------ */ + JarFileResource(URL url, boolean useCaches) + { + super(url, useCaches); + } + + + /* ------------------------------------------------------------ */ + @Override + public synchronized void release() + { + _list=null; + _entry=null; + _file=null; + //if the jvm is not doing url caching, then the JarFiles will not be cached either, + //and so they are safe to close + if (!getUseCaches()) + { + if ( _jarFile != null ) + { + try + { + LOG.debug("Closing JarFile "+_jarFile.getName()); + _jarFile.close(); + } + catch ( IOException ioe ) + { + LOG.ignore(ioe); + } + } + } + _jarFile=null; + super.release(); + } + + /* ------------------------------------------------------------ */ + @Override + protected boolean checkConnection() + { + try + { + super.checkConnection(); + } + finally + { + if (_jarConnection==null) + { + _entry=null; + _file=null; + _jarFile=null; + _list=null; + } + } + return _jarFile!=null; + } + + + /* ------------------------------------------------------------ */ + @Override + protected synchronized void newConnection() + throws IOException + { + super.newConnection(); + + _entry=null; + _file=null; + _jarFile=null; + _list=null; + + int sep = _urlString.indexOf("!/"); + _jarUrl=_urlString.substring(0,sep+2); + _path=_urlString.substring(sep+2); + if (_path.length()==0) + _path=null; + _jarFile=_jarConnection.getJarFile(); + _file=new File(_jarFile.getName()); + } + + + /* ------------------------------------------------------------ */ + /** + * Returns true if the represented resource exists. + */ + @Override + public boolean exists() + { + if (_exists) + return true; + + if (_urlString.endsWith("!/")) + { + + String file_url=_urlString.substring(4,_urlString.length()-2); + try{return newResource(file_url).exists();} + catch(Exception e) {LOG.ignore(e); return false;} + } + + boolean check=checkConnection(); + + // Is this a root URL? + if (_jarUrl!=null && _path==null) + { + // Then if it exists it is a directory + _directory=check; + return true; + } + else + { + // Can we find a file for it? + JarFile jarFile=null; + if (check) + // Yes + jarFile=_jarFile; + else + { + // No - so lets look if the root entry exists. + try + { + JarURLConnection c=(JarURLConnection)((new URL(_jarUrl)).openConnection()); + c.setUseCaches(getUseCaches()); + jarFile=c.getJarFile(); + } + catch(Exception e) + { + LOG.ignore(e); + } + } + + // Do we need to look more closely? + if (jarFile!=null && _entry==null && !_directory) + { + // OK - we have a JarFile, lets look at the entries for our path + Enumeration<JarEntry> e=jarFile.entries(); + while(e.hasMoreElements()) + { + JarEntry entry = (JarEntry) e.nextElement(); + String name=entry.getName().replace('\\','/'); + + // Do we have a match + if (name.equals(_path)) + { + _entry=entry; + // Is the match a directory + _directory=_path.endsWith("/"); + break; + } + else if (_path.endsWith("/")) + { + if (name.startsWith(_path)) + { + _directory=true; + break; + } + } + else if (name.startsWith(_path) && name.length()>_path.length() && name.charAt(_path.length())=='/') + { + _directory=true; + break; + } + } + + if (_directory && !_urlString.endsWith("/")) + { + _urlString+="/"; + try + { + _url=new URL(_urlString); + } + catch(MalformedURLException ex) + { + LOG.warn(ex); + } + } + } + } + + _exists= ( _directory || _entry!=null); + return _exists; + } + + + /* ------------------------------------------------------------ */ + /** + * Returns true if the represented resource is a container/directory. + * If the resource is not a file, resources ending with "/" are + * considered directories. + */ + @Override + public boolean isDirectory() + { + return _urlString.endsWith("/") || exists() && _directory; + } + + /* ------------------------------------------------------------ */ + /** + * Returns the last modified time + */ + @Override + public long lastModified() + { + if (checkConnection() && _file!=null) + { + if (exists() && _entry!=null) + return _entry.getTime(); + return _file.lastModified(); + } + return -1; + } + + /* ------------------------------------------------------------ */ + @Override + public synchronized String[] list() + { + if (isDirectory() && _list==null) + { + List<String> list = null; + try + { + list = listEntries(); + } + catch (Exception e) + { + //Sun's JarURLConnection impl for jar: protocol will close a JarFile in its connect() method if + //useCaches == false (eg someone called URLConnection with defaultUseCaches==true). + //As their sun.net.www.protocol.jar package caches JarFiles and/or connections, we can wind up in + //the situation where the JarFile we have remembered in our _jarFile member has actually been closed + //by other code. + //So, do one retry to drop a connection and get a fresh JarFile + LOG.warn("Retrying list:"+e); + LOG.debug(e); + release(); + list = listEntries(); + } + + if (list != null) + { + _list=new String[list.size()]; + list.toArray(_list); + } + } + return _list; + } + + + /* ------------------------------------------------------------ */ + private List<String> listEntries () + { + checkConnection(); + + ArrayList<String> list = new ArrayList<String>(32); + JarFile jarFile=_jarFile; + if(jarFile==null) + { + try + { + JarURLConnection jc=(JarURLConnection)((new URL(_jarUrl)).openConnection()); + jc.setUseCaches(getUseCaches()); + jarFile=jc.getJarFile(); + } + catch(Exception e) + { + + e.printStackTrace(); + LOG.ignore(e); + } + } + + Enumeration<JarEntry> e=jarFile.entries(); + String dir=_urlString.substring(_urlString.indexOf("!/")+2); + while(e.hasMoreElements()) + { + JarEntry entry = e.nextElement(); + String name=entry.getName().replace('\\','/'); + if(!name.startsWith(dir) || name.length()==dir.length()) + { + continue; + } + String listName=name.substring(dir.length()); + int dash=listName.indexOf('/'); + if (dash>=0) + { + //when listing jar:file urls, you get back one + //entry for the dir itself, which we ignore + if (dash==0 && listName.length()==1) + continue; + //when listing jar:file urls, all files and + //subdirs have a leading /, which we remove + if (dash==0) + listName=listName.substring(dash+1, listName.length()); + else + listName=listName.substring(0,dash+1); + + if (list.contains(listName)) + continue; + } + + list.add(listName); + } + + return list; + } + + + + + + /* ------------------------------------------------------------ */ + /** + * Return the length of the resource + */ + @Override + public long length() + { + if (isDirectory()) + return -1; + + if (_entry!=null) + return _entry.getSize(); + + return -1; + } + + /* ------------------------------------------------------------ */ + /** Encode according to this resource type. + * File URIs are not encoded. + * @param uri URI to encode. + * @return The uri unchanged. + */ + @Override + public String encode(String uri) + { + return uri; + } + + + /** + * Take a Resource that possibly might use URLConnection caching + * and turn it into one that doesn't. + * @param resource + * @return the non-caching resource + */ + public static Resource getNonCachingResource (Resource resource) + { + if (!(resource instanceof JarFileResource)) + return resource; + + JarFileResource oldResource = (JarFileResource)resource; + + JarFileResource newResource = new JarFileResource(oldResource.getURL(), false); + return newResource; + + } + + /** + * Check if this jar:file: resource is contained in the + * named resource. Eg <code>jar:file:///a/b/c/foo.jar!/x.html</code> isContainedIn <code>file:///a/b/c/foo.jar</code> + * @param resource + * @return true if resource is contained in the named resource + * @throws MalformedURLException + */ + @Override + public boolean isContainedIn (Resource resource) + throws MalformedURLException + { + String string = _urlString; + int index = string.indexOf("!/"); + if (index > 0) + string = string.substring(0,index); + if (string.startsWith("jar:")) + string = string.substring(4); + URL url = new URL(string); + return url.sameFile(resource.getURL()); + } +} + + + + + + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/resource/JarResource.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,273 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.resource; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.JarURLConnection; +import java.net.URL; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; +import java.util.jar.Manifest; + +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + + +/* ------------------------------------------------------------ */ +public class JarResource extends URLResource +{ + private static final Logger LOG = Log.getLogger(JarResource.class); + protected JarURLConnection _jarConnection; + + /* -------------------------------------------------------- */ + JarResource(URL url) + { + super(url,null); + } + + /* ------------------------------------------------------------ */ + JarResource(URL url, boolean useCaches) + { + super(url, null, useCaches); + } + + /* ------------------------------------------------------------ */ + @Override + public synchronized void release() + { + _jarConnection=null; + super.release(); + } + + /* ------------------------------------------------------------ */ + @Override + protected synchronized boolean checkConnection() + { + super.checkConnection(); + try + { + if (_jarConnection!=_connection) + newConnection(); + } + catch(IOException e) + { + LOG.ignore(e); + _jarConnection=null; + } + + return _jarConnection!=null; + } + + /* ------------------------------------------------------------ */ + /** + * @throws IOException Sub-classes of <code>JarResource</code> may throw an IOException (or subclass) + */ + protected void newConnection() throws IOException + { + _jarConnection=(JarURLConnection)_connection; + } + + /* ------------------------------------------------------------ */ + /** + * Returns true if the respresenetd resource exists. + */ + @Override + public boolean exists() + { + if (_urlString.endsWith("!/")) + return checkConnection(); + else + return super.exists(); + } + + /* ------------------------------------------------------------ */ + @Override + public File getFile() + throws IOException + { + return null; + } + + /* ------------------------------------------------------------ */ + @Override + public InputStream getInputStream() + throws java.io.IOException + { + checkConnection(); + if (!_urlString.endsWith("!/")) + return new FilterInputStream(super.getInputStream()) + { + @Override + public void close() throws IOException {this.in=IO.getClosedStream();} + }; + + URL url = new URL(_urlString.substring(4,_urlString.length()-2)); + InputStream is = url.openStream(); + return is; + } + + /* ------------------------------------------------------------ */ + @Override + public void copyTo(File directory) + throws IOException + { + if (!exists()) + return; + + if(LOG.isDebugEnabled()) + LOG.debug("Extract "+this+" to "+directory); + + String urlString = this.getURL().toExternalForm().trim(); + int endOfJarUrl = urlString.indexOf("!/"); + int startOfJarUrl = (endOfJarUrl >= 0?4:0); + + if (endOfJarUrl < 0) + throw new IOException("Not a valid jar url: "+urlString); + + URL jarFileURL = new URL(urlString.substring(startOfJarUrl, endOfJarUrl)); + String subEntryName = (endOfJarUrl+2 < urlString.length() ? urlString.substring(endOfJarUrl + 2) : null); + boolean subEntryIsDir = (subEntryName != null && subEntryName.endsWith("/")?true:false); + + if (LOG.isDebugEnabled()) + LOG.debug("Extracting entry = "+subEntryName+" from jar "+jarFileURL); + + InputStream is = jarFileURL.openConnection().getInputStream(); + JarInputStream jin = new JarInputStream(is); + JarEntry entry; + boolean shouldExtract; + while((entry=jin.getNextJarEntry())!=null) + { + String entryName = entry.getName(); + if ((subEntryName != null) && (entryName.startsWith(subEntryName))) + { + // is the subentry really a dir? + if (!subEntryIsDir && subEntryName.length()+1==entryName.length() && entryName.endsWith("/")) + subEntryIsDir=true; + + //if there is a particular subEntry that we are looking for, only + //extract it. + if (subEntryIsDir) + { + //if it is a subdirectory we are looking for, then we + //are looking to extract its contents into the target + //directory. Remove the name of the subdirectory so + //that we don't wind up creating it too. + entryName = entryName.substring(subEntryName.length()); + if (!entryName.equals("")) + { + //the entry is + shouldExtract = true; + } + else + shouldExtract = false; + } + else + shouldExtract = true; + } + else if ((subEntryName != null) && (!entryName.startsWith(subEntryName))) + { + //there is a particular entry we are looking for, and this one + //isn't it + shouldExtract = false; + } + else + { + //we are extracting everything + shouldExtract = true; + } + + + if (!shouldExtract) + { + if (LOG.isDebugEnabled()) + LOG.debug("Skipping entry: "+entryName); + continue; + } + + String dotCheck = entryName.replace('\\', '/'); + dotCheck = URIUtil.canonicalPath(dotCheck); + if (dotCheck == null) + { + if (LOG.isDebugEnabled()) + LOG.debug("Invalid entry: "+entryName); + continue; + } + + File file=new File(directory,entryName); + + if (entry.isDirectory()) + { + // Make directory + if (!file.exists()) + file.mkdirs(); + } + else + { + // make directory (some jars don't list dirs) + File dir = new File(file.getParent()); + if (!dir.exists()) + dir.mkdirs(); + + // Make file + FileOutputStream fout = null; + try + { + fout = new FileOutputStream(file); + IO.copy(jin,fout); + } + finally + { + IO.close(fout); + } + + // touch the file. + if (entry.getTime()>=0) + file.setLastModified(entry.getTime()); + } + } + + if ((subEntryName == null) || (subEntryName != null && subEntryName.equalsIgnoreCase("META-INF/MANIFEST.MF"))) + { + Manifest manifest = jin.getManifest(); + if (manifest != null) + { + File metaInf = new File (directory, "META-INF"); + metaInf.mkdir(); + File f = new File(metaInf, "MANIFEST.MF"); + FileOutputStream fout = new FileOutputStream(f); + manifest.write(fout); + fout.close(); + } + } + IO.close(jin); + } + + public static Resource newJarResource(Resource resource) throws IOException + { + if (resource instanceof JarResource) + return resource; + return Resource.newResource("jar:" + resource + "!/"); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/resource/Resource.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,678 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.resource; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.net.URLConnection; +import java.text.DateFormat; +import java.util.Arrays; +import java.util.Date; + +import org.eclipse.jetty.util.B64Code; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.Loader; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + + +/* ------------------------------------------------------------ */ +/** + * Abstract resource class. + */ +public abstract class Resource implements ResourceFactory +{ + private static final Logger LOG = Log.getLogger(Resource.class); + public static boolean __defaultUseCaches = true; + volatile Object _associate; + + /* ------------------------------------------------------------ */ + /** + * Change the default setting for url connection caches. + * Subsequent URLConnections will use this default. + * @param useCaches + */ + public static void setDefaultUseCaches (boolean useCaches) + { + __defaultUseCaches=useCaches; + } + + /* ------------------------------------------------------------ */ + public static boolean getDefaultUseCaches () + { + return __defaultUseCaches; + } + + /* ------------------------------------------------------------ */ + /** Construct a resource from a uri. + * @param uri A URI. + * @return A Resource object. + * @throws IOException Problem accessing URI + */ + public static Resource newResource(URI uri) + throws IOException + { + return newResource(uri.toURL()); + } + + /* ------------------------------------------------------------ */ + /** Construct a resource from a url. + * @param url A URL. + * @return A Resource object. + * @throws IOException Problem accessing URL + */ + public static Resource newResource(URL url) + throws IOException + { + return newResource(url, __defaultUseCaches); + } + + /* ------------------------------------------------------------ */ + /** + * Construct a resource from a url. + * @param url the url for which to make the resource + * @param useCaches true enables URLConnection caching if applicable to the type of resource + * @return + */ + static Resource newResource(URL url, boolean useCaches) + { + if (url==null) + return null; + + String url_string=url.toExternalForm(); + if( url_string.startsWith( "file:")) + { + try + { + FileResource fileResource= new FileResource(url); + return fileResource; + } + catch(Exception e) + { + LOG.debug(Log.EXCEPTION,e); + return new BadResource(url,e.toString()); + } + } + else if( url_string.startsWith( "jar:file:")) + { + return new JarFileResource(url, useCaches); + } + else if( url_string.startsWith( "jar:")) + { + return new JarResource(url, useCaches); + } + + return new URLResource(url,null,useCaches); + } + + + + /* ------------------------------------------------------------ */ + /** Construct a resource from a string. + * @param resource A URL or filename. + * @return A Resource object. + */ + public static Resource newResource(String resource) + throws MalformedURLException, IOException + { + return newResource(resource, __defaultUseCaches); + } + + /* ------------------------------------------------------------ */ + /** Construct a resource from a string. + * @param resource A URL or filename. + * @param useCaches controls URLConnection caching + * @return A Resource object. + */ + public static Resource newResource (String resource, boolean useCaches) + throws MalformedURLException, IOException + { + URL url=null; + try + { + // Try to format as a URL? + url = new URL(resource); + } + catch(MalformedURLException e) + { + if(!resource.startsWith("ftp:") && + !resource.startsWith("file:") && + !resource.startsWith("jar:")) + { + try + { + // It's a file. + if (resource.startsWith("./")) + resource=resource.substring(2); + + File file=new File(resource).getCanonicalFile(); + url=Resource.toURL(file); + + URLConnection connection=url.openConnection(); + connection.setUseCaches(useCaches); + return new FileResource(url,connection,file); + } + catch(Exception e2) + { + LOG.debug(Log.EXCEPTION,e2); + throw e; + } + } + else + { + LOG.warn("Bad Resource: "+resource); + throw e; + } + } + + return newResource(url); + } + + /* ------------------------------------------------------------ */ + public static Resource newResource (File file) + throws MalformedURLException, IOException + { + file = file.getCanonicalFile(); + URL url = Resource.toURL(file); + + URLConnection connection = url.openConnection(); + FileResource fileResource = new FileResource(url, connection, file); + return fileResource; + } + + /* ------------------------------------------------------------ */ + /** Construct a system resource from a string. + * The resource is tried as classloader resource before being + * treated as a normal resource. + * @param resource Resource as string representation + * @return The new Resource + * @throws IOException Problem accessing resource. + */ + public static Resource newSystemResource(String resource) + throws IOException + { + URL url=null; + // Try to format as a URL? + ClassLoader loader=Thread.currentThread().getContextClassLoader(); + if (loader!=null) + { + try + { + url = loader.getResource(resource); + if (url == null && resource.startsWith("/")) + url = loader.getResource(resource.substring(1)); + } + catch (IllegalArgumentException e) + { + // Catches scenario where a bad Windows path like "C:\dev" is + // improperly escaped, which various downstream classloaders + // tend to have a problem with + url = null; + } + } + if (url==null) + { + loader=Resource.class.getClassLoader(); + if (loader!=null) + { + url=loader.getResource(resource); + if (url==null && resource.startsWith("/")) + url=loader.getResource(resource.substring(1)); + } + } + + if (url==null) + { + url=ClassLoader.getSystemResource(resource); + if (url==null && resource.startsWith("/")) + url=ClassLoader.getSystemResource(resource.substring(1)); + } + + if (url==null) + return null; + + return newResource(url); + } + + /* ------------------------------------------------------------ */ + /** Find a classpath resource. + */ + public static Resource newClassPathResource(String resource) + { + return newClassPathResource(resource,true,false); + } + + /* ------------------------------------------------------------ */ + /** Find a classpath resource. + * The {@link java.lang.Class#getResource(String)} method is used to lookup the resource. If it is not + * found, then the {@link Loader#getResource(Class, String, boolean)} method is used. + * If it is still not found, then {@link ClassLoader#getSystemResource(String)} is used. + * Unlike {@link ClassLoader#getSystemResource(String)} this method does not check for normal resources. + * @param name The relative name of the resource + * @param useCaches True if URL caches are to be used. + * @param checkParents True if forced searching of parent Classloaders is performed to work around + * loaders with inverted priorities + * @return Resource or null + */ + public static Resource newClassPathResource(String name,boolean useCaches,boolean checkParents) + { + URL url=Resource.class.getResource(name); + + if (url==null) + url=Loader.getResource(Resource.class,name,checkParents); + if (url==null) + return null; + return newResource(url,useCaches); + } + + /* ------------------------------------------------------------ */ + public static boolean isContainedIn (Resource r, Resource containingResource) throws MalformedURLException + { + return r.isContainedIn(containingResource); + } + + /* ------------------------------------------------------------ */ + @Override + protected void finalize() + { + release(); + } + + /* ------------------------------------------------------------ */ + public abstract boolean isContainedIn (Resource r) throws MalformedURLException; + + + /* ------------------------------------------------------------ */ + /** Release any temporary resources held by the resource. + */ + public abstract void release(); + + + /* ------------------------------------------------------------ */ + /** + * Returns true if the respresened resource exists. + */ + public abstract boolean exists(); + + + /* ------------------------------------------------------------ */ + /** + * Returns true if the respresenetd resource is a container/directory. + * If the resource is not a file, resources ending with "/" are + * considered directories. + */ + public abstract boolean isDirectory(); + + /* ------------------------------------------------------------ */ + /** + * Returns the last modified time + */ + public abstract long lastModified(); + + + /* ------------------------------------------------------------ */ + /** + * Return the length of the resource + */ + public abstract long length(); + + + /* ------------------------------------------------------------ */ + /** + * Returns an URL representing the given resource + */ + public abstract URL getURL(); + + /* ------------------------------------------------------------ */ + /** + * Returns an URI representing the given resource + */ + public URI getURI() + { + try + { + return getURL().toURI(); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + + + /* ------------------------------------------------------------ */ + /** + * Returns an File representing the given resource or NULL if this + * is not possible. + */ + public abstract File getFile() + throws IOException; + + + /* ------------------------------------------------------------ */ + /** + * Returns the name of the resource + */ + public abstract String getName(); + + + /* ------------------------------------------------------------ */ + /** + * Returns an input stream to the resource + */ + public abstract InputStream getInputStream() + throws java.io.IOException; + + /* ------------------------------------------------------------ */ + /** + * Returns an output stream to the resource + */ + public abstract OutputStream getOutputStream() + throws java.io.IOException, SecurityException; + + /* ------------------------------------------------------------ */ + /** + * Deletes the given resource + */ + public abstract boolean delete() + throws SecurityException; + + /* ------------------------------------------------------------ */ + /** + * Rename the given resource + */ + public abstract boolean renameTo( Resource dest) + throws SecurityException; + + /* ------------------------------------------------------------ */ + /** + * Returns a list of resource names contained in the given resource + * The resource names are not URL encoded. + */ + public abstract String[] list(); + + /* ------------------------------------------------------------ */ + /** + * Returns the resource contained inside the current resource with the + * given name. + * @param path The path segment to add, which should be encoded by the + * encode method. + */ + public abstract Resource addPath(String path) + throws IOException,MalformedURLException; + + /* ------------------------------------------------------------ */ + /** Get a resource from withing this resource. + * <p> + * This method is essentially an alias for {@link #addPath(String)}, but without checked exceptions. + * This method satisfied the {@link ResourceFactory} interface. + * @see org.eclipse.jetty.util.resource.ResourceFactory#getResource(java.lang.String) + */ + public Resource getResource(String path) + { + try + { + return addPath(path); + } + catch(Exception e) + { + LOG.debug(e); + return null; + } + } + + /* ------------------------------------------------------------ */ + /** Encode according to this resource type. + * The default implementation calls URI.encodePath(uri) + * @param uri + * @return String encoded for this resource type. + */ + public String encode(String uri) + { + return URIUtil.encodePath(uri); + } + + /* ------------------------------------------------------------ */ + public Object getAssociate() + { + return _associate; + } + + /* ------------------------------------------------------------ */ + public void setAssociate(Object o) + { + _associate=o; + } + + /* ------------------------------------------------------------ */ + /** + * @return The canonical Alias of this resource or null if none. + */ + public URL getAlias() + { + return null; + } + + /* ------------------------------------------------------------ */ + /** Get the resource list as a HTML directory listing. + * @param base The base URL + * @param parent True if the parent directory should be included + * @return String of HTML + */ + public String getListHTML(String base,boolean parent) + throws IOException + { + base=URIUtil.canonicalPath(base); + if (base==null || !isDirectory()) + return null; + + String[] ls = list(); + if (ls==null) + return null; + Arrays.sort(ls); + + String decodedBase = URIUtil.decodePath(base); + String title = "Directory: "+deTag(decodedBase); + + StringBuilder buf=new StringBuilder(4096); + buf.append("<HTML><HEAD>"); + buf.append("<LINK HREF=\"").append("jetty-dir.css").append("\" REL=\"stylesheet\" TYPE=\"text/css\"/><TITLE>"); + buf.append(title); + buf.append("</TITLE></HEAD><BODY>\n<H1>"); + buf.append(title); + buf.append("</H1>\n<TABLE BORDER=0>\n"); + + if (parent) + { + buf.append("<TR><TD><A HREF=\""); + buf.append(URIUtil.addPaths(base,"../")); + buf.append("\">Parent Directory</A></TD><TD></TD><TD></TD></TR>\n"); + } + + String encodedBase = hrefEncodeURI(base); + + DateFormat dfmt=DateFormat.getDateTimeInstance(DateFormat.MEDIUM, + DateFormat.MEDIUM); + for (int i=0 ; i< ls.length ; i++) + { + Resource item = addPath(ls[i]); + + buf.append("\n<TR><TD><A HREF=\""); + String path=URIUtil.addPaths(encodedBase,URIUtil.encodePath(ls[i])); + + buf.append(path); + + if (item.isDirectory() && !path.endsWith("/")) + buf.append(URIUtil.SLASH); + + // URIUtil.encodePath(buf,path); + buf.append("\">"); + buf.append(deTag(ls[i])); + buf.append(" "); + buf.append("</A></TD><TD ALIGN=right>"); + buf.append(item.length()); + buf.append(" bytes </TD><TD>"); + buf.append(dfmt.format(new Date(item.lastModified()))); + buf.append("</TD></TR>"); + } + buf.append("</TABLE>\n"); + buf.append("</BODY></HTML>\n"); + + return buf.toString(); + } + + /** + * Encode any characters that could break the URI string in an HREF. + * Such as <a href="/path/to;<script>Window.alert("XSS"+'%20'+"here");</script>">Link</a> + * + * The above example would parse incorrectly on various browsers as the "<" or '"' characters + * would end the href attribute value string prematurely. + * + * @param raw the raw text to encode. + * @return the defanged text. + */ + private static String hrefEncodeURI(String raw) + { + StringBuffer buf = null; + + loop: + for (int i=0;i<raw.length();i++) + { + char c=raw.charAt(i); + switch(c) + { + case '\'': + case '"': + case '<': + case '>': + buf=new StringBuffer(raw.length()<<1); + break loop; + } + } + if (buf==null) + return raw; + + for (int i=0;i<raw.length();i++) + { + char c=raw.charAt(i); + switch(c) + { + case '"': + buf.append("%22"); + continue; + case '\'': + buf.append("%27"); + continue; + case '<': + buf.append("%3C"); + continue; + case '>': + buf.append("%3E"); + continue; + default: + buf.append(c); + continue; + } + } + + return buf.toString(); + } + + private static String deTag(String raw) + { + return StringUtil.replace( StringUtil.replace(raw,"<","<"), ">", ">"); + } + + /* ------------------------------------------------------------ */ + /** + * @param out + * @param start First byte to write + * @param count Bytes to write or -1 for all of them. + */ + public void writeTo(OutputStream out,long start,long count) + throws IOException + { + InputStream in = getInputStream(); + try + { + in.skip(start); + if (count<0) + IO.copy(in,out); + else + IO.copy(in,out,count); + } + finally + { + in.close(); + } + } + + /* ------------------------------------------------------------ */ + public void copyTo(File destination) + throws IOException + { + if (destination.exists()) + throw new IllegalArgumentException(destination+" exists"); + writeTo(new FileOutputStream(destination),0,-1); + } + + /* ------------------------------------------------------------ */ + public String getWeakETag() + { + try + { + StringBuilder b = new StringBuilder(32); + b.append("W/\""); + + String name=getName(); + int length=name.length(); + long lhash=0; + for (int i=0; i<length;i++) + lhash=31*lhash+name.charAt(i); + + B64Code.encode(lastModified()^lhash,b); + B64Code.encode(length()^lhash,b); + b.append('"'); + return b.toString(); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + /* ------------------------------------------------------------ */ + /** Generate a properly encoded URL from a {@link File} instance. + * @param file Target file. + * @return URL of the target file. + * @throws MalformedURLException + */ + public static URL toURL(File file) throws MalformedURLException + { + return file.toURI().toURL(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/resource/ResourceCollection.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,482 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.resource; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.StringTokenizer; + +import org.eclipse.jetty.util.URIUtil; + +/** + * A collection of resources (dirs). + * Allows webapps to have multiple (static) sources. + * The first resource in the collection is the main resource. + * If a resource is not found in the main resource, it looks it up in + * the order the resources were constructed. + * + * + * + */ +public class ResourceCollection extends Resource +{ + private Resource[] _resources; + + /* ------------------------------------------------------------ */ + /** + * Instantiates an empty resource collection. + * + * This constructor is used when configuring jetty-maven-plugin. + */ + public ResourceCollection() + { + _resources = new Resource[0]; + } + + /* ------------------------------------------------------------ */ + /** + * Instantiates a new resource collection. + * + * @param resources the resources to be added to collection + */ + public ResourceCollection(Resource... resources) + { + List<Resource> list = new ArrayList<Resource>(); + for (Resource r : resources) + { + if (r==null) + continue; + if (r instanceof ResourceCollection) + { + for (Resource r2 : ((ResourceCollection)r).getResources()) + list.add(r2); + } + else + list.add(r); + } + _resources = list.toArray(new Resource[list.size()]); + for(Resource r : _resources) + { + if(!r.exists() || !r.isDirectory()) + throw new IllegalArgumentException(r + " is not an existing directory."); + } + } + + + /* ------------------------------------------------------------ */ + /** + * Instantiates a new resource collection. + * + * @param resources the resource strings to be added to collection + */ + public ResourceCollection(String[] resources) + { + _resources = new Resource[resources.length]; + try + { + for(int i=0; i<resources.length; i++) + { + _resources[i] = Resource.newResource(resources[i]); + if(!_resources[i].exists() || !_resources[i].isDirectory()) + throw new IllegalArgumentException(_resources[i] + " is not an existing directory."); + } + } + catch(IllegalArgumentException e) + { + throw e; + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + + /* ------------------------------------------------------------ */ + /** + * Instantiates a new resource collection. + * + * @param csvResources the string containing comma-separated resource strings + */ + public ResourceCollection(String csvResources) + { + setResourcesAsCSV(csvResources); + } + + /* ------------------------------------------------------------ */ + /** + * Retrieves the resource collection's resources. + * + * @return the resource array + */ + public Resource[] getResources() + { + return _resources; + } + + /* ------------------------------------------------------------ */ + /** + * Sets the resource collection's resources. + * + * @param resources the new resource array + */ + public void setResources(Resource[] resources) + { + _resources = resources != null ? resources : new Resource[0]; + } + + /* ------------------------------------------------------------ */ + /** + * Sets the resources as string of comma-separated values. + * This method should be used when configuring jetty-maven-plugin. + * + * @param csvResources the comma-separated string containing + * one or more resource strings. + */ + public void setResourcesAsCSV(String csvResources) + { + StringTokenizer tokenizer = new StringTokenizer(csvResources, ",;"); + int len = tokenizer.countTokens(); + if(len==0) + { + throw new IllegalArgumentException("ResourceCollection@setResourcesAsCSV(String) " + + " argument must be a string containing one or more comma-separated resource strings."); + } + + _resources = new Resource[len]; + try + { + for(int i=0; tokenizer.hasMoreTokens(); i++) + { + _resources[i] = Resource.newResource(tokenizer.nextToken().trim()); + if(!_resources[i].exists() || !_resources[i].isDirectory()) + throw new IllegalArgumentException(_resources[i] + " is not an existing directory."); + } + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + + /* ------------------------------------------------------------ */ + /** + * @param path The path segment to add + * @return The contained resource (found first) in the collection of resources + */ + @Override + public Resource addPath(String path) throws IOException, MalformedURLException + { + if(_resources==null) + throw new IllegalStateException("*resources* not set."); + + if(path==null) + throw new MalformedURLException(); + + if(path.length()==0 || URIUtil.SLASH.equals(path)) + return this; + + Resource resource=null; + ArrayList<Resource> resources = null; + int i=0; + for(; i<_resources.length; i++) + { + resource = _resources[i].addPath(path); + if (resource.exists()) + { + if (resource.isDirectory()) + break; + return resource; + } + } + + for(i++; i<_resources.length; i++) + { + Resource r = _resources[i].addPath(path); + if (r.exists() && r.isDirectory()) + { + if (resource!=null) + { + resources = new ArrayList<Resource>(); + resources.add(resource); + resource=null; + } + resources.add(r); + } + } + + if (resource!=null) + return resource; + if (resources!=null) + return new ResourceCollection(resources.toArray(new Resource[resources.size()])); + return null; + } + + /* ------------------------------------------------------------ */ + /** + * @param path + * @return the resource(file) if found, returns a list of resource dirs if its a dir, else null. + * @throws IOException + * @throws MalformedURLException + */ + protected Object findResource(String path) throws IOException, MalformedURLException + { + Resource resource=null; + ArrayList<Resource> resources = null; + int i=0; + for(; i<_resources.length; i++) + { + resource = _resources[i].addPath(path); + if (resource.exists()) + { + if (resource.isDirectory()) + break; + + return resource; + } + } + + for(i++; i<_resources.length; i++) + { + Resource r = _resources[i].addPath(path); + if (r.exists() && r.isDirectory()) + { + if (resource!=null) + { + resources = new ArrayList<Resource>(); + resources.add(resource); + } + resources.add(r); + } + } + + if (resource!=null) + return resource; + if (resources!=null) + return resources; + return null; + } + + /* ------------------------------------------------------------ */ + @Override + public boolean delete() throws SecurityException + { + throw new UnsupportedOperationException(); + } + + /* ------------------------------------------------------------ */ + @Override + public boolean exists() + { + if(_resources==null) + throw new IllegalStateException("*resources* not set."); + + return true; + } + + /* ------------------------------------------------------------ */ + @Override + public File getFile() throws IOException + { + if(_resources==null) + throw new IllegalStateException("*resources* not set."); + + for(Resource r : _resources) + { + File f = r.getFile(); + if(f!=null) + return f; + } + return null; + } + + /* ------------------------------------------------------------ */ + @Override + public InputStream getInputStream() throws IOException + { + if(_resources==null) + throw new IllegalStateException("*resources* not set."); + + for(Resource r : _resources) + { + InputStream is = r.getInputStream(); + if(is!=null) + return is; + } + return null; + } + + /* ------------------------------------------------------------ */ + @Override + public String getName() + { + if(_resources==null) + throw new IllegalStateException("*resources* not set."); + + for(Resource r : _resources) + { + String name = r.getName(); + if(name!=null) + return name; + } + return null; + } + + /* ------------------------------------------------------------ */ + @Override + public OutputStream getOutputStream() throws IOException, SecurityException + { + if(_resources==null) + throw new IllegalStateException("*resources* not set."); + + for(Resource r : _resources) + { + OutputStream os = r.getOutputStream(); + if(os!=null) + return os; + } + return null; + } + + /* ------------------------------------------------------------ */ + @Override + public URL getURL() + { + if(_resources==null) + throw new IllegalStateException("*resources* not set."); + + for(Resource r : _resources) + { + URL url = r.getURL(); + if(url!=null) + return url; + } + return null; + } + + /* ------------------------------------------------------------ */ + @Override + public boolean isDirectory() + { + if(_resources==null) + throw new IllegalStateException("*resources* not set."); + + return true; + } + + /* ------------------------------------------------------------ */ + @Override + public long lastModified() + { + if(_resources==null) + throw new IllegalStateException("*resources* not set."); + + for(Resource r : _resources) + { + long lm = r.lastModified(); + if (lm!=-1) + return lm; + } + return -1; + } + + /* ------------------------------------------------------------ */ + @Override + public long length() + { + return -1; + } + + /* ------------------------------------------------------------ */ + /** + * @return The list of resource names(merged) contained in the collection of resources. + */ + @Override + public String[] list() + { + if(_resources==null) + throw new IllegalStateException("*resources* not set."); + + HashSet<String> set = new HashSet<String>(); + for(Resource r : _resources) + { + for(String s : r.list()) + set.add(s); + } + String[] result=set.toArray(new String[set.size()]); + Arrays.sort(result); + return result; + } + + /* ------------------------------------------------------------ */ + @Override + public void release() + { + if(_resources==null) + throw new IllegalStateException("*resources* not set."); + + for(Resource r : _resources) + r.release(); + } + + /* ------------------------------------------------------------ */ + @Override + public boolean renameTo(Resource dest) throws SecurityException + { + throw new UnsupportedOperationException(); + } + + /* ------------------------------------------------------------ */ + @Override + public void copyTo(File destination) + throws IOException + { + for (int r=_resources.length;r-->0;) + _resources[r].copyTo(destination); + } + + /* ------------------------------------------------------------ */ + /** + * @return the list of resources separated by a path separator + */ + @Override + public String toString() + { + if(_resources==null) + return "[]"; + + return String.valueOf(Arrays.asList(_resources)); + } + + /* ------------------------------------------------------------ */ + @Override + public boolean isContainedIn(Resource r) throws MalformedURLException + { + // TODO could look at implementing the semantic of is this collection a subset of the Resource r? + return false; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/resource/ResourceFactory.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,34 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.resource; + + +/* ------------------------------------------------------------ */ +/** ResourceFactory. + */ +public interface ResourceFactory +{ + + /* ------------------------------------------------------------ */ + /** Get a resource for a path. + * @param path The path to the resource + * @return The resource or null + */ + Resource getResource(String path); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/resource/URLResource.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,321 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.resource; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.security.Permission; + +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/* ------------------------------------------------------------ */ +/** Abstract resource class. + */ +public class URLResource extends Resource +{ + private static final Logger LOG = Log.getLogger(URLResource.class); + protected URL _url; + protected String _urlString; + + protected URLConnection _connection; + protected InputStream _in=null; + transient boolean _useCaches = Resource.__defaultUseCaches; + + /* ------------------------------------------------------------ */ + protected URLResource(URL url, URLConnection connection) + { + _url = url; + _urlString=_url.toString(); + _connection=connection; + } + + /* ------------------------------------------------------------ */ + protected URLResource (URL url, URLConnection connection, boolean useCaches) + { + this (url, connection); + _useCaches = useCaches; + } + + /* ------------------------------------------------------------ */ + protected synchronized boolean checkConnection() + { + if (_connection==null) + { + try{ + _connection=_url.openConnection(); + _connection.setUseCaches(_useCaches); + } + catch(IOException e) + { + LOG.ignore(e); + } + } + return _connection!=null; + } + + /* ------------------------------------------------------------ */ + /** Release any resources held by the resource. + */ + @Override + public synchronized void release() + { + if (_in!=null) + { + try{_in.close();}catch(IOException e){LOG.ignore(e);} + _in=null; + } + + if (_connection!=null) + _connection=null; + } + + /* ------------------------------------------------------------ */ + /** + * Returns true if the represented resource exists. + */ + @Override + public boolean exists() + { + try + { + synchronized(this) + { + if (checkConnection() && _in==null ) + _in = _connection.getInputStream(); + } + } + catch (IOException e) + { + LOG.ignore(e); + } + return _in!=null; + } + + /* ------------------------------------------------------------ */ + /** + * Returns true if the respresenetd resource is a container/directory. + * If the resource is not a file, resources ending with "/" are + * considered directories. + */ + @Override + public boolean isDirectory() + { + return exists() && _url.toString().endsWith("/"); + } + + + /* ------------------------------------------------------------ */ + /** + * Returns the last modified time + */ + @Override + public long lastModified() + { + if (checkConnection()) + return _connection.getLastModified(); + return -1; + } + + + /* ------------------------------------------------------------ */ + /** + * Return the length of the resource + */ + @Override + public long length() + { + if (checkConnection()) + return _connection.getContentLength(); + return -1; + } + + /* ------------------------------------------------------------ */ + /** + * Returns an URL representing the given resource + */ + @Override + public URL getURL() + { + return _url; + } + + /* ------------------------------------------------------------ */ + /** + * Returns an File representing the given resource or NULL if this + * is not possible. + */ + @Override + public File getFile() + throws IOException + { + // Try the permission hack + if (checkConnection()) + { + Permission perm = _connection.getPermission(); + if (perm instanceof java.io.FilePermission) + return new File(perm.getName()); + } + + // Try the URL file arg + try {return new File(_url.getFile());} + catch(Exception e) {LOG.ignore(e);} + + // Don't know the file + return null; + } + + /* ------------------------------------------------------------ */ + /** + * Returns the name of the resource + */ + @Override + public String getName() + { + return _url.toExternalForm(); + } + + /* ------------------------------------------------------------ */ + /** + * Returns an input stream to the resource + */ + @Override + public synchronized InputStream getInputStream() + throws java.io.IOException + { + if (!checkConnection()) + throw new IOException( "Invalid resource"); + + try + { + if( _in != null) + { + InputStream in = _in; + _in=null; + return in; + } + return _connection.getInputStream(); + } + finally + { + _connection=null; + } + } + + + /* ------------------------------------------------------------ */ + /** + * Returns an output stream to the resource + */ + @Override + public OutputStream getOutputStream() + throws java.io.IOException, SecurityException + { + throw new IOException( "Output not supported"); + } + + /* ------------------------------------------------------------ */ + /** + * Deletes the given resource + */ + @Override + public boolean delete() + throws SecurityException + { + throw new SecurityException( "Delete not supported"); + } + + /* ------------------------------------------------------------ */ + /** + * Rename the given resource + */ + @Override + public boolean renameTo( Resource dest) + throws SecurityException + { + throw new SecurityException( "RenameTo not supported"); + } + + /* ------------------------------------------------------------ */ + /** + * Returns a list of resource names contained in the given resource + */ + @Override + public String[] list() + { + return null; + } + + /* ------------------------------------------------------------ */ + /** + * Returns the resource contained inside the current resource with the + * given name + */ + @Override + public Resource addPath(String path) + throws IOException,MalformedURLException + { + if (path==null) + return null; + + path = URIUtil.canonicalPath(path); + + return newResource(URIUtil.addPaths(_url.toExternalForm(),path)); + } + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + return _urlString; + } + + /* ------------------------------------------------------------ */ + @Override + public int hashCode() + { + return _urlString.hashCode(); + } + + /* ------------------------------------------------------------ */ + @Override + public boolean equals( Object o) + { + return o instanceof URLResource && _urlString.equals(((URLResource)o)._urlString); + } + + /* ------------------------------------------------------------ */ + public boolean getUseCaches () + { + return _useCaches; + } + + /* ------------------------------------------------------------ */ + @Override + public boolean isContainedIn (Resource containingResource) throws MalformedURLException + { + return false; //TODO check this! + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/security/B64Code.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,33 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.security; + + +/* ------------------------------------------------------------ */ +/** + * @deprecated use {@link org.eclipse.jetty.util.B64Code} + */ +@Deprecated +public class B64Code extends org.eclipse.jetty.util.B64Code +{ + public B64Code() + { + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/security/CertificateUtils.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,94 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.security; + +import java.io.InputStream; +import java.security.KeyStore; +import java.security.cert.CRL; +import java.security.cert.CertificateFactory; +import java.util.Collection; + +import org.eclipse.jetty.util.resource.Resource; + +public class CertificateUtils +{ + /* ------------------------------------------------------------ */ + public static KeyStore getKeyStore(InputStream storeStream, String storePath, String storeType, String storeProvider, String storePassword) throws Exception + { + KeyStore keystore = null; + + if (storeStream != null || storePath != null) + { + InputStream inStream = storeStream; + try + { + if (inStream == null) + { + inStream = Resource.newResource(storePath).getInputStream(); + } + + if (storeProvider != null) + { + keystore = KeyStore.getInstance(storeType, storeProvider); + } + else + { + keystore = KeyStore.getInstance(storeType); + } + + keystore.load(inStream, storePassword == null ? null : storePassword.toCharArray()); + } + finally + { + if (inStream != null) + { + inStream.close(); + } + } + } + + return keystore; + } + + /* ------------------------------------------------------------ */ + public static Collection<? extends CRL> loadCRL(String crlPath) throws Exception + { + Collection<? extends CRL> crlList = null; + + if (crlPath != null) + { + InputStream in = null; + try + { + in = Resource.newResource(crlPath).getInputStream(); + crlList = CertificateFactory.getInstance("X.509").generateCRLs(in); + } + finally + { + if (in != null) + { + in.close(); + } + } + } + + return crlList; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/security/CertificateValidator.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,343 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.security; + +import java.security.GeneralSecurityException; +import java.security.InvalidParameterException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.Security; +import java.security.cert.CRL; +import java.security.cert.CertPathBuilder; +import java.security.cert.CertPathBuilderResult; +import java.security.cert.CertPathValidator; +import java.security.cert.CertStore; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CollectionCertStoreParameters; +import java.security.cert.PKIXBuilderParameters; +import java.security.cert.X509CertSelector; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.concurrent.atomic.AtomicLong; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + * Convenience class to handle validation of certificates, aliases and keystores + * + * Allows specifying Certificate Revocation List (CRL), as well as enabling + * CRL Distribution Points Protocol (CRLDP) certificate extension support, + * and also enabling On-Line Certificate Status Protocol (OCSP) support. + * + * IMPORTANT: at least one of the above mechanisms *MUST* be configured and + * operational, otherwise certificate validation *WILL FAIL* unconditionally. + */ +public class CertificateValidator +{ + private static final Logger LOG = Log.getLogger(CertificateValidator.class); + private static AtomicLong __aliasCount = new AtomicLong(); + + private KeyStore _trustStore; + private Collection<? extends CRL> _crls; + + /** Maximum certification path length (n - number of intermediate certs, -1 for unlimited) */ + private int _maxCertPathLength = -1; + /** CRL Distribution Points (CRLDP) support */ + private boolean _enableCRLDP = false; + /** On-Line Certificate Status Protocol (OCSP) support */ + private boolean _enableOCSP = false; + /** Location of OCSP Responder */ + private String _ocspResponderURL; + + /** + * creates an instance of the certificate validator + * + * @param trustStore + * @param crls + */ + public CertificateValidator(KeyStore trustStore, Collection<? extends CRL> crls) + { + if (trustStore == null) + { + throw new InvalidParameterException("TrustStore must be specified for CertificateValidator."); + } + + _trustStore = trustStore; + _crls = crls; + } + + /** + * validates all aliases inside of a given keystore + * + * @param keyStore + * @throws CertificateException + */ + public void validate( KeyStore keyStore ) throws CertificateException + { + try + { + Enumeration<String> aliases = keyStore.aliases(); + + for ( ; aliases.hasMoreElements(); ) + { + String alias = aliases.nextElement(); + + validate(keyStore,alias); + } + + } + catch ( KeyStoreException kse ) + { + throw new CertificateException("Unable to retrieve aliases from keystore", kse); + } + } + + + /** + * validates a specific alias inside of the keystore being passed in + * + * @param keyStore + * @param keyAlias + * @return the keyAlias if valid + * @throws CertificateException + */ + public String validate(KeyStore keyStore, String keyAlias) throws CertificateException + { + String result = null; + + if (keyAlias != null) + { + try + { + validate(keyStore, keyStore.getCertificate(keyAlias)); + } + catch (KeyStoreException kse) + { + LOG.debug(kse); + throw new CertificateException("Unable to validate certificate" + + " for alias [" + keyAlias + "]: " + kse.getMessage(), kse); + } + result = keyAlias; + } + + return result; + } + + /** + * validates a specific certificate inside of the keystore being passed in + * + * @param keyStore + * @param cert + * @throws CertificateException + */ + public void validate(KeyStore keyStore, Certificate cert) throws CertificateException + { + Certificate[] certChain = null; + + if (cert != null && cert instanceof X509Certificate) + { + ((X509Certificate)cert).checkValidity(); + + String certAlias = null; + try + { + if (keyStore == null) + { + throw new InvalidParameterException("Keystore cannot be null"); + } + + certAlias = keyStore.getCertificateAlias((X509Certificate)cert); + if (certAlias == null) + { + certAlias = "JETTY" + String.format("%016X",__aliasCount.incrementAndGet()); + keyStore.setCertificateEntry(certAlias, cert); + } + + certChain = keyStore.getCertificateChain(certAlias); + if (certChain == null || certChain.length == 0) + { + throw new IllegalStateException("Unable to retrieve certificate chain"); + } + } + catch (KeyStoreException kse) + { + LOG.debug(kse); + throw new CertificateException("Unable to validate certificate" + + (certAlias == null ? "":" for alias [" +certAlias + "]") + ": " + kse.getMessage(), kse); + } + + validate(certChain); + } + } + + public void validate(Certificate[] certChain) throws CertificateException + { + try + { + ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>(); + for (Certificate item : certChain) + { + if (item == null) + continue; + + if (!(item instanceof X509Certificate)) + { + throw new IllegalStateException("Invalid certificate type in chain"); + } + + certList.add((X509Certificate)item); + } + + if (certList.isEmpty()) + { + throw new IllegalStateException("Invalid certificate chain"); + + } + + X509CertSelector certSelect = new X509CertSelector(); + certSelect.setCertificate(certList.get(0)); + + // Configure certification path builder parameters + PKIXBuilderParameters pbParams = new PKIXBuilderParameters(_trustStore, certSelect); + pbParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList))); + + // Set maximum certification path length + pbParams.setMaxPathLength(_maxCertPathLength); + + // Enable revocation checking + pbParams.setRevocationEnabled(true); + + // Set static Certificate Revocation List + if (_crls != null && !_crls.isEmpty()) + { + pbParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(_crls))); + } + + // Enable On-Line Certificate Status Protocol (OCSP) support + if (_enableOCSP) + { + Security.setProperty("ocsp.enable","true"); + } + // Enable Certificate Revocation List Distribution Points (CRLDP) support + if (_enableCRLDP) + { + System.setProperty("com.sun.security.enableCRLDP","true"); + } + + // Build certification path + CertPathBuilderResult buildResult = CertPathBuilder.getInstance("PKIX").build(pbParams); + + // Validate certification path + CertPathValidator.getInstance("PKIX").validate(buildResult.getCertPath(),pbParams); + } + catch (GeneralSecurityException gse) + { + LOG.debug(gse); + throw new CertificateException("Unable to validate certificate: " + gse.getMessage(), gse); + } + } + + public KeyStore getTrustStore() + { + return _trustStore; + } + + public Collection<? extends CRL> getCrls() + { + return _crls; + } + + /** + * @return Maximum number of intermediate certificates in + * the certification path (-1 for unlimited) + */ + public int getMaxCertPathLength() + { + return _maxCertPathLength; + } + + /* ------------------------------------------------------------ */ + /** + * @param maxCertPathLength + * maximum number of intermediate certificates in + * the certification path (-1 for unlimited) + */ + public void setMaxCertPathLength(int maxCertPathLength) + { + _maxCertPathLength = maxCertPathLength; + } + + /* ------------------------------------------------------------ */ + /** + * @return true if CRL Distribution Points support is enabled + */ + public boolean isEnableCRLDP() + { + return _enableCRLDP; + } + + /* ------------------------------------------------------------ */ + /** Enables CRL Distribution Points Support + * @param enableCRLDP true - turn on, false - turns off + */ + public void setEnableCRLDP(boolean enableCRLDP) + { + _enableCRLDP = enableCRLDP; + } + + /* ------------------------------------------------------------ */ + /** + * @return true if On-Line Certificate Status Protocol support is enabled + */ + public boolean isEnableOCSP() + { + return _enableOCSP; + } + + /* ------------------------------------------------------------ */ + /** Enables On-Line Certificate Status Protocol support + * @param enableOCSP true - turn on, false - turn off + */ + public void setEnableOCSP(boolean enableOCSP) + { + _enableOCSP = enableOCSP; + } + + /* ------------------------------------------------------------ */ + /** + * @return Location of the OCSP Responder + */ + public String getOcspResponderURL() + { + return _ocspResponderURL; + } + + /* ------------------------------------------------------------ */ + /** Set the location of the OCSP Responder. + * @param ocspResponderURL location of the OCSP Responder + */ + public void setOcspResponderURL(String ocspResponderURL) + { + _ocspResponderURL = ocspResponderURL; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/security/Constraint.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,226 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.security; + +import java.io.Serializable; +import java.util.Arrays; + +/* ------------------------------------------------------------ */ +/** + * Describe an auth and/or data constraint. + * + * + */ +public class Constraint implements Cloneable, Serializable +{ + /* ------------------------------------------------------------ */ + public final static String __BASIC_AUTH = "BASIC"; + + public final static String __FORM_AUTH = "FORM"; + + public final static String __DIGEST_AUTH = "DIGEST"; + + public final static String __CERT_AUTH = "CLIENT_CERT"; + + public final static String __CERT_AUTH2 = "CLIENT-CERT"; + + public final static String __SPNEGO_AUTH = "SPNEGO"; + + public final static String __NEGOTIATE_AUTH = "NEGOTIATE"; + + public static boolean validateMethod (String method) + { + if (method == null) + return false; + method = method.trim(); + return (method.equals(__FORM_AUTH) + || method.equals(__BASIC_AUTH) + || method.equals (__DIGEST_AUTH) + || method.equals (__CERT_AUTH) + || method.equals(__CERT_AUTH2) + || method.equals(__SPNEGO_AUTH) + || method.equals(__NEGOTIATE_AUTH)); + } + + /* ------------------------------------------------------------ */ + public final static int DC_UNSET = -1, DC_NONE = 0, DC_INTEGRAL = 1, DC_CONFIDENTIAL = 2, DC_FORBIDDEN = 3; + + /* ------------------------------------------------------------ */ + public final static String NONE = "NONE"; + + public final static String ANY_ROLE = "*"; + + /* ------------------------------------------------------------ */ + private String _name; + + private String[] _roles; + + private int _dataConstraint = DC_UNSET; + + private boolean _anyRole = false; + + private boolean _authenticate = false; + + /* ------------------------------------------------------------ */ + /** + * Constructor. + */ + public Constraint() + { + } + + /* ------------------------------------------------------------ */ + /** + * Conveniance Constructor. + * + * @param name + * @param role + */ + public Constraint(String name, String role) + { + setName(name); + setRoles(new String[] { role }); + } + + /* ------------------------------------------------------------ */ + @Override + public Object clone() throws CloneNotSupportedException + { + return super.clone(); + } + + /* ------------------------------------------------------------ */ + /** + * @param name + */ + public void setName(String name) + { + _name = name; + } + + /* ------------------------------------------------------------ */ + public void setRoles(String[] roles) + { + _roles = roles; + _anyRole = false; + if (roles != null) + for (int i = roles.length; !_anyRole && i-- > 0;) + _anyRole |= ANY_ROLE.equals(roles[i]); + } + + /* ------------------------------------------------------------ */ + /** + * @return True if any user role is permitted. + */ + public boolean isAnyRole() + { + return _anyRole; + } + + /* ------------------------------------------------------------ */ + /** + * @return List of roles for this constraint. + */ + public String[] getRoles() + { + return _roles; + } + + /* ------------------------------------------------------------ */ + /** + * @param role + * @return True if the constraint contains the role. + */ + public boolean hasRole(String role) + { + if (_anyRole) return true; + if (_roles != null) for (int i = _roles.length; i-- > 0;) + if (role.equals(_roles[i])) return true; + return false; + } + + /* ------------------------------------------------------------ */ + /** + * @param authenticate True if users must be authenticated + */ + public void setAuthenticate(boolean authenticate) + { + _authenticate = authenticate; + } + + /* ------------------------------------------------------------ */ + /** + * @return True if the constraint requires request authentication + */ + public boolean getAuthenticate() + { + return _authenticate; + } + + /* ------------------------------------------------------------ */ + /** + * @return True if authentication required but no roles set + */ + public boolean isForbidden() + { + return _authenticate && !_anyRole && (_roles == null || _roles.length == 0); + } + + /* ------------------------------------------------------------ */ + /** + * @param c Data constrain indicator: 0=DC+NONE, 1=DC_INTEGRAL & + * 2=DC_CONFIDENTIAL + */ + public void setDataConstraint(int c) + { + if (c < 0 || c > DC_CONFIDENTIAL) throw new IllegalArgumentException("Constraint out of range"); + _dataConstraint = c; + } + + /* ------------------------------------------------------------ */ + /** + * @return Data constrain indicator: 0=DC+NONE, 1=DC_INTEGRAL & + * 2=DC_CONFIDENTIAL + */ + public int getDataConstraint() + { + return _dataConstraint; + } + + /* ------------------------------------------------------------ */ + /** + * @return True if a data constraint has been set. + */ + public boolean hasDataConstraint() + { + return _dataConstraint >= DC_NONE; + } + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + return "SC{" + _name + + "," + + (_anyRole ? "*" : (_roles == null ? "-" : Arrays.asList(_roles).toString())) + + "," + + (_dataConstraint == DC_UNSET ? "DC_UNSET}" : (_dataConstraint == DC_NONE ? "NONE}" : (_dataConstraint == DC_INTEGRAL ? "INTEGRAL}" : "CONFIDENTIAL}"))); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/security/Credential.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,229 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.security; + +import java.io.Serializable; +import java.security.MessageDigest; + +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/* ------------------------------------------------------------ */ +/** + * Credentials. The Credential class represents an abstract mechanism for + * checking authentication credentials. A credential instance either represents + * a secret, or some data that could only be derived from knowing the secret. + * <p> + * Often a Credential is related to a Password via a one way algorithm, so while + * a Password itself is a Credential, a UnixCrypt or MD5 digest of a a password + * is only a credential that can be checked against the password. + * <p> + * This class includes an implementation for unix Crypt an MD5 digest. + * + * @see Password + * + */ +public abstract class Credential implements Serializable +{ + private static final Logger LOG = Log.getLogger(Credential.class); + + private static final long serialVersionUID = -7760551052768181572L; + + /* ------------------------------------------------------------ */ + /** + * Check a credential + * + * @param credentials The credential to check against. This may either be + * another Credential object, a Password object or a String + * which is interpreted by this credential. + * @return True if the credentials indicated that the shared secret is known + * to both this Credential and the passed credential. + */ + public abstract boolean check(Object credentials); + + /* ------------------------------------------------------------ */ + /** + * Get a credential from a String. If the credential String starts with a + * known Credential type (eg "CRYPT:" or "MD5:" ) then a Credential of that + * type is returned. Else the credential is assumed to be a Password. + * + * @param credential String representation of the credential + * @return A Credential or Password instance. + */ + public static Credential getCredential(String credential) + { + if (credential.startsWith(Crypt.__TYPE)) return new Crypt(credential); + if (credential.startsWith(MD5.__TYPE)) return new MD5(credential); + + return new Password(credential); + } + + /* ------------------------------------------------------------ */ + /** + * Unix Crypt Credentials + */ + public static class Crypt extends Credential + { + private static final long serialVersionUID = -2027792997664744210L; + + public static final String __TYPE = "CRYPT:"; + + private final String _cooked; + + Crypt(String cooked) + { + _cooked = cooked.startsWith(Crypt.__TYPE) ? cooked.substring(__TYPE.length()) : cooked; + } + + @Override + public boolean check(Object credentials) + { + if (credentials instanceof char[]) + credentials=new String((char[])credentials); + if (!(credentials instanceof String) && !(credentials instanceof Password)) + LOG.warn("Can't check " + credentials.getClass() + " against CRYPT"); + + String passwd = credentials.toString(); + return _cooked.equals(UnixCrypt.crypt(passwd, _cooked)); + } + + public static String crypt(String user, String pw) + { + return "CRYPT:" + UnixCrypt.crypt(pw, user); + } + } + + /* ------------------------------------------------------------ */ + /** + * MD5 Credentials + */ + public static class MD5 extends Credential + { + private static final long serialVersionUID = 5533846540822684240L; + + public static final String __TYPE = "MD5:"; + + public static final Object __md5Lock = new Object(); + + private static MessageDigest __md; + + private final byte[] _digest; + + /* ------------------------------------------------------------ */ + MD5(String digest) + { + digest = digest.startsWith(__TYPE) ? digest.substring(__TYPE.length()) : digest; + _digest = TypeUtil.parseBytes(digest, 16); + } + + /* ------------------------------------------------------------ */ + public byte[] getDigest() + { + return _digest; + } + + /* ------------------------------------------------------------ */ + @Override + public boolean check(Object credentials) + { + try + { + byte[] digest = null; + + if (credentials instanceof char[]) + credentials=new String((char[])credentials); + if (credentials instanceof Password || credentials instanceof String) + { + synchronized (__md5Lock) + { + if (__md == null) __md = MessageDigest.getInstance("MD5"); + __md.reset(); + __md.update(credentials.toString().getBytes(StringUtil.__ISO_8859_1)); + digest = __md.digest(); + } + if (digest == null || digest.length != _digest.length) return false; + for (int i = 0; i < digest.length; i++) + if (digest[i] != _digest[i]) return false; + return true; + } + else if (credentials instanceof MD5) + { + MD5 md5 = (MD5) credentials; + if (_digest.length != md5._digest.length) return false; + for (int i = 0; i < _digest.length; i++) + if (_digest[i] != md5._digest[i]) return false; + return true; + } + else if (credentials instanceof Credential) + { + // Allow credential to attempt check - i.e. this'll work + // for DigestAuthModule$Digest credentials + return ((Credential) credentials).check(this); + } + else + { + LOG.warn("Can't check " + credentials.getClass() + " against MD5"); + return false; + } + } + catch (Exception e) + { + LOG.warn(e); + return false; + } + } + + /* ------------------------------------------------------------ */ + public static String digest(String password) + { + try + { + byte[] digest; + synchronized (__md5Lock) + { + if (__md == null) + { + try + { + __md = MessageDigest.getInstance("MD5"); + } + catch (Exception e) + { + LOG.warn(e); + return null; + } + } + + __md.reset(); + __md.update(password.getBytes(StringUtil.__ISO_8859_1)); + digest = __md.digest(); + } + + return __TYPE + TypeUtil.toString(digest, 16); + } + catch (Exception e) + { + LOG.warn(e); + return null; + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/security/Password.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,257 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.security; + +import java.io.IOException; +import java.util.Arrays; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/* ------------------------------------------------------------ */ +/** + * Password utility class. + * + * This utility class gets a password or pass phrase either by: + * + * <PRE> + * + Password is set as a system property. + * + The password is prompted for and read from standard input + * + A program is run to get the password. + * </pre> + * + * Passwords that begin with OBF: are de obfuscated. Passwords can be obfuscated + * by run org.eclipse.util.Password as a main class. Obfuscated password are + * required if a system needs to recover the full password (eg. so that it may + * be passed to another system). They are not secure, but prevent casual + * observation. + * <p> + * Passwords that begin with CRYPT: are oneway encrypted with UnixCrypt. The + * real password cannot be retrieved, but comparisons can be made to other + * passwords. A Crypt can be generated by running org.eclipse.util.UnixCrypt as + * a main class, passing password and then the username. Checksum passwords are + * a secure(ish) way to store passwords that only need to be checked rather than + * recovered. Note that it is not strong security - specially if simple + * passwords are used. + * + * + */ +public class Password extends Credential +{ + private static final Logger LOG = Log.getLogger(Password.class); + + private static final long serialVersionUID = 5062906681431569445L; + + public static final String __OBFUSCATE = "OBF:"; + + private String _pw; + + /* ------------------------------------------------------------ */ + /** + * Constructor. + * + * @param password The String password. + */ + public Password(String password) + { + _pw = password; + + // expand password + while (_pw != null && _pw.startsWith(__OBFUSCATE)) + _pw = deobfuscate(_pw); + } + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + return _pw; + } + + /* ------------------------------------------------------------ */ + public String toStarString() + { + return "*****************************************************".substring(0, _pw.length()); + } + + /* ------------------------------------------------------------ */ + @Override + public boolean check(Object credentials) + { + if (this == credentials) return true; + + if (credentials instanceof Password) return credentials.equals(_pw); + + if (credentials instanceof String) return credentials.equals(_pw); + + if (credentials instanceof char[]) return Arrays.equals(_pw.toCharArray(), (char[]) credentials); + + if (credentials instanceof Credential) return ((Credential) credentials).check(_pw); + + return false; + } + + /* ------------------------------------------------------------ */ + @Override + public boolean equals(Object o) + { + if (this == o) + return true; + + if (null == o) + return false; + + if (o instanceof Password) + { + Password p = (Password) o; + //noinspection StringEquality + return p._pw == _pw || (null != _pw && _pw.equals(p._pw)); + } + + if (o instanceof String) + return o.equals(_pw); + + return false; + } + + /* ------------------------------------------------------------ */ + @Override + public int hashCode() + { + return null == _pw ? super.hashCode() : _pw.hashCode(); + } + + /* ------------------------------------------------------------ */ + public static String obfuscate(String s) + { + StringBuilder buf = new StringBuilder(); + byte[] b = s.getBytes(); + + buf.append(__OBFUSCATE); + for (int i = 0; i < b.length; i++) + { + byte b1 = b[i]; + byte b2 = b[s.length() - (i + 1)]; + int i1 = 127 + b1 + b2; + int i2 = 127 + b1 - b2; + int i0 = i1 * 256 + i2; + String x = Integer.toString(i0, 36); + + switch (x.length()) + { + case 1: + buf.append('0'); + buf.append('0'); + buf.append('0'); + buf.append(x); + break; + case 2: + buf.append('0'); + buf.append('0'); + buf.append(x); + break; + case 3: + buf.append('0'); + buf.append(x); + break; + default: + buf.append(x); + break; + } + } + return buf.toString(); + + } + + /* ------------------------------------------------------------ */ + public static String deobfuscate(String s) + { + if (s.startsWith(__OBFUSCATE)) s = s.substring(4); + + byte[] b = new byte[s.length() / 2]; + int l = 0; + for (int i = 0; i < s.length(); i += 4) + { + String x = s.substring(i, i + 4); + int i0 = Integer.parseInt(x, 36); + int i1 = (i0 / 256); + int i2 = (i0 % 256); + b[l++] = (byte) ((i1 + i2 - 254) / 2); + } + + return new String(b, 0, l); + } + + /* ------------------------------------------------------------ */ + /** + * Get a password. A password is obtained by trying + * <UL> + * <LI>Calling <Code>System.getProperty(realm,dft)</Code> + * <LI>Prompting for a password + * <LI>Using promptDft if nothing was entered. + * </UL> + * + * @param realm The realm name for the password, used as a SystemProperty + * name. + * @param dft The default password. + * @param promptDft The default to use if prompting for the password. + * @return Password + */ + public static Password getPassword(String realm, String dft, String promptDft) + { + String passwd = System.getProperty(realm, dft); + if (passwd == null || passwd.length() == 0) + { + try + { + System.out.print(realm + ((promptDft != null && promptDft.length() > 0) ? " [dft]" : "") + " : "); + System.out.flush(); + byte[] buf = new byte[512]; + int len = System.in.read(buf); + if (len > 0) passwd = new String(buf, 0, len).trim(); + } + catch (IOException e) + { + LOG.warn(Log.EXCEPTION, e); + } + if (passwd == null || passwd.length() == 0) passwd = promptDft; + } + return new Password(passwd); + } + + /* ------------------------------------------------------------ */ + /** + * @param arg + */ + public static void main(String[] arg) + { + if (arg.length != 1 && arg.length != 2) + { + System.err.println("Usage - java org.eclipse.jetty.security.Password [<user>] <password>"); + System.err.println("If the password is ?, the user will be prompted for the password"); + System.exit(1); + } + String p = arg[arg.length == 1 ? 0 : 1]; + Password pw = new Password(p); + System.err.println(pw.toString()); + System.err.println(obfuscate(pw.toString())); + System.err.println(Credential.MD5.digest(p)); + if (arg.length == 2) System.err.println(Credential.Crypt.crypt(arg[0], pw.toString())); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/security/UnixCrypt.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,461 @@ +/* + * @(#)UnixCrypt.java 0.9 96/11/25 + * + * Copyright (c) 1996 Aki Yoshida. All rights reserved. + * + * Permission to use, copy, modify and distribute this software + * for non-commercial or commercial purposes and without fee is + * hereby granted provided that this copyright notice appears in + * all copies. + */ + +/** + * Unix crypt(3C) utility + * + * @version 0.9, 11/25/96 + * @author Aki Yoshida + */ + +/** + * modified April 2001 + * by Iris Van den Broeke, Daniel Deville + */ + +package org.eclipse.jetty.util.security; + + +/* ------------------------------------------------------------ */ +/** + * Unix Crypt. Implements the one way cryptography used by Unix systems for + * simple password protection. + * + * @version $Id: UnixCrypt.java,v 1.1 2005/10/05 14:09:14 janb Exp $ + * @author Greg Wilkins (gregw) + */ +public class UnixCrypt +{ + + /* (mostly) Standard DES Tables from Tom Truscott */ + private static final byte[] IP = { /* initial permutation */ + 58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, 57, 49, 41, 33, 25, 17, 9, 1, + 59, 51, 43, 35, 27, 19, 11, 3, 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7 }; + + /* The final permutation is the inverse of IP - no table is necessary */ + private static final byte[] ExpandTr = { /* expansion operation */ + 32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, 8, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17, 16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25, 24, 25, 26, 27, 28, 29, + 28, 29, 30, 31, 32, 1 }; + + private static final byte[] PC1 = { /* permuted choice table 1 */ + 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36, + + 63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4 }; + + private static final byte[] Rotates = { /* PC1 rotation schedule */ + 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 }; + + private static final byte[] PC2 = { /* permuted choice table 2 */ + 9, 18, 14, 17, 11, 24, 1, 5, 22, 25, 3, 28, 15, 6, 21, 10, 35, 38, 23, 19, 12, 4, 26, 8, 43, 54, 16, 7, 27, 20, 13, 2, + + 0, 0, 41, 52, 31, 37, 47, 55, 0, 0, 30, 40, 51, 45, 33, 48, 0, 0, 44, 49, 39, 56, 34, 53, 0, 0, 46, 42, 50, 36, 29, 32 }; + + private static final byte[][] S = { /* 48->32 bit substitution tables */ + /* S[1] */ + { 14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8, 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, + 7, 3, 10, 5, 0, 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13 }, + /* S[2] */ + { 15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5, 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, + 6, 9, 3, 2, 15, 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9 }, + /* S[3] */ + { 10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1, 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, + 12, 5, 10, 14, 7, 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12 }, + /* S[4] */ + { 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9, 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, + 14, 5, 2, 8, 4, 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14 }, + /* S[5] */ + { 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6, 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, + 5, 6, 3, 0, 14, 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3 }, + /* S[6] */ + { 12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8, 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, + 10, 1, 13, 11, 6, 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13 }, + /* S[7] */ + { 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6, 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, + 6, 8, 0, 5, 9, 2, 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12 }, + /* S[8] */ + { 13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2, 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, + 13, 15, 3, 5, 8, 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11 } }; + + private static final byte[] P32Tr = { /* 32-bit permutation function */ + 16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, 2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25 }; + + private static final byte[] CIFP = { /* + * compressed/interleaved + * permutation + */ + 1, 2, 3, 4, 17, 18, 19, 20, 5, 6, 7, 8, 21, 22, 23, 24, 9, 10, 11, 12, 25, 26, 27, 28, 13, 14, 15, 16, 29, 30, 31, 32, + + 33, 34, 35, 36, 49, 50, 51, 52, 37, 38, 39, 40, 53, 54, 55, 56, 41, 42, 43, 44, 57, 58, 59, 60, 45, 46, 47, 48, 61, 62, 63, 64 }; + + private static final byte[] ITOA64 = { /* 0..63 => ascii-64 */ + (byte) '.', (byte) '/', (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'A', + (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', + (byte) 'N', (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', + (byte) 'Z', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', + (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', + (byte) 'x', (byte) 'y', (byte) 'z' }; + + /* ===== Tables that are initialized at run time ==================== */ + + private static final byte[] A64TOI = new byte[128]; /* ascii-64 => 0..63 */ + + /* Initial key schedule permutation */ + private static final long[][] PC1ROT = new long[16][16]; + + /* Subsequent key schedule rotation permutations */ + private static final long[][][] PC2ROT = new long[2][16][16]; + + /* Initial permutation/expansion table */ + private static final long[][] IE3264 = new long[8][16]; + + /* Table that combines the S, P, and E operations. */ + private static final long[][] SPE = new long[8][64]; + + /* compressed/interleaved => final permutation table */ + private static final long[][] CF6464 = new long[16][16]; + + /* ==================================== */ + + static + { + byte[] perm = new byte[64]; + byte[] temp = new byte[64]; + + // inverse table. + for (int i = 0; i < 64; i++) + A64TOI[ITOA64[i]] = (byte) i; + + // PC1ROT - bit reverse, then PC1, then Rotate, then PC2 + for (int i = 0; i < 64; i++) + perm[i] = (byte) 0; + + for (int i = 0; i < 64; i++) + { + int k; + if ((k = PC2[i]) == 0) continue; + k += Rotates[0] - 1; + if ((k % 28) < Rotates[0]) k -= 28; + k = PC1[k]; + if (k > 0) + { + k--; + k = (k | 0x07) - (k & 0x07); + k++; + } + perm[i] = (byte) k; + } + init_perm(PC1ROT, perm, 8); + + // PC2ROT - PC2 inverse, then Rotate, then PC2 + for (int j = 0; j < 2; j++) + { + int k; + for (int i = 0; i < 64; i++) + perm[i] = temp[i] = 0; + for (int i = 0; i < 64; i++) + { + if ((k = PC2[i]) == 0) continue; + temp[k - 1] = (byte) (i + 1); + } + for (int i = 0; i < 64; i++) + { + if ((k = PC2[i]) == 0) continue; + k += j; + if ((k % 28) <= j) k -= 28; + perm[i] = temp[k]; + } + + init_perm(PC2ROT[j], perm, 8); + } + + // Bit reverse, intial permupation, expantion + for (int i = 0; i < 8; i++) + { + for (int j = 0; j < 8; j++) + { + int k = (j < 2) ? 0 : IP[ExpandTr[i * 6 + j - 2] - 1]; + if (k > 32) + k -= 32; + else if (k > 0) k--; + if (k > 0) + { + k--; + k = (k | 0x07) - (k & 0x07); + k++; + } + perm[i * 8 + j] = (byte) k; + } + } + + init_perm(IE3264, perm, 8); + + // Compression, final permutation, bit reverse + for (int i = 0; i < 64; i++) + { + int k = IP[CIFP[i] - 1]; + if (k > 0) + { + k--; + k = (k | 0x07) - (k & 0x07); + k++; + } + perm[k - 1] = (byte) (i + 1); + } + + init_perm(CF6464, perm, 8); + + // SPE table + for (int i = 0; i < 48; i++) + perm[i] = P32Tr[ExpandTr[i] - 1]; + for (int t = 0; t < 8; t++) + { + for (int j = 0; j < 64; j++) + { + int k = (((j >> 0) & 0x01) << 5) | (((j >> 1) & 0x01) << 3) + | (((j >> 2) & 0x01) << 2) + | (((j >> 3) & 0x01) << 1) + | (((j >> 4) & 0x01) << 0) + | (((j >> 5) & 0x01) << 4); + k = S[t][k]; + k = (((k >> 3) & 0x01) << 0) | (((k >> 2) & 0x01) << 1) | (((k >> 1) & 0x01) << 2) | (((k >> 0) & 0x01) << 3); + for (int i = 0; i < 32; i++) + temp[i] = 0; + for (int i = 0; i < 4; i++) + temp[4 * t + i] = (byte) ((k >> i) & 0x01); + long kk = 0; + for (int i = 24; --i >= 0;) + kk = ((kk << 1) | ((long) temp[perm[i] - 1]) << 32 | (temp[perm[i + 24] - 1])); + + SPE[t][j] = to_six_bit(kk); + } + } + } + + /** + * You can't call the constructer. + */ + private UnixCrypt() + { + } + + /** + * Returns the transposed and split code of a 24-bit code into a 4-byte + * code, each having 6 bits. + */ + private static int to_six_bit(int num) + { + return (((num << 26) & 0xfc000000) | ((num << 12) & 0xfc0000) | ((num >> 2) & 0xfc00) | ((num >> 16) & 0xfc)); + } + + /** + * Returns the transposed and split code of two 24-bit code into two 4-byte + * code, each having 6 bits. + */ + private static long to_six_bit(long num) + { + return (((num << 26) & 0xfc000000fc000000L) | ((num << 12) & 0xfc000000fc0000L) | ((num >> 2) & 0xfc000000fc00L) | ((num >> 16) & 0xfc000000fcL)); + } + + /** + * Returns the permutation of the given 64-bit code with the specified + * permutataion table. + */ + private static long perm6464(long c, long[][] p) + { + long out = 0L; + for (int i = 8; --i >= 0;) + { + int t = (int) (0x00ff & c); + c >>= 8; + long tp = p[i << 1][t & 0x0f]; + out |= tp; + tp = p[(i << 1) + 1][t >> 4]; + out |= tp; + } + return out; + } + + /** + * Returns the permutation of the given 32-bit code with the specified + * permutataion table. + */ + private static long perm3264(int c, long[][] p) + { + long out = 0L; + for (int i = 4; --i >= 0;) + { + int t = (0x00ff & c); + c >>= 8; + long tp = p[i << 1][t & 0x0f]; + out |= tp; + tp = p[(i << 1) + 1][t >> 4]; + out |= tp; + } + return out; + } + + /** + * Returns the key schedule for the given key. + */ + private static long[] des_setkey(long keyword) + { + long K = perm6464(keyword, PC1ROT); + long[] KS = new long[16]; + KS[0] = K & ~0x0303030300000000L; + + for (int i = 1; i < 16; i++) + { + KS[i] = K; + K = perm6464(K, PC2ROT[Rotates[i] - 1]); + + KS[i] = K & ~0x0303030300000000L; + } + return KS; + } + + /** + * Returns the DES encrypted code of the given word with the specified + * environment. + */ + private static long des_cipher(long in, int salt, int num_iter, long[] KS) + { + salt = to_six_bit(salt); + long L = in; + long R = L; + L &= 0x5555555555555555L; + R = (R & 0xaaaaaaaa00000000L) | ((R >> 1) & 0x0000000055555555L); + L = ((((L << 1) | (L << 32)) & 0xffffffff00000000L) | ((R | (R >> 32)) & 0x00000000ffffffffL)); + + L = perm3264((int) (L >> 32), IE3264); + R = perm3264((int) (L & 0xffffffff), IE3264); + + while (--num_iter >= 0) + { + for (int loop_count = 0; loop_count < 8; loop_count++) + { + long kp; + long B; + long k; + + kp = KS[(loop_count << 1)]; + k = ((R >> 32) ^ R) & salt & 0xffffffffL; + k |= (k << 32); + B = (k ^ R ^ kp); + + L ^= (SPE[0][(int) ((B >> 58) & 0x3f)] ^ SPE[1][(int) ((B >> 50) & 0x3f)] + ^ SPE[2][(int) ((B >> 42) & 0x3f)] + ^ SPE[3][(int) ((B >> 34) & 0x3f)] + ^ SPE[4][(int) ((B >> 26) & 0x3f)] + ^ SPE[5][(int) ((B >> 18) & 0x3f)] + ^ SPE[6][(int) ((B >> 10) & 0x3f)] ^ SPE[7][(int) ((B >> 2) & 0x3f)]); + + kp = KS[(loop_count << 1) + 1]; + k = ((L >> 32) ^ L) & salt & 0xffffffffL; + k |= (k << 32); + B = (k ^ L ^ kp); + + R ^= (SPE[0][(int) ((B >> 58) & 0x3f)] ^ SPE[1][(int) ((B >> 50) & 0x3f)] + ^ SPE[2][(int) ((B >> 42) & 0x3f)] + ^ SPE[3][(int) ((B >> 34) & 0x3f)] + ^ SPE[4][(int) ((B >> 26) & 0x3f)] + ^ SPE[5][(int) ((B >> 18) & 0x3f)] + ^ SPE[6][(int) ((B >> 10) & 0x3f)] ^ SPE[7][(int) ((B >> 2) & 0x3f)]); + } + // swap L and R + L ^= R; + R ^= L; + L ^= R; + } + L = ((((L >> 35) & 0x0f0f0f0fL) | (((L & 0xffffffff) << 1) & 0xf0f0f0f0L)) << 32 | (((R >> 35) & 0x0f0f0f0fL) | (((R & 0xffffffff) << 1) & 0xf0f0f0f0L))); + + L = perm6464(L, CF6464); + + return L; + } + + /** + * Initializes the given permutation table with the mapping table. + */ + private static void init_perm(long[][] perm, byte[] p, int chars_out) + { + for (int k = 0; k < chars_out * 8; k++) + { + + int l = p[k] - 1; + if (l < 0) continue; + int i = l >> 2; + l = 1 << (l & 0x03); + for (int j = 0; j < 16; j++) + { + int s = ((k & 0x07) + ((7 - (k >> 3)) << 3)); + if ((j & l) != 0x00) perm[i][j] |= (1L << s); + } + } + } + + /** + * Encrypts String into crypt (Unix) code. + * + * @param key the key to be encrypted + * @param setting the salt to be used + * @return the encrypted String + */ + public static String crypt(String key, String setting) + { + long constdatablock = 0L; /* encryption constant */ + byte[] cryptresult = new byte[13]; /* encrypted result */ + long keyword = 0L; + /* invalid parameters! */ + if (key == null || setting == null) return "*"; // will NOT match under + // ANY circumstances! + + int keylen = key.length(); + + for (int i = 0; i < 8; i++) + { + keyword = (keyword << 8) | ((i < keylen) ? 2 * key.charAt(i) : 0); + } + + long[] KS = des_setkey(keyword); + + int salt = 0; + for (int i = 2; --i >= 0;) + { + char c = (i < setting.length()) ? setting.charAt(i) : '.'; + cryptresult[i] = (byte) c; + salt = (salt << 6) | (0x00ff & A64TOI[c]); + } + + long rsltblock = des_cipher(constdatablock, salt, 25, KS); + + cryptresult[12] = ITOA64[(((int) rsltblock) << 2) & 0x3f]; + rsltblock >>= 4; + for (int i = 12; --i >= 2;) + { + cryptresult[i] = ITOA64[((int) rsltblock) & 0x3f]; + rsltblock >>= 6; + } + + return new String(cryptresult, 0, 13); + } + + public static void main(String[] arg) + { + if (arg.length != 2) + { + System.err.println("Usage - java org.eclipse.util.UnixCrypt <key> <salt>"); + System.exit(1); + } + + System.err.println("Crypt=" + crypt(arg[0], arg[1])); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/ssl/AliasedX509ExtendedKeyManager.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,130 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.ssl; + +import java.net.Socket; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.X509ExtendedKeyManager; +import javax.net.ssl.X509KeyManager; + + +/* ------------------------------------------------------------ */ +/** + * KeyManager to select a key with desired alias + * while delegating processing to specified KeyManager + * Can be used both with server and client sockets + */ +public class AliasedX509ExtendedKeyManager extends X509ExtendedKeyManager +{ + private String _keyAlias; + private X509KeyManager _keyManager; + + /* ------------------------------------------------------------ */ + /** + * Construct KeyManager instance + * @param keyAlias Alias of the key to be selected + * @param keyManager Instance of KeyManager to be wrapped + * @throws Exception + */ + public AliasedX509ExtendedKeyManager(String keyAlias, X509KeyManager keyManager) throws Exception + { + _keyAlias = keyAlias; + _keyManager = keyManager; + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.net.ssl.X509KeyManager#chooseClientAlias(java.lang.String[], java.security.Principal[], java.net.Socket) + */ + public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) + { + return _keyAlias == null ? _keyManager.chooseClientAlias(keyType, issuers, socket) : _keyAlias; + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.net.ssl.X509KeyManager#chooseServerAlias(java.lang.String, java.security.Principal[], java.net.Socket) + */ + public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) + { + return _keyAlias == null ? _keyManager.chooseServerAlias(keyType, issuers, socket) : _keyAlias; + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.net.ssl.X509KeyManager#getClientAliases(java.lang.String, java.security.Principal[]) + */ + public String[] getClientAliases(String keyType, Principal[] issuers) + { + return _keyManager.getClientAliases(keyType, issuers); + } + + + /* ------------------------------------------------------------ */ + /** + * @see javax.net.ssl.X509KeyManager#getServerAliases(java.lang.String, java.security.Principal[]) + */ + public String[] getServerAliases(String keyType, Principal[] issuers) + { + return _keyManager.getServerAliases(keyType, issuers); + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.net.ssl.X509KeyManager#getCertificateChain(java.lang.String) + */ + public X509Certificate[] getCertificateChain(String alias) + { + return _keyManager.getCertificateChain(alias); + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.net.ssl.X509KeyManager#getPrivateKey(java.lang.String) + */ + public PrivateKey getPrivateKey(String alias) + { + return _keyManager.getPrivateKey(alias); + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.net.ssl.X509ExtendedKeyManager#chooseEngineServerAlias(java.lang.String, java.security.Principal[], javax.net.ssl.SSLEngine) + */ + @Override + public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) + { + return _keyAlias == null ? super.chooseEngineServerAlias(keyType,issuers,engine) : _keyAlias; + } + + + /* ------------------------------------------------------------ */ + /** + * @see javax.net.ssl.X509ExtendedKeyManager#chooseEngineClientAlias(String[], Principal[], SSLEngine) + */ + @Override + public String chooseEngineClientAlias(String keyType[], Principal[] issuers, SSLEngine engine) + { + return _keyAlias == null ? super.chooseEngineClientAlias(keyType,issuers,engine) : _keyAlias; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/ssl/AliasedX509KeyManager.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,107 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.ssl; + +import java.net.Socket; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; + +import javax.net.ssl.X509KeyManager; + + +/* ------------------------------------------------------------ */ +/** + * KeyManager to select a key with desired alias + * while delegating processing to specified KeyManager + * Can be used both with server and client sockets + */ +public class AliasedX509KeyManager implements X509KeyManager +{ + private String _keyAlias; + private X509KeyManager _keyManager; + + /* ------------------------------------------------------------ */ + /** + * Construct KeyManager instance + * @param keyAlias Alias of the key to be selected + * @param keyManager Instance of KeyManager to be wrapped + * @throws Exception + */ + public AliasedX509KeyManager(String keyAlias, X509KeyManager keyManager) throws Exception + { + _keyAlias = keyAlias; + _keyManager = keyManager; + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.net.ssl.X509KeyManager#chooseClientAlias(java.lang.String[], java.security.Principal[], java.net.Socket) + */ + public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) + { + return _keyAlias == null ? _keyManager.chooseClientAlias(keyType, issuers, socket) : _keyAlias; + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.net.ssl.X509KeyManager#chooseServerAlias(java.lang.String, java.security.Principal[], java.net.Socket) + */ + public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) + { + return _keyAlias == null ?_keyManager.chooseServerAlias(keyType, issuers, socket) : _keyAlias; + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.net.ssl.X509KeyManager#getClientAliases(java.lang.String, java.security.Principal[]) + */ + public String[] getClientAliases(String keyType, Principal[] issuers) + { + return _keyManager.getClientAliases(keyType, issuers); + } + + + /* ------------------------------------------------------------ */ + /** + * @see javax.net.ssl.X509KeyManager#getServerAliases(java.lang.String, java.security.Principal[]) + */ + public String[] getServerAliases(String keyType, Principal[] issuers) + { + return _keyManager.getServerAliases(keyType, issuers); + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.net.ssl.X509KeyManager#getCertificateChain(java.lang.String) + */ + public X509Certificate[] getCertificateChain(String alias) + { + return _keyManager.getCertificateChain(alias); + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.net.ssl.X509KeyManager#getPrivateKey(java.lang.String) + */ + public PrivateKey getPrivateKey(String alias) + { + return _keyManager.getPrivateKey(alias); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/ssl/SslContextFactory.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,1537 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.ssl; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.InetAddress; +import java.security.InvalidParameterException; +import java.security.KeyStore; +import java.security.SecureRandom; +import java.security.Security; +import java.security.cert.CRL; +import java.security.cert.CertStore; +import java.security.cert.Certificate; +import java.security.cert.CollectionCertStoreParameters; +import java.security.cert.PKIXBuilderParameters; +import java.security.cert.X509CertSelector; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import javax.net.ssl.CertPathTrustManagerParameters; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLServerSocketFactory; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509KeyManager; +import javax.net.ssl.X509TrustManager; + +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.security.CertificateUtils; +import org.eclipse.jetty.util.security.CertificateValidator; +import org.eclipse.jetty.util.security.Password; + + +/* ------------------------------------------------------------ */ +/** + * SslContextFactory is used to configure SSL connectors + * as well as HttpClient. It holds all SSL parameters and + * creates SSL context based on these parameters to be + * used by the SSL connectors. + */ +public class SslContextFactory extends AbstractLifeCycle +{ + public final static TrustManager[] TRUST_ALL_CERTS = new X509TrustManager[]{new X509TrustManager() + { + public java.security.cert.X509Certificate[] getAcceptedIssuers() + { + return new java.security.cert.X509Certificate[]{}; + } + + public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) + { + } + + public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) + { + } + }}; + + private static final Logger LOG = Log.getLogger(SslContextFactory.class); + + public static final String DEFAULT_KEYMANAGERFACTORY_ALGORITHM = + (Security.getProperty("ssl.KeyManagerFactory.algorithm") == null ? + "SunX509" : Security.getProperty("ssl.KeyManagerFactory.algorithm")); + public static final String DEFAULT_TRUSTMANAGERFACTORY_ALGORITHM = + (Security.getProperty("ssl.TrustManagerFactory.algorithm") == null ? + "SunX509" : Security.getProperty("ssl.TrustManagerFactory.algorithm")); + + /** Default value for the keystore location path. */ + public static final String DEFAULT_KEYSTORE_PATH = + System.getProperty("user.home") + File.separator + ".keystore"; + + /** String name of key password property. */ + public static final String KEYPASSWORD_PROPERTY = "org.eclipse.jetty.ssl.keypassword"; + + /** String name of keystore password property. */ + public static final String PASSWORD_PROPERTY = "org.eclipse.jetty.ssl.password"; + + /** Excluded protocols. */ + private final Set<String> _excludeProtocols = new LinkedHashSet<String>(); + /** Included protocols. */ + private Set<String> _includeProtocols = null; + + /** Excluded cipher suites. */ + private final Set<String> _excludeCipherSuites = new LinkedHashSet<String>(); + /** Included cipher suites. */ + private Set<String> _includeCipherSuites = null; + + /** Keystore path. */ + private String _keyStorePath; + /** Keystore provider name */ + private String _keyStoreProvider; + /** Keystore type */ + private String _keyStoreType = "JKS"; + /** Keystore input stream */ + private InputStream _keyStoreInputStream; + + /** SSL certificate alias */ + private String _certAlias; + + /** Truststore path */ + private String _trustStorePath; + /** Truststore provider name */ + private String _trustStoreProvider; + /** Truststore type */ + private String _trustStoreType = "JKS"; + /** Truststore input stream */ + private InputStream _trustStoreInputStream; + + /** Set to true if client certificate authentication is required */ + private boolean _needClientAuth = false; + /** Set to true if client certificate authentication is desired */ + private boolean _wantClientAuth = false; + + /** Set to true if renegotiation is allowed */ + private boolean _allowRenegotiate = true; + + /** Keystore password */ + private transient Password _keyStorePassword; + /** Key manager password */ + private transient Password _keyManagerPassword; + /** Truststore password */ + private transient Password _trustStorePassword; + + /** SSL provider name */ + private String _sslProvider; + /** SSL protocol name */ + private String _sslProtocol = "TLS"; + + /** SecureRandom algorithm */ + private String _secureRandomAlgorithm; + /** KeyManager factory algorithm */ + private String _keyManagerFactoryAlgorithm = DEFAULT_KEYMANAGERFACTORY_ALGORITHM; + /** TrustManager factory algorithm */ + private String _trustManagerFactoryAlgorithm = DEFAULT_TRUSTMANAGERFACTORY_ALGORITHM; + + /** Set to true if SSL certificate validation is required */ + private boolean _validateCerts; + /** Set to true if SSL certificate of the peer validation is required */ + private boolean _validatePeerCerts; + /** Maximum certification path length (n - number of intermediate certs, -1 for unlimited) */ + private int _maxCertPathLength = -1; + /** Path to file that contains Certificate Revocation List */ + private String _crlPath; + /** Set to true to enable CRL Distribution Points (CRLDP) support */ + private boolean _enableCRLDP = false; + /** Set to true to enable On-Line Certificate Status Protocol (OCSP) support */ + private boolean _enableOCSP = false; + /** Location of OCSP Responder */ + private String _ocspResponderURL; + + /** SSL keystore */ + private KeyStore _keyStore; + /** SSL truststore */ + private KeyStore _trustStore; + /** Set to true to enable SSL Session caching */ + private boolean _sessionCachingEnabled = true; + /** SSL session cache size */ + private int _sslSessionCacheSize; + /** SSL session timeout */ + private int _sslSessionTimeout; + + /** SSL context */ + private SSLContext _context; + + private boolean _trustAll; + + /* ------------------------------------------------------------ */ + /** + * Construct an instance of SslContextFactory + * Default constructor for use in XmlConfiguration files + */ + public SslContextFactory() + { + _trustAll=true; + } + + /* ------------------------------------------------------------ */ + /** + * Construct an instance of SslContextFactory + * Default constructor for use in XmlConfiguration files + * @param trustAll whether to blindly trust all certificates + * @see #setTrustAll(boolean) + */ + public SslContextFactory(boolean trustAll) + { + _trustAll=trustAll; + } + + /* ------------------------------------------------------------ */ + /** + * Construct an instance of SslContextFactory + * @param keyStorePath default keystore location + */ + public SslContextFactory(String keyStorePath) + { + _keyStorePath = keyStorePath; + } + + /* ------------------------------------------------------------ */ + /** + * Create the SSLContext object and start the lifecycle + * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart() + */ + @Override + protected void doStart() throws Exception + { + if (_context == null) + { + if (_keyStore==null && _keyStoreInputStream == null && _keyStorePath == null && + _trustStore==null && _trustStoreInputStream == null && _trustStorePath == null ) + { + TrustManager[] trust_managers=null; + + if (_trustAll) + { + LOG.debug("No keystore or trust store configured. ACCEPTING UNTRUSTED CERTIFICATES!!!!!"); + // Create a trust manager that does not validate certificate chains + trust_managers = TRUST_ALL_CERTS; + } + + SecureRandom secureRandom = (_secureRandomAlgorithm == null)?null:SecureRandom.getInstance(_secureRandomAlgorithm); + _context = SSLContext.getInstance(_sslProtocol); + _context.init(null, trust_managers, secureRandom); + } + else + { + // verify that keystore and truststore + // parameters are set up correctly + checkKeyStore(); + + KeyStore keyStore = loadKeyStore(); + KeyStore trustStore = loadTrustStore(); + + Collection<? extends CRL> crls = loadCRL(_crlPath); + + if (_validateCerts && keyStore != null) + { + if (_certAlias == null) + { + List<String> aliases = Collections.list(keyStore.aliases()); + _certAlias = aliases.size() == 1 ? aliases.get(0) : null; + } + + Certificate cert = _certAlias == null?null:keyStore.getCertificate(_certAlias); + if (cert == null) + { + throw new Exception("No certificate found in the keystore" + (_certAlias==null ? "":" for alias " + _certAlias)); + } + + CertificateValidator validator = new CertificateValidator(trustStore, crls); + validator.setMaxCertPathLength(_maxCertPathLength); + validator.setEnableCRLDP(_enableCRLDP); + validator.setEnableOCSP(_enableOCSP); + validator.setOcspResponderURL(_ocspResponderURL); + validator.validate(keyStore, cert); + } + + KeyManager[] keyManagers = getKeyManagers(keyStore); + TrustManager[] trustManagers = getTrustManagers(trustStore,crls); + + SecureRandom secureRandom = (_secureRandomAlgorithm == null)?null:SecureRandom.getInstance(_secureRandomAlgorithm); + _context = (_sslProvider == null)?SSLContext.getInstance(_sslProtocol):SSLContext.getInstance(_sslProtocol,_sslProvider); + _context.init(keyManagers,trustManagers,secureRandom); + + SSLEngine engine=newSslEngine(); + + LOG.info("Enabled Protocols {} of {}",Arrays.asList(engine.getEnabledProtocols()),Arrays.asList(engine.getSupportedProtocols())); + if (LOG.isDebugEnabled()) + LOG.debug("Enabled Ciphers {} of {}",Arrays.asList(engine.getEnabledCipherSuites()),Arrays.asList(engine.getSupportedCipherSuites())); + } + } + } + + /* ------------------------------------------------------------ */ + /** + * @return The array of protocol names to exclude from + * {@link SSLEngine#setEnabledProtocols(String[])} + */ + public String[] getExcludeProtocols() + { + return _excludeProtocols.toArray(new String[_excludeProtocols.size()]); + } + + /* ------------------------------------------------------------ */ + /** + * @param protocols + * The array of protocol names to exclude from + * {@link SSLEngine#setEnabledProtocols(String[])} + */ + public void setExcludeProtocols(String... protocols) + { + checkNotStarted(); + + _excludeProtocols.clear(); + _excludeProtocols.addAll(Arrays.asList(protocols)); + } + + /* ------------------------------------------------------------ */ + /** + * @param protocol Protocol names to add to {@link SSLEngine#setEnabledProtocols(String[])} + */ + public void addExcludeProtocols(String... protocol) + { + checkNotStarted(); + _excludeProtocols.addAll(Arrays.asList(protocol)); + } + + /* ------------------------------------------------------------ */ + /** + * @return The array of protocol names to include in + * {@link SSLEngine#setEnabledProtocols(String[])} + */ + public String[] getIncludeProtocols() + { + return _includeProtocols.toArray(new String[_includeProtocols.size()]); + } + + /* ------------------------------------------------------------ */ + /** + * @param protocols + * The array of protocol names to include in + * {@link SSLEngine#setEnabledProtocols(String[])} + */ + public void setIncludeProtocols(String... protocols) + { + checkNotStarted(); + + _includeProtocols = new LinkedHashSet<String>(Arrays.asList(protocols)); + } + + /* ------------------------------------------------------------ */ + /** + * @return The array of cipher suite names to exclude from + * {@link SSLEngine#setEnabledCipherSuites(String[])} + */ + public String[] getExcludeCipherSuites() + { + return _excludeCipherSuites.toArray(new String[_excludeCipherSuites.size()]); + } + + /* ------------------------------------------------------------ */ + /** + * @param cipherSuites + * The array of cipher suite names to exclude from + * {@link SSLEngine#setEnabledCipherSuites(String[])} + */ + public void setExcludeCipherSuites(String... cipherSuites) + { + checkNotStarted(); + _excludeCipherSuites.clear(); + _excludeCipherSuites.addAll(Arrays.asList(cipherSuites)); + } + + /* ------------------------------------------------------------ */ + /** + * @param cipher Cipher names to add to {@link SSLEngine#setEnabledCipherSuites(String[])} + */ + public void addExcludeCipherSuites(String... cipher) + { + checkNotStarted(); + _excludeCipherSuites.addAll(Arrays.asList(cipher)); + } + + /* ------------------------------------------------------------ */ + /** + * @return The array of cipher suite names to include in + * {@link SSLEngine#setEnabledCipherSuites(String[])} + */ + public String[] getIncludeCipherSuites() + { + return _includeCipherSuites.toArray(new String[_includeCipherSuites.size()]); + } + + /* ------------------------------------------------------------ */ + /** + * @param cipherSuites + * The array of cipher suite names to include in + * {@link SSLEngine#setEnabledCipherSuites(String[])} + */ + public void setIncludeCipherSuites(String... cipherSuites) + { + checkNotStarted(); + + _includeCipherSuites = new LinkedHashSet<String>(Arrays.asList(cipherSuites)); + } + + /* ------------------------------------------------------------ */ + /** + * @return The file or URL of the SSL Key store. + */ + public String getKeyStorePath() + { + return _keyStorePath; + } + + /* ------------------------------------------------------------ */ + @Deprecated + public String getKeyStore() + { + return _keyStorePath; + } + + /* ------------------------------------------------------------ */ + /** + * @param keyStorePath + * The file or URL of the SSL Key store. + */ + public void setKeyStorePath(String keyStorePath) + { + checkNotStarted(); + + _keyStorePath = keyStorePath; + } + + /* ------------------------------------------------------------ */ + /** + * @param keyStorePath the file system path or URL of the keystore + * @deprecated Use {@link #setKeyStorePath(String)} + */ + @Deprecated + public void setKeyStore(String keyStorePath) + { + checkNotStarted(); + + _keyStorePath = keyStorePath; + } + + /* ------------------------------------------------------------ */ + /** + * @return The provider of the key store + */ + public String getKeyStoreProvider() + { + return _keyStoreProvider; + } + + /* ------------------------------------------------------------ */ + /** + * @param keyStoreProvider + * The provider of the key store + */ + public void setKeyStoreProvider(String keyStoreProvider) + { + checkNotStarted(); + + _keyStoreProvider = keyStoreProvider; + } + + /* ------------------------------------------------------------ */ + /** + * @return The type of the key store (default "JKS") + */ + public String getKeyStoreType() + { + return (_keyStoreType); + } + + /* ------------------------------------------------------------ */ + /** + * @param keyStoreType + * The type of the key store (default "JKS") + */ + public void setKeyStoreType(String keyStoreType) + { + checkNotStarted(); + + _keyStoreType = keyStoreType; + } + + /* ------------------------------------------------------------ */ + /** Get the _keyStoreInputStream. + * @return the _keyStoreInputStream + * + * @deprecated + */ + @Deprecated + public InputStream getKeyStoreInputStream() + { + checkKeyStore(); + + return _keyStoreInputStream; + } + + /* ------------------------------------------------------------ */ + /** Set the keyStoreInputStream. + * @param keyStoreInputStream the InputStream to the KeyStore + * + * @deprecated Use {@link #setKeyStore(KeyStore)} + */ + @Deprecated + public void setKeyStoreInputStream(InputStream keyStoreInputStream) + { + checkNotStarted(); + + _keyStoreInputStream = keyStoreInputStream; + } + + /* ------------------------------------------------------------ */ + /** + * @return Alias of SSL certificate for the connector + */ + public String getCertAlias() + { + return _certAlias; + } + + /* ------------------------------------------------------------ */ + /** + * @param certAlias + * Alias of SSL certificate for the connector + */ + public void setCertAlias(String certAlias) + { + checkNotStarted(); + + _certAlias = certAlias; + } + + /* ------------------------------------------------------------ */ + /** + * @return The file name or URL of the trust store location + */ + public String getTrustStore() + { + return _trustStorePath; + } + + /* ------------------------------------------------------------ */ + /** + * @param trustStorePath + * The file name or URL of the trust store location + */ + public void setTrustStore(String trustStorePath) + { + checkNotStarted(); + + _trustStorePath = trustStorePath; + } + + /* ------------------------------------------------------------ */ + /** + * @return The provider of the trust store + */ + public String getTrustStoreProvider() + { + return _trustStoreProvider; + } + + /* ------------------------------------------------------------ */ + /** + * @param trustStoreProvider + * The provider of the trust store + */ + public void setTrustStoreProvider(String trustStoreProvider) + { + checkNotStarted(); + + _trustStoreProvider = trustStoreProvider; + } + + /* ------------------------------------------------------------ */ + /** + * @return The type of the trust store (default "JKS") + */ + public String getTrustStoreType() + { + return _trustStoreType; + } + + /* ------------------------------------------------------------ */ + /** + * @param trustStoreType + * The type of the trust store (default "JKS") + */ + public void setTrustStoreType(String trustStoreType) + { + checkNotStarted(); + + _trustStoreType = trustStoreType; + } + + /* ------------------------------------------------------------ */ + /** Get the _trustStoreInputStream. + * @return the _trustStoreInputStream + * + * @deprecated + */ + @Deprecated + public InputStream getTrustStoreInputStream() + { + checkKeyStore(); + + return _trustStoreInputStream; + } + + /* ------------------------------------------------------------ */ + /** Set the _trustStoreInputStream. + * @param trustStoreInputStream the InputStream to the TrustStore + * + * @deprecated + */ + @Deprecated + public void setTrustStoreInputStream(InputStream trustStoreInputStream) + { + checkNotStarted(); + + _trustStoreInputStream = trustStoreInputStream; + } + + /* ------------------------------------------------------------ */ + /** + * @return True if SSL needs client authentication. + * @see SSLEngine#getNeedClientAuth() + */ + public boolean getNeedClientAuth() + { + return _needClientAuth; + } + + /* ------------------------------------------------------------ */ + /** + * @param needClientAuth + * True if SSL needs client authentication. + * @see SSLEngine#getNeedClientAuth() + */ + public void setNeedClientAuth(boolean needClientAuth) + { + checkNotStarted(); + + _needClientAuth = needClientAuth; + } + + /* ------------------------------------------------------------ */ + /** + * @return True if SSL wants client authentication. + * @see SSLEngine#getWantClientAuth() + */ + public boolean getWantClientAuth() + { + return _wantClientAuth; + } + + /* ------------------------------------------------------------ */ + /** + * @param wantClientAuth + * True if SSL wants client authentication. + * @see SSLEngine#getWantClientAuth() + */ + public void setWantClientAuth(boolean wantClientAuth) + { + checkNotStarted(); + + _wantClientAuth = wantClientAuth; + } + + /* ------------------------------------------------------------ */ + /** + * @return true if SSL certificate has to be validated + * @deprecated + */ + @Deprecated + public boolean getValidateCerts() + { + return _validateCerts; + } + + /* ------------------------------------------------------------ */ + /** + * @return true if SSL certificate has to be validated + */ + public boolean isValidateCerts() + { + return _validateCerts; + } + + /* ------------------------------------------------------------ */ + /** + * @param validateCerts + * true if SSL certificates have to be validated + */ + public void setValidateCerts(boolean validateCerts) + { + checkNotStarted(); + + _validateCerts = validateCerts; + } + + /* ------------------------------------------------------------ */ + /** + * @return true if SSL certificates of the peer have to be validated + */ + public boolean isValidatePeerCerts() + { + return _validatePeerCerts; + } + + /* ------------------------------------------------------------ */ + /** + * @param validatePeerCerts + * true if SSL certificates of the peer have to be validated + */ + public void setValidatePeerCerts(boolean validatePeerCerts) + { + checkNotStarted(); + + _validatePeerCerts = validatePeerCerts; + } + + /* ------------------------------------------------------------ */ + /** + * @return True if SSL re-negotiation is allowed (default false) + */ + public boolean isAllowRenegotiate() + { + return _allowRenegotiate; + } + + /* ------------------------------------------------------------ */ + /** + * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered + * a vulnerability in SSL/TLS with re-negotiation. If your JVM + * does not have CVE-2009-3555 fixed, then re-negotiation should + * not be allowed. CVE-2009-3555 was fixed in Sun java 1.6 with a ban + * of renegotiates in u19 and with RFC5746 in u22. + * + * @param allowRenegotiate + * true if re-negotiation is allowed (default false) + */ + public void setAllowRenegotiate(boolean allowRenegotiate) + { + checkNotStarted(); + + _allowRenegotiate = allowRenegotiate; + } + + /* ------------------------------------------------------------ */ + /** + * @param password + * The password for the key store + */ + public void setKeyStorePassword(String password) + { + checkNotStarted(); + + _keyStorePassword = Password.getPassword(PASSWORD_PROPERTY,password,null); + } + + /* ------------------------------------------------------------ */ + /** + * @param password + * The password (if any) for the specific key within the key store + */ + public void setKeyManagerPassword(String password) + { + checkNotStarted(); + + _keyManagerPassword = Password.getPassword(KEYPASSWORD_PROPERTY,password,null); + } + + /* ------------------------------------------------------------ */ + /** + * @param password + * The password for the trust store + */ + public void setTrustStorePassword(String password) + { + checkNotStarted(); + + _trustStorePassword = Password.getPassword(PASSWORD_PROPERTY,password,null); + } + + /* ------------------------------------------------------------ */ + /** + * @return The SSL provider name, which if set is passed to + * {@link SSLContext#getInstance(String, String)} + */ + public String getProvider() + { + return _sslProvider; + } + + /* ------------------------------------------------------------ */ + /** + * @param provider + * The SSL provider name, which if set is passed to + * {@link SSLContext#getInstance(String, String)} + */ + public void setProvider(String provider) + { + checkNotStarted(); + + _sslProvider = provider; + } + + /* ------------------------------------------------------------ */ + /** + * @return The SSL protocol (default "TLS") passed to + * {@link SSLContext#getInstance(String, String)} + */ + public String getProtocol() + { + return _sslProtocol; + } + + /* ------------------------------------------------------------ */ + /** + * @param protocol + * The SSL protocol (default "TLS") passed to + * {@link SSLContext#getInstance(String, String)} + */ + public void setProtocol(String protocol) + { + checkNotStarted(); + + _sslProtocol = protocol; + } + + /* ------------------------------------------------------------ */ + /** + * @return The algorithm name, which if set is passed to + * {@link SecureRandom#getInstance(String)} to obtain the {@link SecureRandom} instance passed to + * {@link SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], SecureRandom)} + */ + public String getSecureRandomAlgorithm() + { + return _secureRandomAlgorithm; + } + + /* ------------------------------------------------------------ */ + /** + * @param algorithm + * The algorithm name, which if set is passed to + * {@link SecureRandom#getInstance(String)} to obtain the {@link SecureRandom} instance passed to + * {@link SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], SecureRandom)} + */ + public void setSecureRandomAlgorithm(String algorithm) + { + checkNotStarted(); + + _secureRandomAlgorithm = algorithm; + } + + /* ------------------------------------------------------------ */ + /** + * @return The algorithm name (default "SunX509") used by the {@link KeyManagerFactory} + */ + public String getSslKeyManagerFactoryAlgorithm() + { + return (_keyManagerFactoryAlgorithm); + } + + /* ------------------------------------------------------------ */ + /** + * @param algorithm + * The algorithm name (default "SunX509") used by the {@link KeyManagerFactory} + */ + public void setSslKeyManagerFactoryAlgorithm(String algorithm) + { + checkNotStarted(); + + _keyManagerFactoryAlgorithm = algorithm; + } + + /* ------------------------------------------------------------ */ + /** + * @return The algorithm name (default "SunX509") used by the {@link TrustManagerFactory} + */ + public String getTrustManagerFactoryAlgorithm() + { + return (_trustManagerFactoryAlgorithm); + } + + /* ------------------------------------------------------------ */ + /** + * @return True if all certificates should be trusted if there is no KeyStore or TrustStore + */ + public boolean isTrustAll() + { + return _trustAll; + } + + /* ------------------------------------------------------------ */ + /** + * @param trustAll True if all certificates should be trusted if there is no KeyStore or TrustStore + */ + public void setTrustAll(boolean trustAll) + { + _trustAll = trustAll; + } + + /* ------------------------------------------------------------ */ + /** + * @param algorithm + * The algorithm name (default "SunX509") used by the {@link TrustManagerFactory} + * Use the string "TrustAll" to install a trust manager that trusts all. + */ + public void setTrustManagerFactoryAlgorithm(String algorithm) + { + checkNotStarted(); + + _trustManagerFactoryAlgorithm = algorithm; + } + + /* ------------------------------------------------------------ */ + /** + * @return Path to file that contains Certificate Revocation List + */ + public String getCrlPath() + { + return _crlPath; + } + + /* ------------------------------------------------------------ */ + /** + * @param crlPath + * Path to file that contains Certificate Revocation List + */ + public void setCrlPath(String crlPath) + { + checkNotStarted(); + + _crlPath = crlPath; + } + + /* ------------------------------------------------------------ */ + /** + * @return Maximum number of intermediate certificates in + * the certification path (-1 for unlimited) + */ + public int getMaxCertPathLength() + { + return _maxCertPathLength; + } + + /* ------------------------------------------------------------ */ + /** + * @param maxCertPathLength + * maximum number of intermediate certificates in + * the certification path (-1 for unlimited) + */ + public void setMaxCertPathLength(int maxCertPathLength) + { + checkNotStarted(); + + _maxCertPathLength = maxCertPathLength; + } + + /* ------------------------------------------------------------ */ + /** + * @return The SSLContext + */ + public SSLContext getSslContext() + { + if (!isStarted()) + throw new IllegalStateException(getState()); + return _context; + } + + /* ------------------------------------------------------------ */ + /** + * @param sslContext + * Set a preconfigured SSLContext + */ + public void setSslContext(SSLContext sslContext) + { + checkNotStarted(); + + _context = sslContext; + } + + /* ------------------------------------------------------------ */ + /** + * Override this method to provide alternate way to load a keystore. + * + * @return the key store instance + * @throws Exception if the keystore cannot be loaded + */ + protected KeyStore loadKeyStore() throws Exception + { + return _keyStore != null ? _keyStore : getKeyStore(_keyStoreInputStream, + _keyStorePath, _keyStoreType, _keyStoreProvider, + _keyStorePassword==null? null: _keyStorePassword.toString()); + } + + /* ------------------------------------------------------------ */ + /** + * Override this method to provide alternate way to load a truststore. + * + * @return the key store instance + * @throws Exception if the truststore cannot be loaded + */ + protected KeyStore loadTrustStore() throws Exception + { + return _trustStore != null ? _trustStore : getKeyStore(_trustStoreInputStream, + _trustStorePath, _trustStoreType, _trustStoreProvider, + _trustStorePassword==null? null: _trustStorePassword.toString()); + } + + /* ------------------------------------------------------------ */ + /** + * Loads keystore using an input stream or a file path in the same + * order of precedence. + * + * Required for integrations to be able to override the mechanism + * used to load a keystore in order to provide their own implementation. + * + * @param storeStream keystore input stream + * @param storePath path of keystore file + * @param storeType keystore type + * @param storeProvider keystore provider + * @param storePassword keystore password + * @return created keystore + * @throws Exception if the keystore cannot be obtained + * + * @deprecated + */ + @Deprecated + protected KeyStore getKeyStore(InputStream storeStream, String storePath, String storeType, String storeProvider, String storePassword) throws Exception + { + return CertificateUtils.getKeyStore(storeStream, storePath, storeType, storeProvider, storePassword); + } + + /* ------------------------------------------------------------ */ + /** + * Loads certificate revocation list (CRL) from a file. + * + * Required for integrations to be able to override the mechanism used to + * load CRL in order to provide their own implementation. + * + * @param crlPath path of certificate revocation list file + * @return Collection of CRL's + * @throws Exception if the certificate revocation list cannot be loaded + */ + protected Collection<? extends CRL> loadCRL(String crlPath) throws Exception + { + return CertificateUtils.loadCRL(crlPath); + } + + /* ------------------------------------------------------------ */ + protected KeyManager[] getKeyManagers(KeyStore keyStore) throws Exception + { + KeyManager[] managers = null; + + if (keyStore != null) + { + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(_keyManagerFactoryAlgorithm); + keyManagerFactory.init(keyStore,_keyManagerPassword == null?(_keyStorePassword == null?null:_keyStorePassword.toString().toCharArray()):_keyManagerPassword.toString().toCharArray()); + managers = keyManagerFactory.getKeyManagers(); + + if (_certAlias != null) + { + for (int idx = 0; idx < managers.length; idx++) + { + if (managers[idx] instanceof X509KeyManager) + { + managers[idx] = new AliasedX509ExtendedKeyManager(_certAlias,(X509KeyManager)managers[idx]); + } + } + } + } + + return managers; + } + + /* ------------------------------------------------------------ */ + protected TrustManager[] getTrustManagers(KeyStore trustStore, Collection<? extends CRL> crls) throws Exception + { + TrustManager[] managers = null; + if (trustStore != null) + { + // Revocation checking is only supported for PKIX algorithm + if (_validatePeerCerts && _trustManagerFactoryAlgorithm.equalsIgnoreCase("PKIX")) + { + PKIXBuilderParameters pbParams = new PKIXBuilderParameters(trustStore,new X509CertSelector()); + + // Set maximum certification path length + pbParams.setMaxPathLength(_maxCertPathLength); + + // Make sure revocation checking is enabled + pbParams.setRevocationEnabled(true); + + if (crls != null && !crls.isEmpty()) + { + pbParams.addCertStore(CertStore.getInstance("Collection",new CollectionCertStoreParameters(crls))); + } + + if (_enableCRLDP) + { + // Enable Certificate Revocation List Distribution Points (CRLDP) support + System.setProperty("com.sun.security.enableCRLDP","true"); + } + + if (_enableOCSP) + { + // Enable On-Line Certificate Status Protocol (OCSP) support + Security.setProperty("ocsp.enable","true"); + + if (_ocspResponderURL != null) + { + // Override location of OCSP Responder + Security.setProperty("ocsp.responderURL", _ocspResponderURL); + } + } + + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(_trustManagerFactoryAlgorithm); + trustManagerFactory.init(new CertPathTrustManagerParameters(pbParams)); + + managers = trustManagerFactory.getTrustManagers(); + } + else + { + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(_trustManagerFactoryAlgorithm); + trustManagerFactory.init(trustStore); + + managers = trustManagerFactory.getTrustManagers(); + } + } + + return managers; + } + + /* ------------------------------------------------------------ */ + /** + * Check KeyStore Configuration. Ensures that if keystore has been + * configured but there's no truststore, that keystore is + * used as truststore. + * @throws IllegalStateException if SslContextFactory configuration can't be used. + */ + public void checkKeyStore() + { + if (_context != null) + return; //nothing to check if using preconfigured context + + + if (_keyStore == null && _keyStoreInputStream == null && _keyStorePath == null) + throw new IllegalStateException("SSL doesn't have a valid keystore"); + + // if the keystore has been configured but there is no + // truststore configured, use the keystore as the truststore + if (_trustStore == null && _trustStoreInputStream == null && _trustStorePath == null) + { + _trustStore = _keyStore; + _trustStorePath = _keyStorePath; + _trustStoreInputStream = _keyStoreInputStream; + _trustStoreType = _keyStoreType; + _trustStoreProvider = _keyStoreProvider; + _trustStorePassword = _keyStorePassword; + _trustManagerFactoryAlgorithm = _keyManagerFactoryAlgorithm; + } + + // It's the same stream we cannot read it twice, so read it once in memory + if (_keyStoreInputStream != null && _keyStoreInputStream == _trustStoreInputStream) + { + try + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + IO.copy(_keyStoreInputStream, baos); + _keyStoreInputStream.close(); + + _keyStoreInputStream = new ByteArrayInputStream(baos.toByteArray()); + _trustStoreInputStream = new ByteArrayInputStream(baos.toByteArray()); + } + catch (Exception ex) + { + throw new IllegalStateException(ex); + } + } + } + + /* ------------------------------------------------------------ */ + /** + * Select protocols to be used by the connector + * based on configured inclusion and exclusion lists + * as well as enabled and supported protocols. + * @param enabledProtocols Array of enabled protocols + * @param supportedProtocols Array of supported protocols + * @return Array of protocols to enable + */ + public String[] selectProtocols(String[] enabledProtocols, String[] supportedProtocols) + { + Set<String> selected_protocols = new LinkedHashSet<String>(); + + // Set the starting protocols - either from the included or enabled list + if (_includeProtocols!=null) + { + // Use only the supported included protocols + for (String protocol : _includeProtocols) + if(Arrays.asList(supportedProtocols).contains(protocol)) + selected_protocols.add(protocol); + } + else + selected_protocols.addAll(Arrays.asList(enabledProtocols)); + + + // Remove any excluded protocols + if (_excludeProtocols != null) + selected_protocols.removeAll(_excludeProtocols); + + return selected_protocols.toArray(new String[selected_protocols.size()]); + } + + /* ------------------------------------------------------------ */ + /** + * Select cipher suites to be used by the connector + * based on configured inclusion and exclusion lists + * as well as enabled and supported cipher suite lists. + * @param enabledCipherSuites Array of enabled cipher suites + * @param supportedCipherSuites Array of supported cipher suites + * @return Array of cipher suites to enable + */ + public String[] selectCipherSuites(String[] enabledCipherSuites, String[] supportedCipherSuites) + { + Set<String> selected_ciphers = new LinkedHashSet<String>(); + + // Set the starting ciphers - either from the included or enabled list + if (_includeCipherSuites!=null) + { + // Use only the supported included ciphers + for (String cipherSuite : _includeCipherSuites) + if(Arrays.asList(supportedCipherSuites).contains(cipherSuite)) + selected_ciphers.add(cipherSuite); + } + else + selected_ciphers.addAll(Arrays.asList(enabledCipherSuites)); + + + // Remove any excluded ciphers + if (_excludeCipherSuites != null) + selected_ciphers.removeAll(_excludeCipherSuites); + return selected_ciphers.toArray(new String[selected_ciphers.size()]); + } + + /* ------------------------------------------------------------ */ + /** + * Check if the lifecycle has been started and throw runtime exception + */ + protected void checkNotStarted() + { + if (isStarted()) + throw new IllegalStateException("Cannot modify configuration when "+getState()); + } + + /* ------------------------------------------------------------ */ + /** + * @return true if CRL Distribution Points support is enabled + */ + public boolean isEnableCRLDP() + { + return _enableCRLDP; + } + + /* ------------------------------------------------------------ */ + /** Enables CRL Distribution Points Support + * @param enableCRLDP true - turn on, false - turns off + */ + public void setEnableCRLDP(boolean enableCRLDP) + { + checkNotStarted(); + + _enableCRLDP = enableCRLDP; + } + + /* ------------------------------------------------------------ */ + /** + * @return true if On-Line Certificate Status Protocol support is enabled + */ + public boolean isEnableOCSP() + { + return _enableOCSP; + } + + /* ------------------------------------------------------------ */ + /** Enables On-Line Certificate Status Protocol support + * @param enableOCSP true - turn on, false - turn off + */ + public void setEnableOCSP(boolean enableOCSP) + { + checkNotStarted(); + + _enableOCSP = enableOCSP; + } + + /* ------------------------------------------------------------ */ + /** + * @return Location of the OCSP Responder + */ + public String getOcspResponderURL() + { + return _ocspResponderURL; + } + + /* ------------------------------------------------------------ */ + /** Set the location of the OCSP Responder. + * @param ocspResponderURL location of the OCSP Responder + */ + public void setOcspResponderURL(String ocspResponderURL) + { + checkNotStarted(); + + _ocspResponderURL = ocspResponderURL; + } + + /* ------------------------------------------------------------ */ + /** Set the key store. + * @param keyStore the key store to set + */ + public void setKeyStore(KeyStore keyStore) + { + checkNotStarted(); + + _keyStore = keyStore; + } + + /* ------------------------------------------------------------ */ + /** Set the trust store. + * @param trustStore the trust store to set + */ + public void setTrustStore(KeyStore trustStore) + { + checkNotStarted(); + + _trustStore = trustStore; + } + + /* ------------------------------------------------------------ */ + /** Set the key store resource. + * @param resource the key store resource to set + */ + public void setKeyStoreResource(Resource resource) + { + checkNotStarted(); + + try + { + _keyStoreInputStream = resource.getInputStream(); + } + catch (IOException e) + { + throw new InvalidParameterException("Unable to get resource "+ + "input stream for resource "+resource.toString()); + } + } + + /* ------------------------------------------------------------ */ + /** Set the trust store resource. + * @param resource the trust store resource to set + */ + public void setTrustStoreResource(Resource resource) + { + checkNotStarted(); + + try + { + _trustStoreInputStream = resource.getInputStream(); + } + catch (IOException e) + { + throw new InvalidParameterException("Unable to get resource "+ + "input stream for resource "+resource.toString()); + } + } + + /* ------------------------------------------------------------ */ + /** + * @return true if SSL Session caching is enabled + */ + public boolean isSessionCachingEnabled() + { + return _sessionCachingEnabled; + } + + /* ------------------------------------------------------------ */ + /** Set the flag to enable SSL Session caching. + * @param enableSessionCaching the value of the flag + */ + public void setSessionCachingEnabled(boolean enableSessionCaching) + { + _sessionCachingEnabled = enableSessionCaching; + } + + /* ------------------------------------------------------------ */ + /** Get SSL session cache size. + * @return SSL session cache size + */ + public int getSslSessionCacheSize() + { + return _sslSessionCacheSize; + } + + /* ------------------------------------------------------------ */ + /** SEt SSL session cache size. + * @param sslSessionCacheSize SSL session cache size to set + */ + public void setSslSessionCacheSize(int sslSessionCacheSize) + { + _sslSessionCacheSize = sslSessionCacheSize; + } + + /* ------------------------------------------------------------ */ + /** Get SSL session timeout. + * @return SSL session timeout + */ + public int getSslSessionTimeout() + { + return _sslSessionTimeout; + } + + /* ------------------------------------------------------------ */ + /** Set SSL session timeout. + * @param sslSessionTimeout SSL session timeout to set + */ + public void setSslSessionTimeout(int sslSessionTimeout) + { + _sslSessionTimeout = sslSessionTimeout; + } + + + /* ------------------------------------------------------------ */ + public SSLServerSocket newSslServerSocket(String host,int port,int backlog) throws IOException + { + SSLServerSocketFactory factory = _context.getServerSocketFactory(); + + SSLServerSocket socket = + (SSLServerSocket) (host==null ? + factory.createServerSocket(port,backlog): + factory.createServerSocket(port,backlog,InetAddress.getByName(host))); + + if (getWantClientAuth()) + socket.setWantClientAuth(getWantClientAuth()); + if (getNeedClientAuth()) + socket.setNeedClientAuth(getNeedClientAuth()); + + socket.setEnabledCipherSuites(selectCipherSuites( + socket.getEnabledCipherSuites(), + socket.getSupportedCipherSuites())); + socket.setEnabledProtocols(selectProtocols(socket.getEnabledProtocols(),socket.getSupportedProtocols())); + + return socket; + } + + /* ------------------------------------------------------------ */ + public SSLSocket newSslSocket() throws IOException + { + SSLSocketFactory factory = _context.getSocketFactory(); + + SSLSocket socket = (SSLSocket)factory.createSocket(); + + if (getWantClientAuth()) + socket.setWantClientAuth(getWantClientAuth()); + if (getNeedClientAuth()) + socket.setNeedClientAuth(getNeedClientAuth()); + + socket.setEnabledCipherSuites(selectCipherSuites( + socket.getEnabledCipherSuites(), + socket.getSupportedCipherSuites())); + socket.setEnabledProtocols(selectProtocols(socket.getEnabledProtocols(),socket.getSupportedProtocols())); + + return socket; + } + + /* ------------------------------------------------------------ */ + public SSLEngine newSslEngine(String host,int port) + { + SSLEngine sslEngine=isSessionCachingEnabled() + ?_context.createSSLEngine(host, port) + :_context.createSSLEngine(); + + customize(sslEngine); + return sslEngine; + } + + /* ------------------------------------------------------------ */ + public SSLEngine newSslEngine() + { + SSLEngine sslEngine=_context.createSSLEngine(); + customize(sslEngine); + return sslEngine; + } + + /* ------------------------------------------------------------ */ + public void customize(SSLEngine sslEngine) + { + if (getWantClientAuth()) + sslEngine.setWantClientAuth(getWantClientAuth()); + if (getNeedClientAuth()) + sslEngine.setNeedClientAuth(getNeedClientAuth()); + + sslEngine.setEnabledCipherSuites(selectCipherSuites( + sslEngine.getEnabledCipherSuites(), + sslEngine.getSupportedCipherSuites())); + + sslEngine.setEnabledProtocols(selectProtocols(sslEngine.getEnabledProtocols(),sslEngine.getSupportedProtocols())); + } + + /* ------------------------------------------------------------ */ + public String toString() + { + return String.format("%s@%x(%s,%s)", + getClass().getSimpleName(), + hashCode(), + _keyStorePath, + _trustStorePath); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/statistic/CounterStatistic.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,117 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.statistic; + +import java.util.concurrent.atomic.AtomicLong; + +import org.eclipse.jetty.util.Atomics; + + +/* ------------------------------------------------------------ */ +/** Statistics on a counter value. + * <p> + * Keep total, current and maximum values of a counter that + * can be incremented and decremented. The total refers only + * to increments. + * + */ +public class CounterStatistic +{ + protected final AtomicLong _max = new AtomicLong(); + protected final AtomicLong _curr = new AtomicLong(); + protected final AtomicLong _total = new AtomicLong(); + + /* ------------------------------------------------------------ */ + public void reset() + { + reset(0); + } + + /* ------------------------------------------------------------ */ + public void reset(final long value) + { + _max.set(value); + _curr.set(value); + _total.set(0); // total always set to 0 to properly calculate cumulative total + } + + /* ------------------------------------------------------------ */ + /** + * @param delta the amount to add to the count + */ + public void add(final long delta) + { + long value=_curr.addAndGet(delta); + if (delta > 0) + _total.addAndGet(delta); + Atomics.updateMax(_max,value); + } + + /* ------------------------------------------------------------ */ + /** + * @param delta the amount to subtract the count by. + */ + public void subtract(final long delta) + { + add(-delta); + } + + /* ------------------------------------------------------------ */ + /** + */ + public void increment() + { + add(1); + } + + /* ------------------------------------------------------------ */ + /** + */ + public void decrement() + { + add(-1); + } + + /* ------------------------------------------------------------ */ + /** + * @return max value + */ + public long getMax() + { + return _max.get(); + } + + /* ------------------------------------------------------------ */ + /** + * @return current value + */ + public long getCurrent() + { + return _curr.get(); + } + + /* ------------------------------------------------------------ */ + /** + * @return total value + */ + public long getTotal() + { + return _total.get(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/statistic/SampleStatistic.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,109 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.statistic; + +import java.util.concurrent.atomic.AtomicLong; + +import org.eclipse.jetty.util.Atomics; + + +/* ------------------------------------------------------------ */ +/** + * SampledStatistics + * <p> + * Provides max, total, mean, count, variance, and standard + * deviation of continuous sequence of samples. + * <p> + * Calculates estimates of mean, variance, and standard deviation + * characteristics of a sample using a non synchronized + * approximation of the on-line algorithm presented + * in Donald Knuth's Art of Computer Programming, Volume 2, + * Seminumerical Algorithms, 3rd edition, page 232, + * Boston: Addison-Wesley. that cites a 1962 paper by B.P. Welford + * that can be found by following the link http://www.jstor.org/pss/1266577 + * <p> + * This algorithm is also described in Wikipedia at + * http://en.wikipedia.org/w/index.php?title=Algorithms_for_calculating_variance§ion=4#On-line_algorithm + */ +public class SampleStatistic +{ + protected final AtomicLong _max = new AtomicLong(); + protected final AtomicLong _total = new AtomicLong(); + protected final AtomicLong _count = new AtomicLong(); + protected final AtomicLong _totalVariance100 = new AtomicLong(); + + public void reset() + { + _max.set(0); + _total.set(0); + _count.set(0); + _totalVariance100.set(0); + } + + public void set(final long sample) + { + long total = _total.addAndGet(sample); + long count = _count.incrementAndGet(); + + if (count>1) + { + long mean10 = total*10/count; + long delta10 = sample*10 - mean10; + _totalVariance100.addAndGet(delta10*delta10); + } + + Atomics.updateMax(_max, sample); + } + + /** + * @return the max value + */ + public long getMax() + { + return _max.get(); + } + + public long getTotal() + { + return _total.get(); + } + + public long getCount() + { + return _count.get(); + } + + public double getMean() + { + return (double)_total.get()/_count.get(); + } + + public double getVariance() + { + final long variance100 = _totalVariance100.get(); + final long count = _count.get(); + + return count>1?((double)variance100)/100.0/(count-1):0.0; + } + + public double getStdDev() + { + return Math.sqrt(getVariance()); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/thread/ExecutorThreadPool.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,184 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.thread; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/* ------------------------------------------------------------ */ +/** + * Jetty ThreadPool using java 5 ThreadPoolExecutor + * This class wraps a {@link ExecutorService} as a {@link ThreadPool} and + * {@link LifeCycle} interfaces so that it may be used by the Jetty <code>org.eclipse.jetty.server.Server</code> + */ +public class ExecutorThreadPool extends AbstractLifeCycle implements ThreadPool, LifeCycle +{ + private static final Logger LOG = Log.getLogger(ExecutorThreadPool.class); + private final ExecutorService _executor; + + /* ------------------------------------------------------------ */ + public ExecutorThreadPool(ExecutorService executor) + { + _executor = executor; + } + + /* ------------------------------------------------------------ */ + /** + * Wraps an {@link ThreadPoolExecutor}. + * Max pool size is 256, pool thread timeout after 60 seconds and + * an unbounded {@link LinkedBlockingQueue} is used for the job queue; + */ + public ExecutorThreadPool() + { + // Using an unbounded queue makes the maxThreads parameter useless + // Refer to ThreadPoolExecutor javadocs for details + this(new ThreadPoolExecutor(256, 256, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>())); + } + + /* ------------------------------------------------------------ */ + /** + * Wraps an {@link ThreadPoolExecutor}. + * Max pool size is 256, pool thread timeout after 60 seconds, and core pool size is 32 when queueSize >= 0. + * @param queueSize can be -1 for using an unbounded {@link LinkedBlockingQueue}, 0 for using a + * {@link SynchronousQueue}, greater than 0 for using a {@link ArrayBlockingQueue} of the given size. + */ + public ExecutorThreadPool(int queueSize) + { + this(queueSize < 0 ? new ThreadPoolExecutor(256, 256, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()) : + queueSize == 0 ? new ThreadPoolExecutor(32, 256, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()) : + new ThreadPoolExecutor(32, 256, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(queueSize))); + } + + /* ------------------------------------------------------------ */ + /** + * Wraps an {@link ThreadPoolExecutor} using + * an unbounded {@link LinkedBlockingQueue} is used for the jobs queue; + * @param corePoolSize must be equal to maximumPoolSize + * @param maximumPoolSize the maximum number of threads to allow in the pool + * @param keepAliveTime the max time a thread can remain idle, in milliseconds + */ + public ExecutorThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime) + { + this(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.MILLISECONDS); + } + + /* ------------------------------------------------------------ */ + /** + * Wraps an {@link ThreadPoolExecutor} using + * an unbounded {@link LinkedBlockingQueue} is used for the jobs queue. + * @param corePoolSize must be equal to maximumPoolSize + * @param maximumPoolSize the maximum number of threads to allow in the pool + * @param keepAliveTime the max time a thread can remain idle + * @param unit the unit for the keepAliveTime + */ + public ExecutorThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit) + { + this(corePoolSize, maximumPoolSize, keepAliveTime, unit, new LinkedBlockingQueue<Runnable>()); + } + + /* ------------------------------------------------------------ */ + + /** + * Wraps an {@link ThreadPoolExecutor} + * @param corePoolSize the number of threads to keep in the pool, even if they are idle + * @param maximumPoolSize the maximum number of threads to allow in the pool + * @param keepAliveTime the max time a thread can remain idle + * @param unit the unit for the keepAliveTime + * @param workQueue the queue to use for holding tasks before they are executed + */ + public ExecutorThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) + { + this(new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue)); + } + + /* ------------------------------------------------------------ */ + public boolean dispatch(Runnable job) + { + try + { + _executor.execute(job); + return true; + } + catch(RejectedExecutionException e) + { + LOG.warn(e); + return false; + } + } + + /* ------------------------------------------------------------ */ + public int getIdleThreads() + { + if (_executor instanceof ThreadPoolExecutor) + { + final ThreadPoolExecutor tpe = (ThreadPoolExecutor)_executor; + return tpe.getPoolSize() - tpe.getActiveCount(); + } + return -1; + } + + /* ------------------------------------------------------------ */ + public int getThreads() + { + if (_executor instanceof ThreadPoolExecutor) + { + final ThreadPoolExecutor tpe = (ThreadPoolExecutor)_executor; + return tpe.getPoolSize(); + } + return -1; + } + + /* ------------------------------------------------------------ */ + public boolean isLowOnThreads() + { + if (_executor instanceof ThreadPoolExecutor) + { + final ThreadPoolExecutor tpe = (ThreadPoolExecutor)_executor; + // getActiveCount() locks the thread pool, so execute it last + return tpe.getPoolSize() == tpe.getMaximumPoolSize() && + tpe.getQueue().size() >= tpe.getPoolSize() - tpe.getActiveCount(); + } + return false; + } + + /* ------------------------------------------------------------ */ + public void join() throws InterruptedException + { + _executor.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS); + } + + /* ------------------------------------------------------------ */ + @Override + protected void doStop() throws Exception + { + super.doStop(); + _executor.shutdownNow(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/thread/QueuedThreadPool.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,678 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.thread; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +import org.eclipse.jetty.util.BlockingArrayQueue; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.component.AggregateLifeCycle; +import org.eclipse.jetty.util.component.Dumpable; +import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.thread.ThreadPool.SizedThreadPool; + +public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPool, Executor, Dumpable +{ + private static final Logger LOG = Log.getLogger(QueuedThreadPool.class); + + private final AtomicInteger _threadsStarted = new AtomicInteger(); + private final AtomicInteger _threadsIdle = new AtomicInteger(); + private final AtomicLong _lastShrink = new AtomicLong(); + private final ConcurrentLinkedQueue<Thread> _threads=new ConcurrentLinkedQueue<Thread>(); + private final Object _joinLock = new Object(); + private BlockingQueue<Runnable> _jobs; + private String _name; + private int _maxIdleTimeMs=60000; + private int _maxThreads=254; + private int _minThreads=8; + private int _maxQueued=-1; + private int _priority=Thread.NORM_PRIORITY; + private boolean _daemon=false; + private int _maxStopTime=100; + private boolean _detailedDump=false; + + /* ------------------------------------------------------------------- */ + /** Construct + */ + public QueuedThreadPool() + { + _name="qtp"+super.hashCode(); + } + + /* ------------------------------------------------------------------- */ + /** Construct + */ + public QueuedThreadPool(int maxThreads) + { + this(); + setMaxThreads(maxThreads); + } + + /* ------------------------------------------------------------------- */ + /** Construct + */ + public QueuedThreadPool(BlockingQueue<Runnable> jobQ) + { + this(); + _jobs=jobQ; + _jobs.clear(); + } + + + /* ------------------------------------------------------------ */ + @Override + protected void doStart() throws Exception + { + super.doStart(); + _threadsStarted.set(0); + + if (_jobs==null) + { + _jobs=_maxQueued>0 ?new ArrayBlockingQueue<Runnable>(_maxQueued) + :new BlockingArrayQueue<Runnable>(_minThreads,_minThreads); + } + + int threads=_threadsStarted.get(); + while (isRunning() && threads<_minThreads) + { + startThread(threads); + threads=_threadsStarted.get(); + } + } + + /* ------------------------------------------------------------ */ + @Override + protected void doStop() throws Exception + { + super.doStop(); + long start=System.currentTimeMillis(); + + // let jobs complete naturally for a while + while (_threadsStarted.get()>0 && (System.currentTimeMillis()-start) < (_maxStopTime/2)) + Thread.sleep(1); + + // kill queued jobs and flush out idle jobs + _jobs.clear(); + Runnable noop = new Runnable(){public void run(){}}; + for (int i=_threadsIdle.get();i-->0;) + _jobs.offer(noop); + Thread.yield(); + + // interrupt remaining threads + if (_threadsStarted.get()>0) + for (Thread thread : _threads) + thread.interrupt(); + + // wait for remaining threads to die + while (_threadsStarted.get()>0 && (System.currentTimeMillis()-start) < _maxStopTime) + { + Thread.sleep(1); + } + Thread.yield(); + int size=_threads.size(); + if (size>0) + { + LOG.warn(size+" threads could not be stopped"); + + if (size==1 || LOG.isDebugEnabled()) + { + for (Thread unstopped : _threads) + { + LOG.info("Couldn't stop "+unstopped); + for (StackTraceElement element : unstopped.getStackTrace()) + { + LOG.info(" at "+element); + } + } + } + } + + synchronized (_joinLock) + { + _joinLock.notifyAll(); + } + } + + /* ------------------------------------------------------------ */ + /** + * Delegated to the named or anonymous Pool. + */ + public void setDaemon(boolean daemon) + { + _daemon=daemon; + } + + /* ------------------------------------------------------------ */ + /** Set the maximum thread idle time. + * Threads that are idle for longer than this period may be + * stopped. + * Delegated to the named or anonymous Pool. + * @see #getMaxIdleTimeMs + * @param maxIdleTimeMs Max idle time in ms. + */ + public void setMaxIdleTimeMs(int maxIdleTimeMs) + { + _maxIdleTimeMs=maxIdleTimeMs; + } + + /* ------------------------------------------------------------ */ + /** + * @param stopTimeMs maximum total time that stop() will wait for threads to die. + */ + public void setMaxStopTimeMs(int stopTimeMs) + { + _maxStopTime = stopTimeMs; + } + + /* ------------------------------------------------------------ */ + /** Set the maximum number of threads. + * Delegated to the named or anonymous Pool. + * @see #getMaxThreads + * @param maxThreads maximum number of threads. + */ + public void setMaxThreads(int maxThreads) + { + _maxThreads=maxThreads; + if (_minThreads>_maxThreads) + _minThreads=_maxThreads; + } + + /* ------------------------------------------------------------ */ + /** Set the minimum number of threads. + * Delegated to the named or anonymous Pool. + * @see #getMinThreads + * @param minThreads minimum number of threads + */ + public void setMinThreads(int minThreads) + { + _minThreads=minThreads; + + if (_minThreads>_maxThreads) + _maxThreads=_minThreads; + + int threads=_threadsStarted.get(); + while (isStarted() && threads<_minThreads) + { + startThread(threads); + threads=_threadsStarted.get(); + } + } + + /* ------------------------------------------------------------ */ + /** + * @param name Name of the BoundedThreadPool to use when naming Threads. + */ + public void setName(String name) + { + if (isRunning()) + throw new IllegalStateException("started"); + _name= name; + } + + /* ------------------------------------------------------------ */ + /** Set the priority of the pool threads. + * @param priority the new thread priority. + */ + public void setThreadsPriority(int priority) + { + _priority=priority; + } + + /* ------------------------------------------------------------ */ + /** + * @return maximum queue size + */ + public int getMaxQueued() + { + return _maxQueued; + } + + /* ------------------------------------------------------------ */ + /** + * @param max job queue size + */ + public void setMaxQueued(int max) + { + if (isRunning()) + throw new IllegalStateException("started"); + _maxQueued=max; + } + + /* ------------------------------------------------------------ */ + /** Get the maximum thread idle time. + * Delegated to the named or anonymous Pool. + * @see #setMaxIdleTimeMs + * @return Max idle time in ms. + */ + public int getMaxIdleTimeMs() + { + return _maxIdleTimeMs; + } + + /* ------------------------------------------------------------ */ + /** + * @return maximum total time that stop() will wait for threads to die. + */ + public int getMaxStopTimeMs() + { + return _maxStopTime; + } + + /* ------------------------------------------------------------ */ + /** Set the maximum number of threads. + * Delegated to the named or anonymous Pool. + * @see #setMaxThreads + * @return maximum number of threads. + */ + public int getMaxThreads() + { + return _maxThreads; + } + + /* ------------------------------------------------------------ */ + /** Get the minimum number of threads. + * Delegated to the named or anonymous Pool. + * @see #setMinThreads + * @return minimum number of threads. + */ + public int getMinThreads() + { + return _minThreads; + } + + /* ------------------------------------------------------------ */ + /** + * @return The name of the BoundedThreadPool. + */ + public String getName() + { + return _name; + } + + /* ------------------------------------------------------------ */ + /** Get the priority of the pool threads. + * @return the priority of the pool threads. + */ + public int getThreadsPriority() + { + return _priority; + } + + /* ------------------------------------------------------------ */ + /** + * Delegated to the named or anonymous Pool. + */ + public boolean isDaemon() + { + return _daemon; + } + + /* ------------------------------------------------------------ */ + public boolean isDetailedDump() + { + return _detailedDump; + } + + /* ------------------------------------------------------------ */ + public void setDetailedDump(boolean detailedDump) + { + _detailedDump = detailedDump; + } + + /* ------------------------------------------------------------ */ + public boolean dispatch(Runnable job) + { + if (isRunning()) + { + final int jobQ = _jobs.size(); + final int idle = getIdleThreads(); + if(_jobs.offer(job)) + { + // If we had no idle threads or the jobQ is greater than the idle threads + if (idle==0 || jobQ>idle) + { + int threads=_threadsStarted.get(); + if (threads<_maxThreads) + startThread(threads); + } + return true; + } + } + LOG.debug("Dispatched {} to stopped {}",job,this); + return false; + } + + /* ------------------------------------------------------------ */ + public void execute(Runnable job) + { + if (!dispatch(job)) + throw new RejectedExecutionException(); + } + + /* ------------------------------------------------------------ */ + /** + * Blocks until the thread pool is {@link LifeCycle#stop stopped}. + */ + public void join() throws InterruptedException + { + synchronized (_joinLock) + { + while (isRunning()) + _joinLock.wait(); + } + + while (isStopping()) + Thread.sleep(1); + } + + /* ------------------------------------------------------------ */ + /** + * @return The total number of threads currently in the pool + */ + public int getThreads() + { + return _threadsStarted.get(); + } + + /* ------------------------------------------------------------ */ + /** + * @return The number of idle threads in the pool + */ + public int getIdleThreads() + { + return _threadsIdle.get(); + } + + /* ------------------------------------------------------------ */ + /** + * @return True if the pool is at maxThreads and there are not more idle threads than queued jobs + */ + public boolean isLowOnThreads() + { + return _threadsStarted.get()==_maxThreads && _jobs.size()>=_threadsIdle.get(); + } + + /* ------------------------------------------------------------ */ + private boolean startThread(int threads) + { + final int next=threads+1; + if (!_threadsStarted.compareAndSet(threads,next)) + return false; + + boolean started=false; + try + { + Thread thread=newThread(_runnable); + thread.setDaemon(_daemon); + thread.setPriority(_priority); + thread.setName(_name+"-"+thread.getId()); + _threads.add(thread); + + thread.start(); + started=true; + } + finally + { + if (!started) + _threadsStarted.decrementAndGet(); + } + return started; + } + + /* ------------------------------------------------------------ */ + protected Thread newThread(Runnable runnable) + { + return new Thread(runnable); + } + + + /* ------------------------------------------------------------ */ + public String dump() + { + return AggregateLifeCycle.dump(this); + } + + /* ------------------------------------------------------------ */ + public void dump(Appendable out, String indent) throws IOException + { + List<Object> dump = new ArrayList<Object>(getMaxThreads()); + for (final Thread thread: _threads) + { + final StackTraceElement[] trace=thread.getStackTrace(); + boolean inIdleJobPoll=false; + // trace can be null on early java 6 jvms + if (trace != null) + { + for (StackTraceElement t : trace) + { + if ("idleJobPoll".equals(t.getMethodName())) + { + inIdleJobPoll = true; + break; + } + } + } + final boolean idle=inIdleJobPoll; + + if (_detailedDump) + { + dump.add(new Dumpable() + { + public void dump(Appendable out, String indent) throws IOException + { + out.append(String.valueOf(thread.getId())).append(' ').append(thread.getName()).append(' ').append(thread.getState().toString()).append(idle?" IDLE":"").append('\n'); + if (!idle) + AggregateLifeCycle.dump(out,indent,Arrays.asList(trace)); + } + + public String dump() + { + return null; + } + }); + } + else + { + dump.add(thread.getId()+" "+thread.getName()+" "+thread.getState()+" @ "+(trace.length>0?trace[0]:"???")+(idle?" IDLE":"")); + } + } + + AggregateLifeCycle.dumpObject(out,this); + AggregateLifeCycle.dump(out,indent,dump); + + } + + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + return _name+"{"+getMinThreads()+"<="+getIdleThreads()+"<="+getThreads()+"/"+getMaxThreads()+","+(_jobs==null?-1:_jobs.size())+"}"; + } + + /* ------------------------------------------------------------ */ + private Runnable idleJobPoll() throws InterruptedException + { + return _jobs.poll(_maxIdleTimeMs,TimeUnit.MILLISECONDS); + } + + /* ------------------------------------------------------------ */ + private Runnable _runnable = new Runnable() + { + public void run() + { + boolean shrink=false; + try + { + Runnable job=_jobs.poll(); + while (isRunning()) + { + // Job loop + while (job!=null && isRunning()) + { + runJob(job); + job=_jobs.poll(); + } + + // Idle loop + try + { + _threadsIdle.incrementAndGet(); + + while (isRunning() && job==null) + { + if (_maxIdleTimeMs<=0) + job=_jobs.take(); + else + { + // maybe we should shrink? + final int size=_threadsStarted.get(); + if (size>_minThreads) + { + long last=_lastShrink.get(); + long now=System.currentTimeMillis(); + if (last==0 || (now-last)>_maxIdleTimeMs) + { + shrink=_lastShrink.compareAndSet(last,now) && + _threadsStarted.compareAndSet(size,size-1); + if (shrink) + return; + } + } + job=idleJobPoll(); + } + } + } + finally + { + _threadsIdle.decrementAndGet(); + } + } + } + catch(InterruptedException e) + { + LOG.ignore(e); + } + catch(Exception e) + { + LOG.warn(e); + } + finally + { + if (!shrink) + _threadsStarted.decrementAndGet(); + _threads.remove(Thread.currentThread()); + } + } + }; + + /* ------------------------------------------------------------ */ + /** + * <p>Runs the given job in the {@link Thread#currentThread() current thread}.</p> + * <p>Subclasses may override to perform pre/post actions before/after the job is run.</p> + * + * @param job the job to run + */ + protected void runJob(Runnable job) + { + job.run(); + } + + /* ------------------------------------------------------------ */ + /** + * @return the job queue + */ + protected BlockingQueue<Runnable> getQueue() + { + return _jobs; + } + + /* ------------------------------------------------------------ */ + /** + * @param id The thread ID to stop. + * @return true if the thread was found and stopped. + * @deprecated Use {@link #interruptThread(long)} in preference + */ + @Deprecated + public boolean stopThread(long id) + { + for (Thread thread: _threads) + { + if (thread.getId()==id) + { + thread.stop(); + return true; + } + } + return false; + } + + /* ------------------------------------------------------------ */ + /** + * @param id The thread ID to interrupt. + * @return true if the thread was found and interrupted. + */ + public boolean interruptThread(long id) + { + for (Thread thread: _threads) + { + if (thread.getId()==id) + { + thread.interrupt(); + return true; + } + } + return false; + } + + /* ------------------------------------------------------------ */ + /** + * @param id The thread ID to interrupt. + * @return true if the thread was found and interrupted. + */ + public String dumpThread(long id) + { + for (Thread thread: _threads) + { + if (thread.getId()==id) + { + StringBuilder buf = new StringBuilder(); + buf.append(thread.getId()).append(" ").append(thread.getName()).append(" ").append(thread.getState()).append(":\n"); + for (StackTraceElement element : thread.getStackTrace()) + buf.append(" at ").append(element.toString()).append('\n'); + return buf.toString(); + } + } + return null; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/thread/ShutdownThread.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,148 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.thread; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.eclipse.jetty.util.component.Destroyable; +import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + + +/* ------------------------------------------------------------ */ +/** + * ShutdownThread is a shutdown hook thread implemented as + * singleton that maintains a list of lifecycle instances + * that are registered with it and provides ability to stop + * these lifecycles upon shutdown of the Java Virtual Machine + */ +public class ShutdownThread extends Thread +{ + private static final Logger LOG = Log.getLogger(ShutdownThread.class); + private static final ShutdownThread _thread = new ShutdownThread(); + + private boolean _hooked; + private final List<LifeCycle> _lifeCycles = new CopyOnWriteArrayList<LifeCycle>(); + + /* ------------------------------------------------------------ */ + /** + * Default constructor for the singleton + * + * Registers the instance as shutdown hook with the Java Runtime + */ + private ShutdownThread() + { + } + + /* ------------------------------------------------------------ */ + private synchronized void hook() + { + try + { + if (!_hooked) + Runtime.getRuntime().addShutdownHook(this); + _hooked=true; + } + catch(Exception e) + { + LOG.ignore(e); + LOG.info("shutdown already commenced"); + } + } + + /* ------------------------------------------------------------ */ + private synchronized void unhook() + { + try + { + _hooked=false; + Runtime.getRuntime().removeShutdownHook(this); + } + catch(Exception e) + { + LOG.ignore(e); + LOG.debug("shutdown already commenced"); + } + } + + /* ------------------------------------------------------------ */ + /** + * Returns the instance of the singleton + * + * @return the singleton instance of the {@link ShutdownThread} + */ + public static ShutdownThread getInstance() + { + return _thread; + } + + /* ------------------------------------------------------------ */ + public static synchronized void register(LifeCycle... lifeCycles) + { + _thread._lifeCycles.addAll(Arrays.asList(lifeCycles)); + if (_thread._lifeCycles.size()>0) + _thread.hook(); + } + + /* ------------------------------------------------------------ */ + public static synchronized void register(int index, LifeCycle... lifeCycles) + { + _thread._lifeCycles.addAll(index,Arrays.asList(lifeCycles)); + if (_thread._lifeCycles.size()>0) + _thread.hook(); + } + + /* ------------------------------------------------------------ */ + public static synchronized void deregister(LifeCycle lifeCycle) + { + _thread._lifeCycles.remove(lifeCycle); + if (_thread._lifeCycles.size()==0) + _thread.unhook(); + } + + /* ------------------------------------------------------------ */ + @Override + public void run() + { + for (LifeCycle lifeCycle : _thread._lifeCycles) + { + try + { + if (lifeCycle.isStarted()) + { + lifeCycle.stop(); + LOG.debug("Stopped {}",lifeCycle); + } + + if (lifeCycle instanceof Destroyable) + { + ((Destroyable)lifeCycle).destroy(); + LOG.debug("Destroyed {}",lifeCycle); + } + } + catch (Exception ex) + { + LOG.debug(ex); + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/thread/ThreadPool.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,67 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.thread; + +import org.eclipse.jetty.util.component.LifeCycle; + +/* ------------------------------------------------------------ */ +/** ThreadPool. + * + * + */ +public interface ThreadPool +{ + /* ------------------------------------------------------------ */ + public abstract boolean dispatch(Runnable job); + + /* ------------------------------------------------------------ */ + /** + * Blocks until the thread pool is {@link LifeCycle#stop stopped}. + */ + public void join() throws InterruptedException; + + /* ------------------------------------------------------------ */ + /** + * @return The total number of threads currently in the pool + */ + public int getThreads(); + + /* ------------------------------------------------------------ */ + /** + * @return The number of idle threads in the pool + */ + public int getIdleThreads(); + + /* ------------------------------------------------------------ */ + /** + * @return True if the pool is low on threads + */ + public boolean isLowOnThreads(); + + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + public interface SizedThreadPool extends ThreadPool + { + public int getMinThreads(); + public int getMaxThreads(); + public void setMinThreads(int threads); + public void setMaxThreads(int threads); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/thread/Timeout.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,380 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util.thread; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + + +/* ------------------------------------------------------------ */ +/** Timeout queue. + * This class implements a timeout queue for timers that are at least as likely to be cancelled as they are to expire. + * Unlike the util timeout class, the duration of the timeouts is shared by all scheduled tasks and if the duration + * is changed, this affects all scheduled tasks. + * <p> + * The nested class Task should be extended by users of this class to obtain call back notification of + * expires. + */ +public class Timeout +{ + private static final Logger LOG = Log.getLogger(Timeout.class); + private Object _lock; + private long _duration; + private volatile long _now=System.currentTimeMillis(); + private Task _head=new Task(); + + /* ------------------------------------------------------------ */ + public Timeout() + { + _lock=new Object(); + _head._timeout=this; + } + + /* ------------------------------------------------------------ */ + public Timeout(Object lock) + { + _lock=lock; + _head._timeout=this; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the duration. + */ + public long getDuration() + { + return _duration; + } + + /* ------------------------------------------------------------ */ + /** + * @param duration The duration to set. + */ + public void setDuration(long duration) + { + _duration = duration; + } + + /* ------------------------------------------------------------ */ + public long setNow() + { + return _now=System.currentTimeMillis(); + } + + /* ------------------------------------------------------------ */ + public long getNow() + { + return _now; + } + + /* ------------------------------------------------------------ */ + public void setNow(long now) + { + _now=now; + } + + /* ------------------------------------------------------------ */ + /** Get an expired tasks. + * This is called instead of {@link #tick()} to obtain the next + * expired Task, but without calling it's {@link Task#expire()} or + * {@link Task#expired()} methods. + * + * @return the next expired task or null. + */ + public Task expired() + { + synchronized (_lock) + { + long _expiry = _now-_duration; + + if (_head._next!=_head) + { + Task task = _head._next; + if (task._timestamp>_expiry) + return null; + + task.unlink(); + task._expired=true; + return task; + } + return null; + } + } + + /* ------------------------------------------------------------ */ + public void tick() + { + final long expiry = _now-_duration; + + Task task=null; + while (true) + { + try + { + synchronized (_lock) + { + task= _head._next; + if (task==_head || task._timestamp>expiry) + break; + task.unlink(); + task._expired=true; + task.expire(); + } + + task.expired(); + } + catch(Throwable th) + { + LOG.warn(Log.EXCEPTION,th); + } + } + } + + /* ------------------------------------------------------------ */ + public void tick(long now) + { + _now=now; + tick(); + } + + /* ------------------------------------------------------------ */ + public void schedule(Task task) + { + schedule(task,0L); + } + + /* ------------------------------------------------------------ */ + /** + * @param task + * @param delay A delay in addition to the default duration of the timeout + */ + public void schedule(Task task,long delay) + { + synchronized (_lock) + { + if (task._timestamp!=0) + { + task.unlink(); + task._timestamp=0; + } + task._timeout=this; + task._expired=false; + task._delay=delay; + task._timestamp = _now+delay; + + Task last=_head._prev; + while (last!=_head) + { + if (last._timestamp <= task._timestamp) + break; + last=last._prev; + } + last.link(task); + } + } + + + /* ------------------------------------------------------------ */ + public void cancelAll() + { + synchronized (_lock) + { + _head._next=_head._prev=_head; + } + } + + /* ------------------------------------------------------------ */ + public boolean isEmpty() + { + synchronized (_lock) + { + return _head._next==_head; + } + } + + /* ------------------------------------------------------------ */ + public long getTimeToNext() + { + synchronized (_lock) + { + if (_head._next==_head) + return -1; + long to_next = _duration+_head._next._timestamp-_now; + return to_next<0?0:to_next; + } + } + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + StringBuffer buf = new StringBuffer(); + buf.append(super.toString()); + + Task task = _head._next; + while (task!=_head) + { + buf.append("-->"); + buf.append(task); + task=task._next; + } + + return buf.toString(); + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /** Task. + * The base class for scheduled timeouts. This class should be + * extended to implement the expire() method, which is called if the + * timeout expires. + * + * + * + */ + public static class Task + { + Task _next; + Task _prev; + Timeout _timeout; + long _delay; + long _timestamp=0; + boolean _expired=false; + + /* ------------------------------------------------------------ */ + protected Task() + { + _next=_prev=this; + } + + /* ------------------------------------------------------------ */ + public long getTimestamp() + { + return _timestamp; + } + + /* ------------------------------------------------------------ */ + public long getAge() + { + final Timeout t = _timeout; + if (t!=null) + { + final long now=t._now; + if (now!=0 && _timestamp!=0) + return now-_timestamp; + } + return 0; + } + + /* ------------------------------------------------------------ */ + private void unlink() + { + _next._prev=_prev; + _prev._next=_next; + _next=_prev=this; + _expired=false; + } + + /* ------------------------------------------------------------ */ + private void link(Task task) + { + Task next_next = _next; + _next._prev=task; + _next=task; + _next._next=next_next; + _next._prev=this; + } + + /* ------------------------------------------------------------ */ + /** Schedule the task on the given timeout. + * The task exiry will be called after the timeout duration. + * @param timer + */ + public void schedule(Timeout timer) + { + timer.schedule(this); + } + + /* ------------------------------------------------------------ */ + /** Schedule the task on the given timeout. + * The task exiry will be called after the timeout duration. + * @param timer + */ + public void schedule(Timeout timer, long delay) + { + timer.schedule(this,delay); + } + + /* ------------------------------------------------------------ */ + /** Reschedule the task on the current timeout. + * The task timeout is rescheduled as if it had been cancelled and + * scheduled on the current timeout. + */ + public void reschedule() + { + Timeout timeout = _timeout; + if (timeout!=null) + timeout.schedule(this,_delay); + } + + /* ------------------------------------------------------------ */ + /** Cancel the task. + * Remove the task from the timeout. + */ + public void cancel() + { + Timeout timeout = _timeout; + if (timeout!=null) + { + synchronized (timeout._lock) + { + unlink(); + _timestamp=0; + } + } + } + + /* ------------------------------------------------------------ */ + public boolean isExpired() { return _expired; } + + /* ------------------------------------------------------------ */ + public boolean isScheduled() { return _next!=this; } + + /* ------------------------------------------------------------ */ + /** Expire task. + * This method is called when the timeout expires. It is called + * in the scope of the synchronize block (on this) that sets + * the {@link #isExpired()} state to true. + * @see #expired() For an unsynchronized callback. + */ + protected void expire(){} + + /* ------------------------------------------------------------ */ + /** Expire task. + * This method is called when the timeout expires. It is called + * outside of any synchronization scope and may be delayed. + * + */ + public void expired(){} + + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/thread/jmx/QueuedThreadPool-mbean.properties Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,16 @@ +QueuedThreadPool: A thread pool with no max bound by default +minThreads: Minimum number of threads in the pool +maxThreads: Maximum number threads in the pool +maxQueued: The maximum size of the job queue or -1 for unbounded. +name: Name of the thread pool +daemon: Is pool thread using daemon thread +threadsPriority: The priority of threads in the pool +maxIdleTimeMs: Maximum time a thread may be idle in ms +detailedDump: Full stack detail in dump output +dump(): Dump thread state +stopThread(long): Stop a pool thread +stopThread(long)[0]: id:Thread ID +interruptThread(long): Interrupt a pool thread +interruptThread(long)[0]: id:Thread ID +dumpThread(long): Dump a pool thread stack +dumpThread(long)[0]: id:Thread ID
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/thread/jmx/ThreadPool-mbean.properties Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,4 @@ +ThreadPool: Pool of threads +threads: RO:Number of Threads in pool +idleThreads: RO:Number of idle Threads in pool +lowOnThreads: RO:Indicates the pool is low on available threads.