STM32 网络通信Web Server中 SSI与CGI的应用解析
本次主要解析STM32網絡通信中WebServer應用,從網頁界面的編寫到瀏覽器與STM32之間進行通信的數據來說明SSI與CGI的原理及應用,并對GET與POST指令進行應用解析。
硬件和軟件環境:
1.硬件環境:STM32F407,網卡芯片LAN8720,其他部分參考正點原子的407探索者開發板。
2.軟件環境:keil5,LWIP1.4.1,主要是基于正點原子STM32F407探索者的第六十章網絡通信實驗程序。
一、程序流程解析
為了方便查看瀏覽器與STM32之間的數據通信,建議程序中使用固定IP的方式,如192.168.1.101,建議使用軟件Wireshark來查看網絡數據。
?
首先是打開Wireshark,選擇本地連接
?
?
? ?? ?然后在地址欄輸入 ip.addr == 192.168.1.101,然后按右邊的箭頭開始接收數據
?
?
這時在瀏覽器輸入IP地址(如192.168.1.101)就可以看到如下數據:
?
?
這時瀏覽器給STM32發出GET指令數據,而STM32通過函數http_recv()接收數據,并把接收到的數據進行解析、處理,然后把指令要求的數據發給瀏覽器,這時瀏覽器上面就會顯示相應的網頁界面。具體在函數http_recv()里面的執行流程如下:
http_recv()----->判斷收到的是有效數據后,調用函數http_parse_request()----->解析是GET指令還是POST指令(輸入IP后下發的是GET指令),如果是GET指令,則直接是調用函數http_find_file()----->判斷指令的內容是請求打開默認的根文件(如打開index.shtml或test.shtml)還是CGI程序指令(CGI指令主要就是在網頁界面上按下按鈕等下發下來的一系列相關操作指令,后面再對此解說)----->如指令為求打開默認的根文件,則打開存放在SPI FLASH芯片W25Q128或者SD卡中的SHTML文件,并獲取相應的數據----->然后通過函數http_init_file()對數據進行初始化,然后退出函數http_find_file(),再退出函數http_parse_request()----->然后運行到函數http_send_data(),查找SSI的Tag,找到之后把相應的內容添加進入,然后把數據發回給瀏覽器----->瀏覽器顯示對應的網頁界面。
打開一個網頁的程序流程大概就是這樣。瀏覽器與STM32之間的網絡通信數據,簡單理解就是互相發送一串串字符串,而一幀數據字符串里面包含了某些固定的字符串(或字符,比如”GETHTTP/1.1”、”?”、”&”),這些字符有特定的含義,而我們需要在這些字符串中找出幾個特殊字符,然后根據含義進行解析處理。
二、SSI的原理及應用解析
首先來了解一下SSI的原理:將內容發送到瀏覽器之前,可以使用“服務器端包含 (SSI)”指令將文本、圖形或應用程序信息包含到網頁中。例如,可以使用 SSI 包含時間/日期戳、版權聲明或供客戶填寫并返回的表單。對于在多個文件中重復出現的文本或圖形,使用包含文件是一種簡便的方法。將內容存入一個包含文件中即可,而不必將內容輸入所有文件。通過一個非常簡單的語句即可調用包含文件,此語句指示 Web 服務器將內容插入適當網頁。而且,使用包含文件時,對內容的所有更改只需在一個地方就能完成。
因為包含 SSI 指令的文件要求特殊處理,所以必須為所有 SSI 文件賦予 SSI文件擴展名。默認擴展名是 .stm、.shtm 和 .shtml。
以上內容來自百度,簡單的理解就是在把網頁界面程序的SHTML文件發給瀏覽器之前,通過某幾個SSI的主要函數把SHTML里面的數據進行了替換,可以說是增加了某些程序進去。而替換的規則就是查找到<!--#XXX-->,這個XXX是可以自己定義的,比如我定義LWIP_HTTPD_MAX_TAG_NAME_LEN為3,那就是這<!--#XXX-->可以放的是3個字符,比如<!--#adc-->,然后找到這個<!--#adc-->后,我把某個程序加上去,比如加上”測試ADC”。這樣可能不好理解,還是直接上HTML程序,首先看一個簡單的SHTML程序:
<HTML>
<METAcontent="text/html; charset=gb2312" http-equiv=Content-Type>
<METAhttp-equiv="pragma" content="no-cache">
<SCRIPTlanguage=JavaScript><!--
functiondoLoad(){
}
//--></SCRIPT>
<BODYοnlοad=doLoad();>
<FORMMETHOD=POST ACTION="/test.cgi">
<TD>test<!--#adc--></TD>? ?? ?? ???
</FORM>
</BODY>
</HTML>
?
把這個程序復制到文本文檔里面,然后另存為一個index.shtml,然后用瀏覽器打開就可以看到網頁界面上顯示:test
?
?
這個是沒有經過SSI處理的,經過處理之后的SHTML程序為:
<HTML>
<METAcontent="text/html; charset=gb2312" http-equiv=Content-Type>
<METAhttp-equiv="pragma" content="no-cache">
<SCRIPTlanguage=JavaScript><!--
functiondoLoad(){
}
//--></SCRIPT>
<BODYοnlοad=doLoad();>
<FORMMETHOD=POST ACTION="/test.cgi">
<TD>test<!--#adc-->測試ADC</TD>? ?? ?? ???
</FORM>
</BODY>
</HTML>
?
用瀏覽器打開就可以看到網頁界面上顯示:test測試ADC
?
?
這樣應該就能夠理解了,找到指定的<!--#xxx-->字符串后,是在后面添加程序,至于把這個<!--#xxx-->放在哪里,就看怎么去編寫這個SHTML程序了。
SSI的原理其實不難理解,主要在httpdi_cgi_ss.c里面,實際使用到的有:
static constchar *ppcTAGs[]=??//SSI的Tag
{
? ?? ?"t", //ADC值
? ?? ?"w", //溫度值
? ?? ?"h", //時間
? ?? ?"y",??//日期
? ???"adc",//測試ADC
};
這個數組就是存放SSI的Tag數組,在打開SHTML文件時,會通過函數get_tag_insert()把SHTML里面的Tag給找出來并且添加指定的內容,而添加的內容是通過函數SSIHandler()來判斷,然后運行指定的函數去添加,而這個指定的函數,比如:
void ADC_Handler(char*pcInsert)
{
sprintf(pcInsert,”測試ADC”);
}
那么就可以直接達到在網頁界面上的”test”后面添加”測試ADC”這樣的效果。
但是,有個地方要注意了,如果添加的內容比較大,那么就要修改
#define??LWIP_HTTPD_MAX_TAG_INSERT_LEN 的大小,這個就根據自己的需求來更改了,如果需要增加的內容比較多,改成1024甚至更大都可以。如果比實際的內容小了,那會導致網頁界面無法正確顯示,嚴重的會引起STM32硬件錯誤中斷。
可能有些人對這個SHTML的數據(或文件)存放在哪里不是很理解,先說一下目前用的一個方法,就是做好成網頁文件之后,用makefsdata.exe來生成數組,然后存放在程序里,在程序里再調用。這個方法不建議用,一是不直觀,第二是修改起來太麻煩,第三是占用資源。不過有個方法和該方法類似,但方便很多,適用于HTML代碼比較少,界面比較簡單的網頁界面。比如:
voidhttp_write_testweb(char *pbuff, int *ppos)
{
? ?? ?*ppos += sprintf(pbuff + *ppos,"<HTML><META content=\"text/html; charset=gb2312\"http-equiv=Content-Type><META http-equiv=\"pragma\"content=\"no-cache\"> ");
? ?? ?*ppos += sprintf(pbuff + *ppos,"<SCRIPT language=JavaScript><!--functiondoLoad(){}//--></SCRIPT>");
? ?? ?*ppos += sprintf(pbuff + *ppos,"<BODY οnlοad=doLoad();>\r\n");
? ?? ?*ppos += sprintf(pbuff + *ppos,"<FORM METHOD=POST ACTION=\"/test.cgi\">");
? ?? ?*ppos += sprintf(pbuff + *ppos,"<TD>test<!--#adc--></TD>");
? ?? ?*ppos += sprintf(pbuff + *ppos,"</FORM></BODY></HTML>");
}
該函數直接把一個網頁界面的HTML代碼存入pbuff數組中,代碼的長度是ppos,通過調用這個函數,把pbuff數組的內容通過函數get_tag_insert()添加Tag的內容后再發給瀏覽器,在瀏覽器上面就能看到如下的界面:
?
?
另一種方法就是原子哥在第六十四章綜合實例用的,就是把SHTML文件存放在SPI FLASH 芯片W25Q128中,然后通過FATFS文件系統直接打開調用SHTML文件,目前定義的路徑是:#define??HTTP_SRC_PATH??"1:SYSTEM/LWIP/WebServer",不過在網頁界面調試階段,建議把路徑改成SD卡下,那樣不用每次修改SHTML文件后總是去把文件燒進W25Q128中,直接把修改好的文件放到SD卡下就可以。對于制作比較復雜的界面,建議用這種方法,很直觀,而且懂得編寫HTML的話,界面很快就能做出來。
三、CGI的原理及應用解析
CGI是外部應用程序(CGI程序)與WEB服務器之間的接口標準,是在CGI程序和Web服務器之間傳遞信息的過程。CGI規范允許Web服務器執行外部程序,并將它們的輸出發送給Web瀏覽器,CGI將Web的一組簡單的靜態超媒體文檔變成一個完整的新的交互式媒體。
說白了,STM32有了CGI處理程序之后就能和網頁產生互動,比如一個用戶登陸界面:
?
?
輸入正確的用戶名和密碼后,點擊<登陸>按鈕,這時瀏覽器就下發一個指令數據,該指令數據里面就包含了需要處理的用戶名和密碼數據(輸入框里面的數據,比如用戶名是admin),STM32接收到該指令數據之后,經過解析處理,然后把處理之后的數據發回瀏覽器,比如驗證輸入的用戶名和密碼為錯誤,則在瀏覽器上彈出一個串口提示“用戶名或密碼錯誤!”,如果輸入的用戶名和密碼為正確,則在瀏覽器上直接跳轉進入另外一個網頁界面。
為了比較方便理解,先看看這個登陸界面的HTML代碼:
<HTML><METAHTTP-EQUIV = "Pragma" CONTENT="no-cache">
<SCRIPTlanguage=JavaScript>
functiondoLoad(){
}
</SCRIPT>
<BODYοnlοad=doLoad();>
<FORM METHOD=POSTACTION="/checklogin.cgi">
<strong>用戶名</strong>
<inputtype="text" size="20" name="username">
<strong>密碼</strong>
<inputtype="password" size="20" name="password">
<BR>
<inputtype="submit" name="login" value="登陸">
</FORM>
</BODY>
</HTML>
?
這個代碼中的<FORM METHOD=POST ACTION="/checklogin.cgi">表明,瀏覽器是下發POST指令,其中包含了checklogin.cgi處理,而這個checklogin.cgi處理是要STM32在程序里面處理。直接看點擊<登陸>之后瀏覽器下發給STM32的數據:
?
?
從數據中可以看到”POST / checklogin.cgi”這個字符串,這個就是一個POST命令,在函數http_parse_request()中解析出來。而在后面的數據中有”username=admin&password=admin&login=%B5%C7%C2%BD”這一串數據就是要在CGI處理函數Chacklogin_CGI_Handler()中進行解析和處理,并把處理的結果發回給瀏覽器。
在httpdi_cgi_ss.c里面有:
static consttCGI ppcURLs[]= //cgi程序
{
? ?? ?{"/checklogin.cgi",Chacklogin_CGI_Handler},
};
//CGI 用戶和密碼檢測設置 控制句柄
const char*Chacklogin_CGI_Handler(int iIndex, int iNumParams, char *pcParam[], char*pcValue[])
{
??u8 i=0; //注意根據自己的參數的多少來選擇i值范圍
? ?? ?u8 passchack=0;
? ?? ?iIndex =FindCGIParameter("username",pcParam,iNumParams);??//找到bktime的索引號
? ?? ?if(iIndex != -1) //找到pagingvol索引號
? ?? ?{
? ?? ?? ???for(i = 0;i < iNumParams;i++)
? ?? ?? ???{
? ?? ?? ?? ?? ???if(strcmp(pcParam,"username")== 0)??//查找CGI參數
? ?? ?? ?? ?? ???{
? ?? ?? ?? ?? ?? ?? ? if(strcmp(pcValue,"admin")== 0)//用戶名正確
? ?? ?? ?? ?? ?? ?? ? {
? ?? ?? ?? ?? ?? ?? ?? ?? ? passchack++;
? ?? ?? ?? ?? ?? ?? ? }
? ?? ?? ?? ?? ???}
? ?? ?? ?? ?? ???elseif(strcmp(pcParam,"password") == 0)??//查找CGI參數
? ?? ?? ?? ?? ???{
? ?? ?? ?? ?? ?? ?? ? if(strcmp(pcValue,"admin")== 0)//密碼正確
? ?? ?? ?? ?? ?? ?? ? {
? ?? ?? ?? ?? ?? ?? ?? ?? ? passchack++;
? ?? ?? ?? ?? ?? ?? ? }
? ?? ?? ?? ?? ???}
? ?? ?? ???}
? ?? ?? ???if(passchack > 1)//用戶名和密碼都正確了
? ?? ?? ???{
?
? ?? ?? ?? ?? ???return "/index.shtml";? ???//把保存成功的信息以及修改后的信息重新上傳
? ?? ?? ???}
? ?? ?}
?
? ?? ?return "/error.shtml";? ?? ?//把保存成功的信息以及修改后的信息重新上傳
}
函數Chacklogin_CGI_Handler()就是驗證用戶名和密碼是否是”admin”,正確則把index.shtml這個SHTML文件發給瀏覽器,瀏覽器則顯示index.shtml的網頁界面;如果用戶名和密碼錯誤則返回錯誤的網頁界面(error.shtml的界面)。
?
??關于CGI處理函數是在函數http_find_file()中解析到"/checklogin.cgi"后被調用,而在瀏覽器下發的指令中,有GET指令和POST指令的區別,這兩個指令都能夠帶有"/checklogin.cgi",但STM32對這兩個指令的解析方法是不一樣的,這一點是要注意去區分,相對來說目前的程序是直接用的GET指令來下發表單數據(就是帶有<FORM METHOD=GET??ACTION="/checklogin.cgi">代碼的網頁界面),這個指令下發下來可以直接被解析出來并調用CGI處理函數,但它傳下來的參數會直接顯示在瀏覽器的地址輸入欄里,比如“username=admin&password=admin&login=%B5%C7%C2%BD”,這樣就不是特別的安全,所以不建議用次方法下發表單數據。而用POST指令下發則不會這樣,相對來講會更安全,而且如果是下發一個文件的數據,比如做網絡升級時下發的bin文件,這個時候就是得用POST指令。所以接下來重點介紹POST指令的應用。
四、GET與POST指令的應用解析
?
GET和POST是兩種最常用的HTTP請求方法。我們日常生活中“打開網頁”的操作相當于“使用GET方法獲取服務器資源”,而“上傳附件”、“提交表格”等操作相當于“使用POST將本地資源提交給服務器”。似乎GET方法和POST方法的區別就是一個用于“索取”,一個用于“遞交”。這么說也對也不對,實際使用中確實有部分程序這樣使用了,但是HTTP協議設計時可不是這樣考慮的,下面的表格簡單對比了兩者的一些差異。
?
| ?? ?? | GET 方法 : | POST 方法: |
| 可傳遞數據類型 | ASCII文本(漢字有專門的方法轉換) | 不限,支持二進制文件 |
| 可傳送的數據量 | 有限制(2048字節減去URL長度) | 無限制 |
| 內容編碼類型 | application/x-www-form-urlencoded | application/x-www-form-urlencoded或multipart/form-data |
| 后退/刷新 | 回退后再次前進或刷新不會通知用戶 | 數據會被再次提交 |
| 歷史記錄 | 瀏覽器會記錄全部內容 | 瀏覽器只記錄接收POST內容的URL但不記錄POST的具體內容 |
| 典型應用 | 獲取服務器上的資源,如下載 | 向服務器添加資源,如上傳附件 |
?
在網頁界面中應用GET與POST的區別基本在與表單那里,如使用GET指令為:
<FORM METHOD=GET ACTION="/checklogin.cgi">
使用POST指令為:
<FORM METHOD=POST ACTION="/checklogin.cgi">
在STM32中,要使用POST指令需要先設置
#defineLWIP_HTTPD_SUPPORT_POST? ?1,然后還需要注意的是,
#definePBUF_POOL_BUFSIZE? ?? ? 1600這個大小不能太小,因為一幀數據最大是1514,小于1514會導致接收到的數據出現錯誤,這個設置主要在文件上傳時尤其需要注意,因為文件數據基本都是1514字節一幀數據的發給STM32。
打開#define LWIP_HTTPD_SUPPORT_POST??1后需要編寫接收和解析POST指令的三個函數:
err_thttpd_post_begin(void *connection, const char *uri, const char *http_request,
? ?? ?? ?? ?? ?? ?? ???u16_t http_request_len,int content_len, char *response_uri,
? ?? ?? ?? ?? ?? ?? ???u16_t response_uri_len,u8_t *post_auto_wnd)
{? ? memset(http_post_response_filename,0,sizeof(http_post_response_filename));
? ?? ?strcpy(response_uri,uri);
? ?? ?return ERR_OK;
}
?
err_thttpd_post_receive_data(void *connection, struct pbuf *p)
{
? ?? ?struct http_state *hs=(struct http_state*) connection;
? ?? ?*(strrchr(p->payload,'&')) = 0;
? ?? ?sprintf(http_post_response_filename +strlen(http_post_response_filename),"?%s",p->payload);
? ?? ?return ERR_MEM;//不回復ERR_OK,為了讓外面的函數能夠調用http_handle_post_finished()
}
?
voidhttpd_post_finished(void *connection, char *response_uri, u16_tresponse_uri_len)
{
?
}
static err_thttp_handle_post_finished(struct http_state *hs)
{
??/* application error or POST finished */
??/* NULL-terminate the buffer */
//??http_post_response_filename[0] = 0;//數據處理已經在httpd_post_begin()和httpd_post_receive_data()函數里處理
//??httpd_post_finished(hs,http_post_response_filename, LWIP_HTTPD_POST_MAX_RESPONSE_URI_LEN);
??return http_find_file(hs, http_post_response_filename,0);
}
這三個函數的寫法可以有很多種,目前我這樣寫只是為了方便直接使用函數http_find_file()中對CGI的判斷解析方法(GET指令時的解析方法)。如果使用的是GET指令下發CGI,則在函數http_find_file()中uri[]這個數組里面可以找到” ?username=admin&password=admin&login=%B5%C7%C2%BD”,所以找到”?”這個字符就表明找到了第一個參數的地址。而使用POST指令時下發的數據中uri[]數組中是”username=admin&password=admin&login=%B5%C7%C2%BD”,沒有這個”?”,所以為了方便使用函數http_find_file(),所在在函數httpd_post_receive_data()中人為的添加了”?”,并且找到倒數第一個”&”,就是只要” ?username=admin&password=admin”,當然也可以不這么寫,但原理是一樣的,目的就是找出” username=admin&password=admin”這些參數,然后把參數給賦值到hs中,比如把username存入hs->params[0]中,把admin存入hs->param_vals[0],具體看函數extract_uri_parameters()。然后在接下來的處理就是在函數http_handle_post_finished()調用函數http_find_file()來找到并調用對應的CGI函數,比如調用函數Chacklogin_CGI_Handler()。
最后來看一下瀏覽器下發POST指令時,STM32的解析流程:
http_recv()----->判斷收到的是有效數據后,調用函數http_parse_request()----->解析是GET指令還是POST指令(輸入IP后下發的是GET指令),如果是POST指令,則調用函數http_post_request ()----->然后用函數httpd_post_begin()開始處理數據----->然后調用函數http_post_rxpbuf()----->再調用函數httpd_post_receive_data()處理接收到的數據 (這時是人為添加”?”)----->處理結束后是調用函數http_handle_post_finished(),這個函數中其實也就是調用函數http_find_file()來解析CGI程序指令,也就是找到并調用對應的CGI函數,比如Chacklogin_CGI_Handler()----->由于調用的CGI處理函數的返回值是一個shtml的文件路徑,該文件是存放在SPIFLASH芯片W25Q128或者SD卡中的SHTML文件,獲取相應的數據----->然后通過函數http_init_file()對數據進行初始化,然后退出函數http_find_file(),再退出函數http_parse_request()----->然后運行到函數http_send_data(),查找SSI的Tag,找到之后把相應的內容添加進入,然后把數據發回給瀏覽器----->瀏覽器顯示CGI處理后的網頁界面。
?
總結
以上是生活随笔為你收集整理的STM32 网络通信Web Server中 SSI与CGI的应用解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: html ajax 数据传送,HTML
- 下一篇: CM3计算板安装硬件时钟DS3231