Zde můžete vidět rozdíly mezi vybranou verzí a aktuální verzí dané stránky.
Obě strany předchozí revize Předchozí verze Následující verze | Předchozí verze | ||
2014:mbed-http [2015/01/19 04:05] Maximilián Tydor [ROZBOR] |
2014:mbed-http [2015/01/19 06:02] (aktuální) Maximilián Tydor [vykreslení webové stránky] |
||
---|---|---|---|
Řádek 25: | Řádek 25: | ||
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ě. | 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. | ||
+ | |||
+ | <code c> | ||
+ | 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(); | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | ==== 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. | ||
+ | |||
+ | <code c> | ||
+ | } //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 | ||
+ | </code> | ||
+ | |||
+ | ==== 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. | ||
+ | |||
+ | <code c> | ||
+ | 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)); | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | a zde výsledek, který už umí přechroustat snad každý prohlížeč: | ||
+ | |||
+ | <code html> | ||
+ | <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> | ||
+ | </code> | ||
+ | |||
+ | 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 ==== | ||
+ | |||
+ | <code c> | ||
+ | #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(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | </code> | ||
+ | |||
+ | ------- |