Using Headless Chrome to Convert Offline HTML to PDF in Bulk

手里有一批某课程内容的教案但是都是导出的 HTML 文档,因为每次打开都会请求一堆的外部资源(比如JS),导致卡顿几秒钟。

但是这是盗版资源 (shame on me) 所以这些外部请求完全是捣乱;于是想到批量转换为 PDF 就好了。

一开始选择的是 wkhtmltopdf,但是 libpng 会因为源文件的 sRGB 不正确导致失败。看到有个 2017 年报的 issue 到现在还是 Open 状态,遂放弃。

后来想到浏览器可以正常打开,也可以正常 print to pdf,那何不让浏览器批量处理?

恰好 Chrome 支持 headless mode,结合简单的 powershell 就可以做到批量转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$dest_dir = "/your/dest/dir/"
$src_files = Get-ChildItem "/your/src/dir/"

$env:Path += ";C:\Program Files (x86)\Google\Chrome\Application\"
foreach ($file in $src_files) {
$output_file = $file.BaseName + ".pdf"
$dest_file = Join-Path -Path $dest_dir -ChildPath $output_file
Write-Output "Processing $dest_file"
chrome.exe --headless --disable-gpu --print-to-pdf-no-header --print-to-pdf=$dest_file $file
# a simple throttle
Start-Sleep 5s
}

Write-Output "Congratulations!!!"

每次调用都会创建一个 headless chrome 做转换,而一次转换大概需要十几秒;同时这个调用是启动 chrome 之后就立即返回的,并非等到转换结束。

所以为了避免短时间内创建大量的 headless chrome 进程把系统资源耗死,这里简单的用 sleep 做一个节流器

Read More

Postfix Policy Delegation

比较新的版本的 postfix 除了 milter 之外还可以用 policy delegation 做 content filtering;后者的优势在于简单容易实现,但是只能读取有限的 email attributes。

policy delegation 实现上也有三种:

  1. 基于 tcp 的 policy server
  2. 基于 unix domain socket 的 policy server
  3. 基于 master daemon spawn 的 policy server

第三种实现上最简单,并且虽然文档上说它也是基于 unix domain socket 实现通信,但是这部分通信实际上对 policy server 是透明的。

第三种情况下,只需要提供一个简单的 binary,能够读写 stdin/stdout 即可完成通信:

  • 从 stdin 读出 request attributes
  • 将判断结果往 stdout 写

文档上虽然没有明确写出这点,但是通过附带的 example 用例可以证实。

不过使用这种方式要注意一个坑:stdin/stdout 的读写必须不能使用 fread()/fwrite() std::cin/std::cout 这种带有应用层缓存的 IO 设施。

虽然这里是对 stdin/stdout 读写,但是实际上底下通讯还是会经过 unix domain socket,使用带应用层缓存的库函数读写时会导致阻塞。

相应的,这里要么使用 read(2)/write(2) 这种 syscall,要么手动屏蔽标准库的应用层缓存。

对于没有经验的人来说,这里是一个很容易掉的坑

Read More

sa-update 和直率的 spamassassin

背景

amavisd 是 MTA(例如 postfix)流行的 content-filtering 模块,可以提供垃圾邮件试别过滤、病毒邮件检测隔离等功能。

spamassassin 则是 amavisd 使用的垃圾邮件模块。

在目前主流的 Linux 发行版上,这两个系统都会以 systemd 管理的服务运行。

在系统集成过程中遇到一个神奇的问题:amavisd 和 spamassassin 服务会定期挂掉,journalctl -fu 的日志都提到了

  • config: no rules were found! Do you need to run sa-update​?

的错误内容

处理

解决步骤:

  1. 检查 /var/lib/spamassassin/ 目录下是否有一个对应版本的目录,例如 3.00402
  2. 如果有的话,删除这个目录
  3. 然后先后重启 spamassassin 和 amavisd 服务

Read More

第二次跑路

在阿哔呆了 1860 天(2016/04/11 – 2021/05/14)之后终于完成了跑路,跑路到现在也一个多月了,也可以从一个“前任”的角度吐一吐槽了。

其实一开始我预期至少在阿哔待到2022年下半年再考虑是否跑路,主要原因是当初和媳妇儿(那会儿还是女朋友)约定是30岁之后再考虑回杭州工作,毕竟那会儿杭州能叫的上名号的互联网公司给我的印象都不好,印象里也没有什么知名的外企。

Read More

用 unique_ptr 管理 Windows HANDLE

作为一个曾经的资深 Windows developer,Windows HANDLE 一个不引人注意的但是绝对值得引起注意的地方是,它的无效值表示是不唯一的。

除了最常见的 NULL 之外还有一个 INVALID_HANDLE_VALUE,并且即使是在数值上这两个东西也不一样。

如果要为 HANDLE 写一个 C++ RAII Wrapper 那么必须要考虑这个点,比如 Chromium 就是这么做的

Chromium 的这个设施早在 C++ 11 之前就有了,并且经过多年来的迭代已经变得异常复杂;如果你自己需要这样一个东西,又无法从 Chromium 这种工业项目中抽取一个组件的话,只能考虑自己实现。

但是要写对一个工业强度的 handle-wrapper 并不容易,同时还要考虑到通用性和实用性,比如可能后续要支持 fd/socket,Windows HDC 等各种奇奇怪怪的东西。

所以能否用 std::unique_ptr 来作为基础实现,针对一些特定细节做泛化?

这就引出一个问题:如果希望用 std::unique_ptr 来作为 HANDLE 的一个 RAII 包装,如何正确判断一个 handle 是否 valid?

更进一步,问题还可以变为:如何用 std::unique_ptr 管理 pointer-like 的资源?

Read More

一个符合 RFC-4122 的实现

系列第一篇文章里说后面会给出一个满足 RFC 4122 的可用实现,并且那会儿是打算讲一讲实现中一些需要注意的坑和有意思的点。

但是因为3月份开始就忙着准备面试,清明结束后的两周基本不是在面试,就是在准备面试;一直到五一回了趟温州老家才有比较充裕的时间把 uuidxx 的核心逻辑写完。

拉锯到现在一开始的冲动都已经被消磨得不剩下多少了(可能还是因为我比较懒),所以我不打算把具体的实现部分展开,有兴趣的可以自行查阅源代码。

Repo 地址在 https://github.com/kingsamchen/uuidxx


因为这次换工作我有很多槽点想吐,所以后面我会专门写一篇文章讲一讲。

这篇应该不会鸽,毕竟喷人挺爽的一件事。

Read More

UUID 及其实现 3

这一篇来讲 v3 和 v5 的实现。

这两个实现要求使用方提供:

  • 一个称为 namespace 的 UUID
  • 一个叫 name 的数据块(不限于字符串)

然后利用一个 hash 算法算得结果,作为目标 UUID 的基础数据(还需要额外设置 variant 和 version)。

即,对于 v3/v5 的实现,本质上是

1
2
3
uuid = hash(namespace, name)
set_variant(uuid)
set_version(uuid, ver)

v3 和 v5 区别在于,前者是用 MD5,后者使用 SHA-1。

并且可以发现,对于相同的 namespace 和 name,生成的结果 UUID 是一样的。

这个就是 v3/v5 版本的特性。

借用 RFC 的描述就是:

  • The UUIDs generated at different times from the same name in the same namespace MUST be equal.
  • The UUIDs generated from two different names in the same namespace should be different (with very high probability).
  • The UUIDs generated from the same name in two different namespaces should be different with (very high probability).
  • If two UUIDs that were generated from names are equal, then they were generated from the same name in the same namespace (with very high probability).

所以 v3/v5 的版本又被叫做 name-based implementation

Read More