/circuits/web/http.py

https://bitbucket.org/prologic/circuits/ · Python · 522 lines · 344 code · 36 blank · 142 comment · 34 complexity · 2daed33e0f4e278ecb7821fddfea502c MD5 · raw file

  1. # Module: http
  2. # Date: 13th September 2007
  3. # Author: James Mills, prologic at shortcircuit dot net dot au
  4. """Hyper Text Transfer Protocol
  5. This module implements the server side Hyper Text Transfer Protocol
  6. or commonly known as HTTP.
  7. """
  8. from io import BytesIO
  9. try:
  10. from urllib.parse import quote
  11. from urllib.parse import urlparse, urlunparse
  12. except ImportError:
  13. from urllib import quote # NOQA
  14. from urlparse import urlparse, urlunparse # NOQA
  15. from circuits.six import text_type
  16. from circuits.net.events import close, write
  17. from circuits.core import handler, BaseComponent, Value
  18. from . import wrappers
  19. from .url import parse_url
  20. from .utils import is_ssl_handshake
  21. from .exceptions import HTTPException
  22. from .events import request, response, stream
  23. from .parsers import HttpParser, BAD_FIRST_LINE
  24. from .errors import httperror, notfound, redirect
  25. from .exceptions import Redirect as RedirectException
  26. from .constants import SERVER_VERSION, SERVER_PROTOCOL
  27. MAX_HEADER_FRAGENTS = 20
  28. HTTP_ENCODING = 'utf-8'
  29. try:
  30. unicode
  31. except NameError:
  32. unicode = str
  33. class HTTP(BaseComponent):
  34. """HTTP Protocol Component
  35. Implements the HTTP server protocol and parses and processes incoming
  36. HTTP messages, creating and sending an appropriate response.
  37. The component handles :class:`~circuits.net.sockets.Read` events
  38. on its channel and collects the associated data until a complete
  39. HTTP request has been received. It parses the request's content
  40. and puts it in a :class:`~circuits.web.wrappers.Request` object and
  41. creates a corresponding :class:`~circuits.web.wrappers.Response`
  42. object. Then it emits a :class:`~circuits.web.events.Request`
  43. event with these objects as arguments.
  44. The component defines several handlers that send a response back to
  45. the client.
  46. """
  47. channel = "web"
  48. def __init__(self, server, encoding=HTTP_ENCODING, channel=channel):
  49. super(HTTP, self).__init__(channel=channel)
  50. self._server = server
  51. self._encoding = encoding
  52. url = "{0:s}://{1:s}{2:s}".format(
  53. (server.secure and "https") or "http",
  54. server.host or "0.0.0.0",
  55. ":{0:d}".format(server.port or 80)
  56. if server.port not in (80, 443)
  57. else ""
  58. )
  59. self.uri = parse_url(url)
  60. self._clients = {}
  61. self._buffers = {}
  62. @property
  63. def version(self):
  64. return SERVER_VERSION
  65. @property
  66. def protocol(self):
  67. return SERVER_PROTOCOL
  68. @property
  69. def scheme(self):
  70. if not hasattr(self, "_server"):
  71. return
  72. return "https" if self._server.secure else "http"
  73. @property
  74. def base(self):
  75. if not hasattr(self, "uri"):
  76. return
  77. return self.uri.utf8().rstrip(b"/").decode(self._encoding)
  78. @handler("stream") # noqa
  79. def _on_stream(self, res, data):
  80. sock = res.request.sock
  81. if data is not None:
  82. if isinstance(data, text_type):
  83. data = data.encode(self._encoding)
  84. if res.chunked:
  85. buf = [
  86. hex(len(data))[2:].encode(self._encoding),
  87. b"\r\n",
  88. data,
  89. b"\r\n"
  90. ]
  91. data = b"".join(buf)
  92. self.fire(write(sock, data))
  93. if res.body and not res.done:
  94. try:
  95. data = next(res.body)
  96. while not data: # Skip over any null byte sequences
  97. data = next(res.body)
  98. except StopIteration:
  99. data = None
  100. self.fire(stream(res, data))
  101. else:
  102. if res.body:
  103. res.body.close()
  104. if res.chunked:
  105. self.fire(write(sock, b"0\r\n\r\n"))
  106. if res.close:
  107. self.fire(close(sock))
  108. if sock in self._clients:
  109. del self._clients[sock]
  110. res.done = True
  111. @handler("response") # noqa
  112. def _on_response(self, res):
  113. """``Response`` Event Handler
  114. :param response: the ``Response`` object created when the
  115. HTTP request was initially received.
  116. :type response: :class:`~circuits.web.wrappers.Response`
  117. This handler builds an HTTP response data stream from
  118. the information contained in the *response* object and
  119. sends it to the client (firing ``write`` events).
  120. """
  121. # send HTTP response status line and headers
  122. req = res.request
  123. headers = res.headers
  124. sock = req.sock
  125. if req.method == "HEAD":
  126. self.fire(write(sock, bytes(res)))
  127. self.fire(write(sock, bytes(headers)))
  128. elif res.stream and res.body:
  129. try:
  130. data = next(res.body)
  131. except StopIteration:
  132. data = None
  133. self.fire(write(sock, bytes(res)))
  134. self.fire(write(sock, bytes(headers)))
  135. self.fire(stream(res, data))
  136. else:
  137. self.fire(write(sock, bytes(res)))
  138. self.fire(write(sock, bytes(headers)))
  139. if isinstance(res.body, bytes):
  140. body = res.body
  141. elif isinstance(res.body, text_type):
  142. body = res.body.encode(self._encoding)
  143. else:
  144. parts = (
  145. s
  146. if isinstance(s, bytes) else s.encode(self._encoding)
  147. for s in res.body if s is not None
  148. )
  149. body = b"".join(parts)
  150. if body:
  151. if res.chunked:
  152. buf = [
  153. hex(len(body))[2:].encode(self._encoding),
  154. b"\r\n",
  155. body,
  156. b"\r\n"
  157. ]
  158. body = b"".join(buf)
  159. self.fire(write(sock, body))
  160. if res.chunked:
  161. self.fire(write(sock, b"0\r\n\r\n"))
  162. if not res.stream:
  163. if res.close:
  164. self.fire(close(sock))
  165. # Delete the request/response objects if present
  166. if sock in self._clients:
  167. del self._clients[sock]
  168. res.done = True
  169. @handler("disconnect")
  170. def _on_disconnect(self, sock):
  171. if sock in self._clients:
  172. del self._clients[sock]
  173. @handler("read") # noqa
  174. def _on_read(self, sock, data):
  175. """Read Event Handler
  176. Process any incoming data appending it to an internal buffer.
  177. Split the buffer by the standard HTTP delimiter CRLF and create
  178. Raw Event per line. Any unfinished lines of text, leave in the buffer.
  179. """
  180. if sock in self._buffers:
  181. parser = self._buffers[sock]
  182. else:
  183. self._buffers[sock] = parser = HttpParser(0, True)
  184. # If we receive an SSL handshake at the start of a request
  185. # and we're not a secure server, then immediately close the
  186. # client connection since we can't respond to it anyway.
  187. if is_ssl_handshake(data) and not self._server.secure:
  188. if sock in self._buffers:
  189. del self._buffers[sock]
  190. if sock in self._clients:
  191. del self._clients[sock]
  192. return self.fire(close(sock))
  193. _scheme = "https" if self._server.secure else "http"
  194. parser.execute(data, len(data))
  195. if not parser.is_headers_complete():
  196. if parser.errno is not None:
  197. if parser.errno == BAD_FIRST_LINE:
  198. req = wrappers.Request(sock, server=self._server)
  199. else:
  200. req = wrappers.Request(
  201. sock,
  202. parser.get_method(),
  203. parser.get_scheme() or _scheme,
  204. parser.get_path(),
  205. parser.get_version(),
  206. parser.get_query_string(),
  207. server=self._server
  208. )
  209. req.server = self._server
  210. res = wrappers.Response(req, encoding=self._encoding)
  211. del self._buffers[sock]
  212. return self.fire(httperror(req, res, 400))
  213. return
  214. if sock in self._clients:
  215. req, res = self._clients[sock]
  216. else:
  217. method = parser.get_method()
  218. scheme = parser.get_scheme() or _scheme
  219. path = parser.get_path()
  220. version = parser.get_version()
  221. query_string = parser.get_query_string()
  222. req = wrappers.Request(
  223. sock, method, scheme, path, version, query_string,
  224. headers=parser.get_headers(), server=self._server
  225. )
  226. res = wrappers.Response(req, encoding=self._encoding)
  227. self._clients[sock] = (req, res)
  228. rp = req.protocol
  229. sp = self.protocol
  230. if rp[0] != sp[0]:
  231. # the major HTTP version differs
  232. return self.fire(httperror(req, res, 505))
  233. res.protocol = "HTTP/{0:d}.{1:d}".format(*min(rp, sp))
  234. res.close = not parser.should_keep_alive()
  235. clen = int(req.headers.get("Content-Length", "0"))
  236. if clen and not parser.is_message_complete():
  237. return
  238. if hasattr(sock, "getpeercert"):
  239. peer_cert = sock.getpeercert()
  240. if peer_cert:
  241. e = request(req, res, peer_cert)
  242. else:
  243. e = request(req, res)
  244. else:
  245. e = request(req, res)
  246. # Guard against unwanted request paths (SECURITY).
  247. path = req.path
  248. _path = req.uri._path
  249. if (path.encode(self._encoding) != _path) and (
  250. quote(path).encode(self._encoding) != _path):
  251. return self.fire(
  252. redirect(req, res, [req.uri.utf8()], 301)
  253. )
  254. req.body = BytesIO(parser.recv_body())
  255. del self._buffers[sock]
  256. self.fire(e)
  257. @handler("httperror")
  258. def _on_httperror(self, event, req, res, code, **kwargs):
  259. """Default HTTP Error Handler
  260. Default Error Handler that by default just fires a ``Response``
  261. event with the *response* as argument. The *response* is normally
  262. modified by a :class:`~circuits.web.errors.HTTPError` instance
  263. or a subclass thereof.
  264. """
  265. res.body = str(event)
  266. self.fire(response(res))
  267. @handler("request_success") # noqa
  268. def _on_request_success(self, e, value):
  269. """
  270. Handler for the ``RequestSuccess`` event that is automatically
  271. generated after all handlers for a
  272. :class:`~circuits.web.events.Request` event have been invoked
  273. successfully.
  274. :param e: the successfully handled ``Request`` event (having
  275. as attributes the associated
  276. :class:`~circuits.web.wrappers.Request` and
  277. :class:`~circuits.web.wrappers.Response` objects).
  278. :param value: the value(s) returned by the invoked handler(s).
  279. This handler converts the value(s) returned by the
  280. (successfully invoked) handlers for the initial ``Request``
  281. event to a body and assigns it to the ``Response`` object's
  282. ``body`` attribute. It then fires a
  283. :class:`~circuits.web.events.Response` event with the
  284. ``Response`` object as argument.
  285. """
  286. # We only want the non-recursive value at this point.
  287. # If the value is an instance of Value we will set
  288. # the .notify flag and be notified of changes to the value.
  289. value = e.value.getValue(recursive=False)
  290. if isinstance(value, Value) and not value.promise:
  291. value = value.getValue(recursive=False)
  292. req, res = e.args[:2]
  293. if value is None:
  294. self.fire(notfound(req, res))
  295. elif isinstance(value, httperror):
  296. res.body = str(value)
  297. self.fire(response(res))
  298. elif isinstance(value, wrappers.Response):
  299. self.fire(response(value))
  300. elif isinstance(value, Value):
  301. if value.result and not value.errors:
  302. res.body = value.value
  303. self.fire(response(res))
  304. elif value.errors:
  305. error = value.value
  306. etype, evalue, traceback = error
  307. if isinstance(evalue, RedirectException):
  308. self.fire(
  309. redirect(req, res, evalue.urls, evalue.code)
  310. )
  311. elif isinstance(evalue, HTTPException):
  312. if evalue.traceback:
  313. self.fire(
  314. httperror(
  315. req, res, evalue.code,
  316. description=evalue.description,
  317. error=error
  318. )
  319. )
  320. else:
  321. self.fire(
  322. httperror(
  323. req, res, evalue.code,
  324. description=evalue.description
  325. )
  326. )
  327. else:
  328. self.fire(httperror(req, res, error=error))
  329. else:
  330. # We want to be notified of changes to the value
  331. value = e.value.getValue(recursive=False)
  332. value.event = e
  333. value.notify = True
  334. elif isinstance(value, tuple):
  335. etype, evalue, traceback = error = value
  336. if isinstance(evalue, RedirectException):
  337. self.fire(
  338. redirect(req, res, evalue.urls, evalue.code)
  339. )
  340. elif isinstance(evalue, HTTPException):
  341. if evalue.traceback:
  342. self.fire(
  343. httperror(
  344. req, res, evalue.code,
  345. description=evalue.description,
  346. error=error
  347. )
  348. )
  349. else:
  350. self.fire(
  351. httperror(
  352. req, res, evalue.code,
  353. description=evalue.description
  354. )
  355. )
  356. else:
  357. self.fire(httperror(req, res, error=error))
  358. elif not isinstance(value, bool):
  359. res.body = value
  360. self.fire(response(res))
  361. @handler("exception")
  362. def _on_exception(self, *args, **kwargs):
  363. if not len(args) == 3:
  364. return
  365. etype, evalue, etraceback = args
  366. fevent = kwargs["fevent"]
  367. if isinstance(fevent, response):
  368. res = fevent.args[0]
  369. req = res.request
  370. elif isinstance(fevent.value.parent.event, request):
  371. req, res = fevent.value.parent.event.args[:2]
  372. elif len(fevent.args[2:]) == 4:
  373. req, res = fevent.args[2:]
  374. else:
  375. return
  376. if isinstance(evalue, HTTPException):
  377. code = evalue.code
  378. else:
  379. code = None
  380. self.fire(
  381. httperror(
  382. req, res, code=code, error=(etype, evalue, etraceback)
  383. )
  384. )
  385. @handler("request_failure")
  386. def _on_request_failure(self, erequest, error):
  387. req, res = erequest.args
  388. # Ignore filtered requests already handled (eg: HTTPException(s)).
  389. if req.handled:
  390. return
  391. req.handled = True
  392. etype, evalue, traceback = error
  393. if isinstance(evalue, RedirectException):
  394. self.fire(
  395. redirect(req, res, evalue.urls, evalue.code)
  396. )
  397. elif isinstance(evalue, HTTPException):
  398. self.fire(
  399. httperror(
  400. req, res, evalue.code,
  401. description=evalue.description,
  402. error=error
  403. )
  404. )
  405. else:
  406. self.fire(httperror(req, res, error=error))
  407. @handler("response_failure")
  408. def _on_response_failure(self, eresponse, error):
  409. res = eresponse.args[0]
  410. req = res.request
  411. # Ignore failed "response" handlers (eg: Loggers or Tools)
  412. if res.done:
  413. return
  414. res = wrappers.Response(req, self._encoding, 500)
  415. self.fire(httperror(req, res, error=error))
  416. @handler("request_complete")
  417. def _on_request_complete(self, *args, **kwargs):
  418. """Dummy Event Handler for request events
  419. - request_complete
  420. """
  421. @handler("response_success", "response_complete")
  422. def _on_response_feedback(self, *args, **kwargs):
  423. """Dummy Event Handler for response events
  424. - response_success
  425. - response_complete
  426. """
  427. @handler("stream_success", "stream_failure", "stream_complete")
  428. def _on_stream_feedback(self, *args, **kwargs):
  429. """Dummy Event Handler for stream events
  430. - stream_success
  431. - stream_failure
  432. - stream_complete
  433. """