mirror of
https://github.com/nlepage/go-wasm-http-server.git
synced 2025-03-10 09:27:08 +00:00
179 lines
3.1 KiB
Go
179 lines
3.1 KiB
Go
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 !r.wroteHeader {
|
|
r.WriteHeader(200)
|
|
}
|
|
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.Header().Set("Content-Type", "text/plain")
|
|
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))
|
|
}
|
|
}
|