Java tutorial
/* * Copyright (c) 2008-2016 Haulmont. * * 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.haulmont.cuba.web.controllers; import com.haulmont.cuba.core.global.FileTypesHelper; import org.apache.commons.io.IOUtils; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.mvc.LastModified; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.JarURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.zip.GZIPOutputStream; import java.util.zip.ZipEntry; @Controller @RequestMapping(value = "/static/**") public class StaticContentController implements LastModified { public interface LookupResult { void respondGet(HttpServletRequest req, HttpServletResponse resp) throws IOException; void respondHead(HttpServletRequest req, HttpServletResponse resp) throws IOException; long getLastModified(); } public static class Error implements LookupResult { protected final int statusCode; protected final String message; public Error(int statusCode, String message) { this.statusCode = statusCode; this.message = message; } @Override public long getLastModified() { return -1; } @Override public void respondGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.sendError(statusCode, message); } @Override public void respondHead(HttpServletRequest req, HttpServletResponse resp) throws IOException { throw new UnsupportedOperationException(); } } public static class StaticFile implements LookupResult { protected final long lastModified; protected final String mimeType; protected final int contentLength; protected final boolean acceptsDeflate; protected final URL url; public StaticFile(long lastModified, String mimeType, int contentLength, boolean acceptsDeflate, URL url) { this.lastModified = lastModified; this.mimeType = mimeType; this.contentLength = contentLength; this.acceptsDeflate = acceptsDeflate; this.url = url; } @Override public long getLastModified() { return lastModified; } protected boolean willDeflate() { return acceptsDeflate && deflatable(mimeType) && contentLength >= deflateThreshold; } protected void setHeaders(HttpServletResponse resp) { resp.setStatus(HttpServletResponse.SC_OK); resp.setContentType(mimeType); if (contentLength >= 0 && !willDeflate()) resp.setContentLength(contentLength); } @Override public void respondGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { setHeaders(resp); final OutputStream os; if (willDeflate()) { resp.setHeader("Content-Encoding", "gzip"); os = new GZIPOutputStream(resp.getOutputStream(), bufferSize); } else os = resp.getOutputStream(); InputStream is = url.openStream(); try { IOUtils.copy(is, os); } finally { is.close(); os.close(); } } @Override public void respondHead(HttpServletRequest req, HttpServletResponse resp) throws IOException { if (willDeflate()) throw new UnsupportedOperationException(); setHeaders(resp); } } @RequestMapping(method = RequestMethod.GET) public String handleGetRequest(HttpServletRequest request, HttpServletResponse response) throws IOException { lookup(request).respondGet(request, response); return null; } @RequestMapping(method = RequestMethod.POST) public String handlePostRequest(HttpServletRequest request, HttpServletResponse response) throws IOException { return handleGetRequest(request, response); } @RequestMapping(method = RequestMethod.HEAD) public String handleHeadRequest(HttpServletRequest request, HttpServletResponse response) throws IOException { try { lookup(request).respondHead(request, response); } catch (UnsupportedOperationException e) { response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); } return null; } @Override public long getLastModified(HttpServletRequest req) { return lookup(req).getLastModified(); } protected LookupResult lookup(HttpServletRequest req) { LookupResult r = (LookupResult) req.getAttribute("lookupResult"); if (r == null) { r = lookupNoCache(req); req.setAttribute("lookupResult", r); } return r; } protected LookupResult lookupNoCache(HttpServletRequest req) { final String path = getPath(req); if (isForbidden(path)) return new Error(HttpServletResponse.SC_FORBIDDEN, "Forbidden"); ServletContext context = req.getSession().getServletContext(); final URL url; try { url = context.getResource(path); } catch (MalformedURLException e) { return new Error(HttpServletResponse.SC_BAD_REQUEST, "Malformed path"); } if (url == null) return new Error(HttpServletResponse.SC_NOT_FOUND, "Not found"); final String mimeType = getMimeType(path); final String realpath = context.getRealPath(path); if (realpath != null) { // Try as an ordinary file File f = new File(realpath); if (!f.isFile()) return new Error(HttpServletResponse.SC_FORBIDDEN, "Forbidden"); else { return createLookupResult(req, f.lastModified(), mimeType, (int) f.length(), acceptsDeflate(req), url); } } else { try { // Try as a JAR Entry final ZipEntry ze = ((JarURLConnection) url.openConnection()).getJarEntry(); if (ze != null) { if (ze.isDirectory()) return new Error(HttpServletResponse.SC_FORBIDDEN, "Forbidden"); else return createLookupResult(req, ze.getTime(), mimeType, (int) ze.getSize(), acceptsDeflate(req), url); } else // Unexpected? return new StaticFile(-1, mimeType, -1, acceptsDeflate(req), url); } catch (ClassCastException e) { // Unknown resource type return createLookupResult(req, -1, mimeType, -1, acceptsDeflate(req), url); } catch (IOException e) { return new Error(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Internal server error"); } } } protected LookupResult createLookupResult(HttpServletRequest req, long lastModified, String mimeType, int contentLength, boolean acceptsDeflate, URL url) { return new StaticFile(lastModified, mimeType, contentLength, acceptsDeflate, url); } protected String getPath(HttpServletRequest req) { String path = ControllerUtils.getControllerPath(req); String pathInfo = coalesce(req.getPathInfo(), ""); return path + pathInfo; } protected boolean isForbidden(String path) { String lpath = path.toLowerCase(); return lpath.startsWith("/web-inf/") || lpath.startsWith("/meta-inf/"); } protected String getMimeType(String path) { return coalesce(FileTypesHelper.getMIMEType(path), "application/octet-stream"); } protected static boolean acceptsDeflate(HttpServletRequest req) { final String ae = req.getHeader("Accept-Encoding"); return ae != null && ae.contains("gzip"); } protected static boolean deflatable(String mimetype) { return mimetype.startsWith("text/") || mimetype.equals("application/postscript") || mimetype.startsWith("application/ms") || mimetype.startsWith("application/vnd") || mimetype.endsWith("xml"); } public static <T> T coalesce(T... ts) { for (T t : ts) if (t != null) return t; return null; } protected static final int deflateThreshold = 4 * 1024; protected static final int bufferSize = 4 * 1024; }