App Engine Python SDK version 1.9.9
[gae.git] / python / google / appengine / tools / devappserver2 / php_runtime.py
blob28918156bf58d498073796c87ca81ded675d8aae
1 #!/usr/bin/env python
3 # Copyright 2007 Google Inc.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 """Serves content for "script" handlers using the PHP runtime."""
20 import cgi
21 import logging
22 import os
23 import re
24 import subprocess
25 import sys
27 import google
28 from google.appengine.api import appinfo
29 from google.appengine.tools.devappserver2 import http_runtime
30 from google.appengine.tools.devappserver2 import instance
32 from google.appengine.tools.devappserver2 import safe_subprocess
35 _RUNTIME_PATH = os.path.abspath(
36 os.path.join(os.path.dirname(sys.argv[0]), '_php_runtime.py'))
37 _CHECK_ENVIRONMENT_SCRIPT_PATH = os.path.join(
38 os.path.dirname(__file__), 'php', 'check_environment.php')
39 _RUNTIME_ARGS = [sys.executable, _RUNTIME_PATH]
42 class _PHPBinaryError(Exception):
43 pass
46 class _PHPEnvironmentError(Exception):
47 pass
50 class _BadPHPEnvironmentRuntimeProxy(instance.RuntimeProxy):
51 """Serves an error page describing the problem with the user's PHP setup."""
53 def __init__(self, php_executable_path, exception):
54 self._php_executable_path = php_executable_path
55 self._exception = exception
57 def start(self):
58 pass
60 def quit(self):
61 pass
63 def handle(self, environ, start_response, url_map, match, request_id,
64 request_type):
65 """Serves a request by displaying an error page.
67 Args:
68 environ: An environ dict for the request as defined in PEP-333.
69 start_response: A function with semantics defined in PEP-333.
70 url_map: An appinfo.URLMap instance containing the configuration for the
71 handler matching this request.
72 match: A re.MatchObject containing the result of the matched URL pattern.
73 request_id: A unique string id associated with the request.
74 request_type: The type of the request. See instance.*_REQUEST module
75 constants.
77 Yields:
78 A sequence of strings containing the body of the HTTP response.
79 """
80 start_response('500 Internal Server Error',
81 [('Content-Type', 'text/html')])
82 yield '<html><head><title>Invalid PHP Configuration</title></head>'
83 yield '<body>'
84 yield '<title>Invalid PHP Configuration</title>'
85 if isinstance(self._exception, _PHPEnvironmentError):
86 yield '<b>The PHP interpreter specified with the --php_executable_path '
87 yield ' flag (&quot;%s&quot;) is not compatible with the App Engine ' % (
88 self._php_executable_path)
89 yield 'PHP development environment.</b><br>'
90 yield '<br>'
91 yield '<pre>%s</pre>' % self._exception
92 else:
93 yield '<b>%s</b>' % cgi.escape(str(self._exception))
95 yield '</body></html>'
98 class PHPRuntimeInstanceFactory(instance.InstanceFactory):
99 """A factory that creates new PHP runtime Instances."""
101 # A mapping from a php executable path to the _BadPHPEnvironmentRuntimeProxy
102 # descriping why it is not useable. If the php executable is usable then the
103 # path will map to None. Only one PHP executable will be used in a run of the
104 # development server but that is not necessarily the case for tests.
105 _php_binary_to_error_proxy = {}
107 # TODO: Use real script values.
108 START_URL_MAP = appinfo.URLMap(
109 url='/_ah/start',
110 script='$PHP_LIB/default_start_handler',
111 login='admin')
112 WARMUP_URL_MAP = appinfo.URLMap(
113 url='/_ah/warmup',
114 script='$PHP_LIB/default_warmup_handler',
115 login='admin')
116 SUPPORTS_INTERACTIVE_REQUESTS = True
117 FILE_CHANGE_INSTANCE_RESTART_POLICY = instance.NEVER
119 def __init__(self, request_data, runtime_config_getter, module_configuration):
120 """Initializer for PHPRuntimeInstanceFactory.
122 Args:
123 request_data: A wsgi_request_info.WSGIRequestInfo that will be provided
124 with request information for use by API stubs.
125 runtime_config_getter: A function that can be called without arguments
126 and returns the runtime_config_pb2.Config containing the configuration
127 for the runtime.
128 module_configuration: An application_configuration.ModuleConfiguration
129 instance respresenting the configuration of the module that owns the
130 runtime.
132 super(PHPRuntimeInstanceFactory, self).__init__(
133 request_data, 8 if runtime_config_getter().threadsafe else 1)
134 self._runtime_config_getter = runtime_config_getter
135 self._module_configuration = module_configuration
136 self._bad_environment_proxy = None
138 @staticmethod
139 def _check_environment(php_executable_path, application_root):
140 if php_executable_path is None:
141 raise _PHPBinaryError('The development server must be started with the '
142 '--php_executable_path flag set to the path of the '
143 'php-cgi binary.')
145 if not os.path.exists(php_executable_path):
146 raise _PHPBinaryError('The path specified with the --php_executable_path '
147 'flag (%s) does not exist.' % php_executable_path)
149 if not os.access(php_executable_path, os.X_OK):
150 raise _PHPBinaryError('The path specified with the --php_executable_path '
151 'flag (%s) is not executable' % php_executable_path)
153 env = {}
154 # On Windows, in order to run a side-by-side assembly the specified env
155 # must include a valid SystemRoot.
156 if 'SYSTEMROOT' in os.environ:
157 env['SYSTEMROOT'] = os.environ['SYSTEMROOT']
159 args = [php_executable_path, '-v', '-n']
160 version_process = safe_subprocess.start_process(args,
161 stdout=subprocess.PIPE,
162 stderr=subprocess.PIPE,
163 env=env)
164 version_stdout, version_stderr = version_process.communicate()
165 if version_process.returncode:
166 raise _PHPEnvironmentError(
167 '"%s" returned an error [%d]\n%s%s' % (
168 args,
169 version_process.returncode,
170 version_stderr,
171 version_stdout))
173 version_match = re.search(r'PHP (\d+).(\d+)', version_stdout)
174 if version_match is None:
175 raise _PHPEnvironmentError(
176 '"%s" returned an unexpected version string:\n%s%s' % (
177 args,
178 version_stderr,
179 version_stdout))
181 version = tuple(int(v) for v in version_match.groups())
182 if version < (5, 4):
183 raise _PHPEnvironmentError(
184 'The PHP interpreter must be version >= 5.4, %d.%d found' % version)
186 args = [php_executable_path, '-c', application_root, '-f',
187 _CHECK_ENVIRONMENT_SCRIPT_PATH]
188 check_process = safe_subprocess.start_process(
189 args,
190 stdout=subprocess.PIPE,
191 stderr=subprocess.PIPE,
192 env=env)
193 check_process_stdout, _ = check_process.communicate()
194 if check_process.returncode:
195 raise _PHPEnvironmentError(check_process_stdout)
197 def new_instance(self, instance_id, expect_ready_request=False):
198 """Create and return a new Instance.
200 Args:
201 instance_id: A string or integer representing the unique (per module) id
202 of the instance.
203 expect_ready_request: If True then the instance will be sent a special
204 request (i.e. /_ah/warmup or /_ah/start) before it can handle external
205 requests.
207 Returns:
208 The newly created instance.Instance.
211 def instance_config_getter():
212 runtime_config = self._runtime_config_getter()
213 runtime_config.instance_id = str(instance_id)
214 return runtime_config
216 php_executable_path = (
217 self._runtime_config_getter().php_config.php_executable_path)
219 if php_executable_path not in self._php_binary_to_error_proxy:
220 try:
221 self._check_environment(php_executable_path,
222 self._runtime_config_getter().application_root)
223 except Exception as e:
224 self._php_binary_to_error_proxy[php_executable_path] = (
225 _BadPHPEnvironmentRuntimeProxy(php_executable_path, e))
226 logging.exception('The PHP runtime is not available')
227 else:
228 self._php_binary_to_error_proxy[php_executable_path] = None
230 proxy = self._php_binary_to_error_proxy[php_executable_path]
231 if proxy is None:
232 proxy = http_runtime.HttpRuntimeProxy(_RUNTIME_ARGS,
233 instance_config_getter,
234 self._module_configuration)
235 return instance.Instance(self.request_data,
236 instance_id,
237 proxy,
238 self.max_concurrent_requests,
239 self.max_background_threads,
240 expect_ready_request)