ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Spring Boot] RestClient, HttpInterface를 활용한 HTTP 요청
    Spring Boot/기타 2024. 11. 17. 00:35
    반응형

    목차

       

       

      HttpInterface란?

      Spring 6에서는 HTTP 클라이언트를 사용하기 위한 새로운 방법으로 HttpInterface를 도입하였습니다. 이는 Spring HTTP 인터페이스를 통해 직관적이고 간편한 방법으로 HTTP 요청을 수행할 수 있게 합니다. 기존의 RestTemplate이나 WebClient와 달리, HttpInterface는 인터페이스를 선언하고 이를 구현하는 방식을 사용합니다.

      HttpInterface는 애노테이션과 프록시(Proxy) 패턴을 활용하여 HTTP 요청을 마치 메서드 호출처럼 사용할 수 있게 해줍니다. 이는 가독성이 좋고 테스트 편리하게 만들어 줍니다.

       

      주요 애노테이션 및 기능

      HTTP 메서드별 애노테이션

      • @HttpExchange : HTTP 인터페이스와 그 요청에 적용할 수 있는 기본 애노테이션으로, 인터페이스 수준에서 적용하는 경우 모든 요청에 공통된 속성을 지정하는데 유용
      • @PostExchange : HTTP POST 요청에 대한 애노테이션
      • @PutExchange : HTTP PUT 요청에 대한 애노테이션
      • @PatchExchange : HTTP PATCH 요청에 대한 애노테이션
      • @DeleteExchange : HTTP DELETE 요청에 대한 애노테이션

      메서드 매개변수

      • @RequestHeader : 요청 헤더 이름과 값을 추가 (Map 또는 MultiValueMap)
      • @PathVariable : 요청 URL에 포함된 경로 변수를 메서드 매개변수에 대체
      • @RequestBody : 직렬화할 객체 또는 Mono나 Flux와 같은 반응형 스트림으로 요청 본문을 제공
      • @RequestParam : 요청 매개변수 이름과 값을 추가 (Map 또는 MultiValueMap)
      • @CookieValue : 쿠키 이름과 값을 추가 (Map 또는 MultiValueMap)

       

      RestClient, HttpInterface 사용 예제

      1. Gradle 의존성 추가

      build.gradle

      dependencies {
          implementation 'org.springframework.boot:spring-boot-starter-web'
      }

       

      2. RestClient 설정 및 HttpInterface 구성

      HttpInterfaceConfig.java
      package com.example.httpinterface.config;
      
      import com.example.httpinterface.service.PostService;
      import java.time.Duration;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.boot.web.client.ClientHttpRequestFactories;
      import org.springframework.boot.web.client.ClientHttpRequestFactorySettings;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.http.client.ClientHttpRequestFactory;
      import org.springframework.web.client.RestClient;
      import org.springframework.web.client.support.RestClientAdapter;
      import org.springframework.web.service.invoker.HttpServiceProxyFactory;
      
      @Slf4j
      @Configuration
      public class HttpInterfaceConfig {
      
        private static final String JSON_PLACEHOLDER_URL = "https://jsonplaceholder.typicode.com";
      
        /**
         * JSONPlaceholder API를 위한 PostService 빈을 생성
         * <p> RestClient를 기반으로 JSONPlaceholder API와 통신할 PostService 인스턴스를 생성</p>
         *
         * @param restClient RestClient 객체
         * @return JSONPlaceholder API와 통신할 PostService 인스턴스
         */
        @Bean
        PostService jsonPlaceholderInterface(RestClient restClient) {
      
          // RestClient 객체를 사용하여 JSONPlaceholder API의 기본 URL을 설정
          RestClient postRestClient = restClient
              .mutate()
              .baseUrl(JSON_PLACEHOLDER_URL)
              .build();
      
          // RestClientAdapter 생성의 인스턴스를 생성
          RestClientAdapter restClientAdapter = RestClientAdapter.create(postRestClient);
      
          // HttpServiceProxyFactory를 사용하여 HTTP 인터페이스 프록시를 생성
          HttpServiceProxyFactory httpServiceProxyFactory = HttpServiceProxyFactory
              .builderFor(restClientAdapter)
              .build();
      
          return httpServiceProxyFactory.createClient(PostService.class);
        }
      
        /**
         * RestClient 빈을 생성
         *
         * @return RestClient 객체
         */
        @Bean
        public RestClient restClient() {
          return RestClient.builder()
              .requestFactory(customRequestFactory())
              .build();
        }
      
        /**
         * ClientHttpRequestFactory를 생성
         *
         * @return ClientHttpRequestFactory
         */
        ClientHttpRequestFactory customRequestFactory() {
          ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.DEFAULTS
              .withConnectTimeout(Duration.ofSeconds(5))  // 연결 타임아웃을 5초로 설정
              .withReadTimeout(Duration.ofSeconds(5)); // 읽기 타임아웃을 5초로 설정
          return ClientHttpRequestFactories.get(settings);
        }
      }

       

      3. 서비스 인터페이스와 DTO 클래스

      3_1. 서비스 인터페이스

      JSONPlaceholder API와 통신하기 위한 PostService 인터페이스를 정의합니다.

      package com.example.httpinterface.service;
      
      import com.example.httpinterface.dto.PostDto;
      import org.springframework.web.bind.annotation.PathVariable;
      import org.springframework.web.bind.annotation.RequestBody;
      import org.springframework.web.service.annotation.DeleteExchange;
      import org.springframework.web.service.annotation.GetExchange;
      import org.springframework.web.service.annotation.HttpExchange;
      import org.springframework.web.service.annotation.PostExchange;
      import org.springframework.web.service.annotation.PutExchange;
      
      @HttpExchange(url = "")
      public interface PostService {
      
        @GetExchange("/posts/{id}")
        PostDto.Response getPost(@PathVariable int id);
      
        @PostExchange("/posts")
        PostDto.Response createPost(@RequestBody PostDto.Request request);
      
        @PutExchange("/posts/{id}")
        PostDto.Response updatePost(@PathVariable int id, @RequestBody PostDto.Request request);
      
        @DeleteExchange("/posts/{id}")
        PostDto.Response deletePost(@PathVariable int id);
      }

       

      3_2. DTO 클래스

      요청 및 응답 데이터를 위한 DTO 클래스 PostDto를 정의합니다.

      package com.example.httpinterface.dto;
      
      import lombok.AllArgsConstructor;
      import lombok.Builder;
      import lombok.Getter;
      import lombok.NoArgsConstructor;
      
      public class PostDto {
      
        @Getter
        @Builder
        @AllArgsConstructor
        @NoArgsConstructor
        public static class Request {
      
          private int id;
          private String title;
          private String body;
          private int userId;
        }
      
        @Getter
        @Builder
        @AllArgsConstructor
        @NoArgsConstructor
        public static class Response {
      
          private int id;
          private String title;
          private String body;
          private int userId;
        }
      }

       

      4. 단위 테스트 작성

      RestClient, HttpInterface를 활용한 HTTP 요청 (GET, POST, PUT, DELETE)이 정상적으로 동작하는지 확인합니다.

       

      4_1. GET 요청 테스트

      GET 요청을 보내고, 응답의 ID가 요청한 ID와 같은지 확인합니다.

      @DisplayName("GET 요청: ID를 기준으로 포스트 조회 후 응답 ID 확인")
      @Test
      public void testGetRequest() throws Exception {
      
        // Given
        int postId = 1;
      
        // When
        PostDto.Response response = postService.getPost(postId);
        log.debug("response: {}", objectMapper.writeValueAsString(response));
      
        // Then
        assertAll(
            () -> assertNotNull(response),
            () -> assertEquals(1, response.getId())
        );
      }

       

      4_2. POST 요청 테스트

      POST 요청을 보내고, 응답의 title과 body가 요청한 값과 같은지 확인합니다.

      @DisplayName("POST 요청: 포스트 저장 후 응답의 title과 body 확인")
      @Test
      public void testPostRequest() throws Exception {
      
        // Given
        PostDto.Request postData = PostDto.Request.builder()
            .title("foo")
            .body("bar")
            .userId(1)
            .build();
      
        // When
        PostDto.Response response = postService.createPost(postData);
        log.debug("response: {}", objectMapper.writeValueAsString(response));
      
        // Then
        assertAll(
            () -> assertNotNull(response),
            () -> assertEquals("foo", response.getTitle()),
            () -> assertEquals("bar", response.getBody())
        );
      }

       

      4_3. PUT 요청 테스트

      PUT 요청을 보내고, 응답의 title과 body가 요청한 값과 같은지 확인합니다.

      @DisplayName("PUT 요청: 포스트 수정 후 응답의 title과 body 확인")
      @Test
      public void testPutRequest() throws Exception {
      
        // Given
        int postId = 1;
        PostDto.Request putData = PostDto.Request.builder()
            .id(postId)
            .title("foo")
            .body("bar")
            .userId(1)
            .build();
      
        // When
        PostDto.Response response = postService.updatePost(postId, putData);
        log.debug("response: {}", objectMapper.writeValueAsString(response));
      
        // Then
        assertAll(
            () -> assertNotNull(response),
            () -> assertEquals("foo", response.getTitle()),
            () -> assertEquals("bar", response.getBody())
        );
      }

       

      4_4. DELETE 요청 테스트

      DELETE 요청을 보내고, 응답이 빈 값인지 확인합니다.

      @DisplayName("DELETE 요청: 포스트 삭제 후 응답이 빈 값인지 확인")
      @Test
      public void testDeleteRequest() throws Exception {
      
        // Given
        int postId = 1;
      
        // When
        PostDto.Response response = postService.deletePost(postId);
        log.debug("response: {}", objectMapper.writeValueAsString(response));
      
        // Then
        assertAll(
            () -> assertNotNull(response),
            () -> assertEquals(null, response.getTitle()),
            () -> assertEquals(null, response.getBody())
        );
      }

       

       

      참고


      자세한 소스 코드는 Github Repository를 참조하세요.

       

      관련 글

      [Spring Boot] RestTemplate를 활용한 HTTP 요청

      [Spring Boot] RestClient를 활용한 HTTP 요청

      [Spring Boot] Apache HttpClient 5 기반 RestClient 구성하기

       

       

       

       

      반응형

      댓글

    Designed by Tistory.