class HTTP(gobject.GObject): """HTTP protocol client class.""" __gsignals__ = { "error" : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (object,)), # IoError "response-received": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (object,)), # HTTPResponse "request-sent": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (object,)), # HTTPRequest } def __init__(self, host, port=80, proxies={}): """Connection initialization @param host: the host to connect to. @type host: string @param port: the port number to connect to @type port: integer @param proxies: proxies that we can use to connect @type proxies: L{gnet.proxy.ProxyInfos}""" gobject.GObject.__init__(self) self._host = host self._port = port self._proxies = proxies self._http_proxy = None self._transport = None self._http_parser = None self._outgoing_queue = [] self._redirected = False self._waiting_response = False self._parser_handles = [] self._transport_handles = [] if self._proxies and self._proxies.get('http', None): if self._proxies['http'].type == 'http': self._http_proxy = self._proxies['http'] self._errored = False self.connect("error", self._on_self_error) def _on_self_error(self, *args): self._errored = True def _setup_transport(self): if self._transport is None: if self._http_proxy: self._transport = TCPClient(self._http_proxy.host, self._http_proxy.port) else: self._transport = TCPClient(self._host, self._port) if self._proxies: self._transport = ProxyFactory(self._transport, self._proxies, 'http') self._setup_parser() if self._transport.get_property("status") != IoStatus.OPEN: self._transport.open() def _setup_parser(self): self._http_parser = HTTPParser(self._transport) self._parser_handles.append(self._http_parser.connect("received", self._on_response_received)) self._transport_handles.append(self._transport.connect("notify::status", self._on_status_change)) self._transport_handles.append(self._transport.connect("error", self._on_error)) self._transport_handles.append(self._transport.connect("sent", self._on_request_sent)) def _clean_transport(self): if self._http_parser: self._http_parser.disable() for handle in self._parser_handles: self._http_parser.disconnect(handle) self._http_parser = None if self._transport: for handle in self._transport_handles: self._transport.disconnect(handle) def _on_status_change(self, transport, param): if transport.get_property("status") == IoStatus.OPEN: self._process_queue() elif transport.get_property("status") == IoStatus.CLOSED and\ (self._waiting_response or len(self._outgoing_queue) > 0) and\ not (self._errored or self._redirected): self._waiting_response = False self._setup_transport() def _on_request_sent(self, transport, request, length): self._waiting_response = True assert(str(self._outgoing_queue[0]) == request) self.emit("request-sent", self._outgoing_queue[0]) def _on_response_received(self, parser, response): if response.status >= 100 and response.status < 200: return if not self._waiting_response: logger.warning("Received response but wasn't waiting for one") logger.warning("<<< " + str(response)) return self._waiting_response = False if response.status in (301, 302): self.close() location = response.headers['Location'] logger.info("Server moved to %s" % location) logger.info("<<< " + str(response)) protocol, host, path, query, fragment = urlsplit(location) self._redirected = True self._outgoing_queue[0].headers['Host'] = host try: host, port = host.rsplit(":", 1) port = int(port) except (TypeError, ValueError): port = None self._host = host self._redirected = False self._setup_transport() return if len(self._outgoing_queue) > 0: self._outgoing_queue.pop(0) # pop the request from the queue if response.status >= 400: logger.error("Received error code %i (%s) from %s:%i" % (response.status, response.reason, self._host, self._port)) self.emit("error", HTTPError(response)) else: self.emit("response-received", response) self._process_queue() # next request ? def _on_error(self, transport, error): self.emit("error", error) def _process_queue(self): if len(self._outgoing_queue) == 0 or \ self._waiting_response: # no pipelining return if self._transport is None or \ self._transport.get_property("status") != IoStatus.OPEN: self._setup_transport() return self._transport.send(str(self._outgoing_queue[0])) def request(self, resource='/', headers=None, data='', method='GET'): if headers is None: headers = {} headers['Host'] = self._host + ':' + str(self._port) headers['Content-Length'] = str(len(data)) if 'User-Agent' not in headers: user_agent = GNet.NAME, GNet.VERSION, platform.system(), platform.machine() headers['User-Agent'] = "%s/%s (%s %s)" % user_agent if self._http_proxy is not None: url = 'http://%s:%d%s' % (self._host, self._port, resource) if self._http_proxy.user: auth = self._http_proxy.user + ':' + self._http_proxy.password credentials = base64.encodestring(auth).strip() headers['Proxy-Authorization'] = 'Basic ' + credentials else: url = resource request = HTTPRequest(headers, data, method, url) self._outgoing_queue.append(request) self._process_queue() def close(self): self._clean_transport() if self._transport: self._transport.close() self._transport = None
class HTTPConnectProxy(AbstractProxy): def __init__(self, client, proxy_infos): assert(proxy_infos.type in ('http', 'https')), "HTTPConnectProxy expects an http(s) proxy description" assert(client.domain == AF_INET), "HTTP CONNECT only handles INET address family" assert(client.type == SOCK_STREAM), "HTTP CONNECT only handles SOCK_STREAM" assert(client.status == IoStatus.CLOSED), "HTTPConnectProxy expects a closed client" AbstractProxy.__init__(self, client, proxy_infos) self._transport = TCPClient(self._proxy.host, self._proxy.port) self._transport.connect("notify::status", self._on_transport_status) self._transport.connect("error", self._on_transport_error) self._http_parser = HTTPParser(self._transport) self._http_parser.connect("received", self._on_proxy_response) # opening state methods def _pre_open(self, io_object=None): AbstractProxy._pre_open(self) def _post_open(self): AbstractProxy._post_open(self) proxy_protocol = 'CONNECT %s:%s HTTP/1.1\r\n' % (self.host, self.port) proxy_protocol += 'Proxy-Connection: Keep-Alive\r\n' proxy_protocol += 'Pragma: no-cache\r\n' proxy_protocol += 'User-Agent: %s/%s\r\n' % (GNet.NAME, GNet.VERSION) if self._proxy.user: auth = base64.encodestring(self._proxy.user + ':' + self._proxy.password).strip() proxy_protocol += 'Proxy-authorization: Basic ' + auth + '\r\n' proxy_protocol += '\r\n' self._http_parser.enable() self._transport.send(proxy_protocol) # public API @property def protocol(self): return "HTTPConnect" def open(self): """Open the connection.""" if not self._configure(): return self._pre_open() try: self._transport.open() except: pass def close(self): """Close the connection.""" self._http_parser.disable() self._client._proxy_closed() self._transport.close() def send(self, buffer, callback=None, errback=None): self._client.send(buffer, callback, errback=None) # callbacks and signal handlers def _on_transport_status(self, transport, param): if transport.status == IoStatus.OPEN: self._post_open() elif transport.status == IoStatus.OPENING: self._client._proxy_opening(self._transport._transport) self._status = transport.status else: self._status = transport.status def _on_transport_error(self, transport, error): self.close() self.emit("error", error) def _on_proxy_response(self, parser, response): if self.status == IoStatus.OPENING: if response.status == 200: self._http_parser.disable() self._transport.disable() self._client._proxy_open() elif response.status == 100: return True else: logger.error("Connection failed (%s)" % response.status) self.close() self.emit("error", HTTPConnectError(self, response.status)) return False
class HTTPConnectProxy(AbstractProxy): def __init__(self, client, proxy_infos): assert (proxy_infos.type in ( 'http', 'https')), "HTTPConnectProxy expects an http(s) proxy description" assert (client.domain == AF_INET ), "HTTP CONNECT only handles INET address family" assert (client.type == SOCK_STREAM ), "HTTP CONNECT only handles SOCK_STREAM" assert (client.status == IoStatus.CLOSED ), "HTTPConnectProxy expects a closed client" AbstractProxy.__init__(self, client, proxy_infos) self._transport = TCPClient(self._proxy.host, self._proxy.port) self._transport.connect("notify::status", self._on_transport_status) self._transport.connect("error", self._on_transport_error) self._http_parser = HTTPParser(self._transport) self._http_parser.connect("received", self._on_proxy_response) # opening state methods def _pre_open(self, io_object=None): AbstractProxy._pre_open(self) def _post_open(self): AbstractProxy._post_open(self) proxy_protocol = 'CONNECT %s:%s HTTP/1.1\r\n' % (self.host, self.port) proxy_protocol += 'Proxy-Connection: Keep-Alive\r\n' proxy_protocol += 'Pragma: no-cache\r\n' proxy_protocol += 'User-Agent: %s/%s\r\n' % (GNet.NAME, GNet.VERSION) if self._proxy.user: auth = base64.encodestring(self._proxy.user + ':' + self._proxy.password).strip() proxy_protocol += 'Proxy-authorization: Basic ' + auth + '\r\n' proxy_protocol += '\r\n' self._http_parser.enable() self._transport.send(proxy_protocol) # public API @property def protocol(self): return "HTTPConnect" def open(self): """Open the connection.""" if not self._configure(): return self._pre_open() try: self._transport.open() except: pass def close(self): """Close the connection.""" self._http_parser.disable() self._client._proxy_closed() self._transport.close() def send(self, buffer, callback=None, errback=None): self._client.send(buffer, callback, errback=None) # callbacks and signal handlers def _on_transport_status(self, transport, param): if transport.status == IoStatus.OPEN: self._post_open() elif transport.status == IoStatus.OPENING: self._client._proxy_opening(self._transport._transport) self._status = transport.status else: self._status = transport.status def _on_transport_error(self, transport, error): self.close() self.emit("error", error) def _on_proxy_response(self, parser, response): if self.status == IoStatus.OPENING: if response.status == 200: self._http_parser.disable() self._transport.disable() self._client._proxy_open() elif response.status == 100: return True else: logger.error("Connection failed (%s)" % response.status) self.close() self.emit("error", HTTPConnectError(self, response.status)) return False