|
| 1 | +# -*- coding: utf-8 -*- |
| 2 | +""" |
| 3 | +hyper/common/connection |
| 4 | +~~~~~~~~~~~~~~~~~~~~~~~ |
| 5 | +
|
| 6 | +Hyper's HTTP/1.1 and HTTP/2 abstraction layer. |
| 7 | +""" |
| 8 | +from .exceptions import TLSUpgrade |
| 9 | +from ..http11.connection import HTTP11Connection |
| 10 | +from ..http20.connection import HTTP20Connection |
| 11 | +from ..tls import H2_NPN_PROTOCOLS |
| 12 | + |
| 13 | + |
| 14 | +class HTTPConnection(object): |
| 15 | + """ |
| 16 | + An object representing a single HTTP connection to a server. |
| 17 | +
|
| 18 | + This object behaves similarly to the Python standard library's |
| 19 | + ``HTTPConnection`` object, with a few critical differences. |
| 20 | +
|
| 21 | + Most of the standard library's arguments to the constructor are not |
| 22 | + supported by hyper. Most optional parameters apply to *either* HTTP/1.1 or |
| 23 | + HTTP/2. |
| 24 | +
|
| 25 | + :param host: The host to connect to. This may be an IP address or a |
| 26 | + hostname, and optionally may include a port: for example, |
| 27 | + ``'http2bin.org'``, ``'http2bin.org:443'`` or ``'127.0.0.1'``. |
| 28 | + :param port: (optional) The port to connect to. If not provided and one also |
| 29 | + isn't provided in the ``host`` parameter, defaults to 443. |
| 30 | + :param secure: (optional, HTTP/1.1 only) Whether the request should use |
| 31 | + TLS. Defaults to ``False`` for most requests, but to ``True`` for any |
| 32 | + request issued to port 443. |
| 33 | + :param window_manager: (optional) The class to use to manage flow control |
| 34 | + windows. This needs to be a subclass of the |
| 35 | + :class:`BaseFlowControlManager <hyper.http20.window.BaseFlowControlManager>`. |
| 36 | + If not provided, |
| 37 | + :class:`FlowControlManager <hyper.http20.window.FlowControlManager>` |
| 38 | + will be used. |
| 39 | + :param enable_push: (optional) Whether the server is allowed to push |
| 40 | + resources to the client (see |
| 41 | + :meth:`get_pushes() <hyper.HTTP20Connection.get_pushes>`). |
| 42 | + """ |
| 43 | + def __init__(self, |
| 44 | + host, |
| 45 | + port=None, |
| 46 | + secure=None, |
| 47 | + window_manager=None, |
| 48 | + enable_push=False, |
| 49 | + **kwargs): |
| 50 | + |
| 51 | + self._host = host |
| 52 | + self._port = port |
| 53 | + self._h1_kwargs = {'secure': secure} |
| 54 | + self._h2_kwargs = { |
| 55 | + 'window_manager': window_manager, 'enable_push': enable_push |
| 56 | + } |
| 57 | + |
| 58 | + # Add any unexpected kwargs to both dictionaries. |
| 59 | + self._h1_kwargs.update(kwargs) |
| 60 | + self._h2_kwargs.update(kwargs) |
| 61 | + |
| 62 | + self._conn = HTTP11Connection( |
| 63 | + self._host, self._port, **self._h1_kwargs |
| 64 | + ) |
| 65 | + |
| 66 | + def request(self, method, url, body=None, headers={}): |
| 67 | + """ |
| 68 | + This will send a request to the server using the HTTP request method |
| 69 | + ``method`` and the selector ``url``. If the ``body`` argument is |
| 70 | + present, it should be string or bytes object of data to send after the |
| 71 | + headers are finished. Strings are encoded as UTF-8. To use other |
| 72 | + encodings, pass a bytes object. The Content-Length header is set to the |
| 73 | + length of the body field. |
| 74 | +
|
| 75 | + :param method: The request method, e.g. ``'GET'``. |
| 76 | + :param url: The URL to contact, e.g. ``'/path/segment'``. |
| 77 | + :param body: (optional) The request body to send. Must be a bytestring |
| 78 | + or a file-like object. |
| 79 | + :param headers: (optional) The headers to send on the request. |
| 80 | + :returns: A stream ID for the request, or ``None`` if the request is |
| 81 | + made over HTTP/1.1. |
| 82 | + """ |
| 83 | + try: |
| 84 | + return self._conn.request( |
| 85 | + method=method, url=url, body=body, headers=headers |
| 86 | + ) |
| 87 | + except TLSUpgrade as e: |
| 88 | + # We upgraded in the NPN/ALPN handshake. We can just go straight to |
| 89 | + # the world of HTTP/2. Replace the backing object and insert the |
| 90 | + # socket into it. |
| 91 | + assert e.negotiated in H2_NPN_PROTOCOLS |
| 92 | + |
| 93 | + self._conn = HTTP20Connection( |
| 94 | + self._host, self._port, **self._h2_kwargs |
| 95 | + ) |
| 96 | + self._conn._sock = e.sock |
| 97 | + |
| 98 | + # Because we skipped the connecting logic, we need to send the |
| 99 | + # HTTP/2 preamble. |
| 100 | + self._conn._send_preamble() |
| 101 | + |
| 102 | + return self._conn.request( |
| 103 | + method=method, url=url, body=body, headers=headers |
| 104 | + ) |
| 105 | + |
| 106 | + # Can anyone say 'proxy object pattern'? |
| 107 | + def __getattr__(self, name): |
| 108 | + return getattr(self._conn, name) |
0 commit comments