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.
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
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.
43 host: A hostname or an IP address of where application instance is
45 port: Port that application instance is listening on.
46 instance_died_unexpectedly: Function returning True if instance has
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
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
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
()
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
)))])
75 def wait_for_connection(self
, retries
=100000):
76 """Waits while instance is booting.
79 retries: int, Number of connection retries.
82 http_utils.HostNotReachable: if host:port can't be reached after given
85 # If there was a prior error, we don't need to wait for a connection.
89 http_utils
.wait_for_connection(self
._host
, self
._port
, retries
)
91 def handle(self
, environ
, start_response
, url_map
, match
, request_id
,
93 """Serves this request by forwarding it to the runtime process.
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
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
)
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:
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'])
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']))
140 cookies
= environ
.get('HTTP_COOKIE')
141 user_email
, admin
, user_id
= login
.get_user_info(cookies
)
143 nickname
, organization
= user_email
.split('@', 1)
147 headers
[http_runtime_constants
.REQUEST_ID_HEADER
] = request_id
148 headers
[http_runtime_constants
.APPENGINE_HEADER_PREFIX
+ 'User-Id'] = (
150 headers
[http_runtime_constants
.APPENGINE_HEADER_PREFIX
+ 'User-Email'] = (
153 http_runtime_constants
.APPENGINE_HEADER_PREFIX
+ 'User-Is-Admin'] = (
156 http_runtime_constants
.APPENGINE_HEADER_PREFIX
+ 'User-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
):
165 connection
.request(environ
.get('REQUEST_METHOD', 'GET'),
168 dict(headers
.items()))
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
,
180 # Ensures that we avoid merging repeat headers into a single header,
181 # allowing use of multiple Set-Cookie 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
):
192 with
open(self
._error
_handler
_file
) as f
:
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
)))])
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.
210 block
= response
.read(512)
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).
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
)