123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159 |
- # -*- 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 hyper.common.connection import HTTPConnection
- from hyper.compat import urlparse
- from 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
|