mirror of
https://github.com/jwetzell/showbridge-go.git
synced 2026-04-27 05:15:47 +00:00
Merge pull request #31 from jwetzell/feat/http-server-output
support basic http server response control with body string template
This commit is contained in:
@@ -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,66 +87,74 @@ 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)
|
||||||
if aRouteFound {
|
aRouteFound, routingErrors := hs.router.HandleInput(inputContext, hs.Id(), r)
|
||||||
if routingErrors != nil {
|
if !responseWriter.done {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
if aRouteFound {
|
||||||
response.Status = "error"
|
if routingErrors != nil {
|
||||||
response.Message = "routing failed"
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
response.Status = "error"
|
||||||
|
response.Message = "routing failed"
|
||||||
|
|
||||||
response.IOErrors = []ResponseIOError{}
|
response.IOErrors = []ResponseIOError{}
|
||||||
for _, responseIOError := range routingErrors {
|
for _, responseIOError := range routingErrors {
|
||||||
errorToAdd := ResponseIOError{
|
errorToAdd := ResponseIOError{
|
||||||
Index: responseIOError.Index,
|
Index: responseIOError.Index,
|
||||||
}
|
|
||||||
|
|
||||||
if responseIOError.InputError != nil {
|
|
||||||
errorMsg := responseIOError.InputError.Error()
|
|
||||||
errorToAdd.InputError = &errorMsg
|
|
||||||
}
|
|
||||||
|
|
||||||
if responseIOError.ProcessError != nil {
|
|
||||||
errorMsg := responseIOError.ProcessError.Error()
|
|
||||||
errorToAdd.ProcessError = &errorMsg
|
|
||||||
}
|
|
||||||
|
|
||||||
if responseIOError.OutputErrors != nil {
|
|
||||||
outputErrorMsgs := []string{}
|
|
||||||
|
|
||||||
for _, outputError := range responseIOError.OutputErrors {
|
|
||||||
outputErrorMsgs = append(outputErrorMsgs, outputError.Error())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
errorToAdd.OutputErrors = outputErrorMsgs
|
if responseIOError.InputError != nil {
|
||||||
|
errorMsg := responseIOError.InputError.Error()
|
||||||
|
errorToAdd.InputError = &errorMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
if responseIOError.ProcessError != nil {
|
||||||
|
errorMsg := responseIOError.ProcessError.Error()
|
||||||
|
errorToAdd.ProcessError = &errorMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
if responseIOError.OutputErrors != nil {
|
||||||
|
outputErrorMsgs := []string{}
|
||||||
|
|
||||||
|
for _, outputError := range responseIOError.OutputErrors {
|
||||||
|
outputErrorMsgs = append(outputErrorMsgs, outputError.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
errorToAdd.OutputErrors = outputErrorMsgs
|
||||||
|
}
|
||||||
|
|
||||||
|
response.IOErrors = append(response.IOErrors, errorToAdd)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
response.IOErrors = append(response.IOErrors, errorToAdd)
|
return
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
response.Message = "routing successful"
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
response.Message = "routing successful"
|
response.Status = "error"
|
||||||
|
response.Message = "no matching routes found"
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
response.Status = "error"
|
|
||||||
response.Message = "no matching routes found"
|
|
||||||
}
|
}
|
||||||
} 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)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(response)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hs *HTTPServer) Run() error {
|
func (hs *HTTPServer) Run() error {
|
||||||
@@ -156,5 +182,23 @@ 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
if responseWriter.done {
|
||||||
|
return errors.New("http.server response writer has already been written to")
|
||||||
|
}
|
||||||
|
|
||||||
|
responseWriter.WriteHeader(payloadResponse.Status)
|
||||||
|
responseWriter.Write(payloadResponse.Body)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
81
internal/processor/http-response-create.go
Normal file
81
internal/processor/http-response-create.go
Normal 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
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user