Home > Net >  Using Axios transformRequest results in 415 Unsupported Media Type
Using Axios transformRequest results in 415 Unsupported Media Type

Time:02-02

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.

  •  Tags:  
  • Related