使用 Shadowsocks-libev 构建网络代理

1. 序

作为一个程序员,总有着一些科学上网的需求。面对纷繁的服务提供商,面对时断时续的网络服务,总还是觉得老话说的有道理:自己动手丰衣足食!

2. 搭建过程

搭建代理服务器的常见手段是在一台境外服务器上启动 Shadowsocks 服务,然后在终端使用客户端连接过去。大致过程可以分为几个简单步骤:

  1. 购买境外服务器。

  2. 搭建 Shadowsocks 服务。

  3. 下载客户端连接。

2.1 购买服务器

在同事的推荐下,在 Vultr1 购买相关的服务。配置和价格如下:

项目 规格
CPU 1 vCore
RAM 1024 M
Storage 25 G SSD
Bandwidth 1000 GB
Location Silicon Valley
OS Ubuntu 18.04
Price $ 5 /month

但就价格来说每年对应人民币在 400 元左右价格偏贵,但是如果考虑到同时能搭建 WordPress 和 Shadowsocks 服务的话相对更容易接受一些。

另外一个好处是灵活,如果这个 IP 被封了还可以在其他地区的机房搭建服务。

2.2 搭建服务

搭建服务只需要按步骤执行如下命令(默认 root 用户)2

 apt-get update
 apt-get install -y shadowsocks-libev

然后编辑配置文件 /etc/shadowsocks-libev/config.json

 {
     "server":"0.0.0.0",
     "server_port":<port>,
     "local_port":1080,
     "password":"<password>",
     "timeout":60,
     "method":"aes-256-cfb"
 }

这里 <server port><password> 根据喜好设置即可。

然后我们启动服务:

 # 重新启动 shadowsocks-libev (以防按照旧的配置启动了服务)
 systemctl restart shadowsocks-libev
 
 # 设置随着操作系统自启动
 systemctl enable shadowsocks-libev.service
 
 # 查看服务状态
 systemctl status shadowsocks-libev.service

另外针对网络拥塞的情况我们还可以使用 BBR 来提升网速,具体的方法为3

 echo "net.core.default_qdisc=fq" >> /etc/sysctl.conf
 echo "net.ipv4.tcp_congestion_control=bbr" >> /etc/sysctl.conf
 sysctl -p

关于 BBR 的原理可参见参考资料4

2.3 下载客户端

Shadowsocks 的客户端可以从 Github 进行下载,具体页面为:

3. 参考资料

RestTemplate 与 Gzip Content-Encoding

1. 问题描述

最近做一个针对 Yarn Application 进行错误诊断的需求,需要从 Resource Manager 获取 Application 的运行信息,比如:

 GET /ws/v1/cluster/apps/application_1561545353229_936285 HTTP/1.1
 Host: yarn.xxx.com
 Accept: */*
 accept-encoding: gzip, deflate

这个接口使用 Postman 可以得到对应的结果:

 {
     "app": {
         "id": "application_1561545353229_936285",
         "user": "bp_growth",
         "name": "moses:278648",
         "queue": "root.bp_growth_dev",
         "state": "FINISHED",
         "finalStatus": "FAILED",
         "progress": 100,
         "trackingUI": "History",
         "trackingUrl": "http://yarn-rm02.tc.rack.xxx.com:8088/proxy/application_1561545353229_936285/",
         "diagnostics": "Task failed task_1561545353229_936285_m_000008\nJob failed as tasks failed. failedMaps:1 failedReduces:0\n",
         "clusterId": 1561545353229,
         "applicationType": "MAPREDUCE",
         "applicationTags": "",
         "startedTime": 1562740151124,
         "finishedTime": 1562740195218,
         "elapsedTime": 44094,
         "amContainerLogs": "http://data1117.tc.rack.xxx.com:8042/node/containerlogs/container_e93_1561545353229_936285_01_000001/bp_growth",
         "amHostHttpAddress": "data1117.tc.rack.xxx.com:8042",
         "allocatedMB": -1,
         "allocatedVCores": -1,
         "reservedMB": -1,
         "reservedVCores": -1,
         "runningContainers": -1,
         "memorySeconds": 1583688,
         "vcoreSeconds": 721,
         "preemptedResourceMB": 0,
         "preemptedResourceVCores": 0,
         "numNonAMContainerPreempted": 0,
         "numAMContainerPreempted": 0,
         "logAggregationStatus": "TIME_OUT"
     }
 }

但是使用 RestTemplate 去请求的时候却会得到异常:

 org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Illegal character ((CTRL-CHAR, code 31)): only regular white space (\r, \n, \t) is allowed between tokens; nested exception is com.fasterxml.jackson.core.JsonParseException: Illegal character ((CTRL-CHAR, code 31)): only regular white space (\r, \n, \t) is allowed between tokens
  at [Source: (PushbackInputStream); line: 1, column: 2]

原因是 Response 的结果使用了 Gzip 进行压缩。

2. 解决方案

在网上找到的最简单的解决方案来自:How to parse gzip encoded response with RestTemplate from Spring-Web

简单来说就是在构造 RestTemplate 的时候指定 ClientHttpRequestFactory。

Maven 项目引入一个依赖:

 <dependency>
   <groupId>org.apache.httpcomponents</groupId>
   <artifactId>httpclient</artifactId>
 </dependency>

由于我同时使用了 SpringBoot,所以不需要指定版本。

具体使用时如:

   @Test
   public void test() {
 
     HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(
         HttpClientBuilder.create().build());
     RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory);
 
     Map forObject = restTemplate.getForObject("http://yarn.xxx.com/ws/v1/cluster/apps/application_1561545353229_936285", Map.class);
     System.out.println("forObject = " + forObject);
   }