1# coding: utf-8 2 3from django.core.exceptions import ImproperlyConfigured 4from django.http.response import HttpResponse, HttpResponseForbidden 5from django.utils.decorators import method_decorator 6from django.utils.module_loading import import_string 7from django.views.decorators.csrf import csrf_exempt 8from django.views.generic.base import View, TemplateView 9 10from modernrpc.conf import settings 11from modernrpc.core import ALL, registry 12from modernrpc.exceptions import RPCInternalError, RPCException, AuthenticationFailed 13from modernrpc.utils import ensure_sequence, get_modernrpc_logger 14 15logger = get_modernrpc_logger(__name__) 16 17 18class RPCEntryPoint(TemplateView): 19 """ 20 This is the main entry point class. It inherits standard Django View class. 21 """ 22 23 template_name = 'modernrpc/doc_index.html' 24 25 entry_point = settings.MODERNRPC_DEFAULT_ENTRYPOINT_NAME 26 protocol = ALL 27 enable_doc = False 28 enable_rpc = True 29 30 def __init__(self, **kwargs): 31 super(RPCEntryPoint, self).__init__(**kwargs) 32 33 if not self.get_handler_classes(): 34 raise ImproperlyConfigured("At least 1 handler must be instantiated.") 35 36 # Copy static list http_method_names locally (in instance), so we can dynamically customize it 37 self.http_method_names = list(View.http_method_names) 38 39 # Customize allowed HTTP methods name to forbid access to GET when this EntryPoint 40 # must not display docs... 41 if not self.enable_doc: 42 self.http_method_names.remove('get') 43 44 # ... and also forbid access to POST when this EntryPoint must not support RPC request (docs only view) 45 if not self.enable_rpc: 46 self.http_method_names.remove('post') 47 48 logger.debug('RPC entry point "{}" initialized'.format(self.entry_point)) 49 50 # This disable CSRF validation for POST requests 51 @method_decorator(csrf_exempt) 52 def dispatch(self, request, *args, **kwargs): 53 """Overrides the default dispatch method, to disable CSRF validation on POST requests. This 54 is mandatory to ensure RPC calls wil be correctly handled""" 55 return super(RPCEntryPoint, self).dispatch(request, *args, **kwargs) 56 57 def get_handler_classes(self): 58 """Return the list of handlers to use when receiving RPC requests.""" 59 60 handler_classes = [import_string(handler_cls) for handler_cls in settings.MODERNRPC_HANDLERS] 61 62 if self.protocol == ALL: 63 return handler_classes 64 else: 65 return [cls for cls in handler_classes if cls.protocol in ensure_sequence(self.protocol)] 66 67 def post(self, request, *args, **kwargs): 68 """ 69 Handle a XML-RPC or JSON-RPC request. 70 71 :param request: Incoming request 72 :param args: Additional arguments 73 :param kwargs: Additional named arguments 74 :return: A HttpResponse containing XML-RPC or JSON-RPC response, depending on the incoming request 75 """ 76 77 logger.debug('RPC request received...') 78 79 for handler_cls in self.get_handler_classes(): 80 81 handler = handler_cls(request, self.entry_point) 82 83 try: 84 if not handler.can_handle(): 85 continue 86 87 logger.debug('Request will be handled by {}'.format(handler_cls.__name__)) 88 89 result = handler.process_request() 90 91 return handler.result_success(result) 92 93 except AuthenticationFailed as e: 94 # Customize HttpResponse instance used when AuthenticationFailed was raised 95 logger.warning(e) 96 return handler.result_error(e, HttpResponseForbidden) 97 98 except RPCException as e: 99 logger.warning('RPC exception: {}'.format(e), exc_info=settings.MODERNRPC_LOG_EXCEPTIONS) 100 return handler.result_error(e) 101 102 except Exception as e: 103 logger.error('Exception raised from a RPC method: "{}"'.format(e), 104 exc_info=settings.MODERNRPC_LOG_EXCEPTIONS) 105 return handler.result_error(RPCInternalError(str(e))) 106 107 logger.error('Unable to handle incoming request.') 108 109 return HttpResponse('Unable to handle your request. Please ensure you called the right entry point. If not, ' 110 'this could be a server error.') 111 112 def get_context_data(self, **kwargs): 113 """Update context data with list of RPC methods of the current entry point. 114 Will be used to display methods documentation page""" 115 kwargs.update({ 116 'methods': registry.get_all_methods(self.entry_point, sort_methods=True), 117 }) 118 return super(RPCEntryPoint, self).get_context_data(**kwargs) 119