support basic http server response control with body string template for now

This commit is contained in:
Joel Wetzell
2025-12-28 12:25:25 -06:00
parent f97f9b9fc9
commit b15e282d59
2 changed files with 167 additions and 44 deletions

View File

@@ -9,6 +9,7 @@ import (
"net/http" "net/http"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor"
"github.com/jwetzell/showbridge-go/internal/route" "github.com/jwetzell/showbridge-go/internal/route"
) )
@@ -27,12 +28,29 @@ type ResponseIOError struct {
InputError *string `json:"inputError"` InputError *string `json:"inputError"`
} }
type ResponseData struct { type IOResponseData struct {
IOErrors []ResponseIOError `json:"ioErrors"` IOErrors []ResponseIOError `json:"ioErrors"`
Message string `json:"message"` Message string `json:"message"`
Status string `json:"status"` Status string `json:"status"`
} }
type httpServerContextKey string
type HTTPServerResponseWriter struct {
http.ResponseWriter
done bool
}
func (hsrw *HTTPServerResponseWriter) WriteHeader(status int) {
hsrw.done = true
hsrw.ResponseWriter.WriteHeader(status)
}
func (hsrw *HTTPServerResponseWriter) Write(data []byte) (int, error) {
hsrw.done = true
return hsrw.ResponseWriter.Write(data)
}
func init() { func init() {
RegisterModule(ModuleRegistration{ RegisterModule(ModuleRegistration{
Type: "http.server", Type: "http.server",
@@ -69,15 +87,16 @@ func (hs *HTTPServer) Type() string {
} }
func (hs *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (hs *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json") responseWriter := HTTPServerResponseWriter{ResponseWriter: w}
response := ResponseData{ response := IOResponseData{
Message: "routing successful", Message: "routing successful",
Status: "ok", Status: "ok",
} }
if hs.router != nil { if hs.router != nil {
aRouteFound, routingErrors := hs.router.HandleInput(hs.ctx, hs.Id(), r) inputContext := context.WithValue(hs.ctx, httpServerContextKey("responseWriter"), &responseWriter)
aRouteFound, routingErrors := hs.router.HandleInput(inputContext, hs.Id(), r)
if !responseWriter.done {
if aRouteFound { if aRouteFound {
if routingErrors != nil { if routingErrors != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
@@ -113,22 +132,29 @@ func (hs *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
response.IOErrors = append(response.IOErrors, errorToAdd) response.IOErrors = append(response.IOErrors, errorToAdd)
} }
json.NewEncoder(w).Encode(response)
return
} else { } else {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
response.Message = "routing successful" response.Message = "routing successful"
json.NewEncoder(w).Encode(response)
return
} }
} else { } else {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
response.Status = "error" response.Status = "error"
response.Message = "no matching routes found" response.Message = "no matching routes found"
json.NewEncoder(w).Encode(response)
return
}
} }
} else { } else {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
response.Message = "no router registered" response.Message = "no router registered"
response.Status = "error" response.Status = "error"
}
json.NewEncoder(w).Encode(response) json.NewEncoder(w).Encode(response)
return
}
} }
func (hs *HTTPServer) Run() error { func (hs *HTTPServer) Run() error {
@@ -156,5 +182,21 @@ func (hs *HTTPServer) Run() error {
} }
func (hs *HTTPServer) Output(ctx context.Context, payload any) error { func (hs *HTTPServer) Output(ctx context.Context, payload any) error {
return errors.New("http.server output is not implemented") responseWriter, ok := ctx.Value(httpServerContextKey("responseWriter")).(*HTTPServerResponseWriter)
if !ok {
return errors.New("http.server output must originate from an http.server input")
}
payloadResponse, ok := payload.(processor.HTTPResponse)
if !ok {
return errors.New("http.server is only able to output HTTPResponse")
}
responseWriter.WriteHeader(payloadResponse.Status)
responseWriter.Write(payloadResponse.Body)
return nil
} }

View File

@@ -0,0 +1,81 @@
package processor
import (
"bytes"
"context"
"errors"
"text/template"
"github.com/jwetzell/showbridge-go/internal/config"
)
type HTTPResponseCreate struct {
Status int
Body *template.Template
config config.ProcessorConfig
}
type HTTPResponse struct {
Status int
Body []byte
}
func (hre *HTTPResponseCreate) Process(ctx context.Context, payload any) (any, error) {
var bodyBuffer bytes.Buffer
err := hre.Body.Execute(&bodyBuffer, payload)
if err != nil {
return nil, err
}
return HTTPResponse{
Status: hre.Status,
Body: bodyBuffer.Bytes(),
}, nil
}
func (hre *HTTPResponseCreate) Type() string {
return hre.config.Type
}
func init() {
RegisterProcessor(ProcessorRegistration{
Type: "http.response.create",
New: func(config config.ProcessorConfig) (Processor, error) {
params := config.Params
status, ok := params["status"]
if !ok {
return nil, errors.New("http.response.create requires a status parameter")
}
statusNum, ok := status.(float64)
if !ok {
return nil, errors.New("http.resposne.create status must be a number")
}
body, ok := params["body"]
if !ok {
return nil, errors.New("osc.message.create requires an body parameter")
}
bodyString, ok := body.(string)
if !ok {
return nil, errors.New("osc.message.create body must be a string")
}
bodyTemplate, err := template.New("body").Parse(bodyString)
if err != nil {
return nil, err
}
// TODO(jwetzell): support other body kind (direct bytes from input, from file?)
return &HTTPResponseCreate{config: config, Status: int(statusNum), Body: bodyTemplate}, nil
},
})
}