[Spring Boot]HttpClient와 WebClient 사용해보기
- -
오랜만에 외부 API 연동을 하게 되어 기존에 사용중인 HttpClient 방식이 아닌 WebClient를 사용해보기로 했습니다.
오늘은 HttpClient, WebClient 각각의 사용 예제를 알아보겠습니다.
※ 테스트 환경
- Spring Boot 2.7
- JDK 11
WebClient는 Spring 5부터 등장한 HTTP 클라이언트 라이브러리입니다.
WebClient는 다음과 같은 특징이 있습니다.
- Spring Boot 지원: WebClient는 Spring WebFlux의 일부로, Spring Boot와 완벽히 통합됩니다.
- 비동기 처리: WebClient는 기본적으로 비동기(reactive)이며, 동기 처리도 지원합니다.
- 간결한 코드: 요청/응답 처리 코드가 간결하며, JSON 직렬화/역직렬화가 기본 지원됩니다.
- 확장성: 기본 설정 외에도 ExchangeFilterFunction을 통해 요청/응답 로깅, 인증 추가 등 쉽게 확장 가능합니다.
- JDK 11과 호환성: 내부적으로 JDK 11의 HTTP 클라이언트를 활용하며, 성능 최적화가 이루어집니다.
HttpClient 사용 방법부터 살펴보겠습니다.
예제에는 하루단어에 사용되는 API를 사용했습니다. API 호출시 단어 리스트를 응답받을 수 있습니다. 사용된 DTO, Controller는 다음과 같습니다.
import lombok.Data;
@Data
public class WordDto {
private Long wordSeq;
private String wordName;
private String exampleName;
private String wordMean;
private String pronounce;
}
import com.app.demo.common.components.WebClientApi;
import com.app.demo.common.dto.MessageDto;
import com.app.demo.domain.web.rest.dto.WordDto;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
@RequestMapping("/api")
public class TestApiController {
private final WebClientApi webClientApi;
@GetMapping("/test")
public ResponseEntity<MessageDto> getTestCompanyList() {
List<WordDto> wordDtoList = webClientApi.getWordList();
return ResponseEntity.ok().body(new MessageDto("success", wordDtoList));
}
}
Component로 생성된 WebClientApi에서는 코드드림 API를 호출합니다.
import com.app.demo.domain.web.rest.dto.WordDto;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.json.simple.JSONObject;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.client.WebClient;
@Slf4j
@Component
public class WebClientApi {
private static final String TEST_API_URL = "API 호출 주소";
private static final String CLIENT_SECRET = "API시크릿키";
public List<WordDto> getWordList() {
List<WordDto> companyList = new ArrayList<>();
try {
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("searchText", "");
// 1. HttpClient
JSONObject jsonObject = callHttpClientApi("/word", paramMap);
// 2. WebClient
// JSONObject jsonObject = callWebClientApi("/word", paramMap);
if (jsonObject != null) {
ObjectMapper objectMapper = new ObjectMapper();
if ("success".equals(String.valueOf(jsonObject.get("message")))) {
companyList = objectMapper.convertValue(jsonObject.get("result"),
new TypeReference<>() {
});
}
}
} catch (Exception e) {
log.error(e.getMessage());
}
return companyList;
}
}
HttpClient
JDK 11부터 기본적으로 제공되는 HttpClient는 모던한 HTTP/2 프로토콜을 지원하며, 고성능 네트워크 처리가 가능합니다.
[GET]
호출 주소와 Map 파라미터를 받는 callHttpClientApi 메소드입니다.
GET 방식으로 호출시 쿼리 파라미터를 주소뒤에 추가할 수 있도록 paramMap를 String 형태로 변환하여 uri를 생성하게 됩니다. 문자열로 응답 받은 결과는 JSONObject로 변환하여 반환합니다.
private JSONObject callHttpClientApi(
String callUrl,
Map<String, Object> paramMap
) {
JSONObject jsonObject = null;
try {
String query = paramMap.entrySet().stream()
.map(entry ->
URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8) + "=" +
URLEncoder.encode(String.valueOf(entry.getValue()), StandardCharsets.UTF_8))
.collect(Collectors.joining("&"));
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(TEST_API_URL + callUrl + "?" + query))
.header("Content-Type", "application/json")
.header("CLIENT-KEY", CLIENT_SECRET)
.GET()
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
log.info(response.body());
ObjectMapper objectMapper = new ObjectMapper();
jsonObject = objectMapper.readValue(response.body(), JSONObject.class);
} catch (Exception e) {
log.error(e.getMessage());
}
return jsonObject;
}
[POST]
POST 방식으로 호출시에는 다음과 같이 변경할 수 있습니다. ObjectMapper를 이용해 Map을 JSON 형식으로 변환하여 BodyPublishers.ofString에 담아 전송합니다.
ObjectMapper objectMapper = new ObjectMapper();
String paramJson = objectMapper.writeValueAsString(paramMap);
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(TEST_API_URL + callUrl))
.header("Content-Type", "application/json")
.header("CLIENT-KEY", CLIENT_SECRET)
.POST(BodyPublishers.ofString(paramJson))
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
log.info(response.body());
API를 호출해보면 결과값을 확인할 수 있습니다.
WebClient
WebClient를 사용한 방식을 살펴보겠습니다.
[GET]
HttpClient를 사용한 메소드와 동일한 인자값을 갖는 callWebClientApi 메소드를 생성했습니다.
paramMap에 있는 데이터를 MultiValueMap으로 변환하여 쿼리 파라미터로 추가합니다. 응답을 Mono<String>으로 변환하여 결과값을 JSONObject로 반환합니다.
private JSONObject callWebClientApi(
String callUrl,
Map<String, Object> paramMap
) {
JSONObject jsonObject = null;
try {
WebClient webClient = WebClient
.builder()
.baseUrl(TEST_API_URL)
.defaultHeader("Content-Type", "application/json")
.defaultHeader("CLIENT-KEY", CLIENT_SECRET)
.build();
MultiValueMap<String, String> multiValueMap = new LinkedMultiValueMap<>();
paramMap.forEach((key, value) -> multiValueMap.add(key, String.valueOf(value)));
String response = webClient
.get()
.uri(uriBuilder -> uriBuilder.path(callUrl)
.queryParams(multiValueMap)
.build())
.retrieve()
.bodyToMono(String.class)
.block();
ObjectMapper objectMapper = new ObjectMapper();
jsonObject = objectMapper.readValue(response, JSONObject.class);
} catch (Exception e) {
log.error(e.getMessage());
}
return jsonObject;
}
[POST]
POST 방식으로 호출할 때에는 다음과 같이 변경할 수 있습니다.
변환 과정없이 paramMap을 bodyValue에 추가합니다.
WebClient webClient = WebClient
.builder()
.baseUrl(TEST_API_URL)
.defaultHeader("Content-Type", "application/json")
.defaultHeader("CLIENT-KEY", CLIENT_SECRET)
.build();
String response = webClient.post()
.uri(callUrl)
.bodyValue(paramMap)
.retrieve()
.bodyToMono(String.class)
.block();
WebClient 사용시 다음과 같은 에러 메세지를 받을 수 있습니다.
이는 WebFlux 기본으로 설정된 코덱 메모리 사이즈인 256KB를 초과하여 발생한 에러입니다.
nested exception is org.springframework.core.io.buffer.DataBufferLimitException: Exceeded limit on max bytes to buffer : 262144
WebClient 빌드시에 maxInMemorySize 설정을 통해 이를 해결할 수 있습니다.
WebClient webClient = WebClient
.builder()
.baseUrl(API_URL)
// 1MB 설정
.codecs(configure -> configure.defaultCodecs().maxInMemorySize(1024 * 1024)
.build();
'Framework > Spring' 카테고리의 다른 글
[Spring Boot]Springdoc 적용하기 (0) | 2024.12.23 |
---|---|
[Spring Boot]압축 전송하기 (0) | 2024.12.06 |
[Spring Boot]대량 데이터 저장하기 (0) | 2024.10.17 |
[Spring Boot]Oracle DB 연결하기 (1) | 2024.09.02 |
[Error]Spring Security authenticate 302 (2) | 2024.09.02 |
소중한 공감 감사합니다.