UNPKG

4.12 kBJavaScriptView Raw
1
2/**
3 * index.js
4 *
5 * a request API compatible with window.fetch
6 */
7
8var parse_url = require('url').parse;
9var resolve_url = require('url').resolve;
10var http = require('http');
11var https = require('https');
12var zlib = require('zlib');
13var stream = require('stream');
14
15var Response = require('./lib/response');
16var Headers = require('./lib/headers');
17var Request = require('./lib/request');
18
19module.exports = Fetch;
20
21/**
22 * Fetch class
23 *
24 * @param Mixed url Absolute url or Request instance
25 * @param Object opts Fetch options
26 * @return Promise
27 */
28function Fetch(url, opts) {
29
30 // allow call as function
31 if (!(this instanceof Fetch))
32 return new Fetch(url, opts);
33
34 // allow custom promise
35 if (!Fetch.Promise) {
36 throw new Error('native promise missing, set Fetch.Promise to your favorite alternative');
37 }
38
39 Response.Promise = Fetch.Promise;
40
41 var self = this;
42
43 // wrap http.request into fetch
44 return new Fetch.Promise(function(resolve, reject) {
45 // build request object
46 var options;
47 try {
48 options = new Request(url, opts);
49 } catch (err) {
50 reject(err);
51 return;
52 }
53
54 var send;
55 if (options.protocol === 'https:') {
56 send = https.request;
57 } else {
58 send = http.request;
59 }
60
61 // normalize headers
62 var headers = new Headers(options.headers);
63
64 if (options.compress) {
65 headers.set('accept-encoding', 'gzip,deflate');
66 }
67
68 if (!headers.has('user-agent')) {
69 headers.set('user-agent', 'node-fetch/1.0 (+https://github.com/bitinn/node-fetch)');
70 }
71
72 if (!headers.has('connection')) {
73 headers.set('connection', 'close');
74 }
75
76 if (!headers.has('accept')) {
77 headers.set('accept', '*/*');
78 }
79
80 options.headers = headers.raw();
81
82 // http.request only support string as host header, this hack make custom host header possible
83 if (options.headers.host) {
84 options.headers.host = options.headers.host[0];
85 }
86
87 // send request
88 var req = send(options);
89 var reqTimeout;
90
91 if (options.timeout) {
92 req.once('socket', function(socket) {
93 reqTimeout = setTimeout(function() {
94 req.abort();
95 reject(new Error('network timeout at: ' + options.url));
96 }, options.timeout);
97 });
98 }
99
100 req.on('error', function(err) {
101 clearTimeout(reqTimeout);
102 reject(new Error('request to ' + options.url + ' failed, reason: ' + err.message));
103 });
104
105 req.on('response', function(res) {
106 clearTimeout(reqTimeout);
107
108 // handle redirect
109 if (self.isRedirect(res.statusCode)) {
110 if (options.counter >= options.follow) {
111 reject(new Error('maximum redirect reached at: ' + options.url));
112 return;
113 }
114
115 if (!res.headers.location) {
116 reject(new Error('redirect location header missing at: ' + options.url));
117 return;
118 }
119
120 options.counter++;
121
122 resolve(Fetch(resolve_url(options.url, res.headers.location), options));
123 return;
124 }
125
126 // handle compression
127 var body = res.pipe(new stream.PassThrough());
128 var headers = new Headers(res.headers);
129
130 if (options.compress && headers.has('content-encoding')) {
131 var name = headers.get('content-encoding');
132
133 if (name == 'gzip' || name == 'x-gzip') {
134 body = body.pipe(zlib.createGunzip());
135 } else if (name == 'deflate' || name == 'x-deflate') {
136 body = body.pipe(zlib.createInflate());
137 }
138 }
139
140 // response object
141 var output = new Response(body, {
142 url: options.url
143 , status: res.statusCode
144 , headers: headers
145 , size: options.size
146 , timeout: options.timeout
147 });
148
149 resolve(output);
150 });
151
152 // accept string or readable stream as body
153 if (typeof options.body === 'string') {
154 req.write(options.body);
155 req.end();
156 } else if (typeof options.body === 'object' && options.body.pipe) {
157 options.body.pipe(req);
158 } else {
159 req.end();
160 }
161 });
162
163};
164
165/**
166 * Redirect code matching
167 *
168 * @param Number code Status code
169 * @return Boolean
170 */
171Fetch.prototype.isRedirect = function(code) {
172 return code === 301 || code === 302 || code === 303 || code === 307 || code === 308;
173}
174
175// expose Promise
176Fetch.Promise = global.Promise;
177Fetch.Response = Response;
178Fetch.Headers = Headers;
179Fetch.Request = Request;