pipeline.js
13.5 KB
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
'use strict';
var _get = require('babel-runtime/helpers/get')['default'];
var _inherits = require('babel-runtime/helpers/inherits')['default'];
var _createClass = require('babel-runtime/helpers/create-class')['default'];
var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default'];
var _getIterator = require('babel-runtime/core-js/get-iterator')['default'];
var _Promise = require('babel-runtime/core-js/promise')['default'];
var _ = require('lodash');
var assert = require('assert');
var Bluebird = require('bluebird');
var Fetch = require('./fetch');
var File = require('fs');
var _require = require('./fetch');
var Headers = _require.Headers;
var _require2 = require('util');
var isArray = _require2.isArray;
var Path = require('path');
var Request = require('request');
var resourceLoader = require('jsdom/lib/jsdom/browser/resource-loader');
var URL = require('url');
var Utils = require('jsdom/lib/jsdom/utils');
// Pipeline is sequence of request/response handlers that are used to prepare a
// request, make the request, and process the response.
var Pipeline = (function (_Array) {
_inherits(Pipeline, _Array);
function Pipeline(browser) {
_classCallCheck(this, Pipeline);
_get(Object.getPrototypeOf(Pipeline.prototype), 'constructor', this).call(this);
this._browser = browser;
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = _getIterator(Pipeline._default), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var handler = _step.value;
this.push(handler);
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator['return']) {
_iterator['return']();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
}
// The default pipeline. All new pipelines are instantiated with this set of
// handlers.
_createClass(Pipeline, [{
key: '_fetch',
value: function _fetch(input, init) {
var request = new Fetch.Request(input, init);
var browser = this._browser;
browser.emit('request', request);
return this._runPipeline(request).then(function (response) {
response.time = Date.now();
response.request = request;
browser.emit('response', request, response);
return response;
})['catch'](function (error) {
browser._debug('Resource error', error.stack);
throw new TypeError(error.message);
});
}
}, {
key: '_runPipeline',
value: function _runPipeline(request) {
var _this = this;
return this._getOriginalResponse(request).then(function (response) {
return _this._prepareResponse(request, response);
});
}
}, {
key: '_getOriginalResponse',
value: function _getOriginalResponse(request) {
var browser = this._browser;
var requestHandlers = this.getRequestHandlers().concat(Pipeline.makeHTTPRequest);
return Bluebird.reduce(requestHandlers, function (lastResponse, requestHandler) {
return lastResponse || requestHandler(browser, request);
}, null).then(function (response) {
assert(response && response.hasOwnProperty('statusText'), 'Request handler must return a response');
return response;
});
}
}, {
key: '_prepareResponse',
value: function _prepareResponse(request, originalResponse) {
var browser = this._browser;
var responseHandlers = this.getResponseHandlers();
return Bluebird.reduce(responseHandlers, function (lastResponse, responseHandler) {
return responseHandler(browser, request, lastResponse);
}, originalResponse).then(function (response) {
assert(response && response.hasOwnProperty('statusText'), 'Response handler must return a response');
return response;
});
}
// -- Handlers --
// Add a request or response handler. This handler will only be used by this
// pipeline instance (browser).
}, {
key: 'addHandler',
value: function addHandler(handler) {
assert(handler.call, 'Handler must be a function');
assert(handler.length === 2 || handler.length === 3, 'Handler function takes 2 (request handler) or 3 (reponse handler) arguments');
this.push(handler);
}
// Remove a request or response handler.
}, {
key: 'removeHandler',
value: function removeHandler(handler) {
assert(handler.call, 'Handler must be a function');
var index = this.indexOf(handler);
if (index > -1) {
this.splice(index, 1);
}
}
// Add a request or response handler. This handler will be used by any new
// pipeline instance (browser).
}, {
key: 'getRequestHandlers',
// Get array of request handlers
value: function getRequestHandlers() {
// N.B. inheriting from an Array is slightly broken, so just iterate manually
var result = [];
for (var i = 0; i < this.length; i++) {
if (this[i].length === 2) result.push(this[i]);
}return result;
}
// Get array of request handlers
}, {
key: 'getResponseHandlers',
value: function getResponseHandlers() {
// N.B. inheriting from an Array is slightly broken, so just iterate manually
var result = [];
for (var i = 0; i < this.length; i++) {
if (this[i].length === 3) result.push(this[i]);
}return result;
}
// -- Prepare request --
// This handler normalizes the request URL.
//
// It turns relative URLs into absolute URLs based on the current document URL
// or base element, or if no document open, based on browser.site property.
}], [{
key: 'addHandler',
value: function addHandler(handler) {
assert(handler.call, 'Handler must be a function');
assert(handler.length === 2 || handler.length === 3, 'Handler function takes 2 (request handler) or 3 (response handler) arguments');
this._default.push(handler);
}
// Remove a request or response handler.
}, {
key: 'removeHandler',
value: function removeHandler(handler) {
assert(handler.call, 'Handler must be a function');
var index = this._default.indexOf(handler);
if (index > -1) {
this._default.splice(index, 1);
}
}
}, {
key: 'normalizeURL',
value: function normalizeURL(browser, request) {
if (browser.document)
// Resolve URL relative to document URL/base, or for new browser, using
// Browser.site
request.url = resourceLoader.resolveResourceUrl(browser.document, request.url);else request.url = Utils.resolveHref(browser.site || 'http://localhost', request.url);
}
// This handler mergers request headers.
//
// It combines headers provided in the request with custom headers defined by
// the browser (user agent, authentication, etc).
//
// It also normalizes all headers by down-casing the header names.
}, {
key: 'mergeHeaders',
value: function mergeHeaders(browser, request) {
if (browser.headers) _.each(browser.headers, function (value, name) {
request.headers.append(name, browser.headers[name]);
});
if (!request.headers.has('User-Agent')) request.headers.set('User-Agent', browser.userAgent);
// Always pass Host: from request URL
var _URL$parse = URL.parse(request.url);
var host = _URL$parse.host;
request.headers.set('Host', host);
// HTTP Basic authentication
var authenticate = { host: host, username: null, password: null };
browser.emit('authenticate', authenticate);
var username = authenticate.username;
var password = authenticate.password;
if (username && password) {
browser.log('Authenticating as ' + username + ':' + password);
var base64 = new Buffer(username + ':' + password).toString('base64');
request.headers.set('authorization', 'Basic ' + base64);
}
}
// -- Retrieve actual resource --
// Used to perform HTTP request (also supports file: resources). This is always
// the last request handler.
}, {
key: 'makeHTTPRequest',
value: function makeHTTPRequest(browser, request) {
var url = request.url;
var _URL$parse2 = URL.parse(url);
var protocol = _URL$parse2.protocol;
var hostname = _URL$parse2.hostname;
var pathname = _URL$parse2.pathname;
if (protocol === 'file:') {
// If the request is for a file:// descriptor, just open directly from the
// file system rather than getting node's http (which handles file://
// poorly) involved.
if (request.method !== 'GET') return new Fetch.Response('', { url: url, status: 405 });
var filename = Path.normalize(decodeURI(pathname));
var exists = File.existsSync(filename);
if (exists) {
var stream = File.createReadStream(filename);
return new Fetch.Response(stream, { url: url, status: 200 });
} else return new Fetch.Response('', { url: url, status: 404 });
}
// We're going to use cookies later when receiving response.
var cookies = browser.cookies;
var cookieHeader = cookies.serialize(hostname, pathname);
if (cookieHeader) request.headers.append('Cookie', cookieHeader);
var consumeBody = /^POST|PUT/.test(request.method) && request._consume() || _Promise.resolve(null);
return consumeBody.then(function (body) {
var httpRequest = new Request({
method: request.method,
uri: request.url,
headers: request.headers.toObject(),
proxy: browser.proxy,
body: body,
jar: false,
followRedirect: false,
strictSSL: browser.strictSSL,
localAddress: browser.localAddress || 0
});
return new _Promise(function (resolve, reject) {
httpRequest.on('response', function (response) {
// Request returns an object where property name is header name,
// property value is either header value, or an array if header sent
// multiple times (e.g. `Set-Cookie`).
var arrayOfHeaders = _.reduce(response.headers, function (headers, value, name) {
if (isArray(value)) {
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = _getIterator(value), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var item = _step2.value;
headers.push([name, item]);
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2['return']) {
_iterator2['return']();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
} else headers.push([name, value]);
return headers;
}, []);
resolve(new Fetch.Response(response, {
url: request.url,
status: response.statusCode,
headers: new Headers(arrayOfHeaders)
}));
}).on('error', reject);
});
});
}
// -- Handle response --
}, {
key: 'handleHeaders',
value: function handleHeaders(browser, request, response) {
response.headers = new Headers(response.headers);
return response;
}
}, {
key: 'handleCookies',
value: function handleCookies(browser, request, response) {
// Set cookies from response: call update() with array of headers
var _URL$parse3 = URL.parse(request.url);
var hostname = _URL$parse3.hostname;
var pathname = _URL$parse3.pathname;
var newCookies = response.headers.getAll('Set-Cookie');
browser.cookies.update(newCookies, hostname, pathname);
return response;
}
}, {
key: 'handleRedirect',
value: function handleRedirect(browser, request, response) {
var status = response.status;
if (status === 301 || status === 302 || status === 303 || status === 307 || status === 308) {
if (request.redirect === 'error') return Fetch.Response.error();
var _location = response.headers.get('Location');
if (_location === null) return response;
if (request._redirectCount >= 20) return Fetch.Response.error();
browser.emit('redirect', request, response, _location);
++request._redirectCount;
if (status !== 307) {
request.method = 'GET';
request.headers['delete']('Content-Type');
request.headers['delete']('Content-Length');
request.headers['delete']('Content-Transfer-Encoding');
}
// This request is referer for next
request.headers.set('Referer', request.url);
request.url = Utils.resolveHref(request.url, _location);
return browser.pipeline._runPipeline(request);
} else return response;
}
}]);
return Pipeline;
})(Array);
Pipeline._default = [Pipeline.normalizeURL, Pipeline.mergeHeaders, Pipeline.handleHeaders, Pipeline.handleCookies, Pipeline.handleRedirect];
module.exports = Pipeline;
//# sourceMappingURL=pipeline.js.map