Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactored AxiosHeaders class to fix #5067; #5169

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 5 additions & 2 deletions index.d.ts
Expand Up @@ -21,8 +21,7 @@ type AxiosHeaderTester = (matcher?: AxiosHeaderMatcher) => boolean;

export class AxiosHeaders {
constructor(
headers?: RawAxiosHeaders | AxiosHeaders,
defaultHeaders?: RawAxiosHeaders | AxiosHeaders
headers?: RawAxiosHeaders | AxiosHeaders
);

set(headerName?: string, value?: AxiosHeaderValue, rewrite?: boolean | AxiosHeaderMatcher): AxiosHeaders;
Expand All @@ -39,12 +38,16 @@ export class AxiosHeaders {

normalize(format: boolean): AxiosHeaders;

concat(...targets: Array<AxiosHeaders | RawAxiosHeaders | string>): AxiosHeaders;

toJSON(asStrings?: boolean): RawAxiosHeaders;

static from(thing?: AxiosHeaders | RawAxiosHeaders | string): AxiosHeaders;

static accessor(header: string | string[]): AxiosHeaders;

static concat(...targets: Array<AxiosHeaders | RawAxiosHeaders | string>): AxiosHeaders;

setContentType: AxiosHeaderSetter;
getContentType: AxiosHeaderGetter;
hasContentType: AxiosHeaderTester;
Expand Down
4 changes: 2 additions & 2 deletions lib/adapters/http.js
Expand Up @@ -203,7 +203,7 @@ export default function httpAdapter(config) {
data: convertedData,
status: 200,
statusText: 'OK',
headers: {},
headers: new AxiosHeaders(),
config
});
}
Expand Down Expand Up @@ -588,4 +588,4 @@ export default function httpAdapter(config) {
});
}

export const __setProxy = setProxy;
export const __setProxy = setProxy;
2 changes: 1 addition & 1 deletion lib/core/Axios.js
Expand Up @@ -80,7 +80,7 @@ class Axios {
}
);

config.headers = new AxiosHeaders(config.headers, defaultHeaders);
config.headers = AxiosHeaders.concat(defaultHeaders, config.headers);

// filter out skipped interceptors
const requestInterceptorChain = [];
Expand Down
132 changes: 74 additions & 58 deletions lib/core/AxiosHeaders.js
Expand Up @@ -4,7 +4,6 @@ import utils from '../utils.js';
import parseHeaders from '../helpers/parseHeaders.js';

const $internals = Symbol('internals');
const $defaults = Symbol('defaults');

function normalizeHeader(header) {
return header && String(header).trim().toLowerCase();
Expand All @@ -30,6 +29,10 @@ function parseTokens(str) {
return tokens;
}

function isValidHeaderName(str) {
return /^[-_a-zA-Z]+$/.test(str.trim());
}

function matchHeaderValue(context, value, header, filter) {
if (utils.isFunction(filter)) {
return filter.call(this, value, header);
Expand Down Expand Up @@ -80,13 +83,12 @@ function findKey(obj, key) {
return null;
}

function AxiosHeaders(headers, defaults) {
headers && this.set(headers);
this[$defaults] = defaults || null;
}
class AxiosHeaders {
constructor(headers) {
headers && this.set(headers);
}

Object.assign(AxiosHeaders.prototype, {
set: function(header, valueOrRewrite, rewrite) {
set(header, valueOrRewrite, rewrite) {
const self = this;

function setHeader(_value, _header, _rewrite) {
Expand All @@ -98,55 +100,56 @@ Object.assign(AxiosHeaders.prototype, {

const key = findKey(self, lHeader);

if (key && _rewrite !== true && (self[key] === false || _rewrite === false)) {
return;
if(!key || self[key] === undefined || _rewrite === true || (_rewrite === undefined && self[key] !== false)) {
self[key || _header] = normalizeValue(_value);
}

self[key || _header] = normalizeValue(_value);
}

if (utils.isPlainObject(header)) {
utils.forEach(header, (_value, _header) => {
setHeader(_value, _header, valueOrRewrite);
});
const setHeaders = (headers, _rewrite) =>
utils.forEach(headers, (_value, _header) => setHeader(_value, _header, _rewrite));

if (utils.isPlainObject(header) || header instanceof this.constructor) {
setHeaders(header, valueOrRewrite)
} else if(utils.isString(header) && (header = header.trim()) && !isValidHeaderName(header)) {
setHeaders(parseHeaders(header), valueOrRewrite);
} else {
setHeader(valueOrRewrite, header, rewrite);
}

return this;
},
}

get: function(header, parser) {
get(header, parser) {
header = normalizeHeader(header);

if (!header) return undefined;
if (header) {
const key = findKey(this, header);

const key = findKey(this, header);
if (key) {
const value = this[key];

if (key) {
const value = this[key];
if (!parser) {
return value;
}

if (!parser) {
return value;
}
if (parser === true) {
return parseTokens(value);
}

if (parser === true) {
return parseTokens(value);
}
if (utils.isFunction(parser)) {
return parser.call(this, value, key);
}

if (utils.isFunction(parser)) {
return parser.call(this, value, key);
}
if (utils.isRegExp(parser)) {
return parser.exec(value);
}

if (utils.isRegExp(parser)) {
return parser.exec(value);
throw new TypeError('parser must be boolean|regexp|function');
}

throw new TypeError('parser must be boolean|regexp|function');
}
},
}

has: function(header, matcher) {
has(header, matcher) {
header = normalizeHeader(header);

if (header) {
Expand All @@ -156,9 +159,9 @@ Object.assign(AxiosHeaders.prototype, {
}

return false;
},
}

delete: function(header, matcher) {
delete(header, matcher) {
const self = this;
let deleted = false;

Expand All @@ -183,13 +186,13 @@ Object.assign(AxiosHeaders.prototype, {
}

return deleted;
},
}

clear: function() {
clear() {
return Object.keys(this).forEach(this.delete.bind(this));
},
}

normalize: function(format) {
normalize(format) {
const self = this;
const headers = {};

Expand All @@ -214,30 +217,43 @@ Object.assign(AxiosHeaders.prototype, {
});

return this;
},
}

concat(...targets) {
return this.constructor.concat(this, ...targets);
}

toJSON: function(asStrings) {
toJSON(asStrings) {
const obj = Object.create(null);

utils.forEach(Object.assign({}, this[$defaults] || null, this),
(value, header) => {
if (value == null || value === false) return;
obj[header] = asStrings && utils.isArray(value) ? value.join(', ') : value;
});
utils.forEach(this, (value, header) => {
value != null && value !== false && (obj[header] = asStrings && utils.isArray(value) ? value.join(', ') : value);
});

return obj;
}
});

Object.assign(AxiosHeaders, {
from: function(thing) {
if (utils.isString(thing)) {
return new this(parseHeaders(thing));
}
[Symbol.iterator]() {
return Object.entries(this.toJSON())[Symbol.iterator]();
}

toString() {
return Object.entries(this.toJSON()).map(([header, value]) => header + ': ' + value).join('\n');
}

static from(thing) {
return thing instanceof this ? thing : new this(thing);
},
}

static concat(first, ...targets) {
const computed = new this(first);

accessor: function(header) {
targets.forEach((target) => computed.set(target));

return computed;
}

static accessor(header) {
const internals = this[$internals] = (this[$internals] = {
accessors: {}
});
Expand All @@ -258,7 +274,7 @@ Object.assign(AxiosHeaders, {

return this;
}
});
}

AxiosHeaders.accessor(['Content-Type', 'Content-Length', 'Accept', 'Accept-Encoding', 'User-Agent']);

Expand Down
4 changes: 4 additions & 0 deletions lib/core/dispatchRequest.js
Expand Up @@ -41,6 +41,10 @@ export default function dispatchRequest(config) {
config.transformRequest
);

if (['post', 'put', 'patch'].indexOf(config.method) !== -1) {
config.headers.setContentType('application/x-www-form-urlencoded', false);
}

const adapter = config.adapter || defaults.adapter;

return adapter(config).then(function onAdapterResolution(response) {
Expand Down
2 changes: 1 addition & 1 deletion lib/defaults/index.js
Expand Up @@ -10,7 +10,7 @@ import formDataToJSON from '../helpers/formDataToJSON.js';
import adapters from '../adapters/index.js';

const DEFAULT_CONTENT_TYPE = {
'Content-Type': 'application/x-www-form-urlencoded'
'Content-Type': undefined
};

/**
Expand Down
7 changes: 6 additions & 1 deletion lib/utils.js
Expand Up @@ -527,6 +527,11 @@ const reduceDescriptors = (obj, reducer) => {

const freezeMethods = (obj) => {
reduceDescriptors(obj, (descriptor, name) => {
// skip restricted props in strict mode
if (isFunction(obj) && ['arguments', 'caller', 'callee'].indexOf(name) !== -1) {
return false;
}

const value = obj[name];

if (!isFunction(value)) return;
Expand All @@ -540,7 +545,7 @@ const freezeMethods = (obj) => {

if (!descriptor.set) {
descriptor.set = () => {
throw Error('Can not read-only method \'' + name + '\'');
throw Error('Can not rewrite read-only method \'' + name + '\'');
};
}
});
Expand Down
5 changes: 2 additions & 3 deletions test/specs/headers.spec.js
Expand Up @@ -106,12 +106,11 @@ describe('headers', function () {
});
});

it('should preserve content-type if data is false', function (done) {
it('should preserve content-type if data is false', async function () {
axios.post('/foo', false);

getAjaxRequest().then(function (request) {
await getAjaxRequest().then(function (request) {
testHeaderValue(request.requestHeaders, 'Content-Type', 'application/x-www-form-urlencoded');
done();
});
});
});
56 changes: 55 additions & 1 deletion test/unit/core/AxiosHeaders.js
Expand Up @@ -33,7 +33,16 @@ describe('AxiosHeaders', function () {

assert.strictEqual(headers.get('foo'), 'value1');
assert.strictEqual(headers.get('bar'), 'value2');
})
});

it('should support adding multiple headers from raw headers string', function(){
const headers = new AxiosHeaders();

headers.set(`foo:value1\nbar:value2`);

assert.strictEqual(headers.get('foo'), 'value1');
assert.strictEqual(headers.get('bar'), 'value2');
});

it('should not rewrite header the header if the value is false', function(){
const headers = new AxiosHeaders();
Expand Down Expand Up @@ -338,4 +347,49 @@ describe('AxiosHeaders', function () {
});
});
});

describe('AxiosHeaders.concat', function () {
it('should concatenate plain headers into an AxiosHeader instance', function () {
const a = {a: 1};
const b = {b: 2};
const c = {c: 3};
const headers = AxiosHeaders.concat(a, b, c);

assert.deepStrictEqual({...headers.toJSON()}, {
a: '1',
b: '2',
c: '3'
});
});

it('should concatenate raw headers into an AxiosHeader instance', function () {
const a = 'a:1\nb:2';
const b = 'c:3\nx:4';
const headers = AxiosHeaders.concat(a, b);

assert.deepStrictEqual({...headers.toJSON()}, {
a: '1',
b: '2',
c: '3',
x: '4'
});
});

it('should concatenate Axios headers into a new AxiosHeader instance', function () {
const a = new AxiosHeaders({x: 1});
const b = new AxiosHeaders({y: 2});
const headers = AxiosHeaders.concat(a, b);

assert.deepStrictEqual({...headers.toJSON()}, {
x: '1',
y: '2'
});
});
});

describe('toString', function () {
it('should serialize AxiosHeader instance to a raw headers string', function () {
assert.deepStrictEqual(new AxiosHeaders({x:1, y:2}).toString(), 'x: 1\ny: 2');
});
});
});