L’HTTP è un protocollo che lavora con un’architettura di tipo client/server, il client esegue una richiesta ed il server restituisce la risposta mandata da un altro host. Questo blocco funzione da eseguire in task Back, esegue la richiesta in modalità client (Proprio come il browser), per gestire la connessione con il protocollo HTTP utilizzare il FB SysTCPClient collegando tra di loro gli I/O File. Per gestire la connessione con il protocollo HTTPS occorre inserire tra la connessione degli I/O File il FB SysTLSClient.
Attivando Enable viene attivata l’uscita Connect che comanda la connessione al server, a connessione avvenuta viene inviata la richiesta HTTP della pagina definita in Page all’HostName. La pagina viene richiesta con i parametri definiti nel buffer Request passati secondo la definizione di RMethod (GET, POST, PUT).
In DBSize occorre definire la dimensione del buffers che l’FB alloca (Con SysRMalloc) per la gestione dei pacchetti TCP in trasmissione e ricezione. La dimensione minima è 256 bytes, aumentando la dimensione si velocizza il trasferimento (Sono effettuati meno frazionamenti). E’ inutile definire lunghezze superiori al limite del pacchetto TCP 1500 bytes.
L’header e la pagina sono ricevuti in frammenti successivi in base al tipo di trasferimento del server, ad ogni ricezione di un frammento DBChars viene valorizzato (Per un solo loop di esecuzione) con il numero di bytes ricevuti che possono essere letti dal buffer all’indirizzo DBAddress. (HPSelector indica il tipo di dati ricevuti, FALSE dati header, TRUE dati pagina). In questo modo è possibile ricevere qualsiasi dimensione di dati, sarà cura del programma utente trasferire i dati ricevuti in una stringa o in un file.
Al termine dell’invio della richiesta si attiva per un loop di programma l’uscita RSent, su ricezione pagina si attiva per un loop di programma l’uscita PLoad, in HLength e PLength sono ritornate le lunghezze dell’header e della pagina ricevuti, mentre in PLTime il tempo necessario per l’intera richiesta.
In caso di errore esecuzione o tempo di esecuzione comando superiore al tempo definito in Timeout, viene attivata per un loop di programma l’uscita Fault. L’uscita Done si attiva al termine della esecuzione della richiesta e su errore. Per acquisire nuovamente la pagina occorre disabilitare e poi riabilitare l’ingresso Enable.
Upgrade list
HTTPClient_v4
Viene gestita la connessione HTTP Keep Alive nativa dell’HTTP 1.1, per gestirla si è aggiunto il comando Send. Attivando Enable viene comandata la sola connessione al server HTTP, per inviare la richiesta occorre attivare Send. Ricevuta la risposta la connessione rimane attiva per la prossima richiesta, per chiudere la connessione disabilitare Enable.
HTTPClient_v5
Modificata gestione connessione HTTP Keep Alive, aggiunto parametri KeepAlive e KAAccepted, attivando permanentemente Enable, il comando Send, gestisce la connessione automaticamente. Modificato parametro RMethod ora è di tipo HTTP_REQUEST. Modificato tipo parametri Timeout e PLTime ora sono di tipo TIME.
Descrizione
Enable (BOOL) Comando abilitazione esecuzione, la connessione al server si attiva sul comando Send.
Send (BOOL) Comando richiesta pagina, resettando il comando si chiude la connessione in corso. Per eseguire una nuova richiesta occorre disattivare e riattivare l’ingresso.
KeepAlive (BOOL) Se attivo, terminata la richiesta viene mantenuta attiva la connessione al server. Verificare che nel tempo di LifeTme del FB TCPClient vengano eseguite richieste.
SpyOn (BOOL) Se attivo permette di spiare il funzionamento del FB (Vedi articolo).
File (eFILE) Stream di I/O utilizzato per la connessione.
RMethod (HTTP_REQUEST) Metodo gestione richiesta (Definizione).
HostName (@STRING) Nome del server utilizzato nella richiesta.
Page (@STRING) Stringa di definizione pagina richiesta.
Request (@STRING) Indirizzo buffer dati da inviare con la richiesta.
Header (@STRING) Indirizzo stringa header, se eNULL viene inviato l’header standard. Se definito occorre indicare l’header completo da inviare con la richiesta al server i campi vanno sempre terminati con $r$n.
DBSize (UDINT) Dimensione buffers Rx/Tx allocati da FB (SysRMalloc). Aumentando la dimensione del buffer si riduce la frammentazione dei dati, minimo 256 massimo 1500.
Timeout (TIME) Timeout esecuzione richiesta pagina.
Connect (BOOL) Comando di connessione al server, su attivazione occorre gestire la connessione.
Done (BOOL) Attivo a fine esecuzione, si attiva anche in caso di Fault. L’uscita rimane attiva fino a quando non viene settato Enable:=FALSE, o Send:=FALSE.
Fault (BOOL) Attivo per un loop di programma se errore gestione.
KAAccepted (BOOL) Si attiva se il server gestisce la connessione HTTP Keep Alive.
RSent (BOOL) Attivo per un loop di programma al termine dell’invio richiesta HTTP.
HPSelector (BOOL) FALSE: Si stanno ricevendo dati di header. TRUE: Si stanno ricevendo dati di pagina.
PLoad (BOOL) Attivo per un loop di programma su fine ricezione pagina.
HTTPStatus (UINT) Al termine della ricezione pagina ritorna il codice di stato HTTP ricevuto dal server.
CCounter (UDINT) Contatore connessioni al server HTTP. Se la connessione è in keep alive, se non vi sono errori o timeout di comunicazione si ha una sola connessione al server.
PCounter (UDINT) Contatore pagine ricevute.
DBAddress (PVOID) Indirizzo buffer dati ricevuti allocato da FB (Con SysRMalloc) di dimensione DBSize.
DBChars (UDINT) Bytes di pagina ricevuti, viene valorizzato per un loop. Ad ogni valorizzazione occorre estrarre i dati (Header/Pagina) da DBAddress. Nella ricezione dell’header (HPSelector=FALSE) sono ritornate di volta in volta le singole righe di header terminate con <CR><LF>.
HLength (UDINT) Dimensione header ricevuto.
PLength (UDINT) Dimensione pagina ricevuta.
PLTime (TIME) Tempo impiegato per richiesta pagina.
Errors (UDINT) Counter errori di esecuzione.

Spionaggio funzionamento
Se SpyOn attivo è possibile utilizzare la console di spionaggio per verificare il funzionamento della FB. Sono previsti vari livelli di triggers.
Trigger di spionaggio
16#00000001 | ‘Tx’ Invio dati verso server HTTP |
16#00000002 | ‘Rx’ Ricezione dati da server HTTP |
16#00000004 | ‘Rq’ Stringa richiesta |
16#10000000 | ‘Lg’ Log dati di esecuzione |
16#40000000 | ‘Er’ Errori di esecuzione |
Header richiesta
Nel parametro Header è possibile definire un campo header personalizzato da inviare al server, se definito eNULL il FB invia l’header con i campi evidenziati nella tabella seguente. Occorre far seguire alla definizione i caratteri $r$n, è possibile definire più headers semplicemente accodandoli. Da notare che se viene definito un header uguale ad uno definito di default dal FB, il campo di default è sostituito da quello definito.
Ecco alcuni esempi, definendo il campo Accept: seguito dalla definizione application/json il campo di default Accept: text/plain, text/html non verrà più inviato.
HTTPRq.Header:=ADR('Accept: application/json$r$n'); HTTPRq.Header:=ADR('Accept: application/json$r$nAccept-Charset: ascii$r$nConnection: Close$r$n');
Elenco headers
Accept-Language: it-IT | Indica al server quali lingue sono accettate. In questo caso italiano. |
Accept: text/plain, text/html | Indica al server che è accettato testo. |
Accept-Charset: ascii | Indica al server che sono accettati tutti i caratteri ascii. |
Accept-Encoding: *;q=0 | Indica al server il tipo di codifica accettata. |
Connection: Close Connection: Keep-alive | Indica al server che terminata la richiesta deve chiudere la connessione. Indica al server di mantenere attiva la connessione. |
Accept: application/json | Indica al server che sono accettate risposte in JSON |
Autenticazione
Molti servizi REST API richiedono l’autenticazione, esistono diversi tipi di autenticazione i più utilizzati sono il Basic e Digest utilizzati per le autenticazioni basate su credenziali di accesso (Nome utete e password) ed il Bearer utilizzato per autenticazione con token. L’autenticazione deve essere passata nell’header di accesso all’API, i valori di autenticazione sono passati codificati in Base64, esistono molti codificatori on-line che provvedono alla codifica/decodifica.
Particolare attenzione và posta nella codifica delle credenziali definendo username e password separate da “:”. Quindi ad esempio per l’utente Root con password MyPassword codificando la stringa Root:MyPassword, avremo: HTTPRq.Header:=ADR(‘Authorization: Basic Um9vdDpNeVBhc3N3b3Jk$r$n’);
Da notare che dopo la definizione della stringa Base64 ho inserito il <CR><LF> ($r$n) di terminazione campo.
Esempi
ST_HTTPClient: Ogni 5 secondi viene eseguito uno script php sul cloud che esegue la divisione tra i valori passati in GET alla richiesta, sono passati i valori di Dividend e Divisor. E’ possibile testare il funzionamento dello script a questo indirizzo. Se lo script è attivo viene ritornata una pagina con: The result is: 50, nelle variabili Header e Page è possibile visualizzare in debug i valori ritornati dal server.
ST_HTTPClient_ToFile: Attivando da debug la variabile Execute viene eseguito uno script php sul cloud che ritorna un file di testo da 4Kb (4110 bytes). E’ possibile testare il funzionamento dello script a questo indirizzo. Se lo script è attivo vengono creati 2 files uno che contiene l’header e l’altro la pagina con i valori ricevuti dal server.
LogicLab (Ptp156, ST_HTTPClient)
PROGRAM ST_HTTPClient
VAR
CaseNr : USINT; (* Program case *)
Errors : UDINT; (* Execution errors *)
TimeBf : UDINT; (* Time buffer (mS) *)
Page : STRING[ 64 ]; (* Page string *)
Header : STRING[ 256 ]; (* Header string *)
TCPClient : SysTCPClient; (* TCP client management *)
HTTPRq : HTTPClient_v5; (* HTTP client *)
END_VAR
// *****************************************************************************
// PROGRAM "ST_HTTPClient"
// *****************************************************************************
// The program connects to a server om which is running a PHP script like:
//
//
//
// The program sends the "Dividend" and "Divisor" values and receives back from
// the server the result. If the server answers correctly the following result
// is expected.
//
// Header: 'HTTP/1.1 200 OK...
// Page: 'The result is: 50'
// -----------------------------------------------------------------------------
// -------------------------------------------------------------------------
// INITIALIZATIONS
// -------------------------------------------------------------------------
// Program initializations.
IF (SysFirstLoop) THEN
// Set TCPClient parameters.
TCPClient.PeerAdd:=ADR('demos.elsist.biz'); //Peer address
TCPClient.PeerPort:=80; //Peer port
TCPClient.LocalAdd:=ADR('0.0.0.0'); //Local address
TCPClient.LocalPort:=0; //Local port
TCPClient.FlushTm:=50; //Flush time (mS)
TCPClient.LifeTm:=20; //Life time (S)
TCPClient.RxSize:=512; //Rx buffer size
TCPClient.TxSize:=512; //Tx buffer size
// Set HTTPClient parameters.
HTTPRq.SpyOn:=TRUE; //Activate the spy
HTTPRq.KeepAlive:=FALSE; //HTTP keep-alive
HTTPRq.RMethod:=HTTP_REQUEST#HTTP_GET; //HTTP request method
HTTPRq.HostName:=TCPClient.PeerAdd; // Hostname
HTTPRq.Page:=ADR('eLLabHTTPLib/HTTPClient/ST_HTTPClient.php'); //Web page
HTTPRq.Request:=ADR('Dividend=500$26Divisor=10'); //Request string
HTTPRq.Header:=ADR('Accept: application/json$r$n'); //HTTP header
HTTPRq.DBSize:=512; //Data buffer size
HTTPRq.Timeout:=T#10s; //Execution timeout
// Set local variables.
TimeBf:=SysTimeGetMs(); //Time buffer (mS)
END_IF;
// -------------------------------------------------------------------------
// MANAGE CONNECTION
// -------------------------------------------------------------------------
// Manage the connection.
TCPClient(Connect:=HTTPRq.Connect); //TCPClient management
HTTPRq(Enable:=TRUE, File:=TCPClient.File); //HTTP client
IF (HTTPRq.Fault) THEN CaseNr:=0; END_IF;
// -------------------------------------------------------------------------
// PROGRAM SEQUENCIES
// -------------------------------------------------------------------------
// Program sequencies.
CASE (CaseNr) OF
// ---------------------------------------------------------------------
// Initialize the time to manage acquisition delay.
0:
HTTPRq.Send:=FALSE; //Send request
TimeBf:=SysTimeGetMs(); //Time buffer (mS)
CaseNr:=CaseNr+1; //Program case
// ---------------------------------------------------------------------
// Wait delay time then initialize buffers and enable client.
1:
IF ((SysTimeGetMs()-TimeBf) < TO_UDINT(T#5s)) THEN RETURN; END_IF;
eTO_JUNK(Sysmemset(ADR(Header), 0, SIZEOF(Header))); //Empty header string
eTO_JUNK(Sysmemset(ADR(Page), 0, SIZEOF(Page))); //Empty page string
HTTPRq.Send:=TRUE; //Send request
CaseNr:=CaseNr+1; //Program case
// ---------------------------------------------------------------------
// Header/Page data are received from server by chunked. When a chunk
// has been received DBChars returns its length.
2:
IF (HTTPRq.DBChars <> 0) THEN
// Transfer the received chunk on the proper buffer.
IF NOT(HTTPRq.HPSelector) THEN
IF ((Sysstrlen(ADR(Header))+HTTPRq.DBChars) < SIZEOF(Header)) THEN
eTO_JUNK(Sysmemmove(TO_DWORD(ADR(Header))+Sysstrlen(ADR(Header)), HTTPRq.DBAddress, HTTPRq.DBChars));
END_IF;
ELSE
IF ((Sysstrlen(ADR(Page))+HTTPRq.DBChars) < SIZEOF(Page)) THEN
eTO_JUNK(Sysmemmove(TO_DWORD(ADR(Page))+Sysstrlen(ADR(Page)), HTTPRq.DBAddress, HTTPRq.DBChars));
END_IF;
END_IF;
END_IF;
// On Done it's possible to test if page has been loaded.
IF (HTTPRq.Done) THEN
IF NOT(HTTPRq.PLoad) THEN Errors:=Errors+1; END_IF;
CaseNr:=0; //Program case
END_IF;
END_CASE;
// [End of file]
LogicLab (Ptp156, ST_HTTPClient_ToFile)
PROGRAM ST_HTTPClient_ToFile
VAR
Execute : BOOL; (* Execute command *)
CaseNr : USINT; (* Program case *)
Fp : eFILEP; (* File pointer *)
Error : USINT; (* Execution error *)
HFile : STRING[ 32 ]; (* Header storage file definition *)
PFile : STRING[ 32 ]; (* Page storage file definition *)
TCPClient : SysTCPClient; (* TCP client management *)
HTTPRq : HTTPClient_v5; (* HTTP client *)
END_VAR
// *****************************************************************************
// PROGRAM "ST_HTTPClient_ToFile"
// *****************************************************************************
// The program connects to a server om which is running a PHP script that
// returns 4Kb of text.
// THe header and the page returned are stored into a files.
// -----------------------------------------------------------------------------
// -------------------------------------------------------------------------
// INITIALIZATIONS
// -------------------------------------------------------------------------
// Program initializations.
IF (SysFirstLoop) THEN
// Set TCPClient parameters.
TCPClient.PeerAdd:=ADR('demos.elsist.biz'); //Peer address
TCPClient.PeerPort:=80; //Peer port
TCPClient.LocalAdd:=ADR('0.0.0.0'); //Local address
TCPClient.LocalPort:=0; //Local port
TCPClient.FlushTm:=50; //Flush time (mS)
TCPClient.LifeTm:=20; //Life time (S)
TCPClient.RxSize:=512; //Rx buffer size
TCPClient.TxSize:=512; //Tx buffer size
// Set HTTPClient parameters.
HTTPRq.SpyOn:=TRUE; //Activate the spy
HTTPRq.KeepAlive:=FALSE; //HTTP keep-alive
HTTPRq.RMethod:=HTTP_REQUEST#HTTP_GET; //HTTP request method
HTTPRq.HostName:=TCPClient.PeerAdd; // Hostname
HTTPRq.Page:=ADR('eLLabHTTPLib/HTTPClient/ST_HTTPClient_ToFile.php'); //Web page
HTTPRq.Request:=eNULL; //Request string
HTTPRq.Header:=eNULL; //HTTP header
HTTPRq.DBSize:=512; //Data buffer size
HTTPRq.Timeout:=T#10s; //Execution timeout
// Set local variables.
// Store data file definition, change according your requirements.
HFile:='D:/Header.txt'; //Header file definition
PFile:='D:/Page.txt'; //Page file definition
END_IF;
// -------------------------------------------------------------------------
// MANAGE CONNECTION
// -------------------------------------------------------------------------
// Manage the connection.
TCPClient(Connect:=HTTPRq.Connect); //TCPClient management
HTTPRq(Enable:=TRUE, File:=TCPClient.File); //HTTP client
// -------------------------------------------------------------------------
// PROGRAM SEQUENCIES
// -------------------------------------------------------------------------
// Program sequencies.
CASE (CaseNr) OF
// ---------------------------------------------------------------------
// Wait for the command.
0:
HTTPRq.Send:=FALSE; //Send request
IF NOT(Execute) THEN RETURN; END_IF;
Execute:=FALSE; //Execute command
Error:=0; //Execution error
// Initialize buffers and enable client.
eTO_JUNK(SysFileRemove(ADR(HFile))); //Delete the header storage file
eTO_JUNK(SysFileRemove(ADR(PFile))); //Delete the pge storage file
HTTPRq.Enable:=TRUE; //HTTP enable
HTTPRq.Send:=TRUE; //Send request
CaseNr:=CaseNr+1; //Program case
// ---------------------------------------------------------------------
// Header/Page data are received from server by chunked. When a chunk
// has been received DBChars returns its length.
1:
IF (HTTPRq.DBChars <> 0) THEN
// Transfer the received chunk on the proper file.
IF NOT(HTTPRq.HPSelector) THEN
Fp:=SysFfopen(ADR(HFile), ADR('a')); //File pointer
ELSE
Fp:=SysFfopen(ADR(PFile), ADR('a')); //File pointer
END_IF;
// Open the file in append mode, if it doesn't exist i'll be created.
IF (Fp = eNULL) THEN Error:=10; CaseNr:=0; RETURN; END_IF;
// Transfer received data to file.
IF (Sysfwrite(HTTPRq.DBAddress, TO_INT(HTTPRq.DBChars), 1, Fp) <> TO_INT(HTTPRq.DBChars)) THEN
Fp:=FClose(Fp); //Close the file
Error:=20; //Execution error
CaseNr:=0; //Program case
RETURN;
END_IF;
Fp:=FClose(Fp); //Close the file
END_IF;
// Check if request done.
// On Done it's possible to test if page has been loaded.
IF (HTTPRq.Done) THEN
IF NOT(HTTPRq.PLoad) THEN Error:=30; END_IF;
CaseNr:=0; //Program case
END_IF;
END_CASE;
// [End of file]