blob: 2edc5bccb12600cba994d580de3939bc321c2c52 [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright 2007 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Contains logic for writing handlers to app.yaml.
Information about handlers comes from static file paths, appengine-web.xml,
and web.xml. This information is packaged into Handler objects which specify
paths and properties about how they are handled.
In this module:
GenerateYamlHandlers: Returns Yaml string with handlers.
HandlerGenerator: Ancestor class for creating handler lists.
DynamicHandlerGenerator: Creates handlers from web-xml servlet and filter
mappings.
StaticHandlerGenerator: Creates handlers from static file includes and
static files.
"""
from google.appengine.tools import handler
from google.appengine.tools.app_engine_config_exception import AppEngineConfigException
API_ENDPOINT_REGEX = '/_ah/spi/*'
MAX_HANDLERS = 100
def GenerateYamlHandlersList(app_engine_web_xml, web_xml, static_files):
"""Produces a list of Yaml strings for dynamic and static handlers."""
static_handler_generator = StaticHandlerGenerator(
app_engine_web_xml, web_xml, static_files)
dynamic_handler_generator = DynamicHandlerGenerator(
app_engine_web_xml, web_xml)
if (len(static_handler_generator.GenerateOrderedHandlerList()) +
len(dynamic_handler_generator.GenerateOrderedHandlerList())
> MAX_HANDLERS):
dynamic_handler_generator.fall_through = True
dynamic_handler_generator.welcome_properties = {}
yaml_statements = ['handlers:']
if static_files:
static_handler_generator = StaticHandlerGenerator(
app_engine_web_xml, web_xml, static_files)
yaml_statements += static_handler_generator.GetHandlerYaml()
yaml_statements += dynamic_handler_generator.GetHandlerYaml()
return yaml_statements
def GenerateYamlHandlers(app_engine_web_xml, web_xml, static_files):
"""Produces Yaml string writable to a file."""
handler_yaml = '\n'.join(
GenerateYamlHandlersList(app_engine_web_xml, web_xml, static_files))
return handler_yaml + '\n'
class HandlerGenerator(object):
"""Ancestor class for StaticHandlerGenerator and DynamicHandlerGenerator.
Contains shared functionality. Both static and dynamic handler generators
work in a similar way. Both obtain a list of patterns, static includes and
web.xml servlet url patterns, respectively, add security constraint info to
those lists, sort them, and then generate Yaml statements for each entry.
"""
def __init__(self, app_engine_web_xml, web_xml):
self.app_engine_web_xml = app_engine_web_xml
self.web_xml = web_xml
def GetHandlerYaml(self):
handler_yaml = []
for h in self.GenerateOrderedHandlerList():
handler_yaml += self.TranslateHandler(h)
return handler_yaml
def GenerateSecurityConstraintHandlers(self):
"""Creates Handlers for security constraint information."""
handler_list = []
for constraint in self.web_xml.security_constraints:
props = {'transport_guarantee': constraint.transport_guarantee,
'required_role': constraint.required_role}
for pattern in constraint.patterns:
security_handler = handler.SimpleHandler(pattern, props)
handler_list.append(security_handler)
handler_list += self.CreateHandlerWithoutTrailingStar(security_handler)
return handler_list
def GenerateWelcomeFileHandlers(self):
"""Creates handlers for welcome files."""
if not self.welcome_properties:
return []
return [handler.SimpleHandler('/', self.welcome_properties),
handler.SimpleHandler('/*/', self.welcome_properties)]
def TranslateAdditionalOptions(self, h):
"""Generates Yaml statements from security constraint information."""
additional_statements = []
required_role = h.GetProperty('required_role', default='none')
required_role_translation = {'none': 'optional',
'*': 'required',
'admin': 'admin'}
additional_statements.append(
' login: %s' % required_role_translation[required_role])
transport_guarantee = h.GetProperty('transport_guarantee', default='none')
if transport_guarantee == 'none':
if self.app_engine_web_xml.ssl_enabled:
additional_statements.append(' secure: optional')
else:
additional_statements.append(' secure: never')
else:
if self.app_engine_web_xml.ssl_enabled:
additional_statements.append(' secure: always')
else:
raise AppEngineConfigException(
'SSL must be enabled in appengine-web.xml to use '
'transport-guarantee')
handler_id = self.web_xml.pattern_to_id.get(h.pattern)
if handler_id and handler_id in self.app_engine_web_xml.api_endpoints:
additional_statements.append(' api_endpoint: True')
return additional_statements
def CreateHandlerWithoutTrailingStar(self, h):
"""Creates identical handler without trailing star in pattern.
According to servlet spec, baz/* should match baz.
Args:
h: a Handler.
Returns:
If h.pattern is of form "baz/*", returns a singleton list with a
SimpleHandler with pattern "baz" and the properties of h. Otherwise,
returns an empty list.
"""
if len(h.pattern) <= 2 or h.pattern[-2:] != '/*':
return []
return [handler.SimpleHandler(h.pattern[:-2], h.properties)]
class DynamicHandlerGenerator(HandlerGenerator):
"""Generates dynamic handler yaml entries for app.yaml."""
def __init__(self, app_engine_web_xml, web_xml):
super(DynamicHandlerGenerator, self).__init__(app_engine_web_xml, web_xml)
if any([self.web_xml.fall_through_to_runtime,
'/' in self.web_xml.patterns,
'/*' in self.web_xml.patterns]):
self.fall_through = True
self.welcome_properties = {}
else:
self.fall_through = False
self.welcome_properties = {'type': 'dynamic'}
self.has_api_endpoint = API_ENDPOINT_REGEX in self.web_xml.patterns
self.patterns = [pattern for pattern in self.web_xml.patterns if
pattern not in ('/', '/*', API_ENDPOINT_REGEX)]
def MakeServletPatternsIntoHandlers(self):
"""Creates SimpleHandlers from servlet and filter mappings in web.xml."""
handler_patterns = []
has_jsps = False
if self.fall_through:
return [handler.SimpleHandler('/*', {'type': 'dynamic'})]
for pattern in self.patterns:
if pattern.endswith('.jsp'):
has_jsps = True
else:
new_handler = handler.SimpleHandler(pattern, {'type': 'dynamic'})
handler_patterns.append(new_handler)
handler_patterns += self.CreateHandlerWithoutTrailingStar(new_handler)
if has_jsps or self.app_engine_web_xml.vm:
handler_patterns.append(
handler.SimpleHandler('*.jsp', {'type': 'dynamic'}))
handler_patterns.append(
handler.SimpleHandler('/_ah/*', {'type': 'dynamic'}))
return handler_patterns
def GenerateOrderedHandlerList(self):
handler_patterns = (self.MakeServletPatternsIntoHandlers()
+ self.GenerateSecurityConstraintHandlers()
+ self.GenerateWelcomeFileHandlers())
ordered_handlers = handler.GetOrderedIntersection(handler_patterns)
if self.has_api_endpoint:
ordered_handlers.append(
handler.SimpleHandler(API_ENDPOINT_REGEX, {'type': 'dynamic'}))
return ordered_handlers
def TranslateHandler(self, the_handler):
"""Converts a dynamic handler object to Yaml."""
if the_handler.GetProperty('type') != 'dynamic':
return []
statements = ['- url: %s' % the_handler.Regexify(),
' script: unused']
return statements + self.TranslateAdditionalOptions(the_handler)
class StaticHandlerGenerator(HandlerGenerator):
"""Generates static handler yaml entries for app.yaml."""
def __init__(self, app_engine_web_xml, web_xml, static_files):
super(StaticHandlerGenerator, self).__init__(app_engine_web_xml, web_xml)
self.static_files = static_files
static_welcome_files = []
for welcome_file in self.web_xml.welcome_files:
for static_file in static_files:
if static_file.endswith('/' + welcome_file):
static_welcome_files.append(welcome_file)
break
welcome_value = tuple(static_welcome_files) or None
self.welcome_properties = {'welcome': welcome_value}
def MakeStaticFilePatternsIntoHandlers(self):
"""Creates SimpleHandlers out of XML-specified static file includes."""
includes = self.app_engine_web_xml.static_file_includes
if not includes:
return [handler.SimpleHandler('/*', {'type': 'static'})]
handler_patterns = []
for include in includes:
pattern = include.pattern.replace('**', '*')
if pattern[0] != '/':
pattern = '/' + pattern
properties = {'type': 'static'}
if include.expiration:
properties['expiration'] = include.expiration
if include.http_headers:
properties['http_headers'] = tuple(sorted(include.http_headers.items()))
handler_patterns.append(handler.SimpleHandler(pattern, properties))
return handler_patterns
def GenerateOrderedHandlerList(self):
handler_patterns = (self.MakeStaticFilePatternsIntoHandlers()
+ self.GenerateSecurityConstraintHandlers()
+ self.GenerateWelcomeFileHandlers())
return handler.GetOrderedIntersection(handler_patterns)
def TranslateHandler(self, h):
"""Translates SimpleHandler to static handler yaml statements."""
root = self.app_engine_web_xml.public_root
regex_string = h.Regexify()
if regex_string.startswith(root):
regex_string = regex_string[len(root):]
welcome_files = h.GetProperty('welcome')
if welcome_files:
statements = []
for welcome_file in welcome_files:
statements += ['- url: (%s)' % regex_string,
' static_files: __static__%s\\1%s' %
(root, welcome_file),
' upload: __NOT_USED__',
' require_matching_file: True']
statements += self.TranslateAdditionalOptions(h)
statements += self.TranslateAdditionalStaticOptions(h)
return statements
if h.GetProperty('type') != 'static':
return []
statements = ['- url: (%s)' % regex_string,
' static_files: __static__%s\\1' % root,
' upload: __NOT_USED__',
' require_matching_file: True']
return (statements +
self.TranslateAdditionalOptions(h) +
self.TranslateAdditionalStaticOptions(h))
def TranslateAdditionalStaticOptions(self, h):
statements = []
expiration = h.GetProperty('expiration')
if expiration:
statements.append(' expiration: %s' % expiration)
http_headers = h.GetProperty('http_headers')
if http_headers:
statements.append(' http_headers:')
statements += [' %s: %s' % pair for pair in http_headers]
return statements