angular2 / http получить заголовок местоположения ответа 201

Я успешно закончил начальный туториал angular2 "Tour of Heroes". Затем я создаю api на основе symfony3, FosRestBundle и BazingaHateoasBundle в качестве бэкэнда. Сейчас почти все работает, но при создании новых элементов я не могу получить заголовок «Местоположение» из ответа для загрузки вновь созданного элемента.

Вот мой герой-сервис:

import {Injectable} from "angular2/core";
import {Hero} from "./hero";
import {Http, Headers, RequestOptions, Response} from "angular2/http";
import {Observable} from "rxjs/Observable";
import "rxjs/Rx";

@Injectable()
export class HeroService {

    constructor(private _http:Http) {
    }

    private _apiUrl = 'http://tour-of-heros.loc/app_dev.php/api'; // Base URL
    private _heroesUrl = this._apiUrl + '/heroes';  // URL to the hero api


    getHeroes() {
        return this._http.get(this._heroesUrl)
            .map(res => <Hero[]> res.json()._embedded.items)
            .do(data => console.log(data)) // eyeball results in the console
            .catch(this.handleError);
    }

    addHero(name:string):Observable<Hero> {

        let body = JSON.stringify({name});

        let headers = new Headers({'Content-Type': 'application/json'});
        let options = new RequestOptions({headers: headers});

        return this._http.post(this._heroesUrl, body, options)
            // Hero is created, but unable to get URL from the Location header!
            .map((res:Response) => this._http.get(res.headers.get('Location')).map((res:Response) => res.json()))
            .catch(this.handleError)

    }

    private handleError(error:Response) {
        // in a real world app, we may send the error to some remote logging infrastructure
        // instead of just logging it to the console
        console.error(error);
        return Observable.throw(error.json().error || 'Server error');
    }

    getHero(id:number) {
        return this._http.get(this._heroesUrl + '/' + id)
            .map(res => {
                return res.json();
            })
            .do(data => console.log(data)) // eyeball results in the console
            .catch(this.handleError);
    }
}

Поэтому, когда я вызываю метод addHero, создается новый Hero и возвращается ответ 201 без тела, но с установленным заголовком Location:

Cache-Control: no-cache
Connection: Keep-Alive
Content-Length: 0
Content-Type: application/json
Date: Tue, 29 Mar 2016 09:24:42 GMT
Keep-Alive: timeout=5, max=100
Location: http://tour-of-heros.loc/app_dev.php/api/heroes/3
Server: Apache/2.4.10 (Debian)
X-Debug-Token: 06323e
X-Debug-Token-Link: /app_dev.php/_profiler/06323e
access-control-allow-credentials: true
access-control-allow-origin: http://172.18.0.10:3000

Проблема в том, что res.headers.get('Location') не получил заголовок Location из ответа. После некоторой отладки кажется, что res.headers просто предоставляет два заголовка Cache-Control и Content-Type. Но нет места.

Я знал, что проблему также можно решить, добавив вновь созданные данные в тело ответа, но на самом деле я не хочу решать эту проблему.

Заранее спасибо!


person skroczek    schedule 29.03.2016    source источник
comment
Location: http://tour-of-heros.loc/app_dev.php/api/heroes/3. Содержит ли он ваш response object? Если да, то в чем проблема?   -  person micronyks    schedule 29.03.2016
comment
Нет, не нашел в объекте ответа. Я уверен, что это проблема;). Но я понятия не имею, где еще мне его искать.   -  person skroczek    schedule 29.03.2016
comment
Итак, как вы получили этот последний фрагмент информации?   -  person micronyks    schedule 29.03.2016
comment
@micronyks какая информация? Из консоли браузера я получаю все заголовки и заголовок для объекта ответа angular, который я получаю от console.log(res); и console.log(res.headers.keys()) (об этом я не упоминал в вопросе).   -  person skroczek    schedule 29.03.2016


Ответы (2)


Вы должны использовать оператор flatMap вместо оператора map:

return this._http.post(this._heroesUrl, body, options)
        .flatMap((res:Response) => {
          var location = res.headers.get('Location');
          return this._http.get(location);
        }).map((res:Response) => res.json()))
        .catch(this.handleError)

Изменить

Что касается проблемы с заголовком.

Я думаю, что ваша проблема связана с CORS. Я думаю, что предварительный запрос должен возвращать заголовки для авторизации в Access-Control-Allow-Headers в своем ответе. Что-то подобное:

Access-Control-Allow-Headers:location

Если у вас есть рука на стороне сервера вызова, вы можете обновить этот заголовок заголовками, которые хотите использовать на стороне клиента (in your caseLocation`).

Я отлаживал запрос и особенно в классе XhrBackend, который фактически выполняет запрос и получает ответ от объекта XHR. Заголовок не возвращается кодом: _xhr.getAllResponseHeaders(). В моем случае у меня только Content-Type.

См. Эту ссылку: https://github.com/angular/angular/blob/master/modules/angular2/src/http/backends/xhr_backend.ts#L42.

Из следующего вопроса:

Спецификация Cross-Origin Resource Sharing фильтрует заголовки, которые предоставляет getResponseHeader () для запросов с разными источниками. И эта спецификация запрещает доступ к любому полю заголовка ответа, кроме полей простого заголовка ответа (например, Cache-Control, Content-Language, Content-Type, Expires, Last-Modified и Pragma):

См. Этот вопрос для получения более подробной информации:

person Thierry Templier    schedule 29.03.2016
comment
Спасибо за Ваш ответ. Но та же проблема, res.headers.get('Location') возвращает null. - person skroczek; 29.03.2016
comment
Твое право! CORS - это проблема. Смотрите мой собственный ответ. Большое спасибо! - person skroczek; 29.03.2016
comment
И ваше предложение использовать flatMap более чем помогает в решении другой проблемы :) - person skroczek; 29.03.2016

Моя вина!

после прочтения https://github.com/angular/angular/issues/5237 и http://www.aaron-powell.com/posts/2013-11-28-accessing-location-header-in-cors-response.html, я понимаю, что что-то не так с моей конфигурацией CORS NelmioCorsBundle (https://github.com/nelmio/NelmioCorsBundle/issues/9). В качестве быстрого и грязного исправления я добавил expose_headers: ['Origin','Accept','Content-Type', 'Location'] в конфигурацию symfony: До:

nelmio_cors:
    paths:
        '^/api/':
            allow_credentials: true
            origin_regex: true
            allow_origin: ['*']
            allow_headers: ['Origin','Accept','Content-Type', 'Location']
            allow_methods: ['POST','GET','DELETE','PUT','OPTIONS']
            max_age: 3600

После:

nelmio_cors:
    paths:
        '^/api/':
            allow_credentials: true
            origin_regex: true
            allow_origin: ['*']
            allow_headers: ['Origin','Accept','Content-Type', 'Location']
            allow_methods: ['POST','GET','DELETE','PUT','OPTIONS']
            expose_headers: ['Origin','Accept','Content-Type', 'Location']
            max_age: 3600

И теперь это работает как шарм.

Так что это не угловая проблема, и она никогда не была решаемой. Извините! Я сохраню это для всех, кто сталкивался с подобной проблемой CORS.

Большое спасибо всем, кто внес свой вклад

person skroczek    schedule 29.03.2016