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