自建 CA 和 PKI 相关内容

企业内部服务互相认证,或是为内部服务颁发 ssl 证书,包括有些 wifi 认证证书的话,一般会选择使用自建 CA 来解决,既保证了灵活性,又节约了成本。
目前有一个自建 docker registry 的需求,所以就研究了一下自建 CA 及其相关内容。因为之前对 PKI 这部分的东西了解的比较少,所以这一次花了一些时间补了一下课。

首先从最基本的认识开始,如果需要被访问的 https 站点被信任,那么这个站点的证书需要在系统的信任列表中。而证书,是分层级的。分为根证书和由根证书颁发的证书。想要站点被信任,只要信任根证书,或者是颁发的证书,都可以。

重要的是,如果信任了根证书,就相当于信任了所有由这个根证书颁发出来的证书。也就是说,在企业内部,客户端只要信任了根证书,就不用麻烦的每次信任新的颁发证书了。

第一步,需要创建一个根证书,分为两步,
1. 创建私钥,后续用来颁发证书的基础,整个体系最重要的东西。
2. 用私钥生成根证书,提供给客户端信任,同时配合私钥,用来颁发证书

1. 创建私钥:创建私钥非常简单,openssl genrsa -out /etc/pki/CA/private/cakey.pem 4096 ,就可以了。这句话里面包含了很多的信息,我一个一个讲清楚。
a) genrsa 是选择用 rsa 算法生成 key
b) 把文件生成到位置 /etc/pki/CA/private/cakey.pem,是因为在本地颁发证书时,默认使用这个位置的私钥进行颁发。这个位置,是配置在 /etc/pki/tls/openssl.cnf 文件中的,这个文件中还有很多相关内容,一会儿会把重要的配置都列出来。
c) 4096,生成多少位的 rsa key
2. 用私钥生成根证书:openssl req -new -days 3650 -x509 -key /etc/pki/CA/private/cakey.pem -out /etc/pki/CA/cacert.pem,即创建一个 x509 格式的证书,输出到 /etc/pki/CA/cacert.pem,同样的,颁发证书所需要的根证书,默认要放在这个位置,也是 /etc/pki/tls/openssl.cnf 中配置的。

两次提到 openssl.cnf,就把里面的有用的选项拿出来列一下。
1. [ ca ] 下面配置了 ca 证书相关的目录等信息,如:private_key 就是刚才的私钥位置,certificate 就是刚才证书的位置。database 是放置颁发证书的列表位置,serial 存放了当前颁发证书的序号。crl 比较重要,配置失效颁发证书的位置。default_days 默认证书有效时间。大部分配置默认就可以了,证书有效期可以改长点儿,省得麻烦。default_md 不能配置成 sha1,最少也要用 sha256,否则 chrome 会报警告。
2. [ req ] 里面的 default_md 配置了默认的 digest 算法,如果配置成 default 会默认为 sha1,如果用 sha1 来进行 digest,chrome 会报出警告。所以这里要修改成 sha256
3. [ req_distinguished_name ] 里面配置了生成证书的默认配置,为了自己生成证书省事儿,可以修改成常用的,比如 countryName_default = CN
4. [ usr_cert ] 配置了 x509_extensions 信息 nsCaRevocationUrl 比较有用,配置 crl 的地址

第二步,颁发证书,分为三步
1. 创建私钥
2. 创建 csr 用来向 ca 请求证书,包含了请求信息,和公钥
3. 把 csr 发送给 ca,颁发证书

在说明怎么颁发证书之前,先说明一下私钥、公钥、证书这些文件的存储,存储的编码方式有两种,pem 和 der,pem 是 base64 文本的(加了开头一结尾),der 是二进制的。查看 pem 文件信息可以使用 openssl [x509|req|rsa] -in xxx.pem -noout -text 就可以,x509|req|rsa 分别查看 x509, req 和 rsa 密钥内容的文件。如果是 der 的,需要在后面加上 -inform der
还有一种没用到的 pkcs12 文件,这种格式的文件可以包含多组公私钥,而且可以通过密码保护起来。和 java 中经常使用 jks 文件格式功能类似。

那么继续颁发证书
1. 创建私钥,和之前一样:openssl genrsa -out nginx.key 4096
2. 创建 csr:openssl req -new -key nginx.key -out nginx.csr
3. 颁发证书:第一次颁发证书之前,要初始化列表和序号:echo -n > /etc/pki/CA/index.txt;echo 01 > /etc/pki/CA/serial,然后把 csr 文件传到之前创建根证书的机器上,运行 openssl ca -in nginx.csr -out nginx.crt,再把 nginx.crt 文件发回给之前的机器,就可以了。
生成的 crt 文件中,不仅包含了 csr 文件中所有的信息和公钥,还包含了用 ca 私钥签名的部分,以证明是被 ca 颁发的。

多说一句,查看 csr 文件,就可以使用 openssl req -in nginx.csr -noout -text 查看内容。

这两步,主要的问题有几点:
1. 对概念不清晰,导致只能照猫画虎,比如私钥、ca、pem、csr 等乖
2. 默认的 digest 一定要修改为 sha256,sha1 在 chrome 上不被信任
3. 不知道查看文件信息的办法,两眼一码黑。只要使用 openssl [x509|req|rsa] -in xxx.pem -noout -text 就能看到生成文件相关信息,非常方便。

在 docker client 中信任 ca
docker 中增加 ca,需要在 /etc/docker/certs.d 目录中创建和 registry 一样的目录,比如 nexus.scalaone.com:33333,并把证书以扩展名 crt 放在这个目录下,docker 会按字母顺序找,直到匹配。
docker 并没有支持全局的 ca 证书,全局的 ca 证书要靠系统的证书机制。
在 centos 中,需要把 cacert.pem copy 到 /etc/pki/ca-trust/source/anchors/ 目录下,以 crt 结尾。然后执行 update-ca-trust extract 更新一下,就可以了。这里我踩了一个大坑,加完证书,一定要 service docker restart 一下,否则没用的。增加 docker 自己的证书,是不用重启的。

如果使用 nexus 3 作为 docker registry 的话,就需要把 nginx 的证书转换成 java 的 jks 文件。
直接生成一个 jks 的方法很简单,只要:keytool -genkey -keyalg RSA -dname "cn=nexus.scalaone.com,ou=scalaone,o=scalaone.com,l=china,st=shanghai,c=cn" -alias server -keypass password -keystore keystore.jks -storepass password,就可以了。
把现有的 nginx.key 和 nginx.crt 转成 jks,会麻烦一些。
1. 先把公私钥合成一个 p12 文件 openssl pkcs12 -export -in nginx.crt -inkey nginx.key -out nginx.p12
2. 用 java 的 keytool 把 p12 存到 jks 文件中 keytool -importkeystore -srckeystore nginx.p12 -srcstoretype PKCS12 -destkeystore keystore.jks -deststoretype JKS

这时,其实不能用,用 keytool -list -keystore keystore.jks 会发现,里面包含一个 PrivateKeyEntry,但名字是 1,直接给 jetty 用,是无法使用的,所以需要:
3. 把 keystore 中的 PrivateKeyEntry 1 的 alias 改成 server:keytool -changealias -keystore keystore.jks -alias 1 -destalias server -storepass password,就可以了,因为对 jks 不是很熟悉,也花了一些时间。

到这里,就需要使用 docker push 和 docker pull 把自建的 registry 使用起来了,比如 docker tag centos nexus.scalaone.com:33333/centos,然后 docker push nexus.scalaone.com:33333/centos,使用的时候也一样,docker pull nexus.scalaone.com:33333/centos
遇到一个问题,docker 1.7.1 和 nexus 3 有个 bug,不能 push,docker 更高版本就可以了。

nexus 3 和 maven repo 一样,可以配置两类的 docker registry,一种是 host,就是上面那种,另外一种就是 proxy,增加一个 docker proxy mirror,然后需要配置 docker,增加这个私有的 registry 加到的默认 registry 列表中。

加的方法如下:
配置环境 centos 7,docker 通过 systemctl 启动的

systemctl 有个机制,也是 docker 建议的(https://docs.docker.com/engine/admin/systemd/),把配置文件放在 /etc/systemd/system/docker.service.d,并以 .conf 结尾的,名字随意,就能够自动加载,主要是增加了一个 registry,我的配置如下:

registry.conf


[Service]
Environment="ADD_REGISTRY=--add-registry nexus.scalaone.com"

当下次 docker pull 的时候,就会优先从 nexus.scalaone.com 下载了,如果没有,还会继续从 docker.io 下载的。

ps:
遇到一个坑,根证书默认有效期是 30 天,玩了一段时间后,根证书过期了,这个郁闷啊。生成根证书增加参数 -days 3650。

APNManager 能够生成在 4G 环境下使用的 proxy

github:
git clone https://github.com/realityone/APNManager
cd APNManager
pip install tornado redis
python apn_manager.py

然后访问 http://xxx.xxx.xxx.xxx:8000/
页面上可以写 host port,并 generate 就可以生成在 iOS 上安装的 profile

尝试过,正常使用

ps:

国内外各租用一台机器,linode 和 aliyun
linode 安装 shadowsocks,启动了多个端口
aliyun 安装 cow 使用多个端口

Scala or Java NIO 如何读取包含多种编码格式文件?

如果一个文件中包含多种编码,而某个字符的编码在另外一种编码中,又不存在,那么当读取文件时,就会出错。

比如常见的:java.nio.charset.MalformedInputException: Input length = 1

那么实际中,使用 nio 读文件时,一般是可以提供一个 encoder 的,类型是 java.nio.charset.CharsetEncoder,在使用 encoder encode 的过程中,可能出现两种错误:
1. Malformed Input 错误的输入
2. Unmappable Character 无法 mapping 到的字符
针对这两种情况,我们可以单独设置 java.nio.charset.CodingErrorAction,CodingErrorAction 有三种:IGNORE, REPLACE 和 REPORT

在 scala 中可以在 Source 读文件前,提供一个 codec

implicit val codec = Codec(“UTF-8”)
codec.onMalformedInput(CodingErrorAction.REPLACE)
codec.onUnmappableCharacter(CodingErrorAction.REPLACE)

就能够解决多重编码文件带来的问题了。

Java 中需要针对不同的读取接口,来传入 encoder 即可

Traversable 和 TraversableOnce 的关系

之前一直理解的,就如 Traversable 能反复使用,而 TraversableOnce 不能反复使用。
常用的接口如 map, filter 等,都是正常的,并且 Traversable 也是 TraversableOnce 的子类,也就没多想。突然有一天,我用了一个这样的方法:


def f(p: TraversableOnce[Int]) = {
val r = p.map(_ + 1)
r.foreach(println)
r.foreach(println)
}
f(List(1,2,3))

结果悲剧了,只打出了一次,下次,就没了。

而稍微改改,变成


def f(p: Traversable[Int]) = {
val r = p.map(_ + 1)
r.foreach(println)
r.foreach(println)
}
f(List(1,2,3))

就可以了。

用 Java 的思路来想,我传的是 List,即是 Traversable 的子类,也是 TraversableOnce 的子类,为什么调用 map 的时候,行为居然是不一样的。

原因:

TraversableOnce 是没有 map 方法的,map 方法是通过隐式转换至 MonadOps 得到的,所以并不是多态,map 行为不一致。
虽然 List 本身是有 map 方法的,但在调用 TraversableOnce.map 的时候,在编译期,就已经被 implicit convert 成 MonadOps.map 了,这个行为也就可以理解了。
Traversable 的 map 才是真正的多态,会使用 List 中的 map 方法

这个例子感觉暴露出 Scala 的复杂性,implicit 和 Java 的多态在客户端上看起来是一样的,可以行为不一样,非常容易产生误解。

拿到 GH60 的艰辛与幸福之路

最近组了一个 GH60,过程相当波折,全过程大概有半个月吧,组键盘主要分成三步。

1. 了解 GH60
2. 组装 GH60
3. 定制 GH60 固件

其中第3部分最最惊险,不过还是从第1步说起。

1. 了解 GH60
我之前使用 HHKB 有些年头了,看到 GH60 之后,基本上是以 HHKB 备份键盘的心态,准备拼一个玩玩,也是可有可无的东西吧。总体价格不能太离谱,不能弄个 2-3k 的壳子 + 板子,再弄个 1k 的 sp 二色,激动的再弄个 cc,那完蛋了,4-5k 的价格要被老婆打死的。心里的预算在 600-800 左右吧,最后一共花了 1259,超出了一些预算,主要花在壳子上了。

2. 组装 GH60
从得瑟的角度来看,这个键盘要有灯,最好还是彩虹的。既然有灯,就要有透光的键帽,我对原厂高度还是 OEM 高度没要求,但我希望这个键帽不要是 ABS 的,那就基本上只能是 PBT 的,毕竟 POM 的键帽还挺少的,而且也不好。总体色调黑色,我比较喜欢全黑色的。

在这个要求下,再加上预算问题。我选择了 IKBC 的 G104 黑色透光键帽,PBT 的,透光,价格比较便宜,taobao 能买到的价格大概 270 左右吧,我在 pcwaishe 买了个全新二手的,花了 180。这套键盘是 104 的,做工非常一般,不过介于 PBC 二色透光,其实也没什么选择的。

下一步,买 GH60 的板子,在 taobao 某卖家那里弄了一个 all in one 的方案,彩虹灯、GH60板子、五角 Cherry 青轴、大键位卫星轴,这是个无钢板的方案。

最后一步,买壳子。两个大选择,金属还是非金属。非金属的可以用 Poker 的壳子,塑料的,大概 70。或者定制的亚克力壳子,或者树脂,价格就完全不确定了。我要的是金属壳子,而且要低端,不能太贵,有几个选择吧。
1. 菜壳:就是 taobao 上最常见的阳极壳子,大概 350,也没用过,有一点点看不上。
2. Tex60壳子:据说是台湾产的,壳子底下比菜壳多了两个金属支架,原价 750,我 615 收了一个黑色的,其它颜色还有蓝色,银色等。
3. FMJ壳子:不知道哪里产的,比较有特点的是,壳子上有4个扑克牌的金属,价格较贵,要 1.5 左右。太贵了,不是我的菜啊。外观比较花,其实也不是我喜欢的。
4. KMac Happy/Mini:韩国产的,特别贵,是完整的方案,带板子的,价格 2.5K 左右。我要的是 GH60,不想要其它板子,而且价格也太贵了,不是我的菜啊。

基于性价比,买的 Tex60壳子,很低调,我喜欢。

买到之后安装键帽,然后直接就傻眼了。最下面一排装不上,因为没沟通好方案,导致我买的键帽和键盘几个地方不匹配。
1. 右上角是 Backspace,而 HHKB 是分开的两个键
2. 右 Shift 是个大的 Shift,大概是 3X Shift 吧。而 HHKB 是 1.75X Shift + 1X FN
3. 最下面一排完全不对,G104 应该是 6.25X 空格,板子上是 7X 的,左右也对不上。

没办法了,和卖家商量后,寄回去,免费帮我改改。对应上面的3点:
1. Backspace 一分二
2. Shift 一分二
3. 最下面一排,左3,右4,中间 6.25X 空格

经历了漫长的等待,重新寄过来。
在这个过程中,我到 taobao 上买了新的 1.75X Shift,IKBC 的。好像也没有别的选择。GMK 的 1.75 Shift 应该是原厂高度的,我也没法用。
组装成功!

3. 定制 GH60 固件
这步太波折了,真是太波折了

回来之后,基本上是 Windows 键位,这肯定是无法满足我要求的,果断刷机。果断 google gh60 firmware,找到了(之前已经找到无数次)的 github tmk_keyboard 项目,clone 下来吧。

在 mac 下,只需要用 homebrew 把 crosspack 和 dfu-programmer 安装好就行了,前面的用来 build firmware,后面的用来将 build 的输出写到键盘里面去。

键盘后面有个按钮,按之前,键盘被识别成一个 GH60 USB 设备。Mac 下可以用命令 system_profiler SPUSBDataType 看 USB 设备列表,当然,也可以在系统报告里面看。按了那个按钮之后,键盘就变成一个 atmega32u4 设备,名字有一点儿区别,比如会带 DFU,表示在 DFU 模式下。这个状态下,就可以刷机了。

直接 make dfu KEYMAP=hhkb ,就行了,结果非常让人不爽,莫名其妙的打出几个字母,然后整个键盘全是乱的。

完了,这时候就想,要是不刷就好了,反复试了3个小时,还是没弄明白,放弃。等一位懂的同学,从香港回来,再弄!

这哥们终于回来了,发现,GH60 板子分很多种,如 Rev A, Rev B,我这个是 Rev CHN,要用 tmk_keyboard_custom 那个项目,才支持的。可以搜 gh60 rev chn,会找到那个 custom 的项目,在 keyboard/gh60 目录下,matrix.c 里面有支持 REV CHN 和 REV CNY 的代码,然后在 config.h 中,增加一句
#define GH60_REV_CHN 1
就会开启 REV CHN build,从这时开始,我的键盘,就又能用了!

但是,还不够。因为有些键位是多出来的,比如 Backspace 拆开的两个键位,怎么才能知道这个键位的信息呢。这时候,就有一个工具了,在 Mac 下,叫 hid_listen.mac,执行了之后,会得到键盘的信息。还需要配合键盘的 Magic 键。Magic 键是左 Shift + 右 Shift,比如 Magic + x 就开启了 matrix debug,按下相应的键位,就会从 hid_listen.mac 的 console 上看到键位的位置和名称。太好用了,这样就很容易找到没有效果的键位了!

还有就是 Boot Magic Configuration 比较有用,就是在插 USB 之前,按住空格,然后再按相应的键,达到 Virtual DIP Switch 的效果。空格 + Backspace 比较实用,清空 EEPROM 数据。

到这里为止,其实大部分功能就已经可以工作了。最后就是优化功能了,把键盘配置成专人专用最顺手的工具。

补上我的配置吧,这个就要详细的参考 tmk 的文档了。

#include “keymap_common.h”

/*
* HHKB Layout
*/
const uint8_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
/* 0: Default layer
* ,———————————————————–.
* |Esc| 1| 2| 3| 4| 5| 6| 7| 8| 9| 0| -| =| \| `|
* |———————————————————–|
* |Tab | Q| W| E| R| T| Y| U| I| O| P| [| ]|Bspc |
* |———————————————————–|
* |Ctrl | A| S| D| F| G| H| J| K| L|Fn3| ‘|Return |
* |———————————————————–|
* |Shift | Z| X| C| V| B| N| M| ,| .| /|Shift |Fn |
* |———————————————————–|
* | |Gui |Alt | Space | |Alt |Gui | |
* `———————————————————–‘
*/
KEYMAP_HHKB(
ESC, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, MINS,EQL, BSLS, GRV, \
TAB, Q, W, E, R, T, Y, U, I, O, P, LBRC,RBRC,BSPC, \
LCTL,A, S, D, F, G, H, J, K, L, SCLN,QUOT, ENT, \
LSFT,Z, X, C, V, B, N, M, COMM,DOT, SLSH, RSFT,FN0, \
FN2 ,LALT,LGUI, SPC, RGUI,RALT,RCTL,FN1),
/* 1: HHKB Fn layer
* ,———————————————————–.
* |Pwr| F1| F2| F3| F4| F5| F6| F7| F8| F9|F10|F11|F12|Ins|Del|
* |———————————————————–|
* |Caps | | | | | | | |Psc|Slk|Pus|Up | | |
* |———————————————————–|
* | |VoD|VoU|Mut|Ejc| | *| /|Hom|PgU|Lef|Rig|Enter |
* |———————————————————–|
* | | | | | | | +| -|End|PgD|Dow| | |
* |———————————————————–|
* | | | | | | | | |
* `———————————————————–‘
*/
KEYMAP_HHKB(
PWR, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, INS, DEL, \
CAPS,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,PSCR,SLCK,PAUS,UP, TRNS,TRNS, \
TRNS,VOLD,VOLU,MUTE,EJCT,TRNS,PAST,PSLS,HOME,PGUP,LEFT,RGHT, PENT, \
TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,PPLS,PMNS,END, PGDN,DOWN, TRNS,TRNS, \
TRNS,TRNS,TRNS, TRNS, TRNS,TRNS,TRNS,TRNS),
/* 2: HHKB FN1 layer Mouse
* ,———————————————————–.
* |Pwr| F1| F2| F3| F4| F5| F6| F7| F8| F9|F10|F11|F12|Ins|Del|
* |———————————————————–|
* |Caps | | | | | | | |Psc|Slk|Pus|Up | | |
* |———————————————————–|
* | |VoD|VoU|Mut|Ejc| | *| /|Hom|PgU|Lef|Rig|Enter |
* |———————————————————–|
* | | | | | | | +| -|End|PgD|Dow| | |
* |———————————————————–|
* | | | | | | | | |
* `———————————————————–‘
*/
KEYMAP_HHKB(
TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS, \
TRNS,TRNS,BTN1,MS_U,BTN2,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS, \
TRNS,TRNS,MS_L,MS_D,MS_R,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS, TRNS, \
TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS, TRNS,TRNS, \
TRNS,TRNS,TRNS, TRNS, TRNS,TRNS,TRNS,TRNS),
/* 3: HHKB FN2 layer Idea
* ,———————————————————–.
* |Pwr| F1| F2| F3| F4| F5| F6| F7| F8| F9|F10|F11|F12|Ins|Del|
* |———————————————————–|
* |Caps | | | | | | | |Psc|Slk|Pus|Up | | |
* |———————————————————–|
* | |VoD|VoU|Mut|Ejc| | *| /|Hom|PgU|Lef|Rig|Enter |
* |———————————————————–|
* | | | | | | | +| -|End|PgD|Dow| | |
* |———————————————————–|
* | | | | | | | | |
* `———————————————————–‘
*/
KEYMAP_HHKB(
TRNS,FN11,FN3, TRNS,TRNS,TRNS,TRNS,FN4, FN12,FN5, FN6, TRNS,TRNS,TRNS,TRNS, \
TRNS,TRNS,FN7, END, TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,UP, FN8, FN9, TRNS, \
TRNS,HOME,TRNS,TRNS,RGHT,TRNS,TRNS,TRNS,TRNS,FN10,TRNS,TRNS, TRNS, \
TRNS,TRNS,TRNS,PGUP,PGDN,LEFT,DOWN,TRNS,TRNS,TRNS,TRNS, TRNS,TRNS, \
TRNS,TRNS,TRNS, TRNS, TRNS,TRNS,TRNS,TRNS),
};

/*
* Fn action definition
*/
const uint16_t PROGMEM fn_actions[] = {
[0] = ACTION_LAYER_MOMENTARY(1),
[1] = ACTION_LAYER_MOMENTARY(2),
[2] = ACTION_LAYER_MOMENTARY(3),
[3] = ACTION_MODS_KEY(MOD_LCTL, KC_F2),
[4] = ACTION_MODS_KEY(MOD_LALT, KC_F7),
[5] = ACTION_MODS_KEY(MOD_LSFT, KC_F9),
[6] = ACTION_MODS_KEY(MOD_LSFT, KC_F10),
[7] = ACTION_MODS_KEY(MOD_LGUI, KC_F4),
[8] = ACTION_MODS_KEY(MOD_LGUI | MOD_LALT, KC_LEFT),
[9] = ACTION_MODS_KEY(MOD_LGUI | MOD_LALT, KC_RGHT),
[10] = ACTION_MODS_KEY(MOD_LGUI | MOD_LCTL, KC_L),
[11] = ACTION_MODS_KEY(MOD_LALT, KC_F1),
[12] = ACTION_MODS_KEY(MOD_LGUI, KC_F8),
};

我把比 HHKB 多出来的一个键,给 Intellij Idea 专用了,配置了一个层,专门给 Idea 的快捷键,实在是,再也不用鸡爪了!
右边那个多出来的,我现在只是简单的弄了个鼠标键,后续,可以考虑更加丰富的功能吧。

Scala CSV Parser

之前一直用 parboiled2 的 CSVParser,非常好用,当然,不只是 CSVParser 好,而且是整个 parboiled2 弥补了 scala dsl 性能方面的缺陷,原因大家都懂的,scala 只有 regex parser

但是 parboiled2 不能支持 streaming 方式的 parse 和 process,这对于大文件,或是内存不足的情况,是比较致命的。
parboiled2 社区也有人提过这个问题,由于 PEG 解析是需要回溯的,比较难做成 streaming 的。
那就自己写一个好了,也不算复杂。
后续补充性能指标。

object CsvParser extends App {

  def parse[T](path: String, f: List[String] => T): List[T] = {
    val s = Source.fromFile(path)
    val r = s.getLines().map(l => {
      def field(chars: List[Char], record: List[String]) = chars.reverse.mkString :: record
      val (_, _, cs, r) = ((false, false, List[Char](), List[String]()) /: l) { case ((quote, backslash, chars, record), c) =>
        val dq = !backslash && c == '"'
        val q = dq ^ quote
        val bs = !backslash && c == '\\'
        val (cs, r) = if (!q && c == ',') (Nil, field(chars, record)) else (if (dq) chars else (c :: chars), record)
        (q, bs, cs, r)
      }
      val record = field(cs, r).reverse
      f(record)
    }).toList
    s.close()
    r
  }

  parse("test.csv", r => println(r.mkString))

}

中国法治现状

三大基本法,领导的看法、领导的想法、领导的说法;三个诉讼规则,大案讲政治,中案讲影响,小案讲法律;三个法律效力原则:宪法做给外国人看,法律服从内部规定,内部规定服从领导指示;法治基本状况:严格立法,普遍违法,选择执法。