사용자 도구

사이트 도구


angular:httpclient

차이

문서의 선택한 두 판 사이의 차이를 보여줍니다.

차이 보기로 링크

angular:httpclient [2021/10/01 14:20] – 바깥 편집 127.0.0.1angular:httpclient [2025/04/15 10:05] (현재) – 바깥 편집 127.0.0.1
줄 1: 줄 1:
 +====== HttpClient ======
 +
 +관련 POST
 +  * [[angular:httpclient:post1|현창현 POST]]
 +
 +대부분의 프런트 엔드 응용 프로그램은 HTTP 프로토콜을 통해 백엔드 서비스와 통신합니다. 최신 브라우저는 HTTP 요청을하기 위해 **XMLHttpRequest** 인터페이스와 fetch() API의 두 가지 API를 지원합니다.
 +
 +//@angular/common/http//의 **HttpClient**는 브라우저에 의해 노출 된 //XMLHttpRequest interface//에 의존하는 **Angular Application**을 위한 단순화 된 클라이언트 HTTP API를 제공합니다. HttpClient의 추가 이점으로는 테스트 가능성 기능, 형식화 된 요청 및 응답 객체, 요청 및 응답 차단, 관찰 가능한 API 및 간소화 된 오류 처리가 있습니다.
 +
 +===== Setup =====
 +HttpClient를 사용하려면 먼저 Angular HttpClientModule을 가져와야합니다. 대부분의 앱은 루트 AppModule에서 그렇게합니다.
 +
 +//HttpClient//는 HTTP를 통해 원격서버와 소통을하는 Angular의 메커니즘이다.
 +
 +앱의 모든 곳에서 //HttpClient//를 사용 가능하게하려면:
 +  * open the root AppModule
 +  * import the HttpClientModule symbol from @angular/common/http <code javascript>
 +import { HttpClientModule }    from '@angular/common/http';</code>
 +  * add it to the @NgModule.imports array
 +
 +HttpClientModule을 AppModule로 가져온 다음 HttpClient를 다음 ConfigService 예제와 같이 응용 프로그램 클래스에 삽입 할 수 있습니다.
 +<code javascript app/config/config.service.ts>
 +import { Injectable } from '@angular/core';
 +import { HttpClient } from '@angular/common/http';
 +
 +@Injectable()
 +export class ConfigService {
 +  constructor(private http: HttpClient) { }
 +}
 +</code>
 +
 +===== JSON data 얻기 =====
 +subscribe를 통해서 request를 요구한다.
 +<code javascript>
 +this.http.get(this.configUrl)
 +.subscribe((data: Config) => this.config = {
 +        heroesUrl: data['heroesUrl'],
 +        textfile:  data['textfile']
 +    });
 +</code>
 +service method는 Observable의 구성 데이터를 반환하기 때문에 component는 method의 return value을 구독합니다. subscribe callback은 데이터 필드를 구성 요소의 구성 객체로 복사합니다. 구성 객체는 표시를 위해 구성 요소 템플리트에서 데이터 바인딩됩니다.
 +
 +==== 왜 service를 쓰는가 ====
 +이 예제는 너무 간단해서 //Http.get()//을 컴포넌트 자체에 작성하고 서비스를 건너 뛰기를 원합니다.
 +
 +그러나 데이터 액세스는 거의 불가능합니다. 일반적으로 데이터를 사후 처리하고, 오류 처리를 추가하며, 간헐적 인 연결에 대처하기 위해 일부 재시도 논리를 사용합니다.
 +
 +이 구성 요소는 데이터 액세스 세부 사항으로 인해 복잡해집니다. 이 구성 요소는 이해하기 어렵고 테스트하기가 어려워지며 데이터 액세스 논리를 재사용하거나 표준화 할 수 없습니다.
 +
 +그렇기 때문에 데이터 액세스를 별도의 서비스에 캡슐화하고 해당 서비스의 구성 요소에서 위임과 같은 단순한 경우에도 데이터 액세스를 데이터 액세스에서 분리하는 것이 가장 좋은 방법입니다.
 +
 +==== Type-checking the response ====
 +위의 subscribe 콜백은 데이터 값을 추출하기 위해 대괄호 표기법(bracket notation)을 필요로합니다.
 +<code javascript>
 +.subscribe((data: Config) => this.config = {
 +    heroesUrl: data['heroesUrl'],
 +    textfile:  data['textfile']
 +});
 +</code>
 +TypeScript가 서비스의 데이터 객체에 heroesUrl 속성이 없다는 오류 메시지가 올바르게 표시되기 때문에 data.heroesUrl을 쓸 수 없습니다.
 +
 +HttpClient.get () 메소드는 JSON 서버 응답을 익명 객체 유형으로 구문 분석했습니다. 그 물체의 모양이 무엇인지 알지 못합니다.
 +
 +HttpClient에 응답 형식을 알리면 출력을 더 쉽고 분명하게 사용할 수 있습니다.
 +
 +먼저, 올바른 모양의 인터페이스를 정의:
 +<code javascript>
 +export interface Config {
 +  heroesUrl: string;
 +  textfile: string;
 +}
 +</code>
 +그런 다음 해당 인터페이스를 서비스에서 //HttpClient.get()// 호출의 유형 매개 변수로 지정:
 +<code javascript>
 +getConfig() {
 +  // now returns an Observable of Config
 +  return this.http.get<Config>(this.configUrl);
 +}
 +</code>
 +업데이트 된 구성 요소 메소드의 콜백은 형식화 된 데이터 객체를 수신하므로 더 쉽고 안전합니다:
 +<code javascript>
 +config: Config;
 +
 +showConfig() {
 +  this.configService.getConfig()
 +    // clone the data object, using its known Config shape
 +    .subscribe((data: Config) => this.config = { ...data });
 +}
 +</code>
 +
 +==== Reading the full response ====
 +응답 본문은 필요한 모든 데이터를 반환하지 않습니다. 경우에 따라 서버는 특수 헤더 또는 상태 코드를 반환하여 응용 프로그램 워크 플로에서 중요한 특정 조건을 나타냅니다.
 +
 +observe option으로 전체 응답을 원한다고 HttpClient에게 알려줍니다.
 +<code javascript>
 +getConfigResponse(): Observable<HttpResponse<Config>> {
 +  return this.http.get<Config>(
 +    this.configUrl, { observe: 'response' });
 +}
 +</code>
 +
 +이제 HttpClient.get()은 JSON 데이터가 아닌 입력 된 HttpResponse의 Observable을 반환합니다.
 +
 +구성 요소의 showConfigResponse() 메서드는 응답 헤더와 구성을 표시합니다.
 +<code javascript>
 +showConfigResponse() {
 +  this.configService.getConfigResponse()
 +    // resp is of type `HttpResponse<Config>`
 +    .subscribe(resp => {
 +      // display its headers
 +      const keys = resp.headers.keys();
 +      this.headers = keys.map(key =>
 +        `${key}: ${resp.headers.get(key)}`);
 +
 +      // access the body directly, which is typed as `Config`.
 +      this.config = { ... resp.body };
 +    });
 +}
 +</code>
 +보는 것과같이, reponse object는 정확한 type의 //body//속성을 가진다.
 +===== Error Handling =====
 +서버에서 요청이 실패하거나 네트워크 연결 상태가 좋지 않아 서버에 도달하지 못하면 어떻게됩니까? HttpClient는 성공적인 응답 대신 오류 개체를 반환합니다.
 +
 +.subscribe()에 두 번째 콜백을 추가하여 구성 요소를 처리 할 수 있습니다.
 +<code javascript>
 +showConfig() {
 +  this.configService.getConfig()
 +    .subscribe(
 +      (data: Config) => this.config = { ...data }, // success path
 +      error => this.error = error // error path
 +    );
 +}
 +</code>
 +데이터 액세스가 실패 할 경우 사용자에게 어떤 종류의 피드백을 제공하는 것이 좋습니다. 그러나 HttpClient가 반환 한 원시 오류 객체를 표시하는 것은 최선의 방법과 거리가 멀습니다.
 +
 +==== Getting error details ====
 +오류가 발생했음을 감지하는 것이 한 가지입니다. 그 오류를 해석하고 사용자에게 친숙한 응답을 작성하는 것은 좀 더 복잡합니다.
 +
 +두 가지 유형의 오류가 발생할 수 있습니다. 서버 백엔드는 요청을 거부하여 404 또는 500과 같은 상태 코드와 함께 HTTP 응답을 리턴합니다. 이는 오류 응답입니다.
 +
 +또는 요청이 성공적으로 완료되지 못하게하는 네트워크 오류 또는 RxJS 운영자에게 던져지는 예외와 같은 클라이언트 측에서 문제가 발생할 수 있습니다. 이러한 오류는 JavaScript ErrorEvent 객체를 생성합니다.
 +
 +HttpClient는 HttpErrorResponse에서 두 가지 종류의 오류를 모두 캡처하고 실제로 발생한 상황을 파악하기 위해 해당 응답을 검사 할 수 있습니다.
 +
 +오류 검사, 해석 및 해결은 서비스가 아닌 구성 요소에서 수행하려는 작업입니다.
 +
 +먼저 다음과 같은 오류 처리기를 고안해야합니다.
 +<code javascript>
 +private handleError(error: HttpErrorResponse) {
 +  if (error.error instanceof ErrorEvent) {
 +    // A client-side or network error occurred. Handle it accordingly.
 +    console.error('An error occurred:', error.error.message);
 +  } else {
 +    // The backend returned an unsuccessful response code.
 +    // The response body may contain clues as to what went wrong,
 +    console.error(
 +      `Backend returned code ${error.status}, ` +
 +      `body was: ${error.error}`);
 +  }
 +  // return an observable with a user-facing error message
 +  return throwError(
 +    'Something bad happened; please try again later.');
 +};
 +</code>
 +이 핸들러는 사용자 친화적 인 오류 메시지와 함께 RxJS //Error-Observable//을 반환합니다. 서비스 사용자는 서비스 메소드가 Observable을 어떤 종류의 "나쁜"것으로 돌려 주길 기대합니다.
 +
 +이제 HttpClient 메서드에서 반환 한 Observables를 가져 와서 오류 처리기로 전달합니다.
 +
 +<code javascript>
 +getConfig() {
 +  return this.http.get<Config>(this.configUrl)
 +    .pipe(
 +      catchError(this.handleError)
 +    );
 +}
 +</code>
 +==== retry() ====
 +
 +때때로 오류는 일시적이며 다시 시도하면 자동으로 사라집니다. 예를 들어 네트워크 중단은 모바일 시나리오에서 흔히 발생하며 다시 시도하면 성공적인 결과를 얻을 수 있습니다.
 +
 +RxJS 라이브러리는 탐구할만한 몇 가지 재시도 연산자를 제공합니다. 가장 간단한 방법은 retry ()이며 실패한 Observable을 지정된 횟수만큼 자동으로 다시 구독합니다. HttpClient 메서드 호출의 결과에 다시 등록하면 HTTP 요청을 다시 발행하는 효과가 있습니다.
 +
 +오류 처리기 바로 전에 HttpClient 메서드 결과로 파이프를 연결하십시오.
 +<code javascript>
 +getConfig() {
 +  return this.http.get<Config>(this.configUrl)
 +    .pipe(
 +      retry(3), // retry a failed request up to 3 times
 +      catchError(this.handleError) // then handle the error
 +    );
 +}
 +</code>
 +
 +===== Observables and operators =====
 +이 가이드의 이전 섹션에서는 catchError 및 retry와 같은 RxJS Observables 및 연산자에 대해 설명했습니다. 아래에서 계속 진행하면 RxJS 아티팩트가 더 많이 발생합니다.
 +
 +RxJS는 비동기 및 콜백 기반 코드를 기능적, 반응 적 스타일로 작성하기위한 라이브러리입니다. HttpClient를 포함한 많은 Angular API가 RxJS Observables를 생성하고 사용합니다.
 +
 +RxJS 자체는이 가이드의 범위를 벗어납니다. 웹에서 많은 학습 자료를 찾을 수 있습니다. 최소한의 RxJS 지식 만 있으면 HttpClient를 효과적으로 사용하기 위해 시간이 지남에 따라 RxJS 기술을 키울 수 있습니다.
 +
 +이러한 코드 스니펫을 따르고 있다면 해당 스니펫에 나타나는 RxJS 관측 가능 및 연산자 기호를 가져와야합니다. 이러한 ConfigService 가져 오기는 일반적입니다.
 +
 +<code javascript>
 +import { Observable, throwError } from 'rxjs';
 +import { catchError, retry } from 'rxjs/operators';
 +</code>
 +
 +===== Requesting non-JSON data =====
 +모든 API가 JSON 데이터를 반환하지는 않습니다. 이 다음 예제에서 DownloaderService 메서드는 서버에서 텍스트 파일을 읽고 파일 내용을 기록한 다음 Observable<string>으로 호출자에게 반환합니다.
 +<code javascript>
 +getTextFile(filename: string) {
 +  // The Observable returned by get() is of type Observable<string>
 +  // because a text response was specified.
 +  // There's no need to pass a <string> type parameter to get().
 +  return this.http.get(filename, {responseType: 'text'})
 +    .pipe(
 +      tap( // Log the result or error
 +        data => this.log(filename, data),
 +        error => this.logError(filename, error)
 +      )
 +    );
 +}
 +</code>
 +HttpClient.get()은 responseType 옵션 때문에 기본 JSON 대신 문자열을 반환합니다.
 +
 +RxJS 탭 연산자 ( "도청기"에서와 같이)는 코드가 방해받지 않고 관찰 가능을 통과하면서 오류 및 오류 값을 검사 할 수있게합니다.
 +
 +DownloaderComponent의 download() 메소드는 서비스 메소드에 등록하여 요청을 시작합니다.
 +<code javascript>
 +download() {
 +  this.downloaderService.getTextFile('assets/textfile.txt')
 +    .subscribe(results => this.contents = results);
 +}
 +</code>
 +
 +===== Sending data to the server =====
 +HttpClient는 서버에서 데이터를 가져 오는 것 외에도 변경 요청, 즉 PUT, POST 및 DELETE와 같은 다른 HTTP 메서드로 서버에 데이터를 보내는 요청을 지원합니다.
 +
 +이 가이드의 샘플 앱에는 히어로를 가져와 사용자가 추가, 삭제 및 업데이트 할 수있게 해주는 "Tour of Heroes"예제의 단순화 된 버전이 포함되어 있습니다.
 +
 +다음 섹션에서는 샘플 HeroesService의 메소드를 발췌합니다.
 +
 +==== Adding headers ====
 +많은 서버는 저장 조작을 위해 여분의 헤더가 필요합니다. 예를 들어 요청 본문의 MIME 형식을 명시 적으로 선언하기 위해 "Content-Type"헤더가 필요할 수 있습니다. 또는 서버에 인증 토큰이 필요합니다.
 +
 +HeroesService는 모든 HttpClient 저장 메소드에 전달되는 httpOptions 객체에서 이러한 헤더를 정의합니다.
 +<code javascript>
 +import { HttpHeaders } from '@angular/common/http';
 +
 +const httpOptions = {
 +  headers: new HttpHeaders({
 +    'Content-Type':  'application/json',
 +    'Authorization': 'my-auth-token'
 +  })
 +};
 +</code>
 +==== Making a POST request ====
 +애플리케이션은 종종 서버에 데이터를 POST합니다. 양식을 제출할 때 POST됩니다. 다음 예제에서 HeroesService는 영웅을 데이터베이스에 추가 할 때 게시합니다.
 +<code javascript>
 +/** POST: add a new hero to the database */
 +addHero (hero: Hero): Observable<Hero> {
 +  return this.http.post<Hero>(this.heroesUrl, hero, httpOptions)
 +    .pipe(
 +      catchError(this.handleError('addHero', hero))
 +    );
 +}
 +</code>
 +HttpClient.post() 메서드는 서버가 새로운 영웅을 반환 할 것으로 기대하는 형식 매개 변수가 있고 리소스 URL을 사용한다는 점에서 get()과 비슷합니다.
 +
 +두 개의 매개 변수가 더 필요합니다.
 +  - hero - 요청 본문에 POST 할 데이터입니다.
 +  - httpOptions -이 경우 필요한 헤더를 지정하는 메소드 옵션.
 +
 +물론 그것은 위에서 설명한 것과 거의 같은 방식으로 오류를 포착합니다.
 +
 +HeroesComponent는이 서비스 메소드에 의해 반환 된 Observable에 가입하여 실제 POST 작업을 시작합니다.
 +
 +<code javascript>
 +this.heroesService.addHero(newHero)
 +  .subscribe(hero => this.heroes.push(hero));
 +</code>
 +서버가 새로 추가 된 영웅으로 성공적으로 응답하면 구성 요소는 영웅을 표시된 영웅 목록에 추가합니다.
 +
 +==== Making a DELETE request ====
 +이 애플리케이션은 요청 URL에 영웅의 ID를 전달하여 HttpClient.delete 메소드로 영웅을 삭제합니다.
 +<code javascript>
 +/** DELETE: delete the hero from the server */
 +deleteHero (id: number): Observable<{}> {
 +  const url = `${this.heroesUrl}/${id}`; // DELETE api/heroes/42
 +  return this.http.delete(url, httpOptions)
 +    .pipe(
 +      catchError(this.handleError('deleteHero'))
 +    );
 +}
 +</code>
 +HeroesComponent는 이 서비스 메소드에 의해 리턴 된 Observable에 등록함으로써 실제 DELETE 조작을 초기화한다.
 +<code javascript>
 +this.heroesService.deleteHero(hero.id).subscribe();
 +</code>
 +구성 요소가 삭제 작업의 결과를 기대하지 않으므로 콜백없이 구독합니다. 결과를 사용하지 않더라도 구독해야합니다. subscribe() 메소드를 호출하면 관찰 가능 (observable)이 실행되며, 이것은 관찰 요청을 시작하는 것이다.
 +
 +> subscribe()를 호출해야합니다. 그렇지 않으면 아무 일도 일어나지 않습니다. HeroesService.deleteHero()를 호출하면 DELETE 요청이 시작되지 않습니다.
 +
 +<code javascript>
 +// oops ... subscribe() is missing so nothing happens
 +this.heroesService.deleteHero(hero.id);
 +</code>
 +
 +=== 항상 구독하십시오! ===
 +
 +HttpClient 메서드는 해당 메서드에서 반환 한 관찰 가능 항목에 대해 subscribe ()를 호출 할 때까지 HTTP 요청을 시작하지 않습니다. 이는 모든 HttpClient 메소드에 적용됩니다.
 +
 +>AsyncPipe는 자동으로 subscribe[구독] (및 unsubscribe[구독 취소])합니다.
 +
 +HttpClient 메서드에서 반환 된 모든 관찰 가능 항목은 의도적으로 차갑습니다. HTTP 요청의 실행이 지연되므로 실제로 발생하기 전에 관찰 및 관찰 작업을 tap 및 catchError와 같은 추가 작업으로 확장 할 수 있습니다.
 +
 +subscribe (...)를 호출하면 관찰 가능 객체가 실행되고 HttpClient가 HTTP 요청을 작성하고 서버로 전송합니다.
 +
 +이러한 관찰 내용은 실제 HTTP 요청에 대한 청사진이라고 생각할 수 있습니다.
 +
 +> 사실, 각 subscribe()는 독립적으로 observable을 실행합니다. 두 번 구독하면 두 개의 HTTP 요청이 발생합니다.
 +> <code javascript>
 +const req = http.get<Heroes>('/api/heroes');
 +// 0 requests made - .subscribe() not called.
 +req.subscribe();
 +// 1 request made.
 +req.subscribe();
 +// 2 requests made.
 +</code>
 +
 +==== Making a PUT request ====
 +앱이 리소스를 업데이트 된 데이터로 완전히 대체하기 위해 PUT 요청을 보냅니다. 다음 HeroesService 예제는 POST 예제와 같습니다.
 +<code javascript>
 +/** PUT: update the hero on the server. Returns the updated hero upon success. */
 +updateHero (hero: Hero): Observable<Hero> {
 +  return this.http.put<Hero>(this.heroesUrl, hero, httpOptions)
 +    .pipe(
 +      catchError(this.handleError('updateHero', hero))
 +    );
 +}
 +</code>
 +위에서 설명한 이유로 호출자 (이 경우 HeroesComponent.update())는 요청을 시작하기 위해 HttpClient.put()에서 반환 된 관찰 가능 객체에 subscribe()해야합니다.
 +
 +===== Advanced usage =====
 +우리는 **@angular/common/http**에서 기본적인 HTTP기능에 대해 논의했지만 때로는 단순 요청을하고 데이터를 다시 가져 오는 것 이상을해야 할 때도 있습니다.
 +
 +==== Configuring the request ====
 +보내는 요청의 다른 측면은 HttpClient 메서드의 마지막 인수로 전달 된 옵션 개체를 통해 구성 할 수 있습니다.
 +
 +이전에 HeroesService는 options 객체 (httpOptions)를 save 메소드에 전달하여 기본 헤더를 설정했다. 더 많은 일을 할 수 있습니다.
 +=== Update headers ===
 +HttpHeaders 클래스의 인스턴스는 변경 불가능하기 때문에 이전 옵션 객체에서 기존 헤더를 직접 수정할 수 없습니다.
 +
 +대신 set() 메서드를 사용하십시오. 새 변경 내용이 적용된 현재 인스턴스의 복제본을 반환합니다.
 +
 +다음 요청을하기 전에 (오래된 토큰이 만료 된 후) 인증 헤더를 업데이트하는 방법은 다음과 같습니다.
 +<code javascript>
 +httpOptions.headers =
 +  httpOptions.headers.set('Authorization', 'my-new-auth-token');
 +</code>
 +=== URL Parameters ===
 +URL 검색 매개 변수를 추가하는 것도 비슷한 방식으로 작동합니다. 다음은 검색 단어가 포함 된 영웅을 검색하는 searchHeroes 메소드입니다.
 +<code javascript>
 +/* GET heroes whose name contains search term */
 +searchHeroes(term: string): Observable<Hero[]> {
 +  term = term.trim();
 +
 +  // Add safe, URL encoded search parameter if there is a search term
 +  const options = term ?
 +   { params: new HttpParams().set('name', term) } : {};
 +
 +  return this.http.get<Hero[]>(this.heroesUrl, options)
 +    .pipe(
 +      catchError(this.handleError<Hero[]>('searchHeroes', []))
 +    );
 +}
 +</code>
 +검색어가있는 경우 코드는 HTML URL 인코딩 검색 매개 변수가있는 옵션 개체를 구성합니다. 용어가 "foo"이면 GET 요청 URL은 api/heroes/?name=foo가됩니다.
 +
 +HttpParams는 변경할 수 없으므로 set() 메서드를 사용하여 옵션을 업데이트해야합니다.
 +==== Debouncing requests ====
 +이 샘플에는 npm 패키지 검색 기능이 포함되어 있습니다.
 +
 +사용자가 검색 상자에 이름을 입력하면 PackageSearchComponent는 해당 이름의 패키지에 대한 검색 요청을 NPM 웹 API에 전송합니다.
 +
 +다음은 템플릿에서 발췌 한 내용입니다.
 +<code javascript>
 +<input (keyup)="search($event.target.value)" id="name" placeholder="Search"/>
 +
 +<ul>
 +  <li *ngFor="let package of packages$ | async">
 +    <b>{{package.name}} v.{{package.version}}</b> -
 +    <i>{{package.description}}</i>
 +  </li>
 +</ul>
 +</code>
 +(keyup) 이벤트 바인딩은 모든 키 입력을 구성 요소의 search() 메서드로 보냅니다.
 +
 +모든 키 입력에 대한 요청을 보내는 것은 많은 비용이 듭니다. 사용자가 입력을 중지하고 요청을 보낼 때까지 기다리는 것이 좋습니다. 이 발췌 부분에서 볼 수 있듯이 RxJS 연산자로 구현하기 쉽습니다.
 +<code javascript>
 +withRefresh = false;
 +packages$: Observable<NpmPackageInfo[]>;
 +private searchText$ = new Subject<string>();
 +
 +search(packageName: string) {
 +  this.searchText$.next(packageName);
 +}
 +
 +ngOnInit() {
 +  this.packages$ = this.searchText$.pipe(
 +    debounceTime(500),
 +    distinctUntilChanged(),
 +    switchMap(packageName =>
 +      this.searchService.search(packageName, this.withRefresh))
 +  );
 +}
 +
 +constructor(private searchService: PackageSearchService) { }
 +</code>
 +searchText$는 사용자가 제공하는 검색 창 값의 순서입니다. 이것은 RxJS Subject로 정의됩니다. 즉, search() 메소드에서와 같이 next(value)를 호출하여 값을 생성 할 수있는 멀티 캐스팅 관측자입니다.
 +
 +삽입 된 PackageSearchService에 모든 searchText 값을 직접 전달하는 대신 ngOnInit() 파이프의 코드는 세 개의 연산자를 통해 값을 검색합니다.
 +
 +  - debounceTime(500) - 사용자가 입력을 중지 할 때까지 기다립니다 (이 경우 1/2 초).
 +  - distinctUntilChanged() - 검색 텍스트가 변경 될 때까지 대기합니다.
 +  - switchMap() - 서비스에 검색 요청을 보냅니다.
 +
 +코드는이 재구성 된 검색 결과의 Observable에 packages$를 설정합니다. 이 템플릿은 AsyncPipe를 사용하여 packages$를 구독하고 도착한 검색 결과를 표시합니다.
 +
 +검색 값은 새 값이고 사용자가 입력을 중지 한 경우에만 서비스에 도달합니다.
 +> withRefresh 옵션은 아래에 설명되어 있습니다.
 +=== switchMap() ===
 +
 +switchMap () 연산자에는 세 가지 중요한 특징이 있습니다.
 +
 +  - Observable을 반환하는 함수 인수를 사용합니다. 다른 데이터 서비스 메소드와 마찬가지로 PackageSearchService.search가 Observable을 반환합니다.
 +  - 이전 검색 요청이 아직 연결 상태가 좋지 않은 상태에서 계속 진행중인 경우 해당 요청을 취소하고 새 요청을 보냅니다.
 +  - 서버가 순서가 바뀌지 않는 경우에도 원래 요청 순서대로 서비스 응답을 반환합니다.
 +
 +> 이 디 바운싱 로직을 재사용 할 생각이라면 유틸리티 함수 또는 PackageSearchService 자체로 옮기는 것을 고려하십시오.
 +
 +==== Intercepting requests and responses ====
 +//HTTP Interception//는 @angular/common/http의 주요 기능입니다. interceptor를 사용하면 애플리케이션의 HTTP 요청을 검사하여 서버로 변환하는 interceptor를 선언 할 수 있습니다. 동일한 interceptor는 서버의 응답을 검사하여 응용 프로그램으로 다시 변환 할 수 있습니다. 다중 interceptor는 request/response 처리기의 forward-and-backward 체인을 구성합니다.
 +
 +interceptor는 모든 HTTP request/response에 대해 일상적인 표준 방법으로 인증에서 로깅에 이르는 다양한 암시 적 작업을 수행 할 수 있습니다.
 +
 +interceptor가 없으면 개발자는 각 HttpClient 메서드 호출에 대해 이러한 작업을 //명시적//으로 구현해야합니다.
 +
 +=== Write an interceptor ===
 +인터셉터를 구현하려면 HttpInterceptor 인터페이스의 intercept() 메소드를 구현하는 클래스를 선언하십시오.
 +
 +아무 것도하지 않고 그냥 요청을 전달하는 무의미한 요격기 요격기가 있습니다.
 +<code javascript>
 +import { Injectable } from '@angular/core';
 +import {
 +  HttpEvent, HttpInterceptor, HttpHandler, HttpRequest
 +} from '@angular/common/http';
 +
 +import { Observable } from 'rxjs';
 +
 +/** Pass untouched request through to the next request handler. */
 +@Injectable()
 +export class NoopInterceptor implements HttpInterceptor {
 +
 +  intercept(req: HttpRequest<any>, next: HttpHandler):
 +    Observable<HttpEvent<any>> {
 +    return next.handle(req);
 +  }
 +}
 +</code>
 +
 +intercept 메서드는 request을 Observable로 변환하여 결국 HTTP response을 반환합니다. 이러한 의미에서 각 인터셉터는 완전히 그 자체로 요청을 처리 할 수 있습니다.
 +
 +대부분의 인터셉터는 HttpHandler 인터페이스를 구현하는 다음 객체의 handle() 메소드에 대한 (아마도 변경된) request을 전달하고 전달하는 response을 검사합니다.
 +<code javascript>
 +export abstract class HttpHandler {
 +  abstract handle(req: HttpRequest<any>): Observable<HttpEvent<any>>;
 +}
 +</code>
 +intercept()처럼 handle() 메서드는 HTTP 요청을 서버의 응답을 포함하는 HttpEvents Observable로 변환합니다. intercept() 메소드는 관찰자를 검사하여 호출자에게 반환하기 전에이를 변경할 수 있습니다.
 +
 +이 no-op 인터셉터는 단순히 원래 요청으로 next.handle()을 호출하고 일을하지 않고 observable을 반환합니다.
 +
 +=== The next object ===
 +
 +다음 객체는 인터셉터 체인의 다음 인터셉터를 나타냅니다. 마지막으로 체인에있는 요청은 서버에 요청을 보내고 서버의 응답을받는 HttpClient 백엔드 처리기입니다.
 +
 +대부분의 인터셉터는 next.handle ()을 호출하여 요청이 다음 인터셉터와 결국 백엔드 처리기로 전달되도록합니다. 인터셉터는 next.handle () 호출을 건너 뛰고, 체인을 단락시키고, 자체 서버 Observable을 인공 서버 응답으로 리턴 할 수 있습니다.
 +
 +이것은 Express.js와 같은 프레임 워크에서 흔히 볼 수있는 미들웨어 패턴입니다.
 +
 +=== Provide the interceptor ===
 +
 +NoopInterceptor는 Angular의 DI (Dependency Injection) 시스템으로 관리되는 서비스입니다. 다른 서비스와 마찬가지로, 응용 프로그램이 사용하기 전에 인터셉터 클래스를 제공해야합니다.
 +
 +인터셉터는 HttpClient 서비스의 종속성 (선택 사항)이므로 HttpClient를 제공하는 동일한 인젝터 (또는 인젝터의 부모)에 제공해야합니다. DI가 HttpClient를 생성 한 후에 제공되는 인터셉터는 무시됩니다.
 +
 +이 앱은 AppModule에서 HttpClientModule을 가져 오는 부작용으로 앱의 루트 인젝터에 HttpClient를 제공합니다. AppModule에서도 인터셉터를 제공해야합니다.
 +
 +@angular/common/http에서 HTTP_INTERCEPTORS 주입 토큰을 가져온 후 다음과 같이 NoopInterceptor 공급자를 작성합니다.
 +<code javascript>
 +{ provide: HTTP_INTERCEPTORS, useClass: NoopInterceptor, multi: true },
 +</code>
 +multi : true 옵션을 주목하십시오. 이 필수 설정은 Angle에 HTTP_INTERCEPTORS가 단일 값이 아닌 값 배열을 주입하는 다중 제공자에 대한 토큰임을 나타냅니다.
 +
 +이 제공자를 AppModule의 공급자 배열에 직접 추가 할 수 있습니다. 그러나 다소 장황하고 더 많은 인터셉터를 만들어 동일한 방식으로 제공 할 수있는 좋은 기회가 있습니다. 또한 이러한 인터셉터를 제공하는 순서에 세심한주의를 기울여야합니다.
 +
 +모든 인터셉터 제공자를 httpInterceptorProviders 배열로 모으는 "배럴 (barrel)"파일을 작성하는 것을 고려하십시오.이 첫 번째 노드 인 NoopInterceptor부터 시작하십시오.
 +<code javascript>
 +/* "Barrel" of Http Interceptors */
 +import { HTTP_INTERCEPTORS } from '@angular/common/http';
 +
 +import { NoopInterceptor } from './noop-interceptor';
 +
 +/** Http interceptor providers in outside-in order */
 +export const httpInterceptorProviders = [
 +  { provide: HTTP_INTERCEPTORS, useClass: NoopInterceptor, multi: true },
 +];
 +</code>
 +그런 다음 가져 와서 다음과 같이 AppModule 공급자 배열에 추가합니다.
 +<code javascript>
 +providers: [
 +  httpInterceptorProviders
 +],
 +</code>
 +새로운 인터셉터를 만들 때 httpInterceptorProviders 배열에 추가하면 AppModule을 다시 방문 할 필요가 없습니다.
 +
 +> 전체 샘플 코드에는 더 많은 인터셉터가 있습니다.
 +
 +=== Interceptor order ===
 +
 +Angular는 사용자가 제공 한 순서대로 인터셉터를 적용합니다. 인터셉터 A, B, C를 제공하면 요청은 A-> B-> C로 흐르고 응답은 C-> B-> A로 흐르게됩니다.
 +
 +주문을 변경하거나 나중에 인터셉터를 제거 할 수 없습니다. 인터셉터를 동적으로 활성화 및 비활성화해야하는 경우 인터셉터 자체에 해당 기능을 구현해야합니다.
 +
 +=== HttpEvents ===
 +대부분의 HttpClient 메소드가 수행하는 것처럼 intercept() 및 handle() 메서드가 HttpResponse<any>의 관찰 가능 항목을 반환 할 것으로 예상했을 수 있습니다.
 +
 +대신 HttpEvent<any>의 관찰 가능 항목을 반환합니다.
 +
 +인터셉터는 HttpClient 메소드보다 낮은 수준에서 작동하기 때문입니다. 단일 HTTP 요청은 업로드 및 다운로드 진행 이벤트를 포함하여 여러 이벤트를 생성 할 수 있습니다. HttpResponse 클래스 자체는 실제로 이벤트 유형이며 HttpEventType.HttpResponseEvent입니다.
 +
 +많은 인터셉터는 나가는 요청에만 관심이 있으며 next.handle()에서 이벤트 스트림을 수정하지 않고 반환합니다.
 +
 +그러나 next.handle()의 응답을 검사하고 수정하는 인터셉터는 이러한 모든 이벤트를 볼 수 있습니다. 인터셉터는 그렇지 않은 다른 이유가없는 한 손대지 않은 모든 이벤트를 반환해야합니다.
 +
 +=== Immutability ===
 +
 +인터셉터는 요청과 응답을 변경시킬 수 있지만 HttpRequest 및 HttpResponse 인스턴스 속성은 읽기 전용이므로 거의 변경되지 않습니다.
 +
 +좋은 이유는 불변합니다. 앱이 성공하기 전에 요청을 여러 번 다시 시도 할 수 있습니다. 즉, 인터셉터 체인이 동일한 요청을 여러 번 다시 처리 할 수 있습니다. 인터셉터가 원래 요청 객체를 수정할 수있는 경우 재 시도 된 작업은 원본이 아닌 수정 된 요청에서 시작됩니다. Immutability는 인터셉터가 각 시도에 대해 동일한 요청을 볼 수 있도록합니다.
 +
 +TypeScript는 HttpRequest 읽기 전용 속성을 설정하지 못하게합니다.
 +<code javascript>
 +// Typescript disallows the following assignment because req.url is readonly
 +req.url = req.url.replace('http://', 'https://');
 +</code>
 +요청을 변경하려면 먼저 복제하고 next.handle()에 전달하기 전에 복제본을 수정하십시오. 이 예와 같이 요청을 단일 단계에서 복제하고 수정할 수 있습니다.
 +<code javascript>
 +// clone request and replace 'http://' with 'https://' at the same time
 +const secureReq = req.clone({
 +  url: req.url.replace('http://', 'https://')
 +});
 +// send the cloned, "secure" request to the next handler.
 +return next.handle(secureReq);
 +</code>
 +clone() 메서드의 해시 인수를 사용하면 요청의 특정 속성을 변경하면서 다른 속성을 복사 할 수 있습니다.
 +=== The request body ===
 +readonly 할당 보호는 딥 업데이트를 방지 할 수 없으며 특히 요청 본문 개체의 속성을 수정하지 못하게 할 수 없습니다.
 +<code javascript>
 +req.body.name = req.body.name.trim(); // bad idea!
 +</code>
 +
 +request body을 변경해야하는, 경우 먼저 복사 한 다음 복사본을 변경하고 요청을 clone() 한 다음 다음 예제와 같이 복제 본문을 새 본문으로 설정합니다.
 +<code javascript>
 +// copy the body and trim whitespace from the name property
 +const newBody = { ...body, name: body.name.trim() };
 +// clone request and set its body
 +const newReq = req.clone({ body: newBody });
 +// send the cloned request to the next handler.
 +return next.handle(newReq);
 +</code>
 +
 +=== Clearing the request body ===
 +
 +경우에 따라 요청 본문을 대체하지 않고 삭제해야하는 경우도 있습니다. 복제 된 요청 본문을 undefined로 설정하면 Angular는 본문을 그대로 두려고한다고 가정합니다. 그것은 당신이 원하는 것이 아닙니다. 복제 된 요청 본문을 null로 설정하면 Angular는 요청 본문을 지우려는 의도가 있음을 알고 있습니다.
 +
 +<code javascript>
 +newReq = req.clone({ ... }); // body not mentioned => preserve original body
 +newReq = req.clone({ body: undefined }); // preserve original body
 +newReq = req.clone({ body: null }); // clear the body
 +</code>
 +
 +=== Set default headers ===
 +앱은 종종 인터셉터를 사용하여 발신 요청에 기본 헤더를 설정합니다.
 +
 +샘플 앱에는 인증 토큰을 생성하는 AuthService가 있습니다. 다음은 AuthInceptceptor가 해당 서비스를 주입하여 토큰을 가져오고 해당 토큰과 함께 승인 헤더를 모든 발신 요청에 추가합니다.
 +<code javascript>
 +import { AuthService } from '../auth.service';
 +
 +@Injectable()
 +export class AuthInterceptor implements HttpInterceptor {
 +
 +  constructor(private auth: AuthService) {}
 +
 +  intercept(req: HttpRequest<any>, next: HttpHandler) {
 +    // Get the auth token from the service.
 +    const authToken = this.auth.getAuthorizationToken();
 +
 +    // Clone the request and replace the original headers with
 +    // cloned headers, updated with the authorization.
 +    const authReq = req.clone({
 +      headers: req.headers.set('Authorization', authToken)
 +    });
 +
 +    // send cloned request with header to the next handler.
 +    return next.handle(authReq);
 +  }
 +}
 +</code>
 +새 헤더를 설정하라는 요청을 복제하는 관행은 너무 일반적이어서 setHeaders 단축키가 있습니다.
 +<code javascript>
 +// Clone the request and set the new header in one step.
 +const authReq = req.clone({ setHeaders: { Authorization: authToken } });
 +</code>
 +
 +헤더를 변경하는 인터셉터는 다음과 같은 여러 가지 다른 작업에 사용할 수 있습니다.
 +
 +  - Authentication/authorization (인증/승인)
 +  - Caching behavior(캐싱동작); 예 : If-Modified-Since
 +  - XSRF 보호
 +
 +=== Logging ===
 +
 +인터셉터는 요청과 응답을 함께 처리 할 수 있기 때문에 시간과 같은 작업을 수행하고 전체 HTTP 작업을 기록 할 수 있습니다.
 +
 +다음의 LoggingInterceptor를 고려해보십시오.이 LoggingInterceptor는 요청 시간과 응답 시간을 캡처하고 MessageService가 삽입 된 경과 시간으로 결과를 기록합니다.
 +
 +<code javascript>
 +import { finalize, tap } from 'rxjs/operators';
 +import { MessageService } from '../message.service';
 +
 +@Injectable()
 +export class LoggingInterceptor implements HttpInterceptor {
 +  constructor(private messenger: MessageService) {}
 +
 +  intercept(req: HttpRequest<any>, next: HttpHandler) {
 +    const started = Date.now();
 +    let ok: string;
 +
 +    // extend server response observable with logging
 +    return next.handle(req)
 +      .pipe(
 +        tap(
 +          // Succeeds when there is a response; ignore other events
 +          event => ok = event instanceof HttpResponse ? 'succeeded' : '',
 +          // Operation failed; error is an HttpErrorResponse
 +          error => ok = 'failed'
 +        ),
 +        // Log when response observable either completes or errors
 +        finalize(() => {
 +          const elapsed = Date.now() - started;
 +          const msg = `${req.method} "${req.urlWithParams}"
 +             ${ok} in ${elapsed} ms.`;
 +          this.messenger.add(msg);
 +        })
 +      );
 +  }
 +}
 +</code>
 +RxJS 탭 운영자는 요청의 성공 또는 실패 여부를 캡처합니다. RxJS finalize 연산자는 응답이 관찰되거나 완료 될 때 호출되며 MessageService에 결과를보고합니다.
 +
 +탭하거나 파이널 라이즈하지 않으면 발신자에게 반환 된 관찰 가능한 스트림의 값을 터치하지 않습니다.
 +
 +=== Caching ===
 +
 +인터셉터는 next.handle()에 전달하지 않고 스스로 요청을 처리 할 수 있습니다.
 +
 +예를 들어 특정 요청 및 응답을 캐시하여 성능을 향상시킬 수 있습니다. 기존 데이터 서비스를 방해하지 않고 인터셉터에 캐싱을 위임 할 수 있습니다.
 +
 +CachingInterceptor는 이러한 접근 방식을 보여줍니다.
 +<code javascript>
 +@Injectable()
 +export class CachingInterceptor implements HttpInterceptor {
 +  constructor(private cache: RequestCache) {}
 +
 +  intercept(req: HttpRequest<any>, next: HttpHandler) {
 +    // continue if not cachable.
 +    if (!isCachable(req)) { return next.handle(req); }
 +
 +    const cachedResponse = this.cache.get(req);
 +    return cachedResponse ?
 +      of(cachedResponse) : sendRequest(req, next, this.cache);
 +  }
 +}
 +</code>
 +isCachable() 함수는 요청이 캐시 가능한지 여부를 결정합니다. 이 샘플에서는 npm 패키지 검색 API에 대한 GET 요청 만 캐시 할 수 있습니다.
 +
 +요청이 캐시 가능하지 않은 경우 인터셉터는 요청을 체인의 다음 핸들러로 전달하기 만합니다.
 +
 +캐시 가능한 요청이 캐시에서 발견되면 인터셉터는 캐시 된 응답과 함께 of() //observable//을 반환하고, 다음 처리기 (및 모든 다른 인터셉터를 다운 스트림)를 by-passing합니다.
 +
 +캐시 가능한 요청이 캐시에 없으면 코드는 sendRequest를 호출합니다.
 +<code javascript>
 +/**
 + * Get server response observable by sending request to `next()`.
 + * Will add the response to the cache on the way out.
 + */
 +function sendRequest(
 +  req: HttpRequest<any>,
 +  next: HttpHandler,
 +  cache: RequestCache): Observable<HttpEvent<any>> {
 +
 +  // No headers allowed in npm search request
 +  const noHeaderReq = req.clone({ headers: new HttpHeaders() });
 +
 +  return next.handle(noHeaderReq).pipe(
 +    tap(event => {
 +      // There may be other events besides the response.
 +      if (event instanceof HttpResponse) {
 +        cache.put(req, event); // Update the cache.
 +      }
 +    })
 +  );
 +}
 +</code>
 +sendRequest 함수는 npm api가 헤더를 사용하지 않으므로 헤더가없는 요청 복제를 만듭니다.
 +
 +이 요청은 next.handle()에 전달되어 궁극적으로 서버를 호출하고 서버의 응답을 반환합니다.
 +
 +sendRequest가 응답을 응용 프로그램으로 가로 채는 것을 확인하십시오. 그것은 call() 함수가 캐시에 응답을 추가하는 tap() 연산자를 통해 응답을 파이프합니다.
 +
 +원래 응답은 응용 프로그램 호출자에게 인터셉터 체인을 통해 변경되지 않은 상태로 계속 유지됩니다.
 +
 +PackageSearchService와 같은 데이터 서비스는 HttpClient 요청 중 일부가 실제로 캐시 된 응답을 반환한다는 것을 알지 못합니다.
 +=== Return a multi-valued Observable ===
 +HttpClient.get () 메서드는 일반적으로 데이터 또는 오류를 내보내는 관찰 가능 항목을 반환합니다. 어떤 사람들은 그것을 "한 번에 끝낸"관찰 가능한 것으로 묘사합니다.
 +
 +그러나 인터셉터는 이것을 한 번 이상 내보내는 관찰자로 변경할 수 있습니다.
 +
 +개정 된 버전의 CachingInterceptor는 관찰 된 결과를 캐시 된 응답을 즉시 내보내고 NPM 웹 API에 요청을 보내고 나중에 업데이트 된 검색 결과와 함께 다시 방출합니다.
 +<code javascript>
 +// cache-then-refresh
 +if (req.headers.get('x-refresh')) {
 +  const results$ = sendRequest(req, next, this.cache);
 +  return cachedResponse ?
 +    results$.pipe( startWith(cachedResponse) ) :
 +    results$;
 +}
 +// cache-or-fetch
 +return cachedResponse ?
 +  of(cachedResponse) : sendRequest(req, next, this.cache);
 +</code>
 +//cache-then-refresh// 옵션은 **사용자** 정의 x-refresh **헤더**가 있으면 트리거됩니다.
 +> PackageSearchComponent의 확인란은 PackageSearchService.search ()에 대한 인수 중 하나 인 withRefresh 플래그를 토글합니다. search () 메서드는 사용자 정의 x-refresh 헤더를 만들고 HttpClient.get ()을 호출하기 전에 요청에 추가합니다.
 +
 +수정 된 CachingInterceptor는 위에서 설명한 것과 같은 sendRequest () 메서드를 사용하여 캐시 된 값의 존재 여부와 상관없이 서버 요청을 설정합니다. 관찰 가능한 result$는 구독 할 때 요청할 것입니다.
 +
 +캐시 된 값이 없으면 인터셉터는 result$를 반환합니다.
 +
 +캐시 된 값이 있으면 코드는 캐시 된 응답을 result$로 파이프하여 두 번 내보내는 재구성된 관찰 가능 항목을 생성하고 캐시된 응답을 먼저 (그리고 바로) 응답 한 다음 나중에 서버에서 응답합니다. 구독자는 //두개//의 response 시퀀스를 봅니다.
 +
 +==== Listening to progress events ====
 +때때로 응용 프로그램은 많은 양의 데이터를 전송하며 이러한 전송에는 오랜 시간이 걸릴 수 있습니다. 파일 업로드가 전형적인 예입니다. 이러한 전송 진행 상황에 대한 피드백을 제공하여 사용자에게 더 나은 경험을 제공하십시오.
 +
 +진행 이벤트를 사용하도록 요청하려면 reportProgress 옵션을 true로 설정하여 HttpRequest의 인스턴스를 만들어 진행 이벤트를 추적 할 수 있습니다.
 +<code javascript>
 +const req = new HttpRequest('POST', '/upload/file', file, {
 +  reportProgress: true
 +});
 +</code>
 +> 모든 진행 이벤트는 변경 감지를 트리거하므로, UI의 진행 상태를 진정으로보고하려는 경우에만 진행 상태를 설정하십시오.
 +
 +> HTTP 메소드와 함께 HttpClient#request()를 사용할 때 observe: 'events'를 사용하여 구성하면 전송 진행 상황을 비롯한 모든 이벤트를 볼 수 있습니다.
 +
 +그런 다음이 요청 개체를 HttpClient.request () 메서드에 전달합니다.이 메서드는 인터셉터에서 처리 한 것과 동일한 이벤트 인 Observable of HttpEvents를 반환합니다.
 +<code javascript>
 +// The `HttpClient.request` API produces a raw event stream
 +// which includes start (sent), progress, and response events.
 +return this.http.request(req).pipe(
 +  map(event => this.getEventMessage(event, file)),
 +  tap(message => this.showProgress(message)),
 +  last(), // return last (completed) message to caller
 +  catchError(this.handleError(file))
 +);
 +</code>
 +getEventMessage 메소드는 이벤트 스트림에서 각 유형의 HttpEvent를 해석합니다.
 +<code javascript>
 +/** Return distinct message for sent, upload progress, & response events */
 +private getEventMessage(event: HttpEvent<any>, file: File) {
 +  switch (event.type) {
 +    case HttpEventType.Sent:
 +      return `Uploading file "${file.name}" of size ${file.size}.`;
 +
 +    case HttpEventType.UploadProgress:
 +      // Compute and show the % done:
 +      const percentDone = Math.round(100 * event.loaded / event.total);
 +      return `File "${file.name}" is ${percentDone}% uploaded.`;
 +
 +    case HttpEventType.Response:
 +      return `File "${file.name}" was completely uploaded!`;
 +
 +    default:
 +      return `File "${file.name}" surprising upload event: ${event.type}.`;
 +  }
 +}
 +</code>
 +> 이 가이드의 샘플 앱에는 업로드 된 파일을 허용하는 서버가 없습니다. app/http-interceptors/upload-interceptor.ts에있는 UploadInterceptor는 시뮬레이션 된 이벤트의 관찰 결과를 반환하여 업로드 요청을 가로 채고 단락시킵니다.
 +
 +===== Security: XSRF Protection =====
 +XSRF (Cross-Site Request Forgery)는 공격자가 인증 된 사용자를 속여 웹 사이트에서 무의식적으로 실행하는 공격 기법입니다. HttpClient는 XSRF 공격을 막는 데 사용되는 일반적인 메커니즘을 지원합니다. HTTP 요청을 수행 할 때 인터셉터는 기본적으로 XSRF-TOKEN에 의해 ​​쿠키에서 토큰을 읽고 HTTP 헤더 인 X-XSRF-TOKEN으로 설정합니다. 도메인에서 실행되는 코드 만 쿠키를 읽을 수 있기 때문에 백엔드는 HTTP 요청이 공격자가 아닌 클라이언트 응용 프로그램에서 온 것임을 확신 할 수 있습니다.
 +
 +기본적으로 인터셉터는 모든 변이 된 요청 (POST 등)에 대해이 쿠키를 상대 URL로 보내지 만 GET / HEAD 요청이나 절대 URL이있는 요청에는 보내지 않습니다.
 +
 +이 기능을 이용하려면 서버가 페이지로드 또는 첫 번째 GET 요청에서 XSRF-TOKEN이라는 자바 스크립트로 읽을 수있는 세션 쿠키로 토큰을 설정해야합니다. 후속 요청에서 서버는 쿠키가 X-XSRF-TOKEN HTTP 헤더와 일치하는지 확인할 수 있으므로 도메인에서 실행중인 코드 만 요청을 보낼 수 있는지 확인하십시오. 토큰은 각 사용자마다 고유해야하며 서버에서 검증 할 수 있어야합니다. 이것은 클라이언트가 자신의 토큰을 만들지 못하게합니다. 토큰을 보안 강화를 위해 소금으로 사이트의 인증 쿠키 다이제스트로 설정하십시오.
 +
 +여러 Angular 응용 프로그램이 동일한 도메인 또는 하위 도메인을 공유하는 환경에서 충돌을 방지하려면 각 응용 프로그램에 고유 한 쿠키 이름을 지정하십시오.
 +
 +> //HttpClient//는 XSRF 보호 체계의 클라이언트 절반 만 지원합니다. 백엔드 서비스는 페이지에 대한 쿠키를 설정하고 모든 적합한 요청에 헤더가 있는지 확인하도록 구성되어야합니다. 그렇지 않은 경우 Angular의 기본 보호가 비효율적입니다.
 +
 +==== Configuring custom cookie/header names ====
 +백엔드 서비스가 XSRF 토큰 쿠키 또는 헤더에 대해 다른 이름을 사용하는 경우 HttpClientXsrfModule.withOptions ()를 사용하여 기본값을 대체하십시오.
 +<code javascript>
 +imports: [
 +  HttpClientModule,
 +  HttpClientXsrfModule.withOptions({
 +    cookieName: 'My-Xsrf-Cookie',
 +    headerName: 'My-Xsrf-Header',
 +  }),
 +],
 +</code>
 +
 +===== Testing HTTP requests =====
 +외부 종속성과 마찬가지로 HTTP 백엔드가 조롱 받아 테스트가 원격 서버와의 상호 작용을 시뮬레이션 할 수 있어야합니다. @ angular / common / http / testing 라이브러리는 그러한 조롱을 쉽게 설정합니다.
 +
 +==== Mocking philosophy ====
 +Angular의 HTTP 테스트 라이브러리는 앱이 코드를 실행하고 요청을 먼저 처리하는 테스트 패턴을 위해 설계되었습니다.
 +
 +그런 다음 특정 요청이 수행되었거나 수행되지 않았으며 해당 요청에 대한 어설 션을 수행하고 마지막으로 예상되는 요청을 "플러시 (flushing)"하여 응답을 제공합니다.
 +
 +결국 테스트에서 앱이 예기치 않은 요청을하지 않았 음을 확인할 수 있습니다.
 +
 +> 라이브 코딩 환경에서 이러한 샘플 테스트 / 다운로드 예제를 실행할 수 있습니다.
 +>
 +> 이 가이드에서 설명하는 테스트는 src/testing/http-client.spec.ts에 있습니다. src/app/heroes/heroes.service.spec.ts에서 HttpClient를 호출하는 애플리케이션 데이터 서비스에 대한 테스트도 있습니다.
 +
 +==== Setup ====
 +HttpClient에 대한 호출을 테스트하려면 테스트에 필요한 다른 기호와 함께 HttpClientTestingModule 및 조롱 컨트롤러 HttpTestingController를 가져옵니다.
 +<code javascript>
 +// Http testing module and mocking controller
 +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
 +
 +// Other imports
 +import { TestBed } from '@angular/core/testing';
 +import { HttpClient, HttpErrorResponse } from '@angular/common/http';
 +</code>
 +
 +그런 다음 HttpClientTestingModule을 TestBed에 추가하고 //service-under-test//의 설정을 계속합니다.
 +<code javascript>
 +describe('HttpClient testing', () => {
 +  let httpClient: HttpClient;
 +  let httpTestingController: HttpTestingController;
 +
 +  beforeEach(() => {
 +    TestBed.configureTestingModule({
 +      imports: [ HttpClientTestingModule ]
 +    });
 +
 +    // Inject the http service and test controller for each test
 +    httpClient = TestBed.get(HttpClient);
 +    httpTestingController = TestBed.get(HttpTestingController);
 +  });
 +  /// Tests begin ///
 +});
 +</code>
 +이제 테스트 과정에서 요청이 정상 백엔드가 아닌 테스트 백엔드에 부딪칩니다.
 +
 +또한이 설정은 TestBed.get ()을 호출하여 테스트 중에 참조 될 수 있도록 HttpClient 서비스 및 조롱 컨트롤러를 주입합니다.
 +
 +==== Expecting and answering requests ====
 +이제 GET 요청이 발생할 것으로 예상되는 테스트를 작성하고 모의 응답을 제공 할 수 있습니다.
 +<code javascript>
 +it('can test HttpClient.get', () => {
 +  const testData: Data = {name: 'Test Data'};
 +
 +  // Make an HTTP GET request
 +  httpClient.get<Data>(testUrl)
 +    .subscribe(data =>
 +      // When observable resolves, result should match test data
 +      expect(data).toEqual(testData)
 +    );
 +
 +  // The following `expectOne()` will match the request's URL.
 +  // If no requests or multiple requests matched that URL
 +  // `expectOne()` would throw.
 +  const req = httpTestingController.expectOne('/data');
 +
 +  // Assert that the request is a GET.
 +  expect(req.request.method).toEqual('GET');
 +
 +  // Respond with mock data, causing Observable to resolve.
 +  // Subscribe callback asserts that correct data was returned.
 +  req.flush(testData);
 +
 +  // Finally, assert that there are no outstanding requests.
 +  httpTestingController.verify();
 +});
 +</code>
 +마지막 단계는 요청이 아직 해결되지 않았 음을 확인하는 것으로,이를 통해 afterEach () 단계로 이동할 수 있습니다.
 +<code javascript>
 +afterEach(() => {
 +  // After every test, assert that there are no more pending requests.
 +  httpTestingController.verify();
 +});
 +</code>
 +
 +=== Custom request expectations ===
 +URL로 일치하는 것만으로 충분하지 않으면 자체 매칭 기능을 구현할 수 있습니다. 예를 들어, 인증 헤더가있는 발신 요청을 찾을 수 있습니다.
 +<code javascript>
 +// Expect one request with an authorization header
 +const req = httpTestingController.expectOne(
 +  req => req.headers.has('Authorization')
 +);
 +</code>
 +이전 expectOne()과 마찬가지로 0 또는 2 개 이상의 요청이이 조건을 충족하면 테스트가 실패합니다.
 +
 +=== Handling more than one request ===
 +테스트에서 중복 요청에 응답해야하는 경우 expectOne () 대신 match () API를 사용하십시오. 동일한 인수를 사용하지만 일치하는 요청의 배열을 반환합니다. 일단 반환되면 이러한 요청은 나중에 일치하는 것으로부터 제거되며 사용자는이를 플러시하고 확인해야합니다.
 +<code javascript>
 +// get all pending requests that match the given URL
 +const requests = httpTestingController.match(testUrl);
 +expect(requests.length).toEqual(3);
 +
 +// Respond to each request with different results
 +requests[0].flush([]);
 +requests[1].flush([testData[0]]);
 +requests[2].flush(testData);
 +</code>
 +
 +==== Testing for errors ====
 +실패한 HTTP 요청에 대해 앱의 방어를 테스트해야합니다.
 +
 +다음 예제와 같이 request.flush ()를 호출하여 오류 메시지를 표시합니다.
 +<code javascript>
 +it('can test for 404 error', () => {
 +  const emsg = 'deliberate 404 error';
 +
 +  httpClient.get<Data[]>(testUrl).subscribe(
 +    data => fail('should have failed with the 404 error'),
 +    (error: HttpErrorResponse) => {
 +      expect(error.status).toEqual(404, 'status');
 +      expect(error.error).toEqual(emsg, 'message');
 +    }
 +  );
 +
 +  const req = httpTestingController.expectOne(testUrl);
 +
 +  // Respond with mock error
 +  req.flush(emsg, { status: 404, statusText: 'Not Found' });
 +});
 +</code>
 +
 +또는 ErrorEvent와 함께 request.error()를 호출 할 수 있습니다.
 +
 +<code javascript>
 +it('can test for network error', () => {
 +  const emsg = 'simulated network error';
 +
 +  httpClient.get<Data[]>(testUrl).subscribe(
 +    data => fail('should have failed with the network error'),
 +    (error: HttpErrorResponse) => {
 +      expect(error.error.message).toEqual(emsg, 'message');
 +    }
 +  );
 +
 +  const req = httpTestingController.expectOne(testUrl);
 +
 +  // Create mock ErrorEvent, raised when something goes wrong at the network level.
 +  // Connection timeout, DNS error, offline, etc
 +  const mockError = new ErrorEvent('Network error', {
 +    message: emsg,
 +  });
 +
 +  // Respond with mock error
 +  req.error(mockError);
 +});
 +</code>