Exemple #1
0
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
Exemple #3
0
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