PageRenderTime 33ms CodeModel.GetById 1ms app.highlight 27ms RepoModel.GetById 1ms app.codeStats 0ms

/src/StaticServlet.java

http://github.com/zefhemel/adia
Java | 225 lines | 188 code | 33 blank | 4 comment | 30 complexity | 4fff38c220db2ec00a1ad666440bea8e MD5 | raw file
  1import java.io.File;
  2import java.io.IOException;
  3import java.io.InputStream;
  4import java.io.OutputStream;
  5import java.net.JarURLConnection;
  6import java.net.MalformedURLException;
  7import java.net.URL;
  8import java.util.zip.GZIPOutputStream;
  9import java.util.zip.ZipEntry;
 10
 11import javax.servlet.ServletException;
 12import javax.servlet.http.HttpServlet;
 13import javax.servlet.http.HttpServletRequest;
 14import javax.servlet.http.HttpServletResponse;
 15
 16@SuppressWarnings("serial")
 17public class StaticServlet extends HttpServlet {
 18	
 19	public static interface LookupResult {
 20		public void respondGet(HttpServletResponse resp) throws IOException;
 21		public void respondHead(HttpServletResponse resp);
 22		public long getLastModified();
 23	}
 24	
 25	public static class Error implements LookupResult {
 26		protected final int statusCode;
 27		protected final String message;
 28
 29		public Error(int statusCode, String message) {
 30			this.statusCode = statusCode;
 31			this.message = message;
 32		}
 33
 34		public long getLastModified() {
 35			return -1;
 36		}
 37
 38		public void respondGet(HttpServletResponse resp) throws IOException {
 39			resp.sendError(statusCode,message);
 40		}
 41
 42		public void respondHead(HttpServletResponse resp) {
 43			throw new UnsupportedOperationException();
 44		}
 45	}
 46	
 47	public static class StaticFile implements LookupResult {
 48		protected final long lastModified;
 49		protected final String mimeType;
 50		protected final int contentLength;
 51		protected final boolean acceptsDeflate;
 52		protected final URL url;
 53
 54		public StaticFile(long lastModified, String mimeType, int contentLength, boolean acceptsDeflate, URL url) {
 55			this.lastModified = lastModified;
 56			this.mimeType = mimeType;
 57			this.contentLength = contentLength;
 58			this.acceptsDeflate = acceptsDeflate;
 59			this.url = url;
 60		}
 61
 62		public long getLastModified() {
 63			return lastModified;
 64		}
 65
 66		protected boolean willDeflate() {
 67			return acceptsDeflate && deflatable(mimeType) && contentLength >= deflateThreshold;
 68		}
 69
 70		protected void setHeaders(HttpServletResponse resp) {
 71			resp.setStatus(HttpServletResponse.SC_OK);
 72			resp.setContentType(mimeType);
 73			if(contentLength >= 0 && !willDeflate())
 74				resp.setContentLength(contentLength);
 75		}
 76
 77		public void respondGet(HttpServletResponse resp) throws IOException {
 78			setHeaders(resp);
 79			final OutputStream os;
 80			if(willDeflate()) {
 81				resp.setHeader("Content-Encoding", "gzip");
 82				os = new GZIPOutputStream(resp.getOutputStream(), bufferSize);
 83			} else
 84				os = resp.getOutputStream();
 85			transferStreams(url.openStream(),os);
 86		}
 87
 88		public void respondHead(HttpServletResponse resp) {
 89			if(willDeflate())
 90				throw new UnsupportedOperationException();
 91			setHeaders(resp);
 92		}
 93	}
 94
 95	@Override
 96	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
 97		lookup(req).respondGet(resp);
 98	}
 99	
100	@Override
101	protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException {
102		doGet(req, resp);
103	}
104	
105	@Override
106	protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
107		try {
108			lookup(req).respondHead(resp);
109		} catch(UnsupportedOperationException e) {
110			super.doHead(req, resp);
111		}
112	}
113	
114	@Override
115	protected long getLastModified(HttpServletRequest req) {
116		return lookup(req).getLastModified();
117	}
118	
119	protected LookupResult lookup(HttpServletRequest req) {
120		LookupResult r = (LookupResult)req.getAttribute("lookupResult");
121		if(r == null) {
122			r = lookupNoCache(req);
123			req.setAttribute("lookupResult", r);
124		}
125		return r;
126	}
127	
128	protected LookupResult lookupNoCache(HttpServletRequest req) {
129		final String path = getPath(req);
130		if(isForbidden(path))
131			return new Error(HttpServletResponse.SC_FORBIDDEN, "Forbidden");
132
133		final URL url;
134		try {
135			url = getServletContext().getResource(path);
136		} catch(MalformedURLException e) {
137			return new Error(HttpServletResponse.SC_BAD_REQUEST, "Malformed path");
138		}
139		if(url == null)
140			return new Error(HttpServletResponse.SC_NOT_FOUND, "Not found");
141
142		final String mimeType = getMimeType(path);
143
144		final String realpath = getServletContext().getRealPath(path);
145		if(realpath != null) {
146			// Try as an ordinary file
147			File f = new File(realpath);
148			if(!f.isFile())
149				return new Error(HttpServletResponse.SC_FORBIDDEN, "Forbidden");
150			else
151				return new StaticFile(f.lastModified(),mimeType,(int)f.length(),acceptsDeflate(req),url);
152		} else {
153			try {
154				// Try as a JAR Entry
155				final ZipEntry ze = ((JarURLConnection)url.openConnection()).getJarEntry();
156				if (ze != null) {
157					if(ze.isDirectory())
158						return new Error(HttpServletResponse.SC_FORBIDDEN, "Forbidden");
159					else
160						return new StaticFile(ze.getTime(),mimeType,(int)ze.getSize(),acceptsDeflate(req),url);
161				} else
162					// Unexpected?
163					return new StaticFile(-1,mimeType,-1,acceptsDeflate(req),url);
164			} catch(ClassCastException e) {
165				// Unknown resource type
166				return new StaticFile(-1,mimeType,-1,acceptsDeflate(req),url);
167			} catch (IOException e) {
168				return new Error(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Internal server error");
169			}
170		}
171	}
172	
173	protected String getPath(HttpServletRequest req) {
174		String servletPath = req.getServletPath();
175		String pathInfo = coalesce(req.getPathInfo(), "");
176		return servletPath + pathInfo;
177	}
178	
179	protected boolean isForbidden(String path) {
180		String lpath = path.toLowerCase();
181		return lpath.startsWith("/web-inf/") || lpath.startsWith("/meta-inf/");
182	}
183
184        public static <T> T coalesce(T...ts) {
185            for(T t: ts) {
186                if(t != null) {
187                    return t;
188                }
189            }
190            return null;
191        }
192		
193	protected String getMimeType(String path) {
194		return coalesce(getServletContext().getMimeType(path),"application/octet-stream");
195	}
196	
197	protected static boolean acceptsDeflate(HttpServletRequest req) {
198		final String ae = req.getHeader("Accept-Encoding");
199		return ae != null && ae.contains("gzip");
200	}
201	
202	protected static boolean deflatable(String mimetype) {
203		return mimetype.startsWith("text/")
204			|| mimetype.equals("application/postscript")
205			|| mimetype.startsWith("application/ms")
206			|| mimetype.startsWith("application/vnd")
207			|| mimetype.endsWith("xml");
208	}
209	
210	protected static final int deflateThreshold = 4*1024;
211	
212	protected static final int bufferSize = 4*1024;
213	
214	protected static void transferStreams(InputStream is, OutputStream os) throws IOException {
215		try {
216			byte[] buf = new byte[bufferSize];
217			int bytesRead;
218			while ((bytesRead = is.read(buf)) != -1)
219				os.write(buf, 0, bytesRead);
220		} finally {
221			is.close();
222			os.close();
223		}
224	}
225}