aboutsummaryrefslogtreecommitdiff
path: root/cmd/_conferences/gceu23
diff options
context:
space:
mode:
authorXe Iaso <me@xeiaso.net>2024-10-26 09:40:43 -0400
committerXe Iaso <me@xeiaso.net>2024-10-26 09:40:43 -0400
commit091cbdaa149fe9f0889af2f5d7b3015bff975a5f (patch)
tree9e92c984bd574ba148b351f58bab4ee8efadffc7 /cmd/_conferences/gceu23
parentcf16f6188d899a1ab07dcff99909c878f01d8e9d (diff)
downloadx-091cbdaa149fe9f0889af2f5d7b3015bff975a5f.tar.xz
x-091cbdaa149fe9f0889af2f5d7b3015bff975a5f.zip
cmd/stealthmountain: make the code easier to read
Signed-off-by: Xe Iaso <me@xeiaso.net>
Diffstat (limited to 'cmd/_conferences/gceu23')
-rw-r--r--cmd/_conferences/gceu23/README.md34
-rw-r--r--cmd/_conferences/gceu23/cmd/aiyou/main.go90
-rw-r--r--cmd/_conferences/gceu23/cmd/yuechu/main.go95
-rw-r--r--cmd/_conferences/gceu23/diagram.dot47
-rw-r--r--cmd/_conferences/gceu23/wasip1/.gitignore1
-rw-r--r--cmd/_conferences/gceu23/wasip1/Makefile15
-rw-r--r--cmd/_conferences/gceu23/wasip1/cat.go33
-rw-r--r--cmd/_conferences/gceu23/wasip1/echoclient.rs24
-rw-r--r--cmd/_conferences/gceu23/wasip1/envdump.go18
-rw-r--r--cmd/_conferences/gceu23/wasip1/hello.go9
-rw-r--r--cmd/_conferences/gceu23/wasip1/hello.rs3
-rw-r--r--cmd/_conferences/gceu23/wasip1/nihao.go18
-rw-r--r--cmd/_conferences/gceu23/wasip1/promptreply.rs9
13 files changed, 396 insertions, 0 deletions
diff --git a/cmd/_conferences/gceu23/README.md b/cmd/_conferences/gceu23/README.md
new file mode 100644
index 0000000..6f3abb8
--- /dev/null
+++ b/cmd/_conferences/gceu23/README.md
@@ -0,0 +1,34 @@
+# Reaching the Unix Philosophy's Logical Conclusion with WebAssembly
+
+Hey there! This is the example code I wrote for my talk at [GopherCon
+EU](https://gophercon.eu). This consists of a few folders with code:
+
+- `cmd`: Executable commands for the demo.
+- `cmd/aiyou`: The WebAssembly runner. It connects to `cmd/yuechu` and
+ exposes network connections as a filesystem. It is intended to run
+ `wasip1/echoclient.wasm`.
+- `cmd/yuechu`: The echo server that takes lines of inputs from
+ network connections and feeds them to WebAssembly modules then sends
+ the output back to the client. It runs `wasip1/promptreply.wasm` and
+ `wasip1/xesitemd.wasm`.
+- `wasip1`: A folder full of small demo programs. Each is built with
+ makefile commands.
+- `wasip1/echoclient.wasm`: A small Rust program that tries to connect
+ to the echo server, prompts for a line of input, prints what it got
+ back, and then exits.
+- `wasip1/promptreply.wasm`: A small Rust program that reads input
+ from standard in and then writes it to standard out.
+- `wasip1/xesitemd.wasm`: My [blog's](https://xeiaso.net) markdown to
+ HTML parser. It reads xesite-flavored markdown over standard input
+ and returns HTML over standard output.
+
+In order to build and run the code in this folder, you must be using
+Nix and be inside a `nix develop` shell. You can build most files in
+`wasip1` by using `make` such as like this:
+
+```
+make echoreply.wasm promptreply.wasm
+```
+
+If you have any questions, please [email
+me](https://xeiaso.net/contact) or open an issue on this repo.
diff --git a/cmd/_conferences/gceu23/cmd/aiyou/main.go b/cmd/_conferences/gceu23/cmd/aiyou/main.go
new file mode 100644
index 0000000..c36f428
--- /dev/null
+++ b/cmd/_conferences/gceu23/cmd/aiyou/main.go
@@ -0,0 +1,90 @@
+package main
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "io/fs"
+ "log"
+ "net"
+ "os"
+ "path/filepath"
+ "time"
+
+ "github.com/tetratelabs/wazero"
+ "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
+ "within.website/x/internal"
+)
+
+var (
+ r wazero.Runtime
+ code wazero.CompiledModule
+
+ binary = flag.String("wasm-binary", "./bin.wasm", "binary to run against every line of input from connections")
+)
+
+func main() {
+ internal.HandleStartup()
+ ctx := context.Background()
+
+ data, err := os.ReadFile(*binary)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ r = wazero.NewRuntime(ctx)
+
+ wasi_snapshot_preview1.MustInstantiate(ctx, r)
+
+ code, err = r.CompileModule(ctx, data)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ config := wazero.NewModuleConfig().
+ // OS stdio
+ WithStdout(os.Stdout).WithStdin(os.Stdin).WithStderr(os.Stderr).
+ // Placeholder argv[0]
+ WithArgs("aiyou").WithName("aiyou").
+ // Put network in /dev/net
+ WithFSConfig(wazero.NewFSConfig().WithFSMount(ConnFS{}, "/dev/"))
+
+ mod, err := r.InstantiateModule(ctx, code, config)
+ if err != nil {
+ log.Fatal(err)
+ return
+ }
+ defer mod.Close(ctx)
+}
+
+type ConnFS struct{}
+
+func (ConnFS) Open(name string) (fs.File, error) {
+ name = filepath.Base(name)
+ fmt.Println("connecting to", name)
+ conn, err := net.Dial("tcp", name)
+ if err != nil {
+ return nil, err
+ }
+
+ return ConnFile{Conn: conn}, nil
+}
+
+type ConnFile struct {
+ net.Conn
+}
+
+func (c ConnFile) Stat() (fs.FileInfo, error) {
+ return ConnFileInfo{c.Conn}, nil
+}
+
+type ConnFileInfo struct {
+ conn net.Conn
+}
+
+func (c ConnFileInfo) Name() string { return c.conn.RemoteAddr().String() } // base name of the file
+func (c ConnFileInfo) Size() int64 { return 0 } // length in bytes for regular files; system-dependent for others
+func (c ConnFileInfo) Mode() fs.FileMode { return 0 } // file mode bits
+func (c ConnFileInfo) ModTime() time.Time { return time.Now() } // modification time
+func (c ConnFileInfo) IsDir() bool { return false } // abbreviation for Mode().IsDir()
+func (c ConnFileInfo) Sys() any { return c.conn } // underlying data source (can return nil)
diff --git a/cmd/_conferences/gceu23/cmd/yuechu/main.go b/cmd/_conferences/gceu23/cmd/yuechu/main.go
new file mode 100644
index 0000000..b9798e9
--- /dev/null
+++ b/cmd/_conferences/gceu23/cmd/yuechu/main.go
@@ -0,0 +1,95 @@
+package main
+
+import (
+ "bufio"
+ "bytes"
+ "context"
+ "flag"
+ "fmt"
+ "log"
+ "log/slog"
+ "math/rand"
+ "net"
+ "os"
+ "strconv"
+
+ "github.com/tetratelabs/wazero"
+ "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
+ "within.website/x/internal"
+)
+
+var (
+ r wazero.Runtime
+ code wazero.CompiledModule
+
+ binary = flag.String("wasm-binary", "./bin.wasm", "binary to run against every line of input from connections")
+ bind = flag.String("bind", ":1997", "TCP host:port to bind on")
+)
+
+func main() {
+ internal.HandleStartup()
+ ctx := context.Background()
+
+ data, err := os.ReadFile(*binary)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ r = wazero.NewRuntime(ctx)
+
+ wasi_snapshot_preview1.MustInstantiate(ctx, r)
+
+ code, err = r.CompileModule(ctx, data)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ server, err := net.Listen("tcp", *bind)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ slog.Info("listening", "bind", *bind)
+
+ for {
+ conn, err := server.Accept()
+ if err != nil {
+ log.Println("Failed to accept conn.", err)
+ continue
+ }
+
+ fmt.Println(conn.RemoteAddr().String())
+
+ go func(conn net.Conn) {
+ defer func() {
+ fmt.Println("disconnect")
+ conn.Close()
+ }()
+
+ scn := bufio.NewScanner(conn)
+ scn.Split(bufio.ScanLines)
+
+ for scn.Scan() {
+ fout := &bytes.Buffer{}
+ fin := bytes.NewBuffer(scn.Bytes())
+
+ fmt.Println("<", fin.String())
+
+ name := strconv.Itoa(rand.Int())
+ config := wazero.NewModuleConfig().WithStdout(fout).WithStdin(fin).WithArgs("mastosan").WithName(name)
+
+ mod, err := r.InstantiateModule(ctx, code, config)
+ if err != nil {
+ slog.Error("can't instantiate module", "err", err, "remote_host", conn.RemoteAddr().String())
+ return
+ }
+ defer mod.Close(ctx)
+
+ fmt.Print(">", fout.String())
+
+ conn.Write(fout.Bytes())
+ conn.Close()
+ }
+ }(conn)
+ }
+}
diff --git a/cmd/_conferences/gceu23/diagram.dot b/cmd/_conferences/gceu23/diagram.dot
new file mode 100644
index 0000000..02dc327
--- /dev/null
+++ b/cmd/_conferences/gceu23/diagram.dot
@@ -0,0 +1,47 @@
+digraph G {
+ newrank=true;
+ graph [fontname = "Iosevka Aile Iaso"];
+ node [fontname = "Iosevka Aile Iaso"];
+ edge [fontname = "Iosevka Aile Iaso"];
+
+ subgraph cluster_0 {
+ style=filled;
+ color=lightgrey;
+ node [style=filled,color=white];
+ label = "aiyou";
+ echoclient [label="echoclient.wasm"];
+ ConnFS;
+ connFile [label="connection"];
+ }
+
+ subgraph cluster_1 {
+ style=filled;
+ color=lightgrey;
+ node [style=filled,color=white];
+ label = "yuechu";
+ lis [label="net.Listener"];
+ conn [label="net.Conn"];
+ logger [label="ln.Logger"];
+
+ subgraph cluster_2 {
+ style=filled;
+ color=grey;
+ node [style=filled,color=white];
+ label = "wasm program";
+ stdin;
+ stdout;
+ stderr;
+ }
+ }
+
+ { rank=same; echoclient; lis; }
+ { rank=same; connFile; conn; }
+
+ echoclient -> ConnFS [label=" mounted at /dev "];
+ ConnFS -> connFile [label =" opened to yuechu "];
+ connFile -> lis [label = "TCP\nconnection"];
+ lis -> conn [label = " accepted connection "];
+ conn -> stdin [label = "input from\nuser"];
+ stdout -> conn [label = " output from\nprogram"];
+ stderr -> logger [label = "error\nmessages"];
+}
diff --git a/cmd/_conferences/gceu23/wasip1/.gitignore b/cmd/_conferences/gceu23/wasip1/.gitignore
new file mode 100644
index 0000000..19e1bce
--- /dev/null
+++ b/cmd/_conferences/gceu23/wasip1/.gitignore
@@ -0,0 +1 @@
+*.wasm
diff --git a/cmd/_conferences/gceu23/wasip1/Makefile b/cmd/_conferences/gceu23/wasip1/Makefile
new file mode 100644
index 0000000..eed2dbd
--- /dev/null
+++ b/cmd/_conferences/gceu23/wasip1/Makefile
@@ -0,0 +1,15 @@
+GOBIN = gowasi
+
+export GOARCH = wasm
+export GOOS = wasip1
+
+SOURCES := $(wildcard *.go)
+OBJECTS := $(patsubst %.go, %.wasm, $(SOURCES))
+
+%.wasm: %.rs
+ rustc --target=wasm32-wasi $^
+
+%.wasm: %.go
+ $(GOBIN) build -o $@ $^
+
+all: $(OBJECTS)
diff --git a/cmd/_conferences/gceu23/wasip1/cat.go b/cmd/_conferences/gceu23/wasip1/cat.go
new file mode 100644
index 0000000..7033dbd
--- /dev/null
+++ b/cmd/_conferences/gceu23/wasip1/cat.go
@@ -0,0 +1,33 @@
+//go:build ignore
+
+package main
+
+import (
+ "flag"
+ "fmt"
+ "io"
+ "log"
+ "os"
+)
+
+func main() {
+ flag.Usage = func() {
+ fmt.Printf("%s <file>\n\nprints file to standard out\n", os.Args[0])
+ }
+ flag.Parse()
+
+ if flag.NArg() != 1 {
+ log.Fatalf("wanted 1 arg, got %#v", os.Args)
+ }
+
+ fin, err := os.Open(flag.Arg(0))
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer fin.Close()
+
+ _, err = io.Copy(os.Stdout, fin)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/cmd/_conferences/gceu23/wasip1/echoclient.rs b/cmd/_conferences/gceu23/wasip1/echoclient.rs
new file mode 100644
index 0000000..4049203
--- /dev/null
+++ b/cmd/_conferences/gceu23/wasip1/echoclient.rs
@@ -0,0 +1,24 @@
+use std::{io, fs::File, str, thread, time};
+use std::io::prelude::*;
+
+fn main() -> io::Result<()> {
+ let stdin = io::stdin(); // We get `Stdin` here.
+ let mut fout = File::create("/dev/localhost:1997")?;
+
+ print!("input> ");
+ io::stdout().lock().flush()?;
+ let mut buf = String::new();
+ stdin.read_line(&mut buf)?;
+ write!(fout, "{}", buf)?;
+
+ let ten_millis = time::Duration::from_millis(10);
+ thread::sleep(ten_millis);
+
+ let mut buf = Vec::new();
+ fout.read_to_end(&mut buf)?;
+ let buf = unsafe { str::from_utf8_unchecked(&buf) };
+ print!("output> {}", buf);
+ io::stdout().lock().flush()?;
+
+ Ok(())
+}
diff --git a/cmd/_conferences/gceu23/wasip1/envdump.go b/cmd/_conferences/gceu23/wasip1/envdump.go
new file mode 100644
index 0000000..6f940c4
--- /dev/null
+++ b/cmd/_conferences/gceu23/wasip1/envdump.go
@@ -0,0 +1,18 @@
+//go:build ignore
+
+package main
+
+import (
+ "fmt"
+ "os"
+)
+
+func main() {
+ if len(os.Environ()) == 0 {
+ fmt.Println("No environment variables found")
+ return
+ }
+ for _, kv := range os.Environ() {
+ fmt.Println(kv)
+ }
+}
diff --git a/cmd/_conferences/gceu23/wasip1/hello.go b/cmd/_conferences/gceu23/wasip1/hello.go
new file mode 100644
index 0000000..78a7f8f
--- /dev/null
+++ b/cmd/_conferences/gceu23/wasip1/hello.go
@@ -0,0 +1,9 @@
+//go:build ignore
+
+package main
+
+import "log"
+
+func main() {
+ log.Println("Hello, world!")
+}
diff --git a/cmd/_conferences/gceu23/wasip1/hello.rs b/cmd/_conferences/gceu23/wasip1/hello.rs
new file mode 100644
index 0000000..b4998a8
--- /dev/null
+++ b/cmd/_conferences/gceu23/wasip1/hello.rs
@@ -0,0 +1,3 @@
+fn main() {
+ println!("hello, world!")
+}
diff --git a/cmd/_conferences/gceu23/wasip1/nihao.go b/cmd/_conferences/gceu23/wasip1/nihao.go
new file mode 100644
index 0000000..5a5c56e
--- /dev/null
+++ b/cmd/_conferences/gceu23/wasip1/nihao.go
@@ -0,0 +1,18 @@
+//go:build ignore
+
+package main
+
+import (
+ "bufio"
+ "fmt"
+ "os"
+ "strings"
+)
+
+func main() {
+ reader := bufio.NewReader(os.Stdin)
+ fmt.Print("你叫什么名字?")
+ os.Stdout.Sync()
+ 名字, _ := reader.ReadString('\n')
+ fmt.Printf("你好%s!", strings.TrimSpace(名字))
+}
diff --git a/cmd/_conferences/gceu23/wasip1/promptreply.rs b/cmd/_conferences/gceu23/wasip1/promptreply.rs
new file mode 100644
index 0000000..09d29db
--- /dev/null
+++ b/cmd/_conferences/gceu23/wasip1/promptreply.rs
@@ -0,0 +1,9 @@
+use std::io;
+
+fn main() -> io::Result<()> {
+ let mut buffer = String::new();
+ let stdin = io::stdin(); // We get `Stdin` here.
+ stdin.read_line(&mut buffer)?;
+ println!("{}", buffer.trim());
+ Ok(())
+}