WebClient를 사용하여 Get Method를 요청할때 사용하는 QueryParam에 인코딩 문제
다음과 같은 데이터 포맷이 있을때
private static final DateTimeFormatter OUTPUT_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
public static RequsetDto of(String start) {
// start = 2024-10-15
RequsetDto dto = new RequsetDto();
// Parse start and end dates
LocalDate startDate = LocalDate.parse(start, INPUT_FORMATTER);
// Format to the desired output format
dto.lastChangedFrom = startDate.atStartOfDay().atOffset(ZoneOffset.ofHours(9)).format(OUTPUT_FORMATTER);
return dto;
}
이런 코드로 데이터 데이트 포맷을 만들면 dto.lastChangedFrom은 2024-10-15T00:00:00.000+09:00 가 나온다.
내가 보내고 싶었던 queryParam은 lastChangedFrom=2024-10-15T00%3A00%3A00.000%2B09%3A00 데이터 였다.
하지만 WebClient로 전송시 인코딩이 되지않고 lastChangedFrom=2024-10-15T00:00:00.000+09:00 형태로 전송되는 문제가 발생헀다.
처음 시도한 방법은 dto.lastChangedFrom에 들어있는 값 자체를 직접 인코딩을 했다.
dto.lastChangedFrom = URLEncoder.encode(dto.lastChangedFrom);
이렇게 작성하면 내가 원하는 포맷 2024-10-15T00%3A00%3A00.000%2B09%3A00 데이터가 나타나고 이걸 WebClient에 QueryParam으로 전송하면 다음과 같은 포맷이 나온다.
2024-10-15T00%253A00%253A00.000%252B09%253A00
내가 원했던 데이터 포맷보다 훨씬 긴 문자열이 나왔다.
WebClient에서 인코딩하지 않았을땐 WebClient도 인코딩하지 않는데, 한번 인코딩한 데이터를 보내면 WebClient도 인코딩을 해버리는 문제가 발생.
해결방법은 WebClientConfig에서 DefaultUriBulderFactory를 설정해주는 것
다른 부분은 필요없고 defaultUriBuilderFactory 설정만 추가하면 된다.
다만, 다른 부분에 영향이 갈 수 있으니, 기존에 WebClient를 사용하는 코드가 있다면 @Qualifier 사용을 고려할 것
@Bean
public WebClient webClient() {
return WebClient.builder()
.uriBuilderFactory(defaultUriBuilderFactory())
.exchangeStrategies(exchangeStrategies())
.clientConnector(reactorClientHttpConnector())
.filter(logRequest())
.filter(logResponse())
.build();
}
private DefaultUriBuilderFactory defaultUriBuilderFactory() {
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE);
return factory;
}
private ExchangeStrategies exchangeStrategies() {
return ExchangeStrategies.builder()
.codecs(configurer -> configurer
.defaultCodecs()
.maxInMemorySize(MAX_MEMORY))
.build();
}
private ExchangeFilterFunction logRequest() {
return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
log.info("Request: {} {}", clientRequest.method(), clientRequest.url());
clientRequest.headers().forEach((name, values) -> values.forEach(value -> log.info("{}={}", name, value)));
return Mono.just(clientRequest);
});
}
private ExchangeFilterFunction logResponse() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
log.info("Response: {}", clientResponse.statusCode());
clientResponse.headers().asHttpHeaders().forEach((name, values) -> values.forEach(value -> log.info("{}={}", name, value)));
return Mono.just(clientResponse);
});
}
private ReactorClientHttpConnector reactorClientHttpConnector() {
return new ReactorClientHttpConnector(httpClient());
}
private HttpClient httpClient() {
return HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, MAX_TIMEOUT_MS);
}
'Java & Spring Boot' 카테고리의 다른 글
Repository 테스트 @AutoConfigureJsonTesters (2) | 2023.12.05 |
---|---|
서블릿 필터 & 스프링 인터셉터 (0) | 2022.10.04 |
Java 객체, 클래스, 인스턴스의 차이 (0) | 2022.07.18 |
자바에서 효율적으로 Exception 커스텀하기 (0) | 2022.02.08 |
H2 In-memory 사용시 delete log 확인 (0) | 2021.11.25 |