1
1
/*! @license MIT ©2013-2016 Ruben Verborgh - Ghent University / iMinds */
2
- /* Single-function HTTP(S) request module for browsers using jQuery */
2
+ /* Single-function HTTP(S) request module for browsers */
3
3
4
4
var EventEmitter = require ( 'events' ) . EventEmitter ,
5
5
AsyncIterator = require ( 'asynciterator' ) ,
@@ -9,57 +9,72 @@ var EventEmitter = require('events').EventEmitter,
9
9
require ( 'setimmediate' ) ;
10
10
11
11
// Headers we cannot send (see https://www.w3.org/TR/XMLHttpRequest/#the-setrequestheader()-method)
12
- var UNSAFE_REQUEST_HEADERS = [ 'accept-encoding' , 'user-agent' , 'referer' ] ;
13
- // Headers we need to obtain
14
- var RESPONSE_HEADERS = [ 'content-type' , 'content-location' , 'link' , 'memento-datetime' ] ;
12
+ var UNSAFE_REQUEST_HEADERS = _ . object ( [ 'accept-encoding' , 'user-agent' , 'referer' ] ) ;
15
13
16
14
// Resources that were already time-negotiated
17
15
var negotiatedResources = Object . create ( null ) ;
18
16
19
17
// Creates an HTTP request with the given settings
20
18
function createRequest ( settings ) {
21
- var request = new EventEmitter ( ) ;
22
-
23
19
// PERFORMANCE HACK:
24
20
// Reduce OPTIONS preflight requests by removing the Accept-Datetime header
25
21
// on requests for resources that are presumed to have been time-negotiated
26
22
if ( negotiatedResources [ removeQuery ( settings . url ) ] )
27
23
delete settings . headers [ 'accept-datetime' ] ;
28
24
29
- // Delegate the request to jQuery's AJAX module
30
- var jqXHR = jQuery . ajax ( {
31
- url : settings . url ,
32
- timeout : settings . timeout ,
33
- type : settings . method ,
34
- headers : _ . omit ( settings . headers , UNSAFE_REQUEST_HEADERS ) ,
35
- } ) ;
36
- // Emit the result as a readable response iterator
37
- jqXHR . then ( function ( ) {
38
- var response = AsyncIterator . single ( jqXHR . responseText || '' ) ;
39
- response . statusCode = jqXHR . status ;
40
- response . headers = _ . object ( RESPONSE_HEADERS , RESPONSE_HEADERS . map ( jqXHR . getResponseHeader ) ) ;
41
- request . emit ( 'response' , response ) ;
25
+ // Create the actual XMLHttpRequest
26
+ var request = new XMLHttpRequest ( ) , reqHeaders = settings . headers ;
27
+ request . open ( settings . method , settings . url , true ) ;
28
+ request . timeout = settings . timeout ;
29
+ for ( var header in reqHeaders ) {
30
+ if ( ! ( header in UNSAFE_REQUEST_HEADERS ) && reqHeaders [ header ] )
31
+ request . setRequestHeader ( header , reqHeaders [ header ] ) ;
32
+ }
33
+
34
+ // Create a proxy for the XMLHttpRequest
35
+ var requestProxy = new EventEmitter ( ) ;
36
+ requestProxy . abort = function ( ) { request . abort ( ) ; } ;
37
+
38
+ // Handle the arrival of a response
39
+ request . onload = function ( ) {
40
+ // Convert the response into an iterator
41
+ var response = AsyncIterator . single ( request . responseText || '' ) ;
42
+ response . statusCode = request . status ;
43
+
44
+ // Parse the response headers
45
+ var resHeaders = response . headers = { } ,
46
+ rawHeaders = request . getAllResponseHeaders ( ) || '' ,
47
+ headerMatcher = / ^ ( [ ^ : \n \r ] + ) : [ \t ] * ( [ ^ \r \n ] * ) $ / mg, match ;
48
+ while ( match = headerMatcher . exec ( rawHeaders ) )
49
+ resHeaders [ match [ 1 ] . toLowerCase ( ) ] = match [ 2 ] ;
50
+
51
+ // Emit the response
52
+ requestProxy . emit ( 'response' , response ) ;
42
53
43
- // If the resource was time-negotiated, store its "base" URI (= no query string)
44
- if ( settings . headers [ 'accept-datetime' ] && response . headers [ 'memento-datetime' ] ) {
45
- var resource = removeQuery ( response . headers [ 'content-location' ] || settings . url ) ;
54
+ // If the resource was time-negotiated, store its queryless URI
55
+ // to enable the PERFORMANCE HACK explained above
56
+ if ( reqHeaders [ 'accept-datetime' ] && resHeaders [ 'memento-datetime' ] ) {
57
+ var resource = removeQuery ( resHeaders [ 'content-location' ] || settings . url ) ;
46
58
if ( ! negotiatedResources [ resource ] ) {
47
59
// Ensure the resource is not a timegate
48
- var links = response . headers . link && parseLink ( response . headers . link ) ,
60
+ var links = resHeaders . link && parseLink ( resHeaders . link ) ,
49
61
timegate = removeQuery ( links && links . timegate && links . timegate . url ) ;
50
62
if ( resource !== timegate )
51
63
negotiatedResources [ resource ] = true ;
52
64
}
53
65
}
54
- } ,
55
- // Emit an error if the request fails
56
- function ( ) {
57
- request . emit ( 'error' , new Error ( 'Error requesting ' + settings . url ) ) ;
58
- } ) ;
59
- // Aborts the request
60
- request . abort = function ( ) { jqXHR . abort ( ) ; } ;
66
+ } ;
67
+ // Report errors and timeouts
68
+ request . onerror = function ( ) {
69
+ requestProxy . emit ( 'error' , new Error ( 'Error requesting ' + settings . url ) ) ;
70
+ } ;
71
+ request . ontimeout = function ( ) {
72
+ requestProxy . emit ( 'error' , new Error ( 'Timeout requesting ' + settings . url ) ) ;
73
+ } ;
61
74
62
- return request ;
75
+ // Execute the request
76
+ request . send ( ) ;
77
+ return requestProxy ;
63
78
}
64
79
65
80
// Removes the query string from a URL
0 commit comments