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