/circuits/web/client.py
https://bitbucket.org/prologic/circuits/ · Python · 135 lines · 87 code · 37 blank · 11 comment · 23 complexity · 349c0498cf4980a125134f71e3d688e7 MD5 · raw file
- try:
- from urllib.parse import urlparse
- except ImportError:
- from urlparse import urlparse # NOQA
- from circuits.protocols.http import HTTP
- from circuits.web.headers import Headers
- from circuits.net.sockets import TCPClient
- from circuits.net.events import close, connect, write
- from circuits.core import handler, BaseComponent, Event
- def parse_url(url):
- p = urlparse(url)
- if p.hostname:
- host = p.hostname
- else:
- raise ValueError("URL must be absolute")
- if p.scheme == "http":
- secure = False
- port = p.port or 80
- elif p.scheme == "https":
- secure = True
- port = p.port or 443
- else:
- raise ValueError("Invalid URL scheme")
- path = p.path or "/"
- if p.query:
- path += "?" + p.query
- return (host, port, path, secure)
- class HTTPException(Exception):
- pass
- class NotConnected(HTTPException):
- pass
- class request(Event):
- """request Event
- This Event is used to initiate a new request.
- :param method: HTTP Method (PUT, GET, POST, DELETE)
- :type method: str
- :param url: Request URL
- :type url: str
- """
- def __init__(self, method, path, body=None, headers={}):
- "x.__init__(...) initializes x; see x.__class__.__doc__ for signature"
- super(request, self).__init__(method, path, body, headers)
- class Client(BaseComponent):
- channel = "client"
- def __init__(self, channel=channel):
- super(Client, self).__init__(channel=channel)
- self._response = None
- self._transport = TCPClient(channel=channel).register(self)
- HTTP(channel=channel).register(self._transport)
- @handler("write")
- def write(self, data):
- if self._transport.connected:
- self.fire(write(data), self._transport)
- @handler("close")
- def close(self):
- if self._transport.connected:
- self.fire(close(), self._transport)
- @handler("connect", priority=1)
- def connect(self, event, host=None, port=None, secure=None):
- if not self._transport.connected:
- self.fire(connect(host, port, secure), self._transport)
- event.stop()
- @handler("request")
- def request(self, method, url, body=None, headers={}):
- host, port, path, secure = parse_url(url)
- if not self._transport.connected:
- self.fire(connect(host, port, secure))
- yield self.wait("connected", self._transport.channel)
- headers = Headers([(k, v) for k, v in headers.items()])
- # Clients MUST include Host header in HTTP/1.1 requests (RFC 2616)
- if "Host" not in headers:
- headers["Host"] = "{0:s}{1:s}".format(
- host, "" if port in (80, 443) else ":{0:d}".format(port)
- )
- if body is not None:
- headers["Content-Length"] = len(body)
- command = "%s %s HTTP/1.1" % (method, path)
- message = "%s\r\n%s" % (command, headers)
- self.fire(write(message.encode('utf-8')), self._transport)
- if body is not None:
- self.fire(write(body), self._transport)
- yield (yield self.wait("response"))
- @handler("response")
- def _on_response(self, response):
- self._response = response
- if response.headers.get("Connection") == "close":
- self.fire(close(), self._transport)
- return response
- @property
- def connected(self):
- if hasattr(self, "_transport"):
- return self._transport.connected
- @property
- def response(self):
- return getattr(self, "_response", None)