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.
19 """Dispatch configuration tools.
21 Library for parsing dispatch.yaml files and working with these in memory.
33 from google
.appengine
.api
import appinfo
34 from google
.appengine
.api
import validation
35 from google
.appengine
.api
import yaml_builder
36 from google
.appengine
.api
import yaml_listener
37 from google
.appengine
.api
import yaml_object
39 _URL_SPLITTER_RE
= re
.compile(r
'^([^/]+)(/.*)$')
46 _URL_HOST_EXACT_PATTERN_RE
= re
.compile(r
"""
47 # 0 or more . terminated hostname segments (may not start or end in -).
48 ^([a-z0-9]([a-z0-9\-]*[a-z0-9])*\.)*
49 # followed by a host name segment.
50 ([a-z0-9]([a-z0-9\-]*[a-z0-9])*)$""", re
.VERBOSE
)
52 _URL_IP_V4_ADDR_RE
= re
.compile(r
"""
53 #4 1-3 digit numbers separated by .
54 ^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$""", re
.VERBOSE
)
58 _URL_HOST_SUFFIX_PATTERN_RE
= re
.compile(r
"""
61 # Host prefix with no ., Ex '*-a' or
62 [*][a-z0-9\-]*[a-z0-9]|
63 # Host prefix with ., Ex '*-a.b-c.d'
64 [*](\.|[a-z0-9\-]*[a-z0-9]\.)([a-z0-9]([a-z0-9\-]*[a-z0-9])*\.)*
65 ([a-z0-9]([a-z0-9\-]*[a-z0-9])*))$
68 APPLICATION
= 'application'
74 class Error(Exception):
75 """Base class for errors in this module."""
78 class MalformedDispatchConfigurationError(Error
):
79 """Configuration file for dispatch is malformed."""
82 class DispatchEntryURLValidator(validation
.Validator
):
83 """Validater for URL patterns."""
85 def Validate(self
, value
, unused_key
=None):
86 """Validates an URL pattern."""
88 raise validation
.MissingAttribute('url must be specified')
89 if not isinstance(value
, basestring
):
90 raise validation
.ValidationError('url must be a string, not \'%r\'' %
93 url_holder
= ParsedURL(value
)
94 if url_holder
.host_exact
:
95 _ValidateMatch(_URL_HOST_EXACT_PATTERN_RE
, url_holder
.host
,
96 'invalid host_pattern \'%s\'' % url_holder
.host
)
99 _ValidateNotIpV4Address(url_holder
.host
)
101 _ValidateMatch(_URL_HOST_SUFFIX_PATTERN_RE
, url_holder
.host_pattern
,
102 'invalid host_pattern \'%s\'' % url_holder
.host_pattern
)
109 class ParsedURL(object):
110 """Dispath Entry URL holder class.
113 host_pattern: The host pattern component of the URL pattern.
114 host_exact: True iff the host pattern does not start with a *.
115 host: host_pattern with any leading * removed.
116 path_pattern: The path pattern component of the URL pattern.
117 path_exact: True iff path_pattern does not end with a *.
118 path: path_pattern with any trailing * removed.
121 def __init__(self
, url_pattern
):
122 """Initializes this ParsedURL with an URL pattern value.
125 url_pattern: An URL pattern that conforms to the regular expression
129 validation.ValidationError: When url_pattern does not match the required
132 split_matcher
= _ValidateMatch(_URL_SPLITTER_RE
, url_pattern
,
133 'invalid url \'%s\'' % url_pattern
)
134 self
.host_pattern
, self
.path_pattern
= split_matcher
.groups()
135 if self
.host_pattern
.startswith('*'):
136 self
.host_exact
= False
137 self
.host
= self
.host_pattern
[1:]
139 self
.host_exact
= True
140 self
.host
= self
.host_pattern
142 if self
.path_pattern
.endswith('*'):
143 self
.path_exact
= False
144 self
.path
= self
.path_pattern
[:-1]
146 self
.path_exact
= True
147 self
.path
= self
.path_pattern
150 def _ValidateMatch(regex
, value
, message
):
151 """Validate value matches regex."""
152 matcher
= regex
.match(value
)
154 raise validation
.ValidationError(message
)
158 def _ValidateNotIpV4Address(host
):
159 """Validate host is not an IPV4 address."""
160 matcher
= _URL_IP_V4_ADDR_RE
.match(host
)
161 if matcher
and sum(1 for x
in matcher
.groups() if int(x
) <= 255) == 4:
162 raise validation
.ValidationError('Host may not match an ipv4 address \'%s\''
167 class DispatchEntry(validation
.Validated
):
168 """A Dispatch entry describes a mapping from a URL pattern to a module."""
170 URL
: DispatchEntryURLValidator(),
171 MODULE
: validation
.Regex(appinfo
.MODULE_ID_RE_STRING
),
175 class DispatchInfoExternal(validation
.Validated
):
176 """Describes the format of a dispatch.yaml file."""
178 APPLICATION
: validation
.Optional(appinfo
.APPLICATION_RE_STRING
),
179 DISPATCH
: validation
.Optional(validation
.Repeated(DispatchEntry
)),
183 def LoadSingleDispatch(dispatch_info
, open_fn
=None):
184 """Load a dispatch.yaml file or string and return a DispatchInfoExternal.
187 dispatch_info: The contents of a dispatch.yaml file as a string, or an open
189 open_fn: Function for opening files. Unused here, needed to provide
190 a polymorphic API used by appcfg.py yaml parsing.
193 A DispatchInfoExternal instance which represents the contents of the parsed
197 MalformedDispatchConfigurationError: The yaml file contains multiple
199 yaml_errors.EventError: An error occured while parsing the yaml file.
201 builder
= yaml_object
.ObjectBuilder(DispatchInfoExternal
)
202 handler
= yaml_builder
.BuilderHandler(builder
)
203 listener
= yaml_listener
.EventListener(handler
)
204 listener
.Parse(dispatch_info
)
206 parsed_yaml
= handler
.GetResults()
208 return DispatchInfoExternal()
209 if len(parsed_yaml
) > 1:
210 raise MalformedDispatchConfigurationError('Multiple dispatch: sections '
212 return parsed_yaml
[0]