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
;
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
;
55 * Initialize the servlet by extracting some useful configuration
56 * data from the current {@link ServletContext}.
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();
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
);
78 * Retrieve the static resource file indicated.
81 protected void doGet(HttpServletRequest request
, HttpServletResponse response
)
82 throws ServletException
, IOException
{
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();
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
)) {
117 Resource resource
= null;
119 resource
= getResource(pathInContext
);
121 if (resource
!= null && resource
.isDirectory()) {
123 staticFileUtils
.passConditionalHeaders(request
, response
, resource
)) {
124 response
.sendError(HttpServletResponse
.SC_FORBIDDEN
);
127 if (resource
== null || !resource
.exists()) {
128 logger
.warning("No file found for: " + pathInContext
);
129 response
.sendError(HttpServletResponse
.SC_NOT_FOUND
);
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
);
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
);
154 if (included
|| staticFileUtils
.passConditionalHeaders(request
, response
, resource
)) {
155 staticFileUtils
.sendData(request
, response
, included
, resource
);
160 if (resource
!= null) {
167 protected void doPost(HttpServletRequest request
, HttpServletResponse response
)
168 throws ServletException
, IOException
{
169 doGet(request
,response
);
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
) {
185 if (resourceBase
!= null) {
186 return resourceBase
.addPath(pathInContext
);
188 } catch (IOException ex
) {
189 logger
.log(Level
.WARNING
, "Could not find: " + pathInContext
, ex
);
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
204 * @return true if a welcome file was served, false otherwise
205 * @throws IOException
206 * @throws MalformedURLException
208 private boolean maybeServeWelcomeFile(String path
,
210 HttpServletRequest request
,
211 HttpServletResponse response
)
212 throws IOException
, ServletException
{
213 if (welcomeFiles
== null) {
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
,