반응형
여승철
INTP 개발자
여승철
  • 분류 전체보기 (376)
    • CS (16)
      • 면접 준비 (7)
      • 운영체제 (0)
      • 네트워크 (2)
      • HTTP (6)
      • 스프링(Spring) IoC 컨테이너 (0)
      • 알고리즘 (1)
    • Web (13)
    • AWS (6)
    • Java (43)
    • JSP & Servlet (65)
      • 개념 (42)
      • 실습 (23)
    • Spring Framework (33)
    • Spring Boot (10)
    • Spring Data (22)
      • JPA (14)
      • Query DSL (7)
      • Redis (1)
    • Spring Security (9)
    • Spring Batch (4)
    • MyBatis (10)
    • Front-End (51)
      • JS (27)
      • Vue.js (17)
      • React (5)
      • JQuery (0)
      • d3.js (2)
    • DBMS (24)
      • SQL, RDBMS (16)
      • MongoDB (5)
      • Redis (3)
    • Kafka (3)
    • 리눅스 (Linux) (4)
    • 디자인 패턴 (3)
    • VCS (8)
    • API (0)
    • TOOL (3)
    • Reading Book (28)
      • 이펙티브 자바 (11)
      • Clean Code (10)
      • 1분 설명력 (4)
      • HOW TO 맥킨지 문제해결의 기술 (3)
    • C# (4)
    • NSIS (6)
    • ETC (11)

블로그 메뉴

  • 홈
  • 태그

인기 글

태그

  • JSTL
  • 게시판
  • Dao
  • 환경 세팅
  • servlet
  • HTTP
  • 로그인
  • 디자인 패턴
  • querydsl
  • 스트림
  • controller
  • JDBC
  • mybatis
  • ubuntu
  • jsp
  • 이펙티브 자바
  • 회원 관리
  • EC2
  • 맥킨지
  • Spring Batch

최근 댓글

최근 글

hELLO· Designed By 정상우.
여승철

INTP 개발자

Spring WebClient를 활용하여 HTTP 요청 처리하기
Spring Framework

Spring WebClient를 활용하여 HTTP 요청 처리하기

2023. 7. 27. 10:09
반응형

WebClient란?

WebClient가 도입되기 전에는 주로 RestTemplate을 사용하여 HTTP 요청을 보냈었습니다. RestTemplate은 Spring의 기본적인 HTTP 클라이언트 라이브러리로, 동기적으로 동작합니다. 즉, RestTemplate을 사용하면, 요청과 응답을 처리하기 위해 Blocking I/O 방식을 사용하여 스레드를 블로킹하고 기다립니다. 이는 스레드 자원의 낭비를 초래할 수 있고, 이로 인해 서버의 응답성이 저하될 수 있습니다. `WebClient`는 RestTemplate의 단점을 개선하기 위해 비동기적인 방식으로 동작하여, 동시에 여러 요청을 처리할 수 있습니다. 또한 리액티브 프로그래밍과 함께 사용하기에 적합합니다.

 

  1. 비동기 처리 (동기적인 처리도 가능)
  2. 리액티브 프로그래밍에 적합 

 

💁‍♂️ HTTP 요청을 보내는 방법 

`HttpURLConnection`:
JDK에 내장되어 있는 Java의 기본적인 HTTP 클라이언트 라이브러리입니다. 동기적인 방식으로 동작하여, 별도의 스레드 관리가 필요하고, 기능이 제한적입니다.

`RestTemplate`:
Spring의 HTTP 클라이언트 라이브러로, 동기적으로 동작합니다. 기본적인 기능은 편리하게 사용할 수 있지만, 별도의 스레드 관리가 필요합니다.

`HttpClient`: 
Apache에서 제공하는 HTTP 클라이언트 라이브러리입니다. 위 방식들보다 다양한 기능과 커스터마이징이 가능하여 더 복잡한 HTTP 요청 처리가 가능합니다. 동기적인 방식으로 동작하여, 별도의 비동기 처리를 위해선 스레드 관리가 필요합니다.    

`WebClient`:
Spring WebFlux에서 제공하는 비동기 HTTP 클라이언트입니다. 리액티브 프로그래밍을 지원하고, 논 블로킹 I/O 모델로 동작하여 높은 확장성과 성능을 제공합니다. 비동기적인 방식으로 동작하기 때문에 다수의 요청을 동시에 처리하는데 효율적입니다. 

✏️Spring Framework를 사용하는 프로젝트라면 `RestTemplate`을 사용하여 간편하고 일관성 있는 개발. 복잡한 커스터마이징이 필요한 경우 `HttpClient` 사용. 리액티브 프로그래밍과 논 블로킹 I/O를 지원하는경우는 WebClient를 활용하는 것을 고려해보면 됩니다.

✏️WebClient는 비동기적인 방식으로 동작하는HTTP 클라이언트지만,  동기적인 HTTP 요청을 처리할 수 있는 메서드(.block(), .blockOptional())를 제공합니다. 이 메서드를 활용하면 요청을 동기적으로 처리가 가능합니다. 

WebClient 사용 방법

1. 의존성 추가 및 Bean 등록

WebClient를 의존성 추가해주고 빈으로 등록해줍니다.

 

gradle:

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

 

java 메인 클래스:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.reactive.function.client.WebClient;

@SpringBootApplication
public class MainApplication {

    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
    }

    @Bean
    public WebClient.Builder webClientBuilder() {
        return WebClient.builder();
    }

}

 

2. GET 요청 보내기

기본 코드:

    private final WebClient webClient;

    public WebClientUtil(WebClient.Builder webClientBuilder) {
        this.webClient = webClientBuilder.build();
    }

    public <T> Mono<T> get(String baseUrl, String url, Class<T> responseType) {
        return webClient.get()
                .uri(baseUrl + url)        // API 엔드 포인트
                .retrieve()                // 요청 보내기
                .bodyToMono(responseType); // 응답 데이터를 Mono<T>로 변환
    }
  • `WebClient`를 사용하여 HTTP 요청을 생성합니다.
  • `uri()` 메서드를 사용하여 요청의 URI를 설정합니다.
  • `retrieve()` 메서드를 사용하여 요청을 보냅니다.
  • `bodyToMono()` 메서드를 사용하여 응답 데이터를 Mono로 변환합니다.

 

 

파라미터 포함:

파라미터가 존재하지 않는 경우에도 정상 작동. 

public <T> Mono<T> get(String baseUrl, String url, MultiValueMap<String, String> params, Class<T> responseType) {
    UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(baseUrl + url)
            .queryParams(params);

    URI uri = uriBuilder.build().toUri();

    return webClient.get()
            .uri(uri)
            .retrieve()
            .bodyToMono(responseType);
}

 

응답 데이터 처리 방법:

String baseUrl = "http://example.com";

// GET 요청 후 응답 처리
Mono<MyResponse> response = webClientUtil.get(baseUrl, "/path", MyResponse.class);
response.subscribe(
    data ->  {System.out.println("GET 요청 성공 - 응답 데이터: " + data);},
    error -> {System.err.println("GET 요청 실패 - 오류 발생: " + error.getMessage());},
    () ->    {System.out.println("GET 요청 완료"); }
);

// param
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("id", "1");
params.add("id", "2");
params.add("id", "3");


// GET 요청 후 JSON 응답 처리
Mono<MyResponse> jsonResponse = webClientUtil.get(baseUrl, "/json-path", params, MyResponse.class);
jsonResponse.subscribe(response -> {
    System.out.println("JSON 응답 데이터: " + response);
});

// GET 요청 후 XML 응답 처리
Mono<String> xmlResponse = webClientUtil.get(baseUrl, "/xml-data", String.class);
xmlResponse.subscribe(response -> {
    System.out.println("XML 응답 데이터: " + response);
});

// GET 요청 후 문자열 응답 처리
Mono<String> stringResponse = webClientUtil.get(baseUrl, "/string-data", String.class);
stringResponse.subscribe(response -> {
    System.out.println("문자열 응답 데이터: " + response);
});
  • `subscribe()`:
    일반적으로 GET 요청 후 응답 처리 방법은 `subscribe()` 메서드를 사용하여 응답 데이터를 처리합니다. 이 메서드는 비동기로 응답 데이터가 도착했을 때 처리할 콜백을 등록하는역할을합니다.
    `subscribe()`의 첫 번째 인자는 응답 데이터를 처리하는 콜백을 전달하고, 두 번째 인자로는 오류가 발생했을 때 처리하는 콜백을, 세 번째 인자는 응답처리가 완료되었을 때 호출되는 콜백입니다.
  • 응답 데이터 처리 방법:
    객체로 응답되면 객체로, 배열로 응답되면 배열로, 문자열로 응답되면 문자열로 처리해주면 됩니다. JSON으로 응답될 때는 객체로, XML로 응답될 때는 String으로 받아주면 됩니다.

 

3. POST 요청 보내기

post 메서드:

입맛대로 커스텀하면 됩니다. 

public <T> Mono<T> post(String baseUrl, String url, Object request, MediaType mediaType, Class<T> responseType, Map<String, String> headers) {
    return webClient.post()
            .uri(baseUrl + url)
            .contentType(mediaType)
            .headers(httpHeaders -> httpHeaders.addAll(headers))
            .bodyValue(request)
            .retrieve()
            .bodyToMono(responseType);
}

 

    post 호출:
// 요청 데이터 객체 생성
MyRequest request = new MyRequest();
request.setName("John");
request.setAge(30);

// 요청 헤더 정보 설정 (예시로 빈 맵을 전달)
Map<String, String> headers = new HashMap<>();

// POST 요청 보내기
Mono<MyResponse> jsonResponse = webClientUtil.post("http://example.com", "/path", request, MediaType.APPLICATION_JSON, MyResponse.class, headers);

// 응답 데이터 처리
jsonResponse.subscribe(response -> {
    System.out.println("JSON 응답 데이터: " + response);
});

`contentType()`:
적절한 `MediaType` 객체와 함께 호출하여 요청 본문 형식을 지정해주면 됩니다. `bodyValue()` 메서드를 활용해 `MediaType`과 맞는 형식의 데이터를 요청 본문에 담아 보내면 됩니다. 

  • MediaType.APPLICATION_JSON: JSON 데이터 요청 본문에 담기
  • MediaType.APPLICATION_FORM_URLENCODED: 폼 데이터 요청 본문에 담기
  • MediaType.APPLICATION_XML: XML 데이텨 요청 본문에 담기

 

4. PUT, DELETE 요청 보내기

put, delete 메서드:

추가적으로 커스텀하고 싶으면 위를 참고하면 됩니다.

public <T> Mono<T> put(String baseUrl, String url, Object request, Class<T> responseType) {
    return webClient.put()
            .uri(baseUrl + url)
            .bodyValue(request)
            .retrieve()
            .bodyToMono(responseType);
}

public Mono<Void> delete(String baseUrl, String url) {
    return webClient.delete()
            .uri(baseUrl + url)
            .retrieve()
            .bodyToMono(Void.class);
}

 

put, delete 요청:

// PUT 요청
Mono<MyResponse> putResponse = webClientUtil.put(baseUrl, "/path/1", requestBody, MyResponse.class);

// DELETE 요청
Mono<Void> deleteResponse = webClientUtil.delete(baseUrl, "/path/1");

// 결과 처리
putResponse.subscribe(response -> System.out.println("PUT 응답: " + response));
deleteResponse.subscribe(() -> System.out.println("DELETE 성공"));

 

5. 응답의 에러 및 오류 처리

`doOnSuccess` & `doOnError` :

    public <T> Mono<T> get(String baseUrl, String url, Class<T> responseType) {
        return webClient.get()
                .uri(baseUrl + url)
                .retrieve()
                .bodyToMono(responseType)
                .doOnSuccess(response -> System.out.println("응답 데이터: " + response))
                .doOnError(error -> System.err.println("오류 발생: " + error));
    }

`doOnSuccess`와 `doOnError` 메서드는 요청을 보낸후 성공적으로 응답을 받았을 때와 오류가 발생했을 때 실행될 될동작을 지정합니다. doOnSuccess와 doOnError는 Mono나 Flux의 연산자로서, 비동기적인 응답 처리와 관련하여 다른 로직을 수행하기 위해 사용됩니다.

 

`onStatus`:

public <T> Mono<T> get(String url, Class<T> responseType) {
    return webClient.get()
            .uri(url)
            .retrieve()
            .onStatus(status -> status.equals(HttpStatus.NOT_FOUND), this::handleNotFoundError)
            .onStatus(status -> status.isError(), this::handleErrorResponse)
            .bodyToMono(responseType);
}

// 404 Not Found 에러를 처리하는 함수입니다.
private static Mono<? extends Throwable> handleNotFoundError(ClientResponse clientResponse) {
    return clientResponse.createException()
            .flatMap(error -> Mono.error(new RuntimeException("Not Found")));
}

private Mono<? extends Throwable> handleErrorResponse(ClientResponse response) {
    return response.bodyToMono(String.class)
            .flatMap(errorBody -> {
                System.err.println("Error Status Code: " + response.statusCode());
                System.err.println("Error Response Body: " + errorBody);
                return Mono.error(new RuntimeException("HTTP Error: " + response.statusCode()));
            });
}

`onStatus`는 응답 상태 코드가 오류인 경우에만 실행되는로직입니다. 주로 오류 상태 코드에 따라 특정 동작을 수행하거나 에러를 처리하는 로직을 추가할 때 사용합니다. 만약 첫 번째 `onStatus()`의 조건이 true가 되어 걸리게 되면 나머지 뒤에 `onStatus()`는 무시 됩니다.  

 

💁‍♂️ `.onStatus`와 `.doOnError` 차이

둘 다 오류 처리와 관련된 메서드지만,  `.onStatus`는 응답 상태 코드를 기준으로 오류 처리를 하고, `.doOnError`는 Mono나 Flux의 오류 처리를 위해 사용됩니다. 따라서 두 메서드는 서로 다른 상황에서 사용되며, 기능도 다르게 동작합니다.

WebClient 동기적으로 사용하기

`WebClient`는 기본적으로 비동기적인 방식으로 동작합니다. `WebClient`가 동기적인 호출을 수행하기 위해선 `block()` 메서드를 사용해야합니다. `block()` 메서드를 호출하면 응답이 도착할 때까지 현재 스레드가 블록되고, 응답을 받은 후 해당 값을 반환합니다.  

import org.springframework.web.reactive.function.client.WebClient;

public class WebClientSyncExample {

    public static void main(String[] args) {
        String apiUrl = "http://example.com";

        WebClient webClient = WebClient.create(apiUrl);

        String response = webClient.get()
                .uri("/path")
                .retrieve()
                .bodyToMono(String.class)
                .block(); // 동기적으로 호출하여 응답을 받습니다.

        System.out.println("Response: " + response);
    }
}
반응형

'Spring Framework' 카테고리의 다른 글

Spring SpEL: Spring Expression Language 스프링 표현 언어  (0) 2023.08.09
Spring @RequestParam/@PathVariable/@RequestBody/@RequestHeader/@CookieValue  (0) 2023.05.25
Spring web.xml, root-context.xml, servlet-context.xml  (0) 2023.05.23
Spring @ControllAdvice와 @ExceptionHandler로 전역 예외처리 하기  (0) 2022.10.25
로그인 처리를 위한 UserDetailsService  (0) 2022.10.14
    여승철
    여승철

    티스토리툴바