Index: code/modules/http-servlet/src/main/java/org/glassfish/grizzly/servlet/ServletHandler.java =================================================================== --- code/modules/http-servlet/src/main/java/org/glassfish/grizzly/servlet/ServletHandler.java (revision 6110) +++ code/modules/http-servlet/src/main/java/org/glassfish/grizzly/servlet/ServletHandler.java (revision ) @@ -59,6 +59,7 @@ import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; import javax.servlet.ServletResponse; + import org.glassfish.grizzly.Grizzly; import org.glassfish.grizzly.http.Cookie; import org.glassfish.grizzly.http.Note; @@ -221,6 +222,7 @@ Map contextParameters, Map servletInitParameters, List listeners, boolean initialize) { this.servletCtx = servletCtx; + this.servletCtx.setServletHandler( this ); servletConfig = new ServletConfigImpl(servletCtx, servletInitParameters); this.contextParameters = contextParameters; this.servletInitParameters = servletInitParameters; @@ -367,6 +369,21 @@ } } + void doServletService(final ServletRequest servletRequest, final ServletResponse servletResponse) + throws IOException, ServletException { + try { + loadServlet(); + FilterChainImpl filterChain = new FilterChainImpl(servletInstance, servletConfig); + filterChain.invokeFilterChain(servletRequest, servletResponse); + } catch (ServletException se) { + LOGGER.log(Level.SEVERE, "service exception:", se); + throw se; + } catch (IOException ie) { + LOGGER.log(Level.SEVERE, "service exception:", ie); + throw ie; + } + } + /** * Customize the error page returned to the client. * @param response the {@link Response} @@ -791,6 +808,16 @@ this.classLoader = classLoader; } + @Override + public String getName() { + return servletConfig.getServletName(); + } + + @Override + public void setName( String servletName ) { + servletConfig.setServletName( servletName ); + } + // private String getDefaultDocRootPath() { // final File[] basePaths = getDocRoots().getArray(); // return (basePaths != null && basePaths.length > 0) ? basePaths[0].getPath() : null; Index: code/modules/http-server/src/main/java/org/glassfish/grizzly/http/server/HttpHandlerChain.java =================================================================== --- code/modules/http-server/src/main/java/org/glassfish/grizzly/http/server/HttpHandlerChain.java (revision 6232) +++ code/modules/http-server/src/main/java/org/glassfish/grizzly/http/server/HttpHandlerChain.java (revision ) @@ -78,11 +78,8 @@ new ConcurrentHashMap(); private ConcurrentHashMap monitors = new ConcurrentHashMap(); + /** - * Internal {@link Mapper} used to Map request to their associated {@link HttpHandler} - */ - private Mapper mapper; - /** * The default host. */ private final static String LOCAL_HOST = "localhost"; @@ -243,7 +240,7 @@ new String[]{"index.html", "index.htm"}, null); } } - mapper.addWrapper(LOCAL_HOST, ctx, wrapper, httpHandler); + mapper.addWrapper(LOCAL_HOST, ctx, wrapper, httpHandler, false, httpHandler.getName(), false); // String ctx = getContextPath(mapping); @@ -252,7 +249,7 @@ // mapper.addWrapper(LOCAL_HOST, ctx, mapping.substring(ctx.length()), httpHandler); } } - + httpHandler.setMapper( mapper ); } private void registerJmxForHandler(final HttpHandler httpHandler) { Index: code/modules/http-servlet/src/main/java/org/glassfish/grizzly/servlet/DispatcherConstants.java =================================================================== --- code/modules/http-servlet/src/main/java/org/glassfish/grizzly/servlet/DispatcherConstants.java (revision ) +++ code/modules/http-servlet/src/main/java/org/glassfish/grizzly/servlet/DispatcherConstants.java (revision ) @@ -0,0 +1,119 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2008-2011 Oracle and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html + * or packager/legal/LICENSE.txt. See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at packager/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * Oracle designates this particular file as subject to the "Classpath" + * exception as provided by Oracle in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + * + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.glassfish.grizzly.servlet; + +/** + * Constants based on Servlet3.0 spec. + * This class will be able to be replaced by Servlet3.0 API + * such as javax.servlet.RequestDispatcher and javax.servlet.DispatcherType + * + * @author Bongjae Chang + */ +public class DispatcherConstants { + + static enum DispatcherType { + FORWARD, + INCLUDE, + REQUEST, + ASYNC, + ERROR + } + + static final String FORWARD_REQUEST_URI = "javax.servlet.forward.request_uri"; + + static final String FORWARD_CONTEXT_PATH = "javax.servlet.forward.context_path"; + + static final String FORWARD_PATH_INFO = "javax.servlet.forward.path_info"; + + static final String FORWARD_SERVLET_PATH = "javax.servlet.forward.servlet_path"; + + static final String FORWARD_QUERY_STRING = "javax.servlet.forward.query_string"; + + static final String INCLUDE_REQUEST_URI = "javax.servlet.include.request_uri"; + + static final String INCLUDE_CONTEXT_PATH = "javax.servlet.include.context_path"; + + static final String INCLUDE_PATH_INFO = "javax.servlet.include.path_info"; + + static final String INCLUDE_SERVLET_PATH = "javax.servlet.include.servlet_path"; + + static final String INCLUDE_QUERY_STRING = "javax.servlet.include.query_string"; + + static final String ERROR_EXCEPTION = "javax.servlet.error.exception"; + + static final String ERROR_EXCEPTION_TYPE = "javax.servlet.error.exception_type"; + + static final String ERROR_MESSAGE = "javax.servlet.error.message"; + + static final String ERROR_REQUEST_URI = "javax.servlet.error.request_uri"; + + static final String ERROR_SERVLET_NAME = "javax.servlet.error.servlet_name"; + + static final String ERROR_STATUS_CODE = "javax.servlet.error.status_code"; + + // async + static final String ASYNC_REQUEST_URI = "javax.servlet.async.request_uri"; + + static final String ASYNC_CONTEXT_PATH = "javax.servlet.async.context_path"; + + static final String ASYNC_PATH_INFO = "javax.servlet.async.path_info"; + + static final String ASYNC_SERVLET_PATH = "javax.servlet.async.servlet_path"; + + static final String ASYNC_QUERY_STRING = "javax.servlet.async.query_string"; +} Index: code/modules/http-servlet/src/main/java/org/glassfish/grizzly/servlet/ServletContextImpl.java =================================================================== --- code/modules/http-servlet/src/main/java/org/glassfish/grizzly/servlet/ServletContextImpl.java (revision 6018) +++ code/modules/http-servlet/src/main/java/org/glassfish/grizzly/servlet/ServletContextImpl.java (revision ) @@ -82,7 +82,10 @@ import javax.servlet.ServletContextListener; import org.glassfish.grizzly.Grizzly; import org.glassfish.grizzly.http.server.util.Enumerator; +import org.glassfish.grizzly.http.server.util.Mapper; +import org.glassfish.grizzly.http.server.util.MappingData; import org.glassfish.grizzly.http.server.util.MimeType; +import org.glassfish.grizzly.http.util.DataChunk; import org.glassfish.grizzly.localization.LogMessages; /** @@ -115,7 +118,13 @@ private String contextName = ""; private volatile String serverInfo = "Grizzly " + Grizzly.getDotedVersion(); - + + /** + * Thread local data used during request dispatch. + */ + private ThreadLocal dispatchData = new ThreadLocal(); + private ServletHandler servletHandler; + // ----------------------------------------------------------------- // /** * Notify the {@link ServletContextListener} that we are starting. @@ -201,20 +210,55 @@ } /** - * Return a ServletContext object that corresponds to a - * specified URI on the server. This method allows servlets to gain - * access to the context for various parts of the server, and as needed - * obtain RequestDispatcher objects or resources from the - * context. The given path must be absolute (beginning with a "/"), - * and is interpreted based on our virtual host's document root. - * - * @param uri Absolute URI of a resource on the server + * {@inheritDoc} */ @Override - public ServletContext getContext(String uri) { + public ServletContext getContext( String uri ) { - return this; + // Validate the format of the specified argument + if( uri == null || !uri.startsWith( "/" ) ) { + return null; - } + } + if( servletHandler == null ) { + return null; + } + Mapper mapper = servletHandler.getMapper(); + if( mapper == null ) { + return null; + } + // Use the thread local URI and mapping data + DispatchData dd = dispatchData.get(); + if( dd == null ) { + dd = new DispatchData(); + dispatchData.set( dd ); + } else { + dd.recycle(); + } + DataChunk uriDC = dd.uriDC; + // Retrieve the thread local mapping data + MappingData mappingData = dd.mappingData; + + try { + uriDC.setString( uri ); + mapper.map( DataChunk.newInstance(), uriDC, mappingData ); + if( mappingData.context == null ) { + return null; + } + } catch( Exception e ) { + // Should never happen + if( logger.isLoggable( Level.WARNING ) ) { + logger.log( Level.WARNING, "Error during mapping", e ); + } + return null; + } + + if( !( mappingData.context instanceof ServletHandler ) ) { + return null; + } + ServletHandler context = (ServletHandler)mappingData.context; + return context.getServletCtx(); + } + /** * Return the major version of the Java Servlet API that we implement. */ @@ -359,21 +403,124 @@ /** * {@inheritDoc} */ - //TODO. @Override - public RequestDispatcher getRequestDispatcher(String arg0) { - throw new UnsupportedOperationException("Not supported yet."); + public RequestDispatcher getRequestDispatcher( String path ) { + // Validate the path argument + if( path == null ) { + return null; - } + } + if( servletHandler == null ) { + return null; + } + Mapper mapper = servletHandler.getMapper(); + if( mapper == null ) { + return null; + } + if( !path.startsWith( "/" ) && !path.isEmpty() ) { + throw new IllegalArgumentException( "Path " + path + " does not start with ''/'' and is not empty" ); + } + // Get query string + String queryString = null; + int pos = path.indexOf( '?' ); + if( pos >= 0 ) { + queryString = path.substring( pos + 1 ); + path = path.substring( 0, pos ); + } + path = normalize( path ); + if( path == null ) + return null; + pos = path.length(); + + // Use the thread local URI and mapping data + DispatchData dd = dispatchData.get(); + if( dd == null ) { + dd = new DispatchData(); + dispatchData.set( dd ); + } else { + dd.recycle(); + } + DataChunk uriDC = dd.uriDC; + // Retrieve the thread local mapping data + MappingData mappingData = dd.mappingData; + + try { + uriDC.setString( contextPath + path ); + mapper.map( DataChunk.newInstance(), uriDC, mappingData ); + if( mappingData.wrapper == null ) { + return null; + } + } catch( Exception e ) { + // Should never happen + if( logger.isLoggable( Level.WARNING ) ) { + logger.log( Level.WARNING, "Error during mapping", e ); + } + return null; + } + + if( !( mappingData.wrapper instanceof ServletHandler ) ) { + return null; + } + ServletHandler wrapper = (ServletHandler)mappingData.wrapper; + String wrapperPath = mappingData.wrapperPath.toString(); + String pathInfo = mappingData.pathInfo.toString(); + // Construct a RequestDispatcher to process this request + return new RequestDispatcherImpl( wrapper, uriDC.toString(), wrapperPath, pathInfo, queryString, null ); + } + /** * {@inheritDoc} */ - //TODO. @Override - public RequestDispatcher getNamedDispatcher(String arg0) { - throw new UnsupportedOperationException("Not supported yet."); + public RequestDispatcher getNamedDispatcher( String name ) { + // Validate the name argument + if( name == null ) + return null; + if( servletHandler == null ) { + return null; - } + } + Mapper mapper = servletHandler.getMapper(); + if( mapper == null ) { + return null; + } + // Use the thread local URI and mapping data + DispatchData dd = dispatchData.get(); + if( dd == null ) { + dd = new DispatchData(); + dispatchData.set( dd ); + } else { + dd.recycle(); + } + DataChunk uriDC = dd.uriDC; + DataChunk servletNameDC = dd.servletNameDC; + // Retrieve the thread local mapping data + MappingData mappingData = dd.mappingData; + // Map the name + uriDC.setString( contextPath ); + servletNameDC.setString( name ); + + try { + mapper.map( DataChunk.newInstance(), uriDC, servletNameDC, mappingData ); + if( mappingData.context == null ) { + return null; + } + } catch( Exception e ) { + // Should never happen + if( logger.isLoggable( Level.WARNING ) ) { + logger.log( Level.WARNING, "Error during mapping", e ); + } + return null; + } + + if( !( mappingData.wrapper instanceof ServletHandler ) ) { + return null; + } + ServletHandler wrapper = (ServletHandler)mappingData.wrapper; + // Construct a RequestDispatcher to process this request + return new RequestDispatcherImpl( wrapper, null, null, null, null, name ); + } + /** * @deprecated As of Java Servlet API 2.1, with no direct replacement. */ @@ -668,4 +815,33 @@ protected List getListeners() { return eventListeners; } + + protected void setServletHandler( ServletHandler servletHandler ) { + this.servletHandler = servletHandler; -} + } + + /** + * Internal class used as thread-local storage when doing path + * mapping during dispatch. + */ + private static final class DispatchData { + public DataChunk uriDC; + public DataChunk servletNameDC; + public MappingData mappingData; + + public DispatchData() { + uriDC = DataChunk.newInstance(); + servletNameDC = DataChunk.newInstance(); + mappingData = new MappingData(); + } + + public void recycle() { + if( uriDC != null ) + uriDC.recycle(); + if( servletNameDC != null ) + servletNameDC.recycle(); + if( mappingData != null ) + mappingData.recycle(); + } + } +} Index: code/modules/http-server/src/main/java/org/glassfish/grizzly/http/server/HttpHandler.java =================================================================== --- code/modules/http-server/src/main/java/org/glassfish/grizzly/http/server/HttpHandler.java (revision 5930) +++ code/modules/http-server/src/main/java/org/glassfish/grizzly/http/server/HttpHandler.java (revision ) @@ -44,6 +44,7 @@ import org.glassfish.grizzly.http.HttpRequestPacket; import org.glassfish.grizzly.http.server.io.OutputBuffer; import org.glassfish.grizzly.http.server.util.HtmlHelper; +import org.glassfish.grizzly.http.server.util.Mapper; import org.glassfish.grizzly.http.util.HttpStatus; import java.io.CharConversionException; @@ -93,6 +94,13 @@ private boolean allowCustomStatusMessage = true; /** + * Internal {@link Mapper} used to Map request to their associated {@link HttpHandler} + */ + protected Mapper mapper; + + private String name; + + /** * Create HttpHandler. */ public HttpHandler() { @@ -316,4 +324,20 @@ protected static void updateContextPath(final Request request, final String contextPath) { request.setContextPath(contextPath); } + + public Mapper getMapper() { + return mapper; -} + } + + protected void setMapper( Mapper mapper ) { + this.mapper = mapper; + } + + public String getName() { + return name; + } + + public void setName( String name ) { + this.name = name; + } +} Index: code/modules/http-servlet/src/main/java/org/glassfish/grizzly/servlet/DispatchedHttpServletResponse.java =================================================================== --- code/modules/http-servlet/src/main/java/org/glassfish/grizzly/servlet/DispatchedHttpServletResponse.java (revision ) +++ code/modules/http-servlet/src/main/java/org/glassfish/grizzly/servlet/DispatchedHttpServletResponse.java (revision ) @@ -0,0 +1,223 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2008-2011 Oracle and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html + * or packager/legal/LICENSE.txt. See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at packager/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * Oracle designates this particular file as subject to the "Classpath" + * exception as provided by Oracle in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + * + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.glassfish.grizzly.servlet; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; +import java.io.IOException; +import java.util.Locale; + +/** + * Wrapper around a javax.servlet.http.HttpServletResponse + * that transforms an application response object (which might be the original + * one passed to a servlet. + * + * @author Bongjae Chang + */ +public class DispatchedHttpServletResponse extends HttpServletResponseWrapper { + + /** + * Is this wrapped response the subject of an include() + * call? + */ + private boolean included = false; + + public DispatchedHttpServletResponse( HttpServletResponse response, boolean included ) { + super( response ); + this.included = included; + setResponse( response ); + } + + /** + * Set the response that we are wrapping. + * + * @param response The new wrapped response + */ + private void setResponse( HttpServletResponse response ) { + super.setResponse( response ); + } + + @Override + public void setContentLength( int len ) { + if( included ) + return; + super.setContentLength( len ); + } + + @Override + public void setContentType( String type ) { + if( included ) + return; + super.setContentType( type ); + } + + @Override + public void setBufferSize( int size ) { + if( included ) + return; + super.setBufferSize( size ); + } + + @Override + public void reset() { + if( included ) + return; + super.reset(); + } + + @Override + public void setLocale( Locale loc ) { + if( included ) + return; + super.setLocale( loc ); + } + + @Override + public void addCookie( Cookie cookie ) { + if( included ) + return; + super.addCookie( cookie ); + } + + @Override + public void sendError( int sc, String msg ) + throws IOException { + if( included ) + return; + super.sendError( sc, msg ); + } + + @Override + public void sendError( int sc ) + throws IOException { + if( included ) + return; + super.sendError( sc ); + } + + public void sendRedirect( String location ) + throws IOException { + if( included ) + return; + super.sendRedirect( location ); + } + + @Override + public void setDateHeader( String name, long date ) { + if( included ) + return; + super.setDateHeader( name, date ); + } + + @Override + public void addDateHeader( String name, long date ) { + if( included ) + return; + super.addDateHeader( name, date ); + } + + @Override + public void setHeader( String name, String value ) { + if( included ) + return; + super.setHeader( name, value ); + } + + @Override + public void addHeader( String name, String value ) { + if( included ) + return; + super.addHeader( name, value ); + } + + @Override + public void setIntHeader( String name, int value ) { + if( included ) + return; + super.setIntHeader( name, value ); + } + + @Override + public void addIntHeader( String name, int value ) { + if( included ) + return; + super.addIntHeader( name, value ); + } + + @Override + public void setStatus( int sc ) { + if( included ) + return; + super.setStatus( sc ); + } + + @Override + public void setStatus( int sc, String sm ) { + if( included ) + return; + super.setStatus( sc, sm ); + } + + @Override + public void setCharacterEncoding( String charEnc ) { + if( included ) + return; + super.setCharacterEncoding( charEnc ); + } +} Index: code/modules/http-server/src/main/java/org/glassfish/grizzly/http/server/util/Mapper.java =================================================================== --- code/modules/http-server/src/main/java/org/glassfish/grizzly/http/server/util/Mapper.java (revision 6232) +++ code/modules/http-server/src/main/java/org/glassfish/grizzly/http/server/util/Mapper.java (revision ) @@ -538,6 +538,10 @@ newWrapper.object = wrapper; newWrapper.jspWildCard = jspWildCard; newWrapper.servletName = servletName; + if( servletName != null ) { + // todo handling for removing this wrapper. + context.servletNameWrapperMap.put( servletName, wrapper ); + } if (path.endsWith("/*")) { // Wildcard wrapper newWrapper.name = path.substring(0, path.length() - 2); @@ -845,16 +849,23 @@ */ public void map(final DataChunk host, final DataChunk uri, final MappingData mappingData) throws Exception { + map( host, uri, null, mappingData ); + } + public void map(final DataChunk host, final DataChunk uri, final DataChunk servletName, + final MappingData mappingData) throws Exception { if (host.isNull()) { host.getCharChunk().append(defaultHostName); } - host.toChars(DEFAULT_CHARSET); uri.toChars(null); - internalMap(host.getCharChunk(), uri.getCharChunk(), mappingData); - + CharChunk servletNameCC = null; + if( servletName != null ) { + servletName.toChars(null); + servletNameCC = servletName.getCharChunk(); - } + } + internalMap(host.getCharChunk(), uri.getCharChunk(), servletNameCC, mappingData); + } /** @@ -879,11 +890,14 @@ // -------------------------------------------------------- Private Methods + private final void internalMap(CharChunk host, CharChunk uri, MappingData mappingData) throws Exception { + internalMap( host, uri, null, mappingData ); + } + /** * Map the specified URI. */ - private final void internalMap(CharChunk host, CharChunk uri, - MappingData mappingData) + private final void internalMap(CharChunk host, CharChunk uri, CharChunk servletName, MappingData mappingData) throws Exception { uri.setLimit(-1); @@ -993,8 +1007,12 @@ // Wrapper mapping if (ctx != null && mappingData.wrapper == null) { + if( servletName == null ) { - internalMapWrapper(ctx, uri, mappingData); + internalMapWrapper(ctx, uri, mappingData); + } else { + mappingData.wrapper = ctx.servletNameWrapperMap.get( servletName.toString() ); - } + } + } } @@ -1829,6 +1847,7 @@ public Wrapper[] wildcardWrappers = new Wrapper[0]; public Wrapper[] extensionWrappers = new Wrapper[0]; public int nesting = 0; + public HashMap servletNameWrapperMap = new HashMap(); } Index: code/modules/http-servlet/src/main/java/org/glassfish/grizzly/servlet/DispatchedHttpServletRequest.java =================================================================== --- code/modules/http-servlet/src/main/java/org/glassfish/grizzly/servlet/DispatchedHttpServletRequest.java (revision ) +++ code/modules/http-servlet/src/main/java/org/glassfish/grizzly/servlet/DispatchedHttpServletRequest.java (revision ) @@ -0,0 +1,529 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2008-2011 Oracle and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html + * or packager/legal/LICENSE.txt. See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at packager/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * Oracle designates this particular file as subject to the "Classpath" + * exception as provided by Oracle in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + * + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.glassfish.grizzly.servlet; + +import org.glassfish.grizzly.http.server.util.Enumerator; +import org.glassfish.grizzly.http.server.util.Globals; +import org.glassfish.grizzly.http.server.util.ParameterMap; +import org.glassfish.grizzly.http.util.Charsets; +import org.glassfish.grizzly.http.util.DataChunk; +import org.glassfish.grizzly.http.util.Parameters; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.nio.charset.Charset; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + * Wrapper around a javax.servlet.http.HttpServletRequest + * that transforms an application request object (which might be the original + * one passed to a servlet. + * + * @author Bongjae Chang + */ +public class DispatchedHttpServletRequest extends HttpServletRequestWrapper { + + private String contextPath; + private String requestURI; + private String servletPath; + private String pathInfo; + private String queryString; + private HashMap specialAttributes; + private DispatcherConstants.DispatcherType dispatcherType; + private Object requestDispatcherPath; + + /** + * The set of attribute names that are special for request dispatchers + */ + private static final HashSet specials = new HashSet( 15 ); + + static { + specials.add( DispatcherConstants.INCLUDE_REQUEST_URI ); + specials.add( DispatcherConstants.INCLUDE_CONTEXT_PATH ); + specials.add( DispatcherConstants.INCLUDE_SERVLET_PATH ); + specials.add( DispatcherConstants.INCLUDE_PATH_INFO ); + specials.add( DispatcherConstants.INCLUDE_QUERY_STRING ); + specials.add( DispatcherConstants.FORWARD_REQUEST_URI ); + specials.add( DispatcherConstants.FORWARD_CONTEXT_PATH ); + specials.add( DispatcherConstants.FORWARD_SERVLET_PATH ); + specials.add( DispatcherConstants.FORWARD_PATH_INFO ); + specials.add( DispatcherConstants.FORWARD_QUERY_STRING ); + specials.add( DispatcherConstants.ASYNC_REQUEST_URI ); + specials.add( DispatcherConstants.ASYNC_CONTEXT_PATH ); + specials.add( DispatcherConstants.ASYNC_SERVLET_PATH ); + specials.add( DispatcherConstants.ASYNC_PATH_INFO ); + specials.add( DispatcherConstants.ASYNC_QUERY_STRING ); + } + + /** + * Hash map used in the getParametersMap method. + */ + private final ParameterMap parameterMap = new ParameterMap(); + private final Parameters mergedParameters = new Parameters(); + + /** + * Have the parameters for this request already been parsed? + */ + private boolean parsedParams = false; + + public DispatchedHttpServletRequest( HttpServletRequest request, DispatcherConstants.DispatcherType dispatcherType ) { + super( request ); + this.dispatcherType = dispatcherType; + setRequest( request ); + } + + /** + * Set the request that we are wrapping. + * + * @param request The new wrapped request + */ + private void setRequest( HttpServletRequest request ) { + super.setRequest( request ); + // Initialize the attributes for this request + requestDispatcherPath = request.getAttribute( Globals.DISPATCHER_REQUEST_PATH_ATTR ); + // Initialize the path elements for this request + contextPath = request.getContextPath(); + requestURI = request.getRequestURI(); + servletPath = request.getServletPath(); + pathInfo = request.getPathInfo(); + queryString = request.getQueryString(); + } + + @Override + public String getContextPath() { + return contextPath; + } + + public void setContextPath( String contextPath ) { + this.contextPath = contextPath; + } + + @Override + public String getRequestURI() { + return requestURI; + } + + public void setRequestURI( String requestURI ) { + this.requestURI = requestURI; + } + + @Override + public String getServletPath() { + return servletPath; + } + + public void setServletPath( String servletPath ) { + this.servletPath = servletPath; + } + + @Override + public String getPathInfo() { + return pathInfo; + } + + public void setPathInfo( String pathInfo ) { + this.pathInfo = pathInfo; + } + + @Override + public String getQueryString() { + return queryString; + } + + public void setQueryString( String queryString ) { + this.queryString = queryString; + } + + @SuppressWarnings( "unchecked" ) + @Override + public String getParameter( String name ) { + if( !parsedParams ) + parseParameters(); + if( System.getSecurityManager() != null ) { + return (String)AccessController.doPrivileged( + new GetParameterPrivilegedAction( name ) ); + } else { + return mergedParameters.getParameter( name ); + } + } + + @SuppressWarnings( "unchecked" ) + @Override + public Enumeration getParameterNames() { + if( !parsedParams ) + parseParameters(); + if( System.getSecurityManager() != null ) { + return new Enumerator( (Set)AccessController.doPrivileged( + new GetParameterNamesPrivilegedAction() ) ); + } else { + return new Enumerator( mergedParameters.getParameterNames() ); + } + } + + @SuppressWarnings( "unchecked" ) + @Override + public String[] getParameterValues( String name ) { + if( !parsedParams ) + parseParameters(); + String[] ret; + /* + * Clone the returned array only if there is a security manager + * in place, so that performance won't suffer in the nonsecure case + */ + if( System.getSecurityManager() != null ) { + ret = (String[])AccessController.doPrivileged( + new GetParameterValuePrivilegedAction( name ) ); + if( ret != null ) { + ret = ret.clone(); + } + } else { + ret = mergedParameters.getParameterValues( name ); + } + return ret; + } + + @SuppressWarnings( "unchecked" ) + @Override + public Map getParameterMap() { + if( !parsedParams ) + parseParameters(); + if( System.getSecurityManager() != null ) { + return (Map)AccessController.doPrivileged( + new GetParameterMapPrivilegedAction() ); + } else { + return getParameterMapInternal(); + } + } + + private ParameterMap getParameterMapInternal() { + if( parameterMap.isLocked() ) + return parameterMap; + for( final String name : mergedParameters.getParameterNames() ) { + final String[] values = mergedParameters.getParameterValues( name ); + parameterMap.put( name, values ); + } + parameterMap.setLocked( true ); + return parameterMap; + } + + /** + * Parses the parameters of this request. + *

+ * If parameters are present in both the query string and the request + * content, they are merged. + */ + @SuppressWarnings( "unchecked" ) + private void parseParameters() { + if( parsedParams ) { + return; + } + final String enc = getCharacterEncoding(); + Charset charset; + if( enc != null ) { + try { + charset = Charsets.lookupCharset( enc ); + } catch( Exception e ) { + charset = Charsets.DEFAULT_CHARSET; + } + } else { + charset = Charsets.DEFAULT_CHARSET; + } + mergedParameters.setEncoding( charset ); + mergedParameters.setQueryStringEncoding( charset ); + + DataChunk queryDC = DataChunk.newInstance(); + queryDC.setString( queryString ); + mergedParameters.setQuery( queryDC ); + mergedParameters.handleQueryParameters(); + + Map paramMap = getRequest().getParameterMap(); + for( final Map.Entry entry : paramMap.entrySet() ) { + mergedParameters.addParameterValues( entry.getKey(), entry.getValue() ); + } + parsedParams = true; + } + + @Override + public Object getAttribute( String name ) { + if( name.equals( Globals.DISPATCHER_REQUEST_PATH_ATTR ) ) { + return requestDispatcherPath != null ? requestDispatcherPath.toString() : null; + } + + if( !isSpecial( name ) ) { + return getRequest().getAttribute( name ); + } else { + Object value = null; + if( specialAttributes != null ) { + value = specialAttributes.get( name ); + } + if( value == null && name.startsWith( "javax.servlet.forward" ) ) { + /* + * If it's a forward special attribute, and null, delegate + * to the wrapped request. This will allow access to the + * forward special attributes from a request that was first + * forwarded and then included, or forwarded multiple times + * in a row. + * Notice that forward special attributes are set only on + * the wrapper that was created for the initial forward + * (i.e., the top-most wrapper for a request that was + * forwarded multiple times in a row, and never included, + * will not contain any specialAttributes!). + * This is different from an include, where the special + * include attributes are set on every include wrapper. + */ + value = getRequest().getAttribute( name ); + } + return value; + } + } + + @Override + public Enumeration getAttributeNames() { + return new AttributeNamesEnumerator(); + } + + @SuppressWarnings( "unchecked" ) + @Override + public void setAttribute( String name, Object value ) { + if( name.equals( Globals.DISPATCHER_REQUEST_PATH_ATTR ) ) { + requestDispatcherPath = value; + return; + } + + if( isSpecial( name ) ) { + if( specialAttributes != null ) { + specialAttributes.put( name, value ); + } + } else { + getRequest().setAttribute( name, value ); + } + } + + @Override + public void removeAttribute( String name ) { + if( isSpecial( name ) ) { + if( specialAttributes != null ) { + specialAttributes.remove( name ); + } + } else { + getRequest().removeAttribute( name ); + } + } + + private boolean isSpecial( String name ) { + return specials.contains( name ); + } + + /** + * Initializes the special attributes of this request wrapper. + * + * @param requestUri The request URI + * @param contextPath The context path + * @param servletPath The servlet path + * @param pathInfo The path info + * @param queryString The query string + */ + @SuppressWarnings( "unchecked" ) + void initSpecialAttributes( String requestUri, + String contextPath, + String servletPath, + String pathInfo, + String queryString ) { + specialAttributes = new HashMap( 5 ); + + switch( dispatcherType ) { + case INCLUDE: + specialAttributes.put( DispatcherConstants.INCLUDE_REQUEST_URI, requestUri ); + specialAttributes.put( DispatcherConstants.INCLUDE_CONTEXT_PATH, contextPath ); + specialAttributes.put( DispatcherConstants.INCLUDE_SERVLET_PATH, servletPath ); + specialAttributes.put( DispatcherConstants.INCLUDE_PATH_INFO, pathInfo ); + specialAttributes.put( DispatcherConstants.INCLUDE_QUERY_STRING, queryString ); + break; + case FORWARD: + case ERROR: + specialAttributes.put( DispatcherConstants.FORWARD_REQUEST_URI, requestUri ); + specialAttributes.put( DispatcherConstants.FORWARD_CONTEXT_PATH, contextPath ); + specialAttributes.put( DispatcherConstants.FORWARD_SERVLET_PATH, servletPath ); + specialAttributes.put( DispatcherConstants.FORWARD_PATH_INFO, pathInfo ); + specialAttributes.put( DispatcherConstants.FORWARD_QUERY_STRING, queryString ); + break; + case ASYNC: + specialAttributes.put( DispatcherConstants.ASYNC_REQUEST_URI, requestUri ); + specialAttributes.put( DispatcherConstants.ASYNC_CONTEXT_PATH, contextPath ); + specialAttributes.put( DispatcherConstants.ASYNC_SERVLET_PATH, servletPath ); + specialAttributes.put( DispatcherConstants.ASYNC_PATH_INFO, pathInfo ); + specialAttributes.put( DispatcherConstants.ASYNC_QUERY_STRING, queryString ); + break; + } + } + + public HttpServletRequestImpl getRequestFacade() { + if( getRequest() instanceof HttpServletRequestImpl ) { + return (HttpServletRequestImpl)getRequest(); + } else { + return ( (DispatchedHttpServletRequest)getRequest() ).getRequestFacade(); + } + } + + public void recycle() { + parameterMap.setLocked( false ); + parameterMap.clear(); + } + + /** + * Utility class used to expose the special attributes as being available + * as request attributes. + */ + private final class AttributeNamesEnumerator implements Enumeration { + + protected Enumeration parentEnumeration = null; + protected String next = null; + private Iterator specialNames = null; + + @SuppressWarnings( "unchecked" ) + public AttributeNamesEnumerator() { + parentEnumeration = getRequest().getAttributeNames(); + if( specialAttributes != null ) { + specialNames = specialAttributes.keySet().iterator(); + } + } + + public boolean hasMoreElements() { + return ( specialNames != null && specialNames.hasNext() ) + || ( next != null ) + || ( ( next = findNext() ) != null ); + } + + public Object nextElement() { + + if( specialNames != null && specialNames.hasNext() ) { + return specialNames.next(); + } + + String result = next; + if( next != null ) { + next = findNext(); + } else { + throw new NoSuchElementException(); + } + return result; + } + + protected String findNext() { + String result = null; + while( ( result == null ) && ( parentEnumeration.hasMoreElements() ) ) { + String current = parentEnumeration.nextElement(); + if( !isSpecial( current ) || + ( !dispatcherType.equals( DispatcherConstants.DispatcherType.FORWARD ) && + current.startsWith( "javax.servlet.forward" ) && + getAttribute( current ) != null ) ) { + result = current; + } + } + return result; + } + } + + private final class GetParameterPrivilegedAction implements PrivilegedAction { + public final String name; + + public GetParameterPrivilegedAction( String name ) { + this.name = name; + } + + @Override + public Object run() { + return mergedParameters.getParameter( name ); + } + } + + private final class GetParameterNamesPrivilegedAction implements PrivilegedAction { + @Override + public Object run() { + return mergedParameters.getParameterNames(); + } + } + + private final class GetParameterValuePrivilegedAction implements PrivilegedAction { + public final String name; + + public GetParameterValuePrivilegedAction( String name ) { + this.name = name; + } + + @Override + public Object run() { + return mergedParameters.getParameterValues( name ); + } + } + + private final class GetParameterMapPrivilegedAction implements PrivilegedAction { + @Override + public Object run() { + return getParameterMapInternal(); + } + } +} Index: code/modules/http-servlet/src/test/java/org/glassfish/grizzly/servlet/HttpServerAbstractTest.java =================================================================== --- code/modules/http-servlet/src/test/java/org/glassfish/grizzly/servlet/HttpServerAbstractTest.java (revision 5545) +++ code/modules/http-servlet/src/test/java/org/glassfish/grizzly/servlet/HttpServerAbstractTest.java (revision ) @@ -47,6 +47,7 @@ import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; +import java.util.logging.Level; import org.glassfish.grizzly.http.server.HttpHandler; import org.glassfish.grizzly.http.server.HttpServer; @@ -65,6 +66,16 @@ return reader.readLine(); } + protected StringBuilder readMultilineResponse(HttpURLConnection conn) throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); + StringBuilder sb = new StringBuilder(); + String line = null; + while((line = reader.readLine())!=null){ + sb.append(line).append("\n"); + } + return sb; + } + protected HttpURLConnection getConnection(String alias, int port) throws IOException { URL url = new URL("http", "localhost", port, alias); HttpURLConnection urlConn = (HttpURLConnection) url.openConnection(); Index: code/modules/http-servlet/src/main/java/org/glassfish/grizzly/servlet/RequestDispatcherImpl.java =================================================================== --- code/modules/http-servlet/src/main/java/org/glassfish/grizzly/servlet/RequestDispatcherImpl.java (revision ) +++ code/modules/http-servlet/src/main/java/org/glassfish/grizzly/servlet/RequestDispatcherImpl.java (revision ) @@ -0,0 +1,746 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2008-2011 Oracle and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html + * or packager/legal/LICENSE.txt. See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at packager/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * Oracle designates this particular file as subject to the "Classpath" + * exception as provided by Oracle in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + * + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.glassfish.grizzly.servlet; + +import org.glassfish.grizzly.Grizzly; +import org.glassfish.grizzly.http.server.util.Globals; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.logging.*; +import java.security.AccessController; +import java.security.PrivilegedExceptionAction; +import java.security.PrivilegedActionException; +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Standard implementation of RequestDispatcher that allows a + * request to be forwarded to a different resource to create the ultimate + * response, or to include the output of another resource in the response + * from this resource. This implementation allows application level servlets + * to wrap the request and/or response objects that are passed on to the + * called resource, as long as the wrapping classes extend + * javax.servlet.ServletRequestWrapper and + * javax.servlet.ServletResponseWrapper. + * + * @author Craig R. McClanahan + * @author Bongjae Chang + */ +public final class RequestDispatcherImpl implements RequestDispatcher { + + private final static Logger logger = Grizzly.logger( RequestDispatcherImpl.class ); + + /** + * is this dispatch cross context + */ + private Boolean crossContextFlag = null; + + /** + * The servlet name for a named dispatcher. + */ + private String name = null; + + /** + * The request URI for this RequestDispatcher. + */ + private String requestURI = null; + + /** + * The servlet path for this RequestDispatcher. + */ + private String servletPath = null; + + /** + * The extra path information for this RequestDispatcher. + */ + private String pathInfo = null; + + /** + * The query string parameters for this RequestDispatcher. + */ + private String queryString = null; + + /** + * The Wrapper associated with the resource that will be forwarded to + * or included. + */ + private ServletHandler wrapper = null; + + /** + * Construct a new instance of this class, configured according to the + * specified parameters. If both servletPath and pathInfo are + * null, it will be assumed that this RequestDispatcher + * was acquired by name, rather than by path. + * + * @param wrapper The Wrapper associated with the resource that will + * be forwarded to or included (required) + * @param requestURI The request URI to this resource (if any) + * @param servletPath The revised servlet path to this resource (if any) + * @param pathInfo The revised extra path information to this resource + * (if any) + * @param queryString Query string parameters included with this request + * (if any) + * @param name Servlet name (if a named dispatcher was created) + * else null + */ + public RequestDispatcherImpl( ServletHandler wrapper, + String requestURI, + String servletPath, + String pathInfo, + String queryString, + String name ) { + // Save all of our configuration parameters + this.wrapper = wrapper; + this.requestURI = requestURI; + this.servletPath = servletPath; + this.pathInfo = pathInfo; + this.queryString = queryString; + this.name = name; + + if( logger.isLoggable( Level.FINE ) ) + logger.fine( "servletPath=" + this.servletPath + ", pathInfo=" + + this.pathInfo + ", queryString=" + queryString + + ", name=" + this.name ); + } + + /** + * Forwards the given request and response to the resource + * for which this dispatcher was acquired. + *

+ *

Any runtime exceptions, IOException, or ServletException thrown + * by the target will be propogated to the caller. + * + * @param request The request to be forwarded + * @param response The response to be forwarded + * + * @throws IOException if an input/output error occurs + * @throws ServletException if a servlet exception occurs + */ + public void forward( ServletRequest request, ServletResponse response ) + throws ServletException, IOException { + dispatch( request, response, DispatcherConstants.DispatcherType.FORWARD ); + } + + /** + * Dispatches the given request and response to the resource + * for which this dispatcher was acquired. + *

+ *

Any runtime exceptions, IOException, or ServletException thrown + * by the target will be propogated to the caller. + * + * @param request The request to be forwarded + * @param response The response to be forwarded + * @param dispatcherType The type of dispatch to be performed + * + * @throws IOException if an input/output error occurs + * @throws ServletException if a servlet exception occurs + * @throws IllegalArgumentException if the dispatcher type is different + * from FORWARD, ERROR, and ASYNC + */ + @SuppressWarnings( "unchecked" ) + public void dispatch( ServletRequest request, ServletResponse response, DispatcherConstants.DispatcherType dispatcherType ) + throws ServletException, IOException { + if( !DispatcherConstants.DispatcherType.FORWARD.equals( dispatcherType ) && + !DispatcherConstants.DispatcherType.ERROR.equals( dispatcherType ) && + !DispatcherConstants.DispatcherType.ASYNC.equals( dispatcherType ) ) { + throw new IllegalArgumentException( "Illegal dispatcher type" ); + } + + boolean isCommit = ( DispatcherConstants.DispatcherType.FORWARD.equals( dispatcherType ) || + DispatcherConstants.DispatcherType.ERROR.equals( dispatcherType ) ); + + if( System.getSecurityManager() != null ) { + try { + PrivilegedDispatch dp = new PrivilegedDispatch( + request, response, dispatcherType ); + AccessController.doPrivileged( dp ); + // START SJSAS 6374990 + if( isCommit ) { + closeResponse( response ); + } + // END SJSAS 6374990 + } catch( PrivilegedActionException pe ) { + Exception e = pe.getException(); + if( e instanceof ServletException ) + throw (ServletException)e; + throw (IOException)e; + } + } else { + doDispatch( request, response, dispatcherType ); + // START SJSAS 6374990 + if( isCommit ) { + closeResponse( response ); + } + // END SJSAS 6374990 + } + } + + private void doDispatch( ServletRequest request, ServletResponse response, DispatcherConstants.DispatcherType dispatcherType ) + throws ServletException, IOException { + if( !DispatcherConstants.DispatcherType.ASYNC.equals( dispatcherType ) ) { + // Reset any output that has been buffered, but keep + // headers/cookies + if( response.isCommitted() ) { + if( logger.isLoggable( Level.FINE ) ) + logger.fine( " Forward on committed response --> ISE" ); + throw new IllegalStateException( "Cannot forward after response has been committed" ); + } + + try { + response.resetBuffer(); + } catch( IllegalStateException e ) { + if( logger.isLoggable( Level.FINE ) ) + logger.fine( " Forward resetBuffer() returned ISE: " + e ); + throw e; + } + } + + // Set up to handle the specified request and response + State state = new State( request, response, dispatcherType ); + + // Identify the HTTP-specific request and response objects (if any) + HttpServletRequest hrequest = null; + if( request instanceof HttpServletRequest ) { + hrequest = (HttpServletRequest)request; + } + HttpServletResponse hresponse = null; + if( response instanceof HttpServletResponse ) { + hresponse = (HttpServletResponse)response; + } + + if( ( hrequest == null ) || ( hresponse == null ) ) { + // Handle a non-HTTP forward + DispatchedHttpServletRequest wrequest = wrapRequest( state ); + processRequest( request, response, state, + wrequest.getRequestFacade() ); + unwrapRequest( state ); + wrequest.recycle(); + } else if( ( servletPath == null ) && ( pathInfo == null ) ) { + // Handle an HTTP named dispatcher forward + DispatchedHttpServletRequest wrequest = wrapRequest( state ); + wrequest.setContextPath( hrequest.getContextPath() ); + wrequest.setRequestURI( hrequest.getRequestURI() ); + wrequest.setServletPath( hrequest.getServletPath() ); + wrequest.setPathInfo( hrequest.getPathInfo() ); + wrequest.setQueryString( hrequest.getQueryString() ); + processRequest( request, response, state, wrequest.getRequestFacade() ); + unwrapRequest( state ); + wrequest.recycle(); + } else { + // Handle an HTTP path-based forward + DispatchedHttpServletRequest wrequest = wrapRequest( state ); + + // If the request is being FORWARD- or ASYNC-dispatched for + // the first time, initialize it with the required request + // attributes + if( ( DispatcherConstants.DispatcherType.FORWARD.equals( dispatcherType ) && + hrequest.getAttribute( DispatcherConstants.FORWARD_REQUEST_URI ) == null ) || + ( DispatcherConstants.DispatcherType.ASYNC.equals( dispatcherType ) && + hrequest.getAttribute( DispatcherConstants.ASYNC_REQUEST_URI ) == null ) ) { + wrequest.initSpecialAttributes( hrequest.getRequestURI(), + hrequest.getContextPath(), + hrequest.getServletPath(), + hrequest.getPathInfo(), + hrequest.getQueryString() ); + } + + String targetContextPath = wrapper.getContextPath(); + // START IT 10395 + HttpServletRequestImpl requestFacade = wrequest.getRequestFacade(); + String originContextPath = requestFacade.getContextPath(); + if( originContextPath != null && + originContextPath.equals( targetContextPath ) ) { + targetContextPath = hrequest.getContextPath(); + } + // END IT 10395 + wrequest.setContextPath( targetContextPath ); + wrequest.setRequestURI( requestURI ); + wrequest.setServletPath( servletPath ); + wrequest.setPathInfo( pathInfo ); + if( queryString != null ) { + wrequest.setQueryString( queryString ); + } + + processRequest( request, response, state, wrequest.getRequestFacade() ); + + unwrapRequest( state ); + wrequest.recycle(); + } + } + + + /** + * Prepare the request based on the filter configuration. + * + * @param request The servlet request we are processing + * @param response The servlet response we are creating + * + * @throws IOException if an input/output error occurs + * @throws ServletException if a servlet error occurs + */ + private void processRequest( ServletRequest request, + ServletResponse response, + State state, + HttpServletRequestImpl requestFacade ) + throws IOException, ServletException { + if( request != null ) { + if( state.dispatcherType != DispatcherConstants.DispatcherType.ERROR ) { + state.outerRequest.setAttribute( Globals.DISPATCHER_REQUEST_PATH_ATTR, getCombinedPath() ); + invoke( state.outerRequest, response, requestFacade ); + } else { + invoke( state.outerRequest, response, requestFacade ); + } + } + } + + /** + * Combines the servletPath and the pathInfo. + *

+ * If pathInfo is null, it is ignored. If servletPath + * is null, then null is returned. + * + * @return The combined path with pathInfo appended to servletInfo + */ + private String getCombinedPath() { + if( servletPath == null ) { + return null; + } + if( pathInfo == null ) { + return servletPath; + } + return servletPath + pathInfo; + } + + /** + * Include the response from another resource in the current response. + * Any runtime exception, IOException, or ServletException thrown by the + * called servlet will be propogated to the caller. + * + * @param request The servlet request that is including this one + * @param response The servlet response to be appended to + * + * @throws IOException if an input/output error occurs + * @throws ServletException if a servlet exception occurs + */ + @SuppressWarnings( "unchecked" ) + public void include( ServletRequest request, ServletResponse response ) + throws ServletException, IOException { + if( System.getSecurityManager() != null ) { + try { + PrivilegedInclude dp = new PrivilegedInclude( request, response ); + AccessController.doPrivileged( dp ); + } catch( PrivilegedActionException pe ) { + Exception e = pe.getException(); + if( e instanceof ServletException ) + throw (ServletException)e; + throw (IOException)e; + } + } else { + doInclude( request, response ); + } + } + + private void doInclude( ServletRequest request, ServletResponse response ) + throws ServletException, IOException { + + // Set up to handle the specified request and response + State state = new State( request, response, DispatcherConstants.DispatcherType.INCLUDE ); + + // Create a wrapped response to use for this request + wrapResponse( state ); + + // START GlassFish 6386229 + // Handle an HTTP named dispatcher include + if( name != null ) { + // END GlassFish 6386229 + DispatchedHttpServletRequest wrequest = wrapRequest( state ); + wrequest.setAttribute( Globals.NAMED_DISPATCHER_ATTR, name ); + wrequest.setAttribute( Globals.DISPATCHER_REQUEST_PATH_ATTR, getCombinedPath() ); + try { + invoke( state.outerRequest, state.outerResponse, wrequest.getRequestFacade() ); + } finally { + unwrapRequest( state ); + unwrapResponse( state ); + } + } + // Handle an HTTP path based include + else { + DispatchedHttpServletRequest wrequest = wrapRequest( state ); + wrequest.initSpecialAttributes( requestURI, + wrapper.getContextPath(), + servletPath, + pathInfo, + queryString ); + if( queryString != null ) { + wrequest.setQueryString( queryString ); + } + wrequest.setAttribute( Globals.DISPATCHER_REQUEST_PATH_ATTR,getCombinedPath() ); + try { + invoke( state.outerRequest, state.outerResponse, wrequest.getRequestFacade() ); + } finally { + unwrapRequest( state ); + unwrapResponse( state ); + } + } + } + + /** + * Ask the resource represented by this RequestDispatcher to process + * the associated request, and create (or append to) the associated + * response. + *

+ * IMPLEMENTATION NOTE: This implementation assumes + * that no filters are applied to a forwarded or included resource, + * because they were already done for the original request. + * + * @param request The servlet request we are processing + * @param response The servlet response we are creating + * + * @throws IOException if an input/output error occurs + * @throws ServletException if a servlet error occurs + */ + private void invoke( ServletRequest request, ServletResponse response, HttpServletRequestImpl requestFacade ) + throws IOException, ServletException { + boolean crossContext = false; + if( crossContextFlag != null && crossContextFlag.booleanValue() ) { + crossContext = true; + } + try { + doInvoke( request, response, crossContext, requestFacade ); + } finally { + crossContextFlag = null; + } + } + + /** + * Ask the resource represented by this RequestDispatcher to process + * the associated request, and create (or append to) the associated + * response. + *

+ * IMPLEMENTATION NOTE: This implementation assumes + * that no filters are applied to a forwarded or included resource, + * because they were already done for the original request. + * + * @param request The servlet request we are processing + * @param response The servlet response we are creating + * + * @throws IOException if an input/output error occurs + * @throws ServletException if a servlet error occurs + */ + private void doInvoke( ServletRequest request, ServletResponse response, boolean crossContext, HttpServletRequestImpl requestFacade ) + throws IOException, ServletException { + ClassLoader oldCCL = null; + try { + // Checking to see if the context classloader is the current context + // classloader. If it's not, we're saving it, and setting the context + // classloader to the Context classloader + if( crossContext ) { + ClassLoader contextClassLoader = wrapper.getClassLoader(); + if( contextClassLoader != null ) { + oldCCL = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader( contextClassLoader ); + } + } + wrapper.doServletService( request, response ); + } finally { + // Reset the old context class loader + if( oldCCL != null ) + Thread.currentThread().setContextClassLoader( oldCCL ); + } + } + + /** + * Unwrap the request if we have wrapped it. + */ + private void unwrapRequest( State state ) { + if( state.wrapRequest == null ) + return; + + ServletRequest previous = null; + ServletRequest current = state.outerRequest; + + while( current != null ) { + + // If we run into the container request we are done + if( current instanceof HttpServletRequestImpl ) + break; + + // Remove the current request if it is our wrapper + if( current == state.wrapRequest ) { + ServletRequest next = + ( (ServletRequestWrapper)current ).getRequest(); + if( previous == null ) + state.outerRequest = next; + else + ( (ServletRequestWrapper)previous ).setRequest( next ); + break; + } + + // Advance to the next request in the chain + previous = current; + current = ( (ServletRequestWrapper)current ).getRequest(); + } + } + + /** + * Unwrap the response if we have wrapped it. + */ + private void unwrapResponse( State state ) { + + if( state.wrapResponse == null ) + return; + + ServletResponse previous = null; + ServletResponse current = state.outerResponse; + + while( current != null ) { + + // If we run into the container response we are done + if( current instanceof HttpServletResponseImpl ) + break; + + // Remove the current response if it is our wrapper + if( current == state.wrapResponse ) { + ServletResponse next = + ( (ServletResponseWrapper)current ).getResponse(); + if( previous == null ) + state.outerResponse = next; + else + ( (ServletResponseWrapper)previous ).setResponse( next ); + break; + } + + // Advance to the next response in the chain + previous = current; + current = ( (ServletResponseWrapper)current ).getResponse(); + } + } + + /** + * Create and return a request wrapper that has been inserted in the + * appropriate spot in the request chain. + */ + private DispatchedHttpServletRequest wrapRequest( State state ) { + + // Locate the request we should insert in front of + ServletRequest previous = null; + ServletRequest current = state.outerRequest; + + while( current != null ) { + if( !( current instanceof ServletRequestWrapper ) ) { + break; + } + // If we find container-generated wrapper, break out + if( current instanceof DispatchedHttpServletRequest ) { + break; + } + if( current instanceof HttpServletRequestImpl ) { + break; + } + + previous = current; + current = ( (ServletRequestWrapper)current ).getRequest(); + } + if( current == null ) { + return null; + } + + // Compute a crossContext flag + HttpServletRequest hcurrent = (HttpServletRequest)current; + boolean crossContext = !( wrapper.getContextPath().equals( hcurrent.getContextPath() ) ); + //START OF 6364900 + crossContextFlag = Boolean.valueOf( crossContext ); + + // Instantiate a new wrapper and insert it in the chain + DispatchedHttpServletRequest wrapper = new DispatchedHttpServletRequest( hcurrent, state.dispatcherType ); + if( previous == null ) { + state.outerRequest = wrapper; + } else { + ( (ServletRequestWrapper)previous ).setRequest( wrapper ); + } + + state.wrapRequest = wrapper; + + return wrapper; + } + + /** + * Create and return a response wrapper that has been inserted in the + * appropriate spot in the response chain. + */ + private ServletResponse wrapResponse( State state ) { + + // Locate the response we should insert in front of + ServletResponse previous = null; + ServletResponse current = state.outerResponse; + + while( current != null ) { + if( !( current instanceof ServletResponseWrapper ) ) + break; + if( current instanceof DispatchedHttpServletResponse ) + break; + if( current instanceof HttpServletResponseImpl ) + break; + + previous = current; + current = ( (ServletResponseWrapper)current ).getResponse(); + } + + if( current == null ) { + return null; + } + + HttpServletResponse hcurrent = (HttpServletResponse)current; + // Instantiate a new wrapper and insert it in the chain + DispatchedHttpServletResponse wrapper = new DispatchedHttpServletResponse( hcurrent, + DispatcherConstants.DispatcherType.INCLUDE.equals( state.dispatcherType ) ); + if( previous == null ) + state.outerResponse = wrapper; + else + ( (ServletResponseWrapper)previous ).setResponse( wrapper ); + state.wrapResponse = wrapper; + + return wrapper; + } + + private static void closeResponse( ServletResponse response ) { + try { + PrintWriter writer = response.getWriter(); + writer.flush(); + writer.close(); + } catch( IllegalStateException e ) { + try { + ServletOutputStream stream = response.getOutputStream(); + stream.flush(); + stream.close(); + } catch( IllegalStateException f ) { + ; + } catch( IOException f ) { + ; + } + } catch( IOException e ) { + ; + } + } + + private class PrivilegedDispatch implements PrivilegedExceptionAction { + + private ServletRequest request; + private ServletResponse response; + private DispatcherConstants.DispatcherType dispatcherType; + + PrivilegedDispatch( ServletRequest request, ServletResponse response, + DispatcherConstants.DispatcherType dispatcherType ) { + this.request = request; + this.response = response; + this.dispatcherType = dispatcherType; + } + + public Object run() throws java.lang.Exception { + doDispatch( request, response, dispatcherType ); + return null; + } + } + + private class PrivilegedInclude implements PrivilegedExceptionAction { + + private ServletRequest request; + private ServletResponse response; + + PrivilegedInclude( ServletRequest request, ServletResponse response ) { + this.request = request; + this.response = response; + } + + public Object run() throws ServletException, IOException { + doInclude( request, response ); + return null; + } + } + + /** + * Used to pass state when the request dispatcher is used. Using instance + * variables causes threading issues and state is too complex to pass and + * return single ServletRequest or ServletResponse objects. + */ + private static class State { + + // Outermost request that will be passed on to the invoked servlet + ServletRequest outerRequest = null; + + // Outermost response that will be passed on to the invoked servlet. + ServletResponse outerResponse = null; + + // Request wrapper we have created and installed (if any). + ServletRequest wrapRequest = null; + + // Response wrapper we have created and installed (if any). + ServletResponse wrapResponse = null; + + // The type of dispatch we are performing + DispatcherConstants.DispatcherType dispatcherType; + + State( ServletRequest request, ServletResponse response, DispatcherConstants.DispatcherType dispatcherType ) { + this.outerRequest = request; + this.outerResponse = response; + this.dispatcherType = dispatcherType; + } + } +} Index: code/modules/http/src/main/java/org/glassfish/grizzly/http/util/Parameters.java =================================================================== --- code/modules/http/src/main/java/org/glassfish/grizzly/http/util/Parameters.java (revision 6158) +++ code/modules/http/src/main/java/org/glassfish/grizzly/http/util/Parameters.java (revision ) @@ -236,7 +236,7 @@ return currentChild.paramHashStringArray.keys(); */ // START PWC 6057385 - currentChild.paramHashStringArray.keySet(); + return currentChild.paramHashStringArray.keySet(); // END PWC 6057385 } // merge in child Index: code/modules/http-servlet/src/test/java/org/glassfish/grizzly/servlet/RequestDispatcherTest.java =================================================================== --- code/modules/http-servlet/src/test/java/org/glassfish/grizzly/servlet/RequestDispatcherTest.java (revision ) +++ code/modules/http-servlet/src/test/java/org/glassfish/grizzly/servlet/RequestDispatcherTest.java (revision ) @@ -0,0 +1,412 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2009-2011 Oracle and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html + * or packager/legal/LICENSE.txt. See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at packager/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * Oracle designates this particular file as subject to the "Classpath" + * exception as provided by Oracle in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ +package org.glassfish.grizzly.servlet; + +import org.glassfish.grizzly.utils.Utils; + +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.HttpURLConnection; + +/** + * Request/NamedDispatcher Test + * + * @author Bongjae Chang + */ +public class RequestDispatcherTest extends HttpServerAbstractTest { + + private static final int PORT = 18890 + 12; + + public void testForward() throws IOException { + Utils.dumpOut( "testForward" ); + try { + newHttpServer( PORT ); + + String contextPath = "/webapp"; + + ServletHandler servletHandler1 = new ServletHandler(); + servletHandler1.setServletInstance( new HttpServlet() { + @Override + public void doGet( HttpServletRequest request, HttpServletResponse response ) + throws ServletException, IOException { + PrintWriter out = response.getWriter(); + out.println( "Hello, world! I am a servlet1" ); + + // relative path test + RequestDispatcher dispatcher = request.getRequestDispatcher( "servlet2" ); + assertNotNull( dispatcher ); + dispatcher.forward( request, response ); + out.close(); + } + } ); + servletHandler1.setContextPath( contextPath ); + servletHandler1.setServletPath( "/servlet1" ); + addHttpHandler( contextPath + "/servlet1", servletHandler1 ); + + ServletHandler servletHandler2 = new ServletHandler(); + servletHandler2.setServletInstance( new HttpServlet() { + @Override + public void doGet( HttpServletRequest request, HttpServletResponse response ) + throws ServletException, IOException { + PrintWriter out = response.getWriter(); + out.println( "Hello, world! I am a servlet2" ); + out.close(); + } + } ); + servletHandler2.setContextPath( contextPath ); + servletHandler2.setServletPath( "/servlet2" ); + addHttpHandler( contextPath + "/servlet2", servletHandler2 ); + + httpServer.start(); + HttpURLConnection conn = getConnection( "/webapp/servlet1", PORT ); + assertEquals( HttpServletResponse.SC_OK, getResponseCodeFromAlias( conn ) ); + assertEquals( "Hello, world! I am a servlet2", readMultilineResponse( conn ).toString().trim() ); + } finally { + stopHttpServer(); + } + } + + public void testInclude() throws IOException { + Utils.dumpOut( "testInclude" ); + try { + newHttpServer( PORT ); + + String contextPath = "/webapp"; + + ServletHandler servletHandler1 = new ServletHandler(); + servletHandler1.setServletInstance( new HttpServlet() { + @Override + public void doGet( HttpServletRequest request, HttpServletResponse response ) + throws ServletException, IOException { + PrintWriter out = response.getWriter(); + out.println( "Hello, world! I am a servlet1" ); + + // relative path test + RequestDispatcher dispatcher = request.getRequestDispatcher( "servlet2" ); + assertNotNull( dispatcher ); + dispatcher.include( request, response ); + out.close(); + } + } ); + servletHandler1.setContextPath( contextPath ); + servletHandler1.setServletPath( "/servlet1" ); + addHttpHandler( contextPath + "/servlet1", servletHandler1 ); + + ServletHandler servletHandler2 = new ServletHandler(); + servletHandler2.setServletInstance( new HttpServlet() { + @Override + public void doGet( HttpServletRequest request, HttpServletResponse response ) + throws ServletException, IOException { + PrintWriter out = response.getWriter(); + out.println( "Hello, world! I am a servlet2" ); + out.close(); + } + } ); + servletHandler2.setContextPath( contextPath ); + servletHandler2.setServletPath( "/servlet2" ); + addHttpHandler( contextPath + "/servlet2", servletHandler2 ); + + httpServer.start(); + HttpURLConnection conn = getConnection( "/webapp/servlet1", PORT ); + assertEquals( HttpServletResponse.SC_OK, getResponseCodeFromAlias( conn ) ); + assertEquals( "Hello, world! I am a servlet1\nHello, world! I am a servlet2", readMultilineResponse( conn ).toString().trim() ); + } finally { + stopHttpServer(); + } + } + + public void testNamedDispatcherForward() throws IOException { + Utils.dumpOut( "testNamedDispatcherForward" ); + try { + newHttpServer( PORT ); + + String contextPath = "/webapp"; + + ServletHandler servletHandler1 = new ServletHandler(); + servletHandler1.setServletInstance( new HttpServlet() { + @Override + public void doGet( HttpServletRequest request, HttpServletResponse response ) + throws ServletException, IOException { + PrintWriter out = response.getWriter(); + out.println( "Hello, world! I am a servlet1" ); + + ServletContext servletCtx = getServletContext(); + assertNotNull( servletCtx ); + RequestDispatcher dispatcher = servletCtx.getNamedDispatcher( "servlet2" ); + assertNotNull( dispatcher ); + dispatcher.forward( request, response ); + out.close(); + } + } ); + servletHandler1.setContextPath( contextPath ); + servletHandler1.setServletPath( "/servlet1" ); + servletHandler1.setName( "servlet1" ); + addHttpHandler( contextPath + "/servlet1", servletHandler1 ); + + ServletHandler servletHandler2 = new ServletHandler(); + servletHandler2.setServletInstance( new HttpServlet() { + @Override + public void doGet( HttpServletRequest request, HttpServletResponse response ) + throws ServletException, IOException { + PrintWriter out = response.getWriter(); + out.println( "Hello, world! I am a servlet2" ); + out.close(); + } + } ); + servletHandler2.setContextPath( contextPath ); + servletHandler2.setServletPath( "/servlet2" ); + servletHandler2.setName( "servlet2" ); + addHttpHandler( contextPath + "/servlet2", servletHandler2 ); + + httpServer.start(); + HttpURLConnection conn = getConnection( "/webapp/servlet1", PORT ); + assertEquals( HttpServletResponse.SC_OK, getResponseCodeFromAlias( conn ) ); + assertEquals( "Hello, world! I am a servlet2", readMultilineResponse( conn ).toString().trim() ); + } finally { + stopHttpServer(); + } + } + + public void testNamedDispatcherInclude() throws IOException { + Utils.dumpOut( "testNamedDispatcherInclude" ); + try { + newHttpServer( PORT ); + + String contextPath = "/webapp"; + + ServletHandler servletHandler1 = new ServletHandler(); + servletHandler1.setServletInstance( new HttpServlet() { + @Override + public void doGet( HttpServletRequest request, HttpServletResponse response ) + throws ServletException, IOException { + PrintWriter out = response.getWriter(); + out.println( "Hello, world! I am a servlet1" ); + + ServletContext servletCtx = getServletContext(); + assertNotNull( servletCtx ); + RequestDispatcher dispatcher = servletCtx.getNamedDispatcher( "servlet2" ); + assertNotNull( dispatcher ); + dispatcher.include( request, response ); + out.close(); + } + } ); + servletHandler1.setContextPath( contextPath ); + servletHandler1.setServletPath( "/servlet1" ); + servletHandler1.setName( "servlet1" ); + addHttpHandler( contextPath + "/servlet1", servletHandler1 ); + + ServletHandler servletHandler2 = new ServletHandler(); + servletHandler2.setServletInstance( new HttpServlet() { + @Override + public void doGet( HttpServletRequest request, HttpServletResponse response ) + throws ServletException, IOException { + PrintWriter out = response.getWriter(); + out.println( "Hello, world! I am a servlet2" ); + out.close(); + } + } ); + servletHandler2.setContextPath( contextPath ); + servletHandler2.setServletPath( "/servlet2" ); + servletHandler2.setName( "servlet2" ); + addHttpHandler( contextPath + "/servlet2", servletHandler2 ); + + httpServer.start(); + HttpURLConnection conn = getConnection( "/webapp/servlet1", PORT ); + assertEquals( HttpServletResponse.SC_OK, getResponseCodeFromAlias( conn ) ); + assertEquals( "Hello, world! I am a servlet1\nHello, world! I am a servlet2", readMultilineResponse( conn ).toString().trim() ); + } finally { + stopHttpServer(); + } + } + + public void testCrossContextForward() throws IOException { + Utils.dumpOut( "testCrossContextForward" ); + try { + newHttpServer( PORT ); + + ServletHandler servletHandler1 = new ServletHandler(); + servletHandler1.setServletInstance( new HttpServlet() { + @Override + public void doGet( HttpServletRequest request, HttpServletResponse response ) + throws ServletException, IOException { + PrintWriter out = response.getWriter(); + out.println( "Hello, world! I am a servlet1" ); + + ServletContext servletCtx1 = getServletContext(); + assertNotNull( servletCtx1 ); + RequestDispatcher dispatcher = request.getRequestDispatcher( "servlet2" ); + assertNull( dispatcher ); + + // cross context + ServletContext servletCtx2 = servletCtx1.getContext( "/webapp2" ); + assertNotNull( servletCtx2 ); + // The pathname must begin with a "/" + dispatcher = servletCtx2.getRequestDispatcher( "/servlet2" ); + assertNotNull( dispatcher ); + dispatcher.forward( request, response ); + out.close(); + } + } ); + servletHandler1.setContextPath( "/webapp1" ); + servletHandler1.setServletPath( "/servlet1" ); + addHttpHandler( "/webapp1/servlet1", servletHandler1 ); + + ServletHandler servletHandler2 = new ServletHandler(); + servletHandler2.setServletInstance( new HttpServlet() { + @Override + public void doGet( HttpServletRequest request, HttpServletResponse response ) + throws ServletException, IOException { + PrintWriter out = response.getWriter(); + out.println( "Hello, world! I am a servlet2" ); + out.close(); + } + } ); + servletHandler2.setContextPath( "/webapp2" ); + servletHandler2.setServletPath( "/servlet2" ); + addHttpHandler( "/webapp2/servlet2", servletHandler2 ); + + httpServer.start(); + HttpURLConnection conn = getConnection( "/webapp1/servlet1", PORT ); + assertEquals( HttpServletResponse.SC_OK, getResponseCodeFromAlias( conn ) ); + assertEquals( "Hello, world! I am a servlet2", readMultilineResponse( conn ).toString().trim() ); + } finally { + stopHttpServer(); + } + } + + public void testComplexDispatch() throws IOException { + // servlet1 --> dispatcher forward by ServletRequest's API(servlet2) -> + // named dispatcher include(servlet3) -> cross context, dispatcher include by ServletContext's API(servlet4) + Utils.dumpOut( "testComplexDispatch" ); + try { + newHttpServer( PORT ); + + // webapp1(servlet1, servlet2, servlet3) + ServletHandler servletHandler1 = new ServletHandler(); + servletHandler1.setServletInstance( new HttpServlet() { + @Override + public void doGet( HttpServletRequest request, HttpServletResponse response ) + throws ServletException, IOException { + PrintWriter out = response.getWriter(); + out.println( "Hello, world! I am a servlet1" ); + + RequestDispatcher dispatcher = request.getRequestDispatcher( "servlet2" ); + assertNotNull( dispatcher ); + dispatcher.forward( request, response ); + out.close(); + } + } ); + servletHandler1.setContextPath( "/webapp1" ); + servletHandler1.setServletPath( "/servlet1" ); + addHttpHandler( "/webapp1/servlet1", servletHandler1 ); + + ServletHandler servletHandler2 = new ServletHandler(); + servletHandler2.setServletInstance( new HttpServlet() { + @Override + public void doGet( HttpServletRequest request, HttpServletResponse response ) + throws ServletException, IOException { + PrintWriter out = response.getWriter(); + out.println( "Hello, world! I am a servlet2" ); + + ServletContext servletCtx = getServletContext(); + assertNotNull( servletCtx ); + RequestDispatcher dispatcher = servletCtx.getNamedDispatcher( "servlet3" ); + assertNotNull( dispatcher ); + dispatcher.include( request, response ); + out.close(); + } + } ); + servletHandler2.setContextPath( "/webapp1" ); + servletHandler2.setServletPath( "/servlet2" ); + addHttpHandler( "/webapp1/servlet2", servletHandler2 ); + + ServletHandler servletHandler3 = new ServletHandler(); + servletHandler3.setServletInstance( new HttpServlet() { + @Override + public void doGet( HttpServletRequest request, HttpServletResponse response ) + throws ServletException, IOException { + PrintWriter out = response.getWriter(); + out.println( "Hello, world! I am a servlet3" ); + + ServletContext servletCtx1 = getServletContext(); + assertNotNull( servletCtx1 ); + ServletContext servletCtx2 = servletCtx1.getContext( "/webapp2" ); + assertNotNull( servletCtx2 ); + RequestDispatcher dispatcher = servletCtx2.getRequestDispatcher( "/servlet4" ); + dispatcher.include( request, response ); + out.close(); + } + } ); + servletHandler3.setContextPath( "/webapp1" ); + servletHandler3.setServletPath( "/servlet3" ); + servletHandler3.setName( "servlet3" ); + addHttpHandler( "/webapp1/servlet3", servletHandler3 ); + + // webapp2(servlet4) + ServletHandler servletHandler4 = new ServletHandler(); + servletHandler4.setServletInstance( new HttpServlet() { + @Override + public void doGet( HttpServletRequest request, HttpServletResponse response ) + throws ServletException, IOException { + PrintWriter out = response.getWriter(); + out.println( "Hello, world! I am a servlet4" ); + out.close(); + } + } ); + servletHandler4.setContextPath( "/webapp2" ); + servletHandler4.setServletPath( "/servlet4" ); + addHttpHandler( "/webapp2/servlet4", servletHandler4 ); + + httpServer.start(); + HttpURLConnection conn = getConnection( "/webapp1/servlet1", PORT ); + assertEquals( HttpServletResponse.SC_OK, getResponseCodeFromAlias( conn ) ); + assertEquals( "Hello, world! I am a servlet2\nHello, world! I am a servlet3\nHello, world! I am a servlet4", + readMultilineResponse( conn ).toString().trim() ); + } finally { + stopHttpServer(); + } + } +} Index: code/modules/http-servlet/src/main/java/org/glassfish/grizzly/servlet/HttpServletRequestImpl.java =================================================================== --- code/modules/http-servlet/src/main/java/org/glassfish/grizzly/servlet/HttpServletRequestImpl.java (revision 6018) +++ code/modules/http-servlet/src/main/java/org/glassfish/grizzly/servlet/HttpServletRequestImpl.java (revision ) @@ -412,7 +412,7 @@ } - + /** * {@inheritDoc} */ @@ -676,18 +676,66 @@ } - /** * {@inheritDoc} */ @Override - @SuppressWarnings("unchecked") + @SuppressWarnings( "unchecked" ) - public RequestDispatcher getRequestDispatcher(String path) { + public RequestDispatcher getRequestDispatcher( String path ) { - throw new UnsupportedOperationException("Not supported yet."); + if( request == null ) { + throw new IllegalStateException( sm.getString( "requestFacade.nullRequest" ) ); - } + } + if( System.getSecurityManager() != null ) { + return (RequestDispatcher)AccessController.doPrivileged( + new GetRequestDispatcherPrivilegedAction( path ) ); + } else { + return getRequestDispatcherInternal( path ); + } + } - + + private RequestDispatcher getRequestDispatcherInternal( String path ) { + if( contextImpl == null ) { + return null; + } + + // If the path is already context-relative, just pass it through + if( path == null ) { + return ( null ); + } else if( path.startsWith( "/" ) ) { + return ( contextImpl.getRequestDispatcher( path ) ); + } + + // Convert a request-relative path to a context-relative one + String servletPath = (String)getAttribute( DispatcherConstants.INCLUDE_SERVLET_PATH ); + if( servletPath == null ) { + servletPath = getServletPath(); + } + + // Add the path info, if there is any + String pathInfo = getPathInfo(); + String requestPath = null; + + if( pathInfo == null ) { + requestPath = servletPath; + } else { + requestPath = servletPath + pathInfo; + } + + int pos = requestPath.lastIndexOf( '/' ); + String relative = null; + if( pos >= 0 ) { + relative = requestPath.substring( 0, pos + 1 ) + path; + } else { + relative = requestPath + path; + } + + return contextImpl.getRequestDispatcher( relative ); + } + + + /** * {@inheritDoc} */ @@ -1241,7 +1289,7 @@ } - private static final class GetRequestDispatcherPrivilegedAction + private final class GetRequestDispatcherPrivilegedAction implements PrivilegedAction { private final String path; @@ -1252,7 +1300,7 @@ @Override public Object run() { - throw new UnsupportedOperationException("Not supported yet."); + return getRequestDispatcherInternal(path); } }