blob: 9088500098059fe8a291691c348a3359a1715421 [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
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# See the License for the specific language governing permissions and
# limitations under the License.
"""Dispatch configuration tools.
Library for parsing dispatch.yaml files and working with these in memory.
import re
from google.appengine.api import appinfo
from google.appengine.api import validation
from google.appengine.api import yaml_builder
from google.appengine.api import yaml_listener
from google.appengine.api import yaml_object
_URL_SPLITTER_RE = re.compile(r'^([^/]+)(/.*)$')
_URL_HOST_EXACT_PATTERN_RE = re.compile(r"""
# 0 or more . terminated hostname segments (may not start or end in -).
# followed by a host name segment.
([a-z0-9]([a-z0-9\-]*[a-z0-9])*)$""", re.VERBOSE)
_URL_IP_V4_ADDR_RE = re.compile(r"""
#4 1-3 digit numbers separated by .
^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$""", re.VERBOSE)
_URL_HOST_SUFFIX_PATTERN_RE = re.compile(r"""
# Single star or
# Host prefix with no ., Ex '*-a' or
# Host prefix with ., Ex '*-a.b-c.d'
""", re.VERBOSE)
APPLICATION = 'application'
DISPATCH = 'dispatch'
URL = 'url'
MODULE = 'module'
class Error(Exception):
"""Base class for errors in this module."""
class MalformedDispatchConfigurationError(Error):
"""Configuration file for dispatch is malformed."""
class DispatchEntryURLValidator(validation.Validator):
"""Validater for URL patterns."""
def Validate(self, value, unused_key=None):
"""Validates an URL pattern."""
if value is None:
raise validation.MissingAttribute('url must be specified')
if not isinstance(value, basestring):
raise validation.ValidationError('url must be a string, not \'%r\'' %
url_holder = ParsedURL(value)
if url_holder.host_exact:
'invalid host_pattern \'%s\'' %
_ValidateMatch(_URL_HOST_SUFFIX_PATTERN_RE, url_holder.host_pattern,
'invalid host_pattern \'%s\'' % url_holder.host_pattern)
return value
class ParsedURL(object):
"""Dispath Entry URL holder class.
host_pattern: The host pattern component of the URL pattern.
host_exact: True iff the host pattern does not start with a *.
host: host_pattern with any leading * removed.
path_pattern: The path pattern component of the URL pattern.
path_exact: True iff path_pattern does not end with a *.
path: path_pattern with any trailing * removed.
def __init__(self, url_pattern):
"""Initializes this ParsedURL with an URL pattern value.
url_pattern: An URL pattern that conforms to the regular expression
validation.ValidationError: When url_pattern does not match the required
regular expression.
split_matcher = _ValidateMatch(_URL_SPLITTER_RE, url_pattern,
'invalid url \'%s\'' % url_pattern)
self.host_pattern, self.path_pattern = split_matcher.groups()
if self.host_pattern.startswith('*'):
self.host_exact = False = self.host_pattern[1:]
self.host_exact = True = self.host_pattern
if self.path_pattern.endswith('*'):
self.path_exact = False
self.path = self.path_pattern[:-1]
self.path_exact = True
self.path = self.path_pattern
def _ValidateMatch(regex, value, message):
"""Validate value matches regex."""
matcher = regex.match(value)
if not matcher:
raise validation.ValidationError(message)
return matcher
def _ValidateNotIpV4Address(host):
"""Validate host is not an IPV4 address."""
matcher = _URL_IP_V4_ADDR_RE.match(host)
if matcher and sum(1 for x in matcher.groups() if int(x) <= 255) == 4:
raise validation.ValidationError('Host may not match an ipv4 address \'%s\''
% host)
return matcher
class DispatchEntry(validation.Validated):
"""A Dispatch entry describes a mapping from a URL pattern to a module."""
URL: DispatchEntryURLValidator(),
MODULE: validation.Regex(appinfo.MODULE_ID_RE_STRING),
class DispatchInfoExternal(validation.Validated):
"""Describes the format of a dispatch.yaml file."""
APPLICATION: validation.Optional(appinfo.APPLICATION_RE_STRING),
DISPATCH: validation.Optional(validation.Repeated(DispatchEntry)),
def LoadSingleDispatch(dispatch_info, open_fn=None):
"""Load a dispatch.yaml file or string and return a DispatchInfoExternal.
dispatch_info: The contents of a dispatch.yaml file as a string, or an open
file object.
open_fn: Function for opening files. Unused here, needed to provide
a polymorphic API used by yaml parsing.
A DispatchInfoExternal instance which represents the contents of the parsed
yaml file.
MalformedDispatchConfigurationError: The yaml file contains multiple
dispatch sections.
yaml_errors.EventError: An error occured while parsing the yaml file.
builder = yaml_object.ObjectBuilder(DispatchInfoExternal)
handler = yaml_builder.BuilderHandler(builder)
listener = yaml_listener.EventListener(handler)
parsed_yaml = handler.GetResults()
if not parsed_yaml:
return DispatchInfoExternal()
if len(parsed_yaml) > 1:
raise MalformedDispatchConfigurationError('Multiple dispatch: sections '
'in configuration.')
return parsed_yaml[0]