/circuits/web/wsgi.py

https://bitbucket.org/prologic/circuits/ · Python · 235 lines · 170 code · 56 blank · 9 comment · 28 complexity · 3a3fd840dde529c8d09b8a0ee53122db MD5 · raw file

  1. # Module: wsgi
  2. # Date: 6th November 2008
  3. # Author: James Mills, prologic at shortcircuit dot net dot au
  4. """WSGI Components
  5. This module implements WSGI Components.
  6. """
  7. try:
  8. from urllib.parse import unquote
  9. except ImportError:
  10. from urllib import unquote # NOQA
  11. from operator import itemgetter
  12. from traceback import format_tb
  13. from types import GeneratorType
  14. from sys import exc_info as _exc_info
  15. from circuits.tools import tryimport
  16. from circuits.core import handler, BaseComponent
  17. StringIO = tryimport(("cStringIO", "StringIO", "io"), "StringIO")
  18. from .http import HTTP
  19. from .events import request
  20. from .headers import Headers
  21. from .errors import httperror
  22. from circuits.web import wrappers
  23. from .dispatchers import Dispatcher
  24. def create_environ(errors, path, req):
  25. environ = {}
  26. env = environ.__setitem__
  27. env("REQUEST_METHOD", req.method)
  28. env("SERVER_NAME", req.host.split(":", 1)[0])
  29. env("SERVER_PORT", "%i" % (req.server.port or 0))
  30. env("SERVER_PROTOCOL", "HTTP/%d.%d" % req.server.http.protocol)
  31. env("QUERY_STRING", req.qs)
  32. env("SCRIPT_NAME", req.script_name)
  33. env("CONTENT_TYPE", req.headers.get("Content-Type", ""))
  34. env("CONTENT_LENGTH", req.headers.get("Content-Length", ""))
  35. env("REMOTE_ADDR", req.remote.ip)
  36. env("REMOTE_PORT", "%i" % (req.remote.port or 0))
  37. env("wsgi.version", (1, 0))
  38. env("wsgi.input", req.body)
  39. env("wsgi.errors", errors)
  40. env("wsgi.multithread", False)
  41. env("wsgi.multiprocess", False)
  42. env("wsgi.run_once", False)
  43. env("wsgi.url_scheme", req.scheme)
  44. if req.path:
  45. req.script_name = req.path[:len(path)]
  46. req.path = req.path[len(path):]
  47. env("SCRIPT_NAME", req.script_name)
  48. env("PATH_INFO", req.path)
  49. for k, v in list(req.headers.items()):
  50. env("HTTP_%s" % k.upper().replace("-", "_"), v)
  51. return environ
  52. class Application(BaseComponent):
  53. channel = "web"
  54. headerNames = {
  55. "HTTP_CGI_AUTHORIZATION": "Authorization",
  56. "CONTENT_LENGTH": "Content-Length",
  57. "CONTENT_TYPE": "Content-Type",
  58. "REMOTE_HOST": "Remote-Host",
  59. "REMOTE_ADDR": "Remote-Addr",
  60. }
  61. def init(self):
  62. self._finished = False
  63. HTTP(self).register(self)
  64. Dispatcher().register(self)
  65. def translateHeaders(self, environ):
  66. for cgiName in environ:
  67. # We assume all incoming header keys are uppercase already.
  68. if cgiName in self.headerNames:
  69. yield self.headerNames[cgiName], environ[cgiName]
  70. elif cgiName[:5] == "HTTP_":
  71. # Hackish attempt at recovering original header names.
  72. translatedHeader = cgiName[5:].replace("_", "-")
  73. yield translatedHeader, environ[cgiName]
  74. def getRequestResponse(self, environ):
  75. env = environ.get
  76. headers = Headers(list(self.translateHeaders(environ)))
  77. protocol = tuple(map(int, env("SERVER_PROTOCOL")[5:].split(".")))
  78. req = wrappers.Request(
  79. None,
  80. env("REQUEST_METHOD"),
  81. env("wsgi.url_scheme"),
  82. env("PATH_INFO", ""),
  83. protocol,
  84. env("QUERY_STRING", ""),
  85. headers=headers
  86. )
  87. req.remote = wrappers.Host(env("REMOTE_ADDR"), env("REMTOE_PORT"))
  88. req.script_name = env("SCRIPT_NAME")
  89. req.wsgi_environ = environ
  90. try:
  91. cl = int(headers.get("Content-Length", "0"))
  92. except:
  93. cl = 0
  94. req.body.write(env("wsgi.input").read(cl))
  95. req.body.seek(0)
  96. res = wrappers.Response(req)
  97. res.gzip = "gzip" in req.headers.get("Accept-Encoding", "")
  98. return req, res
  99. def __call__(self, environ, start_response, exc_info=None):
  100. self.request, self.response = self.getRequestResponse(environ)
  101. self.fire(request(self.request, self.response))
  102. self._finished = False
  103. while self or not self._finished:
  104. self.tick()
  105. self.response.prepare()
  106. body = self.response.body
  107. status = self.response.status
  108. headers = list(self.response.headers.items())
  109. start_response(str(status), headers, exc_info)
  110. return body
  111. @handler("response", channel="web")
  112. def response(self, event, response):
  113. self._finished = True
  114. event.stop()
  115. @property
  116. def host(self):
  117. return ""
  118. @property
  119. def port(self):
  120. return 0
  121. @property
  122. def secure(self):
  123. return False
  124. class _Empty(str):
  125. def __bool__(self):
  126. return True
  127. __nonzero__ = __bool__
  128. empty = _Empty()
  129. del _Empty
  130. class Gateway(BaseComponent):
  131. channel = "web"
  132. def init(self, apps):
  133. self.apps = apps
  134. self.errors = dict((k, StringIO()) for k in self.apps.keys())
  135. @handler("request", priority=0.2)
  136. def _on_request(self, event, req, res):
  137. if not self.apps:
  138. return
  139. parts = req.path.split("/")
  140. candidates = []
  141. for i in range(len(parts)):
  142. k = "/".join(parts[:(i + 1)]) or "/"
  143. if k in self.apps:
  144. candidates.append((k, self.apps[k]))
  145. candidates = sorted(candidates, key=itemgetter(0), reverse=True)
  146. if not candidates:
  147. return
  148. path, app = candidates[0]
  149. buffer = StringIO()
  150. def start_response(status, headers, exc_info=None):
  151. res.status = int(status.split(" ", 1)[0])
  152. for header in headers:
  153. res.headers.add_header(*header)
  154. return buffer.write
  155. errors = self.errors[path]
  156. environ = create_environ(errors, path, req)
  157. try:
  158. body = app(environ, start_response)
  159. if isinstance(body, list):
  160. body = "".join(body)
  161. elif isinstance(body, GeneratorType):
  162. res.body = body
  163. res.stream = True
  164. return res
  165. if not body:
  166. if not buffer.tell():
  167. return empty
  168. else:
  169. buffer.seek(0)
  170. return buffer
  171. else:
  172. return body
  173. except Exception as error:
  174. etype, evalue, etraceback = _exc_info()
  175. error = (etype, evalue, format_tb(etraceback))
  176. return httperror(req, res, 500, error=error)
  177. finally:
  178. event.stop()