Angular HttpClient

HttpClient

대부분의 프런트 엔드 응용 프로그램은 HTTP 프로토콜을 통해 백엔드 서비스와 통신합니다. 최신 브라우저는 HTTP 요청을하기 위해 XMLHttpRequest 인터페이스와 fetch() API의 두 가지 API를 지원합니다.

@angular/common/http의 HttpClient를 사용하면 브라우저에서 공개되는 XMLHttpRequest 인터페이스 위에 구축된 Angular 응용 프로그램에서 사용할 수 있도록 HTTP 기능에 대한 간단한 API를 제공합니다. HttpClient의 추가 이점으로는 테스트 가능성 지원, 요청 및 응답 객체의 강력한 입력, 요청 및 응답 인터셉터 지원, Observables를 기반으로 한 api를 통한 더 나은 오류 처리가 있습니다.

Setup: installing the module

HttpClient를 사용하려면 먼저 HttpClient를 제공하는 HttpClientModule을 설치해야합니다. 이것은 응용 프로그램 모듈에서 수행할 수 있으며 한번만 필요합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// app.module.ts:
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
// Import HttpClientModule from @angular/common/http
import {HttpClientModule} from '@angular/common/http';
@NgModule({
imports: [
BrowserModule,
// Include it under 'imports' in your application module
// after BrowserModule.
HttpClientModule,
],
})
export class MyAppModule {}

HttpClientModule을 앱 모듈로 import하면 HttpClient를 ComponentService에 삽입할 수 있습니다.

Making a request for JSON data

백엔드에서 가장 많이 요구되는 요청 애플리케이션은 JSON 데이터를 요청하는 것이다. 예를 들어 다음과 같은 형식의 JSON 객체를 반환하는 /api/items 항목을 나열하는 API가 있다고 가정합니다.

1
2
3
4
5
6
{
"results": [
"Item 1",
"Item 2",
]
}

HttpClient에서 get() 메서드를 사용하면 이 데이터에 간단하게 액세스할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component(...)
export class MyComponent implements OnInit {
results: string[];
// Inject HttpClient into your component or service.
constructor(private http: HttpClient) {}
ngOnInit(): void {
// Make the HTTP request:
this.http.get('/api/items').subscribe(data => {
// Read the result field from the JSON response.
this.results = data['results'];
});
}
}

Typechecking the response

위의 예에서 괄호를 사용하여 결과 필드에 액세스하기 때문에 data['results'] 필드 액세스가 두드러집니다. data.results를 작성하려고 시도하면 TypeScript는 HTTP에서 돌아 오는 Objectresults 속성이 없음을 올바르게 알립니다. HttpClient가 JSON 응답을 Object로 파싱하는 동안 그 객체가 어떤 모양인지 모르기 때문입니다.

그러나 HttpClient에 응답 유형이 무엇인지 알려 줄 수 있습니다. 이는 권장 사항입니다. 이렇게하려면 먼저 올바른 모양으로 Interface를 정의하십시오.

1
2
3
interface ItemsResponse {
results: string[];
}

그런 다음 HttpClient.get 호출할 때 타입 파라미터를 전달하십시오.

1
2
3
4
http.get<ItemsResponse>('/api/items').subscribe(data => {
// data is now an instance of type ItemsResponse, so you can do this:
this.results = data.results;
});

Reading the full response

응답 본문은 필요한 모든 데이터를 반환하지 않습니다. 경우에 따라 서버는 특정 조건을 나타내는 특수 헤더나 상태 코드를 반환하고 필요할 때 검사할 수 있어야 합니다. 이렇게 하려면 observe 옵션을 이용하여 Body 대신 전체 응답을 원한다고 HttpClient에 알릴 수 있습니다.

1
2
3
4
5
6
7
8
9
http
.get<MyJsonData>('/data.json', {observe: 'response'})
.subscribe(resp => {
// Here, resp is of type HttpResponse<MyJsonData>.
// You can inspect its headers:
console.log(resp.headers.get('X-Custom-Header'));
// And access the body directly, which is typed as MyJsonData as requested.
console.log(resp.body.someField);
});

보시다시피 결과 객체에는 올바른 유형의 body 프로퍼티가 있습니다.

Error handling

서버에서 요청이 실패하거나 네트워크 연결이 좋지 않아 서버에 도달하지 못하면 어떻게될까요? HttpClient는 성공적인 응답 대신 오류를 반환합니다.

이를 처리하려면 .subscribe() 호출에 오류 핸들러를 추가합니다.

1
2
3
4
5
6
7
8
9
10
http
.get<ItemsResponse>('/api/items')
.subscribe(
// Successful responses call the first callback.
data => {...},
// Errors will call this callback instead:
err => {
console.log('Something went wrong!');
}
});

Getting error details

오류가 발생했음을 감지하는 것이 중요하지만 실제로 발생한 오류를 확인하는 것이 더 유용할 수 있습니다. 위의 콜백에 대한 err 파라미터는 HttpErrorResponse 타입이고, 무엇이 잘못되었는지에 대한 유용한 정보를 담고 있습니다.

발생할 수 있는 두 가지 유형의 오류가 있습니다. 백엔드가 실패한 응답 코드(404, 500 등)를 반환하면 오류로 반환됩니다. 또한 RxJS 연산자에서 예외가 발생하거나 네트워크 오류로 인해 요청이 성공적으로 완료되지 못하는 경우등 클라이언트 측에서 잘못된 것이 발생하면 실제 Error가 발생됩니다.

두 경우 모두 HttpErrorResponse를 보고 무슨 일이 일어 났는지 확인할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
http
.get<ItemsResponse>('/api/items')
.subscribe(
data => {...},
(err: HttpErrorResponse) => {
if (err.error instanceof Error) {
// A client-side or network error occurred. Handle it accordingly.
console.log('An error occurred:', err.error.message);
} else {
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong,
console.log(`Backend returned code ${err.status}, body was: ${err.error}`);
}
}
});

.retry()

오류를 처리하는 한 가지 방법은 단순히 요청을 다시 시도하는 것입니다. 이 전략은 오류가 일시적이고 반복 가능성이 없는 경우에 유용할 수 있습니다.

RxJS는 Observable에 자동으로 다시 Subscribe하여, 오류가 발생하면 요청을 다시 발생시키는 .retry()라는 유용한 연산자가 있습니다.

먼저 import 하기 :

1
import 'rxjs/add/operator/retry';

그러면 다음과 같이 HTTP Observable과 함께 사용할 수 있습니다.

1
2
3
4
5
6
http
.get<ItemsResponse>('/api/items')
// Retry this request up to 3 times.
.retry(3)
// Any errors after the 3rd retry will fall through to the app.
.subscribe(...);

Requesting non-JSON data

모든 API가 JSON 데이터를 반환하지는 않습니다. 서버에서 텍스트 파일을 읽으려 한다고 가정하십시오. 텍스트 응답을 기대한다고 HttpClient에 알려야합니다.

1
2
3
4
5
6
http
.get('/textfile.txt', {responseType: 'text'})
// 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().
.subscribe(data => console.log(data));

Sending data to the server

HttpClient는 서버에서 데이터를 가져 오는것 외에도 여러 가지 형태로 서버에 데이터를 전송하는 요청을 지원합니다.

Making a POST request

하나의 공통된 작업은 예를 들어 Form을 제출할 때 서버에 데이터를 POST하는 것입니다. POST 요청을 보내는 코드는 GET 코드와 매우 유사합니다.

1
2
3
4
5
6
const body = {name: 'Brad'};
http
.post('/api/developers/add', body)
// See below - subscribe() is still necessary when using post().
.subscribe(...);

subscribe() 메서드에 유의하십시오. HttpClient에서 반환된 모든 Observable은 콜드(cold)입니다. 즉 요청을 하기위한 blueprint입니다. subscribe()를 호출할 때까지 아무 일도 일어나지 않으며 모든 호출이 별도의 요청을 합니다. 예를 들어, 아래 코드는 동일한 데이터가 있는 POST 요청을 두 번 보냅니다.

1
2
3
4
5
6
const req = http.post('/api/items/add', body);
// 0 requests made - .subscribe() not called.
req.subscribe();
// 1 request made.
req.subscribe();
// 2 requests made.

Configuring other parts of the request

URL과 요청 가능한 본문 외에도 구성하려는 요청의 다른 측면이 있습니다. 이 모든 것은 요청에 전달하는 옵션 객체를 통해 사용할 수 있습니다.

Headers

일반적인 작업중 하나는 나가는 요청에 Authorization 헤더를 추가하는 것입니다. 방법은 다음과 같습니다.

1
2
3
4
5
http
.post('/api/items/add', body, {
headers: new HttpHeaders().set('Authorization', 'my-auth-token'),
})
.subscribe();

HttpHeaders 클래스는 변경 불가능하므로 모든 set()은 새로운 인스턴스를 반환하고 변경 사항을 적용합니다.

URL Parameters

URL 파라미터를 추가하는 방법도 동일합니다. id 파라미터가 3으로 설정된 요청을 보내려면 다음을 수행합니다.

1
2
3
4
5
http
.post('/api/items/add', body, {
params: new HttpParams().set('id', '3'),
})
.subscribe();

이 방법으로 POST 요청을 /api/items/add?id=3 URL로 보냅니다.

Advanced usage

위의 섹션에서는 @angular/common/http의 기본 HTTP 기능을 사용하는 방법을 자세히 설명했지만 때로는 요청을 만들고 데이터를 다시 가져 오는것 이상을 수행해야합니다.

Intercepting all requests or responses

@angular/common/http의 주요 기능은 응용 프로그램과 백엔드 사이에 있는 인터셉터를 선언할 수있는 Interception 기능입니다. 응용 프로그램이 요청을 하면 인터셉터는 서버로 보내기 전에 이를 변환하고 인터셉터는 응용 프로그램이 보기 전에 응답을 변환합니다. 이것은 인증에서 로깅에 이르기까지 모든 경우에 유용합니다.

Writing an interceptor

인터셉터를 구현하려면 intercept() 메소드 하나가 있는 HttpInterceptor Interface를 구현하는 클래스를 선언해야 합니다. 다음은 아무것도 하지 않고 요청을 변경하지 않고 그대로 전달하는 간단한 인터셉터입니다.

1
2
3
4
5
6
7
8
9
import {Injectable} from '@angular/core';
import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest} from '@angular/common/http';
@Injectable()
export class NoopInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req);
}
}

intercept는 요청을 Observable로 변환하여 response를 반환하는 메소드입니다. 이러한 의미에서 각 인터셉터는 전적으로 자체적으로 요청을 처리해야합니다.

대부분의 경우, 인터셉터는 요청에 약간의 변경을 가하여 체인의 나머지 부분으로 전달합니다. next 파라미터가 오는 곳입니다. next
intercept와 비슷한 인터페이스인 HttpHandler입니다. 요청 응답을 위해 Observable로 변환합니다. 인터셉터에서 next는 항상 체인의 다음 인터셉터를 나타내며, 더 이상 인터셉터가 없는 경우 최종 백엔드를 나타냅니다. 그래서 대부분의 인터셉터들은 그들이 요청을 변형시키고 next를 호출함으로써 끝납니다.

우리의 do-nothing 핸들러는 단순히 원래 요청에서 next.handle을 호출하여 요청을 전혀 변경하지 않고 전달합니다.

이 패턴은 Express.js와 같은 미들웨어 프레임 워크의 패턴과 유사합니다.

Providing your interceptor

단순히 위의 NoopInterceptor를 선언한다고 해서 인터셉터를 앱에서 사용하지는 않습니다. 다음과 같이 인터셉터를 제공하여 앱 모듈에 연결해야합니다.

1
2
3
4
5
6
7
8
9
10
11
import {NgModule} from '@angular/core';
import {HTTP_INTERCEPTORS} from '@angular/common/http';
@NgModule({
providers: [{
provide: HTTP_INTERCEPTORS,
useClass: NoopInterceptor,
multi: true,
}],
})
export class AppModule {}

multi: true 옵션에 유의하십시오. 이것은 필수이며 Angular에 HTTP_INTERCEPTORS가 단일 값이 아닌 값의 배열임을 알려줍니다.

Events

interceptHttpHandler.handle에 의해 반환된 Observable이 Observable<HttpResponse<any >>가 아니라 Observable <HttpEvent<any>>라는 것을 알았을 수도 있습니다. 이는 인터셉터가 HttpClient 인터페이스보다 낮은 수준에서 작동하기 때문입니다. 단일 요청은 업로드 및 다운로드 진행 이벤트를 포함하여 여러 이벤트를 생성할 수 있습니다. HttpResponse 클래스는 실제로는 HttpEventType.HttpResponseEvent 타입의 이벤트 자체입니다.

인터셉터는 이해하지 못하거나 수정하려는 모든 이벤트를 통과 시켜야합니다. 처리할 것으로 예상되지 않은 이벤트를 필터링해서는 안됩니다. 많은 인터셉터는 나가는 요청에 대해서만 관심이 있으며, 단순히 next에서 이벤트 스트림을 수정하지 않고 반환합니다.

Ordering

응용 프로그램에서 여러 개의 인터셉터를 제공하면 Angular는 사용자가 제공한 순서대로 적용합니다.

Immutability

인터셉터는 나가는 요청과 들어오는 응답을 검사하고 변경합니다. 그러나 HttpRequestHttpResponse 클래스가 대부분 변경 불가능하다는 사실을 알면 놀랄 수 있습니다.

이유는 앱이 요청을 다시 시도할 수 있고 인터셉터 체인이 개별 요청을 여러번 처리할 수 있기 때문입니다. 요청이 변경 가능하면 다시 시도한 요청은 원래 요청과 다를 수 있습니다. Immutability는 인터셉터가 각 시도에 대해 동일한 요청을 볼 수 있도록합니다.

인터셉터(request body)를 작성할 때 타입 안전성으로 보호할 수없는 경우가 있습니다. 인터셉터 내에서 요청 본문을 변경하는 것은 유효하지 않고, 타입 시스템에 의해 검증되지 않습니다.

요청 본문을 변경해야하는 경우 본문을 copy하고 copy본을 변경한 다음 clone()을 사용하여 요청을 복사하고 새로운 본문을 설정해야합니다.

요청은 변경 불가능하기 때문에 직접 수정할 수 없습니다. 이들을 변경 시키려면 clone()을 사용하십시오.

1
2
3
4
5
6
7
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpError<any>> {
// This is a duplicate. It is exactly the same as the original.
const dupReq = req.clone();
// Change the URL and replace 'http://' with 'https://'
const secureReq = req.clone({url: req.url.replace('http://', 'https://')});
}

보시다시피,clone()을 사용하면 요청의 특정 프로퍼티를 변경하면서 다른 내용을 복사할 수 있습니다.

Setting new headers

인터셉터의 일반적인 사용은 나가는 요청에 기본 헤더를 설정하는 것입니다. 예를 들어, 인증 토큰을 제공할 수있는 AuthService가 있다고 가정하면 다음과 같이 모든 나가는 요청에 인터셉터를 추가하는 방법이 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import {Injectable} from '@angular/core';
import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest} from '@angular/common/http';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private auth: AuthService) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// Get the auth header from the service.
const authHeader = this.auth.getAuthorizationHeader();
// Clone the request to add the new header.
const authReq = req.clone({headers: req.headers.set('Authorization', authHeader)});
// Pass on the cloned request instead of the original request.
return next.handle(authReq);
}
}

새로운 헤더를 설정하기위해 요청을 복제하는 관행은 너무 일반적이어서 실제적인 단축키가 있습니다.

1
const authReq = req.clone({setHeaders: {Authorization: authHeader}});

헤더를 변경하는 인터셉터는 다음을 포함하여 여러 가지 다른 작업에 사용할 수 있습니다.

  • Authentication/authorization
  • Caching behavior; for example, If-Modified-Since
  • XSRF protection

Logging

인터셉터는 요청과 응답을 함께 처리할 수 있기 때문에 로그 또는 요청 소요 시간 확인과 같은 작업을 수행할 수 있습니다. console.log를 사용하여 각 요청의 소요 시간을 보여주는 이 인터셉터를 생각해볼 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import 'rxjs/add/operator/do';
export class TimingInterceptor implements HttpInterceptor {
constructor(private auth: AuthService) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const started = Date.now();
return next
.handle(req)
.do(event => {
if (event instanceof HttpResponse) {
const elapsed = Date.now() - started;
console.log(`Request for ${req.urlWithParams} took ${elapsed} ms.`);
}
});
}
}

RxJS do() 연산자는 스트림의 값에 영향을 주지 않으면서 Observable에 추가작업을 추가합니다. 여기서는 HttpResponse 이벤트를 감지하고 요청이 발생한 시간을 기록합니다.

Caching

인터셉터를 사용하여 캐싱을 구현할 수도 있습니다. 이 예제에서는 간단한 인터페이스로 HTTP 캐시를 작성했다고 가정합니다.

1
2
3
4
5
6
7
8
9
10
11
abstract class HttpCache {
/**
* Returns a cached response, if any, or null if not present.
*/
abstract get(req: HttpRequest<any>): HttpResponse<any>|null;
/**
* Adds or updates the response in the cache.
*/
abstract put(req: HttpRequest<any>, resp: HttpResponse<any>): void;
}

인터셉터는 나가는 요청에 이 캐시를 적용할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Injectable()
export class CachingInterceptor implements HttpInterceptor {
constructor(private cache: HttpCache) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// Before doing anything, it's important to only cache GET requests.
// Skip this interceptor if the request method isn't GET.
if (req.method !== 'GET') {
return next.handle(req);
}
// First, check the cache to see if this request exists.
const cachedResponse = this.cache.get(req);
if (cachedResponse) {
// A cached response exists. Serve it instead of forwarding
// the request to the next handler.
return Observable.of(cachedResponse);
}
// No cached response exists. Go to the network, and cache
// the response when it arrives.
return next.handle(req).do(event => {
// Remember, there may be other events besides just the response.
if (event instanceof HttpResponse) {
// Update the cache.
this.cache.put(req, event);
}
});
}
}

이 예제는 요청의 일치, 캐시 무효화 등을 설명하지만 인터셉터는 요청을 변형하는것 이상의 많은 힘을 가지고 있음을 쉽게 볼 수 있습니다. 원하는 경우 요청 흐름을 완전히 변경하는데 사용할 수 있습니다.

실제로 위의 예제를 변경하여 캐시에 요청이있는 경우 두 개의 응답 이벤트 (캐시된 응답 먼저)와 업데이트된 네트워크 응답을 나중에 반환할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// Still skip non-GET requests.
if (req.method !== 'GET') {
return next.handle(req);
}
// This will be an Observable of the cached value if there is one,
// or an empty Observable otherwise. It starts out empty.
let maybeCachedResponse: Observable<HttpEvent<any>> = Observable.empty();
// Check the cache.
const cachedResponse = this.cache.get(req);
if (cachedResponse) {
maybeCachedResponse = Observable.of(cachedResponse);
}
// Create an Observable (but don't subscribe) that represents making
// the network request and caching the value.
const networkResponse = next.handle(req).do(event => {
// Just like before, check for the HttpResponse event and cache it.
if (event instanceof HttpResponse) {
this.cache.put(req, event);
}
});
// Now, combine the two and send the cached response first (if there is
// one), and the network response second.
return Observable.concat(maybeCachedResponse, networkResponse);
}

이제 http.get(url)을 수행하면 이전에 해당 URL이 캐시된 경우 두 개의 응답을 받게됩니다.

Listening to progress events

때때로 응용 프로그램은 많은 양의 데이터를 전송 해야하며 이러한 전송에는 시간이 걸릴 수 있습니다. 이러한 전송 진행 상황에 대한 피드백을 제공하는 것은 좋은 사용자 경험입니다. (예를 들어, 파일 업로드 - @angular/common/http는 이를 지원합니다.)

진행 이벤트를 사용하도록 요청하려면 먼저 특수한 reportProgress 옵션 집합을 사용하여 HttpRequest의 인스턴스를 만듭니다.

1
2
3
const req = new HttpRequest('POST', '/upload/file', file, {
reportProgress: true,
});

이 옵션을 사용하면 진행 이벤트를 추적할 수 있습니다. 각 진행 상황 이벤트는 변경 감지를 트리거하므로 실제로 각 이벤트에서 UI를 업데이트하려는 경우에만 진행 상태를 설정하십시오.

그런 다음 HttpClientrequest() 메소드를 통해 요청하십시오. 결과는 인터셉터와 마찬가지로 Observable 이벤트가 됩니다.

1
2
3
4
5
6
7
8
9
10
11
http.request(req).subscribe(event => {
// Via this API, you get access to the raw event stream.
// Look for upload progress events.
if (event.type === HttpEventType.UploadProgress) {
// This is an upload progress event. Compute and show the % done:
const percentDone = Math.round(100 * event.loaded / event.total);
console.log(`File is ${percentDone}% uploaded.`);
} else if (event instanceof HttpResponse) {
console.log('File is completely uploaded!');
}
});

Security: XSRF Protection

Cross-Site Request Forgery(CSRF)는 공격자가 인증된 사용자를 속여서 웹 사이트에서 무의식적으로 실행하는 공격 기법입니다. HttpClientXSRF 공격을 막는데 사용되는 일반적인 메커니즘을 지원합니다. HTTP 요청을 수행할 때 인터셉터는 쿠키에서 토큰 (기본적으로 XSRF-TOKEN)을 읽고 HTTP 헤더인 X-XSRF-TOKEN으로 설정합니다. 도메인에서 실행되는 코드만 쿠키를 읽을 수 있기 때문에 백엔드는 HTTP 요청이 공격자가 아닌 클라이언트 응용 프로그램에서 온 것임을 확신할 수 있습니다.

기본적으로 인터셉터는 모든 변경 요청 (POST 등)에 대해 이 쿠키를 상대 URL로 보내지만 GET/HEAD 요청이나 절대 URL이 있는 요청에는 보내지 않습니다.

이 기능을 이용하려면 서버가 페이지 로드 또는 첫 번째 GET 요청에서 XSRF-TOKEN이라는 JavaScript로 읽을 수있는 세션 쿠키로 토큰을 설정해야합니다. 후속 요청에서 서버는 쿠키가 X-XSRF-TOKEN HTTP 헤더와 일치하는지 확인할 수 있으므로 도메인에서 실행중인 코드만 요청을 보낼 수 있는지 확인합니다. 토큰은 각 사용자마다 고유해야하며 서버에서 확인할 수 있어야합니다. 이것은 클라이언트가 자신의 토큰을 만들지 못하게합니다. 토큰의 보안 강화를 위해 salt로 사이트 인증 쿠키 다이제스트로 설정하십시오.

여러 Angular 응용 프로그램이 동일한 도메인 또는 하위 도메인을 공유하는 환경에서 충돌을 방지하려면 각 응용 프로그램에 고유한 쿠키 이름을 지정하십시오.

HttpClient의 지원은 XSRF 보호 체계의 클라이언트 절반에 불과합니다. 백엔드 서비스는 페이지의 쿠키를 설정하고 헤더가 모든 적합한 요청에 존재하는지 확인하도록 구성되어야합니다. 그렇지 않은 경우 Angular의 기본 보호가 효과적이지 않습니다.

백엔드 서비스가 XSRF 토큰 쿠키 또는 헤더에 다른 이름을 사용하는 경우 HttpClientXsrfModule.withConfig()를 사용하여 기본값을 대체하십시오.

1
2
3
4
5
6
7
imports: [
HttpClientModule,
HttpClientXsrfModule.withConfig({
cookieName: 'My-Xsrf-Cookie',
headerName: 'My-Xsrf-Header',
}),
]

Testing HTTP requests

외부 의존성과 마찬가지로, HTTP 백엔드는 좋은 테스트 관행의 일부로 Mock를 제공 해야합니다. @angular/common/http는 Mocking을 직접 설정하는 테스트 라이브러리 @angular/common/http/testing을 제공합니다.

Mocking philosophy

Angular의 HTTP 테스트 라이브러리는 앱이 코드를 실행하고 요청을 수행하는 테스트 패턴을 위해 설계되었습니다. 그 후, 테스트는 특정 요청이 있었는지 여부를 예상하고, 각 요청을 “플러시”하여 최종적으로 응답을 제공함으로써 더 많은 새로운 요청을 트리거할 수 있습니다. 결국 테스트는 앱이 예기치 않은 요청을 하지 않았음을 선택적으로 확인할 수 있습니다.

Setup

HttpClient를 통해 만들어진 요청을 테스트하려면 HttpClientTestingModule을 가져 와서 TestBed 설정에 추가하십시오.

1
2
3
4
5
6
7
8
9
10
import {HttpClientTestingModule} from '@angular/common/http/testing';
beforeEach(() => {
TestBed.configureTestingModule({
...,
imports: [
HttpClientTestingModule,
],
})
});

이게 전부입니다. 이제 테스트 과정의 요청은 정상 백엔드가 아닌 테스트 백엔드를 사용합니다.

Expecting and answering requests

모듈을 통해 Mock이 설치된 상태에서 GET 요청이 발생할 것으로 예상되는 테스트를 작성하여 Mock 응답을 제공할 수 있습니다. 다음 예제는 HttpClient를 테스트와 HttpTestingController라는 클래스에 모두 주입하여 수행합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
it('expects a GET request', inject([HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => {
// Make an HTTP GET request, and expect that it return an object
// of the form {name: 'Test Data'}.
http
.get('/data')
.subscribe(data => expect(data['name']).toEqual('Test Data'));
// At this point, the request is pending, and no response has been
// sent. The next step is to expect that the request happened.
const req = httpMock.expectOne('/data');
// If no request with that URL was made, or if multiple requests match,
// expectOne() would throw. However this test makes only one request to
// this URL, so it will match and return a mock request. The mock request
// can be used to deliver a response or make assertions against the
// request. In this case, the test asserts that the request is a GET.
expect(req.request.method).toEqual('GET');
// Next, fulfill the request by transmitting a response.
req.flush({name: 'Test Data'});
// Finally, assert that there are no outstanding requests.
httpMock.verify();
}));

마지막 단계는 아직 해결되지 않은 요청이 남아 있지 않음을 확인하는 것입니다. 이것은 afterEach()로 충분합니다.

1
2
3
afterEach(inject([HttpTestingController], (httpMock: HttpTestingController) => {
httpMock.verify();
}));

Custom request expectations

URL 일치로 충분하지 않으면 자체적으로 일치 기능을 구현할 수 있습니다. 예를 들어 Authorization 헤더가있는 발신 요청을 찾을 수 있습니다.

1
const req = httpMock.expectOne((req) => req.headers.has('Authorization'));

위의 테스트에서 URL 별 expectOne()과 마찬가지로 0 또는 2개 이상의 요청이 이 예측과 일치하면 throw됩니다.

Handling more than one request

테스트에서 중복 요청에 응답해야하는 경우 expectOne() 대신 match() API를 사용할 수 있습니다. 이 메서드는 동일한 파라미터를 사용하지만 일치하는 요청의 배열을 반환합니다. 반환된 이러한 요청은 향후 검색에서 제거되며 확인하고 플러시할 책임이 있습니다.

1
2
3
4
// Expect that 5 pings have been made and flush them.
const reqs = httpMock.match('/ping');
expect(reqs.length).toBe(5);
reqs.forEach(req => req.flush());

이 내용은 나중에 참고하기 위해 제가 공부하며 정리한 내용입니다.
의역, 오역, 직역이 있을 수 있음을 알려드립니다.
This post is a translation of this original article [https://angular.io/guide/http]

참고

공유하기