一、簡單的靜態服務器
1、代碼解析
var http = require('http')
// http是nodejs裏面的一個模塊,這個對象能夠提供實現底層的方法。我們通過require去加載這個模塊
var server = http.createServer(function(req, res){
// 函數內部創建一個服務器,創建好之後,通過瀏覽器訪問這個服務器的時候,會把請求封裝成一個對象
// 這個對象就是這個回調函數的第一個參數req。用戶請求的信息都在這個對象內,可以獲取用戶的信息,如ip,請求信息等。
// 第二個參數res是服務器返回給用戶的信息
console.log('jiengu')
res.setHeader("Content-Type","text/html; charset=utf-8")
//設置響應頭的content-type內容,text/html是把響應體當成html解析,
res.write('<h1> 飢人谷</h1>')
//在res寫入服務器返回給瀏覽器的內容
res.end()
})
server.listen(9000)
// 通過listen方法來啓動他,服務器監聽9000端口
2、執行步驟
打開gitbash,切換到js文件當前的文件夾,然後輸入node index.js(index.js是我的js文件名,反正你們取什麼名就輸入啥名)
打開瀏覽器,輸入http://127.0.0.1:9000/,或者http://localhost:9000/
注意哈9000是代碼裏面寫的9000端口,如果下次改成了8080等其他的端口,那就改成對應的端口就好
3、響應頭和響應體
響應頭查看路徑:network-name-headers
響應體:
響應體是response的數據,有點類似於打開網頁的查看源代碼
每次修改了js文件的內容之後,要斷掉git的服務器,重新連接。不然即使刷新網頁沒有辦法顯示修改的內容
4、設置響應頭
4.1response.setHeader
格式:response.setHeader(name, value)
爲一個隱式的響應頭設置值。 如果該響應頭已存在,則值會被覆蓋。 如果要發送多個名稱相同的響應頭,則使用字符串數組。 非字符串的值會保留原樣,所以 response.getHeader() 會返回非字符串的值。 非字符串的值在網絡傳輸時會轉換爲字符串。
舉例:
response.setHeader('Content-Type', 'text/plain'); //當成字符串解析
response.setHeader('Content-Type','text/html; charset=utf-8')//當成html解析,如果是css就設置爲text/css
執行結果
setHeader引申的鏈接,是nodejs中文網的規範
4.2 response.writeHead()
writeHead文檔規範
格式:response.writeHead(statusCode, statusMessage)
參數1 statusCode(狀態碼)是一個三位數的 HTTP 狀態碼,如 404。
參數2是 statusMessage 是可選的狀態描述,是一個string。
參數3 headers 是響應頭,是個對象。其實我們可以理解爲這個對象放的是response headers全部內容。我們設置的writehead的內容處理status碼是放在general,其他的內容都是封裝成一個對象放在響應頭內容response headers。
response.writeHead(404, 'Not Found')
res.writeHead(200,'hhh', { 'Content-Type':'text/plain;charset=utf-8','X-Foo':'bar2222'});
4.3兩者的不同
- response.writeHead() 在消息中只能被調用一次,且必須在 response.end() 被調用之前調用。調用兩次就會報錯。 setheader可以多次調用
- headers.setheader()只允許您設置單一標題。
writehead()允許您設置關於響應頭的幾乎所有內容,包括狀態代碼、內容和多個標題。
4.4遇到的坑
坑1:res.setHeader("Content-Type","text/html; charset=gbk")
纔是對的,charset=gbk必須放在Content-Type內部,展示的時候也是在一起。(我猜想charset應該是Content-Type的一部分)
如果分開寫成下面的格式,不會報錯,但charset就變成了響應頭的單獨子項展示,而且charset=utf-8不會生效(下圖utf-8沒有生效就按照gbk去解碼,就出現了亂碼)。
res.setHeader('Content-Type', 'text/html');
res.setHeader("charset","utf-8")
所以一定注意寫法
坑2:writeHead只能寫一次,所有響應頭要設置的內容都要按照對象的格式,放在參數三headers裏面。以下縮寫是正確的,要記住啊
res.writeHead(200,'hhh', { 'Content-Type':'text/plain;charset=utf-8','X-Foo':'bar2222'});
坑3:response.setHeader() 設置的響應頭會與 response.writeHead() 設置的響應頭合併,但是如果設置的內容重複,以response.writeHead() 的優先爲準。
var server = http.createServer(function(req, res){
res.setHeader("Content-Type","text/html; charset=utf-8")
res.setHeader('X-Foo', 'bar');
res.writeHead(200,'hhh', { 'Content-Type':'text/plain;charset=utf-8','X-Foo':'bar2222'});
res.write('<h1> 飢人谷2</h1>')
res.end()
})
server.listen(9000)
執行結果是:很明顯的看到setHeader和writeHead重複設置的內容,都是以writeHead爲準的
4.5設置status的異常
res.writeHead(404,'hhh');
當我設置status爲404,發現即使是請求成功回送之後,也會出現紅色。這是因爲大家約定404就是一個錯誤的狀態,所以status的值要按照約定來設置
二、一個可用的靜態服務器
搭建一個有圖片,css,js的資源的服務器,github代碼鏈接
1、步驟
- 我在step1文件夾下放置了server.js文件,static文件夾。static文件夾對應放了css,png,js,html等文檔,並在html文檔內引用了圖片,css,js資源。
- 打開gitbash,切換step1文件夾,執行node server.js
- 打開瀏覽器輸入localhost:8080index.html,查看結果
輸出內容
2、js代碼解析
var http = require('http')
var path = require('path')
// path模塊處理url,不同系統(mac/lincx/window)下對url的寫法可能不一致的。(一個寫成c:/project/code/a.png
// 另外一個可能寫成/user/local/project/a.png)。path模塊會對這種情況自動處理url類型
var fs = require('fs')
// fs模塊用來讀取文件數據,也可以往文件裏面寫數據。
var url = require('url')
// url模塊可以自動解析url,得到一個對象,可以獲得對應的信息。
function staticRoot(staticPath, req, res){
console.log(staticPath)
//輸出static文件的絕對路徑,/user/documents/code/node-server/step1/static
console.log(req.url)
//請求的url地址,第一次調用html時,爲/index.html,第二次調用css時,就是css/a.css
var pathObj = url.parse(req.url, true)
// 解析url,得到url對象(包含protocal/hostname/port/pathname/query等等),即pathobj對象就是url的對象。本次要用的是pathname
console.log(pathObj)
if(pathObj.pathname === '/'){
pathObj.pathname += 'index.html'
}
//如果pathname沒有輸入(瀏覽器輸入的值只是localhost:8080,沒有後綴的話),服務器默認選擇去讀取和發送index.html文件
var filePath = path.join(staticPath, pathObj.pathname)
// staticPath=static文件夾的絕對路徑, pathObj.pathname=調用文件的後綴地址。
// 兩個加起來得到filePath(用戶輸入的url想要訪問文件的絕對路徑),舉例本文是/user/documents/code/node-server/step1/static/index.html
// var fileContent = fs.readFileSync(filePath,'binary')
// res.write(fileContent, 'binary')
// // 採用同步的方式讀取filePath的文檔,把讀取的數據寫入res對象內
// res.end()
fs.readFile(filePath, 'binary', function(err, fileContent){
// 異步的方式來讀取filePath的文檔。binary指以二進制的方式來讀取數據,因爲服務器不僅僅要讀取普通的數據,需要兼容圖片和文件等數據。
if(err){
console.log('404')
res.writeHead(404, 'not found')
res.end('<h1>404 Not Found</h1>')
// 在頁面展示404 Not Found。在res.end('數據')等於執行res.write('數據')加上res.end()
}else{
console.log('ok')
res.writeHead(200, 'OK')
res.write(fileContent, 'binary')
// 通過二進制的方式發送數據
res.end()
}
})
}
console.log(path.join(__dirname, 'static'))
// 在瀏覽器輸入localhost:8080/index.html地址,瀏覽器向服務器發起請求。
// 服務器收到請求後,執行相關函數,解析req對象信息,得到了index.html的地址。
// 服務器根據解析的地址在本地static文件夾下找到對應的index.html文件,讀取html裏面數據,並把數據放在res內,當成字符串發給服務器。
var server = http.createServer(function(req, res){
staticRoot(path.join(__dirname, 'static'), req, res) //寫一個staticRoot函數,來處理請求。
/* 參數1:把哪個路徑當成靜態文件路徑,傳遞路徑名。__dirname是nodejs裏面的一個變量,代表當前的server.js執行的這個文件。
path.join(__dirname, 'static')可以使用一個或多個字符串值參數,該參數返回將這些字符串值參數結合而成的路徑。
var joinPath = path.join(__dirname, 'a', 'b', 'c');
console.log(joinPath); // D:\nodePro\fileTest\a\b\c,
__dirname對應的step1文件夾的路徑,加上static文件夾得路徑,就等於static的絕對路徑。、
這樣的好處是每次絕對路徑發生變化的時候,不用重新去修改絕對路徑。*/
})
server.listen(8080) //創建一個服務器,監聽8080端口
console.log('visit http://localhost:8080' )
3、代碼難點解析
3.1 path node.js文檔中的標準解釋
path 模塊用於處理文件與目錄的路徑。不同系統(mac/lincx/window)下對url的寫法可能不一致的。(一個寫成c:/project/code/a.png
// 另外一個可能寫成/user/local/project/a.png)。path模塊會對這種情況自動處理url類型
3.2 path.join([...paths])
參數...paths <string> :路徑片段的序列,返回: <string>
使用平臺特定的分隔符把所有 path 片段連接到一起,並規範化生成的路徑
path.join('C:\Users\jz\documents\code\node-server\step1'
, 'static')
//C:\Users\jz\documents\code\node-server\step1\static
3.3 fs 文件系統node.js文檔中的標準解釋
fs 模塊用於以一種類似標準 POSIX 函數的方式與文件系統進行交互。
所有的文件系統操作都有同步和異步兩種形式。
異步形式的最後一個參數是完成時的回調函數。 傳給回調函數的參數取決於具體方法,但第一個參數會保留給異常。 如果操作成功完成,則第一個參數會是 null 或 undefined。
3.4 fs.readFile(path[, options], callback)異步地讀取文件的內容
path 文件名或文件路徑
options 如果 options 是一個字符串,則指定字符編碼,默認爲 null
callback 是一個回調函數,有兩個參數 (err, data),其中 data 是要讀取文件的內容
fs.readFile(filePath, 'binary', function(err, fileContent){
// 異步的方式來讀取filePath的文檔。binary指以二進制的方式來讀取數據,因爲服務器不僅僅要讀取普通的數據,需要兼容圖片和文件等數據。
if(err){
console.log('404')
res.writeHead(404, 'not found')
res.end('<h1>404 Not Found</h1>')
// 在頁面展示404 Not Found。在res.end('數據')等於執行res.write('數據')加上res.end()
}else{
console.log('ok')
res.writeHead(200, 'OK')
res.write(fileContent, 'binary')
// 通過二進制的方式發送數據
res.end()
}
})
3.5 fs.readFileSync(path[, options])
同步的讀取文件內容,兩個參數和異步的一樣的用法
// var fileContent = fs.readFileSync(filePath,'binary')
// res.write(fileContent, 'binary')
// // 採用同步的方式讀取filePath的文檔,把讀取的數據寫入res對象內
// res.end()
3.6 url模塊node.js文檔中的標準解釋
url 模塊提供了一些實用函數,用於 URL 處理與解析。 URL 字符串可以被解析爲一個 URL 對象,其屬性對應於字符串的各組成部分。
3.7url.parse(urlString[, parseQueryString[, slashesDenoteHost]])
url.parse() 方法會解析一個 URL 字符串並返回一個 URL 對象。
urlString <string>
要解析的 URL 字符串。
parseQueryString <boolean>
如果爲 true,則 query 屬性總會通過 querystring 模塊的 parse() 方法生成一個對象。 如果爲 false,則返回的 URL 對象上的 query 屬性會是一個未解析、未解碼的字符串。 默認爲 false。
slashesDenoteHost <boolean>
如果爲 true,則 // 之後至下一個 / 之前的字符串會被解析作爲 host。 例如,//foo/bar 會被解析爲 {host: 'foo', pathname: '/bar'} 而不是 {pathname: '//foo/bar'}。 默認爲 false。
舉個例子
var pathObj = url.parse(req.url, true)// 解析req.url,得到url對象pathobj
3.8__dirname
當前模塊的文件夾名稱。等同於 __filename 的 path.dirname() 的值
__filename 當前模塊的文件名稱---解析後的絕對路徑
例如:
在 /Users/mjr 目錄下執行 node example.js
console.log(__filename);
// Prints: /Users/mjr/example.js
console.log(__dirname);
// Prints: /Users/mjr
4、坑
有一個問題,爲什麼我們要用req.url解析成url對象pathobj,再通過staticPath文件地址和pathobj.pastname結合成filepath,爲啥我們不直接把req.url和staticPath結合在一起生成filepath呢?這樣還少了一步呢
答案:如果requrl是常規的index.html或者css.css這種,兩種方式都不會報錯。但是如果url比較複雜,像是index.html?query=111#111這種,直接把req.url和staticPath結合在一起是會報錯的,所以需要轉成url對象再把pashname挑出來。
三、實現一個簡單的node.js服務器路由
實現更復雜的服務器,url不僅僅是定位一個靜態文件,可以mock任何數據和前端交互。
1、核心原理:
根據瀏覽器請求的不同路由,導致服務器執行不同的操作。
2、文檔結構:
3、服務器實現3條路由:
- /getWeather,結合b.js文件實現一個ajax來mock天氣數據
- /user/123 ,結合user.tpl文件實現用戶頁面
- /index.html,結合index.html實現index.html的頁面。在html引用css文件,b.js,和圖片
4、對應的文件內容
可以查看GitHub上面的代碼,我這裏截圖說明
html
css
js,實現ajax的代碼
user.tpl
最重要的server-simple.js服務器代碼
本次演示的url是localhost:8080/user/123,localhost:8080之後的內容是路由。所有請求到8080這個服務器內,根據不同的路由給瀏覽器發送不同的數據
var http = require('http')
var fs = require('fs')
var url = require('url')
http.createServer(function(req, res){
var pathObj = url.parse(req.url, true)
console.log(pathObj)
switch (pathObj.pathname){
case '/getWeather': //根據req.url來執行不同的函數
var ret
if(pathObj.query.city == 'beijing'){
ret = {
city: 'beijing',
weather: '晴天'
}
}else{
ret = {
city: pathObj.query.city,
weather: '不知道'
}
}
res.setHeader('content-Type','text/plain;charset=utf-8')
res.end(JSON.stringify(ret)) //給瀏覽器輸入是一個json格式的對象,根據JSON.stringify轉換成字符串
break;
case '/user/123':
res.end( fs.readFileSync(__dirname + '/static/user.tpl' ))
//如果路由是/user/123,讀取user.tpl的內容,並返回給瀏覽器
break;
default:
res.end( fs.readFileSync(__dirname + '/static' + pathObj.pathname) )
}
}).listen(8080)
5、執行結果
index.html
/getWeather
/user/123