thirdparty/google_appengine/google/appengine/tools/dev_appserver.py
changeset 149 f2e327a7c5de
parent 109 620f9b141567
child 209 4ba836d74829
equal deleted inserted replaced
148:37505d64e57b 149:f2e327a7c5de
    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
    62 import sre_parse
    60 import sre_parse
    63 
    61 
    64 import mimetypes
    62 import mimetypes
    65 import socket
    63 import socket
    66 import sys
    64 import sys
       
    65 import time
       
    66 import traceback
       
    67 import types
    67 import urlparse
    68 import urlparse
    68 import urllib
    69 import urllib
    69 import traceback
       
    70 import types
       
    71 
    70 
    72 import google
    71 import google
    73 from google.pyglib import gexcept
    72 from google.pyglib import gexcept
    74 
    73 
    75 from google.appengine.api import apiproxy_stub_map
    74 from google.appengine.api import apiproxy_stub_map
  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)