5

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
Ronin
  • 7,322
  • 6
  • 36
  • 54
  • 1
    What you have looks fine. The problem will be probably somewhere else. Otherwise make a demo that replicates the same problem. – martin Jan 21 '20 at 12:27
  • 1
    How are you providing the interceptor in the ngmodule? – Poul Kruijt Jan 21 '20 at 12:36
  • Can you share the code snipped that initializes the interceptor? – Philipp Meissner Jan 21 '20 at 12:36
  • 2
    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? https://stackoverflow.com/questions/34097158/how-can-i-complete-observable-in-rxjs – standby954 Jan 21 '20 at 12:40
  • @PierreDuc I added the code to the original question – Ronin Jan 21 '20 at 12:41
  • 6
    @Ronin I've created a [stackblitz](https://stackblitz.com/edit/angular-wyrndu?file=src/app/foo.service.ts), 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 Jan 21 '20 at 13:18
  • @PierreDuc thanks so much! I updated the question, actually a `request.clone` is necessary for the issue to occur. Also, I added the request/response headers from I get in my environment. It might have to do with them, I don't see any other difference. – Ronin Jan 21 '20 at 16:38
  • @standby954 Thanks, that's actually helpful. While requests from `HttpClient` usually DO complete (it's http, a request/response protocol, so the process terminates after the response is received) in this special setup they don't. However, I just piped a `take(1)` to my `getFoos` method which did the trick in this case. It's still not clear why it happens though. – Ronin Jan 21 '20 at 16:40

1 Answers1

2

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

Poul Kruijt
  • 69,713
  • 12
  • 145
  • 149