tomcat使用http/https正向代理
前端请求进入内网后,内网服务需要访问外部服务,安全策略要求内网服务不能直接访问外网,所以决定在网关部署nginx,并配置为正向代理,内网服务配置nginx为正向代理。内网服务既有基于spring-boot的微服务,也有传统的Tomcat应用。所以分别进行测试验证。具体过程如下。一、对spring-boot应用进行验证参考网上使用正向代理方法,优先考虑对代码无侵扰的两个方法。一个是在java命令行增
前端请求进入内网后,内网服务需要访问外部服务,安全策略要求内网服务不能直接访问外网,所以决定在网关部署nginx,并配置为正向代理,内网服务配置nginx为正向代理。内网服务既有基于spring-boot的微服务,也有传统的Tomcat应用。所以分别进行测试验证。具体过程如下。
一、对spring-boot应用进行验证
参考网上使用正向代理方法,优先考虑对代码无侵扰的两个方法。一个是在java命令行增加-Dhttp.proxyHost=代理ip -Dhttp.proxyPort=代理端口;一个是操作系统级别设置环境变量http_proxy=代理ip:代理端口,并且在java命令行增加-Djava.net.userSystemProxies,方法2无效。验证结果,方法一可行,方法二无效。
二、对Tomcat应用进行验证
首先在catalina.sh中的JAVA_OPTS环境变量中增加-Dhttp.proxyHost=代理ip -Dhttp.proxyPort=代理端口,发现并未生效,又增加了-Dhttp.proxySet=true,正向代理仍未生效,此路不通。然后,设置环境变量http_proxy,再在catalina.sh文件中的JAVA_OPTS增加-Djava.net.userSystemProxies,测试一下,仍然无效。看来只能侵入代码了,所以在应用启动过程中使用@PostConstruct注解在代码中设置:
System.setProperty("http.proxyHost", "代理ip");
System.setProperty("http.proxyPort", "代理端口");
满怀期待的运行,结果仍是竹篮打水。
不过根据前面的问题能看出,虚拟机启动时设置的代理IP和端口属性并未对Tomcat中部署的应用的外部请求产生影响,再考虑到应用中请求外部HTTP服务使用的是apche的HttpClient,猜想HttpClient有自己的代理处理机制。赶紧下载httpcomponents-client和httpcomponents-core两个包的源码并进行研究,发现确实如此。而在Srping-boot编写的应用,使用RestTemplate进行外部HTTP调用时,能够使用通过JAVA_OPTS设置的代理,分析应该是底层直接使用了JDK的HTTP相关类库。
三、解决办法
直接修改代码,具体片段如下:
CloseableHttpClient client = null;
HttpGet httpGet = new HttpGet(url);
HttpHost proxy = new HttpHost("10.111.2.24", 80);
RequestConfig defaultRequestConfig = RequestConfig.custom()
.setConnectTimeout(60000).setSocketTimeout(60000).setProxy(proxy).build();
client = HttpClients.custom().setDefaultRequestConfig(defaultRequestConfig).build();
打包部署后,测试。通过抓包工具查看数据包,果然在代理端收到了Tomcat服务发来的数据。
四、结论
在Tomcat中使用正向代理时,如果使用HttpClient则必须修改代码,或者直接使用JDK自带的net包中的类自己封装。否则只能升级到spring-boot了,^_^。
五、参考代码
以下是设置代理后,HttpClient包中发送Http请求时获取代理并发送请求的代码:
在HttpClientBuilder的builder方法中,以下代码设置HTTP请求的路由
HttpRoutePlanner routePlannerCopy = this.routePlanner;
if (routePlannerCopy == null) {
SchemePortResolver schemePortResolverCopy = this.schemePortResolver;
if (schemePortResolverCopy == null) {
//先设置缺省的端口解析器,如果HTTP请求中是IP则解析端口。如果是域名,对于http请求域名设置为80,否则为443
schemePortResolverCopy = DefaultSchemePortResolver.INSTANCE;
}
//代码中通过RequestConfig defaultRequestConfig = RequestConfig.custom()
//.setConnectTimeout(60000).setSocketTimeout(60000).setProxy(proxy).build();
//设置了proxy,所以进入第一个分支
if (proxy != null) {
//在路由策略中设置代理
routePlannerCopy = new DefaultProxyRoutePlanner(proxy, schemePortResolverCopy);
} else if (systemProperties) {//该属性缺省为false
routePlannerCopy = nDefaultProxyRoutePlannerew SystemDefaultRoutePlanner(
schemePortResolverCopy, ProxySelector.getDefault());
} else {
routePlannerCopy = new DefaultRoutePlanner(schemePortResolverCopy);
}
}
在DefaultProxyRoutePlanner(在父类DefaultRoutePlanner)中有如下方法确定路由:
@Override
public HttpRoute determineRoute(
final HttpHost host,
final HttpRequest request,
final HttpContext context) throws HttpException {
HttpHost proxy = config.getProxy();
……
if (proxy == null) {
return new HttpRoute(target, local, secure);
} else {//走如下路径
return new HttpRoute(target, local, proxy, secure);
}
}
在具体的HttpClient实现类InternalHttpClient类(继承自CloseableHttpClient)的doExecute方法中:
protected CloseableHttpResponse doExecute(
final HttpHost target//目标地址,
final HttpRequest request//HttpGet或者HttpPost,
final HttpContext context//zbl:为空) throws IOException, ClientProtocolException {
//获取路由,调用上面的方法获取到到代理的路由
final HttpRoute route = determineRoute(target, wrapper, localcontext);
return this.execChain.execute(route, wrapper, localcontext, execAware);
} catch (final HttpException httpException) {
throw new ClientProtocolException(httpException);
}
}
通过看代码,发现通过设置HttpClientBuilder的systemProperties属性,使用Java虚拟机配置的代理(就是前面的-D配置的IP和端口)。
HttpClients.custom().useSystemProperties().setDefaultRequestConfig(defaultRequestConfig).build();
修改代码,打包运行,结果不出所料。
补充https正向代理:
针对spring-boot,使用-Dhttp.proxyHost=代理ip -Dhttp.proxyPort=代理端口,不能代理https请求到指定的代理服务器;使用-Dhttps.proxyHost=代理ip -Dhttps.proxyPort=代理端口,请求被代理到了nginx但却返回400 Bad Request,经查本质原因是nginx缺省安装不支持HTTPS代理,所以需要在nginx中增加https模块并重新编译。
重新编译并安装nginx后,再次验证,使用-Dhttps.proxyHost=代理ip -Dhttps.proxyPort=代理端口,请求被代理到nginx。也可以直接在代码中直接设置:
RestTemplate restTemplate = new RestTemplate(new SimpleClientHttpRequestFactory() {{
setProxy(new java.net.Proxy(java.net.Proxy.Type.HTTP, new InetSocketAddress(proxyIp, proxyPort)));。然后就可以正常进行请求。
更多推荐
所有评论(0)