From 92c95f7fea1951d6a3297c7cb9fab8caebc2b51d Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 12 Apr 2010 16:26:07 +0000 Subject: [PATCH] Enhancements for #297104. git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@1496 7e9141cc-0065-0410-87d8-b60c137991c4 --- .../eclipse/jetty/server/HttpConnection.java | 35 +- .../jetty/server/handler/ContextHandler.java | 375 ++++++------- .../jetty/server/handler/ProxyHandler.java | 497 ++++++++++++++++++ .../handler/ProxyHandlerConnectTest.java | 328 ++++++++++++ .../server/handler/ProxyHandlerSSLTest.java | 82 +++ 5 files changed, 1112 insertions(+), 205 deletions(-) create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/handler/ProxyHandler.java create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/handler/ProxyHandlerConnectTest.java create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/handler/ProxyHandlerSSLTest.java diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java index 037a6e3fb81..9cd0753096b 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java @@ -16,7 +16,6 @@ package org.eclipse.jetty.server; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; - import javax.servlet.ServletInputStream; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; @@ -41,13 +40,12 @@ import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.http.Parser; import org.eclipse.jetty.io.AsyncEndPoint; import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.BufferCache.CachedBuffer; 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.io.BufferCache.CachedBuffer; -import org.eclipse.jetty.io.nio.SelectChannelEndPoint; import org.eclipse.jetty.util.QuotedStringTokenizer; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.URIUtil; @@ -68,7 +66,7 @@ import org.eclipse.jetty.util.thread.Timeout; * with the connection via the parser and/or generator. *

*

- * The connection state is held by 3 separate state machines: The request state, the + * 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. *

@@ -76,12 +74,12 @@ import org.eclipse.jetty.util.thread.Timeout; * 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 + * 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 + * 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()).getHeaderBuffer() and * (HttpParser)http.getParser()).getBodyBuffer() to initialise their new connection. *

* @@ -378,7 +376,7 @@ public class HttpConnection implements Connection public Connection handle() throws IOException { Connection connection = this; - + // Loop while more in buffer boolean more_in_buffer =true; // assume true until proven otherwise boolean progress=true; @@ -396,9 +394,9 @@ public class HttpConnection implements Connection { if (_request._async.isAsync()) { - // TODO - handle the case of input being read for a + // TODO - handle the case of input being read for a // suspended request. - + Log.debug("async request",_request); if (!_request._async.isComplete()) handleRequest(); @@ -470,7 +468,7 @@ public class HttpConnection implements Connection // look for a switched connection instance? Connection switched=(_response.getStatus()==HttpStatus.SWITCHING_PROTOCOLS_101) ?(Connection)_request.getAttribute("org.eclipse.jetty.io.Connection"):null; - + // have we switched? if (switched!=null) { @@ -490,7 +488,7 @@ public class HttpConnection implements Connection if (more_in_buffer) { reset(false); - more_in_buffer = _parser.isMoreInBuffer() || _endp.isBufferingInput(); + more_in_buffer = _parser.isMoreInBuffer() || _endp.isBufferingInput(); } else reset(true); @@ -503,7 +501,7 @@ public class HttpConnection implements Connection Log.debug("return with suspended request"); more_in_buffer=false; } - else if (_generator.isCommitted() && !_generator.isComplete() && _endp instanceof AsyncEndPoint) + else if (_generator.isCommitted() && !_generator.isComplete() && _endp instanceof AsyncEndPoint) ((AsyncEndPoint)_endp).setWritable(false); } } @@ -628,7 +626,7 @@ public class HttpConnection implements Connection Log.debug(e); _request.setHandled(true); _generator.sendError(info==null?400:500, null, null, true); - + } finally { @@ -839,18 +837,17 @@ public class HttpConnection implements Connection switch (HttpMethods.CACHE.getOrdinal(method)) { case HttpMethods.CONNECT_ORDINAL: - // _uri.parseConnect(uri.array(), uri.getIndex(), uri.length()); - _uri.parse("http://"+uri+"/"); + _uri.parseConnect(uri.array(), uri.getIndex(), uri.length()); break; - + case HttpMethods.HEAD_ORDINAL: _head=true; // fall through - + default: _uri.parse(uri.array(), uri.getIndex(), uri.length()); } - + _request.setUri(_uri); if (version==null) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java index 93bd8b3bc1d..29b685e2369 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java @@ -4,11 +4,11 @@ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. -// The Eclipse Public License is available at +// The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php -// You may elect to redistribute this code under either of these licenses. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.server.handler; @@ -28,7 +28,6 @@ import java.util.HashSet; import java.util.Locale; import java.util.Map; import java.util.Set; - import javax.servlet.RequestDispatcher; import javax.servlet.Servlet; import javax.servlet.ServletContext; @@ -44,6 +43,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpException; +import org.eclipse.jetty.http.HttpMethods; import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.server.Dispatcher; @@ -64,31 +64,31 @@ 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. - * + * *

* 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. - * + * * @org.apache.xbean.XBean description="Creates a basic HTTP context" * - * + * * */ public class ContextHandler extends ScopedHandler implements Attributes, Server.Graceful { private static final ThreadLocal __context=new ThreadLocal(); public static final String MANAGED_ATTRIBUTES = "org.eclipse.jetty.server.context.ManagedAttributes"; - + /* ------------------------------------------------------------ */ /** Get the current ServletContext implementation. * This call is only valid during a call to doStart and is available to * nested handlers to access the context. - * + * * @return ServletContext implementation */ public static Context getCurrentContext() @@ -97,14 +97,14 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. } protected Context _scontext; - + private AttributesMap _attributes; private AttributesMap _contextAttributes; private ClassLoader _classLoader; private String _contextPath="/"; private Map _initParams; private String _displayName; - private Resource _baseResource; + private Resource _baseResource; private MimeTypes _mimeTypes; private Map _localeEncodingMap; private String[] _welcomeFiles; @@ -125,15 +125,15 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. private Map _managedAttributes; private boolean _shutdown=false; - private boolean _available=true; + 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() { @@ -142,10 +142,10 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. _attributes=new AttributesMap(); _initParams=new HashMap(); } - + /* ------------------------------------------------------------ */ /** - * + * */ protected ContextHandler(Context context) { @@ -154,20 +154,20 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. _attributes=new AttributesMap(); _initParams=new HashMap(); } - + /* ------------------------------------------------------------ */ /** - * + * */ public ContextHandler(String contextPath) { this(); setContextPath(contextPath); } - + /* ------------------------------------------------------------ */ /** - * + * */ public ContextHandler(HandlerContainer parent, String contextPath) { @@ -184,7 +184,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. { return _scontext; } - + /* ------------------------------------------------------------ */ /** * @return the allowNullPathInfo true if /context is not redirected to /context/ @@ -212,13 +212,13 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. Server old_server=getServer(); if (old_server!=null && old_server!=server) old_server.getContainer().update(this, _errorHandler, null, "error",true); - super.setServer(server); + super.setServer(server); if (server!=null && server!=old_server) server.getContainer().update(this, null, _errorHandler, "error",true); - _errorHandler.setServer(server); + _errorHandler.setServer(server); } else - super.setServer(server); + super.setServer(server); } /* ------------------------------------------------------------ */ @@ -238,8 +238,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. if ( vhosts == null ) { _vhosts = vhosts; - } - else + } + else { _vhosts = new String[vhosts.length]; for ( int i = 0; i < vhosts.length; i++ ) @@ -265,8 +265,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. } /* ------------------------------------------------------------ */ - /** - * @deprecated use {@link #setConnectorNames(String[])} + /** + * @deprecated use {@link #setConnectorNames(String[])} */ @Deprecated public void setHosts(String[] hosts) @@ -293,15 +293,15 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. { 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. */ @@ -312,9 +312,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. else _connectors= new HashSet(Arrays.asList(connectors)); } - + /* ------------------------------------------------------------ */ - /* + /* * @see javax.servlet.ServletContext#getAttribute(java.lang.String) */ public Object getAttribute(String name) @@ -323,7 +323,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. } /* ------------------------------------------------------------ */ - /* + /* * @see javax.servlet.ServletContext#getAttributeNames() */ @SuppressWarnings("unchecked") @@ -331,7 +331,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. { return AttributesMap.getAttributeNamesCopy(_attributes); } - + /* ------------------------------------------------------------ */ /** * @return Returns the attributes. @@ -340,7 +340,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. { return _attributes; } - + /* ------------------------------------------------------------ */ /** * @return Returns the classLoader. @@ -393,9 +393,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. { return _contextPath; } - + /* ------------------------------------------------------------ */ - /* + /* * @see javax.servlet.ServletContext#getInitParameter(java.lang.String) */ public String getInitParameter(String name) @@ -404,7 +404,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. } /* ------------------------------------------------------------ */ - /* + /* * @see javax.servlet.ServletContext#getInitParameterNames() */ @SuppressWarnings("unchecked") @@ -412,7 +412,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. { return Collections.enumeration(_initParams.keySet()); } - + /* ------------------------------------------------------------ */ /** * @return Returns the initParams. @@ -423,7 +423,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. } /* ------------------------------------------------------------ */ - /* + /* * @see javax.servlet.ServletContext#getServletContextName() */ public String getDisplayName() @@ -436,7 +436,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. { return _eventListeners; } - + /* ------------------------------------------------------------ */ /** * Set the context event listeners. @@ -451,26 +451,26 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. _contextAttributeListeners=null; _requestListeners=null; _requestAttributeListeners=null; - + _eventListeners=eventListeners; - + for (int i=0; eventListeners!=null && i0) { String vhost = normalizeHostname( baseRequest.getServerName()); boolean match=false; - + // TODO non-linear lookup for (int i=0;!match && i<_vhosts.length;i++) { @@ -750,7 +750,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. if (!match) return false; } - + // Check the connector if (_connectors!=null && _connectors.size()>0) { @@ -758,8 +758,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. if (connector==null || !_connectors.contains(connector)) return false; } - - + + if (target.startsWith(_contextPath)) { if (_contextPath.length()==target.length() && _contextPath.length()>1 &&!_allowNullPathInfo) @@ -768,20 +768,23 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. baseRequest.setHandled(true); if (baseRequest.getQueryString()!=null) response.sendRedirect(URIUtil.addPaths(baseRequest.getRequestURI(),URIUtil.SLASH)+"?"+baseRequest.getQueryString()); - else + else response.sendRedirect(URIUtil.addPaths(baseRequest.getRequestURI(),URIUtil.SLASH)); return false; } } else { + if (HttpMethods.CONNECT.equalsIgnoreCase(baseRequest.getMethod()) && "/".equals(_contextPath)) + return true; + // Not for this context! return false; } - + return true; - } - + } + /* ------------------------------------------------------------ */ @@ -790,7 +793,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. */ @Override public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException - { + { Context old_context=null; String old_context_path=null; String old_servlet_path=null; @@ -800,9 +803,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. String pathInfo=null; DispatcherType dispatch=baseRequest.getDispatcherType(); - + old_context=baseRequest.getContext(); - + // Are we already in this context? if (old_context!=_scontext) { @@ -813,7 +816,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. target=URIUtil.compactPath(target); if (!checkContext(target,baseRequest,response)) return; - + if (target.length()>_contextPath.length()) { if (_contextPath.length()>1) @@ -840,13 +843,13 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. 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); if (!DispatcherType.INCLUDE.equals(dispatch) && target.startsWith("/")) @@ -858,7 +861,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. baseRequest.setServletPath(null); baseRequest.setPathInfo(pathInfo); } - + // start manual inline of nextScope(target,baseRequest,request,response); //noinspection ConstantIfStatement if (false) @@ -867,7 +870,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. _nextScope.doScope(target,baseRequest,request, response); else if (_outerScope!=null) _outerScope.doHandle(target,baseRequest,request, response); - else + else doHandle(target,baseRequest,request, response); // end manual inline (pathentic attempt to reduce stack depth) } @@ -880,16 +883,16 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. { current_thread.setContextClassLoader(old_classloader); } - + // reset the context and servlet path. baseRequest.setContext(old_context); baseRequest.setContextPath(old_context_path); baseRequest.setServletPath(old_servlet_path); - baseRequest.setPathInfo(old_path_info); + 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) @@ -919,10 +922,10 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. ((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 (false) @@ -950,7 +953,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. for(int i=0;i0;) @@ -959,12 +962,12 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. } } } - + /* ------------------------------------------------------------ */ /* Handle a runnable in this context */ public void handle(Runnable runnable) - { + { ClassLoader old_classloader=null; Thread current_thread=null; try @@ -976,7 +979,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. old_classloader=current_thread.getContextClassLoader(); current_thread.setContextClassLoader(_classLoader); } - + runnable.run(); } finally @@ -997,12 +1000,12 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. */ /* ------------------------------------------------------------ */ protected boolean isProtectedTarget(String target) - { + { return false; } /* ------------------------------------------------------------ */ - /* + /* * @see javax.servlet.ServletContext#removeAttribute(java.lang.String) */ public void removeAttribute(String name) @@ -1014,7 +1017,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. /* ------------------------------------------------------------ */ /* 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 + * 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) */ @@ -1023,7 +1026,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. setManagedAttribute(name,value); _attributes.setAttribute(name,value); } - + /* ------------------------------------------------------------ */ /** * @param attributes The attributes to set. @@ -1068,7 +1071,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. /* ------------------------------------------------------------ */ private void setManagedAttribute(String name, Object value) - { + { if (_managedAttributes!=null && _managedAttributes.containsKey(name)) { Object old =_managedAttributes.put(name,value); @@ -1081,7 +1084,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. } } } - + /* ------------------------------------------------------------ */ /** * @param classLoader The classLoader to set. @@ -1090,7 +1093,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. { _classLoader = classLoader; } - + /* ------------------------------------------------------------ */ /** * @param contextPath The _contextPath to set. @@ -1100,7 +1103,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. 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); @@ -1108,7 +1111,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. ((ContextHandlerCollection)contextCollections[h]).mapContexts(); } } - + /* ------------------------------------------------------------ */ /** * @param initParams The initParams to set. @@ -1119,7 +1122,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. return; _initParams = new HashMap(initParams); } - + /* ------------------------------------------------------------ */ /** * @param servletContextName The servletContextName to set. @@ -1128,7 +1131,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. { _displayName = servletContextName; } - + /* ------------------------------------------------------------ */ /** * @return Returns the resourceBase. @@ -1150,12 +1153,12 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. return null; return _baseResource.toString(); } - + /* ------------------------------------------------------------ */ /** * @param base The resourceBase to set. */ - public void setBaseResource(Resource base) + public void setBaseResource(Resource base) { _baseResource=base; } @@ -1164,7 +1167,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. /** * @param resourceBase The base resource as a string. */ - public void setResourceBase(String resourceBase) + public void setResourceBase(String resourceBase) { try { @@ -1203,7 +1206,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. { return _mimeTypes; } - + /* ------------------------------------------------------------ */ /** * @param mimeTypes The mimeTypes to set. @@ -1216,7 +1219,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. /* ------------------------------------------------------------ */ /** */ - public void setWelcomeFiles(String[] files) + public void setWelcomeFiles(String[] files) { _welcomeFiles=files; } @@ -1227,7 +1230,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. * @see The Servlet Specification * @see #setWelcomeFiles */ - public String[] getWelcomeFiles() + public String[] getWelcomeFiles() { return _welcomeFiles; } @@ -1253,13 +1256,13 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. getServer().getContainer().update(this, _errorHandler, errorHandler, "errorHandler",true); _errorHandler = errorHandler; } - + /* ------------------------------------------------------------ */ public int getMaxFormContentSize() { return _maxFormContentSize; } - + /* ------------------------------------------------------------ */ public void setMaxFormContentSize(int maxSize) { @@ -1289,7 +1292,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. @Override public String toString() { - + return super.toString()+"@"+Integer.toHexString(hashCode())+getContextPath()+","+getBaseResource(); } @@ -1299,13 +1302,13 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. { 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) @@ -1314,12 +1317,12 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. _localeEncodingMap=new HashMap(); _localeEncodingMap.put(locale, encoding); } - + /* ------------------------------------------------------------ */ /** * Get the character encoding for a locale. The full locale name is first * looked up in the map of encodings. If no encoding is found, then the - * locale language is looked up. + * locale language is looked up. * * @param locale a Locale value * @return a String representing the character encoding for @@ -1334,15 +1337,15 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. 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; @@ -1350,7 +1353,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. { path=URIUtil.canonicalPath(path); Resource resource=_baseResource.addPath(path); - + if (!_aliases && resource.getAlias()!=null) { if (resource.exists()) @@ -1359,20 +1362,20 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. Log.debug("Aliased resource: "+resource+"~="+resource.getAlias()); return null; } - + return resource; } catch(Exception e) { Log.ignore(e); } - + return null; } /* ------------------------------------------------------------ */ /** Convert URL to Resource - * wrapper for {@link Resource#newResource(URL)} enables extensions to + * wrapper for {@link Resource#newResource(URL)} enables extensions to * provide alternate resource implementations. */ public Resource newResource(URL url) throws IOException @@ -1382,7 +1385,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. /* ------------------------------------------------------------ */ /** Convert URL to Resource - * wrapper for {@link Resource#newResource(String)} enables extensions to + * wrapper for {@link Resource#newResource(String)} enables extensions to * provide alternate resource implementations. */ public Resource newResource(String url) throws IOException @@ -1391,20 +1394,20 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. } /* ------------------------------------------------------------ */ - /* + /* */ public Set 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) { @@ -1412,7 +1415,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. for(int i=0;i * A partial implementation of {@link javax.servlet.ServletContext}. - * A complete implementation is provided by the derived {@link org.eclipse.jetty.servlet.ServletContextHandler.Context}. + * A complete implementation is provided by the derived {@link org.eclipse.jetty.servlet.ServletContextHandler.Context}. *

- * + * * */ public class Context implements ServletContext @@ -1460,7 +1463,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. } /* ------------------------------------------------------------ */ - /* + /* * @see javax.servlet.ServletContext#getContext(java.lang.String) */ public ServletContext getContext(String uripath) @@ -1481,14 +1484,14 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. context=ch; } } - + if (context!=null) return context._scontext; return null; } /* ------------------------------------------------------------ */ - /* + /* * @see javax.servlet.ServletContext#getMajorVersion() */ public int getMajorVersion() @@ -1497,7 +1500,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. } /* ------------------------------------------------------------ */ - /* + /* * @see javax.servlet.ServletContext#getMimeType(java.lang.String) */ public String getMimeType(String file) @@ -1511,7 +1514,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. } /* ------------------------------------------------------------ */ - /* + /* * @see javax.servlet.ServletContext#getMinorVersion() */ public int getMinorVersion() @@ -1520,7 +1523,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. } /* ------------------------------------------------------------ */ - /* + /* * @see javax.servlet.ServletContext#getNamedDispatcher(java.lang.String) */ public RequestDispatcher getNamedDispatcher(String name) @@ -1528,7 +1531,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. return null; } /* ------------------------------------------------------------ */ - /* + /* * @see javax.servlet.ServletContext#getRequestDispatcher(java.lang.String) */ public RequestDispatcher getRequestDispatcher(String uriInContext) @@ -1538,7 +1541,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. if (!uriInContext.startsWith("/")) return null; - + try { String query=null; @@ -1563,7 +1566,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. return null; } /* ------------------------------------------------------------ */ - /* + /* * @see javax.servlet.ServletContext#getRealPath(java.lang.String) */ public String getRealPath(String path) @@ -1574,7 +1577,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. path = URIUtil.SLASH; else if(path.charAt(0)!='/') path = URIUtil.SLASH + path; - + try { Resource resource=ContextHandler.this.getResource(path); @@ -1589,7 +1592,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. { Log.ignore(e); } - + return null; } @@ -1601,9 +1604,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. return resource.getURL(); return null; } - + /* ------------------------------------------------------------ */ - /* + /* * @see javax.servlet.ServletContext#getResourceAsStream(java.lang.String) */ public InputStream getResourceAsStream(String path) @@ -1623,16 +1626,16 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. } /* ------------------------------------------------------------ */ - /* + /* * @see javax.servlet.ServletContext#getResourcePaths(java.lang.String) */ public Set getResourcePaths(String path) - { + { return ContextHandler.this.getResourcePaths(path); } /* ------------------------------------------------------------ */ - /* + /* * @see javax.servlet.ServletContext#getServerInfo() */ public String getServerInfo() @@ -1641,7 +1644,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. } /* ------------------------------------------------------------ */ - /* + /* * @see javax.servlet.ServletContext#getServlet(java.lang.String) */ public Servlet getServlet(String name) throws ServletException @@ -1650,7 +1653,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. } /* ------------------------------------------------------------ */ - /* + /* * @see javax.servlet.ServletContext#getServletNames() */ @SuppressWarnings("unchecked") @@ -1660,7 +1663,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. } /* ------------------------------------------------------------ */ - /* + /* * @see javax.servlet.ServletContext#getServlets() */ @SuppressWarnings("unchecked") @@ -1670,7 +1673,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. } /* ------------------------------------------------------------ */ - /* + /* * @see javax.servlet.ServletContext#log(java.lang.Exception, java.lang.String) */ public void log(Exception exception, String msg) @@ -1679,7 +1682,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. } /* ------------------------------------------------------------ */ - /* + /* * @see javax.servlet.ServletContext#log(java.lang.String) */ public void log(String msg) @@ -1688,7 +1691,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. } /* ------------------------------------------------------------ */ - /* + /* * @see javax.servlet.ServletContext#log(java.lang.String, java.lang.Throwable) */ public void log(String message, Throwable throwable) @@ -1697,7 +1700,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. } /* ------------------------------------------------------------ */ - /* + /* * @see javax.servlet.ServletContext#getInitParameter(java.lang.String) */ public String getInitParameter(String name) @@ -1706,7 +1709,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. } /* ------------------------------------------------------------ */ - /* + /* * @see javax.servlet.ServletContext#getInitParameterNames() */ @SuppressWarnings("unchecked") @@ -1716,7 +1719,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. } /* ------------------------------------------------------------ */ - /* + /* * @see javax.servlet.ServletContext#getAttribute(java.lang.String) */ public synchronized Object getAttribute(String name) @@ -1728,7 +1731,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. } /* ------------------------------------------------------------ */ - /* + /* * @see javax.servlet.ServletContext#getAttributeNames() */ @SuppressWarnings("unchecked") @@ -1744,17 +1747,17 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. Enumeration e = _attributes.getAttributeNames(); while(e.hasMoreElements()) set.add(e.nextElement()); - + return Collections.enumeration(set); } /* ------------------------------------------------------------ */ - /* + /* * @see javax.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object) */ public synchronized void setAttribute(String name, Object value) { - + if (_contextAttributes==null) { // Set it on the handler @@ -1764,12 +1767,12 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. setManagedAttribute(name,value); Object old_value=_contextAttributes.getAttribute(name); - + if (value==null) _contextAttributes.removeAttribute(name); else _contextAttributes.setAttribute(name,value); - + if (_contextAttributeListeners!=null) { ServletContextAttributeEvent event = @@ -1778,7 +1781,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. for(int i=0;iHandles a CONNECT request.

+ *

CONNECT requests may have authentication headers such as Proxy-Authorization + * that authenticate the client with the proxy.

+ * @param request the http request + * @param response the http response + * @param connectURI the CONNECT URI + * @throws ServletException if an application error occurs + * @throws IOException if an I/O error occurs + */ + protected void handle(HttpServletRequest request, HttpServletResponse response, String connectURI) throws ServletException, IOException + { + boolean proceed = handleAuthentication(request, response, connectURI); + if (!proceed) + return; + + String host = connectURI; + int port = 80; + boolean secure = false; + int colon = connectURI.indexOf(':'); + if (colon > 0) + { + host = connectURI.substring(0, colon); + port = Integer.parseInt(connectURI.substring(colon + 1)); + secure = isTunnelSecure(host, port); + } + + setupTunnel(request, response, host, port, secure); + } + + protected boolean isTunnelSecure(String host, int port) + { + return port == 443; + } + + protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String connectURI) throws ServletException, IOException + { + return true; + } + + protected void setupTunnel(HttpServletRequest request, HttpServletResponse response, String host, int port, boolean secure) throws IOException + { + SocketChannel channel = connect(request, host, port); + channel.configureBlocking(false); + + // 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. + HttpConnection httpConnection = HttpConnection.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(); + } + } + + // Setup connections, before registering the channel to avoid races + // where the server sends data before the connections are set up + ProxyToServerConnection proxyToServer = new ProxyToServerConnection(secure, buffer); + ClientToProxyConnection clientToProxy = new ClientToProxyConnection(channel, httpConnection.getEndPoint(), httpConnection.getTimeStamp()); + clientToProxy.setConnection(proxyToServer); + proxyToServer.setConnection(clientToProxy); + + upgradeConnection(request, response, clientToProxy); + } + + protected SocketChannel connect(HttpServletRequest request, String host, int port) throws IOException + { + logger.info("Establishing connection to {}:{}", host, port); + // Connect to remote server + SocketChannel channel = SocketChannel.open(); + channel.socket().setTcpNoDelay(true); + channel.socket().connect(new InetSocketAddress(host, port), getConnectTimeout()); + logger.info("Established connection to {}:{}", host, port); + return channel; + } + + private void upgradeConnection(HttpServletRequest request, HttpServletResponse response, Connection connection) throws IOException + { + // CONNECT expects a 200 response + response.setStatus(HttpServletResponse.SC_OK); + // Flush it so that the client receives it + response.flushBuffer(); + // 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); + logger.info("Upgraded connection to {}", connection, null); + } + + private void register(SocketChannel channel, ProxyToServerConnection proxyToServer) throws IOException + { + selectorManager.register(channel, proxyToServer); + proxyToServer.waitReady(connectTimeout); + } + + /** + * Writes (with blocking semantic) the given buffer of data onto the given endPoint + * @param endPoint the endPoint to write to + * @param buffer the buffer to write + * @throws IOException if the buffer cannot be written + */ + private void write(EndPoint endPoint, Buffer buffer) throws IOException + { + if (buffer == null) + return; + + int length = buffer.length(); + StringBuilder builder = new StringBuilder(); + int written = endPoint.flush(buffer); + builder.append(written); + buffer.compact(); + if (!endPoint.isBlocking()) + { + while (buffer.space() == 0) + { + boolean ready = endPoint.blockWritable(getWriteTimeout()); + if (!ready) + throw new IOException("Write timeout"); + + written = endPoint.flush(buffer); + builder.append("+").append(written); + buffer.compact(); + } + } + logger.info("Written {}/{} bytes " + endPoint, builder, length); + } + + private class Manager extends SelectorManager + { + @Override + protected SocketChannel acceptChannel(SelectionKey key) throws IOException + { + // This is a client-side selector manager + throw new IllegalStateException(); + } + + @Override + protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey selectionKey) throws IOException + { + ProxyToServerConnection proxyToServer = (ProxyToServerConnection)selectionKey.attachment(); + if (proxyToServer.secure) + { + throw new UnsupportedOperationException(); +// return new SslSelectChannelEndPoint(???, channel, selectSet, selectionKey, sslContext.createSSLEngine(address.host, address.port)); + } + else + { + return new SelectChannelEndPoint(channel, selectSet, selectionKey); + } + } + + @Override + protected Connection newConnection(SocketChannel channel, SelectChannelEndPoint endpoint) + { + ProxyToServerConnection proxyToServer = (ProxyToServerConnection)endpoint.getSelectionKey().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) + { + } + } + + private class ProxyToServerConnection implements Connection + { + private final CountDownLatch ready = new CountDownLatch(1); + private final Buffer buffer = new IndirectNIOBuffer(1024); + private final boolean secure; + private volatile Buffer data; + private volatile ClientToProxyConnection connection; + private volatile long timestamp; + private volatile SelectChannelEndPoint endPoint; + + public ProxyToServerConnection(boolean secure, Buffer data) + { + this.secure = secure; + this.data = data; + } + + public Connection handle() throws IOException + { + logger.info("ProxyToServer: handle entered"); + if (data != null) + { + write(endPoint, data); + data = null; + } + + while (true) + { + int read = endPoint.fill(buffer); + + if (read == -1) + { + logger.info("ProxyToServer: closed connection {}", endPoint, null); + connection.close(); + break; + } + + if (read == 0) + break; + + logger.info("ProxyToServer: read {} bytes {}", read, endPoint); + write(connection.endPoint, buffer); + } + logger.info("ProxyToServer: handle exited"); + return this; + } + + public void setConnection(ClientToProxyConnection connection) + { + this.connection = connection; + } + + public long getTimeStamp() + { + return timestamp; + } + + public void setTimeStamp(long timestamp) + { + this.timestamp = timestamp; + } + + public void setEndPoint(SelectChannelEndPoint endpoint) + { + this.endPoint = endpoint; + } + + public boolean isIdle() + { + return false; + } + + public boolean isSuspended() + { + return false; + } + + public void ready() + { + ready.countDown(); + } + + public void waitReady(long timeout) throws IOException + { + try + { + ready.await(timeout, TimeUnit.MILLISECONDS); + } + catch (InterruptedException x) + { + throw new IOException(x); + } + } + + public void close() throws IOException + { + endPoint.close(); + } + } + + private class ClientToProxyConnection implements Connection + { + private final Buffer buffer = new IndirectNIOBuffer(1024); + private final SocketChannel channel; + private final EndPoint endPoint; + private final long timestamp; + private volatile ProxyToServerConnection connection; + private boolean firstTime = true; + + public ClientToProxyConnection(SocketChannel channel, EndPoint endPoint, long timestamp) + { + this.channel = channel; + this.endPoint = endPoint; + this.timestamp = timestamp; + } + + public Connection handle() throws IOException + { + logger.info("ClientToProxy: handle entered"); + + if (firstTime) + { + firstTime = false; + register(channel, connection); + } + + while (true) + { + int read = endPoint.fill(buffer); + + if (read == -1) + { + logger.info("ClientToProxy: closed connection {}", endPoint, null); + connection.close(); + break; + } + + if (read == 0) + break; + + logger.info("ClientToProxy: read {} bytes {}", read, endPoint); + write(connection.endPoint, buffer); + } + logger.info("ClientToProxy: handle exited"); + return this; + } + + public long getTimeStamp() + { + return timestamp; + } + + public boolean isIdle() + { + return false; + } + + public boolean isSuspended() + { + return false; + } + + public void setConnection(ProxyToServerConnection connection) + { + this.connection = connection; + } + + public void close() throws IOException + { + endPoint.close(); + } + } +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ProxyHandlerConnectTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ProxyHandlerConnectTest.java new file mode 100644 index 00000000000..3f40fc03016 --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ProxyHandlerConnectTest.java @@ -0,0 +1,328 @@ +package org.eclipse.jetty.server.handler; + +import java.io.BufferedReader; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.Socket; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.nio.SelectChannelConnector; + +/** + * @version $Revision$ $Date$ + */ +public class ProxyHandlerConnectTest extends TestCase +{ + private Server server; + private Connector serverConnector; + private Server proxy; + private Connector proxyConnector; + + @Override + protected void setUp() throws Exception + { + server = new Server(); + serverConnector = new SelectChannelConnector(); + server.addConnector(serverConnector); + server.setHandler(new ServerHandler()); + server.start(); + + proxy = new Server(); + proxyConnector = new SelectChannelConnector(); + proxy.addConnector(proxyConnector); + proxy.setHandler(new ProxyHandler()); + proxy.start(); + } + + @Override + protected void tearDown() throws Exception + { + proxy.stop(); + proxy.join(); + + server.stop(); + server.join(); + } + + public void testHttpConnect() throws Exception + { + String request = "" + + "CONNECT localhost:" + serverConnector.getLocalPort() + " HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "\r\n"; + Socket socket = new Socket("localhost", proxyConnector.getLocalPort()); + try + { + OutputStream output = socket.getOutputStream(); + output.write(request.getBytes("UTF-8")); + output.flush(); + + // Expect 200 OK from the CONNECT request + InputStream input = socket.getInputStream(); + Response response = readResponse(input); + System.err.println(response); + assertEquals("200", response.code); + } + finally + { + socket.close(); + } + } + + public void testHttpConnectWithNormalRequest() throws Exception + { + String request = "" + + "CONNECT localhost:" + serverConnector.getLocalPort() + " HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "\r\n"; + Socket socket = new Socket("localhost", proxyConnector.getLocalPort()); + try + { + OutputStream output = socket.getOutputStream(); + output.write(request.getBytes("UTF-8")); + output.flush(); + + // Expect 200 OK from the CONNECT request + InputStream input = socket.getInputStream(); + Response response = readResponse(input); + System.err.println(response); + assertEquals("200", response.code); + + String echoURI = "GET /echo"; + request = "" + + echoURI + " HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "\r\n"; + output.write(request.getBytes("UTF-8")); + output.flush(); + + response = readResponse(input); + System.err.println(response); + assertEquals("200", response.code); + assertEquals(echoURI, response.body); + } + finally + { + socket.close(); + } + } + + public void testHttpConnectWithPipelinedRequest() throws Exception + { + String pipelinedMethodURI = "GET /echo"; + String request = "" + + "CONNECT localhost:" + serverConnector.getLocalPort() + " HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "\r\n" + + pipelinedMethodURI + " HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "\r\n"; + Socket socket = new Socket("localhost", proxyConnector.getLocalPort()); + try + { + OutputStream output = socket.getOutputStream(); + output.write(request.getBytes("UTF-8")); + output.flush(); + + // Expect 200 OK from the CONNECT request + InputStream input = socket.getInputStream(); + Response response = readResponse(input); + System.err.println(response); + assertEquals("200", response.code); + + // The pipelined request must have gone up to the server as is + response = readResponse(input); + System.err.println(response); + assertEquals("200", response.code); + assertEquals(pipelinedMethodURI, response.body); + } + finally + { + socket.close(); + } + } + + public void testHttpConnectWithNoRequestServerClose() throws Exception + { + String request = "" + + "CONNECT localhost:" + serverConnector.getLocalPort() + " HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "\r\n"; + Socket socket = new Socket("localhost", proxyConnector.getLocalPort()); + try + { + OutputStream output = socket.getOutputStream(); + output.write(request.getBytes("UTF-8")); + output.flush(); + + // Expect 200 OK from the CONNECT request + InputStream input = socket.getInputStream(); + Response response = readResponse(input); + System.err.println(response); + assertEquals("200", response.code); + + // Idle server is shut down + server.stop(); + server.join(); + + int read = input.read(); + assertEquals(-1, read); + } + finally + { + socket.close(); + } + } + + public void testHttpConnectWithRequestServerClose() throws Exception + { + String request = "" + + "CONNECT localhost:" + serverConnector.getLocalPort() + " HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "\r\n"; + Socket socket = new Socket("localhost", proxyConnector.getLocalPort()); + try + { + OutputStream output = socket.getOutputStream(); + output.write(request.getBytes("UTF-8")); + output.flush(); + + // Expect 200 OK from the CONNECT request + InputStream input = socket.getInputStream(); + Response response = readResponse(input); + System.err.println(response); + assertEquals("200", response.code); + + request = "" + + "GET /close HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "\r\n"; + output.write(request.getBytes("UTF-8")); + output.flush(); + + int read = input.read(); + assertEquals(-1, read); + } + finally + { + socket.close(); + } + } + + private Response readResponse(InputStream input) throws IOException + { + // Simplified parser for HTTP responses + BufferedReader reader = new BufferedReader(new InputStreamReader(input)); + String line = reader.readLine(); + if (line == null) + throw new EOFException(); + Matcher responseLine = Pattern.compile("HTTP/1\\.1\\s+(\\d+)").matcher(line); + assertTrue(responseLine.lookingAt()); + String code = responseLine.group(1); + + Map headers = new LinkedHashMap(); + while ((line = reader.readLine()) != null) + { + if (line.trim().length() == 0) + break; + + Matcher header = Pattern.compile("([^:]+):\\s*(.*)").matcher(line); + assertTrue(header.lookingAt()); + String headerName = header.group(1); + String headerValue = header.group(2); + headers.put(headerName.toLowerCase(), headerValue.toLowerCase()); + } + + StringBuilder body = new StringBuilder(); + if (headers.containsKey("content-length")) + { + int length = Integer.parseInt(headers.get("content-length")); + for (int i = 0; i < length; ++i) + body.append((char)reader.read()); + } + else if ("chunked".equals(headers.get("transfer-encoding"))) + { + while ((line = reader.readLine()) != null) + { + if ("0".equals(line)) + { + reader.readLine(); + break; + } + + body.append(reader.readLine()); + reader.readLine(); + } + } + + return new Response(code, headers, body.toString().trim()); + } + + public class TestServlet extends HttpServlet + { + } + + private class ServerHandler extends AbstractHandler + { + public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException + { + request.setHandled(true); + + String uri = httpRequest.getRequestURI(); + if ("/echo".equals(uri)) + { + StringBuilder builder = new StringBuilder(); + builder.append(httpRequest.getMethod()).append(" ").append(uri); + System.err.println("server echoing:\r\n" + builder); + ServletOutputStream output = httpResponse.getOutputStream(); + output.println(builder.toString()); + } + else if ("/close".equals(uri)) + { + request.getConnection().getEndPoint().close(); + System.err.println("server closed"); + } + } + } + + private class Response + { + private final String code; + private final Map headers; + private final String body; + + private Response(String code, Map headers, String body) + { + this.code = code; + this.headers = headers; + this.body = body; + } + + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(); + builder.append(code).append("\r\n"); + for (Map.Entry entry : headers.entrySet()) + builder.append(entry.getKey()).append(": ").append(entry.getValue()).append("\r\n"); + builder.append("\r\n"); + builder.append(body); + return builder.toString(); + } + } +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ProxyHandlerSSLTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ProxyHandlerSSLTest.java new file mode 100644 index 00000000000..377de6619e1 --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ProxyHandlerSSLTest.java @@ -0,0 +1,82 @@ +package org.eclipse.jetty.server.handler; + +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + +import junit.framework.TestCase; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ssl.SslSelectChannelConnector; + +/** + * @version $Revision$ $Date$ + */ +public class ProxyHandlerSSLTest extends TestCase +{ + private Server server; + private Connector serverConnector; + private Server proxy; + private Connector proxyConnector; + + @Override + protected void setUp() throws Exception + { + server = new Server(); + serverConnector = new SslSelectChannelConnector(); + server.addConnector(serverConnector); +// server.setHandler(new EchoHandler()); + server.start(); + + proxy = new Server(); + proxyConnector = new SslSelectChannelConnector(); + proxy.addConnector(proxyConnector); + proxy.setHandler(new ProxyHandler() + { + @Override + protected boolean isTunnelSecure(String host, int port) + { + return true; + } + }); + proxy.start(); + } + + @Override + protected void tearDown() throws Exception + { + proxy.stop(); + proxy.join(); + + server.stop(); + server.join(); + } + + public void testHttpConnectWithNormalRequest() throws Exception + { + String request = "" + + "CONNECT localhost:" + serverConnector.getLocalPort() + " HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "\r\n"; + Socket socket = new Socket("localhost", proxyConnector.getLocalPort()); + try + { + OutputStream output = socket.getOutputStream(); + output.write(request.getBytes("UTF-8")); + output.flush(); + + // Expect 200 OK from the CONNECT request + InputStream input = socket.getInputStream(); +// Response response = readResponse(input); +// System.err.println(response); +// assertEquals("200", response.code); + + // Now what ? Upgrade the socket to SSL ? + + } + finally + { + socket.close(); + } + } +}