Le nostre competenze a vostra disposizione sul blog
Amiamo il nostro lavoro e vogliamo condividerlo con voi! Tenetevi aggiornati su tutte le news e le tecnologie con le quali lavoriamo e di cui potreste avere bisogno! Seguite il nostro 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

Come descritto nel precedente post, mentre l’utilizzo di framework di supporto per la serializzazione/deserializzazione delle entità è relativamente comune, risulta invece difficile trovare framework che offrano anche la possibilità di sviluppare facilmente client e server RPC. Questa fortunatamente è una delle caratteristiche più importanti di Thrift.
La definizione dei servizi Thrift è paragonabile alla definizione delle interfacce in Java. Per creare dei servizi che semplicemente sommano, moltiplicano o sottragono due numeri è sufficiente dichiarare:

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),
	
}

La definizione dei servizi in Thrift supporta anche l’ereditarietà grazie all’utilizzo della parola chiave extends.
Partendo dalla definizione del servizio il compilatore di Thrift genererà il codice (IFace) che rappresenta l’interfaccia per il server e lo stub (Client o AsyncClient) per il client nel linguaggio di programmazione prescelto.

Per capire il codice generato da Thrift e come usarlo adeguatamente è necessario analizzare il Thrift Network Task.
Stando alla documentazione di Thrift, il Thrift Network Task è composto da 4 livelli: Server, Processor, Protocol e Transport.

TRANSPORT

Il livello di Transport fornisce un’astrazione dei metodi necessari per la scrittura/lettura da/verso il mezzo di comunicazione permettendo di disaccoppiare la parte di trasporto dal resto del sistema. I Transport più rilevanti forniti da Thrift sono:

  • TSocket: classica comunicazione di rete tramite socket
  • TServerSocket: come suggerito dal nome, questo transport è usato principalmente dal lato server e permette di creare nuovi oggetti Transport per le connessioni in entrata.

Per esempio, lato client, in caso di una connessione TCP/IP, avremo:

PROTOCOLLO

Il livello Protocol, definisce i meccanismi di mapping fra le entità di progetto e la loro codifica a livello di trasporto. L’ implementazione del protocollo controlla lo schema di encoding ed è responsabile della (de)serializzazione delle entità.
Thrift mette a disposizione vari protocolli fra cui: TbinaryProtocol (la lunghezza e il tipo di ogni field sono codificati tramite dei bytes seguite dal reale valore del filed), TCompactProtocol, TDebugProtocol , TJSONProtocol (notazione JSON).
Ad esempio nel caso in cui si voglia utilizzare il protocollo binario il client dovrà semplicemente dichiarare:

TProtocol protocol = new TbinaryProtocol(transport);

PROCESSOR

Il livello di Processor si occupa di leggere le informazioni dall’ input stream e scrivere sull’output stream. Gli stream di input e output sono rappresentati da istanze del livello di Protocol. Il livello di Processor, fondamentalmente, legge i dati dalla rete (utilizzando il protocol di input), ne delega l’elaborazione ad un handler (implementato dall’utente) e scrive la risposta tramite il protocol di output.
Riprendendo l’esempio iniziale nel quale si definiva il servizio MathOperations, lato server l’handler appropriato sarà:

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;
    }
 
 
}

In accordo con questo Handler il Processor sarà:

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

IL SERVER E L’IMPLEMENTAZIONE FINALE

La parte di server riunisce tutte le varie caratteristiche descritte precedentemente e si occupa di mettersi in attesa delle connessioni in entrata per poi inoltrarle al Processor.
Esistono differenti tipi di server che possono essere istanziati:single threaded (TSimpleServer) multi threaded (TThreadedServer e TThreadedPoolServer), http (ThttpServer).

Nel seguente esempio viene usata un’implementazione TThreadPoolServer, la quale utilizza un pool di thread per servire le richieste in entrata parallelamente.

	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();
		}
	}

L’implementazione finale del client invece sarà:


	
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 ....

}

Il client che abbiamo creato in questo esempio è sincrono, infatti rimarrà bloccato sulla chiamata al servizio finché non riceverà la risposta dal server. Thrift, tuttavia, permette anche di creare client asincroni semplicemente cambiando alcune righe di codice nell’implementazione del client e mantenendo la stessa implementazione del server. E’ necessario però definire una funzione di callback per ogni metodo esposto dal server. Questa callback verrà invocata appena il server avrà completato di elaborare la richiesta. Ad esempio:

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 ....

}

E la funzione della callback:

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
	}

}

SICUREZZA DEL SERVIZIO

Quando l’applicazione client-server comporta lo scambio di informazioni su una rete pubblica, un requisito classico è rappresentato dalla richiesta di una comunicazione end-to-end sicura fra sorgente e destinatario.
Riguardo a questo problema, Thrift fornisce le API utili per garantire l’autenticazione, l’ integrità dei dati e la cifratura della connessione operando al di sopra del livello di trasporto.
L'API Thrift richiede solo un keystore con la chiave privata del server lato server e un truststore contenente la chiave pubblica del server sul lato client. Una volta definiti keystore e truststore per instaurare la connessione sicura lato server si dovrà dichiarare:

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);

	...
}

Mentre lato client:

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);
	...
}

CONCLUSIONI

Il maggiore punto di forza di Thrift è sicuramente rappresentato dal fatto che permette di realizzare in maniera semplice e flessibile applicazioni client server RPC. Grazie ai numerosi linguaggi supportati e alle varie tipologie di Protocol, Transport e Server forniti, Thrift permette infatti di soddisfare numerose esigenze in termini di tipo di codifica utilizzata, gestione delle connessioni lato server, sicurezza della comunicazione, tipologia del client (sincrono o asincrono). Tuttavia ci sono alcuni aspetti negativi o comunque alcune limitazioni che vale la pena evidenziare:

  • Non è possibile creare riferimenti circolari nella definizione delle entità Thrift;
  • Non è possibile passare ai servizi parametri che hanno valore null e allo stesso modo i servizi Thrift non possono ritornare valore null;
  • La definizione delle entità, a differenza dei servizi, non supporta l’ereditarietà;
  • Non c’e la possibilià di instaurare uno scambio di messaggi bidirezionale se non creando un secondo servizio Thrift nel quale i ruoli di client e server sono invertiti.
  • Il metalinguaggio di Thrift ha un numero limitato di tipi di dato possibili.

Aggiungi un commento

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.