Sucha's Blog ~ Archive for November, 2021

21年11月25日 周四 22:51

基于 luarocks/luastatic 的二进制打包工具

luastatic 可以打无依赖的二进制包,但是官方的版本有一些限制,比如在命令行里面,lua 源文件之只能是基于当前打包目录的相对路径。

而实际上,由于 lua 的 LUA_PATH、LUA_CPATH 以及 package.path 和 package.cpath 太自由,可以定制很多种搜索优先级,以及目录相对关系,打包目录下的相对路径,就不能反映源码上面的 require 路径逻辑了。

于是我基于上面的 luastatic,做了一点改进,可以输入相对路径,代码在 lalawue/luastatic

举个例子,一方面,我重度使用 luarocks 安装的库,比如 cincau,默认目录可以是 /usr/local/share/lua/5.1/cincau,而另外自己写的源码,都在 app 目录下,然后设置了如下的 LUA_PATH:

export LUA_PATH="/usr/local/share/lua/5.1/cincau/?.lua;./app/?.lua"

这样在 require 的时候,省却了很多相对路径。如果使用官方的 luastatic 打包,就得建立相对目录,然后作为命令行参数输入给 luastatic,用我修改后的 luastatic,只需要先设置 shell 变量:

export LUASTATIC_SOURCE_PATH="/usr/local/share/lua/5.1/cincau/;app/;"

注意目录最后的 '/' 和 ';',如果输入的 lua 源文件检测到相应的路径前缀,会将这个前缀去掉,这样就实现了 LUA_PATH 和 package.path 的功能,运行时的路径搜索和打包时候的路径一致了,不需要构建相对目录。

另外需要注意的是,虽然 luastatic 支持生成无依赖的 binary,但那只是对应官方的 lua,luajit 依赖的 .so 库还是需要动态加载的,比如很多的 ffi.load 依赖的是系统的 dlopen。这个我看了一些文章,大佬们根据安全原则,系统的接口都不支持从内存中加载一个动态库,虽然某些平台上通过 mmap 是可以做到的,但是这个总感觉不大好。

那如果是基于 luajit,lua 源文件是打到 binary 里面了,但是依赖的 so 库,是在运行前,通过 export LD_LIBRARY_PATH 或者 DYLD_LIBRARY_PATH 来设置 so 搜索路径,做成外部库加载的。

通过这样的方式做成的 binary 发布包,可以在对应平台上不依赖 luarocks 就可以运行,安全性也好一些,发布更新都很方便。


话说昨天看了一下 Programming Language and compiler Benchmarks 里面 go vs java,go vs javascript 以及 go vs luajit,不得不说 go 性能确实好,速度跟 java 一样,但是内存消耗少了不知多少。

luajit 平均速度是 go 的一半,内存消耗多了一些,javascript 速度跟 luajit 类似,但内存消耗多很多。

如果是从头学起的话,我觉得 go 是要比 java 简单很多的,工程属性方面,go 更是标杆,如果算上多线程 goroutine 的话,毫无疑问,go 是首选。多线程多难搞呀,所以小公司很可能都慢慢转向 go 了,反正我是觉得真的香呀,现在都有点后悔搞 luajit 了,因为速度是赶不上 go 的,基础工具又差这么多。

上面的所谓打二进制包,就是补充了一部分基础工具的缺失,即便不说标杆 go,人家 deno 也是可以打成二进制包的。

luajit 不多的优点,一方面是不需要编译,虽然 go 编译很快,但是不需要编译连这个部分都省略了;另外就是 luajit 语法几乎是固定了,不大担心后续会有比较大的变动,唯一担心的额是 Mike Pall 是 luajit 几乎唯一的维护者,社区太小了,这个软件太高深,往后一些平台的支持以及升级估计够呛,更新也慢。

lua 语言的的优点在 web 开发上面不大体现得出来,相比之下,比如虚拟机很小,这样轻量的虚拟环境、沙箱,可以造就诸如国内有名的 skynet

lua 语法更简单,类型都可以少关心,但并不意味说这个语言抽象能力不够,或者扩展不行,如果 metatable 抽象得好,可能性太多,OO 小菜一碟,比如我之前基于 lua 构建的 mooncake;所以,也没有泛型渴望症;但是因为缺乏类型系统,那么测试系统得自己保证才放心。话说,也是因为语言自由,lua 很多库的源码,相对 go 源码来说,可太难懂了。

最近还看了 WASM,感觉也很香,go 自从 1.11 后,支持输出 WASM,但 WASM 这个技术还比较早期,W3C 上面到了提案 2 的阶段,还没有多线程这样的标准。从上面效率对比,跟 go 是一个级别。

这次 post 先到这里吧。

21年11月17日 周三 21:52

诛仙读后感

以前没读过仙侠系列,算是第一次读。

前段意境真的很美,特别是上了青云山拜师之后,掌管大厨的这一段,跟师姐砍竹子,带领一猴一狗每天在这仙山里面慢慢悠悠升级,就觉得这感觉很好。

而且内线是有内功见长,又少不了武侠里面的捡到了绝世大宝贝,比如土到不能更土的烧火棍,以及后期那些救人于水火,法力无边的宝贝,更是让人羡慕神往。

起伏线的大起大落也很经典,以及一直不受师傅待见,但最后唯一一个给师傅长脸的徒弟,又比如被掌门下杀心的一段而成为鬼厉,从此杀人无数。

支线里面厉害的有万剑一,以及仙人指路,野狗跟仙人指路能走到一块,这个组合在人心里面又重了,这条线里面的仙人指路水平太高,应该是作者的用来补充剧情的旁白。

但是也有遗憾的支线,比较不能理解的是陆雪琪,一直走不到明线,但是暗线又这么强烈,难受。

前面写得太好了,觉得跟兽人大战之后,跟魔教对决的这一段,马马虎虎,没啥感觉。

总体可以打 7.5 ~ 8 分了。

21年11月11日 周四 00:33

Mithril 使用心得(1)

这个网站已经诞生 10 多年,但其是静态页面,基于 XHTML 结构,而不是最新的 HTML5,站长本人是专业 APP 开发, H5/Web 开发比较业余,对 Mithril 的使用感觉还不够深入,但也算趟了一些坑, 可以作为心得说一下的。

说一下为何不使用 React、Vue 或者 Preact,以及 Angular 等等其他框架,没什么原因,因为其实入手之前看了非常多的入门对比文章, 在对比的过程中总是要上手的嘛,因为看到 Mithril 吹自己简单,其网站入门教程的介绍也是非常到位的(React 没能一下学会, 但是 Mithril 可以),加上不需要 nodejs 这样的依赖,不需要 build,学习的曲线感觉很平缓。

话说我之前是一个连 SPA(Single Page Application)都不懂的人,通过 Mithril 的教程,学会了基础的 component, route、以及 XHR,基本上一个单页面就可以搞定了。

再说还有 stackblitz 这样的平台,入门是很方便的。因为是个无需 build 的方案, 简单的 HTML5 + rel 标签带入 Mithril 就可以开始工作了。

js 我也只懂皮毛,很多东西都得百度,没关系,出来的程序能用就行。最后做了 2 个 SPA 页面,一个是 wiki,一个是 note 便签。

wiki 的相对简单一些,大概 2 - 3 天才搞定,学习了 route,但是内部的很多状态我是通过一个全局状态变量来控制的。 便签这个页面没有用到路由,纯内部状态跳转刷新,两个都没有用到 browser 的 history,浏览器的返回按钮会退回上个 URL。

Mithirl 的 bind 或者 route,都可以仅指定页面的一个部分,一个 element 来刷新,内部是根据 vnode 的变化来判断是否更新的, 这个 vnode 变化感觉有点悬乎,比如我在创建一些子节点的时候,attribute 是有变化的,但是 Mithril 感觉不到这个变化, 所以我不得不在外层做更明显的 vnode 变化,比如增加一个用不到的 span 标签这样,来确保重新输出 HTML 节点。

Mithril 限定 60hz 刷新确实也不错,但是遇到一些极端情况,比如自己本地测试的时候,网络返回就挺快的,当我在用内部变量控制 vnode 输出的不同时,因为间隔过短,虽然变量有切换状态,但 Mithril 间隔读取到的其实都是同一个状态下的 vnode, 而忽略了中间这个变量状态下 vnode 的输出,最后导致认为没有变化,不需要刷新,出了问题,如下:

// 如下的 A、B 表示变量值,后面数字表示 vnode 结构为 A 但内容不同,这里需要保证 A1 -> B 间隔 1/60 秒
A1 -> B -> A2

所以有时候,需要将一些操作延时到 60hz 单次刷新之外才行,让 Mithril 检测到 vnode 变化。

Mithril 教程用的是古老的创建 vnode 的函数调用方案,但其实可以配合 preact 的 htm, 我觉得使用方式上要比教程里面的简单很多,比如教程是下面这样创建 vnode 的:

// 下面 title_str 是变量
// <h1 class="title">My first app</h1>
const title_str = 'title';
m("h1", {class: title_str}, "My first app")

如果用了 htm,可以像下面这样:

// 先绑定,最后也是通过 m 函数来输出 vnode 的
const html = htm.bind(m);
const title_str = 'title';
html`<h1 class="${title_str}">My first app</h1>`

明显是使用了 htm 的代码更容易定位问题。

因为追求短平快,单个 js 文件就完成了 wiki 或者 note 便签这样的功能,所以 Mithril 结构化方面我是没有发言权了,教程上面 呼吁结构化组件,分解功能模块我是一点都没用上。

先这样吧。

21年11月07日 周日 11:46

stunnel 的 SNI 配置

因为是很小的网站,资源也有限,感觉没必要用上 openresty 这个大件。

先贴配置:

debug=info
output=/path/to/access.log
pid=/path/to/process.pid
client=no

[https]
accept = 443
connect = 127.0.0.1:port1
cert = /path/to/default.cer
key = /path/to/default.key

[site1]
connect = 127.0.0.1:port1
sni = https:site1.domain
cert = /path/to/site1.cer
key = /path/to/site1.key

[site2]
connect = 127.0.0.1:port2
sni = https:*.site2.domain
cert = /path/to/site2.cer
key = /path/to/site2.key

说一下上面的配置。

这是类似 INI 方式的配置文件,首先定义是 server 端的配置,设置 access.log 和 process.pid 文件的位置。

之后设置默认协议,比如叫做 https,监听 443 端口,并设置连接地址,使用默认的 cert 和 key 做验证,这里其实是可以跟下面的 site1 或者 site2 用同样的 cert 和 key 配置。

之后分别设置不同网站对应的处理地址,比如 site1,连接的是 port1,注意 sni 字段配置,第一个是协议,就是上面描述过的 https,而不是 HTTPS,之后是需要处理的 domain,这里只处理二级域名,没有通配字,最后加入网站申请到的 cert 和 key。

site2 的 sni 用了通配字,所有 site2.domain 的二级、三级域名都可通过验证处理。