Node.js 系列 - 搭建路由 & 處理表單提交

做爲還在漫漫前端學習路上的一位自學者。我以學習分享的方式來整理本身對於知識的理解,同時也但願可以給你們做爲一份參考。但願可以和你們共同進步,若有任何紕漏的話,但願你們多多指正。感謝萬分!html


以前, 咱們搭建了靜態文件服務器. 用戶經過在瀏覽器搜索欄輸入 URL 來請求保存在服務器的指定文件. 可是除了提供靜態文件, 服務器能作的還有不少不少. 在這一篇, 咱們要學會用 Node.js 處理從前端頁面的 HTML 表單中提交的信息.前端

搭建路由

在平常, 咱們訪問一個站點的不一樣地址時, 一般頁面內容也隨之改變. 這是由於服務器爲了實現更多的功能, 其會根據請求 URL 的不一樣而作出不一樣的處理, 這被稱做 "路由"npm

『 路由 』簡單來講就是 請求和請求處理代碼之間的映射關係. 當服務器爲一個特定 URL 掛在了請求處理代碼時, 全部針對於這個特定 URL 的請求都會交由其處理.json

假設咱們要作一個用於自我介紹的我的網頁, 其包含: "主頁". "項目介紹頁面", "關於我頁面".瀏覽器

那麼咱們能夠像下面代碼中那樣來搭建路由規則:bash

// 引入相關模塊
var http = require('http');
var url = require('url');

// 搭建 HTTP 服務器
var server = http.createServer(function(req, res) {
    // 獲取請求 URL, 根據 URL 中的 pathname 來匹配對應的處理方法.
    var urlObj = url.parse(req.url);
    var urlPathname = urlObj.pathname;

    switch (urlPathname) {
        case "/main":
            // 由於返回內容中有中文, 因此別忘了指定編碼方式
            res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
            res.write("主頁頁面");
            res.end();
            break;
        case "/aboutme":
            res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
            res.write("關於我頁面");
            res.end();
            break;
        case "/projects":
            res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
            res.write("項目介紹頁面");
            res.end();
            break;
        // 若是都不匹配就返回 404 
        default:
            res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
            res.write("404 - Not Found");
            res.end();
            break;
    }
});

// 在 3000 端口監聽請求
server.listen(3000, function() {
    console.log("服務器運行中.");
    console.log("正在監聽 3000 端口:")
})
複製代碼

上面代碼根據請求 URL 的不一樣, 而將請求交給不一樣的處理代碼. 你能夠嘗試運行服務器, 而後用瀏覽器去請求相應的 URL, 來看看獲得的響應是什麼.服務器

123


獲取 GET 表單提交

在學習了路由相關知識以後, 咱們再來了解一下如何獲取從客戶端發過來的 表單提交.app

咱們先介紹用 GET 方法提交的表單. 經過 GET 提交的表單內容會組裝成『 查詢字符串 』嵌入在請求 URL 裏. 例以下面這段:函數

https://www.zhihu.com/search?type=content&q=罐裝汽水Garrik
複製代碼

? 問號開始就是這段 URL 的查詢字符串; 參數之間用 & 分開; = 等號前面的是參數名, 後面的是參數值.post

上面這段 URL 的查詢字符串如何解析成 JSON 的話就是:

{
    "type": "content",
    "q": "罐裝汽水Garrik"
}
複製代碼

那麼再簡單瞭解了基礎知識以後呢, 就讓咱們趕快來寫代碼吧!

首先讓咱們來寫一個有 HTML 表單的頁面, 而後命名爲 login.html (固然你也能夠按照你的想法寫代碼和命名)

這個表單我想用來提交登陸信息, form 元素的 action 屬性我定義爲 login, 意思是將請求發送到 login 這段路徑下. method 屬性我定義爲 get, 意思是以 GET 方法提交表單.

<body>
    <form action="login" method="get">
        帳戶: <input type="text" name="username" />
        <br /> 
        密碼: <input type="text" name="password" />
        <br />
        <input type="submit" value="提交">
    </form>
</body>
複製代碼

以後再讓咱們來寫服務器代碼. 經過前面的介紹, 你知道咱們須要解析 URL 的查詢字符串. 作到這點很簡單, 只須要在調用 url.parse 函數解析請求 URL 時爲其傳入第二個參數 true. 這個函數就會自動幫你把 URL 的查詢字符串解析成一個 JavaScript 對象了, 保存在函數返回對象的 query 屬性中. 若是沒有查詢的話屬性值就是 null

咱們能夠用路由去匹配路徑, 當請求 URL 的路徑和表單發送的路徑相匹配時, 將請求交給特定代碼去處理.

var server = http.createServer(function(req, res) {
    // 解析請求 URL
    var urlObj = url.parse(req.url, true);
    // 獲取請求 URL 的路徑
    var urlPathname = urlObj.pathname;
    // 獲取請求 URL 的查詢字符串解析成的對象
    var queryObj = urlObj.query;
    
    // 路由
    switch (urlPathname) {
        // 響應 login 頁面
        case "/":
        case "":
            // 我用了靜態服務器那篇的模塊, 不瞭解的地方能夠去那篇參考
            readStaticFile(res, "./login.html");
            break;
        // 響應查詢對象的 JSON 形式到瀏覽器 
        case "/login":
            res.writeHead(200, { "Content-Type": "text/plain" });
            res.write(JSON.stringify(queryObj));
            res.end();
            break;
        // 錯誤處理
        default:
            readStaticFile(res, "./404.html");
    }
});
複製代碼

當運行起服務器以後, 訪問 login 頁面, 提交表單你看到的應該像是下面這樣:

Screen Shot 2018-10-09 at 12.49.09 AM

Screen Shot 2018-10-09 at 12.49.20 AM


獲取 POST 表單提交

說完 GET, 咱們再來講說用 POST 方法提交表單. 不一樣於用 GET 方法時, 提交的內容都包含在 URL 裏. POST 提交的內容所有的都在請求體中.

咱們 HTTP 服務器 http.createServer 接收的請求對象 req 並無一個屬性內容爲請求體. 緣由是 POST 請求體可能體積很是大, 若是每次接收請求都包含請求體的話會很耗時. 並且萬一遇到了惡意 POST 請求攻擊, 服務器的資源就被大大地浪費了.

爲了獲取 POST 請求體, 咱們須要手動來操做. 由於 POST 請求數據量可能很大, 因此它被拆分紅了不少個小數據塊 ( chunk ) 咱們經過在服務器監聽請求對象 req 的 'data' 事件來一個個地接收這些數據塊, 並將其拼接在一塊兒.

當請求傳輸完畢, 會觸發請求對象 req 的 'end' 事件. 咱們須要監聽它, 事件觸發後, 在其事件處理函數中解析 POST 的請求體.

var server = http.createServer(function(req, res) {
    var urlObj = url.parse(req.url, true);
    var urlPathname = urlObj.pathname;

    switch (urlPathname) {
        case "/":
        case "":
            readStaticFile(res, "./login.html");
            break;
        case "/login":
            // 當請求方法爲 POST 時觸發
            if (req.method === 'POST') {
                // 用於保存拼接後的請求體
                var post = '';
                // 'data' 事件觸發, 將接受的數據塊 chunk 拼接到 post 變量上
                req.on('data', function(chunk) {
                    post += chunk;
                });
                // 請求完畢, 'end' 事件觸發
                req.on('end', function() {
                    // querystring 是 Node.js 自帶模塊, parse 方法用於將查詢字符串解析成對象
                    var queryObj = querystring.parse(post);
                    // 將接收的 POST 請求體以 JSON 格式響應回客戶端
                    res.writeHead(200, { "Content-Type": "text/plain" });
                    res.write(JSON.stringify(queryObj));
                    res.end();
                });
            }
            break;
        default:
            readStaticFile(res, "./404.html");
    }
});
複製代碼

對了, 最重要的一點, 別忘了將 login.html 文件中的表單提交方法從 get 改爲 post

<body>
    <form action="login" method="post">
        <!-- 省略了 -->
    </form>
</body>
複製代碼

如今運行服務器, 提交表單, 看看結果是什麼. 應該效果像下圖所示:

Screen Shot 2018-10-09 at 12.49.09 AM

Screen Shot 2018-10-09 at 10.43.14 AM


POST 文件上傳

文件上傳咱們能夠很方便的用第三方模塊 formidable 來實現.

首先用 npm 來安裝模塊:

npm install formidable --save
複製代碼

formidable 是用因而表單數據解析的模塊, 很是適合用於文件上傳的處理. 使用該模塊時, 先要調用它的 IncomingForm 構造函數初始模塊. 該函數返回一個 IncomingForm 實例用於解處理表單提交數據. 以後經過調用該實例的 parse 方法來解析數據.

當用戶使用表單提交數據時,表單中可能會包含兩類數據: 普通表單數據, 文件數據. parse 方法解析時,會將這兩種數據分別放到fieldsfiles 這兩個回調參數中.

那麼很少廢話直接上代碼:

// 模塊引入
var formidable = require('formidable');

var server = http.createServer(function(req, res) {
    var urlObj = url.parse(req.url, true);
    var urlPathname = urlObj.pathname;

    switch (urlPathname) {
        case "/":
        case "":
            readStaticFile(res, "./upload.html");
            break;
        // 路由爲 '/upload'
        case "/upload":
            if (req.method === 'POST') {
                // 初始化 formidable 的 IncomingForm 實例
                var form = new formidable.IncomingForm();

                // uploadDir 設置上傳文件時臨時文件存放的位置
                form.uploadDir = "./uploads";
                // keepExtensions 屬性設置是否保留上傳文件的擴展名, 默認爲 false
                form.keepExtensions = true;
                
                // 開始解析
                form.parse(req, function(err, fields, files) {
                    if (err) {
                        var message = "文件解析失敗";
                    } else {
                        var message = "文件上傳成功";
                    }
                    res.writeHead(200, { "Content-Type": "text/plain;charset=utf-8" });
                    res.write(message);
                    res.end();
                })
            }
            break;
        default:
            readStaticFile(res, "./404.html");
    }
});
複製代碼

服務器代碼寫完後, 讓咱們寫 upload.html 文件:

<body>
    <form action="upload" enctype="multipart/form-data" method="post">
        <input type="file" name="upload" />
        <br />
        <input type="submit" value="提交">
    </form>
</body>
複製代碼

注意要設置的表單的編碼方式 enctype"multipart/form-data" 表單數據默認的編碼方式爲 "application/x-www-form-urlencoded" 不可用於文件上傳. 在使用包含文件上傳控件的表單時,必須使用 "multipart/form-data" 這個值.

寫好後, 運行服務器, 上傳一張你喜歡的照片, 看看結果是什麼. 如下是個人操做:

Screen Shot 2018-10-09 at 11.54.45 AM

Screen Shot 2018-10-09 at 11.55.17 AM

能夠看到照片已經上傳到了 uploads 目錄下.

GET vs POST

前面分別用 GET 和 POST 方法提交了表單, 那麼這兩種方法到底區別是什麼呢?

先來看看 MDN 對這兩個方法的定義:

  • 『 HTTP GET 方法 』: 請求指定的資源. 使用 GET 的請求應該只用於獲取數據
  • 『 HTTP POST 方法 』: 發送數據給服務器

上面說的已經很簡潔, 當你想要請求服務器上的資源時用 GET 方法. 發送數據時用 POST 方法. 像我以前用 GET 方法提交登陸信息, 是不符合規範的.  實際開發中, 這種行爲不容許出現.

說完定義, 讓咱們再來看看這兩種方法在表現上有什麼不一樣.

  • 善於觀察的你必定已經發現, GET 提交的表單數據顯式地添加在了請求 URL 的查詢字符串中. 而 POST 把提交的數據放置在了請求體中. 這也體現出爲何 GET 不能用於傳輸數據, 你總不但願你的帳號和密碼這麼明顯地暴露在 URL 裏吧.

  • 由於瀏覽器對 URL 的長度都有限制, 因此 GET 方式提交的數據是有大小限制的, 通常不超過 1024 字節. 理論上講, POST 提交數據時沒有大小限制的. 但出於性能考慮, 服務器接收時可能對 POST 傳輸的數據大小進行限制.


😆 好啦,今天的分享就告一段落啦。下一篇中,我會介紹 "模板引擎"

若是喜歡的話就點個關注吧!O(∩_∩)O 謝謝各位的支持❗️

相關文章
相關標籤/搜索