【问题标题】:Angular HttpClient.get() Not Returning Full Response HeadersAngular HttpClient.get() 不返回完整的响应标头
【发布时间】:2020-10-27 07:48:25
【问题描述】:

我正在使用 HttpClient.get() 从 Angular 9 应用程序调用 REST 服务,但没有看到响应标头的完整列表。我知道服务发送它们是因为:

  1. 我可以在浏览器调试器 Network=>Headers 中看到它们(见图)

  1. 当我使用 Java 应用程序访问相同的 REST 服务时,它会返回完整的标头,总共大约有十几个:

java.net.http.HttpHeaders@1627d314 { {{access-control-allow-origin=[*], 年龄=[0],连接=[保持活动],内容长度=[1207], 内容类型=[应用程序/json],日期=[格林威治标准时间 2020 年 7 月 7 日星期二 05:11:45] <...>

我从 Angular HttpClient.get() 得到的只是 header.keys() 中的一项:

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

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

我创建了一个小型示例应用程序来演示该问题。以下是关键组件:

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);
    }
    
      ));
  }

}

附录: 为了进一步说明问题,这里有一个小型 Java 11 程序,它完全使用相同的凭据调用相同的 REST API。您可以从输出中看到 REST API 正在发回所有标头响应信息。问题仍然存在,为什么调用 exactly 相同 REST API 的 Angular 程序看不到完整的响应标头?通话中是否缺少某些设置/标志/巫术?

Java 11 应用程序:

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("------------------");
    }

}

Java 应用程序的输出(response.header 仅为简洁起见):

response.headers: java.net.http.HttpHeaders@96f4f3fc { {access-control-allow-origin=[*], age=[0], connection=[keep-alive], 内容长度=[1302],内容类型=[应用程序/json],日期=[星期三,08 2020 年 7 月 17:13:42 GMT],服务器=[openresty], 严格传输安全=[最大年龄=31536000;预载], 变化=[接受编码],通过=[http/1.1 api-umbrella (ApacheTrafficServer [cMsSf ])], x-cache=[MISS], x-ratelimit-limit=[40], x-ratelimit-remaining=[39]} }

感谢您的帮助!

【问题讨论】:

    标签: angular header httpclient response


    【解决方案1】:

    您需要设置Access-Control-Expose-Headers 标头以包含其他标头。请参阅 Mozilla 文档:https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers

    默认情况下,仅公开 7 个 CORS 安全列表响应标头:

    • 缓存控制
    • 内容-语言
    • 内容长度
    • 内容类型
    • 过期
    • 最后修改
    • 编译指示

    您将在网络选项卡中看到您的其他标头,但 JavaScript 无法访问它们,除非它们位于 Access-Control-Expose-Headers 标头中。

    这是 Axios 的一个类似问题,与 4 年前不同的 javascript HTTP 库具有相同的答案:Axios get access to response header fields

    2.5 年前的 And 和 Angular 之一 Read response headers from API response - Angular 5 + TypeScript

    更新:由于您正在调用我认为您不拥有的 API (https://api.nasa.gov/),如果您需要在客户端中读取这些标头,则必须让 NASA 添加标头 Access-Control-Expose-Headers: X-RateLimit-Limit, X-RateLimit-Remaining

    您的另一个选择是创建一个代理服务器,而不是调用 NASA,而是调用您自己的服务器,该服务器将调用 NASA 并可以包含您的速率限制标头。

    【讨论】:

    • 感谢您的回复。不幸的是,我试过了,但没有效果。在上面的代码中,我添加了以下内容: const headers= new HttpHeaders() .set('Access-Control-Expose-Headers', 'Server');我还尝试了浏览器调试器中已知标头的各种值。
    • @Ken 设置在客户端什么都不做,你需要在服务器上设置它并且服务器必须在响应中发送该标头。
    • 这是交易,服务器必须发送标头,因为,1) 标头位于浏览器调试器 Network=> 响应标头中,以及 2) 来自小型 Java 的完全相同的 REST API 调用应用程序返回所有标题。看起来这不是服务器端问题,而是某种角度问题,即它如何处理响应标头的“延迟加载”。这是我试图找到解决方案的 Angular 问题。
    • 除非您的服务器发送一个标头“Access-Control-Expose-Headers”:“some-header, another-header, third-header”,否则它将无法被处理javascript,即使它们为这些标头发送值。这是一个浏览器安全/CORS 相关的功能。
    • @cjd82187 我添加了一个小型Java程序,进一步演示了问题的问题。从输出中可以看出,REST 调用正在返回所有标头。如果没有启用 Access-Control-Expose-Headers,Java 程序和 Angular 程序都无法访问标题。显然,Java 程序可以访问标头,而 Angular 程序则不能。我确定看起来 REST 服务器正在发送所有标头。 Angular 程序方面缺少哪些步骤可以访问完整的标头?
    【解决方案2】:

    为了获取 HTTP 响应的完整标头以及内容,您应该在发出请求时传递另一个名为 observe 的参数

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

    【讨论】:

    • 感谢您的回复。 {observe: 'response'} 选项已在我上面的示例中设置。即使使用该选项,Angular 调用中也不会返回完整的标头集。
    • 使用 access-control-expose-headers 从服务器端公开标题键
    • 我在你的截图中没有看到这个标题..你能确认一下
    • 在那 1 和 2 中我没有看到公开标题
    • @sunilbaba 我不明白你的意思。在 1) 调试器 Network=> Response Headers 清楚地显示响应中的标头,在 2) Java 显然返回所有标头。标头可用,但 Angular 中的某些东西阻止了对除一个响应标头字段之外的所有内容的访问。这就是我们要解决的问题。
    【解决方案3】:

    来自 mozila docs

    “Access-Control-Expose-Headers 响应标头允许服务器指示哪些响应标头应可用于在浏览器中运行的脚本,以响应跨域请求。

    默认情况下,仅公开 CORS 安全列表中的响应标头。为了让客户端能够访问其他标头,服务器必须使用 Access-Control-Expose-Headers 标头列出它们。”

    你需要配置服务端api(core.api, exc')

    显式允许您要向客户端公开的标头

    services.AddCors(options =>
    {
        options.AddPolicy("AllowAll", builder =>
    
        {
            builder.AllowAnyHeader()
                   .AllowAnyOrigin()
                   .WithExposedHeaders("Content-Range"); 
        });
    });
    

    配置后你会看到标题“content-Range”

    content-range: Categories 0-2/4
    

    它将在 JavaScript 代码中可用

    【讨论】:

    • 投了反对票,因为如前所述,这不是我的 API,它由另一个实体控制。另外,问题是为什么 Java 可以看到所有的标头,而 Angular 却不能使用相同的 API 调用。
    猜你喜欢
    • 1970-01-01
    • 2019-07-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多