root/galaxy-central/eggs/Paste-1.6-py2.6.egg/paste/httpexceptions.py

リビジョン 3, 23.4 KB (コミッタ: kohda, 14 年 前)

Install Unix tools  http://hannonlab.cshl.edu/galaxy_unix_tools/galaxy.html

行番号 
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# (c) 2005 Ian Bicking, Clark C. Evans and contributors
4# This module is part of the Python Paste Project and is released under
5# the MIT License: http://www.opensource.org/licenses/mit-license.php
6# Some of this code was funded by http://prometheusresearch.com
7"""
8HTTP Exception Middleware
9
10This module processes Python exceptions that relate to HTTP exceptions
11by defining a set of exceptions, all subclasses of HTTPException, and a
12request handler (`middleware`) that catches these exceptions and turns
13them into proper responses.
14
15This module defines exceptions according to RFC 2068 [1]_ : codes with
16100-300 are not really errors; 400's are client errors, and 500's are
17server errors.  According to the WSGI specification [2]_ , the application
18can call ``start_response`` more then once only under two conditions:
19(a) the response has not yet been sent, or (b) if the second and
20subsequent invocations of ``start_response`` have a valid ``exc_info``
21argument obtained from ``sys.exc_info()``.  The WSGI specification then
22requires the server or gateway to handle the case where content has been
23sent and then an exception was encountered.
24
25Exceptions in the 5xx range and those raised after ``start_response``
26has been called are treated as serious errors and the ``exc_info`` is
27filled-in with information needed for a lower level module to generate a
28stack trace and log information.
29
30Exception
31  HTTPException
32    HTTPRedirection
33      * 300 - HTTPMultipleChoices
34      * 301 - HTTPMovedPermanently
35      * 302 - HTTPFound
36      * 303 - HTTPSeeOther
37      * 304 - HTTPNotModified
38      * 305 - HTTPUseProxy
39      * 306 - Unused (not implemented, obviously)
40      * 307 - HTTPTemporaryRedirect
41    HTTPError
42      HTTPClientError
43        * 400 - HTTPBadRequest
44        * 401 - HTTPUnauthorized
45        * 402 - HTTPPaymentRequired
46        * 403 - HTTPForbidden
47        * 404 - HTTPNotFound
48        * 405 - HTTPMethodNotAllowed
49        * 406 - HTTPNotAcceptable
50        * 407 - HTTPProxyAuthenticationRequired
51        * 408 - HTTPRequestTimeout
52        * 409 - HTTPConfict
53        * 410 - HTTPGone
54        * 411 - HTTPLengthRequired
55        * 412 - HTTPPreconditionFailed
56        * 413 - HTTPRequestEntityTooLarge
57        * 414 - HTTPRequestURITooLong
58        * 415 - HTTPUnsupportedMediaType
59        * 416 - HTTPRequestRangeNotSatisfiable
60        * 417 - HTTPExpectationFailed
61      HTTPServerError
62        * 500 - HTTPInternalServerError
63        * 501 - HTTPNotImplemented
64        * 502 - HTTPBadGateway
65        * 503 - HTTPServiceUnavailable
66        * 504 - HTTPGatewayTimeout
67        * 505 - HTTPVersionNotSupported
68
69References:
70
71.. [1] http://www.python.org/peps/pep-0333.html#error-handling
72.. [2] http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5
73
74"""
75
76import types
77from paste.wsgilib import catch_errors_app
78from paste.response import has_header, header_value, replace_header
79from paste.request import resolve_relative_url
80from paste.util.quoting import strip_html, html_quote, no_quote
81
82SERVER_NAME = 'WSGI Server'
83TEMPLATE = """\
84<html>\r
85  <head><title>%(title)s</title></head>\r
86  <body>\r
87    <h1>%(title)s</h1>\r
88    <p>%(body)s</p>\r
89    <hr noshade>\r
90    <div align="right">%(server)s</div>\r
91  </body>\r
92</html>\r
93"""
94
95class HTTPException(Exception):
96    """
97    the HTTP exception base class
98
99    This encapsulates an HTTP response that interrupts normal application
100    flow; but one which is not necessarly an error condition. For
101    example, codes in the 300's are exceptions in that they interrupt
102    normal processing; however, they are not considered errors.
103
104    This class is complicated by 4 factors:
105
106      1. The content given to the exception may either be plain-text or
107         as html-text.
108
109      2. The template may want to have string-substitutions taken from
110         the current ``environ`` or values from incoming headers. This
111         is especially troublesome due to case sensitivity.
112
113      3. The final output may either be text/plain or text/html
114         mime-type as requested by the client application.
115
116      4. Each exception has a default explanation, but those who
117         raise exceptions may want to provide additional detail.
118
119    Attributes:
120
121       ``code``
122           the HTTP status code for the exception
123
124       ``title``
125           remainder of the status line (stuff after the code)
126
127       ``explanation``
128           a plain-text explanation of the error message that is
129           not subject to environment or header substitutions;
130           it is accessible in the template via %(explanation)s
131
132       ``detail``
133           a plain-text message customization that is not subject
134           to environment or header substitutions; accessible in
135           the template via %(detail)s
136
137       ``template``
138           a content fragment (in HTML) used for environment and
139           header substitution; the default template includes both
140           the explanation and further detail provided in the
141           message
142
143       ``required_headers``
144           a sequence of headers which are required for proper
145           construction of the exception
146
147    Parameters:
148
149       ``detail``
150         a plain-text override of the default ``detail``
151
152       ``headers``
153         a list of (k,v) header pairs
154
155       ``comment``
156         a plain-text additional information which is
157         usually stripped/hidden for end-users
158
159    To override the template (which is HTML content) or the plain-text
160    explanation, one must subclass the given exception; or customize it
161    after it has been created.  This particular breakdown of a message
162    into explanation, detail and template allows both the creation of
163    plain-text and html messages for various clients as well as
164    error-free substitution of environment variables and headers.
165    """
166
167    code = None
168    title = None
169    explanation = ''
170    detail = ''
171    comment = ''
172    template = "%(explanation)s\r\n<br/>%(detail)s\r\n<!-- %(comment)s -->"
173    required_headers = ()
174
175    def __init__(self, detail=None, headers=None, comment=None):
176        assert self.code, "Do not directly instantiate abstract exceptions."
177        assert isinstance(headers, (type(None), list)), (
178            "headers must be None or a list: %r"
179            % headers)
180        assert isinstance(detail, (type(None), basestring)), (
181            "detail must be None or a string: %r" % detail)
182        assert isinstance(comment, (type(None), basestring)), (
183            "comment must be None or a string: %r" % comment)
184        self.headers = headers or tuple()
185        for req in self.required_headers:
186            assert headers and has_header(headers, req), (
187                "Exception %s must be passed the header %r "
188                "(got headers: %r)"
189                % (self.__class__.__name__, req, headers))
190        if detail is not None:
191            self.detail = detail
192        if comment is not None:
193            self.comment = comment
194        Exception.__init__(self,"%s %s\n%s\n%s\n" % (
195            self.code, self.title, self.explanation, self.detail))
196
197    def make_body(self, environ, template, escfunc, comment_escfunc=None):
198        comment_escfunc = comment_escfunc or escfunc
199        args = {'explanation': escfunc(self.explanation),
200                'detail': escfunc(self.detail),
201                'comment': comment_escfunc(self.comment)}
202        if HTTPException.template == self.template:
203            return template % args
204        for (k, v) in environ.items():
205            args[k] = escfunc(v)
206        if self.headers:
207            for (k, v) in self.headers:
208                args[k.lower()] = escfunc(v)
209        return template % args
210
211    def plain(self, environ):
212        """ text/plain representation of the exception """
213        body = self.make_body(environ, strip_html(self.template), no_quote)
214        return ('%s %s\r\n%s\r\n' % (self.code, self.title, body))
215
216    def html(self, environ):
217        """ text/html representation of the exception """
218        body = self.make_body(environ, self.template, html_quote, no_quote)
219        return TEMPLATE % {
220                   'title': self.title,
221                   'code': self.code,
222                   'server': SERVER_NAME,
223                   'body': body }
224
225    def prepare_content(self, environ):
226        if self.headers:
227            headers = list(self.headers)
228        else:
229            headers = []
230        if 'html' in environ.get('HTTP_ACCEPT','') or \
231            '*/*' in environ.get('HTTP_ACCEPT',''):
232            replace_header(headers, 'content-type', 'text/html')
233            content = self.html(environ)
234        else:
235            replace_header(headers, 'content-type', 'text/plain')
236            content = self.plain(environ)
237        if isinstance(content, unicode):
238            content = content.encode('utf8')
239            cur_content_type = (
240                header_value(headers, 'content-type')
241                or 'text/html')
242            replace_header(
243                headers, 'content-type',
244                cur_content_type + '; charset=utf8')
245        return headers, content
246
247    def response(self, environ):
248        from paste.wsgiwrappers import WSGIResponse
249        headers, content = self.prepare_content(environ)
250        resp = WSGIResponse(code=self.code, content=content)
251        resp.headers = resp.headers.fromlist(headers)
252        return resp
253
254    def wsgi_application(self, environ, start_response, exc_info=None):
255        """
256        This exception as a WSGI application
257        """
258        headers, content = self.prepare_content(environ)
259        start_response('%s %s' % (self.code, self.title),
260                       headers,
261                       exc_info)
262        return [content]
263
264    __call__ = wsgi_application
265
266    def __repr__(self):
267        return '<%s %s; code=%s>' % (self.__class__.__name__,
268                                     self.title, self.code)
269
270class HTTPError(HTTPException):
271    """
272    base class for status codes in the 400's and 500's
273
274    This is an exception which indicates that an error has occurred,
275    and that any work in progress should not be committed.  These are
276    typically results in the 400's and 500's.
277    """
278
279#
280# 3xx Redirection
281#
282#  This class of status code indicates that further action needs to be
283#  taken by the user agent in order to fulfill the request. The action
284#  required MAY be carried out by the user agent without interaction with
285#  the user if and only if the method used in the second request is GET or
286#  HEAD. A client SHOULD detect infinite redirection loops, since such
287#  loops generate network traffic for each redirection.
288#
289
290class HTTPRedirection(HTTPException):
291    """
292    base class for 300's status code (redirections)
293
294    This is an abstract base class for 3xx redirection.  It indicates
295    that further action needs to be taken by the user agent in order
296    to fulfill the request.  It does not necessarly signal an error
297    condition.
298    """
299
300class _HTTPMove(HTTPRedirection):
301    """
302    redirections which require a Location field
303
304    Since a 'Location' header is a required attribute of 301, 302, 303,
305    305 and 307 (but not 304), this base class provides the mechanics to
306    make this easy.  While this has the same parameters as HTTPException,
307    if a location is not provided in the headers; it is assumed that the
308    detail _is_ the location (this for backward compatibility, otherwise
309    we'd add a new attribute).
310    """
311    required_headers = ('location',)
312    explanation = 'The resource has been moved to'
313    template = (
314        '%(explanation)s <a href="%(location)s">%(location)s</a>;\r\n'
315        'you should be redirected automatically.\r\n'
316        '%(detail)s\r\n<!-- %(comment)s -->')
317
318    def __init__(self, detail=None, headers=None, comment=None):
319        assert isinstance(headers, (type(None), list))
320        headers = headers or []
321        location = header_value(headers,'location')
322        if not location:
323            location = detail
324            detail = ''
325            headers.append(('location', location))
326        assert location, ("HTTPRedirection specified neither a "
327                          "location in the headers nor did it "
328                          "provide a detail argument.")
329        HTTPRedirection.__init__(self, location, headers, comment)
330        if detail is not None:
331            self.detail = detail
332
333    def relative_redirect(cls, dest_uri, environ, detail=None, headers=None, comment=None):
334        """
335        Create a redirect object with the dest_uri, which may be relative,
336        considering it relative to the uri implied by the given environ.
337        """
338        location = resolve_relative_url(dest_uri, environ)
339        headers = headers or []
340        headers.append(('Location', location))
341        return cls(detail=detail, headers=headers, comment=comment)
342   
343    relative_redirect = classmethod(relative_redirect)
344
345    def location(self):
346        for name, value in self.headers:
347            if name.lower() == 'location':
348                return value
349        else:
350            raise KeyError("No location set for %s" % self)
351
352class HTTPMultipleChoices(_HTTPMove):
353    code = 300
354    title = 'Multiple Choices'
355
356class HTTPMovedPermanently(_HTTPMove):
357    code = 301
358    title = 'Moved Permanently'
359
360class HTTPFound(_HTTPMove):
361    code = 302
362    title = 'Found'
363    explanation = 'The resource was found at'
364
365# This one is safe after a POST (the redirected location will be
366# retrieved with GET):
367class HTTPSeeOther(_HTTPMove):
368    code = 303
369    title = 'See Other'
370
371class HTTPNotModified(HTTPRedirection):
372    # @@: but not always (HTTP section 14.18.1)...?
373    # @@: Removed 'date' requirement, as its not required for an ETag
374    # @@: FIXME: This should require either an ETag or a date header
375    code = 304
376    title = 'Not Modified'
377    message = ''
378    # @@: should include date header, optionally other headers
379    # @@: should not return a content body
380    def plain(self, environ):
381        return ''
382    def html(self, environ):
383        """ text/html representation of the exception """
384        return ''
385
386class HTTPUseProxy(_HTTPMove):
387    # @@: OK, not a move, but looks a little like one
388    code = 305
389    title = 'Use Proxy'
390    explanation = (
391        'The resource must be accessed through a proxy '
392        'located at')
393
394class HTTPTemporaryRedirect(_HTTPMove):
395    code = 307
396    title = 'Temporary Redirect'
397
398#
399# 4xx Client Error
400#
401#  The 4xx class of status code is intended for cases in which the client
402#  seems to have erred. Except when responding to a HEAD request, the
403#  server SHOULD include an entity containing an explanation of the error
404#  situation, and whether it is a temporary or permanent condition. These
405#  status codes are applicable to any request method. User agents SHOULD
406#  display any included entity to the user.
407#
408
409class HTTPClientError(HTTPError):
410    """
411    base class for the 400's, where the client is in-error
412
413    This is an error condition in which the client is presumed to be
414    in-error.  This is an expected problem, and thus is not considered
415    a bug.  A server-side traceback is not warranted.  Unless specialized,
416    this is a '400 Bad Request'
417    """
418    code = 400
419    title = 'Bad Request'
420    explanation = ('The server could not comply with the request since\r\n'
421                   'it is either malformed or otherwise incorrect.\r\n')
422
423class HTTPBadRequest(HTTPClientError):
424    pass
425
426class HTTPUnauthorized(HTTPClientError):
427    code = 401
428    title = 'Unauthorized'
429    explanation = (
430        'This server could not verify that you are authorized to\r\n'
431        'access the document you requested.  Either you supplied the\r\n'
432        'wrong credentials (e.g., bad password), or your browser\r\n'
433        'does not understand how to supply the credentials required.\r\n')
434
435class HTTPPaymentRequired(HTTPClientError):
436    code = 402
437    title = 'Payment Required'
438    explanation = ('Access was denied for financial reasons.')
439
440class HTTPForbidden(HTTPClientError):
441    code = 403
442    title = 'Forbidden'
443    explanation = ('Access was denied to this resource.')
444
445class HTTPNotFound(HTTPClientError):
446    code = 404
447    title = 'Not Found'
448    explanation = ('The resource could not be found.')
449
450class HTTPMethodNotAllowed(HTTPClientError):
451    required_headers = ('allow',)
452    code = 405
453    title = 'Method Not Allowed'
454    # override template since we need an environment variable
455    template = ('The method %(REQUEST_METHOD)s is not allowed for '
456                'this resource.\r\n%(detail)s')
457
458class HTTPNotAcceptable(HTTPClientError):
459    code = 406
460    title = 'Not Acceptable'
461    # override template since we need an environment variable
462    template = ('The resource could not be generated that was '
463                'acceptable to your browser (content\r\nof type '
464                '%(HTTP_ACCEPT)s).\r\n%(detail)s')
465
466class HTTPProxyAuthenticationRequired(HTTPClientError):
467    code = 407
468    title = 'Proxy Authentication Required'
469    explanation = ('Authentication /w a local proxy is needed.')
470
471class HTTPRequestTimeout(HTTPClientError):
472    code = 408
473    title = 'Request Timeout'
474    explanation = ('The server has waited too long for the request to '
475                   'be sent by the client.')
476
477class HTTPConflict(HTTPClientError):
478    code = 409
479    title = 'Conflict'
480    explanation = ('There was a conflict when trying to complete '
481                   'your request.')
482
483class HTTPGone(HTTPClientError):
484    code = 410
485    title = 'Gone'
486    explanation = ('This resource is no longer available.  No forwarding '
487                   'address is given.')
488
489class HTTPLengthRequired(HTTPClientError):
490    code = 411
491    title = 'Length Required'
492    explanation = ('Content-Length header required.')
493
494class HTTPPreconditionFailed(HTTPClientError):
495    code = 412
496    title = 'Precondition Failed'
497    explanation = ('Request precondition failed.')
498
499class HTTPRequestEntityTooLarge(HTTPClientError):
500    code = 413
501    title = 'Request Entity Too Large'
502    explanation = ('The body of your request was too large for this server.')
503
504class HTTPRequestURITooLong(HTTPClientError):
505    code = 414
506    title = 'Request-URI Too Long'
507    explanation = ('The request URI was too long for this server.')
508
509class HTTPUnsupportedMediaType(HTTPClientError):
510    code = 415
511    title = 'Unsupported Media Type'
512    # override template since we need an environment variable
513    template = ('The request media type %(CONTENT_TYPE)s is not '
514                'supported by this server.\r\n%(detail)s')
515
516class HTTPRequestRangeNotSatisfiable(HTTPClientError):
517    code = 416
518    title = 'Request Range Not Satisfiable'
519    explanation = ('The Range requested is not available.')
520
521class HTTPExpectationFailed(HTTPClientError):
522    code = 417
523    title = 'Expectation Failed'
524    explanation = ('Expectation failed.')
525
526#
527# 5xx Server Error
528#
529#  Response status codes beginning with the digit "5" indicate cases in
530#  which the server is aware that it has erred or is incapable of
531#  performing the request. Except when responding to a HEAD request, the
532#  server SHOULD include an entity containing an explanation of the error
533#  situation, and whether it is a temporary or permanent condition. User
534#  agents SHOULD display any included entity to the user. These response
535#  codes are applicable to any request method.
536#
537
538class HTTPServerError(HTTPError):
539    """
540    base class for the 500's, where the server is in-error
541
542    This is an error condition in which the server is presumed to be
543    in-error.  This is usually unexpected, and thus requires a traceback;
544    ideally, opening a support ticket for the customer. Unless specialized,
545    this is a '500 Internal Server Error'
546    """
547    code = 500
548    title = 'Internal Server Error'
549    explanation = (
550      'The server has either erred or is incapable of performing\r\n'
551      'the requested operation.\r\n')
552
553class HTTPInternalServerError(HTTPServerError):
554    pass
555
556class HTTPNotImplemented(HTTPServerError):
557    code = 501
558    title = 'Not Implemented'
559    # override template since we need an environment variable
560    template = ('The request method %(REQUEST_METHOD)s is not implemented '
561                'for this server.\r\n%(detail)s')
562
563class HTTPBadGateway(HTTPServerError):
564    code = 502
565    title = 'Bad Gateway'
566    explanation = ('Bad gateway.')
567
568class HTTPServiceUnavailable(HTTPServerError):
569    code = 503
570    title = 'Service Unavailable'
571    explanation = ('The server is currently unavailable. '
572                   'Please try again at a later time.')
573
574class HTTPGatewayTimeout(HTTPServerError):
575    code = 504
576    title = 'Gateway Timeout'
577    explanation = ('The gateway has timed out.')
578
579class HTTPVersionNotSupported(HTTPServerError):
580    code = 505
581    title = 'HTTP Version Not Supported'
582    explanation = ('The HTTP version is not supported.')
583
584# abstract HTTP related exceptions
585__all__ = ['HTTPException', 'HTTPRedirection', 'HTTPError' ]
586
587_exceptions = {}
588for name, value in globals().items():
589    if (isinstance(value, (type, types.ClassType)) and
590        issubclass(value, HTTPException) and
591        value.code):
592        _exceptions[value.code] = value
593        __all__.append(name)
594
595def get_exception(code):
596    return _exceptions[code]
597
598############################################################
599## Middleware implementation:
600############################################################
601
602class HTTPExceptionHandler(object):
603    """
604    catches exceptions and turns them into proper HTTP responses
605
606    Attributes:
607
608       ``warning_level``
609           This attribute determines for what exceptions a stack
610           trace is kept for lower level reporting; by default, it
611           only keeps stack trace for 5xx, HTTPServerError exceptions.
612           To keep a stack trace for 4xx, HTTPClientError exceptions,
613           set this to 400.
614
615    This middleware catches any exceptions (which are subclasses of
616    ``HTTPException``) and turns them into proper HTTP responses.
617    Note if the headers have already been sent, the stack trace is
618    always maintained as this indicates a programming error.
619    """
620
621    def __init__(self, application, warning_level=None):
622        assert not warning_level or ( warning_level > 99 and
623                                      warning_level < 600)
624        self.warning_level = warning_level or 500
625        self.application = application
626
627    def __call__(self, environ, start_response):
628        environ['paste.httpexceptions'] = self
629        environ.setdefault('paste.expected_exceptions',
630                           []).append(HTTPException)
631        try:
632            return self.application(environ, start_response)
633        except HTTPException, exc:
634            return exc(environ, start_response)
635
636def middleware(*args, **kw):
637    import warnings
638    # deprecated 13 dec 2005
639    warnings.warn('httpexceptions.middleware is deprecated; use '
640                  'make_middleware or HTTPExceptionHandler instead',
641                  DeprecationWarning, 2)
642    return make_middleware(*args, **kw)
643
644def make_middleware(app, global_conf=None, warning_level=None):
645    """
646    ``httpexceptions`` middleware; this catches any
647    ``paste.httpexceptions.HTTPException`` exceptions (exceptions like
648    ``HTTPNotFound``, ``HTTPMovedPermanently``, etc) and turns them
649    into proper HTTP responses.
650
651    ``warning_level`` can be an integer corresponding to an HTTP code.
652    Any code over that value will be passed 'up' the chain, potentially
653    reported on by another piece of middleware.
654    """
655    if warning_level:
656        warning_level = int(warning_level)
657    return HTTPExceptionHandler(app, warning_level=warning_level)
658
659__all__.extend(['HTTPExceptionHandler', 'get_exception'])
660
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。