App Engine Python SDK version 1.8.9
[gae.git] / python / google / appengine / tools / handler_generator.py
blob2edc5bccb12600cba994d580de3939bc321c2c52
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.tools import handler
33 from google.appengine.tools.app_engine_config_exception import AppEngineConfigException
35 API_ENDPOINT_REGEX = '/_ah/spi/*'
36 MAX_HANDLERS = 100
39 def GenerateYamlHandlersList(app_engine_web_xml, web_xml, static_files):
40 """Produces a list of Yaml strings for dynamic and static handlers."""
41 static_handler_generator = StaticHandlerGenerator(
42 app_engine_web_xml, web_xml, static_files)
43 dynamic_handler_generator = DynamicHandlerGenerator(
44 app_engine_web_xml, web_xml)
46 if (len(static_handler_generator.GenerateOrderedHandlerList()) +
47 len(dynamic_handler_generator.GenerateOrderedHandlerList())
48 > MAX_HANDLERS):
53 dynamic_handler_generator.fall_through = True
54 dynamic_handler_generator.welcome_properties = {}
56 yaml_statements = ['handlers:']
57 if static_files:
58 static_handler_generator = StaticHandlerGenerator(
59 app_engine_web_xml, web_xml, static_files)
60 yaml_statements += static_handler_generator.GetHandlerYaml()
61 yaml_statements += dynamic_handler_generator.GetHandlerYaml()
63 return yaml_statements
66 def GenerateYamlHandlers(app_engine_web_xml, web_xml, static_files):
67 """Produces Yaml string writable to a file."""
68 handler_yaml = '\n'.join(
69 GenerateYamlHandlersList(app_engine_web_xml, web_xml, static_files))
70 return handler_yaml + '\n'
73 class HandlerGenerator(object):
74 """Ancestor class for StaticHandlerGenerator and DynamicHandlerGenerator.
76 Contains shared functionality. Both static and dynamic handler generators
77 work in a similar way. Both obtain a list of patterns, static includes and
78 web.xml servlet url patterns, respectively, add security constraint info to
79 those lists, sort them, and then generate Yaml statements for each entry.
80 """
82 def __init__(self, app_engine_web_xml, web_xml):
83 self.app_engine_web_xml = app_engine_web_xml
84 self.web_xml = web_xml
86 def GetHandlerYaml(self):
87 handler_yaml = []
88 for h in self.GenerateOrderedHandlerList():
89 handler_yaml += self.TranslateHandler(h)
90 return handler_yaml
92 def GenerateSecurityConstraintHandlers(self):
93 """Creates Handlers for security constraint information."""
94 handler_list = []
95 for constraint in self.web_xml.security_constraints:
96 props = {'transport_guarantee': constraint.transport_guarantee,
97 'required_role': constraint.required_role}
98 for pattern in constraint.patterns:
99 security_handler = handler.SimpleHandler(pattern, props)
100 handler_list.append(security_handler)
101 handler_list += self.CreateHandlerWithoutTrailingStar(security_handler)
102 return handler_list
104 def GenerateWelcomeFileHandlers(self):
105 """Creates handlers for welcome files."""
106 if not self.welcome_properties:
107 return []
111 return [handler.SimpleHandler('/', self.welcome_properties),
112 handler.SimpleHandler('/*/', self.welcome_properties)]
114 def TranslateAdditionalOptions(self, h):
115 """Generates Yaml statements from security constraint information."""
116 additional_statements = []
118 required_role = h.GetProperty('required_role', default='none')
119 required_role_translation = {'none': 'optional',
120 '*': 'required',
121 'admin': 'admin'}
122 additional_statements.append(
123 ' login: %s' % required_role_translation[required_role])
125 transport_guarantee = h.GetProperty('transport_guarantee', default='none')
127 if transport_guarantee == 'none':
128 if self.app_engine_web_xml.ssl_enabled:
129 additional_statements.append(' secure: optional')
130 else:
131 additional_statements.append(' secure: never')
132 else:
133 if self.app_engine_web_xml.ssl_enabled:
134 additional_statements.append(' secure: always')
135 else:
136 raise AppEngineConfigException(
137 'SSL must be enabled in appengine-web.xml to use '
138 'transport-guarantee')
140 handler_id = self.web_xml.pattern_to_id.get(h.pattern)
141 if handler_id and handler_id in self.app_engine_web_xml.api_endpoints:
142 additional_statements.append(' api_endpoint: True')
143 return additional_statements
145 def CreateHandlerWithoutTrailingStar(self, h):
146 """Creates identical handler without trailing star in pattern.
148 According to servlet spec, baz/* should match baz.
150 Args:
151 h: a Handler.
152 Returns:
153 If h.pattern is of form "baz/*", returns a singleton list with a
154 SimpleHandler with pattern "baz" and the properties of h. Otherwise,
155 returns an empty list.
157 if len(h.pattern) <= 2 or h.pattern[-2:] != '/*':
158 return []
159 return [handler.SimpleHandler(h.pattern[:-2], h.properties)]
162 class DynamicHandlerGenerator(HandlerGenerator):
163 """Generates dynamic handler yaml entries for app.yaml."""
165 def __init__(self, app_engine_web_xml, web_xml):
166 super(DynamicHandlerGenerator, self).__init__(app_engine_web_xml, web_xml)
167 if any([self.web_xml.fall_through_to_runtime,
168 '/' in self.web_xml.patterns,
169 '/*' in self.web_xml.patterns]):
170 self.fall_through = True
171 self.welcome_properties = {}
172 else:
173 self.fall_through = False
174 self.welcome_properties = {'type': 'dynamic'}
176 self.has_api_endpoint = API_ENDPOINT_REGEX in self.web_xml.patterns
177 self.patterns = [pattern for pattern in self.web_xml.patterns if
178 pattern not in ('/', '/*', API_ENDPOINT_REGEX)]
180 def MakeServletPatternsIntoHandlers(self):
181 """Creates SimpleHandlers from servlet and filter mappings in web.xml."""
182 handler_patterns = []
183 has_jsps = False
184 if self.fall_through:
185 return [handler.SimpleHandler('/*', {'type': 'dynamic'})]
187 for pattern in self.patterns:
188 if pattern.endswith('.jsp'):
189 has_jsps = True
190 else:
191 new_handler = handler.SimpleHandler(pattern, {'type': 'dynamic'})
192 handler_patterns.append(new_handler)
193 handler_patterns += self.CreateHandlerWithoutTrailingStar(new_handler)
195 if has_jsps or self.app_engine_web_xml.vm:
196 handler_patterns.append(
197 handler.SimpleHandler('*.jsp', {'type': 'dynamic'}))
199 handler_patterns.append(
200 handler.SimpleHandler('/_ah/*', {'type': 'dynamic'}))
201 return handler_patterns
203 def GenerateOrderedHandlerList(self):
204 handler_patterns = (self.MakeServletPatternsIntoHandlers()
205 + self.GenerateSecurityConstraintHandlers()
206 + self.GenerateWelcomeFileHandlers())
207 ordered_handlers = handler.GetOrderedIntersection(handler_patterns)
208 if self.has_api_endpoint:
209 ordered_handlers.append(
210 handler.SimpleHandler(API_ENDPOINT_REGEX, {'type': 'dynamic'}))
211 return ordered_handlers
213 def TranslateHandler(self, the_handler):
214 """Converts a dynamic handler object to Yaml."""
215 if the_handler.GetProperty('type') != 'dynamic':
216 return []
217 statements = ['- url: %s' % the_handler.Regexify(),
218 ' script: unused']
219 return statements + self.TranslateAdditionalOptions(the_handler)
222 class StaticHandlerGenerator(HandlerGenerator):
223 """Generates static handler yaml entries for app.yaml."""
225 def __init__(self, app_engine_web_xml, web_xml, static_files):
226 super(StaticHandlerGenerator, self).__init__(app_engine_web_xml, web_xml)
227 self.static_files = static_files
228 static_welcome_files = []
229 for welcome_file in self.web_xml.welcome_files:
230 for static_file in static_files:
231 if static_file.endswith('/' + welcome_file):
232 static_welcome_files.append(welcome_file)
233 break
235 welcome_value = tuple(static_welcome_files) or None
236 self.welcome_properties = {'welcome': welcome_value}
238 def MakeStaticFilePatternsIntoHandlers(self):
239 """Creates SimpleHandlers out of XML-specified static file includes."""
240 includes = self.app_engine_web_xml.static_file_includes
241 if not includes:
242 return [handler.SimpleHandler('/*', {'type': 'static'})]
244 handler_patterns = []
246 for include in includes:
247 pattern = include.pattern.replace('**', '*')
248 if pattern[0] != '/':
249 pattern = '/' + pattern
250 properties = {'type': 'static'}
251 if include.expiration:
252 properties['expiration'] = include.expiration
253 if include.http_headers:
255 properties['http_headers'] = tuple(sorted(include.http_headers.items()))
256 handler_patterns.append(handler.SimpleHandler(pattern, properties))
257 return handler_patterns
259 def GenerateOrderedHandlerList(self):
260 handler_patterns = (self.MakeStaticFilePatternsIntoHandlers()
261 + self.GenerateSecurityConstraintHandlers()
262 + self.GenerateWelcomeFileHandlers())
263 return handler.GetOrderedIntersection(handler_patterns)
265 def TranslateHandler(self, h):
266 """Translates SimpleHandler to static handler yaml statements."""
267 root = self.app_engine_web_xml.public_root
268 regex_string = h.Regexify()
269 if regex_string.startswith(root):
270 regex_string = regex_string[len(root):]
272 welcome_files = h.GetProperty('welcome')
274 if welcome_files:
275 statements = []
276 for welcome_file in welcome_files:
277 statements += ['- url: (%s)' % regex_string,
278 ' static_files: __static__%s\\1%s' %
279 (root, welcome_file),
280 ' upload: __NOT_USED__',
281 ' require_matching_file: True']
282 statements += self.TranslateAdditionalOptions(h)
283 statements += self.TranslateAdditionalStaticOptions(h)
284 return statements
286 if h.GetProperty('type') != 'static':
287 return []
289 statements = ['- url: (%s)' % regex_string,
290 ' static_files: __static__%s\\1' % root,
291 ' upload: __NOT_USED__',
292 ' require_matching_file: True']
294 return (statements +
295 self.TranslateAdditionalOptions(h) +
296 self.TranslateAdditionalStaticOptions(h))
298 def TranslateAdditionalStaticOptions(self, h):
299 statements = []
300 expiration = h.GetProperty('expiration')
301 if expiration:
302 statements.append(' expiration: %s' % expiration)
303 http_headers = h.GetProperty('http_headers')
304 if http_headers:
305 statements.append(' http_headers:')
306 statements += [' %s: %s' % pair for pair in http_headers]
307 return statements