Reputation: 536
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:
and
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
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
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
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
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