# -*- coding: utf-8 -*- """ hyper/common/connection ~~~~~~~~~~~~~~~~~~~~~~~ Hyper's HTTP/1.1 and HTTP/2 abstraction layer. """ from .exceptions import TLSUpgrade, HTTPUpgrade from ..http11.connection import HTTP11Connection from ..http20.connection import HTTP20Connection from ..tls import H2_NPN_PROTOCOLS, H2C_PROTOCOL class HTTPConnection(object): """ An object representing a single HTTP connection to a server. This object behaves similarly to the Python standard library's ``HTTPConnection`` object, with a few critical differences. Most of the standard library's arguments to the constructor are not supported by hyper. Most optional parameters apply to *either* HTTP/1.1 or HTTP/2. :param host: The host to connect to. This may be an IP address or a hostname, and optionally may include a port: for example, ``'http2bin.org'``, ``'http2bin.org:443'`` or ``'127.0.0.1'``. :param port: (optional) The port to connect to. If not provided and one also isn't provided in the ``host`` parameter, defaults to 80. :param secure: (optional) Whether the request should use TLS. Defaults to ``False`` for most requests, but to ``True`` for any request issued to port 443. :param window_manager: (optional) The class to use to manage flow control windows. This needs to be a subclass of the :class:`BaseFlowControlManager `. If not provided, :class:`FlowControlManager ` will be used. :param enable_push: (optional) Whether the server is allowed to push resources to the client (see :meth:`get_pushes() `). :param ssl_context: (optional) A class with custom certificate settings. If not provided then hyper's default ``SSLContext`` is used instead. :param proxy_host: (optional) The proxy to connect to. This can be an IP address or a host name and may include a port. :param proxy_port: (optional) The proxy port to connect to. If not provided and one also isn't provided in the ``proxy`` parameter, defaults to 8080. """ def __init__(self, host, port=None, secure=None, window_manager=None, enable_push=False, ssl_context=None, proxy_host=None, proxy_port=None, **kwargs): self._host = host self._port = port self._h1_kwargs = { 'secure': secure, 'ssl_context': ssl_context, 'proxy_host': proxy_host, 'proxy_port': proxy_port } self._h2_kwargs = { 'window_manager': window_manager, 'enable_push': enable_push, 'secure': secure, 'ssl_context': ssl_context, 'proxy_host': proxy_host, 'proxy_port': proxy_port } # Add any unexpected kwargs to both dictionaries. self._h1_kwargs.update(kwargs) self._h2_kwargs.update(kwargs) self._conn = HTTP11Connection( self._host, self._port, **self._h1_kwargs ) def request(self, method, url, body=None, headers=None): """ This will send a request to the server using the HTTP request method ``method`` and the selector ``url``. If the ``body`` argument is present, it should be string or bytes object of data to send after the headers are finished. Strings are encoded as UTF-8. To use other encodings, pass a bytes object. The Content-Length header is set to the length of the body field. :param method: The request method, e.g. ``'GET'``. :param url: The URL to contact, e.g. ``'/path/segment'``. :param body: (optional) The request body to send. Must be a bytestring or a file-like object. :param headers: (optional) The headers to send on the request. :returns: A stream ID for the request, or ``None`` if the request is made over HTTP/1.1. """ headers = headers or {} try: return self._conn.request( method=method, url=url, body=body, headers=headers ) except TLSUpgrade as e: # We upgraded in the NPN/ALPN handshake. We can just go straight to # the world of HTTP/2. Replace the backing object and insert the # socket into it. assert e.negotiated in H2_NPN_PROTOCOLS self._conn = HTTP20Connection( self._host, self._port, **self._h2_kwargs ) self._conn._sock = e.sock # Because we skipped the connecting logic, we need to send the # HTTP/2 preamble. self._conn._send_preamble() return self._conn.request( method=method, url=url, body=body, headers=headers ) def get_response(self, *args, **kwargs): """ Returns a response object. """ try: return self._conn.get_response(*args, **kwargs) except HTTPUpgrade as e: # We upgraded via the HTTP Upgrade mechanism. We can just # go straight to the world of HTTP/2. Replace the backing object # and insert the socket into it. assert e.negotiated == H2C_PROTOCOL self._conn = HTTP20Connection( self._host, self._port, **self._h2_kwargs ) self._conn._connect_upgrade(e.sock) # stream id 1 is used by the upgrade request and response # and is half-closed by the client return self._conn.get_response(1) # The following two methods are the implementation of the context manager # protocol. def __enter__(self): # pragma: no cover return self def __exit__(self, type, value, tb): # pragma: no cover self._conn.close() return False # Never swallow exceptions. # Can anyone say 'proxy object pattern'? def __getattr__(self, name): return getattr(self._conn, name)