blob: 1bf6fc7bd99c59b25fa9b0461c2b52fac9b0d942 [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright 2007 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Tests for google.appengine.tools.devappserver2.http_proxy."""
import cStringIO
import httplib
import os
import re
import shutil
import socket
import tempfile
import unittest
import google
import mox
from google.appengine.api import appinfo
from google.appengine.tools.devappserver2 import http_proxy
from google.appengine.tools.devappserver2 import http_runtime_constants
from google.appengine.tools.devappserver2 import instance
from google.appengine.tools.devappserver2 import login
from google.appengine.tools.devappserver2 import wsgi_test_utils
class MockMessage(object):
def __init__(self, headers):
self.headers = headers
def __iter__(self):
return iter(set(name for name, _ in self.headers))
def getheaders(self, name):
return [value for header_name, value in self.headers if header_name == name]
class FakeHttpResponse(object):
def __init__(self, status, reason, headers, body):
self.body = body
self.has_read = False
self.partial_read_error = None
self.status = status
self.reason = reason
self.headers = headers
self.msg = MockMessage(headers)
def read(self, amt=None):
if not self.has_read:
self.has_read = True
return self.body
elif self.partial_read_error:
raise self.partial_read_error
else:
return ''
def getheaders(self):
return self.headers
def get_instance_logs():
return ''
class HttpProxyTest(wsgi_test_utils.WSGITestCase):
def setUp(self):
self.mox = mox.Mox()
self.tmpdir = tempfile.mkdtemp()
self.proxy = http_proxy.HttpProxy(
host='localhost', port=23456,
instance_died_unexpectedly=lambda: False,
instance_logs_getter=get_instance_logs,
error_handler_file=None)
self.mox.StubOutWithMock(httplib.HTTPConnection, 'connect')
self.mox.StubOutWithMock(httplib.HTTPConnection, 'request')
self.mox.StubOutWithMock(httplib.HTTPConnection, 'getresponse')
self.mox.StubOutWithMock(httplib.HTTPConnection, 'close')
self.mox.StubOutWithMock(login, 'get_user_info')
self.url_map = appinfo.URLMap(url=r'/(get|post).*',
script=r'\1.py')
def tearDown(self):
shutil.rmtree(self.tmpdir)
self.mox.UnsetStubs()
def test_handle_get(self):
response = FakeHttpResponse(200,
'OK',
[('Foo', 'a'), ('Foo', 'b'), ('Var', 'c')],
'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-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', '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_handle_post(self):
response = FakeHttpResponse(200,
'OK',
[('Foo', 'a'), ('Foo', 'b'), ('Var', 'c')],
'response')
login.get_user_info('cookie').AndReturn(('user@example.com', 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-User-Email': 'user@example.com',
'X-Appengine-User-Id': '12345',
'X-Appengine-User-Is-Admin': '1',
'X-Appengine-User-Nickname': 'user',
'X-Appengine-User-Organization': 'example.com',
'X-APPENGINE-DEV-SCRIPT': 'post.py',
'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': '/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', '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, '/post'),
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, ''))
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-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',
})
httplib.HTTPConnection.getresponse().AndReturn(response)
httplib.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_handle_with_error_no_error_handler(self):
self.proxy = http_proxy.HttpProxy(
host='localhost', port=23456,
instance_died_unexpectedly=lambda: False,
instance_logs_getter=get_instance_logs,
error_handler_file=None)
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-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',
})
httplib.HTTPConnection.getresponse().AndReturn(response)
httplib.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()
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_with_error_missing_error_handler(self):
error_handler_file = os.path.join(self.tmpdir, 'error.html')
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, ''))
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-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',
})
httplib.HTTPConnection.getresponse().AndReturn(response)
httplib.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': '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_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, ''))
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-SERVER-NAME': 'localhost',
'X-APPENGINE-SERVER-PORT': '8080',
'X-APPENGINE-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_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_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 = httplib.IncompleteRead('')
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-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()
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_connection_error(self):
login.get_user_info(None).AndReturn(('', False, ''))
httplib.HTTPConnection.connect().AndRaise(socket.error())
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_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, ''))
httplib.HTTPConnection.connect().AndRaise(socket.error())
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_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_prior_error(self):
error = 'Oh no! Something is broken again!'
self.proxy = http_proxy.HttpProxy(
host=None, port=None,
instance_died_unexpectedly=None,
instance_logs_getter=get_instance_logs,
error_handler_file=None,
prior_error=error)
expected_headers = {
'Content-Type': 'text/plain',
'Content-Length': str(len(error)),
}
self.assertResponse('500 Internal Server Error', expected_headers,
error,
self.proxy.handle, {},
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()
if __name__ == '__main__':
unittest.main()