package wasmhttp import ( "bufio" "context" "fmt" "io" "log/slog" "net/http" "syscall/js" promise "github.com/nlepage/go-js-promise" "github.com/nlepage/go-wasm-http-server/v2/internal/jstype" "github.com/nlepage/go-wasm-http-server/v2/internal/readablestream" "github.com/nlepage/go-wasm-http-server/v2/internal/safejs" ) type Response interface { http.ResponseWriter io.StringWriter http.Flusher io.Closer Context() context.Context WriteError(string) JSValue() js.Value } type response struct { header http.Header wroteHeader bool promise js.Value resolve func(any) rs *readablestream.Writer body *bufio.Writer } func NewResponse() (Response, error) { rs, err := readablestream.NewWriter() if err != nil { return nil, err } promise, resolve, _ := promise.New() return &response{ promise: promise, resolve: resolve, rs: rs, body: bufio.NewWriter(rs), }, nil } var _ Response = (*response)(nil) // Header implements [http.ResponseWriter]. func (r *response) Header() http.Header { if r.header == nil { r.header = make(http.Header) } return r.header } func (r *response) headerValue() map[string]any { h := r.Header() hh := make(map[string]any, len(h)+1) for k := range h { hh[k] = h.Get(k) } return hh } // Write implements http.ResponseWriter. func (r *response) Write(buf []byte) (int, error) { r.writeHeader(buf, "") return r.body.Write(buf) } // WriteHeader implements [http.ResponseWriter]. func (r *response) WriteHeader(code int) { if r.wroteHeader { return } checkWriteHeaderCode(code) init, err := safejs.ValueOf(map[string]any{ "status": code, "headers": r.headerValue(), }) if err != nil { panic(err) } res, err := jstype.Response.New(r.rs.Value, init) if err != nil { panic(err) } r.wroteHeader = true r.resolve(safejs.Unsafe(res)) } // WriteString implements [io.StringWriter]. func (r *response) WriteString(str string) (int, error) { r.writeHeader(nil, str) return r.body.WriteString(str) } // Flush implements [http.Flusher] func (r *response) Flush() { if !r.wroteHeader { r.WriteHeader(200) } if err := r.body.Flush(); err != nil { panic(err) } } // Close implements [io.Closer] func (r *response) Close() error { if err := r.body.Flush(); err != nil { return err } return r.rs.Close() } func (r *response) Context() context.Context { return r.rs.Context() } func (r *response) WriteError(str string) { slog.Error(str) if !r.wroteHeader { r.WriteHeader(500) _, _ = r.WriteString(str) } } func (r *response) JSValue() js.Value { return r.promise } func (r *response) writeHeader(b []byte, str string) { if r.wroteHeader { return } m := r.Header() _, hasType := m["Content-Type"] hasTE := m.Get("Transfer-Encoding") != "" if !hasType && !hasTE { if b == nil { if len(str) > 512 { str = str[:512] } b = []byte(str) } m.Set("Content-Type", http.DetectContentType(b)) } r.WriteHeader(200) } func checkWriteHeaderCode(code int) { if code < 100 || code > 999 { panic(fmt.Sprintf("invalid WriteHeader code %v", code)) } }