第十章:Node的HTTP模塊

Ryan Dahl開發(fā)node的初衷就是:把Nginx非阻塞IO功能和一個高度封裝的WEB服務(wù)器結(jié)合在一起的東東。所以Node初衷就是為了高性能的Web服務(wù)器去的乱凿,所以:Node的HTTP模塊也是核心的核心顽素。

本文需要您了解的前置知識點:

  • HTTP協(xié)議
  • Web請求模型:請求→處理→響應(yīng)
  • Node的流、事件

http模塊的客戶端

要使用 HTTP 服務(wù)器與客戶端徒蟆,需要 require('http')模塊胁出。http模塊提供了兩個函數(shù)http.request()http.get(),幫助程序向服務(wù)器端發(fā)送請求。

我們可以通過http.request ()方法創(chuàng)建一個發(fā)送請求的http.ClientRequest類實例段审,請求創(chuàng)建后全蝶,并不會立即發(fā)送請求,我們還可以繼續(xù)訪問請求頭:setHeader(name, value)getHeader(name)removeHeader(name) API 進(jìn)行修改抑淫。實際的請求頭會與第一個數(shù)據(jù)塊一起發(fā)送或當(dāng)調(diào)用 request.end() 時發(fā)送绷落。

http.ClientRequest類

  • http.ClientRequest類繼承了EventEmitter,它內(nèi)部定義了以下事件。
事件 說明
'abort' 當(dāng)請求已被客戶端終止時觸發(fā)始苇。 該事件僅在首次調(diào)用 abort() 時觸發(fā)砌烁。
'connect' 每當(dāng)服務(wù)器響應(yīng) CONNECT 請求時觸發(fā)。 如果該事件未被監(jiān)聽催式,則接收到 CONNECT 方法的客戶端會關(guān)閉連接函喉。
'continue' 當(dāng)服務(wù)器發(fā)送了一個 100 Continue 的 HTTP 響應(yīng)時觸發(fā),通常是因為請求包含 Expect: 100-continue蓄氧。 這是客戶端將要發(fā)送請求主體的指令函似。
'response' 當(dāng)請求的響應(yīng)被接收到時觸發(fā)槐脏。 該事件只觸發(fā)一次喉童。如果沒有添加 'response' 事件處理函數(shù),則響應(yīng)會被整個丟棄顿天。 如果添加了 'response' 事件處理函數(shù)堂氯,則必須消耗完響應(yīng)對象的數(shù)據(jù),可通過調(diào)用 response.read()牌废、或添加一個 'data' 事件處理函數(shù)咽白、或調(diào)用 .resume() 方法。 數(shù)據(jù)被消耗完時會觸發(fā) 'end' 事件鸟缕。 在數(shù)據(jù)被讀取完之前會消耗內(nèi)存晶框,可能會造成 'process out of memory' 錯誤。
'socket' 當(dāng) socket 被分配到請求后觸發(fā)懂从。
'timeout' 當(dāng)?shù)讓?socket 超時的時候觸發(fā)授段。該方法只會通知空閑的 socket。請求必須手動停止番甩。
'upgrade' 每當(dāng)服務(wù)器響應(yīng) upgrade 請求時觸發(fā)侵贵。 如果該事件未被監(jiān)聽,則接收到 upgrade 請求頭的客戶端會關(guān)閉連接缘薛。
  • http.ClientRequest類還提供了一些方法供我們進(jìn)行請求和返回響應(yīng)的處理窍育。
方法 參數(shù) 說明
request.end([data[, encoding]][, callback]) data發(fā)送的數(shù)據(jù) ②encoding編碼 ③callback回調(diào)函數(shù) 結(jié)束發(fā)送請求。如果部分請求主體還未被發(fā)送宴胧,則會刷新它們到流中漱抓。如果請求是分塊的,則會發(fā)送終止字符 '0\r\n\r\n'恕齐。如果指定了 data乞娄,則相當(dāng)于調(diào)用 request.write(data, encoding) 之后再調(diào)用 request.end(callback)。如果指定了 callback,則當(dāng)請求流結(jié)束時會被調(diào)用补胚。
request.flushHeaders() 刷新請求頭码耐。出于效率的考慮,Node.js 通常會緩存請求頭直到 request.end() 被調(diào)用或第一塊請求數(shù)據(jù)被寫入溶其。 然后 Node.js 會將請求頭和數(shù)據(jù)打包成一個單一的 TCP 數(shù)據(jù)包骚腥。
request.getHeader(name) ①name ②返回字符串 讀出請求頭,注意:參數(shù)name是大小寫敏感的
request.removeHeader(name) name 字符串 移除一個已經(jīng)在 headers 對象里面的 header瓶逃。
request.setHeader(name, value) ①name是header的key②value 為 headers 對象設(shè)置一個單一的 header 值束铭。如果該 header 已經(jīng)存在了,則將會被替換厢绝。這里使用一個字符串?dāng)?shù)組來設(shè)置有相同名稱的多個 headers契沫。
request.setSocketKeepAlive([enable][, initialDelay]) ①enable類型boolean②initialDelay 一旦 socket 被分配給請求且已連接,socket.setKeepAlive() 會被調(diào)用昔汉。
request.setTimeout(timeout[, callback]) ①timeout請求被認(rèn)為是超時的毫秒數(shù)懈万。②callback 可選的函數(shù),當(dāng)超時發(fā)生時被調(diào)用。 等同于綁定到 timeout 事件靶病。一旦socket被分配給請求且已連接会通,socket.setTimeout() 會被調(diào)用。
request.write(chunk[, encoding][, callback]) ①chunk發(fā)送的請求數(shù)據(jù)娄周。②encoding:編碼涕侈;③callback回調(diào)函數(shù) 發(fā)送請求主體的一個數(shù)據(jù)塊。 通過多次調(diào)用該方法煤辨,一個請求主體可被發(fā)送到一個服務(wù)器裳涛,在這種情況下,當(dāng)創(chuàng)建請求時众辨,建議使用 ['Transfer-Encoding', 'chunked'] 請求頭端三。

發(fā)送GET請求

// 引入http模塊
const http = require('http');

// 創(chuàng)建一個請求
let request = http.request(
  {
    protocol: 'http:',     // 請求的協(xié)議
    host: 'aicoder.com',   // 請求的host
    port: 80,              // 端口
    method: 'GET',         // GET請求
    timeout: 2000,         // 超時時間
    path: '/'              // 請求路徑
  },
  res => {  // 連接成功后,接收到后臺服務(wù)器返回的響應(yīng)泻轰,回調(diào)函數(shù)就會被調(diào)用一次技肩。
    // res => http.IncomingMessage : 是一個Readable Stream
    res.on('data', data => {
      console.log(data.toString('utf8')); // 打印返回的數(shù)據(jù)。
    });
  }
);

// 設(shè)置請求頭部
request.setHeader('Cache-Control', 'max-age=0');

// 真正的發(fā)送請求
request.end();

發(fā)送get請求的另外一個辦法

http模塊還提供了http.get(options,callback)浮声,用來更簡單的處理GET方式的請求虚婿,它是http.request()的簡化版本,唯一的區(qū)別在于http.get自動將請求方法設(shè)為GET請求泳挥,同時不需要手動調(diào)用req.end();

http.get('http://aicoder.com', res => {
  res.on('data', data => {
    console.log(data.toString('utf8'));
  });
});

發(fā)送post請求

且看一個發(fā)送post請求的例子然痊。

const http = require('http');

let request = http.request(
  {
    protocol: 'http:',
    host: 'aicoder.com',
    port: 80,
    method: 'POST',
    timeout: 2000,
    path: '/'
  },
  res => {
    res.on('data', data => {
      console.log(data.toString('utf8'));
    });
  }
);
// 發(fā)送請求的數(shù)據(jù)。
request.write('id=3&name=aicoder');
request.end();

HTTP服務(wù)器端

http.Server實現(xiàn)了簡單的web服務(wù)器屉符,并把請求和響應(yīng)也做了封裝剧浸。

http.server對象的事件

http.server是一個基于事件的HTTP服務(wù)器锹引,所有的請求都被封裝到獨立的事件當(dāng)中,我們只需要對他的事件編寫相應(yīng)的行數(shù)就可以實現(xiàn)HTTP服務(wù)器的所有功能唆香,它繼承自EventEmitter,提供了以下的事件:

  1. request:當(dāng)客戶端請求到來的時候嫌变,該事件被觸發(fā),提供兩個參數(shù)request和response躬它,分別是http.ServerRequest和http.ServerResponse表示請求和響應(yīng)的信息腾啥。
  2. connection:當(dāng)TCP建立連接的時候,該事件被觸發(fā)冯吓,提供了一個參數(shù)socket倘待,為net.socket的實例(底層協(xié)議對象)
  3. close:當(dāng)服務(wù)器關(guān)閉的時候會被觸發(fā)
  4. 除此之外還有checkContinueupgrade组贺、clientError等事件

我們最常用的還是request事件凸舵,http也給這個事件提供了一個捷徑:http.createServer([requestListener])
下面我們來簡單的看一下兩個案例:

第一個:使用request事件的:

const http = require('http');

let server = new http.Server();
server.on('request', (req, res) => {
  console.log(req.url);
  //設(shè)置應(yīng)答頭信息
  res.writeHead(200, { 'Content-Type': 'text/html' });
  res.write('hello we are family<br>');
  res.end('server already end\n');
});
//顯示了三次這也證明了TCP的三次握手
server.on('connection', () => {
  console.log('握手');
});
server.on('close', () => {
  console.log('server will close');
});
//關(guān)閉服務(wù)為了觸發(fā)close事件
server.close();
server.listen(8080);

第二個:利用http.createServer創(chuàng)建服務(wù)器實例代碼:

const http = require('http');

http.createServer(function(req,res){
    res.writeHead(200,{'Content-Type':'text/plain'})
    res.write("hi, from aicoder.com");
    res.end();

}).listen(3000);

http.ServerRequset請求信息

我們都知道HTTP請求分為兩部分:請求頭和請求體,如果請求的內(nèi)容少的話就直接在請求頭協(xié)議完成之后立即讀取失尖,請求體可能相對較長一點啊奄,需要一定的時間傳輸。因此提供了三個事件用于控制請求體傳輸.

1.data:當(dāng)請求體數(shù)據(jù)到來時雹仿,該事件被觸發(fā)增热,該事件一共一個參數(shù)chunk,表示接受到的數(shù)據(jù)胧辽。
1.end:當(dāng)請求體數(shù)據(jù)傳輸完成時,該事件被觸發(fā)公黑,此后將不會再有數(shù)據(jù)到來邑商。
1.close:用戶當(dāng)前請求結(jié)束時,該事件被觸發(fā)凡蚜,不同于end人断,如果用戶強制終止了傳輸,也會觸發(fā)close

ServerRequest的屬性

名稱 含義
complete 客戶端請求是否已經(jīng)發(fā)送完成
httpVersion HTTP協(xié)議版本朝蜘,通常是1.0或1.1
method HTTP請求方法恶迈,如:GET,POST
url 原始的請求路徑
headers HTTP請求頭
trailers HTTP請求尾(不常見)
connection 當(dāng)前HTTP連接套接字,為net.Socket的實例
socket connection屬性的別名
client client屬性的別名
http.createServer(function(req,res){
    console.log(req.httpVersion);
    //console.log(req.socket);
    console.log(req.headers);
    console.log(req.method);
    res.writeHead(404,{'Content-Type':'text/plain'})
    res.write("we are is content");
    res.end();
}).listen(8080);

獲取GET請求內(nèi)容

由于GET請求直接被嵌入在路徑中,URL完整的請求路徑谱醇,包括了?后面的部分暇仲,因此你可以手動解析后面的內(nèi)容作為GET的參數(shù),Nodejs的url模塊中的parse函數(shù)提供了這個功能副渴。

const http = require('http');
const url = require('url');
const util = require('util');

http
  .createServer((req, res) => {
    //利用url模塊去解析客戶端發(fā)送過來的URL
    res.write(util.inspect(url.parse(req.url, true)));
    res.end();
  })
  .listen(8080);

獲得POST請求內(nèi)容

POST請求的內(nèi)容全部都在請求體中奈附,·http.ServerRequest·并沒有一個屬性內(nèi)容為請求體,原因是等待請求體傳輸可能是一件耗時的工作煮剧。譬如上傳文件斥滤。惡意的POST請求會大大消耗服務(wù)器的資源将鸵。所以Nodejs是不會解析請求體,當(dāng)你需要的時候佑颇,需要手動來做顶掉。

簡單的看一下代碼:

// 獲取post請求數(shù)據(jù)
const http = require('http');
const util = require('util');
const querystring = require('querystring');

http
  .createServer((req, res) => {
    let post = '';
    req.on('data', chunk => {
      post += chunk;
    });
    req.on('end', () => {
      post = querystring.parse(post);
      res.end(util.inspect(post));
    });
  })
  .listen(60004);

http.ServerResponse返回客戶端信息

http.ServerResponse這個類實現(xiàn)了(而不是繼承自)可寫流 接口。繼承了EventEmitter挑胸。它用來給用戶發(fā)送響應(yīng)結(jié)果一喘,它是由http.Serverrequest事件發(fā)送的,作為第二個參數(shù)傳遞嗜暴。一般為response或res
主要的三個函數(shù):

  • response.writeHead(statusCode,[headers]):向請求的客戶端發(fā)送響應(yīng)頭凸克。
    • statusCode是HTTP的狀態(tài)碼,如200為成功闷沥,404未找到等萎战。
    • headers是一個類似關(guān)聯(lián)數(shù)組的對象,表示響應(yīng)頭的每個屬性舆逃。
  • response.write(data,[encoding]) 向請求客戶端發(fā)送相應(yīng)內(nèi)容蚂维,data是buffer或字符串,encoding為編碼
  • response.end([data],[encoding]) 結(jié)束響應(yīng)路狮,告知用戶所有發(fā)送已經(jīng)完成虫啥,當(dāng)所有要返回的內(nèi)容發(fā)送完畢,該函數(shù)必須被調(diào)用一次奄妨,如果不調(diào)用涂籽,客戶端永遠(yuǎn)處于等待狀態(tài)

總結(jié)

真正開發(fā)環(huán)境,不會用這么底層的API去做web網(wǎng)站或者微服務(wù)砸抛,一般會選擇KOA或者EXPRESS等框架评雌。不過,通過底層的API也可以感受一下NODE原生的開發(fā)的快樂直焙。


參考:

1.https://blog.csdn.net/woshinannan741/article/details/51357464
1.http://nodejs.cn/api/http.html#http_http_createserver_requestlistener

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末景东,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子奔誓,更是在濱河造成了極大的恐慌斤吐,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件厨喂,死亡現(xiàn)場離奇詭異和措,居然都是意外死亡,警方通過查閱死者的電腦和手機杯聚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進(jìn)店門臼婆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人幌绍,你說我怎么就攤上這事颁褂」氏欤” “怎么了?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵颁独,是天一觀的道長彩届。 經(jīng)常有香客問我,道長誓酒,這世上最難降的妖魔是什么樟蠕? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮靠柑,結(jié)果婚禮上寨辩,老公的妹妹穿的比我還像新娘。我一直安慰自己歼冰,他們只是感情好靡狞,可當(dāng)我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著隔嫡,像睡著了一般甸怕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上腮恩,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天梢杭,我揣著相機與錄音,去河邊找鬼秸滴。 笑死武契,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的缸榛。 我是一名探鬼主播吝羞,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼内颗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起敦腔,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤均澳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后符衔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體找前,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年判族,在試婚紗的時候發(fā)現(xiàn)自己被綠了躺盛。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡形帮,死狀恐怖槽惫,靈堂內(nèi)的尸體忽然破棺而出周叮,到底是詐尸還是另有隱情,我是刑警寧澤界斜,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布仿耽,位于F島的核電站,受9級特大地震影響各薇,放射性物質(zhì)發(fā)生泄漏项贺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一峭判、第九天 我趴在偏房一處隱蔽的房頂上張望开缎。 院中可真熱鬧,春花似錦林螃、人聲如沸奕删。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽急侥。三九已至,卻和暖如春侮邀,著一層夾襖步出監(jiān)牢的瞬間坏怪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工绊茧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留铝宵,地道東北人。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓华畏,卻偏偏與公主長得像鹏秋,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子亡笑,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,543評論 2 349

推薦閱讀更多精彩內(nèi)容