Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package com.nefariouszhen.dropwizard.assets;
import com.google.common.base.CharMatcher;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheBuilderSpec;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.Weigher;
import com.google.common.collect.ImmutableList;
import com.google.common.hash.Hashing;
import com.google.common.io.Files;
import com.google.common.io.Resources;
import com.google.common.net.HttpHeaders;
import com.google.common.net.MediaType;
import io.dropwizard.servlets.assets.ByteRange;
import io.dropwizard.servlets.assets.ResourceURL;
import org.eclipse.jetty.http.MimeTypes;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
/**
* Servlet responsible for serving assets to the caller. This is basically completely stolen from
* {@link io.dropwizard.servlets.assets.AssetServlet} with the exception of allowing for override options.
*
* @see io.dropwizard.servlets.assets.AssetServlet
*/
public class AssetServlet extends HttpServlet {
private static final long serialVersionUID = 6393345594784987908L;
private static final MediaType DEFAULT_MEDIA_TYPE = MediaType.HTML_UTF_8;
private static final CharMatcher SLASHES = CharMatcher.is('/');
private final transient AssetLoader loader;
private final transient LoadingCache cache;
private final transient MimeTypes mimeTypes;
private Charset defaultCharset;
/**
* Creates a new {@code AssetServlet} that serves static assets loaded from {@code resourceURL}
* (typically a file: or jar: URL). The assets are served at URIs rooted at {@code uriPath}. For
* example, given a {@code resourceURL} of {@code "file:/data/assets"} and a {@code uriPath} of
* {@code "/js"}, an {@code AssetServlet} would serve the contents of {@code
* /data/assets/example.js} in response to a request for {@code /js/example.js}. If a directory
* is requested and {@code indexFile} is defined, then {@code AssetServlet} will attempt to
* serve a file with that name in that directory. If a directory is requested and {@code
* indexFile} is null, it will serve a 404.
*
* @param resourcePath the base URL from which assets are loaded
* @param uriPath the URI path fragment in which all requests are rooted
* @param indexFile the filename to use when directories are requested, or null to serve no
* indexes
* @param defaultCharset the default character set
* @param spec the CacheBuilderSpec to use
* @param overrides the path overrides
* @param mimeTypes the mimeType overrides
*/
public AssetServlet(String resourcePath,
String uriPath,
String indexFile,
Charset defaultCharset,
CacheBuilderSpec spec,
Iterable> overrides,
Iterable> mimeTypes) {
this.defaultCharset = defaultCharset;
this.loader = new AssetLoader(resourcePath, uriPath, indexFile, overrides);
this.cache = CacheBuilder.from(spec).weigher(new AssetSizeWeigher()).build(loader);
this.mimeTypes = new MimeTypes();
this.setMimeTypes(mimeTypes);
}
public void setMimeTypes(Iterable> mimeTypes) {
for (Map.Entry mime : mimeTypes) {
this.mimeTypes.addMimeMapping(mime.getKey(), mime.getValue());
}
}
public MimeTypes getMimeTypes() {
return mimeTypes;
}
public void setDefaultCharset(Charset defaultCharset) {
this.defaultCharset = defaultCharset;
}
public Charset getDefaultCharset() {
return this.defaultCharset;
}
public URL getResourceURL() {
return Resources.getResource(loader.resourcePath);
}
public String getUriPath() {
return loader.uriPath;
}
public String getIndexFile() {
return loader.indexFilename;
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
final StringBuilder builder = new StringBuilder(req.getServletPath());
if (req.getPathInfo() != null) {
builder.append(req.getPathInfo());
}
final Asset cachedAsset = cache.getUnchecked(builder.toString());
if (cachedAsset == null) {
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
if (isCachedClientSide(req, cachedAsset)) {
resp.sendError(HttpServletResponse.SC_NOT_MODIFIED);
return;
}
final String rangeHeader = req.getHeader(HttpHeaders.RANGE);
final int resourceLength = cachedAsset.getResource().length;
ImmutableList ranges = ImmutableList.of();
boolean usingRanges = false;
// Support for HTTP Byte Ranges
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
if (rangeHeader != null) {
final String ifRange = req.getHeader(HttpHeaders.IF_RANGE);
if (ifRange == null || cachedAsset.getETag().equals(ifRange)) {
try {
ranges = parseRangeHeader(rangeHeader, resourceLength);
} catch (NumberFormatException e) {
resp.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
return;
}
if (ranges.isEmpty()) {
resp.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
return;
}
resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
usingRanges = true;
resp.addHeader(HttpHeaders.CONTENT_RANGE, "bytes "
+ Joiner.on(",").join(ranges) + "/" + resourceLength);
}
}
resp.setDateHeader(HttpHeaders.LAST_MODIFIED, cachedAsset.getLastModifiedTime());
resp.setHeader(HttpHeaders.ETAG, cachedAsset.getETag());
String mimeTypeOfExtension = mimeTypes.getMimeByExtension(req.getRequestURI());
if (mimeTypeOfExtension == null) {
mimeTypeOfExtension = req.getServletContext().getMimeType(req.getRequestURI());
}
MediaType mediaType = DEFAULT_MEDIA_TYPE;
if (mimeTypeOfExtension != null) {
try {
mediaType = MediaType.parse(mimeTypeOfExtension);
if (defaultCharset != null && mediaType.is(MediaType.ANY_TEXT_TYPE)) {
mediaType = mediaType.withCharset(defaultCharset);
}
} catch (IllegalArgumentException ignore) {
}
}
if (mediaType.is(MediaType.ANY_VIDEO_TYPE)
|| mediaType.is(MediaType.ANY_AUDIO_TYPE) || usingRanges) {
resp.addHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
}
resp.setContentType(mediaType.type() + '/' + mediaType.subtype());
if (mediaType.charset().isPresent()) {
resp.setCharacterEncoding(mediaType.charset().get().toString());
}
try (ServletOutputStream output = resp.getOutputStream()) {
if (usingRanges) {
for (final ByteRange range : ranges) {
output.write(cachedAsset.getResource(), range.getStart(),
range.getEnd() - range.getStart() + 1);
}
} else {
output.write(cachedAsset.getResource());
}
}
} catch (RuntimeException ignored) {
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
private boolean isCachedClientSide(HttpServletRequest req, Asset cachedAsset) {
return cachedAsset.getETag().equals(req.getHeader(HttpHeaders.IF_NONE_MATCH)) ||
(req.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE) >= cachedAsset.getLastModifiedTime());
}
/**
* Parses a given Range header for one or more byte ranges.
*
* @param rangeHeader Range header to parse
* @param resourceLength Length of the resource in bytes
* @return List of parsed ranges
*/
private ImmutableList parseRangeHeader(final String rangeHeader,
final int resourceLength) {
final ImmutableList.Builder builder = ImmutableList.builder();
if (rangeHeader.contains("=")) {
final String[] parts = rangeHeader.split("=");
if (parts.length > 1) {
final List ranges = Splitter.on(",").trimResults().splitToList(parts[1]);
for (final String range : ranges) {
builder.add(ByteRange.parse(range, resourceLength));
}
}
}
return builder.build();
}
private static class AssetLoader extends CacheLoader {
private final String resourcePath;
private final String uriPath;
private final String indexFilename;
private final Iterable> overrides;
private AssetLoader(String resourcePath, String uriPath, String indexFilename, Iterable> overrides) {
final String trimmedPath = SLASHES.trimFrom(resourcePath);
this.resourcePath = trimmedPath.isEmpty() ? trimmedPath : trimmedPath + '/';
final String trimmedUri = SLASHES.trimTrailingFrom(uriPath);
this.uriPath = trimmedUri.isEmpty() ? "/" : trimmedUri;
this.indexFilename = indexFilename;
this.overrides = overrides;
}
@Override
public Asset load(String key) throws Exception {
Preconditions.checkArgument(key.startsWith(uriPath));
Asset asset = loadOverride(key);
if (asset != null) {
return asset;
}
final String requestedResourcePath = SLASHES.trimFrom(key.substring(uriPath.length()));
final String absoluteRequestedResourcePath = SLASHES.trimFrom(this.resourcePath + requestedResourcePath);
URL requestedResourceURL = Resources.getResource(absoluteRequestedResourcePath);
if (ResourceURL.isDirectory(requestedResourceURL)) {
if (indexFilename != null) {
requestedResourceURL = Resources.getResource(absoluteRequestedResourcePath + '/' + indexFilename);
} else {
// directory requested but no index file defined
return null;
}
}
long lastModified = ResourceURL.getLastModified(requestedResourceURL);
if (lastModified < 1) {
// Something went wrong trying to get the last modified time: just use the current time
lastModified = System.currentTimeMillis();
}
// zero out the millis since the date we get back from If-Modified-Since will not have them
lastModified = (lastModified / 1000) * 1000;
return new StaticAsset(Resources.toByteArray(requestedResourceURL), lastModified);
}
private Asset loadOverride(String key) throws Exception {
// TODO: Support prefix matches only for directories
for (Map.Entry override : overrides) {
File file = null;
if (override.getKey().equals(key)) {
// We have an exact match
file = new File(override.getValue());
} else if (key.startsWith(override.getKey())) {
// This resource is in a mapped subdirectory
file = new File(override.getValue(), key.substring(override.getKey().length()));
}
if (file == null || !file.exists()) {
continue;
}
if (file.isDirectory()) {
file = new File(file, indexFilename);
}
if (file.exists()) {
return new FileSystemAsset(file);
}
}
return null;
}
}
private static interface Asset {
byte[] getResource();
String getETag();
long getLastModifiedTime();
}
/**
* Weigh an asset according to the number of bytes it contains.
*/
private static final class AssetSizeWeigher implements Weigher {
@Override
public int weigh(String key, Asset asset) {
return asset.getResource().length;
}
}
/**
* An asset implementation backed by the file-system. If the backing file changes on disk, then this asset
* will automatically reload its contents from disk.
*/
private static class FileSystemAsset implements Asset {
private final File file;
private byte[] bytes;
private String eTag;
private long lastModifiedTime;
public FileSystemAsset(File file) {
this.file = file;
refresh();
}
@Override
public byte[] getResource() {
maybeRefresh();
return bytes;
}
@Override
public String getETag() {
maybeRefresh();
return eTag;
}
@Override
public long getLastModifiedTime() {
maybeRefresh();
return (lastModifiedTime / 1000) * 1000;
}
private synchronized void maybeRefresh() {
if (lastModifiedTime != file.lastModified()) {
refresh();
}
}
private synchronized void refresh() {
try {
byte[] newBytes = Files.toByteArray(file);
String newETag = Hashing.murmur3_128().hashBytes(newBytes).toString();
bytes = newBytes;
eTag = '"' + newETag + '"';
lastModifiedTime = file.lastModified();
} catch (IOException e) {
// Ignored, don't update anything
}
}
}
/**
* A static asset implementation. This implementation just encapsulates the raw bytes of an asset (presumably
* loaded from the classpath) and will never change.
*/
private static class StaticAsset implements Asset {
private final byte[] resource;
private final String eTag;
private final long lastModifiedTime;
private StaticAsset(byte[] resource, long lastModifiedTime) {
this.resource = resource;
this.eTag = Hashing.murmur3_128().hashBytes(resource).toString();
this.lastModifiedTime = lastModifiedTime;
}
public byte[] getResource() {
return resource;
}
public String getETag() {
return eTag;
}
public long getLastModifiedTime() {
return lastModifiedTime;
}
}
}