日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

NodeJS創(chuàng)建HTTP、HTTPS服務(wù)器與客戶(hù)端

 西北望msm66g9f 2019-09-05

(給前端速報(bào)加星標(biāo),提升前端技能.)

作者: 奮飛

超文本傳輸協(xié)議(HTTP,HyperText Transfer Protocol)是互聯(lián)網(wǎng)上應(yīng)用最為廣泛的一種網(wǎng)絡(luò)協(xié)議。所有的WWW文件都必須遵守這個(gè)標(biāo)準(zhǔn)。設(shè)計(jì)HTTP最初的目的是為了提供一種發(fā)布和接收HTML頁(yè)面的方法。其屬于下圖七層網(wǎng)路協(xié)議的“應(yīng)用層”。

HTTP服務(wù)器
創(chuàng)建HTTP服務(wù)器
創(chuàng)建服務(wù)
方式一:回調(diào)方式
var server = http.createServer((request, response) => {// 接受客戶(hù)端請(qǐng)求時(shí)觸發(fā) ...});server.listen(10000, 'localhost', 511, () => {// 開(kāi)始監(jiān)聽(tīng) ...});
方式二:事件監(jiān)聽(tīng)方式
var server = http.createServer();// 接受客戶(hù)端請(qǐng)求時(shí)觸發(fā)server.on('request', (request, rsponse) => { ...});server.listen(10000, 'localhost', 511);// 開(kāi)始監(jiān)聽(tīng)server.on('listening', () => { ...});
注意:
  • server.listen(port, [host], [backlog], [callback])中的backlog參數(shù)為整數(shù),指定位于等待隊(duì)列中客戶(hù)端連接的最大數(shù)量,一旦超過(guò)這個(gè)長(zhǎng)度,HTTP服務(wù)器將開(kāi)始拒絕來(lái)自新客戶(hù)端的連接,默認(rèn)值為511。
  • 在HTTP請(qǐng)求服務(wù)器時(shí),會(huì)發(fā)送兩次請(qǐng)求。一次是用戶(hù)發(fā)出請(qǐng)求,另一次是瀏覽器為頁(yè)面在收藏夾中的顯示圖標(biāo)(默認(rèn)為favicon.ico)而自動(dòng)發(fā)出的請(qǐng)求。
關(guān)閉服務(wù)器
server.close();// 服務(wù)器關(guān)閉時(shí)會(huì)觸發(fā)close事件server.on('close', () => {...});
超時(shí)
server.setTimeout(60 * 1000, () => {console.log('超時(shí)了');});// 或者通過(guò)事件形式server.setTimeout(60 * 1000);server.on('timeout', () => {...});
注意:默認(rèn)超時(shí)時(shí)間為2分鐘
錯(cuò)誤
server.on('error', (e) => {if(e.code === 'EADDRINUSE') {// 端口被占用 }});
獲取客戶(hù)端請(qǐng)求信息
當(dāng)從客戶(hù)端請(qǐng)求流中讀取到數(shù)據(jù)時(shí)會(huì)觸發(fā)data事件,當(dāng)讀取完客戶(hù)端請(qǐng)求流中的數(shù)據(jù)時(shí)觸發(fā)end事件。

Get請(qǐng)求
server.on('request', (request, response) => {if(request.url !== '/favicon.ico') {/* Get請(qǐng)求 */var params = url.parse(req.url, true).query;// 或者// var params = querystring.parse(url.parse(request.url).query);// 根據(jù)參數(shù)做處理// ...// 結(jié)束請(qǐng)求        response.end();    }  });
Post請(qǐng)求
server.on('request', (request, response) => { request.setEncoding('utf-8');if(request.url !== '/favicon.ico') {let result = ''; request.on('data', (data) => { result += data; }); request.on('end', () => {var params = JSON.parse(postData);console.log(`數(shù)據(jù)接收完畢:${result}`); });// 結(jié)束本次請(qǐng)求 response.end(); }// 結(jié)束本次請(qǐng)求 response.end(JSON.stringify({status: 'success'}));});
轉(zhuǎn)換URL字符串與查詢(xún)字符串
querystring模塊:轉(zhuǎn)換URL中的查詢(xún)字符串(URL中?之后,#之前)
querystring.stringify(obj, [sep], [eq])querystring.parse(str, [sep], [eq], [option])
sep:分割符,默認(rèn)&
eq:分配字符,默認(rèn)=
options:{maxKeys: number}指定轉(zhuǎn)換后對(duì)象中的屬性個(gè)數(shù)let str = querystring.stringify({name: 'ligang', age: 27});console.log(str); // name=ligang&age=27let obj = querystring.parse(str);console.log(obj); // { name: 'ligang', age: '27' }
url模塊:轉(zhuǎn)換完整URL字符串
url.parse(urlStr, [parseQueryString])
parseQueryString:如果為true,將查詢(xún)字符通過(guò)querystring轉(zhuǎn)換為對(duì)象;默認(rèn)false。
url.resolve(from, to);
將二者結(jié)合成一個(gè)路徑,from、to既可以是相對(duì)路徑也可以是絕對(duì)路徑。
// http:///javascript/a?a=1url.resolve('http:///javascript/', 'a?a=1'); // http:///a?a=1url.resolve('http:///javascript/', '/a?a=1');
注意:具體合并規(guī)則,請(qǐng)查看《Node權(quán)威指南》— 8.1HTTP服務(wù)器。

var urlStr = 'http:///javascript/?name=lg&uid=1#a/b/c';console.log(url.parse(urlStr, true));/*Url { protocol: 'http:', slashes: true, auth: null, host: '', port: null, hostname: '',hash: '#a/b/c', search: '?name=lg&uid=1', query: { name: 'lg', uid: '1' }, pathname: '/javascript/', path: '/javascript/?name=lg&uid=1', href: 'http:///javascript/?name=lg&uid=1#a/b/c'}*/
發(fā)送服務(wù)器端響應(yīng)流
response.writeHead(statusCode, [reasonPhrase], [headers]);// 或者response.setHeader(name, value);
響應(yīng)頭中包含的一些常用字段:

示例:
response.writeHead(200, {'Content-Type': 'text/plain', 'Access-Control-Allow-Origin': 'http://localhost'});// 或者response.statusCode = 200;response.setHeader('Content-Type', 'text/plain');response.setHeader('Access-Control-Allow-Origin', 'http://localhost');
writeHead和setHeader區(qū)別:
writeHead:該方法被調(diào)用時(shí)發(fā)送響應(yīng)頭
setHeader:write方法第一次被調(diào)用時(shí)發(fā)送響應(yīng)頭
/* 獲取響應(yīng)頭中的某個(gè)字段值 */response.getHeader(name);/* 刪除一個(gè)響應(yīng)字段值 */response.removeHeader(name);/* 該屬性表示響應(yīng)頭是否已發(fā)送 */response.headersSent;/* 在響應(yīng)數(shù)據(jù)的尾部增加一個(gè)頭信息 */response.addTrailers(headers);
示例:
// 必須再響應(yīng)頭中添加Trailer字段,并且其值設(shè)置為追加的響應(yīng)頭中所指定的字段名response.write(200, {'Content-Type': 'text/plain', 'Trailer': 'Content-MD5'});response.write('....');response.addTrailers({'Content-MD5', '...'});response.end();
特別說(shuō)明:
當(dāng)再快速網(wǎng)路且數(shù)據(jù)量很小的情況下,Node將數(shù)據(jù)直接發(fā)送到操作系統(tǒng)內(nèi)核緩存區(qū)中,然后從該內(nèi)核緩存區(qū)中取出數(shù)據(jù)發(fā)送給請(qǐng)求方;如果網(wǎng)速很慢或者數(shù)據(jù)量很大,Node通常會(huì)將數(shù)據(jù)緩存在內(nèi)存中,在對(duì)方可以接受數(shù)據(jù)的情況下將內(nèi)存中的數(shù)據(jù)通過(guò)操作系統(tǒng)內(nèi)核緩存區(qū)發(fā)送給請(qǐng)求方。
response.write返回true,說(shuō)明直接寫(xiě)到了操作系統(tǒng)內(nèi)核緩存區(qū)中;返回false,說(shuō)明暫時(shí)緩存的內(nèi)存中。每次需要通過(guò)調(diào)用response.end()來(lái)結(jié)束響應(yīng)。
響應(yīng)超時(shí)會(huì)觸發(fā)timeout事件;response.end()方法調(diào)用之前,如果連接中斷,會(huì)觸發(fā)close事件。
/* 設(shè)置請(qǐng)求超時(shí)時(shí)間2分鐘 */response.setTimeout(2 * 60 * 1000, () => {console.error('請(qǐng)求超時(shí)!'); });// 或者response.setTimout(2 * 60 * 1000);response.on('timeout', () => {console.error('請(qǐng)求超時(shí)!');});/* 連接中斷 */response.on('close', () => {console.error('連接中斷!');});
/** * HTTP服務(wù)端 * Created by ligang on 17/5/28. */import http from 'http';var server = http.createServer();// 接受客戶(hù)端請(qǐng)求時(shí)觸發(fā)server.on('request', (request, response) => {if(request.url !== '/favicon.ico') { response.setTimeout(2 * 60 * 1000, () => {console.error('請(qǐng)求超時(shí)!'); }); response.on('close', () => {console.error('請(qǐng)求中斷!'); });let result = ''; request.on('data', (data) => { result += data; }); request.on('end', () => {console.log(`服務(wù)器數(shù)據(jù)接收完畢:${result}`); response.statusCode = 200; response.write('收到!'); response.end(); // 結(jié)束本次請(qǐng)求 }); }});server.listen(10000, 'localhost', 511);// 開(kāi)始監(jiān)聽(tīng)server.on('listening', () => {console.log('開(kāi)始監(jiān)聽(tīng)');});server.on('error', (e) => {if(e.code === 'EADDRINUSE') {console.log('端口被占用'); }else {console.log(`發(fā)生錯(cuò)誤:${e.code}`); }});
HTTP客戶(hù)端
Node.js可以輕松向任何網(wǎng)站發(fā)送請(qǐng)求并讀取網(wǎng)站的響應(yīng)數(shù)據(jù)。
var req = http.request(options, callback);// get請(qǐng)求var req = http.get(options, callback);// 向目標(biāo)網(wǎng)站發(fā)送數(shù)據(jù)req.write(chunk, [encoding]);// 結(jié)束本次請(qǐng)求req.end([chucnk], [encoding]);// 中止本次請(qǐng)求req.abort();
其中,options用于指定目標(biāo)URL地址,如果該參數(shù)是一個(gè)字符串,將自動(dòng)使用url模塊中的parse方法轉(zhuǎn)換為一個(gè)對(duì)象。注意:http.get()方法只能使用Get方式請(qǐng)求數(shù)據(jù),且無(wú)需調(diào)用req.end()方法,Node.js會(huì)自動(dòng)調(diào)用。
/** * HTTP客戶(hù)端 * Created by ligang on 17/5/30. */import http from 'http';const options = { hostname: 'localhost', port: 10000, path: '/', method: 'post' },    req = http.request(options);req.write('你好,服務(wù)器');req.end();req.on('response', (res) => {console.log(`狀態(tài)碼:${res.statusCode}`);let result = ''; res.on('data', (data) => { result += data; }); res.on('end', () => {console.log(`客戶(hù)端接受到響應(yīng):${result}`); })});req.setTimeout(60* 1000, () => {console.log('超時(shí)了'); req.abort();});req.on('error', (error) => {if(error.code === 'ECONNERSET') {console.log('socket端口超時(shí)'); }else {console.log(`發(fā)送錯(cuò)誤:${error.code}`); }});
代理服務(wù)器
/** * HTTP代理 * Created by ligang on 17/5/30. */import http from 'http';import url from 'url';/** * 服務(wù)端 */const server = http.createServer(async (req, res) => {// req.setEncoding('utf-8');/* 超時(shí) 2分鐘 */ res.setTimeout(2 * 60 * 1000, () => {// ... });/* 連接中斷 */ res.on('close', () => {// ...    });let options = {},        result = ''; options = await new Promise((resolve, reject) => {if(req.method === 'GET') { options = url.parse('http://localhost:10000' + req.url); resolve(options); }else if(req.method === 'POST') { req.on('data', (data) => { result += data;            }); req.on('end', () => { options = url.parse('http://localhost:10000' + req.url);// post請(qǐng)求必須制定 options.headers = {'content-type': 'application/json', }; resolve(options); }); } });    options.method = req.method;let content = await clientHttp(options, result ? JSON.parse(result) : result); res.setHeader('Content-Type', 'text/html'); res.write('<html><head><meta charset='UTF-8' /></head>') res.write(content); res.write('</html>');// 結(jié)束本次請(qǐng)求 res.end();});server.listen(10010, 'localhost', 511);/* 開(kāi)始監(jiān)聽(tīng) */server.on('listening', () => {// ...});/* 監(jiān)聽(tīng)錯(cuò)誤 */server.on('error', (e) => {console.log(e.code);// ...});/** * 客戶(hù)端 * @param options 請(qǐng)求參數(shù) * @param data 請(qǐng)求數(shù)據(jù) */async function clientHttp(options, data) {let output = new Promise((resolve, reject) => {let req = http.request(options, (res) => {let result = ''; res.setEncoding('utf8'); res.on('data', function (chunk) { result += chunk; }); res.on('end', function () { resolve(result); }); }); req.setTimeout(60000, () => {console.error(`連接后臺(tái)超時(shí) ${options.href}`); reject(); req.abort(); }); req.on('error', err => {console.error(`連接后臺(tái)報(bào)錯(cuò) ${err}`);if (err.code === 'ECONNRESET') {console.error(`socket超時(shí) ${options.href}`); } else {console.error(`連接后臺(tái)報(bào)錯(cuò) ${err}`); } reject(); req.abort(); });// 存在請(qǐng)求數(shù)據(jù),發(fā)送請(qǐng)求數(shù)據(jù)if (data) { req.write(JSON.stringify(data)); } req.end(); });return await output;}
注意:
POST請(qǐng)求必須指定headers信息,否則會(huì)報(bào)錯(cuò)socket hang up
獲取到options后需要重新指定其methodoptions.method = req.method;
HTTPS服務(wù)器
  • HTTPS使用https協(xié)議,默認(rèn)端口號(hào)44;
  • HTTPS需要向證書(shū)授證中心申請(qǐng)證書(shū);
  • HTTPS服務(wù)器與客戶(hù)端之間傳輸是經(jīng)過(guò)SSL安全加密后的密文數(shù)據(jù);
創(chuàng)建公鑰、私鑰及證書(shū)
(1)創(chuàng)建私鑰
openssl genrsa -out privatekey.pem 1024
(2)創(chuàng)建證書(shū)簽名請(qǐng)求
openssl req -new -key privatekey.pem -out certrequest.csr
(3)獲取證書(shū),線上證書(shū)需要經(jīng)過(guò)證書(shū)授證中心簽名的文件;下面只創(chuàng)建一個(gè)學(xué)習(xí)使用證書(shū)
openssl x509 -req -in certrequest.csr -signkey privatekey.pem -out certificate.pem
(4)創(chuàng)建pfx文件
openssl pkcs12 -export -in certificate.pem -inkey privatekey.pem -out certificate.pfx
HTTPS服務(wù)
創(chuàng)建HTTPS服務(wù)器同HTTP服務(wù)器大致相同,需要增加證書(shū),創(chuàng)建HTTPS服務(wù)器時(shí)通過(guò)options參數(shù)設(shè)置。
import https from 'https';import fs from 'fs';var pk = fs.readFileSync('privatekey.pem'),    pc = fs.readFileSync('certificate.pem');var opts = {key: pk,cert: pc};var server = https.createServer(opts);
opts參數(shù)為一個(gè)對(duì)象,用于指定創(chuàng)建HTTPS服務(wù)器時(shí)配置的各種選項(xiàng),下面只描述幾個(gè)必要選項(xiàng):

HTTPS客戶(hù)端
const options = {hostname: 'localhost',port: 1443,path: '/',method: 'post',key: fs.readFileSync('privatekey.pem'),cert: fs.readFileSync('certificate.pem'),rejectUnhauthorized: false,agent: false // 從連接池中指定挑選一個(gè)當(dāng)前連接狀態(tài)為關(guān)閉的https.Agent    },    req = https.request(options);// 或者const options = {hostname: 'localhost',port: 1443,path: '/',method: 'post',key: fs.readFileSync('privatekey.pem'),cert: fs.readFileSync('certificate.pem'),rejectUnhauthorized: false,    };// 顯示指定https.Agent對(duì)象options.agent = new https.Agent(options);var req = https.request(options);
說(shuō)明: 普通的 HTTPS 服務(wù)中,服務(wù)端不驗(yàn)證客戶(hù)端的證書(shū)(但是需要攜帶證書(shū)),中間人可以作為客戶(hù)端與服務(wù)端成功完成 TLS 握手; 但是中間人沒(méi)有證書(shū)私鑰,無(wú)論如何也無(wú)法偽造成服務(wù)端跟客戶(hù)端建立 TLS 連接。
當(dāng)然如果你擁有證書(shū)私鑰,代理證書(shū)對(duì)應(yīng)的 HTTPS 網(wǎng)站當(dāng)然就沒(méi)問(wèn)題了,所以這里的私鑰和公鑰只是格式書(shū)寫(xiě),沒(méi)有太大意義,只要將請(qǐng)求回來(lái)的數(shù)據(jù)原原本本交給瀏覽器來(lái)解析就算完成任務(wù)。 

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶(hù)發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶(hù) 評(píng)論公約

    類(lèi)似文章 更多