aboutsummaryrefslogtreecommitdiff
path: root/dummyserver/server.py
diff options
context:
space:
mode:
Diffstat (limited to 'dummyserver/server.py')
-rwxr-xr-xdummyserver/server.py159
1 files changed, 106 insertions, 53 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()