Skip to content

Instantly share code, notes, and snippets.

@chriso
Last active September 9, 2023 17:16
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save chriso/6c71e968ef1002981a6ff46ceaa39ee3 to your computer and use it in GitHub Desktop.
// Go HTTP Hello World Server + non-blocking I/O test.
//
// This requires gotip (https://pkg.go.dev/golang.org/dl/gotip) for:
// - https://github.com/golang/go/commit/41893389
// - https://github.com/golang/go/commit/c5c21845
// - https://github.com/golang/go/commit/a17de43e
//
// Compile http.wasm:
//
// $ GOOS=wasip1 GOARCH=wasm gotip build -o http.wasm http.go
//
// Alternatively, you can use the http.wasm file attached to the gist,
// which was compiled with:
//
// $ gotip version
// go version devel go1.21-b9baf44 Thu Jun 8 01:52:35 2023 +0000 darwin/arm64
//
// You can use the wasirun command to run the WebAssembly module:
//
// $ go install github.com/stealthrocket/wasi-go/cmd/wasirun@latest
// $ wasirun --listen 127.0.0.1:8080 http.wasm &
// [1] 22810
// $ curl http://127.0.0.1:8080/
// Hello, World!
//
// wazero (https://wazero.io) also supports non-blocking I/O and pre-opening
// sockets:
//
// $ wazero run --listen 127.0.0.1:8080 http.wasm
//
// wasmtime (https://wasmtime.dev) also supports non-blocking I/O and
// pre-opening sockets:
//
// $ wasmtime --tcplisten 127.0.0.1:8080 http.wasm
//
// If additional networking capabilities are required, for example making
// outbound connections, see https://github.com/stealthrocket/net
package main
import (
"errors"
"log"
"net"
"net/http"
"os"
"syscall"
)
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}
func run() error {
l, err := findListener()
if err != nil {
return err
} else if l == nil {
return errors.New("no pre-opened sockets available")
}
defer l.Close()
return http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, World!\n"))
}))
}
func findListener() (net.Listener, error) {
// We start looking for pre-opened sockets at fd=3 because 0, 1, and 2
// are reserved for stdio.
for preopenFd := 3; ; preopenFd++ {
// Pre-opened directories also start at fd=3, so we skip fds that
// aren't sockets. Once we reach EBADF we know there are no more
// pre-opens.
var stat syscall.Stat_t
if err := syscall.Fstat(preopenFd, &stat); err != nil {
var se syscall.Errno
if errors.As(err, &se) && se == syscall.EBADF {
err = nil
}
return nil, err
} else if stat.Filetype != syscall.FILETYPE_SOCKET_STREAM {
continue
}
// Try to enable non-blocking mode. WASM doesn't (yet) support
// threads which means that blocking system calls are
// particularly costly. By enabling non-blocking I/O, we can
// let the Go scheduler schedule goroutines while waiting for
// I/O.
syscall.SetNonblock(preopenFd, true)
f := os.NewFile(uintptr(preopenFd), "")
defer f.Close()
return net.FileListener(f)
}
}
This file has been truncated, but you can view the full file.
@achille-roussel
Copy link

I'm under the impression that os.NewFile will install a finalizer on the object to automatically close the file if it's garbage collected, so even if we don't explicitly close it the outcome may be similar here.

@chriso
Copy link
Author

chriso commented Jun 8, 2023

K that should be fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment