aboutsummaryrefslogtreecommitdiff
path: root/dummyserver
diff options
context:
space:
mode:
authorSVN-Git Migration <python-modules-team@lists.alioth.debian.org>2015-10-08 13:19:35 -0700
committerSVN-Git Migration <python-modules-team@lists.alioth.debian.org>2015-10-08 13:19:35 -0700
commit52980ebd0a4eb75acf055a2256e095772c1fa7c6 (patch)
treef339a7e66934caa7948c15e8c5bd3ea04ee72e5a /dummyserver
parent92b84b67f7b187b81dacbf1ae46d59a1d0b5b125 (diff)
downloadpython-urllib3-52980ebd0a4eb75acf055a2256e095772c1fa7c6.tar
python-urllib3-52980ebd0a4eb75acf055a2256e095772c1fa7c6.tar.gz
Imported Upstream version 1.7.1
Diffstat (limited to 'dummyserver')
-rw-r--r--dummyserver/handlers.py57
-rwxr-xr-xdummyserver/proxy.py137
-rwxr-xr-xdummyserver/server.py50
-rw-r--r--dummyserver/testcase.py86
4 files changed, 286 insertions, 44 deletions
diff --git a/dummyserver/handlers.py b/dummyserver/handlers.py
index ab48b53..bc51f31 100644
--- a/dummyserver/handlers.py
+++ b/dummyserver/handlers.py
@@ -87,7 +87,7 @@ class TestingApp(WSGIHandler):
if request.method != method:
return Response("Wrong method: %s != %s" %
- (method, request.method), status='400')
+ (method, request.method), status='400 Bad Request')
return Response()
def upload(self, request):
@@ -100,17 +100,18 @@ class TestingApp(WSGIHandler):
if len(files_) != 1:
return Response("Expected 1 file for '%s', not %d" %(param, len(files_)),
- status='400')
+ status='400 Bad Request')
file_ = files_[0]
data = file_['body']
if int(size) != len(data):
return Response("Wrong size: %d != %d" %
- (size, len(data)), status='400')
+ (size, len(data)), status='400 Bad Request')
if filename != file_['filename']:
return Response("Wrong filename: %s != %s" %
- (filename, file_.filename), status='400')
+ (filename, file_.filename),
+ status='400 Bad Request')
return Response()
@@ -118,7 +119,7 @@ class TestingApp(WSGIHandler):
"Perform a redirect to ``target``"
target = request.params.get('target', '/')
headers = [('Location', target)]
- return Response(status='303', headers=headers)
+ return Response(status='303 See Other', headers=headers)
def keepalive(self, request):
if request.params.get('close', b'0') == b'1':
@@ -169,3 +170,49 @@ class TestingApp(WSGIHandler):
def shutdown(self, request):
sys.exit()
+
+
+# RFC2231-aware replacement of internal tornado function
+def _parse_header(line):
+ r"""Parse a Content-type like header.
+
+ Return the main content-type and a dictionary of options.
+
+ >>> d = _parse_header("CD: fd; foo=\"bar\"; file*=utf-8''T%C3%A4st")[1]
+ >>> d['file'] == 'T\u00e4st'
+ True
+ >>> d['foo']
+ 'bar'
+ """
+ import tornado.httputil
+ import email.utils
+ from urllib3.packages import six
+ if not six.PY3:
+ line = line.encode('utf-8')
+ parts = tornado.httputil._parseparam(';' + line)
+ key = next(parts)
+ # decode_params treats first argument special, but we already stripped key
+ params = [('Dummy', 'value')]
+ for p in parts:
+ i = p.find('=')
+ if i >= 0:
+ name = p[:i].strip().lower()
+ value = p[i + 1:].strip()
+ params.append((name, value))
+ params = email.utils.decode_params(params)
+ params.pop(0) # get rid of the dummy again
+ pdict = {}
+ for name, value in params:
+ print(repr(value))
+ value = email.utils.collapse_rfc2231_value(value)
+ if len(value) >= 2 and value[0] == '"' and value[-1] == '"':
+ value = value[1:-1]
+ pdict[name] = value
+ return key, pdict
+
+# TODO: make the following conditional as soon as we know a version
+# which does not require this fix.
+# See https://github.com/facebook/tornado/issues/868
+if True:
+ import tornado.httputil
+ tornado.httputil._parse_header = _parse_header
diff --git a/dummyserver/proxy.py b/dummyserver/proxy.py
new file mode 100755
index 0000000..aca92a7
--- /dev/null
+++ b/dummyserver/proxy.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python
+#
+# Simple asynchronous HTTP proxy with tunnelling (CONNECT).
+#
+# GET/POST proxying based on
+# http://groups.google.com/group/python-tornado/msg/7bea08e7a049cf26
+#
+# Copyright (C) 2012 Senko Rasic <senko.rasic@dobarkod.hr>
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+import sys
+import socket
+
+import tornado.httpserver
+import tornado.ioloop
+import tornado.iostream
+import tornado.web
+import tornado.httpclient
+
+__all__ = ['ProxyHandler', 'run_proxy']
+
+
+class ProxyHandler(tornado.web.RequestHandler):
+ SUPPORTED_METHODS = ['GET', 'POST', 'CONNECT']
+
+ @tornado.web.asynchronous
+ def get(self):
+
+ def handle_response(response):
+ if response.error and not isinstance(response.error,
+ tornado.httpclient.HTTPError):
+ self.set_status(500)
+ self.write('Internal server error:\n' + str(response.error))
+ self.finish()
+ else:
+ self.set_status(response.code)
+ for header in ('Date', 'Cache-Control', 'Server',
+ 'Content-Type', 'Location'):
+ v = response.headers.get(header)
+ if v:
+ self.set_header(header, v)
+ if response.body:
+ self.write(response.body)
+ self.finish()
+
+ req = tornado.httpclient.HTTPRequest(url=self.request.uri,
+ method=self.request.method, body=self.request.body,
+ headers=self.request.headers, follow_redirects=False,
+ allow_nonstandard_methods=True)
+
+ client = tornado.httpclient.AsyncHTTPClient()
+ try:
+ client.fetch(req, handle_response)
+ except tornado.httpclient.HTTPError as e:
+ if hasattr(e, 'response') and e.response:
+ self.handle_response(e.response)
+ else:
+ self.set_status(500)
+ self.write('Internal server error:\n' + str(e))
+ self.finish()
+
+ @tornado.web.asynchronous
+ def post(self):
+ return self.get()
+
+ @tornado.web.asynchronous
+ def connect(self):
+ host, port = self.request.uri.split(':')
+ client = self.request.connection.stream
+
+ def read_from_client(data):
+ upstream.write(data)
+
+ def read_from_upstream(data):
+ client.write(data)
+
+ def client_close(data=None):
+ if upstream.closed():
+ return
+ if data:
+ upstream.write(data)
+ upstream.close()
+
+ def upstream_close(data=None):
+ if client.closed():
+ return
+ if data:
+ client.write(data)
+ client.close()
+
+ def start_tunnel():
+ client.read_until_close(client_close, read_from_client)
+ upstream.read_until_close(upstream_close, read_from_upstream)
+ client.write(b'HTTP/1.0 200 Connection established\r\n\r\n')
+
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
+ upstream = tornado.iostream.IOStream(s)
+ upstream.connect((host, int(port)), start_tunnel)
+
+
+def run_proxy(port, start_ioloop=True):
+ """
+ Run proxy on the specified port. If start_ioloop is True (default),
+ the tornado IOLoop will be started immediately.
+ """
+ app = tornado.web.Application([
+ (r'.*', ProxyHandler),
+ ])
+ app.listen(port)
+ ioloop = tornado.ioloop.IOLoop.instance()
+ if start_ioloop:
+ ioloop.start()
+
+if __name__ == '__main__':
+ port = 8888
+ if len(sys.argv) > 1:
+ port = int(sys.argv[1])
+
+ print ("Starting HTTP proxy on port %d" % port)
+ run_proxy(port)
diff --git a/dummyserver/server.py b/dummyserver/server.py
index 9031664..f4f98a4 100755
--- a/dummyserver/server.py
+++ b/dummyserver/server.py
@@ -11,11 +11,14 @@ import sys
import threading
import socket
+from tornado import netutil
import tornado.wsgi
import tornado.httpserver
import tornado.ioloop
+import tornado.web
from dummyserver.handlers import TestingApp
+from dummyserver.proxy import ProxyHandler
log = logging.getLogger(__name__)
@@ -36,28 +39,29 @@ class SocketServerThread(threading.Thread):
"""
:param socket_handler: Callable which receives a socket argument for one
request.
- :param ready_lock: Lock which gets released when the socket handler is
+ :param ready_event: Event which gets set when the socket handler is
ready to receive requests.
"""
def __init__(self, socket_handler, host='localhost', port=8081,
- ready_lock=None):
+ ready_event=None):
threading.Thread.__init__(self)
self.socket_handler = socket_handler
self.host = host
- self.port = port
- self.ready_lock = ready_lock
+ self.ready_event = ready_event
def _start_server(self):
sock = socket.socket()
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- sock.bind((self.host, self.port))
+ if sys.platform != 'win32':
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ sock.bind((self.host, 0))
+ self.port = sock.getsockname()[1]
# Once listen() returns, the server socket is ready
sock.listen(1)
- if self.ready_lock:
- self.ready_lock.release()
+ if self.ready_event:
+ self.ready_event.set()
self.socket_handler(sock)
sock.close()
@@ -67,34 +71,44 @@ class SocketServerThread(threading.Thread):
class TornadoServerThread(threading.Thread):
- def __init__(self, host='localhost', port=8081, scheme='http', certs=None):
+ app = tornado.wsgi.WSGIContainer(TestingApp())
+
+ def __init__(self, host='localhost', scheme='http', certs=None,
+ ready_event=None):
threading.Thread.__init__(self)
self.host = host
- self.port = port
self.scheme = scheme
self.certs = certs
+ self.ready_event = ready_event
def _start_server(self):
- container = tornado.wsgi.WSGIContainer(TestingApp())
-
if self.scheme == 'https':
- http_server = tornado.httpserver.HTTPServer(container,
+ http_server = tornado.httpserver.HTTPServer(self.app,
ssl_options=self.certs)
else:
- http_server = tornado.httpserver.HTTPServer(container)
+ http_server = tornado.httpserver.HTTPServer(self.app)
- http_server.listen(self.port, address=self.host)
+ family = socket.AF_INET6 if ':' in self.host else socket.AF_INET
+ sock, = netutil.bind_sockets(None, address=self.host, family=family)
+ self.port = sock.getsockname()[1]
+ http_server.add_sockets([sock])
return http_server
def run(self):
- self.server = self._start_server()
self.ioloop = tornado.ioloop.IOLoop.instance()
+ self.server = self._start_server()
+ if self.ready_event:
+ self.ready_event.set()
self.ioloop.start()
def stop(self):
- self.server.stop()
- self.ioloop.stop()
+ self.ioloop.add_callback(self.server.stop)
+ self.ioloop.add_callback(self.ioloop.stop)
+
+
+class ProxyServerThread(TornadoServerThread):
+ app = tornado.web.Application([(r'.*', ProxyHandler)])
if __name__ == '__main__':
diff --git a/dummyserver/testcase.py b/dummyserver/testcase.py
index 73b8f2f..a2a1da1 100644
--- a/dummyserver/testcase.py
+++ b/dummyserver/testcase.py
@@ -1,14 +1,15 @@
import unittest
-
-from threading import Lock
+import socket
+import threading
+from nose.plugins.skip import SkipTest
from dummyserver.server import (
TornadoServerThread, SocketServerThread,
DEFAULT_CERTS,
+ ProxyServerThread,
)
-
-# TODO: Change ports to auto-allocated?
+has_ipv6 = hasattr(socket, 'has_ipv6')
class SocketDummyServerTestCase(unittest.TestCase):
@@ -18,19 +19,16 @@ class SocketDummyServerTestCase(unittest.TestCase):
"""
scheme = 'http'
host = 'localhost'
- port = 18080
@classmethod
def _start_server(cls, socket_handler):
- ready_lock = Lock()
- ready_lock.acquire()
+ ready_event = threading.Event()
cls.server_thread = SocketServerThread(socket_handler=socket_handler,
- ready_lock=ready_lock,
- host=cls.host, port=cls.port)
+ ready_event=ready_event,
+ host=cls.host)
cls.server_thread.start()
-
- # Lock gets released by thread above
- ready_lock.acquire()
+ ready_event.wait()
+ cls.port = cls.server_thread.port
@classmethod
def tearDownClass(cls):
@@ -41,20 +39,19 @@ class SocketDummyServerTestCase(unittest.TestCase):
class HTTPDummyServerTestCase(unittest.TestCase):
scheme = 'http'
host = 'localhost'
- host_alt = '127.0.0.1' # Some tests need two hosts
- port = 18081
+ host_alt = '127.0.0.1' # Some tests need two hosts
certs = DEFAULT_CERTS
@classmethod
def _start_server(cls):
- cls.server_thread = TornadoServerThread(host=cls.host, port=cls.port,
+ ready_event = threading.Event()
+ cls.server_thread = TornadoServerThread(host=cls.host,
scheme=cls.scheme,
- certs=cls.certs)
+ certs=cls.certs,
+ ready_event=ready_event)
cls.server_thread.start()
-
- # TODO: Loop-check here instead
- import time
- time.sleep(0.1)
+ ready_event.wait()
+ cls.port = cls.server_thread.port
@classmethod
def _stop_server(cls):
@@ -73,5 +70,52 @@ class HTTPDummyServerTestCase(unittest.TestCase):
class HTTPSDummyServerTestCase(HTTPDummyServerTestCase):
scheme = 'https'
host = 'localhost'
- port = 18082
certs = DEFAULT_CERTS
+
+
+class HTTPDummyProxyTestCase(unittest.TestCase):
+
+ http_host = 'localhost'
+ http_host_alt = '127.0.0.1'
+
+ https_host = 'localhost'
+ https_host_alt = '127.0.0.1'
+ https_certs = DEFAULT_CERTS
+
+ proxy_host = 'localhost'
+ proxy_host_alt = '127.0.0.1'
+
+ @classmethod
+ def setUpClass(cls):
+ cls.http_thread = TornadoServerThread(host=cls.http_host,
+ scheme='http')
+ cls.http_thread._start_server()
+ cls.http_port = cls.http_thread.port
+
+ cls.https_thread = TornadoServerThread(
+ host=cls.https_host, scheme='https', certs=cls.https_certs)
+ cls.https_thread._start_server()
+ cls.https_port = cls.https_thread.port
+
+ ready_event = threading.Event()
+ cls.proxy_thread = ProxyServerThread(
+ host=cls.proxy_host, ready_event=ready_event)
+ cls.proxy_thread.start()
+ ready_event.wait()
+ cls.proxy_port = cls.proxy_thread.port
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.proxy_thread.stop()
+ cls.proxy_thread.join()
+
+
+class IPv6HTTPDummyServerTestCase(HTTPDummyServerTestCase):
+ host = '::1'
+
+ @classmethod
+ def setUpClass(cls):
+ if not has_ipv6:
+ raise SkipTest('IPv6 not available')
+ else:
+ super(IPv6HTTPDummyServerTestCase, cls).setUpClass()