PageRenderTime 51ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/portal.policy/Paste-1.7.4-py2.4.egg/paste/errordocument.py

https://bitbucket.org/eaviles/gobierno
Python | 372 lines | 346 code | 2 blank | 24 comment | 0 complexity | b52e073db5c60c56747e6f6622d2e4df MD5 | raw file
Possible License(s): AGPL-1.0, GPL-2.0
  1. # (c) 2005-2006 James Gardner <james@pythonweb.org>
  2. # This module is part of the Python Paste Project and is released under
  3. # the MIT License: http://www.opensource.org/licenses/mit-license.php
  4. """
  5. Middleware to display error documents for certain status codes
  6. The middleware in this module can be used to intercept responses with
  7. specified status codes and internally forward the request to an appropriate
  8. URL where the content can be displayed to the user as an error document.
  9. """
  10. import warnings
  11. from urlparse import urlparse
  12. from paste.recursive import ForwardRequestException, RecursiveMiddleware
  13. from paste.util import converters
  14. from paste.response import replace_header
  15. def forward(app, codes):
  16. """
  17. Intercepts a response with a particular status code and returns the
  18. content from a specified URL instead.
  19. The arguments are:
  20. ``app``
  21. The WSGI application or middleware chain.
  22. ``codes``
  23. A dictionary of integer status codes and the URL to be displayed
  24. if the response uses that code.
  25. For example, you might want to create a static file to display a
  26. "File Not Found" message at the URL ``/error404.html`` and then use
  27. ``forward`` middleware to catch all 404 status codes and display the page
  28. you created. In this example ``app`` is your exisiting WSGI
  29. applicaiton::
  30. from paste.errordocument import forward
  31. app = forward(app, codes={404:'/error404.html'})
  32. """
  33. for code in codes:
  34. if not isinstance(code, int):
  35. raise TypeError('All status codes should be type int. '
  36. '%s is not valid'%repr(code))
  37. def error_codes_mapper(code, message, environ, global_conf, codes):
  38. if codes.has_key(code):
  39. return codes[code]
  40. else:
  41. return None
  42. #return _StatusBasedRedirect(app, error_codes_mapper, codes=codes)
  43. return RecursiveMiddleware(
  44. StatusBasedForward(
  45. app,
  46. error_codes_mapper,
  47. codes=codes,
  48. )
  49. )
  50. class StatusKeeper(object):
  51. def __init__(self, app, status, url, headers):
  52. self.app = app
  53. self.status = status
  54. self.url = url
  55. self.headers = headers
  56. def __call__(self, environ, start_response):
  57. def keep_status_start_response(status, headers, exc_info=None):
  58. for header, value in headers:
  59. if header.lower() == 'set-cookie':
  60. self.headers.append((header, value))
  61. else:
  62. replace_header(self.headers, header, value)
  63. return start_response(self.status, self.headers, exc_info)
  64. parts = self.url.split('?')
  65. environ['PATH_INFO'] = parts[0]
  66. if len(parts) > 1:
  67. environ['QUERY_STRING'] = parts[1]
  68. else:
  69. environ['QUERY_STRING'] = ''
  70. #raise Exception(self.url, self.status)
  71. return self.app(environ, keep_status_start_response)
  72. class StatusBasedForward(object):
  73. """
  74. Middleware that lets you test a response against a custom mapper object to
  75. programatically determine whether to internally forward to another URL and
  76. if so, which URL to forward to.
  77. If you don't need the full power of this middleware you might choose to use
  78. the simpler ``forward`` middleware instead.
  79. The arguments are:
  80. ``app``
  81. The WSGI application or middleware chain.
  82. ``mapper``
  83. A callable that takes a status code as the
  84. first parameter, a message as the second, and accepts optional environ,
  85. global_conf and named argments afterwards. It should return a
  86. URL to forward to or ``None`` if the code is not to be intercepted.
  87. ``global_conf``
  88. Optional default configuration from your config file. If ``debug`` is
  89. set to ``true`` a message will be written to ``wsgi.errors`` on each
  90. internal forward stating the URL forwarded to.
  91. ``**params``
  92. Optional, any other configuration and extra arguments you wish to
  93. pass which will in turn be passed back to the custom mapper object.
  94. Here is an example where a ``404 File Not Found`` status response would be
  95. redirected to the URL ``/error?code=404&message=File%20Not%20Found``. This
  96. could be useful for passing the status code and message into another
  97. application to display an error document:
  98. .. code-block:: python
  99. from paste.errordocument import StatusBasedForward
  100. from paste.recursive import RecursiveMiddleware
  101. from urllib import urlencode
  102. def error_mapper(code, message, environ, global_conf, kw)
  103. if code in [404, 500]:
  104. params = urlencode({'message':message, 'code':code})
  105. url = '/error?'%(params)
  106. return url
  107. else:
  108. return None
  109. app = RecursiveMiddleware(
  110. StatusBasedForward(app, mapper=error_mapper),
  111. )
  112. """
  113. def __init__(self, app, mapper, global_conf=None, **params):
  114. if global_conf is None:
  115. global_conf = {}
  116. # @@: global_conf shouldn't really come in here, only in a
  117. # separate make_status_based_forward function
  118. if global_conf:
  119. self.debug = converters.asbool(global_conf.get('debug', False))
  120. else:
  121. self.debug = False
  122. self.application = app
  123. self.mapper = mapper
  124. self.global_conf = global_conf
  125. self.params = params
  126. def __call__(self, environ, start_response):
  127. url = []
  128. def change_response(status, headers, exc_info=None):
  129. status_code = status.split(' ')
  130. try:
  131. code = int(status_code[0])
  132. except (ValueError, TypeError):
  133. raise Exception(
  134. 'StatusBasedForward middleware '
  135. 'received an invalid status code %s'%repr(status_code[0])
  136. )
  137. message = ' '.join(status_code[1:])
  138. new_url = self.mapper(
  139. code,
  140. message,
  141. environ,
  142. self.global_conf,
  143. **self.params
  144. )
  145. if not (new_url == None or isinstance(new_url, str)):
  146. raise TypeError(
  147. 'Expected the url to internally '
  148. 'redirect to in the StatusBasedForward mapper'
  149. 'to be a string or None, not %s'%repr(new_url)
  150. )
  151. if new_url:
  152. url.append([new_url, status, headers])
  153. else:
  154. return start_response(status, headers, exc_info)
  155. app_iter = self.application(environ, change_response)
  156. if url:
  157. if hasattr(app_iter, 'close'):
  158. app_iter.close()
  159. def factory(app):
  160. return StatusKeeper(app, status=url[0][1], url=url[0][0],
  161. headers=url[0][2])
  162. raise ForwardRequestException(factory=factory)
  163. else:
  164. return app_iter
  165. def make_errordocument(app, global_conf, **kw):
  166. """
  167. Paste Deploy entry point to create a error document wrapper.
  168. Use like::
  169. [filter-app:main]
  170. use = egg:Paste#errordocument
  171. next = real-app
  172. 500 = /lib/msg/500.html
  173. 404 = /lib/msg/404.html
  174. """
  175. map = {}
  176. for status, redir_loc in kw.items():
  177. try:
  178. status = int(status)
  179. except ValueError:
  180. raise ValueError('Bad status code: %r' % status)
  181. map[status] = redir_loc
  182. forwarder = forward(app, map)
  183. return forwarder
  184. __pudge_all__ = [
  185. 'forward',
  186. 'make_errordocument',
  187. 'empty_error',
  188. 'make_empty_error',
  189. 'StatusBasedForward',
  190. ]
  191. ###############################################################################
  192. ## Deprecated
  193. ###############################################################################
  194. def custom_forward(app, mapper, global_conf=None, **kw):
  195. """
  196. Deprectated; use StatusBasedForward instead.
  197. """
  198. warnings.warn(
  199. "errordocuments.custom_forward has been deprecated; please "
  200. "use errordocuments.StatusBasedForward",
  201. DeprecationWarning, 2)
  202. if global_conf is None:
  203. global_conf = {}
  204. return _StatusBasedRedirect(app, mapper, global_conf, **kw)
  205. class _StatusBasedRedirect(object):
  206. """
  207. Deprectated; use StatusBasedForward instead.
  208. """
  209. def __init__(self, app, mapper, global_conf=None, **kw):
  210. warnings.warn(
  211. "errordocuments._StatusBasedRedirect has been deprecated; please "
  212. "use errordocuments.StatusBasedForward",
  213. DeprecationWarning, 2)
  214. if global_conf is None:
  215. global_conf = {}
  216. self.application = app
  217. self.mapper = mapper
  218. self.global_conf = global_conf
  219. self.kw = kw
  220. self.fallback_template = """
  221. <html>
  222. <head>
  223. <title>Error %(code)s</title>
  224. </html>
  225. <body>
  226. <h1>Error %(code)s</h1>
  227. <p>%(message)s</p>
  228. <hr>
  229. <p>
  230. Additionally an error occurred trying to produce an
  231. error document. A description of the error was logged
  232. to <tt>wsgi.errors</tt>.
  233. </p>
  234. </body>
  235. </html>
  236. """
  237. def __call__(self, environ, start_response):
  238. url = []
  239. code_message = []
  240. try:
  241. def change_response(status, headers, exc_info=None):
  242. new_url = None
  243. parts = status.split(' ')
  244. try:
  245. code = int(parts[0])
  246. except (ValueError, TypeError):
  247. raise Exception(
  248. '_StatusBasedRedirect middleware '
  249. 'received an invalid status code %s'%repr(parts[0])
  250. )
  251. message = ' '.join(parts[1:])
  252. new_url = self.mapper(
  253. code,
  254. message,
  255. environ,
  256. self.global_conf,
  257. self.kw
  258. )
  259. if not (new_url == None or isinstance(new_url, str)):
  260. raise TypeError(
  261. 'Expected the url to internally '
  262. 'redirect to in the _StatusBasedRedirect error_mapper'
  263. 'to be a string or None, not %s'%repr(new_url)
  264. )
  265. if new_url:
  266. url.append(new_url)
  267. code_message.append([code, message])
  268. return start_response(status, headers, exc_info)
  269. app_iter = self.application(environ, change_response)
  270. except:
  271. try:
  272. import sys
  273. error = str(sys.exc_info()[1])
  274. except:
  275. error = ''
  276. try:
  277. code, message = code_message[0]
  278. except:
  279. code, message = ['', '']
  280. environ['wsgi.errors'].write(
  281. 'Error occurred in _StatusBasedRedirect '
  282. 'intercepting the response: '+str(error)
  283. )
  284. return [self.fallback_template
  285. % {'message': message, 'code': code}]
  286. else:
  287. if url:
  288. url_ = url[0]
  289. new_environ = {}
  290. for k, v in environ.items():
  291. if k != 'QUERY_STRING':
  292. new_environ['QUERY_STRING'] = urlparse(url_)[4]
  293. else:
  294. new_environ[k] = v
  295. class InvalidForward(Exception):
  296. pass
  297. def eat_start_response(status, headers, exc_info=None):
  298. """
  299. We don't want start_response to do anything since it
  300. has already been called
  301. """
  302. if status[:3] != '200':
  303. raise InvalidForward(
  304. "The URL %s to internally forward "
  305. "to in order to create an error document did not "
  306. "return a '200' status code." % url_
  307. )
  308. forward = environ['paste.recursive.forward']
  309. old_start_response = forward.start_response
  310. forward.start_response = eat_start_response
  311. try:
  312. app_iter = forward(url_, new_environ)
  313. except InvalidForward, e:
  314. code, message = code_message[0]
  315. environ['wsgi.errors'].write(
  316. 'Error occurred in '
  317. '_StatusBasedRedirect redirecting '
  318. 'to new URL: '+str(url[0])
  319. )
  320. return [
  321. self.fallback_template%{
  322. 'message':message,
  323. 'code':code,
  324. }
  325. ]
  326. else:
  327. forward.start_response = old_start_response
  328. return app_iter
  329. else:
  330. return app_iter