# Axim REST Framework — XWebClient 동적 헤더 지원 요청

> **작성일**: 2026-03-22
> **요청자**: Cryptoments v2 프로젝트
> **대상**: `one.axim.framework.rest.proxy.XWebClient`
> **Framework 버전**: v1.3.1 (피드백 작성 시점: v1.3.0)
> **우선순위**: 높음 — 외부 API 연동 시 XWebClient 사용을 포기하게 만드는 구조적 제약

---

## 1. 문제 상황

### 현재 XWebClient API

```java
// 현재 사용 가능한 메서드
public <T> T get(String path, Class<T> responseType, Object... uriVars);
public <T> T post(String path, Object body, Class<T> responseType, Object... uriVars);
public <T> T put(String path, Object body, Class<T> responseType, Object... uriVars);
```

**문제**: 요청별 **커스텀 HTTP 헤더**를 설정할 방법이 없음.

### 영향받는 실제 케이스: Axim Pay API

Axim Pay는 **HMAC-SHA256 인증**을 사용하며, 모든 요청마다 다른 서명 헤더가 필요:

```
X-API-KEY:      {apiKey}              ← 파트너별 다름
X-TIMESTAMP:    {epochSeconds}        ← 요청 시점마다 다름
X-ACCESS-TOKEN: HMAC(timestamp.apiKey, secret) ← 매번 재계산
```

### 현재 우회 방법

`XWebClient` 대신 `RestClient`를 직접 사용:

```java
// ❌ XWebClient 사용 불가 — 동적 헤더 지원 안 됨
webClient.get("/api/v1/open/payments/" + id, AximPaymentResponse.class);

// ✅ RestClient 직접 사용 (Framework 밖으로 이탈)
restClient.get()
    .uri(path)
    .header("X-API-KEY", apiKey)
    .header("X-TIMESTAMP", String.valueOf(timestamp))
    .header("X-ACCESS-TOKEN", accessToken)
    .retrieve()
    .body(responseType);
```

**결과**: Axim Pay 연동은 Framework 커넥션 풀/에러 핸들링/로깅 통합에서 제외됨.

---

## 2. 다른 영향 사례 (잠재적)

| 외부 서비스 | 인증 방식 | 동적 헤더 필요 |
|------------|----------|---------------|
| Axim Pay | HMAC-SHA256 (요청마다 서명) | ✅ |
| Bithumb API | 공개 API (헤더 없음) | ❌ (현재 XWebClient 사용 가능) |
| Telegram Bot | URL에 토큰 포함 | ❌ (현재 XWebClient 사용 가능) |
| OAuth2 외부 서비스 (향후) | Bearer Token (갱신 필요) | ✅ |
| AWS S3 Presigned (향후) | 서명 URL + 헤더 | ✅ |

동적 헤더가 필요한 외부 서비스 연동 시 항상 `RestClient` 직접 사용으로 이탈하게 됨.

---

## 3. 제안: 3가지 방안

### 방안 A: 기존 메서드에 headers 파라미터 오버로드 추가 (최소 변경)

```java
// 신규 오버로드
public <T> T get(String path, Class<T> responseType, Map<String, String> headers, Object... uriVars);
public <T> T post(String path, Object body, Class<T> responseType, Map<String, String> headers, Object... uriVars);
public <T> T put(String path, Object body, Class<T> responseType, Map<String, String> headers, Object... uriVars);
public <T> T delete(String path, Class<T> responseType, Map<String, String> headers, Object... uriVars);
```

**사용 예시**:
```java
Map<String, String> authHeaders = Map.of(
    "X-API-KEY", apiKey,
    "X-TIMESTAMP", String.valueOf(timestamp),
    "X-ACCESS-TOKEN", accessToken
);
webClient.get("/api/v1/open/payments/" + id, AximPaymentResponse.class, authHeaders);
```

**장점**: 기존 API 100% 호환, 구현 간단
**단점**: `Map<String, String>` 전달이 번거로움, uriVars와 파라미터 순서 혼동 가능

### 방안 B: RequestSpec 빌더 패턴 확장 (유연성 최대화)

현재 `spec()` 메서드가 `RequestSpec`을 반환하지만, 이 `RequestSpec`에 헤더 지원이 없는 것으로 보임.

```java
// RequestSpec에 header() 메서드 추가
public class RequestSpec {
    // 기존
    public RequestSpec get() { ... }
    public RequestSpec post() { ... }
    public RequestSpec uri(String uri) { ... }
    public RequestSpec body(Object body) { ... }
    public <T> T retrieve().body(Class<T> responseType) { ... }

    // 신규
    public RequestSpec header(String name, String value) { ... }
    public RequestSpec headers(Map<String, String> headers) { ... }
}
```

**사용 예시**:
```java
webClient.spec()
    .get()
    .uri("/api/v1/open/payments/" + id)
    .header("X-API-KEY", apiKey)
    .header("X-TIMESTAMP", timestamp)
    .header("X-ACCESS-TOKEN", accessToken)
    .retrieve()
    .body(AximPaymentResponse.class);
```

**장점**: Spring RestClient와 동일한 fluent API, 확장성 최고
**단점**: RequestSpec 내부 구현 변경 필요

### 방안 C: 헤더 Provider 콜백 (인증 자동화)

```java
// XWebClient 생성 시 헤더 Provider 등록
XWebClient client = webClientFactory.create("https://pay.axim.one",
    (path, method) -> {
        long timestamp = Instant.now().getEpochSecond();
        return Map.of(
            "X-API-KEY", apiKey,
            "X-TIMESTAMP", String.valueOf(timestamp),
            "X-ACCESS-TOKEN", hmac(timestamp + "." + apiKey, secret)
        );
    }
);

// 이후 호출 시 자동으로 헤더 적용
client.get("/api/v1/open/payments/" + id, AximPaymentResponse.class);
```

**장점**: 한 번 설정하면 모든 요청에 자동 적용, 인증 로직 캡슐화
**단점**: 콜백 인터페이스 신규 정의 필요, 파트너별 다른 키 사용 시 인스턴스 분리 필요

---

## 4. 권장: 방안 B (RequestSpec 헤더 지원)

### 이유

1. **Spring RestClient와 일관된 API** — 학습 비용 제로
2. **방안 A보다 유연** — 다중 헤더, 조건부 헤더 등 자유로움
3. **방안 C보다 단순** — 별도 인터페이스 불필요, 기존 패턴 확장만
4. **하위 호환** — 기존 `get()/post()` 메서드는 그대로 유지

### 구현 예상 범위

```
XWebClient.java:
  - RequestSpec 내부 클래스에 Map<String, String> customHeaders 필드 추가
  - header(String name, String value) 메서드 추가
  - retrieve() 호출 시 customHeaders를 RestClient 요청에 병합

예상 변경량: ~20줄
```

### 추가 고려사항

- `debug = true`일 때 커스텀 헤더도 로깅에 포함되면 디버깅 편의성 향상
- 민감 헤더(`Authorization`, `X-ACCESS-TOKEN` 등)는 로깅 시 마스킹 처리 권장

---

## 5. 현재 Cryptoments 프로젝트의 XWebClient 사용 현황

| 클라이언트 | 외부 서비스 | XWebClient 사용 | 이유 |
|-----------|-----------|:---------------:|------|
| `TelegramBotClient` | Telegram Bot API | ✅ | 고정 base URL, 동적 헤더 불필요 |
| `BithumbApiClient` | 빗썸 시세 API | ✅ | 공개 API, 인증 없음 |
| `AximPayClient` | Axim Pay API | ❌ `RestClient` | **동적 HMAC 헤더 필요** |
| `BlockchainApiClient` | 내부 blockchain-api | `@XRestService` | 선언적 프록시 |
| `RelayerApiClient` | 내부 relayer-api | `@XRestService` | 선언적 프록시 |

방안 B 적용 시 `AximPayClient`도 `XWebClient`로 통합 가능 → 커넥션 풀/에러 핸들링/로깅 일원화.

---

## 6. 참고: 현재 XWebClient 내부 구조 (v1.3.1)

```java
public class XWebClient {
    private final RestClient restClient;
    private final String baseUrl;
    private boolean debug;

    public XWebClient(RestClient restClient);
    public XWebClient(RestClient restClient, String baseUrl);
    public static XWebClient create(String baseUrl);

    public <T> T get(String path, Class<T> cls, Object... uriVars);
    public <T> T post(String path, Object body, Class<T> cls, Object... uriVars);
    public <T> T put(String path, Object body, Class<T> cls, Object... uriVars);
    public <T> T patch(String path, Object body, Class<T> cls, Object... uriVars);
    public <T> T delete(String path, Class<T> cls, Object... uriVars);

    // ParameterizedTypeReference 오버로드 (List<T> 등)
    public <T> T get(String path, ParameterizedTypeReference<T> type, Object... uriVars);
    // ... 동일 패턴

    public RequestSpec spec();
    public void setDebug(boolean debug);
    public XWebClient errorHandler(XErrorResponseHandler handler);
}
```

**RequestSpec**: `spec()` 반환값 — 현재 내부 구현이 공개되어 있지 않아 정확한 API는 미확인.
빌더 패턴으로 `header()` 메서드를 추가하는 것이 가장 자연스러움.
