XHR requests occasional timeout or network error on Safari iOS devices caused by HTTP/3

299 views Asked by At

We currently have a Vuejs web app using axios for communicating with our API server. All is working well on all devices except we are getting reports from users complaining that they are occasionally having a hard time logging in on Safari iOS. After debugging and reviewing the whole request flow we did not find anything wrong and we've had 0 reports on other devices.

We finally managed to reproduce the issue and on debug we noticed the axios request was hanging and timing out or directly returning a network error. We noticed the request was never actually reaching our API service. It was however reaching our servers and we found some access logs with the failed requests.

It seems that all the failed requests are OPTIONS (preflight) and POST requests (to the login endpoint) coming only from iOS devices. What made those requests stand out was the HTTP version. As it turns out, Safari is using HTTP/3 for the preflight and loging request. Those return a status code of 000 for OPTIONS and 500 for POST. GET requests made using HTTP/3 are handled successfully.

Now our nginx version does not support HTTP/3 (v1.22.0) and we cannot upgrade at the moment. All other browsers seem to be handling support correctly and requests are not being made using HTTP/3 except on iOS Safari. Now we would like to know if there is any way of letting the browser know the maximum version of HTTP supported by our server and even though this might be a browser issue, how can we fix this with either a client side or an Nginx configuration.

Here is a snippet of the XHR request done using axios :

export function handleError(error) {
  console.log('Error', error);
  if (error.response && error.response.status === 401){
    JWTHelper.clearJwt();
    location.reload(true);
  }

  if (error.response) {
      // Request made and server responded
      console.log(error.response.data.message);
      return Promise.reject(error.response.data.message);
  } else {
      // Something happened in setting up the request that triggered an Error
      console.log('Error', error);
      return Promise.reject(error.message);
  }
}

const apiClient = axios.create({
    baseURL: process.env.VUE_APP_API_BASE_URL,
    headers: {
        "Content-type": "application/json",
    },
});

function login(username, password, orgId){
    const postData = {
        "orgId": orgId,
        "password": password,
        "username": username
    };
    return apiClient.post("/user/login", postData)
        .then((response) => {
            const { data, status } = response;
            if (status === 200){
                JWTHelper.saveJwt(data.data.token);
            }
            return response.data
        })
        .catch((error) => handleError(error));
}

(Those functions are in separate files but combined here)

When the issue occurs (it doesn't always happen) the network inspector either shows the request loading indefinetly and finally times out. Or sometimes we directly get a network error. When this happens either the failed preflight causes the request to timeout or when we get a network error directly it is probably the server responding with a 500 error.

Here is the OPTIONS request being sent by Safari after which no POST request is sent :

XXXXXX:- - - [22/Oct/2023:23:33:11 +0000] "OPTIONS /user/login HTTP/3" 000 0 "https://admin.example.com/" "Mozilla/5.0 (iPhone; CPU iPhone OS 16_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.2 Mobile/15E148 Safari/604.1" "-" "api.example.com" sn="_" rt=17.010 ua="-" us="-" ut="-" ul="-" cs=-

When a post request is sent there is usually NO OPTIONS request being sent before it and it would look something like this :

XXXXXX:- - - [22/Oct/2023:22:12:13 +0000] "POST /user/login HTTP/3" 500 0 "https://admin.example.com/" "Mozilla/5.0 (iPhone; CPU iPhone OS 16_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.2 Mobile/15E148 Safari/604.1" "-" "api.example.com" sn="_" rt=17.010 ua="-" us="-" ut="-" ul="-" cs=-

We have tried configuring the CORS settings and it appears that increasing the max-age reduces the issue from happening too often (OPTIONS request get sent less regularly) but this is not a viable solution.

Now the question is, what is the correct way of letting safari use HTTP/2.0 ? Shouldn't it fallback to HTTP/2.0 when a request fails on HTTP/3 ? And is it possible Axios is not handling the failed HTTP/3 preflight request correctly and timing out ? But if that's the case why are all other browsers not having any issues ?

0

There are 0 answers