|
(給前端速報加星標(biāo),提升前端技能.) 作者: 奮飛 超文本傳輸協(xié)議(HTTP,HyperText Transfer Protocol)是互聯(lián)網(wǎng)上應(yīng)用最為廣泛的一種網(wǎng)絡(luò)協(xié)議。所有的WWW文件都必須遵守這個標(biāo)準(zhǔn)。設(shè)計HTTP最初的目的是為了提供一種發(fā)布和接收HTML頁面的方法。其屬于下圖七層網(wǎng)路協(xié)議的“應(yīng)用層”。
var server = http.createServer((request, response) => {// 接受客戶端請求時觸發(fā) ...});server.listen(10000, 'localhost', 511, () => {// 開始監(jiān)聽 ...});
var server = http.createServer();// 接受客戶端請求時觸發(fā)server.on('request', (request, rsponse) => { ...});server.listen(10000, 'localhost', 511);// 開始監(jiān)聽server.on('listening', () => { ...});
- server.listen(port, [host], [backlog], [callback])中的backlog參數(shù)為整數(shù),指定位于等待隊列中客戶端連接的最大數(shù)量,一旦超過這個長度,HTTP服務(wù)器將開始拒絕來自新客戶端的連接,默認(rèn)值為511。
- 在HTTP請求服務(wù)器時,會發(fā)送兩次請求。一次是用戶發(fā)出請求,另一次是瀏覽器為頁面在收藏夾中的顯示圖標(biāo)(默認(rèn)為favicon.ico)而自動發(fā)出的請求。
server.close();// 服務(wù)器關(guān)閉時會觸發(fā)close事件server.on('close', () => {...});
server.setTimeout(60 * 1000, () => {console.log('超時了');});// 或者通過事件形式server.setTimeout(60 * 1000);server.on('timeout', () => {...});
server.on('error', (e) => {if(e.code === 'EADDRINUSE') {// 端口被占用 }}); 當(dāng)從客戶端請求流中讀取到數(shù)據(jù)時會觸發(fā)data事件,當(dāng)讀取完客戶端請求流中的數(shù)據(jù)時觸發(fā)end事件。
server.on('request', (request, response) => {if(request.url !== '/favicon.ico') {/* Get請求 */var params = url.parse(req.url, true).query;// 或者// var params = querystring.parse(url.parse(request.url).query);// 根據(jù)參數(shù)做處理// ...// 結(jié)束請求 response.end(); } });
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é)束本次請求 response.end(); }// 結(jié)束本次請求 response.end(JSON.stringify({status: 'success'}));}); querystring模塊:轉(zhuǎn)換URL中的查詢字符串(URL中?之后,#之前)querystring.stringify(obj, [sep], [eq])querystring.parse(str, [sep], [eq], [option])
options:{maxKeys: number}指定轉(zhuǎn)換后對象中的屬性個數(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.parse(urlStr, [parseQueryString]) parseQueryString:如果為true,將查詢字符通過querystring轉(zhuǎn)換為對象;默認(rèn)false。將二者結(jié)合成一個路徑,from、to既可以是相對路徑也可以是絕對路徑。// http:///javascript/a?a=1url.resolve('http:///javascript/', 'a?a=1'); // http:///a?a=1url.resolve('http:///javascript/', '/a?a=1'); 注意:具體合并規(guī)則,請查看《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'}*/
response.writeHead(statusCode, [reasonPhrase], [headers]);// 或者response.setHeader(name, value);

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)用時發(fā)送響應(yīng)頭setHeader:write方法第一次被調(diào)用時發(fā)送響應(yīng)頭/* 獲取響應(yīng)頭中的某個字段值 */response.getHeader(name);/* 刪除一個響應(yīng)字段值 */response.removeHeader(name);/* 該屬性表示響應(yīng)頭是否已發(fā)送 */response.headersSent;/* 在響應(yīng)數(shù)據(jù)的尾部增加一個頭信息 */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(); 當(dāng)再快速網(wǎng)路且數(shù)據(jù)量很小的情況下,Node將數(shù)據(jù)直接發(fā)送到操作系統(tǒng)內(nèi)核緩存區(qū)中,然后從該內(nèi)核緩存區(qū)中取出數(shù)據(jù)發(fā)送給請求方;如果網(wǎng)速很慢或者數(shù)據(jù)量很大,Node通常會將數(shù)據(jù)緩存在內(nèi)存中,在對方可以接受數(shù)據(jù)的情況下將內(nèi)存中的數(shù)據(jù)通過操作系統(tǒng)內(nèi)核緩存區(qū)發(fā)送給請求方。response.write返回true,說明直接寫到了操作系統(tǒng)內(nèi)核緩存區(qū)中;返回false,說明暫時緩存的內(nèi)存中。每次需要通過調(diào)用response.end()來結(jié)束響應(yīng)。響應(yīng)超時會觸發(fā)timeout事件;response.end()方法調(diào)用之前,如果連接中斷,會觸發(fā)close事件。/* 設(shè)置請求超時時間2分鐘 */response.setTimeout(2 * 60 * 1000, () => {console.error('請求超時!'); });// 或者response.setTimout(2 * 60 * 1000);response.on('timeout', () => {console.error('請求超時!');});/* 連接中斷 */response.on('close', () => {console.error('連接中斷!');});
/** * HTTP服務(wù)端 * Created by ligang on 17/5/28. */import http from 'http';var server = http.createServer();// 接受客戶端請求時觸發(fā)server.on('request', (request, response) => {if(request.url !== '/favicon.ico') { response.setTimeout(2 * 60 * 1000, () => {console.error('請求超時!'); }); response.on('close', () => {console.error('請求中斷!'); });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é)束本次請求 }); }});server.listen(10000, 'localhost', 511);// 開始監(jiān)聽server.on('listening', () => {console.log('開始監(jiān)聽');});server.on('error', (e) => {if(e.code === 'EADDRINUSE') {console.log('端口被占用'); }else {console.log(`發(fā)生錯誤:${e.code}`); }}); Node.js可以輕松向任何網(wǎng)站發(fā)送請求并讀取網(wǎng)站的響應(yīng)數(shù)據(jù)。var req = http.request(options, callback);// get請求var req = http.get(options, callback);// 向目標(biāo)網(wǎng)站發(fā)送數(shù)據(jù)req.write(chunk, [encoding]);// 結(jié)束本次請求req.end([chucnk], [encoding]);// 中止本次請求req.abort(); 其中,options用于指定目標(biāo)URL地址,如果該參數(shù)是一個字符串,將自動使用url模塊中的parse方法轉(zhuǎn)換為一個對象。注意:http.get()方法只能使用Get方式請求數(shù)據(jù),且無需調(diào)用req.end()方法,Node.js會自動調(diào)用。/** * HTTP客戶端 * 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(`客戶端接受到響應(yīng):${result}`); })});req.setTimeout(60* 1000, () => {console.log('超時了'); req.abort();});req.on('error', (error) => {if(error.code === 'ECONNERSET') {console.log('socket端口超時'); }else {console.log(`發(fā)送錯誤:${error.code}`); }});
/** * 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');/* 超時 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請求必須制定 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é)束本次請求 res.end();});server.listen(10010, 'localhost', 511);/* 開始監(jiān)聽 */server.on('listening', () => {// ...});/* 監(jiān)聽錯誤 */server.on('error', (e) => {console.log(e.code);// ...});/** * 客戶端 * @param options 請求參數(shù) * @param data 請求數(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(`連接后臺超時 ${options.href}`); reject(); req.abort(); }); req.on('error', err => {console.error(`連接后臺報錯 ${err}`);if (err.code === 'ECONNRESET') {console.error(`socket超時 ${options.href}`); } else {console.error(`連接后臺報錯 ${err}`); } reject(); req.abort(); });// 存在請求數(shù)據(jù),發(fā)送請求數(shù)據(jù)if (data) { req.write(JSON.stringify(data)); } req.end(); });return await output;} POST請求必須指定headers信息,否則會報錯socket hang up獲取到options后需要重新指定其methodoptions.method = req.method;- HTTPS使用https協(xié)議,默認(rèn)端口號44;
- HTTPS服務(wù)器與客戶端之間傳輸是經(jīng)過SSL安全加密后的密文數(shù)據(jù);
openssl genrsa -out privatekey.pem 1024
openssl req -new -key privatekey.pem -out certrequest.csr (3)獲取證書,線上證書需要經(jīng)過證書授證中心簽名的文件;下面只創(chuàng)建一個學(xué)習(xí)使用證書openssl x509 -req -in certrequest.csr -signkey privatekey.pem -out certificate.pem
openssl pkcs12 -export -in certificate.pem -inkey privatekey.pem -out certificate.pfx 創(chuàng)建HTTPS服務(wù)器同HTTP服務(wù)器大致相同,需要增加證書,創(chuàng)建HTTPS服務(wù)器時通過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ù)為一個對象,用于指定創(chuàng)建HTTPS服務(wù)器時配置的各種選項,下面只描述幾個必要選項:
const options = {hostname: 'localhost',port: 1443,path: '/',method: 'post',key: fs.readFileSync('privatekey.pem'),cert: fs.readFileSync('certificate.pem'),rejectUnhauthorized: false,agent: false // 從連接池中指定挑選一個當(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對象options.agent = new https.Agent(options);var req = https.request(options); 說明: 普通的 HTTPS 服務(wù)中,服務(wù)端不驗證客戶端的證書(但是需要攜帶證書),中間人可以作為客戶端與服務(wù)端成功完成 TLS 握手; 但是中間人沒有證書私鑰,無論如何也無法偽造成服務(wù)端跟客戶端建立 TLS 連接。當(dāng)然如果你擁有證書私鑰,代理證書對應(yīng)的 HTTPS 網(wǎng)站當(dāng)然就沒問題了,所以這里的私鑰和公鑰只是格式書寫,沒有太大意義,只要將請求回來的數(shù)據(jù)原原本本交給瀏覽器來解析就算完成任務(wù)。
|