Java – Trasferimento XML via Socket – Parte 1

Perché!? Perché!?

Ho perso settimane alla ricerca di un modo elegante e pulito di trasferire informazioni fra server e client tramite un protocollo basato su XML. Possibile che nessuno abbia fatto una classe nelle librerie standard in grado di trasferire facilmente una manciata di tag XML? Non lo so. Fatto sta che il web non conteneva nessuna risposta alla mia domanda, così, domandando in giro sono arrivato a sviluppare 4 classi in grado di trasferire con un solo comando pacchetti XML attraverso un Socket.

SERIALIZZAZIONE E PARSING

Quando si lavora con un documento che contiene dati bisogna, innanzitutto, essere in grado di leggerlo. L’azione che compie un programma quando legge un documento/file e ne interpreta il contenuto si chiama parsing. Il parsing di un file consiste, quindi, nell’estrarre da una sequenza di caratteri le informazioni da noi desiderate ed organizzarle in una struttura logica all’interno della memoria.

La serializzazione, invece, è esattamente il contrario del parsing. Serializzare un oggetto consiste nel trasformare tale struttura dati in una sequenza lineare di caratteri.

Questi due passaggi sono essenziali per lavorare con i file in modo avanzato: il computer gestisce nativamente solo sequenza di byte (e quindi caratteri) e quindi tutto ciò che non risiede in memoria deve poter essere memorizzato in questo formato.

LIMITI DEL TRASFERIMENTO XML NATIVO

Le classi che gestiscono XML sia secondo metodologia SAX che DOM permettono di serializzare e di parsare un documento XML direttamente in/da uno Stream dati. Possiamo cioè serializzare un documento XML direttamente in un OutputStream e analogamente possiamo parsare un file XML attingendo direttamente da un InputStream. Questa è l’interfaccia che ci offrono gli strumenti base.

Il problema è che questo meccanismo non funziona attraverso un Socket. Supponiamo che un client voglia inviare una richiesta ad un server. Il client crea il suo documento XML con la richiesta e la serializza nell’OutputStream del socket. A questo punto il server riceve questi dati effettuando direttamente il parsing dall’InputStream del socket.

Qui avviente l’intoppo. XML non prevede un terminatore esplicito. Il server rimane quindi in attesa perenne che il flusso dati provenienti dal client si interrompa (inviando un EOF, Endo-Of-File).

Questo però non può avvenire in nessun caso. Infatti:

  1. Se il client forza l’invio di un EOF (inviando un byte contenente 0x00) il parser del server lancia eccezione. Tale terminatore non può essere parsato!
  2. Se il client chiude lo stream, automaticamente il socket viene disconnesso e la connessione viene interrotta. Il server quindi non può più rispondere, a meno di non instaurare una nuova connessione.

La soluzione non può essere risolta in questo modo. Bisogna escogitare soluzioni alternative.

TROVIAMO UNA SOLUZIONE

Il problema ci appare subito chiaro. Dobbiamo indicare al server che il documento è finito così che lui possa interrompere l’attesa e proseguire con l’esecuzione. Per fare ciò abbiamo due modi:

  1. Inviare alla fine del documento qualcosa che segnali al server che non verrà inviato più nulla.
  2. Istruire il server a leggere solo un TOT di byte corrispondendi alla lunghezza del messaggio.

Il primo metodo lo abbiamo già scartato: qualunque cosa inviamo alla fine del documento verrà parsata generando l’eccezione del server. Ci rimane la seconda soluzione.

Questa presuppone la conoscenza a priori della dimensione del documento. Ma di questo non siamo affatto sicuri. Come potremmo esserlo? Anche per questa cosa abbiamo fondamentalmente due soluzioni:

  1. Dare una dimensione fissata ad ogni pacchetto riempendo lo spazio non usato di dati casuali (inseriti in un campo CDATA).
  2. Inviare prima del documento il numero di byte che verranno inviati.

La prima soluzione è ovviamente uno spreco enorme di memoria. E’ utile nel caso la dimensione di ogni pacchetto sia più o meno sempre la stessa poiché questa soluzione ha il vantaggio di essere trasparente al client.

La seconda soluzione invece è molto più scalabile ed “elegante”. Il problema è: come facciamo a sapere quanti byte verranno inviati? La soluzione è ancora più semplice: invece di serializzare il documento direttamente sullo stream del socket lo serializziamo su un nostro stream privato, valutiamo la sua dimensione e dopodiché la inviamo seguita dal contenuto del nostro stream temporaneo.

Nel lato di ricezione invece leggiamo in questo modo: prima leggiamo il numero di byte (contenuto nei primi 4byte del messaggio) e poi trasferiamo questo esatto numero di byte provenienti dal socket in un nostro stream che poi faremo parsare.

La soluzione non fa una grinza. Ci manca solo di integrarla al meglio per rendere il più semplice possibile l’uso di questo protocollo all’interno della nostra applicazione.

Per oggi è tutto. Vedremo fra poco come fare tutto questo in pratica.

Comments are closed.