WebP Server in Rust

Generate WebP images for JPG / PNG files on-the-fly with Rust! webp_server_rs

Speaking of switching to WebP image, at the first glance, I just did it in a very naive approach.

Then @Nova wrote a Node.JS server that can serve JPG/PNGs as WebP format on-the-fly. You can find that at n0vad3v/webp_server.

A few days ago, @Nova and @Benny rewrite the WebP Server in Golang, webp-sh/webp_server_go

And that looks really promising, the size of the webp server, according to its description, had greatly reduced from 43 MB to 15 MB, and it is a single binary instead of webp_server with node_modules.

I cloned that project and added a tiny feature. However, I just found that although it is absolutely easy to implement the tiny feature, there is a potential design issue with the `fasthttp` module. In order to get everything work, it took me about 4 hours to debug on it.

Finally, it turned out to be a pointer of an internal variable (ctx.Request.uri, or so) was directly returned from ctx.Path(), and if users invoke ctx.SendFile(filepath), the ctx.Request.uri will be set to filepath, which will also propagate to all variables that hold the shared value of ctx.Path(). You may visit my previous blog post for details.

Well, in aforementioned blog post, I said that it would be better if it was written in Rust. Now, let's make it come true and push the WebP server even further.

There are some comparisons among n0vad3v/webp_server, webp-sh/webp_server_go and webp_server_rs.

As for size,

  • webp_server(nodejs) with node_modules takes 43 MB
  • webp-server(go) has reduced to 15 MB, and it's single binary
  • webp-server(Rust) pushes that even further, only 3.6 MB on macOS and 6.4 MB on Linux

In terms of convenience, you can just download the binary file and run if you choose either webp-server(go) or webp-server(Rust). However, webp_server(nodejs) requires pm2 to run.

Performance, to be honest, I haven't got time to run some benchmarks on them. But IMHO it (webp-server(Rust)) should be as fast as golang version.

记 Golang 下遇到的一回「毫无由头」的内存更改

前两天看到 @Nova Kwok@BennyThink 做了一个 WebP Server,于是 clone 下来玩了一下,发现貌似没有做“原始图像更新后,重新生成相应的 WebP 图像”的功能。好的,这个说起来简单,做起来也很简单,就是 os.Stat 一下,然后取到图像最后修改时间的 UNIX timstamp,STAT.ModTime().Unix(),最后再跟先前生成好的 WebP 文件名比较一下就好(timestamp 会放在生成的 WebP 文件名里)。

上面为止真的都很简单,在 macOS 上测试了一下,看起来没问题~于是就提交 Pull Request

然而 Nova 告诉我说,

Nice PR, but there seems a little problem that the older converted images are not deleted after the change of the original image, this might cause a possible leakage of the original one's content.

显然我是一头雾水,一开始还以为自己提交 PR 的时候是不是手滑删掉了几行,检查了一下之后发现并没有!然后姑且先把 macOS 上测试的截图 comment 在了 PR 下面。

接着我估计 Nova 应该是在 Linux 下跑的测试,于是就在一台新的 VPS 上安装了 go,把我 fork 且修改过的那份代码 clone 在 VPS 里测试。本来我预估的时候要么是我搞错了文件,要么也许是 Nova 不小心用了以前编译好的文件。然后一测试我就惊了,居然真的没有删除以前生成的 WebP 图像 Σ(・□・;)?!

由于没有 Linux 机器,也懒得安装虚拟机了,只能一头雾水的在 VPS 用 fmt.Println 输出来简易 debug 了。根据 fmt.Println 的输出,发现 ImgName 不知道为什么就突然之间被改了!

[1]ImgName: webp_server.png
[2]ImgName: webp_server.png
[3]ImgName: webp_server.png
[4]ImgName: webp_server.png
[5]ImgName: root/webp_serve

上面是在 5 处不同的地方 fmt.Println("ImgName", ImgName) 的输出,虽然我放了这么多,但是实际上在代码里 ImgName 在其作用域内只有过一次赋值,

ImgPath := c.Path()
// ... 略去 10 行左右判断文件扩展名的代码
ImgName := path.Base(ImgPath)

然后就没写过了,仅有读的操作,没有任何赋值,中间只有一次被用来当作 Sprintf 的一个参数

WebpImgPath := fmt.Sprintf("%s/%s.%d.webp", DirPath, ImgName, ModifiedTime)

但显然这个也不会更改 ImgName 的内存嘛。“这不科学!” 虽然想这么叫出来,但是想想这个肯定还是有原因的!

Continue reading 记 Golang 下遇到的一回「毫无由头」的内存更改

「不可愛」清單

昨天和今天看到推特上有在玩把人加入到「可愛」清單的遊戲,我自己的話,既沒有被任何人加到這樣的清單裡,也並不在意這些的說,但是剛有看到 @FiveYellowMicepost 說 ——

于是黄鼠有想做类似的事情,可是在考虑了一下之后,觉得还是放弃会比较好了。

因为有着被加入清单的人,就意味有没有被加入清单的人。被加入的人会很开心,可是对于没有加入的人而言,这样就是明显的排除和拒绝了。不在意黄鼠的人当然不会去在意自己有没有被加入,可是如果是在意黄鼠的人,没有被加入清单一定会是一个打击吧。也许这个打击不会很大,因为黄鼠大概也不会有那么重要,也许会很在意黄鼠的看法的人根本不存在。但是那是黄鼠没有获知的事情,万一在意黄鼠的看法的人是存在的,或者在未来的某一天出现了,那样的话,黄鼠不想要让他们觉得自己被排除和拒绝了呢。

那麼既然建立類似於「白名單」的可能會對在意自己的看法的人造成傷害的話,不如建立一個「黑名單」好啦!

於是咕嚕咕嚕地跑去創建了一個空白的「不可愛」清單,然後裡面一個人都沒有~!

不知道為什麼,突然間就想到了內田彩的「Merry Go」裡的一段歌詞

何だって 裏表だね
為什麼會有表裡存在
裏だって 表なら
如果說裡是表
表だったさ 裏なんです!
那表也是裡了呀(・8・)
幸せの反対も
就算在幸福的背面
回せば幸せに姿を変える
只要轉身就能遇見幸福
悲しみの背中には
悲傷的背後
喜びの翼が生えてるの
也有喜悅的翅膀在生長

从零开始的 Rust 学习笔记(19) —— Rewrite insert_dylib in Rust

最近鹹魚了蠻長一段時間,發現大約有一個多月沒有寫這個系列了,今天繼續學習 Rust 好啦!雖然有在看「The Rust Programming Language」,但是還是得寫寫的~想了一會兒之後,決定把在「另一种方法获取 macOS 网易云音乐的正在播放」裡用過的 insert_dylib 用 Rust 重寫一下(^O^)/

insert_dylib 本身來說並不複雜,但因為不像 C/C++/Objective-C 裡那樣可以直接 #import <mach-o/loader.h> 等,於是 MachO 的一些 struct 就需要自己在 Rust 中重寫一遍~

當然,實際上也可以用 Rust 寫個 Parser,然後去 parse 這些 header 文件,並且自動生成 Rust 的 struct。可是我太懶了,留到下次試試看好啦(咕咕咕) 這次的就放在 GitHub 上了,insert_dylib_rs

不過需要注意的就是有個 BigEndian 和 LittleEndian 的問題,不同的 MachO 使用的可能不一樣,因此就增加了一個 swap_bytes! 的 macro 和一個 FixMachOStructEndian 的 trait

src/macho/macho.rs 裡隨機選一個 struct 出來展示的話,大約就是如下這樣子

use super::prelude::*;

macro_rules! swap_bytes {
    ($self:ident, $field_name:ident) => {
        $self.$field_name = $self.$field_name.swap_bytes();
    };
}

pub trait FixMachOStructEndian {
    fn fix_endian(&mut self);
}

#[derive(Debug)]
pub struct SymtabCommand {
    pub cmd: u32,
    pub cmdsize: u32,
    pub symoff: u32,
    pub nsyms: u32,
    pub stroff: u32,
    pub strsize: u32,
}

impl SymtabCommand {
    pub fn from(buffer: [u8; 24], is_little_endian: bool) -> SymtabCommand {
        let sc_buffer: [u32; 6] =
            unsafe { std::mem::transmute_copy::<[u8; 24], [u32; 6]>(&buffer) };
        let mut symtab_command = SymtabCommand {
            cmd: sc_buffer[0],
            cmdsize: sc_buffer[1],
            symoff: sc_buffer[2],
            nsyms: sc_buffer[3],
            stroff: sc_buffer[4],
            strsize: sc_buffer[5],
        };

        if is_little_endian {
            symtab_command.fix_endian();
        }

        symtab_command
    }

    pub fn to_u8(&self) -> [u8; 24] {
        let mut data: [u32; 6] = [0u32; 6];
        data[0] = self.cmd;
        data[1] = self.cmdsize;
        data[2] = self.symoff;
        data[3] = self.nsyms;
        data[4] = self.stroff;
        data[5] = self.strsize;

        unsafe { std::mem::transmute_copy::<[u32; 6], [u8; 24]>(&data) }
    }
}

impl FixMachOStructEndian for SymtabCommand {
    fn fix_endian(&mut self) {
        swap_bytes!(self, cmd);
        swap_bytes!(self, cmdsize);
        swap_bytes!(self, symoff);
        swap_bytes!(self, nsyms);
        swap_bytes!(self, stroff);
        swap_bytes!(self, strsize);
    }
}
Continue reading 从零开始的 Rust 学习笔记(19) —— Rewrite insert_dylib in Rust

另一种方法获取 macOS 网易云音乐的正在播放

虽然标题里面写的是“另一种”,但是先前的方法其实不是我写的?而是来自可爱少女 Makito 的两篇 post ——

于是就看到了直接从 Mach 内核入手的方法,是賢い、かわいい Makito~!不过今天跑去 clone 代码下来尝试的时候似乎会 crash 的样子,毕竟距离上次 update 代码也过去了 9 个月左右了,猜想可能是网易云音乐有所修改导致(在我用别的方法尝试的时候,也是莫名 crash 了)

于是这里就写一个另一种获取 macOS 网易云音乐的正在播放的方法吧~

Continue reading 另一种方法获取 macOS 网易云音乐的正在播放

Copy List with Random Pointer

A linked list is given such that each node contains an additional random pointer which could point to any node in the list or null.

Return a deep copy of the list.

The Linked List is represented in the input/output as a list of n nodes. Each node is represented as a pair of [val, random_index] where:

  • val: an integer representing Node.val
  • random_index: the index of the node (range from 0 to n-1) where random pointer points to, or null if it does not point to any node.

Example 1:

Input: head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
Output: [[7,null],[13,0],[11,4],[10,2],[1,0]]

Example 2:

Input: head = [[1,1],[2,1]]
Output: [[1,1],[2,1]]

Example 3:

Input: head = [[3,null],[3,0],[3,null]]
Output: [[3,null],[3,0],[3,null]]

Example 4:

Input: head = []
Output: []
Explanation: Given linked list is empty (null pointer), so return null.

Constraints:

  • -10000 <= Node.val <= 10000
  • Node.random is null or pointing to a node in the linked list.
  • Number of Nodes will not exceed 1000.
Continue reading Copy List with Random Pointer

Integer to Roman

在家无聊到开始随便找算法题做,嘛,既然都做了,那就写在笔记本上好了~

Roman numerals are represented by seven different symbols: IVXLCD and M.

SymbolValue
I1
V5
X10
L50
C100
D500
M1000

For example, two is written as II in Roman numeral, just two one's added together. Twelve is written as, XII, which is simply X + II. The number twenty seven is written as XXVII, which is XX + V + II.

Roman numerals are usually written largest to smallest from left to right. However, the numeral for four is not IIII. Instead, the number four is written as IV. Because the one is before the five we subtract it making four. The same principle applies to the number nine, which is written as IX. There are six instances where subtraction is used:

  • I can be placed before V (5) and X (10) to make 4 and 9. 
  • X can be placed before L (50) and C (100) to make 40 and 90. 
  • C can be placed before D (500) and M (1000) to make 400 and 900.

Given an integer, convert it to a roman numeral. Input is guaranteed to be within the range from 1 to 3999.

Continue reading Integer to Roman