做爲還在漫漫前端學習路上的一位自學者。我以學習分享的方式來整理本身對於知識的理解,同時也但願可以給你們做爲一份參考。但願可以和你們共同進步,若有任何紕漏的話,但願你們多多指正。感謝萬分!css
在上一章, 咱們搭建了一個很是簡單的 "Hello World" 服務器. 在這一章裏, 咱們要繼續上一章所學的知識, 進一步嘗試搭建, 提供靜態資源的服務器.html
那先說什麼是 靜態資源, 它指的是不會被服務器的動態運行所改變或者生成的文件. 它最初在服務器運行以前是什麼樣子, 到服務器結束運行時, 它仍是那個樣子. 好比平時寫的 js
, css
, html
文件, 均可以算是靜態資源. 那麼很容易理解, 靜態資源服務器的功能就是向客戶端提供靜態資源.前端
話很少說, 開始寫代碼:node
首先咱們知道, 它先是一個 "服務器". 那根據上一章的所學, 咱們要先用 http
模塊建立一個 HTTP 服務器.npm
var http = require('http');
var server = http.createServer(function(req, res) {
// 業務邏輯, 等會兒再寫.
});
server.listen(3000, function() {
console.log("靜態資源服務器運行中.");
console.log("正在監聽 3000 端口:")
})
複製代碼
url 模塊 - 文檔json
有了 HTTP 服務器以後, 咱們就能夠獲取從客戶端發過來的 HTTP 請求了.api
請求報文中包含着請求 URL. 前文說過, URL 用於定位網絡上的資源. 客戶端經過 URL 來指明想要的服務器上資源. 那麼服務器爲了搞清楚客戶端到底想要什麼, 咱們須要處理和解析 URL. 在 Node.js 中, 咱們使用 url
模塊來完成這類操做.瀏覽器
咱們知道 URL 字符串是具備結構的字符串,包含多個意義不一樣的組成部分。 經過 url.parse()
函數, URL 字符串能夠被解析爲一個 URL 對象,其屬性對應於字符串的各組成部分。以下圖所示.bash
那麼回到咱們的靜態文件服務器代碼.:服務器
先在 http.createServer
函數被調用以前, 引入 url
模塊:
var url = require('url');
複製代碼
而後在 HTTP 服務器裏解析請求 URL. 客戶端發來的請求 URL 做爲屬性存放在 http.createServer
的回調函數參數所接收的請求對象裏, 屬性名爲 url
.
var server = http.createServer(function(req, res) {
var urlObj = url.parse(req.url);
});
複製代碼
接下來從解析後的 URL 對象 urlObj
裏取得請求 URL 中的路徑名(pathname). 路徑名保存在 pathname
屬性裏.
var server = http.createServer(function(req, res) {
var urlObj = url.parse(req.url);
var urlPathname = urlObj.pathname;
});
複製代碼
可是光有 URL 對象裏面的路徑名是不夠的. 咱們還須要得到目標文件在服務器中所在目錄的目錄名(dirname).
假如說咱們的項目結構是下面這樣的:
.
├── public
│ ├── index.css
│ └── index.html
└── server.js
複製代碼
咱們的服務器代碼寫在 server.js
文件裏. 客戶端想要請求保存在 public
目錄裏的 index.html
文件. 用戶在瀏覽器中輸入 URL 的時候, 他只知道他想要的文件叫 index.html
, 但這個文件在 HTTP 服務器所在的設備中的 『 絕對位置 』是不被知道的. 因此咱們須要讓 HTTP 服務器本身去處理這部分操做.
在這裏就須要使用 Node.js 自帶的 path
模塊. 其提供了一些工具函數,用於處理文件與目錄的路徑.
使用起來很簡單, 首先仍是在 http.createServer
函數被調用以前, 引入 path
模塊:
var path = require('path');
複製代碼
以後咱們用 path.join
這個方法來把 目標文件所在目錄的目錄名和請求 URL 中的路徑名合併起來. 在這個例子中, 客戶端能夠訪問的靜態文件所有在 public
這個目錄中, 而 public
目錄又在 server.js
文件所在的目錄中. server.js
中保存的是咱們的服務器代碼.
想要得到 server.js
所在目錄的在整個設備中的絕對路徑, 咱們能夠在服務器代碼中調用變量 __dirname
, 它是當前文件在被模塊包裝器包裝時傳入的變量, 保存了當前模塊的目錄名。
var server = http.createServer(function(req, res) {
var urlObj = url.parse(req.url);
var urlPathname = urlObj.pathname;
var filePathname = path.join(__dirname, "/public", urlPathname);
});
複製代碼
若是你想的話, 你能夠用 console.log(filePathname)
來看看服務器運行後, 從客戶端收到的請求 URL 會被轉換成什麼樣.
如今來到了最重要的一步, 讀取目標文件, 而且返回文件給客戶端.
咱們須要用 Node.js 自帶的 fs
模塊中的 fs.write
方法來實現這一步. 該方法第一個參數爲目標文件的路徑, 最後一個參數爲一個回調函數, 回調有兩個參數 (err, data),其中 data
是文件的內容, 若是發生錯誤的話 err
保存錯誤信息. fs.write
方法能夠在第二個參數中指定字符編碼, 若是未指定則返回原始的 buffer. 在這個例子中, 咱們不考慮這一項.
那麼具體代碼以下:
首先引入 fs
模塊, 我就不贅述了, 參照前面就能夠了. 下面是讀取文件的代碼.
var server = http.createServer(function(req, res) {
var urlObj = url.parse(req.url);
var urlPathname = urlObj.pathname;
var filePathname = path.join(__dirname, "/public", urlPathname);
fs.readFile(filePathname, (err, data) => {
// 若是有問題返回 404
if (err) {
res.writeHead(404);
res.write("404 - File is not found!");
res.end();
// 沒問題返回文件內容
} else {
res.writeHead(200);
res.write(data);
res.end();
}
})
});
複製代碼
如今咱們就實現了一個基本的『 靜態文件服務器 』能夠在容許客戶端請求保存在服務器中公開的靜態文件了. 你能夠嘗試啓動服務器, 而後讓瀏覽器中訪問 http://localhost:3000/index.html
. 個人效果以下:
MIME 文檔 - MDN Content-Type 文檔 - MDN
多用途 Internet 郵件擴展(MIME)類型是用一種標準化的方式來表示文檔的 "性質" 和 "格式"。 簡單說, 瀏覽器經過 MIME 類型來肯定如何處理文檔. 所以在響應對象的頭部設置正確 MIME 類型是很是重要的.
MIME 的組成結構很是簡單: 由類型與子類型兩個字符串中間用 '/'
分隔而組成, 其中沒有空格. MIME 類型對大小寫不敏感,可是傳統寫法都是小寫.
例如:
text/plain
: 是文本文件默認值。意思是 未知的文本文件 ,瀏覽器認爲是能夠直接展現的.text/html
: 是全部的HTML內容都應該使用這種類型.image/png
: 是 PNG 格式圖片的 MIME 類型.在服務器中, 咱們經過設置 Content-Type
這個響應頭部的值, 來指示響應回去的資源的 MIME 類型. 在 Node.js 中, 能夠很方便的用響應對象的 writeHead
方法來設置響應狀態碼和響應頭部.
假如咱們要響應給客戶端一個 HTML 文件, 那麼咱們應該使用下面這條代碼:
res.writeHead(200, {"Content-Type":"text/html"});
複製代碼
你會發現我在上面的靜態資源服務器的代碼中, 沒有設置響應資源的 MIME 類型. 但若是你試着運行服務器的話, 你會發現靜態資源也以正確方式被展現到了瀏覽器.
之因此會這樣的緣由是在缺失 MIME 類型或客戶端認爲文件設置了錯誤的 MIME 類型時,瀏覽器可能會經過查看資源來進行猜想 MIME 類型, 叫作 『 MIME 嗅探 』. 不一樣的瀏覽器在不一樣的狀況下可能會執行不一樣的操做。因此爲了保證資源在每個瀏覽器下的行爲一致性, 咱們須要手動設置 MIME 類型.
那麼首先咱們須要獲取到準備響應給客戶端的文件的 後綴名.
要作到這一步咱們須要使用 path
模塊的 parse
方法. 這個方法能夠將一段路徑解析成一個對象, 其中的屬性對應路徑的各個部位.
繼續再剛纔靜態文件服務器案例的代碼上添加:
var server = http.createServer(function(req, res) {
var urlObj = url.parse(req.url);
var urlPathname = urlObj.pathname;
var filePathname = path.join(__dirname, "/public", urlPathname);
// 解析後對象的 ext 屬性中保存着目標文件的後綴名
var ext = path.parse(urlPathname).ext;
// 讀取文件的代碼...
});
複製代碼
獲取了文件後綴以後, 咱們須要查找其對應的 MIME 類型了. 這一步能夠很輕鬆的使用第三方模塊 MIME 來實現. 你能夠自行去 NPM 上去查閱它的使用文檔.
對於咱們目前的需求來講, 只須要用到 MIME 模塊的 getType()
方法. 這個方法接收一個字符串參數 (後綴名), 返回其對應的 MIME 類型, 若是沒有就返回 null
.
使用的話, 首先要用 npm 安裝 MIME 模塊 ( 若是你還沒建立 package.json 文件的話, 別忘了先執行 npm init
)
npm install mime --save
複製代碼
安裝完畢. 引入模塊到服務器代碼中, 而後咱們就直接用剛剛得到的後綴去找到其對應的 MIME 類型
var mime = require('mime');
var server = http.createServer(function(req, res) {
var urlObj = url.parse(req.url);
var urlPathname = urlObj.pathname;
var filePathname = path.join(__dirname, "/public", urlPathname);
// 解析後對象的 ext 屬性中保存着目標文件的後綴名
var ext = path.parse(urlPathname).ext;
// 獲取後綴對應的 MIME 類型
var mimeType = mime.getType(ext);
// 讀取文件的代碼...
});
複製代碼
好了, 如今最重要的東西 MIME 類型咱們已經獲得了. 接下來只要在響應對象的 writeHead
方法裏設置好 Content-Type
就好了.
var server = http.createServer(function(req, res) {
// 代碼省略...
var mimeType = mime.getType(ext);
fs.readFile(filePathname, (err, data) => {
// 若是有問題返回 404
if (err) {
res.writeHead(404, { "Content-Type": "text/plain" });
res.write("404 - File is not found!");
res.end();
// 沒問題返回文件內容
} else {
// 設置好響應頭
res.writeHead(200, { "Content-Type": mimeType });
res.write(data);
res.end();
}
})
});
複製代碼
階段性勝利 ✌️ 如今運行服務器, 在瀏覽器裏訪問一下 localhost:3000/index.html
試試吧!
能夠看到如今 Content-Type
已經被正確設置了!
如今來看看你的代碼, 是否是開始感受有點亂糟糟的. 我想聰明的你已經發現, 整個靜態文件服務器的代碼就是在作一件事: 響應回客戶端想要的靜態文件. 這段代碼職責單一, 且複用頻率很高. 那麼咱們有理由將其封裝成一個模塊.
具體的過程我就不贅述了. 如下是個人模塊代碼:
// readStaticFile.js
// 引入依賴的模塊
var path = require('path');
var fs = require('fs');
var mime = require('mime');
function readStaticFile(res, filePathname) {
var ext = path.parse(filePathname).ext;
var mimeType = mime.getType(ext);
// 判斷路徑是否有後綴, 有的話則說明客戶端要請求的是一個文件
if (ext) {
// 根據傳入的目標文件路徑來讀取對應文件
fs.readFile(filePathname, (err, data) => {
// 錯誤處理
if (err) {
res.writeHead(404, { "Content-Type": "text/plain" });
res.write("404 - NOT FOUND");
res.end();
} else {
res.writeHead(200, { "Content-Type": mimeType });
res.write(data);
res.end();
}
});
// 返回 false 表示, 客戶端想要的 是 靜態文件
return true;
} else {
// 返回 false 表示, 客戶端想要的 不是 靜態文件
return false;
}
}
// 導出函數
module.exports = readStaticFile;
複製代碼
用於讀取靜態文件的模塊 readStaticFile
封裝好了以後. 咱們能夠在項目目錄裏新建一個 modules 目錄, 用於存放模塊. 如下是我目前的項目結構.
封裝好了模塊以後, 咱們就能夠刪去服務器代碼裏那段讀取文件的代碼了, 直接引用模塊就好了. 如下是我修改後的 server.js 代碼:
// server.js
// 引入相關模塊
var http = require('http');
var url = require('url');
var path = require('path');
var readStaticFile = require('./modules/readStaticFile');
// 搭建 HTTP 服務器
var server = http.createServer(function(req, res) {
var urlObj = url.parse(req.url);
var urlPathname = urlObj.pathname;
var filePathname = path.join(__dirname, "/public", urlPathname);
// 讀取靜態文件
readStaticFile(res, filePathname);
});
// 在 3000 端口監聽請求
server.listen(3000, function() {
console.log("服務器運行中.");
console.log("正在監聽 3000 端口:")
})
複製代碼
😆 好啦,今天的分享就告一段落啦。下一篇中,我會介紹 "如何搭建服務器路由" 和 "處理瀏覽器表單提交"
傳送門 - Node.js 系列 - 搭建路由 & 處理表單提交
若是喜歡的話就點個關注吧!O(∩_∩)O 謝謝各位的支持❗️