160 lines
5.2 KiB
Python
160 lines
5.2 KiB
Python
|
# -*- coding: utf-8 -*-
|
||
|
"""
|
||
|
hyper/contrib
|
||
|
~~~~~~~~~~~~~
|
||
|
|
||
|
Contains a few utilities for use with other HTTP libraries.
|
||
|
"""
|
||
|
try:
|
||
|
from requests.adapters import HTTPAdapter
|
||
|
from requests.models import Response
|
||
|
from requests.structures import CaseInsensitiveDict
|
||
|
from requests.utils import get_encoding_from_headers
|
||
|
from requests.cookies import extract_cookies_to_jar
|
||
|
except ImportError: # pragma: no cover
|
||
|
HTTPAdapter = object
|
||
|
|
||
|
from vinetrimmer.vendor.hyper.common.connection import HTTPConnection
|
||
|
from vinetrimmer.vendor.hyper.compat import urlparse
|
||
|
from vinetrimmer.vendor.hyper.tls import init_context
|
||
|
|
||
|
|
||
|
class HTTP20Adapter(HTTPAdapter):
|
||
|
"""
|
||
|
A Requests Transport Adapter that uses hyper to send requests over
|
||
|
HTTP/2. This implements some degree of connection pooling to maximise the
|
||
|
HTTP/2 gain.
|
||
|
"""
|
||
|
def __init__(self, *args, **kwargs):
|
||
|
#: A mapping between HTTP netlocs and ``HTTP20Connection`` objects.
|
||
|
self.connections = {}
|
||
|
|
||
|
def get_connection(self, host, port, scheme, cert=None):
|
||
|
"""
|
||
|
Gets an appropriate HTTP/2 connection object based on
|
||
|
host/port/scheme/cert tuples.
|
||
|
"""
|
||
|
secure = (scheme == 'https')
|
||
|
|
||
|
if port is None: # pragma: no cover
|
||
|
port = 80 if not secure else 443
|
||
|
|
||
|
ssl_context = None
|
||
|
if cert is not None:
|
||
|
ssl_context = init_context(cert=cert)
|
||
|
|
||
|
try:
|
||
|
conn = self.connections[(host, port, scheme, cert)]
|
||
|
except KeyError:
|
||
|
conn = HTTPConnection(
|
||
|
host,
|
||
|
port,
|
||
|
secure=secure,
|
||
|
ssl_context=ssl_context)
|
||
|
self.connections[(host, port, scheme, cert)] = conn
|
||
|
|
||
|
return conn
|
||
|
|
||
|
def send(self, request, stream=False, cert=None, **kwargs):
|
||
|
"""
|
||
|
Sends a HTTP message to the server.
|
||
|
"""
|
||
|
parsed = urlparse(request.url)
|
||
|
conn = self.get_connection(
|
||
|
parsed.hostname,
|
||
|
parsed.port,
|
||
|
parsed.scheme,
|
||
|
cert=cert)
|
||
|
|
||
|
# Build the selector.
|
||
|
selector = parsed.path
|
||
|
selector += '?' + parsed.query if parsed.query else ''
|
||
|
selector += '#' + parsed.fragment if parsed.fragment else ''
|
||
|
|
||
|
conn.request(
|
||
|
request.method,
|
||
|
selector,
|
||
|
request.body,
|
||
|
request.headers
|
||
|
)
|
||
|
resp = conn.get_response()
|
||
|
|
||
|
r = self.build_response(request, resp)
|
||
|
|
||
|
if not stream:
|
||
|
r.content
|
||
|
|
||
|
return r
|
||
|
|
||
|
def build_response(self, request, resp):
|
||
|
"""
|
||
|
Builds a Requests' response object. This emulates most of the logic of
|
||
|
the standard fuction but deals with the lack of the ``.headers``
|
||
|
property on the HTTP20Response object.
|
||
|
|
||
|
Additionally, this function builds in a number of features that are
|
||
|
purely for HTTPie. This is to allow maximum compatibility with what
|
||
|
urllib3 does, so that HTTPie doesn't fall over when it uses us.
|
||
|
"""
|
||
|
response = Response()
|
||
|
|
||
|
response.status_code = resp.status
|
||
|
response.headers = CaseInsensitiveDict(resp.headers.iter_raw())
|
||
|
response.raw = resp
|
||
|
response.reason = resp.reason
|
||
|
response.encoding = get_encoding_from_headers(response.headers)
|
||
|
|
||
|
extract_cookies_to_jar(response.cookies, request, response)
|
||
|
response.url = request.url
|
||
|
|
||
|
response.request = request
|
||
|
response.connection = self
|
||
|
|
||
|
# First horrible patch: Requests expects its raw responses to have a
|
||
|
# release_conn method, which I don't. We should monkeypatch a no-op on.
|
||
|
resp.release_conn = lambda: None
|
||
|
|
||
|
# Next, add the things HTTPie needs. It needs the following things:
|
||
|
#
|
||
|
# - The `raw` object has a property called `_original_response` that is
|
||
|
# a `httplib` response object.
|
||
|
# - `raw._original_response` has three simple properties: `version`,
|
||
|
# `status`, `reason`.
|
||
|
# - `raw._original_response.version` has one of three values: `9`,
|
||
|
# `10`, `11`.
|
||
|
# - `raw._original_response.msg` exists.
|
||
|
# - `raw._original_response.msg._headers` exists and is an iterable of
|
||
|
# two-tuples.
|
||
|
#
|
||
|
# We fake this out. Most of this exists on our response object already,
|
||
|
# and the rest can be faked.
|
||
|
#
|
||
|
# All of this exists for httpie, which I don't have any tests for,
|
||
|
# so I'm not going to bother adding test coverage for it.
|
||
|
class FakeOriginalResponse(object): # pragma: no cover
|
||
|
def __init__(self, headers):
|
||
|
self._headers = headers
|
||
|
|
||
|
def get_all(self, name, default=None):
|
||
|
values = []
|
||
|
|
||
|
for n, v in self._headers:
|
||
|
if n == name.lower():
|
||
|
values.append(v)
|
||
|
|
||
|
if not values:
|
||
|
return default
|
||
|
|
||
|
return values
|
||
|
|
||
|
def getheaders(self, name):
|
||
|
return self.get_all(name, [])
|
||
|
|
||
|
response.raw._original_response = orig = FakeOriginalResponse(None)
|
||
|
orig.version = 20
|
||
|
orig.status = resp.status
|
||
|
orig.reason = resp.reason
|
||
|
orig.msg = FakeOriginalResponse(resp.headers.iter_raw())
|
||
|
|
||
|
return response
|