Angular 4: невозможно прочитать заголовки из ответа - не проблема CORS

В контексте сервера, автоматически обновляющего токен, я борюсь с основами: получение данных заголовка из ответа.

Похоже, что это не связано с CORS, поскольку мой Node/express использует токен авторизации/x-access-token и соответственно отвечает (см. скрин вкладки сети ниже).

Первый шаг, который я хочу видеть работающим, — это просто чтение заголовка из ответа. Посмотрите мой код, он шаблонный, как в документации. Событие, получающее Content-Length, возвращает null.

auth-service.ts

login(username:string, password:string):Observable<boolean>{
    this.loggedIn = false;
    return new Observable( (observer:Observer<boolean>) => {

      const body = {user: username, pwd: password};
      const url = this.config.API_ENDPOINT_PUBLIC_AUTH;

      this.httpClient.post(url, body, {
        responseType: 'text',
        observe: "response"
      }).first().subscribe(
        (response:HttpResponse<string>) => {

          // DEBUG
          console.log(response.headers.get('Authorization'));
          console.log(response.headers.get('X-Test-Header'));
          console.log(response.headers.get('Content-length'));


          this.token = response.headers.get('Authorization');
          this.storeToken(this.token);
          this.currentUser = username;
          this.loggedIn = true;
          this.authChanged$.next('auth');
          observer.next(true);
        },
        (err) => observer.next(false),
        () => {},
      );
    });
  }

Вывод консоли:

нулевой

нулевой

нулевой

Сравните это с содержимым моей сетевой вкладки для этого запроса:

введите здесь описание изображения

Излишне говорить, что мой HttpInterceptor тоже не работает - после предоставления заголовка авторизации в ответе он использует значение как новый токен. Это для реализации автоматического обновления токенов:

token.interceptor.ts

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    const authService = this.injector.get(AuthService);
    const token = authService.getToken();

    if(token){
      request = request.clone({
        setHeaders: { 'x-access-token' : token }
      });
    }
    
    return next.handle(request).do(
      (event: HttpEvent<any>) => {
        if (event instanceof HttpResponse) {
          const response:HttpResponse<any> = (<HttpResponse<any>> event);
          const authorizationHeader = response.headers.get('Authorization');
          console.log('Authorization header value: ' + authorizationHeader);
          if(authorizationHeader){
            authService.setToken(authorizationHeader);
          }
        }
      },
      (err: any) => {
      if (err instanceof HttpErrorResponse){
        if (err.status === 401) {
          this.router.navigate(['/']);
        }
      }
    });
  }

person Jem    schedule 07.11.2017    source источник
comment
Может ли это быть связано с типом контента? Я вижу, что вы установили тип ответа как текст в своем коде TS, но затем сам ответ приходит как application/json. Дайте мне знать, если я ошибаюсь!   -  person avilac    schedule 07.11.2017
comment
Действительно интересно, я не заметил... Мне пришлось установить ожидаемый тип на строку, так как API возвращает нулевое тело, просто статус 200 и заголовок, содержащий токен. Позвольте мне попробовать вернуть пустой json, если он работает лучше.   -  person Jem    schedule 07.11.2017
comment
Нет, не помогает. Никакой заголовок, кроме «Content-Type», не может быть прочитан из ответа.   -  person Jem    schedule 07.11.2017
comment
Понятно... посмотрю еще раз   -  person avilac    schedule 07.11.2017
comment
Хорошо, только что нашел причину. Использование ответа, чтобы воспользоваться возможностями форматирования. Спасибо за ваше время @avilac!   -  person Jem    schedule 07.11.2017


Ответы (2)


Хорошо, вот ответ: я использую Node.js/Express в качестве серверной части, и хотя заголовки видны на вкладке сети Chrome, они не доступны для обработки Angular.

«Почему они видимы, но не могут быть использованы?» мне до сих пор непонятно.

Настройте приложение Node/Express (ищите комментарий «РЕШЕНИЕ ЗДЕСЬ»):

function configureExpress(expressApp){  
    expressApp.use(bodyparser.urlencoded({ extended: true }));
    expressApp.use(bodyparser.json());

    // Middleware: Use CORS, add headers and allow methods
    expressApp.use(expressMiddleware);
}

function expressMiddleware(req, res, next) {

    // Request origin: Allow ALL
    res.header("Access-Control-Allow-Origin", "*");

    // Allowed headers
    res.header("Access-Control-Allow-Headers",

        "Origin"
        +",X-Requested-With"   // Dont allow AJAX CORS without server consent - see http://stackoverflow.com/questions/17478731/whats-the-point-of-the-x-requested-with-header
        +",x-access-token"
        +",Content-Type"
        +",Authorization"
        +",Accept"
    );

    // SOLUTION HERE
    // Allow headers access
    res.header("access-control-expose-headers",
        ",Authorization"
        +",Content-Length"
        );

    // Allowed methods
    res.header('Access-Control-Allow-Methods',
        'GET,'
        +',POST'
        +',OPTIONS'
        +',PUT,'
        +',DELETE'
    );

    // Handle CORS requests: cross-domain/origin requests will begin with an OPTION request to the same endpoint.
    if('OPTIONS' === req.method){
        res.sendStatus(200);
    }

    else{
        // Request validations complete
        next();
    }
}
person Jem    schedule 07.11.2017

Добавление ответа с более актуальной информацией

Я использую Angular 7, и простое добавление пользовательского заголовка в Access-Control-Expose-Headers в API у меня не сработало.

Загвоздка здесь в том, что пользовательский заголовок должен быть в нижнем регистре, поскольку значения, разделенные запятыми, в Access-Control-Expose-Headers

Пример (экспресс)

const cors = require('cors');

const corsOptions = {
    allowedHeaders: 'x-custom-one,x-custom-two',
    exposedHeaders: 'x-custom-one,x-custom-two'
}
app.use(cors(corsOptions));
person Avinash    schedule 09.06.2019