github.com/tetratelabs/wazero@v1.2.1/imports/wasi_snapshot_preview1/testdata/gotip/wasi.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "io" 6 "net" 7 "net/http" 8 "os" 9 "sync" 10 "syscall" 11 ) 12 13 func main() { 14 switch os.Args[1] { 15 case "sock": 16 if err := mainSock(); err != nil { 17 panic(err) 18 } 19 case "http": 20 if err := mainHTTP(); err != nil { 21 panic(err) 22 } 23 case "nonblock": 24 if err := mainNonblock(os.Args[2], os.Args[3:]); err != nil { 25 panic(err) 26 } 27 } 28 } 29 30 // mainSock is an explicit test of a blocking socket. 31 func mainSock() error { 32 // Get a listener from the pre-opened file descriptor. 33 // The listener is the first pre-open, with a file-descriptor of 3. 34 f := os.NewFile(3, "") 35 l, err := net.FileListener(f) 36 defer f.Close() 37 if err != nil { 38 return err 39 } 40 defer l.Close() 41 42 // Accept a connection 43 conn, err := l.Accept() 44 if err != nil { 45 return err 46 } 47 defer conn.Close() 48 49 // Do a blocking read of up to 32 bytes. 50 // Note: the test should write: "wazero", so that's all we should read. 51 var buf [32]byte 52 n, err := conn.Read(buf[:]) 53 if err != nil { 54 return err 55 } 56 fmt.Println(string(buf[:n])) 57 return nil 58 } 59 60 // mainHTTP implicitly tests non-blocking sockets, as they are needed for 61 // middleware. 62 func mainHTTP() error { 63 // Get the file representing a pre-opened TCP socket. 64 // The socket (listener) is the first pre-open, with a file-descriptor of 65 // 3 because the host didn't add any pre-opened files. 66 listenerFD := 3 67 f := os.NewFile(uintptr(listenerFD), "") 68 69 // Wasm runs similarly to GOMAXPROCS=1, so multiple goroutines cannot work 70 // in parallel. non-blocking allows the poller to park the go-routine 71 // accepting connections while work is done on one. 72 if err := syscall.SetNonblock(listenerFD, true); err != nil { 73 return err 74 } 75 76 // Convert the file representing the pre-opened socket to a listener, so 77 // that we can integrate it with HTTP middleware. 78 ln, err := net.FileListener(f) 79 defer f.Close() 80 if err != nil { 81 return err 82 } 83 defer ln.Close() 84 85 // Serve middleware that echos the request body to the response once, then quits. 86 h := &echoOnce{ch: make(chan struct{}, 1)} 87 go http.Serve(ln, h) 88 <-h.ch 89 return nil 90 } 91 92 type echoOnce struct { 93 ch chan struct{} 94 } 95 96 func (e echoOnce) ServeHTTP(w http.ResponseWriter, r *http.Request) { 97 // Copy up to 32 bytes from the request to the response, appending a newline. 98 // Note: the test should write: "wazero", so that's all we should read. 99 var buf [32]byte 100 if n, err := r.Body.Read(buf[:]); err != nil && err != io.EOF { 101 panic(err) 102 } else if n, err = w.Write(append(buf[:n], '\n')); err != nil { 103 panic(err) 104 } 105 // Once one request was served, close the channel. 106 close(e.ch) 107 } 108 109 // Adapted from nonblock.go 110 // https://github.com/golang/go/blob/0fcc70ecd56e3b5c214ddaee4065ea1139ae16b5/src/runtime/internal/wasitest/testdata/nonblock.go 111 func mainNonblock(mode string, files []string) error { 112 ready := make(chan struct{}) 113 114 var wg sync.WaitGroup 115 for _, path := range files { 116 f, err := os.Open(path) 117 if err != nil { 118 return err 119 } 120 switch mode { 121 case "open": 122 case "create": 123 fd := f.Fd() 124 if err = syscall.SetNonblock(int(fd), true); err != nil { 125 return err 126 } 127 f = os.NewFile(fd, path) 128 default: 129 return fmt.Errorf("invalid test mode") 130 } 131 132 spawnWait := make(chan struct{}) 133 134 wg.Add(1) 135 go func(f *os.File) { 136 defer f.Close() 137 defer wg.Done() 138 139 // Signal the routine has been spawned. 140 close(spawnWait) 141 142 // Wait until ready. 143 <-ready 144 145 var buf [256]byte 146 147 if n, err := f.Read(buf[:]); err != nil { 148 panic(err) 149 } else { 150 os.Stderr.Write(buf[:n]) 151 } 152 }(f) 153 154 // Spawn one goroutine at a time. 155 <-spawnWait 156 } 157 158 println("waiting") 159 close(ready) 160 wg.Wait() 161 return nil 162 }