158 lines
6.1 KiB
Python
158 lines
6.1 KiB
Python
|
# -*- 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
|
||
|
<hyper.http20.window.BaseFlowControlManager>`. If not provided,
|
||
|
:class:`FlowControlManager <hyper.http20.window.FlowControlManager>`
|
||
|
will be used.
|
||
|
:param enable_push: (optional) Whether the server is allowed to push
|
||
|
resources to the client (see
|
||
|
:meth:`get_pushes() <hyper.HTTP20Connection.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)
|