创建HTTP连接

Node擅长处理I/O操作,所以它不仅合适提供HTTP服务,也适合使用这些服务。接下来你将学习使用http模块和第三方模块执行和控制http请求。

在HTTP协议中,有两个重要的属性:URL和方法。最常见的是GET、POST方法,还有PUT等别的方法。

一、创建GET请求

const http = require('http');

const options = {
    host: 'localhost',
    port: 1234,
    path: '/'
}

http.get(options, (res) => {
    console.log(res);
})

在上面这个例子中,使用TCP端口1234向主机名localhost和路径/执行GET请求(localhost:1234/)。回调函数会在服务器响应到达的时候处理响应对象。

二、使用其他HTTP动词

2.1 发送POST请求

http.get 是通用的 http.request 的快捷方式,其选项如下:

  • host——请求目标的主机名或者IP地址
  • port——远程服务器的TCP端口号
  • method——指定HTTP请求方法的字符串,值就是GET/POST/PUT/PATCH/DELETE等关键词。
  • path——请求的路径,可以包含一个查询字符串和一个分隔符:/index?id=1
  • headers——配置请求头

http.request 方法会返回一个 http.ClientRequest 对象,它是一个可写流,可以使用这个流发送数据,所发送的数据是请求主体数据的一部分,当完成向请求主体中写入数据时,要结束数据流来终止请求。

完整的请求,你可以这么写:

const http = require('http');

const options = {
    host: 'localhost',
    port: 1234,
    path: '/',
    method: 'POST'
}

// 发送请求,监听响应
const req = http.request(options, (response) => {
    console.log(response.statusCode);
    console.log(response.headers);
    response.setEncoding('utf-8');
    response.on('data', (chunk) => {
        console.log('BODY : ' + chunk);
    })
})

req.write('This is a request');
req.end();

http.request 返回ClientRequest对象,可以通过req.write()方法将请求主体写入该对象,可以传入一个经过编码的字符串,或者缓冲区。必须使用end()去结束请求,不然会被认为请求不完整,从而不会处理该请求。

如果服务器正常工作,会返回一个响应,客户端接收到响应触发response事件:

req.on('response', (res) => {
    console.log(res);
})

2.2 查看响应对象

响应对象是ClientResponse的一个实例,主要属性有:

  • statusCode——HTTP状态码
  • httpVersion——HTTP版本号
  • headers——响应头

2.3 获取响应主体

响应主体并不会在请求的response事件发生时出现,而是需要监听data事件:

http.request(options, (response) => {
    response.setEncoding('utf8');
    response.on('data', (data) => {
        console.log(data);
    })
})

响应主题可以是一个缓冲区,但是如果指定了编码格式,那么就是一个编码字符串了。

2.4 以流的方式传送响应主体

HTTP响应是一个可读流,表示响应主体数据流。可以将一个可读流送入一个可写流中,例如一个HTTP请求或者一个文件:

const http = require('http');
const fs = require('fs');
const options = {
    host: 'localhost',
    port: 1234,
    path: '/',
    method: "GET"
}

const file = fs.createWriteStream('./test.text');

http.request(options, (res) => {
    res.pipe(file);
}).end();

上面创建了一个可写文件流,并将响应体写入文件。

当响应到达,文件流打开,响应体写入;当响应主体结束的时候,文件流也结束,文件流被关闭。

三、使用 HTTP.Agent 维护套接字

在创建HTTP请求的时候,Node在内部使用了一个代理。

代理是Node中的一种实体,该实体用来给你创建请求,它负责维护一个套接字池,对指定的主机名和端口对,套接字池中包含已经被打开但是未被使用的连接。

当出现新的HTTP请求时,代理要求连接保持活跃状态;当请求结束,并且在套接字上没有额外的请求等待释放时,套接字就会被关闭,而不用手动关闭。

当创建了一个请求并选择了一个可用的套接字,或者为该请求创建了一个新的连接时,http.ClientRequest 会发射socket事件。在请求结束之后,套接字会在发射close事件或agentRemove事件时从套接字池中被删除。

四、应用第三方请求模块简化HTTP请求

Node的HTTP客户端非常强大,但是用起来很麻烦:

  • 必须提供一个配置对象,需要所有选项,包括被分割的URL。
  • 如果希望处理响应体,需要专门获取响应体。
  • 如果是重定向响应,需要人工处理该响应。

通过使用 request 模块可以简化操作。

1. 安装和应用 request 模块

使用npm在当前目录下安装该模块

npm install request

然后使用:

const r = require('request');

之后只需要提供URL和回调就可以创建请求了

request('localhost:1234', (error,res,body) => {
    ...
})

如果按上述代码发送请求,默认为GET请求吗,如果想要使用其他HTTP动词,例如post:request.put(url);

还可以用option对象来替代URL,可选参数可以查看官方文档。

2. 创建测试服务器

  1. 服务器端代码
const http = require('http');
const server = http.createServer();

// 监听请求
server.on("request", (req, res) => {
    // 回送一个消息
    const printBack = function () {
        res.writeHead(200, {
            'Content-Type': 'text/plain'
        });
        res.end(JSON.stringify({
            URL: req.url,
            method: req.method,
            Headers: req.headers
        }))
    }

    // 路由
    switch (req.url) {
        // 重定向
        case '/redirect':
            res.writeHead(301, { "Location": '/' })
            res.end();
            break;

        // 打印请求体
        case '/print/body':
            req.setEncoding('utf8');
            let body = "";
            req.on('data', (data) => {
                body += data;
            });
            // 结束之后,响应以发送body结束
            req.on('end', () => {
                res.end(JSON.stringify(body));
            })
            break;

        // 默认
        default:
            printBack();
            break;
    }
})

server.listen(1234);
  1. 客户端代码
const request = require('request');
const inspect = require('util').inspect;

request.get('http://localhost:1234/print/body', (err, res, body) => {
    if(err) {
        throw err;
    }
    console.log(inspect({
        err: err,
        res: {
            statusCode: res.statusCode
        },
        body: JSON.parse(body)
    }))
})

3. 跟随重定向

request 的特性之一是它在默认情况下能够跟随重定向。可以用上面的代码来观察这个特征:

当请求路径为/redirect时,可以用301重定向响应码做出响应。可以用客户端来查看跟随上述URL的请求:

将请求路径修改为/redirect,可以在客户端看到返回信息中路径为/而不是/redirect

如果你希望发生重定向的时候得到通知,可以把请求选项中的followRedirect设置为 false 。

4. 使用option

const options = {
    url: 'http://localhost:1234',
    method: 'POST',
    headers: {
        '...': '...'
    }
}
request(options, function(err, res, body){
    ...........
})

5. 对请求体进行编码

有时候需要在请求主体上发送一些数据,可以使用表单编码对请求体进行编码:

const body = {
    a: 1,
    b: 2
};
const options = {
    url: '...',
    form: body
    // 还可以采用json编码,如下:
    // json: body
}

6. 流式传送

请求返回的是一个客户端请求对象,可以将该对象传入可写流当中,甚至可以将请求对象传入另一个请求。

  1. 传入文件
request.get('...').pipe(fs.createWriteStream('...'));
  1. 传入另一个请求
request.get('...').pipe(request.post('...'));