1.9.30 sync.
[gae.git] / python / google / appengine / tools / devappserver2 / http_proxy.py
blobde22dd59313abbe4855e80a3d22767390c0968f0
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 """Forwards HTTP requests to an application instance on a given host and port.
19 An instance (also referred to as a runtime process) handles dynamic content
20 only. Static files are handled separately.
21 """
23 import contextlib
24 import httplib
25 import logging
26 import urllib
27 import wsgiref.headers
29 from google.appengine.tools.devappserver2 import http_runtime_constants
30 from google.appengine.tools.devappserver2 import http_utils
31 from google.appengine.tools.devappserver2 import instance
32 from google.appengine.tools.devappserver2 import login
33 from google.appengine.tools.devappserver2 import util
36 class HttpProxy:
37 """Forwards HTTP requests to an application instance."""
38 def __init__(self, host, port, instance_died_unexpectedly,
39 instance_logs_getter, error_handler_file, prior_error=None):
40 """Initializer for HttpProxy.
42 Args:
43 host: A hostname or an IP address of where application instance is
44 running.
45 port: Port that application instance is listening on.
46 instance_died_unexpectedly: Function returning True if instance has
47 unexpectedly died.
48 instance_logs_getter: Function returning logs from the instance.
49 error_handler_file: Application specific error handler for default error
50 code if specified (only default error code is supported by
51 Dev AppServer).
52 prior_error: Errors occurred before (for example during creation of an
53 instance). In case prior_error is not None handle will always return
54 corresponding error message without even trying to connect to the
55 instance.
56 """
57 self._host = host
58 self._port = port
59 self._instance_died_unexpectedly = instance_died_unexpectedly
60 self._instance_logs_getter = instance_logs_getter
61 self._error_handler_file = error_handler_file
62 self._prior_error = prior_error
64 def _respond_with_error(self, message, start_response):
65 instance_logs = self._instance_logs_getter()
66 if instance_logs:
67 message += '\n\n' + instance_logs
68 # TODO: change 'text/plain' to 'text/plain; charset=utf-8'
69 # throughout devappserver2.
70 start_response('500 Internal Server Error',
71 [('Content-Type', 'text/plain'),
72 ('Content-Length', str(len(message)))])
73 return message
75 def wait_for_connection(self, retries=100000):
76 """Waits while instance is booting.
78 Args:
79 retries: int, Number of connection retries.
81 Raises:
82 http_utils.HostNotReachable: if host:port can't be reached after given
83 number of retries.
84 """
85 # If there was a prior error, we don't need to wait for a connection.
86 if self._prior_error:
87 return
89 http_utils.wait_for_connection(self._host, self._port, retries)
91 def handle(self, environ, start_response, url_map, match, request_id,
92 request_type):
93 """Serves this request by forwarding it to the runtime process.
95 Args:
96 environ: An environ dict for the request as defined in PEP-333.
97 start_response: A function with semantics defined in PEP-333.
98 url_map: An appinfo.URLMap instance containing the configuration for the
99 handler matching this request.
100 match: A re.MatchObject containing the result of the matched URL pattern.
101 request_id: A unique string id associated with the request.
102 request_type: The type of the request. See instance.*_REQUEST module
103 constants.
105 Yields:
106 A sequence of strings containing the body of the HTTP response.
109 if self._prior_error:
110 logging.error(self._prior_error)
111 yield self._respond_with_error(self._prior_error, start_response)
112 return
114 environ[http_runtime_constants.SCRIPT_HEADER] = match.expand(url_map.script)
115 if request_type == instance.BACKGROUND_REQUEST:
116 environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'background'
117 elif request_type == instance.SHUTDOWN_REQUEST:
118 environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'shutdown'
119 elif request_type == instance.INTERACTIVE_REQUEST:
120 environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'interactive'
122 for name in http_runtime_constants.ENVIRONS_TO_PROPAGATE:
123 if http_runtime_constants.APPENGINE_ENVIRON_PREFIX + name not in environ:
124 value = environ.get(name, None)
125 if value is not None:
126 environ[
127 http_runtime_constants.APPENGINE_ENVIRON_PREFIX + name] = value
128 headers = util.get_headers_from_environ(environ)
129 if environ.get('QUERY_STRING'):
130 url = '%s?%s' % (urllib.quote(environ['PATH_INFO']),
131 environ['QUERY_STRING'])
132 else:
133 url = urllib.quote(environ['PATH_INFO'])
134 if 'CONTENT_LENGTH' in environ:
135 headers['CONTENT-LENGTH'] = environ['CONTENT_LENGTH']
136 data = environ['wsgi.input'].read(int(environ['CONTENT_LENGTH']))
137 else:
138 data = ''
140 cookies = environ.get('HTTP_COOKIE')
141 user_email, admin, user_id = login.get_user_info(cookies)
142 if user_email:
143 nickname, organization = user_email.split('@', 1)
144 else:
145 nickname = ''
146 organization = ''
147 headers[http_runtime_constants.REQUEST_ID_HEADER] = request_id
148 headers[http_runtime_constants.APPENGINE_HEADER_PREFIX + 'User-Id'] = (
149 user_id)
150 headers[http_runtime_constants.APPENGINE_HEADER_PREFIX + 'User-Email'] = (
151 user_email)
152 headers[
153 http_runtime_constants.APPENGINE_HEADER_PREFIX + 'User-Is-Admin'] = (
154 str(int(admin)))
155 headers[
156 http_runtime_constants.APPENGINE_HEADER_PREFIX + 'User-Nickname'] = (
157 nickname)
158 headers[http_runtime_constants.APPENGINE_HEADER_PREFIX +
159 'User-Organization'] = organization
160 headers['X-AppEngine-Country'] = 'ZZ'
161 connection = httplib.HTTPConnection(self._host, self._port)
162 with contextlib.closing(connection):
163 try:
164 connection.connect()
165 connection.request(environ.get('REQUEST_METHOD', 'GET'),
166 url,
167 data,
168 dict(headers.items()))
170 try:
171 response = connection.getresponse()
172 except httplib.HTTPException as e:
173 # The runtime process has written a bad HTTP response. For example,
174 # a Go runtime process may have crashed in app-specific code.
175 yield self._respond_with_error(
176 'the runtime process gave a bad HTTP response: %s' % e,
177 start_response)
178 return
180 # Ensures that we avoid merging repeat headers into a single header,
181 # allowing use of multiple Set-Cookie headers.
182 headers = []
183 for name in response.msg:
184 for value in response.msg.getheaders(name):
185 headers.append((name, value))
187 response_headers = wsgiref.headers.Headers(headers)
189 if self._error_handler_file and (
190 http_runtime_constants.ERROR_CODE_HEADER in response_headers):
191 try:
192 with open(self._error_handler_file) as f:
193 content = f.read()
194 except IOError:
195 content = 'Failed to load error handler'
196 logging.exception('failed to load error file: %s',
197 self._error_handler_file)
198 start_response('500 Internal Server Error',
199 [('Content-Type', 'text/html'),
200 ('Content-Length', str(len(content)))])
201 yield content
202 return
203 del response_headers[http_runtime_constants.ERROR_CODE_HEADER]
204 start_response('%s %s' % (response.status, response.reason),
205 response_headers.items())
207 # Yield the response body in small blocks.
208 while True:
209 try:
210 block = response.read(512)
211 if not block:
212 break
213 yield block
214 except httplib.HTTPException:
215 # The runtime process has encountered a problem, but has not
216 # necessarily crashed. For example, a Go runtime process' HTTP
217 # handler may have panicked in app-specific code (which the http
218 # package will recover from, so the process as a whole doesn't
219 # crash). At this point, we have already proxied onwards the HTTP
220 # header, so we cannot retroactively serve a 500 Internal Server
221 # Error. We silently break here; the runtime process has presumably
222 # already written to stderr (via the Tee).
223 break
224 except Exception:
225 if self._instance_died_unexpectedly():
226 yield self._respond_with_error(
227 'the runtime process for the instance running on port %d has '
228 'unexpectedly quit' % self._port, start_response)
229 else:
230 raise