(給前端速報加星標,提升前端技能.) 作者: 奮飛 超文本傳輸協(xié)議(HTTP,HyperText Transfer Protocol)是互聯(lián)網上應用最為廣泛的一種網絡協(xié)議。所有的WWW文件都必須遵守這個標準。設計HTTP最初的目的是為了提供一種發(fā)布和接收HTML頁面的方法。其屬于下圖七層網路協(xié)議的“應用層”。
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服務器將開始拒絕來自新客戶端的連接,默認值為511。
- 在HTTP請求服務器時,會發(fā)送兩次請求。一次是用戶發(fā)出請求,另一次是瀏覽器為頁面在收藏夾中的顯示圖標(默認為favicon.ico)而自動發(fā)出的請求。
server.close(); // 服務器關閉時會觸發(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') { // 端口被占用 } }); 當從客戶端請求流中讀取到數(shù)據時會觸發(fā)data事件,當讀取完客戶端請求流中的數(shù)據時觸發(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);// 根據參數(shù)做處理// ...// 結束請求 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ù)據接收完畢:${result}`); }); // 結束本次請求 response.end(); } // 結束本次請求 response.end(JSON.stringify({status: 'success'})); }); querystring模塊:轉換URL中的查詢字符串(URL中?之后,#之前)querystring.stringify(obj, [sep], [eq]) querystring.parse(str, [sep], [eq], [option])
options:{maxKeys: number}指定轉換后對象中的屬性個數(shù) let str = querystring.stringify({name: 'ligang', age: 27}); console.log(str); // name=ligang&age=27 let obj = querystring.parse(str); console.log(obj); // { name: 'ligang', age: '27' }
url.parse(urlStr, [parseQueryString]) parseQueryString:如果為true,將查詢字符通過querystring轉換為對象;默認false。將二者結合成一個路徑,from、to既可以是相對路徑也可以是絕對路徑。// http:///javascript/a?a=1 url.resolve('http:///javascript/', 'a?a=1'); // http:///a?a=1 url.resolve('http:///javascript/', '/a?a=1'); 注意:具體合并規(guī)則,請查看《Node權威指南》— 8.1HTTP服務器。
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:該方法被調用時發(fā)送響應頭setHeader:write方法第一次被調用時發(fā)送響應頭/* 獲取響應頭中的某個字段值 */ response.getHeader(name); /* 刪除一個響應字段值 */ response.removeHeader(name); /* 該屬性表示響應頭是否已發(fā)送 */ response.headersSent; /* 在響應數(shù)據的尾部增加一個頭信息 */ response.addTrailers(headers);
// 必須再響應頭中添加Trailer字段,并且其值設置為追加的響應頭中所指定的字段名 response.write(200, {'Content-Type': 'text/plain', 'Trailer': 'Content-MD5'}); response.write('....'); response.addTrailers({'Content-MD5', '...'}); response.end(); 當再快速網路且數(shù)據量很小的情況下,Node將數(shù)據直接發(fā)送到操作系統(tǒng)內核緩存區(qū)中,然后從該內核緩存區(qū)中取出數(shù)據發(fā)送給請求方;如果網速很慢或者數(shù)據量很大,Node通常會將數(shù)據緩存在內存中,在對方可以接受數(shù)據的情況下將內存中的數(shù)據通過操作系統(tǒng)內核緩存區(qū)發(fā)送給請求方。response.write返回true,說明直接寫到了操作系統(tǒng)內核緩存區(qū)中;返回false,說明暫時緩存的內存中。每次需要通過調用response.end()來結束響應。響應超時會觸發(fā)timeout事件;response.end()方法調用之前,如果連接中斷,會觸發(fā)close事件。/* 設置請求超時時間2分鐘 */ response.setTimeout(2 * 60 * 1000, () => { console.error('請求超時!'); }); // 或者 response.setTimout(2 * 60 * 1000); response.on('timeout', () => { console.error('請求超時!'); }); /* 連接中斷 */ response.on('close', () => { console.error('連接中斷!'); });
/** * HTTP服務端 * 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(`服務器數(shù)據接收完畢:${result}`); response.statusCode = 200; response.write('收到!'); response.end(); // 結束本次請求 }); } }); 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可以輕松向任何網站發(fā)送請求并讀取網站的響應數(shù)據。var req = http.request(options, callback); // get請求 var req = http.get(options, callback); // 向目標網站發(fā)送數(shù)據 req.write(chunk, [encoding]); // 結束本次請求 req.end([chucnk], [encoding]); // 中止本次請求 req.abort(); 其中,options用于指定目標URL地址,如果該參數(shù)是一個字符串,將自動使用url模塊中的parse方法轉換為一個對象。注意:http.get()方法只能使用Get方式請求數(shù)據,且無需調用req.end()方法,Node.js會自動調用。/** * 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('你好,服務器'); 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(`客戶端接受到響應:${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'; /** * 服務端 */ 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>'); // 結束本次請求 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ù)據 */ 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ù)據,發(fā)送請求數(shù)據 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é)議,默認端口號44;
- HTTPS服務器與客戶端之間傳輸是經過SSL安全加密后的密文數(shù)據;
openssl genrsa -out privatekey.pem 1024
openssl req -new -key privatekey.pem -out certrequest.csr (3)獲取證書,線上證書需要經過證書授證中心簽名的文件;下面只創(chuàng)建一個學習使用證書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服務器同HTTP服務器大致相同,需要增加證書,創(chuàng)建HTTPS服務器時通過options參數(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服務器時配置的各種選項,下面只描述幾個必要選項:
const options = {hostname: 'localhost',port: 1443,path: '/',method: 'post',key: fs.readFileSync('privatekey.pem'),cert: fs.readFileSync('certificate.pem'),rejectUnhauthorized: false,agent: false // 從連接池中指定挑選一個當前連接狀態(tài)為關閉的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 服務中,服務端不驗證客戶端的證書(但是需要攜帶證書),中間人可以作為客戶端與服務端成功完成 TLS 握手; 但是中間人沒有證書私鑰,無論如何也無法偽造成服務端跟客戶端建立 TLS 連接。當然如果你擁有證書私鑰,代理證書對應的 HTTPS 網站當然就沒問題了,所以這里的私鑰和公鑰只是格式書寫,沒有太大意義,只要將請求回來的數(shù)據原原本本交給瀏覽器來解析就算完成任務。
|