Files
tech-log-backend/httpclient-superpowers-package/docs/superpowers/specs/2026-08-08-httpclient-platform-design.md
T

63 KiB

HTTP Client Platform 설계서

문서 상태: 구현 기준선 확정
작성 기준일: 2026-08-08
입력 근거: Java/Spring 외부 HTTP Client 플랫폼 설계 심층 리서치
대상 저장소: Spring 기반 Backend Skeleton
문서 목적: 구현 중 추가 설계 판단이나 반복 질문 없이 공개 API, 전송 엔진, Named Client Profile, 시간 예산, 재시도 안전성, 복원력, 인증, 보안, 관측성, 테스트 및 릴리스 조건을 확정한다.


1. 요약

이 설계는 httpclientRestClientWebClient를 한 번 감싼 편의 Wrapper가 아니라, 외부 HTTP 호출의 대상, 연결 자원, 시간 예산, 실행 증거, 재시도 안전성, 인증, 보안, 관측성을 하나의 계약으로 통제하는 공통 플랫폼으로 정의한다.

일반 애플리케이션은 다음 네 단계 중 필요한 최소 단계만 사용한다.

  1. H1 Typed Service Client@HttpExchange 기반 interface를 기본 진입점으로 사용한다.
  2. H2 Generic Exchange Gateway — 등록된 Named Client Profile 안에서만 동적 method, path, header, body를 허용한다.
  3. H3 Dynamic Target Gateway — 사용자 입력 URL이 필요한 기능을 별도 보안 경계와 SSRF 정책 아래에서 제공한다.
  4. H4 Native Engine SPI — Apache, Reactor Netty, JDK, Jetty 고유 API는 플랫폼 내부 또는 Lab에만 공개한다.

플랫폼의 핵심 판정은 단순한 성공/예외가 아니다.

요청이 실제 서버에 전달됐는가?
서버가 업무 처리를 완료했을 가능성이 있는가?
요청 Body를 동일한 의미로 다시 생성할 수 있는가?
호출이 표준 또는 계약상 멱등한가?
남은 deadline과 retry budget으로 다음 시도를 완료할 수 있는가?

이를 위해 모든 물리 시도는 다음 세 축을 보존한다.

ExecutionEvidence
├─ NOT_SENT
├─ SENT_NO_RESPONSE
├─ RESPONSE_RECEIVED
└─ PARTIAL_RESPONSE

BodyReplayability
├─ REPLAYABLE
├─ REOPENABLE
├─ ONE_SHOT
└─ UNKNOWN

OperationIdempotency
├─ STANDARD_IDEMPOTENT
├─ CONTRACT_IDEMPOTENT
├─ IDEMPOTENCY_KEY_REQUIRED
└─ NON_IDEMPOTENT

최종 구조는 다음과 같다.

Application
  → Typed Service Client 또는 제한형 Gateway
  → Named Client Profile
  → Operation Policy Validation
  → Effective Deadline
  → Authentication Materialization
  → Retry Coordinator
  → Circuit Breaker
  → Attempt Rate Limiter
  → Attempt Bulkhead
  → RestClient 또는 WebClient
  → Apache / JDK / Reactor Netty / Jetty
  → Execution Evidence Classification
  → Stable Result 또는 Stable Exception

2. 목표와 성공 기준

2.1 목표

  • 다양한 내부 서비스와 외부 SaaS API를 동일한 운영 기준으로 호출할 수 있게 한다.
  • 일반 서비스 코드는 H1 Typed Client만으로 대부분의 호출을 구현하게 한다.
  • upstream별 connection pool, timeout, 인증, retry, circuit, bulkhead를 서로 격리한다.
  • 비멱등 요청의 중복 실행과 장애 시 retry 폭풍을 구조적으로 차단한다.
  • Blocking과 Reactive 호출을 모두 지원하되 resource lifecycle과 cancellation 의미론을 구분한다.
  • Dynamic URL 호출은 Trusted Client와 완전히 다른 보안 경계로 제공한다.
  • Apache, Reactor Netty, JDK 전송 엔진이 동일한 오류·관측 semantic을 제공하게 한다.
  • 구현자가 timeout, retry, redirect, 인증, TLS, SSRF, streaming 정책을 다시 판단하지 않게 한다.

2.2 성공 기준

영역 완료 기준
공개 API 일반 업무 모듈이 Native client를 직접 참조하지 않고 H1 Typed Client를 사용한다.
설정 모든 호출 대상이 clientName으로 등록된 Named Client Profile을 가진다.
시간 예산 pool acquire부터 retry backoff까지 전체 호출이 effective deadline을 초과하지 않는다.
실행 증거 실패 시 NOT_SENT, SENT_NO_RESPONSE, RESPONSE_RECEIVED, PARTIAL_RESPONSE 중 하나를 설명할 수 있다.
Retry idempotency, body replayability, evidence, status, deadline, retry budget을 모두 통과한 시도만 재실행된다.
Resource 성공, timeout, decode 실패, size 초과, cancellation에서 connection과 buffer가 회수된다.
보안 trust-all, hostname verification 해제, unrestricted Dynamic URL, credential redirect leakage가 차단된다.
관측성 논리 호출과 물리 시도 수가 분리되고 전체 URL·사용자 ID·token이 metric label에 들어가지 않는다.
호환성 Spring Framework 6.2와 7.0 지원 범위가 CI 매트릭스로 검증된다.
전송 엔진 Apache·JDK blocking과 Reactor Netty reactive가 공통 계약 테스트를 통과한다.
Streaming 첫 byte가 호출자에게 전달된 이후 투명 retry가 발생하지 않는다.
Dynamic Target canonicalization, DNS/IP 검증, redirect 재검증, egress 정책이 함께 적용된다.

3. 입력 자료의 제약과 구현 가정

첨부 리서치는 설계 방향, 지원 범위, API 초안, 장애 의미론, 테스트 및 구현 순서를 충분히 제공하지만 실제 Backend Skeleton 저장소의 다음 정보는 포함하지 않는다.

  • root package
  • Java toolchain
  • Gradle 구조
  • Spring Boot BOM
  • 기존 observability·security·resilience 공통 모듈
  • 배포 환경의 proxy, service mesh, egress 정책

따라서 이 문서는 실행 가능한 계획을 만들기 위해 다음 구현 기준을 사용한다.

항목 구현 기준
Java Java 21
빌드 Gradle Kotlin DSL 멀티모듈
root package io.backend.skeleton.httpclient
Spring 기준 공통 코드는 Spring Framework 6.2 API 기준으로 컴파일하고 7.0 호환 테스트를 수행한다.
Spring 7 전용 기능 HTTP Service Group은 독립 선택 모듈로 분리한다.
Spring Boot host 저장소의 dependency management를 사용하고 라이브러리가 Boot patch version을 직접 고정하지 않는다.
Reactive type Reactor Mono, Flux는 reactive integration module에서만 공개한다.
Resilience Resilience4j를 실행 primitive로 사용하되 HTTP retry 가능성 판정은 플랫폼이 소유한다.
테스트 JUnit 5, AssertJ, ArchUnit, MockWebServer, WireMock, Testcontainers, Toxiproxy, BlockHound

실제 저장소가 다른 package 또는 더 높은 Java 기준을 사용하면 경로와 toolchain만 조정한다. 본 문서의 공개 계약, 정책 순서, 오류 의미론은 변경하지 않는다.


4. 범위

4.1 포함 범위

  • Spring RestClient
  • Spring WebClient
  • @HttpExchange 기반 HTTP Service Client
  • RestTemplate 마이그레이션 호환 계층
  • Apache HttpClient 5 blocking transport
  • JDK HttpClient blocking transport
  • Reactor Netty reactive transport
  • Jetty HTTP/3 Experimental transport
  • HTTP/1.1과 HTTP/2 Stable
  • 동기 DTO·header·empty response
  • Reactive Mono·Flux
  • JSON, XML, text, bytes, form, multipart, octet-stream
  • streaming upload·download
  • SSE
  • redirect, compression, conditional request, Range client semantics
  • proxy와 HTTPS CONNECT
  • connection pool과 lifecycle
  • 단계별 timeout과 전체 deadline
  • retry, retry budget, backoff, jitter, Retry-After
  • Circuit Breaker, Bulkhead, Rate Limiter
  • API key, Basic, Bearer, OAuth2 Client, mTLS, request signing SPI
  • TLS 1.2·1.3, custom CA, certificate rotation
  • Dynamic URL SSRF 방어
  • RFC 9457 problem response 변환
  • metric, trace, logging, audit
  • 계약·장애·보안·성능 테스트

4.2 제외 또는 별도 모듈

  • WebSocket
  • gRPC
  • GraphQL query·error·subscription 의미론
  • Fileserver의 저장·publish·Range 응답 생성
  • 브라우저 JavaScript HTTP Client
  • API Gateway와 inbound routing
  • 서비스 디스커버리와 client-side load balancing 구현
  • unrestricted Dynamic URL
  • application-facing Native engine access
  • 자동 공유 Cookie Jar
  • TRACE
  • 무제한 redirect
  • one-shot request body의 자동 retry
  • partial response가 호출자에게 전달된 뒤의 투명 retry
  • HTTP/3 공통 Stable 보장
  • request hedging Stable 지원
  • transparent shared response cache
  • trust-all, hostname verification 해제, 평문 fallback
  • Simple request factory의 운영 사용
  • RestTemplate 신규 기능

5. 설계 결정

ID 결정 결과
D-01 기본 진입점은 H1 Typed Service Client다. 일반 업무 코드가 URL, timeout, auth, retry를 매번 조립하지 않는다.
D-02 H2 Generic Gateway는 등록 profile의 base URL과 정책을 변경할 수 없다. 범용 호출 기능은 제공하되 정책 우회를 막는다.
D-03 H3 Dynamic Target Gateway는 별도 모듈·권한·설정으로 제공한다. Trusted credential, Cookie, default header를 상속하지 않는다.
D-04 H4 Native API는 플랫폼 내부 SPI다. 애플리케이션이 engine 설정과 관측성을 우회하지 못한다.
D-05 설정 단위는 upstream별 Named Client Profile이다. pool, timeout, auth, resilience, observability가 upstream마다 격리된다.
D-06 Blocking 기본은 RestClient + Apache HC5, 경량 대안은 JDK HttpClient다. 세밀한 운영 profile과 의존성 최소화 profile을 모두 제공한다.
D-07 Reactive·Streaming 기본은 WebClient + Reactor Netty다. backpressure, cancellation, SSE를 안정적으로 제공한다.
D-08 Jetty와 HTTP/3는 Experimental로 격리한다. Stable portability와 장애 의미론을 훼손하지 않는다.
D-09 Retry 가능성은 HTTP method 하나로 결정하지 않는다. idempotency, idempotency key, body replayability, evidence, deadline, budget을 함께 판정한다.
D-10 전체 deadline이 모든 timeout과 retry의 상위 예산이다. 개별 attempt가 성공해도 전체 사용자 요청 시간을 초과하지 않는다.
D-11 Retry Coordinator 바깥에서 logical admission을 적용하고, 각 물리 시도는 Circuit → Rate Limiter → Bulkhead를 통과한다. backoff 중 permit을 점유하지 않고 실제 upstream 요청 수를 제한한다.
D-12 첫 response byte를 application에 전달한 뒤에는 transparent retry를 금지한다. streaming 중복·순서 오류를 차단한다.
D-13 OAuth2 token 획득은 Spring Security에 위임하되 cache key, refresh single-flight, 401 재호출 규칙은 플랫폼이 고정한다. 인증 구현을 재작성하지 않으면서 동시 갱신과 중복 호출을 통제한다.
D-14 TLS 오류 중 trust·hostname·expiry 오류는 영구 오류로 분류한다. 인증서 오류를 retry하거나 평문으로 fallback하지 않는다.
D-15 Dynamic Target Stable은 검증한 DNS 결과로 실제 연결을 pin할 수 있는 transport에서만 제공한다. DNS rebinding과 검사-연결 간 TOCTOU를 줄인다.
D-16 Spring 표준 http.client.requests는 물리 시도 metric으로 유지하고 logical call metric을 추가한다. retry가 사용자 호출 성공률과 upstream 부하를 왜곡하지 않는다.
D-17 Spring 6.2 공통 API를 기준으로 하고 Spring 7 전용 Service Group은 선택 모듈로 둔다. 두 안정 계열을 지원하면서 공통 모듈의 분기를 줄인다.
D-18 RestTemplate은 migration module에서만 허용한다. 신규 코드가 deprecated API에 고착되지 않는다.

6. 지원 매트릭스

6.1 Spring API

API 등급 역할 제약
RestClient Stable Blocking 요청 실행 bounded concurrency와 deadline 필수
WebClient Stable Reactive·Streaming·SSE event-loop blocking 금지
HTTP Service Client 기본 선언형 Typed Client operation metadata 등록 필수
RestTemplate Migration only 기존 호출 이전 신규 profile·기능 금지
Generic Exchange 제한 동적 method·path·body base URL과 정책 변경 금지
Dynamic Target 제한 사용자 URL 별도 SSRF 정책과 credential 미상속
Native Engine Internal/Lab 엔진 고유 기능 application public API 금지

6.2 전송 엔진

엔진 Blocking Reactive HTTP/2 HTTP/3 Stable 역할
Apache HttpClient 5 내부 async 가능 아니오 Blocking 기본
JDK HttpClient sendAsync 가능 아니오 경량 Blocking 대안
Reactor Netty 제한 Experimental Reactive 기본
Jetty HttpClient sync facade HTTP/3 Experimental
Simple factory 아니오 제한 아니오 local test only

6.3 HTTP 기능

기능 Stable 제약
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS operation idempotency 등록
TRACE 아니오 startup과 runtime에서 차단
custom method 제한 사전 등록 descriptor 필요
path·query template 문자열 연결 금지, component encoding
absolute URI H3만 SSRF 정책 필수
JSON, XML, text, bytes codec와 크기 상한
form, multipart part 수·크기·replayability 계산
InputStream request 제한 one-shot, 자동 retry 금지
reopenable file request 매 시도 새 stream 생성
DTO response decoded size 상한
InputStream response 제한 AutoCloseable lifecycle
Reactive body cancellation·buffer release
SSE setup deadline과 idle timeout 분리
redirect 제한 기본 off, hop·origin 정책
compression wire·decoded size 모두 제한
conditional request validator 전달
Range request client 의미론만 제공
trailer Stable 제외 engine-specific advanced API
100-continue 선택 대용량 replayable body만
HTTP/1.1 fallback
HTTP/2 stream concurrency 별도 제한
HTTP/3 Experimental Jetty/Reactor 전용

7. 전체 아키텍처

flowchart TB
    APP[Application]

    subgraph PublicAPI[Public API]
      H1[H1 Typed Service Client]
      H2[H2 Generic Exchange]
      H3[H3 Dynamic Target Gateway]
    end

    subgraph Runtime[Runtime and Policy]
      REG[Client Profile Registry]
      META[Operation Descriptor Registry]
      TARGET[Target Policy]
      DEADLINE[Deadline Calculator]
      AUTH[Authentication Provider]
      RETRY[Retry Coordinator]
      RES[Attempt Resilience]
      ERROR[Error Mapper]
      OBS[Observation]
    end

    subgraph SpringClients[Spring Client Layer]
      REST[RestClient]
      WEB[WebClient]
    end

    subgraph Transport[Transport Providers]
      APACHE[Apache HC5]
      JDK[JDK HttpClient]
      REACTOR[Reactor Netty]
      JETTY[Jetty Experimental]
    end

    APP --> H1
    APP --> H2
    APP --> H3
    H1 --> REG
    H2 --> REG
    H3 --> REG
    REG --> META
    META --> TARGET
    TARGET --> DEADLINE
    DEADLINE --> AUTH
    AUTH --> RETRY
    RETRY --> RES
    RES --> REST
    RES --> WEB
    REST --> APACHE
    REST --> JDK
    WEB --> REACTOR
    WEB --> JETTY
    REST --> ERROR
    WEB --> ERROR
    ERROR --> OBS

7.1 논리 호출 흐름

1. profileName과 operationName을 해석한다.
2. profile과 operation descriptor를 immutable snapshot으로 가져온다.
3. method, URI template, body type, idempotency metadata를 검증한다.
4. Trusted 또는 Dynamic target policy를 적용한다.
5. parent deadline과 profile timeout에서 effective deadline을 계산한다.
6. credential을 materialize한다.
7. logical admission limit를 통과한다.
8. Retry Coordinator가 attempt 1을 생성한다.
9. attempt가 Circuit Breaker → Rate Limiter → Bulkhead를 통과한다.
10. RestClient 또는 WebClient가 물리 요청을 실행한다.
11. transport classifier가 stage와 execution evidence를 판정한다.
12. Retry Eligibility Engine이 다음 시도 여부를 결정한다.
13. 최종 결과를 `HttpCallResult` 또는 안정 예외로 반환한다.
14. 성공·실패·cancel 모두에서 response body와 connection을 정리한다.

7.2 Runtime 세대 교체

Named Client Profile은 mutable client를 직접 수정하지 않는다.

ClientRuntimeRegistry
  payment → generation 17
  search  → generation 4

인증서, secret, base URL 또는 pool 설정이 변경되면 다음 순서로 교체한다.

  1. 새 immutable ClientRuntime을 생성한다.
  2. startup validation과 선택적 connectivity probe를 수행한다.
  3. registry pointer를 새 generation으로 atomic swap한다.
  4. 신규 호출은 새 runtime을 사용한다.
  5. 기존 runtime은 drain timeout 동안 진행 호출을 완료한다.
  6. timeout 후 pool과 connection을 강제 종료한다.

이 구조는 mTLS certificate와 OAuth client secret rotation을 connection pool lifecycle과 일치시킨다.


8. 모듈 구조

backend-skeleton/
├── modules/httpclient/
│   ├── httpclient-core-api/
│   ├── httpclient-profile/
│   ├── httpclient-transport-spi/
│   ├── httpclient-transport-apache/
│   ├── httpclient-transport-jdk/
│   ├── httpclient-restclient/
│   ├── httpclient-resilience/
│   ├── httpclient-auth/
│   ├── httpclient-security/
│   ├── httpclient-observability/
│   ├── httpclient-transport-reactor-netty/
│   ├── httpclient-webclient/
│   ├── httpclient-service-client/
│   ├── httpclient-dynamic-target/
│   ├── httpclient-resttemplate-migration/
│   ├── httpclient-spring7-service-groups/
│   ├── httpclient-jetty-http3-experimental/
│   ├── httpclient-spring-boot-starter/
│   └── httpclient-testkit/
├── infra/httpclient/
│   ├── proxy/
│   ├── tls/
│   ├── oauth2/
│   └── toxiproxy/
└── docs/httpclient/
모듈 책임 의존 규칙
httpclient-core-api 안정 타입, result, evidence, body, 오류 Spring·Apache·Netty·Resilience4j에 의존하지 않는다.
httpclient-profile Named Client Profile, validation, runtime registry core-api에만 공개적으로 의존한다.
httpclient-transport-spi blocking·reactive transport provider와 classifier Spring Web integration type은 이 SPI부터 허용한다.
httpclient-transport-apache Apache HC5 request factory, pool, proxy, TLS hooks native client를 외부에 반환하지 않는다.
httpclient-transport-jdk JDK request factory와 제한 capability fine-grained pool이 필요한 profile을 거부한다.
httpclient-restclient Blocking Generic Gateway와 RestClient 실행 pipeline Apache/JDK provider를 선택한다.
httpclient-resilience deadline, retry, budget, circuit, rate, bulkhead HTTP-specific retry 판정을 소유한다.
httpclient-auth API key, Basic, Bearer, OAuth2, mTLS identity, signing SPI token과 secret을 result·log에 노출하지 않는다.
httpclient-security target, URI, redirect, header, body size, TLS 정책 H1·H2·H3 모두 우회하지 못한다.
httpclient-observability logical·attempt metric, trace, redaction low-cardinality vocabulary를 소유한다.
httpclient-transport-reactor-netty Reactor connector, pool, timeout, cancel event-loop blocking을 허용하지 않는다.
httpclient-webclient Reactive Generic Gateway, streaming, SSE Reactor Context로 operation metadata를 전달한다.
httpclient-service-client @HttpExchange proxy, profile·operation annotation blocking·reactive proxy를 생성한다.
httpclient-dynamic-target canonicalization, DNS/IP pinning, redirect revalidation trusted credential을 의존하거나 상속하지 않는다.
httpclient-resttemplate-migration 기존 RestTemplate 설정을 RestClient로 이전 신규 feature annotation을 제공하지 않는다.
httpclient-spring7-service-groups Spring 7 HTTP Service Group 통합 Spring 6.2 core에서 완전히 분리한다.
httpclient-jetty-http3-experimental Jetty HTTP/3 connector와 capability matrix Stable starter가 자동 활성화하지 않는다.
httpclient-spring-boot-starter properties, auto-configuration, validation production unsafe 설정에서 startup을 실패시킨다.
httpclient-testkit mock·fault·TLS·H2·proxy·OAuth contract fixture production module에서 의존하지 않는다.

9. 공개 API

9.1 핵심 식별자

public record ClientProfileName(String value) {
    public ClientProfileName {
        if (value == null || !value.matches("[a-z][a-z0-9-]{1,62}")) {
            throw new IllegalArgumentException("invalid client profile name");
        }
    }
}

public record OperationName(String value) {
    public OperationName {
        if (value == null || !value.matches("[a-z][a-z0-9.-]{1,127}")) {
            throw new IllegalArgumentException("invalid operation name");
        }
    }
}

9.2 H1 Typed Service Client

public interface HttpServiceRegistry {
    <T> T client(ClientProfileName profileName, Class<T> serviceType);
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface HttpClientProfile {
    String value();
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface HttpOperationPolicy {
    String name();
    OperationIdempotency idempotency();
    String retryPolicy() default "none";
    String timeoutPolicy() default "default";
    boolean streaming() default false;
}
@HttpClientProfile("payment")
@HttpExchange("/payments")
public interface PaymentClient {

    @PostExchange
    @HttpOperationPolicy(
        name = "create-payment",
        idempotency = OperationIdempotency.IDEMPOTENCY_KEY_REQUIRED,
        retryPolicy = "payment-write")
    PaymentResponse create(
        @RequestHeader("Idempotency-Key") String idempotencyKey,
        @RequestBody PaymentRequest request);
}

Typed interface는 다음 조건을 만족해야 startup에 성공한다.

  • interface에 @HttpClientProfile이 존재한다.
  • 모든 method에 안정적인 operationName이 존재한다.
  • POST·PATCH는 idempotency를 명시한다.
  • IDEMPOTENCY_KEY_REQUIRED method에는 등록된 key parameter가 존재한다.
  • streaming method는 one-shot 여부가 드러나는 wrapper type을 사용한다.
  • 반환형이 blocking인지 reactive인지 하나의 interface에서 모호하지 않다.

9.3 H2 Generic Exchange

public interface GenericHttpGateway {
    <T> HttpCallResult<T> exchange(
        ClientProfileName profileName,
        HttpOperation operation,
        ResponseType<T> responseType);
}

public interface ReactiveHttpGateway {
    <T> Mono<HttpCallResult<T>> exchange(
        ClientProfileName profileName,
        HttpOperation operation,
        ResponseType<T> responseType);
}

H2가 변경할 수 있는 것은 method, profile 내부 상대 path, query, 승인된 header와 body다. 다음은 변경할 수 없다.

  • scheme
  • host
  • port
  • proxy
  • TLS trust
  • credential provider
  • hard body limit
  • redirect cross-origin 허용
  • metric naming

9.4 H3 Dynamic Target

public interface DynamicTargetGateway {
    <T> HttpCallResult<T> exchange(
        DynamicTargetPolicyName policyName,
        URI target,
        HttpOperation operation,
        ResponseType<T> responseType);
}

public interface ReactiveDynamicTargetGateway {
    <T> Mono<HttpCallResult<T>> exchange(
        DynamicTargetPolicyName policyName,
        URI target,
        HttpOperation operation,
        ResponseType<T> responseType);
}

H3는 profile의 API key, OAuth token, Cookie, custom default header를 상속하지 않는다. host별 credential이 필요하면 보안 관리자가 DynamicCredentialBinding을 별도로 등록한다.

9.5 H4 Native SPI

다음 형태의 application-facing API는 제공하지 않는다.

ApacheHttpClient nativeApacheClient();
HttpClient nativeJdkClient();
reactor.netty.http.client.HttpClient nativeReactorClient();
WebClient.Builder mutableBuilder();
RestClient.Builder mutableBuilder();

Native 구성은 TransportProvider 구현 내부와 Experimental 모듈에서만 접근한다.


10. Core 계약

10.1 Operation

public record HttpOperation(
    OperationName operationName,
    HttpMethod method,
    String uriTemplate,
    Map<String, ?> uriVariables,
    Map<String, List<String>> headers,
    BodySource body,
    OperationIdempotency idempotency,
    Optional<IdempotencyKey> idempotencyKey,
    Optional<Instant> deadline) {
}

HttpMethod는 플랫폼 enum을 사용한다. TRACE는 enum에 포함하지 않고 custom method descriptor도 사전 등록해야 한다.

10.2 BodySource

public sealed interface BodySource permits
    EmptyBody,
    ObjectBody,
    ByteArrayBody,
    ReopenableStreamBody,
    OneShotStreamBody {

    BodyReplayability replayability();
    OptionalLong knownLength();
}

public record ReopenableStreamBody(
    IOSupplier<InputStream> opener,
    OptionalLong knownLength,
    String mediaType) implements BodySource {
    @Override public BodyReplayability replayability() {
        return BodyReplayability.REOPENABLE;
    }
}

public record OneShotStreamBody(
    InputStream stream,
    OptionalLong knownLength,
    String mediaType) implements BodySource {
    @Override public BodyReplayability replayability() {
        return BodyReplayability.ONE_SHOT;
    }
}

Reactive body는 httpclient-webclient의 별도 타입을 사용한다.

public record ReactiveBodySource(
    Supplier<? extends Publisher<DataBuffer>> publisherFactory,
    BodyReplayability replayability,
    OptionalLong knownLength,
    MediaType mediaType) {
}

Publisher instance 자체를 받는 API는 one-shot으로 간주한다. retry 가능한 body는 매 시도 새 publisher를 생성하는 factory를 요구한다.

10.3 ResponseType과 lifecycle

public sealed interface ResponseType<T> permits
    ClassResponseType,
    GenericResponseType,
    ByteArrayResponseType,
    EmptyResponseType {
}

public interface BlockingStreamingResponse extends AutoCloseable {
    HttpStatus status();
    Map<String, List<String>> headers();
    InputStream body();
    @Override void close();
}

Streaming response는 반드시 AutoCloseable로 반환한다. InputStream만 단독 반환하지 않는다.

10.4 Result

public record HttpCallResult<T>(
    HttpStatus status,
    Map<String, List<String>> headers,
    T body,
    int attempts,
    Duration elapsed,
    ExecutionEvidence evidence,
    Optional<RemoteProblem> remoteProblem) {
}

2xx 이외 status를 result로 반환할지 예외로 변환할지는 operation policy가 결정한다. 기본 Typed Client는 4xx·5xx를 안정 예외로 변환하고 Generic Gateway는 StatusHandlingPolicy를 명시할 수 있다.


11. Named Client Profile

11.1 구성 모델

http-clients:
  payment:
    mode: TRUSTED
    base-url: https://payment.example.com
    allowed-hosts: [payment.example.com]
    allowed-ports: [443]
    api: REST_CLIENT
    transport: APACHE
    protocols: [HTTP_2, HTTP_1_1]

    pool:
      max-total-connections: 100
      max-connections-per-route: 50
      max-pending-acquires: 200
      pending-acquire-timeout: 200ms
      max-idle-time: 30s
      max-life-time: 5m
      validate-after-inactivity: 5s
      eviction-interval: 15s

    timeout:
      dns: 300ms
      connect: 500ms
      tls-handshake: 1s
      proxy-connect: 500ms
      request-write-idle: 1s
      response-header: 2s
      read-idle: 3s
      total-call: 4s
      streaming-idle: 30s

    redirect:
      enabled: false
      max-hops: 0
      allow-cross-origin: false

    request:
      max-body-bytes: 1048576
      compression: false

    response:
      max-wire-bytes: 5242880
      max-decoded-bytes: 10485760
      allowed-content-types:
        - application/json
        - application/problem+json

    authentication:
      type: OAUTH2_CLIENT_CREDENTIALS
      registration-id: payment
      scopes: [payments.write]
      audience: payment-api

    retry:
      policy: payment-write
      max-attempts: 2
      base-backoff: 50ms
      max-backoff: 200ms
      jitter: FULL
      retry-after: HONOR
      budget: payment

    circuit-breaker: payment
    bulkhead: payment
    rate-limiter: payment-attempts

    observability:
      operation-name-required: true
      full-url-recording: false
      body-logging: false

위 숫자는 플랫폼 default가 아니라 payment profile의 명시적 예시다. production profile은 upstream SLO와 부하 계산 없이 숨은 기본값으로 생성되지 않는다.

11.2 startup validation

다음 조건은 startup 실패다.

  • Trusted profile에 base URL이 없다.
  • http scheme이 production profile에서 사용된다.
  • base URL에 userinfo 또는 query가 포함된다.
  • allowed host와 base URL host가 다르다.
  • redirect가 활성화됐는데 max hops가 0이거나 cross-origin credential 정책이 없다.
  • total call timeout이 connect 또는 response header timeout보다 짧다.
  • max decoded bytes가 global hard maximum을 초과한다.
  • JDK transport에 세밀한 pending queue 또는 route pool 보장을 요구한다.
  • HTTP/3를 Stable profile에서 요청한다.
  • Dynamic mode에 default OAuth, API key, Cookie가 설정된다.
  • trust-all, hostname verification off, plaintext fallback이 설정된다.
  • production에서 Simple request factory가 선택된다.
  • POST retry policy가 idempotency 조건 없이 활성화된다.

11.3 Operation override

operation은 profile 값을 더 위험한 방향으로 넓힐 수 없다.

허용:
- 더 짧은 total timeout
- 더 작은 response size
- retry 비활성화
- stricter content type
- streaming idle timeout 지정

금지:
- 더 긴 timeout
- 더 큰 body limit
- 다른 host
- 다른 credential
- cross-origin redirect 활성화
- non-idempotent retry 강제

12. Target·URI·Header 정책

12.1 Trusted target

Trusted profile은 startup에 다음을 검증한다.

  • URI strict parsing
  • scheme, host, port
  • IDNA canonical host
  • userinfo 없음
  • path base normalization
  • allowed host·port 일치
  • production TLS policy

H2 호출자는 상대 URI template만 전달한다. URI absolute 값이 들어오면 거부한다.

12.2 URI encoding

  • path와 query를 문자열로 연결하지 않는다.
  • template variable은 component별로 encode한다.
  • 이미 인코딩된 값과 raw 값을 동일 API에서 혼용하지 않는다.
  • query value의 민감정보는 log와 trace에서 제거한다.
  • 국제화 host는 Punycode canonical form으로 allowlist와 비교한다.
  • IPv4-mapped IPv6를 원래 IPv4로 정규화한다.

12.3 Header

다음 header는 플랫폼이 소유한다.

Authorization
Proxy-Authorization
Host
Content-Length
Transfer-Encoding
Traceparent
Tracestate
Baggage
Cookie (profile opt-in일 때)

호출자가 임의로 덮어쓰지 못한다. Idempotency-Key는 operation descriptor가 요구할 때만 허용한다. header name·value에 CR 또는 LF가 있으면 요청 전 거부한다.

12.4 Redirect

기본값은 비활성이다.

상태 기본 정책
301, 302, 303 자동 method 변환을 신뢰하지 않고 operation별로 명시한다.
307, 308 method와 body를 보존하므로 body replayable일 때만 허용한다.
same-origin max hop과 method 정책 안에서 선택 허용한다.
cross-origin 기본 거부한다. 허용 시 Authorization, Cookie, API key를 제거한다.
Dynamic Target 각 hop을 새로운 target으로 canonicalize·resolve·IP 검증한다.

13. Transport SPI

13.1 Blocking provider

public interface BlockingTransportProvider {
    TransportId id();
    BlockingTransportCapabilities capabilities();
    ClientHttpRequestFactory create(
        ClientProfile profile,
        TransportLifecycleListener listener);
    TransportFailureClassifier failureClassifier();
}

13.2 Reactive provider

public interface ReactiveTransportProvider {
    TransportId id();
    ReactiveTransportCapabilities capabilities();
    ClientHttpConnector create(
        ClientProfile profile,
        TransportLifecycleListener listener);
    TransportFailureClassifier failureClassifier();
}

13.3 공통 원칙

  • provider는 native client를 반환하지 않는다.
  • capability가 profile 요구사항보다 약하면 startup에 실패한다.
  • transport exception은 public API에 직접 노출하지 않는다.
  • transport가 NOT_SENT를 증명할 수 없으면 SENT_NO_RESPONSE 또는 보수적 unknown reason으로 분류한다.
  • response body를 소비·close하지 않은 경우 connection 재사용 여부를 명시한다.
  • client runtime 종료 시 신규 retry를 금지하고 진행 호출을 drain한다.

13.4 Apache profile

  • 전체·route별 connection 제한
  • pending acquire timeout
  • max idle, max lifetime
  • validate after inactivity
  • background eviction
  • proxy와 CONNECT
  • custom TLS strategy
  • HTTP/1.1·2
  • blocking response lifecycle

13.5 JDK profile

  • 의존성 최소화 profile
  • HTTP/1.1·2
  • sync send 기반
  • 세밀한 pool queue·route limit을 요구하지 않는 경우만 사용
  • Dynamic Target Stable에서 제외
  • streaming body close·cancel contract 검증

13.6 Reactor Netty profile

  • provider를 upstream별로 분리한다.
  • max connections, pending acquire, idle, lifetime, eviction을 설정한다.
  • DNS, connect, TLS, proxy, response timeout을 stage별로 계측한다.
  • event-loop에서 blocking codec·file I/O를 금지한다.
  • cancellation에서 inbound buffer를 release하고 connection을 반환 또는 폐기한다.

13.7 Jetty HTTP/3

  • feature flag와 별도 module이 필요하다.
  • Stable starter가 자동 구성하지 않는다.
  • QUIC native dependency와 TLS 1.3을 요구한다.
  • HTTP/3 failure를 공통 evidence로 변환하는 contract suite를 통과해야 Beta로 승격한다.

14. Connection Pool과 동시성

14.1 pool과 bulkhead 분리

HTTP/1.1은 connection과 in-flight 요청 수가 가까울 수 있지만 HTTP/2는 하나의 connection에 여러 stream을 multiplex한다. 따라서 다음을 독립 설정으로 둔다.

connection pool limit
pending acquire queue limit
HTTP/2 stream capacity
logical admission limit
attempt bulkhead concurrency

14.2 pool 설정

설정 의미
maxTotalConnections runtime 전체 socket 상한
maxConnectionsPerRoute 한 upstream route 상한
maxPendingAcquires 대기 요청 메모리 상한
pendingAcquireTimeout pool·stream 대기 상한
maxIdleTime 유휴 연결 제거
maxLifeTime DNS·LB 변경과 인증서 rotation 반영
validateAfterInactivity stale·half-open 연결 검사
evictionInterval background cleanup
shutdownTimeout drain 후 강제 종료 시각

14.3 DNS와 기존 연결

DNS TTL만으로 pooled connection이 새 IP로 전환된다고 가정하지 않는다. maxLifeTime과 eviction을 함께 사용하고, DNS 변경 contract test에서 일정 시간 내 새 endpoint로 전환되는지 확인한다.


15. Timeout과 Deadline

15.1 단계별 timeout

타입 시작과 종료
DNS hostname resolve 시작부터 결과
Pool Acquire queue 진입부터 connection 또는 stream 확보
Connect socket connect 시작부터 성공
TLS Handshake TCP 이후 TLS·ALPN 완료
Proxy Connect proxy socket 또는 CONNECT 완료
Request Write Idle request chunk 진행이 없는 시간
Response Header request 전송 후 final header 수신까지
Read Idle response chunk 사이 무진행 시간
Total Call 최초 논리 호출부터 모든 retry·backoff 종료까지
Streaming Idle 장기 stream event 사이 무진행 시간
Shutdown runtime drain 시작부터 강제 종료까지

15.2 effective deadline

effectiveDeadline = min(parentDeadline, now + profile.totalCallTimeout)
remaining = effectiveDeadline - now - safetyMargin
attemptBudget = remaining - plannedBackoff - cleanupReserve

다음이면 새 attempt를 시작하지 않는다.

  • remaining <= minimumAttemptBudget
  • 다음 backoff 이후 attempt budget이 없다.
  • body가 replayable하지 않다.
  • ambiguous execution이고 operation이 안전하지 않다.
  • retry budget이 고갈됐다.
  • circuit이 open이다.
  • runtime이 draining 상태다.

15.3 Streaming

Streaming은 연결 설정 단계와 연결 유지 단계를 분리한다.

setupDeadline
→ response headers 수신
→ streamingIdleTimeout
→ optional maxStreamDuration

일반 total timeout을 SSE 전체 수명에 적용하지 않는다.


16. 실행 증거

16.1 public evidence

Evidence 의미
NOT_SENT 서버에 요청이 전달되지 않았음을 증명 profile 거부, pool timeout, DNS 실패, connect 실패, request 전 TLS 실패
SENT_NO_RESPONSE 일부 또는 전체 요청을 보냈으나 final header를 받지 못함 partial write, response header timeout, reset
RESPONSE_RECEIVED final HTTP header를 받음 2xx, 4xx, 5xx, redirect
PARTIAL_RESPONSE header와 body 일부를 받음 decode 중 reset, streaming 중단

16.2 stage

public enum AttemptStage {
    VALIDATION,
    AUTHENTICATION,
    POOL_ACQUIRE,
    DNS,
    CONNECT,
    TLS_HANDSHAKE,
    PROXY_CONNECT,
    REQUEST_HEADERS,
    REQUEST_BODY,
    RESPONSE_HEADERS,
    RESPONSE_BODY,
    COMPLETE
}

16.3 보수적 분류

  • NOT_SENT는 증명 가능한 stage 실패에서만 사용한다.
  • engine generic I/O exception은 false NOT_SENT로 만들지 않는다.
  • request body write가 시작됐으면 기본 SENT_NO_RESPONSE다.
  • response header를 받았으면 status와 무관하게 RESPONSE_RECEIVED다.
  • body 일부가 application에 전달됐으면 PARTIAL_RESPONSE다.
  • HTTP/2 REFUSED_STREAM과 GOAWAY last-stream-id는 내부 protocol evidence로 보존하고 안전한 경우 NOT_SENT에 준해 retry한다.

17. Retry

17.1 판정 입력

public record RetryContext(
    OperationIdempotency idempotency,
    Optional<IdempotencyKey> idempotencyKey,
    BodyReplayability replayability,
    ExecutionEvidence evidence,
    FailureCategory failureCategory,
    Optional<HttpStatus> responseStatus,
    Optional<Duration> retryAfter,
    int attempt,
    Duration remainingDeadline,
    RetryBudgetSnapshot budget) {
}

17.2 판정 결과

public sealed interface RetryDecision permits
    RetryAllowed,
    RetryDenied,
    AmbiguousFailure {
}

17.3 기본 규칙

상황 기본 판정
validation·auth configuration failure retry 금지
pool·DNS·connect failure body 재생 가능하고 deadline·budget이 있으면 허용
certificate·hostname failure retry 금지
request body 일부 송신 standard 또는 contract idempotent가 아니면 ambiguous
response header timeout read-only 또는 idempotency contract가 있을 때만 허용
408 replayability·deadline 조건으로 제한
425 early data 없이 한 번만 제한 retry
429 Retry-After, deadline, budget 내에서 허용
500 기본 금지, upstream policy가 transient로 등록한 경우만
502·503·504 안전한 operation에 제한 허용
401 credential invalidation 후 최대 1회, 안전한 body와 operation만
partial response application 전달 전 read-only buffering에서만 제한
one-shot body retry 금지
first byte delivered retry 금지

17.4 Retry budget

upstream별 token bucket을 사용한다.

원 요청 성공·실패 수에 비례한 retry token 공급
물리 retry마다 token 소비
budget 고갈 시 즉시 최종 실패

metric은 logical call 수와 physical attempt 수를 분리한다.

17.5 Backoff

  • exponential backoff
  • full 또는 decorrelated jitter
  • max backoff
  • Retry-After 상한
  • deadline보다 긴 대기 금지
  • backoff 중 bulkhead permit과 connection을 보유하지 않음

18. Resilience 실행 순서

flowchart LR
    A[Operation Validation] --> B[Effective Deadline]
    B --> C[Authentication]
    C --> D[Logical Admission]
    D --> E[Retry Coordinator]
    E --> F{Circuit Open?}
    F -- Yes --> X[Fail Fast]
    F -- No --> G[Attempt Rate Limiter]
    G --> H[Attempt Bulkhead]
    H --> I[HTTP Attempt]
    I --> J[Evidence Classification]
    J --> K{Retry Safe?}
    K -- Yes --> L[Backoff + Jitter]
    L --> E
    K -- No --> M[Result or Stable Error]

18.1 역할

기능 보호 대상
Logical admission retry coordinator와 대기 객체의 과도한 생성
Circuit Breaker 실패하거나 느린 upstream 호출
Attempt Rate Limiter 외부 API의 물리 요청 quota
Attempt Bulkhead in-flight 물리 요청과 thread·stream capacity
Retry Budget 장애 중 추가 요청 총량
Total Deadline 사용자 요청의 전체 시간 예산

18.2 Blocking과 Reactive

  • Blocking Apache/JDK는 semaphore 또는 bounded executor bulkhead를 사용한다.
  • Reactive는 event-loop를 thread-pool bulkhead로 감싸지 않고 semaphore concurrency를 사용한다.
  • blocking token acquisition이나 secret load는 event-loop에서 실행하지 않는다.

19. 오류 모델

HttpClientException
 ├─ HttpConfigurationException
 ├─ HttpTargetRejectedException
 ├─ HttpDnsException
 ├─ HttpPoolAcquireTimeoutException
 ├─ HttpConnectException
 ├─ HttpProxyException
 ├─ HttpTlsException
 ├─ HttpRequestWriteException
 ├─ HttpResponseTimeoutException
 ├─ HttpResponseTruncatedException
 ├─ HttpRemoteErrorException
 ├─ HttpProblemDetailException
 ├─ HttpRedirectRejectedException
 ├─ HttpAuthenticationException
 ├─ HttpSerializationException
 ├─ HttpResponseTooLargeException
 ├─ HttpDeadlineExceededException
 ├─ HttpCircuitOpenException
 ├─ HttpBulkheadRejectedException
 ├─ HttpRateLimitRejectedException
 └─ HttpAmbiguousExecutionException

19.1 공통 metadata

public record HttpFailureMetadata(
    ClientProfileName clientName,
    OperationName operationName,
    HttpMethod method,
    String uriTemplate,
    ExecutionEvidence evidence,
    BodyReplayability replayability,
    AttemptStage stage,
    boolean retryable,
    int attempt,
    Duration elapsed,
    Duration remainingDeadline,
    Optional<HttpStatus> status,
    Optional<String> traceId) {
}

다음은 예외 message나 public metadata에 포함하지 않는다.

  • 전체 URL
  • query value
  • 실제 path variable
  • request·response body
  • Authorization, Cookie, API key
  • idempotency key 원문
  • client secret
  • resolved IP의 metric label

19.2 RFC 9457

application/problem+json은 다음 필드를 제한 크기로 보존한다.

type
title
status
detail
instance
등록된 extension allowlist

HTTP response status가 authoritative다. body의 status로 실제 status를 덮어쓰지 않는다. detail, instance, extension은 log에 기본 기록하지 않는다.


20. 인증

20.1 지원 방식

방식 등급 정책
None Stable 명시 profile
Basic 제한 TLS 필수, secret provider
API Key Header Stable header name allowlist
API Key Query 승인 필요 provider 요구 시만
Static Bearer 제한 짧은 TTL과 rotation
OAuth2 Client Credentials Stable M2M 기본
Authorization Code authorized client 지원 principal을 명시 전달
token relay 제한 audience·scope 확인
Token Exchange 선택 audience 축소·delegation
mTLS Stable TLS identity profile
Request Signing SPI provider별 module
Proxy Authentication Stable target auth와 분리

20.2 credential provider

public interface RequestCredentialProvider {
    CredentialType type();
    RequestCredentials resolve(CredentialRequest request);
}

public interface ReactiveRequestCredentialProvider {
    CredentialType type();
    Mono<RequestCredentials> resolve(CredentialRequest request);
}

20.3 OAuth2 token cache

cache key는 다음을 포함한다.

registrationId
principalClass
scopeSet
audience
tenantBoundary
mTLSCertificateIdentity

동일 key의 refresh는 single-flight로 수행한다. token endpoint는 target upstream과 별도 Named Client Profile을 사용한다.

20.4 401 재호출

  • token을 한 번 invalidate한다.
  • refresh 후 최대 한 번만 재호출한다.
  • body가 replayable해야 한다.
  • operation이 read-only이거나 인증 실패가 side effect 전 반환된다는 계약이 있어야 한다.
  • one-shot upload와 ambiguous write에는 적용하지 않는다.

21. TLS와 인증서 rotation

21.1 허용

  • TLS 1.2·1.3
  • hostname verification
  • JVM trust store
  • profile별 custom CA
  • profile별 client certificate
  • mTLS
  • SNI와 ALPN
  • 새 runtime generation으로 certificate rotation

21.2 금지

  • trust-all TrustManager
  • hostname verification 비활성화
  • 인증서 오류 무시
  • production self-signed 자동 신뢰
  • HTTPS 실패 후 HTTP fallback
  • key material의 config file·log 기록

21.3 오류 분류

오류 retry
unknown CA 금지
hostname mismatch 금지
expired certificate 금지
revoked certificate 금지
protocol mismatch profile 오류로 금지
transient handshake timeout deadline과 policy 안에서 제한
client certificate 없음 금지

22. Dynamic Target와 SSRF

22.1 처리 순서

1. URI strict parse
2. scheme allowlist
3. userinfo·invalid port 거부
4. host IDNA canonicalization
5. host allowlist 또는 suffix policy
6. 모든 A·AAAA resolve
7. 각 주소를 canonical IP로 정규화
8. loopback, link-local, private, ULA, metadata 대역 검사
9. 검증한 주소로 실제 connection pinning
10. response size·content policy 적용
11. redirect마다 1~10을 반복

22.2 기본 금지 주소

  • IPv4·IPv6 loopback
  • link-local
  • RFC1918 private address
  • IPv6 ULA
  • unspecified·multicast
  • IPv4-mapped IPv6의 차단 대상
  • cloud metadata endpoint
  • 조직이 정의한 internal CIDR

22.3 transport 제한

Dynamic Target Stable은 validated resolver 또는 validated address pinning을 제공하는 Apache와 Reactor Netty에서 먼저 지원한다. JDK와 Jetty는 동일 보장을 contract test로 증명하기 전까지 H3에서 사용할 수 없다.

22.4 redirect credential

origin이 변경되면 다음을 제거한다.

Authorization
Proxy-Authorization
Cookie
API key header
custom sensitive header

Dynamic profile에는 Cookie Jar를 기본 생성하지 않는다.

22.5 네트워크 계층

애플리케이션 검증만으로 충분하다고 간주하지 않는다. Kubernetes NetworkPolicy, service mesh egress, firewall, proxy ACL 중 하나 이상의 네트워크 제어를 운영 완료 조건으로 요구한다.


23. Streaming과 대용량 Body

23.1 request replayability

Body Replayability
immutable byte[] REPLAYABLE
DTO + deterministic codec REPLAYABLE
reopenable file/resource supplier REOPENABLE
one InputStream instance ONE_SHOT
publisher factory 선언값에 따름
publisher instance ONE_SHOT
multipart 가장 약한 part와 동일

23.2 response lifecycle

  • blocking stream은 AutoCloseable response wrapper로 반환한다.
  • reactive body는 consume, cancel, error에서 buffer를 release한다.
  • content length를 신뢰하지 않고 실제 wire bytes와 decoded bytes를 측정한다.
  • gzip·deflate 응답은 압축 전후 상한을 각각 적용한다.
  • decode error와 size 초과에서도 connection을 회수하거나 명시적으로 폐기한다.

23.3 first-byte boundary

response header 수신
→ 내부 buffer에 아직 byte 미전달
   → read-only operation은 제한 retry 가능
→ application InputStream read 또는 Flux onNext 발생
   → transparent retry 영구 금지

23.4 SSE

public interface ReactiveSseGateway {
    <T> Flux<ServerSentEvent<T>> connect(
        ClientProfileName profileName,
        SseOperation operation,
        ResponseType<T> eventType);
}
  • setup deadline
  • streaming idle timeout
  • Last-Event-ID 재연결은 operation opt-in
  • reconnect에도 retry budget 적용
  • application cancel 시 connection close

24. HTTP protocol 세부 정책

24.1 HTTP/2

  • connection 수와 stream concurrency를 분리한다.
  • max concurrent streams를 metric으로 노출한다.
  • REFUSED_STREAM은 peer 미처리 증거로 제한 retry할 수 있다.
  • GOAWAY의 last stream ID 이후 요청만 peer 미처리로 분류한다.
  • stream reset 원인을 stable failure category로 변환한다.
  • connection coalescing은 host·certificate·security policy를 검증한 profile에서만 허용한다.

24.2 HTTP/3

  • TLS 1.3 필수
  • UDP·QUIC 네트워크 경로 테스트
  • proxy·egress 지원 별도 매트릭스
  • Stable H1/H2 API의 result·error semantic을 재사용
  • 별도 experimental=true와 startup acknowledgment 요구

24.3 Proxy

  • target auth와 proxy auth를 분리한다.
  • proxy connect timeout을 별도 metric으로 기록한다.
  • HTTPS CONNECT 실패를 target TLS 실패로 오분류하지 않는다.
  • NO_PROXY 환경변수가 production allowlist를 우회하지 못하게 한다.
  • service mesh retry가 활성화되면 application retry owner 검사를 수행한다.

25. 관측성

25.1 metric

이름 의미
http.client.requests 물리 attempt timer
http.client.logical.calls 사용자 논리 호출 timer
http.client.attempts attempt counter
http.client.retry.count retry 이유별 수
http.client.retry.exhausted retry 소진
http.client.ambiguous 결과 모호성
http.client.timeout timeout stage
http.client.request.bytes request wire bytes
http.client.response.bytes response wire·decoded bytes
http.client.active 진행 중 attempt
http.client.pool.connections active·idle connection
http.client.pool.pending pool 대기
http.client.pool.acquire.duration pool 대기 시간
http.client.dns.duration DNS 시간
http.client.connect.duration connect 시간
http.client.tls.duration TLS 시간
http.client.circuit.state circuit 상태
http.client.bulkhead.rejected bulkhead 거절
http.client.rate_limit.rejected local rate 거절
http.client.oauth.refresh token refresh 결과
http.client.ssrf.rejected dynamic target 거절

25.2 low-cardinality tag

허용:

clientName
operationName
method
uriTemplate
status
outcome
transport
protocol
timeoutType
retryReason
evidence
circuitState

금지:

full URL
query parameter
path variable value
user ID
tenant ID 원문
resolved IP
API key
token
Cookie
idempotency key
request·response body
exception message

25.3 trace

http.client.operation        logical internal span
└─ http.client.request       attempt 1 CLIENT span
└─ http.client.request       attempt 2 CLIENT span
  • W3C Trace Context
  • Baggage allowlist
  • Dynamic target는 기본 trace propagation off
  • retry reason과 evidence를 span event로 기록
  • credential과 remote error body는 attribute에 기록하지 않음

25.4 logging

  • 시도마다 WARN을 남기지 않는다.
  • 최종 실패 한 번을 구조화 로그로 남긴다.
  • retry attempt는 DEBUG 또는 trace event다.
  • URL은 template과 profile name만 남긴다.
  • body logging은 production에서 off다.
  • header는 이름 allowlist, 값은 redaction policy를 적용한다.

26. Spring 통합

26.1 RestClient

  • profile마다 immutable RestClient를 생성한다.
  • Apache 또는 JDK request factory를 선택한다.
  • default header는 credential과 trace보다 먼저 고정하지 않는다.
  • request interceptor는 operation context와 attempt context를 읽는다.
  • response extractor는 body lifecycle과 size를 통제한다.

26.2 WebClient

  • profile마다 immutable WebClient를 생성한다.
  • Reactor Netty provider를 upstream별로 분리한다.
  • filter chain은 context에서 operation metadata를 가져온다.
  • body cancel·discard hook을 등록한다.
  • .block()을 public API 내부에서 호출하지 않는다.

26.3 HTTP Service Client

HttpServiceRegistry는 다음 작업을 수행한다.

  1. interface annotation scan
  2. method descriptor 생성
  3. signature validation
  4. RestClient 또는 WebClient proxy 생성
  5. operation context wrapper proxy 생성
  6. blocking은 try/finally로 context 제거
  7. reactive는 Reactor Context에 descriptor 주입

26.4 Spring 7 Service Group

Spring 7 전용 모듈은 여러 service interface가 같은 profile을 공유하도록 group integration을 제공한다. 공통 계약과 profile validation은 그대로 재사용한다.

26.5 RestTemplate migration

Migration module은 다음만 제공한다.

  • 기존 request factory와 message converter를 조사하는 audit 도구
  • RestTemplate에서 RestClient builder로 이전하는 adapter
  • deprecated usage report
  • 동일 동작 contract test

신규 retry, Dynamic URL, HTTP/3 기능은 RestTemplate 경로에 추가하지 않는다.


27. Spring Boot Starter

27.1 auto-configuration

HttpClientProfileAutoConfiguration
HttpClientTransportAutoConfiguration
HttpClientResilienceAutoConfiguration
HttpClientAuthenticationAutoConfiguration
HttpClientSecurityAutoConfiguration
HttpClientObservationAutoConfiguration
HttpServiceClientAutoConfiguration
DynamicTargetAutoConfiguration

27.2 startup guard

  • production unsafe TLS 설정 탐지
  • Simple factory 차단
  • profile capability mismatch
  • duplicate client name
  • operation name duplicate
  • H1 interface annotation 누락
  • POST/PATCH idempotency 누락
  • Dynamic credential 상속
  • unsupported HTTP/3 Stable 설정
  • response size hard max 위반
  • retry owner 중복 선언

27.3 actuator

관리 endpoint는 값 원문을 숨기고 다음만 제공한다.

profile name
runtime generation
transport
protocol
pool state
circuit state
credential type
TLS profile ID
last reload outcome
capability warnings

base URL 전체, credential, trust store path, resolved IP는 공개하지 않는다.


28. 테스트 전략

28.1 test topology

도구 용도
MockWebServer deterministic request·response contract
WireMock stateful status, redirect, OAuth fixture
Toxiproxy latency, reset, bandwidth, half-open
TLS test server CA, hostname, expiry, mTLS
HTTP/2 server GOAWAY, REFUSED_STREAM, reset
Forward proxy CONNECT, auth, target failure
OAuth2 server token expiry, refresh race, rotation
Testcontainers isolated proxy·server runtime
BlockHound event-loop blocking 검출
ArchUnit module·native type 경계

28.2 계약 테스트

  • method와 URI encoding
  • header ownership과 CRLF 차단
  • JSON·XML·form·multipart
  • empty, generic, streaming response
  • redirect 301·302·303·307·308
  • compression과 decoded size
  • conditional request와 Range
  • HTTP/1.1·2
  • Apache·JDK·Reactor 공통 result·error semantic

28.3 timeout·failure

  • DNS timeout
  • pool saturation
  • connect refused·blackhole
  • TLS timeout·trust·hostname
  • slow request receiver
  • response header delay
  • body idle
  • total deadline
  • streaming idle
  • shutdown 중 신규 retry 금지

28.4 retry·resilience

  • GET connect failure
  • PUT body replay
  • POST idempotency key 있음·없음
  • partial write
  • 408, 425, 429, 500, 502, 503, 504
  • Retry-After
  • budget exhaustion
  • circuit half-open
  • rate limiter와 retry attempt 수
  • bulkhead permit 반환
  • service mesh 중복 retry configuration guard

28.5 security

  • loopback, private, link-local, ULA, metadata
  • IPv4-mapped IPv6
  • IDNA host
  • DNS rebinding
  • public→private redirect
  • Authorization·Cookie leakage
  • trust-all bean startup failure
  • hostname mismatch
  • mTLS certificate 없음·rotation
  • CRLF header
  • compressed bomb
  • JSON nesting·XML entity

28.6 streaming lifecycle

  • response 미소비
  • partial read 후 close
  • decode failure
  • size limit
  • reactive cancel
  • DataBuffer release
  • slow subscriber backpressure
  • first byte 이후 retry 없음
  • SSE idle와 Last-Event-ID reconnect
  • event-loop blocking 없음

28.7 observability

  • logical call 1, attempt N
  • retry reason
  • evidence
  • URI template cardinality
  • 전체 URL label 없음
  • token·API key 마스킹
  • dynamic target trace propagation off

28.8 성능

  • pool·bulkhead saturation
  • HTTP/2 stream saturation
  • 대용량 upload·download
  • gzip decoded size
  • concurrent OAuth refresh
  • runtime generation rotation
  • shutdown drain
  • heap·direct memory·thread 상한

29. 호환성 인증 매트릭스

프로파일 CI 빈도 릴리스 Gate
Spring Framework 6.2 latest patch 모든 PR·release 필수
Spring Framework 7.0 latest patch release 필수
Apache HC5 + RestClient 모든 PR 필수
JDK HttpClient + RestClient 모든 PR 필수
Reactor Netty + WebClient 모든 PR 필수
Jetty HTTP/3 nightly Experimental 비차단
HTTP/1.1 모든 PR 필수
HTTP/2 release 필수
Forward proxy release 지원 선언 시 필수
OAuth2 Client Credentials 모든 PR 필수
mTLS release 지원 선언 시 필수
Dynamic Target Apache security suite 필수
Dynamic Target Reactor security suite 필수
Toxiproxy failure suite nightly·release 필수

30. 운영 설정과 기본 정책

30.1 숨은 운영 기본값 금지

production Named Client Profile은 다음을 명시해야 한다.

base URL
transport
total timeout
response header timeout
pool 또는 concurrency limit
request·response body hard limit
authentication type
retry policy 또는 none
redirect policy
TLS profile

미설정 시 매우 큰 framework default로 조용히 동작하지 않고 startup에 실패한다.

30.2 retry owner

application HTTP Client, 외부 SDK, service mesh 중 하나만 retry owner가 된다. starter는 known mesh annotation 또는 설정을 읽어 중복 retry를 경고하거나 strict mode에서 실패시킨다.

30.3 shutdown

runtime state RUNNING → DRAINING
신규 logical call 거부 또는 새 generation으로 routing
진행 attempt 완료
신규 retry 금지
shutdown timeout
남은 call cancel
pool close

31. 비지원 범위의 runtime 강제

문서만으로 금지하지 않고 다음 guard를 코드로 둔다.

비지원 강제 방식
TRACE method registry에서 부재·runtime reject
unrestricted absolute URL H2 parser에서 reject
trust-all bean·SSLContext validator startup fail
hostname verification off transport capability validator fail
production Simple factory environment guard fail
one-shot retry Retry Eligibility Engine deny
partial stream retry first-byte marker deny
full URL metric observation convention test
Dynamic credential inheritance configuration validator fail
RestTemplate 신규 기능 module dependency·ArchUnit rule
native client exposure public API signature ArchUnit rule
HTTP/3 Stable profile validator fail

32. 릴리스 단계

32.1 Core Alpha

  • core types
  • Named Client Profile
  • stable exceptions
  • transport SPI
  • testkit
  • deadline model
  • target·header·body guard

완료 조건: core module의 public API와 configuration validation contract가 통과한다.

32.2 Blocking Beta

  • Apache HC5
  • JDK HttpClient
  • RestClient Generic Gateway
  • H1 blocking Typed Client
  • connection pool·timeout
  • error mapping

완료 조건: Apache와 JDK가 공통 Blocking contract suite를 통과한다.

32.3 Resilience RC

  • execution evidence
  • retry eligibility
  • retry budget
  • circuit·rate·bulkhead
  • RFC 9457
  • OAuth2·TLS

완료 조건: duplicate POST, partial write, 429, pool saturation, token refresh race가 통과한다.

32.4 Security Release

  • Trusted target validation
  • Dynamic Target Apache
  • DNS/IP pinning
  • redirect revalidation
  • SSRF security suite

완료 조건: loopback·private·metadata·rebind·redirect 공격이 모두 차단된다.

32.5 Reactive Release

  • Reactor Netty
  • WebClient Gateway
  • Reactive Typed Client
  • streaming upload·download
  • SSE
  • cancellation·backpressure

완료 조건: buffer leak, event-loop blocking, first-byte retry, stream idle suite가 통과한다.

32.6 Extended Release

  • Spring Boot starter
  • Spring 7 Service Groups
  • RestTemplate migration
  • proxy·HTTP/2 advanced evidence
  • support matrix와 runbook

32.7 Experimental

  • Jetty HTTP/3
  • Reactor HTTP/3 profile
  • Native engine Lab
  • request hedging Lab

33. 완료 정의

플랫폼은 다음이 코드와 CI로 증명될 때 완료된다.

영역 증명 조건
API 주요 호출이 Typed Client로 구현되고 H2·H3 사용이 별도 권한으로 제한된다.
경계 H1~H4가 timeout, host, TLS, auth, size, observation을 우회하지 못한다.
Engine Apache·JDK·Reactor가 동일 result·exception metadata를 제공한다.
Deadline pool·DNS·connect·TLS·retry backoff를 포함한 전체 시간이 effective deadline 이내다.
Retry 모든 추가 attempt가 idempotency·replayability·evidence·deadline·budget으로 설명된다.
Ambiguity 비멱등 SENT_NO_RESPONSEHttpAmbiguousExecutionException으로 구분된다.
Resource body 미소비, decode 오류, cancel, size 초과 후에도 pool과 buffer가 회수된다.
Auth token refresh single-flight, 401 최대 1회, secret rotation이 검증된다.
TLS trust-all과 hostname 검증 해제가 startup에서 차단된다.
SSRF canonicalization, DNS/IP, redirect, egress 테스트가 통과한다.
Streaming first byte 이후 transparent retry가 0회다.
Observability logical call과 attempt가 분리되고 forbidden label이 없다.
Failure DNS, pool, TLS, reset, partial response, HTTP/2 GOAWAY를 재현한다.
Performance 설정된 thread, heap, direct memory, pool, retry budget 상한을 넘지 않는다.
Compatibility Spring 6.2·7.0과 지원 transport matrix가 release CI에 연결된다.
Documentation support matrix, configuration reference, security guide, runbook, migration guide가 코드와 일치한다.

34. 최종 구현 기준

이 설계의 최종 원칙은 다음과 같다.

HTTP 기능을 최대한 많이 열어두되, 호출자가 URL·timeout·retry·credential·TLS·resource lifecycle을 임의로 조립하게 하지 않는다. 일반 호출은 Typed Client와 Named Client Profile을 사용하고, 플랫폼은 요청이 실제로 실행됐을 가능성과 다시 실행해도 되는지를 증거 기반으로 판정한다.

구현 우선순위는 다음으로 고정한다.

Core 계약
→ Named Client Profile
→ Transport SPI와 Testkit
→ Deadline·Target·Observability
→ Apache·JDK Blocking
→ RestClient와 Typed Client
→ Execution Evidence와 Retry
→ Resilience
→ Error·Auth·TLS
→ Dynamic Target SSRF
→ Reactor Netty·WebClient
→ Streaming·SSE
→ Starter·Migration·Compatibility
→ HTTP/3 Experimental