심화 가이드
HTTP 클라이언트

HTTP 요청 보내기

HttpClient는 데이터를 불러오거나 서버 상태를 변경하는 데 사용하는 각 HTTP 메서드에 대응하는 메서드를 제공합니다. 각 메서드는 RxJS Observable을 반환합니다. 이 옵저버블을 구독하면 요청을 보내고, 서버가 응답하면 결과를 전달합니다.

참고: HttpClient가 생성한 Observable은 여러 번 구독할 수 있으며, 구독할 때마다 백엔드로 새 요청을 보냅니다.

요청 메서드에 전달하는 옵션 객체로 요청의 여러 프로퍼티와 반환할 응답 타입을 조정할 수 있습니다.

JSON 데이터 가져오기

백엔드에서 데이터를 가져올 때는 HttpClient.get() 메서드로 GET 요청을 보내는 경우가 많습니다. 이 메서드는 데이터를 가져올 엔드포인트 URL 문자열과 요청을 설정하는 선택적 옵션 객체를 인자로 받습니다.

예를 들어 HttpClient.get()으로 가상의 API에서 설정 데이터를 가져오려면 다음과 같이 작성합니다:

http.get<Config>('/api/config').subscribe((config) => {
  // 설정을 처리합니다.
});

서버가 반환할 데이터의 타입을 Config로 지정하는 제네릭 타입 인자를 확인해 보세요. 이 인자는 선택 사항이며, 생략하면 반환 데이터의 타입은 Object가 됩니다.

팁: 데이터 구조가 확실하지 않고 값이 undefined 또는 null일 가능성이 있다면, 응답 타입으로 Object 대신 unknown을 사용하는 것도 고려해 보세요.

중요: 요청 메서드의 제네릭 타입은 서버가 반환하는 데이터에 대한 타입 단언입니다. HttpClient는 실제 반환 데이터가 지정한 타입과 일치하는지 검증하지 않습니다.

다른 형식의 데이터 가져오기

HttpClient는 기본적으로 서버가 JSON 데이터를 반환한다고 가정합니다. JSON이 아닌 API와 통신할 때는 요청을 보내면서 responseType 옵션으로 예상 응답 형식을 지정할 수 있습니다.

responseType 값 반환하는 응답 타입
'json'(기본값) 지정한 제네릭 타입의 JSON 데이터
'text' 문자열 데이터
'arraybuffer' 응답의 원시 바이트가 담긴 ArrayBuffer
'blob' Blob 인스턴스

예를 들어 HttpClient로 .jpeg 이미지의 원시 바이트를 ArrayBuffer에 내려받을 수 있습니다:

http.get('/images/dog.jpg', {responseType: 'arraybuffer'}).subscribe((buffer) => {
  console.log('The image is ' + buffer.byteLength + ' bytes large');
});

responseType에는 리터럴 값을 사용하세요

responseType 값은 HttpClient의 반환 타입에 영향을 미치므로, string 타입이 아니라 리터럴 타입이어야 합니다.

요청 메서드에 객체 리터럴로 옵션을 전달하면 자동으로 처리됩니다. 하지만 요청 옵션을 별도 변수나 헬퍼 메서드로 분리한다면 responseType: 'text' as const처럼 리터럴 타입을 명시해야 할 수 있습니다.

서버 상태 변경하기

서버 상태를 변경하는 API는 새로운 상태나 적용할 변경 사항을 요청 본문에 담아 POST 요청으로 보내야 하는 경우가 많습니다.

HttpClient.post() 메서드는 get()과 비슷하게 동작하며, 옵션 앞에 추가로 body 인자를 받습니다:

http.post<Config>('/api/config', newConfig).subscribe((config) => {
  console.log('Updated config:', config);
});

요청의 body에는 여러 타입의 값을 전달할 수 있으며, HttpClient가 타입에 맞춰 직렬화합니다:

body 타입 직렬화 형식
문자열 일반 텍스트
숫자, 불리언, 배열, 일반 객체 JSON
ArrayBuffer 버퍼의 원시 데이터
Blob Blob의 콘텐츠 타입을 사용하는 원시 데이터
FormData multipart/form-data로 인코딩한 데이터
HttpParams 또는 URLSearchParams application/x-www-form-urlencoded 형식의 문자열

중요: 변경 요청을 실제로 보내려면 요청 Observable에서 .subscribe()를 실행해야 한다는 점을 기억하세요.

URL 파라미터 설정하기

요청 URL에 포함할 파라미터는 params 옵션으로 지정합니다.

객체 리터럴을 전달하는 것이 가장 간단한 방법입니다:

http
  .get('/api/config', {
    params: {filter: 'all'},
  })
  .subscribe((config) => {
    // ...
  });

파라미터를 구성하거나 직렬화하는 방식을 더 세밀하게 제어해야 한다면 HttpParams 인스턴스를 전달할 수도 있습니다.

중요: HttpParams 인스턴스는 불변 이므로 직접 변경할 수 없습니다. 대신 append() 같은 변경 메서드가 변경 사항을 적용한 새 HttpParams 인스턴스를 반환합니다.

const baseParams = new HttpParams().set('filter', 'all');

http
  .get('/api/config', {
    params: baseParams.set('details', 'enabled'),
  })
  .subscribe((config) => {
    // ...
  });

커스텀 HttpParameterCodec을 지정해서 HttpParams 인스턴스를 생성하면 HttpClient가 URL 파라미터를 인코딩하는 방식을 정할 수 있습니다.

파라미터 인코딩 커스터마이징하기

HttpParams는 기본적으로 내장 HttpUrlEncodingCodec으로 파라미터의 키와 값을 인코딩하고 디코딩합니다.

HttpParameterCodec을 직접 구현해서 인코딩과 디코딩 방식을 커스터마이징할 수 있습니다.

import {HttpClient, HttpParams, HttpParameterCodec} from '@angular/common/http';
import {inject} from '@angular/core';

export class CustomHttpParamEncoder implements HttpParameterCodec {
  encodeKey(key: string): string {
    return encodeURIComponent(key);
  }

  encodeValue(value: string): string {
    return encodeURIComponent(value);
  }

  decodeKey(key: string): string {
    return decodeURIComponent(key);
  }

  decodeValue(value: string): string {
    return decodeURIComponent(value);
  }
}

export class ApiService {
  private http = inject(HttpClient);

  search() {
    const params = new HttpParams({
      encoder: new CustomHttpParamEncoder(),
    })
      .set('email', 'dev+alerts@example.com')
      .set('q', 'a & b? c/d = e');

    return this.http.get('/api/items', {params});
  }
}

요청 헤더 설정하기

요청에 포함할 헤더는 headers 옵션으로 지정합니다.

객체 리터럴을 전달하는 것이 가장 간단한 방법입니다:

http
  .get('/api/config', {
    headers: {
      'X-Debug-Level': 'verbose',
    },
  })
  .subscribe((config) => {
    // ...
  });

헤더를 구성하는 방식을 더 세밀하게 제어해야 한다면 HttpHeaders 인스턴스를 전달할 수도 있습니다.

중요: HttpHeaders 인스턴스는 불변 이므로 직접 변경할 수 없습니다. 대신 append() 같은 변경 메서드가 변경 사항을 적용한 새 HttpHeaders 인스턴스를 반환합니다.

const baseHeaders = new HttpHeaders().set('X-Debug-Level', 'minimal');

http
  .get<Config>('/api/config', {
    headers: baseHeaders.set('X-Debug-Level', 'verbose'),
  })
  .subscribe((config) => {
    // ...
  });

서버 응답 이벤트 다루기

HttpClient는 편의를 위해 기본적으로 서버가 반환한 데이터, 즉 응답 본문의 Observable을 반환합니다. 하지만 특정 응답 헤더를 가져오는 경우처럼 실제 응답 객체를 확인해야 할 때도 있습니다.

전체 응답에 접근하려면 observe 옵션을 'response'로 설정합니다:

http.get<Config>('/api/config', {observe: 'response'}).subscribe((res) => {
  console.log('Response status:', res.status);
  console.log('Body:', res.body);
});

observe에는 리터럴 값을 사용하세요

observe 값은 HttpClient의 반환 타입에 영향을 미치므로, string 타입이 아니라 리터럴 타입이어야 합니다.

요청 메서드에 객체 리터럴로 옵션을 전달하면 자동으로 처리됩니다. 하지만 요청 옵션을 별도 변수나 헬퍼 메서드로 분리한다면 observe: 'response' as const처럼 리터럴 타입을 명시해야 할 수 있습니다.

진행 상황 이벤트 직접 받기

HttpClient는 응답 본문이나 응답 객체뿐 아니라 요청 라이프사이클의 특정 시점에 해당하는 원시 이벤트 스트림도 반환할 수 있습니다. 요청을 보낸 시점, 응답 헤더를 받은 시점, 본문을 모두 받은 시점 등이 여기에 포함됩니다. 큰 요청 본문이나 응답 본문의 업로드 및 다운로드 상태를 알려주는 진행 상황 이벤트 도 포함할 수 있습니다.

진행 상황 이벤트는 성능 비용이 있어 기본적으로 비활성화되어 있지만, reportUploadProgress와 reportDownloadProgress 옵션으로 활성화할 수 있습니다.

참고: HttpClient의 기본 fetch 백엔드는 업로드 진행 상황 이벤트를 지원하지 않으며, reportUploadProgress를 설정하면 오류가 발생합니다. 업로드 진행 상황 이벤트가 필요하다면 provideHttpClient(...)에 withXhr()를 지정해서 HttpClient를 설정하세요.

이벤트 스트림을 관찰하려면 observe 옵션을 'events'로 설정합니다:

http
  .get('/api/download', {
    reportDownloadProgress: true,
    observe: 'events',
  })
  .subscribe((event) => {
    switch (event.type) {
      case HttpEventType.DownloadProgress:
        console.log('Downloaded ' + event.loaded + ' out of ' + event.total + ' bytes');
        break;
      case HttpEventType.Response:
        console.log('Finished downloading!');
        break;
    }
  });

observe에는 리터럴 값을 사용하세요

observe 값은 HttpClient의 반환 타입에 영향을 미치므로, string 타입이 아니라 리터럴 타입이어야 합니다.

요청 메서드에 객체 리터럴로 옵션을 전달하면 자동으로 처리됩니다. 하지만 요청 옵션을 별도 변수나 헬퍼 메서드로 분리한다면 observe: 'events' as const처럼 리터럴 타입을 명시해야 할 수 있습니다.

이벤트 스트림에서 전달하는 각 HttpEvent에는 이벤트의 의미를 구분하는 type이 있습니다:

type 값 이벤트 의미
HttpEventType.Sent 서버로 요청을 보냈음
HttpEventType.UploadProgress 요청 본문 업로드 진행 상황을 알리는 HttpUploadProgressEvent
HttpEventType.ResponseHeader 상태와 헤더를 포함한 응답의 헤더 부분을 받았음
HttpEventType.DownloadProgress 응답 본문 다운로드 진행 상황을 알리는 HttpDownloadProgressEvent
HttpEventType.Response 본문을 포함한 전체 응답을 받았음
HttpEventType.User HTTP 인터셉터의 커스텀 이벤트

요청 실패 처리하기

HTTP 요청은 다음 세 가지 방식으로 실패할 수 있습니다:

  • 네트워크 또는 연결 오류로 요청이 백엔드 서버에 도달하지 못합니다.
  • 타임아웃 옵션을 설정했는데 제한 시간 안에 응답하지 않습니다.
  • 백엔드가 요청을 받았지만 처리에 실패해서 오류 응답을 반환합니다.

HttpClient는 이런 모든 오류를 HttpErrorResponse에 담아 Observable의 오류 채널로 전달합니다. 네트워크 및 타임아웃 오류의 status 코드는 0입니다. 타임아웃이면 error는 이름이 TimeoutError인 DOMException입니다. 네트워크 오류이면 fetch가 발생시킨 오류이며, withXhr()를 사용하는 경우에는 ProgressEvent입니다. 백엔드 오류는 백엔드가 반환한 실패 상태 코드를 status로, 오류 응답을 error로 제공합니다. 응답을 확인해서 오류 원인과 적절한 처리 방법을 판단하세요.

RxJS 라이브러리에는 오류 처리에 유용한 연산자가 여러 개 있습니다.

catchError 연산자를 사용하면 오류 응답을 UI에서 사용할 값으로 변환할 수 있습니다. 이 값으로 UI에 오류 페이지나 값을 표시하도록 알리고, 필요하면 오류 원인도 기록할 수 있습니다.

때로는 네트워크 중단 같은 일시적 오류로 요청이 예상치 못하게 실패하며, 요청을 다시 보내기만 해도 성공할 수 있습니다. RxJS는 특정 조건에서 실패한 Observable을 자동으로 다시 구독하는 여러 재시도 연산자를 제공합니다. 예를 들어 retry() 연산자는 지정한 횟수만큼 자동으로 재구독을 시도합니다.

타임아웃

요청에 타임아웃을 설정하려면 다른 요청 옵션과 함께 timeout 옵션에 밀리초 단위의 숫자를 지정합니다. 백엔드 요청이 지정한 시간 안에 완료되지 않으면 요청을 취소하고 오류를 전달합니다.

참고: 타임아웃은 백엔드 HTTP 요청 자체에만 적용되며, 전체 요청 처리 체인에 적용되는 것은 아닙니다. 따라서 인터셉터에서 발생한 지연은 이 옵션에 영향을 미치지 않습니다.

http
  .get('/api/config', {
    timeout: 3000,
  })
  .subscribe({
    next: (config) => {
      console.log('Config fetched successfully:', config);
    },
    error: (err) => {
      // 요청 시간이 초과되면 에러가 전달됩니다.
    },
  });

고급 fetch 옵션

Angular의 HttpClient는 성능과 사용자 경험을 개선할 수 있는 고급 fetch API 옵션을 지원합니다. 이 옵션은 기본 백엔드인 fetch 백엔드를 사용할 때 적용할 수 있습니다.

fetch 옵션

다음 옵션을 사용하면 fetch 백엔드의 요청 동작을 세밀하게 제어할 수 있습니다.

연결 유지

keepalive 옵션을 사용하면 요청을 시작한 페이지가 종료된 뒤에도 요청을 유지할 수 있습니다. 사용자가 페이지를 떠나더라도 완료해야 하는 분석 정보나 로그 전송 요청에 특히 유용합니다.

http
  .post('/api/analytics', analyticsData, {
    keepalive: true,
  })
  .subscribe();

HTTP 캐싱 제어

cache 옵션은 요청이 브라우저의 HTTP 캐시를 사용하는 방식을 제어하며, 반복 요청의 성능을 크게 개선할 수 있습니다.

// 최신 여부와 관계없이 캐시된 응답을 사용합니다.
http
  .get('/api/config', {
    cache: 'force-cache',
  })
  .subscribe((config) => {
    // ...
  });

// 캐시를 건너뛰고 항상 네트워크에서 가져옵니다.
http
  .get('/api/live-data', {
    cache: 'no-cache',
  })
  .subscribe((data) => {
    // ...
  });

// 캐시된 응답만 사용하며, 캐시에 없으면 실패합니다.
http
  .get('/api/static-data', {
    cache: 'only-if-cached',
  })
  .subscribe((data) => {
    // ...
  });

Core Web Vitals를 위한 요청 우선순위

priority 옵션은 요청의 상대적 중요도를 나타냅니다. 브라우저가 리소스 로드를 최적화해서 Core Web Vitals 점수를 개선하는 데 도움이 됩니다.

// 중요한 리소스에는 높은 우선순위를 지정합니다.
http
  .get('/api/user-profile', {
    priority: 'high',
  })
  .subscribe((profile) => {
    // ...
  });

// 덜 중요한 리소스에는 낮은 우선순위를 지정합니다.
http
  .get('/api/recommendations', {
    priority: 'low',
  })
  .subscribe((recommendations) => {
    // ...
  });

// 자동 우선순위(기본값)에서는 브라우저가 결정합니다.
http
  .get('/api/settings', {
    priority: 'auto',
  })
  .subscribe((settings) => {
    // ...
  });

사용할 수 있는 priority 값은 다음과 같습니다:

  • 'high': 우선순위가 높으며 먼저 로드합니다. 예를 들어 중요한 사용자 데이터나 스크롤하지 않아도 보이는 콘텐츠에 사용합니다.
  • 'low': 우선순위가 낮으며 리소스에 여유가 있을 때 로드합니다. 예를 들어 분석 정보나 미리 가져올 데이터에 사용합니다.
  • 'auto': 요청 상황에 따라 브라우저가 우선순위를 정합니다. 기본값입니다.

팁: Largest Contentful Paint(LCP)에 영향을 미치는 요청에는 priority: 'high'를, 초기 사용자 경험에 영향을 주지 않는 요청에는 priority: 'low'를 사용하세요.

요청 모드

mode 옵션은 교차 출처 요청을 처리하는 방식을 제어하고 응답 타입을 결정합니다.

// 동일 출처 요청만 허용합니다.
http
  .get('/api/local-data', {
    mode: 'same-origin',
  })
  .subscribe((data) => {
    // ...
  });

// CORS를 사용하는 교차 출처 요청입니다.
http
  .get('https://api.external.com/data', {
    mode: 'cors',
  })
  .subscribe((data) => {
    // ...
  });

// 단순 교차 출처 요청에 No-CORS 모드를 사용합니다.
http
  .get('https://external-api.com/public-data', {
    mode: 'no-cors',
  })
  .subscribe((data) => {
    // ...
  });

사용할 수 있는 mode 값은 다음과 같습니다:

  • 'same-origin': 동일 출처 요청만 허용하고, 교차 출처 요청은 실패 처리합니다.
  • 'cors': CORS를 사용하는 교차 출처 요청을 허용합니다. 기본값입니다.
  • 'no-cors': CORS 없이 단순 교차 출처 요청을 허용하며, 응답은 불투명한(opaque) 응답이 됩니다.

팁: 브라우저에서 다른 출처로 보내면 안 되는 민감한 요청에는 mode: 'same-origin'을 사용하세요.

중요: Node.js의 SSR에서 HttpClient는 Node.js의 Undici 기반 Fetch 구현을 사용합니다. Undici는 브라우저의 CORS 검사를 강제하지 않으므로 mode: 'same-origin'이 서버 측 요청을 제한하지 않습니다. 사용자 입력의 영향을 받는 URL은 허용 목록과 대조하여 검사하세요.

리다이렉트 처리

redirect 옵션은 서버의 리다이렉트 응답을 처리하는 방식을 지정합니다.

// 리다이렉트를 자동으로 따릅니다(기본 동작).
http
  .get('/api/resource', {
    redirect: 'follow',
  })
  .subscribe((data) => {
    // ...
  });

// 자동 리다이렉트를 방지합니다.
http
  .get('/api/resource', {
    redirect: 'manual',
  })
  .subscribe((response) => {
    // 리다이렉트를 직접 처리합니다.
  });

// 리다이렉트를 에러로 처리합니다.
http
  .get('/api/resource', {
    redirect: 'error',
  })
  .subscribe({
    next: (data) => {
      // 성공 응답입니다.
    },
    error: (err) => {
      // 리다이렉트 응답은 이 에러 핸들러를 실행합니다.
    },
  });

사용할 수 있는 redirect 값은 다음과 같습니다:

  • 'follow': 리다이렉트를 자동으로 따릅니다. 기본값입니다.
  • 'error': 리다이렉트를 오류로 처리합니다.
  • 'manual': 리다이렉트를 자동으로 따르지 않고 리다이렉트 응답을 반환합니다.

팁: 리다이렉트를 직접 작성한 로직으로 처리해야 한다면 redirect: 'manual'을 사용하세요.

인증 정보 처리

credentials 옵션은 교차 출처 요청에 쿠키, 인증 헤더 등의 인증 정보를 포함할지 제어합니다. 인증을 사용하는 경우에 특히 중요합니다.

// 교차 출처 요청에 인증 정보를 포함합니다.
http
  .get('https://api.example.com/protected-data', {
    credentials: 'include',
  })
  .subscribe((data) => {
    // ...
  });

// 인증 정보를 보내지 않습니다(교차 출처 요청의 기본 동작).
http
  .get('https://api.example.com/public-data', {
    credentials: 'omit',
  })
  .subscribe((data) => {
    // ...
  });

// 동일 출처 요청에만 인증 정보를 보냅니다.
http
  .get('/api/user-data', {
    credentials: 'same-origin',
  })
  .subscribe((data) => {
    // ...
  });

// withCredentials가 credentials 설정보다 우선합니다.
http
  .get('https://api.example.com/data', {
    credentials: 'omit', // 이 값은 무시됩니다.
    withCredentials: true, // credentials: 'include'로 동작하도록 강제합니다.
  })
  .subscribe((data) => {
    // credentials: 'omit'을 지정했지만 요청에 인증 정보가 포함됩니다.
  });

// 이전 방식도 계속 지원합니다.
http
  .get('https://api.example.com/data', {
    withCredentials: true,
  })
  .subscribe((data) => {
    // credentials: 'include'와 같습니다.
  });

중요: withCredentials 옵션은 credentials 옵션보다 우선합니다. 둘 다 지정하면 명시한 credentials 값과 관계없이 withCredentials: true가 항상 credentials: 'include'로 동작합니다.

사용할 수 있는 credentials 값은 다음과 같습니다:

  • 'omit': 인증 정보를 보내지 않습니다.
  • 'same-origin': 동일 출처 요청에만 인증 정보를 보냅니다. 기본값입니다.
  • 'include': 교차 출처 요청을 포함해서 항상 인증 정보를 보냅니다.

팁: CORS를 지원하는 다른 도메인에 인증 쿠키나 헤더를 보내야 한다면 credentials: 'include'를 사용하세요. 혼동을 피하려면 credentials와 withCredentials 옵션을 함께 사용하지 않는 것이 좋습니다.

중요: Node.js의 SSR에서 credentials: 'include'는 들어온 브라우저 요청의 쿠키를 자동으로 전달하지 않습니다. credentials 옵션은 직접 추가한 Cookie나 Authorization 헤더도 제거하지 않습니다. Undici는 브라우저가 금지하는 일부 헤더를 허용하므로, 인증 정보가 담긴 헤더는 신뢰할 수 있는 출처에만 전달하세요.

리퍼러

referrer 옵션은 요청에 어떤 리퍼러 정보를 보낼지 제어합니다. 개인정보 보호와 보안 측면에서 중요한 설정입니다.

// 지정한 리퍼러 URL을 보냅니다.
http
  .get('/api/data', {
    referrer: 'https://example.com/page',
  })
  .subscribe((data) => {
    // ...
  });

// 현재 페이지를 리퍼러로 사용합니다(기본 동작).
http
  .get('/api/analytics', {
    referrer: 'about:client',
  })
  .subscribe((data) => {
    // ...
  });

referrer 옵션에는 다음 값을 지정할 수 있습니다:

  • 유효한 URL 문자열: 전송할 리퍼러 URL을 직접 지정합니다.
  • 빈 문자열 '': 리퍼러 정보를 보내지 않습니다.
  • 'about:client': 기본 리퍼러인 현재 페이지 URL을 사용합니다.

팁: 요청을 보낸 페이지의 URL을 노출하고 싶지 않은 민감한 요청에는 referrer: ''를 사용하세요.

리퍼러 정책

referrerPolicy 옵션은 HTTP 요청과 함께 요청을 보낸 페이지의 URL, 즉 리퍼러 정보를 어느 범위까지 전달할지 제어합니다. 이 설정은 개인정보 보호와 분석에 모두 영향을 미치므로, 데이터 확인 범위와 보안을 고려해서 조정할 수 있습니다.

// 현재 페이지와 관계없이 리퍼러 정보를 보내지 않습니다.
http
  .get('/api/data', {
    referrerPolicy: 'no-referrer',
  })
  .subscribe();

// 출처만 보냅니다(예: https://example.com).
http
  .get('/api/analytics', {
    referrerPolicy: 'origin',
  })
  .subscribe();

referrerPolicy 옵션에는 다음 값을 지정할 수 있습니다:

  • 'no-referrer': Referer 헤더를 보내지 않습니다.
  • 'no-referrer-when-downgrade': 동일 출처 요청과 보안 수준이 유지되는 요청(HTTPS→HTTPS)에는 리퍼러를 보내지만, 보안 수준이 낮아지는 출처로 이동할 때(HTTPS→HTTP)는 보내지 않습니다.
  • 'origin': 리퍼러의 출처인 스킴, 호스트, 포트만 보내고 경로와 쿼리 정보는 제외합니다.
  • 'origin-when-cross-origin': 동일 출처 요청에는 전체 URL을 보내고, 교차 출처 요청에는 출처만 보냅니다.
  • 'same-origin': 동일 출처 요청에는 전체 URL을 보내고, 교차 출처 요청에는 리퍼러를 보내지 않습니다.
  • 'strict-origin': 프로토콜의 보안 수준이 낮아지지 않을 때만(예: HTTPS→HTTPS) 출처를 보냅니다. 보안 수준이 낮아지면 리퍼러를 보내지 않습니다.
  • 'strict-origin-when-cross-origin': 브라우저의 기본 동작입니다. 동일 출처 요청에는 전체 URL을 보내고, 보안 수준이 낮아지지 않는 교차 출처 요청에는 출처를 보내며, 보안 수준이 낮아지면 리퍼러를 보내지 않습니다.
  • 'unsafe-url': 경로와 쿼리를 포함한 전체 URL을 항상 보냅니다. 민감한 데이터가 노출될 수 있으므로 주의해서 사용해야 합니다.

팁: 개인정보 보호가 중요한 요청에는 'no-referrer', 'origin', 'strict-origin-when-cross-origin'처럼 보수적인 값을 사용하는 것이 좋습니다.

무결성

integrity 옵션에 예상 콘텐츠의 암호학적 해시를 지정하면 응답이 변조되지 않았는지 검증할 수 있습니다. CDN에서 스크립트나 다른 리소스를 불러올 때 특히 유용합니다.

// SHA-256 해시로 응답 무결성을 검증합니다.
http
  .get('/api/script.js', {
    integrity: 'sha256-ABC123...',
    responseType: 'text',
  })
  .subscribe((script) => {
    // 스크립트 콘텐츠가 해시와 일치하는지 검증합니다.
  });

중요: integrity 옵션을 사용하면 응답 콘텐츠와 지정한 해시가 정확히 일치해야 합니다. 일치하지 않으면 요청이 네트워크 오류로 실패합니다.

매우 중요: SSR에서 Fetch 구현은 Fetch 표준에 따라 응답을 반환하기 전에 전체 응답 본문을 읽어 integrity를 검증합니다. Angular의 maxResponseBodySize는 Fetch가 응답을 반환한 뒤에만 적용되므로, 무결성 검증 중에 버퍼링하는 데이터 양을 제한하지 못합니다.

팁: 외부에서 중요한 리소스를 불러올 때는 하위 리소스 무결성 검사(subresource integrity)로 변조 여부를 확인하세요. 해시는 openssl 같은 도구로 생성할 수 있습니다.

HTTP Observable

HttpClient의 각 요청 메서드는 요청한 응답 타입의 Observable을 생성해서 반환합니다. HttpClient를 사용할 때는 이 Observable의 동작을 이해하는 것이 중요합니다.

HttpClient가 생성하는 것은 RxJS에서 "콜드(cold)" Observable이라고 부르는 옵저버블입니다. 즉, Observable을 구독하기 전에는 실제 요청이 발생하지 않으며, 구독해야 서버로 요청을 보냅니다. 같은 Observable을 여러 번 구독하면 백엔드 요청도 여러 번 발생합니다. 각 구독은 독립적입니다.

팁: HttpClient의 Observable은 실제 서버 요청을 위한 설계도 라고 생각할 수 있습니다.

구독한 뒤 구독을 해제하면 진행 중인 요청을 취소합니다. async 파이프로 Observable을 구독하면 사용자가 현재 페이지를 떠날 때 요청을 자동으로 취소하므로 매우 유용합니다. 또한 switchMap 같은 RxJS 조합 연산자와 함께 사용하면 이런 취소 동작으로 더 이상 필요하지 않은 요청을 정리할 수 있습니다.

응답을 받으면 HttpClient의 Observable은 일반적으로 완료됩니다. 다만 인터셉터가 이 동작에 영향을 줄 수 있습니다.

자동으로 완료되므로 HttpClient 구독을 정리하지 않아도 보통 메모리 누수가 발생하지 않습니다. 하지만 다른 비동기 작업과 마찬가지로, 구독을 사용하는 컴포넌트가 종료되면 구독도 정리하는 것을 강하게 권장합니다. 그렇지 않으면 구독 콜백이 실행되면서 이미 종료된 컴포넌트와 상호작용하려다가 오류가 발생할 수 있습니다.

팁: async 파이프나 toSignal로 Observable을 구독하면 구독을 적절하게 정리할 수 있습니다.

권장하는 사용 방식

HttpClient를 컴포넌트에 직접 주입해서 사용할 수도 있지만, 일반적으로 데이터 접근 로직을 분리하고 캡슐화하는 재사용 가능한 주입용 서비스를 만드는 것을 권장합니다. 예를 들어 다음 UserService는 사용자 ID로 데이터를 요청하는 로직을 캡슐화합니다:

@Service()
export class UserService {
  private http = inject(HttpClient);

  getUser(id: string): Observable<User> {
    return this.http.get<User>(`/api/user/${id}`);
  }
}

컴포넌트에서는 @if와 async 파이프를 함께 사용해서 데이터 로드가 완료된 후에만 해당 데이터를 사용하는 UI를 렌더링할 수 있습니다:

import {AsyncPipe} from '@angular/common';
import {Component, input, inject, effect} from '@angular/core';
import {Observable} from 'rxjs';

@Component({
  imports: [AsyncPipe],
  template: `
    @if (user$ | async; as user) {
      <p>Name: {{ user.name }}</p>
      <p>Biography: {{ user.biography }}</p>
    }
  `,
})
export class UserProfile {
  userId = input.required<string>();
  user$!: Observable<User>;

  private userService = inject(UserService);

  constructor() {
    effect(() => {
      this.user$ = this.userService.getUser(this.userId());
    });
  }
}