http模塊
- 創(chuàng)建服務器兩種方式
// 方式一 http.createServer([requestListener])
/*
* [requestListener(req, res)] 用戶請求后回調(diào)函數(shù),有兩個參數(shù)辆童,req請求宜咒;res響應
* @return http.Server 返回一個http.Server實例
*/
var server = http.createServer((req, res) => {
res.writeHeader(200,{'Content-Type':'text/plain'});
res.end('hello world');
})
// 方式二 new http.Server()
var server = new http.Server();
server.on('request', (req, res){
res.writeHeader(200,{'Content-Type':'text/plain'});
res.end('hello world');
})
采用上面的方式并不能創(chuàng)建一個完成的服務器,還必須監(jiān)聽對應的端口把鉴,比如采用下面這種方式故黑。
// 表示引入http模塊
const http = require('http');
// 創(chuàng)建一個http服務
/*
request: 獲取url傳過來的信息 請求頭
response: 像瀏覽器響應信息 響應頭
*/
http.createServer(function(request,response){
// 設置響應頭
response.writeHead(200,{'Content-Type': 'text/plain'});
response.write('1111');// 像頁面返回的內(nèi)容
response.write('222');
// 像瀏覽器輸出一句話 并結束響應
response.end('1123122'); //
}).listen(8081);// 端口
為啥要監(jiān)聽端口(listen(8080))?
端口的功能
一臺擁有IP地址的主機可以提供許多服務庭砍,比如Web服務场晶、FTP服務、SMTP服務等怠缸,這些服務完全可以通過1個IP地址來實現(xiàn)峰搪。那么,主機是怎樣區(qū)分不同的網(wǎng)絡服務呢凯旭?顯然不能只靠IP地址概耻,因為IP 地址與網(wǎng)絡服務的關系是一對多的關系使套。實際上是通過“IP地址+端口號”來區(qū) 分不同的服務的。
客戶端通常對它所使用的端口號并不關心鞠柄,只需保證該端口號在本機上是唯一的就可以了侦高。客戶端口號又稱作臨時端口號(即存在時間很短暫)厌杜。這是因為它通常只是在用戶運行該客戶程序時才存在奉呛,而服務器則只要主機開著的,其服務就運行夯尽。
簡單理解:IP就是一個電腦節(jié)點的網(wǎng)絡物理地址瞧壮,就像你的家住的那個地址;端口是該計算機邏輯通訊接口匙握,不同的應用程序用不同的端口咆槽,就像你家里的各個不同的房間,臥室用來睡覺圈纺,餐廳用來吃飯秦忿。
理解為 node啟動一個服務就是將本機當做了服務器,所以它的ip地址是127.0.0.1 蛾娶,其中端口8080端口里面是提供你要的服務灯谣。
Url模塊
在上面創(chuàng)建的那個服務器里面,頁面所有的請求都會執(zhí)行createServer中傳入的方法蛔琅。但是不同的請求胎许,需要做不同的處理,所以我們需求判斷請求路徑罗售,以便我們做出不同的響應辜窑。
我們可以很簡單的獲取到請求的url,但有數(shù)據(jù)提交上來的url是十分復雜和不確定的。很不利于編寫業(yè)務邏輯莽囤。所以我們就要將url拆分成我們能用的數(shù)據(jù)谬擦。node的url模塊就是幫助我們對提交上來的url進行解析處理
常用方法:
parse(urlStr,queryString,AnalysisHost)
解析url切距,返回一個url屬性對象
urlStr: 要解析的url地址
queryString: 解析出來的query是字符串還是查詢對象朽缎,true是對象 false是字符串
AnalysisHost: 是否要解析出來主機名
實例代碼
var url = require('url')
var obj = url.parse('http://www.baidu.com/vdsa?ie=utf-8&word=sad',true,true)
console.log(obj);
結果:
Url組成部分:
- protocol:url的通信協(xié)議(http/https)
- slashes:如果協(xié)議protocol冒號后跟的是兩個斜杠字符(/),那么值為true
- auth:URL的用戶名與密碼部分
- host:url的主機名 “baidu.com”
- port: 端口號
- hostname: hostname是host屬性排除端口port之后的小寫的主機名部分
- hash:哈希#后面字符串包括#
- search:URL的查詢字符串部分,包括開頭的問號字符(谜悟?)
- query: 不包含問號(话肖?)的search字符串
- pathname:URL的整個路徑部分。跟在host后面葡幸,截止問號(最筒?)或者哈希字符(#)分隔
- path:由pathname與search組成的串接,不包含hash字符后面的東西
- href:解析后的完整的URL字符串,protocol和host都會被轉換成小寫蔚叨。
事件循環(huán)和回調(diào)函數(shù)
我們都知道床蜘,不同的請求對應的Content-type是不一樣的辙培,比如一個頁面的加載html和css的Content-type是不一樣的。
文件目錄為:
mime.json文件的內(nèi)容為:
{ ".323":"text/h323" ,
".3gp":"video/3gpp" ,
".asc":"text/plain" ,
".htm":"text/html" ,
".html":"text/html"
.....
}
我們現(xiàn)在通過getTypeFile方法來獲取不同文件后綴對應的Content-type邢锯。
const http = require('http');
const fs = require('fs');
const path = require('path');
const URL = require('url');
const utils = require('./utils/getType');
const EventEmitter = require('./utils/Listener');
http.createServer(function(request,response){
const pathname = request.url;
let extname = path.extname(URL.parse(pathname).pathname); // 獲取文件后綴名
if(pathname !== '/favicon.ico'){
返回對應的頁面
response.writeHead(200,{
'Content-Type': `${utils.getTypeFile(extname)};charset='utf-8'`
});
fs.readFile('./static/'+URL.parse(pathname).pathname, (err,data)=> {
response.end(data);
}
})
}else {
response.end()
}
}).listen(8081)
console.log('服務啟動了')
getTypeFile方法為:
exports.getTypeFile = function(exname){
const obj = fs.readFileSync('./mime.json').toString();
return JSON.parse(obj)[exname];
}
當getTypeFile是一個同步方法的時候扬蕊,上面的代碼是沒有任何問題的。但是如果使用fs.readFile這個異步方法的時候就會出現(xiàn)問題丹擎。因為外面得不到返回的值尾抑。這里readFildSync是一個同步的方式 ,如果采用異步的方法 readFile 在app.js 如何得到想要值呢 蒂培。
一般解決這種問題的有事件循環(huán)再愈、和回調(diào)函數(shù)的解決方式。
回調(diào)函數(shù)
// app.js
const utils = require('./utils/getType');
...code
utils.getTypeFild(exname,function(str){
response.writeHead(200,{
'Content-Type': `${str};charset='utf-8'`
});
... code
})
// getType.js
exports.getTypeFile = function(exname,callback){
fs.readFile('./mime.json',(err,data) =>{
callback(data.toString);
})
}
通過在給getTypeFile傳入一個回調(diào)方法护戳,在readFile執(zhí)行完成后翎冲,在執(zhí)行回調(diào)方法。Node 使用了大量的回調(diào)函數(shù)灸异,Node 所有 API 都支持回調(diào)函數(shù)
事件驅動
event 模塊只提供了一個對象 events.EventEmitter,
核心就是事件觸發(fā)和事件監(jiān)聽功能府适,原生使用原理類似于觀察者模式。
創(chuàng)建 eventEmitter 對象
// 引入 events 模塊
var events = require('events');
// 創(chuàng)建 eventEmitter 對象
var eventEmitter = new events.EventEmitter();
以下程序綁定事件處理程序:
// 綁定事件及事件的處理程序
eventEmitter.on('eventName', eventHandler);
我們可以通過程序觸發(fā)事件:
// 觸發(fā)事件
eventEmitter.emit('eventName');
所以如果采用事件驅動驅動的方式我們的第二種實現(xiàn)方式為:
1. getType.js
const fs = require('fs');
const events= = require('events');
const EventEmitter = new events.EventEmitter();// 實例化事件對象
exports.getTypeFile = function(exname){
fs.readFile('./mime.json',(err,data) =>{
EventEmitter.emit('exname', data.toString()); // 發(fā)送一個名為exname 的廣播
})
}
2. app.js
const fs = require('fs');
const events= = require('events');
const EventEmitter = new events.EventEmitter();
...code
if(pathname !== '/favicon.ico'){
// 接受一個名為 exname的廣播
EventEmitter.on('exname',(data) =>{
response.writeHead(200,{
'Content-Type': `${data};charset='utf-8'`
});
...code;
})
}
這里有一個錯誤的地方是肺樟,兩個文件中都通過new events.EventEmitter()
來生成了一個EventEmitter對象檐春,盡管他們兩個的名字一樣,但是他們兩個是不同的實例么伯。所以會發(fā)現(xiàn)在app.js中并沒有接受到一個名為 exname的廣播疟暖。
為了保證是一個EventEmitter對象,我們采用這種方法田柔,將app.js俐巴、和getType.js中的
const events= = require('events');
const EventEmitter = new events.EventEmitter();// 實例化事件對象
替換為
const EventEmitter = require('./Listener');
// Listenser.js
const events = require('events');
const EventEmitter = new events.EventEmitter();
module.exports = EventEmitter;
這樣兩個文件使用的就是同一個EventEmitter
參考文章: