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; @@ -367,6 +368,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 +807,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; @@ -819,6 +845,11 @@ } } + @Override + protected void setMapperResolver( MapperResolver mapperResolver ) { + servletCtx.setMapperResolver( mapperResolver ); + } + // ---------------------------------------------------------- Nested Classes /** * Implementation of javax.servlet.FilterChain used to manage 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 ) @@ -42,6 +42,7 @@ import org.glassfish.grizzly.Grizzly; import org.glassfish.grizzly.http.server.jmx.JmxEventListener; import org.glassfish.grizzly.http.server.jmx.Monitorable; +import org.glassfish.grizzly.http.server.util.NamedMapper; import org.glassfish.grizzly.http.util.DataChunk; import org.glassfish.grizzly.http.util.HttpStatus; import org.glassfish.grizzly.http.util.RequestURIRef; @@ -81,7 +82,7 @@ /** * Internal {@link Mapper} used to Map request to their associated {@link HttpHandler} */ - private Mapper mapper; + private NamedMapper mapper; /** * The default host. */ @@ -101,7 +102,7 @@ // ------------------------------------------------------------ Constructors public HttpHandlerChain(final HttpServer httpServer) { this.httpServer = httpServer; - mapper = new Mapper(); + mapper = new NamedMapper(); mapper.setDefaultHostName(LOCAL_HOST); // We will decode it setDecodeUrl(false); @@ -243,7 +244,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 +253,7 @@ // mapper.addWrapper(LOCAL_HOST, ctx, mapping.substring(ctx.length()), httpHandler); } } - + httpHandler.setMapperResolver( 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 ) @@ -81,8 +81,12 @@ import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import org.glassfish.grizzly.Grizzly; +import org.glassfish.grizzly.http.server.HttpHandler; 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 +119,14 @@ private String contextName = ""; private volatile String serverInfo = "Grizzly " + Grizzly.getDotedVersion(); - + + /** + * Thread local data used during request dispatch. + */ + private ThreadLocal dispatchData = new ThreadLocal(); + + private HttpHandler.MapperResolver mapper; + // ----------------------------------------------------------------- // /** * Notify the {@link ServletContextListener} that we are starting. @@ -201,20 +212,51 @@ } /** - * 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( 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 +401,116 @@ /** * {@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( 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( 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 +805,33 @@ protected List getListeners() { return eventListeners; } + + protected void setMapperResolver( HttpHandler.MapperResolver mapper ) { + this.mapper = mapper; -} + } + + /** + * 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,8 @@ 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.MappingData; +import org.glassfish.grizzly.http.util.DataChunk; import org.glassfish.grizzly.http.util.HttpStatus; import java.io.CharConversionException; @@ -92,6 +94,8 @@ */ private boolean allowCustomStatusMessage = true; + private String name; + /** * Create HttpHandler. */ @@ -316,4 +320,45 @@ protected static void updateContextPath(final Request request, final String contextPath) { request.setContextPath(contextPath); } + + public String getName() { + return name; -} + } + + public void setName( String name ) { + this.name = name; + } + + protected void setMapperResolver( MapperResolver mapperResolver ) { + } + + public interface MapperResolver { + /** + * Map the specified host name, URI and servlet name, mutating the given mapping data. + * All parameters should not be null. + * + * @param host Virtual host name. + * @param uri URI + * @param name Servlet name + * @param mappingData This structure will contain the result of the mapping + * operation + */ + public void map( final DataChunk host, + final DataChunk uri, + final DataChunk name, + final MappingData mappingData ) throws Exception; + + /** + * Map the specified host name and URI, mutating the given mapping data. + * All parameters should not be null. + * + * @param host Virtual host name + * @param uri URI + * @param mappingData This structure will contain the result of the mapping + * operation + */ + public void map( final DataChunk host, + final DataChunk uri, + final MappingData mappingData ) throws Exception; + } +} Index: code/modules/http-server/src/main/java/org/glassfish/grizzly/http/server/util/NamedMapper.java =================================================================== --- code/modules/http-server/src/main/java/org/glassfish/grizzly/http/server/util/NamedMapper.java (revision ) +++ code/modules/http-server/src/main/java/org/glassfish/grizzly/http/server/util/NamedMapper.java (revision ) @@ -0,0 +1,167 @@ +/* + * 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.http.server.util; + +import org.glassfish.grizzly.http.server.HttpHandler; +import org.glassfish.grizzly.http.util.CharChunk; +import org.glassfish.grizzly.http.util.DataChunk; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import static org.glassfish.grizzly.http.util.Charsets.DEFAULT_CHARSET; + +/** + * Mapper, which supports for the NamedRequestDispatcher of the servlet API simply. + * + * @author Bongjae Chang + */ +public class NamedMapper extends Mapper implements HttpHandler.MapperResolver { + + private final Map namedCtxMap = new ConcurrentHashMap(); + + @Override + protected void addWrapper( final Context context, + final String hostName, + final String contextPath, + final String path, + final Object wrapper, + final boolean jspWildCard, + final String servletName, + final boolean isEmptyPathSpecial ) { + super.addWrapper( context, hostName, contextPath, path, wrapper, jspWildCard, servletName, isEmptyPathSpecial ); + if( hostName != null && contextPath != null && servletName != null && wrapper != null ) + namedCtxMap.put( new NamedContext( hostName, contextPath, servletName ), wrapper ); // todo handling for removing the wrapper. + } + + @Override + public void map( final DataChunk host, + final DataChunk uri, + final DataChunk servletName, + final MappingData mappingData ) throws Exception { + if( host == null || uri == null || servletName == null || mappingData == null ) + return; + if( host.isNull() ) + host.setString( defaultHostName ); + host.toChars( DEFAULT_CHARSET ); + uri.toChars( null ); + servletName.toChars( null ); + internalMap( host.getCharChunk(), uri.getCharChunk(), servletName.getCharChunk(), mappingData ); + } + + @Override + protected void internalMapWrapper( final Context context, + final CharChunk host, + final CharChunk path, + final CharChunk servletName, + final MappingData mappingData ) throws Exception { + if( host != null && path != null && servletName != null && mappingData != null ) { + mappingData.wrapper = namedCtxMap.get( new NamedContext( host.toString(), path.toString(), servletName.toString() ) ); + } else { + super.internalMapWrapper( context, host, path, servletName, mappingData ); + } + } + + private static class NamedContext { + private final String hostName; + private final String contextPath; + private final String servletName; + + private volatile int hashCode; + + private NamedContext( final String hostName, final String contextPath, final String servletName ) { + this.hostName = hostName; + this.contextPath = contextPath; + this.servletName = servletName; + } + + @Override + public boolean equals( Object obj ) { + if( obj == this ) + return true; + if( !( obj instanceof NamedContext ) ) + return false; + NamedContext nctx = (NamedContext)obj; + if( !( hostName == nctx.hostName || ( hostName != null && hostName.equals( nctx.hostName ) ) ) ) + return false; + if( !( contextPath == nctx.contextPath || ( contextPath != null && contextPath.equals( nctx.contextPath ) ) ) ) { + return false; + } + if( !( servletName == nctx.servletName || ( servletName != null && servletName.equals( nctx.servletName ) ) ) ) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int result = hashCode; + if( result == 0 ) { + result = 17; + if( hostName != null ) + result = 31 * result + hostName.hashCode(); + if( contextPath != null ) + result = 31 * result + contextPath.hashCode(); + if( servletName != null ) + result = 31 * result + servletName.hashCode(); + hashCode = result; + } + return result; + } + } +} 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 ) @@ -485,7 +485,7 @@ } Context ctx = contexts[pos2]; if (ctx.name.equals(contextPath)) { - addWrapper(ctx, path, wrapper, jspWildCard, servletName, isEmptyPathSpecial); + addWrapper(ctx, hostName, contextPath, path, wrapper, jspWildCard, servletName, isEmptyPathSpecial); } } } @@ -515,7 +515,7 @@ protected void addWrapper(Context context, String path, Object wrapper, boolean jspWildCard, boolean isEmptyPathSpecial) { - addWrapper(context, path, wrapper, jspWildCard, null, isEmptyPathSpecial); + addWrapper(context, null, null, path, wrapper, jspWildCard, null, isEmptyPathSpecial); } @@ -529,8 +529,14 @@ * and the mapping path contains a wildcard; false otherwise * @param servletName then name of servletName or null if unknown */ - protected void addWrapper(Context context, String path, Object wrapper, boolean jspWildCard, String servletName, + protected void addWrapper(Context context, + String hostName, + String contextPath, + String path, + Object wrapper, + boolean jspWildCard, + String servletName, - boolean isEmptyPathSpecial) { + boolean isEmptyPathSpecial) { synchronized (context) { @@ -847,12 +853,13 @@ final MappingData mappingData) throws Exception { if (host.isNull()) { - host.getCharChunk().append(defaultHostName); + //host.getCharChunk().append(defaultHostName); // this is maybe bug + host.setString(defaultHostName); } - + host.toChars(DEFAULT_CHARSET); uri.toChars(null); - internalMap(host.getCharChunk(), uri.getCharChunk(), mappingData); + internalMap(host.getCharChunk(), uri.getCharChunk(), null, mappingData); } @@ -871,7 +878,7 @@ uri.toChars(); CharChunk uricc = uri.getCharChunk(); uricc.setLimit(-1); - internalMapWrapper(context, uricc, mappingData); + internalMapWrapper(context, null, uricc, null, mappingData); } @@ -882,9 +889,10 @@ /** * Map the specified URI. */ - private final void internalMap(CharChunk host, CharChunk uri, - MappingData mappingData) - throws Exception { + protected final void internalMap( CharChunk host, + CharChunk uri, + CharChunk servletName, + MappingData mappingData ) throws Exception { uri.setLimit(-1); @@ -993,7 +1001,7 @@ // Wrapper mapping if (ctx != null && mappingData.wrapper == null) { - internalMapWrapper(ctx, uri, mappingData); + internalMapWrapper(ctx, host, uri, servletName, mappingData ); } } @@ -1002,9 +1010,11 @@ /** * Wrapper mapping. */ - private final void internalMapWrapper(Context context, CharChunk path, - MappingData mappingData) - throws Exception { + protected void internalMapWrapper(Context context, + CharChunk host, + CharChunk path, + CharChunk servletName, + MappingData mappingData ) throws Exception { int pathOffset = path.getStart(); int pathEnd = path.getEnd(); 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-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 ) @@ -65,6 +65,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,410 @@ +/* + * 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" ); + 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" ); + 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); } }