PageRenderTime 47ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/desktop/core/ext-py/Paste-1.7.2/paste/recursive.py

https://github.com/jcrobak/hue
Python | 401 lines | 391 code | 0 blank | 10 comment | 4 complexity | 88568cde6b7a39d615908554f95de865 MD5 | raw file
  1. # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
  2. # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
  3. """
  4. Middleware to make internal requests and forward requests internally.
  5. When applied, several keys are added to the environment that will allow
  6. you to trigger recursive redirects and forwards.
  7. paste.recursive.include:
  8. When you call
  9. ``environ['paste.recursive.include'](new_path_info)`` a response
  10. will be returned. The response has a ``body`` attribute, a
  11. ``status`` attribute, and a ``headers`` attribute.
  12. paste.recursive.script_name:
  13. The ``SCRIPT_NAME`` at the point that recursive lives. Only
  14. paths underneath this path can be redirected to.
  15. paste.recursive.old_path_info:
  16. A list of previous ``PATH_INFO`` values from previous redirects.
  17. Raise ``ForwardRequestException(new_path_info)`` to do a forward
  18. (aborting the current request).
  19. """
  20. from cStringIO import StringIO
  21. import warnings
  22. __all__ = ['RecursiveMiddleware']
  23. __pudge_all__ = ['RecursiveMiddleware', 'ForwardRequestException']
  24. class CheckForRecursionMiddleware(object):
  25. def __init__(self, app, env):
  26. self.app = app
  27. self.env = env
  28. def __call__(self, environ, start_response):
  29. path_info = environ.get('PATH_INFO','')
  30. if path_info in self.env.get(
  31. 'paste.recursive.old_path_info', []):
  32. raise AssertionError(
  33. "Forwarding loop detected; %r visited twice (internal "
  34. "redirect path: %s)"
  35. % (path_info, self.env['paste.recursive.old_path_info']))
  36. old_path_info = self.env.setdefault('paste.recursive.old_path_info', [])
  37. old_path_info.append(self.env.get('PATH_INFO', ''))
  38. return self.app(environ, start_response)
  39. class RecursiveMiddleware(object):
  40. """
  41. A WSGI middleware that allows for recursive and forwarded calls.
  42. All these calls go to the same 'application', but presumably that
  43. application acts differently with different URLs. The forwarded
  44. URLs must be relative to this container.
  45. Interface is entirely through the ``paste.recursive.forward`` and
  46. ``paste.recursive.include`` environmental keys.
  47. """
  48. def __init__(self, application, global_conf=None):
  49. self.application = application
  50. def __call__(self, environ, start_response):
  51. environ['paste.recursive.forward'] = Forwarder(
  52. self.application,
  53. environ,
  54. start_response)
  55. environ['paste.recursive.include'] = Includer(
  56. self.application,
  57. environ,
  58. start_response)
  59. environ['paste.recursive.include_app_iter'] = IncluderAppIter(
  60. self.application,
  61. environ,
  62. start_response)
  63. my_script_name = environ.get('SCRIPT_NAME', '')
  64. environ['paste.recursive.script_name'] = my_script_name
  65. try:
  66. return self.application(environ, start_response)
  67. except ForwardRequestException, e:
  68. middleware = CheckForRecursionMiddleware(
  69. e.factory(self), environ)
  70. return middleware(environ, start_response)
  71. class ForwardRequestException(Exception):
  72. """
  73. Used to signal that a request should be forwarded to a different location.
  74. ``url``
  75. The URL to forward to starting with a ``/`` and relative to
  76. ``RecursiveMiddleware``. URL fragments can also contain query strings
  77. so ``/error?code=404`` would be a valid URL fragment.
  78. ``environ``
  79. An altertative WSGI environment dictionary to use for the forwarded
  80. request. If specified is used *instead* of the ``url_fragment``
  81. ``factory``
  82. If specifed ``factory`` is used instead of ``url`` or ``environ``.
  83. ``factory`` is a callable that takes a WSGI application object
  84. as the first argument and returns an initialised WSGI middleware
  85. which can alter the forwarded response.
  86. Basic usage (must have ``RecursiveMiddleware`` present) :
  87. .. code-block:: python
  88. from paste.recursive import ForwardRequestException
  89. def app(environ, start_response):
  90. if environ['PATH_INFO'] == '/hello':
  91. start_response("200 OK", [('Content-type', 'text/plain')])
  92. return ['Hello World!']
  93. elif environ['PATH_INFO'] == '/error':
  94. start_response("404 Not Found", [('Content-type', 'text/plain')])
  95. return ['Page not found']
  96. else:
  97. raise ForwardRequestException('/error')
  98. from paste.recursive import RecursiveMiddleware
  99. app = RecursiveMiddleware(app)
  100. If you ran this application and visited ``/hello`` you would get a
  101. ``Hello World!`` message. If you ran the application and visited
  102. ``/not_found`` a ``ForwardRequestException`` would be raised and the caught
  103. by the ``RecursiveMiddleware``. The ``RecursiveMiddleware`` would then
  104. return the headers and response from the ``/error`` URL but would display
  105. a ``404 Not found`` status message.
  106. You could also specify an ``environ`` dictionary instead of a url. Using
  107. the same example as before:
  108. .. code-block:: python
  109. def app(environ, start_response):
  110. ... same as previous example ...
  111. else:
  112. new_environ = environ.copy()
  113. new_environ['PATH_INFO'] = '/error'
  114. raise ForwardRequestException(environ=new_environ)
  115. Finally, if you want complete control over every aspect of the forward you
  116. can specify a middleware factory. For example to keep the old status code
  117. but use the headers and resposne body from the forwarded response you might
  118. do this:
  119. .. code-block:: python
  120. from paste.recursive import ForwardRequestException
  121. from paste.recursive import RecursiveMiddleware
  122. from paste.errordocument import StatusKeeper
  123. def app(environ, start_response):
  124. if environ['PATH_INFO'] == '/hello':
  125. start_response("200 OK", [('Content-type', 'text/plain')])
  126. return ['Hello World!']
  127. elif environ['PATH_INFO'] == '/error':
  128. start_response("404 Not Found", [('Content-type', 'text/plain')])
  129. return ['Page not found']
  130. else:
  131. def factory(app):
  132. return StatusKeeper(app, status='404 Not Found', url='/error')
  133. raise ForwardRequestException(factory=factory)
  134. app = RecursiveMiddleware(app)
  135. """
  136. def __init__(
  137. self,
  138. url=None,
  139. environ={},
  140. factory=None,
  141. path_info=None):
  142. # Check no incompatible options have been chosen
  143. if factory and url:
  144. raise TypeError(
  145. 'You cannot specify factory and a url in '
  146. 'ForwardRequestException')
  147. elif factory and environ:
  148. raise TypeError(
  149. 'You cannot specify factory and environ in '
  150. 'ForwardRequestException')
  151. if url and environ:
  152. raise TypeError(
  153. 'You cannot specify environ and url in '
  154. 'ForwardRequestException')
  155. # set the path_info or warn about its use.
  156. if path_info:
  157. if not url:
  158. warnings.warn(
  159. "ForwardRequestException(path_info=...) has been deprecated; please "
  160. "use ForwardRequestException(url=...)",
  161. DeprecationWarning, 2)
  162. else:
  163. raise TypeError('You cannot use url and path_info in ForwardRequestException')
  164. self.path_info = path_info
  165. # If the url can be treated as a path_info do that
  166. if url and not '?' in str(url):
  167. self.path_info = url
  168. # Base middleware
  169. class ForwardRequestExceptionMiddleware(object):
  170. def __init__(self, app):
  171. self.app = app
  172. # Otherwise construct the appropriate middleware factory
  173. if hasattr(self, 'path_info'):
  174. p = self.path_info
  175. def factory_(app):
  176. class PathInfoForward(ForwardRequestExceptionMiddleware):
  177. def __call__(self, environ, start_response):
  178. environ['PATH_INFO'] = p
  179. return self.app(environ, start_response)
  180. return PathInfoForward(app)
  181. self.factory = factory_
  182. elif url:
  183. def factory_(app):
  184. class URLForward(ForwardRequestExceptionMiddleware):
  185. def __call__(self, environ, start_response):
  186. environ['PATH_INFO'] = url.split('?')[0]
  187. environ['QUERY_STRING'] = url.split('?')[1]
  188. return self.app(environ, start_response)
  189. return URLForward(app)
  190. self.factory = factory_
  191. elif environ:
  192. def factory_(app):
  193. class EnvironForward(ForwardRequestExceptionMiddleware):
  194. def __call__(self, environ_, start_response):
  195. return self.app(environ, start_response)
  196. return EnvironForward(app)
  197. self.factory = factory_
  198. else:
  199. self.factory = factory
  200. class Recursive(object):
  201. def __init__(self, application, environ, start_response):
  202. self.application = application
  203. self.original_environ = environ.copy()
  204. self.previous_environ = environ
  205. self.start_response = start_response
  206. def __call__(self, path, extra_environ=None):
  207. """
  208. `extra_environ` is an optional dictionary that is also added
  209. to the forwarded request. E.g., ``{'HTTP_HOST': 'new.host'}``
  210. could be used to forward to a different virtual host.
  211. """
  212. environ = self.original_environ.copy()
  213. if extra_environ:
  214. environ.update(extra_environ)
  215. environ['paste.recursive.previous_environ'] = self.previous_environ
  216. base_path = self.original_environ.get('SCRIPT_NAME')
  217. if path.startswith('/'):
  218. assert path.startswith(base_path), (
  219. "You can only forward requests to resources under the "
  220. "path %r (not %r)" % (base_path, path))
  221. path = path[len(base_path)+1:]
  222. assert not path.startswith('/')
  223. path_info = '/' + path
  224. environ['PATH_INFO'] = path_info
  225. environ['REQUEST_METHOD'] = 'GET'
  226. environ['CONTENT_LENGTH'] = '0'
  227. environ['CONTENT_TYPE'] = ''
  228. environ['wsgi.input'] = StringIO('')
  229. return self.activate(environ)
  230. def activate(self, environ):
  231. raise NotImplementedError
  232. def __repr__(self):
  233. return '<%s.%s from %s>' % (
  234. self.__class__.__module__,
  235. self.__class__.__name__,
  236. self.original_environ.get('SCRIPT_NAME') or '/')
  237. class Forwarder(Recursive):
  238. """
  239. The forwarder will try to restart the request, except with
  240. the new `path` (replacing ``PATH_INFO`` in the request).
  241. It must not be called after and headers have been returned.
  242. It returns an iterator that must be returned back up the call
  243. stack, so it must be used like:
  244. .. code-block:: python
  245. return environ['paste.recursive.forward'](path)
  246. Meaningful transformations cannot be done, since headers are
  247. sent directly to the server and cannot be inspected or
  248. rewritten.
  249. """
  250. def activate(self, environ):
  251. warnings.warn(
  252. "recursive.Forwarder has been deprecated; please use "
  253. "ForwardRequestException",
  254. DeprecationWarning, 2)
  255. return self.application(environ, self.start_response)
  256. class Includer(Recursive):
  257. """
  258. Starts another request with the given path and adding or
  259. overwriting any values in the `extra_environ` dictionary.
  260. Returns an IncludeResponse object.
  261. """
  262. def activate(self, environ):
  263. response = IncludedResponse()
  264. def start_response(status, headers, exc_info=None):
  265. if exc_info:
  266. raise exc_info[0], exc_info[1], exc_info[2]
  267. response.status = status
  268. response.headers = headers
  269. return response.write
  270. app_iter = self.application(environ, start_response)
  271. try:
  272. for s in app_iter:
  273. response.write(s)
  274. finally:
  275. if hasattr(app_iter, 'close'):
  276. app_iter.close()
  277. response.close()
  278. return response
  279. class IncludedResponse(object):
  280. def __init__(self):
  281. self.headers = None
  282. self.status = None
  283. self.output = StringIO()
  284. self.str = None
  285. def close(self):
  286. self.str = self.output.getvalue()
  287. self.output.close()
  288. self.output = None
  289. def write(self, s):
  290. assert self.output is not None, (
  291. "This response has already been closed and no further data "
  292. "can be written.")
  293. self.output.write(s)
  294. def __str__(self):
  295. return self.body
  296. def body__get(self):
  297. if self.str is None:
  298. return self.output.getvalue()
  299. else:
  300. return self.str
  301. body = property(body__get)
  302. class IncluderAppIter(Recursive):
  303. """
  304. Like Includer, but just stores the app_iter response
  305. (be sure to call close on the response!)
  306. """
  307. def activate(self, environ):
  308. response = IncludedAppIterResponse()
  309. def start_response(status, headers, exc_info=None):
  310. if exc_info:
  311. raise exc_info[0], exc_info[1], exc_info[2]
  312. response.status = status
  313. response.headers = headers
  314. return response.write
  315. app_iter = self.application(environ, start_response)
  316. response.app_iter = app_iter
  317. return response
  318. class IncludedAppIterResponse(object):
  319. def __init__(self):
  320. self.status = None
  321. self.headers = None
  322. self.accumulated = []
  323. self.app_iter = None
  324. self._closed = False
  325. def close(self):
  326. assert not self._closed, (
  327. "Tried to close twice")
  328. if hasattr(self.app_iter, 'close'):
  329. self.app_iter.close()
  330. def write(self, s):
  331. self.accumulated.append
  332. def make_recursive_middleware(app, global_conf):
  333. return RecursiveMiddleware(app)
  334. make_recursive_middleware.__doc__ = __doc__