Skip to content

Commit f656731

Browse files
committed
refactor: refactor core code to use axios instead of request for network requests
BREAKING CHANGE: Network responses received in callback function may now have different structures (results and errors). Requests no longer return a Stream. See the UPGRADE-4.0.md file for more information.
1 parent d5aece4 commit f656731

12 files changed

Lines changed: 231 additions & 213 deletions

lib/requestwrapper.ts

Lines changed: 93 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414
* limitations under the License.
1515
*/
1616

17+
import axios from 'axios';
1718
import extend = require('extend');
18-
import request = require('request');
19+
import FormData = require('form-data');
20+
import querystring = require('querystring');
1921
import { PassThrough as readableStream } from 'stream';
2022
import { buildRequestFileObject, getMissingParams, isEmptyObject, isFileParam } from './helper';
2123

@@ -46,7 +48,7 @@ function parsePath(path: string, params: Object): string {
4648
* @private
4749
* @returns {request.RequestCallback}
4850
*/
49-
export function formatErrorIfExists(cb: Function): request.RequestCallback {
51+
export function formatErrorIfExists(cb: Function) {
5052
return (error, response, body) => {
5153
// eslint-disable-line complexity
5254

@@ -74,14 +76,14 @@ export function formatErrorIfExists(cb: Function): request.RequestCallback {
7476

7577
// for api-key services
7678
if (response.statusMessage === 'invalid-api-key') {
77-
const error = {
79+
const err = {
7880
error: response.statusMessage,
7981
code: response.statusMessage === 'invalid-api-key' ? 401 : 400,
8082
};
8183
if (response.headers) {
82-
error[globalTransactionId] = response.headers[globalTransactionId];
84+
err[globalTransactionId] = response.headers[globalTransactionId];
8385
}
84-
cb(error, null);
86+
cb(err, null);
8587
return;
8688
}
8789

@@ -141,36 +143,11 @@ export function formatErrorIfExists(cb: Function): request.RequestCallback {
141143
* @throws {Error}
142144
*/
143145
export function sendRequest(parameters, _callback) {
144-
let missingParams = null;
145146
const options = extend(true, {}, parameters.defaultOptions, parameters.options);
146-
const { path, body, form, formData, qs } = options;
147-
148-
// Missing parameters
149-
if (parameters.options.requiredParams) {
150-
// eslint-disable-next-line no-console
151-
console.warn(
152-
new Error(
153-
'requiredParams set on parameters.options - it should be set directly on parameters'
154-
)
155-
);
156-
}
147+
const { path, body, form, formData, qs, method } = options;
148+
let { url, headers } = options;
157149

158-
missingParams = getMissingParams(
159-
parameters.originalParams || extend({}, qs, body, form, formData, path),
160-
parameters.requiredParams
161-
);
162-
163-
if (missingParams) {
164-
if (typeof _callback === 'function') {
165-
return _callback(missingParams);
166-
} else {
167-
const errorStream = new readableStream();
168-
setTimeout(() => {
169-
errorStream.emit('error', missingParams);
170-
}, 0);
171-
return errorStream;
172-
}
173-
}
150+
const multipartForm = new FormData();
174151

175152
// Form params
176153
if (formData) {
@@ -179,59 +156,111 @@ export function sendRequest(parameters, _callback) {
179156
// Remove non-valid inputs for buildRequestFileObject,
180157
// i.e things like {contentType: <contentType>}
181158
Object.keys(formData).forEach(key => {
182-
// tslint:disable-next-line:no-unused-expression
183-
(formData[key] == null ||
159+
if (formData[key] == null ||
184160
isEmptyObject(formData[key]) ||
185-
(formData[key].hasOwnProperty('contentType') && !formData[key].hasOwnProperty('data'))) &&
161+
(formData[key].hasOwnProperty('contentType') && !formData[key].hasOwnProperty('data'))) {
186162
delete formData[key];
163+
}
187164
});
188165
// Convert file form parameters to request-style objects
189-
Object.keys(formData).forEach(
190-
key => formData[key].data != null && (formData[key] = buildRequestFileObject(formData[key]))
191-
);
166+
Object.keys(formData).forEach(key => {
167+
if (formData[key].data != null) {
168+
formData[key] = buildRequestFileObject(formData[key]);
169+
}
170+
});
192171

193172
// Stringify arrays
194-
Object.keys(formData).forEach(
195-
key => Array.isArray(formData[key]) && (formData[key] = formData[key].join(','))
196-
);
173+
Object.keys(formData).forEach(key => {
174+
if (Array.isArray(formData[key])) {
175+
formData[key] = formData[key].join(',');
176+
}
177+
});
197178

198179
// Convert non-file form parameters to strings
199-
Object.keys(formData).forEach(
200-
key =>
201-
!isFileParam(formData[key]) &&
180+
Object.keys(formData).forEach(key => {
181+
if (!isFileParam(formData[key]) &&
202182
!Array.isArray(formData[key]) &&
203-
typeof formData[key] === 'object' &&
204-
(formData[key] = JSON.stringify(formData[key]))
205-
);
183+
typeof formData[key] === 'object') {
184+
(formData[key] = JSON.stringify(formData[key]));
185+
}
186+
});
187+
188+
// build multipart form data
189+
Object.keys(formData).forEach(key => {
190+
// handle files differently to maintain options
191+
if (formData[key].value) {
192+
multipartForm.append(key, formData[key].value, formData[key].options);
193+
} else {
194+
multipartForm.append(key, formData[key]);
195+
}
196+
});
206197
}
207198

208199
// Path params
209-
options.url = parsePath(options.url, path);
210-
delete options.path;
200+
url = parsePath(url, path);
211201

212202
// Headers
213-
options.headers = extend({}, options.headers);
203+
headers = extend({}, headers);
214204
if (!isBrowser) {
215-
options.headers['User-Agent'] = `${pkg.name}-nodejs-${pkg.version};${options.headers[
216-
'User-Agent'
217-
] || ''}`;
205+
headers['User-Agent'] = `${pkg.name}-nodejs-${pkg.version};${headers['User-Agent'] || ''}`;
218206
}
219207

220-
// Query params
221-
if (options.qs && Object.keys(options.qs).length > 0) {
222-
Object.keys(options.qs).forEach(
223-
key => Array.isArray(options.qs[key]) && (options.qs[key] = options.qs[key].join(','))
208+
// Convert array-valued query params to strings
209+
if (qs && Object.keys(qs).length > 0) {
210+
Object.keys(qs).forEach(
211+
key => Array.isArray(qs[key]) && (qs[key] = qs[key].join(','))
224212
);
225-
options.useQuerystring = true;
226213
}
227214

228215
// Add service default endpoint if options.url start with /
229-
if (options.url.charAt(0) === '/') {
230-
options.url = parameters.defaultOptions.url + options.url;
216+
if (url && url.charAt(0) === '/') {
217+
url = parameters.defaultOptions.url + url;
218+
}
219+
220+
let data = body;
221+
222+
if (form) {
223+
data = querystring.stringify(form);
224+
headers['Content-type'] = 'application/x-www-form-urlencoded';
225+
}
226+
227+
if (formData) {
228+
data = multipartForm;
229+
// form-data generates headers that MUST be included or the request will fail
230+
headers = extend(true, {}, headers, multipartForm.getHeaders());
231231
}
232232

233-
// Compression support
234-
options.gzip = true;
233+
const axiosObject = {
234+
url,
235+
method,
236+
headers,
237+
params: qs,
238+
data,
239+
responseType: options.responseType || 'json',
240+
paramsSerializer: params => {
241+
return querystring.stringify(params);
242+
}
243+
};
235244

236-
return request(options, formatErrorIfExists(_callback));
245+
axios(axiosObject)
246+
.then(res => {
247+
_callback(null, res.data, res);
248+
})
249+
.catch(error => {
250+
if (error.response) {
251+
// The request was made and the server responded with a status code
252+
// that falls out of the range of 2xx
253+
delete error.response.config;
254+
delete error.response.request;
255+
_callback(error.response);
256+
} else if (error.request) {
257+
// The request was made but no response was received
258+
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
259+
// http.ClientRequest in node.js
260+
_callback(error.request);
261+
} else {
262+
// Something happened in setting up the request that triggered an Error
263+
_callback(error.message);
264+
}
265+
});
237266
}

0 commit comments

Comments
 (0)