背景
在国内访问海外阿里云SDK时,经常超时,而且丢包严重;现象如下:
为了解决这个问题,尝试在香港搭建Nginx代理,通过代理来访问海外地址
Nginx代理
HTTP代理
首先是搭建Nginx代理,为了快速验证代理的可行性,先以代理HTTP为例进行验证;
Nginx的部署我这里是直接容器部署的,然后给他挂载一个简单的配置文件来负载默认的配置文件
user root;
worker_processes auto;
#error_log logs/error.log notice;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
gzip on;
upstream ap_southeast_5 {
server 149.129.204.7:443 weight=1;
server 147.139.128.4:443 weight=1;
}
server {
listen 32880;
proxy_connect;
proxy_connect_allow 443 563;
proxy_connect_connect_timeout 10s;
proxy_connect_read_timeout 10s;
proxy_connect_send_timeout 10s;
# forward proxy for non-CONNECT request
location / {
proxy_set_header Host $http_host;
proxy_pass http://ap_southeast_5;
}
}
}
在这份配置文件中,Nginx监听的是32880端口,然后将收到的所有请求直接转到阿里云雅加达的SDK站点地址
然后写个简单的单元测试验证下代理的可行性
@Test
public void testListRegions() throws Exception {
Config config = new Config();
config.setAccessKeyId(accessKey);
config.setAccessKeySecret(secretKey);
config.setRegionId("ap-southeast-5");
config.setProtocol("http");
config.setHttpProxy("http://ng-ip:28089");
com.aliyun.ecs20140526.Client client = new com.aliyun.ecs20140526.Client(config);
com.aliyun.ecs20140526.models.DescribeRegionsRequest request = new DescribeRegionsRequest();
request.setResourceType("instance");
DescribeRegionsResponse response = client.describeRegionsWithOptions(request, new RuntimeOptions());
log.info("Done");
}
示例中的ng-ip
替换为实际的Nginx运行IP即可,上述单元测试是可以正常跑通的。
这里吐槽下,最初我还以为是需要设置服务端点为代理地址即可,可是这样是不行的,因为阿里云SDK会把Header中的host作为鉴权签名内容的一部分,直接设置端点的话会引起签名错误
config.setEndpoint("http://ng-ip:28089");
抓包后可以看到Nginx代理上收到的包和发出去的包host变了,因此会引起签名错误;阿里云文档上没有找到相关的使用说明。
HTTPS代理
HTTP跑通之后,再来看看代理HTTPS请求,添加Nginx配置文件
server {
listen 28444 ssl;
proxy_connect;
proxy_connect_allow 443 563;
proxy_connect_connect_timeout 10s;
proxy_connect_read_timeout 10s;
proxy_connect_send_timeout 10s;
#ssl on;
ssl_certificate /data/nginx/cert/xx.com/xx.com.crt;
ssl_certificate_key /data/nginx/cert/xx.com/xx.com.key;
ssl_protocols SSLv3 TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_set_header Host $http_host;
proxy_pass https://slb.ap-southeast-5.aliyuncs.com;
}
}
添加的配置让Nginx通过https方式监听在28444 端口
然后修改示例代码:
@Test
public void testListRegions() throws Exception {
Config config = new Config();
config.setAccessKeyId(accessKey);
config.setAccessKeySecret(secretKey);
config.setRegionId("ap-southeast-5");
config.setHttpsProxy("https://ng-ip:28444");
com.aliyun.ecs20140526.Client client = new com.aliyun.ecs20140526.Client(config);
com.aliyun.ecs20140526.models.DescribeRegionsRequest request = new DescribeRegionsRequest();
request.setResourceType("instance");
// size: 30
DescribeRegionsResponse response = client.describeRegionsWithOptions(request, new RuntimeOptions());
log.info("Done");
}
这次却怎么都不成功,一直报错CONNECT 400
,抓包之后可以看到客户端发出的请求仍然是HTTP类型的,正常来讲应该是TLS才对
看了下SDK代码后,发现他们使用的是okhttp作为客户端发送代理请求的,于是乎,搜了下okhttp proxy https
,找了一圈后,在okhttp的 issue里找到这个问题:https://github.com/square/okhttp/issues/6561
发现使用okhttp代理https时需要设置socketFactory
,而阿里云SDK里没有入口可以设置这个参数,重写SDK的部分代码后,并引入这个类:https://github.com/square/okhttp/blob/480c20e46bb1745e280e42607bbcc73b2c953d97/okhttp/src/test/java/okhttp3/DelegatingSocketFactory.java
设置上了socketFactory
后,确实没有报错CONNECT 400
了,但是这次又开始报错CONNECT 502
一番折腾后,发现示例代码里的配置需要调整
Config config = new Config();
config.setAccessKeyId(accessKey);
config.setAccessKeySecret(secretKey);
config.setRegionId("ap-southeast-5");
config.setProtocol("http");
config.setHttpsProxy("https://ng-ip:28444");
需要设置协议为HTTP,这次终于不报错了,虽然这里设置了协议是http,但是抓包你会发现协议是TLS,至此才终于解决了这个问题。
Nginx forward https
上述为了简化问题,没有特别说明Nginx也是需要重新编译的,参考文档的说明:
https://stackoverflow.com/questions/46330313/nginx-ssl-forward-proxy-config
Nginx默认不支持转发HTTPS,因为不支持CONNECT方法;
为此需要添加ngx_http_proxy_connect_module
模块,参考地址为:
https://github.com/chobits/ngx_http_proxy_connect_module?tab=readme-ov-file#install
如果是容器版本的,可以参考:
https://docs.getconvoy.io/product-manual/forward-proxies/nginx