Ken
Ken

Reputation: 536

Angular HttpClient.get() Not Returning Full Response Headers

I'm calling a REST service from an Angular 9 app using HttpClient.get() and I'm not seeing a full list of the response headers. I know that the service is sending them because:

  1. I can see them in the browser debugger Network=>Headers (see image) browser debugger image

and

  1. when I hit the same REST service with a Java app it returns the full headers, about a dozen all together:

java.net.http.HttpHeaders@1627d314 { {access-control-allow-origin=[*], age=[0], connection=[keep-alive], content-length=[1207], content-type=[application/json], date=[Tue, 07 Jul 2020 05:11:45 GMT] <...etc>

What I get from the Angular HttpClient.get() is only one item in the header.keys():

headers: {
  "normalizedNames": {},
  "lazyUpdate": null,
  "lazyInit": null,
  "headers": {}
}

headerKeys:
[
  "content-type: application/json"
]

I created a small sample app to demonstrate the problem. Here are the key components:

app.modules.ts:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { TesterComponent } from './tester/tester.component';

@NgModule({
  declarations: [
    AppComponent,
    TesterComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

tester.component.ts:

import { Component, OnInit } from '@angular/core';
import { HttpHeaders, HttpParams, HttpResponse, HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Component({
  selector: 'app-tester',
  templateUrl: './tester.component.html',
  styleUrls: ['./tester.component.css']
})
export class TesterComponent implements OnInit {

  _url: string = "https://api.nasa.gov/planetary/apod";
  _api_key: string="DEMO_KEY";

  //
_title: string;
_date: string;

  constructor(private _httpClient: HttpClient) { }

  ngOnInit(): void {

    this.GetData(this._url).subscribe(()  =>
    {
       // do other stuff
     
    });
  }

  
 sendGetRequest(getUrl: string, headers: HttpHeaders, urlParams: HttpParams) : Observable<HttpResponse<Object>>{
    return this._httpClient.get<HttpResponse<Object>>(getUrl, {headers: headers, params: urlParams, observe: 'response'});
  }


  GetData(url: string)
  {

    const params = new HttpParams()
      .set("api_key", this._api_key);

    return this.sendGetRequest(url, headers, params).pipe(
      
      tap( response =>
      {
      
      console.log("returning data");

      if (response.headers)
      {
        console.log('headers', response.headers);
      }

      const keys = response.headers.keys();

      if (keys)
      {
        const headerKeys = keys.map(key =>
          `${key}: ${response.headers.get(key)}`);

        console.log('headerKeys', headerKeys);
      }

      this._date = response.body['date'];
      this._title = response.body['title'];
    },
    err => {
      console.log(err);
    }
    
      ));
  }

}

Addendum: To further illustrate the problem here is a small Java 11 program that calls exactly the same REST API with the same credentials. You can see from the output that the REST API is sending back all of the header response information. The question remains, why can't the Angular program calling exactly the same REST API see the full response headers? Is there some setting/flag/voodoo missing from the call?

Java 11 app:

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;

public class MainClass {

    public static void main(String[] args) {
        String api_key = "DEMO_KEY";
        String uri = "https://api.nasa.gov/planetary/apod";

        uri += "?api_key=" + api_key;

        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder().uri(URI.create(uri)).build();

        HttpResponse<String> response = null;

        try {
            response = client.send(request, BodyHandlers.ofString());
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println("------------------");
        System.out.println("response.headers: " + response.headers());
        System.out.println(response.body());
        System.out.println("------------------");
    }

}

Output from Java app (response.header only for brevity):

response.headers: java.net.http.HttpHeaders@96f4f3fc { {access-control-allow-origin=[*], age=[0], connection=[keep-alive], content-length=[1302], content-type=[application/json], date=[Wed, 08 Jul 2020 17:13:42 GMT], server=[openresty], strict-transport-security=[max-age=31536000; preload], vary=[Accept-Encoding], via=[http/1.1 api-umbrella (ApacheTrafficServer [cMsSf ])], x-cache=[MISS], x-ratelimit-limit=[40], x-ratelimit-remaining=[39]} }

Thanks for your help!

Upvotes: 4

Views: 8891

Answers (4)

Bruce Wilcox
Bruce Wilcox

Reputation: 441

I spent hours doing trial and error to get this to work. In my opinion, the way Chrome and javascript interact is wacky.

My Spring Boot server is returning a header named “x-document-id”.

But Chrome displays it as “X-Document-Id”.

And JavaScript doesn’t see the header unless I expose “X-Document-Id” as seen here:

@CrossOrigin(
    origins = {
        "http://localhost:4200",
        "https://www.xxxxxxx.com"
    },
    allowCredentials = "true",
    exposedHeaders = {
        "X-Document-Id",
        "x-document-id"
    }
)

But when I dump JSON.stringify( resp.headers.keys() ) the console displays:

[
    "cache-control",
    "content-length",
    "content-type",
    "x-document-id"
]
And when I dump JSON.stringify( resp.headers ) the console displays:


 {
    "normalizedNames": {},
    "lazyUpdate": null,
    "lazyInit": null,
    "headers": {}
}

Can it really be that some parts of the system are case sensitive and others are not?

Upvotes: 0

yehoni amran
yehoni amran

Reputation: 29

from mozila docs

"The Access-Control-Expose-Headers response header allows a server to indicate which response headers should be made available to scripts running in the browser, in response to a cross-origin request.

Only the CORS-safelisted response headers are exposed by default. For clients to be able to access other headers, the server must list them using the Access-Control-Expose-Headers header."

you need to config the server side api (core.api, exc')

explicit allow your header you want to expose to the client

services.AddCors(options =>
{
    options.AddPolicy("AllowAll", builder =>

    {
        builder.AllowAnyHeader()
               .AllowAnyOrigin()
               .WithExposedHeaders("Content-Range"); 
    });
});

after you config you will see the header "content-Range"

content-range: Categories 0-2/4

and it will be available in the JavaScript code

Upvotes: -2

cjd82187
cjd82187

Reputation: 3593

You will need to set Access-Control-Expose-Headers header to include the additional headers. See Mozilla docs: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers

By default, only the 7 CORS-safelisted response headers are exposed:

  • Cache-Control
  • Content-Language
  • Content-Length
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

You will see your other headers in the network tab, but JavaScript will not have access to them unless they are in the Access-Control-Expose-Headers header.

Heres a similar question for Axios, a different javascript HTTP library from 4 years ago with the same answer: Axios get access to response header fields

And and Angular one from 2.5 years ago Read response headers from API response - Angular 5 + TypeScript

UPDATE: Since you are calling an API that I assume you don't own (https://api.nasa.gov/), you'll have to have NASA add the header Access-Control-Expose-Headers: X-RateLimit-Limit, X-RateLimit-Remaining if you need to read those headers in your client.

Your other option is to create a proxy server and instead of calling NASA, you call your own server which will call NASA and can include your rate limit headers.

Upvotes: 4

sunilbaba
sunilbaba

Reputation: 445

For getting the full headers of the HTTP response along with the content, you are suppose to pass another parameter called observe while making the request

http
  .get<MyJsonData>('/data.json', {observe: 'response'})
  .subscribe(response => {
      response.headers.keys().map( (key) => console.log(`${key}: ${response.headers.get(key)}`));

 });

Upvotes: 0

Related Questions