2 // ========================================================================
3 // Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4 // ------------------------------------------------------------------------
5 // All rights reserved. This program and the accompanying materials
6 // are made available under the terms of the Eclipse Public License v1.0
7 // and Apache License v2.0 which accompanies this distribution.
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
19 package org.eclipse.jetty.servlet;
21 import java.io.FileNotFoundException;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.OutputStream;
25 import java.net.MalformedURLException;
27 import java.nio.ByteBuffer;
28 import java.util.ArrayList;
29 import java.util.Enumeration;
30 import java.util.List;
31 import java.util.StringTokenizer;
33 import javax.servlet.AsyncContext;
34 import javax.servlet.RequestDispatcher;
35 import javax.servlet.ServletContext;
36 import javax.servlet.ServletException;
37 import javax.servlet.UnavailableException;
38 import javax.servlet.http.HttpServlet;
39 import javax.servlet.http.HttpServletRequest;
40 import javax.servlet.http.HttpServletResponse;
42 import org.eclipse.jetty.http.HttpContent;
43 import org.eclipse.jetty.http.HttpField;
44 import org.eclipse.jetty.http.HttpFields;
45 import org.eclipse.jetty.http.HttpGenerator.CachedHttpField;
46 import org.eclipse.jetty.http.HttpHeader;
47 import org.eclipse.jetty.http.HttpMethod;
48 import org.eclipse.jetty.http.MimeTypes;
49 import org.eclipse.jetty.http.PathMap.MappedEntry;
50 import org.eclipse.jetty.io.WriterOutputStream;
51 import org.eclipse.jetty.server.HttpOutput;
52 import org.eclipse.jetty.server.InclusiveByteRange;
53 import org.eclipse.jetty.server.ResourceCache;
54 import org.eclipse.jetty.server.Response;
55 import org.eclipse.jetty.server.handler.ContextHandler;
56 import org.eclipse.jetty.util.BufferUtil;
57 import org.eclipse.jetty.util.Callback;
58 import org.eclipse.jetty.util.IO;
59 import org.eclipse.jetty.util.MultiPartOutputStream;
60 import org.eclipse.jetty.util.QuotedStringTokenizer;
61 import org.eclipse.jetty.util.URIUtil;
62 import org.eclipse.jetty.util.log.Log;
63 import org.eclipse.jetty.util.log.Logger;
64 import org.eclipse.jetty.util.resource.Resource;
65 import org.eclipse.jetty.util.resource.ResourceCollection;
66 import org.eclipse.jetty.util.resource.ResourceFactory;
70 /* ------------------------------------------------------------ */
71 /** The default servlet.
73 * This servlet, normally mapped to /, provides the handling for static
74 * content, OPTION and TRACE methods for the context.
75 * The following initParameters are supported, these can be set either
76 * on the servlet itself or as ServletContext initParameters with a prefix
77 * of org.eclipse.jetty.servlet.Default. :
79 * acceptRanges If true, range requests and responses are
82 * dirAllowed If true, directory listings are returned if no
83 * welcome file is found. Else 403 Forbidden.
85 * welcomeServlets If true, attempt to dispatch to welcome files
86 * that are servlets, but only after no matching static
87 * resources could be found. If false, then a welcome
88 * file must exist on disk. If "exact", then exact
89 * servlet matches are supported without an existing file.
92 * This must be false if you want directory listings,
93 * but have index.jsp in your welcome file list.
95 * redirectWelcome If true, welcome files are redirected rather than
98 * gzip If set to true, then static content will be served as
99 * gzip content encoded if a matching resource is
100 * found ending with ".gz"
102 * resourceBase Set to replace the context resource base
104 * resourceCache If set, this is a context attribute name, which the servlet
105 * will use to look for a shared ResourceCache instance.
107 * relativeResourceBase
108 * Set with a pathname relative to the base of the
109 * servlet context root. Useful for only serving static content out
110 * of only specific subdirectories.
112 * pathInfoOnly If true, only the path info will be applied to the resourceBase
114 * stylesheet Set with the location of an optional stylesheet that will be used
115 * to decorate the directory listing html.
117 * etags If True, weak etags will be generated and handled.
119 * maxCacheSize The maximum total size of the cache or 0 for no cache.
120 * maxCachedFileSize The maximum size of a file to cache
121 * maxCachedFiles The maximum number of files to cache
123 * useFileMappedBuffer
124 * If set to true, it will use mapped file buffer to serve static content
125 * when using NIO connector. Setting this value to false means that
126 * a direct buffer will be used instead of a mapped file buffer.
127 * This is set to false by default by this class, but may be overridden
128 * by eg webdefault.xml
130 * cacheControl If set, all static content will have this value set as the cache-control
133 * otherGzipFileExtensions
134 * Other file extensions that signify that a file is gzip compressed. Eg ".svgz"
143 public class DefaultServlet extends HttpServlet implements ResourceFactory
145 private static final Logger LOG = Log.getLogger(DefaultServlet.class);
147 private static final long serialVersionUID = 4930458713846881193L;
149 private static final CachedHttpField ACCEPT_RANGES = new CachedHttpField(HttpHeader.ACCEPT_RANGES, "bytes");
151 private ServletContext _servletContext;
152 private ContextHandler _contextHandler;
154 private boolean _acceptRanges=true;
155 private boolean _dirAllowed=true;
156 private boolean _welcomeServlets=false;
157 private boolean _welcomeExactServlets=false;
158 private boolean _redirectWelcome=false;
159 private boolean _gzip=false;
160 private boolean _pathInfoOnly=false;
161 private boolean _etags=false;
163 private Resource _resourceBase;
164 private ResourceCache _cache;
166 private MimeTypes _mimeTypes;
167 private String[] _welcomes;
168 private Resource _stylesheet;
169 private boolean _useFileMappedBuffer=false;
170 private HttpField _cacheControl;
171 private String _relativeResourceBase;
172 private ServletHandler _servletHandler;
173 private ServletHolder _defaultHolder;
174 private List<String> _gzipEquivalentFileExtensions;
176 /* ------------------------------------------------------------ */
179 throws UnavailableException
181 _servletContext=getServletContext();
182 _contextHandler = initContextHandler(_servletContext);
184 _mimeTypes = _contextHandler.getMimeTypes();
186 _welcomes = _contextHandler.getWelcomeFiles();
188 _welcomes=new String[] {"index.html","index.jsp"};
190 _acceptRanges=getInitBoolean("acceptRanges",_acceptRanges);
191 _dirAllowed=getInitBoolean("dirAllowed",_dirAllowed);
192 _redirectWelcome=getInitBoolean("redirectWelcome",_redirectWelcome);
193 _gzip=getInitBoolean("gzip",_gzip);
194 _pathInfoOnly=getInitBoolean("pathInfoOnly",_pathInfoOnly);
196 if ("exact".equals(getInitParameter("welcomeServlets")))
198 _welcomeExactServlets=true;
199 _welcomeServlets=false;
202 _welcomeServlets=getInitBoolean("welcomeServlets", _welcomeServlets);
204 _useFileMappedBuffer=getInitBoolean("useFileMappedBuffer",_useFileMappedBuffer);
206 _relativeResourceBase = getInitParameter("relativeResourceBase");
208 String rb=getInitParameter("resourceBase");
211 if (_relativeResourceBase!=null)
212 throw new UnavailableException("resourceBase & relativeResourceBase");
213 try{_resourceBase=_contextHandler.newResource(rb);}
216 LOG.warn(Log.EXCEPTION,e);
217 throw new UnavailableException(e.toString());
221 String css=getInitParameter("stylesheet");
226 _stylesheet = Resource.newResource(css);
227 if(!_stylesheet.exists())
233 if(_stylesheet == null)
235 _stylesheet = Resource.newResource(this.getClass().getResource("/jetty-dir.css"));
240 LOG.warn(e.toString());
244 String cc=getInitParameter("cacheControl");
246 _cacheControl=new CachedHttpField(HttpHeader.CACHE_CONTROL, cc);
248 String resourceCache = getInitParameter("resourceCache");
249 int max_cache_size=getInitInt("maxCacheSize", -2);
250 int max_cached_file_size=getInitInt("maxCachedFileSize", -2);
251 int max_cached_files=getInitInt("maxCachedFiles", -2);
252 if (resourceCache!=null)
254 if (max_cache_size!=-1 || max_cached_file_size!= -2 || max_cached_files!=-2)
255 LOG.debug("ignoring resource cache configuration, using resourceCache attribute");
256 if (_relativeResourceBase!=null || _resourceBase!=null)
257 throw new UnavailableException("resourceCache specified with resource bases");
258 _cache=(ResourceCache)_servletContext.getAttribute(resourceCache);
260 if (LOG.isDebugEnabled())
261 LOG.debug("Cache {}={}",resourceCache,_cache);
264 _etags = getInitBoolean("etags",_etags);
268 if (_cache==null && (max_cached_files!=-2 || max_cache_size!=-2 || max_cached_file_size!=-2))
270 _cache= new ResourceCache(null,this,_mimeTypes,_useFileMappedBuffer,_etags);
272 if (max_cache_size>=0)
273 _cache.setMaxCacheSize(max_cache_size);
274 if (max_cached_file_size>=-1)
275 _cache.setMaxCachedFileSize(max_cached_file_size);
276 if (max_cached_files>=-1)
277 _cache.setMaxCachedFiles(max_cached_files);
282 LOG.warn(Log.EXCEPTION,e);
283 throw new UnavailableException(e.toString());
286 _gzipEquivalentFileExtensions = new ArrayList<String>();
287 String otherGzipExtensions = getInitParameter("otherGzipFileExtensions");
288 if (otherGzipExtensions != null)
290 //comma separated list
291 StringTokenizer tok = new StringTokenizer(otherGzipExtensions,",",false);
292 while (tok.hasMoreTokens())
294 String s = tok.nextToken().trim();
295 _gzipEquivalentFileExtensions.add((s.charAt(0)=='.'?s:"."+s));
300 //.svgz files are gzipped svg files and must be served with Content-Encoding:gzip
301 _gzipEquivalentFileExtensions.add(".svgz");
304 _servletHandler= _contextHandler.getChildHandlerByClass(ServletHandler.class);
305 for (ServletHolder h :_servletHandler.getServlets())
306 if (h.getServletInstance()==this)
310 if (LOG.isDebugEnabled())
311 LOG.debug("resource base = "+_resourceBase);
315 * Compute the field _contextHandler.<br/>
316 * In the case where the DefaultServlet is deployed on the HttpService it is likely that
317 * this method needs to be overwritten to unwrap the ServletContext facade until we reach
318 * the original jetty's ContextHandler.
319 * @param servletContext The servletContext of this servlet.
320 * @return the jetty's ContextHandler for this servletContext.
322 protected ContextHandler initContextHandler(ServletContext servletContext)
324 ContextHandler.Context scontext=ContextHandler.getCurrentContext();
327 if (servletContext instanceof ContextHandler.Context)
328 return ((ContextHandler.Context)servletContext).getContextHandler();
330 throw new IllegalArgumentException("The servletContext " + servletContext + " " +
331 servletContext.getClass().getName() + " is not " + ContextHandler.Context.class.getName());
334 return ContextHandler.getCurrentContext().getContextHandler();
337 /* ------------------------------------------------------------ */
339 public String getInitParameter(String name)
341 String value=getServletContext().getInitParameter("org.eclipse.jetty.servlet.Default."+name);
343 value=super.getInitParameter(name);
347 /* ------------------------------------------------------------ */
348 private boolean getInitBoolean(String name, boolean dft)
350 String value=getInitParameter(name);
351 if (value==null || value.length()==0)
353 return (value.startsWith("t")||
354 value.startsWith("T")||
355 value.startsWith("y")||
356 value.startsWith("Y")||
357 value.startsWith("1"));
360 /* ------------------------------------------------------------ */
361 private int getInitInt(String name, int dft)
363 String value=getInitParameter(name);
365 value=getInitParameter(name);
366 if (value!=null && value.length()>0)
367 return Integer.parseInt(value);
371 /* ------------------------------------------------------------ */
372 /** get Resource to serve.
373 * Map a path to a resource. The default implementation calls
374 * HttpContext.getResource but derived servlets may provide
376 * @param pathInContext The path to find a resource for.
377 * @return The resource to serve.
380 public Resource getResource(String pathInContext)
383 if (_relativeResourceBase!=null)
384 pathInContext=URIUtil.addPaths(_relativeResourceBase,pathInContext);
388 if (_resourceBase!=null)
390 r = _resourceBase.addPath(pathInContext);
391 if (!_contextHandler.checkAlias(pathInContext,r))
394 else if (_servletContext instanceof ContextHandler.Context)
396 r = _contextHandler.getResource(pathInContext);
400 URL u = _servletContext.getResource(pathInContext);
401 r = _contextHandler.newResource(u);
404 if (LOG.isDebugEnabled())
405 LOG.debug("Resource "+pathInContext+"="+r);
407 catch (IOException e)
412 if((r==null || !r.exists()) && pathInContext.endsWith("/jetty-dir.css"))
418 /* ------------------------------------------------------------ */
420 protected void doGet(HttpServletRequest request, HttpServletResponse response)
421 throws ServletException, IOException
423 String servletPath=null;
424 String pathInfo=null;
425 Enumeration<String> reqRanges = null;
426 Boolean included =request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI)!=null;
427 if (included!=null && included.booleanValue())
429 servletPath=(String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
430 pathInfo=(String)request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
431 if (servletPath==null)
433 servletPath=request.getServletPath();
434 pathInfo=request.getPathInfo();
439 included = Boolean.FALSE;
440 servletPath = _pathInfoOnly?"/":request.getServletPath();
441 pathInfo = request.getPathInfo();
443 // Is this a Range request?
444 reqRanges = request.getHeaders(HttpHeader.RANGE.asString());
445 if (!hasDefinedRange(reqRanges))
449 String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
450 boolean endsWithSlash=(pathInfo==null?request.getServletPath():pathInfo).endsWith(URIUtil.SLASH);
453 // Find the resource and content
454 Resource resource=null;
455 HttpContent content=null;
456 boolean close_content=true;
460 String pathInContextGz=null;
462 if (!included.booleanValue() && _gzip && reqRanges==null && !endsWithSlash )
464 // Look for a gzip resource
465 pathInContextGz=pathInContext+".gz";
467 resource=getResource(pathInContextGz);
470 content=_cache.lookup(pathInContextGz);
471 resource=(content==null)?null:content.getResource();
474 // Does a gzip resource exist?
475 if (resource!=null && resource.exists() && !resource.isDirectory())
477 // Tell caches that response may vary by accept-encoding
478 response.addHeader(HttpHeader.VARY.asString(),HttpHeader.ACCEPT_ENCODING.asString());
480 // Does the client accept gzip?
481 String accept=request.getHeader(HttpHeader.ACCEPT_ENCODING.asString());
482 if (accept!=null && accept.indexOf("gzip")>=0)
491 resource=getResource(pathInContext);
494 content=_cache.lookup(pathInContext);
495 resource=content==null?null:content.getResource();
499 if (LOG.isDebugEnabled())
500 LOG.debug(String.format("uri=%s, resource=%s, content=%s",request.getRequestURI(),resource,content));
503 if (resource==null || !resource.exists())
506 throw new FileNotFoundException("!" + pathInContext);
507 response.sendError(HttpServletResponse.SC_NOT_FOUND);
509 else if (!resource.isDirectory())
511 if (endsWithSlash && pathInContext.length()>1)
513 String q=request.getQueryString();
514 pathInContext=pathInContext.substring(0,pathInContext.length()-1);
515 if (q!=null&&q.length()!=0)
516 pathInContext+="?"+q;
517 response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(_servletContext.getContextPath(),pathInContext)));
521 // ensure we have content
523 content=new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),response.getBufferSize(),_etags);
525 if (included.booleanValue() || passConditionalHeaders(request,response, resource,content))
527 if (gzip || isGzippedContent(pathInContext))
529 response.setHeader(HttpHeader.CONTENT_ENCODING.asString(),"gzip");
530 String mt=_servletContext.getMimeType(pathInContext);
532 response.setContentType(mt);
534 close_content=sendData(request,response,included.booleanValue(),resource,content,reqRanges);
542 if (!endsWithSlash || (pathInContext.length()==1 && request.getAttribute("org.eclipse.jetty.server.nullPathInfo")!=null))
544 StringBuffer buf=request.getRequestURL();
547 int param=buf.lastIndexOf(";");
551 buf.insert(param,'/');
552 String q=request.getQueryString();
553 if (q!=null&&q.length()!=0)
558 response.setContentLength(0);
559 response.sendRedirect(response.encodeRedirectURL(buf.toString()));
562 // else look for a welcome file
563 else if (null!=(welcome=getWelcomeFile(pathInContext)))
565 if (LOG.isDebugEnabled())
566 LOG.debug("welcome={}",welcome);
567 if (_redirectWelcome)
569 // Redirect to the index
570 response.setContentLength(0);
571 String q=request.getQueryString();
572 if (q!=null&&q.length()!=0)
573 response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths( _servletContext.getContextPath(),welcome)+"?"+q));
575 response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths( _servletContext.getContextPath(),welcome)));
579 // Forward to the index
580 RequestDispatcher dispatcher=request.getRequestDispatcher(welcome);
581 if (dispatcher!=null)
583 if (included.booleanValue())
584 dispatcher.include(request,response);
587 request.setAttribute("org.eclipse.jetty.server.welcome",welcome);
588 dispatcher.forward(request,response);
595 content=new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),_etags);
596 if (included.booleanValue() || passConditionalHeaders(request,response, resource,content))
597 sendDirectory(request,response,resource,pathInContext);
601 catch(IllegalArgumentException e)
603 LOG.warn(Log.EXCEPTION,e);
604 if(!response.isCommitted())
605 response.sendError(500, e.getMessage());
613 else if (resource!=null)
624 protected boolean isGzippedContent(String path)
626 if (path == null) return false;
628 for (String suffix:_gzipEquivalentFileExtensions)
629 if (path.endsWith(suffix))
634 /* ------------------------------------------------------------ */
635 private boolean hasDefinedRange(Enumeration<String> reqRanges)
637 return (reqRanges!=null && reqRanges.hasMoreElements());
640 /* ------------------------------------------------------------ */
642 protected void doPost(HttpServletRequest request, HttpServletResponse response)
643 throws ServletException, IOException
645 doGet(request,response);
648 /* ------------------------------------------------------------ */
650 * @see javax.servlet.http.HttpServlet#doTrace(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
653 protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
655 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
658 /* ------------------------------------------------------------ */
660 protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
661 throws ServletException, IOException
663 resp.setHeader("Allow", "GET,HEAD,POST,OPTIONS");
666 /* ------------------------------------------------------------ */
668 * Finds a matching welcome file for the supplied {@link Resource}. This will be the first entry in the list of
669 * configured {@link #_welcomes welcome files} that existing within the directory referenced by the <code>Resource</code>.
670 * If the resource is not a directory, or no matching file is found, then it may look for a valid servlet mapping.
671 * If there is none, then <code>null</code> is returned.
672 * The list of welcome files is read from the {@link ContextHandler} for this servlet, or
673 * <code>"index.jsp" , "index.html"</code> if that is <code>null</code>.
675 * @return The path of the matching welcome file in context or null.
676 * @throws IOException
677 * @throws MalformedURLException
679 private String getWelcomeFile(String pathInContext) throws MalformedURLException, IOException
684 String welcome_servlet=null;
685 for (int i=0;i<_welcomes.length;i++)
687 String welcome_in_context=URIUtil.addPaths(pathInContext,_welcomes[i]);
688 Resource welcome=getResource(welcome_in_context);
689 if (welcome!=null && welcome.exists())
692 if ((_welcomeServlets || _welcomeExactServlets) && welcome_servlet==null)
694 MappedEntry<?> entry=_servletHandler.getHolderEntry(welcome_in_context);
695 if (entry!=null && entry.getValue()!=_defaultHolder &&
696 (_welcomeServlets || (_welcomeExactServlets && entry.getKey().equals(welcome_in_context))))
697 welcome_servlet=welcome_in_context;
701 return welcome_servlet;
704 /* ------------------------------------------------------------ */
705 /* Check modification date headers.
707 protected boolean passConditionalHeaders(HttpServletRequest request,HttpServletResponse response, Resource resource, HttpContent content)
712 if (!HttpMethod.HEAD.is(request.getMethod()))
716 String ifm=request.getHeader(HttpHeader.IF_MATCH.asString());
720 if (content.getETag()!=null)
722 QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifm,", ",false,true);
723 while (!match && quoted.hasMoreTokens())
725 String tag = quoted.nextToken();
726 if (content.getETag().equals(tag))
733 response.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED);
738 String if_non_match_etag=request.getHeader(HttpHeader.IF_NONE_MATCH.asString());
739 if (if_non_match_etag!=null && content.getETag()!=null)
741 // Look for GzipFiltered version of etag
742 if (content.getETag().equals(request.getAttribute("o.e.j.s.GzipFilter.ETag")))
744 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
745 response.setHeader(HttpHeader.ETAG.asString(),if_non_match_etag);
749 // Handle special case of exact match.
750 if (content.getETag().equals(if_non_match_etag))
752 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
753 response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
757 // Handle list of tags
758 QuotedStringTokenizer quoted = new QuotedStringTokenizer(if_non_match_etag,", ",false,true);
759 while (quoted.hasMoreTokens())
761 String tag = quoted.nextToken();
762 if (content.getETag().equals(tag))
764 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
765 response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
770 // If etag requires content to be served, then do not check if-modified-since
775 // Handle if modified since
776 String ifms=request.getHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
779 //Get jetty's Response impl
780 String mdlm=content.getLastModified();
781 if (mdlm!=null && ifms.equals(mdlm))
783 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
785 response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
786 response.flushBuffer();
790 long ifmsl=request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
791 if (ifmsl!=-1 && resource.lastModified()/1000 <= ifmsl/1000)
793 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
795 response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
796 response.flushBuffer();
801 // Parse the if[un]modified dates and compare to resource
802 long date=request.getDateHeader(HttpHeader.IF_UNMODIFIED_SINCE.asString());
803 if (date!=-1 && resource.lastModified()/1000 > date/1000)
805 response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
811 catch(IllegalArgumentException iae)
813 if(!response.isCommitted())
814 response.sendError(400, iae.getMessage());
821 /* ------------------------------------------------------------------- */
822 protected void sendDirectory(HttpServletRequest request,
823 HttpServletResponse response,
825 String pathInContext)
830 response.sendError(HttpServletResponse.SC_FORBIDDEN);
835 String base = URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH);
837 //If the DefaultServlet has a resource base set, use it
838 if (_resourceBase != null)
840 // handle ResourceCollection
841 if (_resourceBase instanceof ResourceCollection)
842 resource=_resourceBase.addPath(pathInContext);
844 //Otherwise, try using the resource base of its enclosing context handler
845 else if (_contextHandler.getBaseResource() instanceof ResourceCollection)
846 resource=_contextHandler.getBaseResource().addPath(pathInContext);
848 String dir = resource.getListHTML(base,pathInContext.length()>1);
851 response.sendError(HttpServletResponse.SC_FORBIDDEN,
856 data=dir.getBytes("UTF-8");
857 response.setContentType("text/html; charset=UTF-8");
858 response.setContentLength(data.length);
859 response.getOutputStream().write(data);
862 /* ------------------------------------------------------------ */
863 protected boolean sendData(HttpServletRequest request,
864 HttpServletResponse response,
867 final HttpContent content,
868 Enumeration<String> reqRanges)
871 final long content_length = (content==null)?resource.length():content.getContentLength();
873 // Get the output stream (or writer)
874 OutputStream out =null;
878 out = response.getOutputStream();
880 // has a filter already written to the response?
881 written = out instanceof HttpOutput
882 ? ((HttpOutput)out).isWritten()
885 catch(IllegalStateException e)
887 out = new WriterOutputStream(response.getWriter());
888 written=true; // there may be data in writer buffer, so assume written
891 if (LOG.isDebugEnabled())
892 LOG.debug(String.format("sendData content=%s out=%s async=%b",content,out,request.isAsyncSupported()));
894 if ( reqRanges == null || !reqRanges.hasMoreElements() || content_length<0)
896 // if there were no ranges, send entire entity
899 resource.writeTo(out,0,content_length);
901 // else if we can't do a bypass write because of wrapping
902 else if (content==null || written || !(out instanceof HttpOutput))
905 writeHeaders(response,content,written?-1:content_length);
906 ByteBuffer buffer = (content==null)?null:content.getIndirectBuffer();
908 BufferUtil.writeTo(buffer,out);
910 resource.writeTo(out,0,content_length);
912 // else do a bypass write
916 if (response instanceof Response)
918 Response r = (Response)response;
919 writeOptionHeaders(r.getHttpFields());
920 r.setHeaders(content);
923 writeHeaders(response,content,content_length);
925 // write the content asynchronously if supported
926 if (request.isAsyncSupported())
928 final AsyncContext context = request.startAsync();
929 context.setTimeout(0);
931 ((HttpOutput)out).sendContent(content,new Callback()
934 public void succeeded()
941 public void failed(Throwable x)
943 if (x instanceof IOException)
952 public String toString()
954 return String.format("DefaultServlet@%x$CB", DefaultServlet.this.hashCode());
959 // otherwise write content blocking
960 ((HttpOutput)out).sendContent(content);
966 // Parse the satisfiable ranges
967 List<InclusiveByteRange> ranges =InclusiveByteRange.satisfiableRanges(reqRanges,content_length);
969 // if there are no satisfiable ranges, send 416 response
970 if (ranges==null || ranges.size()==0)
972 writeHeaders(response, content, content_length);
973 response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
974 response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
975 InclusiveByteRange.to416HeaderRangeString(content_length));
976 resource.writeTo(out,0,content_length);
980 // if there is only a single valid range (must be satisfiable
981 // since were here now), send that range with a 216 response
982 if ( ranges.size()== 1)
984 InclusiveByteRange singleSatisfiableRange = ranges.get(0);
985 long singleLength = singleSatisfiableRange.getSize(content_length);
986 writeHeaders(response,content,singleLength );
987 response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
988 if (!response.containsHeader(HttpHeader.DATE.asString()))
989 response.addDateHeader(HttpHeader.DATE.asString(),System.currentTimeMillis());
990 response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
991 singleSatisfiableRange.toHeaderRangeString(content_length));
992 resource.writeTo(out,singleSatisfiableRange.getFirst(content_length),singleLength);
996 // multiple non-overlapping valid ranges cause a multipart
997 // 216 response which does not require an overall
998 // content-length header
1000 writeHeaders(response,content,-1);
1001 String mimetype=(content==null?null:content.getContentType());
1003 LOG.warn("Unknown mimetype for "+request.getRequestURI());
1004 MultiPartOutputStream multi = new MultiPartOutputStream(out);
1005 response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
1006 if (!response.containsHeader(HttpHeader.DATE.asString()))
1007 response.addDateHeader(HttpHeader.DATE.asString(),System.currentTimeMillis());
1009 // If the request has a "Request-Range" header then we need to
1010 // send an old style multipart/x-byteranges Content-Type. This
1011 // keeps Netscape and acrobat happy. This is what Apache does.
1013 if (request.getHeader(HttpHeader.REQUEST_RANGE.asString())!=null)
1014 ctp = "multipart/x-byteranges; boundary=";
1016 ctp = "multipart/byteranges; boundary=";
1017 response.setContentType(ctp+multi.getBoundary());
1019 InputStream in=resource.getInputStream();
1022 // calculate the content-length
1024 String[] header = new String[ranges.size()];
1025 for (int i=0;i<ranges.size();i++)
1027 InclusiveByteRange ibr = ranges.get(i);
1028 header[i]=ibr.toHeaderRangeString(content_length);
1031 2+multi.getBoundary().length()+2+
1032 (mimetype==null?0:HttpHeader.CONTENT_TYPE.asString().length()+2+mimetype.length())+2+
1033 HttpHeader.CONTENT_RANGE.asString().length()+2+header[i].length()+2+
1035 (ibr.getLast(content_length)-ibr.getFirst(content_length))+1;
1037 length+=2+2+multi.getBoundary().length()+2+2;
1038 response.setContentLength(length);
1040 for (int i=0;i<ranges.size();i++)
1042 InclusiveByteRange ibr = ranges.get(i);
1043 multi.startPart(mimetype,new String[]{HttpHeader.CONTENT_RANGE+": "+header[i]});
1045 long start=ibr.getFirst(content_length);
1046 long size=ibr.getSize(content_length);
1049 // Handle non cached resource
1053 in=resource.getInputStream();
1062 IO.copy(in,multi,size);
1066 // Handle cached resource
1067 (resource).writeTo(multi,start,size);
1076 /* ------------------------------------------------------------ */
1077 protected void writeHeaders(HttpServletResponse response,HttpContent content,long count)
1079 if (content == null)
1081 // No content, then no headers to process
1082 // This is possible during bypass write because of wrapping
1083 // See .sendData() for more details.
1087 if (content.getContentType()!=null && response.getContentType()==null)
1088 response.setContentType(content.getContentType().toString());
1090 if (response instanceof Response)
1092 Response r=(Response)response;
1093 HttpFields fields = r.getHttpFields();
1095 if (content.getLastModified()!=null)
1096 fields.put(HttpHeader.LAST_MODIFIED,content.getLastModified());
1097 else if (content.getResource()!=null)
1099 long lml=content.getResource().lastModified();
1101 fields.putDateField(HttpHeader.LAST_MODIFIED,lml);
1105 r.setLongContentLength(count);
1107 writeOptionHeaders(fields);
1110 fields.put(HttpHeader.ETAG,content.getETag());
1114 long lml=content.getResource().lastModified();
1116 response.setDateHeader(HttpHeader.LAST_MODIFIED.asString(),lml);
1120 if (count<Integer.MAX_VALUE)
1121 response.setContentLength((int)count);
1123 response.setHeader(HttpHeader.CONTENT_LENGTH.asString(),Long.toString(count));
1126 writeOptionHeaders(response);
1129 response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
1133 /* ------------------------------------------------------------ */
1134 protected void writeOptionHeaders(HttpFields fields)
1137 fields.put(ACCEPT_RANGES);
1139 if (_cacheControl!=null)
1140 fields.put(_cacheControl);
1143 /* ------------------------------------------------------------ */
1144 protected void writeOptionHeaders(HttpServletResponse response)
1147 response.setHeader(HttpHeader.ACCEPT_RANGES.asString(),"bytes");
1149 if (_cacheControl!=null)
1150 response.setHeader(HttpHeader.CACHE_CONTROL.asString(),_cacheControl.getValue());
1153 /* ------------------------------------------------------------ */
1155 * @see javax.servlet.Servlet#destroy()
1158 public void destroy()
1161 _cache.flushCache();