通读SuperAgent文档

发表:
大纲
  1. 1. 前言
  2. 2. 基本用法
  3. 3. 设置请求头
  4. 4. GET请求
  5. 5. HEAD请求
  6. 6. POST/PUT请求
  7. 7. 设置Content-Type
  8. 8. 设置Accept
  9. 9. 查询字符串
  10. 10. 解析响应体
    1. 10.1. JSON/Urlencoded
    2. 10.2. Multipart
  11. 11. 响应体属性
    1. 11.1. Response Text
    2. 11.2. Response Body
    3. 11.3. Response header fields
    4. 11.4. Response Content-Type
    5. 11.5. Response Status
  12. 12. 中断请求
  13. 13. 请求超时
  14. 14. 基本授权
  15. 15. 跟随重定向
  16. 16. 管道数据
  17. 17. 复合请求
    1. 17.1. 附加文件
    2. 17.2. 字段值
  18. 18. 压缩
  19. 19. 缓冲响应
  20. 20. 跨域资源共享
  21. 21. 异常处理

本文主要参考superagent的官方文档,基本上就是它的翻译。

题外话,superagent真是一个不错的nodejs模块,推荐使用。

前言

superagent是一个流行的nodejs第三方模块,专注于处理服务端/客户端的http请求。

在nodejs中,我们可以使用内置的http等模块来进行请求的发送、响应处理等操作,不过superagent提供了更加简单、优雅的API,让你在处理请求时更加方便。而且它很轻量,学习曲线平滑,内部其实就是对内置模块的封装。

下面让我们先来看一个简单的例子,大致的了解下request的基本用法,以及它和使用内置模块的区别。

var request = require('superagent');
var http = require('http')
var queryString = require('queryString');

// 使用superagent
request
    .post('/api/pet')
    .send({ name: 'Manny', species: 'cat' })
    .set('X-API-Key', 'foobar')
    .set('Accept', 'application/json')
    .end(function(err, res){
        if (res.ok) {
            alert('yay got ' + JSON.stringify(res.body));
        } else {
            alert('Oh no! error ' + res.text);
        }
    });

// 使用http
var postData = queryString.stringify({name: 'Manny', species: 'cat'});
var options = {
    path: '/api/pet',
    method: 'POST',
    headers: {
        'X-API-Key': 'foobar',
        'Accept': 'application/json'
    }
};

var req = http.request(options, function (res) {
    res.on('data', function (chunk) {
        console.log(chunk);
    });
});

req.on('error', function (err) {
    console.log(err);
});

req.write(postData);
req.end();

从上面的代码中,我们可以看出,使用superagent发送一个post请求,并设置了相关请求头信息(可以链式操作),相比较使用内置的模块,要简单很多。

基本用法

var request = require('superagent')之后,request对象上将会有许多方法可供使用。我们可以使用getpost等方法名来声明一个get或者post请求,然后再通过end()方法来发出请求。下面是个例子。

request
    .get('/search')
    .end(function (err, res) {

    })

这里有一点需要注意,就是只要当调用了end()方法之后,这个请求才会发出。在调用end()之前的所有动作其实都是对请求的配置。

我们在具体使用时,还可以将请求方法作为字符串参数,比如

request('GET', '/search').end(function (err, res) {

});

在nodejs客户端中,还可以提供绝对路径,

request
    .get('http://www.baidu.com/search')
    .end(function (err, res) {

    });

其余的http谓语动词也是可以使用,比如DELETEHEADPOSTPUT等等。使用的时候,我们只需要变更request[METHOD]中的METHOD即可。

request
    .head('/favicon.ico')
    .end(function (err, res) {

    });

不过,针对DELETE方法,有一点不同,因为delete是一个保留关键字,所以我在使用的时候应该是使用del()而不是delete()

request
    .del('/user/111')
    .end(function (err, res) {

    });

superagent默认的http方法是GET。就是说,如果你的请求是get请求,那么你可以省略http方法的相关配置。懒人必备。

request('/search', function(err, res) {

});

设置请求头

在之前的例子中,我们看到使用内置的http模块在设置请求头信息时,要求传递给http.request一个options,这个options包含了所有的请求头信息。相对这种方式,superagent提供了一种更佳简单优雅的方式。在superagent中,设置头信息,使用set()方法即可,而且它有多种形式,必能满足你的需求。

// 传递key-value键值对,可以一次设置一个头信息
request
    .get('/search')
    .set('X-API-Key', 'foobar')
    .set('Accept', 'application/json')
    .end(function (err, res) {

    });

// 传递对象,可以一次设置多次头信息
request
    .get('/search2')
    .set({
        'API-Key': 'foobar',
        'Accept': 'application/json'
    })
    .end(function (err, res) {

    });

GET请求

在使用super.get处理GET请求时,我们可以使用query()来处理请求字符串。比如,

request
    .get('/search')
    .query({name: 'Manny'})
    .query({range: '1..5'})
    .query({order: 'desc'})
    .end(function (err, res) {

    });

上面的代码将会发出一个/search?name=Manny&range=1..5&order=desc的请求。

我们可以使用query()传递一个大对象,将所有的查询参数都写入。

request
    .get('/search')
    .query({
        name: 'Manny',
        range: '1..5',
        order: 'desc'
    })
    .end(function (err, res) {

    });

我们还可以简单粗暴的直接将整个查询字符串拼接起来作为query()的参数,

request
    .get('/search')
    .query('name=Manny&range=1..5&order=desc')
    .end(function (err, res) {

    });

或者是实时拼接字符串,

var name = 'Manny';
var range = '1..5';
var order = 'desc';

request
    .get('/search')
    .query('name=' + name)
    .query('range=' + range)
    .query('order=' + order)
    .end(function (err, res) {

    });

HEAD请求

request.head请求中,我们也可以使用query()来设置参数,

request
    .head('/users')
    .query({
        email: 'joe@gmail.com'
    })
    .end(function (err, res) {

    });

上面的代码将会发出一个/users?email=joe@gamil.com的请求。

POST/PUT请求

一个典型的json post请求看起来像下面这样,

request
    .post('/user')
    .set('Content-Type', 'application/json')
    .send('{"name": "tj", "pet": "tobi"}')
    .end(callback);

我们通过set()设置一个合适的Content-Type,然后通过send()写入一些post data,最后通过end()发出这个post请求。注意这里我们send()的参数是一个json字符串。

因为json格式现在是一个通用的标准,所以默认的Content-Type就是json格式。下面的代码跟前一个示例是等价的,

request
    .post('/user')
    .send({name: "tj", pet: "tobi"})
    .end(callback);

我们也可以使用多个send()拼接post data,

request
    .post('/user')
    .send({name: "tj"})
    .send({pet: 'tobi'})
    .end(callback);

send()方法发送字符串时,将默认设置Content-Typeapplication/x-www-form-urlencoded,多次send()的调用,将会使用&将所有的数据拼接起来。比如下面这个例子,我们发送的数据将为name=tj&per=tobi

request
    .post('/user')
    .send('name=tj')
    .send('pet=tobi')
    .end(callback);

superagent的请求数据格式化是可以扩展的,不过默认支持formjson两种格式,想发送数据以application/x-www-form-urlencoded类型的话,则可以简单的调用type()方法传递form参数就行,type()方法的默认参数是json

下面的代码将会使用"name=tj&pet=tobi"来发出一个post请求。

request
    .post('/user')
    .type('form')
    .send({name: 'tj'})
    .send({pet: 'tobi'})
    end(callback);

设置Content-Type

设置Content-Type最常用的方法就是使用set()

request
    .post('/user')
    .set('Content-Type', 'application/json')
    .end(callback);

另一个简便的方法是调用type()方法,传递一个规范的MIME名称,包括type/subtype,或者是一个简单的后缀就像xmljsonpng这样。比如,

request.post('/user').type('application/json')

request.post('/user').type('json')

request.post('/user').type('png')

设置Accept

type()类似,设置accept也可以简单的调用accept()。参数接受一个规范的MIME名称,包括type/subtype,或者是一个简单的后缀就像xmljsonpng这样。这个参数值将会被request.types所引用。

request.get('/user').accept('application/json')

request.get('/user').accept('json')

request.get('/user').accept('png')

查询字符串

当用send(obj)方法来发送一个post请求,并且希望传递一些查询字符串时,可以调用query()方法,比如向?format=json&dest=/login发送post请求,

request
    .post('/')
    .query({format: 'json'})
    .query({dest: '/login'})
    .send({post: 'data', here: 'wahoo'})
    .end(callback);

解析响应体

superagent会解析一些常用的格式给请求者,当前支持application/x-www-form-urlencodedapplication/jsonmultipart/form-data

JSON/Urlencoded

此种情况下,res.body是一个解析过的对象。比如一个请求的响应是一个json字符串'{"user": {"name": "tobi"}}',那么res.body.user.name将会返回tobi

同样的,x-www-form-urlencoded格式的user[name]=tobi解析完的值是一样的。

Multipart

nodejs客户端通过Formidable模块来支持multipart/form-data类型,当解析一个multipart响应时,res.files属性就可以用了。假设一个请求响应下面的数据,

--whoop
Content-Disposition: attachment; name="image"; filename="tobi.png"
Content-Type: image/png

... data here ...
--whoop
Content-Disposition: form-data; name="name"
Content-Type: text/plain

Tobi
--whoop--

你将可以获取到res.body.name名为'Tobi'res.files.image为一个file对象,包括一个磁盘文件路径,文件名称,还有其它的文件属性等。

响应体属性

response对象设置了许多有用的标识和属性。包括response.text,解析后的respnse.body,头信息,返回状态标识等。

Response Text

res.text包含未解析前的响应内容。基于节省内存和性能因素的原因,一般只在mime类型能够匹配text/jsonx-www-form-urlencoding的情况下默认为nodejs客户端提供。因为当响应以文件或者图片等大体积文件的情况下将会影响性能。

Response Body

跟请求数据自动序列化一样,响应数据也会自动的解析。当Content-Type定义一个解析器后,就能自动解析,默认解析的Content-Type包含application/jsonapplication/x-www-form-urlencoded,可以通过访问res.body来访问解析对象。

Response header fields

res.header包含解析之后的响应头数据,键值都是小写字母形式,比如res.header['content-length']

Response Content-Type

Content-Type响应头字段是一个特列,服务器提供res.type来访问它,同时res.charset默认是空的。比如,Content-Type值为text/html; charset=utf8,则res.typetext/htmlres.charsetutf8

Response Status

响应状态标识可以用来判断请求是否成功以及一些其他的额外信息。除此之外,还可以用superagent来构建理想的restful服务器。目前提供的标识定义如下,

var type = status / 100 | 0;

// status / class
res.status = status;
res.statusType = type;

// basics
res.info = 1 == type;
res.ok = 2 == type;
res.clientError = 4 == type;
res.serverError = 5 == type;
res.error = 4 == type || 5 == type;

// sugar
res.accepted = 202 == status;
res.noContent = 204 == status || 1223 == status;
res.badRequest = 400 == status;
res.unauthorized = 401 == status;
res.notAcceptable = 406 == status;
res.notFound = 404 == status;
res.forbidden = 403 == status;

中断请求

要想中断请求,可以简单的调用request.abort()即可。

请求超时

可以通过request.timeout()来定义超时时间。当超时错误发生时,为了区别于别的错误,err.timeout属性被定义为超时时间(一个ms的值)。注意,当超时错误发生后,后续的请求都会被重定向。意思就说超时是针对所有的请求而不是单个某个请求。

基本授权

nodejs客户端目前提供两种基本授权的方式。

一种是通过类似下面这样的url,

request.get('http://tobi:learnboost@local').end(callback);

意思就是说要求在url中指明user:password

第二种方式就是通过auth()方法。

request
    .get('http://local')
    .auth('tobi', 'learnboost')
    .end(callback);

跟随重定向

默认是向上跟随5个重定向,不过可以通过调用res.redirects(n)来设置个数,

request
    .get('/some.png')
    .redirects(2)
    .end(callback);

管道数据

nodejs客户端允许使用一个请求流来输送数据。比如请求一个文件作为输出流,

var request = require('superagent');
var fs = require('fs');

var stream = fs.createReadStream('path/to/my.json');
var req = request.post('/somewhere');
req.type('json');
stream.pipe(req);

或者将一个响应流写到文件中,

var request = require('superagent')
var fs = require('fs');

var stream = fs.createWriteStream('path/to/my.json');
var req = request.get('/some.json');
req.pipe(stream);

复合请求

superagent用来构建复合请求非常不错,提供了低级和高级的api方法。

低级的api是使用多个部分来表现一个文件或者字段,part()方法返回一个新的部分,提供了跟request本身相似的api方法。

var req = request.post('/upload');

req.part()
    .set('Content-Type', 'image/png')
    .set('Content-Disposition', 'attachment; filename="myimage.png"')
    .write('some image data')
    .write('some more image data');

req.part()
    .set('Content-Disposition', 'form-data; name="name"')
    .set('Content-Type', 'text/plain')
    .write('tobi');

req.end(callback);

附加文件

上面提及的高级api方法,可以通用attach(name, [path], [filename])field(name, value)这两种形式来调用。添加多个附件也比较简单,只需要给附件提供自定义的文件名称,同样的基础名称也要提供。

request
    .post('/upload')
    .attach('avatar', 'path/to/tobi.png', 'user.png')
    .attach('image', 'path/to/loki.png')
    .attach('file', 'path/to/jane.png')
    .end(callback);

字段值

跟html的字段很像,你可以调用field(name, value)方法来设置字段。假设你想上传一个图片的时候带上自己的名称和邮箱,那么你可以像下面写的那样,

request
    .post('/upload')
    .field('user[name]', 'Tobi')
    .field('user[email]', 'tobi@learnboost.com')
    .attach('image', 'path/to/tobi.png')
    .end(callback);

压缩

nodejs客户端本身对响应体就做了压缩,而且做的很好。所以这块你不需要做额外的事情了。

缓冲响应

当想要强制缓冲res.text这样的响应内容时,可以调用buffer()方法。想取消默认的文本缓冲响应,比如text/plaintext/html这样的,可以调用buffer(false)方法。

一旦设置了缓冲标识res.buffered,那么就可以在一个回调函数里处理缓冲和没缓冲的响应。

跨域资源共享

withCredentials()方法可以激活发送原始cookie的能力。不过只有在Access-Control-Allow-Origin不是一个通配符(),并且*Access-Control-Allow-Credentialstrue的情况下才可以。

request
    .get('http://localhost:4001/')
    .withCredentials()
    .end(function(err, res, next){
        assert(200 == res.status);
        assert('tobi' == res.text);
        next();
    });

异常处理

当请求出错时,superagent首先会检查回调函数的参数数量,当err参数提供的话,参数就是两个,如下

request
    .post('/upload')
    .attach('image', 'path/to/tobi.png')
    .end(function(err, res){

    });

如果没有回调函数,或者回调函数只有一个参数的话,可以添加error事件的处理。

request
    .post('/upload')
    .attach('image', 'path/to/tobi.png')
    .on('error', errorHandle)
    .end(function(res){

    });

注意:superagent不认为返回4xx5xx的情况是错误。比如当请求返回500或者403之类的状态码时,可以通过res.error或者res.status等属性来查看。此时并不会有错误对象传递到回调函数中。当发生网络错误或者解析错误时,superagent才会认为是发生了请求错误,此时会传递一个错误对象err作为回调函数的第一个参数。

当产生一个4xx或者5xx的http响应,res.error提供了一个错误信息的对象,你可以通过检查这个来做某些事情。

if (err && err.status === 404) {
    alert('oh no ' + res.body.message);
} else if (err) {
    // all other error types we handle generically
}