wasm-http-server/response.go

175 lines
3.0 KiB
Go
Raw Normal View History

package wasmhttp
import (
"bufio"
"context"
"fmt"
"io"
"log/slog"
"net/http"
"syscall/js"
promise "github.com/nlepage/go-js-promise"
2024-10-14 22:24:46 +02:00
"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{
2024-10-14 23:28:35 +02:00
"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))
}
}