공공데이터포털의 TAGO API 를 사용할 일이 있었다.
이상하게도 인증키를 쿼리파라미터로 넣는다.
이를 호출하기 위해 Webflux & Webclient 를 사용 중에 있었는데,
인증키값이 이상하게 인코딩되는 것을 발견했다.
public <T> T get(
ParameterizedTypeReference<T> typeReference,
TagoBusLaneApiRequestBodyDto requestBody
) {
return webClient
.get()
.uri(uriBuilder -> uriBuilder
.path("/getRouteAcctoThrghSttnList")
// 인증키 URI 쿼리파라미터
.queryParam("serviceKey", dataGoDecodingKey)
.queryParam("pageNo", "1")
.queryParam("numOfRows", "10")
.queryParam("_type", "json")
.queryParam("cityCode", "25")
.queryParam("routeId", "DJB30300004")
.build())
.retrieve()
.onStatus(HttpStatusCode::isError, WebClientErrorHandler.handleError())
.bodyToMono(typeReference)
.timeout(Duration.ofSeconds(1))
.retry(3)
.block();
}
하지만 아래와 같이 이상하게 값이 인코딩되어 요청되는 것을 확인할 수 있었다.
왜일까?
이유인 즉슨 WebClient 의 인코딩 정책과 UriBuilder 이다.
https://www.baeldung.com/webflux-webclient-parameters
If the default behavior doesn’t fit our requirements, we can change it. We need to provide a UriBuilderFactory implementation while building a WebClient instance. In this case, we’ll use the DefaultUriBuilderFactory class. To set encoding, we’ll call the setEncodingMode() method. The following modes are available:
- TEMPLATE_AND_VALUES: Pre-encode the URI template and strictly encode URI variables when expanded
- VALUES_ONLY: Do not encode the URI template, but strictly encode URI variables after expanding them into the template
- URI_COMPONENTS: Encode URI component value after expending URI variables
- NONE: No encoding will be applied
The default value is TEMPLATE_AND_VALUES.
위 baeldung 포스트에서 확인할 수 있듯이 WebClient 의 URI 인코딩 기본정책은 TEMPLATE_AND_VALUES
이다.
그렇다면 기본정책인 TEMPLATE_AND_VALUES
는 내부적으로 어떻게 인코딩하는 걸까 ??
DefaultUriBuilderFactory.EncodingMode :: Spring Docs
Pre-encode the URI template first, then strictly encode URI variables when expanded, with the following rules:
- For the URI template replace only non-ASCII and illegal (within a given URI component type) characters with escaped octets.
- For URI variables do the same and also replace characters with reserved meaning.
요컨대 strict uri encoding 을 지원한다는 것이다. 여기서 reserved 는 uri reserved characters 이다.
uri reserved characters 는 아래와 같다.
RFC 3986 section 2.2 Reserved Characters (January 2005)
! | # | $ | & | ' | ( | ) | * | + | , | / | : | ; | = | ? | @ | [ | ] |
---|
RFC 3986 section 2.3 Unreserved Characters (January 2005)
A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | - | . | _ | ~ |
하지만 나의 케이스인 .... Cohg%3D%3D
에는 예약캐릭터가 없다. 즉, 이미 인코딩된 값으로 보여진다.
근데 왜 .... Cohg%253D%253D
으로 나올까?
webClient 사용 시 request value 를 넣기 위해서는 아래와 같이 처리한다.
public <T> T get(
ParameterizedTypeReference<T> typeReference,
TagoBusLaneApiRequestBodyDto requestBody
) {
return webClient
.get()
.uri(uriBuilder -> uriBuilder
.path("/getRouteAcctoThrghSttnList")
.queryParam("serviceKey", dataGoDecodingKey) // Prevent re-encoding
,,,
}
즉 내부적으로 lambda 를 사용하여 uriBuilder 를 사용하게 되는데
uriBuilder 를 build 하면 내부적으로 DefaultUriBuilderFactory 에서
this.uriComponentsBuilder.build().expand(uriVars) 가 수행되고 디폴트로 인코딩이 안됐다고(false) 설정된다.
public UriComponents build() {
return build(false);
}
즉 따라서 이미 인코딩된 값이더라도 다시 인코딩된다는 것이다.
TEMPLATE_AND_VALUES
.uri(uriBuilder -> uriBuilder ,,,)
사용 시 중복인코딩 될 수 있음다양한 해결방안이 있지만 나의 경우 간단하게 URI 기본 EncodingMode 를 바꿔주었다.
그러게 왜 인증키값을 쿼리파라미터로 받아가지고,,,
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(BASE_URL);
factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);
webClient = WebClient
.builder()
.uriBuilderFactory(factory)
.baseUrl(BASE_URL)
.exchangeFunction(exchangeFunction)
.build();