| /******************************************************************************* |
| * Copyright (c) 2009, Cloudsmith Inc and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Cloudsmith Inc. - this copy and adaption to abstract servlet |
| *******************************************************************************/ |
| package org.eclipse.equinox.p2.testserver.servlets; |
| |
| import java.io.BufferedReader; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.PrintWriter; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.net.URL; |
| import java.net.URLConnection; |
| import java.text.DateFormat; |
| import java.text.SimpleDateFormat; |
| import java.util.Date; |
| import java.util.Locale; |
| import java.util.TimeZone; |
| import javax.servlet.ServletException; |
| import javax.servlet.ServletOutputStream; |
| import javax.servlet.http.HttpServlet; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| import org.eclipse.equinox.p2.testserver.Activator; |
| import org.eclipse.equinox.p2.testserver.HttpConstants; |
| import org.eclipse.equinox.p2.testserver.MimeLookup; |
| import org.eclipse.equinox.p2.testserver.SecureAction; |
| import org.osgi.service.http.HttpService; |
| |
| public class BasicResourceDelivery extends HttpServlet { |
| private static final long serialVersionUID = 1L; |
| // private static final String CHARSET = "utf-8"; //$NON-NLS-1$ |
| private SecureAction secureAction; |
| private String alias; |
| private URI path; |
| protected static final String defaultMimeType = "application/octet-stream"; //$NON-NLS-1$ |
| |
| /** |
| * Delivers resources from the bundle, or from an absolute URI |
| */ |
| public BasicResourceDelivery(String theAlias, URI thePath) { |
| secureAction = new SecureAction(); |
| alias = theAlias; |
| path = thePath; |
| } |
| |
| protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { |
| URI resource = null; |
| try { |
| resource = getFilename(request.getRequestURI()); |
| } catch (URISyntaxException e) { |
| /* ignore - just leave resource == null */ |
| } |
| |
| URL url = null; |
| if (resource != null) { |
| if (!resource.isAbsolute()) |
| url = getServletContext().getResource(resource.getPath()); |
| else |
| url = resource.toURL(); |
| } |
| |
| if (url == null) { |
| fileNotFound(resource, request, response); |
| return; |
| } |
| |
| // process cache control |
| URLConnection conn = secureAction.openURL(url); |
| |
| long modifiedSince = request.getDateHeader("If-Modified-Since"); //$NON-NLS-1$ |
| if (modifiedSince >= 0) { |
| long modified = getLastModified(conn); |
| if ((modified > 0) && (modifiedSince >= modified)) { |
| response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); |
| return; |
| } |
| } |
| InputStream in; |
| try { |
| in = conn.getInputStream(); |
| } catch (IOException ex) { |
| fileNotFound(resource, request, response); |
| return; |
| } |
| try { |
| // always set the default charset |
| // not in Servlet 2.1 // response.setCharacterEncoding(CHARSET); |
| deliver(conn, in, resource.toString(), request, response); // TODO: modify to resource==URI ? |
| } finally { |
| in.close(); |
| } |
| } |
| |
| protected void deliver(URLConnection conn, InputStream in, String filename, HttpServletRequest request, HttpServletResponse response) throws IOException { |
| this.doDeliver(conn, in, filename, request, response); |
| } |
| |
| /** |
| * Default method delivering content from a file. Subclasses should override this method |
| * to deliver "broken content" (e.g. truncated, mismatched content length etc.). |
| * This default method performs the same delivery as the default "register resource" in the |
| * http service. |
| * @param conn - The URLConnection to the resource |
| * @param in - InputStream to read from |
| * @param filename - the filename being read |
| * @param request - the servlet request |
| * @param response - the servlet response |
| * @throws IOException - on errors |
| */ |
| protected void doDeliver(URLConnection conn, InputStream in, String filename, HttpServletRequest request, HttpServletResponse response) throws IOException { |
| // set when the resource was modified |
| addDateHeader(response, HttpConstants.LAST_MODIFIED, getLastModified(conn)); |
| int statusCode = HttpHeaderToStatus(conn.getHeaderField(0)); |
| |
| response.setStatus(statusCode != -1 ? HttpServletResponse.SC_OK : statusCode); |
| |
| int contentlength = getContentLength(conn); |
| if (contentlength >= 0) { |
| response.setContentLength(contentlength); |
| |
| String mimeType = computeMimeType(filename, conn); |
| response.setContentType(mimeType); |
| |
| // We want to use a writer if we are sending text |
| if (mimeType.startsWith("text/")) //$NON-NLS-1$ |
| { |
| PrintWriter writer = response.getWriter(); |
| |
| writer.flush(); /* write the headers and unbuffer the output */ |
| |
| BufferedReader reader = new BufferedReader(new InputStreamReader(in)); |
| |
| char buffer[] = new char[4096]; |
| int read; |
| while ((read = reader.read(buffer, 0, buffer.length)) != -1) { |
| writer.write(buffer, 0, read); |
| } |
| } else { |
| ServletOutputStream out = response.getOutputStream(); |
| |
| out.flush(); /* write the headers and unbuffer the output */ |
| |
| byte buffer[] = new byte[4096]; |
| int read; |
| while ((read = in.read(buffer, 0, buffer.length)) != -1) { |
| out.write(buffer, 0, read); |
| out.flush(); |
| } |
| } |
| } |
| } |
| |
| protected void fileNotFound(URI file, HttpServletRequest request, HttpServletResponse response) throws IOException { |
| response.sendError(HttpServletResponse.SC_NOT_FOUND, (file == null ? "<no file>" : file.toString()) + " not found"); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| protected void doHead(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { |
| URI resource = null; |
| try { |
| resource = getFilename(request.getRequestURI()); |
| } catch (URISyntaxException e) { |
| /* ignore - just leave resource == null */ |
| } |
| |
| URL url = null; |
| if (resource != null) { |
| if (!resource.isAbsolute()) |
| url = getServletContext().getResource(resource.getPath()); |
| else |
| url = resource.toURL(); |
| } |
| |
| if (url == null) { |
| fileNotFound(resource, request, response); |
| return; |
| } |
| URLConnection conn = secureAction.openURL(url); |
| // always set default charset |
| // Not in Servlet 2.1 // response.setCharacterEncoding(CHARSET); |
| doDeliverHead(resource.toString(), conn, request, response); // TODO: change API to use URI? |
| } |
| |
| protected void deliverHead(String filename, URLConnection conn, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { |
| this.doDeliverHead(filename, conn, request, response); |
| } |
| |
| protected void doDeliverHead(String filename, URLConnection conn, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { |
| int contentlength = getContentLength(conn); |
| int statusCode = HttpHeaderToStatus(conn.getHeaderField(0)); |
| // set status = ok if there was no http header... |
| response.setStatus(statusCode == -1 ? HttpServletResponse.SC_OK : statusCode); |
| if (contentlength >= 0) { |
| response.setContentLength(contentlength); |
| |
| String mimeType = computeMimeType(filename, conn); |
| response.setContentType(mimeType); |
| long modified = getLastModified(conn); |
| addDateHeader(response, HttpConstants.LAST_MODIFIED, modified); |
| } else { |
| super.doHead(request, response); |
| } |
| |
| } |
| |
| public int HttpHeaderToStatus(String httpHeader) { |
| if (httpHeader == null) |
| return -1; |
| try { |
| return Integer.valueOf((httpHeader.substring(9, 12))).intValue(); |
| } catch (IndexOutOfBoundsException e) { |
| return -1; |
| } catch (NumberFormatException e) { |
| return -1; |
| } |
| } |
| |
| /** |
| * Override to lie about last modified |
| * @param conn |
| * @return time in ms |
| */ |
| protected long getLastModified(URLConnection conn) { |
| return conn.getLastModified(); |
| } |
| |
| /** |
| * Override to lie about content length |
| * @param conn |
| * @return length in bytes |
| */ |
| protected int getContentLength(URLConnection conn) { |
| return conn.getContentLength(); |
| } |
| |
| protected URI getFilename(String filename) throws URISyntaxException { |
| //If the requested URI is equal to the Registeration's alias, send the file |
| //corresponding to the alias. Otherwise, we have request for a file in an |
| //registered directory (the file was not directly registered itself). |
| if (filename.equals(alias)) { |
| filename = path.getPath(); |
| } else { |
| // The file we re looking for is the registered resource (alias) + the rest of the |
| // filename that is not part of the registered resource. For example, if we export |
| // /a to /tmp and we have a request for /a/b/foo.txt, then /tmp is our directory |
| // (file.toString()) and /b/foo.txt is the rest. |
| // The result is that we open the file /tmp/b/foo.txt. |
| |
| int aliaslen = alias.length(); |
| int pathlen = path.getPath().length(); |
| |
| if (pathlen == 1) /* path == "/" */ |
| { |
| if (aliaslen > 1) /* alias != "/" */ |
| { |
| filename = filename.substring(aliaslen); |
| } |
| } else /* path != "/" */ |
| { |
| StringBuffer buf = new StringBuffer(aliaslen + pathlen); |
| buf.append(path.getPath()); |
| |
| if (aliaslen == 1) /* alias == "/" */ |
| { |
| buf.append(filename); |
| } else /* alias != "/" */ |
| { |
| buf.append(filename.substring(aliaslen)); |
| } |
| |
| filename = buf.toString(); |
| } |
| } |
| return new URI(path.getScheme(), path.getUserInfo(), path.getHost(), path.getPort(), filename, path.getQuery(), path.getFragment()); |
| // return (filename); |
| } |
| |
| /** |
| * This method returns the correct MIME type of a given URI by first checking |
| * the HttpContext::getMimeType and, if null, checking the httpservice's MIMETypes table. |
| * return mimetype with charset=utf-8 for all text/... types |
| */ |
| protected String computeMimeType(String name, URLConnection conn) { |
| String mimeType = computeMimeType2(name, conn); |
| if (mimeType.startsWith("text/")) //$NON-NLS-1$ |
| return mimeType + "; charset=utf-8"; //$NON-NLS-1$ |
| return mimeType; |
| } |
| |
| protected String computeMimeType2(String name, URLConnection conn) { |
| // use type set in connection if it is available |
| String mimeType = conn.getContentType(); |
| if (mimeType != null) { |
| return (mimeType); |
| } |
| // use type from context |
| mimeType = getServletContext().getMimeType(name); |
| if (mimeType != null) { |
| return (mimeType); |
| } |
| // try the "extras" |
| return MimeLookup.getMimeType(name); |
| } |
| |
| public HttpService getHttpService() { |
| return Activator.getInstance().getHttp(); |
| } |
| |
| public static final DateFormat PATTERN_RFC1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); //$NON-NLS-1$ |
| |
| /** |
| * Support setting date header - in servlet > 2.1 it is possible to call this method directly on the HttpResponse. |
| * @param response |
| * @param name |
| * @param timestamp |
| */ |
| public void addDateHeader(HttpServletResponse response, String name, long timestamp) { |
| DateFormat df = PATTERN_RFC1123; |
| // must always be GMT |
| df.setTimeZone(TimeZone.getTimeZone("GMT")); //$NON-NLS-1$ |
| response.setHeader(name, df.format(new Date(timestamp))); |
| |
| } |
| |
| } |