摘要:的官方描述是是一個獨立于中間件和路由的實例,你可以將看作是只能執行執行中間件和路由的小心應用。最大的不同在于只能已模塊形式存在并不能獨立運行。另外,加密的公鑰也被稱為證書。客戶端在拿到公鑰證書后會向這樣的證書頒發機構進行驗證。
作為 Express 中的最大特點之一,路由讓你可以將不同的請求映射到不同的中間件中。這一章我們將會深入學習這部分的內容,另外還包括如何在 Express 使用 HTTPS 以及部分 Express 4 中的新特性等等。當然,學習過程還是通過示例應用和代碼的形式進行展現的。
什么是路由?假設,現在你嘗試通過 example.com/someone 訪問某人的推特或者微博主頁,你會發現該請求的 HTTP 內容大致如下:
GET /someone http/1.1
其中包含了 HTTP 請求使用的方法(GET),URI 信息(/someone) 以及 HTTP 協議版本 (1.1)。Express 中的路由就是負責將其中的 HTTP 方法和 URI 這對組合映射到對應的中間件。簡單說就是, /about_me 的GET 請求會執行某個中間件而對于 /new_user 的 POST 請求則執行另一個中間件。
下面我們通過一個簡單示例來看看到底路由時如何工作的。
路由的一個簡單示例下面我們就對 example.com/someone 請求進行一個簡單的實現,代碼如下:
var express = require("express"); var app = express(); app.get("/someone", function(request, response) { response.send(" Welcome to someone"s homepage! "); }); app.use(function(request, response) { response.status(404).send("Page not found!"); }); app.listen(3000);
上面代碼中真正有價值的是第三行:當你通過 HTTP 的 GET 方法對 /someone 發起請求時,程序會執行該中間件中的代碼,其他請求則會被忽略并跳轉到下一個中間件。
路由的特性從工作原理來說:路由就是通過對 HTTP 方法和的 URI 的組合進行映射來實現對不同請求的分別處理。當然,除了上面那種最簡單的使用方式之外,Express 的路由還有更多實用的使用技巧和方法。
含參的通配路由注意:在其它一些框架中(例如,Ruby on Rails )會有一個專門的文件進行路由管理,但是 Express 中并沒有這樣的規定,你可以將路由按模塊分開管理。
在上面的使用方式中使用的是全等判斷來進行路由匹配的。雖然對于 /someone 這類非常管用,但是對于形如 /users/1、/users/2 這類 RESTful 路由就明顯不那么友好了。因為如果將后者路由一一列出的話,不管是從工作量還是后期維護來說都是非常差開發體驗。針對這種情況,我們可以使用 Express 中含參的通配路由來解決。
該方法的工作原理就是,在路由中使用參數進行通配表示。而該參數所表示的具體數值會在變量 params 中獲取到,下面是簡單的代碼示例:
app.get("/users/:userid", function(req, res) { // 將userId轉換為整型 var userId = parseInt(req.params.userid, 10); // ... });
這樣 RESTful 風格的動態路由就完全可以通過這種含參的通配路由進行處理。那么無論是 /users/123 還是 /users/8 都會被映射到同一中間件。需要注意的是:雖然 /users/ 或者 /users/123/posts 不會被匹配,但是 /users/cake 和 /users/horse_ebooks 確會被匹配到。所以,如果實現更精準的路由匹配的話就需要使用其他方式了。
使用正則表達式匹配路由針對上面的問題,我們可以使用正則來對路由進行更精準的匹配。
注意:如果你對正則表達式部分的內容不熟悉的話,那么我建議你去查看該文檔。
假設現在我們只需要匹配 /users/123 和 /users/456 這種通配參數為數字的動態路由的同時忽略其他路由格式,那么可以將代碼改為:
app.get(/^/users/(d+)$/, function(req, res) { var userId = parseInt(req.params[0], 10); // ... });
通過正則表達式代碼對通配參數作為了嚴格限定:該參數必須是數字類型。
正則表達式可能閱讀起來并不是很友好,但是它卻可以實現對復雜路由匹配規則的準確定義。例如,你想匹配路由 /users/100-500 這類表示某個用戶范圍的列表頁面,那么該正則如下:
app.get(/^/users/(d+)-(d+)$/, function(req, res) { var startId = parseInt(req.params[0], 10); var endId = parseInt(req.params[1], 10); // … });
甚至你還可以作出更復雜的正則匹配路由定義,例如:匹配某個包含特定 UUID 的路由。UUID 是一長串 16 進制的字符串,大致如下:
xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
如果,其中的 x 表示任何 16 進制數字,而 y 只能是 8,9,A 或者 B 。那么該路由的正則匹配就是:
var horribleRegexp = /^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})$/i; app.get(horribleRegexp, function(req, res) { var uuid = req.params[0]; // ... });
還有更多的使用示例就不一一列舉了。這里只需記住一點:正則表達式可以讓你的路由匹配定義更上一層樓。
捕獲查詢參數另一種常用的動態傳入 URL 參數的方法就是通過查詢字符串(query string)。例如,當你使用谷歌搜索 javascript-themed burrito 時,你可以會發現對應的 URL 可能是 https://www.google.com/search... 。
如果 Google 是用 Express 進行實現的話(實際上不是),那么可以這樣來獲取用戶傳入的信息:
app.get("/search", function(req, res) { // req.query.q == "javasript-themed burrito" // ... });
需要注意的是:查詢參數中存在其實存在著類型安全問題。例如:如果你訪問 ?arg=something 那么 req.query.arg 就是一個字符串類型,但是如果訪問的是 ?arg=something&arg=somethingelse 的話 req.query.arg 就變為了一個數組類型。簡單來說:不要輕易的斷定查詢參數的類型。
使用 Router 劃分你的 app伴隨著應用的擴張,程序中產生的路由也會越來越多。而對這些龐大的路由進行管理并不是一件輕松的事,不過好在 Express 4 新增了 Router (可以理解為路由器)特性。Router 的官方描述是:
Router 是一個獨立于中間件和路由的實例,你可以將 Router 看作是只能執行執行中間件和路由的小心應用。而 Express 程序本身就內置了一個 Router 實例。
Router 的行為與中間件類型,它可以通過 .use() 來調用其他的 Router 實例。
換句話就是,可以使用 Router 將應用劃分為幾個小的模塊。雖然對于一些小型應用來說這樣做可能是過度設計,但是一旦 app.js 中的路由擴張太快的話你就可以考慮使用 Router 進行模塊拆分了。
注意:程序越大 Router 發揮的作用就越明顯。雖然這里我不會編寫一個大型應用程序,但是你可以在你的腦海中對下面的示例功能進行無限擴張。
var express = require("express"); var path = require("path"); // 引入 API Router var apiRouter = require("./routes/api_router"); var app = express(); var staticPath = path.resolve(__dirname, "static"); app.use(express.static(staticPath)); // API Router 文件的調用 app.use("/api", apiRouter); app.listen(3000);
如上所示,Router 的使用方式和之前的中間件非常類似。其實 Router 本質上就是中間件。在代碼中我們將所有 /api 開頭的 URL 全部轉發到了 apiRouter 中了, 這意味著 /api/users 和 /api/message 的處理都會在 apiRouter 中進行。
下面就是 api_router.js 文件的一個簡單代碼示例:
var express = require("express"); var ALLOWED_IPS = [ "127.0.0.1", "123.456.7.89" ]; var api = express.Router(); api.use(function(req, res, next) { var userIsAllowed = ALLOWED_IPS.indexOf(req.ip) !== -1; if(!userIsAllowed) { res.status(401).send("Not authorized!"); } else { next(); } }); api.get("/users", function(req, res) { /* ... */ }); api.post("/users", function(req, res) { /* ... */ }); api.get("/messages", function(req, res) { /* ... */ }); api.post("/messages", function(req, res) { /* ... */ }); module.exports = api;
其實 Router 與 app.js 在功能上沒有任何區別,都是處理中間件和路由。最大的不同在于:Router 只能已模塊形式存在并不能獨立運行。
參照示例,你可以在自己的應用中按模塊劃分出更多的 Router 。
靜態文件除非應用是純 API 服務,否則總可能需要發送靜態文件。這些文件可能是靜態圖片 CSS 樣式文件或者是靜態 HTML 文件。在前面文章的基礎之上,這部分將介紹更深入的部分內容。
靜態文件中間件因為前面章節對靜態文件中間件實現進行過詳細介紹,所以這里直接查看代碼:
var express = require("express"); var path = require("path"); var http = require("http"); var app = express(): // 設置你的靜態文件路徑 var publicPath = pathresolve(dirname, "public"); // 從靜態文件夾中發送靜態文件 app.use(express.static(publicPath)); app.use(function(request, response) { response.writeHead(200, { "Content-Type": "text/plain"}); reponse.end("Looks like you didn"t find a static file."); }); http.createServer(app).listen(3000);修改靜態文件的 URL
通常情況下,我們會把站點的靜態文件 URL 路徑直接掛在域名后面,例如:http://jokes.edu 站點中的 jokes.txt 文件 URL 樣式應該是 http://jokes.edu/jokes.txt 。
當然,你可可以按照自己的習慣給這些靜態文件提供 URL 。例如,將一些無序但有趣的圖片存放在文件夾 offensive 中并將其中圖片的 URL 設置為 http://jokes.edu/offensive/ph... 這種形式。那么該樣式 URL 如何實現呢?
在 Express 中,我們可以使用指定前綴的中間件來對靜態文件 URL 進行自定義。所以上面問題的代碼實現如下:
// ... var photoPath = path.resolve(__dirname, "offensive-photos-folder"); app.use("/offensive", express.static(photoPath)); // ...
這樣你所有靜態文件的 URL 都可以實現自定義了,而不是粗暴的直接掛在域名后面了。其實除了靜態中間件和前面 Router 外,其它中間件同樣可以指定 URL 前綴。
多個靜態文件夾的路由實際上砸真實項目中可能戶存在多個靜態文件夾,例如:一個存放 CSS 等公用文件的 public 文件夾,一個存放用戶上傳文件的 user_uploads 文件夾。那么對于這種情況又該如何處理呢?
首先 epxress.static 本身作為中間件是可以在代碼中多次調用的:
// ... var publiscPath = path.resolve(__dirname, "public"); var userUploadPath = path.resove(__dirname, "user_uploads"); app.use(express.static(publicPath)); app.use(express.static(userUploadsPath)); // ...
接下來,我們通過四個模擬場景看看上面代碼是如何工作的:
用戶請求的資源兩個文件夾里都沒有則上面兩個中間件都會被跳過執行。
用戶請求的資源只在 public 里面則第一個中間件響應執行并返回。
用戶請求的資源只在 user_uploads 里面則第一個中間件被跳過而第二個得道執行。
用戶請求的資源在兩個文件夾中都存在則第一個中間件響應執行并返回,第二個不會得到執行。
對于第四章情況,如果該資源是相同的還好說,但是一旦只是資源同名就存在明顯錯誤了。為此,我們依舊可以使用 URL 前綴來應對:
// ... app.use("/public", express.static(publicPath)); app.use("/uploads", express.static(userUploadsPath)); // ...
這樣對于同名文件 image.jpg Express 會將其分別映射到 /public/image.jpg 和 /uploads/image.jpg 。
路由到靜態文件映射在程序中有可能還存在對動態路由請求響應靜態文件情形,例如,當用戶訪問 /users/123/profile_photo 路徑時程序需要發送該用戶的圖片。靜態中間件本身時無法處理該需求,不過好在 Express 可以使用與靜態中間件類似的機制來處理這種情況。
假設當有人發起 /users/:userid/profile_photo 請求時,我們都需要響應對應 userid 用戶的圖片。另外,假設程序中存在一個名為 getProfilePhotoPath 的函數,該函數可以根據 userid 獲取圖片的存儲路徑。那么該功能的實現代碼如下:
app.get("/users/:userid/profile_photo", function(req, res) { res.sendFile(getProfilePhotoPath(req.params.userid)); });
僅僅只需指定路由然后通過 sendFile 函數,我們就可以完成該路由對應文件的發送任務。
在 Express 使用 HTTPSHTTPS 是在 HTTP 基礎上添加了一個安全層,通常情況下該安全層被稱為 TLS 或者 SSL 。雖然兩個名字可以互換,但是 TSL 在技術上涵蓋了 SSL。
這里并不會介紹 HTTPS 復雜的 RSA 加密數學原理(歐拉函數)。簡單來說 HTTPS 的加密過程就是:所有的客戶端都使用服務端公開的公鑰加密請求信息,然后服務端使用私鑰對加密后內容進行解密。這樣就能在某種程度上防止信息被竊聽。另外,加密的公鑰也被稱為證書。客戶端在拿到公鑰證書后會向 Google 這樣的證書頒發機構進行驗證。
注意:類似 Heroku 這樣的虛擬主機商已經提供了 HTPPS 服務,所以這部分內容只在你需要自己實現 HTTPS 時才派得上用場。
首先,我們通過 OpenSSL 生成自簽名的公鑰和私鑰。Windows 系統可以使用去官網獲取 OpenSSL 安裝文件,Linux 可以使用保管理器進行安裝,而 macOS 系統已經預裝過了。通過 openssl version 驗證系統是否成功安裝了 OpenSSL, 確保安裝后輸入下面兩個命令:
openssl genrsa -out privatekey.pem 1024
openssl req -new -key privatekey.pem -out request.pem
第一個命令會生成名為 privatekey.pem 的私鑰。第二個命令會讓你輸入一些信息,然后使用 privatekey.pem 生成簽名的證書請求文件 request.pem 。然后你就可以去證書請求機構申請一個加密的公鑰證書。雖然大部分證書都是收費的,但是你還是可以去 letsencrypt 申請免費版本證書。
一旦獲取了 SSL 證書文件,你就可以使用 Node 內置的 HTTPS 模塊了,代碼如下:
var express = require("express"); var https = require("https"); var fs = require("fs"); var app = express(); // ... 定義你的app ... // 定義一個對象來保存證書和私鑰 var httpsOptions = { key: fs.fs.readFileSync("path/to/private/key.pem"); cert: fs.fs.readFileSync("path/to/certificate.pem"); } https.createServer(httpsOptions, app).listen(3000);
除了配置私鑰和公鑰證書參數之外,其他部分與之前 HTTP 模塊的使用時一致的。當然,如果你想同時支持 HTTP 和 HTTPS 協議的話也是可以的:
var express = require("express"); var http = require("http"); var https = require("https"); var fs = require("fs"); var app = express(); // ... 定義你的app ... var httpsOptions = { key: fs.readFileSync("path/to/private/key.pem"), cret: fs.readFileSync("path/to/certificate.pem") }; http.createServer(app).listen(80); https.createServer(httpsOptions, app).listen(443);
需要注意的是 HTTP 和 HTTPS 協議 同時開啟時需要使用不同的端口號。
路由的應用示例接下來,我們搭建一個簡單的 web 程序鞏固一下這章所學的路由內容。該應用的主要功能是通過美國的 ZIP 郵政編碼返回該地區的溫度。
示例使用的是美式郵政編碼,所以該示例只能在作者所在的美國正常使用。當然,你完全可以使用 H5 的 Geolocation API 對其進行改造。
示例主要包含兩個部分:
一個靜態頁,用于詢問用戶的 ZPI 編碼。用戶輸入編碼后會通過 AJAX 發送異步請求獲取天氣。
解析獲得 JSON 格式數據,并將結果映射 ZIP 編碼對應的動態路由上。
準備工作在示例中需要使用的 Node 類庫有:Express、ForecastIO (用于獲取天氣數據)、Zippity-do-dah ( 將ZIP編碼轉為緯度/經度 )、EJS 模版引擎。
新建應用文件夾,并復制下面內容到 package.json 文件中:
{ "name": "temperature-by-zip", "private": true, "scripts": { "start": "node app.js" }, "dependencies": { "ejs": "^2.3.1", "express": "^5.0.0", "forecastio": "^0.2.0", "zippity-do-dah": "0.0.x" } }
使用 npm install 命令完成依賴項的安裝,并新建兩個文件夾:public 和 views。另外,示例程序還會用到 jQuery 和名為 Pure 的 CSS 框架。最后,你需要去 Forecast.io 官網 注冊開發賬號獲取 API 接口密鑰。
主入口代碼準備工作完成后,接下來就是編寫代碼了。這里我們從程序的主入口開始編寫 JavaScript 代碼,新建 app.js 文件并拷貝代碼:
var path = require("path"); var express = require("express"); var zipdb = require("zippity-do-dah"); var ForecastIo = require("forecastio"); var app = express(); var weather = new ForecastIo("你的FORECAST.IO的API密鑰"); app.use(express.static(path.resolve(__dirname, "public"))); app.set("views", path.resolve(__dirname, "views")); app.set("view engine", "ejs"); app.get("/", function(req, res) { res.render("index"); }); app.get(/^/(d{5})$/, function(req, res, next) { var zipcode = req.params[0]; var location = zipdb.zipcode(zipcode); if (!location.zipcode) { next(); return; } var latitude = location.latitude; var longitude = location.longitude; weather.forecast(latitude, longitude, function(err, data) { if (err) { next(); return; } res.json({ zipcode: zipcode, temperature: data.currently.temperature }); }); }); app.use(function(req, res) { res.status(404).render("404"); }); app.listen(3000);
接下來就是使用 EJS 引擎編寫視圖文件了。
兩個視圖示例應用中會有兩個視圖:404 頁面和主頁。為了盡可能保持頁面風格的統一,這里將會使用到模版技術。首先動手實現通用的 header 和 footer 模版。
其中 views/header.ejs 文件中的代碼如下:
Temperature by ZIP code
緊接著就是 views/footer.ejs :
完成上面通用模版之后,下面就可以實現 404 頁面 views/404.ejs 了:
<% include header %>404 error! File not found.
<% include footer %>
同樣的,主頁 views/index.ejs 代碼如下:
<% include header %><% include footer %>What"s your ZIP code?
上面頁面代碼中使用了一些 Pure 框架里的樣式來優化界面 UI 。
除此之外,我們還需要在 public/main.css 指定頁面布局:
html { display: table; width: 100%; height: 100%; } body { display: table-cell; vertical-align: middle; text-align: center; }
在該樣式文件中,我們將頁面內容同時設置為了水平和垂直居中。
最后拷貝下面的代碼,把缺失的 public/main.js 補充完整。
$(function() { var $h1 = $("h1"); var $zip = $("input[name="zip"]"); $("form").on("submit", function(event) { // 禁止表單的默認提交 event.preventDefault(); var zipCode = $.trim($zip.val()); $h1.text("Loading..."); var request = $.ajax({ url: "/" + zipCode, dataType: "json" }); request.done(function(data) { var temperature = data.temperature; $h1.html("It is " + temperature + "° in " + zipCode + "."); }); request.fail(function() { $h1.text("Error!"); }); }); });運行示例程序
結束所有編碼任務后,下面我們通過 npm start 運行示例程序。當你訪問 http://localhost:3000 并輸入 ZIP 編碼后界面如下:
在這個簡單的示例中,我們使用了 Express 中的路由特性,另外還使用了 EJS 模版引擎來編寫視圖文件。你可以在此基礎上繼續發揮想象力完善該示例。
總結在本章中,我們學到了:
從概念上知道了什么是路由:進行 URL 和代碼的映射的工具。
簡單的路由以及常用映射處理。
獲取路由中的參數。
Express 4 路由模塊的新特性。
將路由應用到中間件處理。
如何在 Express 中使用 HTTPS。
原文地址
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/84850.html
摘要:前言要做一個全沾的工程師,對于后端和數據庫來說,即使不認識也要見個面的。基本了解的概念就好,主要是安裝上數據庫,并進行簡單的增刪操作。 前言:要做一個全沾的工程師,對于后端和數據庫來說,即使不認識也要見個面的。本文給的例子很簡單,也貼出來源碼,只要一步步下來,就可以跑起來啦~~~ 思考一個需求:做一個登錄頁面,自己搭建服務和數據庫,將用戶輸入的登錄信息保存到數據庫如何完成呢:首先選擇...
摘要:多一個技能多一條出路,祝你在自學道路上越走越好,掌握自己的核心技能,不只是優秀,還要成為不可替代的人 NodeJs+Express+Mysql + Vuejs 項目實戰 最近準備寫一系列文章,全面講述如何基于NodeJs + Express + Mysql + Vuejs 從零開發前后端完全分離項目; 文筆及技術可能在某些方面欠佳,請您指正,共同學習進步 前端:Vuejs全家桶 后端:...
摘要:上面代碼的關鍵是模塊的方法,表示生成一個服務器實例。該方法接受一個回調函數,該回調函數的參數,分別為代表請求和回應的對象和對象。循環請求過來時放入數組的對象,當請求方法和路徑與對象中的一致時,執行回調方法。 目錄 概述 hello-world 實例 運行原理 多路由多回調以及中間件 概述 Express是一個基于 Node.js 平臺,快速、開放、極簡的 web 開發框架。主要有 ...
閱讀 1631·2021-10-14 09:43
閱讀 5536·2021-09-07 10:21
閱讀 1280·2019-08-30 15:56
閱讀 2131·2019-08-30 15:53
閱讀 1236·2019-08-30 15:44
閱讀 2013·2019-08-30 15:44
閱讀 1324·2019-08-29 17:24
閱讀 757·2019-08-29 15:19