176 lines
6.5 KiB
Python
176 lines
6.5 KiB
Python
# This module is a modified copy of code of Yasha's Borevich library
|
|
# django-jsonresponse (https://github.com/jjay/django-jsonresponse) distributed
|
|
# under BSD license
|
|
|
|
from __future__ import absolute_import
|
|
|
|
import datetime
|
|
import json
|
|
import functools
|
|
import logging
|
|
import re
|
|
import time
|
|
|
|
from django.http import HttpResponse, Http404, HttpResponseBadRequest
|
|
from django.http.response import HttpResponseBase
|
|
from django.conf import settings
|
|
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
|
|
from django.core.serializers.json import DjangoJSONEncoder
|
|
from django.utils.encoding import force_text
|
|
from django.utils import six
|
|
|
|
from requests import RequestException, HTTPError
|
|
|
|
from passerelle.utils import log_http_request
|
|
from passerelle.utils.conversion import exception_to_text
|
|
|
|
|
|
DEFAULT_DEBUG = getattr(settings, 'JSONRESPONSE_DEFAULT_DEBUG', False)
|
|
CALLBACK_NAME = getattr(settings, 'JSONRESPONSE_CALLBACK_NAME', 'callback')
|
|
|
|
|
|
class APIError(RuntimeError):
|
|
'''Exception to raise when there is a remote application or business logic error.'''
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.err = kwargs.pop('err', 1)
|
|
self.log_error = kwargs.pop('log_error', False)
|
|
self.http_status = kwargs.pop('http_status', 200)
|
|
self.__dict__.update(kwargs)
|
|
super(APIError, self).__init__(*args)
|
|
|
|
|
|
class JSONEncoder(DjangoJSONEncoder):
|
|
def default(self, o):
|
|
if isinstance(o, time.struct_time):
|
|
o = datetime.datetime(*tuple(o)[:6])
|
|
return super(JSONEncoder, self).default(o)
|
|
|
|
|
|
class to_json(object):
|
|
def __init__(self, error_code=500, logger=None, **kwargs):
|
|
self.error_code = error_code
|
|
self.kwargs = kwargs
|
|
self.logger = logger
|
|
if 'cls' not in self.kwargs:
|
|
self.kwargs['cls'] = JSONEncoder
|
|
|
|
def __call__(self, f):
|
|
@functools.wraps(f)
|
|
def wrapper(*args, **kwargs):
|
|
method = self.api_func
|
|
if args and getattr(getattr(args[0], f.__name__, None), "__self__", False):
|
|
method = self.api_method
|
|
return method(f, *args, **kwargs)
|
|
|
|
return wrapper
|
|
|
|
def obj_to_response(self, req, obj):
|
|
if isinstance(obj, dict) and not 'err' in obj:
|
|
obj['err'] = 0
|
|
return obj
|
|
|
|
def err_to_response(self, err):
|
|
if hasattr(err, "__module__"):
|
|
err_module = err.__module__ + "."
|
|
else:
|
|
err_module = ""
|
|
|
|
if hasattr(err, "owner"):
|
|
err_module += err.owner.__name__ + "."
|
|
|
|
err_class = err_module + err.__class__.__name__
|
|
|
|
err_desc = force_text(err)
|
|
|
|
response = {
|
|
"err": 1,
|
|
"err_class": err_class,
|
|
"err_desc": err_desc,
|
|
"data": getattr(err, 'data', None),
|
|
}
|
|
if getattr(err, 'extra_dict', None):
|
|
response.update(err.extra_dict)
|
|
return response
|
|
|
|
def render_data(self, req, data, status=200):
|
|
debug = DEFAULT_DEBUG
|
|
debug = debug or req.GET.get('debug', 'false').lower() in ('true', 't', '1', 'on')
|
|
debug = debug or req.GET.get('decode', '0').lower() in ('true', 't', '1', 'on')
|
|
if 'callback' in req.GET or 'jsonpCallback' in req.GET:
|
|
format = req.GET.get('format', 'jsonp')
|
|
else:
|
|
format = req.GET.get('format', 'json')
|
|
jsonp_cb = req.GET.get('callback') or req.GET.get('jsonpCallback') or CALLBACK_NAME
|
|
if not re.match(r'^[$a-zA-Z_][a-zA-Z0-9_]*$', jsonp_cb):
|
|
return HttpResponseBadRequest('invalid JSONP callback name')
|
|
content_type = "application/json"
|
|
|
|
kwargs = dict(self.kwargs)
|
|
if debug:
|
|
kwargs["indent"] = 4
|
|
kwargs["ensure_ascii"] = False
|
|
|
|
plain = json.dumps(data, **kwargs)
|
|
if format == 'jsonp':
|
|
plain = "%s(%s);" % (jsonp_cb, plain)
|
|
content_type = "application/javascript"
|
|
|
|
return HttpResponse(plain, content_type="%s; charset=UTF-8" % content_type, status=status)
|
|
|
|
def api_func(self, f, *args, **kwargs):
|
|
return self.api(f, args[0], *args, **kwargs)
|
|
|
|
def api_method(self, f, *args, **kwargs):
|
|
return self.api(f, args[1], *args, **kwargs)
|
|
|
|
def api(self, f, req, *args, **kwargs):
|
|
logger = self.logger or logging.getLogger('passerelle.jsonresponse')
|
|
try:
|
|
resp = f(*args, **kwargs)
|
|
if isinstance(resp, HttpResponseBase):
|
|
return resp
|
|
|
|
data = self.obj_to_response(req, resp)
|
|
status = 200
|
|
return self.render_data(req, data, status)
|
|
except Exception as e:
|
|
extras = {'method': req.method, 'exception': exception_to_text(e), 'request': req}
|
|
if req.method == 'POST':
|
|
max_size = settings.LOGGED_REQUESTS_MAX_SIZE
|
|
if hasattr(logger, 'connector'):
|
|
max_size = logger.connector.logging_parameters.requests_max_size or max_size
|
|
extras.update({'body': repr(req.body[:max_size])})
|
|
if not isinstance(
|
|
e, (Http404, PermissionDenied, ObjectDoesNotExist, RequestException)
|
|
) and getattr(e, 'log_error', True):
|
|
logger.exception("Error occurred while processing request", extra=extras)
|
|
elif isinstance(e, ObjectDoesNotExist):
|
|
logger.warning('object not found: %r', e, extra=extras)
|
|
elif isinstance(e, PermissionDenied):
|
|
logger.warning('Permission denied', extra=extras)
|
|
elif isinstance(e, HTTPError):
|
|
log_http_request(logger, request=e.request, response=e.response, extra=extras)
|
|
elif isinstance(e, RequestException):
|
|
log_http_request(logger, request=e.request, exception=e, extra=extras)
|
|
elif isinstance(e, Http404):
|
|
# Http404 is for silent object not found exceptions
|
|
pass
|
|
else:
|
|
logger.warning("Error occurred while processing request", extra=extras)
|
|
if int(req.GET.get('raise', 0)):
|
|
raise
|
|
|
|
data = self.err_to_response(e)
|
|
if getattr(e, 'err_code', None):
|
|
data['err'] = e.err_code
|
|
if getattr(e, 'http_status', None):
|
|
status = e.http_status
|
|
elif isinstance(e, (ObjectDoesNotExist, Http404)):
|
|
status = 404
|
|
elif isinstance(e, PermissionDenied):
|
|
status = 403
|
|
else:
|
|
status = self.error_code
|
|
return self.render_data(req, data, status)
|