github.com/AlpineAIO/wails/v2@v2.0.0-beta.32.0.20240505041856-1047a8fa5fef/pkg/assetserver/webview/responsewriter_linux.go (about) 1 //go:build linux 2 // +build linux 3 4 package webview 5 6 /* 7 #cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 gio-unix-2.0 8 9 #include "gtk/gtk.h" 10 #include "webkit2/webkit2.h" 11 #include "gio/gunixinputstream.h" 12 13 */ 14 import "C" 15 import ( 16 "fmt" 17 "io" 18 "net/http" 19 "os" 20 "strconv" 21 "syscall" 22 "unsafe" 23 ) 24 25 type responseWriter struct { 26 req *C.WebKitURISchemeRequest 27 28 header http.Header 29 wroteHeader bool 30 finished bool 31 32 w io.WriteCloser 33 wErr error 34 } 35 36 func (rw *responseWriter) Header() http.Header { 37 if rw.header == nil { 38 rw.header = http.Header{} 39 } 40 return rw.header 41 } 42 43 func (rw *responseWriter) Write(buf []byte) (int, error) { 44 if rw.finished { 45 return 0, errResponseFinished 46 } 47 48 rw.WriteHeader(http.StatusOK) 49 if rw.wErr != nil { 50 return 0, rw.wErr 51 } 52 return rw.w.Write(buf) 53 } 54 55 func (rw *responseWriter) WriteHeader(code int) { 56 if rw.wroteHeader || rw.finished { 57 return 58 } 59 rw.wroteHeader = true 60 61 contentLength := int64(-1) 62 if sLen := rw.Header().Get(HeaderContentLength); sLen != "" { 63 if pLen, _ := strconv.ParseInt(sLen, 10, 64); pLen > 0 { 64 contentLength = pLen 65 } 66 } 67 68 // We can't use os.Pipe here, because that returns files with a finalizer for closing the FD. But the control over the 69 // read FD is given to the InputStream and will be closed there. 70 // Furthermore we especially don't want to have the FD_CLOEXEC 71 rFD, w, err := pipe() 72 if err != nil { 73 rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to open pipe: %s", err)) 74 return 75 } 76 rw.w = w 77 78 stream := C.g_unix_input_stream_new(C.int(rFD), C.gboolean(1)) 79 defer C.g_object_unref(C.gpointer(stream)) 80 81 if err := webkit_uri_scheme_request_finish(rw.req, code, rw.Header(), stream, contentLength); err != nil { 82 rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to finish request: %s", err)) 83 return 84 } 85 } 86 87 func (rw *responseWriter) Finish() error { 88 if !rw.wroteHeader { 89 rw.WriteHeader(http.StatusNotImplemented) 90 } 91 92 if rw.finished { 93 return nil 94 } 95 rw.finished = true 96 if rw.w != nil { 97 rw.w.Close() 98 } 99 return nil 100 } 101 102 func (rw *responseWriter) finishWithError(code int, err error) { 103 if rw.w != nil { 104 rw.w.Close() 105 rw.w = &nopCloser{io.Discard} 106 } 107 rw.wErr = err 108 109 msg := C.CString(err.Error()) 110 gerr := C.g_error_new_literal(C.g_quark_from_string(msg), C.int(code), msg) 111 C.webkit_uri_scheme_request_finish_error(rw.req, gerr) 112 C.g_error_free(gerr) 113 C.free(unsafe.Pointer(msg)) 114 } 115 116 type nopCloser struct { 117 io.Writer 118 } 119 120 func (nopCloser) Close() error { return nil } 121 122 func pipe() (r int, w *os.File, err error) { 123 var p [2]int 124 e := syscall.Pipe2(p[0:], 0) 125 if e != nil { 126 return 0, nil, fmt.Errorf("pipe2: %s", e) 127 } 128 129 return p[0], os.NewFile(uintptr(p[1]), "|1"), nil 130 }