Example usage for javax.servlet.http HttpServletResponse SC_REQUESTED_RANGE_NOT_SATISFIABLE

List of usage examples for javax.servlet.http HttpServletResponse SC_REQUESTED_RANGE_NOT_SATISFIABLE

Introduction

In this page you can find the example usage for javax.servlet.http HttpServletResponse SC_REQUESTED_RANGE_NOT_SATISFIABLE.

Prototype

int SC_REQUESTED_RANGE_NOT_SATISFIABLE

To view the source code for javax.servlet.http HttpServletResponse SC_REQUESTED_RANGE_NOT_SATISFIABLE.

Click Source Link

Document

Status code (416) indicating that the server cannot serve the requested byte range.

Usage

From source file:org.orderofthebee.addons.support.tools.share.AbstractLogFileWebScript.java

protected boolean processSingleRange(final WebScriptResponse res, final File file, final String range,
        final String mimetype) throws IOException {
    // return the specific set of bytes as requested in the content-range header

    /*/* www.j a v  a2  s .c  om*/
     * Examples of byte-content-range-spec values, assuming that the entity contains total of 1234 bytes:
     * The first 500 bytes:
     * bytes 0-499/1234
     * The second 500 bytes:
     * bytes 500-999/1234
     * All except for the first 500 bytes:
     * bytes 500-1233/1234
     */
    /*
     * 'Range' header example:
     * bytes=10485760-20971519
     */

    boolean processedRange = false;
    Range r = null;
    try {
        r = Range.constructRange(range, mimetype, file.length());
    } catch (final IllegalArgumentException err) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Failed to parse range header - returning 416 status code: " + err.getMessage());
        }

        res.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
        res.setHeader(HEADER_CONTENT_RANGE, "\"*\"");
        res.getOutputStream().close();
        return true;
    }

    // set Partial Content status and range headers
    final String contentRange = "bytes " + Long.toString(r.start) + "-" + Long.toString(r.end) + "/"
            + Long.toString(file.length());

    res.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
    res.setContentType(mimetype);
    res.setHeader(HEADER_CONTENT_RANGE, contentRange);
    res.setHeader(HEADER_CONTENT_LENGTH, Long.toString((r.end - r.start) + 1L));

    if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Processing: Content-Range: " + contentRange);
    }

    InputStream is = null;
    try {
        // output the binary data for the range
        OutputStream os = null;
        os = res.getOutputStream();
        is = new FileInputStream(file);

        this.streamRangeBytes(r, is, os, 0L);

        os.close();
        processedRange = true;
    } catch (final IOException err) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Unable to process single range due to IO Exception: " + err.getMessage());
        }
        throw err;
    } finally {
        if (is != null) {
            is.close();
        }
    }

    return processedRange;
}

From source file:com.liferay.lms.servlet.SCORMFileServerServlet.java

/**
 * Procesa los metodos HTTP GET y POST.<br>
 * Busca en la ruta que se le ha pedido el comienzo del directorio
 * "contenidos" y sirve el fichero./*from   w  ww.  j  a  v a  2s  . c o m*/
 */
protected void processRequest(HttpServletRequest request, HttpServletResponse response, boolean content)
        throws ServletException, java.io.IOException {
    String mime_type;
    String charset;
    String patharchivo;
    String uri;

    try {
        User user = PortalUtil.getUser(request);

        if (user == null) {
            String userId = null;
            String companyId = null;
            Cookie[] cookies = ((HttpServletRequest) request).getCookies();
            if (Validator.isNotNull(cookies)) {
                for (Cookie c : cookies) {
                    if ("COMPANY_ID".equals(c.getName())) {
                        companyId = c.getValue();
                    } else if ("ID".equals(c.getName())) {
                        userId = hexStringToStringByAscii(c.getValue());
                    }
                }
            }

            if (userId != null && companyId != null) {
                try {
                    Company company = CompanyLocalServiceUtil.getCompany(Long.parseLong(companyId));
                    Key key = company.getKeyObj();

                    String userIdPlain = Encryptor.decrypt(key, userId);

                    user = UserLocalServiceUtil.getUser(Long.valueOf(userIdPlain));

                    // Now you can set the liferayUser into a thread local
                    // for later use or
                    // something like that.

                } catch (Exception pException) {
                    throw new RuntimeException(pException);
                }
            }
        }

        String rutaDatos = SCORMContentLocalServiceUtil.getBaseDir();

        // Se comprueba que el usuario tiene permisos para acceder.
        // Damos acceso a todo el mundo al directorio "personalizacion",
        // para permitir mostrar a todos la pantalla de identificacion.
        uri = URLDecoder.decode(request.getRequestURI(), "UTF-8");
        uri = uri.substring(uri.indexOf("scorm/") + "scorm/".length());
        patharchivo = rutaDatos + "/" + uri;

        String[] params = uri.split("/");
        long groupId = GetterUtil.getLong(params[1]);
        String uuid = params[2];
        SCORMContent scormContent = SCORMContentLocalServiceUtil.getSCORMContentByUuidAndGroupId(uuid, groupId);

        boolean allowed = false;
        if (user == null) {
            user = UserLocalServiceUtil.getDefaultUser(PortalUtil.getDefaultCompanyId());
        }
        PermissionChecker pc = PermissionCheckerFactoryUtil.create(user);
        allowed = pc.hasPermission(groupId, SCORMContent.class.getName(), scormContent.getScormId(),
                ActionKeys.VIEW);
        if (!allowed) {
            AssetEntry scormAsset = AssetEntryLocalServiceUtil.getEntry(SCORMContent.class.getName(),
                    scormContent.getPrimaryKey());
            long scormAssetId = scormAsset.getEntryId();
            int typeId = new Long((new SCORMLearningActivityType()).getTypeId()).intValue();
            long[] groupIds = user.getGroupIds();
            for (long gId : groupIds) {
                List<LearningActivity> acts = LearningActivityLocalServiceUtil
                        .getLearningActivitiesOfGroupAndType(gId, typeId);
                for (LearningActivity act : acts) {
                    String entryId = LearningActivityLocalServiceUtil.getExtraContentValue(act.getActId(),
                            "assetEntry");
                    if (Validator.isNotNull(entryId) && Long.valueOf(entryId) == scormAssetId) {
                        allowed = pc.hasPermission(gId, LearningActivity.class.getName(), act.getActId(),
                                ActionKeys.VIEW);
                        if (allowed) {
                            break;
                        }
                    }
                }
                if (allowed) {
                    break;
                }
            }

        }
        if (allowed) {

            File archivo = new File(patharchivo);

            // Si el archivo existe y no es un directorio se sirve. Si no,
            // no se hace nada.
            if (archivo.exists() && archivo.isFile()) {

                // El content type siempre antes del printwriter
                mime_type = MimeTypesUtil.getContentType(archivo);
                charset = "";
                if (archivo.getName().toLowerCase().endsWith(".html")
                        || archivo.getName().toLowerCase().endsWith(".htm")) {
                    mime_type = "text/html";
                    if (isISO(FileUtils.readFileToString(archivo))) {
                        charset = "ISO-8859-1";
                    }
                }
                if (archivo.getName().toLowerCase().endsWith(".swf")) {
                    mime_type = "application/x-shockwave-flash";
                }
                if (archivo.getName().toLowerCase().endsWith(".mp4")) {
                    mime_type = "video/mp4";
                }
                if (archivo.getName().toLowerCase().endsWith(".flv")) {
                    mime_type = "video/x-flv";
                }
                response.setContentType(mime_type);
                if (Validator.isNotNull(charset)) {
                    response.setCharacterEncoding(charset);

                }
                response.addHeader("Content-Type",
                        mime_type + (Validator.isNotNull(charset) ? "; " + charset : ""));
                /*if (archivo.getName().toLowerCase().endsWith(".swf")
                      || archivo.getName().toLowerCase().endsWith(".flv")) {
                   response.addHeader("Content-Length",
                String.valueOf(archivo.length()));
                }
                */
                if (archivo.getName().toLowerCase().endsWith("imsmanifest.xml")) {
                    FileInputStream fis = new FileInputStream(patharchivo);

                    String sco = ParamUtil.get(request, "scoshow", "");
                    Document manifest = SAXReaderUtil.read(fis);
                    if (sco.length() > 0) {

                        Element organizatEl = manifest.getRootElement().element("organizations")
                                .element("organization");
                        Element selectedItem = selectItem(organizatEl, sco);
                        if (selectedItem != null) {
                            selectedItem.detach();
                            java.util.List<Element> items = organizatEl.elements("item");
                            for (Element item : items) {

                                organizatEl.remove(item);
                            }
                            organizatEl.add(selectedItem);
                        }
                    }
                    //clean unused resources.
                    Element resources = manifest.getRootElement().element("resources");
                    java.util.List<Element> theResources = resources.elements("resource");
                    Element organizatEl = manifest.getRootElement().element("organizations")
                            .element("organization");
                    java.util.List<String> identifiers = getIdentifierRefs(organizatEl);
                    for (Element resource : theResources) {
                        String identifier = resource.attributeValue("identifier");
                        if (!identifiers.contains(identifier)) {
                            resources.remove(resource);
                        }
                    }
                    response.getWriter().print(manifest.asXML());
                    fis.close();
                    return;

                }

                if (mime_type.startsWith("text") || mime_type.endsWith("javascript")
                        || mime_type.equals("application/xml")) {

                    java.io.OutputStream out = response.getOutputStream();
                    FileInputStream fis = new FileInputStream(patharchivo);

                    byte[] buffer = new byte[512];
                    int i = 0;

                    while (fis.available() > 0) {
                        i = fis.read(buffer);
                        if (i == 512)
                            out.write(buffer);
                        else
                            out.write(buffer, 0, i);

                    }

                    fis.close();
                    out.flush();
                    out.close();
                    return;
                }
                //If not manifest
                String fileName = archivo.getName();
                long length = archivo.length();
                long lastModified = archivo.lastModified();
                String eTag = fileName + "_" + length + "_" + lastModified;
                long expires = System.currentTimeMillis() + DEFAULT_EXPIRE_TIME;
                String ifNoneMatch = request.getHeader("If-None-Match");
                if (ifNoneMatch != null && matches(ifNoneMatch, eTag)) {
                    response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                    response.setHeader("ETag", eTag); // Required in 304.
                    response.setDateHeader("Expires", expires); // Postpone cache with 1 week.
                    return;
                }
                long ifModifiedSince = request.getDateHeader("If-Modified-Since");
                if (ifNoneMatch == null && ifModifiedSince != -1 && ifModifiedSince + 1000 > lastModified) {
                    response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                    response.setHeader("ETag", eTag); // Required in 304.
                    response.setDateHeader("Expires", expires); // Postpone cache with 1 week.
                    return;
                }

                // If-Match header should contain "*" or ETag. If not, then return 412.
                String ifMatch = request.getHeader("If-Match");
                if (ifMatch != null && !matches(ifMatch, eTag)) {
                    response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
                    return;
                }

                // If-Unmodified-Since header should be greater than LastModified. If not, then return 412.
                long ifUnmodifiedSince = request.getDateHeader("If-Unmodified-Since");
                if (ifUnmodifiedSince != -1 && ifUnmodifiedSince + 1000 <= lastModified) {
                    response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
                    return;
                }

                // Validate and process range -------------------------------------------------------------

                // Prepare some variables. The full Range represents the complete file.
                Range full = new Range(0, length - 1, length);
                List<Range> ranges = new ArrayList<Range>();

                // Validate and process Range and If-Range headers.
                String range = request.getHeader("Range");
                if (range != null) {

                    // Range header should match format "bytes=n-n,n-n,n-n...". If not, then return 416.
                    if (!range.matches("^bytes=\\d*-\\d*(,\\d*-\\d*)*$")) {
                        response.setHeader("Content-Range", "bytes */" + length); // Required in 416.
                        response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
                        return;
                    }

                    // If-Range header should either match ETag or be greater then LastModified. If not,
                    // then return full file.
                    String ifRange = request.getHeader("If-Range");
                    if (ifRange != null && !ifRange.equals(eTag)) {
                        try {
                            long ifRangeTime = request.getDateHeader("If-Range"); // Throws IAE if invalid.
                            if (ifRangeTime != -1 && ifRangeTime + 1000 < lastModified) {
                                ranges.add(full);
                            }
                        } catch (IllegalArgumentException ignore) {
                            ranges.add(full);
                        }
                    }

                    // If any valid If-Range header, then process each part of byte range.
                    if (ranges.isEmpty()) {
                        for (String part : range.substring(6).split(",")) {
                            // Assuming a file with length of 100, the following examples returns bytes at:
                            // 50-80 (50 to 80), 40- (40 to length=100), -20 (length-20=80 to length=100).
                            long start = sublong(part, 0, part.indexOf("-"));
                            long end = sublong(part, part.indexOf("-") + 1, part.length());

                            if (start == -1) {
                                start = length - end;
                                end = length - 1;
                            } else if (end == -1 || end > length - 1) {
                                end = length - 1;
                            }

                            // Check if Range is syntactically valid. If not, then return 416.
                            if (start > end) {
                                response.setHeader("Content-Range", "bytes */" + length); // Required in 416.
                                response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
                                return;
                            }

                            // Add range.
                            ranges.add(new Range(start, end, length));
                        }
                    }
                }
                boolean acceptsGzip = false;
                String disposition = "inline";

                if (mime_type.startsWith("text")) {
                    //String acceptEncoding = request.getHeader("Accept-Encoding");
                    // acceptsGzip = acceptEncoding != null && accepts(acceptEncoding, "gzip");
                    // mime_type += ";charset=UTF-8";
                }

                // Else, expect for images, determine content disposition. If content type is supported by
                // the browser, then set to inline, else attachment which will pop a 'save as' dialogue.
                else if (!mime_type.startsWith("image")) {
                    String accept = request.getHeader("Accept");
                    disposition = accept != null && accepts(accept, mime_type) ? "inline" : "attachment";
                }

                // Initialize response.
                response.reset();
                response.setBufferSize(DEFAULT_BUFFER_SIZE);
                response.setHeader("Content-Disposition", disposition + ";filename=\"" + fileName + "\"");
                response.setHeader("Accept-Ranges", "bytes");
                response.setHeader("ETag", eTag);
                response.setDateHeader("Last-Modified", lastModified);
                response.setDateHeader("Expires", expires);

                // Send requested file (part(s)) to client ------------------------------------------------

                // Prepare streams.
                RandomAccessFile input = null;
                OutputStream output = null;

                try {
                    // Open streams.
                    input = new RandomAccessFile(archivo, "r");
                    output = response.getOutputStream();

                    if (ranges.isEmpty() || ranges.get(0) == full) {

                        // Return full file.
                        Range r = full;
                        response.setContentType(mime_type);
                        response.setHeader("Content-Range", "bytes " + r.start + "-" + r.end + "/" + r.total);

                        if (content) {

                            // Content length is not directly predictable in case of GZIP.
                            // So only add it if there is no means of GZIP, else browser will hang.
                            response.setHeader("Content-Length", String.valueOf(r.length));

                            // Copy full range.
                            copy(input, output, r.start, r.length);
                        }

                    } else if (ranges.size() == 1) {

                        // Return single part of file.
                        Range r = ranges.get(0);
                        response.setContentType(mime_type);
                        response.setHeader("Content-Range", "bytes " + r.start + "-" + r.end + "/" + r.total);
                        response.setHeader("Content-Length", String.valueOf(r.length));
                        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // 206.

                        if (content) {
                            // Copy single part range.
                            copy(input, output, r.start, r.length);
                        }

                    } else {

                        // Return multiple parts of file.
                        response.setContentType("multipart/byteranges; boundary=" + MULTIPART_BOUNDARY);
                        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // 206.

                        if (content) {
                            // Cast back to ServletOutputStream to get the easy println methods.
                            ServletOutputStream sos = (ServletOutputStream) output;

                            // Copy multi part range.
                            for (Range r : ranges) {
                                // Add multipart boundary and header fields for every range.
                                sos.println();
                                sos.println("--" + MULTIPART_BOUNDARY);
                                sos.println("Content-Type: " + mime_type);
                                sos.println("Content-Range: bytes " + r.start + "-" + r.end + "/" + r.total);

                                // Copy single part range of multi part range.
                                copy(input, output, r.start, r.length);
                            }

                            // End with multipart boundary.
                            sos.println();
                            sos.println("--" + MULTIPART_BOUNDARY + "--");
                        }
                    }
                } finally {
                    // Gently close streams.
                    close(output);
                    close(input);
                }
            } else {
                //java.io.OutputStream out = response.getOutputStream();
                response.sendError(404);
                //out.write(uri.getBytes());
            }
        } else {
            response.sendError(401);
        }
    } catch (Exception e) {
        System.out.println("Error en el processRequest() de ServidorArchivos: " + e.getMessage());
    }
}

From source file:edu.chalmers.dat076.moviefinder.controller.FileController.java

/**
 * Process the actual request.//from  ww w . j ava  2  s  .  c  o m
 *
 * @param request The request to be processed.
 * @param response The response to be created.
 * @param content Whether the request body should be written (GET) or not
 * (HEAD).
 * @throws IOException If something fails at I/O level.
 */
private void processRequest(HttpServletRequest request, HttpServletResponse response, boolean content,
        String path, String defaultContentType) throws IOException {
    // Validate the requested file ------------------------------------------------------------

    // URL-decode the file name (might contain spaces and on) and prepare file object.
    File file = new File(path);

    // Check if file actually exists in filesystem.
    if (!file.exists()) {
        // Do your thing if the file appears to be non-existing.
        // Throw an exception, or send 404, or show default/warning page, or just ignore it.
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
    }

    // Prepare some variables. The ETag is an unique identifier of the file.
    String fileName = file.getName();
    long length = file.length();
    long lastModified = file.lastModified();
    String eTag = fileName + "_" + length + "_" + lastModified;
    long expires = System.currentTimeMillis() + FileControllerUtils.DEFAULT_EXPIRE_TIME;

    // Validate request headers for caching ---------------------------------------------------
    // If-None-Match header should contain "*" or ETag. If so, then return 304.
    String ifNoneMatch = request.getHeader("If-None-Match");
    if (ifNoneMatch != null && FileControllerUtils.matches(ifNoneMatch, eTag)) {
        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
        response.setHeader("ETag", eTag); // Required in 304.
        response.setDateHeader("Expires", expires); // Postpone cache with 1 week.
        return;
    }

    // If-Modified-Since header should be greater than LastModified. If so, then return 304.
    // This header is ignored if any If-None-Match header is specified.
    long ifModifiedSince = request.getDateHeader("If-Modified-Since");
    if (ifNoneMatch == null && ifModifiedSince != -1 && ifModifiedSince + 1000 > lastModified) {
        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
        response.setHeader("ETag", eTag); // Required in 304.
        response.setDateHeader("Expires", expires); // Postpone cache with 1 week.
        return;
    }

    // Validate request headers for resume ----------------------------------------------------
    // If-Match header should contain "*" or ETag. If not, then return 412.
    String ifMatch = request.getHeader("If-Match");
    if (ifMatch != null && !FileControllerUtils.matches(ifMatch, eTag)) {
        response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
        return;
    }

    // If-Unmodified-Since header should be greater than LastModified. If not, then return 412.
    long ifUnmodifiedSince = request.getDateHeader("If-Unmodified-Since");
    if (ifUnmodifiedSince != -1 && ifUnmodifiedSince + 1000 <= lastModified) {
        response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
        return;
    }

    // Validate and process range -------------------------------------------------------------
    // Prepare some variables. The full Range represents the complete file.
    Range full = new Range(0, length - 1, length);
    List<Range> ranges = new ArrayList<>();

    // Validate and process Range and If-Range headers.
    String range = request.getHeader("Range");
    if (range != null) {

        // Range header should match format "bytes=n-n,n-n,n-n...". If not, then return 416.
        if (!range.matches("^bytes=\\d*-\\d*(,\\d*-\\d*)*$")) {
            response.setHeader("Content-Range", "bytes */" + length); // Required in 416.
            response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
            return;
        }

        // If-Range header should either match ETag or be greater then LastModified. If not,
        // then return full file.
        String ifRange = request.getHeader("If-Range");
        if (ifRange != null && !ifRange.equals(eTag)) {
            try {
                long ifRangeTime = request.getDateHeader("If-Range"); // Throws IAE if invalid.
                if (ifRangeTime != -1 && ifRangeTime + 1000 < lastModified) {
                    ranges.add(full);
                }
            } catch (IllegalArgumentException ignore) {
                ranges.add(full);
            }
        }

        // If any valid If-Range header, then process each part of byte range.
        if (ranges.isEmpty()) {
            for (String part : range.substring(6).split(",")) {
                // Assuming a file with length of 100, the following examples returns bytes at:
                // 50-80 (50 to 80), 40- (40 to length=100), -20 (length-20=80 to length=100).
                long start = FileControllerUtils.sublong(part, 0, part.indexOf("-"));
                long end = FileControllerUtils.sublong(part, part.indexOf("-") + 1, part.length());

                if (start == -1) {
                    start = length - end;
                    end = length - 1;
                } else if (end == -1 || end > length - 1) {
                    end = length - 1;
                }

                // Check if Range is syntactically valid. If not, then return 416.
                if (start > end) {
                    response.setHeader("Content-Range", "bytes */" + length); // Required in 416.
                    response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
                    return;
                }

                // Add range.
                ranges.add(new Range(start, end, length));
            }
        }
    }

    // Prepare and initialize response --------------------------------------------------------
    // Get content type by file name and set default GZIP support and content disposition.
    String contentType = request.getServletContext().getMimeType(fileName);
    boolean acceptsGzip = false;
    String disposition = "inline";

    // If content type is unknown, then set the default value.
    // For all content types, see: http://www.w3schools.com/media/media_mimeref.asp
    // To add new content types, add new mime-mapping entry in web.xml.
    //if (contentType == null) {
    contentType = defaultContentType;
    //}

    // If content type is text, then determine whether GZIP content encoding is supported by
    // the browser and expand content type with the one and right character encoding.
    if (contentType.startsWith("text")) {
        String acceptEncoding = request.getHeader("Accept-Encoding");
        acceptsGzip = acceptEncoding != null && FileControllerUtils.accepts(acceptEncoding, "gzip");
        contentType += ";charset=UTF-8";
    } // Else, expect for images, determine content disposition. If content type is supported by
      // the browser, then set to inline, else attachment which will pop a 'save as' dialogue.
    else if (!contentType.startsWith("image")) {
        String accept = request.getHeader("Accept");
        disposition = accept != null && FileControllerUtils.accepts(accept, contentType) ? "inline"
                : "attachment";
    }

    // Initialize response.
    response.reset();
    response.setBufferSize(FileControllerUtils.DEFAULT_BUFFER_SIZE);
    //response.setHeader("Content-Disposition", disposition + ";filename=\"" + fileName + "\"");
    response.setHeader("Accept-Ranges", "bytes");
    response.setHeader("ETag", eTag);
    response.setDateHeader("Last-Modified", lastModified);
    response.setDateHeader("Expires", expires);

    // Send requested file (part(s)) to client ------------------------------------------------
    // Prepare streams.
    RandomAccessFile input = null;
    OutputStream output = null;

    try {
        // Open streams.
        input = new RandomAccessFile(file, "r");
        output = response.getOutputStream();

        if (ranges.isEmpty() || ranges.get(0) == full) {

            // Return full file.
            Range r = full;
            response.setContentType(contentType);
            response.setHeader("Content-Range", "bytes " + r.start + "-" + r.end + "/" + r.total);

            if (content) {
                if (acceptsGzip) {
                    // The browser accepts GZIP, so GZIP the content.
                    response.setHeader("Content-Encoding", "gzip");
                    output = new GZIPOutputStream(output, FileControllerUtils.DEFAULT_BUFFER_SIZE);
                } else {
                    // Content length is not directly predictable in case of GZIP.
                    // So only add it if there is no means of GZIP, else browser will hang.
                    response.setHeader("Content-Length", String.valueOf(r.length));
                }

                // Copy full range.
                FileControllerUtils.copy(input, output, r.start, r.length);
            }

        } else if (ranges.size() == 1) {

            // Return single part of file.
            Range r = ranges.get(0);
            response.setContentType(contentType);
            response.setHeader("Content-Range", "bytes " + r.start + "-" + r.end + "/" + r.total);
            response.setHeader("Content-Length", String.valueOf(r.length));
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // 206.

            if (content) {
                // Copy single part range.
                FileControllerUtils.copy(input, output, r.start, r.length);
            }

        } else {

            // Return multiple parts of file.
            response.setContentType("multipart/byteranges; boundary=" + FileControllerUtils.MULTIPART_BOUNDARY);
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // 206.

            if (content) {
                // Cast back to ServletOutputStream to get the easy println methods.
                ServletOutputStream sos = (ServletOutputStream) output;

                // Copy multi part range.
                for (Range r : ranges) {
                    // Add multipart boundary and header fields for every range.
                    sos.println();
                    sos.println("--" + FileControllerUtils.MULTIPART_BOUNDARY);
                    sos.println("Content-Type: " + contentType);
                    sos.println("Content-Range: bytes " + r.start + "-" + r.end + "/" + r.total);

                    // Copy single part range of multi part range.
                    FileControllerUtils.copy(input, output, r.start, r.length);
                }

                // End with multipart boundary.
                sos.println();
                sos.println("--" + FileControllerUtils.MULTIPART_BOUNDARY + "--");
            }
        }
    } finally {
        // Gently close streams.
        FileControllerUtils.close(output);
        FileControllerUtils.close(input);
    }
}

From source file:org.springframework.web.servlet.resource.ResourceHttpRequestHandler.java

/**
 * Processes a resource request./*w w  w . j  a va  2s  .c o m*/
 * <p>Checks for the existence of the requested resource in the configured list of locations.
 * If the resource does not exist, a {@code 404} response will be returned to the client.
 * If the resource exists, the request will be checked for the presence of the
 * {@code Last-Modified} header, and its value will be compared against the last-modified
 * timestamp of the given resource, returning a {@code 304} status code if the
 * {@code Last-Modified} value  is greater. If the resource is newer than the
 * {@code Last-Modified} value, or the header is not present, the content resource
 * of the resource will be written to the response with caching headers
 * set to expire one year in the future.
 */
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    // For very general mappings (e.g. "/") we need to check 404 first
    Resource resource = getResource(request);
    if (resource == null) {
        logger.trace("No matching resource found - returning 404");
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
    }

    if (HttpMethod.OPTIONS.matches(request.getMethod())) {
        response.setHeader("Allow", getAllowHeader());
        return;
    }

    // Supported methods and required session
    checkRequest(request);

    // Header phase
    if (new ServletWebRequest(request, response).checkNotModified(resource.lastModified())) {
        logger.trace("Resource not modified - returning 304");
        return;
    }

    // Apply cache settings, if any
    prepareResponse(response);

    // Check the media type for the resource
    MediaType mediaType = getMediaType(request, resource);
    if (mediaType != null) {
        if (logger.isTraceEnabled()) {
            logger.trace("Determined media type '" + mediaType + "' for " + resource);
        }
    } else {
        if (logger.isTraceEnabled()) {
            logger.trace("No media type found for " + resource + " - not sending a content-type header");
        }
    }

    // Content phase
    if (METHOD_HEAD.equals(request.getMethod())) {
        setHeaders(response, resource, mediaType);
        logger.trace("HEAD request - skipping content");
        return;
    }

    ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
    if (request.getHeader(HttpHeaders.RANGE) == null) {
        Assert.state(this.resourceHttpMessageConverter != null, "Not initialized");
        setHeaders(response, resource, mediaType);
        this.resourceHttpMessageConverter.write(resource, mediaType, outputMessage);
    } else {
        Assert.state(this.resourceRegionHttpMessageConverter != null, "Not initialized");
        response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
        ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(request);
        try {
            List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
            this.resourceRegionHttpMessageConverter.write(HttpRange.toResourceRegions(httpRanges, resource),
                    mediaType, outputMessage);
        } catch (IllegalArgumentException ex) {
            response.setHeader("Content-Range", "bytes */" + resource.contentLength());
            response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
        }
    }
}

From source file:de.digitalcollections.streaming.euphoria.controller.StreamingController.java

/**
 * Create response for the request.//from  www. j a  v a 2  s. com
 *
 * @param id The id of requested content.
 * @param extension The (target) file extension/format of requested content.
 * @param request The request to be responded to.
 * @param response The response to the request.
 * @param head "true" if response body should be written (GET) or "false" if not (HEAD).
 * @throws IOException If something fails at I/O level.
 */
private void respond(String id, String extension, HttpServletRequest request, HttpServletResponse response,
        boolean head) throws IOException {
    logRequestHeaders(request);

    response.reset();

    // try to get access to resource
    Resource resource;
    try {
        resource = getResource(id, extension);
    } catch (ResourceIOException ex) {
        LOGGER.warn("*** Response {}: Error referencing streaming resource with id {} and extension {}",
                HttpServletResponse.SC_NOT_FOUND, id, extension);
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
    }

    // get resource metadata
    ResourceInfo resourceInfo = new ResourceInfo(id, resource);
    if (resourceInfo.length <= 0) {
        LOGGER.warn("*** Response {}: Error streaming resource with id {} and extension {}: not found/no size",
                HttpServletResponse.SC_NOT_FOUND, id, extension);
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
    }

    if (preconditionFailed(request, resourceInfo)) {
        LOGGER.warn(
                "*** Response {}: Precondition If-Match/If-Unmodified-Since failed for resource with id {} and extension {}.",
                HttpServletResponse.SC_PRECONDITION_FAILED, id, extension);
        response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
        return;
    }

    setCacheHeaders(response, resourceInfo);

    if (notModified(request, resourceInfo)) {
        LOGGER.debug("*** Response {}: 'Not modified'-response for resource with id {} and extension {}.",
                HttpServletResponse.SC_NOT_MODIFIED, id, extension);
        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
        return;
    }

    List<Range> ranges = getRanges(request, resourceInfo);

    if (ranges == null) {
        response.setHeader("Content-Range", "bytes */" + resourceInfo.length);
        LOGGER.warn("Response {}: Header Range for resource with id {} and extension {} not satisfiable",
                HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE, id, extension);
        response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
        return;
    }

    if (!ranges.isEmpty()) {
        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
    } else {
        ranges.add(new Range(0, resourceInfo.length - 1)); // Full content.
    }

    String contentType = setContentHeaders(request, response, resourceInfo, ranges);
    boolean acceptsGzip = false;
    // If content type is text, then determine whether GZIP content encoding is supported by
    // the browser and expand content type with the one and right character encoding.
    if (contentType.startsWith("text")) {
        String acceptEncoding = request.getHeader("Accept-Encoding");
        acceptsGzip = acceptEncoding != null && accepts(acceptEncoding, "gzip");
        contentType += ";charset=UTF-8";
    }

    if (head) {
        return;
    }

    writeContent(response, resource, resourceInfo, ranges, contentType, acceptsGzip);
    LOGGER.debug("*** RESPONSE FINISHED ***");
}

From source file:org.orderofthebee.addons.support.tools.share.ContentStreamer.java

protected boolean processSingleRange(final Response res, final File file, final String range,
        final String mimetype) throws IOException {
    // return the specific set of bytes as requested in the content-range header

    /*/*from   w w w  .ja  v  a2 s .c o m*/
     * Examples of byte-content-range-spec values, assuming that the entity contains total of 1234 bytes:
     * The first 500 bytes:
     * bytes 0-499/1234
     * The second 500 bytes:
     * bytes 500-999/1234
     * All except for the first 500 bytes:
     * bytes 500-1233/1234
     */
    /*
     * 'Range' header example:
     * bytes=10485760-20971519
     */

    boolean processedRange = false;
    Range r = null;
    try {
        r = Range.constructRange(range, mimetype, file.length());
    } catch (final IllegalArgumentException err) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Failed to parse range header - returning 416 status code: " + err.getMessage());
        }

        res.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
        res.setHeader(HEADER_CONTENT_RANGE, "\"*\"");
        res.getOutputStream().close();
        return true;
    }

    // set Partial Content status and range headers
    final String contentRange = "bytes " + Long.toString(r.start) + "-" + Long.toString(r.end) + "/"
            + Long.toString(file.length());

    res.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
    res.setContentType(mimetype);
    res.setHeader(HEADER_CONTENT_RANGE, contentRange);
    res.setHeader(HEADER_CONTENT_LENGTH, Long.toString((r.end - r.start) + 1L));

    if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Processing: Content-Range: " + contentRange);
    }

    InputStream is = null;
    try {
        // output the binary data for the range
        OutputStream os = null;
        os = res.getOutputStream();
        is = new FileInputStream(file);

        this.streamRangeBytes(r, is, os, 0L);

        os.close();
        processedRange = true;
    } catch (final IOException err) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Unable to process single range due to IO Exception: " + err.getMessage());
        }
        throw err;
    } finally {
        if (is != null) {
            is.close();
        }
    }

    return processedRange;
}

From source file:com.xpn.xwiki.web.DownloadActionTest.java

@Test
public void testOutsideRange() throws XWikiException, IOException {
    // This test expects a 416 response
    final Date d = new Date();
    createAttachment(d, DEFAULT_FILE_NAME);
    setRequestExpectations(DEFAULT_URI, null, null, "bytes=129-145", -1l);
    getMockery().checking(new Expectations() {
        {//from   w  w  w  .j  a  va2 s.  com
            one(DownloadActionTest.this.response)
                    .setStatus(with(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE));
        }
    });
    Assert.assertNull(this.action.render(getContext()));
}

From source file:net.yacy.http.servlets.YaCyDefaultServlet.java

/**
 * send static content/*from  w  w  w  .j  a va 2 s . c  o  m*/
 * 
 * @param request
 * @param response
 * @param include  is a include file (send without changing/adding headers)
 * @param resource the static content
 * @param reqRanges
 * @throws IOException 
 */
protected void sendData(HttpServletRequest request, HttpServletResponse response, boolean include,
        Resource resource, Enumeration<String> reqRanges) throws IOException {

    final long content_length = resource.length();

    // Get the output stream (or writer)
    OutputStream out;
    try {
        out = response.getOutputStream();
    } catch (IllegalStateException e) {
        out = new WriterOutputStream(response.getWriter());
    }

    // remove the last-modified field since caching otherwise does not work
    /*
       https://www.ietf.org/rfc/rfc2616.txt
       "if the response does have a Last-Modified time, the heuristic
       expiration value SHOULD be no more than some fraction of the interval
       since that time. A typical setting of this fraction might be 10%."
    */
    if (response.containsHeader(HeaderFramework.LAST_MODIFIED)) {
        response.getHeaders(HeaderFramework.LAST_MODIFIED).clear(); // if this field is present, the reload-time is a 10% fraction of ttl and other caching headers do not work
    }

    // cache-control: allow shared caching (i.e. proxies) and set expires age for cache
    response.setHeader(HeaderFramework.CACHE_CONTROL, "public, max-age=" + Integer.toString(600)); // seconds; ten minutes

    if (reqRanges == null || !reqRanges.hasMoreElements() || content_length < 0) {
        //  if there were no ranges, send entire entity
        if (include) {
            resource.writeTo(out, 0, content_length);
        } else {
            writeHeaders(response, resource, content_length);
            resource.writeTo(out, 0, content_length);
        }
    } else {
        // Parse the satisfiable ranges
        List<?> ranges = InclusiveByteRange.satisfiableRanges(reqRanges, content_length);

        //  if there are no satisfiable ranges, send 416 response
        if (ranges == null || ranges.isEmpty()) {
            writeHeaders(response, resource, content_length);
            response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
            response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
                    InclusiveByteRange.to416HeaderRangeString(content_length));
            resource.writeTo(out, 0, content_length);
            out.close();
            return;
        }

        //  if there is only a single valid range (must be satisfiable
        //  since were here now), send that range with a 216 response
        if (ranges.size() == 1) {
            InclusiveByteRange singleSatisfiableRange = (InclusiveByteRange) ranges.get(0);
            long singleLength = singleSatisfiableRange.getSize(content_length);
            writeHeaders(response, resource, singleLength);
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
            response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
                    singleSatisfiableRange.toHeaderRangeString(content_length));
            resource.writeTo(out, singleSatisfiableRange.getFirst(content_length), singleLength);
            out.close();
            return;
        }

        //  multiple non-overlapping valid ranges cause a multipart
        //  216 response which does not require an overall
        //  content-length header
        //
        writeHeaders(response, resource, -1);
        String mimetype = response.getContentType();
        if (mimetype == null) {
            ConcurrentLog.warn("FILEHANDLER",
                    "YaCyDefaultServlet: Unknown mimetype for " + request.getRequestURI());
        }
        MultiPartOutputStream multi = new MultiPartOutputStream(out);
        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);

        // If the request has a "Request-Range" header then we need to
        // send an old style multipart/x-byteranges Content-Type. This
        // keeps Netscape and acrobat happy. This is what Apache does.
        String ctp;
        if (request.getHeader(HttpHeader.REQUEST_RANGE.asString()) != null) {
            ctp = "multipart/x-byteranges; boundary=";
        } else {
            ctp = "multipart/byteranges; boundary=";
        }
        response.setContentType(ctp + multi.getBoundary());

        InputStream in = resource.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 = (InclusiveByteRange) ranges.get(i);
            header[i] = ibr.toHeaderRangeString(content_length);
            length += ((i > 0) ? 2 : 0) + 2 + multi.getBoundary().length() + 2
                    + (mimetype == null ? 0 : HeaderFramework.CONTENT_TYPE.length() + 2 + mimetype.length()) + 2
                    + HeaderFramework.CONTENT_RANGE.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 = (InclusiveByteRange) ranges.get(i);
            multi.startPart(mimetype, new String[] { HeaderFramework.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 = resource.getInputStream();
                    pos = 0;
                }
                if (pos < start) {
                    in.skip(start - pos);
                    pos = start;
                }

                FileUtils.copy(in, multi, size);
                pos += size;
            } else // Handle cached resource
            {
                (resource).writeTo(multi, start, size);
            }

        }
        if (in != null)
            in.close();
        multi.close();
    }
}

From source file:com.xpn.xwiki.web.DownloadActionTest.java

@Test
public void testOutsideRestRange() throws XWikiException, IOException {
    // This test expects a 416 response
    final Date d = new Date();
    createAttachment(d, DEFAULT_FILE_NAME);
    setRequestExpectations(DEFAULT_URI, null, null, "bytes=129-", -1L);
    getMockery().checking(new Expectations() {
        {/*from   ww  w .j  a v a2s . c o m*/
            one(DownloadActionTest.this.response)
                    .setStatus(with(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE));
        }
    });
    Assert.assertNull(this.action.render(getContext()));
}

From source file:org.openhab.ui.cometvisu.servlet.CometVisuServlet.java

/**
 * Process the actual request./*from  w w  w  .j a  va 2s . c  o m*/
 *
 * @param request
 *            The request to be processed.
 * @param response
 *            The response to be created.
 * @param content
 *            Whether the request body should be written (GET) or not
 *            (HEAD).
 * @throws IOException
 *             If something fails at I/O level.
 *
 * @author BalusC
 * @link
 *       http://balusc.blogspot.com/2009/02/fileservlet-supporting-resume-and
 *       .html
 */
private void processStaticRequest(File file, HttpServletRequest request, HttpServletResponse response,
        boolean content) throws IOException {
    // Validate the requested file
    // ------------------------------------------------------------

    if (file == null) {
        // Get requested file by path info.
        String requestedFile = request.getPathInfo();

        // Check if file is actually supplied to the request URL.
        if (requestedFile == null) {
            // Do your thing if the file is not supplied to the request URL.
            // Throw an exception, or send 404, or show default/warning
            // page, or
            // just ignore it.
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        // URL-decode the file name (might contain spaces and on) and
        // prepare
        // file object.
        file = new File(rootFolder, URLDecoder.decode(requestedFile, "UTF-8"));
    }
    // Check if file actually exists in filesystem.
    if (!file.exists()) {
        // Do your thing if the file appears to be non-existing.
        // Throw an exception, or send 404, or show default/warning page, or
        // just ignore it.
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
    }

    // Prepare some variables. The ETag is an unique identifier of the file.
    String fileName = file.getName();
    long length = file.length();
    long lastModified = file.lastModified();
    String eTag = fileName + "_" + length + "_" + lastModified;
    long expires = System.currentTimeMillis() + DEFAULT_EXPIRE_TIME;

    // Validate request headers for caching
    // ---------------------------------------------------

    // If-None-Match header should contain "*" or ETag. If so, then return
    // 304.
    String ifNoneMatch = request.getHeader("If-None-Match");
    if (ifNoneMatch != null && matches(ifNoneMatch, eTag)) {
        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
        response.setHeader("ETag", eTag); // Required in 304.
        response.setDateHeader("Expires", expires); // Postpone cache with 1
                                                    // week.
        return;
    }

    // If-Modified-Since header should be greater than LastModified. If so,
    // then return 304.
    // This header is ignored if any If-None-Match header is specified.
    long ifModifiedSince = request.getDateHeader("If-Modified-Since");
    if (ifNoneMatch == null && ifModifiedSince != -1 && ifModifiedSince + 1000 > lastModified) {
        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
        response.setHeader("ETag", eTag); // Required in 304.
        response.setDateHeader("Expires", expires); // Postpone cache with 1
                                                    // week.
        return;
    }

    // Validate request headers for resume
    // ----------------------------------------------------

    // If-Match header should contain "*" or ETag. If not, then return 412.
    String ifMatch = request.getHeader("If-Match");
    if (ifMatch != null && !matches(ifMatch, eTag)) {
        response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
        return;
    }

    // If-Unmodified-Since header should be greater than LastModified. If
    // not, then return 412.
    long ifUnmodifiedSince = request.getDateHeader("If-Unmodified-Since");
    if (ifUnmodifiedSince != -1 && ifUnmodifiedSince + 1000 <= lastModified) {
        response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
        return;
    }

    // Validate and process range
    // -------------------------------------------------------------

    // Prepare some variables. The full Range represents the complete file.
    Range full = new Range(0, length - 1, length);
    List<Range> ranges = new ArrayList<Range>();

    // Validate and process Range and If-Range headers.
    String range = request.getHeader("Range");
    if (range != null) {

        // Range header should match format "bytes=n-n,n-n,n-n...". If not,
        // then return 416.
        if (!range.matches("^bytes=\\d*-\\d*(,\\d*-\\d*)*$")) {
            response.setHeader("Content-Range", "bytes */" + length); // Required
                                                                      // in
                                                                      // 416.
            response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
            return;
        }

        // If-Range header should either match ETag or be greater then
        // LastModified. If not,
        // then return full file.
        String ifRange = request.getHeader("If-Range");
        if (ifRange != null && !ifRange.equals(eTag)) {
            try {
                long ifRangeTime = request.getDateHeader("If-Range"); // Throws
                                                                      // IAE
                                                                      // if
                                                                      // invalid.
                if (ifRangeTime != -1 && ifRangeTime + 1000 < lastModified) {
                    ranges.add(full);
                }
            } catch (IllegalArgumentException ignore) {
                ranges.add(full);
            }
        }

        // If any valid If-Range header, then process each part of byte
        // range.
        if (ranges.isEmpty()) {
            for (String part : range.substring(6).split(",")) {
                // Assuming a file with length of 100, the following
                // examples returns bytes at:
                // 50-80 (50 to 80), 40- (40 to length=100), -20
                // (length-20=80 to length=100).
                long start = sublong(part, 0, part.indexOf("-"));
                long end = sublong(part, part.indexOf("-") + 1, part.length());

                if (start == -1) {
                    start = length - end;
                    end = length - 1;
                } else if (end == -1 || end > length - 1) {
                    end = length - 1;
                }

                // Check if Range is syntactically valid. If not, then
                // return 416.
                if (start > end) {
                    response.setHeader("Content-Range", "bytes */" + length); // Required
                                                                              // in
                                                                              // 416.
                    response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
                    return;
                }

                // Add range.
                ranges.add(new Range(start, end, length));
            }
        }
    }

    // Prepare and initialize response
    // --------------------------------------------------------

    // Get content type by file name and set default GZIP support and
    // content disposition.
    String contentType = getServletContext().getMimeType(fileName);
    boolean acceptsGzip = false;
    String disposition = "inline";

    // If content type is unknown, then set the default value.
    // For all content types, see:
    // http://www.w3schools.com/media/media_mimeref.asp
    // To add new content types, add new mime-mapping entry in web.xml.
    if (contentType == null) {
        contentType = "application/octet-stream";
    }

    // If content type is text, then determine whether GZIP content encoding
    // is supported by
    // the browser and expand content type with the one and right character
    // encoding.
    if (contentType.startsWith("text")) {
        String acceptEncoding = request.getHeader("Accept-Encoding");
        acceptsGzip = acceptEncoding != null && accepts(acceptEncoding, "gzip");
        contentType += ";charset=UTF-8";
    }

    // Else, expect for images, determine content disposition. If content
    // type is supported by
    // the browser, then set to inline, else attachment which will pop a
    // 'save as' dialogue.
    else if (!contentType.startsWith("image")) {
        String accept = request.getHeader("Accept");
        disposition = accept != null && accepts(accept, contentType) ? "inline" : "attachment";
    }

    // Initialize response.
    response.reset();
    response.setBufferSize(DEFAULT_BUFFER_SIZE);
    response.setHeader("Content-Disposition", disposition + ";filename=\"" + fileName + "\"");
    response.setHeader("Accept-Ranges", "bytes");
    response.setHeader("ETag", eTag);
    response.setDateHeader("Last-Modified", lastModified);
    response.setDateHeader("Expires", expires);

    // Send requested file (part(s)) to client
    // ------------------------------------------------

    // Prepare streams.
    RandomAccessFile input = null;
    OutputStream output = null;

    try {
        // Open streams.
        input = new RandomAccessFile(file, "r");
        output = response.getOutputStream();

        if (ranges.isEmpty() || ranges.get(0) == full) {

            // Return full file.
            Range r = full;
            response.setContentType(contentType);
            response.setHeader("Content-Range", "bytes " + r.start + "-" + r.end + "/" + r.total);

            if (content) {
                if (acceptsGzip) {
                    // The browser accepts GZIP, so GZIP the content.
                    response.setHeader("Content-Encoding", "gzip");
                    output = new GZIPOutputStream(output, DEFAULT_BUFFER_SIZE);
                } else {
                    // Content length is not directly predictable in case of
                    // GZIP.
                    // So only add it if there is no means of GZIP, else
                    // browser will hang.
                    response.setHeader("Content-Length", String.valueOf(r.length));
                }

                // Copy full range.
                copy(input, output, r.start, r.length);
            }

        } else if (ranges.size() == 1) {

            // Return single part of file.
            Range r = ranges.get(0);
            response.setContentType(contentType);
            response.setHeader("Content-Range", "bytes " + r.start + "-" + r.end + "/" + r.total);
            response.setHeader("Content-Length", String.valueOf(r.length));
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // 206.

            if (content) {
                // Copy single part range.
                copy(input, output, r.start, r.length);
            }

        } else {

            // Return multiple parts of file.
            response.setContentType("multipart/byteranges; boundary=" + MULTIPART_BOUNDARY);
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // 206.

            if (content) {
                // Cast back to ServletOutputStream to get the easy println
                // methods.
                ServletOutputStream sos = (ServletOutputStream) output;

                // Copy multi part range.
                for (Range r : ranges) {
                    // Add multipart boundary and header fields for every
                    // range.
                    sos.println();
                    sos.println("--" + MULTIPART_BOUNDARY);
                    sos.println("Content-Type: " + contentType);
                    sos.println("Content-Range: bytes " + r.start + "-" + r.end + "/" + r.total);

                    // Copy single part range of multi part range.
                    copy(input, output, r.start, r.length);
                }

                // End with multipart boundary.
                sos.println();
                sos.println("--" + MULTIPART_BOUNDARY + "--");
            }
        }
    } finally {
        // Gently close streams.
        close(output);
        close(input);
    }
}