1# -*- coding: utf-8 -*-
2"""
3hyper/httplib_compat
4~~~~~~~~~~~~~~~~~~~~
5
6This file defines the publicly-accessible API for hyper. This API also
7constitutes the abstraction layer between HTTP/1.1 and HTTP/2.
8
9This API doesn't currently work, and is a lower priority than the HTTP/2
10stack at this time.
11"""
12import socket
13try:
14    import http.client as httplib
15except ImportError:
16    import httplib
17
18from .compat import ssl
19from .http20.tls import wrap_socket
20
21# If there's no NPN support, we're going to drop all support for HTTP/2.
22try:
23    support_20 = ssl.HAS_NPN
24except AttributeError:
25    support_20 = False
26
27# The HTTPConnection object is currently always the underlying one.
28HTTPConnection = httplib.HTTPConnection
29HTTPSConnection = httplib.HTTPSConnection
30
31# If we have NPN support, define our custom one, otherwise just use the
32# default.
33if support_20:
34    class HTTPSConnection(object):
35        """
36        An object representing a single HTTPS connection, whether HTTP/1.1 or
37        HTTP/2.
38
39        More specifically, this object represents an abstraction over the
40        distinction. This object encapsulates a connection object for one of
41        the specific types of connection, and delegates most of the work to
42        that object.
43        """
44        def __init__(self, *args, **kwargs):
45            # Whatever arguments and keyword arguments are passed to this
46            # object need to be saved off for when we initialise one of our
47            # subsidiary objects.
48            self._original_args = args
49            self._original_kwargs = kwargs
50
51            # Set up some variables we're going to use later.
52            self._sock = None
53            self._conn = None
54
55            # Prepare our backlog of method calls.
56            self._call_queue = []
57
58        def __getattr__(self, name):
59            # Anything that can't be found on this instance is presumably a
60            # property of underlying connection object.
61            # We need to be a little bit careful here. There are a few methods
62            # that can act on a HTTPSConnection before it actually connects to
63            # the remote server. We don't want to change the semantics of the,
64            # HTTPSConnection so we need to spot these and queue them up. When
65            # we actually create the backing Connection, we'll apply them
66            # immediately. These methods can't throw exceptions, so we should
67            # be fine.
68            delay_methods = ["set_tunnel", "set_debuglevel"]
69
70            if self._conn is None and name in delay_methods:
71                # Return a little closure that saves off the method call to
72                # apply later.
73                def capture(obj, *args, **kwargs):
74                    self._call_queue.append((name, args, kwargs))
75                return capture
76            elif self._conn is None:
77                # We're being told to do something! We can now connect to the
78                # remote server and build the connection object.
79                self._delayed_connect()
80
81            # Call through to the underlying object.
82            return getattr(self._conn, name)
83
84        def _delayed_connect(self):
85            """
86            Called when we need to work out what kind of HTTPS connection we're
87            actually going to use.
88            """
89            # Because we're ghetto, we're going to quickly create a
90            # HTTPConnection object to parse the args and kwargs for us, and
91            # grab the values out.
92            tempconn = httplib.HTTPConnection(*self._original_args,
93                                              **self._original_kwargs)
94            host = tempconn.host
95            port = tempconn.port
96            timeout = tempconn.timeout
97            source_address = tempconn.source_address
98
99            # Connect to the remote server.
100            sock = socket.create_connection(
101                (host, port),
102                timeout,
103                source_address
104            )
105
106            # Wrap it in TLS. This needs to be looked at in future when I pull
107            # in the TLS verification logic from urllib3, but right now we
108            # accept insecurity because no-one's using this anyway.
109            sock = wrap_socket(sock, host)
110
111            # At this early stage the library can't do HTTP/2, so who cares?
112            tempconn.sock = sock
113            self._sock = sock
114            self._conn = tempconn
115
116            return
117