Java tutorial
// ~~~~~~~~~~~~~~~~~~~~~~~~~~ // //// /// /// /// ////// //// // //// //// /// //// //// // //// //// /// //// ///// // /// /// //// ///// // //// ////// // /// ///// // //// //// // //// ///// // //// //// ///////////// //// //// //////////// /// /// ///// ///// //// //// ///// ///// //// ///// // //// //// /// ///// // ///// ///// //////////// //// //// //// //// // The Web framework with class. // ~~~~~~~~~~~~~~~~~~~~~~~~~~ // // Copyright (c) 2013 Adam R. Nelson // // 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 com.sector91.wit.responders; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.zip.GZIPOutputStream; import org.simpleframework.http.Request; import org.simpleframework.http.Response; import org.simpleframework.http.Status; import com.esotericsoftware.minlog.Log; import com.google.common.base.Predicate; import com.google.common.cache.CacheBuilder; import com.google.common.io.ByteStreams; import com.google.common.net.HttpHeaders; import com.sector91.wit.Responder; import com.sector91.wit.http.CompressionDecider; import com.sector91.wit.http.ContentTypes; import com.sector91.wit.http.HttpException; import com.sector91.wit.params.One; import com.sector91.wit.resources.ResourceLoader; import com.sector91.wit.resources.ResourceNotFoundException; public class ResourceResponder implements Responder<One<String>> { private static final String TAG = ResourceResponder.class.getSimpleName(); public static final long DEFAULT_CACHE_CAPACITY = 256; private final long timestamp; final ClassLoader context; final String basePath; private final ResourceLoader<byte[]> loader; final CacheBuilder<? super String, ? super byte[]> cacheBuilder; Predicate<String> gzipIf = CompressionDecider.DEFAULT; public ResourceResponder(Class<?> context, String basePath) { this(context.getClassLoader(), basePath); } public ResourceResponder(Class<?> context, String basePath, CacheBuilder<? super String, ? super byte[]> cacheBuilder) { this(context.getClassLoader(), basePath, cacheBuilder); } public ResourceResponder(ClassLoader context, String basePath) { this(context, basePath, CacheBuilder.newBuilder().maximumSize(DEFAULT_CACHE_CAPACITY)); } public ResourceResponder(ClassLoader context, String basePath, CacheBuilder<? super String, ? super byte[]> cacheBuilder) { this.context = context; this.basePath = basePath; this.timestamp = (System.currentTimeMillis() / 1000) * 1000; // Floor to seconds. this.cacheBuilder = cacheBuilder; this.loader = new GZippingResourceLoader(); } public ResourceResponder gzipIf(Predicate<String> decider) { gzipIf = decider; return this; } @Override public void respond(One<String> param, Request request, Response response) throws IOException, HttpException { Log.debug(TAG, "Starting ResourceResponder for path " + basePath); // Read basic information from the request. final String path = param.first(); Log.trace(TAG, "Responding with resource: " + path); final String mimetype = ContentTypes.forPath(path); final boolean gzipSupported = request.getValue(HttpHeaders.ACCEPT_ENCODING).contains("gzip"); final boolean shouldCompressFile = gzipIf.apply(mimetype); // EDGE CASE: If we're caching a compressed version of the resource, but // the client doesn't support compression, then don't bother caching, and // just read the resource directly. if (!gzipSupported && shouldCompressFile) { Log.debug(TAG, "Resource '" + basePath + path + "' is cached in gzipped form, but client does" + " not support gzip. Skipping cache."); final InputStream stream = context.getResourceAsStream(basePath + path); if (stream == null) throw new HttpException(Status.NOT_FOUND); response.setContentType(mimetype); response.setDate(HttpHeaders.LAST_MODIFIED, timestamp); try (OutputStream out = response.getOutputStream()) { ByteStreams.copy(stream, out); } return; } // Retrieve the resource from the cache, loading it if necessary final byte[] data; try { data = loader.load(path); } catch (ResourceNotFoundException ex) { throw new HttpException(Status.NOT_FOUND); } // Return a 304 Not Modified if the client already has a cached copy. response.setDate(HttpHeaders.LAST_MODIFIED, timestamp); final long ifModifiedSince = request.getDate(HttpHeaders.IF_MODIFIED_SINCE); if (ifModifiedSince >= timestamp) { Log.trace(TAG, "Resource not modified: " + path); response.setStatus(Status.NOT_MODIFIED); response.getOutputStream().close(); return; } // Otherwise, write the response headers and data. response.setContentType(mimetype); if (shouldCompressFile) response.setValue(HttpHeaders.CONTENT_ENCODING, "gzip"); response.setContentLength(data.length); try (OutputStream out = response.getOutputStream()) { out.write(data); } } private class GZippingResourceLoader extends ResourceLoader<byte[]> { GZippingResourceLoader() { super(context, basePath, cacheBuilder); } @Override protected byte[] convert(String path, InputStream in) throws IOException { final String mimetype = ContentTypes.forPath(path); final boolean shouldCompress = gzipIf.apply(mimetype); if (shouldCompress) { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); try (final GZIPOutputStream gzip = new GZIPOutputStream(baos)) { ByteStreams.copy(in, gzip); } return baos.toByteArray(); } else return ByteStreams.toByteArray(in); } } }