1.9.30 sync.
[gae.git] / python / google / appengine / tools / handler_generator.py
blobbcf877a2d6cc8b04606d54238b8324e971871517
1 #!/usr/bin/env python
3 # Copyright 2007 Google Inc.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 """Contains logic for writing handlers to app.yaml.
19 Information about handlers comes from static file paths, appengine-web.xml,
20 and web.xml. This information is packaged into Handler objects which specify
21 paths and properties about how they are handled.
23 In this module:
24 GenerateYamlHandlers: Returns Yaml string with handlers.
25 HandlerGenerator: Ancestor class for creating handler lists.
26 DynamicHandlerGenerator: Creates handlers from web-xml servlet and filter
27 mappings.
28 StaticHandlerGenerator: Creates handlers from static file includes and
29 static files.
30 """
32 from google.appengine.api import appinfo
33 from google.appengine.tools import handler
34 from google.appengine.tools.app_engine_config_exception import AppEngineConfigException
36 API_ENDPOINT_REGEX = '/_ah/spi/*'
39 def GenerateYamlHandlersList(app_engine_web_xml, web_xml, static_files):
40 """Produces a list of Yaml strings for dynamic and static handlers."""
41 welcome_properties = _MakeWelcomeProperties(web_xml, static_files)
42 static_handler_generator = StaticHandlerGenerator(
43 app_engine_web_xml, web_xml, welcome_properties)
44 dynamic_handler_generator = DynamicHandlerGenerator(
45 app_engine_web_xml, web_xml)
47 handler_length = len(dynamic_handler_generator.GenerateOrderedHandlerList())
48 if static_files:
49 handler_length += len(static_handler_generator.GenerateOrderedHandlerList())
50 if handler_length > appinfo.MAX_URL_MAPS:
55 dynamic_handler_generator.fall_through = True
56 dynamic_handler_generator.welcome_properties = {}
58 yaml_statements = ['handlers:']
59 if static_files:
60 yaml_statements += static_handler_generator.GetHandlerYaml()
61 yaml_statements += dynamic_handler_generator.GetHandlerYaml()
63 return yaml_statements
66 def GenerateYamlHandlersListForDevAppServer(
67 app_engine_web_xml, web_xml, static_urls):
68 r"""Produces a list of Yaml strings for dynamic and static handlers.
70 This variant of GenerateYamlHandlersList is for the Dev App Server case.
71 The key difference there is that we serve files directly from the war
72 directory rather than constructing a parallel hierarchy with a special
73 __static__ directory. Since app.yaml doesn't support excluding URL patterns
74 and appengine-web.xml does, this means that we have to define patterns that
75 cover exactly the set of static files we want without pulling in any files
76 that are not supposed to be served as static files.
78 Args:
79 app_engine_web_xml: an app_engine_web_xml_parser.AppEngineWebXml object.
80 web_xml: a web_xml_parser.WebXml object.
81 static_urls: a list of two-item tuples where the first item is a URL pattern
82 string for a static file, such as '/stylesheets/main\.css', and the
83 second item is the app_engine_web_xml_parser.StaticFileInclude
84 representing the <static-files><include> XML element that caused that URL
85 pattern to be included in the list.
87 Returns:
88 A list of strings that together make up the lines of the generated app.yaml
89 file.
90 """
98 appinfo.MAX_URL_MAPS = 10000
99 static_handler_generator = StaticHandlerGeneratorForDevAppServer(
100 app_engine_web_xml, web_xml, static_urls)
101 dynamic_handler_generator = DynamicHandlerGenerator(
102 app_engine_web_xml, web_xml)
103 return (['handlers:'] +
104 static_handler_generator.GetHandlerYaml() +
105 dynamic_handler_generator.GetHandlerYaml())
108 def _MakeWelcomeProperties(web_xml, static_files):
109 """Makes the welcome_properties dict given web_xml and the static files.
111 Args:
112 web_xml: a parsed web.xml that may contain a <welcome-file-list> clause.
113 static_files: the list of all static files found in the app.
115 Returns:
116 A dict with a single entry where the key is 'welcome' and the value is
117 either None or a tuple of the file names in all the <welcome-file> clauses
118 that were retained. A <welcome-file> clause is retained if its file name
119 matches at least one actual file in static_files.
121 For example, if the input looked like this:
122 <welcome-file-list>
123 <welcome-file>index.jsp</welcome-file>
124 <welcome-file>index.html</welcome-file>
125 </welcome-file-list>
126 and if there was a file /foo/bar/index.html but no file called index.jsp
127 anywhere in static_files, the result would be {'welcome': ('index.html',)}.
129 static_welcome_files = []
130 for welcome_file in web_xml.welcome_files:
131 if any(f.endswith('/' + welcome_file) for f in static_files):
132 static_welcome_files.append(welcome_file)
134 welcome_value = tuple(static_welcome_files) or None
135 return {'welcome': welcome_value}
138 class HandlerGenerator(object):
139 """Ancestor class for StaticHandlerGenerator and DynamicHandlerGenerator.
141 Contains shared functionality. Both static and dynamic handler generators
142 work in a similar way. Both obtain a list of patterns, static includes and
143 web.xml servlet url patterns, respectively, add security constraint info to
144 those lists, sort them, and then generate Yaml statements for each entry.
147 def __init__(self, app_engine_web_xml, web_xml):
148 self.app_engine_web_xml = app_engine_web_xml
149 self.web_xml = web_xml
151 def GetHandlerYaml(self):
152 handler_yaml = []
153 for h in self.GenerateOrderedHandlerList():
154 handler_yaml += self.TranslateHandler(h)
155 return handler_yaml
157 def GenerateSecurityConstraintHandlers(self):
158 """Creates Handlers for security constraint information."""
159 handler_list = []
160 for constraint in self.web_xml.security_constraints:
161 props = {'transport_guarantee': constraint.transport_guarantee,
162 'required_role': constraint.required_role}
163 for pattern in constraint.patterns:
164 security_handler = handler.SimpleHandler(pattern, props)
165 handler_list.append(security_handler)
166 handler_list += self.CreateHandlerWithoutTrailingStar(security_handler)
167 return handler_list
169 def GenerateWelcomeFileHandlers(self):
170 """Creates handlers for welcome files."""
171 if not self.welcome_properties:
172 return []
176 return [handler.SimpleHandler('/', self.welcome_properties),
177 handler.SimpleHandler('/*/', self.welcome_properties)]
179 def TranslateAdditionalOptions(self, h):
180 """Generates Yaml statements from security constraint information."""
181 additional_statements = []
183 required_role = h.GetProperty('required_role', default='none')
184 required_role_translation = {'none': 'optional',
185 '*': 'required',
186 'admin': 'admin'}
187 additional_statements.append(
188 ' login: %s' % required_role_translation[required_role])
190 transport_guarantee = h.GetProperty('transport_guarantee', default='none')
192 if transport_guarantee == 'none':
193 if self.app_engine_web_xml.ssl_enabled:
194 additional_statements.append(' secure: optional')
195 else:
196 additional_statements.append(' secure: never')
197 else:
198 if self.app_engine_web_xml.ssl_enabled:
199 additional_statements.append(' secure: always')
200 else:
201 raise AppEngineConfigException(
202 'SSL must be enabled in appengine-web.xml to use '
203 'transport-guarantee')
205 handler_id = self.web_xml.pattern_to_id.get(h.pattern)
206 if handler_id and handler_id in self.app_engine_web_xml.api_endpoints:
207 additional_statements.append(' api_endpoint: True')
208 return additional_statements
210 def CreateHandlerWithoutTrailingStar(self, h):
211 """Creates identical handler without trailing star in pattern.
213 According to servlet spec, baz/* should match baz.
215 Args:
216 h: a Handler.
217 Returns:
218 If h.pattern is of form "baz/*", returns a singleton list with a
219 SimpleHandler with pattern "baz" and the properties of h. Otherwise,
220 returns an empty list.
222 if len(h.pattern) <= 2 or h.pattern[-2:] != '/*':
223 return []
224 return [handler.SimpleHandler(h.pattern[:-2], h.properties)]
227 class DynamicHandlerGenerator(HandlerGenerator):
228 """Generates dynamic handler yaml entries for app.yaml."""
230 def __init__(self, app_engine_web_xml, web_xml):
231 super(DynamicHandlerGenerator, self).__init__(app_engine_web_xml, web_xml)
232 if any([self.web_xml.fall_through_to_runtime,
233 '/' in self.web_xml.patterns,
234 '/*' in self.web_xml.patterns]):
235 self.fall_through = True
236 self.welcome_properties = {}
237 else:
238 self.fall_through = False
239 self.welcome_properties = {'type': 'dynamic'}
241 self.has_api_endpoint = API_ENDPOINT_REGEX in self.web_xml.patterns
242 self.patterns = [pattern for pattern in self.web_xml.patterns if
243 pattern not in ('/', '/*', API_ENDPOINT_REGEX)]
244 self.has_jsps = web_xml.has_jsps
246 def MakeServletPatternsIntoHandlers(self):
247 """Creates SimpleHandlers from servlet and filter mappings in web.xml."""
248 handler_patterns = []
249 has_jsps = self.has_jsps
250 if self.fall_through:
251 return [handler.SimpleHandler('/*', {'type': 'dynamic'})]
253 for pattern in self.patterns:
254 if pattern.endswith('.jsp'):
255 has_jsps = True
256 else:
257 new_handler = handler.SimpleHandler(pattern, {'type': 'dynamic'})
258 handler_patterns.append(new_handler)
259 handler_patterns += self.CreateHandlerWithoutTrailingStar(new_handler)
261 if has_jsps or self.app_engine_web_xml.vm:
262 handler_patterns.append(
263 handler.SimpleHandler('*.jsp', {'type': 'dynamic'}))
265 handler_patterns.append(
266 handler.SimpleHandler('/_ah/*', {'type': 'dynamic'}))
267 return handler_patterns
269 def GenerateOrderedHandlerList(self):
270 handler_patterns = (self.MakeServletPatternsIntoHandlers()
271 + self.GenerateSecurityConstraintHandlers()
272 + self.GenerateWelcomeFileHandlers())
273 ordered_handlers = handler.GetOrderedIntersection(handler_patterns)
274 if self.has_api_endpoint:
275 ordered_handlers.append(
276 handler.SimpleHandler(API_ENDPOINT_REGEX, {'type': 'dynamic'}))
277 return ordered_handlers
279 def TranslateHandler(self, the_handler):
280 """Converts a dynamic handler object to Yaml."""
281 if the_handler.GetProperty('type') != 'dynamic':
282 return []
283 statements = ['- url: %s' % the_handler.Regexify(),
284 ' script: unused']
285 return statements + self.TranslateAdditionalOptions(the_handler)
288 class StaticHandlerGenerator(HandlerGenerator):
289 """Generates static handler yaml entries for app.yaml."""
291 def __init__(self, app_engine_web_xml, web_xml, welcome_properties):
292 super(StaticHandlerGenerator, self).__init__(app_engine_web_xml, web_xml)
293 self.static_file_includes = self.app_engine_web_xml.static_file_includes
294 self.welcome_properties = welcome_properties
296 def MakeStaticFilePatternsIntoHandlers(self):
297 """Creates SimpleHandlers out of XML-specified static file includes."""
298 includes = self.static_file_includes
299 if not includes:
300 return [handler.SimpleHandler('/*', {'type': 'static'})]
302 handler_patterns = []
304 for include in includes:
305 pattern = include.pattern.replace('**', '*')
306 if pattern[0] != '/':
307 pattern = '/' + pattern
308 properties = {'type': 'static'}
309 if include.expiration:
310 properties['expiration'] = include.expiration
311 if include.http_headers:
313 properties['http_headers'] = tuple(sorted(include.http_headers.items()))
314 handler_patterns.append(handler.SimpleHandler(pattern, properties))
315 return handler_patterns
317 def GenerateOrderedHandlerList(self):
318 handler_patterns = (self.MakeStaticFilePatternsIntoHandlers()
319 + self.GenerateSecurityConstraintHandlers()
320 + self.GenerateWelcomeFileHandlers())
321 return handler.GetOrderedIntersection(handler_patterns)
323 def TranslateHandler(self, h):
324 """Translates SimpleHandler to static handler yaml statements."""
325 root = self.app_engine_web_xml.public_root
326 regex_string = h.Regexify()
327 if regex_string.startswith(root):
328 regex_string = regex_string[len(root):]
330 welcome_files = h.GetProperty('welcome')
332 if welcome_files:
333 statements = []
334 for welcome_file in welcome_files:
335 statements += ['- url: (%s)' % regex_string,
336 ' static_files: __static__%s\\1%s' %
337 (root, welcome_file),
338 ' upload: __NOT_USED__',
339 ' require_matching_file: True']
340 statements += self.TranslateAdditionalOptions(h)
341 statements += self.TranslateAdditionalStaticOptions(h)
342 return statements
344 if h.GetProperty('type') != 'static':
345 return []
347 statements = ['- url: (%s)' % regex_string,
348 ' static_files: __static__%s\\1' % root,
349 ' upload: __NOT_USED__',
350 ' require_matching_file: True']
352 return (statements +
353 self.TranslateAdditionalOptions(h) +
354 self.TranslateAdditionalStaticOptions(h))
356 def TranslateAdditionalStaticOptions(self, h):
357 statements = []
358 expiration = h.GetProperty('expiration')
359 if expiration:
360 statements.append(' expiration: %s' % expiration)
361 http_headers = h.GetProperty('http_headers')
362 if http_headers:
363 statements.append(' http_headers:')
364 statements += [' %s: %s' % pair for pair in http_headers]
365 return statements
368 class StaticHandlerGeneratorForDevAppServer(StaticHandlerGenerator):
369 """Generates static handler yaml entries for app.yaml in Dev App Server.
371 This class overrides the GenerateOrderedHanderList and TranslateHandler
372 methods from its parent to work with the Dev App Server environment.
373 See the GenerateYamlHandlersListForDevAppServer method above for further
374 details.
377 def __init__(self, app_engine_web_xml, web_xml, static_urls):
378 super(StaticHandlerGeneratorForDevAppServer, self).__init__(
379 app_engine_web_xml, web_xml, {})
380 self.static_urls = static_urls
382 def GenerateOrderedHandlerList(self):
383 handler_patterns = self.MakeStaticUrlsIntoHandlers()
388 return handler.GetOrderedIntersection(handler_patterns)
390 def MakeStaticUrlsIntoHandlers(self):
391 handler_patterns = []
392 for url, include in self.static_urls:
393 properties = {'type': 'static'}
394 if include.expiration:
395 properties['expiration'] = include.expiration
396 if include.http_headers:
397 properties['http_headers'] = tuple(sorted(include.http_headers.items()))
398 handler_patterns.append(handler.SimpleHandler(url, properties))
399 return handler_patterns
401 def TranslateHandler(self, h):
402 """Translates SimpleHandler to static handler yaml statements."""
404 root = self.app_engine_web_xml.public_root
407 regex = h.Regexify()
411 split = 1 if regex.startswith('/') else 0
413 statements = ['- url: /(%s)' % regex[split:],
414 ' static_files: %s\\1' % root,
415 ' upload: __NOT_USED__',
416 ' require_matching_file: True']
418 return (statements +
419 self.TranslateAdditionalOptions(h) +
420 self.TranslateAdditionalStaticOptions(h))