Our competences at your disposal on our blog
We love our job and we are glad to share it with you! Keep yourself fully informed about news and technologies we use to develop and which you may need! Follow our blog.
×

Error message

The spam filter installed on this site is currently unavailable. Per site policy, we are unable to accept new submissions until that problem is resolved. Please try resubmitting the form in a couple of minutes.
andrea.piolanti's picture

As described in the previous post, while there are several popular serialization/deserialization frameworks it is difficult to find frameworks that provide also support for RPC-based services. This is one of the major attractions of Thrift.
The definition of the Thrift services is comparable to the definition of Java interfaces, in fact it  requires just a name and signatures for the methods that you would like to implement.

service MathOperations {
	i32 multiply(1:i32 factor_1, 2:i32 factor_2),

	i32 add(1:i32 addend_1, 2:i32 addend_2),

	i32 sub(1:i32 subtrahend, 2:i32 subtractor),
	
}

Moreover services support inheritance: a service may optionally inherit from another service using the extends keyword.
Starting from the service definition, the Thrift compiler generates service interface code IFace (for the server) and stubs (for the client) in the chosen language.

To understand the generated code and how to use it properly it is necessary to look into the Thrift network task.
According to the Thrift documentation the Thrift network task is composed by 4 levels: Server, Processor, Protocol and Transport.

Transport

The Transport layer provides a simple abstraction for reading/writing from/to the network. This enables Thrift to decouple the transport from the rest of the system (for example serialization/deserialization).
The most important transports provided by thrift are:

  • TSocket: Used to create blocking socket I/O for transport.
  • TserverSocket: As the name suggest, is used mainly on the server side and it is used to create new Transport objects for incoming connections.

For example at client side we will have:

int port = 9091;
	TTransport transport = new TSocket("localhost", port);

and at server side:

int port = 9091;
	TServerSocket serverTransport = new TServerSocket(port);

Protocol

The Protocol abstraction defines a mechanism to map data structures to a wire-format. The protocol implementation governs the encoding scheme and is responsible for (de)serialization. In other words, a protocol specifies how datatypes use the underlying Transport to encode/decode themselves. Thrift supports a various number of protocols: TbinaryProtocol, TCompactProtocol, TDebugProtocol , TJSONProtocol.
In order to use the binary protocol the client just need to declare:

TProtocol protocol = new TbinaryProtocol(transport);

Processor

The processor layer allows to read data from input streams and write to output streams. The input and output streams are represented by Protocol objects. Service-specific processor implementations are generated by the compiler. The Processor essentially reads data from the wire (using the input protocol), delegates processing to the handler (implemented by the user) and writes the response over the wire (using the output protocol).
This could be our server handler:

public class MathOperationsHandler implements MathOperations.Iface {
 
    public long add(int addend_1, int addend_2) throws TException {
        return addend + addend;
    }
 
    public long multiply(int factor_1, int factor_2) throws TException {
        return factor_1 * factor_2;
    }

    public long sub(int subtrahend, int subtractor) throws TException {
        return subtrahend - subtractor;
    }
 
 
}

and accordingly this is the processor:

MathematicsService.Processor processor = 
	new MathematicsService.Processor(
		new MathOperationsHandler()
	);

Server and final implementation

A Server pulls together all of the various features described above and so creates the transport, the protocol for the transport, a processor based on the protocol and finally wait for incoming connections and forward them to the processor.
There are some different kinds of server that could be instantiated: single threaded (TSimpleServer) multi threaded (TThreadedServer e TThreadedPoolServer), http (ThttpServer).
In the following example a TThreadPoolServer implementation is used and it will utilize a thread pool to serve incoming requests in parallel:

	public class MathematicsServer {
		public void start(Integer port) throw TTransportException{
			TServerSocket serverTransport = new TServerSocket(port);
			MathematicsService.Processor processor =
				 new MathematicsService.Processor(
				 	new ArithmeticServiceImpl());

			TServer server = new TThreadPoolServer(
				new TThreadPoolServer.Args(serverTransport).processor(processor));
	 		System.out.println("Starting server ...");
	 		server.serve();
		}
	}

At the opposite the client impementation will be:


	
public class MathematicClient {
	
	private Integer mPort;
	private String mIpServer;
	
	public MathematicClient(Integer port, String ipServer) {
		mIpServer = ipServer;
 		mPort = port;
	}

	public Integer add(Integer add1, Integer add2) {
		TTransport transport;
		try {
			transport = new TSocket(mIpServer, mPort);

			TProtocol protocol = new TBinaryProtocol(transport);

			MathematicsService.Client client = new MathematicsService.Client(protocol);
			transport.open();

			Integer addResult = client.add(add1, add2);
			return addResult;
		} catch (TTransportException e) {
			//Handle exception
		} catch (TException e) {
			//Handle exception
		} finally {
			transport.close();
		}
	}
	// Other functions ....

}

The client we have created in this example is synchronous so it will wait until the response of the server. But Thrift offers also the possibilities to create asynchrounous client simply changing few rows of code at client side and keeping the same server implementation. In this scenario a callback for each method needs to be registered. This callback will get invoked at a successful completion of the request.

public class MathematicClientAsync {
	
	private Integer mPort;
	private String mIpServer;
	
	public MathematicClient(Integer port, String ipServer) {
		mIpServer = ipServer;
		mPort = port;
	}
	
	public void add(Integer add1, Integer add2) {
	
		try {
			MathematicsService.AsyncClient client = new MathematicsService.AsyncClient(
				new TBinaryProtocol.Factory(), new TAsyncClientManager(),
				new TNonblockingSocket(mIpServer, mPort));
			client.add(add1, add2, new AddMethodCallback());
		} catch (TTransportException e) {
			//Handle exception
		} catch (TException e) {
			//Handle exception
		} catch (IOException e) {
			//Handle exception
		}
	}
	// Other functions ....

}

And the callback function:

public class AddMethodCallback
	implements AsyncMethodCallback<ArithmeticService.AsyncClient.add_call> {

	public void onComplete(ArithmeticService.AsyncClient.add_call add_call) {
		try {
			int result = add_call.getResult();
			// elaboration of the result
		} catch (TException e) {
			// Handle exception
		}
	}

	public void onError(Exception e) {
		// error management
	}

}

Securing the service

When the client-server application needs to exchange data over a public network, a common requirement is to secure the connection. Concerning this problem, Thrift provided an useful API to create a secure socket connection that involves both client and server sides. The Thrift API just requires a key store with server private key at server-side and a trust store containing server’s public key at client side. Then the server secures socket and transport creation will be:

public void start(Integer port) throw TtransportException{
	...
	
	TSSLTransportFactory.TSSLTransportParameters params =
		new TSSLTransportFactory.TSSLTransportParameters();
	params.setKeyStore("path to keystore.jks", "keystore.jks password");

	TServerSocket serverTransport = TSSLTransportFactory.getServerSocket(
	listenPort, timeout, InetAddress.getByName(listenIp), params);

	...
}

And at client side:

public Integer add(Integer add1, Integer add2) {
	...

	TTransport transport;
	TSSLTransportFactory.TSSLTransportParameters params =
		new TSSLTransportFactory.TSSLTransportParameters();
	params.setTrustStore("path to truststore.jks", "truststore.jks password");

	transport = TSSLTransportFactory.getClientSocket(mIpServer, mPort, mTimeout, params);
	...
}

Final consideration

Thrift’s greatest strong point is represented by the fact that it allows the realization of RPC-based applications in a simple and flexible way. Thanks to the numerous well-supported languages and to the various types of Protocol, Transport and Server offered, Thrift satisfies many necessities in terms of type of codification, management of the server-side connections, communications safety, type of client (synchronous or asynchronous). Anyway there are some negative aspects or some limitations that needs to be underlined:

  • It is not possible to create circular references
  • there are no circular references between data structures;
  • Services can’t return null and also have null parameters;
  • No inheritance between data structures;
  • There are no possibilities to have a bidirectional message exchanging. You need to implement two separate client/server applications;
  • Set of datatype limited.

Add new comment

Filtered HTML

  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.