2.HTTP学习

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.jianshu.com/p/ab2741f78858
/*
 [A] HTTP module function
 ①http.METHODS
 ②http.STATUS_CODE
 ③http.createServer([requestListener]) //创建一个sever
    //return http.Server
 ④http.get(options[,callback)//设置method为get,且自动调用req.end()
    //return http.clientRequest
 ⑤http.request(options[,callback])//创建一个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
    //当收到响应头部的时候将会触发"response"事件,
    //响应时间有个http.IncomingMessage参数
    //针对"response"事件,可以listen for "data" event
    //如果添加了"response"事件,必须消费响应对象的data,直到数据处理完成才会触发"end"事件
  -"response" event[http.IncomingMessage] 当请求的response被接收时触发。 该事件只触发一次。

 -req.write(chunk[,encoding][,callback]) //可以以Stream的形式多次发送body(可以设置header ['Transfer-Encoding', 'chunked'] )
 //return request
 -req.end([data][,encoding][,callback]) //相当于调用wirte发送数据后再end

  -"abort" event 当请求已被客户端中止时触发。 该事件仅在首次调用 abort() 时触发。
  -"aborted" event 当请求已被服务器中止且网络 socket 已关闭时触发。
  -"connect" event 通过代理http访问数据[response(http.IncomingMessage),socket(net.socket),head(Buffer)]每当服务器响应一个带有 CONNECT 方法的请求时触发。 如果该事件未被监听,则接收到 CONNECT 方法的客户端会关闭它们的连接。
  -"continue" event 当服务器发送了一个 100 Continue 的 HTTP 响应时触发,通常因为该请求包含 Expect: 100-continue。 这是客户端应发送request body的指令

  -"socket" event[net.Socket] 当 socket 被分配给request后触发。
  -"upgrade" event[response(http.IncomingMessage),socket(net.socket),head(Buffer)] 每当服务器响应一个upgrade请求时触发。 如果该事件未被监听,则接收到升级请求头的客户端会关闭它们的连接。

  -req.abort()
  -req.setTimeout(timeout[,callback]) //设置request超时
  -req.setSocketKeepAlive([enable][,initDelay] //设置request保持长连接
  -req.setNoDelay([noDelay]) //设置即可发送数据,不缓存

 ②http.ClientResponse[Writable Stream interface[自己实现] + EventEmitter]
 ////作为http.Server()request事件的第二个参数
 -This object is created internally by an HTTP server--not by the user.
 -"close" event 表明在 response.end() 被调用或能够刷新之前,底层连接被终止了。
 -"finish" event 当响应头和主体的最后一部分已被交给操作系统通过网络进行传输时,触发该事件。 这并不意味着客户端已接收到任何东西。
    //这个事件被触发后,响应对象上不再触发其他任何事件
     res.addTrailers(headers) //消息发送后添加头信息,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) //发送response header to the request.
     该方法在消息中只能被调用一次,且必须在 response.end() 被调用之前调用。
     如果在调用该方法之前调用 response.write() 或 response.end(),则隐式或可变的消息头会被计算并调用该函数。
     当消息头已使用 response.setHeader() 设置,它们会被与其他消息头合并传给 response.writeHead(),带消息头的 response.writeHead() 有更高优先级。
     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可以被多次调用
 -res.end([data][,encoding][,callback])  对于每个响应,response.end() 方法必须被调用。告诉服务器res被发送完成

 -res.finished //boolean代表res是否被发送完成
 -res.getHeader("name") //获取指定name的头内容
    var contentType = response.getHeader('content-type');
 -res.headersSent //boolean如果消息头被发送了就是true
 -res.removeHeader("name")//从隐式发送中移除一个头
 -res.sendDate 当为true时会自动添加日期消息头
 -res.setHeader("name","value")设置响应的消息头
    response.setHeader('Set-Cookie', ['type=ninja', 'language=javascript']);
 -res.setTimeout(msecs,callback) 设置socket的超时时间
 -res.statusCode 返回给客户端的状态码
 -res.statusMessage 返回给客户端的状态信息

 ③http.IncomingMessage[可读流接口,可由http.Server或http.ClientRequest创建,作为第一个参数传给request或response事件]
 //作为http.Server()request事件的第一个参数
 -"data" event 请求体数据到来时被触发。提供一个chunk参数,表示接收的数据
 -"end" event 请求体数据传送完毕时被触发,此后不会再有数据了
 -"close" event用户请求结束时触发,用户强制终止传输也是用close不是end事件,表明底层连接被关闭,每次响应只发生一次

 -"aborted" event请求被客户端终止且socket关闭时触发

 -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 服务器关闭时触发
 -"connection" event [socket]一个新的TCP流被创建时触发
 -"request" event [req(msg),res(res)]每次接到一个请求时触发

 -"connect" event[req(msg),socket(net.Scoket),head(Buffer)] //客户端请求CONNECT方法是被触发
 -"upgrade" event[req(msg),socket(net.Scoket),head(Buffer)] //客户端请求UPGRADE时被触发

 -server.close([callback]) //停止服务端接收服务
 -server.listen([port][,hostname][,backlog][,callback]) //绑定监听端口
 -server.listening //boolean 服务器是否在监听
 -server.maxHeadersCount //最大头数量
 -server.setTimeout(msecs,calllback) //设置超时,默认是两分钟
 -server.timeout //默认是两分钟



 */

//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 基础信息
    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}`);

        //准备回传数据给client
        let resBody= {
            "name":"www.baidu.com",
            "age":25,
            "scholl":"Sichuan University",
            "city":"America",
            "title":"Senior SoftEngineer",
            "tax":"090-33546"
        };

        //设置响应头,优先级高于writeHead
        res.writeHead(200,{
            "Content-Type":"application/json",
            "Set-Cookie":["account=ryan","password=A123"],
            "Conten-Length":Buffer.byteLength(JSON.stringify(resBody))
        });

        //回传数据
        res.write(JSON.stringify(resBody));
       // res.write(JSON.stringify(resBody));

        //每个响应必须调用res.end()
        console.log(`${new Date().getTime()} Prepared to exec res.end()`);
        //如果这句话被屏蔽,15s后将会自动触发req监听的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(()=>{
            //触发server的close事件,但是直到所有连接结束,才会关闭服务。
            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,()=>{
    //查看服务器是否在监听
    console.log(`Server.listening:${server.listening}`);
    //查看服务器的监听port,host,family
    console.log("Opened server on ",server.address());
    //设置服务器的最大连接数
    server.maxConnections = 4;
    //设置服务器的最大header数量
    server.maxHeadersCount = 10;
    //查看最大头数量
    console.log(`Server.maxHeadersCount:${server.maxHeadersCount}`);
    //设置timeout时间
    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;
//将一个对象序列化为查询字符串"msg=Hello Server From Http Client"
var postData1 = querystring.stringify({
    "msg":"Hello Server From Http Client"
});

//请求中的第一个参数
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();

//设置响应超时时间
req.setTimeout(1000*WAIT_TIME,()=>{
    console.log(`${new Date().getTime()} Server beyond ${WAIT_TIME}s to return data`);
});

//监听请求的错误
req.on("error",(err)=>{
    console.log(`Errors:${err.message}`);
});

3.正常的执行结果

[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.断点在Server.js的177行res.end(),观察Client.js中是否触发超时

[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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容