github.com/vugu/vugu@v0.3.6-0.20240430171613-3f6f402e014b/devutil/handlers.go (about) 1 package devutil 2 3 import ( 4 "bytes" 5 "io" 6 "log" 7 "net/http" 8 "os" 9 "sync" 10 "time" 11 ) 12 13 // Compiler is implemented by WasmCompiler and TinygoCompiler. 14 type Compiler interface { 15 Execute() (outpath string, err error) 16 } 17 18 // WasmExecJSer is implemented by WasmCompiler and TinygoCompiler. 19 type WasmExecJSer interface { 20 WasmExecJS() (contents io.Reader, err error) 21 } 22 23 // MainWasmHandler calls WasmCompiler.Build and responds with the resulting .wasm file. 24 type MainWasmHandler struct { 25 wc Compiler 26 } 27 28 // NewMainWasmHandler returns an initialized MainWasmHandler. 29 func NewMainWasmHandler(wc Compiler) *MainWasmHandler { 30 return &MainWasmHandler{ 31 wc: wc, 32 } 33 } 34 35 // ServeHTTP implements http.Handler. 36 func (h *MainWasmHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 37 38 outpath, err := h.wc.Execute() 39 if err != nil { 40 log.Printf("MainWasmHandler: Execute error:\n%v", err) 41 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 42 http.Error(w, "MainWasmHandler: Execute error:\n"+err.Error(), 500) 43 return 44 } 45 defer os.Remove(outpath) 46 47 w.Header().Set("Content-Type", "application/wasm") 48 49 f, err := os.Open(outpath) 50 if err != nil { 51 log.Printf("MainWasmHandler: File open error:\n%v", err) 52 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 53 http.Error(w, "MainWasmHandler: File open error:\n"+err.Error(), 500) 54 return 55 } 56 defer f.Close() 57 st, err := f.Stat() 58 if err != nil { 59 log.Printf("MainWasmHandler: File stat error:\n%v", err) 60 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 61 http.Error(w, "MainWasmHandler: File stat error:\n"+err.Error(), 500) 62 return 63 } 64 65 http.ServeContent(w, r, r.URL.Path, st.ModTime(), f) 66 67 } 68 69 // WasmExecJSHandler calls WasmCompiler.WasmExecJS and responds with the resulting .js file. 70 // WasmCompiler.WasmExecJS will only be called the first time and subsequent times 71 // will return the same result from memory. (We're going to assume that you'll restart 72 // whatever process this is running in when upgrading your Go version.) 73 type WasmExecJSHandler struct { 74 wc WasmExecJSer 75 76 rwmu sync.RWMutex 77 content []byte 78 modTime time.Time 79 } 80 81 // NewWasmExecJSHandler returns an initialized WasmExecJSHandler. 82 func NewWasmExecJSHandler(wc WasmExecJSer) *WasmExecJSHandler { 83 return &WasmExecJSHandler{ 84 wc: wc, 85 } 86 } 87 88 // ServeHTTP implements http.Handler. 89 func (h *WasmExecJSHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 90 91 h.rwmu.RLock() 92 content := h.content 93 modTime := h.modTime 94 h.rwmu.RUnlock() 95 96 if content == nil { 97 98 h.rwmu.Lock() 99 defer h.rwmu.Unlock() 100 101 rd, err := h.wc.WasmExecJS() 102 if err != nil { 103 log.Printf("error getting wasm_exec.js: %v", err) 104 http.Error(w, "error getting wasm_exec.js: "+err.Error(), 500) 105 return 106 } 107 108 b, err := io.ReadAll(rd) 109 if err != nil { 110 log.Printf("error reading wasm_exec.js: %v", err) 111 http.Error(w, "error reading wasm_exec.js: "+err.Error(), 500) 112 return 113 } 114 115 h.content = b 116 content = h.content 117 h.modTime = time.Now() 118 modTime = h.modTime 119 120 } 121 122 w.Header().Set("Content-Type", "text/javascript") 123 http.ServeContent(w, r, r.URL.Path, modTime, bytes.NewReader(content)) 124 }