spring 5 webclient使用指南

Posted 码匠的流水账

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring 5 webclient使用指南相关的知识,希望对你有一定的参考价值。

之前写了一篇restTemplate使用实例,由于spring 5全面引入reactive,同时也有了restTemplate的reactive版webclient,本文就来对应展示下webclient的基本使用。

请求携带header

  • 携带cookie

      @Test
      public void testWithCookie(){
          Mono<String> resp = WebClient.create()
                  .method(HttpMethod.GET)
                  .uri("http://baidu.com")
                  .cookie("token","xxxx")
                  .cookie("JSESSIONID","XXXX")
                  .retrieve()
                  .bodyToMono(String.class);
          LOGGER.info("result:{}",resp.block());
      }
  • 携带basic auth

      @Test
      public void testWithBasicAuth(){
          String basicAuth = "Basic "+ Base64.getEncoder().encodeToString("user:pwd".getBytes(StandardCharsets.UTF_8));
          LOGGER.info(basicAuth);
          Mono<String> resp = WebClient.create()
                  .get()
                  .uri("http://baidu.com")
                  .header(HttpHeaders.AUTHORIZATION,basicAuth)
                  .retrieve()
                  .bodyToMono(String.class);
          LOGGER.info("result:{}",resp.block());
      }
  • 设置全局user-agent

      @Test
      public void testWithHeaderFilter(){
          WebClient webClient = WebClient.builder()
                  .defaultHeader(HttpHeaders.USER_AGENT, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/63.0.3239.132 Safari/537.36")
                  .filter(ExchangeFilterFunctions
                          .basicAuthentication("user","password"))
                  .filter((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);
                  })
                  .build();
          Mono<String> resp = webClient.get()
                  .uri("https://baidu.com")
                  .retrieve()
                  .bodyToMono(String.class);
          LOGGER.info("result:{}",resp.block());
      }

get请求

  • 使用placeholder传递参数

      @Test
      public void testUrlPlaceholder(){
          Mono<String> resp = WebClient.create()
                  .get()
                  //多个参数也可以直接放到map中,参数名与placeholder对应上即可
                  .uri("http://www.baidu.com/s?wd={key}&other={another}","北京天气","test") //使用占位符
                  .retrieve()
                  .bodyToMono(String.class);
          LOGGER.info("result:{}",resp.block());
    
      }
  • 使用uriBuilder传递参数

      @Test
      public void testUrlBiulder(){
          Mono<String> resp = WebClient.create()
                  .get()
                  .uri(uriBuilder -> uriBuilder
                          .scheme("http")
                          .host("www.baidu.com")
                          .path("/s")
                          .queryParam("wd", "北京天气")
                          .queryParam("other", "test")
                          .build())
                  .retrieve()
                  .bodyToMono(String.class);
          LOGGER.info("result:{}",resp.block());
      }

post表单

    @Test
    public void testFormParam(){
        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
        formData.add("name1","value1");
        formData.add("name2","value2");
        Mono<String> resp = WebClient.create().post()
                .uri("http://www.w3school.com.cn/test/demo_form.asp")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .body(BodyInserters.fromFormData(formData))
                .retrieve().bodyToMono(String.class);
        LOGGER.info("result:{}",resp.block());
    }

post json

  • 使用bean来post

    static class Book {
          String name;
          String title;
    
          public String getName() {
              return name;
          }
    
          public void setName(String name) {
              this.name = name;
          }
    
          public String getTitle() {
              return title;
          }
    
          public void setTitle(String title) {
              this.title = title;
          }
      }
    
      @Test
      public void testPostJson(){
          Book book = new Book();
          book.setName("name");
          book.setTitle("this is title");
          Mono<String> resp = WebClient.create().post()
                  .uri("http://localhost:8080/demo/json")
                  .contentType(MediaType.APPLICATION_JSON_UTF8)
                  .body(Mono.just(book),Book.class)
                  .retrieve().bodyToMono(String.class);
          LOGGER.info("result:{}",resp.block());
      }
  • 直接post raw json

      @Test
      public void testPostRawJson(){
          Mono<String> resp = WebClient.create().post()
                  .uri("http://localhost:8080/demo/json")
                  .contentType(MediaType.APPLICATION_JSON_UTF8)
                  .body(BodyInserters.fromObject("{\n" +
                          "  \"title\" : \"this is title\",\n" +
                          "  \"author\" : \"this is author\"\n" +
                          "}"))
                  .retrieve().bodyToMono(String.class);
          LOGGER.info("result:{}",resp.block());
      }

post二进制—上传文件

    @Test
    public void testUploadFile(){
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.IMAGE_PNG);
        HttpEntity<ClassPathResource> entity = new HttpEntity<>(new ClassPathResource("parallel.png"), headers);
        MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
        parts.add("file", entity);
        Mono<String> resp = WebClient.create().post()
                .uri("http://localhost:8080/upload")
                .contentType(MediaType.MULTIPART_FORM_DATA)
                .body(BodyInserters.fromMultipartData(parts))
                .retrieve().bodyToMono(String.class);
        LOGGER.info("result:{}",resp.block());
    }

下载二进制

  • 下载图片

      @Test
      public void testDownloadImage() throws IOException {
          Mono<Resource> resp = WebClient.create().get()
                  .uri("http://www.toolip.gr/captcha?complexity=99&size=60&length=9")
                  .accept(MediaType.IMAGE_PNG)
                  .retrieve().bodyToMono(Resource.class);
          Resource resource = resp.block();
          BufferedImage bufferedImage = ImageIO.read(resource.getInputStream());
          ImageIO.write(bufferedImage, "png", new File("captcha.png"));
    
      }
  • 下载文件

      @Test
      public void testDownloadFile() throws IOException {
          Mono<ClientResponse> resp = WebClient.create().get()
                  .uri("http://localhost:8080/file/download")
                  .accept(MediaType.APPLICATION_OCTET_STREAM)
                  .exchange();
          ClientResponse response = resp.block();
          String disposition = response.headers().asHttpHeaders().getFirst(HttpHeaders.CONTENT_DISPOSITION);
          String fileName = disposition.substring(disposition.indexOf("=")+1);
          Resource resource = response.bodyToMono(Resource.class).block();
          File out = new File(fileName);
          FileUtils.copyInputStreamToFile(resource.getInputStream(),out);
          LOGGER.info(out.getAbsolutePath());
      }

错误处理

    @Test
    public void testRetrieve4xx(){
        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.ResponseSpec responseSpec = webClient.method(HttpMethod.GET)
                .uri("/user/repos?sort={sortField}&direction={sortDirection}",
                        "updated", "desc")
                .retrieve();
        Mono<String> mono = responseSpec
                .onStatus(e -> e.is4xxClientError(),resp -> {
                    LOGGER.error("error:{},msg:{}",resp.statusCode().value(),resp.statusCode().getReasonPhrase());
                    return Mono.error(new RuntimeException(resp.statusCode().value() + " : " + resp.statusCode().getReasonPhrase()));
                })
                .bodyToMono(String.class)
                .doOnError(WebClientResponseException.class, err -> {
                    LOGGER.info("ERROR status:{},msg:{}",err.getRawStatusCode(),err.getResponseBodyAsString());
                    throw new RuntimeException(err.getMessage());
                })
                .onErrorReturn("fallback");
        String result = mono.block();
        LOGGER.info("result:{}",result);
    }
  • 可以使用onStatus根据status code进行异常适配

  • 可以使用doOnError异常适配

  • 可以使用onErrorReturn返回默认值

小结

webclient是新一代的async rest template,api也相对简洁,而且是reactive的,非常值得使用。

doc

  • restTemplate使用实例

  • Decode ByteArray with spring 5 WebFlux framework


以上是关于spring 5 webclient使用指南的主要内容,如果未能解决你的问题,请参考以下文章

Spring 5 webflux如何在Webclient上设置超时

如何在 Spring 5 WebFlux WebClient 中设置超时

Spring Security 5.1 - 使用 WebClient 获取客户端凭证流的令牌

Spring Security 5.2 / WebClient 使用用户名和密码连接到另一个服务的方式是啥?

如果所有重试都用尽,Spring Webclient 重试并执行代码

Spring - WebClient & RestTemplate