All Downloads are FREE. Search and download functionalities are using the official Maven repository.

restx.ResourcesRoute Maven / Gradle / Ivy

There is a newer version: 1.2.0-rc2
Show newest version
package restx;

import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.Resources;
import restx.common.MoreResources;
import restx.http.HTTP;
import restx.http.HttpStatus;

import java.io.IOException;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Resources route allows to serves files from the classpath.
 *
 * Example:
 * 
new ResourcesRoute("myResources", "web", "static")
* We will consider every urls matching /web/* will serve files into 'static' classpath's directory * For instance, /web/foo/bar.json URL will serve classpath:static/foo/bar.json */ public class ResourcesRoute implements RestxRoute, RestxHandler { /** * Resource name, for toString only. */ private final String name; /** * Base restx path from which resources should be served. * Sanitized will always a leading and trailing slash. */ private final String baseRestPath; /** * Base resource name path from which resources should be located in classpath. * Sanitized with no leading slash, dots replaced with slashes and always a trailing slash. */ private final String baseResourcePath; private final ImmutableMap aliases; private final ImmutableList cachedResourcePolicies; public static class ResourceInfo { final String contentType; final String path; public ResourceInfo(String contentType, String path) { this.contentType = contentType; this.path = path; } public String getContentType() { return contentType; } public String getPath() { return path; } } public static class CachedResourcePolicy { final Predicate matcher; final String cacheValue; public CachedResourcePolicy(Predicate matcher, String cacheValue) { this.matcher = matcher; this.cacheValue = cacheValue; } public boolean matches(String contentType, String path) { return matcher.apply(new ResourceInfo(contentType, path)); } public String getCacheValue() { return cacheValue; } } public ResourcesRoute(String name, String baseRestPath, String baseResourcePath) { this(name, baseRestPath, baseResourcePath, ImmutableMap.of()); } public ResourcesRoute(String name, String baseRestPath, String baseResourcePath, ImmutableMap aliases) { this(name, baseRestPath, baseResourcePath, aliases, Collections.emptyList()); } public ResourcesRoute(String name, String baseRestPath, String baseResourcePath, ImmutableMap aliases, List cachedResourcePolicies) { this.name = checkNotNull(name); this.baseRestPath = ("/" + checkNotNull(baseRestPath) + "/").replaceAll("/+", "/"); this.baseResourcePath = this.ensureBaseResourcePathValid(baseResourcePath); this.aliases = checkNotNull(aliases); this.cachedResourcePolicies = ImmutableList.copyOf(cachedResourcePolicies); } protected String ensureBaseResourcePathValid(String baseResourcePath) { String escapedBaseResourcePath = checkNotNull(baseResourcePath) .replace('.', '/') .replaceAll("^/", "") .replaceAll("/$", "") + "/"; if("/".equals(escapedBaseResourcePath)){ throw new IllegalArgumentException("Please, avoid using '/' as ResourcesRoute's baseResourcePath as it represents serious security flaws (people will be able to read your classpath configuration files)"); } return escapedBaseResourcePath; } @Override public Optional match(RestxRequest req) { if (req.getHttpMethod().equals("GET") && req.getRestxPath().startsWith(baseRestPath)) { return Optional.of(new RestxHandlerMatch( new StdRestxRequestMatch(baseRestPath + "*", req.getRestxPath()), this)); } else { return Optional.absent(); } } @Override public void handle(RestxRequestMatch match, RestxRequest req, RestxResponse resp, RestxContext ctx) throws IOException { String relativePath = this.requestRelativePath(req); relativePath = Optional.fromNullable(aliases.get(relativePath)).or(relativePath); try { URL resource = MoreResources.getResource( baseResourcePath + relativePath, RestxContext.Modes.DEV.equals(ctx.getMode()) || RestxContext.Modes.TEST.equals(ctx.getMode()) || RestxContext.Modes.INFINIREST.equals(ctx.getMode()) ); serveCacheableResource(resp, resource, relativePath); } catch (IllegalArgumentException e) { notFound(resp, relativePath); } } protected String requestRelativePath(RestxRequest req) { return req.getRestxPath().substring(baseRestPath.length()); } protected void serveCacheableResource(RestxResponse resp, URL resource, String relativePath) throws IOException { String contentType = HTTP.getContentTypeFromExtension(relativePath).or("application/octet-stream"); ImmutableMap.Builder headers = ImmutableMap.builder(); Optional cachedResourcePolicy = cachePolicyMatching(contentType, relativePath); if(cachedResourcePolicy.isPresent()) { headers.put("Cache-Control", cachedResourcePolicy.get().getCacheValue()); } serveResource(resp, resource, contentType, headers.build()); } protected void serveResource(RestxResponse resp, URL resource, String contentType) throws IOException { serveResource(resp, resource, contentType, ImmutableMap.of()); } protected void serveResource(RestxResponse resp, URL resource, String contentType, Map headers) throws IOException { resp.setLogLevel(RestxLogLevel.QUIET); resp.setStatus(HttpStatus.OK); for(Map.Entry headerEntry: headers.entrySet()) { resp.setHeader(headerEntry.getKey(), headerEntry.getValue()); } resp.setContentType(contentType); Resources.asByteSource(resource).copyTo(resp.getOutputStream()); } protected Optional cachePolicyMatching(String contentType, String path) { for(CachedResourcePolicy cachedResourcePolicy : cachedResourcePolicies){ if(cachedResourcePolicy.matches(contentType, path)){ return Optional.of(cachedResourcePolicy); } } return Optional.absent(); } protected void notFound(RestxResponse resp, String relativePath) throws IOException { resp.setStatus(HttpStatus.NOT_FOUND); resp.setContentType("text/plain"); resp.getWriter().println("Resource route matched '" + this + "', but resource " + relativePath + " not found in " + baseResourcePath + "."); } public String getName() { return name; } public String getBaseRestPath() { return baseRestPath; } public String getBaseResourcePath() { return baseResourcePath; } @Override public String toString() { return "GET " + baseRestPath + "* => " + name; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy