UNPKG

3.15 kBJavaScriptView Raw
1
2/**
3 * response.js
4 *
5 * Response class provides content decoding
6 */
7
8var http = require('http');
9var convert = require('encoding').convert;
10
11module.exports = Response;
12
13/**
14 * Response class
15 *
16 * @param Stream body Readable stream
17 * @param Object opts Response options
18 * @return Void
19 */
20function Response(body, opts) {
21
22 this.url = opts.url;
23 this.status = opts.status;
24 this.statusText = http.STATUS_CODES[this.status];
25 this.headers = opts.headers;
26 this.body = body;
27 this.bodyUsed = false;
28 this.size = opts.size;
29 this.ok = this.status >= 200 && this.status < 300;
30
31}
32
33/**
34 * Decode response as json
35 *
36 * @return Promise
37 */
38Response.prototype.json = function() {
39
40 return this._decode().then(function(text) {
41 return JSON.parse(text);
42 });
43
44}
45
46/**
47 * Decode response as text
48 *
49 * @return Promise
50 */
51Response.prototype.text = function() {
52
53 return this._decode();
54
55}
56
57/**
58 * Decode buffers into utf-8 string
59 *
60 * @return Promise
61 */
62Response.prototype._decode = function() {
63
64 var self = this;
65
66 if (this.bodyUsed) {
67 return Response.Promise.reject(new Error('body used already for: ' + this.url));
68 }
69
70 this.bodyUsed = true;
71 this._bytes = 0;
72 this._abort = false;
73 this._raw = [];
74
75 return new Response.Promise(function(resolve, reject) {
76 self.body.on('data', function(chunk) {
77 if (self._abort || chunk === null) {
78 return;
79 }
80
81 if (self.size && self._bytes + chunk.length > self.size) {
82 self._abort = true;
83 reject(new Error('content size at ' + self.url + ' over limit: ' + self.size));
84 return;
85 }
86
87 self._bytes += chunk.length;
88 self._raw.push(chunk);
89 });
90
91 self.body.on('end', function() {
92 if (self._abort) {
93 return;
94 }
95
96 resolve(self._convert());
97 });
98 });
99
100};
101
102/**
103 * Detect buffer encoding and convert to target encoding
104 * ref: http://www.w3.org/TR/2011/WD-html5-20110113/parsing.html#determining-the-character-encoding
105 *
106 * @param String encoding Target encoding
107 * @return String
108 */
109Response.prototype._convert = function(encoding) {
110
111 encoding = encoding || 'utf-8';
112
113 var charset = 'utf-8';
114 var res, str;
115
116 // header
117 if (this.headers.has('content-type')) {
118 res = /charset=(.*)/i.exec(this.headers.get('content-type'));
119 }
120
121 // no charset in content type, peek at response body
122 if (!res && this._raw.length > 0) {
123 str = this._raw[0].toString().substr(0, 1024);
124 }
125
126 // html5
127 if (!res && str) {
128 res = /<meta.+?charset=(['"])(.+?)\1/i.exec(str);
129 }
130
131 // html4
132 if (!res && str) {
133 res = /<meta[\s]+?http-equiv=(['"])content-type\1[\s]+?content=(['"])(.+?)\2/i.exec(str);
134
135 if (res) {
136 res = /charset=(.*)/i.exec(res.pop());
137 }
138 }
139
140 // xml
141 if (!res && str) {
142 res = /<\?xml.+?encoding=(['"])(.+?)\1/i.exec(str);
143 }
144
145 // found charset
146 if (res) {
147 charset = res.pop();
148
149 // prevent decode issues when sites use incorrect encoding
150 // ref: https://hsivonen.fi/encoding-menu/
151 if (charset === 'gb2312' || charset === 'gbk') {
152 charset = 'gb18030';
153 }
154 }
155
156 // turn raw buffers into utf-8 string
157 return convert(
158 Buffer.concat(this._raw)
159 , encoding
160 , charset
161 ).toString();
162
163}
164
165// expose Promise
166Response.Promise = global.Promise;