use of org.apache.catalina.webresources.CachedResource in project tomcat by apache.
the class DefaultServlet method serveResource.
/**
* Serve the specified resource, optionally including the data content.
*
* @param request The servlet request we are processing
* @param response The servlet response we are creating
* @param content Should the content be included?
* @param inputEncoding The encoding to use if it is necessary to access the
* source as characters rather than as bytes
*
* @exception IOException if an input/output error occurs
* @exception ServletException if a servlet-specified error occurs
*/
protected void serveResource(HttpServletRequest request, HttpServletResponse response, boolean content, String inputEncoding) throws IOException, ServletException {
boolean serveContent = content;
// Identify the requested resource path
String path = getRelativePath(request, true);
if (debug > 0) {
if (serveContent) {
log("DefaultServlet.serveResource: Serving resource '" + path + "' headers and data");
} else {
log("DefaultServlet.serveResource: Serving resource '" + path + "' headers only");
}
}
if (path.length() == 0) {
// Context root redirect
doDirectoryRedirect(request, response);
return;
}
WebResource resource = resources.getResource(path);
boolean isError = DispatcherType.ERROR == request.getDispatcherType();
if (!resource.exists()) {
// Check if we're included so we can return the appropriate
// missing resource name in the error
String requestUri = (String) request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI);
if (requestUri == null) {
requestUri = request.getRequestURI();
} else {
// SRV.9.3 says we must throw a FNFE
throw new FileNotFoundException(sm.getString("defaultServlet.missingResource", requestUri));
}
if (isError) {
response.sendError(((Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE)).intValue());
} else {
response.sendError(HttpServletResponse.SC_NOT_FOUND, sm.getString("defaultServlet.missingResource", requestUri));
}
return;
}
if (!resource.canRead()) {
// Check if we're included so we can return the appropriate
// missing resource name in the error
String requestUri = (String) request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI);
if (requestUri == null) {
requestUri = request.getRequestURI();
} else {
// reasonable
throw new FileNotFoundException(sm.getString("defaultServlet.missingResource", requestUri));
}
if (isError) {
response.sendError(((Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE)).intValue());
} else {
response.sendError(HttpServletResponse.SC_FORBIDDEN, requestUri);
}
return;
}
boolean included = false;
// satisfied.
if (resource.isFile()) {
// Checking If headers
included = (request.getAttribute(RequestDispatcher.INCLUDE_CONTEXT_PATH) != null);
if (!included && !isError && !checkIfHeaders(request, response, resource)) {
return;
}
}
// Find content type.
String contentType = resource.getMimeType();
if (contentType == null) {
contentType = getServletContext().getMimeType(resource.getName());
resource.setMimeType(contentType);
}
// These need to reflect the original resource, not the potentially
// precompressed version of the resource so get them now if they are going to
// be needed later
String eTag = null;
String lastModifiedHttp = null;
if (resource.isFile() && !isError) {
eTag = generateETag(resource);
lastModifiedHttp = resource.getLastModifiedHttp();
}
// Serve a precompressed version of the file if present
boolean usingPrecompressedVersion = false;
if (compressionFormats.length > 0 && !included && resource.isFile() && !pathEndsWithCompressedExtension(path)) {
List<PrecompressedResource> precompressedResources = getAvailablePrecompressedResources(path);
if (!precompressedResources.isEmpty()) {
ResponseUtil.addVaryFieldName(response, "accept-encoding");
PrecompressedResource bestResource = getBestPrecompressedResource(request, precompressedResources);
if (bestResource != null) {
response.addHeader("Content-Encoding", bestResource.format.encoding);
resource = bestResource.resource;
usingPrecompressedVersion = true;
}
}
}
Ranges ranges = FULL;
long contentLength = -1L;
if (resource.isDirectory()) {
if (!path.endsWith("/")) {
doDirectoryRedirect(request, response);
return;
}
// suppress them
if (!listings) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, sm.getString("defaultServlet.missingResource", request.getRequestURI()));
return;
}
contentType = "text/html;charset=UTF-8";
} else {
if (!isError) {
if (useAcceptRanges) {
// Accept ranges header
response.setHeader("Accept-Ranges", "bytes");
}
// Parse range specifier
ranges = parseRange(request, response, resource);
if (ranges == null) {
return;
}
// ETag header
response.setHeader("ETag", eTag);
// Last-Modified header
response.setHeader("Last-Modified", lastModifiedHttp);
}
// Get content length
contentLength = resource.getContentLength();
// (silent) ISE when setting the output buffer size
if (contentLength == 0L) {
serveContent = false;
}
}
ServletOutputStream ostream = null;
PrintWriter writer = null;
if (serveContent) {
// Trying to retrieve the servlet output stream
try {
ostream = response.getOutputStream();
} catch (IllegalStateException e) {
// trying to serve a text file
if (!usingPrecompressedVersion && isText(contentType)) {
writer = response.getWriter();
// Cannot reliably serve partial content with a Writer
ranges = FULL;
} else {
throw e;
}
}
}
// Check to see if a Filter, Valve or wrapper has written some content.
// If it has, disable range requests and setting of a content length
// since neither can be done reliably.
ServletResponse r = response;
long contentWritten = 0;
while (r instanceof ServletResponseWrapper) {
r = ((ServletResponseWrapper) r).getResponse();
}
if (r instanceof ResponseFacade) {
contentWritten = ((ResponseFacade) r).getContentWritten();
}
if (contentWritten > 0) {
ranges = FULL;
}
String outputEncoding = response.getCharacterEncoding();
Charset charset = B2CConverter.getCharset(outputEncoding);
boolean conversionRequired;
/*
* The test below deliberately uses != to compare two Strings. This is
* because the code is looking to see if the default character encoding
* has been returned because no explicit character encoding has been
* defined. There is no clean way of doing this via the Servlet API. It
* would be possible to add a Tomcat specific API but that would require
* quite a bit of code to get to the Tomcat specific request object that
* may have been wrapped. The != test is a (slightly hacky) quick way of
* doing this.
*/
boolean outputEncodingSpecified = outputEncoding != org.apache.coyote.Constants.DEFAULT_BODY_CHARSET.name() && outputEncoding != resources.getContext().getResponseCharacterEncoding();
if (!usingPrecompressedVersion && isText(contentType) && outputEncodingSpecified && !charset.equals(fileEncodingCharset)) {
conversionRequired = true;
// Conversion often results fewer/more/different bytes.
// That does not play nicely with range requests.
ranges = FULL;
} else {
conversionRequired = false;
}
if (resource.isDirectory() || isError || ranges == FULL) {
// Set the appropriate output headers
if (contentType != null) {
if (debug > 0) {
log("DefaultServlet.serveFile: contentType='" + contentType + "'");
}
// Don't override a previously set content type
if (response.getContentType() == null) {
response.setContentType(contentType);
}
}
if (resource.isFile() && contentLength >= 0 && (!serveContent || ostream != null)) {
if (debug > 0) {
log("DefaultServlet.serveFile: contentLength=" + contentLength);
}
// written to the response or if conversion will be taking place
if (contentWritten == 0 && !conversionRequired) {
response.setContentLengthLong(contentLength);
}
}
if (serveContent) {
try {
response.setBufferSize(output);
} catch (IllegalStateException e) {
// Silent catch
}
InputStream renderResult = null;
if (ostream == null) {
// content directly.
if (resource.isDirectory()) {
renderResult = render(request, getPathPrefix(request), resource, inputEncoding);
} else {
renderResult = resource.getInputStream();
if (included) {
// Need to make sure any BOM is removed
if (!renderResult.markSupported()) {
renderResult = new BufferedInputStream(renderResult);
}
Charset bomCharset = processBom(renderResult, useBomIfPresent.stripBom);
if (bomCharset != null && useBomIfPresent.useBomEncoding) {
inputEncoding = bomCharset.name();
}
}
}
copy(renderResult, writer, inputEncoding);
} else {
// Output is via an OutputStream
if (resource.isDirectory()) {
renderResult = render(request, getPathPrefix(request), resource, inputEncoding);
} else {
// Check to see if conversion is required
if (conversionRequired || included) {
// When including a file, we need to check for a BOM
// to determine if a conversion is required, so we
// might as well always convert
InputStream source = resource.getInputStream();
if (!source.markSupported()) {
source = new BufferedInputStream(source);
}
Charset bomCharset = processBom(source, useBomIfPresent.stripBom);
if (bomCharset != null && useBomIfPresent.useBomEncoding) {
inputEncoding = bomCharset.name();
}
// specified
if (outputEncodingSpecified) {
OutputStreamWriter osw = new OutputStreamWriter(ostream, charset);
PrintWriter pw = new PrintWriter(osw);
copy(source, pw, inputEncoding);
pw.flush();
} else {
// Just included but no conversion
renderResult = source;
}
} else {
if (!checkSendfile(request, response, resource, contentLength, null)) {
// sendfile not possible so check if resource
// content is available directly via
// CachedResource. Do not want to call
// getContent() on other resource
// implementations as that could trigger loading
// the contents of a very large file into memory
byte[] resourceBody = null;
if (resource instanceof CachedResource) {
resourceBody = resource.getContent();
}
if (resourceBody == null) {
// Resource content not directly available,
// use InputStream
renderResult = resource.getInputStream();
} else {
// Use the resource content directly
ostream.write(resourceBody);
}
}
}
}
// the output (this method closes the stream)
if (renderResult != null) {
copy(renderResult, ostream);
}
}
}
} else {
if ((ranges == null) || (ranges.getEntries().isEmpty())) {
return;
}
// Partial content response.
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
if (ranges.getEntries().size() == 1) {
Ranges.Entry range = ranges.getEntries().get(0);
long start = getStart(range, contentLength);
long end = getEnd(range, contentLength);
response.addHeader("Content-Range", "bytes " + start + "-" + end + "/" + contentLength);
long length = end - start + 1;
response.setContentLengthLong(length);
if (contentType != null) {
if (debug > 0) {
log("DefaultServlet.serveFile: contentType='" + contentType + "'");
}
response.setContentType(contentType);
}
if (serveContent) {
try {
response.setBufferSize(output);
} catch (IllegalStateException e) {
// Silent catch
}
if (ostream != null) {
if (!checkSendfile(request, response, resource, contentLength, range)) {
copy(resource, contentLength, ostream, range);
}
} else {
// we should not get here
throw new IllegalStateException();
}
}
} else {
response.setContentType("multipart/byteranges; boundary=" + mimeSeparation);
if (serveContent) {
try {
response.setBufferSize(output);
} catch (IllegalStateException e) {
// Silent catch
}
if (ostream != null) {
copy(resource, contentLength, ostream, ranges, contentType);
} else {
// we should not get here
throw new IllegalStateException();
}
}
}
}
}
Aggregations