目前公司的产品采用了多个 ECS 加 SLB 的方式负载均衡部署。出于节省成本和安全方面的考虑,我们只有一台作为 CDN 源站的 ECS 开通了外网带宽。
由于业务上有调用互联网 API 的需求(比如调用微信开放平台的接口),我用 Node.js 写了一个简单的 HTTP API 代理,部署在开通外网带宽的 ECS 上。其他 ECS 上的业务系统调用互联网 API 时均通过这个代理进行,功能上虽然满足了但是不够通用。
为了解决这个问题,我今天换了一种实现方案:在开通外网带宽的 ECS 上安装 Squid
实现正向代理,让业务系统通过 Squid 请求互联网 API。选用 Squid 的原因是它是一个高性能的代理缓存服务器,支持 FTP、gopher 和 HTTP 协议,它使用一个单独的、非模块化的、I/O 驱动的进程来处理所有的客户端请求。
正向代理的安装
安装并启动 Squid
1 2 3
| $ sudo yum -y squid # 安装 $ sudo squid -z # 初始化 $ sudo /etc/init.d/squid start # 启动
|
测试 Squid
在没有外网带宽的 ECS 添加配置文件 http-proxy.sh:
1 2
| $ sudo vi /etc/profile.d/http-proxy.sh $ source /etc/profile.d/http-proxy.sh
|
文件内容如下:
1 2 3 4
| #!/bin/sh export http_proxy=http://proxy.mydomain.com:3128 export https_proxy=http://proxy.mydomain.com:3128
|
其中的 proxy.mydomain.com
是作为 HTTP 代理的 ECS 服务器名称。
1
| $ curl -v http://www.douban.com/
|
测试结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| * About to connect() to proxy proxy.mydomain.com port 3128 (#0) * Trying xxx.xxx.xxx.xxx... * Connected to proxy.mydomain.com (xxx.xxx.xxx.xxx) port 3128 (#0) > GET http://www.douban.com/ HTTP/1.1 > User-Agent: curl/7.29.0 > Host: www.douban.com > Accept: */* > Proxy-Connection: Keep-Alive > * HTTP 1.0, assume close after body < HTTP/1.0 301 Moved Permanently < Date: Sat, 03 Dec 2016 05:49:58 GMT < Content-Type: text/html < Content-Length: 178 < Location: https://www.douban.com/ < Server: dae < X-Cache: MISS from proxy.mydomain.com < X-Cache-Lookup: MISS from proxy.mydomain.com:3128 < Via: 1.0 proxy.mydomain.com (squid/3.1.23) * HTTP/1.0 connection set to keep alive! < Connection: keep-alive < <html> <head><title>301 Moved Permanently</title></head> <body bgcolor="white"> <center><h1>301 Moved Permanently</h1></center> <hr><center>nginx</center> </body> </html> * Connection #0 to host proxy.mydomain.com left intact
|
从结果中可以看出请求时通过 proxy.mydomain.com
服务器完成的。
应用端的配置
Java 应用的配置
对于 Java 应用,最简单的方式是修改 JRE 安装目录下的网络配置文件 lib/net.properties
,启用系统代理:
1 2
| java.net.useSystemProxies=true http.nonProxyHosts=localhost|127.*|10.*.*.*
|
配置项 http.nonProxyHosts
的目的是对本地接口的调用不通过代理。
比上述方式更灵活的方式是在应用的启动命令上添加相关参数:
1 2 3 4 5 6
| $ java -Dhttp.proxyHost=proxy.mydomain.com \ -Dhttp.proxyPort=3128 \ -Dhttps.proxyHost=proxy.mydomain.com \ -Dhttps.proxyPort=3128 \ -Dhttp.nonProxyHosts="localhost|127.*|10.*.*.*" \ ...
|
最灵活的方式是在程序中指定代理,示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import java.net.InetSocketAddress; import java.net.Proxy; import java.net.SocketAddress; import java.net.URL; import java.net.URLConnection; SocketAddress addr = new InetSocketAddress("proxy.mydomain.com", 3128); Proxy proxy = new Proxy(Proxy.Type.HTTP, addr); URL url = new URL("http://foo.example.com/"); URLConnection conn = url.openConnection(proxy); URL url2 = new URL("http://bar.example.com/"); URLConnection conn2 = url2.openConnection(Proxy.NO_PROXY);
|
Node.js 应用的修改
对于 Node.js 应用,如果使用了 http
进行接口调用,则代码类似如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const options = { host: 'proxy.mydomain.com', port: 3128, path: apiUrl, } const request = http.request(options, (res) => { let str = '' res.on('data', (chunk) => { str += chunk }) res.on('end', () => { console.log('No more data in response.') }) }) request.on('error', (e) => { console.log(`problem with request: ${e.message}`) }) request.end()
|