github.com/openfga/openfga@v1.5.4-rc1/pkg/middleware/http/handler.go (about) 1 package http 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "net/http" 9 "net/textproto" 10 "strconv" 11 "strings" 12 13 "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" 14 "google.golang.org/grpc/grpclog" 15 "google.golang.org/protobuf/proto" 16 17 "github.com/openfga/openfga/pkg/server/errors" 18 ) 19 20 // XHttpCode is used to set the header for the response HTTP code. 21 const XHttpCode = "x-http-code" 22 23 // HTTPResponseModifier is a helper function designed to modify the status code in the context of HTTP responses. 24 func HTTPResponseModifier(ctx context.Context, w http.ResponseWriter, p proto.Message) error { 25 md, ok := runtime.ServerMetadataFromContext(ctx) 26 if !ok { 27 return nil 28 } 29 30 // Set http status code. 31 if vals := md.HeaderMD.Get(XHttpCode); len(vals) > 0 { 32 code, err := strconv.Atoi(vals[0]) 33 if err != nil { 34 return err 35 } 36 // Delete the headers to not expose any grpc-metadata in http response. 37 delete(md.HeaderMD, XHttpCode) 38 delete(w.Header(), "Grpc-Metadata-X-Http-Code") 39 w.WriteHeader(code) 40 } 41 42 return nil 43 } 44 45 func requestAcceptsTrailers(req *http.Request) bool { 46 te := req.Header.Get("TE") 47 return strings.Contains(strings.ToLower(te), "trailers") 48 } 49 50 func handleForwardResponseTrailerHeader(w http.ResponseWriter, md runtime.ServerMetadata) { 51 for k := range md.TrailerMD { 52 tKey := textproto.CanonicalMIMEHeaderKey(fmt.Sprintf("%s%s", runtime.MetadataTrailerPrefix, k)) 53 w.Header().Add("Trailer", tKey) 54 } 55 } 56 57 func handleForwardResponseTrailer(w http.ResponseWriter, md runtime.ServerMetadata) { 58 for k, vs := range md.TrailerMD { 59 tKey := fmt.Sprintf("%s%s", runtime.MetadataTrailerPrefix, k) 60 for _, v := range vs { 61 w.Header().Add(tKey, v) 62 } 63 } 64 } 65 66 // CustomHTTPErrorHandler handles custom error objects in the context of HTTP requests. 67 // It is similar to [runtime.DefaultHTTPErrorHandler] but accepts an [*errors.EncodedError] object. 68 func CustomHTTPErrorHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, err *errors.EncodedError) { 69 // Convert as error object. 70 pb := err.ActualError 71 72 w.Header().Del("Trailer") 73 w.Header().Del("Transfer-Encoding") 74 75 w.Header().Set("Content-Type", "application/json") 76 77 buf := bytes.NewBuffer([]byte{}) 78 jsonEncoder := json.NewEncoder(buf) 79 jsonEncoder.SetEscapeHTML(false) 80 if err := jsonEncoder.Encode(pb); err != nil { 81 grpclog.Errorf("failed to json encode the protobuf error '%v'", pb) 82 } 83 84 md, ok := runtime.ServerMetadataFromContext(ctx) 85 if !ok { 86 grpclog.Infof("Failed to extract ServerMetadata from context") 87 } 88 for k, val := range md.HeaderMD { 89 for _, individualVal := range val { 90 if k != "content-type" { 91 w.Header().Set(k, individualVal) 92 } 93 } 94 } 95 96 // RFC 7230 https://tools.ietf.org/html/rfc7230#section-4.1.2 97 // Unless the request includes a TE header field indicating "trailers" 98 // is acceptable, as described in Section 4.3, a server SHOULD NOT 99 // generate trailer fields that it believes are necessary for the user 100 // agent to receive. 101 doForwardTrailers := requestAcceptsTrailers(r) 102 103 if doForwardTrailers { 104 handleForwardResponseTrailerHeader(w, md) 105 w.Header().Set("Transfer-Encoding", "chunked") 106 } 107 108 st := err.HTTPStatusCode 109 110 w.WriteHeader(st) 111 if _, err := w.Write(buf.Bytes()); err != nil { // nosemgrep: no-direct-write-to-responsewriter 112 grpclog.Infof("Failed to write response: %v", err) 113 } 114 115 if doForwardTrailers { 116 handleForwardResponseTrailer(w, md) 117 } 118 }