From ecbfab8a3c6a571dceab3811613d37349d6a5333 Mon Sep 17 00:00:00 2001 From: Wes Date: Thu, 25 Jan 2024 10:33:41 -0700 Subject: [PATCH] feat: add support for different content types (#835) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Basic support for different content-types on HTTP responses. This will need to be extended in the future, but is setup now to support `text/` types. This will definitely need more thought in the future. This allows us to return HTML content from an HTTP ingress ```go return builtin.HttpResponse[string]{ Status: 200, Headers: map[string][]string{"Content-Type": {"text/html; charset=utf-8"}}, Body: "

HTML Page From FTL 🚀!

", }, nil ``` ![Screenshot 2024-01-25 at 10 12 07 AM](https://github.com/TBD54566975/ftl/assets/51647/acb413f9-5be3-4c5c-868b-dda155512f44) --- backend/controller/controller.go | 14 ++++++++++---- backend/controller/ingress/ingress.go | 13 +++++++++++++ examples/go/httpingress/httpingress.go | 12 ++++++++++++ 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/backend/controller/controller.go b/backend/controller/controller.go index 1c28519982..1ca3d3aa48 100644 --- a/backend/controller/controller.go +++ b/backend/controller/controller.go @@ -170,9 +170,9 @@ func New(ctx context.Context, db *dal.DAL, config Config, runnerScaling scaling. } type HTTPResponse struct { - Status int - Headers map[string][]string - Body json.RawMessage + Status int `json:"status"` + Headers map[string][]string `json:"headers"` + Body json.RawMessage `json:"body"` } // ServeHTTP handles ingress routes. @@ -248,11 +248,17 @@ func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } + responseBody, err = ingress.ResponseBodyForContentType(response.Headers, response.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + for k, v := range response.Headers { w.Header()[k] = v } w.WriteHeader(response.Status) - responseBody = response.Body + } else { w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json; charset=utf-8") diff --git a/backend/controller/ingress/ingress.go b/backend/controller/ingress/ingress.go index de9754ed99..8454900ecb 100644 --- a/backend/controller/ingress/ingress.go +++ b/backend/controller/ingress/ingress.go @@ -61,6 +61,19 @@ func matchSegments(pattern, urlPath string, onMatch func(segment, value string)) return true } +func ResponseBodyForContentType(headers map[string][]string, body []byte) ([]byte, error) { + contentType, hasContentType := headers["Content-Type"] + if !hasContentType || len(contentType) == 0 || contentType[0] == "" || !strings.HasPrefix(contentType[0], "text/") { + return body, nil + } + + var htmlContent string + if err := json.Unmarshal(body, &htmlContent); err != nil { + return nil, err + } + return []byte(htmlContent), nil +} + func ValidateCallBody(body []byte, verbRef *schema.VerbRef, sch *schema.Schema) error { verb := sch.ResolveVerbRef(verbRef) if verb == nil { diff --git a/examples/go/httpingress/httpingress.go b/examples/go/httpingress/httpingress.go index 2af1e04b14..7c08412fe8 100644 --- a/examples/go/httpingress/httpingress.go +++ b/examples/go/httpingress/httpingress.go @@ -102,3 +102,15 @@ func Delete(ctx context.Context, req builtin.HttpRequest[DeleteRequest]) (builti Body: DeleteResponse{}, }, nil } + +type HtmlRequest struct{} + +//ftl:verb +//ftl:ingress http GET /http/html +func Html(ctx context.Context, req builtin.HttpRequest[HtmlRequest]) (builtin.HttpResponse[string], error) { + return builtin.HttpResponse[string]{ + Status: 200, + Headers: map[string][]string{"Content-Type": {"text/html; charset=utf-8"}}, + Body: "

HTML Page From FTL 🚀!

", + }, nil +}