记RestTemplateBuilder奇诡的坑

2023-09-16 11:48:32

前言

在紧张的开发工作中,总能遇到一些奇怪的问题。今天的主角是RestTemplateBuilder。

问题描述

由于某些原因,我需要一个不检查HTTPS证书的RestTemplate。但是不管我怎么搞,就是依然会被检查到证书而抛出请求异常!在构建RestTemplate时,我使用了RestTemplateBuilder. (以下代码不涉及公司业务,属于纯技术代码)

package com.evan.demo.nacos.config.demos;

import org.apache.http.client.HttpClient;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustAllStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.ssl.SSLContextBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import javax.net.ssl.SSLContext;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.util.Objects;

@Configuration
public class RestTemplateConfig {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Primary
    @Bean
    public RestTemplate restTemplate() {
        return newRestTemplateBuilder(null)
                .build();
    }

    /**
     * 不检查https的SSL证书
     */
    @Bean("unsafeRestTemplate")
    public RestTemplate unsafeRestTemplate() {
        HttpComponentsClientHttpRequestFactory unsafeRequestFactory = new HttpComponentsClientHttpRequestFactory();
        unsafeRequestFactory.setHttpClient(unsafeHttpClient());

        return newRestTemplateBuilder(unsafeRequestFactory)
        	.builder();
    }

    private static RestTemplateBuilder newRestTemplateBuilder(HttpComponentsClientHttpRequestFactory unsafeRequestFactory) {
        RestTemplateBuilder builder = new RestTemplateBuilder()
                .setConnectTimeout(Duration.ofSeconds(5))
                .setReadTimeout(Duration.ofSeconds(50));

        if (Objects.nonNull(unsafeRequestFactory)) {
            builder.requestFactory(() -> unsafeRequestFactory);
        }
        return builder;
    }

    private HttpClient unsafeHttpClient() {
        SSLContext unsafeSSLContext;
        try {
            unsafeSSLContext = SSLContextBuilder.create()
                    .setProtocol(SSLConnectionSocketFactory.TLS)
                    .loadTrustMaterial(new TrustAllStrategy())
                    .build();
        } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException("unsafe SSLContext create error.");
        }
        return HttpClientBuilder.create()
                .setSSLContext(unsafeSSLContext)
                .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
                .build();
    }
}

大致就是,有两个RestTemplate,其中一个正常校验证书,另一个不用。不需要校验证书的,通过提供一个不检查Https证书的ClientHttpRequestFactory实现。为了复用超时设置等公共信息,所以就抽取了个方法,并且判空factory参数再设置到builder中。

问题解决

由于一时间想不到办法,所以我试了一下,直接使用
public RestTemplate.RestTemplate (ClientHttpRequestFactory requestFactory)。这种方式则正常了。但是超时时间,则需要通过HttpComponentsClientHttpRequestFactory来设置了。

但这个问题还是在脑海中萦绕,为什么使用builder就不行呢?于是开始打断点。发现以下代码很诡异:

		if (Objects.nonNull(unsafeRequestFactory)) {
            builder.requestFactory(() -> unsafeRequestFactory);
        }
        return builder;

当跟着断点进入到builder.requestFactory中时,里面创建了个新的builder,这个新的factory也确实设置进去了。但是出来的时候,factory没了!?!!
这个时候,相信脑子转得快的同学应该已经知道原因了。但是当时由于太想了解这个问题,一时间竟然没反应过来。又继续断点了好几次。。。

好了,公布原因:因为builder的每个方法都是重新创建了一个新的builder。当我们使用流式调用时,不会出现问题,因为每个调用都是对于新builder的调用。但是,当我们存在非流式调用的情况时,问题就发生了。这个调用则不会实际生效。

要解决这个问题也比较简单,只要在非流式调用后,给变量重新赋值就好了。像上面的代码的话,这样处理一下就可以了:

builder = builder.requestFactory(() -> unsafeRequestFactory);

总结

方案一: 直接创建RestTemplate,然后通过HttpComponentsClientHttpRequestFactory来设置超时时间。

方案二:像上面那样,在非流式调用时,用返回值再次对builder变量赋值。

后记

由于大家都习惯流式调用,一般情况下倒也不会出现问题。但是当出现非流式调用时,大家对于builder的使用习惯应该大多都是直接调用的,而不会再次给变量赋值。所以提醒大家在设计builder的时候,这可能不是一个好的实现。

更多推荐

聊聊自动化测试路上会遇到的挑战~

一、测试范围无论是功能测试,还是自动化或者性能测试,第一步要做的,是明确测试范围和需求指标。对于自动化测试来说,特别是UI自动化,并不是所有的功能点都适合做UI自动化。根据具体的业务情况和项目稳定程度,选择UI自动化+API自动化结合,选择合适的业务点来进行针对性的自动化测试方案设计,才是最佳方案。①、使用频次较高,异

Logstash8.3.3 parse 包含a(AM PM)时间报错_dateparsefailure

遇到个奇奇怪怪的错误,我log中的时间是2023/08/2412:01:39AM,我写的格式化时间和转成东八区时间,但是就是一直报错_dateparsefailure,我反复检查了format没有问题,[xxxx][timestamp]这个字段从grok读出来也是正确的,百思不得其解。如果是这种的format的话,在g

Arduino程序设计(十三)触摸按键实验(TTP223)

触摸按键实验前言一、TTP223触摸按键模块二、触摸按键控制LED二、触摸按键状态检测前言本文介绍触摸按键控制LED的原理及实验,主要内容有:1、介绍TTP223触摸按键模块;2、触摸按键控制LED;3、触摸按键状态检测。一、TTP223触摸按键模块1、模块介绍:该模块是一个基于触摸检测IC(TTP223B)的电容式点

小样本目标检测:ECEA: Extensible Co-Existing Attention for Few-Shot Object Detection

论文作者:ZhimengXin,TianxuWu,ShimingChen,YixiongZou,LingShao,XingeYou作者单位:HuazhongUniversityofScienceandTechnology;UCAS-TerminusAILab论文链接:http://arxiv.org/abs/2309.

快速搭建接口自动化测试框架

1接口测试接口测试是对系统或组件之间的接口进行测试,主要是校验数据的交换,传递和控制管理过程,以及相互逻辑依赖关系。接口自动化相对于UI自动化来说,属于更底层的测试,这样带来的好处就是测试收益更大,且维护成本相对来说较低,是我们进行自动化测试的首选2框架选型目前接口自动化的框架比较多,比如jmeter,就可以集接口自动

企业数字化转型如何成功落地

企业数字化转型是当前趋势,是企业在面对日益变化的商业环境时必须采取的关键策略之一。然而,要实现数字化转型的成功落地并不容易,需要企业有明确的目标和正确的方法。数聚将探讨一些关键的步骤和策略,帮助企业实现数字化转型的顺利落地,并在竞争激烈的市场中取得优势。第一步是明确转型目标。企业在进行数字化转型时,必须清楚地知道自己的

【springMVC】高级部分

拦截器1拦截器(Interceptor)是一种动态拦截方法调用的机制#作用:1.在指定的方法调用前后执行预先设定后的的代码2.阻止原始方法的执行#核心原理:AOP思想#拦截器链:多个拦截器按照一定的顺序,对原始被调用功能进行增强2拦截器使用步骤1实现HandlerInterceptor接口/***三个方法的运行顺序为p

UI自动化测试用例管理平台搭建

用到的工具:python3+django2+mysql+RabbitMQ+celery+seleniumpython3和selenium这个网上很多教程,我不在这一一说明;平台功能介绍:项目管理:用于管理项目。每个项目可以设置多个环境,例如开发环境,测试环境,预发布环境,生产环境等。页面管理:主要用来方便对元素进行管理

通讯网关软件009——利用CommGate X2MQTT实现MQTT访问ODBC数据源

本文介绍利用CommGateX2MQTT实现MQTT访问ODBC数据源。CommGateX2MQTT是宁波科安网信开发的网关软件,软件可以登录到网信智汇(http://wangxinzhihui.com)下载。【案例】如下图所示,实现上位机通过MQTT来获取ODBC数据源的数据。【解决方案】设置网关机,与ODBC数据源

2023数A题——WLAN网络信道接入机制建模

A题——WLAN网络信道接入机制建模思路:该题主要考察的WLAN下退避机制建模仿真。资料获取问题1:假设AP发送包的载荷长度为1500Bytes(1Bytes=8bits),PHY头时长为13.6μs,MAC头为30Bytes,MAC头和有效载荷采用物理层速率455.8Mbps发送。AP之间的RSSI为-70dBm。大

git和github的入门操作

之前因为工作中用的都是SVN版本控制工具,没接触过git和github,现在开始深入自学Django框架技术后,看到官网推荐使用git,然后这两天网上查阅了很多文章教程,学到入门操作需要学习的点,太多的知识点要后面慢慢深入学习了。看到一个网上教程说的一段话:“如果你是一枚Coder,但是你不知道Github,那么我觉的

热文推荐