Individální projekty MPOA

Mikroprocesory s architekturou ARM

Uživatelské nástroje

Nástroje pro tento web


2014:mbed-http

HTTP server v prostředí mbed

Maximilián Tydor, 18.1.2015, 23:59

ZADÁNÍ

Realizujte jednoduchý HTTP server na FRDM-K64F, implementovaný v prostředí mbed. Statické stránky uložte do Flash paměti mikrokontroléru (uvítací stránka, obrázek…), pomocí dynamické stránky umožněte několika HTML prvky nastavovat RGB LED a číst stav obou tlačítek.


ROZBOR

Projekt jako celek se skládá z těchto dílčích částí:

  • Ethernetového rozhraní (TCP socket-u)
  • jednoduchého HTTP serveru
  • HTTP autorizace
  • řídící webové stránky
  • obsluhy periferií (RGB LED, tlačítka)

Ethernet a HTTP server

Jako základ byl použit projekt HTTP_SD_Server_K64F, který již má vyřešenou inicializaci síťového rozhraní včetně vyžádání IP adresy od DHCP serveru a taky virtuálního sériového portu přes programovací USB konektor, vhodného k odlaďování aplikace. Projekt by měl být schopný pracovat i s připojenou SD kartou, ale tuto funkci se mi nepovedlo plnohodnotně rozchodit. Obsah karty se sice zobrazil, ale při pokusu o převzetí jakéhokoliv souboru bylo jedinou odezvou chbové hlášení „čas spojení vypršel!“

Vzhledem k tomu, že pro potřeby této aplikace postačuje jedna jediná obslužná webová stránka, byla podpora SD karty odebrána a celá stránka je tak uložena přímo v paměti procesoru (pro grafické spříjemnění stránky postačuje formátování kaskádních stylů).

Převzatý kód již obsahoval základní konfiguraci HTTP serveru jako otevření portu 80, základní chybové hlášení apod. Mimo to i podporu metody GET pro odesílání dat. Vzhledem k tomu, že stránka vyžaduje i několik ovládacích prvků, byla doplněna metoda POST, zejména z estetického důvodu, či pro potřeby budoucího rozšiřování.

HTTP autorizace

Protože se jedná o aplikaci, kdy je možné vzdáleně měnit stav fyzického zařízení, ať už je to pouhá LED nebo vysoká pec ve slévárně, je vhodné omezit, nebo alespoň zkomplikovat, přístup neoprávněným osobám či strojům. Implementace moderních autorizačních systémů jako je například SSL by byla výpočetně příliš náročná pro tuto aplikaci, nicméně na odrazení většiny lajických uživatelů i v současnosti bohatě postačuje využití HTTP autorizace. Jedná se o velice jednoduchý systém. Pokud server vyžaduje autorizaci odešle klientovi odpověď „401 Authorization Required!“ a prohlížeč na straně uživatele se postará o zobrazení přihlašovacího dialogového okna. Po zadání, obvykle jména a hesla uživatele, jsou tyto data doplněna do HTTP hlavičky buď jako obyčejný text nebo v zahashované podobě.

Webová stránka a periferie

Jak již bylo zmíněno dříve, obslužná stránka je pouze jedna a je kompletně uložena v paměti procesoru. Absenci obrázků je možné z velké částí nahradit kaskádními styly.

V zadání nejsou vyžadovány speciální periferie, stačí využití RGB LED a 2 uživatelských tlačítek, které na desce již jsou a jejich obsluha je elementární záležitost.


REALIZACE

Postup prací:

  1. Importovat dříve zmíněný projekt a neaktualizovat žádnou knihovnu! (jinak se aplikace kompletně rozpadne)
  2. Odebrat obsluhu SD karty (nebylo už možné tímto strácet čas)
  3. Implementovat HTTP autorizaci
  4. Implementovat POST metodu
  5. Nakonfigurovat periferie
  6. Vytovřit webovou stránku a obslužné metody

HTTP Autorizace

Vytvořený skript hledá v HTTP hlavičce odeslané klientem tento řádek: „Authorization: Basic TVBPQToyMDE0“, co je zahashovaná kombinace uživatelského jména „MPOA“ a hesla „2014“. Pokud tuto kombinaci nenajde, odešle klientovi zprávu „401 Authorization Required“. Prohlížeč pak vyzve uživatele k zadání přihlašovacích údajů jednoduchým formulářem.

Přihlašovací dialog

Pokud uživatel dialog zruší, daleko se nedostane…

Chybová stránka v případě zrušení přihlašovacího dialogu

Pokouší-li se užívatel odklepnout formulář prázdný nebo s nesprávnými údaji vyskakuje okno pořád dokola.

if(!strstr(buffer, "Authorization: Basic TVBPQToyMDE0")){        //MPOA 2014
  sprintf(httpHeader,"HTTP/1.1 401 Authorization Required \r\nContent-Type: text\r\nWWW-Authenticate: Basic realm='Login required!'\r\n\r\n");
  client.send(httpHeader,strlen(httpHeader));                    //Odeslání výzvy k přihlášení
  sprintf(httpHeader, "<HTML>\r\n\t<HEAD>\r\n\t\t<TITLE>Error</TITLE>\r\n\t</HEAD>\r\n\t<BODY>\r\n\t\t<H1>401 Unauthorised.</H1>\r\n\t</BODY>\r\n</HTML>");
  client.send(httpHeader,strlen(httpHeader));                    //Formát stránky s chybovým hlášením
  client.close();
}

metoda POST

Přesto že předat těch několik parametrů není problém skrze URL, tedy metodou GET, ale výsledek není zrovna lahodivý oku, proto byla implementována podpora metody POST. Obsluha je ve výsledku velice jednoduchá (ale ladění zabralo jeden celý den). Data se posílají dvěma pakety (i když i přes WireShark to lze jen těžko poznat). První obsahuje identifikační hlavičku s identifikátorem metody, prohlížeče klienta, autorizačních ůdajů apod. V druhém paketu se nacházejí samotná data a je nutné jej příjmout až v samotném průběhu zpracování metody POST, jinak by do toho kecal autorizační algoritmus a ani selektor metod by si s tím nevěděl rady.

}                                                        //konec metody GET
else if (!strncmp(buffer, "POST ", 5)) {                 //ověření, že se jedná o POST data
  int n = client.receive(buffer, sizeof(buffer));        //příjem dalšího paketu se samotnými daty  
  ...                                                    //následuje zpracování přijatých dat

vykreslení webové stránky

Dekódování přijatých dat je věc hraní si s řetězci a následnými převody, ale toho je internet plný a o radu v případě problému není nouze. Zde tedy pár řádku k nahlédnutí jak vypadá generátor webové stránky uvnitř procesoru.

void display_page(void){
  sprintf(httpHeader,"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: Close\r\n\r\n");
  client.send(httpHeader,strlen(httpHeader));
  sprintf(httpHeader,"<html>\r\n\t<head>\r\n\t\t<title>K64F HTTP RGB controller</title>\r\n\t\t<meta http-equiv='refresh' content='5'>\r\n\t</head>\r\n\t<body>\r\n\t\t<h1>K64F RGB on-line controller</h1>");
  client.send(httpHeader,strlen(httpHeader));
  sprintf(httpHeader,"\r\n\t\t<table cellspacing='5px'>\r\n\t\t\t<tr>\r\n\t\t\t\t<th colspan='2'>RGB controls & status</th>\r\n\t\t\t\t<th>SW2 status</th>\r\n\t\t\t\t<th>SW3 status</th>\r\n\t\t\t</tr>\r\n\t\t\t<tr>\r\n\t\t\t\t<td><form action='' method='post'>");
  client.send(httpHeader,strlen(httpHeader));
  sprintf(httpHeader,"\r\n\t\t\t\t\t<label for='R'>R:\t</label><input type='checkbox' name='R' id='R'");
  if(r_on)
    strcat(httpHeader, " checked='checked'><br>");
  else
    strcat(httpHeader, "><br>");
  client.send(httpHeader,strlen(httpHeader));
  sprintf(httpHeader,"\r\n\t\t\t\t\t<label for='G'>G:\t</label><input type='checkbox' name='G' id='G'");
  if(g_on)
    strcat(httpHeader, " checked='checked'><br>");
  else
    strcat(httpHeader, "><br>");
  client.send(httpHeader,strlen(httpHeader));
  sprintf(httpHeader,"\r\n\t\t\t\t\t<label for='B'>B:\t</label><input type='checkbox' name='B' id='B'");
  if(b_on)
    strcat(httpHeader, " checked='checked'><br>");
  else
    strcat(httpHeader, "><br>");
  client.send(httpHeader,strlen(httpHeader));
  sprintf(httpHeader,"\r\n\t\t\t\t\t<input type='submit' value='Save'>\r\n\t\t\t\t</form></td>");
  client.send(httpHeader,strlen(httpHeader));       
  sprintf(httpHeader,"\r\n\t\t\t\t<td style='vertical-align: bottom;'><div style='background: #%02x%02x%02x; width: 80px; height: 95%; border: 2px solid;'></div></td>",r_col, g_col, b_col);
  client.send(httpHeader,strlen(httpHeader));
  sprintf(httpHeader,"\r\n\t\t\t\t<td style='vertical-align: bottom;'><div style='background: black; width: 84px; height: %dpx;'></div></td>",s2_col);
  client.send(httpHeader,strlen(httpHeader));
  sprintf(httpHeader,"\r\n\t\t\t\t<td style='vertical-align: bottom;'><div style='background: black; width: 84px; height: %dpx;'></div></td>",s3_col);
  client.send(httpHeader,strlen(httpHeader));
  sprintf(httpHeader,"\r\n\t\t\t</tr>\r\n\t\t</table>\r\n\t</body>\r\n</html>");
  client.send(httpHeader,strlen(httpHeader));
}

a zde výsledek, který už umí přechroustat snad každý prohlížeč:

<html>
	<head>
		<title>K64F HTTP RGB controller</title>
		<meta http-equiv='refresh' content='5'>
	</head>
	<body>
		<h1>K64F RGB on-line controller</h1>
		<table cellspacing='5px'>
			<tr>
				<th colspan='2'>RGB controls & status</th>
				<th>SW2 status</th>
				<th>SW3 status</th>
			</tr>
			<tr>
				<td><form action='' method='post'>
					<label for='R'>R:	</label><input type='checkbox' name='R' id='R' checked='checked'><br>
					<label for='G'>G:	</label><input type='checkbox' name='G' id='G' checked='checked'><br>
					<label for='B'>B:	</label><input type='checkbox' name='B' id='B'><br>
					<input type='submit' value='Save'>
				</form></td>
				<td style='vertical-align: bottom;'><div style='background: #ffff00; width: 80px; height: 95; border: 2px solid;'></div></td>
				<td style='vertical-align: bottom;'><div style='background: black; width: 84px; height: 30px;'></div></td>
				<td style='vertical-align: bottom;'><div style='background: black; width: 84px; height: 70px;'></div></td>
			</tr>
		</table>
	</body>
</html>

a zde ještě výsledek, který je mezi lidmi oblíbenější:

Obrázek výsledné webové stránky

Přes tuto stránku je možné zapínat a vypínat jednotlivé barvy RGB LED a hned vidět i výsledek bez nutnosti být zrovna u desky. Výsledná barevná kombinace je spočítaná ze všech tří složek a kdyby se aplikace poupravila tak, že samostatné vlákno by simulovalo PWM modulaci, pořád by přibližně odpovídala barva na webové stránce barvě na LED. Je to možné velice snadno ověřit na paletě webových barev. Na stránce se dále zobrazují dva jakoby bargrafy simulující klasické tlačítka (v horní poloze neaktivní). Pro větší pohodlí a uživatelský komfort se stránka sama obnovuje každých 5 sekund.


PŘÍLOHY

Zdrojový kód main.cpp

#include "mbed.h"
#include "EthernetInterface.h"
#include "SDFileSystem.h"
#include <stdio.h>
#include <string.h>
 
#define HTTPD_SERVER_PORT   80
#define HTTPD_MAX_REQ_LENGTH   1023
#define HTTPD_MAX_HDR_LENGTH   255
 
Serial uart(USBTX, USBRX);
 
EthernetInterface eth;
TCPSocketServer server;
TCPSocketConnection client;
 
char buffer[HTTPD_MAX_REQ_LENGTH+1];
char httpHeader[HTTPD_MAX_HDR_LENGTH+1];
 
char *uristr;
char *eou;
char *qrystr;
 
int r_on=0, g_on=0, b_on=0;
int r_col=0, g_col=0, b_col=0, s2_col=80, s3_col=80;
 
void display_page(void){
 
    sprintf(httpHeader,"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: Close\r\n\r\n");
    client.send(httpHeader,strlen(httpHeader));
    sprintf(httpHeader,"<html>\r\n\t<head>\r\n\t\t<title>K64F HTTP RGB controller</title>\r\n\t\t<meta http-equiv='refresh' content='5'>\r\n\t</head>\r\n\t<body>\r\n\t\t<h1>K64F RGB on-line controller</h1>");
    client.send(httpHeader,strlen(httpHeader));
    sprintf(httpHeader,"\r\n\t\t<table cellspacing='5px'>\r\n\t\t\t<tr>\r\n\t\t\t\t<th colspan='2'>RGB controls & status</th>\r\n\t\t\t\t<th>SW2 status</th>\r\n\t\t\t\t<th>SW3 status</th>\r\n\t\t\t</tr>\r\n\t\t\t<tr>\r\n\t\t\t\t<td><form action='' method='post'>");
    client.send(httpHeader,strlen(httpHeader));
    sprintf(httpHeader,"\r\n\t\t\t\t\t<label for='R'>R:\t</label><input type='checkbox' name='R' id='R'");
    if(r_on)
        strcat(httpHeader, " checked='checked'><br>");
    else
        strcat(httpHeader, "><br>");
    client.send(httpHeader,strlen(httpHeader));
    sprintf(httpHeader,"\r\n\t\t\t\t\t<label for='G'>G:\t</label><input type='checkbox' name='G' id='G'");
    if(g_on)
        strcat(httpHeader, " checked='checked'><br>");
    else
        strcat(httpHeader, "><br>");
    client.send(httpHeader,strlen(httpHeader));
    sprintf(httpHeader,"\r\n\t\t\t\t\t<label for='B'>B:\t</label><input type='checkbox' name='B' id='B'");
    if(b_on)
        strcat(httpHeader, " checked='checked'><br>");
    else
        strcat(httpHeader, "><br>");
    client.send(httpHeader,strlen(httpHeader));
    sprintf(httpHeader,"\r\n\t\t\t\t\t<input type='submit' value='Save'>\r\n\t\t\t\t</form></td>");
    client.send(httpHeader,strlen(httpHeader));       
    sprintf(httpHeader,"\r\n\t\t\t\t<td style='vertical-align: bottom;'><div style='background: #%02x%02x%02x; width: 80px; height: 95%; border: 2px solid;'></div></td>",r_col, g_col, b_col);
    client.send(httpHeader,strlen(httpHeader));
    sprintf(httpHeader,"\r\n\t\t\t\t<td style='vertical-align: bottom;'><div style='background: black; width: 84px; height: %dpx;'></div></td>",s2_col);
    client.send(httpHeader,strlen(httpHeader));
    sprintf(httpHeader,"\r\n\t\t\t\t<td style='vertical-align: bottom;'><div style='background: black; width: 84px; height: %dpx;'></div></td>",s3_col);
    client.send(httpHeader,strlen(httpHeader));
    sprintf(httpHeader,"\r\n\t\t\t</tr>\r\n\t\t</table>\r\n\t</body>\r\n</html>");
    client.send(httpHeader,strlen(httpHeader));
 
}
 
int main (void)
{
//    RGB LED outputs
    DigitalOut  rled(LED_RED, 1);
    DigitalOut  gled(LED_GREEN, 1);
    DigitalOut  bled(LED_BLUE, 1);
 
//    SW2 & SW3 inputs
    DigitalIn   sw2(PTC6);
    DigitalIn   sw3(PTA4);
 
//    Serial Interface eth;
    uart.baud(115200);
    uart.printf("Initializing\n");
 
//    EthernetInterface eth;
    uart.printf("Initializing Ethernet\n");
    eth.init(); //Use DHCP
    uart.printf("Connecting\n");
    eth.connect();
    uart.printf("IP Address is %s\n", eth.getIPAddress());
 
//    TCPSocketServer server;
    server.bind(HTTPD_SERVER_PORT);
    server.listen();
    uart.printf("Server Listening\n");
 
    while (true) {
        uart.printf("\nWaiting for new connection...\r\n");
        server.accept(client);
        client.set_blocking(false, 1500); // Timeout after (1.5)s
 
        if(r_on){           //zadost o rozsviceni cervene LED
            rled=0;         //led aktivni logickou 0
        }
        else{
            rled=1;
        }
 
        if(g_on){           //zadost o rozsviceni zelene LED
            gled=0;         //led aktivni logickou 0
        }
        else{
            gled=1;
        }
 
        if(b_on){           //zadost o rozsviceni modre LED
            bled=0;         //led aktivni logickou 0
        }
        else{
            bled=1;
        }
 
        if(sw2){            //nacteni stavu tlacitka SW2
            s2_col = 70;    //pokud neni tlacitko zmacknute zobrazi se vysoky sloupecek
        }
        else{
            s2_col = 30;    //pokud je tlacitko zmacknute zobrazi se nizky sloupecek
        }
        if(sw3){
            s3_col = 70;
        }
        else{  
            s3_col = 30;  
        }
 
 
        uart.printf("Connection from: %s\r\n", client.get_address());
        while (true) {
 
            int n = client.receive(buffer, sizeof(buffer));
            if (n <= 0) break;
            uart.printf("Recieved Data: %d\r\n\r\n%.*s\r\n",n,n,buffer);
            if (n >= 1024) {
                sprintf(httpHeader,"HTTP/1.1 413 Request Entity Too Large \r\nContent-Type: text\r\nConnection: Close\r\n\r\n");
                client.send(httpHeader,strlen(httpHeader));
                client.send(buffer,n);
                break;
            } else {
                buffer[n]=0;
            }
 
            if(!strstr(buffer, "Authorization: Basic TVBPQToyMDE0")){        //MPOA 2014
                sprintf(httpHeader,"HTTP/1.1 401 Authorization Required \r\nContent-Type: text\r\nWWW-Authenticate: Basic realm='Login required!'\r\n\r\n");
                client.send(httpHeader,strlen(httpHeader));
                sprintf(httpHeader, "<HTML>\r\n\t<HEAD>\r\n\t\t<TITLE>Error</TITLE>\r\n\t</HEAD>\r\n\t<BODY>\r\n\t\t<H1>401 Unauthorised.</H1>\r\n\t</BODY>\r\n</HTML>");
                client.send(httpHeader,strlen(httpHeader));
                client.close();
            }
 
                if (!strncmp(buffer, "GET ", 4)) {
                    uristr = buffer + 4;
                    eou = strstr(uristr, " ");
                    if (eou == NULL) {
                        sprintf(httpHeader,"HTTP/1.1 400 Bad Request \r\nContent-Type: text\r\nConnection: Close\r\n\r\n");
                        client.send(httpHeader,strlen(httpHeader));
                        client.send(buffer,n);
                    } 
                    else {
                        *eou = 0;                                                
                        display_page();
                        client.close();
                    }
                }                
                else if (!strncmp(buffer, "POST ", 5)) {
 
                    int n = client.receive(buffer, sizeof(buffer));
 
                    if (strstr(buffer, "R=on")) {
                        r_on = 1;
                        r_col = 255;
                    }
                    else{
                        r_on = 0;
                        r_col = 0;
                    }
                    if (strstr(buffer, "G=on")) {
                        g_on = 1;
                        g_col = 255;
                    }
                    else{
                        g_on = 0;
                        g_col = 0;
                    }
                    if (strstr(buffer, "B=on")) {
                        b_on = 1;
                        b_col = 255;
                    }
                    else{
                        b_on = 0;
                        b_col = 0;
                    }
 
                    display_page();
                    client.close();
                }           
            }
            client.close();   
    }
}

2014/mbed-http.txt · Poslední úprava: 2015/01/19 06:02 autor: Maximilián Tydor