使用 Axios transformRequest 导致 415 Unsupported Media Type

Using Axios transformRequest results in 415 Unsupported Media Type

我刚刚将我的 React 应用程序升级到 axios 0.25.0,并开始在某些 REST POST/PUT API 调用中收到 http 415 Unsupported Media type 错误。这些调用都有一个共同点,它们在请求中包含一个日期字段 body.

我使用 axios transformRequest 来确保任何日期字段都以本地时间发送到 API,而不是 JSON 默认的 UTC。

当我查看这些请求的 header 时,“Content-Type”已从“application/json”更改为“application/x-www-form-urlencoded”,随后被REST API 期望 [FromBody] JSON object。 我没有更改 API,而是添加了一个 axios 拦截器来确保 header“Content-Type”是“application/json”。这解决了问题,但我不喜欢这个修复。

转换请求

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];

拦截器

axios.interceptors.request.use(req => {
        if(req.headers) {
            req.headers["Content-Type"] = 'application/json';
        }
        
        return req;
    });

为什么在使用转换时 content-type 变为“application/x-www-form-urlencoded”?有没有办法防止这种情况或更好的处理方式?

更新: 设置默认 headers 不需要拦截器。

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";

更新: 根据 Phils 的评论,问题原来是我 替换 transformRequest 链以及从我的转换器返回一个字符串 - 解决方案是。

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);

Axios 请求的 default content-typeapplication/x-www-form-urlencoded

default request transformer 做的一件事是为请求数据设置适当的 content-type...

if (utils.isObject(data) || (headers && headers['Content-Type'] === 'application/json')) {
  setContentTypeIfUnset(headers, 'application/json');
  return stringifySafely(data);
}

由于您要替换整个 transformRequest 链,您现在错过了这个。

你应该在你的变压器中处理它,例如

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 JSON.stringify(data)
}

或者更常见的是,将你的转换器移到现有链上并让它 return 成为一个普通的 object,让默认转换器完成它的工作

const instance = axios.create({
  transformRequest: [ serialiseDateLocal ].concat(axios.defaults.transformRequest)
})

我还不确定为什么您的拦截器没有正确设置 header。我怀疑这是一个竞争条件,它可能值得 raising a bug report