====== HTTP server v prostředí mbed ====== [[xtydor00@stud.feec.vutbr.cz|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://developer.mbed.org/teams/FRDM-K64F-Code-Share/code/HTTP_SD_Server_K64F/|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 [[http://css-tricks.com/examples/ShapesOfCSS/|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í: - Importovat dříve zmíněný projekt a **neaktualizovat** žádnou knihovnu! (jinak se aplikace kompletně rozpadne) - Odebrat obsluhu SD karty (nebylo už možné tímto strácet čas) - Implementovat HTTP autorizaci - Implementovat POST metodu - Nakonfigurovat periferie - 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. {{ 2014:mbed-http:auth.png |Přihlašovací dialog}} Pokud uživatel dialog zruší, daleko se nedostane... {{ 2014:mbed-http:unauthorized.png |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, "\r\n\t\r\n\t\tError\r\n\t\r\n\t\r\n\t\t

401 Unauthorised.

\r\n\t\r\n"); 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,"\r\n\t\r\n\t\tK64F HTTP RGB controller\r\n\t\t\r\n\t\r\n\t\r\n\t\t

K64F RGB on-line controller

"); client.send(httpHeader,strlen(httpHeader)); sprintf(httpHeader,"\r\n\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t"); client.send(httpHeader,strlen(httpHeader)); sprintf(httpHeader,"\r\n\t\t\t\t",r_col, g_col, b_col); client.send(httpHeader,strlen(httpHeader)); sprintf(httpHeader,"\r\n\t\t\t\t",s2_col); client.send(httpHeader,strlen(httpHeader)); sprintf(httpHeader,"\r\n\t\t\t\t",s3_col); client.send(httpHeader,strlen(httpHeader)); sprintf(httpHeader,"\r\n\t\t\t\r\n\t\t
RGB controls & statusSW2 statusSW3 status
"); client.send(httpHeader,strlen(httpHeader)); sprintf(httpHeader,"\r\n\t\t\t\t\t
"); else strcat(httpHeader, ">
"); client.send(httpHeader,strlen(httpHeader)); sprintf(httpHeader,"\r\n\t\t\t\t\t
"); else strcat(httpHeader, ">
"); client.send(httpHeader,strlen(httpHeader)); sprintf(httpHeader,"\r\n\t\t\t\t\t
"); else strcat(httpHeader, ">
"); client.send(httpHeader,strlen(httpHeader)); sprintf(httpHeader,"\r\n\t\t\t\t\t\r\n\t\t\t\t
\r\n\t\r\n"); client.send(httpHeader,strlen(httpHeader)); }
a zde výsledek, který už umí přechroustat snad každý prohlížeč: K64F HTTP RGB controller

K64F RGB on-line controller

RGB controls & status SW2 status SW3 status



a zde ještě výsledek, který je mezi lidmi oblíbenější: {{ 2014:mbed-http:webpage.png |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 [[http://www.quackit.com/css/css_color_codes.cfm|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 #include #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,"\r\n\t\r\n\t\tK64F HTTP RGB controller\r\n\t\t\r\n\t\r\n\t\r\n\t\t

K64F RGB on-line controller

"); client.send(httpHeader,strlen(httpHeader)); sprintf(httpHeader,"\r\n\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t"); client.send(httpHeader,strlen(httpHeader)); sprintf(httpHeader,"\r\n\t\t\t\t",r_col, g_col, b_col); client.send(httpHeader,strlen(httpHeader)); sprintf(httpHeader,"\r\n\t\t\t\t",s2_col); client.send(httpHeader,strlen(httpHeader)); sprintf(httpHeader,"\r\n\t\t\t\t",s3_col); client.send(httpHeader,strlen(httpHeader)); sprintf(httpHeader,"\r\n\t\t\t\r\n\t\t
RGB controls & statusSW2 statusSW3 status
"); client.send(httpHeader,strlen(httpHeader)); sprintf(httpHeader,"\r\n\t\t\t\t\t
"); else strcat(httpHeader, ">
"); client.send(httpHeader,strlen(httpHeader)); sprintf(httpHeader,"\r\n\t\t\t\t\t
"); else strcat(httpHeader, ">
"); client.send(httpHeader,strlen(httpHeader)); sprintf(httpHeader,"\r\n\t\t\t\t\t
"); else strcat(httpHeader, ">
"); client.send(httpHeader,strlen(httpHeader)); sprintf(httpHeader,"\r\n\t\t\t\t\t\r\n\t\t\t\t
\r\n\t\r\n"); 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, "\r\n\t\r\n\t\tError\r\n\t\r\n\t\r\n\t\t

401 Unauthorised.

\r\n\t\r\n"); 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(); } }
-------