在 myApp.js
文件的前兩行中,創(chuàng)建一個(gè) Express 應(yīng)用對(duì)象很簡(jiǎn)單。 這個(gè)對(duì)象有幾種方法,一個(gè)基礎(chǔ)的方法是 app.listen(port)
。 它處于運(yùn)行狀態(tài)時(shí)告訴服務(wù)器監(jiān)聽(tīng)指定的端口。 出于測(cè)試的原因,需要應(yīng)用在后臺(tái)運(yùn)行,所以在 server.js
中已經(jīng)添加了這個(gè)方法。
創(chuàng)新互聯(lián)專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于做網(wǎng)站、網(wǎng)站制作、平山網(wǎng)絡(luò)推廣、小程序制作、平山網(wǎng)絡(luò)營(yíng)銷、平山企業(yè)策劃、平山品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運(yùn)營(yíng)等,從售前售中售后,我們都將竭誠(chéng)為您服務(wù),您的肯定,是我們最大的嘉獎(jiǎng);創(chuàng)新互聯(lián)為所有大學(xué)生創(chuàng)業(yè)者提供平山建站搭建服務(wù),24小時(shí)服務(wù)熱線:028-86922220,官方網(wǎng)址:www.cdcxhl.com
在 Express 中,路由采用這種結(jié)構(gòu):app.METHOD(PATH, HANDLER)
, METHOD 是 http 請(qǐng)求方法的小寫(xiě)形式, PATH 是服務(wù)器上的相對(duì)路徑(它可以是一個(gè)字符串,甚至可以是正則表達(dá)式), HANDLER 是匹配路由時(shí) Express 調(diào)用的函數(shù), 處理函數(shù)采用這種形式:function(req, res) {...}
,其中 req 是請(qǐng)求對(duì)象,res 是響應(yīng)對(duì)象, 例如:
function(req, res) {
res.send('Response String');
} // “Response String”
app.get("/", function(req, res) {
res.send("Hello Express");
})
通過(guò) res.sendFile(path)
方法給請(qǐng)求響應(yīng)一個(gè)文件, 可以把它放到路由處理 app.get('/', ...)
中。 在后臺(tái),這個(gè)方法會(huì)根據(jù)想發(fā)送的文件的類型,設(shè)置適當(dāng)?shù)南㈩^信息來(lái)告訴瀏覽器如何處理它, 然后讀取并發(fā)送文件, 此方法需要文件的絕對(duì)路徑。 建議使用 Node. js 的全局變量 __dirname
來(lái)計(jì)算出這個(gè)文件的絕對(duì)路徑:
absolutePath = __dirname + relativePath/file.ext
var express = require('express');
?
var app = express();
?
let absolutePath = __dirname + "/views/index.html";
?
app.get("/", function(req, res) {
res.sendFile(absolutePath);
});
?
module.exports = app;
HTML 服務(wù)器通常有一個(gè)或多個(gè)用戶可以訪問(wèn)的目錄。 可以將應(yīng)用程序所需的靜態(tài)資源 (樣式表、腳本、圖片) 放在那里。在 Express 中可以使用中間件 express.static(path)
來(lái)設(shè)置此功能,它的參數(shù) path
就是包含靜態(tài)資源文件的絕對(duì)路徑。
其實(shí),中間件就是一個(gè)攔截路由處理方法并在里面添加一些信息的函數(shù)。 使用 app.use(path, middlewareFunction)
方法來(lái)加載一個(gè)中間件, 它的第一個(gè)參數(shù) path
是可選的, 如果沒(méi)設(shè)置第一個(gè)參數(shù),那么所有的請(qǐng)求都會(huì)經(jīng)過(guò)這個(gè)中間件處理。
app.use("/public", express.static(__dirname + "/public"));
HTML 服務(wù)器提供 HTML 服務(wù),而 API 提供數(shù)據(jù)服務(wù)。 REST(REpresentational State Transfer)API 允許以簡(jiǎn)單的方式進(jìn)行數(shù)據(jù)交換,對(duì)于客戶端不必要知道服務(wù)器的細(xì)節(jié)。 客戶只需要知道資源在哪里(URL),以及想執(zhí)行的動(dòng)作(動(dòng)詞)。 GET 動(dòng)詞常被用來(lái)獲取無(wú)需修改的信息。 如今,網(wǎng)絡(luò)上的移動(dòng)數(shù)據(jù)首選格式是 JSON, 簡(jiǎn)而言之,JSON 是一種可以方便地用字符串表示 JavaScript 對(duì)象的方式,因此它很容易傳輸。
來(lái)創(chuàng)建一個(gè)簡(jiǎn)單的 API,創(chuàng)建一個(gè)路徑為 /json
且返回?cái)?shù)據(jù)是 JSON 格式的路由, 可以像之前那樣用 app.get()
方法來(lái)做。 然后在路由處理部分使用 res.json()
方法,并傳入一個(gè)對(duì)象作為參數(shù), 這個(gè)方法會(huì)結(jié)束請(qǐng)求響應(yīng)循環(huán)(request-response loop),然后返回?cái)?shù)據(jù)。 在后臺(tái),它把一個(gè)有效的 JavaScript 對(duì)象轉(zhuǎn)化為字符串,然后會(huì)設(shè)置適當(dāng)?shù)南㈩^來(lái)告訴瀏覽器:“這是一個(gè) JSON 數(shù)據(jù)”,最后將數(shù)據(jù)返回給客戶端。 一個(gè)有效的對(duì)象通常是這種結(jié)構(gòu):{key: data}
, data
可以是數(shù)字、字符串、嵌套對(duì)象或數(shù)組, data
也可以是變量或者函數(shù)返回值,在這種情況下,它們先求值再轉(zhuǎn)成字符串。
app.get("/json", function(req, res) {
res.json({
"message": "Hello json"
});
})
當(dāng)向路由 /json
發(fā)送 GET 請(qǐng)求,將對(duì)象 {"message": "Hello json"}
以 JSON 格式返回給客戶端, 瀏覽器訪問(wèn) your-app-url/json
時(shí),應(yīng)該在屏幕上看到這個(gè)消息。
.env
文件是一個(gè)用于將環(huán)境變量傳給應(yīng)用程序的隱藏文件, 這是一個(gè)除了開(kāi)發(fā)者之外沒(méi)人可以訪問(wèn)的私密文件,它可以用來(lái)存儲(chǔ)保密或者隱藏的數(shù)據(jù), 例如,它可以存儲(chǔ)第三方服務(wù)的 API 密鑰或者數(shù)據(jù)庫(kù) URI, 也可以使用它來(lái)存儲(chǔ)配置選項(xiàng), 通過(guò)設(shè)置配置選項(xiàng),改變應(yīng)用程序的行為,而無(wú)需重寫(xiě)一些代碼。
在應(yīng)用程序中通過(guò) process.env.VAR_NAME
訪問(wèn)到環(huán)境變量。 process.env
對(duì)象是 Node 程序中的一個(gè)全局對(duì)象,可以給這個(gè)變量傳字符串。 習(xí)慣上,變量名全部大寫(xiě),單詞之間用下劃線分隔。 .env
是一個(gè) shell 文件,因此不需要用給變量名和值加引號(hào)。 還有一點(diǎn)需要注意,當(dāng)你給變量賦值時(shí)等號(hào)兩側(cè)不能有空格,例如:VAR_NAME=value
。 通常來(lái)講,每一個(gè)變量定義會(huì)獨(dú)占一行。
app.get('/json', (function(req,res) {
const mySecret = process.env['MESSAGE_STYLE'];
if( mySecret === "uppercase" ){
res.json({"message" : "Hello json".toUpperCase() });
} else {
res.json({"message" : "Hello json" });
}
?
}))
添加一個(gè)環(huán)境變量作為配置選項(xiàng)。
在項(xiàng)目根目錄創(chuàng)建一個(gè) .env
文件,并存儲(chǔ)變量 MESSAGE_STYLE=uppercase
。
當(dāng)向 /json
發(fā) GET 請(qǐng)求時(shí),如果 process.env.MESSAGE_STYLE
的值為 uppercase
,那么上一次挑戰(zhàn)中的路由處理程序返回的對(duì)象的消息則應(yīng)該大寫(xiě)。 響應(yīng)對(duì)象應(yīng)該是 {"message": "Hello json"}
or {"message": "HELLO JSON"}
,取決于 MESSAGE_STYLE
的值。
注意: 在本地工作,需要 dotenv
包。 它將環(huán)境變量從的 .env
文件加載到 process.env
中。 使用 npm install dotenv
安裝它。 然后,在 myApp.js
文件的頂部,使用 require('dotenv').config()
導(dǎo)入和加載變量。
中間件函數(shù)是一個(gè)接收 3 個(gè)參數(shù)的函數(shù),這 3 個(gè)參數(shù)分別是:請(qǐng)求對(duì)象、響應(yīng)對(duì)象和在應(yīng)用的請(qǐng)求-響應(yīng)循環(huán)中的下一個(gè)函數(shù)。 中間件函數(shù)執(zhí)行一些可能對(duì)應(yīng)用程序產(chǎn)生一些效果的代碼,通常還會(huì)在請(qǐng)求對(duì)象或者響應(yīng)對(duì)象里添加一些信息, 它們也可以在滿足某些條件時(shí)通過(guò)發(fā)送響應(yīng)來(lái)結(jié)束循環(huán), 如果在它們完成時(shí)沒(méi)有發(fā)送響應(yīng),那么就會(huì)開(kāi)始執(zhí)行堆棧中的下一個(gè)函數(shù), next()
將觸發(fā)調(diào)用第 3 個(gè)參數(shù)。
請(qǐng)看以下示例:
function(req, res, next) {
console.log("I'm a middleware...");
next();
}
假設(shè)在某個(gè)路由上安裝了這個(gè)中間件函數(shù), 當(dāng)一個(gè)請(qǐng)求與路由匹配時(shí),它會(huì)顯示字符串“I’m a middleware…”,然后它執(zhí)行堆棧中的下一個(gè)函數(shù)??梢允褂?app.use(
方法。 在這種情況下,該函數(shù)將對(duì)所有請(qǐng)求執(zhí)行,但也可以設(shè)置更具體的條件來(lái)執(zhí)行, 例如,如果你希望某個(gè)函數(shù)只針對(duì) POST 請(qǐng)求執(zhí)行,可以使用 app.post(
方法。 所有的 HTTP 動(dòng)詞(GET、DELETE、PUT……)都存在類似的方法。
app.use(function(req, res, next) {
console.log(req.method + " " + req.path + " - " + req.ip);
next();
})
注意: Express 按照函數(shù)在代碼中出現(xiàn)的順序來(lái)執(zhí)行, 中間件也是如此。 如果想讓中間件函數(shù)適用于所有路由,那么應(yīng)該在路由之前配置好中間件。
使用 app.METHOD(path, middlewareFunction)
可以在指定的路由掛載中間件, 也可以在路由定義中鏈?zhǔn)秸{(diào)用中間件。
請(qǐng)看以下示例:
app.get('/user', function(req, res, next) {
req.user = getTheUserSync();
next();
}, function(req, res) {
res.send(req.user);
});
此方法可用于將服務(wù)操作拆分為較小的單元, 這可以讓?xiě)?yīng)用擁有更好的結(jié)構(gòu),也便于在不同的位置上復(fù)用代碼; 此方法還可用于對(duì)數(shù)據(jù)執(zhí)行某些驗(yàn)證。 可以在每一個(gè)中間件堆棧中,阻止當(dāng)前鏈的執(zhí)行,并將控制權(quán)傳遞給專門(mén)設(shè)計(jì)用于處理錯(cuò)誤的函數(shù); 或者可以將控制權(quán)傳遞給下一個(gè)匹配的路由,以處理特殊情況。
在構(gòu)建 API 時(shí),要讓用戶說(shuō)明他們想從服務(wù)中獲取什么。 舉個(gè)例子,如果客戶請(qǐng)求數(shù)據(jù)庫(kù)中存儲(chǔ)的用戶信息,需要一種方法讓開(kāi)發(fā)者知道用戶對(duì)哪個(gè)數(shù)據(jù)庫(kù)用戶項(xiàng)感興趣, 使用路由參數(shù)可以實(shí)現(xiàn)這個(gè)需求。 路由參數(shù)是由斜杠(/)分隔的 URL 命名段, 每一小段能捕獲與其位置匹配的 URL 部分的值, 捕獲的值能夠在 req.params
對(duì)象中找到。
路由地址:'/user/:userId/book/:bookId' 實(shí)際請(qǐng)求 URL:'/user/546/book/6754' req.params:{userId: '546', bookId: '6754'}
app.get("/:word/echo", (req, res) => {
const { word } = req.params;
res.json({
echo: word
});
});
從客戶端獲取輸入的另一種常見(jiàn)方式是使用查詢字符串對(duì)路由路徑中的數(shù)據(jù)進(jìn)行編碼, 查詢字符串使用標(biāo)記(?)分隔,并且包含鍵值對(duì) field=value, 每對(duì)鍵值使用連字號(hào)(&)分隔。 Express 能夠從查詢字符串中解析這些數(shù)據(jù),并且把它放到 req.query
對(duì)象中。 有些字符(如百分號(hào)(%))不能在出現(xiàn)在 URL 中,它們?cè)诎l(fā)送前必須以不同的格式進(jìn)行編碼。 如果使用 JavaScript 的 API,可以用特定的方法來(lái)編碼/解碼這些字符。
路由地址:'/library' 實(shí)際請(qǐng)求 URL:'/library?userId=546&bookId=6754' req.query:{userId: '546', bookId: '6754'}
注意: 在后面的練習(xí)中,我們將向相同的路由路徑 /name
發(fā)送 POST 請(qǐng)求來(lái)接收數(shù)據(jù)。 如果愿意,可以使用app.route(path).get(handler).post(handler)
這中寫(xiě)法, 這種語(yǔ)法允許在同一路徑路由上鏈?zhǔn)秸{(diào)用不同的請(qǐng)求方法, 可以節(jié)省一點(diǎn)打字時(shí)間,也可以讓代碼看起來(lái)更清晰。
app.get("/name", function(req, res) {
res.json({
name: `${req.query.first} ${req.query.last}`
});
})
除了 GET 還有另一個(gè)常見(jiàn)的 HTTP 動(dòng)詞,即 POST。 POST 是使用 HTML 表單發(fā)送客戶端數(shù)據(jù)的默認(rèn)方法。 在 REST 規(guī)范中,POST 常用于發(fā)送數(shù)據(jù)以在數(shù)據(jù)庫(kù)中創(chuàng)建新項(xiàng)目(新用戶或新博客文章)。 在這個(gè)項(xiàng)目中沒(méi)有使用數(shù)據(jù)庫(kù),但下面將學(xué)習(xí)如何處理 POST 請(qǐng)求。
在這種類型的請(qǐng)求中,數(shù)據(jù)不會(huì)出現(xiàn)在 URL 中,而是隱藏在請(qǐng)求正文中。 請(qǐng)求正文也是 HTML 請(qǐng)求的一部分,被稱為負(fù)載。 即使數(shù)據(jù)在 URL 中是不可見(jiàn)的,也不意味著它是私有的。 要了解原因,請(qǐng)觀察 HTTP POST 請(qǐng)求的原始內(nèi)容:
POST /path/subpath HTTP/1.0
From: john@example.com
User-Agent: someBrowser/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 20
?
name=John+Doe&age=25
正如你所看到的,正文被編碼成類似查詢字符串的形式, 這是 HTML 表單使用的默認(rèn)格式。 可以通過(guò) Ajax 使用 JSON 來(lái)處理具有更復(fù)雜結(jié)構(gòu)的數(shù)據(jù)。 還有另一種類型的編碼:multipart/form-data, 它被用來(lái)上傳二進(jìn)制文件。 使用 URL 編碼請(qǐng)求正文。 要解析來(lái)自 POST 請(qǐng)求的數(shù)據(jù),必須安裝 body-parser
包, 這個(gè)包包含一套可以解碼不同格式數(shù)據(jù)的中間件。
var express = require('express');
var bodyParser = require("body-parser");
var app = express();
?
?
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
在路徑 /name
掛載一個(gè) POST 處理方法, 和前面一樣, 我們已經(jīng)在 html 首頁(yè)準(zhǔn)備了一份表單, 它將提交與練習(xí) 10 相同的數(shù)據(jù)(查詢字符串), 如果 body-parser 正確配置好了,那么就可以在 req.body
對(duì)象中找到請(qǐng)求的參數(shù)。 來(lái)看看一個(gè)常規(guī)的例子:
路由:POST '/library' URL 編碼的請(qǐng)求正文:userId=546&bookId=6754 req.body:{userId: '546', bookId: '6754'}
響應(yīng)和前面一樣的 JSON 對(duì)象 {name: 'firstname lastname'}
。 可以使用首頁(yè)應(yīng)用提供的 html 表單,來(lái)測(cè)試 API 是否正常工作。
提示:除了 GET 和 POST,還有其他幾種 http 方法。 按照慣例,http 動(dòng)詞和在服務(wù)端執(zhí)行的某種操作之間有對(duì)應(yīng)關(guān)系, 這種對(duì)應(yīng)關(guān)系通常如下:
POST(有時(shí)候是 PUT)- 使用請(qǐng)求發(fā)送信息,以創(chuàng)建新資源;
GET - 讀取不用修改的已存在的資源;
PUT 或者 PATCH(有時(shí)候是 POST)- 發(fā)送數(shù)據(jù),以更新資源;
DELETE => 刪除一個(gè)資源。
var express = require('express');
var app = express();
var bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({extended: false}));
const path = require('path')
?
?
app.post("/name", function(req, res) {
var string = req.body.first + " " + req.body.last;
res.json({ name: string });
});
?
module.exports = app;