2020年10月18日 星期日

三、 存取點(AP)模式內建網路伺服器(WebServer)設計—GET請求的解析

 

圖三、1 網路通訊基本架構 : 客戶端(Client)--伺服器(Server)示意圖


在網路通訊的世界裡,如【圖三、1】所示最基本就是兩個角色,一個是提出請求(Rquest)的客戶端(Client),另一邊則是回應需求的伺服器(Server)端,這兩種裝置透過網路(包括網際網路(Internet)本地網路(Local Net)與無線WiFi網路等)連接在一起至於其他的網路設備,如路由器(Router)、集線器(Hub)、通訊閘道(Gateway)、中繼器(Repeater)...等,都只是為了要讓這兩種裝置能順利地連接在一起,達成網路通訊的目的而已。ESP32這個晶片模組可分別執行客戶端(Client)與伺服器(Server)端這兩個腳色可以連上網際網路亦可自行組成一個區域性的無線WiFi網路如果要在這個無線WiFi區域網路擔任伺服器(Server)個腳色時就必須扮演無線WiFi網路中的存取點(AP:Access Point)功能了

所謂的網路伺服器(WebServer),是指一種連接在網路上的設備,這種設備主要用於儲存和提供數據/文件,而客戶端(Client)這種裝置可以請求(Request)這些文件或其他數據,網路伺服器將此請求(Request)解譯之後,再將正確的數據/文件發送回客戶端一般來說這兩種設備之間使用所謂的HTTP通訊協定來進行需求與資料的雙向傳輸所謂的HTTP(Hypertext Transfer potocal)或超文本傳輸協定是用於與(Web)伺服器通信的基於文本的協定HTTP請求方法有多種,但由於ESP32只是一顆內部資源有限的單晶片微電腦而已所以我們只會介紹兩種最常用的方法也就是:GET和POST。

HTTP通訊協定中GET這種請求用於從伺服器檢索數據/文件,例如瀏覽器連接網頁它只是單純從伺服器獲取數據/文件,不會要求或試著更改伺服器內部的資料,因此沒有其他的副作用。當我們使用瀏覽器中打開某一個網頁時,它將獲取URL並將其放入HTTP GET請求中這只是純文本(plain text)資料然後它將使用TCP通訊協定將請求發送到正確的伺服器伺服器讀取請求後,檢查URL,並將該URL的正確HTTP響應發送回瀏覽器。


www_example_com

圖三、2  電腦瀏覽器連接http://www.example.com網站後的內容


在HTTP 的GET請求中最重要的部分是請求行(request Line)主機檔頭(host header)接下來我們以一個範例來說明:

請啟動你個人電腦的瀏覽器(在此使用的是Google Chrome其他的瀏覽器也可以當然你的電腦必須已經連上網際網路)接著在URL列輸入以下鏈接網址:

https//www.example.com


這時您的瀏覽器將發出以下HTTP請求:

在【圖三、2】中標記1是我們要前往網頁的URL網址,這是一個網路上有名的樣本網站,的內容就是左邊標記2簡單的文字說明;不過要看到右邊相關的資訊,請在連上這個網站之後按下電腦鍵盤的[F12]功能鍵,然後依序點擊的標記3重新載入按鈕再次的載入網頁,接著是標記4的[ Network ]標籤、標記5的網站名稱[www.example.com]、標記7的解析(parsed)選項後,便可以完整的看到標記6與8的內容了。

所謂的請求行主要包括兩個部份,分別是URI(Uniform Resource Identifier)也就是請求的內容,再來就是請求的方式「GET」;至於主機檔頭,則是要連接的主機伺服器(host server)的網域名稱。在標記6的區域中我們可看到前兩行的內容為:

Request URL:http://www.example.com/

Request Method:GET

第二行的內容就是請求的方式「GET」,而第一行的內容其實是由兩個部分所構成,分別是代表主機檔頭的主機伺服器(host server)網域名稱:[http://www.example.com],及請求的內容:「’/’」,這個內容也就是一般網站主網頁的意思。

接下來我們就以一個範例程式來看看,當我們用手機或平板電腦的瀏覽器去連接ESP32的AP存取點伺服器網站時,會送出那些資訊出來:


範例程式功能與動作說明:

1、以ESP32建立一無線WiFi存取點,SSID名稱為『ES32_softAP01』,此AP點不使用密碼。

2、此AP存取點內建伺服器的IP位址為:[ 192.168.4.1 ]

3、當有客戶端裝置連線上ESP32時,系統會讀取客戶端(手機、平板或筆電上的瀏覽器)所傳送來的完整請求訊息,接著除了會顯示在電腦上的Arduino IDE序列監控視窗中之外還會將此訊息回傳給客戶端也就是會顯示在連線裝置的瀏覽器螢幕上。

【圖三、3】是本範例在行動通訊裝置連線上ESP32並開啟瀏覽器後預期的顯示畫面上方標記1是本範例的提示訊息而下方的一系列文字都是客戶端瀏覽器所送出的網頁首頁請求訊息內容本範例程式只是將此訊息原封不動的回傳給使用者而已

圖三、3 客戶端瀏覽器螢幕預期畫面


程式說明與列表

以下是這個範例程式的全文:

  1. /*   

  2.  *    ESP32 soft AP 範例二: 分析及顯示瀏覽器傳送的GET請求訊息內容

  3.  */

  4.  

  5. #include <WiFi.h>

  6. #include  "index.h"

  7.  

  8. const char* ssid     = "ESP32_softAP01";

  9. const char* password = "12345678";

  10.  

  11. String  yourGet="";

  12.  

  13. WiFiServer server(80);

  14.  

  15. // 初始化設定程式開始:

  16. void setup()

  17. {

  18.     Serial.begin(115200);

  19.     delay(10);

  20.  

  21.     // We start by connecting to a WiFi network

  22.     WiFi.softAP(ssid);        // 本範例暫時不使用WiFi連線密碼

  23.     Serial.println("Setting softAP ...");

  24.     Serial.println();

  25.     Serial.print("Your softAP is : ");

  26.     Serial.println(ssid);               // 顯示ESP32 soft AP名稱

  27.     Serial.println("IP address: ");

  28.     Serial.println(WiFi.softAPIP());    // 顯示ESP32內建伺服器IP位址

  29.     

  30.     server.begin();         // 啟動伺服器功能

  31. }   // 初始化設定程式結束.

  32.  

  33. // 主迴圈程式開始:

  34. void loop(){

  35.  

  36.   WiFiClient client = server.available();   // listen for incoming clients

  37.   // wait for a client (web browser) to connect

  38.   if (client)

  39.   {

  40.     Serial.println("\n[Client connected]"); // 在序列監控視窗輸出連線提示訊息

  41.     while (client.connected())

  42.     {

  43.       // read line by line what the client (web browser) is requesting

  44.       if (client.available())

  45.       {

  46.         String line = client.readStringUntil('\r'); // 逐行讀取客戶端送來的訊息

  47.         Serial.print(line);

  48.         yourGet+=line;        // 將客戶端送來的訊息整合成一個字串變數

  49.         yourGet+="<br>";     // 替HTTP網頁程式上換行的指令

  50.         // wait for end of client's request, that is marked with an empty line

  51.         if (line.length() == 1 && line[0] == '\n')  // 測試HTTP請求是否結束

  52.         {

  53.           String tmpString = MAIN_page;             // 取出html網頁回應程式

  54.           tmpString.replace("%yourGet%", yourGet ); // 帶入客戶端HTTP請求內容至回應網頁

  55.           client.print( tmpString );

  56.           break;

  57.         }

  58.       }

  59.     }

  60.     delay(1); // give the web browser time to receive the data

  61.  

  62.     // close the connection:

  63.     client.stop();

  64.     yourGet="";       // 清除回應客戶端訊息字串變數

  65.     Serial.println("[Client disonnected]");

  66.   }

  67. }   // 主迴圈程式結束.

  68.  

  69. //  以下為”index.h” 標籤頁面的內容:

  70. const char MAIN_page[] PROGMEM = R"=====(

  71. HTTP/1.1 200 OK

  72. Content-Type:text/html

  73.  

  74. <!DOCTYPE html>

  75. <html>

  76.  <head>

  77.  <meta name='viewport' content='width=device-width, initial-scale=1.0'/>

  78.  <meta charset='utf-8'>

  79.  <meta http-equiv='refresh' content='%rTime%'>

  80.  <style>

  81.    body {font-size:100%;} 

  82.    #main {display: table; margin: auto;  padding: 0 10px 0 10px; } 

  83.  </style>

  84.    <title>Soft AP模式-Station連線內容讀取實習</title>

  85.  </head>

  86.  

  87.  <body> 

  88.    <div id='main'>

  89.      <h2><center>[Soft AP模式] <br>

  90.          Station連線內容讀取實習<br></center>

  91.      </h2>

  92.      <h3><br> %yourGet% </h3>

  93.    </div> 

  94.  </body>

  95. </html>

  96. )=====";

程式名稱softAP1_ShowGet.ino


圖三、4  Arduino IDE 中建構index.h程式頁面畫面


我們的程式中會有一段回應客戶端的HTML網頁程式但是在Arduino IDE上面並不適合直接用來撰寫HTML網頁程式語法為了讓程式容易設計而且版面看起來比較乾淨俐落因此建議把相關的HTML網頁程式另外開一個標籤頁面來撰寫然後用引入的方式來使用這樣就可以把兩者分得比較清楚。【圖三、4】的畫面即為本範例程式實際在Arduino IDE的寫法在前面的程式列表中70~96行便是「index.h」這個頁面的內容。

程式的13行在宣告一個網路伺服器物件物件變數:


13. WiFiServer  server(80); // 宣告網路伺服器物件


在初始化程式(setup())部分和上一個範例幾乎完全一樣只是多了30行程式啟動伺服器的功能:


30. server.begin(); // 啟動伺服器功能


在主迴圈(loop())開始的第36行程式呼叫「server.available()」這個方法函式去監測是否有客戶端裝置連線到本機的伺服器,若無則回頭繼續執行主迴圈:


36. WiFiClient client = server.available(); // 測試是否有客戶端連接本機伺服器


如果有客戶端裝置連線到伺服器,則主迴圈程式剩下的部分便會開始接收與回應客戶端的請求訊息。41~59行程式是以while()迴圈的方式來持續的接收客戶端傳來的請求訊息,直到接收到代表請求結束的空行字串為止。46~49行程式會逐行讀取客戶端送來的訊息,並把它們合併為「yourGet」這個字串變數,因為一般文字的換行碼”\n”在html網頁程式中並不會讓瀏覽器執行換行的動作,因此在每一行的後面必須加上<br>這個指令標籤才能產生換行的效果。


46.      String line = client.readStringUntil('\r'); // 逐行讀取客戶端送來的訊息

47.      Serial.print(line);

48.      yourGet+=line;         // 將客戶端送來的訊息整合成一個字串變數

49.      yourGet+="<br>";      // 替HTTP網頁程式上換行的指令


一般的瀏覽器在結束請求傳送時,會送出一個空白行,也就是一整行只有一個代表換行的字元”\n”,因此當ESP32接收到這樣的字串時(51~58行),便會把網頁首頁程式MAIN_page[]傳送到tmpString這個字串變數中同時也把整的客戶端的請求訊息(即「yourGet」字串變數)帶入到tmpString這個網頁html程式內在送出回應網頁程式至客戶端後以”break”的整令結束while()迴圈。


51.     if (line.length() == 1 && line[0] == '\n')  // 測試HTTP請求是否結束

52.     {

53.        String tmpString = MAIN_page;         // 取出html網頁回應程式

54.        tmpString.replace("%yourGet%", yourGet ); // 帶入客戶端HTTP請求55. 內容至回應網頁

56.        client.print( tmpString ); // 送出回應網頁程式至客戶端

57.        break;

58.      }


由於我們的html網頁程式是放在ESP32的程式記(ROM)憶體中,是無法更改的,可是我們的網頁內容又常是動態的,因此在54行程使用了一個Arduino的內建字串指令「tmpString.replace("%yourGet%", yourGet )」,它原來的格式如下:

 String.replace(string1, string2 )

 也就是說用第二個字串參數去取代第一個,用這種程式設計法可以讓程式寫起來更方便簡單,也更有彈性!

接著的70~96行是是「index.h」這個頁面的內容,是一個完整的伺服器網頁首頁程式,在此是使用了下面這種在C++語言中稱為原始字串 (Raw String) 的語法,也就是將整個網頁程式字串包在後面的左右括弧「R”=====(……..)=====”」中間”…”的部分,而左右括弧的兩邊一共用了5個等號(“=”),缺一個都不行!這種語法可以保持html程式原來的樣貌,增加程式設計的方便性與可看性,而這點是Arduino IDE這個軟體所無法提供的。


const  char  MAIN_page[]  PROGMEM = R"=====( ……….. )=====";


程式在此是定義了一個名稱為「MAIN_page[]」的字元常數陣列,而「PROGMEM」的意思是這字元常數陣列最後會放在程式記憶體中,這樣就不會占用到ESP32內部的RAM主記憶體,以免程式太大時造成記憶體溢位的問題!

這個html網頁程式的主體(即87~94行的<body>…</body>中間)部分如下


87. <body> 

88.   <div id='main'>

89.     <h2><center>[Soft AP模式] <br>

90.         Station連線內容讀取實習<br></center>

91.     </h2>

92.     <h3><br> %yourGet% </h3>

93.   </div> 

94. </body>


其中的8990行是用來實現【圖三、3】中標記1的提示訊息而92行則會將前面54行所傳來的「yourGet」字串變數帶入程式中這樣的寫法可讓我們的網頁程式部分更有彈性


執行結果:

在程式執行之後接著開啟手機/平板/筆電的WiFi功能,如【圖三、5】所示在看到”ESP32_softAP01”這個AP存取點後點選它,接著啟動瀏覽器,並在網址輸入欄中輸入[ 192.168.4.1 ]這個IP位址並前往


圖三、5 手機啟動WiFi功能後連接ESP32的AP存取點


這時我們可以看到如【圖三、6】所示的手機瀏覽器畫面這也就是實現了前面【圖三、3】的功能。而在Arduino IDE串列監控視窗中則會看到【圖三、7】的內容,其中標記1的部分是在程式初始化時前設定的SSID名稱(在此為”ESP32_softAP01”),和而標記2的ESP32內定的本地IP位址(192.168.4.1),在本例中為了方便連線測試,所以沒有加上連線密碼。


圖三、6 客戶端手機瀏覽器連線後畫面


圖三、7  客戶端瀏覽器連線後Arduino IDE串列監控視窗之內容


如果我們在手機瀏覽器的網址輸入欄輸入URL:「 192.168.4.1/test 」這樣的內容送出之後,可看到【圖三、8】的內容,其中紅色框線中的部分就是此次客戶端送來請求行(request Line)


GET  /test  HTTP/1.1


跟前面【圖三、6的差別就是請求的內容由「’/’」 變成「’/test’」,,也就是說只要在網域名稱或是網路IP後面加上一參數,便可將這些訊息當成請求的一部分傳送給目的伺服器,以作為一些延伸應用。


圖三、8 瀏覽器網址列輸入”192.168.4.1/test”後回傳送資料內容


沒有留言:

張貼留言

三、使用Line Notify傳送照片之安全監控系統之二---低功耗篇

在前一個章節中 , 我們建構了一個標準照片擷取 、 傳送與儲存的按全監控裝置 , 不過假如我們使用的場域中有許多的地方都必須按裝這類的裝置時 , 例如在一個有許多門 、 窗的家庭或辦公室 , 由於我們的系統使用無線WiFi作為信號傳輸之用 , 所以信號的傳輸除非裝置離WiFi分享...