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.
28 import wsgiref
.headers
30 from google
.appengine
.tools
.devappserver2
import http_runtime_constants
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 Error(Exception):
37 """Base class for errors in this module."""
40 class HostNotReachable(Error
):
41 """Raised if host can't be reached at given port."""
45 """Forwards HTTP requests to an application instance."""
46 def __init__(self
, host
, port
, instance_died_unexpectedly
,
47 instance_logs_getter
, error_handler_file
, prior_error
=None):
48 """Initializer for HttpProxy.
51 host: A hostname or an IP address of where application instance is
53 port: Port that application instance is listening on.
54 instance_died_unexpectedly: Function returning True if instance has
56 instance_logs_getter: Function returning logs from the instance.
57 error_handler_file: Application specific error handler for default error
58 code if specified (only default error code is supported by
60 prior_error: Errors occurred before (for example during creation of an
61 instance). In case prior_error is not None handle will always return
62 corresponding error message without even trying to connect to the
67 self
._instance
_died
_unexpectedly
= instance_died_unexpectedly
68 self
._instance
_logs
_getter
= instance_logs_getter
69 self
._error
_handler
_file
= error_handler_file
70 self
._prior
_error
= prior_error
72 def _respond_with_error(self
, message
, start_response
):
73 instance_logs
= self
._instance
_logs
_getter
()
75 message
+= '\n\n' + instance_logs
76 # TODO: change 'text/plain' to 'text/plain; charset=utf-8'
77 # throughout devappserver2.
78 start_response('500 Internal Server Error',
79 [('Content-Type', 'text/plain'),
80 ('Content-Length', str(len(message
)))])
83 def wait_for_connection(self
, retries
=100000):
84 """Waits while instance is booting.
87 retries: int, Number of connection retries.
90 HostNotReachable: if host:port can't be reached after given number of
93 # If there was a prior error, we don't need to wait for a connection.
98 connection
= httplib
.HTTPConnection(self
._host
, self
._port
)
99 with contextlib
.closing(connection
):
102 except (socket
.error
, httplib
.HTTPException
):
107 while not ping() and retries
> 0:
110 raise HostNotReachable(
111 'Cannot connect to the instance on {host}:{port}'.format(
112 host
=self
._host
, port
=self
._port
))
114 def handle(self
, environ
, start_response
, url_map
, match
, request_id
,
116 """Serves this request by forwarding it to the runtime process.
119 environ: An environ dict for the request as defined in PEP-333.
120 start_response: A function with semantics defined in PEP-333.
121 url_map: An appinfo.URLMap instance containing the configuration for the
122 handler matching this request.
123 match: A re.MatchObject containing the result of the matched URL pattern.
124 request_id: A unique string id associated with the request.
125 request_type: The type of the request. See instance.*_REQUEST module
129 A sequence of strings containing the body of the HTTP response.
132 if self
._prior
_error
:
133 logging
.error(self
._prior
_error
)
134 yield self
._respond
_with
_error
(self
._prior
_error
, start_response
)
137 environ
[http_runtime_constants
.SCRIPT_HEADER
] = match
.expand(url_map
.script
)
138 if request_type
== instance
.BACKGROUND_REQUEST
:
139 environ
[http_runtime_constants
.REQUEST_TYPE_HEADER
] = 'background'
140 elif request_type
== instance
.SHUTDOWN_REQUEST
:
141 environ
[http_runtime_constants
.REQUEST_TYPE_HEADER
] = 'shutdown'
142 elif request_type
== instance
.INTERACTIVE_REQUEST
:
143 environ
[http_runtime_constants
.REQUEST_TYPE_HEADER
] = 'interactive'
145 for name
in http_runtime_constants
.ENVIRONS_TO_PROPAGATE
:
146 if http_runtime_constants
.APPENGINE_ENVIRON_PREFIX
+ name
not in environ
:
147 value
= environ
.get(name
, None)
148 if value
is not None:
150 http_runtime_constants
.APPENGINE_ENVIRON_PREFIX
+ name
] = value
151 headers
= util
.get_headers_from_environ(environ
)
152 if environ
.get('QUERY_STRING'):
153 url
= '%s?%s' % (urllib
.quote(environ
['PATH_INFO']),
154 environ
['QUERY_STRING'])
156 url
= urllib
.quote(environ
['PATH_INFO'])
157 if 'CONTENT_LENGTH' in environ
:
158 headers
['CONTENT-LENGTH'] = environ
['CONTENT_LENGTH']
159 data
= environ
['wsgi.input'].read(int(environ
['CONTENT_LENGTH']))
163 cookies
= environ
.get('HTTP_COOKIE')
164 user_email
, admin
, user_id
= login
.get_user_info(cookies
)
166 nickname
, organization
= user_email
.split('@', 1)
170 headers
[http_runtime_constants
.REQUEST_ID_HEADER
] = request_id
171 headers
[http_runtime_constants
.APPENGINE_HEADER_PREFIX
+ 'User-Id'] = (
173 headers
[http_runtime_constants
.APPENGINE_HEADER_PREFIX
+ 'User-Email'] = (
176 http_runtime_constants
.APPENGINE_HEADER_PREFIX
+ 'User-Is-Admin'] = (
179 http_runtime_constants
.APPENGINE_HEADER_PREFIX
+ 'User-Nickname'] = (
181 headers
[http_runtime_constants
.APPENGINE_HEADER_PREFIX
+
182 'User-Organization'] = organization
183 headers
['X-AppEngine-Country'] = 'ZZ'
184 connection
= httplib
.HTTPConnection(self
._host
, self
._port
)
185 with contextlib
.closing(connection
):
188 connection
.request(environ
.get('REQUEST_METHOD', 'GET'),
191 dict(headers
.items()))
194 response
= connection
.getresponse()
195 except httplib
.HTTPException
as e
:
196 # The runtime process has written a bad HTTP response. For example,
197 # a Go runtime process may have crashed in app-specific code.
198 yield self
._respond
_with
_error
(
199 'the runtime process gave a bad HTTP response: %s' % e
,
203 # Ensures that we avoid merging repeat headers into a single header,
204 # allowing use of multiple Set-Cookie headers.
206 for name
in response
.msg
:
207 for value
in response
.msg
.getheaders(name
):
208 headers
.append((name
, value
))
210 response_headers
= wsgiref
.headers
.Headers(headers
)
212 if self
._error
_handler
_file
and (
213 http_runtime_constants
.ERROR_CODE_HEADER
in response_headers
):
215 with
open(self
._error
_handler
_file
) as f
:
218 content
= 'Failed to load error handler'
219 logging
.exception('failed to load error file: %s',
220 self
._error
_handler
_file
)
221 start_response('500 Internal Server Error',
222 [('Content-Type', 'text/html'),
223 ('Content-Length', str(len(content
)))])
226 del response_headers
[http_runtime_constants
.ERROR_CODE_HEADER
]
227 start_response('%s %s' % (response
.status
, response
.reason
),
228 response_headers
.items())
230 # Yield the response body in small blocks.
233 block
= response
.read(512)
237 except httplib
.HTTPException
:
238 # The runtime process has encountered a problem, but has not
239 # necessarily crashed. For example, a Go runtime process' HTTP
240 # handler may have panicked in app-specific code (which the http
241 # package will recover from, so the process as a whole doesn't
242 # crash). At this point, we have already proxied onwards the HTTP
243 # header, so we cannot retroactively serve a 500 Internal Server
244 # Error. We silently break here; the runtime process has presumably
245 # already written to stderr (via the Tee).
248 if self
._instance
_died
_unexpectedly
():
249 yield self
._respond
_with
_error
(
250 'the runtime process for the instance running on port %d has '
251 'unexpectedly quit' % self
._port
, start_response
)