#!/usr/bin/env python
# Copyright 2015 Google Inc. All Rights Reserved.
#
# 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.
import httparchive
import httplib
import httpproxy
import threading
import unittest
import util
class MockCustomResponseHandler(object):
def __init__(self, response):
"""
Args:
response: An instance of ArchivedHttpResponse that is returned for each
request.
"""
self._response = response
def handle(self, request):
del request
return self._response
class MockHttpArchiveFetch(object):
def __init__(self, response):
"""
Args:
response: An instance of ArchivedHttpResponse that is returned for each
request.
"""
self.is_record_mode = False
self._response = response
def __call__(self, request):
del request # unused
return self._response
class MockHttpArchiveHandler(httpproxy.HttpArchiveHandler):
def handle_one_request(self):
httpproxy.HttpArchiveHandler.handle_one_request(self)
HttpProxyTest.HANDLED_REQUEST_COUNT += 1
class MockRules(object):
def Find(self, unused_rule_type_name): # pylint: disable=unused-argument
return lambda unused_request, unused_response: None
class HttpProxyTest(unittest.TestCase):
def setUp(self):
self.has_proxy_server_bound_port = False
self.has_proxy_server_started = False
self.allow_generate_304 = False
self.serve_response_by_http_archive = False
def set_up_proxy_server(self, response):
"""
Args:
response: An instance of ArchivedHttpResponse that is returned for each
request.
"""
HttpProxyTest.HANDLED_REQUEST_COUNT = 0
self.host = 'localhost'
self.port = 8889
custom_handlers = MockCustomResponseHandler(
response if not self.serve_response_by_http_archive else None)
rules = MockRules()
http_archive_fetch = MockHttpArchiveFetch(
response if self.serve_response_by_http_archive else None)
self.proxy_server = httpproxy.HttpProxyServer(
http_archive_fetch, custom_handlers, rules,
host=self.host, port=self.port,
allow_generate_304=self.allow_generate_304)
self.proxy_server.RequestHandlerClass = MockHttpArchiveHandler
self.has_proxy_server_bound_port = True
def tear_down_proxy_server(self):
if self.has_proxy_server_started:
self.proxy_server.shutdown()
if self.has_proxy_server_bound_port:
self.proxy_server.server_close()
def tearDown(self):
self.tear_down_proxy_server()
def serve_requests_forever(self):
self.has_proxy_server_started = True
self.proxy_server.serve_forever(poll_interval=0.01)
# Tests that handle_one_request does not leak threads, and does not try to
# re-handle connections that are finished.
def test_handle_one_request_closes_connection(self):
# By default, BaseHTTPServer.py treats all HTTP 1.1 requests as keep-alive.
# Intentionally use HTTP 1.0 to prevent this behavior.
response = httparchive.ArchivedHttpResponse(
version=10, status=200, reason="OK",
headers=[], response_data=["bat1"])
self.set_up_proxy_server(response)
t = threading.Thread(
target=HttpProxyTest.serve_requests_forever, args=(self,))
t.start()
initial_thread_count = threading.activeCount()
# Make a bunch of requests.
request_count = 10
for _ in range(request_count):
conn = httplib.HTTPConnection('localhost', 8889, timeout=10)
conn.request("GET", "/index.html")
res = conn.getresponse().read()
self.assertEqual(res, "bat1")
conn.close()
# Check to make sure that there is no leaked thread.
util.WaitFor(lambda: threading.activeCount() == initial_thread_count, 2)
self.assertEqual(request_count, HttpProxyTest.HANDLED_REQUEST_COUNT)
# Tests that keep-alive header works.
def test_keep_alive_header(self):
response = httparchive.ArchivedHttpResponse(
version=11, status=200, reason="OK",
headers=[("Connection", "keep-alive")], response_data=["bat1"])
self.set_up_proxy_server(response)
t = threading.Thread(
target=HttpProxyTest.serve_requests_forever, args=(self,))
t.start()
initial_thread_count = threading.activeCount()
# Make a bunch of requests.
request_count = 10
connections = []
for _ in range(request_count):
conn = httplib.HTTPConnection('localhost', 8889, timeout=10)
conn.request("GET", "/index.html", headers={"Connection": "keep-alive"})
res = conn.getresponse().read()
self.assertEqual(res, "bat1")
connections.append(conn)
# Repeat the same requests.
for conn in connections:
conn.request("GET", "/index.html", headers={"Connection": "keep-alive"})
res = conn.getresponse().read()
self.assertEqual(res, "bat1")
# Check that the right number of requests have been handled.
self.assertEqual(2 * request_count, HttpProxyTest.HANDLED_REQUEST_COUNT)
# Check to make sure that exactly "request_count" new threads are active.
self.assertEqual(
threading.activeCount(), initial_thread_count + request_count)
for conn in connections:
conn.close()
util.WaitFor(lambda: threading.activeCount() == initial_thread_count, 1)
# Test that opening 400 simultaneous connections does not cause httpproxy to
# hit a process fd limit. The default limit is 256 fds.
def test_max_fd(self):
response = httparchive.ArchivedHttpResponse(
version=11, status=200, reason="OK",
headers=[("Connection", "keep-alive")], response_data=["bat1"])
self.set_up_proxy_server(response)
t = threading.Thread(
target=HttpProxyTest.serve_requests_forever, args=(self,))
t.start()
# Make a bunch of requests.
request_count = 400
connections = []
for _ in range(request_count):
conn = httplib.HTTPConnection('localhost', 8889, timeout=10)
conn.request("GET", "/index.html", headers={"Connection": "keep-alive"})
res = conn.getresponse().read()
self.assertEqual(res, "bat1")
connections.append(conn)
# Check that the right number of requests have been handled.
self.assertEqual(request_count, HttpProxyTest.HANDLED_REQUEST_COUNT)
for conn in connections:
conn.close()
# Tests that conditional requests return 304.
def test_generate_304(self):
REQUEST_HEADERS = [
{},
{'If-Modified-Since': 'whatever'},
{'If-None-Match': 'whatever yet again'}]
RESPONSE_STATUSES = [200, 204, 304, 404]
for allow_generate_304 in [False, True]:
self.allow_generate_304 = allow_generate_304
for serve_response_by_http_archive in [False, True]:
self.serve_response_by_http_archive = serve_response_by_http_archive
for response_status in RESPONSE_STATUSES:
response = None
if response_status != 404:
response = httparchive.ArchivedHttpResponse(
version=11, status=response_status, reason="OK", headers=[],
response_data=["some content"])
self.set_up_proxy_server(response)
t = threading.Thread(
target=HttpProxyTest.serve_requests_forever, args=(self,))
t.start()
for method in ['GET', 'HEAD', 'POST']:
for headers in REQUEST_HEADERS:
connection = httplib.HTTPConnection('localhost', 8889, timeout=10)
connection.request(method, "/index.html", headers=headers)
response = connection.getresponse()
connection.close()
if (allow_generate_304 and
serve_response_by_http_archive and
method in ['GET', 'HEAD'] and
headers and
response_status == 200):
self.assertEqual(304, response.status)
self.assertEqual('', response.read())
else:
self.assertEqual(response_status, response.status)
self.tear_down_proxy_server()
if __name__ == '__main__':
unittest.main()