def test_connection_error_process_quit(self): self.proxy = http_proxy.HttpProxy( host='localhost', port=123, instance_died_unexpectedly=lambda: True, instance_logs_getter=get_instance_logs, error_handler_file=None) login.get_user_info(None).AndReturn(('', False, '')) http.client.HTTPConnection.connect().AndRaise(socket.error()) http.client.HTTPConnection.close() self.mox.ReplayAll() expected_headers = { 'Content-Type': 'text/plain', 'Content-Length': '78', } expected_content = ('the runtime process for the instance running on port ' '123 has unexpectedly quit') self.assertResponse('500 Internal Server Error', expected_headers, expected_content, self.proxy.handle, {'PATH_INFO': '/'}, url_map=self.url_map, match=re.match(self.url_map.url, '/get%20error'), request_id='request id', request_type=instance.NORMAL_REQUEST) self.mox.VerifyAll()
def test_connection_error_process_quit(self): self.proxy = http_runtime.HttpRuntimeProxy( ['/runtime'], self.runtime_config_getter, appinfo.AppInfoExternal()) self.proxy._process = self.mox.CreateMockAnything() self.proxy._port = 123 login.get_user_info(None).AndReturn(('', False, '')) httplib.HTTPConnection.connect().AndRaise(socket.error()) self.proxy._process.poll().AndReturn(1) self.proxy._stderr_tee = FakeTee('') httplib.HTTPConnection.close() self.mox.ReplayAll() expected_headers = { 'Content-Type': 'text/plain', 'Content-Length': '78', } expected_content = ('the runtime process for the instance running on port ' '123 has unexpectedly quit') self.assertResponse('500 Internal Server Error', expected_headers, expected_content, self.proxy.handle, {'PATH_INFO': '/'}, url_map=self.url_map, match=re.match(self.url_map.url, '/get%20error'), request_id='request id', request_type=instance.NORMAL_REQUEST) self.mox.VerifyAll()
def test_handle_with_error(self): error_handler_file = os.path.join(self.tmpdir, 'error.html') with open(error_handler_file, 'w') as f: f.write('error') self.proxy = http_proxy.HttpProxy( host='localhost', port=23456, instance_died_unexpectedly=lambda: False, instance_logs_getter=get_instance_logs, error_handler_file=error_handler_file) response = FakeHttpResponse( 500, 'Internal Server Error', [(http_runtime_constants.ERROR_CODE_HEADER, '1')], '') login.get_user_info(None).AndReturn(('', False, '')) http.client.HTTPConnection.connect() http.client.HTTPConnection.request( 'GET', '/get%20error', '', {'HEADER': 'value', http_runtime_constants.REQUEST_ID_HEADER: 'request id', 'X-AppEngine-Country': 'ZZ', 'X-Appengine-User-Email': '', 'X-Appengine-User-Id': '', 'X-Appengine-User-Is-Admin': '0', 'X-Appengine-User-Nickname': '', 'X-Appengine-User-Organization': '', 'X-APPENGINE-DEV-SCRIPT': 'get.py', 'X-APPENGINE-SERVER-NAME': 'localhost', 'X-APPENGINE-SERVER-PORT': '8080', 'X-APPENGINE-SERVER-PROTOCOL': 'HTTP/1.1', }) http.client.HTTPConnection.getresponse().AndReturn(response) http.client.HTTPConnection.close() environ = {'HTTP_HEADER': 'value', 'PATH_INFO': '/get error', 'QUERY_STRING': '', 'HTTP_X_APPENGINE_USER_ID': '123', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '8080', 'SERVER_PROTOCOL': 'HTTP/1.1', } self.mox.ReplayAll() expected_headers = { 'Content-Type': 'text/html', 'Content-Length': '5', } self.assertResponse('500 Internal Server Error', expected_headers, 'error', self.proxy.handle, environ, url_map=self.url_map, match=re.match(self.url_map.url, '/get%20error'), request_id='request id', request_type=instance.NORMAL_REQUEST) self.mox.VerifyAll()
def test_http_response_early_failure(self): header = ('the runtime process gave a bad HTTP response: ' 'IncompleteRead(0 bytes read)\n\n') def dave_message(): return "I'm sorry, Dave. I'm afraid I can't do that.\n" self.proxy = http_proxy.HttpProxy( host='localhost', port=23456, instance_died_unexpectedly=lambda: False, instance_logs_getter=dave_message, error_handler_file=None) login.get_user_info(None).AndReturn(('', False, '')) http.client.HTTPConnection.connect() http.client.HTTPConnection.request( 'GET', '/get%20request?key=value', '', {'HEADER': 'value', http_runtime_constants.REQUEST_ID_HEADER: 'request id', 'X-AppEngine-Country': 'ZZ', 'X-Appengine-User-Email': '', 'X-Appengine-User-Id': '', 'X-Appengine-User-Is-Admin': '0', 'X-Appengine-User-Nickname': '', 'X-Appengine-User-Organization': '', 'X-APPENGINE-DEV-SCRIPT': 'get.py', 'X-APPENGINE-SERVER-NAME': 'localhost', 'X-APPENGINE-SERVER-PORT': '8080', 'X-APPENGINE-SERVER-PROTOCOL': 'HTTP/1.1', }) http.client.HTTPConnection.getresponse().AndRaise(http.client.IncompleteRead('')) http.client.HTTPConnection.close() environ = {'HTTP_HEADER': 'value', 'PATH_INFO': '/get request', 'QUERY_STRING': 'key=value', 'HTTP_X_APPENGINE_USER_ID': '123', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '8080', 'SERVER_PROTOCOL': 'HTTP/1.1', } self.mox.ReplayAll() expected_headers = { 'Content-Type': 'text/plain', 'Content-Length': '%d' % (len(header) + len(dave_message())) } self.assertResponse('500 Internal Server Error', expected_headers, header + dave_message(), self.proxy.handle, environ, url_map=self.url_map, match=re.match(self.url_map.url, '/get%20request'), request_id='request id', request_type=instance.NORMAL_REQUEST) self.mox.VerifyAll()
def test_connection_error(self): login.get_user_info(None).AndReturn(('', False, '')) http.client.HTTPConnection.connect().AndRaise(socket.error()) http.client.HTTPConnection.close() self.mox.ReplayAll() self.assertRaises(socket.error, self.proxy.handle( {'PATH_INFO': '/'}, start_response=None, # Not used. url_map=self.url_map, match=re.match(self.url_map.url, '/get%20error'), request_id='request id', request_type=instance.NORMAL_REQUEST).__next__) self.mox.VerifyAll()
def test_http_response_late_failure(self): line0 = "I know I've made some very poor decisions recently...\n" def dave_message(): return "I'm afraid. I'm afraid, Dave.\n" self.proxy = http_proxy.HttpProxy( host='localhost', port=23456, instance_died_unexpectedly=lambda: False, instance_logs_getter=dave_message, error_handler_file=None) response = FakeHttpResponse(200, 'OK', [], line0) response.partial_read_error = http.client.IncompleteRead('') login.get_user_info(None).AndReturn(('', False, '')) http.client.HTTPConnection.connect() http.client.HTTPConnection.request( 'GET', '/get%20request?key=value', '', {'HEADER': 'value', http_runtime_constants.REQUEST_ID_HEADER: 'request id', 'X-AppEngine-Country': 'ZZ', 'X-Appengine-User-Email': '', 'X-Appengine-User-Id': '', 'X-Appengine-User-Is-Admin': '0', 'X-Appengine-User-Nickname': '', 'X-Appengine-User-Organization': '', 'X-APPENGINE-DEV-SCRIPT': 'get.py', 'X-APPENGINE-SERVER-NAME': 'localhost', 'X-APPENGINE-SERVER-PORT': '8080', 'X-APPENGINE-SERVER-PROTOCOL': 'HTTP/1.1', }) http.client.HTTPConnection.getresponse().AndReturn(response) http.client.HTTPConnection.close() environ = {'HTTP_HEADER': 'value', 'PATH_INFO': '/get request', 'QUERY_STRING': 'key=value', 'HTTP_X_APPENGINE_USER_ID': '123', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '8080', 'SERVER_PROTOCOL': 'HTTP/1.1', } self.mox.ReplayAll() self.assertResponse('200 OK', {}, line0, self.proxy.handle, environ, url_map=self.url_map, match=re.match(self.url_map.url, '/get%20request'), request_id='request id', request_type=instance.NORMAL_REQUEST) self.mox.VerifyAll()
def test_get_user_info_does_not_exist(self): """Tests the get_user_info function when the cookie is not present.""" http_cookie = 'one=two; three=four' email, admin, user_id = login.get_user_info(http_cookie, cookie_name=COOKIE_NAME) self.assertEqual('', email) self.assertFalse(admin)
def test_handle_post(self): response = FakeHttpResponse(200, 'OK', [('Foo', 'Bar')], 'response') login.get_user_info('cookie').AndReturn(('*****@*****.**', True, '12345')) httplib.HTTPConnection.connect() httplib.HTTPConnection.request( 'POST', '/post', 'post data', {'HEADER': 'value', 'COOKIE': 'cookie', 'CONTENT-TYPE': 'text/plain', 'CONTENT-LENGTH': '9', http_runtime_constants.REQUEST_ID_HEADER: 'request id', 'X-AppEngine-Country': 'ZZ', 'X-Appengine-Internal-Datacenter': 'us1', 'X-Appengine-Internal-User-Email': '*****@*****.**', 'X-Appengine-Internal-User-Id': '12345', 'X-Appengine-Internal-User-Is-Admin': '1', 'X-Appengine-Internal-User-Nickname': 'user', 'X-Appengine-Internal-User-Organization': 'example.com', 'X-APPENGINE-INTERNAL-SCRIPT': 'post.py', 'X-APPENGINE-INTERNAL-SERVER-NAME': 'localhost', 'X-APPENGINE-INTERNAL-SERVER-PORT': '8080', 'X-APPENGINE-INTERNAL-SERVER-PROTOCOL': 'HTTP/1.1', }) httplib.HTTPConnection.getresponse().AndReturn(response) httplib.HTTPConnection.close() environ = {'HTTP_HEADER': 'value', 'PATH_INFO': '/post', 'wsgi.input': cStringIO.StringIO('post data'), 'CONTENT_LENGTH': '9', 'CONTENT_TYPE': 'text/plain', 'REQUEST_METHOD': 'POST', 'HTTP_COOKIE': 'cookie', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '8080', 'SERVER_PROTOCOL': 'HTTP/1.1', } self.mox.ReplayAll() expected_headers = { 'Foo': 'Bar', } self.assertResponse('200 OK', expected_headers, 'response', self.proxy.handle, environ, url_map=self.url_map, match=re.match(self.url_map.url, '/post'), request_id='request id', request_type=instance.NORMAL_REQUEST) self.mox.VerifyAll()
def test_http_response_early_failure(self): header = ('the runtime process gave a bad HTTP response: ' 'IncompleteRead(0 bytes read)\n\n') stderr0 = "I'm sorry, Dave. I'm afraid I can't do that.\n" self.proxy._stderr_tee = FakeTee(stderr0) login.get_user_info(None).AndReturn(('', False, '')) httplib.HTTPConnection.connect() httplib.HTTPConnection.request( 'GET', '/get%20request?key=value', '', {'HEADER': 'value', http_runtime_constants.REQUEST_ID_HEADER: 'request id', 'X-AppEngine-Country': 'ZZ', 'X-Appengine-Internal-User-Email': '', 'X-Appengine-Internal-User-Id': '', 'X-Appengine-Internal-User-Is-Admin': '0', 'X-Appengine-Internal-User-Nickname': '', 'X-Appengine-Internal-User-Organization': '', 'X-APPENGINE-INTERNAL-SCRIPT': 'get.py', 'X-APPENGINE-INTERNAL-SERVER-NAME': 'localhost', 'X-APPENGINE-INTERNAL-SERVER-PORT': '8080', 'X-APPENGINE-INTERNAL-SERVER-PROTOCOL': 'HTTP/1.1', }) httplib.HTTPConnection.getresponse().AndRaise(httplib.IncompleteRead('')) httplib.HTTPConnection.close() environ = {'HTTP_HEADER': 'value', 'PATH_INFO': '/get request', 'QUERY_STRING': 'key=value', 'HTTP_X_APPENGINE_INTERNAL_USER_ID': '123', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '8080', 'SERVER_PROTOCOL': 'HTTP/1.1', } self.mox.ReplayAll() expected_headers = { 'Content-Type': 'text/plain', 'Content-Length': '121',#str(len(header) + len(stderr0)), } self.assertResponse('500 Internal Server Error', expected_headers, header + stderr0, self.proxy.handle, environ, url_map=self.url_map, match=re.match(self.url_map.url, '/get%20request'), request_id='request id', request_type=instance.NORMAL_REQUEST) self.mox.VerifyAll()
def test_connection_error(self): self.proxy = http_runtime.HttpRuntimeProxy( ['/runtime'], self.runtime_config_getter, appinfo.AppInfoExternal()) self.proxy._process = self.mox.CreateMockAnything() login.get_user_info(None).AndReturn(('', False, '')) httplib.HTTPConnection.connect().AndRaise(socket.error()) self.proxy._process.poll().AndReturn(None) httplib.HTTPConnection.close() self.mox.ReplayAll() self.assertRaises(socket.error, self.proxy.handle( {'PATH_INFO': '/'}, start_response=None, # Not used. url_map=self.url_map, match=re.match(self.url_map.url, '/get%20error'), request_id='request id', request_type=instance.NORMAL_REQUEST).next) self.mox.VerifyAll()
def test_get_user_info_not_admin(self): """Tests the get_user_info function when the admin field is False.""" cookie_value = self.get_cookie_value(EMAIL, NICKNAME, False) http_cookie = 'one=two; %s=%s; three=four' % (COOKIE_NAME, cookie_value) email, admin, user_id = login.get_user_info(http_cookie, cookie_name=COOKIE_NAME) self.assertEqual(EMAIL, email) self.assertFalse(admin)
def test_handle_with_error_missing_error_handler(self): response = FakeHttpResponse( 500, 'Internal Server Error', [(http_runtime_constants.ERROR_CODE_HEADER, '1')], '') login.get_user_info(None).AndReturn(('', False, '')) httplib.HTTPConnection.connect() httplib.HTTPConnection.request( 'GET', '/get%20error', '', {'HEADER': 'value', http_runtime_constants.REQUEST_ID_HEADER: 'request id', 'X-AppEngine-Country': 'ZZ', 'X-Appengine-Internal-Datacenter': 'us1', 'X-Appengine-Internal-User-Email': '', 'X-Appengine-Internal-User-Id': '', 'X-Appengine-Internal-User-Is-Admin': '0', 'X-Appengine-Internal-User-Nickname': '', 'X-Appengine-Internal-User-Organization': '', 'X-APPENGINE-INTERNAL-SCRIPT': 'get.py', 'X-APPENGINE-INTERNAL-SERVER-NAME': 'localhost', 'X-APPENGINE-INTERNAL-SERVER-PORT': '8080', 'X-APPENGINE-INTERNAL-SERVER-PROTOCOL': 'HTTP/1.1', }) httplib.HTTPConnection.getresponse().AndReturn(response) httplib.HTTPConnection.close() environ = {'HTTP_HEADER': 'value', 'PATH_INFO': '/get error', 'QUERY_STRING': '', 'HTTP_X_APPENGINE_INTERNAL_USER_ID': '123', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '8080', 'SERVER_PROTOCOL': 'HTTP/1.1', } self.mox.ReplayAll() expected_headers = { 'Content-Type': 'text/html', 'Content-Length': '28', } self.assertResponse('500 Internal Server Error', expected_headers, 'Failed to load error handler', self.proxy.handle, environ, url_map=self.url_map, match=re.match(self.url_map.url, '/get%20error'), request_id='request id', request_type=instance.NORMAL_REQUEST) self.mox.VerifyAll()
def test_http_response_late_failure(self): line0 = "I know I've made some very poor decisions recently...\n" line1 = "I'm afraid. I'm afraid, Dave.\n" line2 = "Dave, my mind is going. I can feel it.\n" response = FakeHttpResponse(200, 'OK', [], line0) response.partial_read_error = httplib.IncompleteRead('') self.proxy._stderr_tee = FakeTee(line1 + line2) login.get_user_info(None).AndReturn(('', False, '')) httplib.HTTPConnection.connect() httplib.HTTPConnection.request( 'GET', '/get%20request?key=value', '', {'HEADER': 'value', http_runtime_constants.REQUEST_ID_HEADER: 'request id', 'X-AppEngine-Country': 'ZZ', 'X-Appengine-Internal-User-Email': '', 'X-Appengine-Internal-User-Id': '', 'X-Appengine-Internal-User-Is-Admin': '0', 'X-Appengine-Internal-User-Nickname': '', 'X-Appengine-Internal-User-Organization': '', 'X-APPENGINE-INTERNAL-SCRIPT': 'get.py', 'X-APPENGINE-INTERNAL-SERVER-NAME': 'localhost', 'X-APPENGINE-INTERNAL-SERVER-PORT': '8080', 'X-APPENGINE-INTERNAL-SERVER-PROTOCOL': 'HTTP/1.1', }) httplib.HTTPConnection.getresponse().AndReturn(response) httplib.HTTPConnection.close() environ = {'HTTP_HEADER': 'value', 'PATH_INFO': '/get request', 'QUERY_STRING': 'key=value', 'HTTP_X_APPENGINE_INTERNAL_USER_ID': '123', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '8080', 'SERVER_PROTOCOL': 'HTTP/1.1', } self.mox.ReplayAll() self.assertResponse('200 OK', {}, line0, self.proxy.handle, environ, url_map=self.url_map, match=re.match(self.url_map.url, '/get%20request'), request_id='request id', request_type=instance.NORMAL_REQUEST) self.mox.VerifyAll()
def test_handle_ssl(self): response = FakeHttpResponse(200, 'OK', [('Foo', 'a'), ('Foo', 'b'), ('Var', 'c')], 'response') login.get_user_info(None).AndReturn(('', False, '')) httplib.HTTPConnection.connect() # pylint: disable=no-value-for-parameter httplib.HTTPConnection.request( 'GET', '/get%20request?key=value', None, {'HEADER': 'value', http_runtime_constants.REQUEST_ID_HEADER: 'request id', 'X-AppEngine-Country': 'ZZ', 'X-Appengine-User-Email': '', 'X-Appengine-User-Id': '', 'X-Appengine-User-Is-Admin': '0', 'X-Appengine-User-Nickname': '', 'X-Appengine-User-Organization': '', 'X-Appengine-Dev-LocalSSL': '1', 'X-APPENGINE-DEV-SCRIPT': 'get.py', 'X-APPENGINE-SERVER-NAME': 'localhost', 'X-APPENGINE-SERVER-PORT': '8080', 'X-APPENGINE-SERVER-PROTOCOL': 'HTTP/1.1', }) httplib.HTTPConnection.getresponse().AndReturn(response) # pylint: disable=no-value-for-parameter httplib.HTTPConnection.close() # pylint: disable=no-value-for-parameter environ = {'HTTP_HEADER': 'value', 'PATH_INFO': '/get request', 'QUERY_STRING': 'key=value', 'HTTP_X_APPENGINE_USER_ID': '123', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '8080', 'SERVER_PROTOCOL': 'HTTP/1.1', 'wsgi.url_scheme': 'https', } self.mox.ReplayAll() expected_headers = [('Foo', 'a'), ('Foo', 'b'), ('Var', 'c')] self.assertResponse('200 OK', expected_headers, 'response', self.proxy.handle, environ, url_map=self.url_map, match=re.match(self.url_map.url, '/get%20request'), request_id='request id', request_type=instance.NORMAL_REQUEST) self.mox.VerifyAll()
def test_get_user_info_not_admin(self): """Tests the get_user_info function when the admin field is False.""" cookie_value = '%s:False:%s' % (EMAIL, USER_ID) http_cookie = 'one=two; %s=%s; three=four' % (COOKIE_NAME, cookie_value) email, admin, user_id = login.get_user_info(http_cookie, cookie_name=COOKIE_NAME) self.assertEqual(EMAIL, email) self.assertFalse(admin) self.assertEqual(USER_ID, user_id)
def test_get_user_info_invalid_email(self): """Tests the get_user_info function when the admin field is False.""" cookie_value = 'foo:True:%s' % USER_ID http_cookie = 'one=two; %s=%s; three=four' % (COOKIE_NAME, cookie_value) email, admin, user_id = login.get_user_info(http_cookie, cookie_name=COOKIE_NAME) self.assertEqual('', email) self.assertFalse(admin) self.assertEqual('', user_id)
def test_get_user_info_admin(self): """Tests the get_user_info function when the admin field is True.""" cookie_value = '%s:True:%s' % (EMAIL, USER_ID) http_cookie = 'one=two; %s=%s; three=four' % (COOKIE_NAME, cookie_value) email, admin, user_id = login.get_user_info(http_cookie, cookie_name=COOKIE_NAME) self.assertEqual(EMAIL, email) self.assertTrue(admin) self.assertEqual(USER_ID, user_id)
def handle_authorization(self, environ, start_response): """Handles the response if the user is not authorized to access this URL. The authorization check is based on the 'login' setting for this handler, configured by the supplied url_map. Args: environ: An environ dict for the current request as defined in PEP-333. start_response: A function with semantics defined in PEP-333. Returns: An iterable over strings containing the body of an HTTP response, if the authorization check fails or the login UI must be displayed. None if the user is authorized to access the resource. """ admin_only = self._url_map.login == appinfo.LOGIN_ADMIN requires_login = self._url_map.login == appinfo.LOGIN_REQUIRED or admin_only auth_fail_action = self._url_map.auth_fail_action cookies = environ.get('HTTP_COOKIE') email_addr, admin, _ = login.get_user_info(cookies) # AppScale: Here we check to see if our secret hash is in the header which # authenticates that the task was created from an AppScale deployment and # not an unauthorized party. if (constants.FAKE_IS_ADMIN_HEADER in environ and self._secret_hash == environ[constants.FAKE_IS_ADMIN_HEADER]): admin = True if constants.FAKE_LOGGED_IN_HEADER in environ: email_addr = 'Fake User' # admin has an effect only with login: admin (not login: required). if requires_login and not email_addr and not (admin and admin_only): if auth_fail_action == appinfo.AUTH_FAIL_ACTION_REDIRECT: logging.debug('login required, redirecting user') return login.login_redirect(wsgiref.util.application_uri(environ), wsgiref.util.request_uri(environ), start_response) elif auth_fail_action == appinfo.AUTH_FAIL_ACTION_UNAUTHORIZED: logging.debug('login required, user unauthorized') start_response('401 Not authorized', [('Content-Type', 'text/html'), ('Cache-Control', 'no-cache')]) return ['Login required to view page.'] elif admin_only and not admin: logging.debug('admin required, user unauthorized') start_response('401 Not authorized', [('Content-Type', 'text/html'), ('Cache-Control', 'no-cache')]) return ['Current logged in user %s is not ' 'authorized to view this page.' % email_addr] # Authorization check succeeded return None
def test_get_user_info_invalid_email(self): """Tests the get_user_info function when the admin field is False.""" cookie_value = 'foo:True:%s' % USER_ID http_cookie = 'one=two; %s=%s; three=four' % (COOKIE_NAME, cookie_value) email, admin, user_id = login.get_user_info(http_cookie, cookie_name=COOKIE_NAME) self.assertEqual('', email) self.assertFalse(admin) self.assertEqual('', user_id)
def test_get_user_info_bad_cookie(self): """Tests the get_user_info function when the cookie is malformed.""" cookie_name = 'SinaRot/g/get' # seen in the wild cookie_value = 'blah' http_cookie = '%s=%s' % (cookie_name, cookie_value) email, admin, user_id = login.get_user_info(http_cookie, cookie_name=cookie_name) self.assertEqual('', email) self.assertFalse(admin)
def test_get_user_info_admin(self): """Tests the get_user_info function when the admin field is True.""" cookie_value = self.get_cookie_value(EMAIL, NICKNAME, True) http_cookie = 'one=two; %s=%s; three=four' % (COOKIE_NAME, cookie_value) email, admin, _ = login.get_user_info(http_cookie, cookie_name=COOKIE_NAME) self.assertEqual(EMAIL, email) self.assertTrue(admin)
def test_get_user_info_bad_cookie(self): """Tests the get_user_info function when the cookie is malformed.""" cookie_name = 'SinaRot/g/get' # seen in the wild cookie_value = 'blah' http_cookie = '%s=%s' % (cookie_name, cookie_value) email, admin, user_id = login.get_user_info(http_cookie, cookie_name=cookie_name) self.assertEqual('', email) self.assertFalse(admin)
def test_handle_with_error_no_error_handler(self): self.proxy = http_runtime.HttpRuntimeProxy( ['/runtime'], self.runtime_config_getter, appinfo.AppInfoExternal()) self.proxy._port = 23456 response = FakeHttpResponse( 500, 'Internal Server Error', [(http_runtime_constants.ERROR_CODE_HEADER, '1')], '') login.get_user_info(None).AndReturn(('', False, '')) httplib.HTTPConnection.connect() httplib.HTTPConnection.request( 'GET', '/get%20error', '', {'HEADER': 'value', http_runtime_constants.REQUEST_ID_HEADER: 'request id', 'X-AppEngine-Country': 'ZZ', 'X-Appengine-Internal-User-Email': '', 'X-Appengine-Internal-User-Id': '', 'X-Appengine-Internal-User-Is-Admin': '0', 'X-Appengine-Internal-User-Nickname': '', 'X-Appengine-Internal-User-Organization': '', 'X-APPENGINE-INTERNAL-SCRIPT': 'get.py', 'X-APPENGINE-INTERNAL-SERVER-NAME': 'localhost', 'X-APPENGINE-INTERNAL-SERVER-PORT': '8080', 'X-APPENGINE-INTERNAL-SERVER-PROTOCOL': 'HTTP/1.1', }) httplib.HTTPConnection.getresponse().AndReturn(response) httplib.HTTPConnection.close() environ = {'HTTP_HEADER': 'value', 'PATH_INFO': '/get error', 'QUERY_STRING': '', 'HTTP_X_APPENGINE_INTERNAL_USER_ID': '123', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '8080', 'SERVER_PROTOCOL': 'HTTP/1.1', } self.mox.ReplayAll() self.assertResponse('500 Internal Server Error', {}, '', self.proxy.handle, environ, url_map=self.url_map, match=re.match(self.url_map.url, '/get%20error'), request_id='request id', request_type=instance.NORMAL_REQUEST) self.mox.VerifyAll()
def test_handle_background_thread(self): response = FakeHttpResponse(200, 'OK', [('Foo', 'Bar')], 'response') login.get_user_info(None).AndReturn(('', False, '')) httplib.HTTPConnection.connect() httplib.HTTPConnection.request( 'GET', '/get%20request?key=value', '', {'HEADER': 'value', http_runtime_constants.REQUEST_ID_HEADER: 'request id', 'X-AppEngine-Country': 'ZZ', 'X-Appengine-Internal-Datacenter': 'us1', 'X-Appengine-Internal-User-Email': '', 'X-Appengine-Internal-User-Id': '', 'X-Appengine-Internal-User-Is-Admin': '0', 'X-Appengine-Internal-User-Nickname': '', 'X-Appengine-Internal-User-Organization': '', 'X-APPENGINE-INTERNAL-SCRIPT': 'get.py', 'X-APPENGINE-INTERNAL-REQUEST-TYPE': 'background', 'X-APPENGINE-INTERNAL-SERVER-NAME': 'localhost', 'X-APPENGINE-INTERNAL-SERVER-PORT': '8080', 'X-APPENGINE-INTERNAL-SERVER-PROTOCOL': 'HTTP/1.1', }) httplib.HTTPConnection.getresponse().AndReturn(response) httplib.HTTPConnection.close() environ = {'HTTP_HEADER': 'value', 'PATH_INFO': '/get request', 'QUERY_STRING': 'key=value', 'HTTP_X_APPENGINE_INTERNAL_USER_ID': '123', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '8080', 'SERVER_PROTOCOL': 'HTTP/1.1', } self.mox.ReplayAll() expected_headers = { 'Foo': 'Bar', } self.assertResponse('200 OK', expected_headers, 'response', self.proxy.handle, environ, url_map=self.url_map, match=re.match(self.url_map.url, '/get%20request'), request_id='request id', request_type=instance.BACKGROUND_REQUEST) self.mox.VerifyAll()
def handle_authorization(self, environ, start_response): """Handles the response if the user is not authorized to access this URL. The authorization check is based on the 'login' setting for this handler, configured by the supplied url_map. Args: environ: An environ dict for the current request as defined in PEP-333. start_response: A function with semantics defined in PEP-333. Returns: An iterable over strings containing the body of an HTTP response, if the authorization check fails or the login UI must be displayed. None if the user is authorized to access the resource. """ admin_only = self._url_map.login == appinfo.LOGIN_ADMIN requires_login = self._url_map.login == appinfo.LOGIN_REQUIRED or admin_only auth_fail_action = self._url_map.auth_fail_action cookies = environ.get('HTTP_COOKIE') email_addr, admin, _ = login.get_user_info(cookies) if constants.FAKE_IS_ADMIN_HEADER in environ: admin = True if constants.FAKE_LOGGED_IN_HEADER in environ: email_addr = 'Fake User' # admin has an effect only with login: admin (not login: required). if requires_login and not email_addr and not (admin and admin_only): if auth_fail_action == appinfo.AUTH_FAIL_ACTION_REDIRECT: logging.debug('login required, redirecting user') return login.login_redirect( wsgiref.util.application_uri(environ), wsgiref.util.request_uri(environ), start_response) elif auth_fail_action == appinfo.AUTH_FAIL_ACTION_UNAUTHORIZED: logging.debug('login required, user unauthorized') start_response('401 Not authorized', [('Content-Type', 'text/html'), ('Cache-Control', 'no-cache')]) return ['Login required to view page.'] elif admin_only and not admin: logging.debug('admin required, user unauthorized') start_response('401 Not authorized', [('Content-Type', 'text/html'), ('Cache-Control', 'no-cache')]) return [ 'Current logged in user %s is not ' 'authorized to view this page.' % email_addr ] # Authorization check succeeded return None
def test_handle_with_error_no_error_handler(self): self.proxy = http_runtime.HttpRuntimeProxy( ['/runtime'], self.runtime_config_getter, appinfo.AppInfoExternal()) response = FakeHttpResponse( 500, 'Internal Server Error', [(http_runtime_constants.ERROR_CODE_HEADER, '1')], '') login.get_user_info(None).AndReturn(('', False, '')) httplib.HTTPConnection.connect() httplib.HTTPConnection.request( 'GET', '/get%20error', '', {'HEADER': 'value', http_runtime_constants.REQUEST_ID_HEADER: 'request id', 'X-AppEngine-Country': 'ZZ', 'X-Appengine-Internal-User-Email': '', 'X-Appengine-Internal-User-Id': '', 'X-Appengine-Internal-User-Is-Admin': '0', 'X-Appengine-Internal-User-Nickname': '', 'X-Appengine-Internal-User-Organization': '', 'X-APPENGINE-INTERNAL-SCRIPT': 'get.py', 'X-APPENGINE-INTERNAL-SERVER-NAME': 'localhost', 'X-APPENGINE-INTERNAL-SERVER-PORT': '8080', 'X-APPENGINE-INTERNAL-SERVER-PROTOCOL': 'HTTP/1.1', }) httplib.HTTPConnection.getresponse().AndReturn(response) httplib.HTTPConnection.close() environ = {'HTTP_HEADER': 'value', 'PATH_INFO': '/get error', 'QUERY_STRING': '', 'HTTP_X_APPENGINE_INTERNAL_USER_ID': '123', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '8080', 'SERVER_PROTOCOL': 'HTTP/1.1', } self.mox.ReplayAll() self.assertResponse('500 Internal Server Error', {}, '', self.proxy.handle, environ, url_map=self.url_map, match=re.match(self.url_map.url, '/get%20error'), request_id='request id', request_type=instance.NORMAL_REQUEST) self.mox.VerifyAll()
def test_handle_background_thread(self): response = FakeHttpResponse(200, 'OK', [('Foo', 'Bar')], 'response') login.get_user_info(None).AndReturn(('', False, '')) httplib.HTTPConnection.connect() httplib.HTTPConnection.request( 'GET', '/get%20request?key=value', '', {'HEADER': 'value', http_runtime_constants.REQUEST_ID_HEADER: 'request id', 'X-AppEngine-Country': 'ZZ', 'X-Appengine-User-Email': '', 'X-Appengine-User-Id': '', 'X-Appengine-User-Is-Admin': '0', 'X-Appengine-User-Nickname': '', 'X-Appengine-User-Organization': '', 'X-APPENGINE-DEV-SCRIPT': 'get.py', 'X-APPENGINE-DEV-REQUEST-TYPE': 'background', 'X-APPENGINE-SERVER-NAME': 'localhost', 'X-APPENGINE-SERVER-PORT': '8080', 'X-APPENGINE-SERVER-PROTOCOL': 'HTTP/1.1', }) httplib.HTTPConnection.getresponse().AndReturn(response) httplib.HTTPConnection.close() environ = {'HTTP_HEADER': 'value', 'PATH_INFO': '/get request', 'QUERY_STRING': 'key=value', 'HTTP_X_APPENGINE_USER_ID': '123', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '8080', 'SERVER_PROTOCOL': 'HTTP/1.1', } self.mox.ReplayAll() expected_headers = { 'Foo': 'Bar', } self.assertResponse('200 OK', expected_headers, 'response', self.proxy.handle, environ, url_map=self.url_map, match=re.match(self.url_map.url, '/get%20request'), request_id='request id', request_type=instance.BACKGROUND_REQUEST) self.mox.VerifyAll()
def test_connection_error(self): self.proxy = http_runtime.HttpRuntimeProxy(['/runtime'], self.runtime_config_getter, appinfo.AppInfoExternal()) self.proxy._process = self.mox.CreateMockAnything() login.get_user_info(None).AndReturn(('', False, '')) httplib.HTTPConnection.connect().AndRaise(socket.error()) self.proxy._process.poll().AndReturn(None) httplib.HTTPConnection.close() self.mox.ReplayAll() self.assertRaises( socket.error, self.proxy.handle( { 'PATH_INFO': '/' }, start_response=None, # Not used. url_map=self.url_map, match=re.match(self.url_map.url, '/get%20error'), request_id='request id', request_type=instance.NORMAL_REQUEST).next) self.mox.VerifyAll()
def handle_authorization(self, environ, start_response): """Handles the response if the user is not authorized to access this URL. The authorization check is based on the 'login' setting for this handler, configured by the supplied url_map. Args: environ: An environ dict for the current request as defined in PEP-333. start_response: A function with semantics defined in PEP-333. Returns: An iterable over strings containing the body of an HTTP response, if the authorization check fails or the login UI must be displayed. None if the user is authorized to access the resource. """ admin_only = self._url_map.login == appinfo.LOGIN_ADMIN requires_login = self._url_map.login == appinfo.LOGIN_REQUIRED or admin_only auth_fail_action = self._url_map.auth_fail_action cookies = environ.get("HTTP_COOKIE") email_addr, admin, _ = login.get_user_info(cookies) if constants.FAKE_IS_ADMIN_HEADER in environ: admin = True if constants.FAKE_LOGGED_IN_HEADER in environ: email_addr = "Fake User" # admin has an effect only with login: admin (not login: required). if requires_login and not email_addr and not (admin and admin_only): if auth_fail_action == appinfo.AUTH_FAIL_ACTION_REDIRECT: logging.debug("login required, redirecting user") return login.login_redirect( wsgiref.util.application_uri(environ), wsgiref.util.request_uri(environ), start_response ) elif auth_fail_action == appinfo.AUTH_FAIL_ACTION_UNAUTHORIZED: logging.debug("login required, user unauthorized") start_response("401 Not authorized", [("Content-Type", "text/html"), ("Cache-Control", "no-cache")]) return ["Login required to view page."] elif admin_only and not admin: logging.debug("admin required, user unauthorized") start_response("401 Not authorized", [("Content-Type", "text/html"), ("Cache-Control", "no-cache")]) return ["Current logged in user %s is not " "authorized to view this page." % email_addr] # Authorization check succeeded return None
def handle(self, environ, start_response, url_map, match, request_id, request_type): """Serves this request by forwarding it to the runtime process. Args: environ: An environ dict for the request as defined in PEP-333. start_response: A function with semantics defined in PEP-333. url_map: An appinfo.URLMap instance containing the configuration for the handler matching this request. match: A re.MatchObject containing the result of the matched URL pattern. request_id: A unique string id associated with the request. request_type: The type of the request. See instance.*_REQUEST module constants. Yields: A sequence of strings containing the body of the HTTP response. """ environ[http_runtime_constants.SCRIPT_HEADER] = match.expand( url_map.script) if request_type == instance.BACKGROUND_REQUEST: environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'background' elif request_type == instance.SHUTDOWN_REQUEST: environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'shutdown' elif request_type == instance.INTERACTIVE_REQUEST: environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'interactive' for name in http_runtime_constants.ENVIRONS_TO_PROPAGATE: if http_runtime_constants.INTERNAL_ENVIRON_PREFIX + name not in environ: value = environ.get(name, None) if value is not None: environ[http_runtime_constants.INTERNAL_ENVIRON_PREFIX + name] = value headers = util.get_headers_from_environ(environ) if environ.get('QUERY_STRING'): url = '%s?%s' % (urllib.quote( environ['PATH_INFO']), environ['QUERY_STRING']) else: url = urllib.quote(environ['PATH_INFO']) if 'CONTENT_LENGTH' in environ: headers['CONTENT-LENGTH'] = environ['CONTENT_LENGTH'] data = environ['wsgi.input'].read(int(environ['CONTENT_LENGTH'])) else: data = '' cookies = environ.get('HTTP_COOKIE') user_email, admin, user_id = login.get_user_info(cookies) if user_email: nickname, organization = user_email.split('@', 1) else: nickname = '' organization = '' headers[http_runtime_constants.REQUEST_ID_HEADER] = request_id prefix = http_runtime_constants.INTERNAL_HEADER_PREFIX # The Go runtime from the 1.9.48 SDK looks for different headers. if self._module_configuration.runtime == 'go': headers['X-Appengine-Dev-Request-Id'] = request_id prefix = 'X-Appengine-' headers[prefix + 'User-Id'] = (user_id) headers[prefix + 'User-Email'] = (user_email) headers[prefix + 'User-Is-Admin'] = (str(int(admin))) headers[prefix + 'User-Nickname'] = (nickname) headers[prefix + 'User-Organization'] = (organization) headers['X-AppEngine-Country'] = 'ZZ' connection = httplib.HTTPConnection(self._host, self._port) with contextlib.closing(connection): try: connection.connect() connection.request(environ.get('REQUEST_METHOD', 'GET'), url, data, dict(headers.items())) response = connection.getresponse() # Ensures that we avoid merging repeat headers into a single header, # allowing use of multiple Set-Cookie headers. headers = [] for name in response.msg: for value in response.msg.getheaders(name): headers.append((name, value)) response_headers = wsgiref.headers.Headers(headers) error_file = self._get_error_file() if (error_file and http_runtime_constants.ERROR_CODE_HEADER in response_headers): try: with open(error_file) as f: content = f.read() except IOError: content = 'Failed to load error handler' logging.exception('failed to load error file: %s', error_file) start_response('500 Internal Server Error', [('Content-Type', 'text/html'), ('Content-Length', str(len(content)))]) yield content return del response_headers[http_runtime_constants.ERROR_CODE_HEADER] start_response('%s %s' % (response.status, response.reason), response_headers.items()) # Yield the response body in small blocks. block = response.read(512) while block: yield block block = response.read(512) except Exception: with self._process_lock: if self._process and self._process.poll() is not None: # The development server is in a bad state. Log and return an error # message and quit. message = ( 'the runtime process for the instance running on port ' '%d has unexpectedly quit; exiting the development ' 'server' % (self._port)) logging.error(message) start_response('500 Internal Server Error', [('Content-Type', 'text/plain'), ('Content-Length', str(len(message)))]) shutdown.async_quit() yield message else: raise
def handle(self, environ, start_response, url_map, match, request_id, request_type): """Serves this request by forwarding it to the runtime process. Args: environ: An environ dict for the request as defined in PEP-333. start_response: A function with semantics defined in PEP-333. url_map: An appinfo.URLMap instance containing the configuration for the handler matching this request. match: A re.MatchObject containing the result of the matched URL pattern. request_id: A unique string id associated with the request. request_type: The type of the request. See instance.*_REQUEST module constants. Yields: A sequence of strings containing the body of the HTTP response. """ if self._prior_error: yield self._handle_error(self._prior_error, start_response) return environ[http_runtime_constants.SCRIPT_HEADER] = match.expand( url_map.script) if request_type == instance.BACKGROUND_REQUEST: environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'background' elif request_type == instance.SHUTDOWN_REQUEST: environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'shutdown' elif request_type == instance.INTERACTIVE_REQUEST: environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'interactive' for name in http_runtime_constants.ENVIRONS_TO_PROPAGATE: if http_runtime_constants.INTERNAL_ENVIRON_PREFIX + name not in environ: value = environ.get(name, None) if value is not None: environ[http_runtime_constants.INTERNAL_ENVIRON_PREFIX + name] = value headers = util.get_headers_from_environ(environ) if environ.get('QUERY_STRING'): url = '%s?%s' % (urllib.quote( environ['PATH_INFO']), environ['QUERY_STRING']) else: url = urllib.quote(environ['PATH_INFO']) if 'CONTENT_LENGTH' in environ: headers['CONTENT-LENGTH'] = environ['CONTENT_LENGTH'] data = environ['wsgi.input'].read(int(environ['CONTENT_LENGTH'])) else: data = '' cookies = environ.get('HTTP_COOKIE') user_email, admin, user_id = login.get_user_info(cookies) if user_email: nickname, organization = user_email.split('@', 1) else: nickname = '' organization = '' headers[http_runtime_constants.REQUEST_ID_HEADER] = request_id headers[http_runtime_constants.INTERNAL_HEADER_PREFIX + 'User-Id'] = (user_id) headers[http_runtime_constants.INTERNAL_HEADER_PREFIX + 'User-Email'] = (user_email) headers[http_runtime_constants.INTERNAL_HEADER_PREFIX + 'User-Is-Admin'] = (str(int(admin))) headers[http_runtime_constants.INTERNAL_HEADER_PREFIX + 'User-Nickname'] = (nickname) headers[http_runtime_constants.INTERNAL_HEADER_PREFIX + 'User-Organization'] = (organization) headers['X-AppEngine-Country'] = 'ZZ' connection = httplib.HTTPConnection(self._host, self._port) with contextlib.closing(connection): try: connection.connect() connection.request(environ.get('REQUEST_METHOD', 'GET'), url, data, dict(headers.items())) try: response = connection.getresponse() except httplib.HTTPException as e: # The runtime process has written a bad HTTP response. For example, # a Go runtime process may have crashed in app-specific code. yield self._handle_error( 'the runtime process gave a bad HTTP response: %s' % e, start_response) return # Ensures that we avoid merging repeat headers into a single header, # allowing use of multiple Set-Cookie headers. headers = [] for name in response.msg: for value in response.msg.getheaders(name): headers.append((name, value)) response_headers = wsgiref.headers.Headers(headers) error_file = self._get_error_file() if (error_file and http_runtime_constants.ERROR_CODE_HEADER in response_headers): try: with open(error_file) as f: content = f.read() except IOError: content = 'Failed to load error handler' logging.exception('failed to load error file: %s', error_file) start_response('500 Internal Server Error', [('Content-Type', 'text/html'), ('Content-Length', str(len(content)))]) yield content return del response_headers[http_runtime_constants.ERROR_CODE_HEADER] start_response('%s %s' % (response.status, response.reason), response_headers.items()) # Yield the response body in small blocks. while True: try: block = response.read(512) if not block: break yield block except httplib.HTTPException: # The runtime process has encountered a problem, but has not # necessarily crashed. For example, a Go runtime process' HTTP # handler may have panicked in app-specific code (which the http # package will recover from, so the process as a whole doesn't # crash). At this point, we have already proxied onwards the HTTP # header, so we cannot retroactively serve a 500 Internal Server # Error. We silently break here; the runtime process has presumably # already written to stderr (via the Tee). break except Exception: with self._process_lock: if self._process and self._process.poll() is not None: # The development server is in a bad state. Log and return an error # message. self._prior_error = ( 'the runtime process for the instance running ' 'on port %d has unexpectedly quit' % (self._port)) yield self._handle_error(self._prior_error, start_response) else: raise
def handle(self, environ, start_response, url_map, match, request_id, request_type): """Serves this request by forwarding it to the runtime process. Args: environ: An environ dict for the request as defined in PEP-333. start_response: A function with semantics defined in PEP-333. url_map: An appinfo.URLMap instance containing the configuration for the handler matching this request. match: A re.MatchObject containing the result of the matched URL pattern. request_id: A unique string id associated with the request. request_type: The type of the request. See instance.*_REQUEST module constants. Yields: A sequence of strings containing the body of the HTTP response. """ environ[http_runtime_constants.SCRIPT_HEADER] = match.expand(url_map.script) if request_type == instance.BACKGROUND_REQUEST: environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'background' elif request_type == instance.SHUTDOWN_REQUEST: environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'shutdown' elif request_type == instance.INTERACTIVE_REQUEST: environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'interactive' for name in http_runtime_constants.ENVIRONS_TO_PROPAGATE: if http_runtime_constants.INTERNAL_ENVIRON_PREFIX + name not in environ: value = environ.get(name, None) if value is not None: environ[ http_runtime_constants.INTERNAL_ENVIRON_PREFIX + name] = value headers = wsgiref.headers.Headers([]) for header, value in environ.iteritems(): if header.startswith('HTTP_'): headers[header[5:].replace('_', '-')] = value # Content-Type is special; it does not start with 'HTTP_'. if 'CONTENT_TYPE' in environ: headers['CONTENT-TYPE'] = environ['CONTENT_TYPE'] if environ.get('QUERY_STRING'): url = '%s?%s' % (urllib.quote(environ['PATH_INFO']), environ['QUERY_STRING']) else: url = urllib.quote(environ['PATH_INFO']) if 'CONTENT_LENGTH' in environ: headers['CONTENT-LENGTH'] = environ['CONTENT_LENGTH'] data = environ['wsgi.input'].read(int(environ['CONTENT_LENGTH'])) else: data = '' cookies = environ.get('HTTP_COOKIE') user_email, admin, user_id = login.get_user_info(cookies) if user_email: nickname, organization = user_email.split('@', 1) else: nickname = '' organization = '' headers[http_runtime_constants.REQUEST_ID_HEADER] = request_id headers[ http_runtime_constants.INTERNAL_HEADER_PREFIX + 'Datacenter'] = 'us1' headers[http_runtime_constants.INTERNAL_HEADER_PREFIX + 'User-Id'] = ( user_id) headers[http_runtime_constants.INTERNAL_HEADER_PREFIX + 'User-Email'] = ( user_email) headers[ http_runtime_constants.INTERNAL_HEADER_PREFIX + 'User-Is-Admin'] = ( str(int(admin))) headers[ http_runtime_constants.INTERNAL_HEADER_PREFIX + 'User-Nickname'] = ( nickname) headers[ http_runtime_constants.INTERNAL_HEADER_PREFIX + 'User-Organization'] = ( organization) headers['X-AppEngine-Country'] = 'ZZ' connection = httplib.HTTPConnection(self._host, self._port) with contextlib.closing(connection): connection.connect() connection.request(environ.get('REQUEST_METHOD', 'GET'), url, data, dict(headers.items())) response = connection.getresponse() response_headers = wsgiref.headers.Headers(response.getheaders()) error_file = self._get_error_file() if (error_file and http_runtime_constants.ERROR_CODE_HEADER in response_headers): try: with open(error_file) as f: content = f.read() except IOError: content = 'Failed to load error handler' logging.exception('failed to load error file: %s', error_file) start_response('500 Internal Server Error', [('Content-Type', 'text/html'), ('Content-Length', str(len(content)))]) yield content return del response_headers[http_runtime_constants.ERROR_CODE_HEADER] start_response('%s %s' % (response.status, response.reason), response_headers.items()) # Yield the response body in small blocks. block = response.read(512) while block: yield block block = response.read(512)
def handle(self, environ, start_response, url_map, match, request_id, request_type): """Serves this request by forwarding it to the runtime process. Args: environ: An environ dict for the request as defined in PEP-333. start_response: A function with semantics defined in PEP-333. url_map: An appinfo.URLMap instance containing the configuration for the handler matching this request. match: A re.MatchObject containing the result of the matched URL pattern. request_id: A unique string id associated with the request. request_type: The type of the request. See instance.*_REQUEST module constants. Yields: A sequence of strings containing the body of the HTTP response. """ if self._prior_error: logging.error(self._prior_error) yield self._respond_with_error(self._prior_error, start_response) return environ[http_runtime_constants.SCRIPT_HEADER] = match.expand(url_map.script) if request_type == instance.BACKGROUND_REQUEST: environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'background' elif request_type == instance.SHUTDOWN_REQUEST: environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'shutdown' elif request_type == instance.INTERACTIVE_REQUEST: environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'interactive' for name in http_runtime_constants.ENVIRONS_TO_PROPAGATE: if http_runtime_constants.APPENGINE_ENVIRON_PREFIX + name not in environ: value = environ.get(name, None) if value is not None: environ[ http_runtime_constants.APPENGINE_ENVIRON_PREFIX + name] = value headers = util.get_headers_from_environ(environ) if environ.get('QUERY_STRING'): url = '%s?%s' % (urllib.quote(environ['PATH_INFO']), environ['QUERY_STRING']) else: url = urllib.quote(environ['PATH_INFO']) if 'CONTENT_LENGTH' in environ: headers['CONTENT-LENGTH'] = environ['CONTENT_LENGTH'] data = environ['wsgi.input'].read(int(environ['CONTENT_LENGTH'])) else: data = None cookies = environ.get('HTTP_COOKIE') user_email, admin, user_id = login.get_user_info(cookies) if user_email: nickname, organization = user_email.split('@', 1) else: nickname = '' organization = '' headers[self.request_id_header_name] = request_id headers[http_runtime_constants.APPENGINE_HEADER_PREFIX + 'User-Id'] = ( user_id) headers[http_runtime_constants.APPENGINE_HEADER_PREFIX + 'User-Email'] = ( user_email) headers[ http_runtime_constants.APPENGINE_HEADER_PREFIX + 'User-Is-Admin'] = ( str(int(admin))) headers[ http_runtime_constants.APPENGINE_HEADER_PREFIX + 'User-Nickname'] = ( nickname) headers[http_runtime_constants.APPENGINE_HEADER_PREFIX + 'User-Organization'] = organization headers['X-AppEngine-Country'] = 'ZZ' connection = httplib.HTTPConnection(self._host, self._port) with contextlib.closing(connection): try: connection.connect() connection.request(environ.get('REQUEST_METHOD', 'GET'), url, data, dict(headers.items())) try: response = connection.getresponse() except httplib.HTTPException as e: # The runtime process has written a bad HTTP response. For example, # a Go runtime process may have crashed in app-specific code. yield self._respond_with_error( 'the runtime process gave a bad HTTP response: %s' % e, start_response) return # Ensures that we avoid merging repeat headers into a single header, # allowing use of multiple Set-Cookie headers. headers = [] for name in response.msg: for value in response.msg.getheaders(name): headers.append((name, value)) response_headers = wsgiref.headers.Headers(headers) if self._error_handler_file and ( http_runtime_constants.ERROR_CODE_HEADER in response_headers): try: with open(self._error_handler_file) as f: content = f.read() except IOError: content = 'Failed to load error handler' logging.exception('failed to load error file: %s', self._error_handler_file) start_response('500 Internal Server Error', [('Content-Type', 'text/html'), ('Content-Length', str(len(content)))]) yield content return del response_headers[http_runtime_constants.ERROR_CODE_HEADER] start_response('%s %s' % (response.status, response.reason), response_headers.items()) # Yield the response body in small blocks. while True: try: block = response.read(512) if not block: break yield block except httplib.HTTPException: # The runtime process has encountered a problem, but has not # necessarily crashed. For example, a Go runtime process' HTTP # handler may have panicked in app-specific code (which the http # package will recover from, so the process as a whole doesn't # crash). At this point, we have already proxied onwards the HTTP # header, so we cannot retroactively serve a 500 Internal Server # Error. We silently break here; the runtime process has presumably # already written to stderr (via the Tee). break except Exception: if self._instance_died_unexpectedly(): yield self._respond_with_error( 'the runtime process for the instance running on port %d has ' 'unexpectedly quit' % self._port, start_response) else: raise
def handle(self, environ, start_response, url_map, match, request_id, request_type): """Serves this request by forwarding it to the runtime process. Args: environ: An environ dict for the request as defined in PEP-333. start_response: A function with semantics defined in PEP-333. url_map: An appinfo.URLMap instance containing the configuration for the handler matching this request. match: A re.MatchObject containing the result of the matched URL pattern. request_id: A unique string id associated with the request. request_type: The type of the request. See instance.*_REQUEST module constants. Yields: A sequence of strings containing the body of the HTTP response. """ environ[http_runtime_constants.SCRIPT_HEADER] = match.expand(url_map.script) if request_type == instance.BACKGROUND_REQUEST: environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'background' elif request_type == instance.SHUTDOWN_REQUEST: environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'shutdown' elif request_type == instance.INTERACTIVE_REQUEST: environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'interactive' for name in http_runtime_constants.ENVIRONS_TO_PROPAGATE: if http_runtime_constants.INTERNAL_ENVIRON_PREFIX + name not in environ: value = environ.get(name, None) if value is not None: environ[ http_runtime_constants.INTERNAL_ENVIRON_PREFIX + name] = value headers = util.get_headers_from_environ(environ) if environ.get('QUERY_STRING'): url = '%s?%s' % (urllib.quote(environ['PATH_INFO']), environ['QUERY_STRING']) else: url = urllib.quote(environ['PATH_INFO']) if 'CONTENT_LENGTH' in environ: headers['CONTENT-LENGTH'] = environ['CONTENT_LENGTH'] data = environ['wsgi.input'].read(int(environ['CONTENT_LENGTH'])) else: data = '' cookies = environ.get('HTTP_COOKIE') user_email, admin, user_id = login.get_user_info(cookies) if user_email: nickname, organization = user_email.split('@', 1) else: nickname = '' organization = '' headers[http_runtime_constants.REQUEST_ID_HEADER] = request_id headers[http_runtime_constants.INTERNAL_HEADER_PREFIX + 'User-Id'] = ( user_id) headers[http_runtime_constants.INTERNAL_HEADER_PREFIX + 'User-Email'] = ( user_email) headers[ http_runtime_constants.INTERNAL_HEADER_PREFIX + 'User-Is-Admin'] = ( str(int(admin))) headers[ http_runtime_constants.INTERNAL_HEADER_PREFIX + 'User-Nickname'] = ( nickname) headers[ http_runtime_constants.INTERNAL_HEADER_PREFIX + 'User-Organization'] = ( organization) headers['X-AppEngine-Country'] = 'ZZ' connection = httplib.HTTPConnection(self._host, self._port) with contextlib.closing(connection): try: connection.connect() connection.request(environ.get('REQUEST_METHOD', 'GET'), url, data, dict(headers.items())) response = connection.getresponse() # Ensures that we avoid merging repeat headers into a single header, # allowing use of multiple Set-Cookie headers. headers = [] for name in response.msg: for value in response.msg.getheaders(name): headers.append((name, value)) response_headers = wsgiref.headers.Headers(headers) error_file = self._get_error_file() if (error_file and http_runtime_constants.ERROR_CODE_HEADER in response_headers): try: with open(error_file) as f: content = f.read() except IOError: content = 'Failed to load error handler' logging.exception('failed to load error file: %s', error_file) start_response('500 Internal Server Error', [('Content-Type', 'text/html'), ('Content-Length', str(len(content)))]) yield content return del response_headers[http_runtime_constants.ERROR_CODE_HEADER] start_response('%s %s' % (response.status, response.reason), response_headers.items()) # Yield the response body in small blocks. block = response.read(512) while block: yield block block = response.read(512) except Exception: with self._process_lock: if self._process and self._process.poll() is not None: # The development server is in a bad state. Log and return an error # message and quit. message = ('the runtime process for the instance running on port ' '%d has unexpectedly quit; exiting the development ' 'server' % ( self._port)) logging.error(message) start_response('500 Internal Server Error', [('Content-Type', 'text/plain'), ('Content-Length', str(len(message)))]) shutdown.async_quit() yield message else: raise