Example usage for javax.servlet.http HttpServletResponse SC_NOT_MODIFIED

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

Introduction

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

Prototype

int SC_NOT_MODIFIED

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

Click Source Link

Document

Status code (304) indicating that a conditional GET operation found that the resource was available and not modified.

Usage

From source file:inti.ws.spring.resource.ResourceController.java

protected void resource(String name, WebResource resource, HttpServletRequest request,
        HttpServletResponse response, ConfigParserSettings settings) throws Exception {
    String tmp;/*from  w  ww. ja v a 2s.  c o m*/

    resource.updateIfNeeded();

    if ((tmp = request.getHeader(HttpHeaders.IF_MODIFIED_SINCE)) != null) {
        if (resource.getLastModified() <= dateFormatter.parseDate(tmp)) {
            response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
            return;
        }
    } else if ((tmp = request.getHeader(HttpHeaders.IF_NONE_MATCH)) != null) {
        if (tmp.equals(resource.getMessageDigest())) {
            response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
            return;
        }
    }

    if (settings != null) {

        try {
            applyCachingHeader(response, resource, settings);
        } catch (Exception exception) {
            LOGGER.debug("", exception);
        }

        if (settings.getContentType() != null) {
            response.setHeader(HttpHeaders.CONTENT_TYPE, settings.getContentType());
        }

        if (settings.getContentEncoding() != null) {
            response.setHeader(HttpHeaders.CONTENT_ENCODING, settings.getContentEncoding());
        }

        try {
            resource.processResponse(request, response, settings);
        } catch (Exception exception) {
            LOGGER.debug("", exception);
        }

        byte[] data = resource.getByteContent(settings);

        if (data != null) {
            response.getOutputStream().write(data);
            response.getOutputStream().close();
        }

    }

}

From source file:org.openmrs.module.atomfeed.web.AtomFeedDownloadServletTest.java

/**
 * @see AtomFeedDownloadServlet#doHead(javax.servlet.http.HttpServletRequest,
 *      javax.servlet.http.HttpServletResponse)
 * @verifies send not modified error if atom feed has not changed
 *//*from   ww  w.j  a  v a2s .  c om*/
@Test
public void doHead_shouldSendNotModifiedErrorIfAtomFeedHasNotChanged() throws Exception {
    // create servlet and corresponding request and response object to be sent
    AtomFeedDownloadServlet atomFeedDownloadServlet = new AtomFeedDownloadServlet();
    MockHttpServletRequest request = new MockHttpServletRequest("HEAD", "/atomfeed");
    request.setContextPath("/somecontextpath");
    MockHttpServletResponse response = new MockHttpServletResponse();

    // intentionally change atom feed in order to not depend from other tests
    AtomFeedUtil.objectCreated(new Encounter());

    // read atom feed header specific information from the header file
    String headerFileContent = AtomFeedUtil.readFeedHeaderFile();
    int contentLength = 0;
    String etagToken = "";
    Date lastModified = null;
    if (StringUtils.isNotBlank(headerFileContent)) {
        contentLength = headerFileContent.length() + Integer
                .valueOf(StringUtils.substringBetween(headerFileContent, "<entriesSize>", "</entriesSize>"));
        etagToken = StringUtils.substringBetween(headerFileContent, "<versionId>", "</versionId>");
        try {
            lastModified = new SimpleDateFormat(AtomFeedUtil.RFC_3339_DATE_FORMAT)
                    .parse(StringUtils.substringBetween(headerFileContent, "<updated>", "</updated>"));
        } catch (ParseException e) {
            // ignore it here
        }
    }
    // set request headers 
    request.addHeader("If-None-Match", '"' + etagToken + '"');
    request.addHeader("If-Modified-Since", lastModified);

    atomFeedDownloadServlet.service(request, response);
    // check response headers
    Assert.assertEquals(contentLength, response.getContentLength());
    Assert.assertEquals("application/atom+xml", response.getContentType());
    Assert.assertEquals(HttpServletResponse.SC_NOT_MODIFIED, response.getStatus());
    Assert.assertNotNull(response.getHeader("Last-Modified"));
}

From source file:com.autentia.common.util.web.ClasspathServlet.java

/**
 * Serve the file from the classpath//from w  w w . j ava  2  s  . c om
 */
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    String path = request.getPathInfo();

    /* if this servlet is not mapped to a path, use the request URI */
    if (path == null) {
        path = request.getRequestURI().substring(request.getContextPath().length());
    }

    if (!path.endsWith(PAGE_403)) {
        final String extension = path.substring(path.lastIndexOf('.'));
        if (!allowedExtensions.contains(extension)) {
            response.sendError(403, path + " denied");
            return;
        }
    }

    final URL resource = Thread.currentThread().getContextClassLoader().getResource(path.substring(1));
    if (resource == null) {
        response.sendError(404, path + " not found on classpath");

    } else {
        /* check modification date */
        final long ifModifiedSince = request.getDateHeader("If-Modified-Since");
        final long lastModified = resource.openConnection().getLastModified();
        if (ifModifiedSince != -1 && lastModified <= ifModifiedSince) {
            response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
            return;
        }

        if (log.isDebugEnabled()) {
            log.debug("Resolving URL " + path);
        }

        /* write to response */
        response.setContentType(getServletContext().getMimeType(path));
        final OutputStream out = new BufferedOutputStream(response.getOutputStream(), 512);
        final InputStream in = new BufferedInputStream(resource.openStream(), 512);
        final byte[] data = new byte[512];
        int len;
        try {
            while ((len = in.read(data)) != -1) {
                out.write(data, 0, len);
            }
        } finally {
            in.close();
            out.close();
        }
    }
}

From source file:org.b3log.solo.processor.FileUploadProcessor.java

@Override
public void doGet(final HttpServletRequest req, final HttpServletResponse resp)
        throws ServletException, IOException {
    if (QN_ENABLED) {
        return;//ww w .ja v  a  2s .co  m
    }

    final String uri = req.getRequestURI();
    String key = StringUtils.substringAfter(uri, "/upload/");
    key = StringUtils.substringBeforeLast(key, "?"); // Erase Qiniu template
    key = StringUtils.substringBeforeLast(key, "?"); // Erase Qiniu template

    String path = UPLOAD_DIR + key;
    path = URLDecoder.decode(path, "UTF-8");

    if (!FileUtil.isExistingFile(new File(path))) {
        resp.sendError(HttpServletResponse.SC_NOT_FOUND);

        return;
    }

    final byte[] data = IOUtils.toByteArray(new FileInputStream(path));

    final String ifNoneMatch = req.getHeader("If-None-Match");
    final String etag = "\"" + MD5.hash(new String(data)) + "\"";

    resp.addHeader("Cache-Control", "public, max-age=31536000");
    resp.addHeader("ETag", etag);
    resp.setHeader("Server", "Latke Static Server (v" + SoloServletListener.VERSION + ")");

    if (etag.equals(ifNoneMatch)) {
        resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);

        return;
    }

    final OutputStream output = resp.getOutputStream();
    IOUtils.write(data, output);
    output.flush();

    IOUtils.closeQuietly(output);
}

From source file:com.harrywu.springweb.common.StreamingViewRenderer.java

@Override
public void renderMergedOutputModel(Map<String, Object> objectMap, HttpServletRequest request,
        HttpServletResponse response) throws Exception {

    InputStream dataStream = (InputStream) objectMap.get(INPUT_STREAM);

    if (dataStream == null) {
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;/*from   w w w . ja  va  2 s  .  co m*/
    }
    long length = (Long) objectMap.get(CONTENT_LENGTH);
    String fileName = (String) objectMap.get(FILENAME);
    Date lastModifiedObj = (Date) objectMap.get(LAST_MODIFIED);

    if (StringUtils.isEmpty(fileName) || lastModifiedObj == null) {
        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        return;
    }
    long lastModified = lastModifiedObj.getTime();
    String contentType = (String) objectMap.get(CONTENT_TYPE);

    // 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, fileName)) {
        response.setHeader("ETag", fileName); // Required in 304.
        response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
        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.setHeader("ETag", fileName); // Required in 304.
        response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
        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, fileName)) {
        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;
        }

        String ifRange = request.getHeader("If-Range");
        if (ifRange != null && !ifRange.equals(fileName)) {
            try {
                long ifRangeTime = request.getDateHeader("If-Range"); // Throws
                                                                      // IAE
                                                                      // if
                                                                      // invalid.
                if (ifRangeTime != -1) {
                    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 content disposition.
    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";
    } else if (!contentType.startsWith("image")) {
        // 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.
        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", fileName);
    response.setDateHeader("Last-Modified", lastModified);
    response.setDateHeader("Expires", System.currentTimeMillis() + DEFAULT_EXPIRE_TIME);

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

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

    try {
        // Open streams.
        input = new BufferedInputStream(dataStream);
        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);
            response.setHeader("Content-Length", String.valueOf(r.length));
            copy(input, output, length, 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.

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

        } else {

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

            // 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, length, r.start, r.length);
            }

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

}

From source file:io.wcm.wcm.commons.caching.CacheHeaderTest.java

@Test
public void testIsNotModified_WithIfModifiedSinceHeader_Publish() throws Exception {
    when(request.getAttribute(WCMMode.REQUEST_ATTRIBUTE_NAME)).thenReturn(WCMMode.DISABLED);
    when(request.getHeader(HEADER_IF_MODIFIED_SINCE)).thenReturn(formatDate(SAMPLE_CALENDAR_2.getTime()));
    assertTrue(CacheHeader.isNotModified(resource, request, response));
    verify(response).setStatus(HttpServletResponse.SC_NOT_MODIFIED);
    verifyNoMoreInteractions(response);/*ww w .  ja va2  s.co  m*/
}

From source file:com.haulmont.cuba.web.sys.CubaWebJarsHandler.java

@Override
public boolean handleRequest(VaadinSession session, VaadinRequest request, VaadinResponse response)
        throws IOException {
    String path = request.getPathInfo();

    if (StringUtils.isEmpty(path) || StringUtils.isNotEmpty(path) && !path.startsWith(VAADIN_WEBJARS_PREFIX)) {
        return false;
    }/*from ww w.  ja  va2 s.c om*/

    log.trace("WebJar resource requested: {}", path.replace(VAADIN_WEBJARS_PREFIX, ""));

    String errorMessage = checkResourcePath(path);
    if (StringUtils.isNotEmpty(errorMessage)) {
        log.warn(errorMessage);
        response.sendError(HttpServletResponse.SC_FORBIDDEN, errorMessage);
        return false;
    }

    URL resourceUrl = getStaticResourceUrl(path);

    if (resourceUrl == null) {
        resourceUrl = getClassPathResourceUrl(path);
    }

    if (resourceUrl == null) {
        String msg = String.format("Requested WebJar resource is not found: %s", path);
        response.sendError(HttpServletResponse.SC_NOT_FOUND, msg);
        log.warn(msg);
        return false;
    }

    String resourceName = getResourceName(path);
    String mimeType = servletContext.getMimeType(resourceName);
    response.setContentType(mimeType != null ? mimeType : FileTypesHelper.DEFAULT_MIME_TYPE);

    String cacheControl = "public, max-age=0, must-revalidate";
    int resourceCacheTime = getCacheTime(resourceName);
    if (resourceCacheTime > 0) {
        cacheControl = "max-age=" + String.valueOf(resourceCacheTime);
    }
    response.setHeader("Cache-Control", cacheControl);
    response.setDateHeader("Expires", System.currentTimeMillis() + (resourceCacheTime * 1000));

    InputStream inputStream = null;
    try {
        URLConnection connection = resourceUrl.openConnection();
        long lastModifiedTime = connection.getLastModified();
        // Remove milliseconds to avoid comparison problems (milliseconds
        // are not returned by the browser in the "If-Modified-Since"
        // header).
        lastModifiedTime = lastModifiedTime - lastModifiedTime % 1000;
        response.setDateHeader("Last-Modified", lastModifiedTime);

        if (browserHasNewestVersion(request, lastModifiedTime)) {
            response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
            return true;
        }

        inputStream = connection.getInputStream();

        copy(inputStream, response.getOutputStream());

        return true;
    } finally {
        if (inputStream != null) {
            inputStream.close();
        }
    }
}

From source file:ch.entwine.weblounge.test.util.TestSiteUtils.java

/**
 * Test for the correct response when modified since header is set.
 * /*from  w  ww .jav a 2 s  . c  o  m*/
 * @param request
 *          the http request
 * @param date
 *          the expected modification date
 * @param logger
 *          used to log test output
 * @param params
 *          the request parameters
 * @throws Exception
 *           if processing the request fails
 */
public static void testModifiedHeader(HttpUriRequest request, Date modificationDate, Logger logger,
        String[][] params) throws Exception {
    DefaultHttpClient httpClient = new DefaultHttpClient();
    Date before = new Date(modificationDate.getTime() - Times.MS_PER_DAY);
    Date after = new Date(modificationDate.getTime() + Times.MS_PER_DAY);
    SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
    try {
        request.removeHeaders("If-None-Match");
        request.setHeader("If-Modified-Since", format.format(after));
        logger.info("Sending 'If-Modified-Since' request to {}", request.getURI());
        HttpResponse response = TestUtils.request(httpClient, request, params);
        assertEquals(HttpServletResponse.SC_NOT_MODIFIED, response.getStatusLine().getStatusCode());
        assertNull(response.getEntity());
    } finally {
        httpClient.getConnectionManager().shutdown();
    }

    httpClient = new DefaultHttpClient();
    try {
        request.removeHeaders("If-None-Match");
        request.setHeader("If-Modified-Since", format.format(before));
        logger.info("Sending 'If-Modified-Since' request to {}", request.getURI());
        HttpResponse response = TestUtils.request(httpClient, request, params);
        assertEquals(HttpServletResponse.SC_OK, response.getStatusLine().getStatusCode());
        assertNotNull(response.getEntity());
        response.getEntity().consumeContent();
    } finally {
        httpClient.getConnectionManager().shutdown();
    }
}

From source file:org.tightblog.rendering.processors.MediaFileProcessor.java

@RequestMapping(method = { RequestMethod.GET, RequestMethod.HEAD })
void getMediaFile(HttpServletRequest request, HttpServletResponse response) throws IOException {
    WeblogRequest incomingRequest = WeblogRequest.create(request);

    Weblog weblog = weblogRepository.findByHandleAndVisibleTrue(incomingRequest.getWeblogHandle());
    if (weblog == null) {
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;//  w ww.  ja  v a 2 s .  c  om
    } else {
        incomingRequest.setWeblog(weblog);
    }

    MediaFile mediaFile = null;
    // path info here is the resourceId
    String pathInfo = incomingRequest.getExtraPathInfo();
    if (StringUtils.isNotBlank(pathInfo)) {
        mediaFile = mediaManager.getMediaFileWithContent(pathInfo);
    }

    if (mediaFile == null) {
        log.info("Could not obtain media file for resource path: ", request.getRequestURL());
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
    }

    weblogMediaCache.incrementIncomingRequests();

    // DB stores last modified in millis, browser if-modified-since in seconds, so need to truncate millis from the former.
    long inDb = mediaFile.getLastUpdated().truncatedTo(ChronoUnit.SECONDS).toEpochMilli();
    long inBrowser = getBrowserCacheExpireDate(request);

    if (inDb <= inBrowser) {
        weblogMediaCache.incrementRequestsHandledBy304();
        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
        return;
    }

    boolean useThumbnail = false;
    if (mediaFile.isImageFile() && "true".equals(request.getParameter("tn"))) {
        useThumbnail = true;
    }

    File desiredFile = useThumbnail ? mediaFile.getThumbnail() : mediaFile.getContent();
    if (desiredFile == null) {
        log.info("Could not obtain {} file content for resource path: ", useThumbnail ? "thumbnail" : "",
                request.getRequestURL());
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
    }

    try (InputStream resourceStream = new FileInputStream(desiredFile);
            OutputStream out = response.getOutputStream()) {

        byte[] buf = new byte[Utilities.EIGHT_KB_IN_BYTES];
        int length;
        while ((length = resourceStream.read(buf)) > 0) {
            out.write(buf, 0, length);
        }
        response.setContentType(useThumbnail ? MediaFile.THUMBNAIL_CONTENT_TYPE : mediaFile.getContentType());
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Last-Modified", mediaFile.getLastUpdated().toEpochMilli());
    } catch (IOException ex) {
        log.error("Error obtaining media file {}", desiredFile.getAbsolutePath(), ex);
        if (!response.isCommitted()) {
            response.reset();
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
        }
    }
}

From source file:com.sastix.cms.server.utils.MultipartFileSender.java

public void serveResource() throws Exception {
    if (response == null || request == null) {
        return;/*from  www  .  ja v  a2s .co m*/
    }

    Long length = Files.size(filepath);
    String fileName = filepath.getFileName().toString();
    FileTime lastModifiedObj = Files.getLastModifiedTime(filepath);

    if (StringUtils.isEmpty(fileName) || lastModifiedObj == null) {
        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        return;
    }
    long lastModified = LocalDateTime
            .ofInstant(lastModifiedObj.toInstant(), ZoneId.of(ZoneOffset.systemDefault().getId()))
            .toEpochSecond(ZoneOffset.UTC);

    // 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 && HttpUtils.matches(ifNoneMatch, fileName)) {
        response.setHeader("ETag", fileName); // Required in 304.
        response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
        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.setHeader("ETag", fileName); // Required in 304.
        response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
        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 && !HttpUtils.matches(ifMatch, fileName)) {
        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;
        }

        String ifRange = request.getHeader("If-Range");
        if (ifRange != null && !ifRange.equals(fileName)) {
            try {
                long ifRangeTime = request.getDateHeader("If-Range"); // Throws IAE if invalid.
                if (ifRangeTime != -1) {
                    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 = Range.sublong(part, 0, part.indexOf("-"));
                long end = Range.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 content disposition.
    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";
    } else if (!contentType.startsWith("image")) {
        // 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.
        String accept = request.getHeader("Accept");
        disposition = accept != null && HttpUtils.accepts(accept, contentType) ? "inline" : "attachment";
    }
    LOG.debug("Content-Type : {}", contentType);
    // Initialize response.
    response.reset();
    response.setBufferSize(DEFAULT_BUFFER_SIZE);
    response.setHeader("Content-Type", contentType);
    response.setHeader("Content-Disposition", disposition + ";filename=\"" + fileName + "\"");
    LOG.debug("Content-Disposition : {}", disposition);
    response.setHeader("Accept-Ranges", "bytes");
    response.setHeader("ETag", fileName);
    response.setDateHeader("Last-Modified", lastModified);
    response.setDateHeader("Expires", System.currentTimeMillis() + DEFAULT_EXPIRE_TIME);

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

    // Prepare streams.
    try (InputStream input = new BufferedInputStream(Files.newInputStream(filepath));
            OutputStream output = response.getOutputStream()) {

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

            // Return full file.
            LOG.debug("Return full file");
            response.setContentType(contentType);
            response.setHeader("Content-Range", "bytes " + full.start + "-" + full.end + "/" + full.total);
            response.setHeader("Content-Length", String.valueOf(full.length));
            Range.copy(input, output, length, full.start, full.length);

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

            // Return single part of file.
            Range r = ranges.get(0);
            LOG.debug("Return 1 part of file : from ({}) to ({})", r.start, r.end);
            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.

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

        } else {

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

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

            // Copy multi part range.
            for (Range r : ranges) {
                LOG.debug("Return multi part of file : from ({}) to ({})", r.start, r.end);
                // 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.
                Range.copy(input, output, length, r.start, r.length);
            }

            // End with multipart boundary.
            sos.println();
            sos.println("--" + MULTIPART_BOUNDARY + "--");
        }
    }

}