1. 개요
많은 프레임워크와 프로젝트에서 반응형 프로그래밍과 비동기식 요청 처리 를 도입하고 있습니다 . 따라서 Spring 5 는 WebFlux 프레임워크 의 일부로 반응형 WebClient 구현을 도입했습니다 .
이 사용방법(예제)에서는 WebClient 를 사용하여 REST API Endpoints을 반응적 으로 사용하는 방법을 배웁니다 .
2. REST API 엔드포인트
시작하려면 다음 GET Endpoints 을 사용하여 샘플 REST API 를 정의해 보겠습니다 .
- /products – 모든 제품 가져오기
- /products/{id} – ID로 제품 가져오기
- /products/{id}/attributes/{attributeId} – ID로 제품 속성 가져오기
- /products/?name={이름}&deliveryDate={배송일}&color={color} – 제품 찾기
- /products/?tag[]={tag1}&tag[]={tag2} – 태그로 제품 가져오기
- /products/?category={category1}&category={category2} – 카테고리별로 제품 가져오기
여기서 우리는 몇 가지 다른 URI를 정의했습니다. 잠시 후 WebClient 를 사용하여 각 유형의 URI를 빌드하고 전송하는 방법을 알아낼 것입니다 .
태그 및 카테고리로 제품을 가져오기 위한 URI에는 쿼리 매개변수로 배열이 포함되어 있습니다. 그러나 URI에서 배열을 표현하는 방법에 대한 엄격한 정의가 없기 때문에 구문이 다릅니다 . 이것은 주로 서버 측 구현에 따라 다릅니다. 따라서 두 가지 경우를 모두 다룰 것입니다.
3. 웹클라이언트 설정
먼저 WebClient 인스턴스를 만들어야 합니다 . 이 문서에서는 유효한 URI가 요청되었는지 확인하기 위해 모의 개체 를 사용합니다.
클라이언트 및 관련 모의 개체를 정의해 보겠습니다.
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("https://example.com/api")
.exchangeFunction(exchangeFunction)
.build();
또한 클라이언트의 모든 요청 앞에 추가될 기본 URL을 전달합니다.
마지막으로 특정 URI가 기본 ExchangeFunction 인스턴스 에 전달되었는지 확인하기 위해 다음 도우미 메서드를 사용합니다.
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);
}
WebClientBuilder 클래스에는 UriBuilder 인스턴스를 인수로 제공하는 uri ( ) 메서드 가 있습니다. 일반적으로 다음과 같은 방식으로 API를 호출합니다.
webClient.get()
.uri(uriBuilder -> uriBuilder
//... building a URI
.build())
.retrieve()
.bodyToMono(String.class)
.block();
이 사용방법(예제)에서는 URI를 구성하기 위해 UriBuilder 를 광범위하게 사용할 것 입니다. 다른 메서드를 사용하여 URI를 빌드한 다음 생성된 URI를 문자열로 전달할 수 있다는 점은 주목할 가치가 있습니다.
4. URI 경로 구성 요소
경로 구성 요소는 슬래시(/)로 구분된 일련의 경로 세그먼트로 구성됩니다 . 먼저 URI에 변수 세그먼트인 /products 가 없는 간단한 경우부터 시작하겠습니다 .
webClient.get()
.uri("/products")
.retrieve()
.bodyToMono(String.class)
.block();
verifyCalledUrl("/products");
이 경우 문자열 을 인수로 전달할 수 있습니다.
다음으로 /products/{id} 엔드포인트를 가져오고 해당 URI를 빌드합니다.
webClient.get()
.uri(uriBuilder - > uriBuilder
.path("/products/{id}")
.build(2))
.retrieve()
.bodyToMono(String.class)
.block();
verifyCalledUrl("/products/2");
위의 코드에서 실제 세그먼트 값이 build() 메서드로 전달되는 것을 볼 수 있습니다.
비슷한 방식으로 /products/{id}/attributes/{attributeId} 엔드포인트 에 대한 여러 경로 세그먼트가 있는 URI를 만들 수 있습니다 .
webClient.get()
.uri(uriBuilder - > uriBuilder
.path("/products/{id}/attributes/{attributeId}")
.build(2, 13))
.retrieve()
.bodyToMono(String.class)
.block();
verifyCalledUrl("/products/2/attributes/13");
URI는 필요한 만큼 많은 경로 세그먼트를 가질 수 있지만 최종 URI 길이는 제한을 초과하지 않아야 합니다. 마지막으로 build() 메서드 에 전달된 실제 세그먼트 값의 올바른 순서를 유지해야 한다는 점을 기억해야 합니다 .
5. URI 쿼리 매개변수
일반적으로 쿼리 매개변수는 title=Baeldung 과 같은 간단한 키-값 쌍 입니다. 이러한 URI를 빌드하는 방법을 살펴보겠습니다.
5.1. 단일 값 매개변수
단일 값 매개변수로 시작하여 /products/?name={name}&deliveryDate={deliveryDate}&color={color} 엔드포인트를 사용합니다. 쿼리 매개변수를 설정하기 위해 UriBuilder 인터페이스 의 queryParam() 메서드를 호출합니다.
webClient.get()
.uri(uriBuilder - > uriBuilder
.path("/products/")
.queryParam("name", "AndroidPhone")
.queryParam("color", "black")
.queryParam("deliveryDate", "13/04/2019")
.build())
.retrieve()
.bodyToMono(String.class)
.block();
verifyCalledUrl("/products/?name=AndroidPhone&color=black&deliveryDate=13/04/2019");
여기에서 세 개의 쿼리 매개변수를 추가하고 실제 값을 즉시 할당했습니다. 반대로 정확한 값 대신 자리 표시자를 남길 수도 있습니다.
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)
.block();
verifyCalledUrl("/products/?name=AndroidPhone&color=black&deliveryDate=13%2F04%2F2019");
이는 빌더 객체를 체인에서 추가로 전달할 때 특히 유용할 수 있습니다.
위의 두 코드 스니펫 사이에는 한 가지 중요한 차이점이 있습니다 . 예상 URI에 주의를 기울이면 URI가 다르게 인코딩 된다는 것을 알 수 있습니다 . 특히 마지막 예제에서 슬래시 문자 ( / ) 가 이스케이프되었습니다.
일반적으로 RFC3986 은 쿼리에서 슬래시 인코딩을 요구하지 않습니다. 그러나 일부 서버 측 애플리케이션에는 이러한 변환이 필요할 수 있습니다. 따라서 이 사용방법(예제)의 뒷부분에서 이 동작을 변경하는 방법을 살펴보겠습니다.
5.2. 배열 매개변수
값의 배열을 전달해야 할 수도 있으며 쿼리 문자열에서 배열을 전달하는 데 엄격한 규칙이 없습니다. 따라서 쿼리 문자열의 배열 표현은 프로젝트마다 다르며 일반적으로 기본 프레임워크에 따라 다릅니다 . 이 기사에서는 가장 널리 사용되는 형식을 다룰 것입니다.
/products/?tag[]={tag1}&tag[]={tag2} 엔드포인트 부터 시작하겠습니다 .
webClient.get()
.uri(uriBuilder - > uriBuilder
.path("/products/")
.queryParam("tag[]", "Snapdragon", "NFC")
.build())
.retrieve()
.bodyToMono(String.class)
.block();
verifyCalledUrl("/products/?tag%5B%5D=Snapdragon&tag%5B%5D=NFC");
보시다시피 최종 URI에는 여러 태그 매개변수와 인코딩된 대괄호가 포함되어 있습니다. queryParam () 메서드는 변수 인수를 값으로 허용하므로 메서드를 여러 번 호출할 필요가 없습니다.
또는 대괄호를 생략하고 키 는 같지만 값이 다른 여러 쿼리 매개변수를 전달할 수 있습니다. /products/?category={category1}&category={category2} :
webClient.get()
.uri(uriBuilder - > uriBuilder
.path("/products/")
.queryParam("category", "Phones", "Tablets")
.build())
.retrieve()
.bodyToMono(String.class)
.block();
verifyCalledUrl("/products/?category=Phones&category=Tablets");
마지막으로 배열을 인코딩하는 데 광범위하게 사용되는 방법이 하나 더 있는데, 쉼표로 구분된 값을 전달하는 것입니다. 이전 예제를 쉼표로 구분된 값으로 변환해 보겠습니다.
webClient.get()
.uri(uriBuilder - > uriBuilder
.path("/products/")
.queryParam("category", String.join(",", "Phones", "Tablets"))
.build())
.retrieve()
.bodyToMono(String.class)
.block();
verifyCalledUrl("/products/?category=Phones,Tablets");
쉼표로 구분된 문자열을 만들기 위해 String 클래스 의 join() 메서드 를 사용 하고 있습니다. 응용 프로그램에서 예상하는 다른 구분 기호를 사용할 수도 있습니다.
6. 인코딩 모드
이전에 URL 인코딩을 어떻게 언급했는지 기억하십니까?
기본 동작이 요구 사항에 맞지 않으면 변경할 수 있습니다. WebClient 인스턴스 를 빌드하는 동안 UriBuilderFactory 구현 을 제공해야 합니다. 이 경우 DefaultUriBuilderFactory 클래스를 사용합니다. 인코딩을 설정하기 위해 setEncodingMode() 메서드를 호출합니다. 다음 모드를 사용할 수 있습니다.
- TEMPLATE_AND_VALUES : URI 템플릿을 미리 인코딩하고 확장 시 URI 변수를 엄격하게 인코딩합니다.
- VALUES_ONLY : URI 템플릿을 인코딩하지 않고 URI 변수를 템플릿으로 확장한 후 엄격하게 인코딩
- URI_COMPONENTS : URI 변수를 확장한 후 URI 구성 요소 값을 인코딩합니다.
- NONE : 인코딩이 적용되지 않습니다.
기본값은 TEMPLATE_AND_VALUES 입니다. 모드를 URI_COMPONENTS 로 설정해 보겠습니다 .
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(BASE_URL);
factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.URI_COMPONENT);
webClient = WebClient
.builder()
.uriBuilderFactory(factory)
.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)
.block();
verifyCalledUrl("/products/?name=AndroidPhone&color=black&deliveryDate=13/04/2019");
물론 URI 생성을 수동으로 처리하기 위해 완전한 사용자 정의 UriBuilderFactory 구현을 제공할 수 있습니다.
7. 결론
이 문서에서는 WebClient 및 DefaultUriBuilder 를 사용하여 다양한 유형의 URI를 빌드하는 방법을 배웠습니다 .
그 과정에서 쿼리 매개변수의 다양한 유형과 형식을 다루었습니다. 마지막으로 URL 빌더의 기본 인코딩 모드를 변경하여 마무리했습니다.
항상 그렇듯이 기사의 모든 코드 스니펫은 GitHub 리포지토리에서 사용할 수 있습니다 .