| #!/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 |