4
votes

In my angular 8 project I implemented the simplest possible HttpInterceptor that just passes the request by, without doing any action:

In my angular 8 project I implemented a simple HttpInterceptor that just clones the original request and adds a parameter:

@Injectable()
export class RequestHeadersInterceptor implements HttpInterceptor {

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // original code return next.handle(request) // pass-by request as-is

    return next.handle(request.clone({
      params: request.params.set('language', 'en') }
    ));
  }
}

In my service I then have a getFoos() method that makes an HTTP call which will be intercepted by the RequestHeadersInterceptor:

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { finalize } from 'rxjs/operators';
import { Foo } from '.';

@Injectable({
  providedIn: 'root'
})
export class FooService {
  constructor(private http: HttpClient) { }

  getFoos() {
    return this.http.get<Foo[]>('/foos')
      .pipe(
        finalize(() => console.log('observable completed!'))
      );
  }
}

In my component I finally subscribe to getFoos():

fooService.getFoos().subscribe(console.log);

Expected Output

[{ foo: 1 }, { foo: 2 }]
observable completed!

Actual Output

[{ foo: 1 }, { foo: 2 }]

As you can see, the finalize is never triggered. Why is that?

Notes

  • if the interceptor is removed, the finalize is triggered which is the expected behaviour for both scenarios
  • How I provide the interceptor to the module:
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { RequestHeadersInterceptor } from './shared/http-requests';

@NgModule({
  providers: [
    { provide: HTTP_INTERCEPTORS, useClass: RequestHeadersInterceptor, multi: true },
  ]
);
  • I updated the interceptor code since I wrongly stated, that the issue persists even when passing-by the request as-is. Instead, it needs to be cloned and changed.

  • I added a demo, based on @PierreDuc 's demo (major props!). However, I couldn't reproduce the issue in the demo. This might have to do with some request or response headers.

Response Headers on live system API

Cache-Control: no-store, no-cache, must-revalidate, max-age=0 Cache-Control: post-check=0, pre-check=0
Cache-Control: no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0
Connection: keep-alive
Content-Language: en-US
Content-Length: 42
Content-Type: application/json;charset=utf-8
Date: Tue, 21 Jan 2020 15:44:33 GMT
Pragma: no-cache
Pragma: no-cache
Server: nginx/1.16.1
X-Content-Type-Options: nosniff
X-Powered-By: Servlet/3.1

Request Headers on live system API

Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Authorization: Basic xyzABC123
Cache-Control: no-cache
Connection: keep-alive
Content-Type: application/json
Cookie: check=true; anotherCookie=1; bla=2;
Host: some.page.com:11001
Pragma: no-cache
Referer: https://some.page.com
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36
1
What you have looks fine. The problem will be probably somewhere else. Otherwise make a demo that replicates the same problem. - martin
How are you providing the interceptor in the ngmodule? - Poul Kruijt
Can you share the code snipped that initializes the interceptor? - Philipp Meissner
When your http request reponds it doesn't mean that your observable completes. The problem there is that you observable doesn't complete at all. Try to put take(1) before the finalize(). How can I complete Observable in RxJS? stackoverflow.com/questions/34097158/… - standby954
@Ronin I've created a stackblitz, but I do not get your result. So I'm guessing the question is missing some information on what the issue could be. Where do you provide the interceptor? - Poul Kruijt

1 Answers

2
votes

The "issue" is the Connection: Keep-Alive header. This persists an open connection

The Connection general header controls whether or not the network connection stays open after the current transaction finishes. If the value sent is keep-alive, the connection is persistent and not closed, allowing for subsequent requests to the same server to be done.

This will result in a non completing Observable, until the connection is terminated.

So it's not really a bug or issue on your side. I guess in your HttpInterceptor you add this header, so that's the reason it's only caused once you add the interceptor