ESP8266 & NodeMCU 開發入門 (Part 4) - Node.js 風格的 HTTP Server

本文介紹 NodeMCU 最主要的特色,同時也是關鍵字「Node」隱含的一個意思:Node.js 的程式碼風格(Node.js style)。

教學「ESP8266 & NodeMCU 開發入門 (Part 2) - 實作 HTTP Server」介紹了如何在 NodeMCU 共實作 REST API 的基本觀念:

  • 使用 Lua 內建的 net 模組
  • 實作 TCP Server
  • 初步認識了 HTTP request header

接下來,我們將重新改寫這個範例:

  • 改用 http 模組,http 模組封裝了 net 模組,提供更有彈性的 HTTP Server API
  • http 模組提供的 API,能以 Node.js 的程式碼風格,來撰寫 Lua Script

實作步驟如下。

Step 1:取得 http 模組

下載 nodemcu-firmware 後,可以找到 lua_modules/http/http.lua 檔案,這是 nodemcu-firmware 所收錄的一個 HTTP 模組。

Step 2:上傳 http.lua

使用 ESPLorer 將 lua_modules/http/http.lua 上傳到 NodeMCU 開發板,如圖 4.1。

圖 4.1:上傳 http.lua 圖 4.1:上傳 http.lua

Step 3:引用外部模組

httpd.lua 上傳至 NodeMCU 後,再使用 Lua 的 require 語法引入 http.lua 模組使用。語法如下:

sv = require("http")  

將引入的模組 assign 給 sv 變數,接著呼叫 http.lua 模組的 createServer() 來建立 HTTP Server:

sv.createServer(80, function(req, res)  
end)  

參數說明:

  • 第一個參數是 Listening port
  • 第二個參數是 HTTP request handler

當 HTTP Client 對 NodeMCU 發出 HTTP request 時,第二個參數的 Request handler 會被 callback。

一般我們在撰寫 JavaScript 時,經常使用 Chaining Pattern,Lua 也支援這樣的程式碼風格。將上述範例修改如下:

require("http").createServer(80, function(req, res)  
  ...
end)  

Request handler 被 callback 時,會收到二個參數:第一個參數是此連線的 HTTP request connection 物件,第二個參數則是 HTTP response connection 物件。

Step 4:HTTP Request Connection 事件處理

HTTP request connection 物件有 2 個事件:

  • onheader:接收到 HTTP headers 時觸發
  • ondata:接收到 HTTP body 時觸發

範例如下:

require("http").createServer(80, function(req, res)  
  req.onheader = function(self, name, value)
  end
  req.ondata = function(self, chunk)
  end
end)  

例如,在完成 HTTP request 後,要回應 "Hello, World!" 文件,實作範例如下:

require("http").createServer(80, function(req, res)  
  req.ondata = function(self, chunk)
    -- end of this request ?
    if not chunk then
      res:send(nil, 200)
      res:send_header("Connection", "close")
      res:send_header("Content-Type", "text/html")

      res:send("<h1>Hello, world!</h1>")
      res:finish()
    end
  end
end)  

測試時,使用瀏覽器瀏覽 NodeMCU,可看到如圖 4.2 的畫面。

圖 4.2:執行結果 圖 4.2:執行結果

完整程式碼列表

以下是本文的完整程式碼:

-- Print IP address
ip = wifi.sta.getip()  
print(ip)

-- Configure the ESP as a station (client)
wifi.setmode(wifi.STATION)  
wifi.sta.config("<SSID>", "<PASSWORD>")  
wifi.sta.autoconnect(1)

-- Node.js style httpd
require("http").createServer(80, function(req, res)  
  req.ondata = function(self, chunk)
    -- end of this request ?
    if not chunk then
      res:send(nil, 200)
      res:send_header("Connection", "close")
      res:send_header("Content-Type", "text/html")

      res:send("<h1>Hello, world!</h1>")
      res:finish()
    end
  end
end)  

小結

導入 http.lua 模組後,讓 Lua 的程式碼變得很有 Node.js 的風格。為什麼在 NodeMCU 上,能寫出這樣的 Lua 程式碼?這除了 Lua 本身語法設計上的特性外,http.lua 的 API 實作也是一個關鍵。

步驟 3 實作 HTTP response 時,使用到 ConnectionContent-Type 二個 HTTP headers。IoT 開發者,應該試著了解 HTTP 標準,特別是 HTTP headers 的部份;若能認識基本的 HTTP headers,對 IoT 的開發將非常有幫助。

URL Routing 是實作 REST API 的關鍵,這部份將在後文進行介紹。

其它