Refactored DefaultServlet and ResourceHandler to use commong ResourceService
This commit is contained in:
parent
7b713507c6
commit
460e6e2ff3
|
@ -0,0 +1,781 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2015 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.server;
|
||||
|
||||
import static org.eclipse.jetty.http.GzipHttpContent.ETAG_GZIP_QUOTE;
|
||||
import static org.eclipse.jetty.http.GzipHttpContent.removeGzipFromETag;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.DateParser;
|
||||
import org.eclipse.jetty.http.HttpContent;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.PreEncodedHttpField;
|
||||
import org.eclipse.jetty.io.WriterOutputStream;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.MultiPartOutputStream;
|
||||
import org.eclipse.jetty.util.QuotedStringTokenizer;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
|
||||
/**
|
||||
* Abstract resource service, used by DefaultServlet and ResourceHandler
|
||||
*
|
||||
*/
|
||||
public abstract class ResourceService
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(ResourceService.class);
|
||||
|
||||
private static final PreEncodedHttpField ACCEPT_RANGES = new PreEncodedHttpField(HttpHeader.ACCEPT_RANGES, "bytes");
|
||||
|
||||
private HttpContent.Factory _contentFactory;
|
||||
private boolean _acceptRanges=true;
|
||||
private boolean _dirAllowed=true;
|
||||
private boolean _redirectWelcome=false;
|
||||
private boolean _gzip=false;
|
||||
private boolean _pathInfoOnly=false;
|
||||
private boolean _etags=false;
|
||||
private HttpField _cacheControl;
|
||||
private List<String> _gzipEquivalentFileExtensions;
|
||||
|
||||
public HttpContent.Factory getContentFactory()
|
||||
{
|
||||
return _contentFactory;
|
||||
}
|
||||
|
||||
public void setContentFactory(HttpContent.Factory contentFactory)
|
||||
{
|
||||
_contentFactory = contentFactory;
|
||||
}
|
||||
|
||||
public boolean isAcceptRanges()
|
||||
{
|
||||
return _acceptRanges;
|
||||
}
|
||||
|
||||
public void setAcceptRanges(boolean acceptRanges)
|
||||
{
|
||||
_acceptRanges = acceptRanges;
|
||||
}
|
||||
|
||||
public boolean isDirAllowed()
|
||||
{
|
||||
return _dirAllowed;
|
||||
}
|
||||
|
||||
public void setDirAllowed(boolean dirAllowed)
|
||||
{
|
||||
_dirAllowed = dirAllowed;
|
||||
}
|
||||
|
||||
public boolean isRedirectWelcome()
|
||||
{
|
||||
return _redirectWelcome;
|
||||
}
|
||||
|
||||
public void setRedirectWelcome(boolean redirectWelcome)
|
||||
{
|
||||
_redirectWelcome = redirectWelcome;
|
||||
}
|
||||
|
||||
public boolean isGzip()
|
||||
{
|
||||
return _gzip;
|
||||
}
|
||||
|
||||
public void setGzip(boolean gzip)
|
||||
{
|
||||
_gzip = gzip;
|
||||
}
|
||||
|
||||
public boolean isPathInfoOnly()
|
||||
{
|
||||
return _pathInfoOnly;
|
||||
}
|
||||
|
||||
public void setPathInfoOnly(boolean pathInfoOnly)
|
||||
{
|
||||
_pathInfoOnly = pathInfoOnly;
|
||||
}
|
||||
|
||||
public boolean isEtags()
|
||||
{
|
||||
return _etags;
|
||||
}
|
||||
|
||||
public void setEtags(boolean etags)
|
||||
{
|
||||
_etags = etags;
|
||||
}
|
||||
|
||||
public HttpField getCacheControl()
|
||||
{
|
||||
return _cacheControl;
|
||||
}
|
||||
|
||||
public void setCacheControl(HttpField cacheControl)
|
||||
{
|
||||
_cacheControl = cacheControl;
|
||||
}
|
||||
|
||||
public List<String> getGzipEquivalentFileExtensions()
|
||||
{
|
||||
return _gzipEquivalentFileExtensions;
|
||||
}
|
||||
|
||||
public void setGzipEquivalentFileExtensions(List<String> gzipEquivalentFileExtensions)
|
||||
{
|
||||
_gzipEquivalentFileExtensions = gzipEquivalentFileExtensions;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public void doGet(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException
|
||||
{
|
||||
String servletPath=null;
|
||||
String pathInfo=null;
|
||||
Enumeration<String> reqRanges = null;
|
||||
boolean included =request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI)!=null;
|
||||
if (included)
|
||||
{
|
||||
servletPath= _pathInfoOnly?"/":(String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
|
||||
pathInfo=(String)request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
|
||||
if (servletPath==null)
|
||||
{
|
||||
servletPath=request.getServletPath();
|
||||
pathInfo=request.getPathInfo();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
servletPath = _pathInfoOnly?"/":request.getServletPath();
|
||||
pathInfo = request.getPathInfo();
|
||||
|
||||
// Is this a Range request?
|
||||
reqRanges = request.getHeaders(HttpHeader.RANGE.asString());
|
||||
if (!hasDefinedRange(reqRanges))
|
||||
reqRanges = null;
|
||||
}
|
||||
|
||||
String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
|
||||
|
||||
boolean endsWithSlash=(pathInfo==null?request.getServletPath():pathInfo).endsWith(URIUtil.SLASH);
|
||||
boolean gzippable=_gzip && !endsWithSlash && !included && reqRanges==null;
|
||||
|
||||
HttpContent content=null;
|
||||
boolean release_content=true;
|
||||
try
|
||||
{
|
||||
// Find the content
|
||||
content=_contentFactory.getContent(pathInContext,response.getBufferSize());
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.info("content={}",content);
|
||||
|
||||
// Not found?
|
||||
if (content==null || !content.getResource().exists())
|
||||
{
|
||||
if (included)
|
||||
throw new FileNotFoundException("!" + pathInContext);
|
||||
notFound(request,response);
|
||||
return;
|
||||
}
|
||||
|
||||
// Directory?
|
||||
if (content.getResource().isDirectory())
|
||||
{
|
||||
sendWelcome(content,pathInContext,endsWithSlash,included,request,response);
|
||||
return;
|
||||
}
|
||||
|
||||
// Strip slash?
|
||||
if (endsWithSlash && pathInContext.length()>1)
|
||||
{
|
||||
String q=request.getQueryString();
|
||||
pathInContext=pathInContext.substring(0,pathInContext.length()-1);
|
||||
if (q!=null&&q.length()!=0)
|
||||
pathInContext+="?"+q;
|
||||
response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),pathInContext)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Conditional response?
|
||||
if (!included && !passConditionalHeaders(request,response,content))
|
||||
return;
|
||||
|
||||
// Gzip?
|
||||
HttpContent gzip_content = gzippable?content.getGzipContent():null;
|
||||
if (gzip_content!=null)
|
||||
{
|
||||
// Tell caches that response may vary by accept-encoding
|
||||
response.addHeader(HttpHeader.VARY.asString(),HttpHeader.ACCEPT_ENCODING.asString());
|
||||
|
||||
// Does the client accept gzip?
|
||||
String accept=request.getHeader(HttpHeader.ACCEPT_ENCODING.asString());
|
||||
if (accept!=null && accept.indexOf("gzip")>=0)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("gzip={}",gzip_content);
|
||||
content=gzip_content;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO this should be done by HttpContent#getContentEncoding
|
||||
if (isGzippedContent(pathInContext))
|
||||
response.setHeader(HttpHeader.CONTENT_ENCODING.asString(),"gzip");
|
||||
|
||||
// Send the data
|
||||
release_content=sendData(request,response,included,content,reqRanges);
|
||||
|
||||
}
|
||||
catch(IllegalArgumentException e)
|
||||
{
|
||||
LOG.warn(Log.EXCEPTION,e);
|
||||
if(!response.isCommitted())
|
||||
response.sendError(500, e.getMessage());
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (release_content)
|
||||
{
|
||||
if (content!=null)
|
||||
content.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected void sendWelcome(HttpContent content, String pathInContext, boolean endsWithSlash, boolean included, HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException
|
||||
{
|
||||
// Redirect to directory
|
||||
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()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// look for a welcome file
|
||||
String welcome=getWelcomeFile(pathInContext);
|
||||
if (welcome!=null)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("welcome={}",welcome);
|
||||
if (_redirectWelcome)
|
||||
{
|
||||
// Redirect to the index
|
||||
response.setContentLength(0);
|
||||
String q=request.getQueryString();
|
||||
if (q!=null&&q.length()!=0)
|
||||
response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),welcome)+"?"+q));
|
||||
else
|
||||
response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),welcome)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Forward to the index
|
||||
RequestDispatcher dispatcher=request.getRequestDispatcher(welcome);
|
||||
if (dispatcher!=null)
|
||||
{
|
||||
if (included)
|
||||
dispatcher.include(request,response);
|
||||
else
|
||||
{
|
||||
request.setAttribute("org.eclipse.jetty.server.welcome",welcome);
|
||||
dispatcher.forward(request,response);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (included || passConditionalHeaders(request,response, content))
|
||||
sendDirectory(request,response,content.getResource(),pathInContext);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
protected boolean isGzippedContent(String path)
|
||||
{
|
||||
if (path == null || _gzipEquivalentFileExtensions==null)
|
||||
return false;
|
||||
|
||||
for (String suffix:_gzipEquivalentFileExtensions)
|
||||
if (path.endsWith(suffix))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
private boolean hasDefinedRange(Enumeration<String> reqRanges)
|
||||
{
|
||||
return (reqRanges!=null && reqRanges.hasMoreElements());
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Finds a matching welcome file for the supplied {@link Resource}.
|
||||
* @param pathInContext the path of the request
|
||||
* @return The path of the matching welcome file in context or null.
|
||||
*/
|
||||
protected abstract String getWelcomeFile(String pathInContext);
|
||||
|
||||
protected abstract void notFound(HttpServletRequest request, HttpServletResponse response) throws IOException;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* Check modification date headers.
|
||||
*/
|
||||
protected boolean passConditionalHeaders(HttpServletRequest request,HttpServletResponse response, HttpContent content)
|
||||
throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
String ifm=null;
|
||||
String ifnm=null;
|
||||
String ifms=null;
|
||||
long ifums=-1;
|
||||
|
||||
if (request instanceof Request)
|
||||
{
|
||||
// Find multiple fields by iteration as an optimization
|
||||
HttpFields fields = ((Request)request).getHttpFields();
|
||||
for (int i=fields.size();i-->0;)
|
||||
{
|
||||
HttpField field=fields.getField(i);
|
||||
if (field.getHeader() != null)
|
||||
{
|
||||
switch (field.getHeader())
|
||||
{
|
||||
case IF_MATCH:
|
||||
ifm=field.getValue();
|
||||
break;
|
||||
case IF_NONE_MATCH:
|
||||
ifnm=field.getValue();
|
||||
break;
|
||||
case IF_MODIFIED_SINCE:
|
||||
ifms=field.getValue();
|
||||
break;
|
||||
case IF_UNMODIFIED_SINCE:
|
||||
ifums=DateParser.parseDate(field.getValue());
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ifm=request.getHeader(HttpHeader.IF_MATCH.asString());
|
||||
ifnm=request.getHeader(HttpHeader.IF_NONE_MATCH.asString());
|
||||
ifms=request.getHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
|
||||
ifums=request.getDateHeader(HttpHeader.IF_UNMODIFIED_SINCE.asString());
|
||||
}
|
||||
|
||||
if (!HttpMethod.HEAD.is(request.getMethod()))
|
||||
{
|
||||
if (_etags)
|
||||
{
|
||||
String etag=content.getETagValue();
|
||||
if (ifm!=null)
|
||||
{
|
||||
boolean match=false;
|
||||
if (etag!=null)
|
||||
{
|
||||
QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifm,", ",false,true);
|
||||
while (!match && quoted.hasMoreTokens())
|
||||
{
|
||||
String tag = quoted.nextToken();
|
||||
if (etag.equals(tag) || tag.endsWith(ETAG_GZIP_QUOTE) && etag.equals(removeGzipFromETag(tag)))
|
||||
match=true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!match)
|
||||
{
|
||||
response.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ifnm!=null && etag!=null)
|
||||
{
|
||||
// Handle special case of exact match OR gzip exact match
|
||||
if (etag.equals(ifnm) || ifnm.endsWith(ETAG_GZIP_QUOTE) && ifnm.indexOf(',')<0 && etag.equals(removeGzipFromETag(etag)))
|
||||
{
|
||||
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
||||
response.setHeader(HttpHeader.ETAG.asString(),ifnm);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle list of tags
|
||||
QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifnm,", ",false,true);
|
||||
while (quoted.hasMoreTokens())
|
||||
{
|
||||
String tag = quoted.nextToken();
|
||||
if (etag.equals(tag) || tag.endsWith(ETAG_GZIP_QUOTE) && etag.equals(removeGzipFromETag(tag)))
|
||||
{
|
||||
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
||||
response.setHeader(HttpHeader.ETAG.asString(),tag);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If etag requires content to be served, then do not check if-modified-since
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle if modified since
|
||||
if (ifms!=null)
|
||||
{
|
||||
//Get jetty's Response impl
|
||||
String mdlm=content.getLastModifiedValue();
|
||||
if (mdlm!=null && ifms.equals(mdlm))
|
||||
{
|
||||
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
||||
if (_etags)
|
||||
response.setHeader(HttpHeader.ETAG.asString(),content.getETagValue());
|
||||
response.flushBuffer();
|
||||
return false;
|
||||
}
|
||||
|
||||
long ifmsl=request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
|
||||
if (ifmsl!=-1 && content.getResource().lastModified()/1000 <= ifmsl/1000)
|
||||
{
|
||||
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
||||
if (_etags)
|
||||
response.setHeader(HttpHeader.ETAG.asString(),content.getETagValue());
|
||||
response.flushBuffer();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the if[un]modified dates and compare to resource
|
||||
if (ifums!=-1 && content.getResource().lastModified()/1000 > ifums/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,
|
||||
String pathInContext)
|
||||
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,pathInContext.length()>1);
|
||||
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 boolean sendData(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
boolean include,
|
||||
final HttpContent content,
|
||||
Enumeration<String> reqRanges)
|
||||
throws IOException
|
||||
{
|
||||
final long content_length = content.getContentLengthValue();
|
||||
|
||||
// Get the output stream (or writer)
|
||||
OutputStream out =null;
|
||||
boolean written;
|
||||
try
|
||||
{
|
||||
out = response.getOutputStream();
|
||||
|
||||
// has something already written to the response?
|
||||
written = out instanceof HttpOutput
|
||||
? ((HttpOutput)out).isWritten()
|
||||
: true;
|
||||
}
|
||||
catch(IllegalStateException e)
|
||||
{
|
||||
out = new WriterOutputStream(response.getWriter());
|
||||
written=true; // there may be data in writer buffer, so assume written
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(String.format("sendData content=%s out=%s async=%b",content,out,request.isAsyncSupported()));
|
||||
|
||||
if ( reqRanges == null || !reqRanges.hasMoreElements() || content_length<0)
|
||||
{
|
||||
// if there were no ranges, send entire entity
|
||||
if (include)
|
||||
{
|
||||
// write without headers
|
||||
content.getResource().writeTo(out,0,content_length);
|
||||
}
|
||||
// else if we can't do a bypass write because of wrapping
|
||||
else if (written || !(out instanceof HttpOutput))
|
||||
{
|
||||
// write normally
|
||||
putHeaders(response,content,written?-1:0);
|
||||
ByteBuffer buffer = content.getIndirectBuffer();
|
||||
if (buffer!=null)
|
||||
BufferUtil.writeTo(buffer,out);
|
||||
else
|
||||
content.getResource().writeTo(out,0,content_length);
|
||||
}
|
||||
// else do a bypass write
|
||||
else
|
||||
{
|
||||
// write the headers
|
||||
putHeaders(response,content,0);
|
||||
|
||||
// write the content asynchronously if supported
|
||||
if (request.isAsyncSupported() && content.getContentLengthValue()>response.getBufferSize())
|
||||
{
|
||||
final AsyncContext context = request.startAsync();
|
||||
context.setTimeout(0);
|
||||
|
||||
((HttpOutput)out).sendContent(content,new Callback()
|
||||
{
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
context.complete();
|
||||
content.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
if (x instanceof IOException)
|
||||
LOG.debug(x);
|
||||
else
|
||||
LOG.warn(x);
|
||||
context.complete();
|
||||
content.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("ResourceService@%x$CB", ResourceService.this.hashCode());
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
// otherwise write content blocking
|
||||
((HttpOutput)out).sendContent(content);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Parse the satisfiable ranges
|
||||
List<InclusiveByteRange> ranges =InclusiveByteRange.satisfiableRanges(reqRanges,content_length);
|
||||
|
||||
// if there are no satisfiable ranges, send 416 response
|
||||
if (ranges==null || ranges.size()==0)
|
||||
{
|
||||
putHeaders(response,content,0);
|
||||
response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
|
||||
response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
|
||||
InclusiveByteRange.to416HeaderRangeString(content_length));
|
||||
content.getResource().writeTo(out,0,content_length);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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 = ranges.get(0);
|
||||
long singleLength = singleSatisfiableRange.getSize(content_length);
|
||||
putHeaders(response,content,singleLength);
|
||||
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
|
||||
if (!response.containsHeader(HttpHeader.DATE.asString()))
|
||||
response.addDateHeader(HttpHeader.DATE.asString(),System.currentTimeMillis());
|
||||
response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
|
||||
singleSatisfiableRange.toHeaderRangeString(content_length));
|
||||
content.getResource().writeTo(out,singleSatisfiableRange.getFirst(content_length),singleLength);
|
||||
return true;
|
||||
}
|
||||
|
||||
// multiple non-overlapping valid ranges cause a multipart
|
||||
// 216 response which does not require an overall
|
||||
// content-length header
|
||||
//
|
||||
putHeaders(response,content,-1);
|
||||
String mimetype=(content==null?null:content.getContentTypeValue());
|
||||
if (mimetype==null)
|
||||
LOG.warn("Unknown mimetype for "+request.getRequestURI());
|
||||
MultiPartOutputStream multi = new MultiPartOutputStream(out);
|
||||
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
|
||||
if (!response.containsHeader(HttpHeader.DATE.asString()))
|
||||
response.addDateHeader(HttpHeader.DATE.asString(),System.currentTimeMillis());
|
||||
|
||||
// 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(HttpHeader.REQUEST_RANGE.asString())!=null)
|
||||
ctp = "multipart/x-byteranges; boundary=";
|
||||
else
|
||||
ctp = "multipart/byteranges; boundary=";
|
||||
response.setContentType(ctp+multi.getBoundary());
|
||||
|
||||
InputStream in=content.getResource().getInputStream();
|
||||
long pos=0;
|
||||
|
||||
// calculate the content-length
|
||||
int length=0;
|
||||
String[] header = new String[ranges.size()];
|
||||
for (int i=0;i<ranges.size();i++)
|
||||
{
|
||||
InclusiveByteRange ibr = ranges.get(i);
|
||||
header[i]=ibr.toHeaderRangeString(content_length);
|
||||
length+=
|
||||
((i>0)?2:0)+
|
||||
2+multi.getBoundary().length()+2+
|
||||
(mimetype==null?0:HttpHeader.CONTENT_TYPE.asString().length()+2+mimetype.length())+2+
|
||||
HttpHeader.CONTENT_RANGE.asString().length()+2+header[i].length()+2+
|
||||
2+
|
||||
(ibr.getLast(content_length)-ibr.getFirst(content_length))+1;
|
||||
}
|
||||
length+=2+2+multi.getBoundary().length()+2+2;
|
||||
response.setContentLength(length);
|
||||
|
||||
for (int i=0;i<ranges.size();i++)
|
||||
{
|
||||
InclusiveByteRange ibr = ranges.get(i);
|
||||
multi.startPart(mimetype,new String[]{HttpHeader.CONTENT_RANGE+": "+header[i]});
|
||||
|
||||
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=content.getResource().getInputStream();
|
||||
pos=0;
|
||||
}
|
||||
if (pos<start)
|
||||
{
|
||||
in.skip(start-pos);
|
||||
pos=start;
|
||||
}
|
||||
|
||||
IO.copy(in,multi,size);
|
||||
pos+=size;
|
||||
}
|
||||
else
|
||||
// Handle cached resource
|
||||
content.getResource().writeTo(multi,start,size);
|
||||
}
|
||||
if (in!=null)
|
||||
in.close();
|
||||
multi.close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
protected void putHeaders(HttpServletResponse response,HttpContent content, long contentLength)
|
||||
{
|
||||
if (response instanceof Response)
|
||||
{
|
||||
Response r = (Response)response;
|
||||
r.putHeaders(content,contentLength,_etags);
|
||||
HttpFields f = r.getHttpFields();
|
||||
if (_acceptRanges)
|
||||
f.put(ACCEPT_RANGES);
|
||||
|
||||
if (_cacheControl!=null)
|
||||
f.put(_cacheControl);
|
||||
}
|
||||
else
|
||||
{
|
||||
Response.putHeaders(response,content,contentLength,_etags);
|
||||
if (_acceptRanges)
|
||||
response.setHeader(ACCEPT_RANGES.getName(),ACCEPT_RANGES.getValue());
|
||||
|
||||
if (_cacheControl!=null)
|
||||
response.setHeader(_cacheControl.getName(),_cacheControl.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -19,43 +19,35 @@
|
|||
package org.eclipse.jetty.server.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.MimeTypes;
|
||||
import org.eclipse.jetty.io.WriterOutputStream;
|
||||
import org.eclipse.jetty.server.HttpOutput;
|
||||
import org.eclipse.jetty.http.PreEncodedHttpField;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Response;
|
||||
import org.eclipse.jetty.server.ResourceContentFactory;
|
||||
import org.eclipse.jetty.server.ResourceService;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler.Context;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.resource.PathResource;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Resource Handler.
|
||||
/**
|
||||
* Resource Handler.
|
||||
*
|
||||
* This handle will serve static content and handle If-Modified-Since headers.
|
||||
* No caching is done.
|
||||
* Requests for resources that do not exist are let pass (Eg no 404's).
|
||||
* This handle will serve static content and handle If-Modified-Since headers. No caching is done. Requests for resources that do not exist are let pass (Eg no
|
||||
* 404's).
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
@ -63,126 +55,56 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory
|
|||
{
|
||||
private static final Logger LOG = Log.getLogger(ResourceHandler.class);
|
||||
|
||||
ContextHandler _context;
|
||||
Resource _baseResource;
|
||||
ContextHandler _context;
|
||||
Resource _defaultStylesheet;
|
||||
Resource _stylesheet;
|
||||
String[] _welcomeFiles={"index.html"};
|
||||
MimeTypes _mimeTypes;
|
||||
String _cacheControl;
|
||||
boolean _directory;
|
||||
boolean _gzip;
|
||||
boolean _etags;
|
||||
int _minMemoryMappedContentLength=0;
|
||||
int _minAsyncContentLength=16*1024;
|
||||
private final ResourceService _resourceService;
|
||||
Resource _stylesheet;
|
||||
String[] _welcomes =
|
||||
{ "index.html" };
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public ResourceHandler()
|
||||
{
|
||||
_resourceService = new ResourceService()
|
||||
{
|
||||
@Override
|
||||
protected String getWelcomeFile(String pathInContext)
|
||||
{
|
||||
if (_welcomes == null)
|
||||
return null;
|
||||
|
||||
}
|
||||
String welcome_servlet = null;
|
||||
for (int i = 0; i < _welcomes.length; i++)
|
||||
{
|
||||
String welcome_in_context = URIUtil.addPaths(pathInContext,_welcomes[i]);
|
||||
Resource welcome = getResource(welcome_in_context);
|
||||
if (welcome != null && welcome.exists())
|
||||
return _welcomes[i];
|
||||
}
|
||||
return welcome_servlet;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public MimeTypes getMimeTypes()
|
||||
{
|
||||
return _mimeTypes;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public void setMimeTypes(MimeTypes mimeTypes)
|
||||
{
|
||||
_mimeTypes = mimeTypes;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Get the directory option.
|
||||
* @return true if directories are listed.
|
||||
*/
|
||||
public boolean isDirectoriesListed()
|
||||
{
|
||||
return _directory;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Set the directory.
|
||||
* @param directory true if directories are listed.
|
||||
*/
|
||||
public void setDirectoriesListed(boolean directory)
|
||||
{
|
||||
_directory = directory;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Get minimum memory mapped file content length.
|
||||
* @return the minimum size in bytes of a file resource that will
|
||||
* be served using a memory mapped buffer, or -1 (default) for no memory mapped
|
||||
* buffers.
|
||||
*/
|
||||
public int getMinMemoryMappedContentLength()
|
||||
{
|
||||
return _minMemoryMappedContentLength;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Set minimum memory mapped file content length.
|
||||
* @param minMemoryMappedFileSize the minimum size in bytes of a file resource that will
|
||||
* be served using a memory mapped buffer, or -1 for no memory mapped
|
||||
* buffers.
|
||||
*/
|
||||
public void setMinMemoryMappedContentLength(int minMemoryMappedFileSize)
|
||||
{
|
||||
_minMemoryMappedContentLength = minMemoryMappedFileSize;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Get the minimum content length for async handling.
|
||||
* @return The minimum size in bytes of the content before asynchronous
|
||||
* handling is used, or -1 for no async handling or 0 (default) for using
|
||||
* {@link HttpServletResponse#getBufferSize()} as the minimum length.
|
||||
*/
|
||||
public int getMinAsyncContentLength()
|
||||
{
|
||||
return _minAsyncContentLength;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Set the minimum content length for async handling.
|
||||
* @param minAsyncContentLength The minimum size in bytes of the content before asynchronous
|
||||
* handling is used, or -1 for no async handling or 0 for using
|
||||
* {@link HttpServletResponse#getBufferSize()} as the minimum length.
|
||||
*/
|
||||
public void setMinAsyncContentLength(int minAsyncContentLength)
|
||||
{
|
||||
_minAsyncContentLength = minAsyncContentLength;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @return True if ETag processing is done
|
||||
*/
|
||||
public boolean isEtags()
|
||||
{
|
||||
return _etags;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @param etags True if ETag processing is done
|
||||
*/
|
||||
public void setEtags(boolean etags)
|
||||
{
|
||||
_etags = etags;
|
||||
@Override
|
||||
protected void notFound(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
}
|
||||
};
|
||||
_resourceService.setGzipEquivalentFileExtensions(new ArrayList<>(Arrays.asList(new String[]
|
||||
{ ".svgz" })));
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@Override
|
||||
public void doStart()
|
||||
throws Exception
|
||||
public void doStart() throws Exception
|
||||
{
|
||||
Context scontext = ContextHandler.getCurrentContext();
|
||||
_context = (scontext==null?null:scontext.getContextHandler());
|
||||
_mimeTypes = _context==null?new MimeTypes():_context.getMimeTypes();
|
||||
|
||||
_context = (scontext == null?null:scontext.getContextHandler());
|
||||
_mimeTypes = _context == null?new MimeTypes():_context.getMimeTypes();
|
||||
|
||||
_resourceService.setContentFactory(new ResourceContentFactory(this,_mimeTypes,_resourceService.isGzip()));
|
||||
|
||||
super.doStart();
|
||||
}
|
||||
|
||||
|
@ -192,35 +114,366 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory
|
|||
*/
|
||||
public Resource getBaseResource()
|
||||
{
|
||||
if (_baseResource==null)
|
||||
if (_baseResource == null)
|
||||
return null;
|
||||
return _baseResource;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @return the cacheControl header to set on all static content.
|
||||
*/
|
||||
public String getCacheControl()
|
||||
{
|
||||
return _resourceService.getCacheControl().getValue();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @return file extensions that signify that a file is gzip compressed. Eg ".svgz"
|
||||
*/
|
||||
public List<String> getGzipEquivalentFileExtensions()
|
||||
{
|
||||
return _resourceService.getGzipEquivalentFileExtensions();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public MimeTypes getMimeTypes()
|
||||
{
|
||||
return _mimeTypes;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Get the minimum content length for async handling.
|
||||
*
|
||||
* @return The minimum size in bytes of the content before asynchronous handling is used, or -1 for no async handling or 0 (default) for using
|
||||
* {@link HttpServletResponse#getBufferSize()} as the minimum length.
|
||||
*/
|
||||
@Deprecated
|
||||
public int getMinAsyncContentLength()
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Get minimum memory mapped file content length.
|
||||
*
|
||||
* @return the minimum size in bytes of a file resource that will be served using a memory mapped buffer, or -1 (default) for no memory mapped buffers.
|
||||
*/
|
||||
@Deprecated
|
||||
public int getMinMemoryMappedContentLength()
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/*
|
||||
*/
|
||||
@Override
|
||||
public Resource getResource(String path)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} getResource({})",_context == null?_baseResource:_context,_baseResource,path);
|
||||
|
||||
if (path == null || !path.startsWith("/"))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
Resource r = null;
|
||||
|
||||
if (_baseResource != null)
|
||||
{
|
||||
path = URIUtil.canonicalPath(path);
|
||||
r = _baseResource.addPath(path);
|
||||
|
||||
if (r != null && r.isAlias() && (_context == null || !_context.checkAlias(path,r)))
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("resource={} alias={}",r,r.getAlias());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else if (_context != null)
|
||||
r = _context.getResource(path);
|
||||
|
||||
if ((r == null || !r.exists()) && path.endsWith("/jetty-dir.css"))
|
||||
r = getStylesheet();
|
||||
|
||||
return r;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.debug(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @return Returns the base resource as a string.
|
||||
*/
|
||||
public String getResourceBase()
|
||||
{
|
||||
if (_baseResource==null)
|
||||
if (_baseResource == null)
|
||||
return null;
|
||||
return _baseResource.toString();
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @param base The resourceBase to set.
|
||||
* @return Returns the stylesheet as a Resource.
|
||||
*/
|
||||
public void setBaseResource(Resource base)
|
||||
public Resource getStylesheet()
|
||||
{
|
||||
_baseResource=base;
|
||||
if (_stylesheet != null)
|
||||
{
|
||||
return _stylesheet;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_defaultStylesheet == null)
|
||||
{
|
||||
_defaultStylesheet = Resource.newResource(this.getClass().getResource("/jetty-dir.css"));
|
||||
}
|
||||
return _defaultStylesheet;
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public String[] getWelcomeFiles()
|
||||
{
|
||||
return _welcomes;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/*
|
||||
* @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
|
||||
*/
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
{
|
||||
if (baseRequest.isHandled())
|
||||
return;
|
||||
|
||||
if (!HttpMethod.GET.is(request.getMethod()))
|
||||
{
|
||||
if (!HttpMethod.HEAD.is(request.getMethod()))
|
||||
{
|
||||
// try another handler
|
||||
super.handle(target,baseRequest,request,response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_resourceService.doGet(request,response);
|
||||
|
||||
if (response.isCommitted())
|
||||
baseRequest.setHandled(true);
|
||||
else
|
||||
// no resource - try other handlers
|
||||
super.handle(target,baseRequest,request,response);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @param resourceBase The base resource as a string.
|
||||
* @return If true, range requests and responses are supported
|
||||
*/
|
||||
public boolean isAcceptRanges()
|
||||
{
|
||||
return _resourceService.isAcceptRanges();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If true, directory listings are returned if no welcome file is found. Else 403 Forbidden.
|
||||
*/
|
||||
public boolean isDirAllowed()
|
||||
{
|
||||
return _resourceService.isDirAllowed();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Get the directory option.
|
||||
*
|
||||
* @return true if directories are listed.
|
||||
*/
|
||||
public boolean isDirectoriesListed()
|
||||
{
|
||||
return _resourceService.isDirAllowed();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @return True if ETag processing is done
|
||||
*/
|
||||
public boolean isEtags()
|
||||
{
|
||||
return _resourceService.isEtags();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @return If set to true, then static content will be served as gzip content encoded if a matching resource is found ending with ".gz"
|
||||
*/
|
||||
public boolean isGzip()
|
||||
{
|
||||
return _resourceService.isGzip();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @return true, only the path info will be applied to the resourceBase
|
||||
*/
|
||||
public boolean isPathInfoOnly()
|
||||
{
|
||||
return _resourceService.isPathInfoOnly();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @return If true, welcome files are redirected rather than forwarded to.
|
||||
*/
|
||||
public boolean isRedirectWelcome()
|
||||
{
|
||||
return _resourceService.isRedirectWelcome();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @param acceptRanges If true, range requests and responses are supported
|
||||
*/
|
||||
public void setAcceptRanges(boolean acceptRanges)
|
||||
{
|
||||
_resourceService.setAcceptRanges(acceptRanges);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @param base The resourceBase to server content from. If null the
|
||||
* context resource base is used.
|
||||
*/
|
||||
public void setBaseResource(Resource base)
|
||||
{
|
||||
_baseResource = base;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @param cacheControl
|
||||
* the cacheControl header to set on all static content.
|
||||
*/
|
||||
public void setCacheControl(String cacheControl)
|
||||
{
|
||||
_resourceService.setCacheControl(new PreEncodedHttpField(HttpHeader.CACHE_CONTROL,cacheControl));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param dirAllowed
|
||||
* If true, directory listings are returned if no welcome file is found. Else 403 Forbidden.
|
||||
*/
|
||||
public void setDirAllowed(boolean dirAllowed)
|
||||
{
|
||||
_resourceService.setDirAllowed(dirAllowed);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Set the directory.
|
||||
*
|
||||
* @param directory
|
||||
* true if directories are listed.
|
||||
*/
|
||||
public void setDirectoriesListed(boolean directory)
|
||||
{
|
||||
_resourceService.setDirAllowed(directory);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @param etags
|
||||
* True if ETag processing is done
|
||||
*/
|
||||
public void setEtags(boolean etags)
|
||||
{
|
||||
_resourceService.setEtags(etags);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @param gzip
|
||||
* If set to true, then static content will be served as gzip content encoded if a matching resource is found ending with ".gz"
|
||||
*/
|
||||
public void setGzip(boolean gzip)
|
||||
{
|
||||
_resourceService.setGzip(gzip);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @param gzipEquivalentFileExtensions file extensions that signify that a file is gzip compressed. Eg ".svgz"
|
||||
*/
|
||||
public void setGzipEquivalentFileExtensions(List<String> gzipEquivalentFileExtensions)
|
||||
{
|
||||
_resourceService.setGzipEquivalentFileExtensions(gzipEquivalentFileExtensions);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public void setMimeTypes(MimeTypes mimeTypes)
|
||||
{
|
||||
_mimeTypes = mimeTypes;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Set the minimum content length for async handling.
|
||||
*
|
||||
* @param minAsyncContentLength
|
||||
* The minimum size in bytes of the content before asynchronous handling is used, or -1 for no async handling or 0 for using
|
||||
* {@link HttpServletResponse#getBufferSize()} as the minimum length.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setMinAsyncContentLength(int minAsyncContentLength)
|
||||
{
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Set minimum memory mapped file content length.
|
||||
*
|
||||
* @param minMemoryMappedFileSize
|
||||
* the minimum size in bytes of a file resource that will be served using a memory mapped buffer, or -1 for no memory mapped buffers.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setMinMemoryMappedContentLength(int minMemoryMappedFileSize)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pathInfoOnly
|
||||
* true, only the path info will be applied to the resourceBase
|
||||
*/
|
||||
public void setPathInfoOnly(boolean pathInfoOnly)
|
||||
{
|
||||
_resourceService.setPathInfoOnly(pathInfoOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param redirectWelcome
|
||||
* If true, welcome files are redirected rather than forwarded to.
|
||||
*/
|
||||
public void setRedirectWelcome(boolean redirectWelcome)
|
||||
{
|
||||
_resourceService.setRedirectWelcome(redirectWelcome);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @param resourceBase
|
||||
* The base resource as a string.
|
||||
*/
|
||||
public void setResourceBase(String resourceBase)
|
||||
{
|
||||
|
@ -238,40 +491,21 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory
|
|||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @return Returns the stylesheet as a Resource.
|
||||
*/
|
||||
public Resource getStylesheet()
|
||||
{
|
||||
if(_stylesheet != null)
|
||||
{
|
||||
return _stylesheet;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(_defaultStylesheet == null)
|
||||
{
|
||||
_defaultStylesheet = Resource.newResource(this.getClass().getResource("/jetty-dir.css"));
|
||||
}
|
||||
return _defaultStylesheet;
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @param stylesheet The location of the stylesheet to be used as a String.
|
||||
* @param stylesheet
|
||||
* The location of the stylesheet to be used as a String.
|
||||
*/
|
||||
public void setStylesheet(String stylesheet)
|
||||
{
|
||||
try
|
||||
{
|
||||
_stylesheet = Resource.newResource(stylesheet);
|
||||
if(!_stylesheet.exists())
|
||||
if (!_stylesheet.exists())
|
||||
{
|
||||
LOG.warn("unable to find custom stylesheet: " + stylesheet);
|
||||
_stylesheet = null;
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.warn(e.toString());
|
||||
LOG.debug(e);
|
||||
|
@ -279,361 +513,10 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory
|
|||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @return the cacheControl header to set on all static content.
|
||||
*/
|
||||
public String getCacheControl()
|
||||
{
|
||||
return _cacheControl;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @param cacheControl the cacheControl header to set on all static content.
|
||||
*/
|
||||
public void setCacheControl(String cacheControl)
|
||||
{
|
||||
_cacheControl=cacheControl;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/*
|
||||
*/
|
||||
@Override
|
||||
public Resource getResource(String path)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} getResource({})",_context==null?_baseResource:_context,_baseResource,path);
|
||||
|
||||
if (path==null || !path.startsWith("/"))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
Resource base = _baseResource;
|
||||
if (base==null)
|
||||
{
|
||||
if (_context==null)
|
||||
return null;
|
||||
return _context.getResource(path);
|
||||
}
|
||||
|
||||
path=URIUtil.canonicalPath(path);
|
||||
Resource r = base.addPath(path);
|
||||
if (r!=null && r.isAlias() && (_context==null || !_context.checkAlias(path, r)))
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("resource={} alias={}",r,r.getAlias());
|
||||
return null;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.debug(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
protected Resource getResource(HttpServletRequest request) throws MalformedURLException
|
||||
{
|
||||
String servletPath;
|
||||
String pathInfo;
|
||||
Boolean included = request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) != null;
|
||||
if (included != null && included.booleanValue())
|
||||
{
|
||||
servletPath = (String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
|
||||
pathInfo = (String)request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
|
||||
|
||||
if (servletPath == null && pathInfo == null)
|
||||
{
|
||||
servletPath = request.getServletPath();
|
||||
pathInfo = request.getPathInfo();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
servletPath = request.getServletPath();
|
||||
pathInfo = request.getPathInfo();
|
||||
}
|
||||
|
||||
String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
|
||||
return getResource(pathInContext);
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public String[] getWelcomeFiles()
|
||||
{
|
||||
return _welcomeFiles;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public void setWelcomeFiles(String[] welcomeFiles)
|
||||
{
|
||||
_welcomeFiles=welcomeFiles;
|
||||
_welcomes = welcomeFiles;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
protected Resource getWelcome(Resource directory) throws MalformedURLException, IOException
|
||||
{
|
||||
for (int i=0;i<_welcomeFiles.length;i++)
|
||||
{
|
||||
Resource welcome=directory.addPath(_welcomeFiles[i]);
|
||||
if (welcome.exists() && !welcome.isDirectory())
|
||||
return welcome;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/*
|
||||
* @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
|
||||
*/
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
{
|
||||
if (baseRequest.isHandled())
|
||||
return;
|
||||
|
||||
boolean skipContentBody = false;
|
||||
|
||||
if(!HttpMethod.GET.is(request.getMethod()))
|
||||
{
|
||||
if(!HttpMethod.HEAD.is(request.getMethod()))
|
||||
{
|
||||
//try another handler
|
||||
super.handle(target, baseRequest, request, response);
|
||||
return;
|
||||
}
|
||||
skipContentBody = true;
|
||||
}
|
||||
|
||||
Resource resource = getResource(request);
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
if (resource==null)
|
||||
LOG.debug("resource=null");
|
||||
else
|
||||
LOG.debug("resource={} alias={} exists={}",resource,resource.getAlias(),resource.exists());
|
||||
}
|
||||
|
||||
|
||||
// If resource is not found
|
||||
if (resource==null || !resource.exists())
|
||||
{
|
||||
// inject the jetty-dir.css file if it matches
|
||||
if (target.endsWith("/jetty-dir.css"))
|
||||
{
|
||||
resource = getStylesheet();
|
||||
if (resource==null)
|
||||
return;
|
||||
response.setContentType("text/css");
|
||||
}
|
||||
else
|
||||
{
|
||||
//no resource - try other handlers
|
||||
super.handle(target, baseRequest, request, response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// We are going to serve something
|
||||
baseRequest.setHandled(true);
|
||||
|
||||
// handle directories
|
||||
if (resource.isDirectory())
|
||||
{
|
||||
String pathInfo = request.getPathInfo();
|
||||
boolean endsWithSlash=(pathInfo==null?request.getServletPath():pathInfo).endsWith(URIUtil.SLASH);
|
||||
if (!endsWithSlash)
|
||||
{
|
||||
response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH)));
|
||||
return;
|
||||
}
|
||||
|
||||
Resource welcome=getWelcome(resource);
|
||||
if (welcome!=null && welcome.exists())
|
||||
resource=welcome;
|
||||
else
|
||||
{
|
||||
doDirectory(request,response,resource);
|
||||
baseRequest.setHandled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle ETAGS
|
||||
long last_modified=resource.lastModified();
|
||||
String etag=null;
|
||||
if (_etags)
|
||||
{
|
||||
// simple handling of only a single etag
|
||||
String ifnm = request.getHeader(HttpHeader.IF_NONE_MATCH.asString());
|
||||
etag=resource.getWeakETag();
|
||||
if (ifnm!=null && resource!=null && ifnm.equals(etag))
|
||||
{
|
||||
response.setStatus(HttpStatus.NOT_MODIFIED_304);
|
||||
baseRequest.getResponse().getHttpFields().put(HttpHeader.ETAG,etag);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle if modified since
|
||||
if (last_modified>0)
|
||||
{
|
||||
long if_modified=request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
|
||||
if (if_modified>0 && last_modified/1000<=if_modified/1000)
|
||||
{
|
||||
response.setStatus(HttpStatus.NOT_MODIFIED_304);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// set the headers
|
||||
String mime=_mimeTypes.getMimeByExtension(resource.toString());
|
||||
if (mime==null)
|
||||
mime=_mimeTypes.getMimeByExtension(request.getPathInfo());
|
||||
doResponseHeaders(response,resource,mime);
|
||||
if (_etags)
|
||||
baseRequest.getResponse().getHttpFields().put(HttpHeader.ETAG,etag);
|
||||
if (last_modified>0)
|
||||
response.setDateHeader(HttpHeader.LAST_MODIFIED.asString(),last_modified);
|
||||
|
||||
if(skipContentBody)
|
||||
return;
|
||||
|
||||
// Send the content
|
||||
OutputStream out =null;
|
||||
try {out = response.getOutputStream();}
|
||||
catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());}
|
||||
|
||||
// Has the output been wrapped
|
||||
if (!(out instanceof HttpOutput))
|
||||
// Write content via wrapped output
|
||||
resource.writeTo(out,0,resource.length());
|
||||
else
|
||||
{
|
||||
// select async by size
|
||||
int min_async_size=_minAsyncContentLength==0?response.getBufferSize():_minAsyncContentLength;
|
||||
|
||||
if (request.isAsyncSupported() &&
|
||||
min_async_size>0 &&
|
||||
resource.length()>=min_async_size)
|
||||
{
|
||||
final AsyncContext async = request.startAsync();
|
||||
async.setTimeout(0);
|
||||
Callback callback = new Callback()
|
||||
{
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
async.complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
LOG.warn(x.toString());
|
||||
LOG.debug(x);
|
||||
async.complete();
|
||||
}
|
||||
};
|
||||
|
||||
// Can we use a memory mapped file?
|
||||
if (_minMemoryMappedContentLength>0 &&
|
||||
resource.length()>_minMemoryMappedContentLength &&
|
||||
resource.length()<Integer.MAX_VALUE &&
|
||||
resource instanceof PathResource)
|
||||
{
|
||||
ByteBuffer buffer = BufferUtil.toMappedBuffer(resource.getFile());
|
||||
((HttpOutput)out).sendContent(buffer,callback);
|
||||
}
|
||||
else // Do a blocking write of a channel (if available) or input stream
|
||||
{
|
||||
// Close of the channel/inputstream is done by the async sendContent
|
||||
ReadableByteChannel channel= resource.getReadableByteChannel();
|
||||
if (channel!=null)
|
||||
((HttpOutput)out).sendContent(channel,callback);
|
||||
else
|
||||
((HttpOutput)out).sendContent(resource.getInputStream(),callback);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Can we use a memory mapped file?
|
||||
if (_minMemoryMappedContentLength>0 &&
|
||||
resource.length()>_minMemoryMappedContentLength &&
|
||||
resource instanceof PathResource)
|
||||
{
|
||||
ByteBuffer buffer = BufferUtil.toMappedBuffer(resource.getFile());
|
||||
((HttpOutput)out).sendContent(buffer);
|
||||
}
|
||||
else // Do a blocking write of a channel (if available) or input stream
|
||||
{
|
||||
ReadableByteChannel channel= resource.getReadableByteChannel();
|
||||
if (channel!=null)
|
||||
((HttpOutput)out).sendContent(channel);
|
||||
else
|
||||
((HttpOutput)out).sendContent(resource.getInputStream());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
protected void doDirectory(HttpServletRequest request,HttpServletResponse response, Resource resource)
|
||||
throws IOException
|
||||
{
|
||||
if (_directory)
|
||||
{
|
||||
String listing = resource.getListHTML(request.getRequestURI(),request.getPathInfo().lastIndexOf("/") > 0);
|
||||
response.setContentType("text/html;charset=utf-8");
|
||||
response.getWriter().println(listing);
|
||||
}
|
||||
else
|
||||
response.sendError(HttpStatus.FORBIDDEN_403);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Set the response headers.
|
||||
* This method is called to set the response headers such as content type and content length.
|
||||
* May be extended to add additional headers.
|
||||
* @param response the http response
|
||||
* @param resource the resource
|
||||
* @param mimeType the mime type
|
||||
*/
|
||||
protected void doResponseHeaders(HttpServletResponse response, Resource resource, String mimeType)
|
||||
{
|
||||
if (mimeType!=null)
|
||||
response.setContentType(mimeType);
|
||||
|
||||
long length=resource.length();
|
||||
|
||||
if (response instanceof Response)
|
||||
{
|
||||
HttpFields fields = ((Response)response).getHttpFields();
|
||||
|
||||
if (length>0)
|
||||
((Response)response).setLongContentLength(length);
|
||||
|
||||
if (_cacheControl!=null)
|
||||
fields.put(HttpHeader.CACHE_CONTROL,_cacheControl);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (length>Integer.MAX_VALUE)
|
||||
response.setHeader(HttpHeader.CONTENT_LENGTH.asString(),Long.toString(length));
|
||||
else if (length>0)
|
||||
response.setContentLength((int)length);
|
||||
|
||||
if (_cacheControl!=null)
|
||||
response.setHeader(HttpHeader.CACHE_CONTROL.asString(),_cacheControl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -121,8 +121,6 @@ public class ResourceHandlerTest
|
|||
_server.setConnectors(new Connector[] { _connector, _local });
|
||||
|
||||
_resourceHandler = new ResourceHandler();
|
||||
_resourceHandler.setMinAsyncContentLength(4096);
|
||||
_resourceHandler.setMinMemoryMappedContentLength(8192);
|
||||
|
||||
_resourceHandler.setResourceBase(MavenTestingUtils.getTargetFile("test-classes/simple").getAbsolutePath());
|
||||
|
||||
|
@ -145,10 +143,10 @@ public class ResourceHandlerTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testMissing() throws Exception
|
||||
public void testJettyDirCss() throws Exception
|
||||
{
|
||||
SimpleRequest sr = new SimpleRequest(new URI("http://localhost:" + _connector.getLocalPort()));
|
||||
Assert.assertNotNull("missing jetty.css",sr.getString("/resource/jetty-dir.css"));
|
||||
Assert.assertNotNull(sr.getString("/resource/jetty-dir.css"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -18,23 +18,12 @@
|
|||
|
||||
package org.eclipse.jetty.servlet;
|
||||
|
||||
import static org.eclipse.jetty.http.GzipHttpContent.ETAG_GZIP_QUOTE;
|
||||
import static org.eclipse.jetty.http.GzipHttpContent.removeGzipFromETag;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.UnavailableException;
|
||||
|
@ -42,36 +31,19 @@ import javax.servlet.http.HttpServlet;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.DateParser;
|
||||
import org.eclipse.jetty.http.GzipHttpContent;
|
||||
import org.eclipse.jetty.http.HttpContent;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.MimeTypes;
|
||||
import org.eclipse.jetty.http.PathMap.MappedEntry;
|
||||
import org.eclipse.jetty.http.pathmap.MappedResource;
|
||||
import org.eclipse.jetty.http.PreEncodedHttpField;
|
||||
import org.eclipse.jetty.http.ResourceHttpContent;
|
||||
import org.eclipse.jetty.io.WriterOutputStream;
|
||||
import org.eclipse.jetty.server.HttpOutput;
|
||||
import org.eclipse.jetty.server.InclusiveByteRange;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.http.pathmap.MappedResource;
|
||||
import org.eclipse.jetty.server.ResourceCache;
|
||||
import org.eclipse.jetty.server.ResourceContentFactory;
|
||||
import org.eclipse.jetty.server.Response;
|
||||
import org.eclipse.jetty.server.ResourceService;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.MultiPartOutputStream;
|
||||
import org.eclipse.jetty.util.QuotedStringTokenizer;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.eclipse.jetty.util.resource.ResourceCollection;
|
||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||
|
||||
|
||||
|
@ -149,36 +121,64 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
|
|||
{
|
||||
private static final Logger LOG = Log.getLogger(DefaultServlet.class);
|
||||
|
||||
private static final long serialVersionUID = 4930458713846881193L;
|
||||
|
||||
private static final PreEncodedHttpField ACCEPT_RANGES = new PreEncodedHttpField(HttpHeader.ACCEPT_RANGES, "bytes");
|
||||
|
||||
private static final long serialVersionUID = 4930458713846881193L;
|
||||
|
||||
private final ResourceService _resourceService;
|
||||
private ServletContext _servletContext;
|
||||
private ContextHandler _contextHandler;
|
||||
|
||||
private boolean _acceptRanges=true;
|
||||
private boolean _dirAllowed=true;
|
||||
private boolean _welcomeServlets=false;
|
||||
private boolean _welcomeExactServlets=false;
|
||||
private boolean _redirectWelcome=false;
|
||||
private boolean _gzip=false;
|
||||
private boolean _pathInfoOnly=false;
|
||||
private boolean _etags=false;
|
||||
|
||||
private Resource _resourceBase;
|
||||
private ResourceCache _cache;
|
||||
private HttpContent.Factory _contentFactory;
|
||||
|
||||
private MimeTypes _mimeTypes;
|
||||
private String[] _welcomes;
|
||||
private Resource _stylesheet;
|
||||
private boolean _useFileMappedBuffer=false;
|
||||
private HttpField _cacheControl;
|
||||
private String _relativeResourceBase;
|
||||
private ServletHandler _servletHandler;
|
||||
private ServletHolder _defaultHolder;
|
||||
private List<String> _gzipEquivalentFileExtensions;
|
||||
|
||||
public DefaultServlet()
|
||||
{
|
||||
_resourceService = new ResourceService()
|
||||
{
|
||||
@Override
|
||||
protected String getWelcomeFile(String pathInContext)
|
||||
{
|
||||
if (_welcomes==null)
|
||||
return null;
|
||||
|
||||
String welcome_servlet=null;
|
||||
for (int i=0;i<_welcomes.length;i++)
|
||||
{
|
||||
String welcome_in_context=URIUtil.addPaths(pathInContext,_welcomes[i]);
|
||||
Resource welcome=getResource(welcome_in_context);
|
||||
if (welcome!=null && welcome.exists())
|
||||
return _welcomes[i];
|
||||
|
||||
if ((_welcomeServlets || _welcomeExactServlets) && welcome_servlet==null)
|
||||
{
|
||||
MappedResource<ServletHolder> entry=_servletHandler.getHolderEntry(welcome_in_context);
|
||||
if (entry!=null && entry.getResource()!=_defaultHolder &&
|
||||
(_welcomeServlets || (_welcomeExactServlets && entry.getPathSpec().getDeclaration().equals(welcome_in_context))))
|
||||
welcome_servlet=welcome_in_context;
|
||||
|
||||
}
|
||||
}
|
||||
return welcome_servlet;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void notFound(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
response.sendError(HttpServletResponse.SC_NOT_FOUND);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@Override
|
||||
public void init()
|
||||
|
@ -193,12 +193,13 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
|
|||
if (_welcomes==null)
|
||||
_welcomes=new String[] {"index.html","index.jsp"};
|
||||
|
||||
_acceptRanges=getInitBoolean("acceptRanges",_acceptRanges);
|
||||
_dirAllowed=getInitBoolean("dirAllowed",_dirAllowed);
|
||||
_redirectWelcome=getInitBoolean("redirectWelcome",_redirectWelcome);
|
||||
_gzip=getInitBoolean("gzip",_gzip);
|
||||
_pathInfoOnly=getInitBoolean("pathInfoOnly",_pathInfoOnly);
|
||||
|
||||
_resourceService.setAcceptRanges(getInitBoolean("acceptRanges",_resourceService.isAcceptRanges()));
|
||||
_resourceService.setDirAllowed(getInitBoolean("dirAllowed",_resourceService.isDirAllowed()));
|
||||
_resourceService.setRedirectWelcome(getInitBoolean("redirectWelcome",_resourceService.isRedirectWelcome()));
|
||||
_resourceService.setGzip(getInitBoolean("gzip",_resourceService.isGzip()));
|
||||
_resourceService.setPathInfoOnly(getInitBoolean("pathInfoOnly",_resourceService.isPathInfoOnly()));
|
||||
_resourceService.setEtags(getInitBoolean("etags",_resourceService.isEtags()));
|
||||
|
||||
if ("exact".equals(getInitParameter("welcomeServlets")))
|
||||
{
|
||||
_welcomeExactServlets=true;
|
||||
|
@ -249,8 +250,9 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
|
|||
|
||||
String cc=getInitParameter("cacheControl");
|
||||
if (cc!=null)
|
||||
_cacheControl=new PreEncodedHttpField(HttpHeader.CACHE_CONTROL, cc);
|
||||
|
||||
_resourceService.setCacheControl(new PreEncodedHttpField(HttpHeader.CACHE_CONTROL,cc));
|
||||
|
||||
|
||||
String resourceCache = getInitParameter("resourceCache");
|
||||
int max_cache_size=getInitInt("maxCacheSize", -2);
|
||||
int max_cached_file_size=getInitInt("maxCachedFileSize", -2);
|
||||
|
@ -262,18 +264,13 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
|
|||
if (_relativeResourceBase!=null || _resourceBase!=null)
|
||||
throw new UnavailableException("resourceCache specified with resource bases");
|
||||
_cache=(ResourceCache)_servletContext.getAttribute(resourceCache);
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Cache {}={}",resourceCache,_contentFactory);
|
||||
}
|
||||
|
||||
_etags = getInitBoolean("etags",_etags);
|
||||
|
||||
try
|
||||
{
|
||||
if (_cache==null && (max_cached_files!=-2 || max_cache_size!=-2 || max_cached_file_size!=-2))
|
||||
{
|
||||
_cache = new ResourceCache(null,this,_mimeTypes,_useFileMappedBuffer,_etags,_gzip);
|
||||
_cache = new ResourceCache(null,this,_mimeTypes,_useFileMappedBuffer,_resourceService.isEtags(),_resourceService.isGzip());
|
||||
if (max_cache_size>=0)
|
||||
_cache.setMaxCacheSize(max_cache_size);
|
||||
if (max_cached_file_size>=-1)
|
||||
|
@ -289,16 +286,16 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
|
|||
throw new UnavailableException(e.toString());
|
||||
}
|
||||
|
||||
if (_cache!=null)
|
||||
_contentFactory=_cache;
|
||||
else
|
||||
HttpContent.Factory contentFactory=_cache;
|
||||
if (contentFactory==null)
|
||||
{
|
||||
_contentFactory=new ResourceContentFactory(this,_mimeTypes,_gzip);
|
||||
contentFactory=new ResourceContentFactory(this,_mimeTypes,_resourceService.isGzip());
|
||||
if (resourceCache!=null)
|
||||
_servletContext.setAttribute(resourceCache,_contentFactory);
|
||||
_servletContext.setAttribute(resourceCache,contentFactory);
|
||||
}
|
||||
_resourceService.setContentFactory(contentFactory);
|
||||
|
||||
_gzipEquivalentFileExtensions = new ArrayList<String>();
|
||||
List<String> gzip_equivalent_file_extensions = new ArrayList<String>();
|
||||
String otherGzipExtensions = getInitParameter("otherGzipFileExtensions");
|
||||
if (otherGzipExtensions != null)
|
||||
{
|
||||
|
@ -307,15 +304,17 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
|
|||
while (tok.hasMoreTokens())
|
||||
{
|
||||
String s = tok.nextToken().trim();
|
||||
_gzipEquivalentFileExtensions.add((s.charAt(0)=='.'?s:"."+s));
|
||||
gzip_equivalent_file_extensions.add((s.charAt(0)=='.'?s:"."+s));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//.svgz files are gzipped svg files and must be served with Content-Encoding:gzip
|
||||
_gzipEquivalentFileExtensions.add(".svgz");
|
||||
gzip_equivalent_file_extensions.add(".svgz");
|
||||
}
|
||||
_resourceService.setGzipEquivalentFileExtensions(gzip_equivalent_file_extensions);
|
||||
|
||||
|
||||
_servletHandler= _contextHandler.getChildHandlerByClass(ServletHandler.class);
|
||||
for (ServletHolder h :_servletHandler.getServlets())
|
||||
if (h.getServletInstance()==this)
|
||||
|
@ -434,196 +433,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
|
|||
protected void doGet(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException
|
||||
{
|
||||
String servletPath=null;
|
||||
String pathInfo=null;
|
||||
Enumeration<String> reqRanges = null;
|
||||
boolean included =request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI)!=null;
|
||||
if (included)
|
||||
{
|
||||
servletPath=(String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
|
||||
pathInfo=(String)request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
|
||||
if (servletPath==null)
|
||||
{
|
||||
servletPath=request.getServletPath();
|
||||
pathInfo=request.getPathInfo();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
servletPath = _pathInfoOnly?"/":request.getServletPath();
|
||||
pathInfo = request.getPathInfo();
|
||||
|
||||
// Is this a Range request?
|
||||
reqRanges = request.getHeaders(HttpHeader.RANGE.asString());
|
||||
if (!hasDefinedRange(reqRanges))
|
||||
reqRanges = null;
|
||||
}
|
||||
|
||||
String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
|
||||
boolean endsWithSlash=(pathInfo==null?request.getServletPath():pathInfo).endsWith(URIUtil.SLASH);
|
||||
boolean gzippable=_gzip && !endsWithSlash && !included && reqRanges==null;
|
||||
|
||||
HttpContent content=null;
|
||||
boolean release_content=true;
|
||||
try
|
||||
{
|
||||
// Find the content
|
||||
content=_contentFactory.getContent(pathInContext,response.getBufferSize());
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.info("content={}",content);
|
||||
|
||||
// Not found?
|
||||
if (content==null || !content.getResource().exists())
|
||||
{
|
||||
if (included)
|
||||
throw new FileNotFoundException("!" + pathInContext);
|
||||
response.sendError(HttpServletResponse.SC_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
// Directory?
|
||||
if (content.getResource().isDirectory())
|
||||
{
|
||||
sendWelcome(content,pathInContext,endsWithSlash,included,request,response);
|
||||
return;
|
||||
}
|
||||
|
||||
// Strip slash?
|
||||
if (endsWithSlash && pathInContext.length()>1)
|
||||
{
|
||||
String q=request.getQueryString();
|
||||
pathInContext=pathInContext.substring(0,pathInContext.length()-1);
|
||||
if (q!=null&&q.length()!=0)
|
||||
pathInContext+="?"+q;
|
||||
response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(_servletContext.getContextPath(),pathInContext)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Conditional response?
|
||||
if (!included && !passConditionalHeaders(request,response,content))
|
||||
return;
|
||||
|
||||
// Gzip?
|
||||
HttpContent gzip_content = gzippable?content.getGzipContent():null;
|
||||
if (gzip_content!=null)
|
||||
{
|
||||
// Tell caches that response may vary by accept-encoding
|
||||
response.addHeader(HttpHeader.VARY.asString(),HttpHeader.ACCEPT_ENCODING.asString());
|
||||
|
||||
// Does the client accept gzip?
|
||||
String accept=request.getHeader(HttpHeader.ACCEPT_ENCODING.asString());
|
||||
if (accept!=null && accept.indexOf("gzip")>=0)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("gzip={}",gzip_content);
|
||||
content=gzip_content;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO this should be done by HttpContent#getContentEncoding
|
||||
if (isGzippedContent(pathInContext))
|
||||
response.setHeader(HttpHeader.CONTENT_ENCODING.asString(),"gzip");
|
||||
|
||||
// Send the data
|
||||
release_content=sendData(request,response,included,content,reqRanges);
|
||||
|
||||
}
|
||||
catch(IllegalArgumentException e)
|
||||
{
|
||||
LOG.warn(Log.EXCEPTION,e);
|
||||
if(!response.isCommitted())
|
||||
response.sendError(500, e.getMessage());
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (release_content)
|
||||
{
|
||||
if (content!=null)
|
||||
content.release();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void sendWelcome(HttpContent content, String pathInContext, boolean endsWithSlash, boolean included, HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException
|
||||
{
|
||||
// Redirect to directory
|
||||
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()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// look for a welcome file
|
||||
String welcome=getWelcomeFile(pathInContext);
|
||||
if (welcome!=null)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("welcome={}",welcome);
|
||||
if (_redirectWelcome)
|
||||
{
|
||||
// Redirect to the index
|
||||
response.setContentLength(0);
|
||||
String q=request.getQueryString();
|
||||
if (q!=null&&q.length()!=0)
|
||||
response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths( _servletContext.getContextPath(),welcome)+"?"+q));
|
||||
else
|
||||
response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths( _servletContext.getContextPath(),welcome)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Forward to the index
|
||||
RequestDispatcher dispatcher=request.getRequestDispatcher(welcome);
|
||||
if (dispatcher!=null)
|
||||
{
|
||||
if (included)
|
||||
dispatcher.include(request,response);
|
||||
else
|
||||
{
|
||||
request.setAttribute("org.eclipse.jetty.server.welcome",welcome);
|
||||
dispatcher.forward(request,response);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (included || passConditionalHeaders(request,response, content))
|
||||
sendDirectory(request,response,content.getResource(),pathInContext);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
protected boolean isGzippedContent(String path)
|
||||
{
|
||||
if (path == null) return false;
|
||||
|
||||
for (String suffix:_gzipEquivalentFileExtensions)
|
||||
if (path.endsWith(suffix))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
private boolean hasDefinedRange(Enumeration<String> reqRanges)
|
||||
{
|
||||
return (reqRanges!=null && reqRanges.hasMoreElements());
|
||||
_resourceService.doGet(request,response);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
@ -652,462 +462,6 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
|
|||
resp.setHeader("Allow", "GET,HEAD,POST,OPTIONS");
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* 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 it may look for a valid servlet mapping.
|
||||
* If there is none, 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 path of the matching welcome file in context or null.
|
||||
* @throws IOException
|
||||
* @throws MalformedURLException
|
||||
*/
|
||||
private String getWelcomeFile(String pathInContext) throws MalformedURLException, IOException
|
||||
{
|
||||
if (_welcomes==null)
|
||||
return null;
|
||||
|
||||
String welcome_servlet=null;
|
||||
for (int i=0;i<_welcomes.length;i++)
|
||||
{
|
||||
String welcome_in_context=URIUtil.addPaths(pathInContext,_welcomes[i]);
|
||||
Resource welcome=getResource(welcome_in_context);
|
||||
if (welcome!=null && welcome.exists())
|
||||
return _welcomes[i];
|
||||
|
||||
if ((_welcomeServlets || _welcomeExactServlets) && welcome_servlet==null)
|
||||
{
|
||||
MappedResource<ServletHolder> entry=_servletHandler.getHolderEntry(welcome_in_context);
|
||||
if (entry!=null && entry.getResource()!=_defaultHolder &&
|
||||
(_welcomeServlets || (_welcomeExactServlets && entry.getPathSpec().getDeclaration().equals(welcome_in_context))))
|
||||
welcome_servlet=welcome_in_context;
|
||||
|
||||
}
|
||||
}
|
||||
return welcome_servlet;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* Check modification date headers.
|
||||
*/
|
||||
protected boolean passConditionalHeaders(HttpServletRequest request,HttpServletResponse response, HttpContent content)
|
||||
throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
String ifm=null;
|
||||
String ifnm=null;
|
||||
String ifms=null;
|
||||
long ifums=-1;
|
||||
|
||||
if (request instanceof Request)
|
||||
{
|
||||
// Find multiple fields by iteration as an optimization
|
||||
HttpFields fields = ((Request)request).getHttpFields();
|
||||
for (int i=fields.size();i-->0;)
|
||||
{
|
||||
HttpField field=fields.getField(i);
|
||||
if (field.getHeader() != null)
|
||||
{
|
||||
switch (field.getHeader())
|
||||
{
|
||||
case IF_MATCH:
|
||||
ifm=field.getValue();
|
||||
break;
|
||||
case IF_NONE_MATCH:
|
||||
ifnm=field.getValue();
|
||||
break;
|
||||
case IF_MODIFIED_SINCE:
|
||||
ifms=field.getValue();
|
||||
break;
|
||||
case IF_UNMODIFIED_SINCE:
|
||||
ifums=DateParser.parseDate(field.getValue());
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ifm=request.getHeader(HttpHeader.IF_MATCH.asString());
|
||||
ifnm=request.getHeader(HttpHeader.IF_NONE_MATCH.asString());
|
||||
ifms=request.getHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
|
||||
ifums=request.getDateHeader(HttpHeader.IF_UNMODIFIED_SINCE.asString());
|
||||
}
|
||||
|
||||
if (!HttpMethod.HEAD.is(request.getMethod()))
|
||||
{
|
||||
if (_etags)
|
||||
{
|
||||
String etag=content.getETagValue();
|
||||
if (ifm!=null)
|
||||
{
|
||||
boolean match=false;
|
||||
if (etag!=null)
|
||||
{
|
||||
QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifm,", ",false,true);
|
||||
while (!match && quoted.hasMoreTokens())
|
||||
{
|
||||
String tag = quoted.nextToken();
|
||||
if (etag.equals(tag) || tag.endsWith(ETAG_GZIP_QUOTE) && etag.equals(removeGzipFromETag(tag)))
|
||||
match=true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!match)
|
||||
{
|
||||
response.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ifnm!=null && etag!=null)
|
||||
{
|
||||
// Handle special case of exact match OR gzip exact match
|
||||
if (etag.equals(ifnm) || ifnm.endsWith(ETAG_GZIP_QUOTE) && ifnm.indexOf(',')<0 && etag.equals(removeGzipFromETag(etag)))
|
||||
{
|
||||
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
||||
response.setHeader(HttpHeader.ETAG.asString(),ifnm);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle list of tags
|
||||
QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifnm,", ",false,true);
|
||||
while (quoted.hasMoreTokens())
|
||||
{
|
||||
String tag = quoted.nextToken();
|
||||
if (etag.equals(tag) || tag.endsWith(ETAG_GZIP_QUOTE) && etag.equals(removeGzipFromETag(tag)))
|
||||
{
|
||||
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
||||
response.setHeader(HttpHeader.ETAG.asString(),tag);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If etag requires content to be served, then do not check if-modified-since
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle if modified since
|
||||
if (ifms!=null)
|
||||
{
|
||||
//Get jetty's Response impl
|
||||
String mdlm=content.getLastModifiedValue();
|
||||
if (mdlm!=null && ifms.equals(mdlm))
|
||||
{
|
||||
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
||||
if (_etags)
|
||||
response.setHeader(HttpHeader.ETAG.asString(),content.getETagValue());
|
||||
response.flushBuffer();
|
||||
return false;
|
||||
}
|
||||
|
||||
long ifmsl=request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
|
||||
if (ifmsl!=-1 && content.getResource().lastModified()/1000 <= ifmsl/1000)
|
||||
{
|
||||
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
||||
if (_etags)
|
||||
response.setHeader(HttpHeader.ETAG.asString(),content.getETagValue());
|
||||
response.flushBuffer();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the if[un]modified dates and compare to resource
|
||||
if (ifums!=-1 && content.getResource().lastModified()/1000 > ifums/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,
|
||||
String pathInContext)
|
||||
throws IOException
|
||||
{
|
||||
if (!_dirAllowed)
|
||||
{
|
||||
response.sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] data=null;
|
||||
String base = URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH);
|
||||
|
||||
//If the DefaultServlet has a resource base set, use it
|
||||
if (_resourceBase != null)
|
||||
{
|
||||
// handle ResourceCollection
|
||||
if (_resourceBase instanceof ResourceCollection)
|
||||
resource=_resourceBase.addPath(pathInContext);
|
||||
}
|
||||
//Otherwise, try using the resource base of its enclosing context handler
|
||||
else if (_contextHandler.getBaseResource() instanceof ResourceCollection)
|
||||
resource=_contextHandler.getBaseResource().addPath(pathInContext);
|
||||
|
||||
String dir = resource.getListHTML(base,pathInContext.length()>1);
|
||||
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 boolean sendData(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
boolean include,
|
||||
final HttpContent content,
|
||||
Enumeration<String> reqRanges)
|
||||
throws IOException
|
||||
{
|
||||
final long content_length = content.getContentLengthValue();
|
||||
|
||||
// Get the output stream (or writer)
|
||||
OutputStream out =null;
|
||||
boolean written;
|
||||
try
|
||||
{
|
||||
out = response.getOutputStream();
|
||||
|
||||
// has something already written to the response?
|
||||
written = out instanceof HttpOutput
|
||||
? ((HttpOutput)out).isWritten()
|
||||
: true;
|
||||
}
|
||||
catch(IllegalStateException e)
|
||||
{
|
||||
out = new WriterOutputStream(response.getWriter());
|
||||
written=true; // there may be data in writer buffer, so assume written
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(String.format("sendData content=%s out=%s async=%b",content,out,request.isAsyncSupported()));
|
||||
|
||||
if ( reqRanges == null || !reqRanges.hasMoreElements() || content_length<0)
|
||||
{
|
||||
// if there were no ranges, send entire entity
|
||||
if (include)
|
||||
{
|
||||
// write without headers
|
||||
content.getResource().writeTo(out,0,content_length);
|
||||
}
|
||||
// else if we can't do a bypass write because of wrapping
|
||||
else if (written || !(out instanceof HttpOutput))
|
||||
{
|
||||
// write normally
|
||||
putHeaders(response,content,written?-1:0);
|
||||
ByteBuffer buffer = content.getIndirectBuffer();
|
||||
if (buffer!=null)
|
||||
BufferUtil.writeTo(buffer,out);
|
||||
else
|
||||
content.getResource().writeTo(out,0,content_length);
|
||||
}
|
||||
// else do a bypass write
|
||||
else
|
||||
{
|
||||
// write the headers
|
||||
putHeaders(response,content,0);
|
||||
|
||||
// write the content asynchronously if supported
|
||||
if (request.isAsyncSupported())
|
||||
{
|
||||
final AsyncContext context = request.startAsync();
|
||||
context.setTimeout(0);
|
||||
|
||||
((HttpOutput)out).sendContent(content,new Callback()
|
||||
{
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
context.complete();
|
||||
content.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
if (x instanceof IOException)
|
||||
LOG.debug(x);
|
||||
else
|
||||
LOG.warn(x);
|
||||
context.complete();
|
||||
content.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("DefaultServlet@%x$CB", DefaultServlet.this.hashCode());
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
// otherwise write content blocking
|
||||
((HttpOutput)out).sendContent(content);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Parse the satisfiable ranges
|
||||
List<InclusiveByteRange> ranges =InclusiveByteRange.satisfiableRanges(reqRanges,content_length);
|
||||
|
||||
// if there are no satisfiable ranges, send 416 response
|
||||
if (ranges==null || ranges.size()==0)
|
||||
{
|
||||
putHeaders(response,content,0);
|
||||
response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
|
||||
response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
|
||||
InclusiveByteRange.to416HeaderRangeString(content_length));
|
||||
content.getResource().writeTo(out,0,content_length);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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 = ranges.get(0);
|
||||
long singleLength = singleSatisfiableRange.getSize(content_length);
|
||||
putHeaders(response,content,singleLength);
|
||||
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
|
||||
if (!response.containsHeader(HttpHeader.DATE.asString()))
|
||||
response.addDateHeader(HttpHeader.DATE.asString(),System.currentTimeMillis());
|
||||
response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
|
||||
singleSatisfiableRange.toHeaderRangeString(content_length));
|
||||
content.getResource().writeTo(out,singleSatisfiableRange.getFirst(content_length),singleLength);
|
||||
return true;
|
||||
}
|
||||
|
||||
// multiple non-overlapping valid ranges cause a multipart
|
||||
// 216 response which does not require an overall
|
||||
// content-length header
|
||||
//
|
||||
putHeaders(response,content,-1);
|
||||
String mimetype=(content==null?null:content.getContentTypeValue());
|
||||
if (mimetype==null)
|
||||
LOG.warn("Unknown mimetype for "+request.getRequestURI());
|
||||
MultiPartOutputStream multi = new MultiPartOutputStream(out);
|
||||
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
|
||||
if (!response.containsHeader(HttpHeader.DATE.asString()))
|
||||
response.addDateHeader(HttpHeader.DATE.asString(),System.currentTimeMillis());
|
||||
|
||||
// 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(HttpHeader.REQUEST_RANGE.asString())!=null)
|
||||
ctp = "multipart/x-byteranges; boundary=";
|
||||
else
|
||||
ctp = "multipart/byteranges; boundary=";
|
||||
response.setContentType(ctp+multi.getBoundary());
|
||||
|
||||
InputStream in=content.getResource().getInputStream();
|
||||
long pos=0;
|
||||
|
||||
// calculate the content-length
|
||||
int length=0;
|
||||
String[] header = new String[ranges.size()];
|
||||
for (int i=0;i<ranges.size();i++)
|
||||
{
|
||||
InclusiveByteRange ibr = ranges.get(i);
|
||||
header[i]=ibr.toHeaderRangeString(content_length);
|
||||
length+=
|
||||
((i>0)?2:0)+
|
||||
2+multi.getBoundary().length()+2+
|
||||
(mimetype==null?0:HttpHeader.CONTENT_TYPE.asString().length()+2+mimetype.length())+2+
|
||||
HttpHeader.CONTENT_RANGE.asString().length()+2+header[i].length()+2+
|
||||
2+
|
||||
(ibr.getLast(content_length)-ibr.getFirst(content_length))+1;
|
||||
}
|
||||
length+=2+2+multi.getBoundary().length()+2+2;
|
||||
response.setContentLength(length);
|
||||
|
||||
for (int i=0;i<ranges.size();i++)
|
||||
{
|
||||
InclusiveByteRange ibr = ranges.get(i);
|
||||
multi.startPart(mimetype,new String[]{HttpHeader.CONTENT_RANGE+": "+header[i]});
|
||||
|
||||
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=content.getResource().getInputStream();
|
||||
pos=0;
|
||||
}
|
||||
if (pos<start)
|
||||
{
|
||||
in.skip(start-pos);
|
||||
pos=start;
|
||||
}
|
||||
|
||||
IO.copy(in,multi,size);
|
||||
pos+=size;
|
||||
}
|
||||
else
|
||||
// Handle cached resource
|
||||
content.getResource().writeTo(multi,start,size);
|
||||
}
|
||||
if (in!=null)
|
||||
in.close();
|
||||
multi.close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
protected void putHeaders(HttpServletResponse response,HttpContent content, long contentLength)
|
||||
{
|
||||
if (response instanceof Response)
|
||||
{
|
||||
Response r = (Response)response;
|
||||
r.putHeaders(content,contentLength,_etags);
|
||||
HttpFields f = r.getHttpFields();
|
||||
if (_acceptRanges)
|
||||
f.put(ACCEPT_RANGES);
|
||||
|
||||
if (_cacheControl!=null)
|
||||
f.put(_cacheControl);
|
||||
}
|
||||
else
|
||||
{
|
||||
Response.putHeaders(response,content,contentLength,_etags);
|
||||
if (_acceptRanges)
|
||||
response.setHeader(ACCEPT_RANGES.getName(),ACCEPT_RANGES.getValue());
|
||||
|
||||
if (_cacheControl!=null)
|
||||
response.setHeader(_cacheControl.getName(),_cacheControl.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/*
|
||||
|
|
|
@ -86,6 +86,7 @@ public class DispatcherTest
|
|||
_contextCollection.addHandler(_contextHandler);
|
||||
_resourceHandler = new ResourceHandler();
|
||||
_resourceHandler.setResourceBase(MavenTestingUtils.getTestResourceDir("dispatchResourceTest").getAbsolutePath());
|
||||
_resourceHandler.setPathInfoOnly(true);
|
||||
ContextHandler resourceContextHandler = new ContextHandler("/resource");
|
||||
resourceContextHandler.setHandler(_resourceHandler);
|
||||
_contextCollection.addHandler(resourceContextHandler);
|
||||
|
|
Loading…
Reference in New Issue