Search in sources :

Example 1 with CachedResource

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();
                }
            }
        }
    }
}
Also used : Ranges(org.apache.tomcat.util.http.parser.Ranges) ServletResponse(jakarta.servlet.ServletResponse) HttpServletResponse(jakarta.servlet.http.HttpServletResponse) ServletOutputStream(jakarta.servlet.ServletOutputStream) BufferedInputStream(java.io.BufferedInputStream) ByteArrayInputStream(java.io.ByteArrayInputStream) FileInputStream(java.io.FileInputStream) InputStream(java.io.InputStream) FileNotFoundException(java.io.FileNotFoundException) WebResource(org.apache.catalina.WebResource) Charset(java.nio.charset.Charset) BufferedInputStream(java.io.BufferedInputStream) ResponseFacade(org.apache.catalina.connector.ResponseFacade) CachedResource(org.apache.catalina.webresources.CachedResource) OutputStreamWriter(java.io.OutputStreamWriter) ServletResponseWrapper(jakarta.servlet.ServletResponseWrapper) PrintWriter(java.io.PrintWriter)

Aggregations

ServletOutputStream (jakarta.servlet.ServletOutputStream)1 ServletResponse (jakarta.servlet.ServletResponse)1 ServletResponseWrapper (jakarta.servlet.ServletResponseWrapper)1 HttpServletResponse (jakarta.servlet.http.HttpServletResponse)1 BufferedInputStream (java.io.BufferedInputStream)1 ByteArrayInputStream (java.io.ByteArrayInputStream)1 FileInputStream (java.io.FileInputStream)1 FileNotFoundException (java.io.FileNotFoundException)1 InputStream (java.io.InputStream)1 OutputStreamWriter (java.io.OutputStreamWriter)1 PrintWriter (java.io.PrintWriter)1 Charset (java.nio.charset.Charset)1 WebResource (org.apache.catalina.WebResource)1 ResponseFacade (org.apache.catalina.connector.ResponseFacade)1 CachedResource (org.apache.catalina.webresources.CachedResource)1 Ranges (org.apache.tomcat.util.http.parser.Ranges)1