Search code examples
angular.net-coreiisangular-universal

Why I'm getting Unknown error with status 0 - Angular Universal?


I'm getting below error in 10 % of my requests on production env.

"error": "[ProgressEvent]",
"headers": "[ti]",
"message": "Http failure response for https://api.xxxxxx:5000/api/public-web/files: 0 Unknown Error",
"name": "HttpErrorResponse",
"ok": false,
"status": 0,
"statusText": "Unknown Error",
"url": "https://api.xxxxxx:5000/api/public-web/files"

Logs are from Sentry.
Stack:

  • Angular 15 - Universal
  • .Net Core 6
  • Server: IIS 10

Firstly, I was thinking an issue is related to the CORS but although that I had already configured CORS on backend side, I've also added following to all response headers on the server in web-config (IIS):

access-control-allow-headers: *
access-control-allow-methods: GET,POST,PATCH,PUT,DELETE,OPTIONS
access-control-allow-origin: *

I've covered CORS just on the IIS with above custom headers in web.config, not on the application side.

Also I've read that it might be related to the bad internet connection (as per Angular official documentation) but that is not an issue here (maybe but just in 1% of 10% of cases).

Users have tried to change their network connection from WIFI to mobile but still the same issue appears.


Solution

  • I've faced this same situation. Angular + CORS properly configured + lots of Sentry issues with "HTTP 0 Unknown Error".

    You're on the right track. This happens when some users have spotty/bad internet/Wi-Fi connections causing some requests to fail.

    I've managed to decrease these errors, and consequently improve UX, by implementing a retry interceptor that retries those failed requests:

    import { HttpErrorResponse, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
    import { Injectable } from '@angular/core';
    import { retry, timer } from 'rxjs';
    
    // These variables have to stay outside the class because of 'this' keyword binding issues
    const maxRetries = 2;
    const delayMs = 2000;
    
    @Injectable()
    export class RetryInterceptor implements HttpInterceptor {
      intercept(request: HttpRequest<unknown>, next: HttpHandler) {
        // Pass the request to the next request handler and retry HTTP 0 errors 2 times with a 2-second delay
        return next.handle(request).pipe(retry({ count: maxRetries, delay: this.delayNotifier }));
      }
    
      delayNotifier(error: any, retryCount: number) {
        if (error instanceof HttpErrorResponse && error.status === 0) {
          return timer(delayMs);
        }
    
        // re-throw other errors for anything else that might be catching them
        throw error;
      }
    }
    

    Even with this retry interceptor, you will always have some of these errors because, well, we can't control the users' internet connections :)