在axios reactjs中动态更改内容类型(content-type)

4

这是我的问题。 我在项目中使用JWT身份验证,我的React项目中还设置了axiosInstance。 我还为axiosInstance设置了拦截器,负责在需要时拦截并刷新令牌。

const axiosInstance = axios.create({
 ​baseURL: baseURL,
 ​timeout: 360000,
 ​transformRequest: [
   ​function (data, headers) {
     ​const accessToken = window.localStorage.getItem('access_token');
     ​if (accessToken) {
       ​headers['Authorization'] = `Bearer ${accessToken}`;
     ​} else {
       ​delete headers.Authorization;
     ​}

     ​return JSON.stringify(data);
   ​},
 ​],
 ​headers: {
   ​'Content-Type': 'application/json',
   ​accept: 'application/json',
 ​},
});


axiosInstance.interceptors.response.use(
  (response) => {
    return response;
  },
  async function (error) {
    const originalRequest = error.config;

    console.log(
      'Caught the error response. Here is your request  ',
      originalRequest,
    );

    // case 1: No error specified Most likely to be server error

    if (typeof error.response === 'undefined') {
      //  Uncomment this later
      alert('Server error occured');

      return Promise.reject(error);
    }

    //  case 2: Tried to refresh the token but it is expired. So ask user to login again

    if (
      error.response.status === 401 &&
      originalRequest.url === baseURL + 'auth/api/token/refresh/'
    ) {
      store.dispatch(setLoginFalse());
      return Promise.reject(error);
    }

    // Case 3: Got 401 Unauthorized error. There are different possiblities
    console.log('Error message in axios = ', error.response.data);
    if (
      error.response.status === 401 &&
      error.response.statusText === 'Unauthorized'
    ) {
      const refreshToken = localStorage.getItem('refresh_token');
      console.log('Refresh token = ', refreshToken);

      // See if refresh token exists
      // Some times undefined gets written in place of refresh token.
      // To avoid that we check if refreshToken !== "undefined". This bug is still unknown need to do more research on this

      if (refreshToken !== undefined && refreshToken !== 'undefined') {
        console.log(typeof refreshToken == 'undefined');
        console.log('Refresh token is present = ', refreshToken);
        const tokenParts = JSON.parse(atob(refreshToken.split('.')[1]));

        // exp date in token is expressed in seconds, while now() returns milliseconds:
        const now = Math.ceil(Date.now() / 1000);
        console.log(tokenParts.exp);

        // Case 3.a Refresh token is present and it is not expired - use it to get new access token

        if (tokenParts.exp > now) {
          return axiosInstance
            .post('auth/api/token/refresh/', { refresh: refreshToken })
            .then((response) => {
              localStorage.setItem('access_token', response.data.access);

              axiosInstance.defaults.headers['Authorization'] =
                'Bearer ' + response.data.access;
              originalRequest.headers['Authorization'] =
                'Bearer ' + response.data.access;

              console.log('access token updated');

              // After refreshing the token request again user's previous url
              // which was blocked due to unauthorized error

              // I am not sure by default axios performs get request
              // But since we are passing the entire config of previous request
              // It seems to perform same request method as previous

              return axiosInstance(originalRequest);
            })

            .catch((err) => {
              // If any error occurs at this point we cannot guess what it is
              // So just console log it

              console.log(err);
            });
        } else {
          // Refresh token is expired ask user to login again.

          console.log('Refresh token is expired', tokenParts.exp, now);
          store.dispatch(setLoginFalse());
        }
      } else {
        // refresh token is not present in local storage so ask user to login again

        console.log('Refresh token not available.');
        store.dispatch(setLoginFalse());
      }
    }

    // specific error handling done elsewhere
    return Promise.reject(error);
  },
);
export default axiosInstance;


请注意,我在 axiosInstance 中设置了 Content-Type 为 'application/json'。
但是我的问题是,为了上传图像,内容类型应该为“multipart/form-data--boundary: set-automatically”。
(注意:手动设置多部分数据的边界似乎不起作用)
如果我们没有在标头中放置内容类型,axios 会自动设置多部分数据的边界。但是我必须找到一种方法,在不影响项目其他部分使用的 axiosInstance 的情况下,在一个地方从 axiosInstance 中删除 content-type(从我上传图片的地方)。
我使用 fetch 进行了测试,并设置了新的 axios 实例,结果达到了预期。但问题是这些请求将不会被 axios 拦截以刷新 JWT 令牌(如果需要)。
我阅读了各种帖子,但仍然看不到解决此问题的方法。
如果需要,我可以提供更多细节。请帮帮我,我已经花费了 8+ 小时来调试它。
谢谢。
编辑 1
我将 handleSubmit 函数更改为以下内容

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log(file);

    let formData = new FormData();
    formData.append('profile_pic', file);
    formData.append('name', 'root');

    axiosInstance.defaults.headers.common['Content-Type'] =
      'multipart/form-data';

    axiosInstance
      .put('/users/profile-pic-upload/', formData)
      .then((res) => console.log(res))
      .catch((err) => console.log(err));
  };

但是内容类型仍然是application/json。content type is still same

但是假设我在axios.js中更改了content-type为'multipart/form-data',它会更改所有请求的内容类型。这会破坏其他事情,但是如预期的那样,它不会解决此问题。因为设置手动边界似乎无效。甚至这篇文章也建议在多部分数据时删除内容类型,以便库(在这种情况下为axios)自动处理。

4个回答

3

如果需要将任何动态内容传递给您的axios实例,请使用一个返回axios实例的函数,如下所示:

import axios from 'axios';

const customAxios = (contentType) => {
  // axios instance for making requests
  const axiosInstance = axios.create({
    // your other properties for axios instance
    headers: {
      'Content-Type': contentType,
    },
  });

  // your response interceptor
  axiosInstance.interceptors.response.use(// handle response);

  return axiosInstance;
};

export default customAxios;

现在,您可以这样使用axios:

import customAxios from './customAxios';

const axiosForJSON = customAxios('application/json');
const axiosForMultipart = customAxios('multipart/form-data');

axiosForJSON.get('/hello');
axiosForMultipart.post('/hello', {});

// OR
cusomAxios('application/json').get('/hello');

非常感谢。这正是我一直在寻找的东西。我有一个问题,之前我在整个项目中使用了一个单独的axiosInstance。但现在每次导入axiosinstance都会创建一个新实例并返回,这会导致任何性能问题吗?因为每个实例都带有拦截器。 - Sanketh B. K
你可以通过 contentType 进行 记忆化 调用。虽然不是必须的,但假设你有 lodash 的 memoize 可用:const customAxios = _.memoize((contentType) => { ... }); - romellem

2
axiosInstance.defaults.headers.put['Content-Type'] = "multipart/form-data";

或者

axiosInstance.interceptors.request.use(config => {
  config.headers.put['Content-Type'] = 'multipart/form-data';
  return config;
});

尝试针对您特定的实例进行此操作。

嗨,谢谢回复。我编辑了问题,请查看编辑。 - Sanketh B. K
1
@SankethB.K 请检查最新的更新。 - Lovlesh Pokra
这对我很有帮助。在我的情况下,当下载文件时检查访问令牌 - 需要解析响应以获取新的访问令牌。但是,由于此拦截器位于用于使用responseType设置为arrayBuffer创建文件下载的类中,responseType:'arraybuffer'我不得不像下面这样将responseType更改为jsonyouraxiosinstance.defaults.responseType =“json”;然后将其设置回arraybuffer-以便文件下载可以继续youraxiosinstance.defaults.responseType =“arraybuffer”; - KManish

0

我也曾经和你一样为将媒体上传到服务器而苦苦挣扎。 由于我的全局 Axios 实例具有 Content-Type 为 'application/json',因此当请求被发出时,我使用以下脚本更新了 Content-Type 为 'multipart/form-data'。

// Not Working
this.axiosInstance.defaults.headers.common['Content-Type'] = 'multipart/form-data';

由于网络标签页中的请求头仍然包含全局配置中的“application/json”(可能的原因是全局标头保存在其他引用中,而我们正在更新其它引用),因此该文件未被更新。

因此,解决方法是在请求飞出之前拦截它,然后按下面所示修改标题。

// Working
this.axiosInstance.interceptors.request.use(config => {
    config.headers['Content-Type'] = 'multipart/form-data';
    return config;
});

一旦设置了Content-Type为'multipart/form-data',Axios将自动处理边界

希望这能帮到你或其他人。谢谢!

愉快编程 :-)


0

Lovlesh Pokra 上面的答案对我很有帮助。

在我的情况下,当下载文件时检查访问令牌 - 响应需要解析新的访问令牌。但是,由于这个拦截器在用于下载文件的类中,而创建时responseType设置为arrayBuffer,因此需要进行解析。

responseType: 'arraybuffer'

我不得不像下面这样将 responseType 更改为 json
youraxiosinstance.defaults.responseType = "json"; 

然后将其设置回ArrayBuffer - 以便文件下载可以继续进行

youraxiosinstance.defaults.responseType = "arraybuffer";

根据您的需求 - 在调用之前 - 可以根据您的要求进行更改。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接