PageRenderTime 50ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/google/appengine/tools/devappserver2/php/instance_factory.py

https://github.com/jsa/gaesdk-python
Python | 297 lines | 281 code | 0 blank | 16 comment | 0 complexity | f4d278ad1e02601006d73e3ad29370f9 MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0, MIT, GPL-2.0
  1. #!/usr/bin/env python
  2. #
  3. # Copyright 2007 Google Inc.
  4. #
  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
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  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.
  16. #
  17. """Serves content for "script" handlers using the PHP runtime."""
  18. import os
  19. import re
  20. import subprocess
  21. import sys
  22. import google
  23. from google.appengine.api import appinfo
  24. from google.appengine.tools.devappserver2 import http_runtime
  25. from google.appengine.tools.devappserver2 import instance
  26. from google.appengine.tools.devappserver2 import safe_subprocess
  27. _RUNTIME_PATH = os.path.abspath(
  28. os.path.join(os.path.dirname(sys.argv[0]), '_php_runtime.py')
  29. )
  30. _CHECK_ENVIRONMENT_SCRIPT_PATH = os.path.join(
  31. os.path.dirname(__file__), 'check_environment.php')
  32. _RUNTIME_ARGS = [sys.executable, _RUNTIME_PATH]
  33. GAE_EXTENSION_NAME = 'GAE Runtime Module'
  34. # OS-specific file naming for bundled PHP binaries. Assume empty string
  35. # if no corresponding OS is found.
  36. _EXECUTABLE_EXT = {'win32': '.exe'}
  37. _EXTENSION_PREFIX = {'win32': 'php_'}
  38. _DYNAMIC_LIB_EXT = {'win32': '.dll', 'darwin': '.so'}
  39. def _get_php_executable_path(runtime):
  40. filename = 'php-cgi%s' % _EXECUTABLE_EXT.get(sys.platform, '')
  41. return _get_php_binary_path(filename, runtime)
  42. def _get_php_extension_path(extension_stem, runtime):
  43. filename = '%s%s%s' % (_EXTENSION_PREFIX.get(sys.platform, ''),
  44. extension_stem,
  45. _DYNAMIC_LIB_EXT.get(sys.platform, ''))
  46. return _get_php_binary_path(filename, runtime)
  47. def _get_php_binary_path(filename, runtime):
  48. """Returns the path to the siloed php-cgi binary or None if not present."""
  49. php_binary_dir = None
  50. if sys.platform == 'win32':
  51. if runtime == 'php55':
  52. php_binary_dir = 'php/php-5.5-Win32-VC11-x86'
  53. elif sys.platform == 'darwin':
  54. if runtime == 'php55':
  55. php_binary_dir = '../php55'
  56. if php_binary_dir:
  57. # The Cloud SDK uses symlinks in its packaging of the Mac Launcher. First
  58. # try to find PHP relative to the absolute path of this executable. If that
  59. # doesn't work, try using the path without dereferencing all symlinks.
  60. base_paths = [os.path.realpath(sys.argv[0]), sys.argv[0]]
  61. for base_path in base_paths:
  62. root = os.path.dirname(base_path)
  63. abs_path = os.path.abspath(os.path.join(root, php_binary_dir, filename))
  64. if os.path.exists(abs_path):
  65. return abs_path
  66. return None
  67. class _PHPBinaryError(Exception):
  68. pass
  69. class _PHPEnvironmentError(Exception):
  70. pass
  71. class PHPRuntimeInstanceFactory(instance.InstanceFactory):
  72. """A factory that creates new PHP runtime Instances."""
  73. # TODO: Use real script values.
  74. START_URL_MAP = appinfo.URLMap(
  75. url='/_ah/start',
  76. script='$PHP_LIB/default_start_handler',
  77. login='admin')
  78. WARMUP_URL_MAP = appinfo.URLMap(
  79. url='/_ah/warmup',
  80. script='$PHP_LIB/default_warmup_handler',
  81. login='admin')
  82. SUPPORTS_INTERACTIVE_REQUESTS = True
  83. FILE_CHANGE_INSTANCE_RESTART_POLICY = instance.NEVER
  84. def __init__(self, request_data, runtime_config_getter, module_configuration):
  85. """Initializer for PHPRuntimeInstanceFactory.
  86. Args:
  87. request_data: A wsgi_request_info.WSGIRequestInfo that will be provided
  88. with request information for use by API stubs.
  89. runtime_config_getter: A function that can be called without arguments
  90. and returns the runtime_config_pb2.Config containing the configuration
  91. for the runtime.
  92. module_configuration: An application_configuration.ModuleConfiguration
  93. instance respresenting the configuration of the module that owns the
  94. runtime.
  95. """
  96. super(PHPRuntimeInstanceFactory, self).__init__(
  97. request_data, 8 if runtime_config_getter().threadsafe else 1)
  98. self._runtime_config_getter = runtime_config_getter
  99. self._module_configuration = module_configuration
  100. @classmethod
  101. def _check_php_version(cls, php_executable_path, env):
  102. """Check if php-cgi has the correct version."""
  103. version_process = safe_subprocess.start_process([php_executable_path, '-v'],
  104. stdout=subprocess.PIPE,
  105. stderr=subprocess.PIPE,
  106. env=env)
  107. version_stdout, version_stderr = version_process.communicate()
  108. if version_process.returncode:
  109. raise _PHPEnvironmentError(
  110. '"%s -v" returned an error [%d]\n%s%s' % (
  111. php_executable_path,
  112. version_process.returncode,
  113. version_stderr,
  114. version_stdout))
  115. version_match = re.search(r'PHP (\d+).(\d+)', version_stdout)
  116. if version_match is None:
  117. raise _PHPEnvironmentError(
  118. '"%s -v" returned an unexpected version string:\n%s%s' % (
  119. php_executable_path,
  120. version_stderr,
  121. version_stdout))
  122. version = tuple(int(v) for v in version_match.groups())
  123. if version < (5, 5):
  124. raise _PHPEnvironmentError(
  125. 'The PHP interpreter must be version >= 5.5, %d.%d found' % version)
  126. @classmethod
  127. def _check_gae_extension(cls, php_executable_path, gae_extension_path, env):
  128. """Check if GAE extension can be loaded."""
  129. if not os.path.exists(gae_extension_path):
  130. raise _PHPBinaryError('The path specified with the '
  131. '--php_gae_extension_path flag (%s) does not '
  132. 'exist.' % gae_extension_path)
  133. # The GAE extension requires APPLICATION_ROOT to be set.
  134. env['APPLICATION_ROOT'] = os.getcwd()
  135. args = [php_executable_path, '-m',
  136. '-d', 'extension="%s"' % os.path.basename(gae_extension_path),
  137. '-d', 'extension_dir="%s"' % os.path.dirname(gae_extension_path)]
  138. ext_process = safe_subprocess.start_process(args,
  139. stdout=subprocess.PIPE,
  140. stderr=subprocess.PIPE,
  141. env=env)
  142. ext_stdout, ext_stderr = ext_process.communicate()
  143. if ext_process.returncode:
  144. raise _PHPEnvironmentError('"%s -m" returned an error [%d]\n%s%s' % (
  145. php_executable_path,
  146. ext_process.returncode,
  147. ext_stderr,
  148. ext_stdout))
  149. if GAE_EXTENSION_NAME not in ext_stdout:
  150. raise _PHPEnvironmentError('Unable to load GAE runtime module at %s' %
  151. gae_extension_path)
  152. @classmethod
  153. def _check_environment(cls, php_executable_path, env):
  154. # Clear auto_prepend_file & auto_append_file ini directives as they can
  155. # trigger error and cause non-zero return.
  156. args = [php_executable_path, '-f', _CHECK_ENVIRONMENT_SCRIPT_PATH,
  157. '-d', 'auto_prepend_file=NULL', '-d', 'auto_append_file=NULL']
  158. check_process = safe_subprocess.start_process(args,
  159. stdout=subprocess.PIPE,
  160. stderr=subprocess.PIPE,
  161. env=env)
  162. check_process_stdout, _ = check_process.communicate()
  163. if check_process.returncode:
  164. raise _PHPEnvironmentError(check_process_stdout)
  165. @classmethod
  166. def _check_binaries(cls, php_executable_path, gae_extension_path):
  167. """Perform sanity check on php-cgi & gae extension."""
  168. if not php_executable_path:
  169. raise _PHPBinaryError('The development server must be started with the '
  170. '--php_executable_path flag set to the path of the '
  171. 'php-cgi binary.')
  172. if not os.path.exists(php_executable_path):
  173. raise _PHPBinaryError('The path specified with the --php_executable_path '
  174. 'flag (%s) does not exist.' % php_executable_path)
  175. if not os.access(php_executable_path, os.X_OK):
  176. raise _PHPBinaryError('The path specified with the --php_executable_path '
  177. 'flag (%s) is not executable' % php_executable_path)
  178. env = {}
  179. # On Windows, in order to run a side-by-side assembly the specified env
  180. # must include a valid SystemRoot.
  181. if 'SYSTEMROOT' in os.environ:
  182. env['SYSTEMROOT'] = os.environ['SYSTEMROOT']
  183. if 'LD_LIBRARY_PATH' in os.environ:
  184. env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
  185. cls._check_php_version(php_executable_path, env)
  186. cls._check_environment(php_executable_path, env)
  187. if gae_extension_path:
  188. cls._check_gae_extension(php_executable_path, gae_extension_path, env)
  189. def _GenerateConfigForRuntime(self):
  190. """Return a copy of runtime config for starting a PHP runtime instance.
  191. The returned config uses the bundled PHP binaries if none is specified
  192. already through the command line arguments.
  193. Returns:
  194. The created runtime_config_pb2.Config protobuf object.
  195. """
  196. def setattr_if_empty(obj, field, value):
  197. if not getattr(obj, field) and value:
  198. setattr(obj, field, value)
  199. runtime = self._module_configuration.runtime
  200. runtime_config = self._runtime_config_getter()
  201. setattr_if_empty(runtime_config.php_config,
  202. 'php_executable_path',
  203. _get_php_executable_path(runtime))
  204. setattr_if_empty(runtime_config.php_config,
  205. 'gae_extension_path',
  206. _get_php_extension_path('gae_runtime_module', runtime))
  207. setattr_if_empty(runtime_config.php_config,
  208. 'xdebug_extension_path',
  209. _get_php_extension_path('xdebug', runtime))
  210. return runtime_config
  211. def new_instance(self, instance_id, expect_ready_request=False):
  212. """Create and return a new Instance.
  213. Args:
  214. instance_id: A string or integer representing the unique (per module) id
  215. of the instance.
  216. expect_ready_request: If True then the instance will be sent a special
  217. request (i.e. /_ah/warmup or /_ah/start) before it can handle external
  218. requests.
  219. Returns:
  220. The newly created instance.Instance.
  221. """
  222. def instance_config_getter():
  223. runtime_config = self._GenerateConfigForRuntime()
  224. runtime_config.instance_id = str(instance_id)
  225. return runtime_config
  226. php_executable_path = (
  227. self._GenerateConfigForRuntime().php_config.php_executable_path)
  228. gae_extension_path = (
  229. self._GenerateConfigForRuntime().php_config.gae_extension_path)
  230. self._check_binaries(php_executable_path, gae_extension_path)
  231. proxy = http_runtime.HttpRuntimeProxy(
  232. _RUNTIME_ARGS, instance_config_getter, self._module_configuration)
  233. return instance.Instance(self.request_data,
  234. instance_id,
  235. proxy,
  236. self.max_concurrent_requests,
  237. self.max_background_threads,
  238. expect_ready_request)