Java tutorial
/* * 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(); } } }