1.Server.js
const http = require("http");
const PORT = 8080;
const HOST = "localhost";
const WAIT_TIME =15;
//REF https://www.hacksparrow.com/express-js-https-server-client-example.html
//REF http://www.lxweimin.com/p/ab2741f78858
/*
[A] HTTP module function
①http.METHODS
②http.STATUS_CODE
③http.createServer([requestListener]) //創(chuàng)建一個(gè)sever
//return http.Server
④http.get(options[,callback)//設(shè)置method為get,且自動(dòng)調(diào)用req.end()
//return http.clientRequest
⑤http.request(options[,callback])//創(chuàng)建一個(gè)client
//return http.clientRequest 是可寫流
var options = {
hostname: 'www.google.com',
port: 80,
path: '/upload',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(postData)
}
};
[B] HTTP module class[http.ClientRequest | ]
①http.ClientRequest[Writable Stream interface + EventEmitter]
-This object is created internally and returned from http.request().
-add "responde" event on the request object
//當(dāng)收到響應(yīng)頭部的時(shí)候?qū)?huì)觸發(fā)"response"事件,
//響應(yīng)時(shí)間有個(gè)http.IncomingMessage參數(shù)
//針對(duì)"response"事件,可以listen for "data" event
//如果添加了"response"事件,必須消費(fèi)響應(yīng)對(duì)象的data,直到數(shù)據(jù)處理完成才會(huì)觸發(fā)"end"事件
-"response" event[http.IncomingMessage] 當(dāng)請(qǐng)求的response被接收時(shí)觸發(fā)。 該事件只觸發(fā)一次。
-req.write(chunk[,encoding][,callback]) //可以以Stream的形式多次發(fā)送body(可以設(shè)置header ['Transfer-Encoding', 'chunked'] )
//return request
-req.end([data][,encoding][,callback]) //相當(dāng)于調(diào)用wirte發(fā)送數(shù)據(jù)后再end
-"abort" event 當(dāng)請(qǐng)求已被客戶端中止時(shí)觸發(fā)。 該事件僅在首次調(diào)用 abort() 時(shí)觸發(fā)。
-"aborted" event 當(dāng)請(qǐng)求已被服務(wù)器中止且網(wǎng)絡(luò) socket 已關(guān)閉時(shí)觸發(fā)。
-"connect" event 通過代理http訪問數(shù)據(jù)[response(http.IncomingMessage),socket(net.socket),head(Buffer)]每當(dāng)服務(wù)器響應(yīng)一個(gè)帶有 CONNECT 方法的請(qǐng)求時(shí)觸發(fā)。 如果該事件未被監(jiān)聽,則接收到 CONNECT 方法的客戶端會(huì)關(guān)閉它們的連接。
-"continue" event 當(dāng)服務(wù)器發(fā)送了一個(gè) 100 Continue 的 HTTP 響應(yīng)時(shí)觸發(fā),通常因?yàn)樵撜?qǐng)求包含 Expect: 100-continue。 這是客戶端應(yīng)發(fā)送request body的指令
-"socket" event[net.Socket] 當(dāng) socket 被分配給request后觸發(fā)。
-"upgrade" event[response(http.IncomingMessage),socket(net.socket),head(Buffer)] 每當(dāng)服務(wù)器響應(yīng)一個(gè)upgrade請(qǐng)求時(shí)觸發(fā)。 如果該事件未被監(jiān)聽,則接收到升級(jí)請(qǐng)求頭的客戶端會(huì)關(guān)閉它們的連接。
-req.abort()
-req.setTimeout(timeout[,callback]) //設(shè)置request超時(shí)
-req.setSocketKeepAlive([enable][,initDelay] //設(shè)置request保持長(zhǎng)連接
-req.setNoDelay([noDelay]) //設(shè)置即可發(fā)送數(shù)據(jù),不緩存
②http.ClientResponse[Writable Stream interface[自己實(shí)現(xiàn)] + EventEmitter]
////作為http.Server()request事件的第二個(gè)參數(shù)
-This object is created internally by an HTTP server--not by the user.
-"close" event 表明在 response.end() 被調(diào)用或能夠刷新之前,底層連接被終止了。
-"finish" event 當(dāng)響應(yīng)頭和主體的最后一部分已被交給操作系統(tǒng)通過網(wǎng)絡(luò)進(jìn)行傳輸時(shí),觸發(fā)該事件。 這并不意味著客戶端已接收到任何東西。
//這個(gè)事件被觸發(fā)后,響應(yīng)對(duì)象上不再觸發(fā)其他任何事件
res.addTrailers(headers) //消息發(fā)送后添加頭信息,Trailers will only be emitted if chunked encoding is used for the response
res.writeHead(200, { 'Content-Type': 'text/plain','Trailer': 'Content-MD5' });
res.write(fileData);
res.addTrailers({'Content-MD5': '7895bf4b8828b55ceaf47747b4bca667'});
res.end();
-res.writeHead(statusCode[,statusMessage][,headers) //發(fā)送response header to the request.
該方法在消息中只能被調(diào)用一次,且必須在 response.end() 被調(diào)用之前調(diào)用。
如果在調(diào)用該方法之前調(diào)用 response.write() 或 response.end(),則隱式或可變的消息頭會(huì)被計(jì)算并調(diào)用該函數(shù)。
當(dāng)消息頭已使用 response.setHeader() 設(shè)置,它們會(huì)被與其他消息頭合并傳給 response.writeHead(),帶消息頭的 response.writeHead() 有更高優(yōu)先級(jí)。
var body = 'hello world';
response.writeHead(200, {'Content-Length': Buffer.byteLength(body),
'Content-Type': 'text/plain' });
-res.write(chunk[,encoding][,callback]) This sends a chunk of the response body可以被多次調(diào)用
-res.end([data][,encoding][,callback]) 對(duì)于每個(gè)響應(yīng),response.end() 方法必須被調(diào)用。告訴服務(wù)器res被發(fā)送完成
-res.finished //boolean代表res是否被發(fā)送完成
-res.getHeader("name") //獲取指定name的頭內(nèi)容
var contentType = response.getHeader('content-type');
-res.headersSent //boolean如果消息頭被發(fā)送了就是true
-res.removeHeader("name")//從隱式發(fā)送中移除一個(gè)頭
-res.sendDate 當(dāng)為true時(shí)會(huì)自動(dòng)添加日期消息頭
-res.setHeader("name","value")設(shè)置響應(yīng)的消息頭
response.setHeader('Set-Cookie', ['type=ninja', 'language=javascript']);
-res.setTimeout(msecs,callback) 設(shè)置socket的超時(shí)時(shí)間
-res.statusCode 返回給客戶端的狀態(tài)碼
-res.statusMessage 返回給客戶端的狀態(tài)信息
③http.IncomingMessage[可讀流接口,可由http.Server或http.ClientRequest創(chuàng)建,作為第一個(gè)參數(shù)傳給request或response事件]
//作為http.Server()request事件的第一個(gè)參數(shù)
-"data" event 請(qǐng)求體數(shù)據(jù)到來時(shí)被觸發(fā)。提供一個(gè)chunk參數(shù),表示接收的數(shù)據(jù)
-"end" event 請(qǐng)求體數(shù)據(jù)傳送完畢時(shí)被觸發(fā),此后不會(huì)再有數(shù)據(jù)了
-"close" event用戶請(qǐng)求結(jié)束時(shí)觸發(fā),用戶強(qiáng)制終止傳輸也是用close不是end事件,表明底層連接被關(guān)閉,每次響應(yīng)只發(fā)生一次
-"aborted" event請(qǐng)求被客戶端終止且socket關(guān)閉時(shí)觸發(fā)
-msg.headers
-msg.httpVersion
-msg.method
-msg.url
-msg.statusCode
-msg.rawHeaders
-msg.rawTrailers
-msg.setTimeout(msecs,callback)
-msg.statusMessage
-msg.socket
-msg.trailers
④http.Server[繼承net.Server,額外添加了一些事件]
-"close" event 服務(wù)器關(guān)閉時(shí)觸發(fā)
-"connection" event [socket]一個(gè)新的TCP流被創(chuàng)建時(shí)觸發(fā)
-"request" event [req(msg),res(res)]每次接到一個(gè)請(qǐng)求時(shí)觸發(fā)
-"connect" event[req(msg),socket(net.Scoket),head(Buffer)] //客戶端請(qǐng)求CONNECT方法是被觸發(fā)
-"upgrade" event[req(msg),socket(net.Scoket),head(Buffer)] //客戶端請(qǐng)求UPGRADE時(shí)被觸發(fā)
-server.close([callback]) //停止服務(wù)端接收服務(wù)
-server.listen([port][,hostname][,backlog][,callback]) //綁定監(jiān)聽端口
-server.listening //boolean 服務(wù)器是否在監(jiān)聽
-server.maxHeadersCount //最大頭數(shù)量
-server.setTimeout(msecs,calllback) //設(shè)置超時(shí),默認(rèn)是兩分鐘
-server.timeout //默認(rèn)是兩分鐘
*/
//listener for "request" event
//req is http.IncomingMessage[readableStream]
//res is http.clientResponse[writableStream]
//return http.Server
const server = http.createServer((req,res)=>{
//console.log 基礎(chǔ)信息
console.log(`\nreq.headers:${JSON.stringify(req.headers)}`);
console.log(`req.url:${req.url}`);
//console.log(`req.statusCode:${req.statusCode}`);//valid for response
console.log(`req.method:${req.method}`);
console.log(`req.httpVersion:${req.httpVersion}`);
let reqData = "";
//msg有data,end,close事件
req.on("data",(chunk)=>{
reqData +=chunk;
});
req.on("end",()=>{
console.log(`${new Date().getTime()} The request sends data completed`);
console.log(`${new Date().getTime()} The reqData:${reqData}`);
//準(zhǔn)備回傳數(shù)據(jù)給client
let resBody= {
"name":"www.baidu.com",
"age":25,
"scholl":"Sichuan University",
"city":"America",
"title":"Senior SoftEngineer",
"tax":"090-33546"
};
//設(shè)置響應(yīng)頭,優(yōu)先級(jí)高于writeHead
res.writeHead(200,{
"Content-Type":"application/json",
"Set-Cookie":["account=ryan","password=A123"],
"Conten-Length":Buffer.byteLength(JSON.stringify(resBody))
});
//回傳數(shù)據(jù)
res.write(JSON.stringify(resBody));
// res.write(JSON.stringify(resBody));
//每個(gè)響應(yīng)必須調(diào)用res.end()
console.log(`${new Date().getTime()} Prepared to exec res.end()`);
//如果這句話被屏蔽,15s后將會(huì)自動(dòng)觸發(fā)req監(jiān)聽的close事件
res.end();
});
req.on("close",()=>{
console.log(`${new Date().getTime()} IncomingMessage close`);
});
});
//listener for "error"
server.on("error",(err)=>{
if(err.code =="EADDRINUSE"){
console.log(`Errors:${err.message}`);
setTimeout(()=>{
//觸發(fā)server的close事件,但是直到所有連接結(jié)束,才會(huì)關(guān)閉服務(wù)。
server.close();
// grab a random port
server.listen(()=>{
console.log("opened server on ",server.address());
});
})
}else{
console.log(err.message);
}
},10000);
//listener for "listening"
server.listen(PORT,HOST,()=>{
//查看服務(wù)器是否在監(jiān)聽
console.log(`Server.listening:${server.listening}`);
//查看服務(wù)器的監(jiān)聽port,host,family
console.log("Opened server on ",server.address());
//設(shè)置服務(wù)器的最大連接數(shù)
server.maxConnections = 4;
//設(shè)置服務(wù)器的最大header數(shù)量
server.maxHeadersCount = 10;
//查看最大頭數(shù)量
console.log(`Server.maxHeadersCount:${server.maxHeadersCount}`);
//設(shè)置timeout時(shí)間
server.timeout = 1000*WAIT_TIME;
});
//listener for "close"
server.on("close",()=>{
console.log("Server has closed.");
})
2.Client.js
const http = require("http");
const querystring = require("querystring");
const WAIT_TIME =2;
//將一個(gè)對(duì)象序列化為查詢字符串"msg=Hello Server From Http Client"
var postData1 = querystring.stringify({
"msg":"Hello Server From Http Client"
});
//請(qǐng)求中的第一個(gè)參數(shù)
var options = {
hostname:"localhost",
port:8080,
path:"/",
method:"POST",
headers:{
"content-Type":"application/json",
"content-Length":Buffer.byteLength(postData1)
}
};
//http.request() returns an instance of the http.ClientRequest[Writable]
//listener for the 'response' event as a one time
//callback arguments http.IncomingMessage
const req =http.request(options,(res)=>{
console.log(`STATUS:${res.statusCode}`);
console.log(`HEADERS:${JSON.stringify(res.headers)}`);
var resData="";
//msg有data,end,close事件
res.on("data",(chunk)=>{
resData +=chunk;
});
res.on("end",()=>{
console.log("The request receive data completed");
console.log(`${new Date().getTime()} The resData:${resData}`);
});
res.on("close",()=>{
console.log(`${new Date().getTime()} IncomingMessage close`);
});
});
//write data to request body
//The ClientRequest instance is a writable stream.
// If one needs to upload a file with a POST request,
// then write to the ClientRequest object.
req.write(postData1);
req.end();
//設(shè)置響應(yīng)超時(shí)時(shí)間
req.setTimeout(1000*WAIT_TIME,()=>{
console.log(`${new Date().getTime()} Server beyond ${WAIT_TIME}s to return data`);
});
//監(jiān)聽請(qǐng)求的錯(cuò)誤
req.on("error",(err)=>{
console.log(`Errors:${err.message}`);
});
3.正常的執(zhí)行結(jié)果
[Server.js]
Server.listening:true
Opened server on { address: '127.0.0.1', family: 'IPv4', port: 8080 }
Server.maxHeadersCount:10
req.headers:{"content-type":"application/json","content-length":"41","host":"localhost:8080","connection":"close"}
req.url:/
req.method:POST
req.httpVersion:1.1
1489919108333 The request sends data completed
1489919108334 The reqData:msg=Hello%20Server%20From%20Http%20Client
1489919108339 Prepared to exec res.end()
[Client.js]
STATUS:200
HEADERS:{"content-type":"application/json","set-cookie":["account=ryan","password=A123"],"conten-length":"128","date":"Sun, 19 Mar 2017 10:25:08 GMT","connection":"close","transfer-encoding":"chunked"}
The request receive data completed
1489919108349 The resData:{"name":"www.baidu.com","age":25,"scholl":"Sichuan University","city":"America","title":"Senior SoftEngineer","tax":"090-33546"}
4.斷點(diǎn)在Server.js的177行res.end(),觀察Client.js中是否觸發(fā)超時(shí)
[Server.js]
Server.listening:true
Opened server on { address: '127.0.0.1', family: 'IPv4', port: 8080 }
Server.maxHeadersCount:10
req.headers:{"content-type":"application/json","content-length":"41","host":"localhost:8080","connection":"close"}
req.url:/
req.method:POST
req.httpVersion:1.1
1489918606073 The request sends data completed
1489918606073 The reqData:msg=Hello%20Server%20From%20Http%20Client
1489918606079 Prepared to exec res.end()
[Client.js]
STATUS:200
HEADERS:{"content-type":"application/json","set-cookie":["account=ryan","password=A123"],"conten-length":"128","date":"Sun, 19 Mar 2017 10:23:49 GMT","connection":"close","transfer-encoding":"chunked"}
1489919031123 Server beyond 2s to return data