1. 程式人生 > >使用WebClient呼叫rest api測試

使用WebClient呼叫rest api測試

1.引言
Spring開發人員,您是否曾經覺得需要一個易於使用且高效的流暢功能樣式 API 的非同步/非阻塞 HTTP客戶端?
如果是,那麼我歡迎您閱讀關於WebClient的文章,WebClient是Spring 5中引入的新的被動HTTP客戶端。

2.如何使用WebClient
WebClient是Spring 5的反應性Web框架Spring WebFlux的一部分。要使用WebClient,您需要將spring-webflux模組包含在您的專案中。

在現有的Spring Boot專案中新增依賴項

如果您有一個現有的Spring Boot專案,則可以spring-webflux通過在該pom.xml檔案中新增以下依賴項來新增該模組-

org.springframework.boot spring-boot-starter-webflux 請注意,您需要Spring Boot 2.xx版本才能使用Spring WebFlux模組。

從Scratch建立一個新專案

如果您從頭開始建立專案,那麼您可以使用Spring Initializr網站的spring-webflux模組生成初始專案-

在依賴項部分新增反應性Web依賴項。
如果需要,請更改組和工件的詳細資訊,然後單擊生成工程下載專案。

3.使用WebClient消費遠端API
讓我們做一些有趣的事情,並使用WebClient來使用Real World API。

在本文中,我們將使用WebClient來使用Github的API。我們將使用WebClient在使用者的Github儲存庫上執行CRUD操作。

建立WebClient的一個例項
1.使用該create()方法建立WebClient

您可以使用create()工廠方法建立WebClient的例項-

WebClient webClient = WebClient.create();
如果您只使用特定服務的API,那麼您可以使用該服務的baseUrl來初始化WebClient

WebClient webClient = WebClient.create(“https://api.github.com

”);
2.使用WebClient構建器建立WebClient

WebClient還附帶了一個構建器,它為您提供了一些自定義選項,包括過濾器,預設標題,cookie,客戶端聯結器等 -

WebClient webClient = WebClient.builder()
        .baseUrl("https://api.github.com")
        .defaultHeader(HttpHeaders.CONTENT_TYPE, "application/vnd.github.v3+json")
        .defaultHeader(HttpHeaders.USER_AGENT, "Spring 5 WebClient")
        .build();

使用WebClient發出請求並檢索響應
以下是如何使用WebClient GET向Github的List Repositories API發出請求-

public Flux<GithubRepo> listGithubRepositories(String username, String token) {
     return webClient.get()
            .uri("/user/repos")
            .header("Authorization", "Basic " + Base64Utils
                    .encodeToString((username + ":" + token).getBytes(UTF_8)))
            .retrieve()
            .bodyToFlux(GithubRepo.class);
}

瞭解API呼叫的簡單性和簡潔性!

假設我們有一個名為類GithubRepo,確認到GitHub的API響應,上面的函式會返回一個Flux的GithubRepo物件。

請注意,我使用Github的基本認證機制來呼叫API。它需要您的github使用者名稱和個人訪問令牌,您可以從https://github.com/settings/tokens中生成該令牌。

使用exchange()方法來檢索響應
該retrieve()方法是獲取響應主體的最簡單方法。但是,如果您希望對響應擁有更多的控制權,那麼您可以使用可exchange()訪問整個ClientResponse標題和正文的方法 -

public Flux<GithubRepo> listGithubRepositories(String username, String token) {
     return webClient.get()
            .uri("/user/repos")
            .header("Authorization", "Basic " + Base64Utils
                    .encodeToString((username + ":" + token).getBytes(UTF_8)))
            .exchange()
            .flatMapMany(clientResponse -> clientResponse.bodyToFlux(GithubRepo.class));
}

在請求URI中使用引數
您可以在請求URI中使用引數,並在uri()函式中分別傳遞它們的值。所有引數都被花括號包圍。在提出請求之前,這些引數將被WebClient自動替換 -

public Flux<GithubRepo> listGithubRepositories(String username, String token) {
     return webClient.get()
            .uri("/user/repos?sort={sortField}&direction={sortDirection}", 
                     "updated", "desc")
            .header("Authorization", "Basic " + Base64Utils
                    .encodeToString((username + ":" + token).getBytes(UTF_8)))
            .retrieve()
            .bodyToFlux(GithubRepo.class);
}

使用URIBuilder構造請求URI
您也可以使用UriBuilder類似的方法獲取對請求URI的完全程式控制,

public Flux<GithubRepo> listGithubRepositories(String username, String token) {
     return webClient.get()
            .uri(uriBuilder -> uriBuilder.path("/user/repos")
                    .queryParam("sort", "updated")
                    .queryParam("direction", "desc")
                    .build())
            .header("Authorization", "Basic " + Base64Utils
                    .encodeToString((username + ":" + token).getBytes(UTF_8)))
            .retrieve()
            .bodyToFlux(GithubRepo.class);
}

在WebClient請求中傳遞Request Body
如果你有一個Mono或一個形式的請求體Flux,那麼你可以直接將它傳遞給body()WebClient中的方法,否則你可以從一個物件中建立一個單聲道/通量並像這樣傳遞 -

public Mono<GithubRepo> createGithubRepository(String username, String token, 
    RepoRequest createRepoRequest) {
    return webClient.post()
            .uri("/user/repos")
            .body(Mono.just(createRepoRequest), RepoRequest.class)
            .header("Authorization", "Basic " + Base64Utils
                    .encodeToString((username + ":" + token).getBytes(UTF_8)))
            .retrieve()
            .bodyToMono(GithubRepo.class);
}

如果您具有實際值而不是Publisher(Flux/ Mono),則可以使用syncBody()快捷方式傳遞請求正文 -

public Mono<GithubRepo> createGithubRepository(String username, String token, 
    RepoRequest createRepoRequest) {
    return webClient.post()
            .uri("/user/repos")
            .syncBody(createRepoRequest)
            .header("Authorization", "Basic " + Base64Utils
                    .encodeToString((username + ":" + token).getBytes(UTF_8)))
            .retrieve()
            .bodyToMono(GithubRepo.class);
}

最後,你可以使用BodyInserters類提供的各種工廠方法來構造一個BodyInserter物件並將其傳遞給該body()方法。本BodyInserters類包含的方法來建立一個BodyInserter從Object,Publisher,Resource,FormData,MultipartData等-

public Mono<GithubRepo> createGithubRepository(String username, String token, 
    RepoRequest createRepoRequest) {
    return webClient.post()
            .uri("/user/repos")
            .body(BodyInserters.fromObject(createRepoRequest))
            .header("Authorization", "Basic " + Base64Utils
                    .encodeToString((username + ":" + token).getBytes(UTF_8)))
            .retrieve()
            .bodyToMono(GithubRepo.class);
}

新增過濾器功能
WebClient支援使用ExchangeFilterFunction。您可以使用過濾器函式以任何方式攔截和修改請求。例如,您可以使用過濾器函式為Authorization每個請求新增一個標頭,或記錄每個請求的詳細資訊。

這ExchangeFilterFunction需要兩個引數 -

在ClientRequest與
ExchangeFilterFunction過濾器鏈中的下一個。
它可以修改ClientRequest並呼叫ExchangeFilterFucntion過濾器鏈中的下一個來繼續下一個過濾器或ClientRequest直接返回修改以阻止過濾器鏈。

1.使用過濾器功能新增基本認證
在上面的所有示例中,我們都包含一個Authorization用於使用Github API進行基本身份驗證的標頭。由於這是所有請求共有的內容,因此您可以在建立過濾器函式時將此邏輯新增到過濾器函式中WebClient。

該ExchaneFilterFunctionsAPI已經為基本認證提供了一個過濾器。你可以像這樣使用它 -

WebClient webClient = WebClient.builder()
        .baseUrl(GITHUB_API_BASE_URL)
        .defaultHeader(HttpHeaders.CONTENT_TYPE, GITHUB_V3_MIME_TYPE)
        .filter(ExchangeFilterFunctions
                .basicAuthentication(username, token))
        .build();

現在,您不需要Authorization在每個請求中新增標題。過濾器函式將攔截每個WebClient請求新增此標頭。

2.使用過濾器功能記錄所有請求
我們來看一個習慣的例子ExchangeFilterFunction。我們將編寫一個過濾器函式來攔截並記錄每個請求 -

WebClient webClient = WebClient.builder()
        .baseUrl(GITHUB_API_BASE_URL)
        .defaultHeader(HttpHeaders.CONTENT_TYPE, GITHUB_V3_MIME_TYPE)
        .filter(ExchangeFilterFunctions
                .basicAuthentication(username, token))
        .filter(logRequest())
        .build();

這裡是logRequest()過濾器功能的實現-

private ExchangeFilterFunction logRequest() {
    return (clientRequest, next) -> {
        logger.info("Request: {} {}", clientRequest.method(), clientRequest.url());
        clientRequest.headers()
                .forEach((name, values) -> values.forEach(value -> logger.info("{}={}", name, value)));
        return next.exchange(clientRequest);
    };
}

3.使用ofRequestProcessor()和ofResponseProcessor()工廠方法來建立過濾器
ExchangeFilterFunction API提供兩個名為工廠方法ofRequestProcessor()和ofResponseProcessor()用於建立分別截獲該請求和響應濾波器的功能。

logRequest()我們在前一節中建立的過濾器函式可以使用ofRequestProcessor()這種工廠方法建立-

private ExchangeFilterFunction logRequest() {
    ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
        logger.info("Request: {} {}", clientRequest.method(), clientRequest.url());
        clientRequest.headers()
                .forEach((name, values) -> values.forEach(value -> logger.info("{}={}", name, value)));
        return Mono.just(clientRequest);
    });
}        

如果您想攔截WebClient響應,則可以使用該ofResponseProcessor()方法建立像這樣的過濾器功能 -

private ExchangeFilterFunction logResposneStatus() {
    return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
        logger.info("Response Status {}", clientResponse.statusCode());
        return Mono.just(clientResponse);
    });
}

處理WebClient錯誤
只要接收到狀態碼為4xx或5xx的響應retrieve(),WebClient中的方法WebClientResponseException就會丟擲一個。

您可以使用onStatus()像這樣的方法來自定義,

public Flux<GithubRepo> listGithubRepositories() {
     return webClient.get()
            .uri("/user/repos?sort={sortField}&direction={sortDirection}", 
                     "updated", "desc")
            .retrieve()
            .onStatus(HttpStatus::is4xxClientError, clientResponse ->
                Mono.error(new MyCustomClientException())
            )
            .onStatus(HttpStatus::is5xxServerError, clientResponse ->
                Mono.error(new MyCustomServerException())
            )
            .bodyToFlux(GithubRepo.class);

}

請注意,與retrieve()方法不同,該exchange()方法在4xx或5xx響應的情況下不會引發異常。您需要自己檢查狀態程式碼,並以您想要的方式處理它們。

使用@ExceptionHandler控制器內部的WebClientResponseExceptions處理
您可以@ExceptionHandler在控制器內部使用這種方式來處理WebClientResponseException並返回適當的響應給客戶端 -

@ExceptionHandler(WebClientResponseException.class)
public ResponseEntity<String> handleWebClientResponseException(WebClientResponseException ex) {
    logger.error("Error from WebClient - Status {}, Body {}", ex.getRawStatusCode(), ex.getResponseBodyAsString(), ex);
    return ResponseEntity.status(ex.getRawStatusCode()).body(ex.getResponseBodyAsString());
}