In my last blog post I said I wanted to spend some time learning new things. The first of those is Rust. I had previously tried learning it, but got distracted before I got very far.

Since one of the things I'd use Rust for is web pages, I decided to learn how to compile to WebAssembly, how to interface with Javascript, and how to use WebSockets. At home, I use a Mac to work on my web projects, so for Rust I am compiling a native server and a wasm client. But I also wanted to try running this on redblobgames.com, which is a Linux server. How should I compile to Linux? My first thought was to use my Linux machine at home. I can install the Rust compiler there and compile the server on that machine. Alternatively, I could use a virtual machine running Linux. Both of these options seemed slightly annoying.

I've been curious how much work it would take to cross-compile, and I found this great post from Tim Ryan. My setup is simpler than his, so I didn't need everything he did. I started with these commands from his blog post:

rustup target add x86_64-unknown-linux-musl
brew install FiloSottile/musl-cross/musl-cross
mkdir -p .cargo
cat >>.cargo/config <<EOF
[target.x86_64-unknown-linux-musl]
linker = "x86_64-linux-musl-gcc"
EOF

I then compiled for Linux:

TARGET_CC=x86_64-linux-musl-gcc cargo build --release --target=x86_64-unknown-linux-musl

Unfortunately this failed with an error about OpenSSL. Tim's post has a solution to this. Before implementing that complicated solution I realized that I should't need SSL/TLS anyway. My server talks regular websockets, not secure websockets, and then I use nginx to proxy them into secure websockets. So I disabled the secure websockets with this in Cargo.toml, the file that has the Rust project configuration:

[target.'cfg(target_arch = "x86_64")'.dependencies]
tungstenite = { version = "0.9", default-features = false, features = [] }

At first I tried features = [] but that wasn't good enough. I needed to also use default-features = false to disable the TLS. With this, the binary built, and I was able to run it on Linux!

So now I have a Makefile that builds the wasm client, the Mac server for local testing, and the Linux server for production. Fun!

BUILD = build

RS_SRC = $(shell find src -type f -name '*.rs') Cargo.toml
WASM = target/wasm32-unknown-unknown/debug/rust_chat_server.wasm

run-server: target/debug/chat_server
 # local testing server
 RUST_BACKTRACE=1 cargo run --bin chat_server

target/debug/chat_server: $(RS_SRC)
 # production server
 cargo build --bin chat_server

target/x86_64-unknown-linux-musl/release/chat_server: $(RS_SRC)
 TARGET_CC=x86_64-linux-musl-gcc cargo build \
     --release --target=x86_64-unknown-linux-musl

$(WASM): $(RS_SRC)
 cargo build --lib --target wasm32-unknown-unknown

$(BUILD)/rust_chat_server_bg.wasm: $(WASM) index.html
 wasm-bindgen --target no-modules $< --out-dir $(BUILD)
 mkdir -p $(BUILD)
 cp index.html $(BUILD)/

My Cargo.toml file is kind of terrible but it works so far for building the three outputs:

[package]
name = "rust_chat_server"
version = "0.1.0"
authors = ["Amit Patel <redblobgames@gmail.com>"]
edition = "2018"

[lib.'cfg(target_arch = "wasm32")']
crate-type = ["cdylib"]

[[bin]]
name = "chat_server"
path = "src/chat_server.rs"

[dependencies]
wasm-bindgen = "0.2"
serde = { version = "1.0", features = ["derive"] }
bincode = "1.2"

[target.'cfg(target_arch = "x86_64")'.dependencies]
tungstenite = { version = "0.9", default-features = false, features = [] }

That's it for now. I'm not a big fan of writing client-server code in large part because I want my pages to still work in thirty years, and that's best if there's no server component. But I want to spend time this year learning things for myself rather than trying to produce useful tutorials, so I'm going to explore this.

Tim's blog post was a huge help. Without it, I would've compiled the server on Linux. Thanks Tim!

I've placed it on github.

Labels: ,

0 comments: