diff --git a/README.md b/README.md index df1d3bf..c5a9029 100644 --- a/README.md +++ b/README.md @@ -217,6 +217,7 @@ These are the available config options for making requests. Only the `url` is re headers: {'X-Requested-With': 'XMLHttpRequest'}, // `params` are the URL parameters to be sent with the request + // Must be a plain object or a URLSearchParams object params: { ID: 12345 }, @@ -229,7 +230,10 @@ These are the available config options for making requests. Only the `url` is re // `data` is the data to be sent as the request body // Only applicable for request methods 'PUT', 'POST', and 'PATCH' - // When no `transformRequest` is set, must be a string, an ArrayBuffer, a hash, or a Stream + // When no `transformRequest` is set, must be of one of the following types: + // - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams + // - Browser only: FormData, File, Blob + // - Node only: Stream data: { firstName: 'Fred' }, diff --git a/lib/defaults.js b/lib/defaults.js index 1942ce0..162ffa7 100644 --- a/lib/defaults.js +++ b/lib/defaults.js @@ -1,33 +1,39 @@ 'use strict'; var utils = require('./utils'); +var normalizeHeaderName = require('./helpers/normalizeHeaderName'); var PROTECTION_PREFIX = /^\)\]\}',?\n/; var DEFAULT_CONTENT_TYPE = { 'Content-Type': 'application/x-www-form-urlencoded' }; +function setContentTypeIfUnset(headers, value) { + if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) { + headers['Content-Type'] = value; + } +} + module.exports = { transformRequest: [function transformRequest(data, headers) { - if (utils.isFormData(data) || utils.isArrayBuffer(data) || utils.isStream(data)) { + normalizeHeaderName(headers, 'Content-Type'); + if (utils.isFormData(data) || + utils.isArrayBuffer(data) || + utils.isStream(data) || + utils.isFile(data) || + utils.isBlob(data) + ) { return data; } if (utils.isArrayBufferView(data)) { return data.buffer; } - if (utils.isObject(data) && !utils.isFile(data) && !utils.isBlob(data)) { - // Set application/json if no Content-Type has been specified - if (!utils.isUndefined(headers)) { - utils.forEach(headers, function processContentTypeHeader(val, key) { - if (key.toLowerCase() === 'content-type') { - headers['Content-Type'] = val; - } - }); - - if (utils.isUndefined(headers['Content-Type'])) { - headers['Content-Type'] = 'application/json;charset=utf-8'; - } - } + if (utils.isURLSearchParams(data)) { + setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8'); + return data.toString(); + } + if (utils.isObject(data)) { + setContentTypeIfUnset(headers, 'application/json;charset=utf-8'); return JSON.stringify(data); } return data; diff --git a/lib/helpers/buildURL.js b/lib/helpers/buildURL.js index c8365f4..1e66456 100644 --- a/lib/helpers/buildURL.js +++ b/lib/helpers/buildURL.js @@ -29,6 +29,8 @@ module.exports = function buildURL(url, params, paramsSerializer) { var serializedParams; if (paramsSerializer) { serializedParams = paramsSerializer(params); + } else if (utils.isURLSearchParams(params)) { + serializedParams = params.toString(); } else { var parts = []; @@ -64,4 +66,3 @@ module.exports = function buildURL(url, params, paramsSerializer) { return url; }; - diff --git a/lib/helpers/normalizeHeaderName.js b/lib/helpers/normalizeHeaderName.js new file mode 100644 index 0000000..738c9fe --- /dev/null +++ b/lib/helpers/normalizeHeaderName.js @@ -0,0 +1,12 @@ +'use strict'; + +var utils = require('../utils'); + +module.exports = function normalizeHeaderName(headers, normalizedName) { + utils.forEach(headers, function processHeader(value, name) { + if (name !== normalizedName && name.toUpperCase() === normalizedName.toUpperCase()) { + headers[normalizedName] = value; + delete headers[name]; + } + }); +}; diff --git a/lib/utils.js b/lib/utils.js index b0d376b..74668df 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -142,6 +142,16 @@ function isStream(val) { return isObject(val) && isFunction(val.pipe); } +/** + * Determine if a value is a URLSearchParams object + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a URLSearchParams object, otherwise false + */ +function isURLSearchParams(val) { + return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams; +} + /** * Trim excess whitespace off the beginning and end of a string * @@ -259,6 +269,7 @@ module.exports = { isBlob: isBlob, isFunction: isFunction, isStream: isStream, + isURLSearchParams: isURLSearchParams, isStandardBrowserEnv: isStandardBrowserEnv, forEach: forEach, merge: merge, diff --git a/package.json b/package.json index 50befbf..6bfd96d 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,8 @@ "phantomjs-prebuilt": "2.1.6", "sinon": "1.17.3", "webpack": "1.12.14", - "webpack-dev-server": "1.14.1" + "webpack-dev-server": "1.14.1", + "url-search-params": "0.5.0" }, "browser": { "./lib/adapters/http.js": "./lib/adapters/xhr.js" diff --git a/test/specs/__helpers.js b/test/specs/__helpers.js index 80ecc74..8a06d0d 100644 --- a/test/specs/__helpers.js +++ b/test/specs/__helpers.js @@ -1,6 +1,9 @@ // Polyfill ES6 Promise require('es6-promise').polyfill(); +// Polyfill URLSearchParams +URLSearchParams = require('url-search-params'); + // Import axios axios = require('../../index'); diff --git a/test/specs/helpers/buildURL.spec.js b/test/specs/helpers/buildURL.spec.js index 453c1f9..83d7ba1 100644 --- a/test/specs/helpers/buildURL.spec.js +++ b/test/specs/helpers/buildURL.spec.js @@ -1,4 +1,5 @@ var buildURL = require('../../../lib/helpers/buildURL'); +var URLSearchParams = require('url-search-params'); describe('helpers::buildURL', function () { it('should support null params', function () { @@ -60,7 +61,9 @@ describe('helpers::buildURL', function () { expect(buildURL('/foo', params, serializer)).toEqual('/foo?foo=bar'); expect(serializer.calledOnce).toBe(true); expect(serializer.calledWith(params)).toBe(true); - }) + }); + + it('should support URLSearchParams', function () { + expect(buildURL('/foo', new URLSearchParams('bar=baz'))).toEqual('/foo?bar=baz'); + }); }); - - diff --git a/test/specs/helpers/normalizeHeaderName.spec.js b/test/specs/helpers/normalizeHeaderName.spec.js new file mode 100644 index 0000000..d8d5e82 --- /dev/null +++ b/test/specs/helpers/normalizeHeaderName.spec.js @@ -0,0 +1,21 @@ +var normalizeHeaderName = require('../../../lib/helpers/normalizeHeaderName'); + +describe('helpers::normalizeHeaderName', function () { + it('should normalize matching header name', function () { + var headers = { + 'conTenT-Type': 'foo/bar', + }; + normalizeHeaderName(headers, 'Content-Type'); + expect(headers['Content-Type']).toBe('foo/bar'); + expect(headers['conTenT-Type']).toBeUndefined(); + }); + + it('should not change non-matching header name', function () { + var headers = { + 'content-type': 'foo/bar', + }; + normalizeHeaderName(headers, 'Content-Length'); + expect(headers['content-type']).toBe('foo/bar'); + expect(headers['Content-Length']).toBeUndefined(); + }); +}); diff --git a/test/specs/requests.spec.js b/test/specs/requests.spec.js index 053de1e..47be0b0 100644 --- a/test/specs/requests.spec.js +++ b/test/specs/requests.spec.js @@ -277,4 +277,17 @@ describe('requests', function () { }); }); + it('should support URLSearchParams', function (done) { + var params = new URLSearchParams(); + params.append('param1', 'value1'); + params.append('param2', 'value2'); + + axios.post('/foo', params); + + getAjaxRequest().then(function (request) { + expect(request.requestHeaders['Content-Type']).toBe('application/x-www-form-urlencoded;charset=utf-8'); + expect(request.params).toBe('param1=value1¶m2=value2'); + done(); + }); + }); }); diff --git a/test/specs/utils/isX.spec.js b/test/specs/utils/isX.spec.js index d58d113..7620f8c 100644 --- a/test/specs/utils/isX.spec.js +++ b/test/specs/utils/isX.spec.js @@ -78,4 +78,9 @@ describe('utils::isX', function () { expect(utils.isStream(new Stream.Readable())).toEqual(true); expect(utils.isStream({ foo: 'bar' })).toEqual(false); }); + + it('should validate URLSearchParams', function () { + expect(utils.isURLSearchParams(new URLSearchParams())).toEqual(true); + expect(utils.isURLSearchParams('foo=1&bar=2')).toEqual(false); + }); });