1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | var parse_url = require('url').parse;
|
9 | var resolve_url = require('url').resolve;
|
10 | var http = require('http');
|
11 | var https = require('https');
|
12 | var zlib = require('zlib');
|
13 | var stream = require('stream');
|
14 |
|
15 | var Response = require('./lib/response');
|
16 | var Headers = require('./lib/headers');
|
17 | var Request = require('./lib/request');
|
18 |
|
19 | module.exports = Fetch;
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 | function Fetch(url, opts) {
|
29 |
|
30 |
|
31 | if (!(this instanceof Fetch))
|
32 | return new Fetch(url, opts);
|
33 |
|
34 |
|
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 |
|
44 | return new Fetch.Promise(function(resolve, reject) {
|
45 |
|
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 |
|
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 |
|
83 | if (options.headers.host) {
|
84 | options.headers.host = options.headers.host[0];
|
85 | }
|
86 |
|
87 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
167 |
|
168 |
|
169 |
|
170 |
|
171 | Fetch.prototype.isRedirect = function(code) {
|
172 | return code === 301 || code === 302 || code === 303 || code === 307 || code === 308;
|
173 | }
|
174 |
|
175 |
|
176 | Fetch.Promise = global.Promise;
|
177 | Fetch.Response = Response;
|
178 | Fetch.Headers = Headers;
|
179 | Fetch.Request = Request;
|