tcalmant / jsonrpclib

A Python (2 & 3) JSON-RPC over HTTP that mirrors the syntax of xmlrpclib (aka jsonrpclib-pelix)
https://jsonrpclib-pelix.readthedocs.io/
Apache License 2.0
53 stars 24 forks source link

Get client IP from dispatcher-method? #16

Closed byaka closed 9 years ago

byaka commented 9 years ago

now i'm do this:


from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer
from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCRequestHandler

class myRequestHandler(SimpleJSONRPCRequestHandler):
   rpc_paths=('/adWolf-accApi')
   clientIp=None #неясно как такой способ работает при многопоточности

   def do_POST(self):
      global clientIp
      clientIp, clientPort=self.client_address
      SimpleJSONRPCRequestHandler.do_POST(self)

class mySharedMethods:
   def test(self): print clientIp

if __name__=='__main__':
   server=SimpleJSONRPCServer(("0.0.0.0", 8013), requestHandler=myRequestHandler, logRequests=False)
   server.register_instance(mySharedMethods())
   server.serve_forever()

It's ugly and i'm think it's doesn't work properly with threaded version of server (when more then one clients per time connected, global variable "clientIp" will be shared and contain IP of last client only).

More elegant way?

tcalmant commented 9 years ago

I think the most elegant way is to set the client IP as a parameter of the called method.

You can do this by defining a _dispatch method in your request handler. That method is looked for in the request parsing process, but is not defined by default. => You don't have to override the do_POST method, as it only give the JSON data to the dispatcher.

The custom _dispatch method will be called once the request has been parsed, instead of the dispatch method of the SimpleJSONRPCDispatcher. => You will be able to get the client IP in this method (see the sample) Then, you will be able to call the default method by using the server member of your request handler, because the SimpleJSONRPCServer class inherits from SimpleJSONRPCDispatcher.

The next step is to add the client IP parameter, but only when calling methods that wants it. I prefer to add a custom field to the method object for that (here, needs_ip), as it's really easy to check, and you can use a decorator to set it.

Here is a working example:

from __future__ import print_function
from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer
from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCRequestHandler
import jsonrpclib.utils
try:
    import xmlrpc.server as xmlrpcserver
except ImportError:
    import SimpleXMLRPCServer as xmlrpcserver

class myRequestHandler(SimpleJSONRPCRequestHandler):
    rpc_paths = ('/adWolf-accApi')

    def _dispatch(self, method, params):
        try:
            # You can look into self.server.funcs to get the method object
            called_method = self.server.funcs[method]
        except KeyError:
            # Try with the installed instance
            try:
                called_method = xmlrpcserver.resolve_dotted_attribute(
                    self.server.instance, method, True)
            except AttributeError:
                # No installed instance: the method is either unknown,
                # or dispatched with a specific method
                called_method = None

        if hasattr(called_method, "needs_ip"):
            # Add client IP to parameters
            clientIp, _ = self.client_address
            if isinstance(params, jsonrpclib.utils.ListType):
                params.append(clientIp)
            else:
                params['client_ip'] = clientIp

        return self.server._dispatch(method, params)

def needs_ip(method):
    # Nice way to request an IP: define a decorator that will set the needs_ip
    # flag
    method.needs_ip = True
    return method

class mySharedMethods:
    def test1(self):
        print("Test 1 called")

    def test2(self, client_ip):
        print("Test 2 IP=", client_ip)

    # Not readable, but valid; set the flag manually
    test2.needs_ip = True

    @needs_ip
    def test3(self, a, b, client_ip):
        # Using the decorator
        print("Test 3 IP=", client_ip)

if __name__ == '__main__':
    server = SimpleJSONRPCServer(("0.0.0.0", 8013),
                                 requestHandler=myRequestHandler,
                                 logRequests=False)
    server.register_instance(mySharedMethods())
    server.serve_forever()
byaka commented 9 years ago

It's really beauty, Thx u!