Webclient Query Param 에 대하여 중복 인코딩 이슈해결기

tony·2024년 10월 1일
0

이슈해결기

목록 보기
3/3

Episode 📜


공공데이터포털의 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();
}

하지만 아래와 같이 이상하게 값이 인코딩되어 요청되는 것을 확인할 수 있었다.

  • 입력한 인증키 : ".... Cohg%3D%3D"
  • 실제 요청된 인증키 : ".... Cohg%253D%253D"

왜일까?

이유인 즉슨 WebClient 의 인코딩 정책과 UriBuilder 이다.

WebClient 인코딩 정책

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 는 아래와 같다.

percent-encoding :: wikipedia

RFC 3986 section 2.2 Reserved Characters (January 2005)

!#$&'()*+,/:;=?@[]

RFC 3986 section 2.3 Unreserved Characters (January 2005)

ABCDEFGHIJKLMNOPQRSTUVWXYZ
abcdefghijklmnopqrstuvwxyz
0123456789-._~

하지만 나의 케이스인 .... Cohg%3D%3D 에는 예약캐릭터가 없다. 즉, 이미 인코딩된 값으로 보여진다.

근데 왜 .... Cohg%253D%253D 으로 나올까?

중복 인코딩 이슈

WebClient 사용할때 주의 (3편)

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);
}

즉 따라서 이미 인코딩된 값이더라도 다시 인코딩된다는 것이다.

Reason 🤷‍♂️


  • WebClient 의 URI 기본 EncodingMode 는 TEMPLATE_AND_VALUES
  • WebClient 의 .uri(uriBuilder -> uriBuilder ,,,) 사용 시 중복인코딩 될 수 있음

Fix 🔧


다양한 해결방안이 있지만 나의 경우 간단하게 URI 기본 EncodingMode 를 바꿔주었다.
그러게 왜 인증키값을 쿼리파라미터로 받아가지고,,,

  • URI 기본 EncodingMode 변경 -> URI 인코딩 정책이 변경됨.
  • UriComponentsBuilder 를 활용하여 해결 -> 처리과정 중 Exception 이 발생할 수 있어 이를 try-catch 로 해결해야함. 불필요한 코드가 발생함.
  • 직접 query 에 대한 하드코딩(e.g. url = "apiPath?value=1") -> 가장 최악인 방법. 하드코딩된 값으로 인해 기획 변경에 따른 기능변경이 불가능함.
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(BASE_URL);
factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);
webClient = WebClient
  .builder()
  .uriBuilderFactory(factory)
  .baseUrl(BASE_URL)
  .exchangeFunction(exchangeFunction)
  .build();
profile
내 코드로 세상이 더 나은 방향으로 나아갈 수 있기를

0개의 댓글