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  }