/circuits/web/wrappers.py
https://bitbucket.org/prologic/circuits/ · Python · 384 lines · 335 code · 25 blank · 24 comment · 34 complexity · b81dbd0a9a3d13d066cf5643489925e9 MD5 · raw file
- # Module: wrappers
- # Date: 13th September 2007
- # Author: James Mills, prologic at shortcircuit dot net dot au
- """Request/Response Wrappers
- This module implements the Request and Response objects.
- """
- from time import time
- from io import BytesIO
- from functools import partial
- try:
- from Cookie import SimpleCookie
- except ImportError:
- from http.cookies import SimpleCookie # NOQA
- from .url import parse_url
- from .headers import Headers
- from ..six import binary_type
- from .errors import httperror
- from circuits.net.sockets import BUFSIZE
- from .constants import HTTP_STATUS_CODES, SERVER_VERSION
- try:
- unicode
- except NameError:
- unicode = str
- try:
- from email.utils import formatdate
- formatdate = partial(formatdate, usegmt=True)
- except ImportError:
- from rfc822 import formatdate as HTTPDate # NOQA
- def file_generator(input, chunkSize=BUFSIZE):
- chunk = input.read(chunkSize)
- while chunk:
- yield chunk
- chunk = input.read(chunkSize)
- input.close()
- class Host(object):
- """An internet address.
- name should be the client's host name. If not available (because no DNS
- lookup is performed), the IP address should be used instead.
- """
- ip = "0.0.0.0"
- port = 80
- name = "unknown.tld"
- def __init__(self, ip, port, name=None):
- self.ip = ip
- self.port = port
- if name is None:
- name = ip
- self.name = name
- def __repr__(self):
- return "Host(%r, %r, %r)" % (self.ip, self.port, self.name)
- class HTTPStatus(object):
- __slots__ = ("_reason", "_status",)
- def __init__(self, status=200, reason=None):
- self._status = status
- self._reason = reason or HTTP_STATUS_CODES.get(status, "")
- def __int__(self):
- return self._status
- def __lt__(self, other):
- if isinstance(other, int):
- return self._status < other
- return super(HTTPStatus, self).__lt__(other)
- def __gt__(self, other):
- if isinstance(other, int):
- return self._status > other
- return super(HTTPStatus, self).__gt__(other)
- def __le__(self, other):
- if isinstance(other, int):
- return self._status <= other
- return super(HTTPStatus, self).__le__(other)
- def __ge__(self, other):
- if isinstance(other, int):
- return self._status >= other
- return super(HTTPStatus, self).__ge__(other)
- def __eq__(self, other):
- if isinstance(other, int):
- return self._status == other
- return super(HTTPStatus, self).__eq__(other)
- def __str__(self):
- return "{0:d} {1:s}".format(self._status, self._reason)
- def __repr__(self):
- return "<Status (status={0:d} reason={1:s}>".format(
- self._status, self._reason
- )
- def __format__(self, format_spec):
- return format(str(self), format_spec)
- @property
- def status(self):
- return self._status
- @property
- def reason(self):
- return self._reason
- class Request(object):
- """Creates a new Request object to hold information about a request.
- :param sock: The socket object of the request.
- :type sock: socket.socket
- :param method: The requested method.
- :type method: str
- :param scheme: The requested scheme.
- :type scheme: str
- :param path: The requested path.
- :type path: str
- :param protocol: The requested protocol.
- :type protocol: str
- :param qs: The query string of the request.
- :type qs: str
- """
- server = None
- """:cvar: A reference to the underlying server"""
- scheme = "http"
- protocol = (1, 1)
- host = ""
- local = Host("127.0.0.1", 80)
- remote = Host("", 0)
- index = None
- script_name = ""
- login = None
- handled = False
- def __init__(self, sock, method="GET", scheme="http", path="/",
- protocol=(1, 1), qs="", headers=None, server=None):
- "initializes x; see x.__class__.__doc__ for signature"
- self.sock = sock
- self.method = method
- self.scheme = scheme or Request.scheme
- self.path = path
- self.protocol = protocol
- self.qs = qs
- self.headers = headers or Headers()
- self.server = server
- self.cookie = SimpleCookie()
- if sock is not None:
- name = sock.getpeername()
- if name is not None:
- self.remote = Host(*name)
- else:
- name = sock.getsockname()
- self.remote = Host(name, "", name)
- cookie = self.headers.get("Cookie")
- if cookie is not None:
- self.cookie.load(cookie)
- self.body = BytesIO()
- if self.server is not None:
- self.local = Host(self.server.host, self.server.port)
- try:
- host = self.headers["Host"]
- if ":" in host:
- parts = host.split(":", 1)
- host = parts[0]
- port = int(parts[1])
- else:
- port = 443 if self.scheme == "https" else 80
- except KeyError:
- host = self.local.name or self.local.ip
- port = getattr(self.server, "port")
- self.host = host
- self.port = port
- base = "{0:s}://{1:s}{2:s}/".format(
- self.scheme,
- self.host,
- ":{0:d}".format(self.port) if self.port not in (80, 443) else ""
- )
- self.base = parse_url(base)
- url = "{0:s}{1:s}{2:s}".format(
- base,
- self.path,
- "?{0:s}".format(self.qs) if self.qs else ""
- )
- self.uri = parse_url(url)
- self.uri.sanitize()
- def __repr__(self):
- protocol = "HTTP/%d.%d" % self.protocol
- return "<Request %s %s %s>" % (self.method, self.path, protocol)
- class Body(object):
- """Response Body"""
- def __get__(self, response, cls=None):
- if response is None:
- return self
- else:
- return response._body
- def __set__(self, response, value):
- if response == value:
- return
- if isinstance(value, binary_type):
- if value:
- value = [value]
- else:
- value = []
- elif hasattr(value, "read"):
- response.stream = True
- value = file_generator(value)
- elif isinstance(value, httperror):
- value = [str(value)]
- elif value is None:
- value = []
- response._body = value
- class Status(object):
- """Response Status"""
- def __get__(self, response, cls=None):
- if response is None:
- return self
- else:
- return response._status
- def __set__(self, response, value):
- value = HTTPStatus(value) if isinstance(value, int) else value
- response._status = value
- class Response(object):
- """Response(sock, request) -> new Response object
- A Response object that holds the response to
- send back to the client. This ensure that the correct data
- is sent in the correct order.
- """
- body = Body()
- status = Status()
- done = False
- close = False
- stream = False
- chunked = False
- def __init__(self, request, encoding='utf-8', status=None):
- "initializes x; see x.__class__.__doc__ for signature"
- self.request = request
- self.encoding = encoding
- self._body = []
- self._status = HTTPStatus(status if status is not None else 200)
- self.time = time()
- self.headers = Headers()
- self.headers["Date"] = formatdate()
- if self.request.server is not None:
- self.headers.add_header("Server", self.request.server.http.version)
- else:
- self.headers.add_header("X-Powered-By", SERVER_VERSION)
- self.cookie = self.request.cookie
- self.protocol = "HTTP/%d.%d" % self.request.protocol
- def __repr__(self):
- return "<Response %s %s (%d)>" % (
- self.status,
- self.headers.get("Content-Type"),
- (len(self.body) if isinstance(self.body, str) else 0)
- )
- def __str__(self):
- self.prepare()
- protocol = self.protocol
- status = "{0:s}".format(self.status)
- return "{0:s} {1:s}\r\n".format(protocol, status)
- def __bytes__(self):
- return str(self).encode(self.encoding)
- def prepare(self):
- # Set a default content-Type if we don't have one.
- self.headers.setdefault(
- "Content-Type", "text/html; charset={0:s}".format(self.encoding)
- )
- cLength = None
- if self.body is not None:
- if isinstance(self.body, bytes):
- cLength = len(self.body)
- elif isinstance(self.body, unicode):
- cLength = len(self.body.encode(self.encoding))
- elif isinstance(self.body, list):
- cLength = sum(
- [
- len(s.encode(self.encoding))
- if not isinstance(s, bytes)
- else len(s) for s in self.body
- if s is not None
- ]
- )
- if cLength is not None:
- self.headers["Content-Length"] = str(cLength)
- for k, v in self.cookie.items():
- self.headers.add_header("Set-Cookie", v.OutputString())
- status = self.status
- if status == 413:
- self.close = True
- elif "Content-Length" not in self.headers:
- if status < 200 or status in (204, 205, 304):
- pass
- else:
- if self.protocol == "HTTP/1.1" \
- and self.request.method != "HEAD" \
- and self.request.server is not None \
- and not cLength == 0:
- self.chunked = True
- self.headers.add_header("Transfer-Encoding", "chunked")
- else:
- self.close = True
- if (self.request.server is not None
- and "Connection" not in self.headers):
- if self.protocol == "HTTP/1.1":
- if self.close:
- self.headers.add_header("Connection", "close")
- else:
- if not self.close:
- self.headers.add_header("Connection", "Keep-Alive")
- if self.headers.get("Transfer-Encoding", "") == "chunked":
- self.chunked = True