1
1
var sys = require ( 'sys' ) ,
2
2
http = require ( 'http' ) ,
3
+ https = require ( 'https' ) ,
3
4
url = require ( 'url' ) ,
4
5
qs = require ( 'querystring' ) ,
5
6
multipart = require ( './multipartform' ) ;
6
7
7
- function Request ( url , options ) {
8
- this . url = uri . parse ( url ) ;
8
+ function Request ( uri , options ) {
9
+ this . url = url . parse ( uri ) ;
9
10
this . options = options ;
10
11
this . headers = {
11
12
'Accept' : '*/*' ,
12
- 'Host' : this . url . domain ,
13
13
'User-Agent' : 'Restler for node.js'
14
14
}
15
- var _this = this
16
- Object . keys ( options . headers || { } ) . forEach ( function ( headerKey ) {
17
- _this . headers [ headerKey ] = options . headers [ headerKey ]
18
- } )
19
15
20
- if ( ! this . url . path ) this . url . path = '/'
16
+ Object . keys ( options . headers || { } ) . forEach ( function ( headerKey ) {
17
+ this . headers [ headerKey ] = options . headers [ headerKey ]
18
+ } , this ) ;
21
19
22
20
// set port and method defaults
23
- if ( ! this . url . port ) this . url . port = ( this . url . protocol == 'https' ) ? '443' : '80' ;
21
+ if ( ! this . url . port ) this . url . port = ( this . url . protocol == 'https: ' ) ? '443' : '80' ;
24
22
if ( ! this . options . method ) this . options . method = ( this . options . data ) ? 'POST' : 'GET' ;
25
23
if ( typeof this . options . followRedirects == 'undefined' ) this . options . followRedirects = true ;
26
24
@@ -33,10 +31,27 @@ function Request(url, options) {
33
31
34
32
this . _applyBasicAuth ( ) ;
35
33
36
- this . client = this . _getClient ( this . url . port , this . url . domain , ( this . url . protocol == 'https' ) ) ;
34
+ // TODO support chunked requests as well
35
+ if ( typeof this . options . data == 'object' ) {
36
+ this . options . data = qs . stringify ( this . options . data ) ;
37
+ this . headers [ 'Content-Type' ] = 'application/x-www-form-urlencoded' ;
38
+ this . headers [ 'Content-Length' ] = this . options . data . length ;
39
+ } else if ( this . options . multipart ) {
40
+ this . headers [ 'Content-Type' ] = 'multipart/form-data; boundary=' + multipart . defaultBoundary ;
41
+ this . headers [ 'Transfer-Encoding' ] = 'chunked' ;
42
+ }
37
43
38
- if ( this . options . multipart ) this . _createMultipartRequest ( ) ;
39
- else this . _createRequest ( ) ;
44
+ var proto = ( this . url . protocol == 'https:' ) ? https : http ;
45
+
46
+ this . request = proto . request ( {
47
+ host : this . url . host ,
48
+ port : this . url . port ,
49
+ path : this . _fullPath ( ) ,
50
+ method : this . options . method ,
51
+ headers : this . headers
52
+ } ) ;
53
+
54
+ this . _makeRequest ( ) ;
40
55
}
41
56
42
57
Request . prototype = new process . EventEmitter ( ) ;
@@ -51,25 +66,27 @@ var requestMethods = {
51
66
return path ;
52
67
} ,
53
68
_applyBasicAuth : function ( ) {
54
- // use URL credentials over options
55
- if ( this . url . user ) this . options . username = this . url . user ;
56
- if ( this . url . password ) this . options . password = this . url . password ;
69
+ var authParts ;
70
+
71
+ if ( this . url . auth ) {
72
+ authParts = this . url . auth . split ( ':' ) ;
73
+ this . options . username = authParts [ 0 ] ;
74
+ this . options . password = authParts [ 1 ] ;
75
+ }
57
76
58
77
if ( this . options . username && this . options . password ) {
59
- var auth = base64 . encode ( this . options . username + ':' + this . options . password ) ;
60
- this . headers [ 'Authorization' ] = "Basic " + auth ;
78
+ var b = new Buffer ( [ this . options . username , this . options . password ] . join ( ':' ) ) ;
79
+ this . headers [ 'Authorization' ] = "Basic " + b . toString ( 'base64' ) ;
61
80
}
62
81
} ,
63
- _getClient : function ( port , host , https ) {
64
- return this . options . client || http . createClient ( port , host , https ) ;
65
- } ,
66
82
_responseHandler : function ( response ) {
67
83
var self = this ;
68
84
69
85
if ( this . _isRedirect ( response ) && this . options . followRedirects == true ) {
70
86
try {
71
- var location = uri . resolve ( this . url , response . headers [ 'location' ] ) ;
72
- this . options . originalRequest = this ;
87
+ var location = url . resolve ( this . url , response . headers [ 'location' ] ) ;
88
+ this . options . originalRequest = this ;
89
+
73
90
request ( location , this . options ) ;
74
91
} catch ( e ) {
75
92
self . _respond ( 'error' , '' , 'Failed to follow redirect' ) ;
@@ -78,7 +95,8 @@ var requestMethods = {
78
95
var body = '' ;
79
96
80
97
response . addListener ( 'data' , function ( chunk ) {
81
- body += chunk ;
98
+ // TODO Handle different encodings
99
+ body += chunk . toString ( ) ;
82
100
} ) ;
83
101
84
102
response . addListener ( 'end' , function ( ) {
@@ -94,53 +112,38 @@ var requestMethods = {
94
112
}
95
113
} ,
96
114
_respond : function ( type , data , response ) {
97
- if ( this . options . originalRequest ) this . options . originalRequest . emit ( type , data , response ) ;
98
- else this . emit ( type , data , response ) ;
99
- } ,
100
- _makeRequest : function ( method ) {
101
- var self = this ;
102
- // Support new and old interface for making requests for now
103
- var request ;
104
- if ( typeof this . client . request == 'function' ) {
105
- request = this . client . request ( method , this . _fullPath ( ) , this . headers ) ;
115
+ if ( this . options . originalRequest ) {
116
+ this . options . originalRequest . emit ( type , data , response ) ;
106
117
} else {
107
- method = method . toLowerCase ( ) ;
108
- if ( method == 'delete' ) method = 'del' ;
109
- request = this . client [ method ] ( this . _fullPath ( ) , this . headers ) ;
110
- }
111
- request . addListener ( "response" , function ( response ) {
112
- self . _responseHandler ( response ) ;
113
- } ) ;
114
- return request ;
115
- } ,
116
- _createRequest : function ( ) {
117
- if ( typeof this . options . data == 'object' ) {
118
- this . options . data = qs . stringify ( this . options . data ) ;
119
- this . headers [ 'Content-Type' ] = 'application/x-www-form-urlencoded' ;
120
- this . headers [ 'Content-Length' ] = this . options . data . length ;
118
+ this . emit ( type , data , response ) ;
121
119
}
122
-
123
- this . request = this . _makeRequest ( this . options . method ) ;
124
- if ( this . options . data ) this . request . write ( this . options . data . toString ( ) , this . options . encoding || 'utf8' ) ;
125
120
} ,
126
- _createMultipartRequest : function ( ) {
127
- this . headers [ 'Content-Type' ] = 'multipart/form-data; boundary=' + multipart . defaultBoundary ;
128
- this . headers [ 'Transfer-Encoding' ] = 'chunked' ;
121
+ _makeRequest : function ( ) {
122
+ var self = this ;
123
+
124
+ this . request . addListener ( "response" , function ( response ) {
125
+ self . _responseHandler ( response ) ;
126
+ } ) ;
129
127
130
- this . request = this . _makeRequest ( this . options . method ) ;
128
+ if ( this . options . data ) {
129
+ this . request . write ( this . options . data . toString ( ) , this . options . encoding || 'utf8' ) ;
130
+ }
131
131
} ,
132
132
run : function ( ) {
133
133
var self = this ;
134
+
134
135
if ( this . options . multipart ) {
135
136
multipart . write ( this . request , this . options . data , function ( ) {
136
137
self . request . end ( ) ;
137
138
} ) ;
138
139
} else {
139
- this . request . end ( ) ;
140
+ this . request . end ( ) ;
140
141
}
142
+
141
143
return this ;
142
144
}
143
145
}
146
+
144
147
Object . keys ( requestMethods ) . forEach ( function ( requestMethod ) {
145
148
Request . prototype [ requestMethod ] = requestMethods [ requestMethod ]
146
149
} )
@@ -157,7 +160,7 @@ function request(url, options) {
157
160
}
158
161
159
162
function get ( url , options ) {
160
- return request ( url , shortcutOptions ( options , 'GET' ) ) ; ;
163
+ return request ( url , shortcutOptions ( options , 'GET' ) ) ;
161
164
}
162
165
163
166
function post ( url , options ) {
@@ -172,33 +175,50 @@ function del(url, options) {
172
175
return request ( url , shortcutOptions ( options , 'DELETE' ) ) ;
173
176
}
174
177
178
+ var ResponseParser = function ( contentTypeMatcher , parser ) {
179
+ this . contentTypeMatcher = contentTypeMatcher ;
180
+ this . parser = parser ;
181
+ }
182
+
183
+ ResponseParser . prototype . match = function ( contentType ) {
184
+ // TODO enhance for fuzzier matching if required
185
+ return contentType == this . contentTypeMatcher ;
186
+ } ;
187
+
188
+ ResponseParser . prototype . parse = function ( data ) {
189
+ return this . parser ( data ) ;
190
+ } ;
191
+
175
192
var parsers = {
176
193
auto : function ( data ) {
177
194
var contentType = this . headers [ 'content-type' ] ;
195
+
178
196
if ( contentType ) {
179
- if ( contentType . indexOf ( 'application/' ) == 0 ) {
180
- if ( contentType . indexOf ( 'json' , 12 ) == 12 )
181
- return parsers . json ( data ) ;
182
- if ( contentType . indexOf ( 'xml' , 12 ) == 12 )
183
- return parsers . xml ( data ) ;
184
- if ( contentType . indexOf ( 'yaml' , 12 ) == 12 )
185
- return parsers . yaml ( data ) ;
197
+ for ( var matcher in parsers . auto . parsers ) {
198
+ if ( contentType == matcher ) {
199
+ return parsers . auto . parsers [ matcher ] . call ( this , data ) ;
200
+ }
186
201
}
187
202
}
188
-
189
- // Data doesn't match any known application/* content types.
203
+
190
204
return data ;
191
- } ,
192
- xml : function ( data ) {
193
- return data && x2j . parse ( data ) ;
194
- } ,
195
- json : function ( data ) {
205
+ }
206
+ }
207
+
208
+ // TODO Make these async so it'll work with async parsers (xml2js)
209
+ parsers . auto . parsers = {
210
+ 'application/json' : function ( data ) {
196
211
return data && JSON . parse ( data ) ;
197
- } ,
198
- yaml : function ( data ) {
212
+ }
213
+ } ;
214
+
215
+ try {
216
+ var yaml = require ( 'yaml' ) ;
217
+ parsers . auto . parsers [ 'application/yaml' ] = function ( data ) {
199
218
return data && yaml . eval ( data ) ;
200
219
}
201
- }
220
+ } catch ( e ) { }
221
+
202
222
203
223
function Service ( defaults ) {
204
224
if ( defaults . baseURL ) {
@@ -231,17 +251,20 @@ var serviceMethods = {
231
251
} ,
232
252
_withDefaults : function ( options ) {
233
253
var o = { } ;
234
- var defaults = this . defaults
254
+ var defaults = this . defaults ;
255
+
235
256
Object . keys ( defaults ) . forEach ( function ( _default ) {
236
257
o [ _default ] = defaults [ _default ]
237
- } )
258
+ } ) ;
259
+
238
260
Object . keys ( options ) . forEach ( function ( option ) {
239
261
o [ option ] = options [ option ]
240
- } )
262
+ } ) ;
241
263
242
264
return o ;
243
265
}
244
266
}
267
+
245
268
Object . keys ( serviceMethods ) . forEach ( function ( serviceMethod ) {
246
269
Service . prototype [ serviceMethod ] = serviceMethods [ serviceMethod ]
247
270
} )
0 commit comments