RestTemplate:Spring 封装的 HTTP 同步请求类

简介: RestTemplate:Spring 封装的 HTTP 同步请求类

RestTemplate介绍

Spring用于同步client端的核心类,简化了与http服务的通信,并满足RestFul原则,程序代码可以给它提供URL,并提取结果。默认情况下,RestTemplate默认依赖jdk的HTTP连接工具。当然你也可以 通过setRequestFactory属性切换到不同的HTTP源,比如Apache HttpComponentsNettyOkHttp

RestTemplate能大幅简化了提交表单数据的难度,并且附带了自动转换JSON数据的功能,但只有理解了HttpEntity的组成结构(header与body),且理解了与uriVariables之间的差异,才能真正掌握其用法。

其中:

    • RestTemplate默认使用HttpMessageConverter实例将HTTP消息转换成POJO或者从POJO转换成HTTP消息。默认情况下会注册主mime类型的转换器,但也可以通过setMessageConverters注册自定义转换器。
    • RestTemplate使用了默认的DefaultResponseErrorHandler,对40X Bad Request或50X internal异常error等错误信息捕捉。
    • RestTemplate还可以使用拦截器interceptor,进行对请求链接跟踪,以及统一head的设置。

    RestTemplate 类是在 Spring Framework 3.0 开始引入的,这里我们使用的 Spring 版本为当前最新的 GA 版本 5.1.6。而在 5.0 以上,官方标注了更推荐使用非阻塞的响应式 HTTP 请求处理类 org.springframework.web.reactive.client.WebClient 来替代 RestTemplate,尤其是对应异步请求处理的场景上 。

    RestTemplate 类提供的 API 有哪些,RestTemplate 提供了将近 30 个请求方法,其中多数是单个方法重载实现,这里我主要参考官方文档 rest-client-access 进行如下分类:

    方法 解析
    delete() 在特定的URL上对资源执行HTTP DELETE操作
    exchange() 在URL上执行特定的HTTP方法,返回包含对象的ResponseEntity
    execute() 在URL上执行特定的HTTP方法,返回一个从响应体映射得到的对象
    getForEntity() 发送一个HTTP GET请求,返回的ResponseEntity包含了响应体所映射成的对象
    getForObject() 发送一个HTTP GET请求,返回的请求体将映射为一个对象
    postForEntity() POST 数据到一个URL,返回包含一个对象的ResponseEntity
    postForObject() POST 数据到一个URL,返回根据响应体匹配形成的对象
    headForHeaders() 发送HTTP HEAD请求,返回包含特定资源URL的HTTP头
    optionsForAllow() 发送HTTP OPTIONS请求,返回对特定URL的Allow头信息
    postForLocation() POST 数据到一个URL,返回新创建资源的URL
    put() PUT 资源到特定的URL

    RestTemplate简单使用

    GET 请求

    不带任何参数 的 GET 请求

    // 一个不带任何参数 的 GET 请求
    @Test
    public void testGet_product1() {
       String url = "http://localhost:8080/product/get_product1";
       //方式一:GET 方式获取 JSON 串数据
       String result = restTemplate.getForObject(url, String.class);
       System.out.println("get_product1返回结果:" + result);
       Assert.hasText(result, "get_product1返回结果为空");
       //方式二:GET 方式获取 JSON 数据映射后的 Product 实体对象
       Product product = restTemplate.getForObject(url, Product.class);
       System.out.println("get_product1返回结果:" + product);
       Assert.notNull(product, "get_product1返回结果为空");
       //方式三:GET 方式获取包含 Product 实体对象 的响应实体 ResponseEntity 对象,用 getBody() 获取
       ResponseEntity<Product> responseEntity = restTemplate.getForEntity(url, Product.class);
       System.out.println("get_product1返回结果:" + responseEntity);
       Assert.isTrue(responseEntity.getStatusCode().equals(HttpStatus.OK), "get_product1响应不成功");
    }

    image.gif

    带有参数的 GET 请求

    @Test
    public void testGet_product2() {
        String url = "http://localhost:8080/product/get_product2/id={id}";
      //方式一:将参数的值存在可变长度参数里,按照顺序进行参数匹配
        ResponseEntity<Product> responseEntity = restTemplate.getForEntity(url, Product.class, 101);
        System.out.println(responseEntity);
        Assert.isTrue(responseEntity.getStatusCode().equals(HttpStatus.OK), "get_product2 请求不成功");
        Assert.notNull(responseEntity.getBody().getId(), "get_product2  传递参数不成功");
      //方式二:将请求参数以键值对形式存储到 Map 集合中,用于请求时URL上的拼接
        Map<String, Object> uriVariables = new HashMap<>();
        uriVariables.put("id", 101);
        Product result = restTemplate.getForObject(url, Product.class, uriVariables);
        System.out.println(result);
        Assert.notNull(result.getId(), "get_product2  传递参数不成功");
    }

    image.gif

    getForEntity()方法

    public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables){}
    public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables){}
    public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType){}

    image.gif

    与getForObject()方法不同的是返回的是ResponseEntity对象,如果需要转换成pojo,还需要json工具类的引入,这个按个人喜好用。不会解析json的可以百度FastJson或者Jackson等工具类。然后我们就研究一下ResponseEntity下面有啥方法。

    ResponseEntity、HttpStatus、BodyBuilder结构

    ResponseEntity.java

    public HttpStatus getStatusCode(){}
    public int getStatusCodeValue(){}
    public boolean equals(@Nullable Object other) {}
    public String toString() {}
    public static BodyBuilder status(HttpStatus status) {}
    public static BodyBuilder ok() {}
    public static <T> ResponseEntity<T> ok(T body) {}
    public static BodyBuilder created(URI location) {}
    ...

    image.gif

    HttpStatus.java

    public enum HttpStatus {
    public boolean is1xxInformational() {}
    public boolean is2xxSuccessful() {}
    public boolean is3xxRedirection() {}
    public boolean is4xxClientError() {}
    public boolean is5xxServerError() {}
    public boolean isError() {}
    }

    image.gif

    BodyBuilder.java

    public interface BodyBuilder extends HeadersBuilder<BodyBuilder> {
        //设置正文的长度,以字节为单位,由Content-Length标头
          BodyBuilder contentLength(long contentLength);
        //设置body的MediaType 类型
          BodyBuilder contentType(MediaType contentType);
        //设置响应实体的主体并返回它。
          <T> ResponseEntity<T> body(@Nullable T body);
    

    image.gif

    可以看出来,ResponseEntity包含了HttpStatus和BodyBuilder的这些信息,这更方便我们处理response原生的东西。

    POST 请求

    发送 Content-Typeapplication/x-www-form-urlencoded 的 POST 请求:

    @Test
    public void testPost_product1() {
        String url = "http://localhost:8080/product/post_product1";
      Product product = new Product(201, "Macbook", BigDecimal.valueOf(10000));
        // 设置请求的 Content-Type 为 application/x-www-form-urlencoded
        MultiValueMap<String, String> header = new LinkedMultiValueMap();
        header.add(HttpHeaders.CONTENT_TYPE, (MediaType.APPLICATION_FORM_URLENCODED_VALUE));
        //方式二: 将请求参数值以 K=V 方式用 & 拼接,发送请求使用
        String productStr = "id=" + product.getId() + "&name=" + product.getName() + "&price=" + product.getPrice();
        HttpEntity<String> request = new HttpEntity<>(productStr, header);
        ResponseEntity<String> exchangeResult = restTemplate.exchange(url, HttpMethod.POST, request, String.class);
        System.out.println("post_product1: " + exchangeResult);
        Assert.isTrue(exchangeResult.getStatusCode().equals(HttpStatus.OK), "post_product1 请求不成功");
        //方式一: 将请求参数以键值对形式存储在 MultiValueMap 集合,发送请求时使用
        MultiValueMap<String, Object> map = new LinkedMultiValueMap();
        map.add("id", (product.getId()));
        map.add("name", (product.getName()));
        map.add("price", (product.getPrice()));
        HttpEntity<MultiValueMap> request2 = new HttpEntity<>(map, header);
        ResponseEntity<String> exchangeResult2 = restTemplate.exchange(url, HttpMethod.POST, request2, String.class);
        System.out.println("post_product1: " + exchangeResult2);
        Assert.isTrue(exchangeResult.getStatusCode().equals(HttpStatus.OK), "post_product1 请求不成功");
    }

    image.gif

    发送 Content-Typeapplication/json 的 POST 请求:

    @Test
    public void testPost_product2() {
        String url = "http://localhost:8080/product/post_product2";
        // 设置请求的 Content-Type 为 application/json
        MultiValueMap<String, String> header = new LinkedMultiValueMap();
        header.put(HttpHeaders.CONTENT_TYPE, Arrays.asList(MediaType.APPLICATION_JSON_VALUE));
        // 设置 Accept 向服务器表明客户端可处理的内容类型
        header.put(HttpHeaders.ACCEPT, Arrays.asList(MediaType.APPLICATION_JSON_VALUE));
        // 直接将实体 Product 作为请求参数传入,底层利用 Jackson 框架序列化成 JSON 串发送请求
        HttpEntity<Product> request = new HttpEntity<>(new Product(2, "Macbook", BigDecimal.valueOf(10000)), header);
        ResponseEntity<String> exchangeResult = restTemplate.exchange(url, HttpMethod.POST, request, String.class);
        System.out.println("post_product2: " + exchangeResult);
        Assert.isTrue(exchangeResult.getStatusCode().equals(HttpStatus.OK), "post_product2 请求不成功");
    }

    image.gif

    RestTemplate源码

    1.1 默认调用链路

    restTemplate进行API调用时,默认调用链:

    ###########1.使用createRequest创建请求########
    resttemplate->execute()->doExecute()
    HttpAccessor->createRequest()
    //获取拦截器Interceptor,InterceptingClientHttpRequestFactory,SimpleClientHttpRequestFactory
    InterceptingHttpAccessor->getRequestFactory() 
    //获取默认的SimpleBufferingClientHttpRequest
    SimpleClientHttpRequestFactory->createRequest()
    #######2.获取响应response进行处理###########
    AbstractClientHttpRequest->execute()->executeInternal()
    AbstractBufferingClientHttpRequest->executeInternal()
    ###########3.异常处理#####################
    resttemplate->handleResponse()
    ##########4.响应消息体封装为java对象#######
    HttpMessageConverterExtractor->extractData()

    image.gif

    1.2 restTemplate->doExecute()

    在默认调用链中,restTemplate 进行API调用都会调用 doExecute 方法,此方法主要可以进行如下步骤:

    1)使用createRequest创建请求,获取响应

    2)判断响应是否异常,处理异常

    3)将响应消息体封装为java对象

    @Nullable
    protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
        @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
      Assert.notNull(url, "URI is required");
      Assert.notNull(method, "HttpMethod is required");
      ClientHttpResponse response = null;
      try {
        //使用createRequest创建请求
        ClientHttpRequest request = createRequest(url, method);
        if (requestCallback != null) {
          requestCallback.doWithRequest(request);
        }
        //获取响应response进行处理
        response = request.execute();
        //异常处理
        handleResponse(url, method, response);
        //响应消息体封装为java对象
        return (responseExtractor != null ? responseExtractor.extractData(response) : null);
      }catch (IOException ex) {
        String resource = url.toString();
        String query = url.getRawQuery();
        resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);
        throw new ResourceAccessException("I/O error on " + method.name() +
            " request for \"" + resource + "\": " + ex.getMessage(), ex);
      }finally {
        if (response != null) {
          response.close();
        }
      }
    }

    image.gif

    1.3 InterceptingHttpAccessor->getRequestFactory()

    在默认调用链中,InterceptingHttpAccessor的getRequestFactory()方法中,如果没有设置interceptor拦截器,就返回默认的SimpleClientHttpRequestFactory,反之,返回InterceptingClientHttpRequestFactoryrequestFactory,可以通过resttemplate.setInterceptors设置自定义拦截器interceptor

    //Return the request factory that this accessor uses for obtaining client request handles.
    public ClientHttpRequestFactory getRequestFactory() {
            //获取拦截器interceptor(自定义的)
        List<ClientHttpRequestInterceptor> interceptors = getInterceptors();
        if (!CollectionUtils.isEmpty(interceptors)) {
          ClientHttpRequestFactory factory = this.interceptingRequestFactory;
          if (factory == null) {
            factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
            this.interceptingRequestFactory = factory;
          }
          return factory;
        }
        else {
          return super.getRequestFactory();
        }
      }

    image.gif

    然后再调用SimpleClientHttpRequestFactory的createRequest创建连接:

    @Override
    public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
      HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
      prepareConnection(connection, httpMethod.name());
      if (this.bufferRequestBody) {
        return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);
      }
      else {
        return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);
      }
    }

    image.gif

    1.4 resttemplate->handleResponse()

    在默认调用链中,resttemplate的handleResponse,响应处理,包括异常处理,而且异常处理可以通过调用setErrorHandler方法设置自定义的ErrorHandler,实现对请求响应异常的判别和处理。自定义的ErrorHandler需实现ResponseErrorHandler接口,同时Spring boot也提供了默认实现DefaultResponseErrorHandler,因此也可以通过继承该类来实现自己的ErrorHandler

    DefaultResponseErrorHandler默认对40X Bad Request或50X internal异常error等错误信息捕捉。如果想捕捉服务本身抛出的异常信息,需要通过自行实现RestTemplateErrorHandler

    ResponseErrorHandler errorHandler = getErrorHandler();
                   //判断响应是否有异常
      boolean hasError = errorHandler.hasError(response);
      if (logger.isDebugEnabled()) {
        try {
          int code = response.getRawStatusCode();
          HttpStatus status = HttpStatus.resolve(code);
          logger.debug("Response " + (status != null ? status : code));
        }catch (IOException ex) {
          // ignore
        }
      }
      //有异常进行异常处理
      if (hasError) {
        errorHandler.handleError(url, method, response);
      }
    }

    image.gif

    1.5 HttpMessageConverterExtractor->extractData()

    在默认调用链中, HttpMessageConverterExtractorextractData中进行响应消息体封装为java对象,就需要使用message转换器,可以通过追加的方式增加自定义的messageConverter:先获取现有的messageConverter,再将自定义的messageConverter添加进去。

    根据restTemplatesetMessageConverters的源码可得,使用追加的方式可防止原有的messageConverter丢失,源码:

    public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
            //检验
        validateConverters(messageConverters);
        // Take getMessageConverters() List as-is when passed in here
        if (this.messageConverters != messageConverters) {
            //先清除原有的messageConverter
          this.messageConverters.clear();
          //后加载重新定义的messageConverter
          this.messageConverters.addAll(messageConverters);
        }
      }

    image.gif

    HttpMessageConverterExtractor的extractData源码:

    MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response);
      if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) {
        return null;
      }
      //获取到response的ContentType类型
      MediaType contentType = getContentType(responseWrapper);
      try {
          //依次循环messageConverter进行判断是否符合转换条件,进行转换java对象
        for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
        //会根据设置的返回类型responseType和contentType参数进行匹配,选择合适的MessageConverter
          if (messageConverter instanceof GenericHttpMessageConverter) {
            GenericHttpMessageConverter<?> genericMessageConverter =
                (GenericHttpMessageConverter<?>) messageConverter;
            if (genericMessageConverter.canRead(this.responseType, null, contentType)) {
              if (logger.isDebugEnabled()) {
                ResolvableType resolvableType = ResolvableType.forType(this.responseType);
                logger.debug("Reading to [" + resolvableType + "]");
              }
              return (T) genericMessageConverter.read(this.responseType, null, responseWrapper);
            }
          }
          if (this.responseClass != null) {
            if (messageConverter.canRead(this.responseClass, contentType)) {
              if (logger.isDebugEnabled()) {
                String className = this.responseClass.getName();
                logger.debug("Reading to [" + className + "] as \"" + contentType + "\"");
              }
              return (T) messageConverter.read((Class) this.responseClass, responseWrapper);
            }
          }
        }
      }
      .....
    }

    image.gif

    1.6 contentType与messageConverter之间的关系

    HttpMessageConverterExtractorextractData方法中看出,会根据contentTyperesponseClass选择messageConverter是否可读、消息转换。关系如下:

    类名 支持的JavaType 支持的MediaType
    ByteArrayHttpMessageConverter byte[] application/octet-stream, */*
    StringHttpMessageConverter String text/plain, */*
    ResourceHttpMessageConverter Resource */*
    SourceHttpMessageConverter Source application/xml, text/xml, application/*+xml
    AllEncompassingFormHttpMessageConverter Map<K, List<?>> application/x-www-form-urlencoded, multipart/form-data
    MappingJackson2HttpMessageConverter Object application/json, application/*+json
    Jaxb2RootElementHttpMessageConverter Object application/xml, text/xml, application/*+xml
    JavaSerializationConverter Serializable x-java-serialization;charset=UTF-8
    FastJsonHttpMessageConverter Object */*

    springboot集成RestTemplate

      根据上述源码的分析学习,可以轻松,简单地在项目进行对RestTemplate进行优雅地使用,比如增加自定义的异常处理、MessageConverter以及拦截器interceptor。本文使用示例demo,详情请查看接下来的内容。

    1.1. 导入依赖:(RestTemplate集成在Web Start中)

    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <version>2.2.0.RELEASE</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.10</version>
      <scope>provided</scope>
    </dependency>

    image.gif

    1.2. RestTemplat配置:

      • 使用ClientHttpRequestFactory属性配置RestTemplat参数,比如ConnectTimeoutReadTimeout;
      • 增加自定义的interceptor拦截器和异常处理;
      • 追加message转换器;
      • 配置自定义的异常处理.
      @Configuration
      public class RestTemplateConfig {
          @Value("${resttemplate.connection.timeout}")
          private int restTemplateConnectionTimeout;
          @Value("${resttemplate.read.timeout}")
          private int restTemplateReadTimeout;
          @Bean
          //@LoadBalanced
          public RestTemplate restTemplate( ClientHttpRequestFactory simleClientHttpRequestFactory) {
              RestTemplate restTemplate = new RestTemplate();
              //配置自定义的message转换器
              List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
              messageConverters.add(new CustomMappingJackson2HttpMessageConverter());
              restTemplate.setMessageConverters(messageConverters);
              //配置自定义的interceptor拦截器
              List<ClientHttpRequestInterceptor> interceptors=new ArrayList<ClientHttpRequestInterceptor>();
              interceptors.add(new HeadClientHttpRequestInterceptor());
              interceptors.add(new TrackLogClientHttpRequestInterceptor());
              restTemplate.setInterceptors(interceptors);
              //配置自定义的异常处理
              restTemplate.setErrorHandler(new CustomResponseErrorHandler());
              restTemplate.setRequestFactory(simleClientHttpRequestFactory);
              return restTemplate;
          }
          @Bean
          public ClientHttpRequestFactory simleClientHttpRequestFactory(){
              SimpleClientHttpRequestFactory reqFactory= new SimpleClientHttpRequestFactory();
              reqFactory.setConnectTimeout(restTemplateConnectionTimeout);
              reqFactory.setReadTimeout(restTemplateReadTimeout);
              return reqFactory;
          }
      }

      image.gif

      1.3. 组件(自定义异常处理、interceptor拦截器、message转化器)

      自定义interceptor拦截器,实现ClientHttpRequestInterceptor接口

        • 自定义TrackLogClientHttpRequestInterceptor,记录resttemplaterequestresponse信息,可进行追踪分析;
        • 自定义HeadClientHttpRequestInterceptor,设置请求头的参数。API发送各种请求,很多请求都需要用到相似或者相同的Http Header。如果在每次请求之前都把Header填入HttpEntity/RequestEntity,这样的代码会显得十分冗余,可以在拦截器统一设置。

        TrackLogClientHttpRequestInterceptor:

        /**
         * @Date: 2019/10/25 22:48,记录resttemplate访问信息
         * @Description:   记录resttemplate访问信息
         */
        @Slf4j
        public class TrackLogClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
            public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
                trackRequest(request,body);
                ClientHttpResponse httpResponse = execution.execute(request, body);
                trackResponse(httpResponse);
                return httpResponse;
            }
            private void trackResponse(ClientHttpResponse httpResponse)throws IOException {
                log.info("============================response begin==========================================");
                log.info("Status code  : {}", httpResponse.getStatusCode());
                log.info("Status text  : {}", httpResponse.getStatusText());
                log.info("Headers      : {}", httpResponse.getHeaders());
                log.info("=======================response end=================================================");
            }
            private void trackRequest(HttpRequest request, byte[] body)throws UnsupportedEncodingException {
                log.info("======= request begin ========");
                log.info("uri : {}", request.getURI());
                log.info("method : {}", request.getMethod());
                log.info("headers : {}", request.getHeaders());
                log.info("request body : {}", new String(body, "UTF-8"));
                log.info("======= request end ========");
            }
        }

        image.gif

        HeadClientHttpRequestInterceptor:

        @Slf4j
        public class HeadClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
            public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
               log.info("#####head handle########");
                HttpHeaders headers = httpRequest.getHeaders();
                headers.add("Accept", "application/json");
                headers.add("Accept-Encoding", "gzip");
                headers.add("Content-Encoding", "UTF-8");
                headers.add("Content-Type", "application/json; charset=UTF-8");
                ClientHttpResponse response = clientHttpRequestExecution.execute(httpRequest, bytes);
                HttpHeaders headersResponse = response.getHeaders();
                headersResponse.add("Accept", "application/json");
                return  response;
            }
        }

        image.gif

        自定义异常处理

        可继承DefaultResponseErrorHandler或者实现ResponseErrorHandler接口:

          • 实现自定义ErrorHandler的思路是根据响应消息体进行相应的异常处理策略,对于其他异常情况由父类DefaultResponseErrorHandler来进行处理。
          • 自定义CustomResponseErrorHandler进行30x异常处理

          CustomResponseErrorHandler:

          /**
           * @Description:  30X的异常处理
           */
          @Slf4j
          public class CustomResponseErrorHandler extends DefaultResponseErrorHandler {
              @Override
              public boolean hasError(ClientHttpResponse response) throws IOException {
                  HttpStatus statusCode = response.getStatusCode();
                  if(statusCode.is3xxRedirection()){
                      return true;
                  }
                  return super.hasError(response);
              }
              @Override
              public void handleError(ClientHttpResponse response) throws IOException {
                  HttpStatus statusCode = response.getStatusCode();
                  if(statusCode.is3xxRedirection()){
                      log.info("########30X错误,需要重定向!##########");
                      return;
                  }
                  super.handleError(response);
              }
          }

          image.gif

          自定义message转化器

          /**
           * @Description: 将Content-Type:"text/html"转换为Map类型格式
           */
          public class CustomMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
              public CustomMappingJackson2HttpMessageConverter() {
                  List<MediaType> mediaTypes = new ArrayList<MediaType>();
                  mediaTypes.add(MediaType.TEXT_PLAIN);
                  mediaTypes.add(MediaType.TEXT_HTML);  //加入text/html类型的支持
                  setSupportedMediaTypes(mediaTypes);// tag6
              }
          }

          image.gif

          RestTemplate工具类

          package com.fly.apigateway;
          package com.devicemag.core.utils;
          import com.alibaba.fastjson.JSONObject;
          import lombok.extern.slf4j.Slf4j;
          import org.springframework.http.HttpEntity;
          import org.springframework.http.HttpHeaders;
          import org.springframework.http.client.ClientHttpResponse;
          import org.springframework.http.client.SimpleClientHttpRequestFactory;
          import org.springframework.http.converter.StringHttpMessageConverter;
          import org.springframework.util.MultiValueMap;
          import org.springframework.web.client.ResponseErrorHandler;
          import org.springframework.web.client.RestTemplate;
          import javax.servlet.http.HttpServletResponse;
          import java.io.BufferedReader;
          import java.io.IOException;
          import java.io.InputStreamReader;
          import java.nio.charset.StandardCharsets;
          import java.util.Map;
          import java.util.Set;
          /**
           * @Title: restTemplateUtils
           * @ClassName: com.fly.apigateway.RestTemplateUtils.java
           * @Description:
           *
           * @Copyright 2016-2019  - Powered By 研发中心
           * @author: 王延飞
           * @date:  2020/4/10 18:25
           * @version V1.0
           */
          @Slf4j
          public class RestTemplateUtils {
              /**
               * 读取时间,自定义默认8s,0表示没有超时时间
               */
              public static final int READ_TIMEOUT = 1000*8;
              /**
               * 连接时间,自定义默认8s,0表示没有超时时间
               */
              public static final int CONNEC_TIMEOUT = 1000*8;
              /**
               * 重试次数,自定义默认1
               */
              public static final int RETRY_COUNT = 1;
              /**
               * http 请求 GET
               *
               * @param url           地址
               * @param params        参数
               * @return String 类型
               */
              public static String getHttp(String url, JSONObject params) {
                  String result = getHttp(url, params, READ_TIMEOUT, CONNEC_TIMEOUT, RETRY_COUNT);
                  return result;
              }
              /**
               * http 请求 GET
               *
               * @param url           地址
               * @param params        参数
               * @param connecTimeout 连接时间
               * @param readTimeout   读取时间
               * @param retryCount    重试机制
               * @return String 类型
               */
              public static String getHttp(String url, JSONObject params, int connecTimeout, int readTimeout, int retryCount) {
                  SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
                  requestFactory.setConnectTimeout(connecTimeout);
                  requestFactory.setReadTimeout(readTimeout);
                  RestTemplate restTemplate = new RestTemplate(requestFactory);
                  restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8)); // 设置编码集
                  restTemplate.setErrorHandler(new DefaultResponseErrorHandler()); // 异常处理
                  url = expandURL(url, params);
                  String result = null; // 返回值类型;
                  for (int i = 1; i <= retryCount; i++) {
                      try {
                          log.info("【GET/HTTP请求信息】,请求地址:{},请求参数:{}", url, params);
                          result = restTemplate.getForObject(url, String.class, params);
                          log.info("【GET/HTTP请求信息】,请求地址:{},请求参数:{},返回结果:{}", url, params,result);
                          return result;
                      } catch (Exception e) {
                          log.error("【GET/HTTP请求信息】异常,重试count:{},请求地址:{},请求参数:{},异常信息:{}", i, url, params,e);
                          e.printStackTrace();
                      }
                  }
                  return result;
              }
              /**
               * https 请求 GET
               *
               * @param url           地址
               * @param params        参数
               * @return String 类型
               */
              public static String getHttps(String url, JSONObject params) {
                  String result = getHttps(url, params, READ_TIMEOUT, CONNEC_TIMEOUT, RETRY_COUNT);
                  return result;
              }
              /**
               * https 请求 GET
               *
               * @param url           地址
               * @param params        参数
               * @param connecTimeout 连接时间
               * @param readTimeout   读取时间
               * @param retryCount    重试机制
               * @return String 类型
               */
              public static String getHttps(String url, JSONObject params, int connecTimeout, int readTimeout, int retryCount) {
                  SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
                  requestFactory.setConnectTimeout(connecTimeout);
                  requestFactory.setReadTimeout(readTimeout);
                  RestTemplate restTemplate = new RestTemplate(requestFactory);
                  restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8)); // 设置编码集
                  restTemplate.setErrorHandler(new DefaultResponseErrorHandler()); //error处理
                  restTemplate.setRequestFactory(new HttpsClientRequestFactory()); // 绕过https
                  url = expandURL(url, params);
                  String result = null; // 返回值类型;
                  for (int i = 1; i <= retryCount; i++) {
                      try {
                          log.info("【GET/HTTPS请求信息】,请求地址:{},请求参数:{}", url, params);
                          result = restTemplate.getForObject(url, String.class, params);
                          log.info("【GET/HTTPS请求信息】,请求地址:{},请求参数:{},返回结果:{}", url, params,result);
                          return result;
                      } catch (Exception e) {
                          log.error("【GET/HTTPS请求信息】异常,重试count:{},请求地址:{},请求参数:{},异常信息:{}", i, url, params,e);
                          e.printStackTrace();
                      }
                  }
                  return result;
              }
              /**
               * http 请求 post/JSON
               *
               * @param url           地址
               * @param params        参数
               * @return String 类型
               */
              public static String postHttp(String url, JSONObject params, Map headersMap) {
                  String result = postHttp(url, params,headersMap, READ_TIMEOUT, CONNEC_TIMEOUT, RETRY_COUNT);
                  return result;
              }
              /**
               * http请求 post/JSON
               *
               * @param url           地址
               * @param params        参数
               * @param headersMap    header
               * @param connecTimeout 连接时间
               * @param readTimeout   读取时间
               * @param retryCount    重试机制
               * @return String 类型
               */
              public static String postHttp(String url, JSONObject params, Map headersMap, int connecTimeout, int readTimeout, int retryCount) {
                  SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); // 时间函数
                  requestFactory.setConnectTimeout(connecTimeout);
                  requestFactory.setReadTimeout(readTimeout);
                  //内部实际实现为 HttpClient
                  RestTemplate restTemplate = new RestTemplate(requestFactory);
                  restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8)); // 设置编码集
                  restTemplate.setErrorHandler(new DefaultResponseErrorHandler()); // 异常处理的headers error 处理
                  // 设置·header信息
                  HttpHeaders requestHeaders = new HttpHeaders();
                  requestHeaders.setAll(headersMap);
                  HttpEntity<JSONObject> requestEntity = new HttpEntity<JSONObject>(params, requestHeaders); // josn utf-8 格式
                  String result = null; // 返回值类型;
                  for (int i = 1; i <= retryCount; i++) {
                      try {
                          log.info("【POST/HTTP请求信息】,请求地址:{},请求参数:{}", url, params);
                          result = restTemplate.postForObject(url, requestEntity, String.class);
                          log.info("【POST/HTTP请求信息】,请求地址:{},请求参数:{},返回结果:{}", url, params,result);
                          return result;
                      } catch (Exception e) {
                          log.error("【POST/HTTP请求信息】异常,重试count:{},请求地址:{},请求参数:{},异常信息:{}", i, url, params,e);
                          e.printStackTrace();
                      }
                  }
                  return result;
              }
              /**
               * http请求 post/MAP
               *
               * @param url           地址
               * @param params        参数
               * @return String 类型
               */
              public static String postHttp(String url, MultiValueMap params, Map headersMap) {
                  String result = postHttp(url, params,headersMap, READ_TIMEOUT, CONNEC_TIMEOUT, RETRY_COUNT);
                  return result;
              }
              /**
               * http 普通请求 post/MAP
               * @param url           地址
               * @param params         MultiValueMap<String, String> paramMap = new LinkedMultiValueMap<>();
               * @param headersMap    header
               * @param connecTimeout 连接时间
               * @param readTimeout   读取时间
               * @param retryCount    重试机制
               * @return String 类型
               */
              public static String postHttp(String url, MultiValueMap params, Map headersMap, int connecTimeout, int readTimeout, int retryCount) {
                  SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); // 时间函数
                  requestFactory.setConnectTimeout(connecTimeout);
                  requestFactory.setReadTimeout(readTimeout);
                  //内部实际实现为 HttpClient
                  RestTemplate restTemplate = new RestTemplate(requestFactory);
                  restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8)); // 设置编码集
                  restTemplate.setErrorHandler(new DefaultResponseErrorHandler()); // 异常处理的headers error 处理
                  // 设置·header信息
                  HttpHeaders requestHeaders = new HttpHeaders();
                  requestHeaders.setAll(headersMap);
                  HttpEntity<Map> requestEntity = new HttpEntity<Map>(params, requestHeaders); // json utf-8 格式
                  String result = null; // 返回值类型;
                  for (int i = 1; i <= retryCount; i++) {
                      try {
                          log.info("【POST/HTTP请求信息】,请求地址:{},请求参数:{}", url, params);
                          result = restTemplate.postForObject(url, requestEntity, String.class);
                          log.info("【POST/HTTP请求信息】,请求地址:{},请求参数:{},返回结果:{}", url, params,result);
                          return result;
                      } catch (Exception e) {
                          log.error("【POST/HTTP请求信息】异常,重试count:{},请求地址:{},请求参数:{},异常信息:{}", i, url, params,e);
                          e.printStackTrace();
                      }
                  }
                  return result;
              }
              /**
               * https 普通请求 post/JSON
               *
               * @param url           地址
               * @param params        参数
               * @return String 类型
               */
              public static String postHttps(String url, JSONObject params, Map headersMap) {
                  String result = postHttps(url, params,headersMap, READ_TIMEOUT, CONNEC_TIMEOUT, RETRY_COUNT);
                  return result;
              }
              /**
               * https 普通请求 post/JSON
               * @param url        请求地址
               * @param params     请求 josn 格式参数
               * @param headersMap headers 头部需要参数
               * @param retryCount 重试机制
               * @return 返回string类型返回值
               */
              public static String postHttps(String url, JSONObject params, Map headersMap, int connecTimeout, int readTimeout, int retryCount) {
                  SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); // 时间函数
                  requestFactory.setConnectTimeout(connecTimeout);
                  requestFactory.setReadTimeout(readTimeout);
                  //内部实际实现为 HttpClient
                  RestTemplate restTemplate = new RestTemplate(requestFactory);
                  restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8)); // 设置编码集
                  restTemplate.setRequestFactory(new HttpsClientRequestFactory()); // 绕过https
                  restTemplate.setErrorHandler(new DefaultResponseErrorHandler()); // 异常处理的headers error 处理
                  // 设置·header信息
                  HttpHeaders requestHeaders = new HttpHeaders();
                  requestHeaders.setAll(headersMap);
                  HttpEntity<JSONObject> requestEntity = new HttpEntity<JSONObject>(params, requestHeaders); // josn utf-8 格式
                  String result = null; // 返回值类型;
                  for (int i = 1; i <= retryCount; i++) {
                      try {
                          log.info("【POST/HTTPS请求信息】,请求地址:{},请求参数:{}", url, params);
                          result = restTemplate.postForObject(url, requestEntity, String.class);
                          log.info("【POST/HTTPS请求信息】,请求地址:{},请求参数:{},返回结果:{}", url, params,result);
                          return result;
                      } catch (Exception e) {
                          log.error("【POST/HTTPS请求信息】异常,重试count:{},请求地址:{},请求参数:{},异常信息:{}", i, url, params,e);
                          e.printStackTrace();
                      }
                  }
                  return result;
              }
              /**
               * @Title: URL拼接
               * @MethodName:  expandURL
               * @param url
               * @param jsonObject
               * @Return java.lang.String
               * @Exception
               * @Description:
               *
               * @author: 王延飞
               * @date:  2021/1/4 15:30
               */
              private static String expandURL(String url,JSONObject jsonObject) {
                  StringBuilder sb = new StringBuilder(url);
                  sb.append("?");
                  Set<String> keys = jsonObject.keySet();
                  for (String key : keys) {
                      sb.append(key).append("=").append(jsonObject.getString(key)).append("&");
                  }
                  return sb.deleteCharAt(sb.length() - 1).toString();
              }
              /**
               * 出现异常,可自定义
               */
              private static class DefaultResponseErrorHandler implements ResponseErrorHandler {
                  /**
                   * 对response进行判断,如果是异常情况,返回true
                   */
                  @Override
                  public boolean hasError(ClientHttpResponse response) throws IOException {
                      return response.getStatusCode().value() != HttpServletResponse.SC_OK;
                  }
                  /**
                   * 异常情况时的处理方法
                   */
                  @Override
                  public void handleError(ClientHttpResponse response) throws IOException {
                      BufferedReader br = new BufferedReader(new InputStreamReader(response.getBody()));
                      StringBuilder sb = new StringBuilder();
                      String str = null;
                      while ((str = br.readLine()) != null) {
                          sb.append(str);
                      }
                      try {
                          throw new Exception(sb.toString());
                      } catch (Exception e) {
                          e.printStackTrace();
                      }
                  }
              }
          }

          image.gif

          import org.springframework.http.client.SimpleClientHttpRequestFactory;
          import javax.net.ssl.*;
          import java.io.IOException;
          import java.net.HttpURLConnection;
          import java.net.InetAddress;
          import java.net.Socket;
          import java.security.cert.X509Certificate;
          /**
           * @Title: Https 绕过
           * @Description:
           *
           * @Copyright 2020-2021  - Powered By 研发中心
           * @author: 王延飞
           * @date:  2020/9/10 0010 9:01
           * @version V1.0
           */
          public class HttpsClientRequestFactory extends SimpleClientHttpRequestFactory {
              @Override
              protected void prepareConnection(HttpURLConnection connection, String httpMethod) {
                  try {
                      if (!(connection instanceof HttpsURLConnection)) {
                          throw new RuntimeException("An instance of HttpsURLConnection is expected");
                      }
                      HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
                      TrustManager[] trustAllCerts = new TrustManager[]{
                              new X509TrustManager() {
                                  @Override
                                  public X509Certificate[] getAcceptedIssuers() {
                                      return null;
                                  }
                                  @Override
                                  public void checkClientTrusted(X509Certificate[] certs, String authType) {
                                  }
                                  @Override
                                  public void checkServerTrusted(X509Certificate[] certs, String authType) {
                                  }
                              }
                      };
                      SSLContext sslContext = SSLContext.getInstance("TLS");
                      sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
                      httpsConnection.setSSLSocketFactory(new MyCustomSSLSocketFactory(sslContext.getSocketFactory()));
                      httpsConnection.setHostnameVerifier(new HostnameVerifier() {
                          @Override
                          public boolean verify(String s, SSLSession sslSession) {
                              return true;
                          }
                      });
                      super.prepareConnection(httpsConnection, httpMethod);
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }
              /**
               * We need to invoke sslSocket.setEnabledProtocols(new String[] {"SSLv3"});
               * see http://www.oracle.com/technetwork/java/javase/documentation/cve-2014-3566-2342133.html (Java 8 section)
               */
              // SSLSocketFactory用于创建 SSLSockets
              private static class MyCustomSSLSocketFactory extends SSLSocketFactory {
                  private final SSLSocketFactory delegate;
                  public MyCustomSSLSocketFactory(SSLSocketFactory delegate) {
                      this.delegate = delegate;
                  }
                  // 返回默认启用的密码套件。除非一个列表启用,对SSL连接的握手会使用这些密码套件。
                  // 这些默认的服务的最低质量要求保密保护和服务器身份验证
                  @Override
                  public String[] getDefaultCipherSuites() {
                      return delegate.getDefaultCipherSuites();
                  }
                  // 返回的密码套件可用于SSL连接启用的名字
                  @Override
                  public String[] getSupportedCipherSuites() {
                      return delegate.getSupportedCipherSuites();
                  }
                  @Override
                  public Socket createSocket(final Socket socket, final String host, final int port,
                                             final boolean autoClose) throws IOException {
                      final Socket underlyingSocket = delegate.createSocket(socket, host, port, autoClose);
                      return overrideProtocol(underlyingSocket);
                  }
                  @Override
                  public Socket createSocket(final String host, final int port) throws IOException {
                      final Socket underlyingSocket = delegate.createSocket(host, port);
                      return overrideProtocol(underlyingSocket);
                  }
                  @Override
                  public Socket createSocket(final String host, final int port, final InetAddress localAddress,
                                             final int localPort) throws
                          IOException {
                      final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
                      return overrideProtocol(underlyingSocket);
                  }
                  @Override
                  public Socket createSocket(final InetAddress host, final int port) throws IOException {
                      final Socket underlyingSocket = delegate.createSocket(host, port);
                      return overrideProtocol(underlyingSocket);
                  }
                  @Override
                  public Socket createSocket(final InetAddress host, final int port, final InetAddress localAddress,
                                             final int localPort) throws
                          IOException {
                      final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
                      return overrideProtocol(underlyingSocket);
                  }
                  private Socket overrideProtocol(final Socket socket) {
                      if (!(socket instanceof SSLSocket)) {
                          throw new RuntimeException("An instance of SSLSocket is expected");
                      }
                      ((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1"});
                      return socket;
                  }
              }
          }

          image.gif

          参考链接

          https://juejin.im/post/5db99c285188257e435592ac

          https://docs.spring.io/spring-framework/docs/4.3.7.RELEASE/javadoc-api/org/springframework/web/client/RestTemplate.html

          目录
          相关文章
          |
          8天前
          |
          缓存 安全 Java
          Spring Get请求 与post请求
          本文详细介绍了Spring框架中GET请求和POST请求的区别及应用场景。GET请求用于从服务器获取资源,参数附在URL末尾,适合查看非敏感信息;POST请求用于向服务器提交数据,参数在请求体中传输,适合处理敏感信息。Spring通过`@GetMapping`和`@PostMapping`注解分别处理这两种请求。此外,文章还提供了示例代码,展示了如何在Spring中实现这两种请求的处理。最后,文章总结了推荐使用POST请求的原因,包括更高的安全性、更大的数据传输量、更好的幂等性及灵活性。
          Spring Get请求 与post请求
          |
          2月前
          |
          安全 Java 应用服务中间件
          如何在 Spring Boot 3.3 中实现请求 IP 白名单拦截功能
          【8月更文挑战第30天】在构建Web应用时,确保应用的安全性是至关重要的。其中,对访问者的IP地址进行限制是一种常见的安全措施,特别是通过实施IP白名单策略,可以只允许特定的IP地址或IP段访问应用,从而有效防止未授权的访问。在Spring Boot 3.3中,我们可以通过多种方式实现这一功能,下面将详细介绍几种实用的方法。
          87 1
          |
          2月前
          |
          Java API UED
          【实战秘籍】Spring Boot开发者的福音:掌握网络防抖动,告别无效请求,提升用户体验!
          【8月更文挑战第29天】网络防抖动技术能有效处理频繁触发的事件或请求,避免资源浪费,提升系统响应速度与用户体验。本文介绍如何在Spring Boot中实现防抖动,并提供代码示例。通过使用ScheduledExecutorService,可轻松实现延迟执行功能,确保仅在用户停止输入后才触发操作,大幅减少服务器负载。此外,还可利用`@Async`注解简化异步处理逻辑。防抖动是优化应用性能的关键策略,有助于打造高效稳定的软件系统。
          41 2
          |
          2月前
          |
          负载均衡 Java API
          深度解析SpringCloud微服务跨域联动:RestTemplate如何驾驭HTTP请求,打造无缝远程通信桥梁
          【8月更文挑战第3天】踏入Spring Cloud的微服务世界,服务间的通信至关重要。RestTemplate作为Spring框架的同步客户端工具,以其简便性成为HTTP通信的首选。本文将介绍如何在Spring Cloud环境中运用RestTemplate实现跨服务调用,从配置到实战代码,再到注意事项如错误处理、服务发现与负载均衡策略,帮助你构建高效稳定的微服务系统。
          57 2
          |
          3月前
          |
          Java Spring
          spring restTemplate 进行http请求的工具类封装
          spring restTemplate 进行http请求的工具类封装
          106 3
          |
          3月前
          |
          Go 开发者
          golang的http客户端封装
          golang的http客户端封装
          33 0
          |
          Java 测试技术 API
          简单封装 HTTP 请求
          2017-2-19 更新到第二版: 源码地址:http://git.oschina.net/sp42/ajaxjs/tree/master/ajaxjs-base/src/com/ajaxjs/net?dir=1&filepath=ajaxjs-base%2Fsrc%2Fcom%2Fajaxjs%2Fnet 和文件操作一样,其内部使用了链式风格的调用方式。
          832 0
          |
          7天前
          |
          监控 安全 搜索推荐
          设置 HTTPS 协议以确保数据传输的安全性
          设置 HTTPS 协议以确保数据传输的安全性
          |
          4月前
          |
          安全 网络协议 网络安全
          IP代理的三大协议:HTTP、HTTPS与SOCKS5的区别
          **HTTP代理**适用于基本网页浏览,简单但不安全;**HTTPS代理**提供加密,适合保护隐私;**SOCKS5代理**灵活强大,支持TCP/UDP及认证,适用于绕过限制。选择代理协议应考虑安全、效率及匿名需求。
          |
          1月前
          HAProxy的高级配置选项-配置haproxy支持https协议及服务器动态上下线
          文章介绍了如何配置HAProxy以支持HTTPS协议和实现服务器的动态上下线。
          80 8
          HAProxy的高级配置选项-配置haproxy支持https协议及服务器动态上下线
          下一篇
          无影云桌面