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  }