Skip to content

Commit

Permalink
Fixed & Imporoved AxiosHeaders class (#5224)
Browse files Browse the repository at this point in the history
* Refactored AxiosHeaders class;

* Added support for instances of AxiosHeaders as a value for the headers option;

Co-authored-by: Jay <jasonsaayman@gmail.com>
  • Loading branch information
DigitalBrainJS and jasonsaayman committed Nov 7, 2022
1 parent c0a723a commit ab77a40
Show file tree
Hide file tree
Showing 16 changed files with 310 additions and 148 deletions.
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;
1 change: 1 addition & 0 deletions lib/axios.js
Expand Up @@ -75,5 +75,6 @@ axios.AxiosHeaders = AxiosHeaders;
axios.formToJSON = thing => formDataToJSON(utils.isHTMLForm(thing) ? new FormData(thing) : thing);

axios.default = axios;

// this module should only have a default export
export default axios
18 changes: 10 additions & 8 deletions lib/core/Axios.js
Expand Up @@ -47,7 +47,7 @@ class Axios {

config = mergeConfig(this.defaults, config);

const {transitional, paramsSerializer} = config;
const {transitional, paramsSerializer, headers} = config;

if (transitional !== undefined) {
validator.assertOptions(transitional, {
Expand All @@ -67,20 +67,22 @@ class Axios {
// Set config.method
config.method = (config.method || this.defaults.method || 'get').toLowerCase();

let contextHeaders;

// Flatten headers
const defaultHeaders = config.headers && utils.merge(
config.headers.common,
config.headers[config.method]
contextHeaders = headers && utils.merge(
headers.common,
headers[config.method]
);

defaultHeaders && utils.forEach(
contextHeaders && utils.forEach(
['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
function cleanHeaderConfig(method) {
delete config.headers[method];
(method) => {
delete headers[method];
}
);

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

// filter out skipped interceptors
const requestInterceptorChain = [];
Expand Down
158 changes: 82 additions & 76 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 @@ -66,27 +69,12 @@ function buildAccessors(obj, header) {
});
}

function findKey(obj, key) {
key = key.toLowerCase();
const keys = Object.keys(obj);
let i = keys.length;
let _key;
while (i-- > 0) {
_key = keys[i];
if (key === _key.toLowerCase()) {
return _key;
}
class AxiosHeaders {
constructor(headers) {
headers && this.set(headers);
}
return null;
}

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

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

function setHeader(_value, _header, _rewrite) {
Expand All @@ -96,77 +84,78 @@ Object.assign(AxiosHeaders.prototype, {
throw new Error('header name must be a non-empty string');
}

const key = findKey(self, lHeader);
const key = utils.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);
header != null && setHeader(valueOrRewrite, header, rewrite);
}

return this;
},
}

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

if (!header) return undefined;
if (header) {
const key = utils.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) {
const key = findKey(this, header);
const key = utils.findKey(this, header);

return !!(key && (!matcher || matchHeaderValue(this, this[key], key, matcher)));
}

return false;
},
}

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

function deleteHeader(_header) {
_header = normalizeHeader(_header);

if (_header) {
const key = findKey(self, _header);
const key = utils.findKey(self, _header);

if (key && (!matcher || matchHeaderValue(self, self[key], key, matcher))) {
delete self[key];
Expand All @@ -183,18 +172,18 @@ 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 = {};

utils.forEach(this, (value, header) => {
const key = findKey(headers, header);
const key = utils.findKey(headers, header);

if (key) {
self[key] = normalizeValue(value);
Expand All @@ -214,30 +203,47 @@ 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');
}

get [Symbol.toStringTag]() {
return 'AxiosHeaders';
}

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

accessor: function(header) {
static concat(first, ...targets) {
const computed = new this(first);

targets.forEach((target) => computed.set(target));

return computed;
}

static accessor(header) {
const internals = this[$internals] = (this[$internals] = {
accessors: {}
});
Expand All @@ -258,7 +264,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

0 comments on commit ab77a40

Please sign in to comment.