I just upgraded my react app to axios 0.25.0 and started getting http 415 Unsupported Media type errors in some REST POST/PUT API calls. These calls all have one thing in common, they include a date field in the request body.
I use an axios transformRequest to ensure that any date fields are sent to the API in local time rather than the JSON default UTC.
When I look at the header for these requests the "Content-Type" has changed from "application/json" to "application/x-www-form-urlencoded" and is subsequently rejected by the REST API which expects [FromBody] JSON object. Rather than change the API I have added an axios interceptor to ensure the header "Content-Type" is "application/json". This fixes the problem but I do not like the fix.
transformRequest
const serialiseDateLocal = (data: any) => {
Date.prototype.toJSON = function() {
// dateToString returns local time in specified format
return dateToString(this, "yyyy-MM-ddTHH:mm:ss.sssZ");
};
return JSON.stringify(data);
}
axios.defaults.transformRequest = [serialiseDateLocal];
interceptor
axios.interceptors.request.use(req => {
if(req.headers) {
req.headers["Content-Type"] = 'application/json';
}
return req;
});
Why is the content-type changing to "application/x-www-form-urlencoded" when the transform is used? is there a way to prevent this or a better way to handle it?
UPDATE: Setting default headers negates the need for the interceptor.
axios.defaults.headers.common["Content-Type"] = "application/json";
axios.defaults.headers.post["Content-Type"] = "application/json";
axios.defaults.headers.put["Content-Type"] = "application/json";
axios.defaults.headers.patch["Content-Type"] = "application/json";
UPDATE: Based on Phils comments problem turned out to be me replacing the transformRequest chain as well as returning a string from my transformer - solution is.
const serialiseDateLocal: AxiosRequestTransformer = (data: any) => {
if (data instanceof Date) {
return dateToString(data, serializedFormat);
}
if (typeof data === "object" && data !== null) {
return Object.fromEntries(Object.entries(data).map(([ key, val ]) =>
[ key, serialiseDateLocal(val) ]))
}
return data;
}
axios.defaults.transformRequest = [serialiseDateLocal].concat(axios.defaults.transformRequest);
CodePudding user response:
The default content-type for Axios requests is application/x-www-form-urlencoded.
One thing the default request transformer does is set the appropriate content-type for the request data...
if (utils.isObject(data) || (headers && headers['Content-Type'] === 'application/json')) { setContentTypeIfUnset(headers, 'application/json'); return stringifySafely(data); }
Since you're replacing the entire transformRequest chain, you now miss out on this.
You should either handle it in your transformer, eg
import { AxiosRequestTransformer } from "axios"
const serialiseDateLocal: AxiosRequestTransformer = (data, headers) => {
// TODO: actually transform the request, don't just modify the Date prototype
headers["Content-Type"] = "application/json"
return data
}
or as is more common, unshift your transformer onto the existing chain
const instance = axios.create({
transformRequest: [ serialiseDateLocal ].concat(axios.defaults.transformRequest)
})
I'm not sure yet why your interceptor wasn't setting the header correctly. I suspect it's a race condition and it might be worth raising a bug report.
