HttpClient
最早做项目时,还不知道 egg 基于 urllib 内置实现了一个 HttpClient, 可以非常方便的完成任何 HTTP 请求。最近刚好有时间来看下 egg 的文档,就来聊聊 egg 内置的 HttpClient。
可以通过 app 使用 HttpClient,也可以通过 Context 使用 HttpClient。
框架在初始化时,自动将HttpClient 初始化到 app.httpclient,同时增加了一个 app.curl(url, options) 方法, 同 app.httpclient.request(url, options)是等价的。
1 2 3 4 5 6 7 8
| module.exports = app => { app.beforeStart(async () => { const result = await app.curl('https://registry.npm.taobao.org/egg/latest', { dataType: 'json', }); }) }
|
在 Context 中同样提供了 ctx.curl(url, options) 和 ctx.httpclient,使用方法同 app 下使用相同。
1 2 3 4 5 6 7 8 9 10 11
| class NpmController extends Controller { async index() { const ctx = this.ctx; const result = await ctx.curl('https://registry.npm.taobao.org/egg/latest', { dataType: 'json', timeout: 3000, }); } }
|
基本HTTP请求
GET
1 2 3 4 5 6 7 8 9 10
| class NpmController extends Controller { async get() { const ctx = this.ctx; const result = await ctx.curl('https://httpbin.org/get?foo=bar'); ctx.status = result.status; ctx.set(result.headers); ctx.body = result.data; } }
|
- GET 请求可以不用设置 options.methods, HttpClient 的默认 method 会设置为 GET。
- 返回值 result 会包含3个属性: status, headers, data
- status: 响应状态码,这个就是大家熟悉的 http 状态码: 200, 302, 404等;
- headers: 响应头,类似 { ‘content-type’: ‘text/html’}
- data: 响应body, 默认 HttpClient 不会做处理,直接返回 Buffer 类型数据。一旦设置了 options.dataType, HttpClient 会根据此参数对 data 进行处理。
POST
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class NpmController extends Controller { async post() { const ctx = this.ctx; const result = await ctx.curl('https://httpbin.org/post', { method: 'POST', contentType: 'json', data: { hello: 'world', now: Date.now(), }, dataType: 'json', }) } }
|
PUT
用法与 POST 类似,更加适合更新数据和替换数据的语义,除了 method 参数需要设置为 PUT,其他参数与 POST 几乎一致。
DELETE
删除数据会选择 DELETE 请求,通常不需要加请求 body, 但 HttpClient 不会限制。
1 2 3 4 5 6 7 8 9 10 11
| class NpmController extends Controller { async del() { const ctx = this.ctx; const result = await ctx.curl('https://httpbin.org/delete', { method: 'DELETE', dataType: 'json', }); ctx.body = result.data; } }
|
高级 HTTP 请求
这部分主要是涉及到表单及文件上传相关的请求。
面向浏览器的 Form 表单(不包含文件)的提交接口,通常都是以 content-type: application/x-www-form-urlencoded 的格式提交请求数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class NpmController extends Controller { async submit() { const ctx = this.ctx; const result = await ctx.curl('https://httpbin.org/post', { method: 'POST', data: { now: Date.now(), foo: 'bar', }, dataType: 'json', }); ctx.body = result.data.form; } }
|
以 Multipart 方式上传文件
当 From 表单提交包含文件的时候,请求数据格式,就必须以 multipart/form-data 进行提交。此时,需引入 fromstream 帮助我们生成可被 HttpClient 消费的 form 对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| const FormStream = require('formstream'); class NpmController extends Controller { async upload() { const ctx = this.ctx; const form = new FormStream(); form.field('foo', 'bar'); form.file('file', __filename);
const result = await ctx.curl('https://httpbin.org/post', { method: 'POST', headers: form.headers(), stream: form, dataType: 'json', }); ctx.body = result.data.files; } }
|
可以继续通过 form.file() 添加更多文件以实现一次性上传多个文件的需求。
1 2
| form.file('file1', file1); form.file('file2', file2);
|
以 Stream 方式上传文件
Stream 实际会以 Transfer-Encoding: Chunked 传输编码格式发送,这个转换是 HTTP 模块自动实现的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const fs = require('fs'); const FormStream = require('formstream'); class NpmController extends Controller { async uploadByStream() { const ctx = this.ctx; const fileStream = fs.createReadStream(__filename); const url = `${ctx.protocol}://${ctx.host}/stream`; const result = await ctx.curl(url, { method: 'POST', stream: fileStream, }); ctx.status = result.status; ctx.set(result.headers); ctx.body = result.data; } }
|
Options 参数详解
HttpClient 默认全局配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| exports.httpclient = { enableDNSCache: false, dnsCacheLookupInterval: 10000, dnsCacheMaxLength: 1000, request: { timeout: 3000, },
httpAgent: { keepAlive: true, freeSocketTimeout: 4000, timeout: 30000, maxSockets: Number.MAX_SAFE_INTEGER, maxFreeSockets: 256, },
httpsAgent: { keepAlive: true, freeSocketTimeout: 4000, timeout: 30000, maxSockets: Number.MAX_SAFE_INTEGER, maxFreeSockets: 256, }, };
|
data: Object
- GET, HEAD: 通过querystring.stringify(data)处理后拼接到url的 query 参数上。
- POST, PUT, DELETE等:需要根据 contentType 做进一步处理判断:
- contentTyp = json: 通过 JSON.stringify(data) 处理,并设置body 发送。
- 其他: 通过 querystring.stringify(data) 处理,并设置 body 发送。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ctx.curl(url, { data: { foo: 'bar' }, });
ctx.curl(url, { method: 'POST', data: { foo: 'bar' } });
ctx.curl(url, { method: 'POST', contentType: 'json', data: { foo: 'bar' }, });
|
dataAsQueryString: Boolean
如果 dataAsQueryString=true ,即使在 POST 情况下,也会强制 options.data 以 querystring.stringify 处理之后拼接到 url 的 query 参数上。
可以解决以 stream 发送数据,且额外的请求参数以 url query 形式传递的场景:
1 2 3 4 5 6 7 8
| ctx.curl(url, { method: 'POST', dataAsQueryString: true, data: {, accessToken: 'some data', }, stream: fileStream, })
|
content: String|Buffer
发送请求正文,如若设置了此参数,会直接忽略 data 参数。
1 2 3 4 5 6 7
| ctx.curl(url, { method: 'POST', content: '<xml>hello world</xml>', headers: { 'content-type': 'text/html', }, });
|