> For the complete documentation index, see [llms.txt](https://tech.x2bee.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tech.x2bee.com/dev-guide/dev-start/markdown/http-interface.md).

# HTTP Interface 구현하기

다음은 HTTP Interface에 대한 설명입니다.

Spring에서의 REST Api, Http 요청, Method Parameter, Return Values, HttpExchangeInterface X2BEE 공통 커스텀 어노테이션에 대해 설명합니다.

***

## 설명

Spring 6.0 (SpringBoot3.0) 부터는 REST API 통신으로 쓰이는 HTTP Interface가 추가 되었습니다. spring-cloud에 있는 FeignClient와 사용법이 유사합니다. spring-data-\*의 데이터 인터페이스와 비슷하게 인터페이스를 통해 REST API endpoint를 통해 데이터 통신을 하는 구조입니다.

Spring에서 REST Api 지원 역사는 아래와 같습니다.

* Spring 3.0 → RestTemplate (앞으로 deprecated될 예정이므로 사용X)
* Spring 5.0 → WebFlux의 WebClient
* Spring 6.0 → HTTP Interface (내부적으로 WebClient 사용)

FeignClient와 다른 점은 각 endpoint의 BASE URL을 WebClient에 선언하고 해당 인터페이스가 Bean으로 등록되어야 하는 점입니다. 이는 Spring에서 명시적으로 endpoint를 집중해서 관리하려는 의미이기도 합니다.

역자 주) FeignClient의 편리함에 익숙해져 있다면 다소 불편하게 느껴질 수도 있습니다.

아래는 WebClient와 HttpServiceProxyFactory를 사용해 인터페이스를 Bean으로 등록하는 예시입니다.

{% code title="HttpConfig.java" %}

```java
@Configuration
public class HttpConfig {
    @Bean
    RepositoryService repositoryService() {
        WebClient client = WebClient.create("https://jsonplaceholder.typicode.com");
        HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build();
        RepositoryService service = factory.createClient(RepositoryService.class);
        return service;
    }
}
```

{% endcode %}

위 코드에 대한 RepositoryService 코드는 아래와 같습니다.

{% code title="RepositoryService.java" %}

```java
public interface RepositoryService {
    @GetExchange("/posts")
    List<Post> getPosts();
}
```

{% endcode %}

여러 개의 REST Api endpoint를 관리하기 위해서는 아래와 같은 모양이 됩니다.

{% code title="HttpConfig (multiple services).java" %}

```java
@Configuration
public class HttpConfig {
    @Bean
    public WebClient client() {
        return WebClient.builder().baseUrl("http://localhost:8080").build();
    }

    @Bean
    public HttpServiceProxyFactory httpServiceProxyFactory(WebClient client) {
        return HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build();
    }

    @Bean
    public ProductService productService(HttpServiceProxyFactory httpServiceProxyFactory) {
        return httpServiceProxyFactory.createClient(ProductService.class);
    }

    @Bean
    public PaymentService paymentService(HttpServiceProxyFactory httpServiceProxyFactory) {
        return httpServiceProxyFactory.createClient(PaymentService.class);
    }
}
```

{% endcode %}

서비스 인터페이스 예시:

{% code title="PaymentService.java" %}

```java
public interface PaymentService {
    @PutExchange("/payment")
    void payAmount(@RequestParam BigDecimal amount);
}
```

{% endcode %}

{% code title="ProductService.java" %}

```java
public interface ProductService {
    @PutExchange("/product/decrease/{productId}")
    void decreaseQuantity(@PathVariable String productId, @RequestParam int quantity);
}
```

{% endcode %}

## Http 요청

| 종류              | 설명                   |
| --------------- | -------------------- |
| @GetExchange    | HTTP GET requests    |
| @PostExchange   | HTTP POST requests   |
| @PutExchange    | HTTP PUT requests    |
| @PatchExchange  | HTTP PATCH requests  |
| @DelectExchange | HTTP DELETE requests |

## Method Parameter

| 종류             | 설명                                                                                                                                                                                                                                           |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| URI            | annotation의 url을 덮어쓰고, 동적으로 요청의 URL을 설정한다.                                                                                                                                                                                                   |
| HttpMethod     | annotation의 method를 덮어쓰고, 동적으로 요청의 HTTP 메서드를 설정한다.                                                                                                                                                                                           |
| @RequestHeader | 요청에 헤더를 추가한다. Map\<String, ?> 또는 여러값인 경우 MultiValueMap\<String, ?>, Collection\<?>, 단일 값을 사용한다. 문자열이 아닌 경우에는 타입변환이 일어난다.                                                                                                                     |
| @PathVariable  | URL에 포함된 path variable을 추가한다. 단일 값을 쓰거나, 여러 인자가인 경우 Map\<String, ?>을 사용할 수 있다. 문자열이 아닌 경우에는 타입변환이 일어난다.                                                                                                                                      |
| @RequestBody   | serialize 될 객체 또는 Mono, Flux 같은 Publisher 또는 ReactiveAdapterRegistry에 의해 지원하는 비동기 타입의 어느 값을 body 제공한다.                                                                                                                                       |
| @RequestParam  | 요청에 파라미터를 추가한다. Map\<String, ?> 또는 여러값인 경우 MultiValueMap\<String, ?>, Collection\<?>, 단일 값을 사용한다. 문자열이 아닌 경우에는 타입변환이 일어난다. Content-type이 application/x-www-form-urlencoded로 설정되어 있으면, 요청의 파라미터는 body로 encode 된다. 그 외의 경우는 url 쿼리 파라미터로 추가된다. |
| @RequestPart   | 문자열, Resource(org.springframework.http.codec.multipart.FilePart), Object(JSON 같은 값으로 encode 될 엔티티), HttpEntity(part의 콘텐츠와 헤더), Part(org.springframework.http.codec.multipart), 위의 값들을 가진 Publisher를 멀티파트의 한 파트를 추가한다.                        |
| @CookieValue   | 쿠키 값을 추가한다. Map\<String, ?> 또는 여러값인 경우 MultiValueMap\<String, ?>, Collection\<?>, 단일 값을 사용한다. 문자열이 아닌 경우에는 타입변환이 일어난다.                                                                                                                       |

## Return Values

| 종류                                    | 설명                                                                                                  |
| ------------------------------------- | --------------------------------------------------------------------------------------------------- |
| void, Mono                            | 요청을 수행하고, 응답의 콘텐츠를 버린다.                                                                             |
| HttpHeaders, Mono                     | 요청을 수행하고, 응답의 콘텐츠를 버리고 응답의 헤더를 리턴한다.                                                                |
| , Mono                                | 요청을 수행해고, 응답을 주어진 리턴타입으로 decode 한다.                                                                 |
| , Flux                                | 요청을 수행하고, 응답을 주어진 리턴타입의 스트림으로 decode 한다.                                                            |
| ResponseEntity, Mono\<ResponseEntity> | 요청을 수행하고, 응답의 콘텐츠를 버린다. 그리고 ResponseEntity를 상태와 헤더와 함께 리턴한다.                                        |
| ResponseEntity, Mono\<ResponseEntity> | 요청을 수행하고, 응답을 주어진 리턴타입으로 decode한다. 그리고 ResponseEntity를 상태와 헤더, 그리고 decode 된 body와 함께 리턴한다.          |
| Mono\<ResponseEntity\<Flux>>          | 요청을 수행하고, 응답을 주어진 리턴타입의 스트림으로 decode한다. 그리고 ResponseEntity를 상태와 헤더, 그리고 decode 된 body 스트림과 함께 리턴한다. |

## HttpExchangeInterface (X2BEE 공통 커스텀 어노테이션)

위 설명에서와 같이 REST Api endpoint를 관리하기 위해서는 WebClient 설정을 Bean으로 등록하여 사용하여야 합니다. X2BEE에서는 해당 endpoint 관리를 편리하게 하기 위해서 HttpExchangeInterface 커스텀 어노테이션을 지원합니다.

HttpExchangeInterface.java (개요)

```java
/**
 * Http Interface 용 커스텀 어노테이션
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HttpExchangeInterface {
    String baseUrl() default "";
    String defaultPath() default "";
    int responseTimeoutSeconds() default 0;
    int connectionTimeoutSeconds() default 10;
    boolean enableTokenAuth() default true;
    boolean useMemberToken() default false;
}
```

해당 어노테이션은 다음과 같은 항목들을 제공합니다.

| 종류                       | 설명                                                                                                                                                                                                     |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| baseUrl                  | Api endpoint url 설정값으로 Bean으로 등록하기 위하여 필수값임. 참고로 그냥 String값을 사용하면 해당값이 그대로 사용되고 ${app.apiUrl.event}와 같은 형태로 사용되면, application.yml에 등록된 환경변수값을 `env`.getProperty("app.apiUrl.event"); 같은 형태로 가져와 사용합니다. |
| defaultPath              | 해당 Api 서버의 ContextPath값으로 해당값이 있을 경우 baseUrl과 합쳐서 사용함. 해당값 역시 baseUrl과 동일하게 ${default.path} 형태를 제공합니다.                                                                                                 |
| responseTimeoutSeconds   | Http Client의 Response Timeout 설정값으로 기본값은 0 입니다.                                                                                                                                                        |
| connectionTimeoutSeconds | Http Client의 Connection Timeout 설정값으로 기본값은 10 입니다.                                                                                                                                                     |
| enableTokenAuth          | 헤더값에 Auth 토큰값을 포함할지 여부로서 기본값은 true입니다. 해당값은 기존 공통 RestApi Class의 enableTokenAuth값 함수를 그대로 가져왔습니다.                                                                                                      |
| useMemberToken           | 헤더값에 로그인 사용자 토큰값을 포함할지 여부로서 기본값은 false입니다. 해당 옵션은 enableTokenAuth값이 true일때만 동작합니다. 기존 공통 RestApi Class의 useMemberToken값 함수를 그대로 가져왔습니다.                                                                |

예시 인터페이스 사용:

{% code title="HttpEventSampleService.java" %}

```java
@HttpExchangeInterface(baseUrl = "${app.apiUrl.event}", defaultPath = "/api/event")
public interface HttpEventSampleService {
    @GetExchange("/samples/search2")
    ResponseEntity<Response> searchSamples2(@RequestHeader Map<String, Object> requestHeader,
                                           @RequestBody SampleRequest sampleRequest);
}
```

{% endcode %}

{% code title="HttpGoodsSampleService.java" %}

```java
@HttpExchangeInterface(baseUrl = "${app.apiUrl.goods}", defaultPath = "/api/goods", enableTokenAuth = false)
public interface HttpGoodsSampleService {
    @GetExchange("/samples/search2")
    ResponseEntity<Response> searchSamples2(@RequestBody SampleRequest sampleRequest);
}
```

{% endcode %}

위 예제와 같이 interface class에 @HttpExchangeInterface 어노테이션을 붙여줍니다.

해당 어노테이션이 있을 경우 스프링 시작 시점에 모든 패키지의 Class 파일을 검사하여 @HttpExchangeInterface이 붙은 Class 파일을 찾아 해당 어노테이션의 설정값으로 WebClient를 생성하고 Bean으로 등록해 줍니다.

사용 예시:

{% code title="HttpSample.java" %}

```java
public ResponseEntity<Response> http1SearchSamples1() {
    Map<String, Object> requestHeader = new HashMap<>();
    requestHeader.put("test1", "kang1");
    requestHeader.put("test2", "kang2");

    SampleRequest body = new SampleRequest();
    body.setName("test");

    ResponseEntity<Response> result = httpEventSampleService.searchSamples2(requestHeader, body);
    return result;
}
```

{% endcode %}

실행 시 enableTokenAuth 값이 true일 경우 헤더에 Authorization 값 또한 정상적으로 포함되어 요청이 전송되는 것을 확인할 수 있습니다.

<figure><img src="/files/i8vyi0ljMPDO2C78OoM7" alt=""><figcaption></figcaption></figure>

***


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://tech.x2bee.com/dev-guide/dev-start/markdown/http-interface.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
