简体   繁体   中英

How to get client's IP in a python thrift server

I'm writing a thrift service in python and I would like to understand how I can get the client's IP address in the handler functions context.

Thanks, Love.

You need to obtain the transport, and get the data from there. Not sure how to do this exactly in Python, but there's a mailing list thread and there's this JIRA-ticket THRIFT-1053 describing a solution for C++/Java.

This is the relevant part from the mailing list thread:

I did it by decorating the TProcessor like this psuedo-code.

-craig

class TrackingProcessor implements TProcessor {
 TrackingProcessor (TProcessor processor) {this.processor=processor;}

 public boolean process(TProtocol in, TProtocol out) throws TException {
   TTransport t = in.getTransport();
   InetAddress ia = t instanceof TSocket ?
       ((TSocket)t).getSocket().getInetAddress() : null;
   // Now you have the IP address, so what ever you want.

   // Delegate to the processor we are decorating.
   return processor.process(in,out);
  }  
}

This is a bit old but I'm currently solving the same problem. Here's my solution with thriftpy:

import thriftpy
from thriftpy.thrift import TProcessor, TApplicationException, TType
from thriftpy.server import TThreadedServer
from thriftpy.protocol import TBinaryProtocolFactory
from thriftpy.transport import TBufferedTransportFactory, TServerSocket

class CustomTProcessor(TProcessor):
    def process_in(self, iprot):
        api, type, seqid = iprot.read_message_begin()
        if api not in self._service.thrift_services:
            iprot.skip(TType.STRUCT)
            iprot.read_message_end()
            return api, seqid, TApplicationException(TApplicationException.UNKNOWN_METHOD), None  # noqa

        args = getattr(self._service, api + "_args")()
        args.read(iprot)
        iprot.read_message_end()
        result = getattr(self._service, api + "_result")()

        # convert kwargs to args
        api_args = [args.thrift_spec[k][1] for k in sorted(args.thrift_spec)]

        # get client IP address
        client_ip, client_port = iprot.trans.sock.getpeername()

        def call():
            f = getattr(self._handler, api)
            return f(*(args.__dict__[k] for k in api_args), client_ip=client_ip)

        return api, seqid, result, call

class PingPongDispatcher:
    def ping(self, param1, param2, client_ip):
        return "pong %s" % client_ip

pingpong_thrift = thriftpy.load("pingpong.thrift")
processor = CustomTProcessor(pingpong_thrift.PingService, PingPongDispatcher())
server_socket = TServerSocket(host="127.0.0.1", port=12345, client_timeout=10000)
server = TThreadedServer(processor,
                         server_socket,
                         iprot_factory=TBinaryProtocolFactory(),
                         itrans_factory=TBufferedTransportFactory())
server.serve()

Remember that every method in the dispatcher will be called with extra parameter client_ip

Here's the same wrapper, translated to C#. This answer is just about the only good result of a google search for this issue, so I figure that others might have an easier time translating it to their language of choice given two points of reference instead of one. For the record, this worked perfectly for me.

(note that "in" and "out" are reserved words in C#)

using Thrift;
using System.Net;

class TrackingProcessor : TProcessor
{
    private TProcessor processor;
    public TrackingProcessor(TProcessor processor)
    {
        this.processor = processor;
    }

    public Boolean Process(TProtocol inProt, TProtocol outProt)
    {
        TTransport t = inProt.Transport;
        IPAddress ip = ((IPEndPoint)((TSocket)t).TcpClient.Client.RemoteEndPoint).Address;
        //Presumably we want to do something with this IP
        return processor.Process(inProt, outProt);
    }
}

The only way I found to get the TProtocol at the service handler is to extend the processor and create one handler instance for each client related by transport/protocol. Here's an example:

public class MyProcessor implements TProcessor {

    // Maps sockets to processors
    private static final Map<, Processor<ServiceHandler>> PROCESSORS = Collections.synchronizedMap(new HashMap<String, Service.Processor<ServiceHandler>>());

    // Maps sockets to handlers
    private static final Map<String, ServiceHandler> HANDLERS = Collections.synchronizedMap(new HashMap<String, ServiceHandler>());

    @Override
    public boolean process(final TProtocol in, final TProtocol out)
            throws TException {

        // Get the socket for this request
        final TTransport t = in.getTransport();
        // Note that this cast will fail if the transport is not a socket, so you might want to add some checking.
        final TSocket socket = (TSocket) t; 

        // Get existing processor for this socket if any
        Processor<ServiceHandler> processor = PROCESSORS.get(socket);
        // If there's no processor, create a processor and a handler for
        // this client and link them to this new socket
        if (processor == null) {
            // Inform the handler of its socket
            final ServiceHandler handler = new ServiceHandler(socket);
            processor = new Processor<ServiceHandler>(handler);
            PROCESSORS.put(clientRemote, processor);
            HANDLERS.put(clientRemote, handler);
        }
        return processor.process(in, out);
    }
}

Then you need to tell Thrift to use this processor for incoming requests. For a TThreadPoolServer it goes like this:

final TThreadPoolServer.Args args = new TThreadPoolServer.Args(new TServerSocket(port));
args.processor(new MyProcessor());
final TThreadPoolServer server = new TThreadPoolServer(args);

The PROCESSORS map might look superfluous, but it is not since there's no way to get the handler for a processor (ie there's no getter).

Note that it is your ServiceHandler instance that needs to keep which socket it is associated to. Here I pass it on the constructor but any way will do. Then when the ServiceHandler 's IFace implementation is called, it will already have the associated Socket.

This also means you will have an instance of MyProcessor and ServiceHandler for each connected client, which I think is not the case with base Thrift where only one instance of each of the classes are created.

This solution also has a quite annoying drawback: you need to figure out a method to remove obsolete data from PROCESSORS and HANDLERS maps, otherwise these maps will grow indefinitely. In my case each client has a unique ID, so I can check if there are obsolete sockets for this client and remove them from the maps.

PS: the Thrift guys need to figure out a way to let the service handler get the used protocol for current call (for example by allowing to extend a base class instead of implementing an interface). This is very useful in so many scenarios.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM