Permalink
188 lines (168 sloc) 5.9 KB
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
| This file is part of the web2py Web Framework
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
HTTP statuses helpers
--------------------------------------------
"""
import re
from gluon._compat import iteritems, unicodeT, to_bytes
__all__ = ['HTTP', 'redirect']
defined_status = {
200: 'OK',
201: 'CREATED',
202: 'ACCEPTED',
203: 'NON-AUTHORITATIVE INFORMATION',
204: 'NO CONTENT',
205: 'RESET CONTENT',
206: 'PARTIAL CONTENT',
301: 'MOVED PERMANENTLY',
302: 'FOUND',
303: 'SEE OTHER',
304: 'NOT MODIFIED',
305: 'USE PROXY',
307: 'TEMPORARY REDIRECT',
400: 'BAD REQUEST',
401: 'UNAUTHORIZED',
402: 'PAYMENT REQUIRED',
403: 'FORBIDDEN',
404: 'NOT FOUND',
405: 'METHOD NOT ALLOWED',
406: 'NOT ACCEPTABLE',
407: 'PROXY AUTHENTICATION REQUIRED',
408: 'REQUEST TIMEOUT',
409: 'CONFLICT',
410: 'GONE',
411: 'LENGTH REQUIRED',
412: 'PRECONDITION FAILED',
413: 'REQUEST ENTITY TOO LARGE',
414: 'REQUEST-URI TOO LONG',
415: 'UNSUPPORTED MEDIA TYPE',
416: 'REQUESTED RANGE NOT SATISFIABLE',
417: 'EXPECTATION FAILED',
422: 'UNPROCESSABLE ENTITY',
429: 'TOO MANY REQUESTS',
451: 'UNAVAILABLE FOR LEGAL REASONS', # http://www.451unavailable.org/
500: 'INTERNAL SERVER ERROR',
501: 'NOT IMPLEMENTED',
502: 'BAD GATEWAY',
503: 'SERVICE UNAVAILABLE',
504: 'GATEWAY TIMEOUT',
505: 'HTTP VERSION NOT SUPPORTED',
509: 'BANDWIDTH LIMIT EXCEEDED',
}
regex_status = re.compile('^\d{3} [0-9A-Z ]+$')
class HTTP(Exception):
"""Raises an HTTP response
Args:
status: usually an integer. If it's a well known status code, the ERROR
message will be automatically added. A string can also be passed
as `510 Foo Bar` and in that case the status code and the error
message will be parsed accordingly
body: what to return as body. If left as is, will return the error code
and the status message in the body itself
cookies: pass cookies along (usually not needed)
headers: pass headers as usual dict mapping
"""
def __init__(
self,
status,
body='',
cookies=None,
**headers
):
self.status = status
self.body = body
self.headers = headers
self.cookies2headers(cookies)
def cookies2headers(self, cookies):
if cookies and len(cookies) > 0:
self.headers['Set-Cookie'] = [
str(cookie)[11:] for cookie in cookies.values()]
def to(self, responder, env=None):
env = env or {}
status = self.status
headers = self.headers
if status in defined_status:
status = '%d %s' % (status, defined_status[status])
elif isinstance(status, int):
status = '%d UNKNOWN ERROR' % status
else:
status = str(status)
if not regex_status.match(status):
status = '500 %s' % (defined_status[500])
headers.setdefault('Content-Type', 'text/html; charset=UTF-8')
body = self.body
if status[:1] == '4':
if not body:
body = status
if isinstance(body, (str, bytes, bytearray)):
if isinstance(body, unicodeT):
body = to_bytes(body) # This must be done before len
headers['Content-Length'] = len(body)
rheaders = []
for k, v in iteritems(headers):
if isinstance(v, list):
rheaders += [(k, str(item)) for item in v]
elif v is not None:
rheaders.append((k, str(v)))
responder(status, rheaders)
if env.get('request_method', '') == 'HEAD':
return ['']
elif isinstance(body, (str, bytes, bytearray)):
if isinstance(body, unicodeT):
body = to_bytes(body)
return [body]
elif hasattr(body, '__iter__'):
return body
else:
body = str(body)
if isinstance(body, unicodeT):
body = to_bytes(body)
return [body]
@property
def message(self):
"""
compose a message describing this exception
"status defined_status [web2py_error]"
message elements that are not defined are omitted
"""
msg = '%(status)s'
if self.status in defined_status:
msg = '%(status)s %(defined_status)s'
if 'web2py_error' in self.headers:
msg += ' [%(web2py_error)s]'
return msg % dict(
status=self.status,
defined_status=defined_status.get(self.status),
web2py_error=self.headers.get('web2py_error'))
def __str__(self):
"""stringify me"""
return self.message
def redirect(location='', how=303, client_side=False, headers=None):
"""Raises a redirect (303)
Args:
location: the url where to redirect
how: what HTTP status code to use when redirecting
client_side: if set to True, it triggers a reload of the entire page
when the fragment has been loaded as a component
"""
headers = headers or {}
if location:
from gluon.globals import current
loc = location.replace('\r', '%0D').replace('\n', '%0A')
if client_side and current.request.ajax:
headers['web2py-redirect-location'] = loc
raise HTTP(200, **headers)
else:
headers['Location'] = loc
raise HTTP(how,
'You are being redirected <a href="%s">here</a>' % loc,
**headers)
else:
from gluon.globals import current
if client_side and current.request.ajax:
headers['web2py-component-command'] = 'window.location.reload(true)'
raise HTTP(200, **headers)