1.9.22 release.
[gae.git] / java / src / main / com / google / appengine / tools / development / LocalResourceFileServlet.java
blob5b9e4ca5f16102ea5fdc57b462335016947823f0
1 // Copyright 2008 Google Inc. All rights reserved.
3 package com.google.appengine.tools.development;
5 import com.google.apphosting.utils.config.AppEngineWebXml;
6 import com.google.apphosting.utils.config.WebXml;
8 import org.mortbay.jetty.handler.ContextHandler;
9 import org.mortbay.jetty.servlet.Context;
10 import org.mortbay.jetty.servlet.Dispatcher;
11 import org.mortbay.jetty.servlet.PathMap;
12 import org.mortbay.jetty.servlet.ServletHandler;
13 import org.mortbay.resource.Resource;
14 import org.mortbay.util.URIUtil;
16 import java.io.IOException;
17 import java.net.MalformedURLException;
18 import java.util.logging.Level;
19 import java.util.logging.Logger;
21 import javax.servlet.RequestDispatcher;
22 import javax.servlet.ServletException;
23 import javax.servlet.ServletContext;
24 import javax.servlet.http.HttpServlet;
25 import javax.servlet.http.HttpServletRequest;
26 import javax.servlet.http.HttpServletResponse;
28 /**
29 * {@code ResourceFileServlet} is a copy of {@code
30 * org.mortbay.jetty.servlet.DefaultServlet} that has been trimmed
31 * down to only support the subset of features that we want to take
32 * advantage of (e.g. no gzipping, no chunked encoding, no buffering,
33 * etc.). A number of Jetty-specific optimizations and assumptions
34 * have also been removed (e.g. use of custom header manipulation
35 * API's, use of {@code ByteArrayBuffer} instead of Strings, etc.).
37 * A few remaining Jetty-centric details remain, such as use of the
38 * {@link ContextHandler.SContext} class, and Jetty-specific request
39 * attributes, but these are specific cases where there is no
40 * servlet-engine-neutral API available. This class also uses Jetty's
41 * {@link Resource} class as a convenience, but could be converted to
42 * use {@link ServletContext#getResource(String)} instead.
45 public class LocalResourceFileServlet extends HttpServlet {
46 private static final Logger logger =
47 Logger.getLogger(LocalResourceFileServlet.class.getName());
49 private StaticFileUtils staticFileUtils;
50 private Resource resourceBase;
51 private String[] welcomeFiles;
52 private String resourceRoot;
54 /**
55 * Initialize the servlet by extracting some useful configuration
56 * data from the current {@link ServletContext}.
58 @Override
59 public void init() throws ServletException {
60 ContextHandler.SContext context = (ContextHandler.SContext) getServletContext();
61 staticFileUtils = new StaticFileUtils(context);
63 welcomeFiles = context.getContextHandler().getWelcomeFiles();
65 AppEngineWebXml appEngineWebXml = (AppEngineWebXml) getServletContext().getAttribute(
66 "com.google.appengine.tools.development.appEngineWebXml");
68 resourceRoot = appEngineWebXml.getPublicRoot();
69 try {
70 resourceBase = context.getContextHandler().getResource(URIUtil.SLASH + resourceRoot);
71 } catch (MalformedURLException ex) {
72 logger.log(Level.WARNING, "Could not initialize:", ex);
73 throw new ServletException(ex);
77 /**
78 * Retrieve the static resource file indicated.
80 @Override
81 protected void doGet(HttpServletRequest request, HttpServletResponse response)
82 throws ServletException, IOException {
83 String servletPath;
84 String pathInfo;
86 AppEngineWebXml appEngineWebXml = (AppEngineWebXml) getServletContext().getAttribute(
87 "com.google.appengine.tools.development.appEngineWebXml");
89 WebXml webXml = (WebXml) getServletContext().getAttribute(
90 "com.google.appengine.tools.development.webXml");
92 Boolean forwarded = (Boolean) request.getAttribute(Dispatcher.__FORWARD_JETTY);
93 if (forwarded == null) {
94 forwarded = Boolean.FALSE;
97 Boolean included = (Boolean) request.getAttribute(Dispatcher.__INCLUDE_JETTY);
98 if (included != null && included) {
99 servletPath = (String) request.getAttribute(Dispatcher.__INCLUDE_SERVLET_PATH);
100 pathInfo = (String) request.getAttribute(Dispatcher.__INCLUDE_PATH_INFO);
101 if (servletPath == null) {
102 servletPath = request.getServletPath();
103 pathInfo = request.getPathInfo();
105 } else {
106 included = Boolean.FALSE;
107 servletPath = request.getServletPath();
108 pathInfo = request.getPathInfo();
111 String pathInContext = URIUtil.addPaths(servletPath, pathInfo);
113 if (maybeServeWelcomeFile(pathInContext, included, request, response)) {
114 return;
117 Resource resource = null;
118 try {
119 resource = getResource(pathInContext);
121 if (resource != null && resource.isDirectory()) {
122 if (included ||
123 staticFileUtils.passConditionalHeaders(request, response, resource)) {
124 response.sendError(HttpServletResponse.SC_FORBIDDEN);
126 } else {
127 if (resource == null || !resource.exists()) {
128 logger.warning("No file found for: " + pathInContext);
129 response.sendError(HttpServletResponse.SC_NOT_FOUND);
130 } else {
131 boolean isStatic = appEngineWebXml.includesStatic(resourceRoot + pathInContext);
132 boolean isResource = appEngineWebXml.includesResource(
133 resourceRoot + pathInContext);
134 boolean usesRuntime = webXml.matches(pathInContext);
135 Boolean isWelcomeFile = (Boolean)
136 request.getAttribute("com.google.appengine.tools.development.isWelcomeFile");
137 if (isWelcomeFile == null) {
138 isWelcomeFile = false;
141 if (!isStatic && !usesRuntime && !(included || forwarded)) {
142 logger.warning("Can not serve " + pathInContext + " directly. " +
143 "You need to include it in <static-files> in your " +
144 "appengine-web.xml.");
145 response.sendError(HttpServletResponse.SC_NOT_FOUND);
146 return;
147 } else if (!isResource && !isWelcomeFile && (included || forwarded)) {
148 logger.warning("Could not serve " + pathInContext + " from a forward or " +
149 "include. You need to include it in <resource-files> in " +
150 "your appengine-web.xml.");
151 response.sendError(HttpServletResponse.SC_NOT_FOUND);
152 return;
154 if (included || staticFileUtils.passConditionalHeaders(request, response, resource)) {
155 staticFileUtils.sendData(request, response, included, resource);
159 } finally {
160 if (resource != null) {
161 resource.release();
166 @Override
167 protected void doPost(HttpServletRequest request, HttpServletResponse response)
168 throws ServletException, IOException {
169 doGet(request,response);
172 @Override
173 protected void doTrace(HttpServletRequest request, HttpServletResponse response)
174 throws ServletException, IOException {
175 response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
179 * Get Resource to serve.
180 * @param pathInContext The path to find a resource for.
181 * @return The resource to serve. Can be null.
183 private Resource getResource(String pathInContext) {
184 try {
185 if (resourceBase != null) {
186 return resourceBase.addPath(pathInContext);
188 } catch (IOException ex) {
189 logger.log(Level.WARNING, "Could not find: " + pathInContext, ex);
191 return null;
195 * Finds a matching welcome file for the supplied path and, if
196 * found, serves it to the user. This will be the first entry in
197 * the list of configured {@link #welcomeFiles welcome files} that
198 * exists within the directory referenced by the path. If the
199 * resource is not a directory, or no matching file is found, then
200 * <code>null</code> is returned. The list of welcome files is read
201 * from the {@link ContextHandler} for this servlet, or
202 * <code>"index.jsp" , "index.html"</code> if that is
203 * <code>null</code>.
204 * @return true if a welcome file was served, false otherwise
205 * @throws IOException
206 * @throws MalformedURLException
208 private boolean maybeServeWelcomeFile(String path,
209 boolean included,
210 HttpServletRequest request,
211 HttpServletResponse response)
212 throws IOException, ServletException {
213 if (welcomeFiles == null) {
214 return false;
217 if (!path.endsWith(URIUtil.SLASH)) {
218 path += URIUtil.SLASH;
221 AppEngineWebXml appEngineWebXml = (AppEngineWebXml) getServletContext().getAttribute(
222 "com.google.appengine.tools.development.appEngineWebXml");
224 ContextHandler.SContext context = (ContextHandler.SContext) getServletContext();
225 ServletHandler handler = ((Context) context.getContextHandler()).getServletHandler();
226 PathMap.Entry defaultEntry = handler.getHolderEntry("/");
227 PathMap.Entry jspEntry = handler.getHolderEntry("/foo.jsp");
229 for (String welcomeName : welcomeFiles) {
230 String welcomePath = path + welcomeName;
231 String relativePath = welcomePath.substring(1);
233 PathMap.Entry entry = handler.getHolderEntry(welcomePath);
234 if (entry != defaultEntry && entry != jspEntry) {
235 RequestDispatcher dispatcher = request.getRequestDispatcher(path + welcomeName);
236 return staticFileUtils.serveWelcomeFileAsForward(dispatcher, included, request, response);
239 Resource welcomeFile = getResource(path + welcomeName);
240 if (welcomeFile != null && welcomeFile.exists()) {
241 if (entry != defaultEntry) {
242 RequestDispatcher dispatcher = request.getRequestDispatcher(path + welcomeName);
243 return staticFileUtils.serveWelcomeFileAsForward(dispatcher, included, request, response);
245 if (appEngineWebXml.includesResource(relativePath)) {
246 RequestDispatcher dispatcher = request.getRequestDispatcher(path + welcomeName);
247 return staticFileUtils.serveWelcomeFileAsForward(dispatcher, included, request, response);
250 RequestDispatcher namedDispatcher = context.getNamedDispatcher(welcomeName);
251 if (namedDispatcher != null) {
252 return staticFileUtils.serveWelcomeFileAsForward(namedDispatcher, included,
253 request, response);
257 return false;