From: Ben Darnell Date: Mon, 16 May 2011 03:01:58 +0000 (-0700) Subject: Client-side ipv6 support (disabled by default) X-Git-Tag: v2.0.0~70 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0ec8dcec983c7dffb240b626a5818464d32ae68f;p=thirdparty%2Ftornado.git Client-side ipv6 support (disabled by default) --- diff --git a/tornado/curl_httpclient.py b/tornado/curl_httpclient.py index fea02fb3b..e911596a6 100644 --- a/tornado/curl_httpclient.py +++ b/tornado/curl_httpclient.py @@ -353,6 +353,12 @@ def _curl_setup_request(curl, request, buffer, headers): # request uses a custom ca_certs file, they all must. pass + if request.allow_ipv6 is False: + # Curl behaves reasonably when DNS resolution gives an ipv6 address + # that we can't reach, so allow ipv6 unless the user asks to disable. + # (but see version check in _process_queue above) + curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4) + # Set the request method through curl's retarded interface which makes # up names for almost every single method curl_options = { diff --git a/tornado/httpclient.py b/tornado/httpclient.py index 5c462e871..af77f777d 100644 --- a/tornado/httpclient.py +++ b/tornado/httpclient.py @@ -154,7 +154,8 @@ class HTTPRequest(object): header_callback=None, prepare_curl_callback=None, proxy_host=None, proxy_port=None, proxy_username=None, proxy_password='', allow_nonstandard_methods=False, - validate_cert=True, ca_certs=None): + validate_cert=True, ca_certs=None, + allow_ipv6=None): if headers is None: headers = httputil.HTTPHeaders() if if_modified_since: @@ -195,6 +196,9 @@ class HTTPRequest(object): # SimpleAsyncHTTPClient does not have this limitation. self.validate_cert = validate_cert self.ca_certs = ca_certs + # allow_ipv6 may be True, False, or None for default behavior + # that varies by httpclient implementation. + self.allow_ipv6 = allow_ipv6 self.start_time = time.time() diff --git a/tornado/simple_httpclient.py b/tornado/simple_httpclient.py index bc3c21203..030d2640a 100644 --- a/tornado/simple_httpclient.py +++ b/tornado/simple_httpclient.py @@ -134,14 +134,35 @@ class _HTTPConnection(object): self._timeout = None with stack_context.StackContext(self.cleanup): parsed = urlparse.urlsplit(_unicode(self.request.url)) - host = parsed.hostname - if parsed.port is None: - port = 443 if parsed.scheme == "https" else 80 + # urlsplit results have hostname and port results, but they + # didn't support ipv6 literals until python 2.7. + netloc = parsed.netloc + if "@" in netloc: + userpass, _, netloc = netloc.rpartition("@") + match = re.match(r'^(.+):(\d+)$', netloc) + if match: + host = match.group(1) + port = int(match.group(2)) else: - port = parsed.port + host = netloc + port = 443 if parsed.scheme == "https" else 80 + if re.match(r'^\[.*\]$', host): + # raw ipv6 addresses in urls are enclosed in brackets + host = host[1:-1] if self.client.hostname_mapping is not None: host = self.client.hostname_mapping.get(host, host) + if request.allow_ipv6: + af = socket.AF_UNSPEC + else: + # We only try the first IP we get from getaddrinfo, + # so restrict to ipv4 by default. + af = socket.AF_INET + + addrinfo = socket.getaddrinfo(host, port, af, socket.SOCK_STREAM, + 0, 0) + af, socktype, proto, canonname, sockaddr = addrinfo[0] + if parsed.scheme == "https": ssl_options = {} if request.validate_cert: @@ -150,11 +171,11 @@ class _HTTPConnection(object): ssl_options["ca_certs"] = request.ca_certs else: ssl_options["ca_certs"] = _DEFAULT_CA_CERTS - self.stream = SSLIOStream(socket.socket(), + self.stream = SSLIOStream(socket.socket(af, socktype, proto), io_loop=self.io_loop, ssl_options=ssl_options) else: - self.stream = IOStream(socket.socket(), + self.stream = IOStream(socket.socket(af, socktype, proto), io_loop=self.io_loop) timeout = min(request.connect_timeout, request.request_timeout) if timeout: @@ -162,7 +183,7 @@ class _HTTPConnection(object): self.start_time + timeout, self._on_timeout) self.stream.set_close_callback(self._on_close) - self.stream.connect((host, port), + self.stream.connect(sockaddr, functools.partial(self._on_connect, parsed)) def _on_timeout(self): diff --git a/tornado/test/simple_httpclient_test.py b/tornado/test/simple_httpclient_test.py index 2b82b36b8..0478e1a16 100644 --- a/tornado/test/simple_httpclient_test.py +++ b/tornado/test/simple_httpclient_test.py @@ -257,3 +257,15 @@ class SimpleHTTPClientTestCase(AsyncHTTPTestCase, LogTrapTestCase): user_agent=u"foo") self.assertEqual(response.headers["Content-Length"], "1") self.assertEqual(response.body, byte_body) + + def test_ipv6(self): + url = self.get_url("/hello").replace("localhost", "[::1]") + + # ipv6 is currently disabled by default and must be explicitly requested + self.http_client.fetch(url, self.stop) + response = self.wait() + self.assertEqual(response.code, 599) + + self.http_client.fetch(url, self.stop, allow_ipv6=True) + response = self.wait() + self.assertEqual(response.body, b("Hello world!"))