29 server = dev_appserver.CreateServer(root_path, login_url, port, template_dir) |
29 server = dev_appserver.CreateServer(root_path, login_url, port, template_dir) |
30 server.serve_forever() |
30 server.serve_forever() |
31 """ |
31 """ |
32 |
32 |
33 |
33 |
34 import os |
34 from google.appengine.tools import os_compat |
35 os.environ['TZ'] = 'UTC' |
|
36 import time |
|
37 if hasattr(time, 'tzset'): |
|
38 time.tzset() |
|
39 |
35 |
40 import __builtin__ |
36 import __builtin__ |
41 import BaseHTTPServer |
37 import BaseHTTPServer |
42 import Cookie |
38 import Cookie |
43 import cStringIO |
39 import cStringIO |
44 import cgi |
40 import cgi |
45 import cgitb |
41 import cgitb |
46 import dummy_thread |
42 import dummy_thread |
|
43 import email.Utils |
47 import errno |
44 import errno |
48 import httplib |
45 import httplib |
49 import imp |
46 import imp |
50 import inspect |
47 import inspect |
51 import itertools |
48 import itertools |
52 import logging |
49 import logging |
53 import mimetools |
50 import mimetools |
54 import mimetypes |
51 import mimetypes |
|
52 import os |
55 import pickle |
53 import pickle |
56 import pprint |
54 import pprint |
57 import random |
55 import random |
58 |
56 |
59 import re |
57 import re |
1140 Tuple (source_file, pathname, description) where: |
1139 Tuple (source_file, pathname, description) where: |
1141 source_file: File-like object that contains the module; in the case |
1140 source_file: File-like object that contains the module; in the case |
1142 of packages, this will be None, which implies to look at __init__.py. |
1141 of packages, this will be None, which implies to look at __init__.py. |
1143 pathname: String containing the full path of the module on disk. |
1142 pathname: String containing the full path of the module on disk. |
1144 description: Tuple returned by imp.find_module(). |
1143 description: Tuple returned by imp.find_module(). |
|
1144 However, in the case of an import using a path hook (e.g. a zipfile), |
|
1145 source_file will be a PEP-302-style loader object, pathname will be None, |
|
1146 and description will be a tuple filled with None values. |
1145 |
1147 |
1146 Raises: |
1148 Raises: |
1147 ImportError exception if the requested module was found, but importing |
1149 ImportError exception if the requested module was found, but importing |
1148 it is disallowed. |
1150 it is disallowed. |
1149 |
1151 |
1150 CouldNotFindModuleError exception if the request module could not even |
1152 CouldNotFindModuleError exception if the request module could not even |
1151 be found for import. |
1153 be found for import. |
1152 """ |
1154 """ |
1153 try: |
1155 if search_path is None: |
1154 source_file, pathname, description = self._imp.find_module(submodule, search_path) |
1156 search_path = [None] + sys.path |
1155 except ImportError: |
1157 for path_entry in search_path: |
|
1158 result = self.FindPathHook(submodule, submodule_fullname, path_entry) |
|
1159 if result is not None: |
|
1160 source_file, pathname, description = result |
|
1161 if description == (None, None, None): |
|
1162 return result |
|
1163 else: |
|
1164 break |
|
1165 else: |
1156 self.log('Could not find module "%s"', submodule_fullname) |
1166 self.log('Could not find module "%s"', submodule_fullname) |
1157 raise CouldNotFindModuleError() |
1167 raise CouldNotFindModuleError() |
1158 |
1168 |
1159 suffix, mode, file_type = description |
1169 suffix, mode, file_type = description |
1160 |
1170 |
1170 'or built-in module' % submodule_fullname) |
1180 'or built-in module' % submodule_fullname) |
1171 logging.debug(error_message) |
1181 logging.debug(error_message) |
1172 raise ImportError(error_message) |
1182 raise ImportError(error_message) |
1173 |
1183 |
1174 return source_file, pathname, description |
1184 return source_file, pathname, description |
|
1185 |
|
1186 def FindPathHook(self, submodule, submodule_fullname, path_entry): |
|
1187 """Helper for FindModuleRestricted to find a module in a sys.path entry. |
|
1188 |
|
1189 Args: |
|
1190 submodule: |
|
1191 submodule_fullname: |
|
1192 path_entry: A single sys.path entry, or None representing the builtins. |
|
1193 |
|
1194 Returns: |
|
1195 Either None (if nothing was found), or a triple (source_file, path_name, |
|
1196 description). See the doc string for FindModuleRestricted() for the |
|
1197 meaning of the latter. |
|
1198 """ |
|
1199 if path_entry is None: |
|
1200 if submodule_fullname in sys.builtin_module_names: |
|
1201 try: |
|
1202 result = self._imp.find_module(submodule) |
|
1203 except ImportError: |
|
1204 pass |
|
1205 else: |
|
1206 source_file, pathname, description = result |
|
1207 suffix, mode, file_type = description |
|
1208 if file_type == self._imp.C_BUILTIN: |
|
1209 return result |
|
1210 return None |
|
1211 |
|
1212 |
|
1213 if path_entry in sys.path_importer_cache: |
|
1214 importer = sys.path_importer_cache[path_entry] |
|
1215 else: |
|
1216 importer = None |
|
1217 for hook in sys.path_hooks: |
|
1218 try: |
|
1219 importer = hook(path_entry) |
|
1220 break |
|
1221 except ImportError: |
|
1222 pass |
|
1223 sys.path_importer_cache[path_entry] = importer |
|
1224 |
|
1225 if importer is None: |
|
1226 try: |
|
1227 return self._imp.find_module(submodule, [path_entry]) |
|
1228 except ImportError: |
|
1229 pass |
|
1230 else: |
|
1231 loader = importer.find_module(submodule) |
|
1232 if loader is not None: |
|
1233 return (loader, None, (None, None, None)) |
|
1234 |
|
1235 return None |
1175 |
1236 |
1176 @Trace |
1237 @Trace |
1177 def LoadModuleRestricted(self, |
1238 def LoadModuleRestricted(self, |
1178 submodule_fullname, |
1239 submodule_fullname, |
1179 source_file, |
1240 source_file, |
1184 As a byproduct, the new module will be added to the module dictionary. |
1245 As a byproduct, the new module will be added to the module dictionary. |
1185 |
1246 |
1186 Args: |
1247 Args: |
1187 submodule_fullname: The fully qualified name of the module to find (e.g., |
1248 submodule_fullname: The fully qualified name of the module to find (e.g., |
1188 'foo.bar'). |
1249 'foo.bar'). |
1189 source_file: File-like object that contains the module's source code. |
1250 source_file: File-like object that contains the module's source code, |
|
1251 or a PEP-302-style loader object. |
1190 pathname: String containing the full path of the module on disk. |
1252 pathname: String containing the full path of the module on disk. |
1191 description: Tuple returned by imp.find_module(). |
1253 description: Tuple returned by imp.find_module(), or (None, None, None) |
|
1254 in case source_file is a PEP-302-style loader object. |
1192 |
1255 |
1193 Returns: |
1256 Returns: |
1194 The new module. |
1257 The new module. |
1195 |
1258 |
1196 Raises: |
1259 Raises: |
1197 ImportError exception of the specified module could not be loaded for |
1260 ImportError exception of the specified module could not be loaded for |
1198 whatever reason. |
1261 whatever reason. |
1199 """ |
1262 """ |
|
1263 if description == (None, None, None): |
|
1264 return source_file.load_module(submodule_fullname) |
|
1265 |
1200 try: |
1266 try: |
1201 try: |
1267 try: |
1202 return self._imp.load_module(submodule_fullname, |
1268 return self._imp.load_module(submodule_fullname, |
1203 source_file, |
1269 source_file, |
1204 pathname, |
1270 pathname, |
1315 Args: |
1381 Args: |
1316 fullname: Full name of the module to look up (e.g., foo.bar). |
1382 fullname: Full name of the module to look up (e.g., foo.bar). |
1317 |
1383 |
1318 Returns: |
1384 Returns: |
1319 Tuple (pathname, search_path, submodule) where: |
1385 Tuple (pathname, search_path, submodule) where: |
1320 pathname: String containing the full path of the module on disk. |
1386 pathname: String containing the full path of the module on disk, |
|
1387 or None if the module wasn't loaded from disk (e.g. from a zipfile). |
1321 search_path: List of paths that belong to the found package's search |
1388 search_path: List of paths that belong to the found package's search |
1322 path or None if found module is not a package. |
1389 path or None if found module is not a package. |
1323 submodule: The relative name of the submodule that's being imported. |
1390 submodule: The relative name of the submodule that's being imported. |
1324 """ |
1391 """ |
1325 submodule, search_path = self.GetParentSearchPath(fullname) |
1392 submodule, search_path = self.GetParentSearchPath(fullname) |
1357 |
1424 |
1358 @Trace |
1425 @Trace |
1359 def get_source(self, fullname): |
1426 def get_source(self, fullname): |
1360 """See PEP 302 extensions.""" |
1427 """See PEP 302 extensions.""" |
1361 full_path, search_path, submodule = self.GetModuleInfo(fullname) |
1428 full_path, search_path, submodule = self.GetModuleInfo(fullname) |
|
1429 if full_path is None: |
|
1430 return None |
1362 source_file = open(full_path) |
1431 source_file = open(full_path) |
1363 try: |
1432 try: |
1364 return source_file.read() |
1433 return source_file.read() |
1365 finally: |
1434 finally: |
1366 source_file.close() |
1435 source_file.close() |
1367 |
1436 |
1368 @Trace |
1437 @Trace |
1369 def get_code(self, fullname): |
1438 def get_code(self, fullname): |
1370 """See PEP 302 extensions.""" |
1439 """See PEP 302 extensions.""" |
1371 full_path, search_path, submodule = self.GetModuleInfo(fullname) |
1440 full_path, search_path, submodule = self.GetModuleInfo(fullname) |
|
1441 if full_path is None: |
|
1442 return None |
1372 source_file = open(full_path) |
1443 source_file = open(full_path) |
1373 try: |
1444 try: |
1374 source_code = source_file.read() |
1445 source_code = source_file.read() |
1375 finally: |
1446 finally: |
1376 source_file.close() |
1447 source_file.close() |
1511 exc_type, exc_value, exc_tb = sys.exc_info() |
1582 exc_type, exc_value, exc_tb = sys.exc_info() |
1512 import_error_message = str(exc_type) |
1583 import_error_message = str(exc_type) |
1513 if exc_value: |
1584 if exc_value: |
1514 import_error_message += ': ' + str(exc_value) |
1585 import_error_message += ': ' + str(exc_value) |
1515 |
1586 |
1516 logging.error('Encountered error loading module "%s": %s', |
1587 logging.exception('Encountered error loading module "%s": %s', |
1517 module_fullname, import_error_message) |
1588 module_fullname, import_error_message) |
1518 missing_inits = FindMissingInitFiles(cgi_path, module_fullname) |
1589 missing_inits = FindMissingInitFiles(cgi_path, module_fullname) |
1519 if missing_inits: |
1590 if missing_inits: |
1520 logging.warning('Missing package initialization files: %s', |
1591 logging.warning('Missing package initialization files: %s', |
1521 ', '.join(missing_inits)) |
1592 ', '.join(missing_inits)) |
1660 sys.stdin = infile |
1731 sys.stdin = infile |
1661 sys.stdout = outfile |
1732 sys.stdout = outfile |
1662 os.environ.clear() |
1733 os.environ.clear() |
1663 os.environ.update(env) |
1734 os.environ.update(env) |
1664 before_path = sys.path[:] |
1735 before_path = sys.path[:] |
1665 os.chdir(os.path.dirname(cgi_path)) |
1736 cgi_dir = os.path.normpath(os.path.dirname(cgi_path)) |
|
1737 root_path = os.path.normpath(os.path.abspath(root_path)) |
|
1738 if cgi_dir.startswith(root_path + os.sep): |
|
1739 os.chdir(cgi_dir) |
|
1740 else: |
|
1741 os.chdir(root_path) |
1666 |
1742 |
1667 hook = HardenedModulesHook(sys.modules) |
1743 hook = HardenedModulesHook(sys.modules) |
1668 sys.meta_path = [hook] |
1744 sys.meta_path = [hook] |
1669 if hasattr(sys, 'path_importer_cache'): |
1745 if hasattr(sys, 'path_importer_cache'): |
1670 sys.path_importer_cache.clear() |
1746 sys.path_importer_cache.clear() |
1846 path = os.path.join(self._root_path, path) |
1922 path = os.path.join(self._root_path, path) |
1847 |
1923 |
1848 return path |
1924 return path |
1849 |
1925 |
1850 |
1926 |
1851 class StaticFileMimeTypeMatcher(object): |
1927 class StaticFileConfigMatcher(object): |
1852 """Computes mime type based on URLMap and file extension. |
1928 """Keeps track of file/directory specific application configuration. |
|
1929 |
|
1930 Specifically: |
|
1931 - Computes mime type based on URLMap and file extension. |
|
1932 - Decides on cache expiration time based on URLMap and default expiration. |
1853 |
1933 |
1854 To determine the mime type, we first see if there is any mime-type property |
1934 To determine the mime type, we first see if there is any mime-type property |
1855 on each URLMap entry. If non is specified, we use the mimetypes module to |
1935 on each URLMap entry. If non is specified, we use the mimetypes module to |
1856 guess the mime type from the file path extension, and use |
1936 guess the mime type from the file path extension, and use |
1857 application/octet-stream if we can't find the mimetype. |
1937 application/octet-stream if we can't find the mimetype. |
1858 """ |
1938 """ |
1859 |
1939 |
1860 def __init__(self, |
1940 def __init__(self, |
1861 url_map_list, |
1941 url_map_list, |
1862 path_adjuster): |
1942 path_adjuster, |
|
1943 default_expiration): |
1863 """Initializer. |
1944 """Initializer. |
1864 |
1945 |
1865 Args: |
1946 Args: |
1866 url_map_list: List of appinfo.URLMap objects. |
1947 url_map_list: List of appinfo.URLMap objects. |
1867 If empty or None, then we always use the mime type chosen by the |
1948 If empty or None, then we always use the mime type chosen by the |
1868 mimetypes module. |
1949 mimetypes module. |
1869 path_adjuster: PathAdjuster object used to adjust application file paths. |
1950 path_adjuster: PathAdjuster object used to adjust application file paths. |
1870 """ |
1951 default_expiration: String describing default expiration time for browser |
|
1952 based caching of static files. If set to None this disallows any |
|
1953 browser caching of static content. |
|
1954 """ |
|
1955 if default_expiration is not None: |
|
1956 self._default_expiration = appinfo.ParseExpiration(default_expiration) |
|
1957 else: |
|
1958 self._default_expiration = None |
|
1959 |
1871 self._patterns = [] |
1960 self._patterns = [] |
1872 |
1961 |
1873 if url_map_list: |
1962 if url_map_list: |
1874 for entry in url_map_list: |
1963 for entry in url_map_list: |
1875 if entry.mime_type is None: |
|
1876 continue |
|
1877 handler_type = entry.GetHandlerType() |
1964 handler_type = entry.GetHandlerType() |
1878 if handler_type not in (appinfo.STATIC_FILES, appinfo.STATIC_DIR): |
1965 if handler_type not in (appinfo.STATIC_FILES, appinfo.STATIC_DIR): |
1879 continue |
1966 continue |
1880 |
1967 |
1881 if handler_type == appinfo.STATIC_FILES: |
1968 if handler_type == appinfo.STATIC_FILES: |
1890 try: |
1977 try: |
1891 path_re = re.compile(adjusted_regex) |
1978 path_re = re.compile(adjusted_regex) |
1892 except re.error, e: |
1979 except re.error, e: |
1893 raise InvalidAppConfigError('regex does not compile: %s' % e) |
1980 raise InvalidAppConfigError('regex does not compile: %s' % e) |
1894 |
1981 |
1895 self._patterns.append((path_re, entry.mime_type)) |
1982 if self._default_expiration is None: |
|
1983 expiration = 0 |
|
1984 elif entry.expiration is None: |
|
1985 expiration = self._default_expiration |
|
1986 else: |
|
1987 expiration = appinfo.ParseExpiration(entry.expiration) |
|
1988 |
|
1989 self._patterns.append((path_re, entry.mime_type, expiration)) |
1896 |
1990 |
1897 def GetMimeType(self, path): |
1991 def GetMimeType(self, path): |
1898 """Returns the mime type that we should use when serving the specified file. |
1992 """Returns the mime type that we should use when serving the specified file. |
1899 |
1993 |
1900 Args: |
1994 Args: |
1902 |
1996 |
1903 Returns: |
1997 Returns: |
1904 String containing the mime type to use. Will be 'application/octet-stream' |
1998 String containing the mime type to use. Will be 'application/octet-stream' |
1905 if we have no idea what it should be. |
1999 if we have no idea what it should be. |
1906 """ |
2000 """ |
1907 for (path_re, mime_type) in self._patterns: |
2001 for (path_re, mime_type, expiration) in self._patterns: |
|
2002 if mime_type is not None: |
|
2003 the_match = path_re.match(path) |
|
2004 if the_match: |
|
2005 return mime_type |
|
2006 |
|
2007 filename, extension = os.path.splitext(path) |
|
2008 return mimetypes.types_map.get(extension, 'application/octet-stream') |
|
2009 |
|
2010 def GetExpiration(self, path): |
|
2011 """Returns the cache expiration duration to be users for the given file. |
|
2012 |
|
2013 Args: |
|
2014 path: String containing the file's path on disk. |
|
2015 |
|
2016 Returns: |
|
2017 Integer number of seconds to be used for browser cache expiration time. |
|
2018 """ |
|
2019 for (path_re, mime_type, expiration) in self._patterns: |
1908 the_match = path_re.match(path) |
2020 the_match = path_re.match(path) |
1909 if the_match: |
2021 if the_match: |
1910 return mime_type |
2022 return expiration |
1911 |
2023 |
1912 filename, extension = os.path.splitext(path) |
2024 return self._default_expiration or 0 |
1913 return mimetypes.types_map.get(extension, 'application/octet-stream') |
2025 |
1914 |
2026 |
1915 |
2027 |
1916 def ReadDataFile(data_path, openfile=file): |
2028 def ReadDataFile(data_path, openfile=file): |
1917 """Reads a file on disk, returning a corresponding HTTP status and data. |
2029 """Reads a file on disk, returning a corresponding HTTP status and data. |
1918 |
2030 |
1948 class FileDispatcher(URLDispatcher): |
2060 class FileDispatcher(URLDispatcher): |
1949 """Dispatcher that reads data files from disk.""" |
2061 """Dispatcher that reads data files from disk.""" |
1950 |
2062 |
1951 def __init__(self, |
2063 def __init__(self, |
1952 path_adjuster, |
2064 path_adjuster, |
1953 static_file_mime_type_matcher, |
2065 static_file_config_matcher, |
1954 read_data_file=ReadDataFile): |
2066 read_data_file=ReadDataFile): |
1955 """Initializer. |
2067 """Initializer. |
1956 |
2068 |
1957 Args: |
2069 Args: |
1958 path_adjuster: Instance of PathAdjuster to use for finding absolute |
2070 path_adjuster: Instance of PathAdjuster to use for finding absolute |
1959 paths of data files on disk. |
2071 paths of data files on disk. |
1960 static_file_mime_type_matcher: StaticFileMimeTypeMatcher object. |
2072 static_file_config_matcher: StaticFileConfigMatcher object. |
1961 read_data_file: Used for dependency injection. |
2073 read_data_file: Used for dependency injection. |
1962 """ |
2074 """ |
1963 self._path_adjuster = path_adjuster |
2075 self._path_adjuster = path_adjuster |
1964 self._static_file_mime_type_matcher = static_file_mime_type_matcher |
2076 self._static_file_config_matcher = static_file_config_matcher |
1965 self._read_data_file = read_data_file |
2077 self._read_data_file = read_data_file |
1966 |
2078 |
1967 def Dispatch(self, |
2079 def Dispatch(self, |
1968 relative_url, |
2080 relative_url, |
1969 path, |
2081 path, |
1972 outfile, |
2084 outfile, |
1973 base_env_dict=None): |
2085 base_env_dict=None): |
1974 """Reads the file and returns the response status and data.""" |
2086 """Reads the file and returns the response status and data.""" |
1975 full_path = self._path_adjuster.AdjustPath(path) |
2087 full_path = self._path_adjuster.AdjustPath(path) |
1976 status, data = self._read_data_file(full_path) |
2088 status, data = self._read_data_file(full_path) |
1977 content_type = self._static_file_mime_type_matcher.GetMimeType(full_path) |
2089 content_type = self._static_file_config_matcher.GetMimeType(full_path) |
|
2090 expiration = self._static_file_config_matcher.GetExpiration(full_path) |
1978 |
2091 |
1979 outfile.write('Status: %d\r\n' % status) |
2092 outfile.write('Status: %d\r\n' % status) |
1980 outfile.write('Content-type: %s\r\n' % content_type) |
2093 outfile.write('Content-type: %s\r\n' % content_type) |
|
2094 if expiration: |
|
2095 outfile.write('Expires: %s\r\n' |
|
2096 % email.Utils.formatdate(time.time() + expiration, |
|
2097 usegmt=True)) |
|
2098 outfile.write('Cache-Control: public, max-age=%i\r\n' % expiration) |
1981 outfile.write('\r\n') |
2099 outfile.write('\r\n') |
1982 outfile.write(data) |
2100 outfile.write(data) |
1983 |
2101 |
1984 def __str__(self): |
2102 def __str__(self): |
1985 """Returns a string representation of this dispatcher.""" |
2103 """Returns a string representation of this dispatcher.""" |
2063 Args: |
2181 Args: |
2064 modules: Dictionary containing monitored modules. |
2182 modules: Dictionary containing monitored modules. |
2065 """ |
2183 """ |
2066 self._modules = modules |
2184 self._modules = modules |
2067 self._default_modules = self._modules.copy() |
2185 self._default_modules = self._modules.copy() |
2068 |
2186 self._save_path_hooks = sys.path_hooks[:] |
2069 self._modification_times = {} |
2187 self._modification_times = {} |
2070 |
2188 |
2071 @staticmethod |
2189 @staticmethod |
2072 def GetModuleFile(module, is_file=os.path.isfile): |
2190 def GetModuleFile(module, is_file=os.path.isfile): |
2073 """Helper method to try to determine modules source file. |
2191 """Helper method to try to determine modules source file. |
2130 |
2248 |
2131 def ResetModules(self): |
2249 def ResetModules(self): |
2132 """Clear modules so that when request is run they are reloaded.""" |
2250 """Clear modules so that when request is run they are reloaded.""" |
2133 self._modules.clear() |
2251 self._modules.clear() |
2134 self._modules.update(self._default_modules) |
2252 self._modules.update(self._default_modules) |
|
2253 sys.path_hooks[:] = self._save_path_hooks |
2135 |
2254 |
2136 |
2255 |
2137 def _ClearTemplateCache(module_dict=sys.modules): |
2256 def _ClearTemplateCache(module_dict=sys.modules): |
2138 """Clear template cache in webapp.template module. |
2257 """Clear template cache in webapp.template module. |
2139 |
2258 |
2143 template_module = module_dict.get('google.appengine.ext.webapp.template') |
2262 template_module = module_dict.get('google.appengine.ext.webapp.template') |
2144 if template_module is not None: |
2263 if template_module is not None: |
2145 template_module.template_cache.clear() |
2264 template_module.template_cache.clear() |
2146 |
2265 |
2147 |
2266 |
2148 def CreateRequestHandler(root_path, login_url, require_indexes=False): |
2267 def CreateRequestHandler(root_path, login_url, require_indexes=False, |
|
2268 static_caching=True): |
2149 """Creates a new BaseHTTPRequestHandler sub-class for use with the Python |
2269 """Creates a new BaseHTTPRequestHandler sub-class for use with the Python |
2150 BaseHTTPServer module's HTTP server. |
2270 BaseHTTPServer module's HTTP server. |
2151 |
2271 |
2152 Python's built-in HTTP server does not support passing context information |
2272 Python's built-in HTTP server does not support passing context information |
2153 along to instances of its request handlers. This function gets around that |
2273 along to instances of its request handlers. This function gets around that |
2156 |
2276 |
2157 Args: |
2277 Args: |
2158 root_path: Path to the root of the application running on the server. |
2278 root_path: Path to the root of the application running on the server. |
2159 login_url: Relative URL which should be used for handling user logins. |
2279 login_url: Relative URL which should be used for handling user logins. |
2160 require_indexes: True if index.yaml is read-only gospel; default False. |
2280 require_indexes: True if index.yaml is read-only gospel; default False. |
|
2281 static_caching: True if browser caching of static files should be allowed. |
2161 |
2282 |
2162 Returns: |
2283 Returns: |
2163 Sub-class of BaseHTTPRequestHandler. |
2284 Sub-class of BaseHTTPRequestHandler. |
2164 """ |
2285 """ |
2165 application_module_dict = SetupSharedModules(sys.modules) |
2286 application_module_dict = SetupSharedModules(sys.modules) |
2166 |
2287 |
2167 if require_indexes: |
2288 if require_indexes: |
2168 index_yaml_updater = None |
2289 index_yaml_updater = None |
2169 else: |
2290 else: |
2170 index_yaml_updater = dev_appserver_index.IndexYamlUpdater(root_path) |
2291 index_yaml_updater = dev_appserver_index.IndexYamlUpdater(root_path) |
|
2292 |
|
2293 application_config_cache = AppConfigCache() |
2171 |
2294 |
2172 class DevAppServerRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): |
2295 class DevAppServerRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): |
2173 """Dispatches URLs using patterns from a URLMatcher, which is created by |
2296 """Dispatches URLs using patterns from a URLMatcher, which is created by |
2174 loading an application's configuration file. Executes CGI scripts in the |
2297 loading an application's configuration file. Executes CGI scripts in the |
2175 local process so the scripts can use mock versions of APIs. |
2298 local process so the scripts can use mock versions of APIs. |
2186 """ |
2309 """ |
2187 server_version = 'Development/1.0' |
2310 server_version = 'Development/1.0' |
2188 |
2311 |
2189 module_dict = application_module_dict |
2312 module_dict = application_module_dict |
2190 module_manager = ModuleManager(application_module_dict) |
2313 module_manager = ModuleManager(application_module_dict) |
|
2314 |
|
2315 config_cache = application_config_cache |
2191 |
2316 |
2192 def __init__(self, *args, **kwargs): |
2317 def __init__(self, *args, **kwargs): |
2193 """Initializer. |
2318 """Initializer. |
2194 |
2319 |
2195 Args: |
2320 Args: |
2253 self.module_manager.ResetModules() |
2378 self.module_manager.ResetModules() |
2254 |
2379 |
2255 implicit_matcher = CreateImplicitMatcher(self.module_dict, |
2380 implicit_matcher = CreateImplicitMatcher(self.module_dict, |
2256 root_path, |
2381 root_path, |
2257 login_url) |
2382 login_url) |
2258 config, explicit_matcher = LoadAppConfig(root_path, self.module_dict) |
2383 config, explicit_matcher = LoadAppConfig(root_path, self.module_dict, |
|
2384 cache=self.config_cache, |
|
2385 static_caching=static_caching) |
2259 env_dict['CURRENT_VERSION_ID'] = config.version + ".1" |
2386 env_dict['CURRENT_VERSION_ID'] = config.version + ".1" |
2260 env_dict['APPLICATION_ID'] = config.application |
2387 env_dict['APPLICATION_ID'] = config.application |
2261 dispatcher = MatcherDispatcher(login_url, |
2388 dispatcher = MatcherDispatcher(login_url, |
2262 [implicit_matcher, explicit_matcher]) |
2389 [implicit_matcher, explicit_matcher]) |
2263 |
2390 |
2351 |
2478 |
2352 |
2479 |
2353 def CreateURLMatcherFromMaps(root_path, |
2480 def CreateURLMatcherFromMaps(root_path, |
2354 url_map_list, |
2481 url_map_list, |
2355 module_dict, |
2482 module_dict, |
|
2483 default_expiration, |
2356 create_url_matcher=URLMatcher, |
2484 create_url_matcher=URLMatcher, |
2357 create_cgi_dispatcher=CGIDispatcher, |
2485 create_cgi_dispatcher=CGIDispatcher, |
2358 create_file_dispatcher=FileDispatcher, |
2486 create_file_dispatcher=FileDispatcher, |
2359 create_path_adjuster=PathAdjuster): |
2487 create_path_adjuster=PathAdjuster, |
|
2488 normpath=os.path.normpath): |
2360 """Creates a URLMatcher instance from URLMap. |
2489 """Creates a URLMatcher instance from URLMap. |
2361 |
2490 |
2362 Creates all of the correct URLDispatcher instances to handle the various |
2491 Creates all of the correct URLDispatcher instances to handle the various |
2363 content types in the application configuration. |
2492 content types in the application configuration. |
2364 |
2493 |
2368 matcher with. Can be an empty list if you would like to add patterns |
2497 matcher with. Can be an empty list if you would like to add patterns |
2369 manually. |
2498 manually. |
2370 module_dict: Dictionary in which application-loaded modules should be |
2499 module_dict: Dictionary in which application-loaded modules should be |
2371 preserved between requests. This dictionary must be separate from the |
2500 preserved between requests. This dictionary must be separate from the |
2372 sys.modules dictionary. |
2501 sys.modules dictionary. |
|
2502 default_expiration: String describing default expiration time for browser |
|
2503 based caching of static files. If set to None this disallows any |
|
2504 browser caching of static content. |
2373 create_url_matcher, create_cgi_dispatcher, create_file_dispatcher, |
2505 create_url_matcher, create_cgi_dispatcher, create_file_dispatcher, |
2374 create_path_adjuster: Used for dependency injection. |
2506 create_path_adjuster: Used for dependency injection. |
2375 |
2507 |
2376 Returns: |
2508 Returns: |
2377 Instance of URLMatcher with the supplied URLMap objects properly loaded. |
2509 Instance of URLMatcher with the supplied URLMap objects properly loaded. |
2378 """ |
2510 """ |
2379 url_matcher = create_url_matcher() |
2511 url_matcher = create_url_matcher() |
2380 path_adjuster = create_path_adjuster(root_path) |
2512 path_adjuster = create_path_adjuster(root_path) |
2381 cgi_dispatcher = create_cgi_dispatcher(module_dict, root_path, path_adjuster) |
2513 cgi_dispatcher = create_cgi_dispatcher(module_dict, root_path, path_adjuster) |
2382 file_dispatcher = create_file_dispatcher(path_adjuster, |
2514 file_dispatcher = create_file_dispatcher(path_adjuster, |
2383 StaticFileMimeTypeMatcher(url_map_list, path_adjuster)) |
2515 StaticFileConfigMatcher(url_map_list, path_adjuster, default_expiration)) |
2384 |
2516 |
2385 for url_map in url_map_list: |
2517 for url_map in url_map_list: |
2386 admin_only = url_map.login == appinfo.LOGIN_ADMIN |
2518 admin_only = url_map.login == appinfo.LOGIN_ADMIN |
2387 requires_login = url_map.login == appinfo.LOGIN_REQUIRED or admin_only |
2519 requires_login = url_map.login == appinfo.LOGIN_REQUIRED or admin_only |
2388 |
2520 |
2404 regex = '/'.join((re.escape(regex), '(.*)')) |
2536 regex = '/'.join((re.escape(regex), '(.*)')) |
2405 if os.path.sep == '\\': |
2537 if os.path.sep == '\\': |
2406 backref = r'\\1' |
2538 backref = r'\\1' |
2407 else: |
2539 else: |
2408 backref = r'\1' |
2540 backref = r'\1' |
2409 path = os.path.normpath(path) + os.path.sep + backref |
2541 path = (normpath(path).replace('\\', '\\\\') + |
|
2542 os.path.sep + backref) |
2410 |
2543 |
2411 url_matcher.AddURL(regex, |
2544 url_matcher.AddURL(regex, |
2412 dispatcher, |
2545 dispatcher, |
2413 path, |
2546 path, |
2414 requires_login, admin_only) |
2547 requires_login, admin_only) |
2415 |
2548 |
2416 return url_matcher |
2549 return url_matcher |
2417 |
2550 |
2418 |
2551 |
|
2552 class AppConfigCache(object): |
|
2553 """Cache used by LoadAppConfig. |
|
2554 |
|
2555 If given to LoadAppConfig instances of this class are used to cache contents |
|
2556 of the app config (app.yaml or app.yml) and the Matcher created from it. |
|
2557 |
|
2558 Code outside LoadAppConfig should treat instances of this class as opaque |
|
2559 objects and not access its members. |
|
2560 """ |
|
2561 |
|
2562 path = None |
|
2563 mtime = None |
|
2564 config = None |
|
2565 matcher = None |
|
2566 |
|
2567 |
2419 def LoadAppConfig(root_path, |
2568 def LoadAppConfig(root_path, |
2420 module_dict, |
2569 module_dict, |
|
2570 cache=None, |
|
2571 static_caching=True, |
2421 read_app_config=ReadAppConfig, |
2572 read_app_config=ReadAppConfig, |
2422 create_matcher=CreateURLMatcherFromMaps): |
2573 create_matcher=CreateURLMatcherFromMaps): |
2423 """Creates a Matcher instance for an application configuration file. |
2574 """Creates a Matcher instance for an application configuration file. |
2424 |
2575 |
2425 Raises an InvalidAppConfigError exception if there is anything wrong with |
2576 Raises an InvalidAppConfigError exception if there is anything wrong with |
2428 Args: |
2579 Args: |
2429 root_path: Path to the root of the application to load. |
2580 root_path: Path to the root of the application to load. |
2430 module_dict: Dictionary in which application-loaded modules should be |
2581 module_dict: Dictionary in which application-loaded modules should be |
2431 preserved between requests. This dictionary must be separate from the |
2582 preserved between requests. This dictionary must be separate from the |
2432 sys.modules dictionary. |
2583 sys.modules dictionary. |
2433 read_url_map, create_matcher: Used for dependency injection. |
2584 cache: Instance of AppConfigCache or None. |
|
2585 static_caching: True if browser caching of static files should be allowed. |
|
2586 read_app_config, create_matcher: Used for dependency injection. |
2434 |
2587 |
2435 Returns: |
2588 Returns: |
2436 tuple: (AppInfoExternal, URLMatcher) |
2589 tuple: (AppInfoExternal, URLMatcher) |
2437 """ |
2590 """ |
2438 |
|
2439 for appinfo_path in [os.path.join(root_path, 'app.yaml'), |
2591 for appinfo_path in [os.path.join(root_path, 'app.yaml'), |
2440 os.path.join(root_path, 'app.yml')]: |
2592 os.path.join(root_path, 'app.yml')]: |
2441 |
2593 |
2442 if os.path.isfile(appinfo_path): |
2594 if os.path.isfile(appinfo_path): |
|
2595 if cache is not None: |
|
2596 mtime = os.path.getmtime(appinfo_path) |
|
2597 if cache.path == appinfo_path and cache.mtime == mtime: |
|
2598 return (cache.config, cache.matcher) |
|
2599 |
|
2600 cache.config = cache.matcher = cache.path = None |
|
2601 cache.mtime = mtime |
|
2602 |
2443 try: |
2603 try: |
2444 config = read_app_config(appinfo_path, appinfo.LoadSingleAppInfo) |
2604 config = read_app_config(appinfo_path, appinfo.LoadSingleAppInfo) |
2445 |
2605 |
|
2606 if static_caching: |
|
2607 if config.default_expiration: |
|
2608 default_expiration = config.default_expiration |
|
2609 else: |
|
2610 default_expiration = '0' |
|
2611 else: |
|
2612 default_expiration = None |
|
2613 |
2446 matcher = create_matcher(root_path, |
2614 matcher = create_matcher(root_path, |
2447 config.handlers, |
2615 config.handlers, |
2448 module_dict) |
2616 module_dict, |
|
2617 default_expiration) |
|
2618 |
|
2619 if cache is not None: |
|
2620 cache.path = appinfo_path |
|
2621 cache.config = config |
|
2622 cache.matcher = matcher |
2449 |
2623 |
2450 return (config, matcher) |
2624 return (config, matcher) |
2451 except gexcept.AbstractMethod: |
2625 except gexcept.AbstractMethod: |
2452 pass |
2626 pass |
2453 |
2627 |
2484 smtp_password = config.get('smtp_password', '') |
2658 smtp_password = config.get('smtp_password', '') |
2485 enable_sendmail = config.get('enable_sendmail', False) |
2659 enable_sendmail = config.get('enable_sendmail', False) |
2486 show_mail_body = config.get('show_mail_body', False) |
2660 show_mail_body = config.get('show_mail_body', False) |
2487 remove = config.get('remove', os.remove) |
2661 remove = config.get('remove', os.remove) |
2488 |
2662 |
|
2663 os.environ['APPLICATION_ID'] = app_id |
|
2664 |
2489 if clear_datastore: |
2665 if clear_datastore: |
2490 for path in (datastore_path, history_path): |
2666 for path in (datastore_path, history_path): |
2491 if os.path.lexists(path): |
2667 if os.path.lexists(path): |
2492 logging.info('Attempting to remove file at %s', path) |
2668 logging.info('Attempting to remove file at %s', path) |
2493 try: |
2669 try: |
2614 login_url, |
2790 login_url, |
2615 port, |
2791 port, |
2616 template_dir, |
2792 template_dir, |
2617 serve_address='', |
2793 serve_address='', |
2618 require_indexes=False, |
2794 require_indexes=False, |
|
2795 static_caching=True, |
2619 python_path_list=sys.path): |
2796 python_path_list=sys.path): |
2620 """Creates an new HTTPServer for an application. |
2797 """Creates an new HTTPServer for an application. |
2621 |
2798 |
2622 Args: |
2799 Args: |
2623 root_path: String containing the path to the root directory of the |
2800 root_path: String containing the path to the root directory of the |
2626 port: Port to start the application server on. |
2803 port: Port to start the application server on. |
2627 template_dir: Path to the directory in which the debug console templates |
2804 template_dir: Path to the directory in which the debug console templates |
2628 are stored. |
2805 are stored. |
2629 serve_address: Address on which the server should serve. |
2806 serve_address: Address on which the server should serve. |
2630 require_indexes: True if index.yaml is read-only gospel; default False. |
2807 require_indexes: True if index.yaml is read-only gospel; default False. |
|
2808 static_caching: True if browser caching of static files should be allowed. |
2631 python_path_list: Used for dependency injection. |
2809 python_path_list: Used for dependency injection. |
2632 |
2810 |
2633 Returns: |
2811 Returns: |
2634 Instance of BaseHTTPServer.HTTPServer that's ready to start accepting. |
2812 Instance of BaseHTTPServer.HTTPServer that's ready to start accepting. |
2635 """ |
2813 """ |
2639 FakeFile.SetAllowedPaths([absolute_root_path, |
2817 FakeFile.SetAllowedPaths([absolute_root_path, |
2640 os.path.dirname(os.path.dirname(google.__file__)), |
2818 os.path.dirname(os.path.dirname(google.__file__)), |
2641 template_dir]) |
2819 template_dir]) |
2642 |
2820 |
2643 handler_class = CreateRequestHandler(absolute_root_path, login_url, |
2821 handler_class = CreateRequestHandler(absolute_root_path, login_url, |
2644 require_indexes) |
2822 require_indexes, static_caching) |
2645 |
2823 |
2646 if absolute_root_path not in python_path_list: |
2824 if absolute_root_path not in python_path_list: |
2647 python_path_list.insert(0, absolute_root_path) |
2825 python_path_list.insert(0, absolute_root_path) |
2648 |
2826 |
2649 return BaseHTTPServer.HTTPServer((serve_address, port), handler_class) |
2827 return BaseHTTPServer.HTTPServer((serve_address, port), handler_class) |