aboutsummaryrefslogtreecommitdiff
path: root/dummyserver
diff options
context:
space:
mode:
authorSVN-Git Migration <python-modules-team@lists.alioth.debian.org>2015-10-08 13:19:36 -0700
committerSVN-Git Migration <python-modules-team@lists.alioth.debian.org>2015-10-08 13:19:36 -0700
commit5f949ee35667a6065ab02a3e7ab8c98c9fcdcaed (patch)
tree35ce945e6f6fe74276a6745c96e6a48f6e5d3c68 /dummyserver
parent52980ebd0a4eb75acf055a2256e095772c1fa7c6 (diff)
downloadpython-urllib3-5f949ee35667a6065ab02a3e7ab8c98c9fcdcaed.tar
python-urllib3-5f949ee35667a6065ab02a3e7ab8c98c9fcdcaed.tar.gz
Imported Upstream version 1.8
Diffstat (limited to 'dummyserver')
-rwxr-xr-xdummyserver/server.py159
-rw-r--r--dummyserver/testcase.py63
2 files changed, 139 insertions, 83 deletions
diff --git a/dummyserver/server.py b/dummyserver/server.py
index f4f98a4..22de456 100755
--- a/dummyserver/server.py
+++ b/dummyserver/server.py
@@ -5,21 +5,21 @@ Dummy server used for unit testing.
"""
from __future__ import print_function
+import errno
import logging
import os
+import random
+import string
import sys
import threading
import socket
-from tornado import netutil
+from tornado.platform.auto import set_close_exec
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__)
@@ -51,7 +51,7 @@ class SocketServerThread(threading.Thread):
self.ready_event = ready_event
def _start_server(self):
- sock = socket.socket()
+ sock = socket.socket(socket.AF_INET6)
if sys.platform != 'win32':
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((self.host, 0))
@@ -70,59 +70,112 @@ class SocketServerThread(threading.Thread):
self.server = self._start_server()
-class TornadoServerThread(threading.Thread):
- app = tornado.wsgi.WSGIContainer(TestingApp())
+# FIXME: there is a pull request patching bind_sockets in Tornado directly.
+# If it gets merged and released we can drop this and use
+# `tornado.netutil.bind_sockets` again.
+# https://github.com/facebook/tornado/pull/977
- def __init__(self, host='localhost', scheme='http', certs=None,
- ready_event=None):
- threading.Thread.__init__(self)
+def bind_sockets(port, address=None, family=socket.AF_UNSPEC, backlog=128,
+ flags=None):
+ """Creates listening sockets bound to the given port and address.
- self.host = host
- self.scheme = scheme
- self.certs = certs
- self.ready_event = ready_event
+ Returns a list of socket objects (multiple sockets are returned if
+ the given address maps to multiple IP addresses, which is most common
+ for mixed IPv4 and IPv6 use).
- def _start_server(self):
- if self.scheme == 'https':
- http_server = tornado.httpserver.HTTPServer(self.app,
- ssl_options=self.certs)
- else:
- http_server = tornado.httpserver.HTTPServer(self.app)
+ Address may be either an IP address or hostname. If it's a hostname,
+ the server will listen on all IP addresses associated with the
+ name. Address may be an empty string or None to listen on all
+ available interfaces. Family may be set to either `socket.AF_INET`
+ or `socket.AF_INET6` to restrict to IPv4 or IPv6 addresses, otherwise
+ both will be used if available.
- 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
+ The ``backlog`` argument has the same meaning as for
+ `socket.listen() <socket.socket.listen>`.
- def run(self):
- 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.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__':
- log.setLevel(logging.DEBUG)
- log.addHandler(logging.StreamHandler(sys.stderr))
-
- from urllib3 import get_host
+ ``flags`` is a bitmask of AI_* flags to `~socket.getaddrinfo`, like
+ ``socket.AI_PASSIVE | socket.AI_NUMERICHOST``.
+ """
+ sockets = []
+ if address == "":
+ address = None
+ if not socket.has_ipv6 and family == socket.AF_UNSPEC:
+ # Python can be compiled with --disable-ipv6, which causes
+ # operations on AF_INET6 sockets to fail, but does not
+ # automatically exclude those results from getaddrinfo
+ # results.
+ # http://bugs.python.org/issue16208
+ family = socket.AF_INET
+ if flags is None:
+ flags = socket.AI_PASSIVE
+ binded_port = None
+ for res in set(socket.getaddrinfo(address, port, family,
+ socket.SOCK_STREAM, 0, flags)):
+ af, socktype, proto, canonname, sockaddr = res
+ try:
+ sock = socket.socket(af, socktype, proto)
+ except socket.error as e:
+ if e.args[0] == errno.EAFNOSUPPORT:
+ continue
+ raise
+ set_close_exec(sock.fileno())
+ if os.name != 'nt':
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ if af == socket.AF_INET6:
+ # On linux, ipv6 sockets accept ipv4 too by default,
+ # but this makes it impossible to bind to both
+ # 0.0.0.0 in ipv4 and :: in ipv6. On other systems,
+ # separate sockets *must* be used to listen for both ipv4
+ # and ipv6. For consistency, always disable ipv4 on our
+ # ipv6 sockets and use a separate ipv4 socket when needed.
+ #
+ # Python 2.x on windows doesn't have IPPROTO_IPV6.
+ if hasattr(socket, "IPPROTO_IPV6"):
+ sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
+
+ # automatic port allocation with port=None
+ # should bind on the same port on IPv4 and IPv6
+ host, requested_port = sockaddr[:2]
+ if requested_port == 0 and binded_port is not None:
+ sockaddr = tuple([host, binded_port] + list(sockaddr[2:]))
+
+ sock.setblocking(0)
+ sock.bind(sockaddr)
+ binded_port = sock.getsockname()[1]
+ sock.listen(backlog)
+ sockets.append(sock)
+ return sockets
+
+
+def run_tornado_app(app, io_loop, certs, scheme, host):
+ if scheme == 'https':
+ http_server = tornado.httpserver.HTTPServer(app, ssl_options=certs,
+ io_loop=io_loop)
+ else:
+ http_server = tornado.httpserver.HTTPServer(app, io_loop=io_loop)
+
+ sockets = bind_sockets(None, address=host)
+ port = sockets[0].getsockname()[1]
+ http_server.add_sockets(sockets)
+ return http_server, port
+
+
+def run_loop_in_thread(io_loop):
+ t = threading.Thread(target=io_loop.start)
+ t.start()
+ return t
- url = "http://localhost:8081"
- if len(sys.argv) > 1:
- url = sys.argv[1]
- print("Starting WSGI server at: %s" % url)
+def get_unreachable_address():
+ while True:
+ host = ''.join(random.choice(string.ascii_lowercase)
+ for _ in range(60))
+ sockaddr = (host, 54321)
- scheme, host, port = get_host(url)
- t = TornadoServerThread(scheme=scheme, host=host, port=port)
- t.start()
+ # check if we are really "lucky" and hit an actual server
+ try:
+ s = socket.create_connection(sockaddr)
+ except socket.error:
+ return sockaddr
+ else:
+ s.close()
diff --git a/dummyserver/testcase.py b/dummyserver/testcase.py
index a2a1da1..35769ef 100644
--- a/dummyserver/testcase.py
+++ b/dummyserver/testcase.py
@@ -2,14 +2,17 @@ import unittest
import socket
import threading
from nose.plugins.skip import SkipTest
+from tornado import ioloop, web, wsgi
from dummyserver.server import (
- TornadoServerThread, SocketServerThread,
+ SocketServerThread,
+ run_tornado_app,
+ run_loop_in_thread,
DEFAULT_CERTS,
- ProxyServerThread,
)
+from dummyserver.handlers import TestingApp
+from dummyserver.proxy import ProxyHandler
-has_ipv6 = hasattr(socket, 'has_ipv6')
class SocketDummyServerTestCase(unittest.TestCase):
@@ -33,7 +36,7 @@ class SocketDummyServerTestCase(unittest.TestCase):
@classmethod
def tearDownClass(cls):
if hasattr(cls, 'server_thread'):
- cls.server_thread.join()
+ cls.server_thread.join(0.1)
class HTTPDummyServerTestCase(unittest.TestCase):
@@ -44,18 +47,16 @@ class HTTPDummyServerTestCase(unittest.TestCase):
@classmethod
def _start_server(cls):
- ready_event = threading.Event()
- cls.server_thread = TornadoServerThread(host=cls.host,
- scheme=cls.scheme,
- certs=cls.certs,
- ready_event=ready_event)
- cls.server_thread.start()
- ready_event.wait()
- cls.port = cls.server_thread.port
+ cls.io_loop = ioloop.IOLoop()
+ app = wsgi.WSGIContainer(TestingApp())
+ cls.server, cls.port = run_tornado_app(app, cls.io_loop, cls.certs,
+ cls.scheme, cls.host)
+ cls.server_thread = run_loop_in_thread(cls.io_loop)
@classmethod
def _stop_server(cls):
- cls.server_thread.stop()
+ cls.io_loop.add_callback(cls.server.stop)
+ cls.io_loop.add_callback(cls.io_loop.stop)
cls.server_thread.join()
@classmethod
@@ -87,27 +88,29 @@ class HTTPDummyProxyTestCase(unittest.TestCase):
@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.io_loop = ioloop.IOLoop()
- 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
+ app = wsgi.WSGIContainer(TestingApp())
+ cls.http_server, cls.http_port = run_tornado_app(
+ app, cls.io_loop, None, 'http', cls.http_host)
- 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
+ app = wsgi.WSGIContainer(TestingApp())
+ cls.https_server, cls.https_port = run_tornado_app(
+ app, cls.io_loop, cls.https_certs, 'https', cls.http_host)
+
+ app = web.Application([(r'.*', ProxyHandler)])
+ cls.proxy_server, cls.proxy_port = run_tornado_app(
+ app, cls.io_loop, None, 'http', cls.proxy_host)
+
+ cls.server_thread = run_loop_in_thread(cls.io_loop)
@classmethod
def tearDownClass(cls):
- cls.proxy_thread.stop()
- cls.proxy_thread.join()
+ cls.io_loop.add_callback(cls.http_server.stop)
+ cls.io_loop.add_callback(cls.https_server.stop)
+ cls.io_loop.add_callback(cls.proxy_server.stop)
+ cls.io_loop.add_callback(cls.io_loop.stop)
+ cls.server_thread.join()
class IPv6HTTPDummyServerTestCase(HTTPDummyServerTestCase):
@@ -115,7 +118,7 @@ class IPv6HTTPDummyServerTestCase(HTTPDummyServerTestCase):
@classmethod
def setUpClass(cls):
- if not has_ipv6:
+ if not socket.has_ipv6:
raise SkipTest('IPv6 not available')
else:
super(IPv6HTTPDummyServerTestCase, cls).setUpClass()