1 | 'use strict';
|
2 |
|
3 | var util = require('util');
|
4 | var http = require('http');
|
5 | var parameters = require('./parameters');
|
6 | var ApiHttpError = require('./errors').ApiHttpError;
|
7 | var OAuthError = require('./errors').OAuthError;
|
8 | var qs = require('querystring');
|
9 | var _ = require('lodash');
|
10 | var helpers = require('./helpers');
|
11 | var oauthHelper = require('./oauth');
|
12 | var USER_AGENT = 'Node.js HTTP Client';
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 | function prepare(data, consumerkey) {
|
20 | var prop;
|
21 | data = data || {};
|
22 |
|
23 | for (prop in data) {
|
24 | if (data.hasOwnProperty(prop)) {
|
25 | if (_.isDate(data[prop])) {
|
26 | data[prop] = helpers.toYYYYMMDD(data[prop]);
|
27 | }
|
28 | }
|
29 | }
|
30 |
|
31 | data.oauth_consumer_key = consumerkey;
|
32 |
|
33 | return data;
|
34 | }
|
35 |
|
36 |
|
37 |
|
38 |
|
39 | function createHeaders(host, headers) {
|
40 | return _.extend({}, headers, {
|
41 | 'host': host,
|
42 | 'User-Agent' : USER_AGENT
|
43 | });
|
44 | }
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 | function logHeaders(logger, headers) {
|
51 | return _.each(_.keys(headers), function (key) {
|
52 | logger.info(key + ': ' + headers[key]);
|
53 | });
|
54 | }
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 | function get(endpointInfo, requestData, headers, credentials, logger,
|
65 | callback) {
|
66 |
|
67 | var normalisedData = prepare(requestData, credentials.consumerkey);
|
68 | var fullUrl = endpointInfo.url + '?' + qs.stringify(normalisedData);
|
69 | var hostInfo = {
|
70 | host: endpointInfo.host,
|
71 | port: endpointInfo.port
|
72 | };
|
73 |
|
74 |
|
75 | if (endpointInfo.authtype) {
|
76 | hostInfo.host = endpointInfo.sslHost;
|
77 | dispatchSecure(endpointInfo.url, 'GET', requestData, headers,
|
78 | endpointInfo.authtype, hostInfo, credentials, logger,
|
79 | callback);
|
80 | } else {
|
81 | dispatch(endpointInfo.url, 'GET', requestData, headers, hostInfo,
|
82 | credentials, logger, callback);
|
83 | }
|
84 | }
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 |
|
95 | function postOrPut(httpMethod, endpointInfo, requestData, headers, credentials,
|
96 | logger, callback) {
|
97 |
|
98 | var hostInfo = {
|
99 | host: endpointInfo.host,
|
100 | port: endpointInfo.port
|
101 | };
|
102 |
|
103 | if (endpointInfo.authtype) {
|
104 | hostInfo.host = endpointInfo.sslHost;
|
105 | dispatchSecure(endpointInfo.url, httpMethod, requestData, headers,
|
106 | endpointInfo.authtype, hostInfo, credentials, logger, callback);
|
107 | } else {
|
108 | dispatch(endpointInfo.url, httpMethod, requestData, headers, hostInfo,
|
109 | credentials, logger, callback);
|
110 | }
|
111 | }
|
112 |
|
113 | function buildSecureUrl(httpMethod, hostInfo, path, requestData) {
|
114 | var querystring = httpMethod === 'GET'
|
115 | ? '?' + qs.stringify(requestData)
|
116 | : '';
|
117 | path = parameters.template(path, requestData);
|
118 | return 'https://' + hostInfo.host + ':' + hostInfo.port + path + querystring;
|
119 | }
|
120 |
|
121 |
|
122 |
|
123 |
|
124 |
|
125 |
|
126 |
|
127 |
|
128 |
|
129 |
|
130 | function dispatchSecure(path, httpMethod, requestData, headers, authtype,
|
131 | hostInfo, credentials, logger, callback) {
|
132 | var url;
|
133 | var is2Legged = authtype === '2-legged';
|
134 | var token = is2Legged ? null : requestData.accesstoken;
|
135 | var secret = is2Legged ? null : requestData.accesssecret;
|
136 | var mergedHeaders = createHeaders(hostInfo.host, headers);
|
137 | var oauthClient = oauthHelper.createOAuthWrapper(credentials.consumerkey,
|
138 | credentials.consumersecret, mergedHeaders);
|
139 | var methodLookup = {
|
140 | 'POST' : oauthClient.post.bind(oauthClient),
|
141 | 'PUT' : oauthClient.put.bind(oauthClient)
|
142 | };
|
143 | var oauthMethod = methodLookup[httpMethod];
|
144 |
|
145 | hostInfo.port = hostInfo.port || 443;
|
146 |
|
147 | requestData = prepare(requestData, credentials.consumerkey);
|
148 |
|
149 | if (!is2Legged) {
|
150 | delete requestData.accesstoken;
|
151 | delete requestData.accesssecret;
|
152 | }
|
153 |
|
154 | url = buildSecureUrl(httpMethod, hostInfo, path, requestData);
|
155 |
|
156 | logger.info('token: ' + token + ' secret: ' + secret);
|
157 | logger.info(httpMethod + ': ' + url + ' (' + authtype + ' oauth)');
|
158 | logHeaders(logger, mergedHeaders);
|
159 |
|
160 | function cbWithDataAndResponse(err, data, response) {
|
161 | var apiError;
|
162 |
|
163 | if (err) {
|
164 |
|
165 | if (err.statusCode && err.statusCode >= 400) {
|
166 |
|
167 |
|
168 | if (typeof err.data === 'string' && /oauth/i.test(err.data)) {
|
169 | apiError = new OAuthError(err.data, err.data + ': ' +
|
170 | path);
|
171 | } else {
|
172 | apiError = new ApiHttpError(
|
173 | response.statusCode, err.data, path);
|
174 | }
|
175 |
|
176 | return callback(apiError);
|
177 | }
|
178 |
|
179 |
|
180 | logger.error(err);
|
181 | return callback(err);
|
182 | }
|
183 |
|
184 | return callback(null, data, response);
|
185 | }
|
186 |
|
187 | if (httpMethod === 'GET') {
|
188 | return oauthClient.get(url, token, secret, cbWithDataAndResponse);
|
189 | }
|
190 |
|
191 | if ( oauthMethod ) {
|
192 | logger.info('DATA: ' + qs.stringify(requestData));
|
193 | return oauthMethod(url, token, secret, requestData,
|
194 | 'application/x-www-form-urlencoded', cbWithDataAndResponse);
|
195 | }
|
196 |
|
197 | return callback(new Error('Unsupported HTTP verb: ' + httpMethod));
|
198 | }
|
199 |
|
200 |
|
201 |
|
202 |
|
203 |
|
204 |
|
205 |
|
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 | function dispatch(url, httpMethod, data, headers, hostInfo, credentials,
|
212 | logger, callback) {
|
213 |
|
214 | hostInfo.port = hostInfo.port || 80;
|
215 |
|
216 | var apiRequest, prop, hasErrored;
|
217 | var mergedHeaders = createHeaders(hostInfo.host, headers);
|
218 | var apiPath = url;
|
219 |
|
220 | data = prepare(data, credentials.consumerkey);
|
221 |
|
222 |
|
223 |
|
224 | if (url.indexOf('track/preview') >= 0) {
|
225 | data.redirect = 'false';
|
226 | }
|
227 |
|
228 | if (httpMethod === 'GET') {
|
229 | url = url + '?' + qs.stringify(data);
|
230 | }
|
231 |
|
232 | logger.info(util.format('%s: http://%s:%s%s', httpMethod,
|
233 | hostInfo.host, hostInfo.port, url));
|
234 | logHeaders(logger, mergedHeaders);
|
235 |
|
236 |
|
237 | apiRequest = http.request({
|
238 | method: httpMethod,
|
239 |
|
240 |
|
241 | agent: false,
|
242 | hostname: hostInfo.host,
|
243 |
|
244 |
|
245 | scheme: 'http',
|
246 |
|
247 |
|
248 |
|
249 |
|
250 | withCredentials: false,
|
251 | path: url,
|
252 | port: hostInfo.port,
|
253 | headers: mergedHeaders
|
254 | }, function handleResponse(response) {
|
255 | var responseBuffer = '';
|
256 |
|
257 | if (typeof response.setEncoding === 'function') {
|
258 | response.setEncoding('utf8');
|
259 | }
|
260 |
|
261 | response.on('data', function bufferData(chunk) {
|
262 | responseBuffer += chunk;
|
263 | });
|
264 |
|
265 | response.on('end', function endResponse() {
|
266 | if (+response.statusCode >= 400) {
|
267 | return callback(new ApiHttpError(
|
268 | response.statusCode, responseBuffer, apiPath));
|
269 | }
|
270 |
|
271 | if (!hasErrored) {
|
272 | return callback(null, responseBuffer, response);
|
273 | }
|
274 | });
|
275 | });
|
276 |
|
277 | apiRequest.on('error', function logErrorAndCallback(data) {
|
278 |
|
279 |
|
280 | hasErrored = true;
|
281 | logger.info('Error fetching [' + url + ']. Body:\n' + data);
|
282 |
|
283 | return callback(new Error('Error connecting to ' + url));
|
284 | });
|
285 |
|
286 | if (httpMethod === 'GET') {
|
287 | apiRequest.end();
|
288 | } else {
|
289 | apiRequest.end(data);
|
290 | }
|
291 | }
|
292 |
|
293 | module.exports.get = get;
|
294 | module.exports.postOrPut = postOrPut;
|
295 | module.exports.createHeaders = createHeaders;
|
296 | module.exports.prepare = prepare;
|
297 | module.exports.dispatch = dispatch;
|
298 | module.exports.dispatchSecure = dispatchSecure;
|