원본 글: https://www.baeldung.com/webflux-webclient-parameters
1. REST API Endpoints
다음과 같은 시나리오가 있다고 생각합시다.
- /products
- /products/{id}
- /products/{id}/attributes/{attributedId}
- /products/?name=[name]&deliveryDate={deliveryDate} ..
- /products/?tag[]=[tag1]
- /products/?category=[categoray1]&
Webclient를 사용할 때 위 시나리오에서 어떻게 파라미터를 설정할지 작성해봅니다.
참고로 위 url에서 tag, category는 배열형태로 가져오는데 이에 대해 정해진 포맷이 없다보니 여러 방법으로 가져올 수 있게 되었습니다.
위에서는 2가지 방법을 사용하고 있으므로 2가지 방법 모두 살펴봅니다.
2. WebClient Setup
먼저 WebClient 인스턴스를 생성해야합니다. Mokito
를 이용해서 요청 url이 제대로 이루어졌는지 확인도 진행할겁니다.
원본 글에서는 코드를 따로따로 설명하고 있지만 전체적으로 보는게 편합니다.
@SpringBootTest
class SpringWebfluxWebClientWithParamApplicationTests {
private ExchangeFunction exchangeFunction;
private WebClient webClient;
private String BASE_URL = "http://localhost:8080";
private final ArgumentCaptor<ClientRequest> argumentCaptor = ArgumentCaptor.forClass(ClientRequest.class);
@Test
void contextLoads() {
}
@BeforeEach
void setUp() {
exchangeFunction = mock(ExchangeFunction.class);
ClientResponse mockResponse = mock(ClientResponse.class);
when(mockResponse.bodyToMono(String.class)).thenReturn(Mono.just("test"));
when(exchangeFunction.exchange(argumentCaptor.capture()))
.thenReturn(Mono.just(mockResponse));
webClient = WebClient
.builder()
.baseUrl("http://localhost:8080")
.exchangeFunction(exchangeFunction)
.build();
}
@Test
public void urlTest() {
webClient.get()
.uri("/products")
.retrieve()
.bodyToMono(String.class)
.onErrorResume(e -> Mono.empty())
.block();
verifyCalledUrl("/products");
}
private void verifyCalledUrl(String relativeUrl) {
ClientRequest request = argumentCaptor.getValue();
assertEquals(String.format("%s%s", BASE_URL, relativeUrl), request.url().toString());
verify(this.exchangeFunction).exchange(request);
verifyNoMoreInteractions(this.exchangeFunction);
}
}
- BASE_URL인 localhost:8080으로 요청을 보낼 것입니다.
- 각 테스트를 진행하기 전에 webclient 인스턴스를 세팅해줍니다.
verifyCalledUrl()
함수는 검증용 헬퍼 함수라고 생각하시면 됩니다.urlTest()
는 해당 포맷을 기준으로 앞으로의 테스트를 진행할 예정입니다. 하나의 룰이라고 보시면 됩니다.
3. URI Path Component
먼저 어떠한 변수 세그먼트가 없는 간단한 경우부터 시작하겠습니다.
@Test
public void uriComponentTest() {
webClient.get()
.uri("/products")
.retrieve()
.bodyToMono(String.class)
.onErrorResume(e -> Mono.empty())
.block();
verifyCalledUrl("/products");
}
다음은 하나의 변수가 주어질 때 테스트하는 방법입니다.
@Test
public void uriComponentVariableTest() {
webClient.get()
.uri(uriBuilder -> uriBuilder.path("/products/{id}").build(2))
.retrieve()
.bodyToMono(String.class)
.onErrorResume(e -> Mono.empty())
.block();
verifyCalledUrl("/products/2");
}
위 방식과 비슷하게, 여러개의 변수를 주어줄때 테스트하는 방법입니다.
@Test
public void uriComponentMultiVariableTest() {
webClient.get()
.uri(uriBuilder -> uriBuilder.path("/products/{id}/attributes/{attributeId}").build(2,13))
.retrieve()
.bodyToMono(String.class)
.onErrorResume(e -> Mono.empty())
.block();
verifyCalledUrl("/products/2/attributes/13");
}
이렇듯 여러개의 변수에 대해서도 대응이 가능합니다만, url의 최대길이를 넘으면 안되고 순서가 맞게 보장되어야한다는 주의점이 있습니다.
4. URI Query Parameters
다음은 쿼리파라미터를 넘기는 방법에 대해서 알아보겠습니다.
4.1 Single Value Parameters
여기서 말하는 Single Value란 리스트형태가 아닌 단일 구성으로 되어있는 변수들을 말합니다.
코드를 보면 이해가 되실겁니다.
@Test
public void uriComponentSingleValueTest() {
webClient.get()
.uri(uriBuilder -> uriBuilder.path("/products")
.queryParam("name", "AndroidPhone")
.queryParam("color", "black")
.queryParam("deliveryDate", "13/04/2019")
.build())
.retrieve()
.bodyToMono(String.class)
.onErrorResume(e -> Mono.empty())
.block();
verifyCalledUrl("/products/?name=AndroidPhone&color=black&deliveryDate=13/04/2019");
}
위의 테스트에서는 키-값을 바로바로 설정해주었지만 플레이스 홀더를 통해 값을 대체할 수도 있습니다.
@Test
public void uriComponentSingleValueByPlaceHolderTest() {
webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/products/")
.queryParam("name", "{title}")
.queryParam("color", "{authorId}")
.queryParam("deliveryDate", "{date}")
.build("AndroidPhone", "black", "13/04/2019"))
.retrieve()
.bodyToMono(String.class)
.onErrorResume(e -> Mono.empty())
.block();
verifyCalledUrl("/products/?name=AndroidPhone&color=black&deliveryDate=13%2F04%2F2019");
}
검증하는데를 보면 '/'가 인코딩된 것을 확인할 수 있습니다.
RFC3986(표준)에서는 '/'를 꼭 인코딩할 필요 없지만 몇몇 서버 애플리케이션에서는 인코딩해야할 수도 있습니다. 뒤의 섹션에서 이걸 해결하는 방법도 가이드 하고 있습니다.
4.2 Array Parameters
쿼리 문자열(query string)에서 배열을 전달할 때, 정해진 공식적인 규칙이 없습니다.
그래서 프로젝트마다 다르게 표현될 수 있고, 사용 중인 프레임워크에 따라 방식이 달라질 수 있습니다.
이 글에서는 대표적으로 사용되는 방법들을 소개합니다.
@Test
public void uriComponentArrayParameterTest() {
webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/products/")
.queryParam("tag[]", "Snapdragon", "NFC")
.build())
.retrieve()
.bodyToMono(String.class)
.onErrorResume(e -> Mono.empty())
.block();
verifyCalledUrl("/products/?tag%5B%5D=Snapdragon&tag%5B%5D=NFC");
}
최종 URL을 보면 여러 개의 tag 파라미터가 포함되어 있고, 가 인코딩된 상태로 들어간 걸 알 수 있습니다.
queryParam() 메서드는 여러 개의 값을 한 번에 받을 수 있어서, 여러 번 호출할 필요가 없습니다.
다른방법으로는 대괄호를 생략하고 하나의 키값으로 여러 값들을 넣게할 수 있습니다.
@Test
public void uriComponentArrayParameterOmitSquareBracketsTest() {
webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/products/")
.queryParam("category", "Phones", "Tablets")
.build())
.retrieve()
.bodyToMono(String.class)
.onErrorResume(e -> Mono.empty())
.block();
verifyCalledUrl("/products/?category=Phones&category=Tablets");
}
마지막으로, 배열을 인코딩하는 데 널리 사용되는 방법이 하나 더 있는데, 그것은 쉼표로 구분된 값을 전달하는 것입니다. 이전 예제를 쉼표로 구분된 값으로 변환해 보겠습니다.
@Test
public void uriComponentArrayParameterByStringJoin() {
webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/products/")
.queryParam("category", String.join(",", "Phones", "Tablets"))
.build())
.retrieve()
.bodyToMono(String.class)
.onErrorResume(e -> Mono.empty())
.block();
verifyCalledUrl("/products/?category=Phones,Tablets");
}
String 클래스의 join()
함수를 통해서 구분기호를 넣어서 나타냈습니다.
5. Encoding Mode
이전에 URL 인코딩에 대해 언급했었습니다.
만약 기본 동작이 요구에 맞지 않으면, 이를 변경할 수 있습니다.
WebClient 인스턴스를 만들 때, UriBuilderFactory 구현체를 제공해야 하여 이를 해결할 수 있습니다.
이 경우, DefaultUriBuilderFactory 클래스를 사용합니다.
setEncodingMode()함수를 통해서 인코딩을 셋팅할 수 있으며, 다음과 같은 모드를 가집니다.
• TEMPLATE_AND_VALUES: 템플릿과 값 모두 인코딩.
• VALUES_ONLY: 템플릿은 그대로 두고 값만 인코딩.
• URI_COMPONENTS: 확장된 변수 후 URI 구성 요소만 인코딩.
• NONE: 인코딩하지 않음.
기본값인 TEMPLATE_AND_VALUES
를 URI_COMPONENTS
로 변경해서 확인해보겠습니다.
@Test
public void uriComponentEncodingModeTest() {
DefaultUriBuilderFactory defaultUriBuilderFactory = new DefaultUriBuilderFactory(BASE_URL);
defaultUriBuilderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.URI_COMPONENT);
webClient = WebClient
.builder()
.uriBuilderFactory(defaultUriBuilderFactory)
.baseUrl(BASE_URL)
.exchangeFunction(exchangeFunction)
.build();
webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/products/")
.queryParam("name", "AndroidPhone")
.queryParam("color", "black")
.queryParam("deliveryDate", "13/04/2019")
.build())
.retrieve()
.bodyToMono(String.class)
.onErrorResume(e -> Mono.empty())
.block();
verifyCalledUrl("/products/?name=AndroidPhone&color=black&deliveryDate=13/04/2019");
}
6. 결론
이 글에서는 WebClient와 DefaultUriBuilder를 사용하여 다양한 유형의 URI를 빌드하는 방법을 알아보았습니다.
그 과정에서 다양한 유형과 형식의 쿼리 매개변수를 다루었습니다. 마지막으로 URL 빌더의 기본 인코딩 모드를 변경하여 마무리했습니다.
'Baeldung번역&공부 > Spring-Reactive' 카테고리의 다른 글
Spring WebClient와 Filter(Spring WebClient Filters) (0) | 2025.03.06 |
---|---|
Spring WebClient vs RestTemplate (0) | 2025.03.02 |
Spring WebClient (0) | 2025.03.01 |
WebFlux-Retry(Guide to Retry in Spring WebFlux) (0) | 2025.02.28 |
Mono와 Flux의 차이점(Difference Between Flux and Mono) (0) | 2025.02.27 |