io.divolte.server.JavaScriptHandler.java Source code

Java tutorial

Introduction

Here is the source code for io.divolte.server.JavaScriptHandler.java

Source

/*
 * Copyright 2014 GoDataDriven B.V.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.divolte.server;

import io.divolte.server.js.GzippableHttpBody;
import io.divolte.server.js.HttpBody;
import io.divolte.server.js.JavaScriptResource;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.ETag;
import io.undertow.util.ETagUtils;
import io.undertow.util.HeaderMap;
import io.undertow.util.HeaderValues;
import io.undertow.util.Headers;
import io.undertow.util.StatusCodes;

import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.Objects;
import java.util.Optional;

import javax.annotation.ParametersAreNonnullByDefault;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;

/**
 * Request handler for a JavaScript resource, with caching support.
 */
@ParametersAreNonnullByDefault
final class JavaScriptHandler implements HttpHandler {
    private static final Logger logger = LoggerFactory.getLogger(Server.class);

    private static final Duration CACHING_AGE = Duration.ofHours(1);
    private static final String CACHE_CONTROL_HEADER_VALUE = "public, max-age=" + CACHING_AGE.getSeconds();

    private static final Splitter HEADER_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings();

    private final JavaScriptResource resource;

    public JavaScriptHandler(final JavaScriptResource resource) {
        this.resource = Objects.requireNonNull(resource);
    }

    @Override
    public void handleRequest(final HttpServerExchange exchange) throws Exception {
        if (logger.isDebugEnabled()) {
            logger.debug("Requested received for {} from {}", resource.getResourceName(),
                    exchange.getSourceAddress().getHostString());
        }
        // Start with headers that we always set the same way.
        final HeaderMap responseHeaders = exchange.getResponseHeaders();
        responseHeaders.put(Headers.CACHE_CONTROL, CACHE_CONTROL_HEADER_VALUE);

        // Figure out if we possibly need to deal with a compressed response,
        // based on client capability.
        final GzippableHttpBody uncompressedBody = resource.getEntityBody();
        final Optional<HttpBody> gzippedBody = uncompressedBody.getGzippedBody();
        final HttpBody bodyToSend;
        if (gzippedBody.isPresent()) {
            /*
             * Compressed responses can use Content-Encoding and/or Transfer-Encoding.
             * The semantics differ slightly, but it is suffice to say that most user
             * agents don't advertise their Transfer-Encoding support.
             * So for now we only support the Content-Encoding mechanism.
             * Some other notes:
             *  - Some clients implement 'deflate' incorrectly. Hence we only support 'gzip',
             *    despite it having slightly more overhead.
             *  - We don't use Undertow's built-in compression support because we've
             *    pre-calculated the compressed response and expect to serve it up
             *    repeatedly, instead of calculating it on-the-fly for every request.
             */
            responseHeaders.put(Headers.VARY, Headers.ACCEPT_ENCODING_STRING);
            final HeaderValues acceptEncoding = exchange.getRequestHeaders().get(Headers.ACCEPT_ENCODING);
            if (null != acceptEncoding && acceptEncoding.stream()
                    .anyMatch((header) -> Iterables.contains(HEADER_SPLITTER.split(header), "gzip"))) {
                responseHeaders.put(Headers.CONTENT_ENCODING, "gzip");
                bodyToSend = gzippedBody.get();
            } else {
                bodyToSend = uncompressedBody;
            }
        } else {
            bodyToSend = uncompressedBody;
        }

        // Now we know which version of the entity is visible to this user-agent,
        // figure out if the client already has the current version or not.
        final ETag eTag = bodyToSend.getETag();
        responseHeaders.put(Headers.ETAG, eTag.toString());
        if (ETagUtils.handleIfNoneMatch(exchange, eTag, true)) {
            final ByteBuffer entityBody = bodyToSend.getBody();
            responseHeaders.put(Headers.CONTENT_TYPE, "application/javascript");
            exchange.getResponseSender().send(entityBody);
        } else {
            exchange.setResponseCode(StatusCodes.NOT_MODIFIED);
            exchange.endExchange();
        }
    }
}