diff options
Diffstat (limited to 'jetty-servlet/src/main')
13 files changed, 5326 insertions, 0 deletions
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java new file mode 100644 index 0000000000..a75e1572ee --- /dev/null +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java @@ -0,0 +1,905 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlet; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Enumeration; +import java.util.List; + +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.UnavailableException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpContent; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeaderValues; +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.HttpMethods; +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.io.WriterOutputStream; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Dispatcher; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.InclusiveByteRange; +import org.eclipse.jetty.server.ResourceCache; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.nio.NIOConnector; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.MultiPartOutputStream; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.resource.ResourceFactory; + + + +/* ------------------------------------------------------------ */ +/** The default servlet. + * This servlet, normally mapped to /, provides the handling for static + * content, OPTION and TRACE methods for the context. + * The following initParameters are supported, these can be set either + * on the servlet itself or as ServletContext initParameters with a prefix + * of org.eclipse.jetty.servlet.Default. : + * <PRE> + * acceptRanges If true, range requests and responses are + * supported + * + * dirAllowed If true, directory listings are returned if no + * welcome file is found. Else 403 Forbidden. + * + * redirectWelcome If true, welcome files are redirected rather than + * forwarded to. + * + * gzip If set to true, then static content will be served as + * gzip content encoded if a matching resource is + * found ending with ".gz" + * + * resourceBase Set to replace the context resource base + * + * relativeResourceBase + * Set with a pathname relative to the base of the + * servlet context root. Useful for only serving static content out + * of only specific subdirectories. + * + * aliases If True, aliases of resources are allowed (eg. symbolic + * links and caps variations). May bypass security constraints. + * + * maxCacheSize The maximum total size of the cache or 0 for no cache. + * maxCachedFileSize The maximum size of a file to cache + * maxCachedFiles The maximum number of files to cache + * cacheType Set to "bio", "nio" or "both" to determine the type resource cache. + * A bio cached buffer may be used by nio but is not as efficient as an + * nio buffer. An nio cached buffer may not be used by bio. + * + * useFileMappedBuffer + * If set to true, it will use mapped file buffer to serve static content + * when using NIO connector. Setting this value to false means that + * a direct buffer will be used instead of a mapped file buffer. + * By default, this is set to true. + * + * cacheControl If set, all static content will have this value set as the cache-control + * header. + * + * + * </PRE> + * + * + * + * + */ +public class DefaultServlet extends HttpServlet implements ResourceFactory +{ + private ServletContext _servletContext; + private ContextHandler _contextHandler; + + private boolean _acceptRanges=true; + private boolean _dirAllowed=true; + private boolean _redirectWelcome=false; + private boolean _gzip=true; + + private Resource _resourceBase; + private NIOResourceCache _nioCache; + private ResourceCache _bioCache; + + private MimeTypes _mimeTypes; + private String[] _welcomes; + private boolean _useFileMappedBuffer=false; + private ByteArrayBuffer _cacheControl; + private String _relativeResourceBase; + + + /* ------------------------------------------------------------ */ + public void init() + throws UnavailableException + { + _servletContext=getServletContext(); + ContextHandler.Context scontext=ContextHandler.getCurrentContext(); + if (scontext==null) + _contextHandler=((ContextHandler.Context)_servletContext).getContextHandler(); + else + _contextHandler = ContextHandler.getCurrentContext().getContextHandler(); + + _mimeTypes = _contextHandler.getMimeTypes(); + + _welcomes = _contextHandler.getWelcomeFiles(); + if (_welcomes==null) + _welcomes=new String[] {"index.jsp","index.html"}; + + _acceptRanges=getInitBoolean("acceptRanges",_acceptRanges); + _dirAllowed=getInitBoolean("dirAllowed",_dirAllowed); + _redirectWelcome=getInitBoolean("redirectWelcome",_redirectWelcome); + _gzip=getInitBoolean("gzip",_gzip); + + String aliases=_servletContext.getInitParameter("aliases"); + if (aliases!=null) + _contextHandler.setAliases(Boolean.parseBoolean(aliases)); + + _useFileMappedBuffer=getInitBoolean("useFileMappedBuffer",_useFileMappedBuffer); + + _relativeResourceBase = getInitParameter("relativeResourceBase"); + + String rb=getInitParameter("resourceBase"); + if (rb!=null) + { + if (_relativeResourceBase!=null) + throw new UnavailableException("resourceBase & relativeResourceBase"); + try{_resourceBase=_contextHandler.newResource(rb);} + catch (Exception e) + { + Log.warn(Log.EXCEPTION,e); + throw new UnavailableException(e.toString()); + } + } + + String t=getInitParameter("cacheControl"); + if (t!=null) + _cacheControl=new ByteArrayBuffer(t); + + try + { + String cache_type =getInitParameter("cacheType"); + int max_cache_size=getInitInt("maxCacheSize", -2); + int max_cached_file_size=getInitInt("maxCachedFileSize", -2); + int max_cached_files=getInitInt("maxCachedFiles", -2); + + if (cache_type==null || "nio".equals(cache_type)|| "both".equals(cache_type)) + { + if (max_cache_size==-2 || max_cache_size>0) + { + _nioCache=new NIOResourceCache(_mimeTypes); + _nioCache.setUseFileMappedBuffer(_useFileMappedBuffer); + if (max_cache_size>0) + _nioCache.setMaxCacheSize(max_cache_size); + if (max_cached_file_size>=-1) + _nioCache.setMaxCachedFileSize(max_cached_file_size); + if (max_cached_files>=-1) + _nioCache.setMaxCachedFiles(max_cached_files); + _nioCache.start(); + } + } + if ("bio".equals(cache_type)|| "both".equals(cache_type)) + { + if (max_cache_size==-2 || max_cache_size>0) + { + _bioCache=new ResourceCache(_mimeTypes); + if (max_cache_size>0) + _bioCache.setMaxCacheSize(max_cache_size); + if (max_cached_file_size>=-1) + _bioCache.setMaxCachedFileSize(max_cached_file_size); + if (max_cached_files>=-1) + _bioCache.setMaxCachedFiles(max_cached_files); + _bioCache.start(); + } + } + if (_nioCache==null) + _bioCache=null; + + } + catch (Exception e) + { + Log.warn(Log.EXCEPTION,e); + throw new UnavailableException(e.toString()); + } + + if (Log.isDebugEnabled()) Log.debug("resource base = "+_resourceBase); + } + + /* ------------------------------------------------------------ */ + public String getInitParameter(String name) + { + String value=getServletContext().getInitParameter("org.eclipse.jetty.servlet.Default."+name); + if (value==null) + value=super.getInitParameter(name); + return value; + } + + /* ------------------------------------------------------------ */ + private boolean getInitBoolean(String name, boolean dft) + { + String value=getInitParameter(name); + if (value==null || value.length()==0) + return dft; + return (value.startsWith("t")|| + value.startsWith("T")|| + value.startsWith("y")|| + value.startsWith("Y")|| + value.startsWith("1")); + } + + /* ------------------------------------------------------------ */ + private int getInitInt(String name, int dft) + { + String value=getInitParameter(name); + if (value==null) + value=getInitParameter(name); + if (value!=null && value.length()>0) + return Integer.parseInt(value); + return dft; + } + + /* ------------------------------------------------------------ */ + /** get Resource to serve. + * Map a path to a resource. The default implementation calls + * HttpContext.getResource but derived servlets may provide + * their own mapping. + * @param pathInContext The path to find a resource for. + * @return The resource to serve. + */ + public Resource getResource(String pathInContext) + { + Resource r=null; + if (_relativeResourceBase!=null) + pathInContext=URIUtil.addPaths(_relativeResourceBase,pathInContext); + + try + { + if (_resourceBase!=null) + r = _resourceBase.addPath(pathInContext); + else + { + URL u = _servletContext.getResource(pathInContext); + r = _contextHandler.newResource(u); + } + + if (Log.isDebugEnabled()) Log.debug("RESOURCE="+r); + } + catch (IOException e) + { + Log.ignore(e); + } + return r; + } + + /* ------------------------------------------------------------ */ + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + String servletPath=null; + String pathInfo=null; + Enumeration reqRanges = null; + Boolean included =request.getAttribute(Dispatcher.__INCLUDE_REQUEST_URI)!=null; + if (included!=null && included.booleanValue()) + { + servletPath=(String)request.getAttribute(Dispatcher.__INCLUDE_SERVLET_PATH); + pathInfo=(String)request.getAttribute(Dispatcher.__INCLUDE_PATH_INFO); + if (servletPath==null) + { + servletPath=request.getServletPath(); + pathInfo=request.getPathInfo(); + } + } + else + { + included=Boolean.FALSE; + servletPath=request.getServletPath(); + pathInfo=request.getPathInfo(); + + // Is this a range request? + reqRanges = request.getHeaders(HttpHeaders.RANGE); + if (reqRanges!=null && !reqRanges.hasMoreElements()) + reqRanges=null; + } + + String pathInContext=URIUtil.addPaths(servletPath,pathInfo); + boolean endsWithSlash=pathInContext.endsWith(URIUtil.SLASH); + + // Can we gzip this request? + String pathInContextGz=null; + boolean gzip=false; + if (!included.booleanValue() && _gzip && reqRanges==null && !endsWithSlash ) + { + String accept=request.getHeader(HttpHeaders.ACCEPT_ENCODING); + if (accept!=null && accept.indexOf("gzip")>=0) + gzip=true; + } + + // Find the resource and content + Resource resource=null; + HttpContent content=null; + + Connector connector = HttpConnection.getCurrentConnection().getConnector(); + ResourceCache cache=(connector instanceof NIOConnector) ?_nioCache:_bioCache; + try + { + // Try gzipped content first + if (gzip) + { + pathInContextGz=pathInContext+".gz"; + + if (cache==null) + { + resource=getResource(pathInContextGz); + } + else + { + content=cache.lookup(pathInContextGz,this); + + if (content!=null) + resource=content.getResource(); + else + resource=getResource(pathInContextGz); + } + + if (resource==null || !resource.exists()|| resource.isDirectory()) + { + if (cache!=null && content==null) + { + String real_path=_servletContext.getRealPath(pathInContextGz); + if (real_path!=null) + cache.miss(pathInContextGz,_contextHandler.newResource(real_path)); + } + gzip=false; + pathInContextGz=null; + } + } + + // find resource + if (!gzip) + { + if (cache==null) + resource=getResource(pathInContext); + else + { + content=cache.lookup(pathInContext,this); + + if (content!=null) + resource=content.getResource(); + else + resource=getResource(pathInContext); + } + } + + if (Log.isDebugEnabled()) + Log.debug("resource="+resource+(content!=null?" content":"")); + + // Handle resource + if (resource==null || !resource.exists()) + response.sendError(HttpServletResponse.SC_NOT_FOUND); + else if (!resource.isDirectory()) + { + // ensure we have content + if (content==null) + content=new UnCachedContent(resource); + + if (included.booleanValue() || passConditionalHeaders(request,response, resource,content)) + { + if (gzip) + { + response.setHeader(HttpHeaders.CONTENT_ENCODING,"gzip"); + String mt=_servletContext.getMimeType(pathInContext); + if (mt!=null) + response.setContentType(mt); + } + sendData(request,response,included.booleanValue(),resource,content,reqRanges); + } + } + else + { + String welcome=null; + + if (!endsWithSlash || (pathInContext.length()==1 && request.getAttribute("org.eclipse.jetty.server.nullPathInfo")!=null)) + { + StringBuffer buf=request.getRequestURL(); + synchronized(buf) + { + int param=buf.lastIndexOf(";"); + if (param<0) + buf.append('/'); + else + buf.insert(param,'/'); + String q=request.getQueryString(); + if (q!=null&&q.length()!=0) + { + buf.append('?'); + buf.append(q); + } + response.setContentLength(0); + response.sendRedirect(response.encodeRedirectURL(buf.toString())); + } + } + // else look for a welcome file + else if (null!=(welcome=getWelcomeFile(resource))) + { + String ipath=URIUtil.addPaths(pathInContext,welcome); + if (_redirectWelcome) + { + // Redirect to the index + response.setContentLength(0); + String q=request.getQueryString(); + if (q!=null&&q.length()!=0) + response.sendRedirect(URIUtil.addPaths( _servletContext.getContextPath(),ipath)+"?"+q); + else + response.sendRedirect(URIUtil.addPaths( _servletContext.getContextPath(),ipath)); + } + else + { + // Forward to the index + RequestDispatcher dispatcher=request.getRequestDispatcher(ipath); + if (dispatcher!=null) + { + if (included.booleanValue()) + dispatcher.include(request,response); + else + { + request.setAttribute("org.eclipse.jetty.server.welcome",ipath); + dispatcher.forward(request,response); + } + } + } + } + else + { + content=new UnCachedContent(resource); + if (included.booleanValue() || passConditionalHeaders(request,response, resource,content)) + sendDirectory(request,response,resource,pathInContext.length()>1); + } + } + } + catch(IllegalArgumentException e) + { + Log.warn(Log.EXCEPTION,e); + if(!response.isCommitted()) + response.sendError(500, e.getMessage()); + } + finally + { + if (content!=null) + content.release(); + else if (resource!=null) + resource.release(); + } + + } + + /* ------------------------------------------------------------ */ + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + doGet(request,response); + } + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see javax.servlet.http.HttpServlet#doTrace(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); + } + + /* ------------------------------------------------------------ */ + /** + * Finds a matching welcome file for the supplied {@link Resource}. This will be the first entry in the list of + * configured {@link #_welcomes welcome files} that existing within the directory referenced by the <code>Resource</code>. + * If the resource is not a directory, or no matching file is found, then <code>null</code> is returned. + * The list of welcome files is read from the {@link ContextHandler} for this servlet, or + * <code>"index.jsp" , "index.html"</code> if that is <code>null</code>. + * @param resource + * @return The name of the matching welcome file. + * @throws IOException + * @throws MalformedURLException + */ + private String getWelcomeFile(Resource resource) throws MalformedURLException, IOException + { + if (!resource.isDirectory() || _welcomes==null) + return null; + + for (int i=0;i<_welcomes.length;i++) + { + Resource welcome=resource.addPath(_welcomes[i]); + if (welcome.exists()) + return _welcomes[i]; + } + + return null; + } + + /* ------------------------------------------------------------ */ + /* Check modification date headers. + */ + protected boolean passConditionalHeaders(HttpServletRequest request,HttpServletResponse response, Resource resource, HttpContent content) + throws IOException + { + try + { + if (!request.getMethod().equals(HttpMethods.HEAD) ) + { + String ifms=request.getHeader(HttpHeaders.IF_MODIFIED_SINCE); + if (ifms!=null) + { + if (content!=null) + { + Buffer mdlm=content.getLastModified(); + if (mdlm!=null) + { + if (ifms.equals(mdlm.toString())) + { + response.reset(); + response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); + response.flushBuffer(); + return false; + } + } + } + + long ifmsl=request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE); + if (ifmsl!=-1) + { + if (resource.lastModified()/1000 <= ifmsl/1000) + { + response.reset(); + response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); + response.flushBuffer(); + return false; + } + } + } + + // Parse the if[un]modified dates and compare to resource + long date=request.getDateHeader(HttpHeaders.IF_UNMODIFIED_SINCE); + + if (date!=-1) + { + if (resource.lastModified()/1000 > date/1000) + { + response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED); + return false; + } + } + + } + } + catch(IllegalArgumentException iae) + { + if(!response.isCommitted()) + response.sendError(400, iae.getMessage()); + throw iae; + } + return true; + } + + + /* ------------------------------------------------------------------- */ + protected void sendDirectory(HttpServletRequest request, + HttpServletResponse response, + Resource resource, + boolean parent) + throws IOException + { + if (!_dirAllowed) + { + response.sendError(HttpServletResponse.SC_FORBIDDEN); + return; + } + + byte[] data=null; + String base = URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH); + String dir = resource.getListHTML(base,parent); + if (dir==null) + { + response.sendError(HttpServletResponse.SC_FORBIDDEN, + "No directory"); + return; + } + + data=dir.getBytes("UTF-8"); + response.setContentType("text/html; charset=UTF-8"); + response.setContentLength(data.length); + response.getOutputStream().write(data); + } + + /* ------------------------------------------------------------ */ + protected void sendData(HttpServletRequest request, + HttpServletResponse response, + boolean include, + Resource resource, + HttpContent content, + Enumeration reqRanges) + throws IOException + { + long content_length=resource.length(); + + // Get the output stream (or writer) + OutputStream out =null; + try{out = response.getOutputStream();} + catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());} + + if ( reqRanges == null || !reqRanges.hasMoreElements()) + { + // if there were no ranges, send entire entity + if (include) + { + resource.writeTo(out,0,content_length); + } + else + { + // See if a short direct method can be used? + if (out instanceof HttpConnection.Output) + { + if (_cacheControl!=null) + { + if (response instanceof Response) + ((Response)response).getHttpFields().put(HttpHeaders.CACHE_CONTROL_BUFFER,_cacheControl); + else + response.setHeader(HttpHeaders.CACHE_CONTROL,_cacheControl.toString()); + } + ((HttpConnection.Output)out).sendContent(content); + } + else + { + + // Write content normally + writeHeaders(response,content,content_length); + resource.writeTo(out,0,content_length); + } + } + } + else + { + // Parse the satisfiable ranges + List ranges =InclusiveByteRange.satisfiableRanges(reqRanges,content_length); + + // if there are no satisfiable ranges, send 416 response + if (ranges==null || ranges.size()==0) + { + writeHeaders(response, content, content_length); + response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); + response.setHeader(HttpHeaders.CONTENT_RANGE, + InclusiveByteRange.to416HeaderRangeString(content_length)); + resource.writeTo(out,0,content_length); + return; + } + + + // if there is only a single valid range (must be satisfiable + // since were here now), send that range with a 216 response + if ( ranges.size()== 1) + { + InclusiveByteRange singleSatisfiableRange = + (InclusiveByteRange)ranges.get(0); + long singleLength = singleSatisfiableRange.getSize(content_length); + writeHeaders(response,content,singleLength ); + response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); + response.setHeader(HttpHeaders.CONTENT_RANGE, + singleSatisfiableRange.toHeaderRangeString(content_length)); + resource.writeTo(out,singleSatisfiableRange.getFirst(content_length),singleLength); + return; + } + + + // multiple non-overlapping valid ranges cause a multipart + // 216 response which does not require an overall + // content-length header + // + writeHeaders(response,content,-1); + String mimetype=content.getContentType().toString(); + MultiPartOutputStream multi = new MultiPartOutputStream(out); + response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); + + // If the request has a "Request-Range" header then we need to + // send an old style multipart/x-byteranges Content-Type. This + // keeps Netscape and acrobat happy. This is what Apache does. + String ctp; + if (request.getHeader(HttpHeaders.REQUEST_RANGE)!=null) + ctp = "multipart/x-byteranges; boundary="; + else + ctp = "multipart/byteranges; boundary="; + response.setContentType(ctp+multi.getBoundary()); + + InputStream in=resource.getInputStream(); + long pos=0; + + for (int i=0;i<ranges.size();i++) + { + InclusiveByteRange ibr = (InclusiveByteRange) ranges.get(i); + String header=HttpHeaders.CONTENT_RANGE+": "+ + ibr.toHeaderRangeString(content_length); + multi.startPart(mimetype,new String[]{header}); + + long start=ibr.getFirst(content_length); + long size=ibr.getSize(content_length); + if (in!=null) + { + // Handle non cached resource + if (start<pos) + { + in.close(); + in=resource.getInputStream(); + pos=0; + } + if (pos<start) + { + in.skip(start-pos); + pos=start; + } + IO.copy(in,multi,size); + pos+=size; + } + else + // Handle cached resource + (resource).writeTo(multi,start,size); + + } + if (in!=null) + in.close(); + multi.close(); + } + return; + } + + /* ------------------------------------------------------------ */ + protected void writeHeaders(HttpServletResponse response,HttpContent content,long count) + throws IOException + { + if (content.getContentType()!=null) + response.setContentType(content.getContentType().toString()); + + if (response instanceof Response) + { + Response r=(Response)response; + HttpFields fields = r.getHttpFields(); + + if (content.getLastModified()!=null) + fields.put(HttpHeaders.LAST_MODIFIED_BUFFER,content.getLastModified(),content.getResource().lastModified()); + else if (content.getResource()!=null) + { + long lml=content.getResource().lastModified(); + if (lml!=-1) + fields.putDateField(HttpHeaders.LAST_MODIFIED_BUFFER,lml); + } + + if (count != -1) + r.setLongContentLength(count); + + if (_acceptRanges) + fields.put(HttpHeaders.ACCEPT_RANGES_BUFFER,HttpHeaderValues.BYTES_BUFFER); + + if (_cacheControl!=null) + fields.put(HttpHeaders.CACHE_CONTROL_BUFFER,_cacheControl); + + } + else + { + long lml=content.getResource().lastModified(); + if (lml>=0) + response.setDateHeader(HttpHeaders.LAST_MODIFIED,lml); + + if (count != -1) + { + if (count<Integer.MAX_VALUE) + response.setContentLength((int)count); + else + response.setHeader(HttpHeaders.CONTENT_LENGTH,TypeUtil.toString(count)); + } + + if (_acceptRanges) + response.setHeader(HttpHeaders.ACCEPT_RANGES,"bytes"); + + if (_cacheControl!=null) + response.setHeader(HttpHeaders.CACHE_CONTROL,_cacheControl.toString()); + } + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.Servlet#destroy() + */ + public void destroy() + { + try + { + if (_nioCache!=null) + _nioCache.stop(); + if (_bioCache!=null) + _bioCache.stop(); + } + catch(Exception e) + { + Log.warn(Log.EXCEPTION,e); + } + finally + { + super.destroy(); + } + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + private class UnCachedContent implements HttpContent + { + Resource _resource; + + UnCachedContent(Resource resource) + { + _resource=resource; + } + + /* ------------------------------------------------------------ */ + public Buffer getContentType() + { + return _mimeTypes.getMimeByExtension(_resource.toString()); + } + + /* ------------------------------------------------------------ */ + public Buffer getLastModified() + { + return null; + } + + /* ------------------------------------------------------------ */ + public Buffer getBuffer() + { + return null; + } + + /* ------------------------------------------------------------ */ + public long getContentLength() + { + return _resource.length(); + } + + /* ------------------------------------------------------------ */ + public InputStream getInputStream() throws IOException + { + return _resource.getInputStream(); + } + + /* ------------------------------------------------------------ */ + public Resource getResource() + { + return _resource; + } + + /* ------------------------------------------------------------ */ + public void release() + { + _resource.release(); + _resource=null; + } + + } +} diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ErrorPageErrorHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ErrorPageErrorHandler.java new file mode 100644 index 0000000000..a40bdab5a8 --- /dev/null +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ErrorPageErrorHandler.java @@ -0,0 +1,267 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlet; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpMethods; +import org.eclipse.jetty.server.Dispatcher; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.ErrorHandler; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.log.Log; + +/** Error Page Error Handler + * + * An ErrorHandler that maps exceptions and status codes to URIs for dispatch using + * the internal ERROR style of dispatch. + * + * + */ +public class ErrorPageErrorHandler extends ErrorHandler +{ + public final static String ERROR_PAGE="org.eclipse.jetty.server.error_page"; + + protected ServletContext _servletContext; + protected Map _errorPages; // code or exception to URL + protected List _errorPageList; // list of ErrorCode by range + + /* ------------------------------------------------------------ */ + /** + * @param context + */ + public ErrorPageErrorHandler() + {} + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.handler.ErrorHandler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int) + */ + public void handle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException + { + String method = request.getMethod(); + if(!method.equals(HttpMethods.GET) && !method.equals(HttpMethods.POST)) + { + HttpConnection.getCurrentConnection().getRequest().setHandled(true); + return; + } + if (_errorPages!=null) + { + String error_page= null; + Class exClass= (Class)request.getAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE); + + if (ServletException.class.equals(exClass)) + { + error_page= (String)_errorPages.get(exClass.getName()); + if (error_page == null) + { + Throwable th= (Throwable)request.getAttribute(RequestDispatcher.ERROR_EXCEPTION); + while (th instanceof ServletException) + th= ((ServletException)th).getRootCause(); + if (th != null) + exClass= th.getClass(); + } + } + + while (error_page == null && exClass != null ) + { + error_page= (String)_errorPages.get(exClass.getName()); + exClass= exClass.getSuperclass(); + } + + if (error_page == null) + { + // look for an exact code match + Integer code=(Integer)request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); + if (code!=null) + { + error_page= (String)_errorPages.get(TypeUtil.toString(code.intValue())); + + // if still not found + if ((error_page == null) && (_errorPageList != null)) + { + // look for an error code range match. + for (int i = 0; i < _errorPageList.size(); i++) + { + ErrorCodeRange errCode = (ErrorCodeRange) _errorPageList.get(i); + if (errCode.isInRange(code.intValue())) + { + error_page = errCode.getUri(); + break; + } + } + } + } + } + + if (error_page!=null) + { + String old_error_page=(String)request.getAttribute(ERROR_PAGE); + if (old_error_page==null || !old_error_page.equals(error_page)) + { + request.setAttribute(ERROR_PAGE, error_page); + + Dispatcher dispatcher = (Dispatcher) _servletContext.getRequestDispatcher(error_page); + try + { + if(dispatcher!=null) + { + dispatcher.error(request, response); + return; + } + else + { + Log.warn("No error page "+error_page); + } + } + catch (ServletException e) + { + Log.warn(Log.EXCEPTION, e); + return; + } + } + } + } + + super.handle(target, request, response); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the errorPages. + */ + public Map getErrorPages() + { + return _errorPages; + } + + /* ------------------------------------------------------------ */ + /** + * @param errorPages The errorPages to set. A map of Exception class name or error code as a string to URI string + */ + public void setErrorPages(Map errorPages) + { + _errorPages = errorPages; + } + + /* ------------------------------------------------------------ */ + /** Add Error Page mapping for an exception class + * This method is called as a result of an exception-type element in a web.xml file + * or may be called directly + * @param code The class (or superclass) of the matching exceptions + * @param uri The URI of the error page. + */ + public void addErrorPage(Class exception,String uri) + { + if (_errorPages==null) + _errorPages=new HashMap(); + _errorPages.put(exception.getName(),uri); + } + + /* ------------------------------------------------------------ */ + /** Add Error Page mapping for a status code. + * This method is called as a result of an error-code element in a web.xml file + * or may be called directly + * @param code The HTTP status code to match + * @param uri The URI of the error page. + */ + public void addErrorPage(int code,String uri) + { + if (_errorPages==null) + _errorPages=new HashMap(); + _errorPages.put(TypeUtil.toString(code),uri); + } + + /* ------------------------------------------------------------ */ + /** Add Error Page mapping for a status code range. + * This method is not available from web.xml and must be called + * directly. + * @param from The lowest matching status code + * @param to The highest matching status code + * @param uri The URI of the error page. + */ + public void addErrorPage(int from, int to, String uri) + { + if (_errorPageList == null) + { + _errorPageList = new ArrayList(); + } + _errorPageList.add(new ErrorCodeRange(from, to, uri)); + } + + /* ------------------------------------------------------------ */ + protected void doStart() throws Exception + { + super.doStart(); + _servletContext=ContextHandler.getCurrentContext(); + } + + /* ------------------------------------------------------------ */ + protected void doStop() throws Exception + { + // TODO Auto-generated method stub + super.doStop(); + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + private class ErrorCodeRange + { + private int _from; + private int _to; + private String _uri; + + ErrorCodeRange(int from, int to, String uri) + throws IllegalArgumentException + { + if (from > to) + throw new IllegalArgumentException("from>to"); + + _from = from; + _to = to; + _uri = uri; + } + + boolean isInRange(int value) + { + if ((value >= _from) && (value <= _to)) + { + return true; + } + + return false; + } + + String getUri() + { + return _uri; + } + + public String toString() + { + return "from: " + _from + ",to: " + _to + ",uri: " + _uri; + } + } +} diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java new file mode 100644 index 0000000000..b855763410 --- /dev/null +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterHolder.java @@ -0,0 +1,194 @@ +// ======================================================================== +// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlet; + +import java.util.EnumSet; + +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.FilterConfig; +import javax.servlet.FilterRegistration; + +import org.eclipse.jetty.util.log.Log; + +/* --------------------------------------------------------------------- */ +/** + * + */ +public class FilterHolder extends Holder +{ + /* ------------------------------------------------------------ */ + private transient Filter _filter; + private transient Config _config; + + /* ---------------------------------------------------------------- */ + /** Constructor for Serialization. + */ + public FilterHolder() + { + } + + /* ---------------------------------------------------------------- */ + /** Constructor for Serialization. + */ + public FilterHolder(Class filter) + { + super (filter); + } + + /* ---------------------------------------------------------------- */ + /** Constructor for existing filter. + */ + public FilterHolder(Filter filter) + { + setFilter(filter); + } + + /* ------------------------------------------------------------ */ + public void doStart() + throws Exception + { + super.doStart(); + + if (!javax.servlet.Filter.class + .isAssignableFrom(_class)) + { + String msg = _class+" is not a javax.servlet.Filter"; + super.stop(); + throw new IllegalStateException(msg); + } + + if (_filter==null) + _filter=(Filter)newInstance(); + + _filter = getServletHandler().customizeFilter(_filter); + + _config=new Config(); + _filter.init(_config); + } + + /* ------------------------------------------------------------ */ + public void doStop() + { + if (_filter!=null) + { + try + { + destroyInstance(_filter); + } + catch (Exception e) + { + Log.warn(e); + } + } + if (!_extInstance) + _filter=null; + + _config=null; + super.doStop(); + } + + /* ------------------------------------------------------------ */ + public void destroyInstance (Object o) + throws Exception + { + if (o==null) + return; + Filter f = (Filter)o; + f.destroy(); + getServletHandler().customizeFilterDestroy(f); + } + + /* ------------------------------------------------------------ */ + public synchronized void setFilter(Filter filter) + { + _filter=filter; + _extInstance=true; + setHeldClass(filter.getClass()); + if (getName()==null) + setName(filter.getClass().getName()); + } + + /* ------------------------------------------------------------ */ + public Filter getFilter() + { + return _filter; + } + + /* ------------------------------------------------------------ */ + public String toString() + { + return getName(); + } + + + public FilterRegistration getRegistration() + { + return new Registration(); + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + protected class Registration extends HolderRegistration implements FilterRegistration + { + /* ------------------------------------------------------------ */ + public boolean addMappingForServletNames(EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter, String... servletNames) + { + illegalStateIfContextStarted(); + FilterMapping mapping = new FilterMapping(); + mapping.setFilterHolder(FilterHolder.this); + mapping.setServletNames(servletNames); + mapping.setDispatcherTypes(dispatcherTypes); + if (isMatchAfter) + _servletHandler.addFilterMapping(mapping); + else + _servletHandler.prependFilterMapping(mapping); + + return true; + } + + public boolean addMappingForUrlPatterns(EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter, String... urlPatterns) + { + illegalStateIfContextStarted(); + FilterMapping mapping = new FilterMapping(); + mapping.setFilterHolder(FilterHolder.this); + mapping.setPathSpecs(urlPatterns); + mapping.setDispatcherTypes(dispatcherTypes); + if (isMatchAfter) + _servletHandler.addFilterMapping(mapping); + else + _servletHandler.prependFilterMapping(mapping); + return true; + } + + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + class Config extends HolderConfig implements FilterConfig + { + /* ------------------------------------------------------------ */ + public String getFilterName() + { + return _name; + } + } +} + + + + + diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterMapping.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterMapping.java new file mode 100644 index 0000000000..169067353d --- /dev/null +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterMapping.java @@ -0,0 +1,280 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlet; + +import java.util.Arrays; +import java.util.EnumSet; + +import javax.servlet.DispatcherType; + +import org.eclipse.jetty.http.PathMap; +import org.eclipse.jetty.server.Handler; + + +public class FilterMapping +{ + /** Dispatch types */ + public static final int DEFAULT=0; + public static final int REQUEST=1; + public static final int FORWARD=2; + public static final int INCLUDE=4; + public static final int ERROR=8; + public static final int ASYNC=16; + public static final int ALL=31; + + + /* ------------------------------------------------------------ */ + /** Dispatch type from name + */ + public static int dispatch(String type) + { + if ("request".equalsIgnoreCase(type)) + return REQUEST; + if ("forward".equalsIgnoreCase(type)) + return FORWARD; + if ("include".equalsIgnoreCase(type)) + return INCLUDE; + if ("error".equalsIgnoreCase(type)) + return ERROR; + if ("async".equalsIgnoreCase(type)) + return ASYNC; + throw new IllegalArgumentException(type); + } + + /* ------------------------------------------------------------ */ + /** Dispatch type from name + */ + public static int dispatch(DispatcherType type) + { + switch(type) + { + case REQUEST: + return REQUEST; + case ASYNC: + return ASYNC; + case FORWARD: + return FORWARD; + case INCLUDE: + return INCLUDE; + case ERROR: + return ERROR; + } + throw new IllegalArgumentException(type.toString()); + } + + + /* ------------------------------------------------------------ */ + /** Dispatch type from name + */ + public static DispatcherType dispatch(int type) + { + switch(type) + { + case REQUEST: + return DispatcherType.REQUEST; + case ASYNC: + return DispatcherType.ASYNC; + case FORWARD: + return DispatcherType.FORWARD; + case INCLUDE: + return DispatcherType.INCLUDE; + case ERROR: + return DispatcherType.ERROR; + } + throw new IllegalArgumentException(""+type); + } + + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + + + private int _dispatches=DEFAULT; + private String _filterName; + private transient FilterHolder _holder; + private String[] _pathSpecs; + private String[] _servletNames; + + /* ------------------------------------------------------------ */ + public FilterMapping() + {} + + /* ------------------------------------------------------------ */ + /** Check if this filter applies to a path. + * @param path The path to check or null to just check type + * @param type The type of request: __REQUEST,__FORWARD,__INCLUDE, __ASYNC or __ERROR. + * @return True if this filter applies + */ + boolean appliesTo(String path, int type) + { + if (appliesTo(type)) + { + for (int i=0;i<_pathSpecs.length;i++) + if (_pathSpecs[i]!=null && PathMap.match(_pathSpecs[i], path,true)) + return true; + } + + return false; + } + + /* ------------------------------------------------------------ */ + /** Check if this filter applies to a particular dispatch type. + * @param type The type of request: + * {@link Handler#REQUEST}, {@link Handler#FORWARD}, {@link Handler#INCLUDE} or {@link Handler#ERROR}. + * @return <code>true</code> if this filter applies + */ + boolean appliesTo(int type) + { + if (_dispatches==0) + return type==REQUEST || type==ASYNC && _holder.isAsyncSupported(); + return (_dispatches&type)!=0; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the dispatches. + */ + public int getDispatches() + { + return _dispatches; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the filterName. + */ + public String getFilterName() + { + return _filterName; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the holder. + */ + FilterHolder getFilterHolder() + { + return _holder; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the pathSpec. + */ + public String[] getPathSpecs() + { + return _pathSpecs; + } + + /* ------------------------------------------------------------ */ + public void setDispatcherTypes(EnumSet<DispatcherType> dispatcherTypes) + { + _dispatches=DEFAULT; + if (dispatcherTypes.contains(DispatcherType.ERROR)) + _dispatches|=ERROR; + if (dispatcherTypes.contains(DispatcherType.FORWARD)) + _dispatches|=FORWARD; + if (dispatcherTypes.contains(DispatcherType.INCLUDE)) + _dispatches|=INCLUDE; + if (dispatcherTypes.contains(DispatcherType.REQUEST)) + _dispatches|=REQUEST; + } + + /* ------------------------------------------------------------ */ + /** + * @param dispatches The dispatches to set. + * @see Handler#DEFAULT + * @see Handler#REQUEST + * @see Handler#ERROR + * @see Handler#FORWARD + * @see Handler#INCLUDE + */ + public void setDispatches(int dispatches) + { + _dispatches = dispatches; + } + + /* ------------------------------------------------------------ */ + /** + * @param filterName The filterName to set. + */ + public void setFilterName(String filterName) + { + _filterName = filterName; + } + + /* ------------------------------------------------------------ */ + /** + * @param holder The holder to set. + */ + void setFilterHolder(FilterHolder holder) + { + _holder = holder; + setFilterName(holder.getName()); + } + + /* ------------------------------------------------------------ */ + /** + * @param pathSpecs The Path specifications to which this filter should be mapped. + */ + public void setPathSpecs(String[] pathSpecs) + { + _pathSpecs = pathSpecs; + } + + /* ------------------------------------------------------------ */ + /** + * @param pathSpec The pathSpec to set. + */ + public void setPathSpec(String pathSpec) + { + _pathSpecs = new String[]{pathSpec}; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the servletName. + */ + public String[] getServletNames() + { + return _servletNames; + } + + /* ------------------------------------------------------------ */ + /** + * @param servletNames Maps the {@link #setFilterName(String) named filter} to multiple servlets + * @see #setServletName + */ + public void setServletNames(String[] servletNames) + { + _servletNames = servletNames; + } + + /* ------------------------------------------------------------ */ + /** + * @param servletName Maps the {@link #setFilterName(String) named filter} to a single servlet + * @see #setServletNames + */ + public void setServletName(String servletName) + { + _servletNames = new String[]{servletName}; + } + + /* ------------------------------------------------------------ */ + public String toString() + { + return "(F="+_filterName+","+(_pathSpecs==null?"[]":Arrays.asList(_pathSpecs).toString())+","+(_servletNames==null?"[]":Arrays.asList(_servletNames).toString())+","+_dispatches+")"; + } + +} diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Holder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Holder.java new file mode 100644 index 0000000000..b63267baf8 --- /dev/null +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Holder.java @@ -0,0 +1,379 @@ +// ======================================================================== +// Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlet; + +import java.io.Serializable; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.ServletContext; +import javax.servlet.UnavailableException; + +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.AttributesMap; +import org.eclipse.jetty.util.Loader; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.log.Log; + + +/* --------------------------------------------------------------------- */ +/** + * + */ +public class Holder extends AbstractLifeCycle implements Serializable +{ + protected transient Class _class; + protected String _className; + protected String _displayName; + protected Map _initParams; + protected boolean _extInstance; + protected boolean _asyncSupported; + protected AttributesMap _initAttributes; + + /* ---------------------------------------------------------------- */ + protected String _name; + protected ServletHandler _servletHandler; + + protected Holder() + {} + + /* ---------------------------------------------------------------- */ + protected Holder(Class held) + { + _class=held; + if (held!=null) + { + _className=held.getName(); + _name=held.getName()+"-"+this.hashCode(); + } + } + + /* ------------------------------------------------------------ */ + public void doStart() + throws Exception + { + //if no class already loaded and no classname, make servlet permanently unavailable + if (_class==null && (_className==null || _className.equals(""))) + throw new UnavailableException("No class for Servlet or Filter", -1); + + //try to load class + if (_class==null) + { + try + { + _class=Loader.loadClass(Holder.class, _className); + if(Log.isDebugEnabled())Log.debug("Holding {}",_class); + } + catch (Exception e) + { + Log.warn(e); + throw new UnavailableException(e.getMessage(), -1); + } + } + } + + /* ------------------------------------------------------------ */ + public void doStop() + { + if (!_extInstance) + _class=null; + } + + /* ------------------------------------------------------------ */ + public String getClassName() + { + return _className; + } + + /* ------------------------------------------------------------ */ + public Class getHeldClass() + { + return _class; + } + + /* ------------------------------------------------------------ */ + public String getDisplayName() + { + return _displayName; + } + + /* ---------------------------------------------------------------- */ + public String getInitParameter(String param) + { + if (_initParams==null) + return null; + return (String)_initParams.get(param); + } + + /* ------------------------------------------------------------ */ + public Enumeration getInitParameterNames() + { + if (_initParams==null) + return Collections.enumeration(Collections.EMPTY_LIST); + return Collections.enumeration(_initParams.keySet()); + } + + /* ---------------------------------------------------------------- */ + public Map getInitParameters() + { + return _initParams; + } + + /* ------------------------------------------------------------ */ + public String getName() + { + return _name; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the servletHandler. + */ + public ServletHandler getServletHandler() + { + return _servletHandler; + } + + /* ------------------------------------------------------------ */ + public synchronized Object newInstance() + throws InstantiationException, + IllegalAccessException + { + if (_class==null) + throw new InstantiationException("!"+_className); + return _class.newInstance(); + } + + /* ------------------------------------------------------------ */ + public void destroyInstance(Object instance) + throws Exception + { + } + + /* ------------------------------------------------------------ */ + /** + * @param className The className to set. + */ + public void setClassName(String className) + { + _className = className; + _class=null; + } + + /* ------------------------------------------------------------ */ + /** + * @param className The className to set. + */ + public void setHeldClass(Class held) + { + _class=held; + _className = held!=null?held.getName():null; + } + + /* ------------------------------------------------------------ */ + public void setDisplayName(String name) + { + _displayName=name; + } + + /* ------------------------------------------------------------ */ + public void setInitParameter(String param,String value) + { + if (_initParams==null) + _initParams=new HashMap(3); + _initParams.put(param,value); + } + + /* ---------------------------------------------------------------- */ + public void setInitParameters(Map map) + { + _initParams=map; + } + + /* ------------------------------------------------------------ */ + /** + * The name is a primary key for the held object. + * Ensure that the name is set BEFORE adding a Holder + * (eg ServletHolder or FilterHolder) to a ServletHandler. + * @param name The name to set. + */ + public void setName(String name) + { + _name = name; + } + + /* ------------------------------------------------------------ */ + /** + * @param servletHandler The {@link ServletHandler} that will handle requests dispatched to this servlet. + */ + public void setServletHandler(ServletHandler servletHandler) + { + _servletHandler = servletHandler; + } + + /* ------------------------------------------------------------ */ + public void setAsyncSupported(boolean suspendable) + { + _asyncSupported=suspendable; + } + + /* ------------------------------------------------------------ */ + public boolean isAsyncSupported() + { + return _asyncSupported; + } + + /* ------------------------------------------------------------ */ + public String toString() + { + return _name; + } + + /* ------------------------------------------------------------ */ + protected void illegalStateIfContextStarted() + { + if (_servletHandler!=null) + { + ContextHandler.Context context=(ContextHandler.Context)_servletHandler.getServletContext(); + if (context!=null && context.getContextHandler().isStarted()) + throw new IllegalStateException("Started"); + } + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + protected class HolderConfig + { + + /* -------------------------------------------------------- */ + public ServletContext getServletContext() + { + return _servletHandler.getServletContext(); + } + + /* -------------------------------------------------------- */ + public String getInitParameter(String param) + { + return Holder.this.getInitParameter(param); + } + + /* -------------------------------------------------------- */ + public Enumeration getInitParameterNames() + { + return Holder.this.getInitParameterNames(); + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletConfig#getInitAttribute(java.lang.String) + */ + public Object getInitAttribute(String name) + { + return (Holder.this._initAttributes==null)?null:Holder.this._initAttributes.getAttribute(name); + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletConfig#getInitAttributeNames() + */ + public Iterable<String> getInitAttributeNames() + { + if (Holder.this._initAttributes!=null) + return Holder.this._initAttributes.keySet(); + return Collections.emptySet(); + } + + } + + /* -------------------------------------------------------- */ + /* -------------------------------------------------------- */ + /* -------------------------------------------------------- */ + protected class HolderRegistration + { + public boolean setAsyncSupported(boolean isAsyncSupported) + { + illegalStateIfContextStarted(); + Holder.this.setAsyncSupported(isAsyncSupported); + return true; + } + + public boolean setDescription(String description) + { + return true; + } + + public boolean setInitParameter(String name, String value) + { + illegalStateIfContextStarted(); + if (Holder.this.getInitParameter(name)!=null) + return false; + Holder.this.setInitParameter(name,value); + return true; + } + + public boolean setInitParameters(Map<String, String> initParameters) + { + illegalStateIfContextStarted(); + for (String name : initParameters.keySet()) + if (Holder.this.getInitParameter(name)!=null) + return false; + Holder.this.setInitParameters(initParameters); + return true; + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletRegistration#setInitAttribute(java.lang.String, java.lang.Object) + */ + public boolean setInitAttribute(String name, Object value) + { + illegalStateIfContextStarted(); + if (_initAttributes==null) + _initAttributes=new AttributesMap(); + else if (_initAttributes.getAttribute(name)!=null) + return false; + _initAttributes.setAttribute(name,value); + return true; + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletRegistration#setInitAttributes(java.util.Map) + */ + public boolean setInitAttributes(Map<String, Object> initAttributes) + { + illegalStateIfContextStarted(); + if (_initAttributes==null) + _initAttributes=new AttributesMap(); + else + { + for (String name : initAttributes.keySet()) + if (_initAttributes.getAttribute(name)!=null) + return false; + } + for (String name : initAttributes.keySet()) + _initAttributes.setAttribute(name,initAttributes.get(name)); + + return true; + }; + } +} + + + + + diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java new file mode 100644 index 0000000000..2b0175f897 --- /dev/null +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Invoker.java @@ -0,0 +1,303 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlet; + +import java.io.IOException; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.UnavailableException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.Dispatcher; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.HandlerWrapper; +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.log.Log; + +/* ------------------------------------------------------------ */ +/** Dynamic Servlet Invoker. + * This servlet invokes anonymous servlets that have not been defined + * in the web.xml or by other means. The first element of the pathInfo + * of a request passed to the envoker is treated as a servlet name for + * an existing servlet, or as a class name of a new servlet. + * This servlet is normally mapped to /servlet/* + * This servlet support the following initParams: + * <PRE> + * nonContextServlets If false, the invoker can only load + * servlets from the contexts classloader. + * This is false by default and setting this + * to true may have security implications. + * + * verbose If true, log dynamic loads + * + * * All other parameters are copied to the + * each dynamic servlet as init parameters + * </PRE> + * @version $Id: Invoker.java 4780 2009-03-17 15:36:08Z jesse $ + * + */ +public class Invoker extends HttpServlet +{ + + private ContextHandler _contextHandler; + private ServletHandler _servletHandler; + private Map.Entry _invokerEntry; + private Map _parameters; + private boolean _nonContextServlets; + private boolean _verbose; + + /* ------------------------------------------------------------ */ + public void init() + { + ServletContext config=getServletContext(); + _contextHandler=((ContextHandler.Context)config).getContextHandler(); + + Handler handler=_contextHandler.getHandler(); + while (handler!=null && !(handler instanceof ServletHandler) && (handler instanceof HandlerWrapper)) + handler=((HandlerWrapper)handler).getHandler(); + _servletHandler = (ServletHandler)handler; + Enumeration e = getInitParameterNames(); + while(e.hasMoreElements()) + { + String param=(String)e.nextElement(); + String value=getInitParameter(param); + String lvalue=value.toLowerCase(); + if ("nonContextServlets".equals(param)) + { + _nonContextServlets=value.length()>0 && lvalue.startsWith("t"); + } + if ("verbose".equals(param)) + { + _verbose=value.length()>0 && lvalue.startsWith("t"); + } + else + { + if (_parameters==null) + _parameters=new HashMap(); + _parameters.put(param,value); + } + } + } + + /* ------------------------------------------------------------ */ + protected void service(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + // Get the requested path and info + boolean included=false; + String servlet_path=(String)request.getAttribute(Dispatcher.__INCLUDE_SERVLET_PATH); + if (servlet_path==null) + servlet_path=request.getServletPath(); + else + included=true; + String path_info = (String)request.getAttribute(Dispatcher.__INCLUDE_PATH_INFO); + if (path_info==null) + path_info=request.getPathInfo(); + + // Get the servlet class + String servlet = path_info; + if (servlet==null || servlet.length()<=1 ) + { + response.sendError(404); + return; + } + + + int i0=servlet.charAt(0)=='/'?1:0; + int i1=servlet.indexOf('/',i0); + servlet=i1<0?servlet.substring(i0):servlet.substring(i0,i1); + + // look for a named holder + ServletHolder[] holders = _servletHandler.getServlets(); + ServletHolder holder = getHolder (holders, servlet); + + if (holder!=null) + { + // Found a named servlet (from a user's web.xml file) so + // now we add a mapping for it + Log.debug("Adding servlet mapping for named servlet:"+servlet+":"+URIUtil.addPaths(servlet_path,servlet)+"/*"); + ServletMapping mapping = new ServletMapping(); + mapping.setServletName(servlet); + mapping.setPathSpec(URIUtil.addPaths(servlet_path,servlet)+"/*"); + _servletHandler.setServletMappings((ServletMapping[])LazyList.addToArray(_servletHandler.getServletMappings(), mapping, ServletMapping.class)); + } + else + { + // look for a class mapping + if (servlet.endsWith(".class")) + servlet=servlet.substring(0,servlet.length()-6); + if (servlet==null || servlet.length()==0) + { + response.sendError(404); + return; + } + + synchronized(_servletHandler) + { + // find the entry for the invoker (me) + _invokerEntry=_servletHandler.getHolderEntry(servlet_path); + + // Check for existing mapping (avoid threaded race). + String path=URIUtil.addPaths(servlet_path,servlet); + Map.Entry entry = _servletHandler.getHolderEntry(path); + + if (entry!=null && !entry.equals(_invokerEntry)) + { + // Use the holder + holder=(ServletHolder)entry.getValue(); + } + else + { + // Make a holder + Log.debug("Making new servlet="+servlet+" with path="+path+"/*"); + holder=_servletHandler.addServletWithMapping(servlet, path+"/*"); + + if (_parameters!=null) + holder.setInitParameters(_parameters); + + try {holder.start();} + catch (Exception e) + { + Log.debug(e); + throw new UnavailableException(e.toString()); + } + + // Check it is from an allowable classloader + if (!_nonContextServlets) + { + Object s=holder.getServlet(); + + if (_contextHandler.getClassLoader()!= + s.getClass().getClassLoader()) + { + try + { + holder.stop(); + } + catch (Exception e) + { + Log.ignore(e); + } + + Log.warn("Dynamic servlet "+s+ + " not loaded from context "+ + request.getContextPath()); + throw new UnavailableException("Not in context"); + } + } + + if (_verbose) + Log.debug("Dynamic load '"+servlet+"' at "+path); + } + } + } + + if (holder!=null) + { + final Request base_request=(request instanceof Request)?((Request)request):HttpConnection.getCurrentConnection().getRequest(); + holder.handle(base_request, + new InvokedRequest(request,included,servlet,servlet_path,path_info), + response); + } + else + { + Log.info("Can't find holder for servlet: "+servlet); + response.sendError(404); + } + + + } + + /* ------------------------------------------------------------ */ + class InvokedRequest extends HttpServletRequestWrapper + { + String _servletPath; + String _pathInfo; + boolean _included; + + /* ------------------------------------------------------------ */ + InvokedRequest(HttpServletRequest request, + boolean included, + String name, + String servletPath, + String pathInfo) + { + super(request); + _included=included; + _servletPath=URIUtil.addPaths(servletPath,name); + _pathInfo=pathInfo.substring(name.length()+1); + if (_pathInfo.length()==0) + _pathInfo=null; + } + + /* ------------------------------------------------------------ */ + public String getServletPath() + { + if (_included) + return super.getServletPath(); + return _servletPath; + } + + /* ------------------------------------------------------------ */ + public String getPathInfo() + { + if (_included) + return super.getPathInfo(); + return _pathInfo; + } + + /* ------------------------------------------------------------ */ + public Object getAttribute(String name) + { + if (_included) + { + if (name.equals(Dispatcher.__INCLUDE_REQUEST_URI)) + return URIUtil.addPaths(URIUtil.addPaths(getContextPath(),_servletPath),_pathInfo); + if (name.equals(Dispatcher.__INCLUDE_PATH_INFO)) + return _pathInfo; + if (name.equals(Dispatcher.__INCLUDE_SERVLET_PATH)) + return _servletPath; + } + return super.getAttribute(name); + } + } + + + private ServletHolder getHolder(ServletHolder[] holders, String servlet) + { + if (holders == null) + return null; + + ServletHolder holder = null; + for (int i=0; holder==null && i<holders.length; i++) + { + if (holders[i].getName().equals(servlet)) + { + holder = holders[i]; + } + } + return holder; + } +} diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/NIOResourceCache.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/NIOResourceCache.java new file mode 100644 index 0000000000..12c900bf26 --- /dev/null +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/NIOResourceCache.java @@ -0,0 +1,86 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +/** + * + */ +package org.eclipse.jetty.servlet; + +import java.io.IOException; +import java.io.InputStream; + +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.nio.DirectNIOBuffer; +import org.eclipse.jetty.io.nio.IndirectNIOBuffer; +import org.eclipse.jetty.io.nio.NIOBuffer; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.ResourceCache; +import org.eclipse.jetty.server.nio.NIOConnector; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.resource.Resource; + +class NIOResourceCache extends ResourceCache +{ + boolean _useFileMappedBuffer; + + /* ------------------------------------------------------------ */ + public NIOResourceCache(MimeTypes mimeTypes) + { + super(mimeTypes); + } + + /* ------------------------------------------------------------ */ + protected void fill(Content content) throws IOException + { + Buffer buffer=null; + Resource resource=content.getResource(); + long length=resource.length(); + + if (_useFileMappedBuffer && resource.getFile()!=null) + { + buffer = new DirectNIOBuffer(resource.getFile()); + } + else + { + InputStream is = resource.getInputStream(); + try + { + Connector connector = HttpConnection.getCurrentConnection().getConnector(); + buffer = ((NIOConnector)connector).getUseDirectBuffers()? + (NIOBuffer)new DirectNIOBuffer((int)length): + (NIOBuffer)new IndirectNIOBuffer((int)length); + } + catch(OutOfMemoryError e) + { + Log.warn(e.toString()); + Log.debug(e); + buffer = new IndirectNIOBuffer((int) length); + } + buffer.readFrom(is,(int)length); + is.close(); + } + content.setBuffer(buffer); + } + + public boolean isUseFileMappedBuffer() + { + return _useFileMappedBuffer; + } + + public void setUseFileMappedBuffer(boolean useFileMappedBuffer) + { + _useFileMappedBuffer = useFileMappedBuffer; + } +} diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/NoJspServlet.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/NoJspServlet.java new file mode 100644 index 0000000000..29c5f78247 --- /dev/null +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/NoJspServlet.java @@ -0,0 +1,35 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlet; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class NoJspServlet extends HttpServlet +{ + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException + { + response.sendError(500,"JSP support not configured"); + } + +} diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java new file mode 100644 index 0000000000..3b10330cd1 --- /dev/null +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java @@ -0,0 +1,502 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlet; + +import java.util.EnumSet; + +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.FilterRegistration; +import javax.servlet.RequestDispatcher; +import javax.servlet.Servlet; +import javax.servlet.ServletContext; +import javax.servlet.ServletRegistration; +import javax.servlet.SessionTrackingMode; + +import org.eclipse.jetty.security.SecurityHandler; +import org.eclipse.jetty.server.Dispatcher; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HandlerContainer; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.ErrorHandler; +import org.eclipse.jetty.server.session.SessionHandler; +import org.eclipse.jetty.util.log.Log; + + +/* ------------------------------------------------------------ */ +/** Servlet Context. + * This extension to the ContextHandler allows for + * simple construction of a context with ServletHandler and optionally + * session and security handlers, et.<pre> + * new ServletContext("/context",Context.SESSIONS|Context.NO_SECURITY); + * </pre> + * <p/> + * This class should have been called ServletContext, but this would have + * cause confusion with {@link ServletContext}. + */ +public class ServletContextHandler extends ContextHandler +{ + public final static int SESSIONS=1; + public final static int SECURITY=2; + public final static int NO_SESSIONS=0; + public final static int NO_SECURITY=0; + + protected Class<? extends SecurityHandler> _defaultSecurityHandlerClass=org.eclipse.jetty.security.ConstraintSecurityHandler.class; + protected SessionHandler _sessionHandler; + protected SecurityHandler _securityHandler; + protected ServletHandler _servletHandler; + protected int _options; + + /* ------------------------------------------------------------ */ + public ServletContextHandler() + { + this(null,null,null,null,null); + } + + /* ------------------------------------------------------------ */ + public ServletContextHandler(int options) + { + this(null,null,options); + } + + /* ------------------------------------------------------------ */ + public ServletContextHandler(HandlerContainer parent, String contextPath) + { + this(parent,contextPath,null,null,null,null); + } + + /* ------------------------------------------------------------ */ + public ServletContextHandler(HandlerContainer parent, String contextPath, int options) + { + this(parent,contextPath,null,null,null,null); + _options=options; + } + + /* ------------------------------------------------------------ */ + public ServletContextHandler(HandlerContainer parent, String contextPath, boolean sessions, boolean security) + { + this(parent,contextPath,(sessions?SESSIONS:0)|(security?SECURITY:0)); + } + + /* ------------------------------------------------------------ */ + public ServletContextHandler(HandlerContainer parent, SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler) + { + this(parent,null,sessionHandler,securityHandler,servletHandler,errorHandler); + } + + /* ------------------------------------------------------------ */ + public ServletContextHandler(HandlerContainer parent, String contextPath, SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler) + { + super((ContextHandler.Context)null); + _scontext = new Context(); + _sessionHandler = sessionHandler; + _securityHandler = securityHandler; + _servletHandler = servletHandler; + + if (errorHandler!=null) + setErrorHandler(errorHandler); + + if (contextPath!=null) + setContextPath(contextPath); + + if (parent!=null) + parent.addHandler(this); + } + + + /* ------------------------------------------------------------ */ + /** Get the defaultSecurityHandlerClass. + * @return the defaultSecurityHandlerClass + */ + public Class<? extends SecurityHandler> getDefaultSecurityHandlerClass() + { + return _defaultSecurityHandlerClass; + } + + /* ------------------------------------------------------------ */ + /** Set the defaultSecurityHandlerClass. + * @param defaultSecurityHandlerClass the defaultSecurityHandlerClass to set + */ + public void setDefaultSecurityHandlerClass(Class<? extends SecurityHandler> defaultSecurityHandlerClass) + { + _defaultSecurityHandlerClass = defaultSecurityHandlerClass; + } + + /* ------------------------------------------------------------ */ + protected SessionHandler newSessionHandler() + { + return new SessionHandler(); + } + + /* ------------------------------------------------------------ */ + protected SecurityHandler newSecurityHandler() + { + try + { + return (SecurityHandler)_defaultSecurityHandlerClass.newInstance(); + } + catch(Exception e) + { + throw new IllegalStateException(e); + } + } + + /* ------------------------------------------------------------ */ + protected ServletHandler newServletHandler() + { + return new ServletHandler(); + } + + /* ------------------------------------------------------------ */ + /** + * Finish constructing handlers and link them together. + * + * @see org.eclipse.jetty.server.handler.ContextHandler#startContext() + */ + protected void startContext() throws Exception + { + // force creation of missing handlers. + getSessionHandler(); + getSecurityHandler(); + getServletHandler(); + + Handler handler = _servletHandler; + if (_securityHandler!=null) + { + _securityHandler.setHandler(handler); + handler=_securityHandler; + } + + if (_sessionHandler!=null) + { + _sessionHandler.setHandler(handler); + handler=_sessionHandler; + } + + setHandler(handler); + + super.startContext(); + + // OK to Initialize servlet handler now + if (_servletHandler != null && _servletHandler.isStarted()) + _servletHandler.initialize(); + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the securityHandler. + */ + public SecurityHandler getSecurityHandler() + { + if (_securityHandler==null && (_options&SECURITY)!=0 && !isStarted()) + _securityHandler=newSecurityHandler(); + + return _securityHandler; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the servletHandler. + */ + public ServletHandler getServletHandler() + { + if (_servletHandler==null && !isStarted()) + _servletHandler=newServletHandler(); + return _servletHandler; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the sessionHandler. + */ + public SessionHandler getSessionHandler() + { + if (_sessionHandler==null && (_options&SESSIONS)!=0 && !isStarted()) + _sessionHandler=newSessionHandler(); + return _sessionHandler; + } + + /* ------------------------------------------------------------ */ + /** conveniance method to add a servlet. + */ + public ServletHolder addServlet(String className,String pathSpec) + { + return getServletHandler().addServletWithMapping(className, pathSpec); + } + + /* ------------------------------------------------------------ */ + /** conveniance method to add a servlet. + */ + public ServletHolder addServlet(Class<? extends Servlet> servlet,String pathSpec) + { + return getServletHandler().addServletWithMapping(servlet.getName(), pathSpec); + } + + /* ------------------------------------------------------------ */ + /** conveniance method to add a servlet. + */ + public void addServlet(ServletHolder servlet,String pathSpec) + { + getServletHandler().addServletWithMapping(servlet, pathSpec); + } + + /* ------------------------------------------------------------ */ + /** conveniance method to add a filter + */ + public void addFilter(FilterHolder holder,String pathSpec,int dispatches) + { + getServletHandler().addFilterWithMapping(holder,pathSpec,dispatches); + } + + /* ------------------------------------------------------------ */ + /** convenience method to add a filter + */ + public FilterHolder addFilter(Class<? extends Filter> filterClass,String pathSpec,int dispatches) + { + return getServletHandler().addFilterWithMapping(filterClass,pathSpec,dispatches); + } + + /* ------------------------------------------------------------ */ + /** convenience method to add a filter + */ + public FilterHolder addFilter(String filterClass,String pathSpec,int dispatches) + { + return getServletHandler().addFilterWithMapping(filterClass,pathSpec,dispatches); + } + + /* ------------------------------------------------------------ */ + /** + * @param sessionHandler The sessionHandler to set. + */ + public void setSessionHandler(SessionHandler sessionHandler) + { + if (isStarted()) + throw new IllegalStateException("STARTED"); + + _sessionHandler = sessionHandler; + } + + /* ------------------------------------------------------------ */ + /** + * @param securityHandler The {@link org.eclipse.jetty.server.handler.SecurityHandler} to set on this context. + */ + public void setSecurityHandler(SecurityHandler securityHandler) + { + if (isStarted()) + throw new IllegalStateException("STARTED"); + + _securityHandler = securityHandler; + } + + /* ------------------------------------------------------------ */ + /** + * @param servletHandler The servletHandler to set. + */ + public void setServletHandler(ServletHandler servletHandler) + { + if (isStarted()) + throw new IllegalStateException("STARTED"); + + _servletHandler = servletHandler; + } + + /* ------------------------------------------------------------ */ + public class Context extends ContextHandler.Context + { + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletContext#getNamedDispatcher(java.lang.String) + */ + public RequestDispatcher getNamedDispatcher(String name) + { + ContextHandler context=org.eclipse.jetty.servlet.ServletContextHandler.this; + if (_servletHandler==null || _servletHandler.getServlet(name)==null) + return null; + return new Dispatcher(context, name); + } + + + /* ------------------------------------------------------------ */ + public void addFilterMappingForServletNames(String filterName, EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter, String... servletNames) + { + if (isStarted()) + throw new IllegalStateException(); + ServletHandler handler = ServletContextHandler.this.getServletHandler(); + FilterMapping mapping = new FilterMapping(); + mapping.setFilterName(filterName); + mapping.setServletNames(servletNames); + mapping.setDispatcherTypes(dispatcherTypes); + handler.addFilterMapping(mapping); + } + + /* ------------------------------------------------------------ */ + public void addServletMapping(String servletName, String[] urlPatterns) + { + if (isStarted()) + throw new IllegalStateException(); + ServletHandler handler = ServletContextHandler.this.getServletHandler(); + ServletHolder holder= handler.newServletHolder(); + holder.setName(servletName); + handler.addServlet(holder); + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#addFilter(java.lang.String, java.lang.Class) + */ + public FilterRegistration addFilter(String filterName, Class<? extends Filter> filterClass) + { + if (isStarted()) + throw new IllegalStateException(); + + final ServletHandler handler = ServletContextHandler.this.getServletHandler(); + final FilterHolder holder= handler.newFilterHolder(); + holder.setName(filterName); + holder.setHeldClass(filterClass); + handler.addFilter(holder); + return holder.getRegistration(); + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#addFilter(java.lang.String, java.lang.String) + */ + public FilterRegistration addFilter(String filterName, String className) + { + if (isStarted()) + throw new IllegalStateException(); + + final ServletHandler handler = ServletContextHandler.this.getServletHandler(); + final FilterHolder holder= handler.newFilterHolder(); + holder.setName(filterName); + holder.setClassName(className); + handler.addFilter(holder); + return holder.getRegistration(); + } + + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#addFilter(java.lang.String, javax.servlet.Filter) + */ + public FilterRegistration addFilter(String filterName, Filter filter) + { + if (isStarted()) + throw new IllegalStateException(); + + final ServletHandler handler = ServletContextHandler.this.getServletHandler(); + final FilterHolder holder= handler.newFilterHolder(); + holder.setName(filterName); + holder.setFilter(filter); + handler.addFilter(holder); + return holder.getRegistration(); + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#addServlet(java.lang.String, java.lang.Class) + */ + public ServletRegistration addServlet(String servletName, Class<? extends Servlet> servletClass) + { + if (!isStarting()) + throw new IllegalStateException(); + + final ServletHandler handler = ServletContextHandler.this.getServletHandler(); + final ServletHolder holder= handler.newServletHolder(); + holder.setName(servletName); + holder.setHeldClass(servletClass); + handler.addServlet(holder); + return holder.getRegistration(); + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#addServlet(java.lang.String, java.lang.String) + */ + public ServletRegistration addServlet(String servletName, String className) + { + if (!isStarting()) + throw new IllegalStateException(); + + final ServletHandler handler = ServletContextHandler.this.getServletHandler(); + final ServletHolder holder= handler.newServletHolder(); + holder.setName(servletName); + holder.setClassName(className); + handler.addServlet(holder); + return holder.getRegistration(); + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#addServlet(java.lang.String, javax.servlet.Servlet) + */ + public ServletRegistration addServlet(String servletName, Servlet servlet) + { + if (!isStarting()) + throw new IllegalStateException(); + + final ServletHandler handler = ServletContextHandler.this.getServletHandler(); + final ServletHolder holder= handler.newServletHolder(); + holder.setName(servletName); + holder.setServlet(servlet); + handler.addServlet(holder); + return holder.getRegistration(); + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#findFilterRegistration(java.lang.String) + */ + public FilterRegistration findFilterRegistration(String filterName) + { + final ServletHandler handler = ServletContextHandler.this.getServletHandler(); + final FilterHolder holder=handler.getFilter(filterName); + return (holder==null)?null:holder.getRegistration(); + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#findServletRegistration(java.lang.String) + */ + public ServletRegistration findServletRegistration(String servletName) + { + final ServletHandler handler = ServletContextHandler.this.getServletHandler(); + final ServletHolder holder=handler.getServlet(servletName); + return (holder==null)?null:holder.getRegistration(); + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#getDefaultSessionTrackingModes() + */ + public EnumSet<SessionTrackingMode> getDefaultSessionTrackingModes() + { + return SessionHandler.DEFAULT_TRACKING; + } + + /* ------------------------------------------------------------ */ + /** + * @see javax.servlet.ServletContext#getEffectiveSessionTrackingModes() + */ + public EnumSet<SessionTrackingMode> getEffectiveSessionTrackingModes() + { + Log.warn("Not Implemented"); + return null; + } + + + + } +} diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java new file mode 100644 index 0000000000..6945e29ada --- /dev/null +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java @@ -0,0 +1,1370 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlet; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.RequestDispatcher; +import javax.servlet.Servlet; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.ServletResponse; +import javax.servlet.UnavailableException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.PathMap; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.io.HttpException; +import org.eclipse.jetty.security.IdentityService; +import org.eclipse.jetty.security.SecurityHandler; +import org.eclipse.jetty.server.Dispatcher; +import org.eclipse.jetty.server.HttpConnection; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.RetryRequest; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.UserIdentity; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.LazyList; +import org.eclipse.jetty.util.MultiException; +import org.eclipse.jetty.util.MultiMap; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.log.Log; + + +/* --------------------------------------------------------------------- */ +/** Servlet HttpHandler. + * This handler maps requests to servlets that implement the + * javax.servlet.http.HttpServlet API. + * <P> + * This handler does not implement the full J2EE features and is intended to + * be used when a full web application is not required. Specifically filters + * and request wrapping are not supported. + * + * Unless run as part of a {@link ServletContextHandler} or derivative, the {@link #initialize()} + * method must be called manually after start(). + * + * @see org.eclipse.jetty.webapp.WebAppContext + * + */ +public class ServletHandler extends AbstractHandler +{ + /* ------------------------------------------------------------ */ + public static final String __DEFAULT_SERVLET="default"; + + /* ------------------------------------------------------------ */ + private ContextHandler _contextHandler; + private ContextHandler.Context _servletContext; + private FilterHolder[] _filters; + private FilterMapping[] _filterMappings; + private boolean _filterChainsCached=true; + private int _maxFilterChainsCacheSize=1000; + private boolean _startWithUnavailable=true; + private IdentityService<UserIdentity,?> _identityService; + + private ServletHolder[] _servlets; + private ServletMapping[] _servletMappings; + + private transient Map<String,FilterHolder> _filterNameMap= new HashMap<String,FilterHolder>(); + private transient List<FilterMapping> _filterPathMappings; + private transient MultiMap<String> _filterNameMappings; + + private transient Map<String,ServletHolder> _servletNameMap=new HashMap(); + private transient PathMap _servletPathMap; + + protected transient ConcurrentHashMap _chainCache[]; + + + /* ------------------------------------------------------------ */ + /** Constructor. + */ + public ServletHandler() + { + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.handler.AbstractHandler#setServer(org.eclipse.jetty.server.Server) + */ + public void setServer(Server server) + { + if (getServer()!=null && getServer()!=server) + { + getServer().getContainer().update(this, _filters, null, "filter",true); + getServer().getContainer().update(this, _filterMappings, null, "filterMapping",true); + getServer().getContainer().update(this, _servlets, null, "servlet",true); + getServer().getContainer().update(this, _servletMappings, null, "servletMapping",true); + } + if (server!=null && getServer()!=server) + { + server.getContainer().update(this, null, _filters, "filter",true); + server.getContainer().update(this, null, _filterMappings, "filterMapping",true); + server.getContainer().update(this, null, _servlets, "servlet",true); + server.getContainer().update(this, null, _servletMappings, "servletMapping",true); + } + super.setServer(server); + } + + /* ----------------------------------------------------------------- */ + protected synchronized void doStart() + throws Exception + { + _servletContext=ContextHandler.getCurrentContext(); + _contextHandler=_servletContext==null?null:_servletContext.getContextHandler(); + + if (_contextHandler!=null) + { + SecurityHandler security_handler = (SecurityHandler)_contextHandler.getChildHandlerByClass(SecurityHandler.class); + if (security_handler!=null) + _identityService=(IdentityService<UserIdentity,?>)security_handler.getIdentityService(); + } + + updateNameMappings(); + updateMappings(); + + if(_filterChainsCached) + _chainCache= new ConcurrentHashMap[]{null,new ConcurrentHashMap(),new ConcurrentHashMap(),null,new ConcurrentHashMap(),null,null,null,new ConcurrentHashMap(),null,null,null,null,null,null,null,new ConcurrentHashMap()}; + + super.doStart(); + + if (_contextHandler==null || !(_contextHandler instanceof ServletContextHandler)) + initialize(); + } + + /* ----------------------------------------------------------------- */ + protected synchronized void doStop() + throws Exception + { + super.doStop(); + + // Stop filters + if (_filters!=null) + { + for (int i=_filters.length; i-->0;) + { + try { _filters[i].stop(); }catch(Exception e){Log.warn(Log.EXCEPTION,e);} + } + } + + // Stop servlets + if (_servlets!=null) + { + for (int i=_servlets.length; i-->0;) + { + try { _servlets[i].stop(); }catch(Exception e){Log.warn(Log.EXCEPTION,e);} + } + } + + _filterPathMappings=null; + _filterNameMappings=null; + + _servletPathMap=null; + _chainCache=null; + } + + /* ------------------------------------------------------------ */ + IdentityService getIdentityService() + { + return _identityService; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the contextLog. + */ + public Object getContextLog() + { + return null; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the filterMappings. + */ + public FilterMapping[] getFilterMappings() + { + return _filterMappings; + } + + /* ------------------------------------------------------------ */ + /** Get Filters. + * @return Array of defined servlets + */ + public FilterHolder[] getFilters() + { + return _filters; + } + + /* ------------------------------------------------------------ */ + /** ServletHolder matching path. + * @param pathInContext Path within _context. + * @return PathMap Entries pathspec to ServletHolder + */ + public PathMap.Entry getHolderEntry(String pathInContext) + { + if (_servletPathMap==null) + return null; + return _servletPathMap.getMatch(pathInContext); + } + + /* ------------------------------------------------------------ */ + /** + * @param uriInContext uri to get dispatcher for + * @return A {@link RequestDispatcher dispatcher} wrapping the resource at <code>uriInContext</code>, + * or <code>null</code> if the specified uri cannot be dispatched to. + */ + public RequestDispatcher getRequestDispatcher(String uriInContext) + { + if (uriInContext == null) + return null; + + if (!uriInContext.startsWith("/")) + return null; + + try + { + String query=null; + int q; + if ((q=uriInContext.indexOf('?'))>0) + { + query=uriInContext.substring(q+1); + uriInContext=uriInContext.substring(0,q); + } + if ((q=uriInContext.indexOf(';'))>0) + uriInContext=uriInContext.substring(0,q); + + String pathInContext=URIUtil.canonicalPath(URIUtil.decodePath(uriInContext)); + String uri=URIUtil.addPaths(_contextHandler.getContextPath(), uriInContext); + return new Dispatcher(_contextHandler, uri, pathInContext, query); + } + catch(Exception e) + { + Log.ignore(e); + } + return null; + } + + /* ------------------------------------------------------------ */ + public ServletContext getServletContext() + { + return _servletContext; + } + /* ------------------------------------------------------------ */ + /** + * @return Returns the servletMappings. + */ + public ServletMapping[] getServletMappings() + { + return _servletMappings; + } + + /* ------------------------------------------------------------ */ + /** Get Servlets. + * @return Array of defined servlets + */ + public ServletHolder[] getServlets() + { + return _servlets; + } + + /* ------------------------------------------------------------ */ + public ServletHolder getServlet(String name) + { + return (ServletHolder)_servletNameMap.get(name); + } + + /* ------------------------------------------------------------ */ + /* + * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int) + */ + public void handle(String target, HttpServletRequest request,HttpServletResponse response) + throws IOException, ServletException + { + if (!isStarted()) + return; + + // Get the base requests + final Request base_request=(request instanceof Request)?((Request)request):HttpConnection.getCurrentConnection().getRequest(); + final String old_servlet_name=base_request.getServletName(); + final String old_servlet_path=base_request.getServletPath(); + final String old_path_info=base_request.getPathInfo(); + UserIdentity scoped_identity = null; + + DispatcherType type = request.getDispatcherType(); + Object request_listeners=null; + ServletRequestEvent request_event=null; + ServletHolder servlet_holder=null; + FilterChain chain=null; + UserIdentity old_identity=null; + + // find the servlet + if (target.startsWith("/")) + { + // Look for the servlet by path + PathMap.Entry entry=getHolderEntry(target); + if (entry!=null) + { + servlet_holder=(ServletHolder)entry.getValue(); + + if(Log.isDebugEnabled())Log.debug("servlet="+servlet_holder); + + String servlet_path_spec=(String)entry.getKey(); + String servlet_path=entry.getMapped()!=null?entry.getMapped():PathMap.pathMatch(servlet_path_spec,target); + String path_info=PathMap.pathInfo(servlet_path_spec,target); + + if (DispatcherType.INCLUDE.equals(type)) + { + base_request.setAttribute(Dispatcher.__INCLUDE_SERVLET_PATH,servlet_path); + base_request.setAttribute(Dispatcher.__INCLUDE_PATH_INFO, path_info); + } + else + { + base_request.setServletPath(servlet_path); + base_request.setPathInfo(path_info); + } + + if (servlet_holder!=null && _filterMappings!=null && _filterMappings.length>0) + chain=getFilterChain(base_request, target, servlet_holder); + } + } + else + { + // look for a servlet by name! + servlet_holder=(ServletHolder)_servletNameMap.get(target); + if (servlet_holder!=null) + { + if (_filterMappings!=null && _filterMappings.length>0) + { + chain=getFilterChain(base_request, null,servlet_holder); + } + } + } + + if (Log.isDebugEnabled()) + { + Log.debug("chain="+chain); + Log.debug("servlet holder="+servlet_holder); + } + + try + { + // Do the filter/handling thang + if (servlet_holder==null) + { + notFound(request, response); + } + else + { + base_request.setServletName(servlet_holder.getName()); + if (_identityService!=null) + { + old_identity=base_request.getUserIdentity(); + scoped_identity=_identityService.associate(old_identity,servlet_holder); + base_request.setUserIdentity(scoped_identity); + } + + // Handle context listeners + request_listeners = base_request.takeRequestListeners(); + if (request_listeners!=null) + { + request_event = new ServletRequestEvent(getServletContext(),request); + final int s=LazyList.size(request_listeners); + for(int i=0;i<s;i++) + { + final ServletRequestListener listener = (ServletRequestListener)LazyList.get(request_listeners,i); + listener.requestInitialized(request_event); + } + } + + base_request.setHandled(true); + + if (chain!=null) + chain.doFilter(request, response); + else + servlet_holder.handle(base_request,request,response); + } + } + catch(RetryRequest e) + { + throw e; + } + catch(EofException e) + { + throw e; + } + catch(Exception e) + { + if (!(DispatcherType.REQUEST.equals(type) || DispatcherType.ASYNC.equals(type))) + { + if (e instanceof IOException) + throw (IOException)e; + if (e instanceof RuntimeException) + throw (RuntimeException)e; + if (e instanceof ServletException) + throw (ServletException)e; + } + + + // unwrap cause + Throwable th=e; + if (th instanceof UnavailableException) + { + Log.debug(th); + } + else if (th instanceof ServletException) + { + Log.debug(th); + Throwable cause=((ServletException)th).getRootCause(); + if (cause!=th && cause!=null) + th=cause; + } + + // handle or log exception + if (th instanceof RetryRequest) + { + base_request.setHandled(false); + throw (RetryRequest)th; + } + else if (th instanceof HttpException) + { + throw (HttpException)th; + } + else if (Log.isDebugEnabled()) + { + Log.warn(request.getRequestURI(), th); + Log.debug(request.toString()); + } + else if (th instanceof IOException || th instanceof UnavailableException) + { + Log.warn(request.getRequestURI()+": "+th); + } + else + { + Log.warn(request.getRequestURI(),th); + } + + // TODO httpResponse.getHttpConnection().forceClose(); + if (!response.isCommitted()) + { + request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,th.getClass()); + request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,th); + if (th instanceof UnavailableException) + { + UnavailableException ue = (UnavailableException)th; + if (ue.isPermanent()) + response.sendError(HttpServletResponse.SC_NOT_FOUND,th.getMessage()); + else + response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,th.getMessage()); + } + else + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,th.getMessage()); + } + else + if(Log.isDebugEnabled())Log.debug("Response already committed for handling "+th); + } + catch(Error e) + { + if (!(DispatcherType.REQUEST.equals(type) || DispatcherType.ASYNC.equals(type))) + throw e; + Log.warn("Error for "+request.getRequestURI(),e); + if(Log.isDebugEnabled())Log.debug(request.toString()); + + // TODO httpResponse.getHttpConnection().forceClose(); + if (!response.isCommitted()) + { + request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,e.getClass()); + request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,e); + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,e.getMessage()); + } + else + if(Log.isDebugEnabled())Log.debug("Response already committed for handling ",e); + } + finally + { + if (request_listeners!=null) + { + for(int i=LazyList.size(request_listeners);i-->0;) + { + final ServletRequestListener listener = (ServletRequestListener)LazyList.get(request_listeners,i); + listener.requestDestroyed(request_event); + } + } + + if (scoped_identity!=null) + { + _identityService.disassociate(scoped_identity); + base_request.setUserIdentity(old_identity); + } + base_request.setServletName(old_servlet_name); + + if (!(DispatcherType.INCLUDE.equals(type))) + { + base_request.setServletPath(old_servlet_path); + base_request.setPathInfo(old_path_info); + } + } + } + + /* ------------------------------------------------------------ */ + private FilterChain getFilterChain(Request baseRequest, String pathInContext, ServletHolder servletHolder) + { + String key=pathInContext==null?servletHolder.getName():pathInContext; + int dispatch = FilterMapping.dispatch(baseRequest.getDispatcherType()); + + if (_filterChainsCached && _chainCache!=null) + { + FilterChain chain = (FilterChain)_chainCache[dispatch].get(key); + if (chain!=null) + return chain; + } + + // Build list of filters + Object filters= null; + // Path filters + if (pathInContext!=null && _filterPathMappings!=null) + { + for (int i= 0; i < _filterPathMappings.size(); i++) + { + FilterMapping mapping = (FilterMapping)_filterPathMappings.get(i); + if (mapping.appliesTo(pathInContext, dispatch)) + filters= LazyList.add(filters, mapping.getFilterHolder()); + } + } + + // Servlet name filters + if (servletHolder != null && _filterNameMappings!=null && _filterNameMappings.size() > 0) + { + // Servlet name filters + if (_filterNameMappings.size() > 0) + { + Object o= _filterNameMappings.get(servletHolder.getName()); + for (int i=0; i<LazyList.size(o);i++) + { + FilterMapping mapping = (FilterMapping)LazyList.get(o,i); + if (mapping.appliesTo(dispatch)) + filters=LazyList.add(filters,mapping.getFilterHolder()); + } + + o= _filterNameMappings.get("*"); + for (int i=0; i<LazyList.size(o);i++) + { + FilterMapping mapping = (FilterMapping)LazyList.get(o,i); + if (mapping.appliesTo(dispatch)) + filters=LazyList.add(filters,mapping.getFilterHolder()); + } + } + } + + if (filters==null) + return null; + + FilterChain chain = null; + if (_filterChainsCached) + { + if (LazyList.size(filters) > 0) + chain= new CachedChain(filters, servletHolder); + if (_maxFilterChainsCacheSize>0 && _chainCache[dispatch].size()>_maxFilterChainsCacheSize) + _chainCache[dispatch].clear(); + _chainCache[dispatch].put(key,chain); + } + else if (LazyList.size(filters) > 0) + chain = new Chain(baseRequest,filters, servletHolder); + + return chain; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the initializeAtStart. + * @deprecated + */ + public boolean isInitializeAtStart() + { + return false; + } + + /* ------------------------------------------------------------ */ + /** + * @param initializeAtStart The initializeAtStart to set. + * @deprecated + */ + public void setInitializeAtStart(boolean initializeAtStart) + { + } + + /* ------------------------------------------------------------ */ + /** + * @return true if the handler is started and there are no unavailable servlets + */ + public boolean isAvailable() + { + if (!isStarted()) + return false; + ServletHolder[] holders = getServlets(); + for (int i=0;i<holders.length;i++) + { + ServletHolder holder = holders[i]; + if (holder!=null && !holder.isAvailable()) + return false; + } + return true; + } + + /* ------------------------------------------------------------ */ + /** + * @param start True if this handler will start with unavailable servlets + */ + public void setStartWithUnavailable(boolean start) + { + _startWithUnavailable=start; + } + + /* ------------------------------------------------------------ */ + /** + * @return True if this handler will start with unavailable servlets + */ + public boolean isStartWithUnavailable() + { + return _startWithUnavailable; + } + + + + /* ------------------------------------------------------------ */ + /** Initialize filters and load-on-startup servlets. + * Called automatically from start if autoInitializeServlet is true. + */ + public void initialize() + throws Exception + { + MultiException mx = new MultiException(); + + // Start filters + if (_filters!=null) + { + for (int i=0;i<_filters.length; i++) + _filters[i].start(); + } + + if (_servlets!=null) + { + // Sort and Initialize servlets + ServletHolder[] servlets = (ServletHolder[])_servlets.clone(); + Arrays.sort(servlets); + for (int i=0; i<servlets.length; i++) + { + try + { + if (servlets[i].getClassName()==null && servlets[i].getForcedPath()!=null) + { + ServletHolder forced_holder = (ServletHolder)_servletPathMap.match(servlets[i].getForcedPath()); + if (forced_holder==null || forced_holder.getClassName()==null) + { + mx.add(new IllegalStateException("No forced path servlet for "+servlets[i].getForcedPath())); + continue; + } + servlets[i].setClassName(forced_holder.getClassName()); + } + + servlets[i].start(); + } + catch(Throwable e) + { + Log.debug(Log.EXCEPTION,e); + mx.add(e); + } + } + mx.ifExceptionThrow(); + } + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the filterChainsCached. + */ + public boolean isFilterChainsCached() + { + return _filterChainsCached; + } + + /* ------------------------------------------------------------ */ + /** + * see also newServletHolder(Class) + */ + public ServletHolder newServletHolder() + { + return new ServletHolder(); + } + + /* ------------------------------------------------------------ */ + public ServletHolder newServletHolder(Class servlet) + { + return new ServletHolder(servlet); + } + + /* ------------------------------------------------------------ */ + /** conveniance method to add a servlet. + * @return The servlet holder. + */ + public ServletHolder addServletWithMapping (String className,String pathSpec) + { + ServletHolder holder = newServletHolder(null); + holder.setName(className+"-"+holder.hashCode()); + holder.setClassName(className); + + addServletWithMapping(holder,pathSpec); + + return holder; + } + + /* ------------------------------------------------------------ */ + /** conveniance method to add a servlet. + * @return The servlet holder. + */ + public ServletHolder addServletWithMapping (Class<? extends Servlet> servlet,String pathSpec) + { + ServletHolder holder = newServletHolder(servlet); + setServlets((ServletHolder[])LazyList.addToArray(getServlets(), holder, ServletHolder.class)); + + addServletWithMapping(holder,pathSpec); + + return holder; + } + + /* ------------------------------------------------------------ */ + /** conveniance method to add a servlet. + * @param servlet servlet holder to add + * @param pathSpec servlet mappings for the servletHolder + */ + public void addServletWithMapping (ServletHolder servlet,String pathSpec) + { + ServletHolder[] holders=getServlets(); + if (holders!=null) + holders = holders.clone(); + + try + { + setServlets((ServletHolder[])LazyList.addToArray(holders, servlet, ServletHolder.class)); + + ServletMapping mapping = new ServletMapping(); + mapping.setServletName(servlet.getName()); + mapping.setPathSpec(pathSpec); + setServletMappings((ServletMapping[])LazyList.addToArray(getServletMappings(), mapping, ServletMapping.class)); + } + catch (Exception e) + { + setServlets(holders); + if (e instanceof RuntimeException) + throw (RuntimeException)e; + throw new RuntimeException(e); + } + } + + /* ------------------------------------------------------------ */ + /** Convenience method to add a servlet with a servlet mapping. + * @param className + * @param pathSpec + * @return + * @deprecated + */ + public ServletHolder addServlet (String className, String pathSpec) + { + return addServletWithMapping (className, pathSpec); + } + + + /* ------------------------------------------------------------ */ + /**Convenience method to add a pre-constructed ServletHolder. + * @param holder + */ + public void addServlet(ServletHolder holder) + { + setServlets((ServletHolder[])LazyList.addToArray(getServlets(), holder, ServletHolder.class)); + } + + /* ------------------------------------------------------------ */ + /** Convenience method to add a pre-constructed ServletMapping. + * @param mapping + */ + public void addServletMapping (ServletMapping mapping) + { + setServletMappings((ServletMapping[])LazyList.addToArray(getServletMappings(), mapping, ServletMapping.class)); + } + + /* ------------------------------------------------------------ */ + public FilterHolder newFilterHolder(Class<? extends Filter> filter) + { + return new FilterHolder(filter); + } + + /* ------------------------------------------------------------ */ + /** + * @see {@link #newFilterHolder(Class)} + */ + public FilterHolder newFilterHolder() + { + return new FilterHolder(); + } + + /* ------------------------------------------------------------ */ + public FilterHolder getFilter(String name) + { + return (FilterHolder)_filterNameMap.get(name); + } + + /* ------------------------------------------------------------ */ + /** conveniance method to add a filter. + * @param filter class of filter to create + * @param pathSpec filter mappings for filter + * @param dispatches see {@link FilterMapping#setDispatches(int)} + * @return The filter holder. + */ + public FilterHolder addFilterWithMapping (Class<? extends Filter> filter,String pathSpec,int dispatches) + { + FilterHolder holder = newFilterHolder(filter); + addFilterWithMapping(holder,pathSpec,dispatches); + + return holder; + } + + /* ------------------------------------------------------------ */ + /** conveniance method to add a filter. + * @param className of filter + * @param pathSpec filter mappings for filter + * @param dispatches see {@link FilterMapping#setDispatches(int)} + * @return The filter holder. + */ + public FilterHolder addFilterWithMapping (String className,String pathSpec,int dispatches) + { + FilterHolder holder = newFilterHolder(null); + holder.setName(className+"-"+holder.hashCode()); + holder.setClassName(className); + + addFilterWithMapping(holder,pathSpec,dispatches); + return holder; + } + + /* ------------------------------------------------------------ */ + /** conveniance method to add a filter. + * @param holder filter holder to add + * @param pathSpec filter mappings for filter + * @param dispatches see {@link FilterMapping#setDispatches(int)} + */ + public void addFilterWithMapping (FilterHolder holder,String pathSpec,int dispatches) + { + FilterHolder[] holders = getFilters(); + if (holders!=null) + holders = (FilterHolder[])holders.clone(); + + try + { + setFilters((FilterHolder[])LazyList.addToArray(holders, holder, FilterHolder.class)); + + FilterMapping mapping = new FilterMapping(); + mapping.setFilterName(holder.getName()); + mapping.setPathSpec(pathSpec); + mapping.setDispatches(dispatches); + setFilterMappings((FilterMapping[])LazyList.addToArray(getFilterMappings(), mapping, FilterMapping.class)); + } + catch (RuntimeException e) + { + setFilters(holders); + throw e; + } + catch (Error e) + { + setFilters(holders); + throw e; + } + + } + + /* ------------------------------------------------------------ */ + /** Convenience method to add a filter with a mapping + * @param className + * @param pathSpec + * @param dispatches + * @return + * @deprecated + */ + public FilterHolder addFilter (String className,String pathSpec,int dispatches) + { + return addFilterWithMapping(className, pathSpec, dispatches); + } + + /* ------------------------------------------------------------ */ + /** + * convenience method to add a filter and mapping + * @param filter + * @param filterMapping + */ + public void addFilter (FilterHolder filter, FilterMapping filterMapping) + { + if (filter != null) + setFilters((FilterHolder[])LazyList.addToArray(getFilters(), filter, FilterHolder.class)); + if (filterMapping != null) + setFilterMappings((FilterMapping[])LazyList.addToArray(getFilterMappings(), filterMapping, FilterMapping.class)); + } + + /* ------------------------------------------------------------ */ + /** Convenience method to add a preconstructed FilterHolder + * @param filter + */ + public void addFilter (FilterHolder filter) + { + if (filter != null) + setFilters((FilterHolder[])LazyList.addToArray(getFilters(), filter, FilterHolder.class)); + } + + /* ------------------------------------------------------------ */ + /** Convenience method to add a preconstructed FilterMapping + * @param mapping + */ + public void addFilterMapping (FilterMapping mapping) + { + if (mapping != null) + setFilterMappings((FilterMapping[])LazyList.addToArray(getFilterMappings(), mapping, FilterMapping.class)); + } + + /* ------------------------------------------------------------ */ + /** Convenience method to add a preconstructed FilterMapping + * @param mapping + */ + public void prependFilterMapping (FilterMapping mapping) + { + if (mapping != null) + { + FilterMapping[] mappings =getFilterMappings(); + if (mappings==null || mappings.length==0) + setFilterMappings(new FilterMapping[] {mapping}); + else + { + + FilterMapping[] new_mappings=new FilterMapping[mappings.length+1]; + System.arraycopy(mappings,0,new_mappings,1,mappings.length); + new_mappings[0]=mapping; + setFilterMappings(new_mappings); + } + } + } + + /* ------------------------------------------------------------ */ + protected synchronized void updateNameMappings() + { + // update filter name map + _filterNameMap.clear(); + if (_filters!=null) + { + for (int i=0;i<_filters.length;i++) + { + _filterNameMap.put(_filters[i].getName(),_filters[i]); + _filters[i].setServletHandler(this); + } + } + + // Map servlet names to holders + _servletNameMap.clear(); + if (_servlets!=null) + { + // update the maps + for (int i=0;i<_servlets.length;i++) + { + _servletNameMap.put(_servlets[i].getName(),_servlets[i]); + _servlets[i].setServletHandler(this); + } + } + } + + /* ------------------------------------------------------------ */ + protected synchronized void updateMappings() + { + // update filter mappings + if (_filterMappings==null) + { + _filterPathMappings=null; + _filterNameMappings=null; + } + else + { + _filterPathMappings=new ArrayList(); + _filterNameMappings=new MultiMap(); + for (int i=0;i<_filterMappings.length;i++) + { + FilterHolder filter_holder = (FilterHolder)_filterNameMap.get(_filterMappings[i].getFilterName()); + if (filter_holder==null) + throw new IllegalStateException("No filter named "+_filterMappings[i].getFilterName()); + _filterMappings[i].setFilterHolder(filter_holder); + if (_filterMappings[i].getPathSpecs()!=null) + _filterPathMappings.add(_filterMappings[i]); + + if (_filterMappings[i].getServletNames()!=null) + { + String[] names=_filterMappings[i].getServletNames(); + for (int j=0;j<names.length;j++) + { + if (names[j]!=null) + _filterNameMappings.add(names[j], _filterMappings[i]); + } + } + } + } + + // Map servlet paths to holders + if (_servletMappings==null || _servletNameMap==null) + { + _servletPathMap=null; + } + else + { + PathMap pm = new PathMap(); + + // update the maps + for (int i=0;i<_servletMappings.length;i++) + { + ServletHolder servlet_holder = (ServletHolder)_servletNameMap.get(_servletMappings[i].getServletName()); + if (servlet_holder==null) + throw new IllegalStateException("No such servlet: "+_servletMappings[i].getServletName()); + else if (_servletMappings[i].getPathSpecs()!=null) + { + String[] pathSpecs = _servletMappings[i].getPathSpecs(); + for (int j=0;j<pathSpecs.length;j++) + if (pathSpecs[j]!=null) + pm.put(pathSpecs[j],servlet_holder); + } + } + + _servletPathMap=pm; + } + + + + if (Log.isDebugEnabled()) + { + Log.debug("filterNameMap="+_filterNameMap); + Log.debug("pathFilters="+_filterPathMappings); + Log.debug("servletFilterMap="+_filterNameMappings); + Log.debug("servletPathMap="+_servletPathMap); + Log.debug("servletNameMap="+_servletNameMap); + } + + try + { + if (isStarted()) + initialize(); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + /* ------------------------------------------------------------ */ + protected void notFound(HttpServletRequest request, + HttpServletResponse response) + throws IOException + { + if(Log.isDebugEnabled())Log.debug("Not Found "+request.getRequestURI()); + response.sendError(HttpServletResponse.SC_NOT_FOUND); + } + + /* ------------------------------------------------------------ */ + /** + * @param filterChainsCached The filterChainsCached to set. + */ + public void setFilterChainsCached(boolean filterChainsCached) + { + _filterChainsCached = filterChainsCached; + } + + /* ------------------------------------------------------------ */ + /** + * @param filterMappings The filterMappings to set. + */ + public void setFilterMappings(FilterMapping[] filterMappings) + { + if (getServer()!=null) + getServer().getContainer().update(this,_filterMappings,filterMappings,"filterMapping",true); + _filterMappings = filterMappings; + updateMappings(); + } + + /* ------------------------------------------------------------ */ + public synchronized void setFilters(FilterHolder[] holders) + { + if (getServer()!=null) + getServer().getContainer().update(this,_filters,holders,"filter",true); + _filters=holders; + updateNameMappings(); + } + + /* ------------------------------------------------------------ */ + /** + * @param servletMappings The servletMappings to set. + */ + public void setServletMappings(ServletMapping[] servletMappings) + { + if (getServer()!=null) + getServer().getContainer().update(this,_servletMappings,servletMappings,"servletMapping",true); + _servletMappings = servletMappings; + updateMappings(); + } + + /* ------------------------------------------------------------ */ + /** Set Servlets. + * @param holders Array of servletsto define + */ + public synchronized void setServlets(ServletHolder[] holders) + { + if (getServer()!=null) + getServer().getContainer().update(this,_servlets,holders,"servlet",true); + _servlets=holders; + updateNameMappings(); + } + + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + private class CachedChain implements FilterChain + { + FilterHolder _filterHolder; + CachedChain _next; + ServletHolder _servletHolder; + + /* ------------------------------------------------------------ */ + CachedChain(Object filters, ServletHolder servletHolder) + { + if (LazyList.size(filters)>0) + { + _filterHolder=(FilterHolder)LazyList.get(filters, 0); + filters=LazyList.remove(filters,0); + _next=new CachedChain(filters,servletHolder); + } + else + _servletHolder=servletHolder; + } + + /* ------------------------------------------------------------ */ + public void doFilter(ServletRequest request, ServletResponse response) + throws IOException, ServletException + { + // pass to next filter + if (_filterHolder!=null) + { + if (Log.isDebugEnabled()) + Log.debug("call filter " + _filterHolder); + Filter filter= _filterHolder.getFilter(); + if (_filterHolder.isAsyncSupported()) + filter.doFilter(request, response, _next); + else + { + final Request base_request=(request instanceof Request)?((Request)request):HttpConnection.getCurrentConnection().getRequest(); + final boolean suspendable=base_request.isAsyncSupported(); + if (suspendable) + { + try + { + base_request.setAsyncSupported(false); + filter.doFilter(request, response, _next); + } + finally + { + base_request.setAsyncSupported(true); + } + } + else + filter.doFilter(request, response, _next); + } + return; + } + + // Call servlet + if (_servletHolder != null) + { + if (Log.isDebugEnabled()) + Log.debug("call servlet " + _servletHolder); + final Request base_request=(request instanceof Request)?((Request)request):HttpConnection.getCurrentConnection().getRequest(); + _servletHolder.handle(base_request,request, response); + } + else // Not found + notFound((HttpServletRequest)request, (HttpServletResponse)response); + } + + public String toString() + { + if (_filterHolder!=null) + return _filterHolder+"->"+_next.toString(); + if (_servletHolder!=null) + return _servletHolder.toString(); + return "null"; + } + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + private class Chain implements FilterChain + { + final Request _baseRequest; + final Object _chain; + final ServletHolder _servletHolder; + int _filter= 0; + + /* ------------------------------------------------------------ */ + Chain(Request baseRequest, Object filters, ServletHolder servletHolder) + { + _baseRequest=baseRequest; + _chain= filters; + _servletHolder= servletHolder; + } + + /* ------------------------------------------------------------ */ + public void doFilter(ServletRequest request, ServletResponse response) + throws IOException, ServletException + { + if (Log.isDebugEnabled()) Log.debug("doFilter " + _filter); + + // pass to next filter + if (_filter < LazyList.size(_chain)) + { + FilterHolder holder= (FilterHolder)LazyList.get(_chain, _filter++); + if (Log.isDebugEnabled()) Log.debug("call filter " + holder); + Filter filter= holder.getFilter(); + + if (holder.isAsyncSupported() || !_baseRequest.isAsyncSupported()) + { + filter.doFilter(request, response, this); + } + else + { + try + { + _baseRequest.setAsyncSupported(false); + filter.doFilter(request, response, this); + } + finally + { + _baseRequest.setAsyncSupported(true); + } + } + + return; + } + + // Call servlet + if (_servletHolder != null) + { + if (Log.isDebugEnabled()) Log.debug("call servlet " + _servletHolder); + _servletHolder.handle(_baseRequest,request, response); + } + else // Not found + notFound((HttpServletRequest)request, (HttpServletResponse)response); + } + + /* ------------------------------------------------------------ */ + public String toString() + { + StringBuilder b = new StringBuilder(); + for (int i=0; i<LazyList.size(_chain);i++) + { + Object o=LazyList.get(_chain, i); + b.append(o.toString()); + b.append("->"); + } + b.append(_servletHolder); + return b.toString(); + } + } + + /* ------------------------------------------------------------ */ + /** + * @return The maximum entries in a filter chain cache. + */ + public int getMaxFilterChainsCacheSize() + { + return _maxFilterChainsCacheSize; + } + + /* ------------------------------------------------------------ */ + /** Set the maximum filter chain cache size. + * Filter chains are cached if {@link #isFilterChainsCached()} is true. If the max cache size + * is greater than zero, then the cache is flushed whenever it grows to be this size. + * + * @param maxFilterChainsCacheSize the maximum number of entries in a filter chain cache. + */ + public void setMaxFilterChainsCacheSize(int maxFilterChainsCacheSize) + { + _maxFilterChainsCacheSize = maxFilterChainsCacheSize; + } + + /** + * Customize a servlet. + * + * Called before the servlet goes into service. + * Subclasses of ServletHandler should override + * this method. + * + * @param servlet + * @return + * @throws Exception + */ + public Servlet customizeServlet (Servlet servlet) + throws Exception + { + return servlet; + } + + + public Servlet customizeServletDestroy (Servlet servlet) + throws Exception + { + return servlet; + } + + + /** + * Customize a Filter. + * + * Called before the Filter goes into service. + * Subclasses of ServletHandler should override + * this method. + * + * @param filter + * @return + * @throws Exception + */ + public Filter customizeFilter (Filter filter) + throws Exception + { + return filter; + } + + + public Filter customizeFilterDestroy (Filter filter) + throws Exception + { + return filter; + } +} diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java new file mode 100644 index 0000000000..d37504fe4f --- /dev/null +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java @@ -0,0 +1,685 @@ +// ======================================================================== +// Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlet; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Stack; + +import javax.servlet.Servlet; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRegistration; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.SingleThreadModel; +import javax.servlet.UnavailableException; + +import org.eclipse.jetty.security.IdentityService; +import org.eclipse.jetty.security.RunAsToken; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.UserIdentity; +import org.eclipse.jetty.util.log.Log; + + + + +/* --------------------------------------------------------------------- */ +/** Servlet Instance and Context Holder. + * Holds the name, params and some state of a javax.servlet.Servlet + * instance. It implements the ServletConfig interface. + * This class will organise the loading of the servlet when needed or + * requested. + * + * + */ +public class ServletHolder extends Holder implements UserIdentity.Scope, Comparable +{ + /* ---------------------------------------------------------------- */ + private int _initOrder; + private boolean _initOnStartup=false; + private Map<String, String> _roleMap; + private String _forcedPath; + private String _runAsRole; + private RunAsToken _runAsToken; + private IdentityService _identityService; + + + private transient Servlet _servlet; + private transient Config _config; + private transient long _unavailable; + private transient UnavailableException _unavailableEx; + public static final Map<String,String> NO_MAPPED_ROLES = Collections.emptyMap(); + + /* ---------------------------------------------------------------- */ + /** Constructor . + */ + public ServletHolder() + {} + + /* ---------------------------------------------------------------- */ + /** Constructor for existing servlet. + */ + public ServletHolder(Servlet servlet) + { + setServlet(servlet); + } + + /* ---------------------------------------------------------------- */ + /** Constructor for existing servlet. + */ + public ServletHolder(Class servlet) + { + super(servlet); + } + + /* ---------------------------------------------------------------- */ + /** + * @return The unavailable exception or null if not unavailable + */ + public UnavailableException getUnavailableException() + { + return _unavailableEx; + } + + /* ------------------------------------------------------------ */ + public synchronized void setServlet(Servlet servlet) + { + if (servlet==null || servlet instanceof SingleThreadModel) + throw new IllegalArgumentException(); + + _extInstance=true; + _servlet=servlet; + setHeldClass(servlet.getClass()); + if (getName()==null) + setName(servlet.getClass().getName()+"-"+super.hashCode()); + } + + /* ------------------------------------------------------------ */ + public int getInitOrder() + { + return _initOrder; + } + + /* ------------------------------------------------------------ */ + /** Set the initialize order. + * Holders with order<0, are initialized on use. Those with + * order>=0 are initialized in increasing order when the handler + * is started. + */ + public void setInitOrder(int order) + { + _initOnStartup=true; + _initOrder = order; + } + + /* ------------------------------------------------------------ */ + /** Comparitor by init order. + */ + public int compareTo(Object o) + { + if (o instanceof ServletHolder) + { + ServletHolder sh= (ServletHolder)o; + if (sh==this) + return 0; + if (sh._initOrder<_initOrder) + return 1; + if (sh._initOrder>_initOrder) + return -1; + + int c=(_className!=null && sh._className!=null)?_className.compareTo(sh._className):0; + if (c==0) + c=_name.compareTo(sh._name); + if (c==0) + c=this.hashCode()>o.hashCode()?1:-1; + return c; + } + return 1; + } + + /* ------------------------------------------------------------ */ + public boolean equals(Object o) + { + return compareTo(o)==0; + } + + /* ------------------------------------------------------------ */ + public int hashCode() + { + return _name==null?System.identityHashCode(this):_name.hashCode(); + } + + /* ------------------------------------------------------------ */ + /** Link a user role. + * Translate the role name used by a servlet, to the link name + * used by the container. + * @param name The role name as used by the servlet + * @param link The role name as used by the container. + */ + public synchronized void setUserRoleLink(String name,String link) + { + if (_roleMap==null) + _roleMap=new HashMap<String, String>(); + _roleMap.put(name,link); + } + + /* ------------------------------------------------------------ */ + /** get a user role link. + * @param name The name of the role + * @return The name as translated by the link. If no link exists, + * the name is returned. + */ + public String getUserRoleLink(String name) + { + if (_roleMap==null) + return name; + String link= _roleMap.get(name); + return (link==null)?name:link; + } + + /* ------------------------------------------------------------ */ + public Map<String, String> getRoleMap() + { + return _roleMap == null? NO_MAPPED_ROLES : _roleMap; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the forcedPath. + */ + public String getForcedPath() + { + return _forcedPath; + } + + /* ------------------------------------------------------------ */ + /** + * @param forcedPath The forcedPath to set. + */ + public void setForcedPath(String forcedPath) + { + _forcedPath = forcedPath; + } + + /* ------------------------------------------------------------ */ + public void doStart() + throws Exception + { + _unavailable=0; + try + { + super.doStart(); + checkServletType(); + } + catch (UnavailableException ue) + { + makeUnavailable(ue); + } + + _identityService = _servletHandler.getIdentityService(); + if (_identityService!=null && _runAsRole!=null) + _runAsToken=_identityService.newRunAsToken(_runAsRole); + + _config=new Config(); + + if (_class!=null && javax.servlet.SingleThreadModel.class.isAssignableFrom(_class)) + _servlet = new SingleThreadedWrapper(); + + if (_extInstance || _initOnStartup) + { + try + { + initServlet(); + } + catch(Exception e) + { + if (_servletHandler.isStartWithUnavailable()) + Log.ignore(e); + else + throw e; + } + } + } + + /* ------------------------------------------------------------ */ + public void doStop() + { + Object old_run_as = null; + if (_servlet!=null) + { + try + { + if (_identityService!=null && _runAsToken!=null) + old_run_as=_identityService.associateRunAs(_runAsToken); + + destroyInstance(_servlet); + } + catch (Exception e) + { + Log.warn(e); + } + finally + { + if (_identityService!=null && _runAsToken!=null) + _identityService.disassociateRunAs(old_run_as); + } + } + + if (!_extInstance) + _servlet=null; + + _config=null; + } + + /* ------------------------------------------------------------ */ + public void destroyInstance (Object o) + throws Exception + { + if (o==null) + return; + Servlet servlet = ((Servlet)o); + servlet.destroy(); + getServletHandler().customizeServletDestroy(servlet); + } + + /* ------------------------------------------------------------ */ + /** Get the servlet. + * @return The servlet + */ + public synchronized Servlet getServlet() + throws ServletException + { + // Handle previous unavailability + if (_unavailable!=0) + { + if (_unavailable<0 || _unavailable>0 && System.currentTimeMillis()<_unavailable) + throw _unavailableEx; + _unavailable=0; + _unavailableEx=null; + } + + if (_servlet==null) + initServlet(); + return _servlet; + } + + /* ------------------------------------------------------------ */ + /** + * Check to ensure class of servlet is acceptable. + * @throws UnavailableException + */ + public void checkServletType () + throws UnavailableException + { + if (_class==null || !javax.servlet.Servlet.class.isAssignableFrom(_class)) + { + throw new UnavailableException("Servlet "+_class+" is not a javax.servlet.Servlet"); + } + } + + /* ------------------------------------------------------------ */ + /** + * @return true if the holder is started and is not unavailable + */ + public boolean isAvailable() + { + if (isStarted()&& _unavailable==0) + return true; + try + { + getServlet(); + } + catch(Exception e) + { + Log.ignore(e); + } + + return isStarted()&& _unavailable==0; + } + + /* ------------------------------------------------------------ */ + private void makeUnavailable(UnavailableException e) + { + if (_unavailableEx==e && _unavailable!=0) + return; + + _servletHandler.getServletContext().log("unavailable",e); + + _unavailableEx=e; + _unavailable=-1; + if (e.isPermanent()) + _unavailable=-1; + else + { + if (_unavailableEx.getUnavailableSeconds()>0) + _unavailable=System.currentTimeMillis()+1000*_unavailableEx.getUnavailableSeconds(); + else + _unavailable=System.currentTimeMillis()+5000; // TODO configure + } + } + + + /* ------------------------------------------------------------ */ + + private void makeUnavailable(Throwable e) + { + if (e instanceof UnavailableException) + makeUnavailable((UnavailableException)e); + else + { + _servletHandler.getServletContext().log("unavailable",e); + _unavailableEx=new UnavailableException(e.toString(),-1); + _unavailable=-1; + } + } + + /* ------------------------------------------------------------ */ + private void initServlet() + throws ServletException + { + Object old_run_as = null; + try + { + if (_servlet==null) + _servlet=(Servlet)newInstance(); + if (_config==null) + _config=new Config(); + + //handle any cusomizations of the servlet, such as @postConstruct + if (!(_servlet instanceof SingleThreadedWrapper)) + _servlet = getServletHandler().customizeServlet(_servlet); + + // Handle run as + if (_identityService!=null && _runAsToken!=null) + { + old_run_as=_identityService.associateRunAs(_runAsToken); + } + + _servlet.init(_config); + } + catch (UnavailableException e) + { + makeUnavailable(e); + _servlet=null; + _config=null; + throw e; + } + catch (ServletException e) + { + makeUnavailable(e.getCause()==null?e:e.getCause()); + _servlet=null; + _config=null; + throw e; + } + catch (Exception e) + { + makeUnavailable(e); + _servlet=null; + _config=null; + throw new ServletException(this.toString(),e); + } + finally + { + // pop run-as role + if (_identityService!=null && _runAsToken!=null) + _identityService.disassociateRunAs(old_run_as); + } + } + + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.UserIdentity.Scope#getContextPath() + */ + public String getContextPath() + { + return _config.getServletContext().getContextPath(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.UserIdentity.Scope#getRoleRefMap() + */ + public Map<String, String> getRoleRefMap() + { + return _roleMap; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.server.UserIdentity.Scope#getRunAsRole() + */ + public String getRunAsRole() + { + return _runAsRole; + } + + /* ------------------------------------------------------------ */ + /** + * Set the run-as role for this servlet + * @param role run-as role for this servlet + */ + public void setRunAsRole(String role) + { + _runAsRole=role; + } + + /* ------------------------------------------------------------ */ + /** Service a request with this servlet. + */ + public void handle(Request baseRequest, + ServletRequest request, + ServletResponse response) + throws ServletException, + UnavailableException, + IOException + { + if (_class==null) + throw new UnavailableException("Servlet Not Initialized"); + + Servlet servlet=_servlet; + synchronized(this) + { + if (_unavailable!=0 || !_initOnStartup) + servlet=getServlet(); + if (servlet==null) + throw new UnavailableException("Could not instantiate "+_class); + } + + // Service the request + boolean servlet_error=true; + Object old_run_as = null; + boolean suspendable = baseRequest.isAsyncSupported(); + try + { + // Handle aliased path + if (_forcedPath!=null) + // TODO complain about poor naming to the Jasper folks + request.setAttribute("org.apache.catalina.jsp_file",_forcedPath); + + // Handle run as + if (_identityService!=null && _runAsToken!=null) + old_run_as=_identityService.associateRunAs(_runAsToken); + + if (!isAsyncSupported()) + baseRequest.setAsyncSupported(false); + + servlet.service(request,response); + servlet_error=false; + } + catch(UnavailableException e) + { + makeUnavailable(e); + throw _unavailableEx; + } + finally + { + baseRequest.setAsyncSupported(suspendable); + + // pop run-as role + if (_identityService!=null && _runAsToken!=null) + _identityService.disassociateRunAs(old_run_as); + + // Handle error params. + if (servlet_error) + request.setAttribute("javax.servlet.error.servlet_name",getName()); + } + } + + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + protected class Config extends HolderConfig implements ServletConfig + { + /* -------------------------------------------------------- */ + public String getServletName() + { + return getName(); + } + + } + + /* -------------------------------------------------------- */ + /* -------------------------------------------------------- */ + /* -------------------------------------------------------- */ + protected class Registration extends HolderRegistration implements ServletRegistration + { + public boolean addMapping(String... urlPatterns) + { + illegalStateIfContextStarted(); + ServletMapping mapping = new ServletMapping(); + mapping.setServletName(ServletHolder.this.getName()); + mapping.setPathSpecs(urlPatterns); + _servletHandler.addServletMapping(mapping); + return true; + } + + public boolean setLoadOnStartup(int loadOnStartup) + { + illegalStateIfContextStarted(); + ServletHolder.this.setInitOrder(loadOnStartup); + return false; + } + } + + public ServletRegistration getRegistration() + { + return new Registration(); + } + + /* -------------------------------------------------------- */ + /* -------------------------------------------------------- */ + /* -------------------------------------------------------- */ + private class SingleThreadedWrapper implements Servlet + { + Stack _stack=new Stack(); + + public void destroy() + { + synchronized(this) + { + while(_stack.size()>0) + try { ((Servlet)_stack.pop()).destroy(); } catch (Exception e) { Log.warn(e); } + } + } + + public ServletConfig getServletConfig() + { + return _config; + } + + public String getServletInfo() + { + return null; + } + + public void init(ServletConfig config) throws ServletException + { + synchronized(this) + { + if(_stack.size()==0) + { + try + { + Servlet s = (Servlet) newInstance(); + s = getServletHandler().customizeServlet(s); + s.init(config); + _stack.push(s); + } + catch (ServletException e) + { + throw e; + } + catch (Exception e) + { + throw new ServletException(e); + } + } + } + } + + public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException + { + Servlet s; + synchronized(this) + { + if(_stack.size()>0) + s=(Servlet)_stack.pop(); + else + { + try + { + s = (Servlet) newInstance(); + s = getServletHandler().customizeServlet(s); + s.init(_config); + } + catch (ServletException e) + { + throw e; + } + catch (IOException e) + { + throw e; + } + catch (Exception e) + { + throw new ServletException(e); + } + } + } + + try + { + s.service(req,res); + } + finally + { + synchronized(this) + { + _stack.push(s); + } + } + } + + } +} + + + + + diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletMapping.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletMapping.java new file mode 100644 index 0000000000..88ecb0963a --- /dev/null +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletMapping.java @@ -0,0 +1,80 @@ +// ======================================================================== +// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.servlet; + +import java.util.Arrays; + + +public class ServletMapping +{ + private String[] _pathSpecs; + private String _servletName; + + /* ------------------------------------------------------------ */ + public ServletMapping() + { + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the pathSpec. + */ + public String[] getPathSpecs() + { + return _pathSpecs; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the servletName. + */ + public String getServletName() + { + return _servletName; + } + + /* ------------------------------------------------------------ */ + /** + * @param pathSpec The pathSpec to set. + */ + public void setPathSpecs(String[] pathSpecs) + { + _pathSpecs = pathSpecs; + } + + /* ------------------------------------------------------------ */ + /** + * @param pathSpec The pathSpec to set. + */ + public void setPathSpec(String pathSpec) + { + _pathSpecs = new String[]{pathSpec}; + } + + /* ------------------------------------------------------------ */ + /** + * @param servletName The servletName to set. + */ + public void setServletName(String servletName) + { + _servletName = servletName; + } + + + /* ------------------------------------------------------------ */ + public String toString() + { + return "(S="+_servletName+","+(_pathSpecs==null?"[]":Arrays.asList(_pathSpecs).toString())+")"; + } +} diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/StatisticsServlet.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/StatisticsServlet.java new file mode 100644 index 0000000000..bad7293cb4 --- /dev/null +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/StatisticsServlet.java @@ -0,0 +1,240 @@ +package org.eclipse.jetty.servlet; + +// ======================================================================== +// Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.StatisticsHandler; +import org.eclipse.jetty.util.log.Log; + +public class StatisticsServlet extends HttpServlet +{ + boolean _restrictToLocalhost = true; // defaults to true + private Server _server = null; + private StatisticsHandler _statsHandler; + private MemoryMXBean _memoryBean; + private Connector[] _connectors; + + public void init() throws ServletException + { + _memoryBean = ManagementFactory.getMemoryMXBean(); + + ServletContext context = getServletContext(); + ContextHandler.Context scontext = (ContextHandler.Context) context; + _server = scontext.getContextHandler().getServer(); + + Handler handler = _server.getChildHandlerByClass(StatisticsHandler.class); + + if (handler != null) + { + _statsHandler = (StatisticsHandler) handler; + } + else + { + Log.info("Installing Statistics Handler"); + _statsHandler = new StatisticsHandler(); + _server.addHandler(_statsHandler); + } + + + _connectors = _server.getConnectors(); + + if (getInitParameter("restrictToLocalhost") != null) + { + _restrictToLocalhost = "true".equals(getInitParameter("restrictToLocalhost")); + } + + } + + public void doPost(HttpServletRequest sreq, HttpServletResponse sres) throws ServletException, IOException + { + doGet(sreq, sres); + } + + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + + if (_restrictToLocalhost) + { + if (!"127.0.0.1".equals(req.getRemoteAddr())) + { + resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); + return; + } + } + + if (req.getParameter("xml") != null && "true".equals(req.getParameter("xml"))) + { + sendXmlResponse(resp); + } else + { + sendTextResponse(resp); + } + + } + + private void sendXmlResponse(HttpServletResponse response) throws IOException + { + StringBuilder sb = new StringBuilder(); + + sb.append("<statistics>\n"); + + sb.append(" <requests>\n"); + sb.append(" <statsOnMs>").append(_statsHandler.getStatsOnMs()).append("</statsOnMs>\n"); + sb.append(" <requests>").append(_statsHandler.getRequests()).append("</requests>\n"); + sb.append(" <requestsTimedout>").append(_statsHandler.getRequestsTimedout()).append("</requestsTimedout>\n"); + sb.append(" <requestsResumed>").append(_statsHandler.getRequestsResumed()).append("</requestsResumed>\n"); + sb.append(" <requestsActive>").append(_statsHandler.getRequestsActive()).append("</requestsActive>\n"); + sb.append(" <requestsActiveMin>").append(_statsHandler.getRequestsActiveMin()).append("</requestsActiveMin>\n"); + sb.append(" <requestsActiveMax>").append(_statsHandler.getRequestsActiveMax()).append("</requestsActiveMax>\n"); + sb.append(" <requestsDurationTotal>").append(_statsHandler.getRequestsDurationTotal()).append("</requestsDurationTotal>\n"); + sb.append(" <requestsDurationAve>").append(_statsHandler.getRequestsDurationAve()).append("</requestsDurationAve>\n"); + sb.append(" <requestsDurationMin>").append(_statsHandler.getRequestsDurationMin()).append("</requestsDurationMin>\n"); + sb.append(" <requestsDurationMax>").append(_statsHandler.getRequestsDurationMax()).append("</requestsDurationMax>\n"); + sb.append(" <requestsActiveDurationAve>").append(_statsHandler.getRequestsActiveDurationAve()).append("</requestsActiveDurationAve>\n"); + sb.append(" <requestsActiveDurationMin>").append(_statsHandler.getRequestsActiveDurationMin()).append("</requestsActiveDurationMin>\n"); + sb.append(" <requestsActiveDurationMax>").append(_statsHandler.getRequestsActiveDurationMax()).append("</requestsActiveDurationMax>\n"); + sb.append(" </requests>\n"); + + sb.append(" <responses>\n"); + sb.append(" <responses1xx>").append(_statsHandler.getResponses1xx()).append("</responses1xx>\n"); + sb.append(" <responses2xx>").append(_statsHandler.getResponses2xx()).append("</responses2xx>\n"); + sb.append(" <responses3xx>").append(_statsHandler.getResponses3xx()).append("</responses3xx>\n"); + sb.append(" <responses4xx>").append(_statsHandler.getResponses4xx()).append("</responses4xx>\n"); + sb.append(" <responses5xx>").append(_statsHandler.getResponses5xx()).append("</responses5xx>\n"); + sb.append(" <responsesBytesTotal>").append(_statsHandler.getResponsesBytesTotal()).append("</responsesBytesTotal>\n"); + sb.append(" </responses>\n"); + + sb.append(" <connections>\n"); + for (Connector connector : _connectors) + { + sb.append(" <connector>\n"); + sb.append(" <name>").append(connector.getName()).append("</name>\n"); + sb.append(" <statsOn>").append(connector.getStatsOn()).append("</statsOn>\n"); + if (connector.getStatsOn()) + { + sb.append(" <statsOnMs>").append(connector.getStatsOnMs()).append("</statsOnMs>\n"); + sb.append(" <connections>").append(connector.getConnections()).append("</connections>\n"); + sb.append(" <connectionsOpen>").append(connector.getConnectionsOpen()).append("</connectionsOpen>\n"); + sb.append(" <connectionsOpenMin>").append(connector.getConnectionsOpenMin()).append("</connectionsOpenMin>\n"); + sb.append(" <connectionsOpenMax>").append(connector.getConnectionsOpenMax()).append("</connectionsOpenMax>\n"); + sb.append(" <connectionsDurationTotal>").append(connector.getConnectionsDurationTotal()).append("</connectionsDurationTotal>\n"); + sb.append(" <connectionsDurationAve>").append(connector.getConnectionsDurationAve()).append("</connectionsDurationAve>\n"); + sb.append(" <connectionsDurationMin>").append(connector.getConnectionsDurationMin()).append("</connectionsDurationMin>\n"); + sb.append(" <connectionsDurationMax>").append(connector.getConnectionsDurationMax()).append("</connectionsDurationMax>\n"); + sb.append(" <requests>").append(connector.getRequests()).append("</requests>\n"); + sb.append(" <connectionsRequestsAve>").append(connector.getConnectionsRequestsAve()).append("</connectionsRequestsAve>\n"); + sb.append(" <connectionsRequestsMin>").append(connector.getConnectionsRequestsMin()).append("</connectionsRequestsMin>\n"); + sb.append(" <connectionsRequestsMax>").append(connector.getConnectionsRequestsMax()).append("</connectionsRequestsMax>\n"); + } + sb.append(" </connector>\n"); + } + sb.append(" </connections>\n"); + + sb.append(" <memory>\n"); + sb.append(" <heapMemoryUsage>").append(_memoryBean.getHeapMemoryUsage().getUsed()).append("</heapMemoryUsage>\n"); + sb.append(" <nonHeapMemoryUsage>").append(_memoryBean.getNonHeapMemoryUsage().getUsed()).append("</nonHeapMemoryUsage>\n"); + sb.append(" </memory>\n"); + + sb.append("</statistics>\n"); + + response.setContentType("text/xml"); + PrintWriter pout = null; + pout = response.getWriter(); + pout.write(sb.toString()); + } + + private void sendTextResponse(HttpServletResponse response) throws IOException + { + + StringBuilder sb = new StringBuilder(); + + sb.append("<h1>Statistics:</h1>\n"); + + sb.append("<h2>Requests:</h2>\n"); + sb.append("Statistics gathering started " + _statsHandler.getStatsOnMs() + "ms ago").append("<br />\n"); + sb.append("Total requests: " + _statsHandler.getRequests()).append("<br />\n"); + sb.append("Total requests timed out: " + _statsHandler.getRequestsTimedout()).append("<br />\n"); + sb.append("Total requests resumed: " + _statsHandler.getRequestsResumed()).append("<br />\n"); + sb.append("Current requests active: " + _statsHandler.getRequestsActive()).append("<br />\n"); + sb.append("Min concurrent requests active: " + _statsHandler.getRequestsActiveMin()).append("<br />\n"); + sb.append("Max concurrent requests active: " + _statsHandler.getRequestsActiveMax()).append("<br />\n"); + sb.append("Total requests duration: " + _statsHandler.getRequestsDurationTotal()).append("<br />\n"); + sb.append("Average request duration: " + _statsHandler.getRequestsDurationAve()).append("<br />\n"); + sb.append("Min request duration: " + _statsHandler.getRequestsDurationMin()).append("<br />\n"); + sb.append("Max request duration: " + _statsHandler.getRequestsDurationMax()).append("<br />\n"); + sb.append("Average request active duration: " + _statsHandler.getRequestsActiveDurationAve()).append("<br />\n"); + sb.append("Min request active duration: " + _statsHandler.getRequestsActiveDurationMin()).append("<br />\n"); + sb.append("Max request active duration: " + _statsHandler.getRequestsActiveDurationMax()).append("<br />\n"); + + sb.append("<h2>Responses:</h2>\n"); + sb.append("1xx responses: " + _statsHandler.getResponses1xx()).append("<br />\n"); + sb.append("2xx responses: " + _statsHandler.getResponses2xx()).append("<br />\n"); + sb.append("3xx responses: " + _statsHandler.getResponses3xx()).append("<br />\n"); + sb.append("4xx responses: " + _statsHandler.getResponses4xx()).append("<br />\n"); + sb.append("5xx responses: " + _statsHandler.getResponses5xx()).append("<br />\n"); + sb.append("Bytes sent total: " + _statsHandler.getResponsesBytesTotal()).append("<br />\n"); + + sb.append("<h2>Connections:</h2>\n"); + for (Connector connector : _connectors) + { + sb.append("<h3>" + connector.getName() + "</h3>"); + + if (connector.getStatsOn()) + { + sb.append("Statistics gathering started " + connector.getStatsOnMs() + "ms ago").append("<br />\n"); + sb.append("Total connections: " + connector.getConnections()).append("<br />\n"); + sb.append("Current connections open: " + connector.getConnectionsOpen()); + sb.append("Min concurrent connections open: " + connector.getConnectionsOpenMin()).append("<br />\n"); + sb.append("Max concurrent connections open: " + connector.getConnectionsOpenMax()).append("<br />\n"); + sb.append("Total connections duration: " + connector.getConnectionsDurationTotal()).append("<br />\n"); + sb.append("Average connection duration: " + connector.getConnectionsDurationAve()).append("<br />\n"); + sb.append("Min connection duration: " + connector.getConnectionsDurationMin()).append("<br />\n"); + sb.append("Max connection duration: " + connector.getConnectionsDurationMax()).append("<br />\n"); + sb.append("Total requests: " + connector.getRequests()).append("<br />\n"); + sb.append("Average requests per connection: " + connector.getConnectionsRequestsAve()).append("<br />\n"); + sb.append("Min requests per connection: " + connector.getConnectionsRequestsMin()).append("<br />\n"); + sb.append("Max requests per connection: " + connector.getConnectionsRequestsMax()).append("<br />\n"); + } + else + { + sb.append("Statistics gathering off.\n"); + } + + } + + sb.append("<h2>Memory:</h2>\n"); + sb.append("Heap memory usage: " + _memoryBean.getHeapMemoryUsage().getUsed() + " bytes").append("<br />\n"); + sb.append("Non-heap memory usage: " + _memoryBean.getNonHeapMemoryUsage().getUsed() + " bytes").append("<br />\n"); + + response.setContentType("text/html"); + PrintWriter pout = null; + pout = response.getWriter(); + pout.write(sb.toString()); + + } +} |