强制 body-parser 解析无 Content-Type 请求

需求

原版 EOS 历史 API 插件将数据都保存在内存,随着历史数据越来越多,内存消耗高达 T 级以上,使得这个插件失去实用性。于是出现很多替代产品,比如把数据同步到 MongoDB,然后用 Nodejs 对接 MongoDB 来实现 API 服务。

2019 年 3 月份,MEET.ONE 实现了一个基于 MongoDB 的 EOS 历史 API 服务。劣者将去掉 MongoDB 交互部分的框架开源于 UMU618/eos-history-api-service

测试

这个 API 服务的开发者是公司另一名 Web 全栈开发,他测试通过之后,劣者用 cleos 一试,立马 bug!调试后端代码,发现 req.body 不是一个 JSON 对象。

劣者立刻用 tcpdump 抓包,发现 cleos 发出去的包并无异常,body 就是一段 JSON 数据。

交流后,发现测试工具的差异:劣者是 C++ 开发,自然而然使用 cleos 测试,而 Web 全栈开发对 cleos 比较陌生,他们会选择 postman 或者自己写测试性客户端。比如:

1
2
3
4
5
6
7
const request = require('request-json')
const client = request.createClient('http://127.0.0.1:8888/')

const json = {"id": "d5245026c757532ea3dd5b3a02a07620eb7238113d0a49cae5ebb93921a34135"};
client.post('/v1/history/get_transaction', json, function(err, res, body) {
console.log(res.statusCode, body)
})

后来劣者写了一个简易的 Web 服务器,显示请求头。

1
2
3
4
5
6
7
8
9
10
11
12
13
const http = require('http')

http.createServer(function (req, res) {
let buffer = req.method + ' ' + req.url + ' ' + req.httpVersion
console.log(buffer)
res.write(buffer + '\r\n')
for (let i = 0; i < req.rawHeaders.length; i += 2) {
buffer = req.rawHeaders[i] + ': ' + req.rawHeaders[i + 1]
console.log(buffer)
res.write(buffer + '\r\n')
}
res.end()
}).listen(8888)

经对比,cleos 发的请求不带 Content-Type。cleos 是 EOSIO 的官方工具,使用者众多,若不支持它是不合理的,后端也不能要求客户端都带上 Content-Type。

调试

检查后端代码,其对 body 的解析是用 body-parser 完成的:

1
app.use(bodyParser.json())

使用以下命令启动服务:

1
DEBUG=body-parser:* node app.js

发现有这样 2 行关键的调试信息:

1
2
body-parser:json content-type undefined +0ms
body-parser:json skip parsing +1ms

原来 body-parser 会检查 Content-Type,不符合它的预期,就不解析,于是 body 就不是 JSON 对象。

开发

暴力的解决方案

参考《Express 解析 json 格式 post 数据》后,我们这样解决:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 显式调用 JSON.parse 强行解析
app.use((req, res, next) => {
req.rawBody = ''
req.on('data', (chunk) => {
req.rawBody += chunk
})
req.on('end', () => {
try {
req.body = JSON.parse(req.rawBody)
} catch (err) {
req.body = null
}
next()
})
})

但劣者认为以上方案比较不优雅,JavaScript 作为一门高级语言,我们希望更多专注于业务逻辑,尽量复用现有代码,少自己写工具性代码。下面探讨使用 body-parser 的解法。

优雅的解决方案

劣者希望能告诉 body-parser 遇到不传 Content-Type 依然当它是 JSON 去解析。于是这就得去看它的代码!我们从上面关键的调试信息入手,可以很快发现:

1
2
3
4
5
6
// determine if request should be parsed
if (!shouldParse(req)) {
debug('skip parsing')
next()
return
}

而 shouldParse 是可以由传入的选项影响的:

1
2
3
4
5
6
7
8
9
10
11
12
function json (options) {
var opts = options || {}
// 省略部分无关代码
var type = opts.type || 'application/json'
// 省略部分无关代码

// create the appropriate type checking function
var shouldParse = typeof type !== 'function'
? typeChecker(type)
: type
// 省略部分无关代码
}

于是最终的解决方案是:如果不传 Content-Type,当做 application/json;但如果有传,那得传对,否则也是不理。效果上,比之前无脑地当成 application/json,稍微好一些。实现上,则更优雅。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
// Force body-parser to parse data as JSON
const bodyParser = require('body-parser')
const typeis = require('type-is')

app.use(bodyParser.json({ type: function(req) {
if (undefined === req.headers['content-type']) {
// cleos POST data without content-type
return true
} else {
return Boolean(typeis(req, 'application/json'))
}
}}))

(完)

优化思维【4】Bash for 循环

常规教材

Bash 循环有三种写法。

1. 类 C 语言语法

1
2
3
4
5

for ((i = 1; i <= 10000000; ++i))
do
echo $i
done

2. for in 语法

1
2
3
4
for i in {1..10000000}
do
echo $i
done

3. 使用外部命令 seq

1
2
3
4
for i in `seq 1 10000000`
do
echo $i
done

分析

这三种方法中,性能最好的是第一种,最差的是第三种。

方法 1 是语言层面的循环语法,循环会立刻开始,而后两种,作为对比,并不会立刻开始!

方法 2 中的 {1..10000000} 会产生一个 1 到 10000000 的序列,然后再开始循环。

方法 3 中的 seq 1 10000000 是调用 seq 产生一个 1 到 10000000 的序列,然后再开始循环。涉及到外部进程调用和管道传递,所以比方法 2 更慢。

总结

  • 方法 1 写起来最麻烦,性能却是最好的。“做一件事,有很多种方式”,有时候不是好事,有对比,就有伤害……优化往往是和人性作对!

  • 语言原生的方法一般比调用外部命令好。

优化思维【3】消除没必要步骤

故事

四月底给 EOSIO / eos 提了一个优化 MongoDB 插件性能的 PR,被连续感谢好几个 Release

分析

原先的流程:fc::variant -> JSON string -> BSON,实现起来很简单,因为 JSON 是很常见的,fc::variant 和 BSON 都有到 JSON 的转化,所以实现代码很简单,一行两个函数。

但数据大时,性能问题就暴露了,这个过程先把 fc::variant 对象序列化为 JSON 字符串,然后反序列化到 BSON 对象。两步都是 CPU 密集型操作,由于 nodeos 及其插件暂时对多核支持不好,导致单核跑爆。

两个过程都要用递归实现,调用栈可能很深。调用函数可能有入栈出栈的消耗,有一种优化思路正是用 inline 减少函数的频繁调用

回归到本质,fc::variant 和 BSON 都是对象,应该直接转化才对。只是实现起来就不是一行能搞定的。先挑简单的方式实现,后期再优化,这是一种挺常规的做法。

优化思维【2】有符号和无符号的本质区别

做题

以下代码打印什么?

1
2
3
4
5
6
7
auto count = sizeof (int);
if (count > -1) {
std::cout << "> -1";
}
else {
std::cout << "<= -1";
}

答案是:<= -1,因为 sizeof (int) 是无符号的,把 auto 改为 int 则结果是 > -1

本质论

当我们声明 unsigned/signed int count 时,unsigned/signed 是变量 count 的使用属性,int 是其容量属性。

所谓使用属性,就是当它存在寄存器或内存时,不管是 unsigned 还是 signed 本质是一样的,但对它进行访问时,就区别对待。

比如对 count 进行加法,unsigned 时用的是 ADD 指令,signed 时用的是 ADC 指令,其余减乘除也都类似地使用不同指令。

再来一个问题:把一个变量保存到文件里,再读出来,怎么知道它是有符号还是无符号?

答案是:如果你不在序列化时考虑符号,则反序列化时,无法知道原来的符号,把它赋值给什么类型的变量它就变成什么类型。

这也是 JSON 文本转对象后,要自己选择数据类型的原因,因为 JSON 文本没表示符号的语法。

结论

优化思路:理解本质,就能了解限制和优化方向。

优化思维【1】字符串去空格

In place 版本

传入的字符串将被改变。

为方便复用,一般会实现 ltrim 和 rtrim 两个函数,然后 trim 函数调用这两者实现。

  • ltrim:从 str 头部开始找到非空格字符,偏移量记为 offset,将 str 左移(move)offset 个字符。

  • rtrim:从 str 尾部向头部找到非空格字符,偏移量记为 offset,将 str 截断为 offset。

一个想当然的实现:

1
2
3
4
trim(str) {
ltrim(str)
rtrim(str)
}

假设 str 是 x 个空格 + y 个非空格 + z 个空格,则以上代码需要把 y + z 个字符向左移动 x 个位置。

更好的实现是:

1
2
3
4
trim(string& str) {
rtrim(str)
ltrim(str)
}

由于先截断,剩 x + y,再去左移,只需要把 y 个字符左移 x 个位置。

优化思路:尽量减少复制,调整顺序也是优化手段。

Copy 版本

传入的字符串不会被改变,返回一个新的字符串。

一个复用前面代码的实现:

1
2
3
4
5
6
string trim_copy(string str) {
// str is a copy
rtrim(str)
ltrim(str)
return str
}

这个版本需要复制 x + y + z 个字符,ltrim 和 rtrim 里面都有找偏移量的代码可以复用,直接找到 y 个非空格字符是起点和终点,复制这 y 个字符就好了。

1
2
3
4
5
6
string trim_copy(const string& str) {
// str is a copy
l = lfind(str)
r = rfind(str)
return str[l, r]
}

优化思路:尽量减少复制。

move

strlen、strcpy、memmove 这类函数,都有一个优化思路:机器字长对齐,一次处理一个机器字。对于长字符串,效果显著。

优化思路:针对硬件特征调整策略。

修复 Clang 编译错误:error: expected unqualified-id

问题

今天编译 EOSIO/eos 出现一些 error: expected unqualified-id

环境

  • 操作系统:macOS Mojave
  • 编译器:AppleClang 10.0.1.10010046
  • SDK:MacOSX10.14.sdk
  • Boost:1.69.0(1.67.0 也有问题,干脆用这个版本)

验证问题

1
2
3
4
5
6
7
8
9
#include <signal.h>

int main() {
::sigset_t sigset;

::sigemptyset(&sigset);
::sigaddset(&sigset, SIGCHLD);
return 0;
}

编译输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Scanning dependencies of target signal
[ 50%] Building CXX object CMakeFiles/signal.dir/signal.cpp.o
/Users/umu/umutech/macos-cpp/source/study/posix/signal/signal.cpp:6:5: error: expected unqualified-id
::sigemptyset(&sigset);
^
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/usr/include/signal.h:125:26: note: expanded from macro 'sigemptyset'
#define sigemptyset(set) (*(set) = 0, 0)
^
/Users/umu/umutech/macos-cpp/source/study/posix/signal/signal.cpp:7:5: error: expected unqualified-id
::sigaddset(&sigset, SIGCHLD);
^
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/usr/include/signal.h:122:31: note: expanded from macro 'sigaddset'
#define sigaddset(set, signo) (*(set) |= __sigbits(signo), 0)
^
2 errors generated.
make[2]: *** [CMakeFiles/signal.dir/signal.cpp.o] Error 1
make[1]: *** [CMakeFiles/signal.dir/all] Error 2
make: *** [all] Error 2

解决

去掉 sigemptysetsigaddset 前面的 :: 即可。因为他们是宏,宏都是全局的,用 :: 修饰反而错了,严格!

Boost 开发分支上已经修复:

https://github.com/boostorg/process/blob/develop/include/boost/process/detail/posix/wait_for_exit.hpp#L60

https://github.com/boostorg/process/blob/develop/include/boost/process/detail/posix/wait_group.hpp#L65

纯数字 EOS 账号

作者: UMU @ MEET.ONE 实验室

问题

2018 年最后一个工作日,智能合约开发小哥哥遇到一个奇怪的现象:某个账号给我们合约转账,在 EOS 浏览器上都可以找到记录,但用 cleos get table 在合约的 RAM 里找却找不到!

解决

观测

了解具体情况后,注意到两个事实:

  1. 只有某个特定账号有问题,其它账号很正常。

  2. 那个有问题的账号是纯数字的。

这是 EOS 账号解析的问题,UMU 曾经给 EOS 提过一个相关的 issue:get_table_by_scope parameter lower_bound is NOT properly converted, cause enumeration dead loop #5824,里面有问题产生原因和解决方案。

原因

eosio::name 本质是一个 uint64_t 数字的 base32 编码,编码形式是为了方便人类记忆。举个例子:

shengxiaokai 本质上是 14075216089888066784 (0xc3553675c6a40ce0)

cleos get table 在解析账号时,兼容了这两种表达形式,所以 14075216089888066784shengxiaokai 是等价的。

但本身是纯数字的账号可就有歧义了,比如 313131313131 是当成一个 uint64_t 解释,还是当成 base32?很不巧,解析代码是优先当成 uint64_t 解释的。

解决方案

给纯数字 EOS 账号加上个空格后缀,比如 111122223333 可以改为 "111122223333 "

改进 EOSIO MongoDB 插件对分片集群的插入性能

作者: UMU @ MEET.ONE 实验室

问题

为了保证 MongoDB 服务器的容量足够应对未来发展,我们做了分片,但经过对比测试,发现每隔 15 分钟左右,nodeos 就会报错,并优雅退出。

查看了社区的 issues 发现有类似情况:mongodb shard: line 870, code 61, generic server error

解决

定位代码

反复测试发现,总是同一个地方抛出异常:

1
2
3
4
5
6
7
8
9
10
11
// insert action_traces
if( write_atraces ) {
try {
if( !bulk_action_traces.execute() ) {
EOS_ASSERT( false, chain::mongo_db_insert_fail,
"Bulk action traces insert failed for transaction trace: ${id}", ("id", t->id) );
}
} catch( ... ) {
handle_mongo_exception( "action traces insert", __LINE__ );
}
}

插入数据的代码很多,但就这个地方报错,说明 action_traces 表有特殊性。

分析异同

action_traces 的 shard key 被定义为 _id,而其他没报错的表并不是 _id。

理论

批量插入时,_id 是有单调递增性的,根据官方文档
Avoid Monotonic Throttling,需要降低单调递增性,才能使批量插入均匀分散到各个 shard 上。

成果

(#6498) Fix cluster writes for mongo DB

EOSIO 1.5.0 MongoDB 插件搭配 MongoDB 4.0.4

问题描述

在前文《为 EOSIO MongoDB 插件搭建高可用集群》中,我们使用了 MongoDB 4.0.4,如果直接配套 EOSIO 1.5 版本,MongoDB 插件用的 MongoDB C Driver 与 MongoDB 4.0.4 是不完全兼容的。

  • EOSIO 1.5 指定的 MongoDB 版本是 3.6.3;

  • MongoDB 4.0 的客户端需要 MongoDB C Driver 1.11.0,而 EOSIO 1.5 用的是 1.10.2。

参考:Release Notes for MongoDB 4.0

改进方案

修改 eos/scripts/eosio_build_${SYS_NAME}.sh,其中 SYS_NAME 是系统名字,以 macOS 为例,应该改的是 eos/scripts/eosio_build_darwin.sh

  • 把 mongo-c-driver 版本改到足够高,比如 1.13.0,即把脚本里的下载链接 https://github.com/mongodb/mongo-c-driver/releases/download/1.10.2/mongo-c-driver-1.10.2.tar.gz 改为 https://github.com/mongodb/mongo-c-driver/releases/download/1.13.0/mongo-c-driver-1.13.0.tar.gz

  • 把 mongo-cxx-driver 版本改到足够高,比如 3.4.0,可以直接把脚本里的 git clone https://github.com/mongodb/mongo-cxx-driver.git --branch releases/v3.3 --depth 1 改为 git clone https://github.com/mongodb/mongo-cxx-driver.git --branch releases/stable --depth 1

  • Ubuntu 操作参考:

1
2
3
4
5
6
7
8
9
10
11
12
sed -i 's/releases\/v3.3/releases\/stable/;s/1\.10\.2/1\.13\.0/g' scripts/eosio_build_ubuntu.sh

# 编译前
grep "Version:" /usr/local/lib/pkgconfig/libmongocxx-static.pc
Version: 3.3.2-pre

# 编译
./eosio_build.sh

# 编译后
grep "Version:" /usr/local/lib/pkgconfig/libmongocxx-static.pc
Version: 3.4.0

然后编译、安装 eos。

参考:

mongo-c-driver 1.13.0

MongoDB C++11 Driver 3.4.0

注意事项

以上方法不适用于 v1.7.0,新版 eos 已经使用新版 mongo-c-driver 和 MongoDB C++11 Driver,唯一需要改的是 eos/scripts/eosio_build.sh 的这行:

1
export MONGODB_VERSION=4.0.6 #3.6.3

为编译 EOSIO 降级 boost 到 1.67

问题

目前 EOSIO 1.5 依赖的 boost 版本为 1.67,但最新的 boost 是 1.68,如果不小心用 brew upgrade 把 boost 升级到最新,则 EOSIO 代码将无法顺利编译。

解决

不必删除 1.68 版本,当之前安装过 1.67,再安装 1.68 时,其实两者是同时存在的,只是系统目录下的链接是指向最新版本而已。只要把链接改回 1.67 即可。

以下脚本适用于 macOS Mojave 系统,其它系统请自行做相应修改。

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
ln -s /usr/local/include/boost /usr/local/Cellar/boost/1.67.0_1/include/boost 

ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_atomic-mt.a /usr/local/lib/libboost_atomic-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_atomic-mt.dylib /usr/local/lib/libboost_atomic-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_chrono-mt.a /usr/local/lib/libboost_chrono-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_chrono-mt.dylib /usr/local/lib/libboost_chrono-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_chrono.a /usr/local/lib/libboost_chrono.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_chrono.dylib /usr/local/lib/libboost_chrono.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_container-mt.a /usr/local/lib/libboost_container-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_container-mt.dylib /usr/local/lib/libboost_container-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_container.a /usr/local/lib/libboost_container.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_container.dylib /usr/local/lib/libboost_container.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_context-mt.a /usr/local/lib/libboost_context-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_context-mt.dylib /usr/local/lib/libboost_context-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_contract-mt.a /usr/local/lib/libboost_contract-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_contract-mt.dylib /usr/local/lib/libboost_contract-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_contract.a /usr/local/lib/libboost_contract.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_contract.dylib /usr/local/lib/libboost_contract.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_coroutine-mt.a /usr/local/lib/libboost_coroutine-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_coroutine-mt.dylib /usr/local/lib/libboost_coroutine-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_coroutine.a /usr/local/lib/libboost_coroutine.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_coroutine.dylib /usr/local/lib/libboost_coroutine.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_date_time-mt.a /usr/local/lib/libboost_date_time-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_date_time-mt.dylib /usr/local/lib/libboost_date_time-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_date_time.a /usr/local/lib/libboost_date_time.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_date_time.dylib /usr/local/lib/libboost_date_time.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_exception-mt.a /usr/local/lib/libboost_exception-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_exception.a /usr/local/lib/libboost_exception.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_fiber-mt.a /usr/local/lib/libboost_fiber-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_fiber-mt.dylib /usr/local/lib/libboost_fiber-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_filesystem-mt.a /usr/local/lib/libboost_filesystem-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_filesystem-mt.dylib /usr/local/lib/libboost_filesystem-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_filesystem.a /usr/local/lib/libboost_filesystem.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_filesystem.dylib /usr/local/lib/libboost_filesystem.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_graph-mt.a /usr/local/lib/libboost_graph-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_graph-mt.dylib /usr/local/lib/libboost_graph-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_graph.a /usr/local/lib/libboost_graph.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_graph.dylib /usr/local/lib/libboost_graph.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_iostreams-mt.a /usr/local/lib/libboost_iostreams-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_iostreams-mt.dylib /usr/local/lib/libboost_iostreams-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_iostreams.a /usr/local/lib/libboost_iostreams.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_iostreams.dylib /usr/local/lib/libboost_iostreams.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_locale-mt.a /usr/local/lib/libboost_locale-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_locale-mt.dylib /usr/local/lib/libboost_locale-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_log-mt.a /usr/local/lib/libboost_log-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_log-mt.dylib /usr/local/lib/libboost_log-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_log.a /usr/local/lib/libboost_log.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_log.dylib /usr/local/lib/libboost_log.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_log_setup-mt.a /usr/local/lib/libboost_log_setup-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_log_setup-mt.dylib /usr/local/lib/libboost_log_setup-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_log_setup.a /usr/local/lib/libboost_log_setup.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_log_setup.dylib /usr/local/lib/libboost_log_setup.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_math_c99-mt.a /usr/local/lib/libboost_math_c99-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_math_c99-mt.dylib /usr/local/lib/libboost_math_c99-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_math_c99.a /usr/local/lib/libboost_math_c99.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_math_c99.dylib /usr/local/lib/libboost_math_c99.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_math_c99f-mt.a /usr/local/lib/libboost_math_c99f-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_math_c99f-mt.dylib /usr/local/lib/libboost_math_c99f-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_math_c99f.a /usr/local/lib/libboost_math_c99f.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_math_c99f.dylib /usr/local/lib/libboost_math_c99f.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_math_c99l-mt.a /usr/local/lib/libboost_math_c99l-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_math_c99l-mt.dylib /usr/local/lib/libboost_math_c99l-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_math_c99l.a /usr/local/lib/libboost_math_c99l.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_math_c99l.dylib /usr/local/lib/libboost_math_c99l.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_math_tr1-mt.a /usr/local/lib/libboost_math_tr1-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_math_tr1-mt.dylib /usr/local/lib/libboost_math_tr1-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_math_tr1.a /usr/local/lib/libboost_math_tr1.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_math_tr1.dylib /usr/local/lib/libboost_math_tr1.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_math_tr1f-mt.a /usr/local/lib/libboost_math_tr1f-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_math_tr1f-mt.dylib /usr/local/lib/libboost_math_tr1f-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_math_tr1f.a /usr/local/lib/libboost_math_tr1f.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_math_tr1f.dylib /usr/local/lib/libboost_math_tr1f.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_math_tr1l-mt.a /usr/local/lib/libboost_math_tr1l-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_math_tr1l-mt.dylib /usr/local/lib/libboost_math_tr1l-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_math_tr1l.a /usr/local/lib/libboost_math_tr1l.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_math_tr1l.dylib /usr/local/lib/libboost_math_tr1l.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_prg_exec_monitor-mt.a /usr/local/lib/libboost_prg_exec_monitor-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_prg_exec_monitor-mt.dylib /usr/local/lib/libboost_prg_exec_monitor-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_prg_exec_monitor.a /usr/local/lib/libboost_prg_exec_monitor.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_prg_exec_monitor.dylib /usr/local/lib/libboost_prg_exec_monitor.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_program_options-mt.a /usr/local/lib/libboost_program_options-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_program_options-mt.dylib /usr/local/lib/libboost_program_options-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_program_options.a /usr/local/lib/libboost_program_options.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_program_options.dylib /usr/local/lib/libboost_program_options.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_random-mt.a /usr/local/lib/libboost_random-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_random-mt.dylib /usr/local/lib/libboost_random-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_random.a /usr/local/lib/libboost_random.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_random.dylib /usr/local/lib/libboost_random.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_regex-mt.a /usr/local/lib/libboost_regex-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_regex-mt.dylib /usr/local/lib/libboost_regex-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_regex.a /usr/local/lib/libboost_regex.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_regex.dylib /usr/local/lib/libboost_regex.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_serialization-mt.a /usr/local/lib/libboost_serialization-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_serialization-mt.dylib /usr/local/lib/libboost_serialization-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_serialization.a /usr/local/lib/libboost_serialization.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_serialization.dylib /usr/local/lib/libboost_serialization.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_signals-mt.a /usr/local/lib/libboost_signals-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_signals-mt.dylib /usr/local/lib/libboost_signals-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_signals.a /usr/local/lib/libboost_signals.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_signals.dylib /usr/local/lib/libboost_signals.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_stacktrace_addr2line-mt.a /usr/local/lib/libboost_stacktrace_addr2line-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_stacktrace_addr2line-mt.dylib /usr/local/lib/libboost_stacktrace_addr2line-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_stacktrace_addr2line.a /usr/local/lib/libboost_stacktrace_addr2line.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_stacktrace_addr2line.dylib /usr/local/lib/libboost_stacktrace_addr2line.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_stacktrace_basic-mt.a /usr/local/lib/libboost_stacktrace_basic-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_stacktrace_basic-mt.dylib /usr/local/lib/libboost_stacktrace_basic-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_stacktrace_basic.a /usr/local/lib/libboost_stacktrace_basic.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_stacktrace_basic.dylib /usr/local/lib/libboost_stacktrace_basic.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_stacktrace_noop-mt.a /usr/local/lib/libboost_stacktrace_noop-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_stacktrace_noop-mt.dylib /usr/local/lib/libboost_stacktrace_noop-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_stacktrace_noop.a /usr/local/lib/libboost_stacktrace_noop.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_stacktrace_noop.dylib /usr/local/lib/libboost_stacktrace_noop.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_system-mt.a /usr/local/lib/libboost_system-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_system-mt.dylib /usr/local/lib/libboost_system-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_system.a /usr/local/lib/libboost_system.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_system.dylib /usr/local/lib/libboost_system.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_test_exec_monitor-mt.a /usr/local/lib/libboost_test_exec_monitor-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_test_exec_monitor.a /usr/local/lib/libboost_test_exec_monitor.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_thread-mt.a /usr/local/lib/libboost_thread-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_thread-mt.dylib /usr/local/lib/libboost_thread-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_timer-mt.a /usr/local/lib/libboost_timer-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_timer-mt.dylib /usr/local/lib/libboost_timer-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_timer.a /usr/local/lib/libboost_timer.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_timer.dylib /usr/local/lib/libboost_timer.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_type_erasure-mt.a /usr/local/lib/libboost_type_erasure-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_type_erasure-mt.dylib /usr/local/lib/libboost_type_erasure-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_type_erasure.a /usr/local/lib/libboost_type_erasure.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_type_erasure.dylib /usr/local/lib/libboost_type_erasure.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_unit_test_framework-mt.a /usr/local/lib/libboost_unit_test_framework-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_unit_test_framework-mt.dylib /usr/local/lib/libboost_unit_test_framework-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_unit_test_framework.a /usr/local/lib/libboost_unit_test_framework.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_unit_test_framework.dylib /usr/local/lib/libboost_unit_test_framework.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_wave-mt.a /usr/local/lib/libboost_wave-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_wave-mt.dylib /usr/local/lib/libboost_wave-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_wserialization-mt.a /usr/local/lib/libboost_wserialization-mt.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_wserialization-mt.dylib /usr/local/lib/libboost_wserialization-mt.dylib
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_wserialization.a /usr/local/lib/libboost_wserialization.a
ln -f /usr/local/Cellar/boost/1.67.0_1/lib/libboost_wserialization.dylib /usr/local/lib/libboost_wserialization.dylib