Spring Cloud zuul与CloseableHttpClient连接池,TLS证书认证

news2025/1/21 6:00:36

前言

最近做项目,需要一个代理逻辑,实际上这种代理NGINX最好,但是有些额外功能的开发,NGINX就需要额外能力支持,比如lua脚本,常见的做法有kong,apisix等,据说apisix的性能较强,界面较好,不过如果需要Java开发(方便二次开发),那么zuul也是可以的,实际上gateway相对主流,但是实现逻辑相对复杂,而且跟zuul(配置连接池和线程)性能差不多,只不过zuul不再被Spring Cloud支持,需要自己维护,但是servlet貌似也没啥维护的了。

zuul

zuul的设计之初是为了微服务网关,但是如果做TCP、websocket等转发就需要自己实现,实际上开源的goproxy就是一个性能较强的代理,go-gateway等,但是开发语言最终选择zuul,因为定制性极强。

zuul改造

zuul默认需要注册注册中心,需要把这一部分剥离出来,做成插件,需要的时候才会注册,拿到zuul starter源码,发现

默认加载这2个配置类,因为zuul被Spring Cloud废弃,所以没有Spring Boot新版本的引入配置类的方式import模式

Cloud模式,支持Cloud的负载均衡,熔断等

域名或者Host模式,可以使用域名,或者APP端负载均衡,限流等

zuul源码分析

zuul的注入依赖EnableZuulProxy还是EnableZuulServer,EnableZuulProxy的能力更强,原因如下

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ZuulServerMarkerConfiguration.class)
public @interface EnableZuulServer {

}

@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {

}

Import的marker决定的,注意proxy模式有EnableCircuitBreaker注解,这个是过时注解,而且依赖hystrix等熔断器,这个需要去掉,限流熔断自己实现吧,或者依赖Cloud的自定义实现

Server的能力,依赖marker类的bean创建,来源于上面的Enable注解,所以注解开启不同功能,

@ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)

这个之上的注解是会执行的,毕竟需要开启ZuulProperties的注入

Proxy的能力更强, 因为继承(简单粗暴)

剥离注册中心相关的操作

 逻辑很简单,实际只需要把robbin的filter和相关的支持类剥离即可

剥离后注册中心相关的可以单独加载,以http转https转发为例,流程分析

HTTPS转发逻辑

@SpringBootApplication
//@EnableDiscoveryClient
@EnableZuulProxy
public class ZuulMain {
    public static void main(String[] args) {
        SpringApplication.run(ZuulMain.class, args);
    }
}

因为剥离注册中心,就不需要服务发现了,但是只能转发Host、IP或者域名

配置转发博客为例:

zuul:
  routes:
    rule1:
      path: /demo/**
      url: https://blog.csdn.net/

笔者很早讲了SCI模式,不通过配置文件注入servlet和filter,那么zuul也是这种方式注入的

安装加载顺序一般情况下注入servlet,而不是filter,那么在http请求时

经过类型,然后执行zuulfilter

以http为例,笔者访问http://localhost:8766/demo/hello ,返回了csdn的地址

关键逻辑1:路径匹配

org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter

预处理会设置后面需要的url转发

threadlocal线程安全,在post的filter执行后会被uset

然后再取出使用转发

 

逻辑简单就是一条链传递,filter各个阶段的转发,中间做逻辑处理,收到Http请求,在通过“httpclient”发送出去。

连接池研究

笔者使用的是Apache的httpclient 4.5,实际上现在最新版本是5.2.x,可能略有不同,研究

CloseableHttpClient

的用法,连接池的用法,连接是怎么关闭的,看看Spring怎么做的

    @PostConstruct
	private void initialize() {
		if (!customHttpClient) {
			this.connectionManager = newConnectionManager();
			this.httpClient = newClient();
			this.connectionManagerTimer.schedule(new TimerTask() {
				@Override
				public void run() {
					if (SimpleHostRoutingFilter.this.connectionManager == null) {
						return;
					}
					SimpleHostRoutingFilter.this.connectionManager
							.closeExpiredConnections();
				}
			}, 30000, 5000);
		}
	}

3步:

1. 创建连接池

2. 创建httpclient(可关闭)

3. 定时关闭过期连接(实际是应该pool自己定时清除)

但是没看到在优雅停机时关闭连接池的代码,只有关闭定时器的代码

	@PreDestroy
	public void stop() {
		this.connectionManagerTimer.cancel();
	}

或者可以在这个里面加入关闭连接池的代码,但是流量能不能做到无损就需要外部支持了,不让外部进,内部流量消耗完

创建连接池 

	protected HttpClientConnectionManager newConnectionManager() {
		return connectionManagerFactory.newConnectionManager(
				!this.sslHostnameValidationEnabled,
				this.hostProperties.getMaxTotalConnections(),
				this.hostProperties.getMaxPerRouteConnections(),
				this.hostProperties.getTimeToLive(), this.hostProperties.getTimeUnit(),
				null);
	}

过度代码,封装参数,zuul的调参数就可以调这里参数,注意

!this.sslHostnameValidationEnabled

坑啊,sslenable取反,表示ssl不验证,Spring Cloud commons封装创建流程

	public HttpClientConnectionManager newConnectionManager(boolean disableSslValidation,
			int maxTotalConnections, int maxConnectionsPerRoute, long timeToLive,
			TimeUnit timeUnit, RegistryBuilder registryBuilder) {
		if (registryBuilder == null) {
            //支持HTTP,注册的是map,可以注册多种协议
			registryBuilder = RegistryBuilder.<ConnectionSocketFactory>create()
					.register(HTTP_SCHEME, PlainConnectionSocketFactory.INSTANCE);
		}
		if (disableSslValidation) {//刚刚的标记,不验证ssl
			try {
				final SSLContext sslContext = SSLContext.getInstance("SSL");
				sslContext.init(null,
						new TrustManager[] { new DisabledValidationTrustManager() },
						new SecureRandom());//不验证信任
				registryBuilder.register(HTTPS_SCHEME, new SSLConnectionSocketFactory(
						sslContext, NoopHostnameVerifier.INSTANCE));
			}
			catch (NoSuchAlgorithmException e) {
				LOG.warn("Error creating SSLContext", e);
			}
			catch (KeyManagementException e) {
				LOG.warn("Error creating SSLContext", e);
			}
		}
		else {
            //验证信任,默认是验证的
			registryBuilder.register("https",
					SSLConnectionSocketFactory.getSocketFactory());
		}
		final Registry<ConnectionSocketFactory> registry = registryBuilder.build();
        //连接池,相对的就是basic模式,单链接
		PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(
				registry, null, null, null, timeToLive, timeUnit);
		connectionManager.setMaxTotal(maxTotalConnections);//最大连接数
		connectionManager.setDefaultMaxPerRoute(maxConnectionsPerRoute);//每个路由最大连接

		return connectionManager;
	}

先看看怎么验证的

    public static SSLConnectionSocketFactory getSocketFactory() throws SSLInitializationException {
        return new SSLConnectionSocketFactory(SSLContexts.createDefault(), getDefaultHostnameVerifier());
    }

 读取火狐的认证的后缀:Public Suffix List - MozillaWiki

 

验证逻辑,host和x509,如果是自定义证书,比如我们自己做的jdk或者openssl,可以自定义验证,或者不验证

 

再看看创建PoolingHttpClientConnectionManager的过程,创建了CPool,继承自AbstractConnPool,有创建和回收方法,池子就可以循环

    public PoolingHttpClientConnectionManager(
        final HttpClientConnectionOperator httpClientConnectionOperator,
        final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory,
        final long timeToLive, final TimeUnit timeUnit) {
        super();
        this.configData = new ConfigData();
        //默认值每个路由最大2个连接,最大20个连接
        this.pool = new CPool(new InternalConnectionFactory(
                this.configData, connFactory), 2, 20, timeToLive, timeUnit);
        this.pool.setValidateAfterInactivity(2000);
        this.connectionOperator = Args.notNull(httpClientConnectionOperator, "HttpClientConnectionOperator");
        this.isShutDown = new AtomicBoolean(false);
    }

居然没用Apache的commons-pools,自己实现了,造轮子

httpclient的创建 

	protected CloseableHttpClient newClient() {
		final RequestConfig requestConfig = RequestConfig.custom()
				.setConnectionRequestTimeout(
						this.hostProperties.getConnectionRequestTimeoutMillis())
				.setSocketTimeout(this.hostProperties.getSocketTimeoutMillis())
				.setConnectTimeout(this.hostProperties.getConnectTimeoutMillis())
				.setCookieSpec(CookieSpecs.IGNORE_COOKIES).build();
		return httpClientFactory.createBuilder().setDefaultRequestConfig(requestConfig)
				.setConnectionManager(this.connectionManager).disableRedirectHandling()
				.build();
	}

核心是通过刚刚创建的连接管理对象创建Client,执行client的时候可以创建连接和回收复用,里面封装的很复杂,考虑

 

涉及权限和user agent,尤其是user agent,这个在很多地方有限制,比如浏览器 

获取连接,发送请求

发送请求需要封装method,jdk8自带的urlconnection不能支持patch:[JDK-8207840] HTTPUrlConnection does not accept PATCH method - Java Bug System,所以jdk8只能使用httpclient或者okhttp,httpclient使用serversocket自己实现的

根据实际verb写入method

那么在哪里去池子获取连接的呢,httpclient是自己封装的,装载获取连接超时

关闭连接 

异常关闭

那么正常情况下呢,response的body流关闭时

参考API,实际上就是读取流结束,关闭response的输入流

还包括经常用的toString的API

toString

那么zuul呢,注释写的很明白,释放连接

线程栈,zuul是读取结束就直接关闭连接了,实际上是EofSensorWatcher在生效,httpclient的ResponseEntityProxy实现了EofSensorWatcher接口

"http-nio-8766-exec-4@8603" daemon prio=5 tid=0x1c nid=NA runnable
  java.lang.Thread.State: RUNNABLE
	  at org.apache.http.impl.execchain.ResponseEntityProxy.releaseConnection(ResponseEntityProxy.java:76)
	  at org.apache.http.impl.execchain.ResponseEntityProxy.eofDetected(ResponseEntityProxy.java:121)
	  at org.apache.http.conn.EofSensorInputStream.checkEOF(EofSensorInputStream.java:199)
	  at org.apache.http.conn.EofSensorInputStream.read(EofSensorInputStream.java:136)
	  at org.apache.http.conn.EofSensorInputStream.read(EofSensorInputStream.java:148)
	  at org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter.writeResponse(SendResponseFilter.java:259)
	  at org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter.writeResponse(SendResponseFilter.java:162)
	  at org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter.run(SendResponseFilter.java:112)
	  at com.netflix.zuul.ZuulFilter.runFilter(ZuulFilter.java:117)

流在读取结束时也会调起钩子,实际上流关闭也是同理 

 

这个设计真不错,各个环节解决内存泄漏问题,有C++的味道

流关闭时

总结

实际上,对于技术而言,无论使用任何框架,设计思路都是有异曲同工的地方,对于HTTP代理,无论是zuul(servlet)还是gateway(netty),或者NGINX;本质处理逻辑还是IO的区别,HTTPS协议对于所有的逻辑都是一样的,关键在于定制化吧,zuul对于简单应用还是很不错的,方便定制化,也可以使用gateway,相对要复杂一点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/991268.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python 03(循环语句)

Python03&#xff08;循环语句&#xff09; 文章目录 Python03&#xff08;循环语句&#xff09;一、while语句二、while实现猜数字三、while循环的嵌套while循环嵌套实例需求&#xff1a; 四、for循环1、什么 是for循环2、语法3、执行流程4、for循环的基本使用5、range()函数6…

光刻机的原理和技术路线概览

一、简介 集成电路产业被誉为现代“工业粮食”&#xff0c;引领未来科学技术产业革命的发展&#xff0c;带动世界前沿技术创新发展。 集成电路产业是社会发展的先导产业&#xff0c;主要包括半导体材料、装备、芯片制造、封装测试、电路设计等重要环节。 随着工业智能制造和电子…

OpenCV(三十):图像膨胀

1.图像膨胀原理 图像膨胀的原理是将一个结构元素&#xff08;也称为核或模板&#xff09;在图像上滑动&#xff0c;并将其与图像中对应位置的像素进行比较。如果结构元素的所有像素与图像中对应位置的像素都匹配&#xff0c;那么该位置的像素值保持不变。如果结构元素的任何一个…

Telnet

远程登录/管理 目录 1、什么是Telnet 2、Telnet工作原理 3、常见的Telnet使用场景 4、Telnet协议结构 5、Telnet工作流程 6、Telnet常见命令 6.1、[Huawei]user-interface ? 用户界面 6.2、[Huawei]user-interface vty ? 6.3、[Huawei-ui-vty0-4]? 6.4、[Huawei-…

软件测试技术题目大全【含答案】

请看下面 你的测试职业发展是什么?  测试经验越多&#xff0c;测试能力越高。所以我的职业发展是需要时间积累的&#xff0c;一步步向着高级测试工程师奔去。而且我也有初步的职业规划&#xff0c;前3年积累测试经验&#xff0c;按如何做好测试工程师的要点去要求自己&#x…

buuctf crypto 【[AFCTF2018]Morse】解题记录

1.打开文件 2.摩斯密码解密 3.这个结果验证之后发现不对&#xff0c;猜测是16进制转字符 4.验证发现是对的

直方图均衡化原理

import numpy as np import cv2 hist, bins np.histogram(img, bins256, range(0, 256))# 计算累积分布函数&#xff08;CDF&#xff09; cdf np.cumsum(hist)# 归一化CDF&#xff1a; 归一化CDF以确保其范围在0到255之间 cdf_norm 255 * (cdf - cdf.min()) / (cdf.max()…

提词软件有哪些?了解一下这几个

提词软件有哪些&#xff1f;提词软件可以广泛应用于各种场景。除了在学习和工作中提高效率外&#xff0c;在日常生活中也有很多用处。例如&#xff0c;在进行演讲的时候&#xff0c;如果担心会因为紧张而忘词的话&#xff0c;就可以使用一些提词软件&#xff0c;将演讲词编辑进…

使用P5.js来制作一个快乐的小风车动画

p5.js简介 前一段时间偶然了解到一个觉得很好玩儿的东西p5.js,于是就去了解了一下&#xff0c;发现可以自己设计一些有趣的动画效果&#xff0c;设计出来的动画可以放置到页面当中&#xff0c;而且也是简单易学的。 下面是一段官方的介绍&#xff1a; p5.js是一个以 Processi…

0016Java程序设计-springboot幼儿园管理系统

摘 要目 录系统设计开发环境 摘 要 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身的优势&#xff1b;对于幼儿园管理系统当然也不能排除在外&#xff0c;随着网络技术的不断成熟&#xff0c;带动了幼儿园管理系统&a…

从JVM角度看继承

从JVM角度看继承 最近重读了周志明老师的《深入理解JAVA虚拟机》一书&#xff0c;看完大有收获&#xff0c;但仍对继承情况下对象内存布局有所疑惑&#xff0c;所以查阅资料&#xff0c;结合本书进行分析 参考文档&#xff1a; 【深入理解JVM】&#xff1a;Java类继承关系中…

算法训练营day45|动态规划 part07:完全背包 (LeetCode 70. 爬楼梯(进阶)、322. 零钱兑换、279.完全平方数)

文章目录 70. 爬楼梯(进阶)(求排列方法数)思路分析代码实现 322. 零钱兑换(求等于背包重量的最小物品数)思路分析代码实现思考总结 279.完全平方数 (求等于背包重量的最小物品数)思路分析代码实现 70. 爬楼梯(进阶)(求排列方法数) 题目链接&#x1f525; 假设你正在爬楼梯。需…

jquery jstree的懒加载

效果如下 使用jquery的jstree组件 1.前端准备工作 1.1引入jstree样式和js <link rel"stylesheet" href"/public/vendor/jstree/jstree.css"> <div id"departmentJstree"></div> <script src"/public/vendor/jstree…

Dominosa/数邻(2) | C++ | BFS

目录 一、Dominosa简介二、题目描述三、编程思路四、完整代码 一、Dominosa简介 Dominosa&#xff0c;中文名称为数邻&#xff0c;是一种棋盘游戏&#xff0c;基于骨牌的排列和匹配来进行。它是从骨牌游戏中发展而来的&#xff0c;在骨牌的基础上添加了一些规则和难度。具体的游…

canape中快速配置需要录制参数的技巧

以前在车里录制数据时&#xff0c;大量融合数据一个一个拖拽 &#xff0c;不仅慢&#xff0c;有时心不细的话&#xff0c;还会漏选、挑错。 用正则表达式的挑选方法&#xff0c;可以既快速又准确的挑出所需数据。 以下蓝色字体是操作方法&#xff1a; 正则表达式 示例&#xff…

QTableView通过setColumnWidth设置了列宽无效的问题

在用到QT的QTableView时&#xff0c;为了显示效果&#xff0c;向手动的设置每一列的宽度&#xff0c;但是如下的代码是无效的。 ui->tableView->setColumnWidth(0,150);ui->tableView->setColumnWidth(1,150);ui->tableView->setColumnWidth(2,150);ui->t…

docker-compose安装nginx

基于docker-compose安装nginx 目录 一、目录结构 1、docker-compose.yml 2、nginx.conf 3、default.conf 4、index.html 二、访问测试 一、目录结构 1、docker-compose.yml version: 3 services:nginx:image: registry.cn-hangzhou.aliyuncs.com/zhengqing/nginx:1.21.1…

Linux指令二【进程,权限】

进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程&#xff0c;是操作系统进行 资源分配和调度的一个独立单位&#xff0c;是应用程序运行的载体。 一、进程基本指令 1.ps&#xff1a;当前的用户进程 ps 只显示隶属于自己的进程状态ps -aux 显示所有进程…

C/C++计算(a+b)c的值 2019年9月电子学会青少年软件编程(C/C++)等级考试一级真题答案解析

目录 C/C计算(ab)c的值 一、题目要求 1、编程实现 2、输入输出 二、解题思路 1、案例分析 三、程序代码 四、程序说明 五、运行结果 六、考点分析 C/C计算(ab)c的值 2019年9月 C/C编程等级考试一级编程题 一、题目要求 1、编程实现 给定3个整数a、b、c&#xff0…

错误: 找不到或无法加载主类 Main

在用git回退到上个版本后发现&#xff0c;无法运行项目并提示 错误: 找不到或无法加载主类 Main 可以看到Main前面的图标也是号。 查了半天没有解决&#xff0c;问了个大佬&#xff0c;大佬一下就解决掉了&#xff0c;本文记录下解决过程。 错误原因是编辑器无法找到代码位置&…