1

I'm using grpc-gateway inside same go app, to proxy convert HTTP to GRPC. As far as I see by default grpc-gateway sets application application/json format for all rpcs, including streaming.

So, my task is:

  1. Incoming HTTP requests MUST always be Content-type: application/json, otherwise request should be rejected and 406 sent according to RFC.
  2. Incoming HTTP request MAY have Accept: application/x-ndjson set for unary RPCs and Accept: applcation/x-ndjson header set for server streams. If conditions don't met 406, should be returned.
  3. Outgoing HTTP request MUST set Content-type: applicaiton/json, for simple unary RPCs, and Content-type: application/x-ndjson for server streams.

So, grpc-gateway proposes only to set custom marshaller, for application/x-ndjson, which would do actually the same as default one, so with just overwritten ContentType method. This approach not allowing me to set marshaler per method call, and doesn't allow me to reject unsupported content type per request.

How can I achieve this still using grpc-gateway? Or I should consider then implementing http grpc conversion manually?

Artsiom Miksiuk
  • 3,896
  • 9
  • 33
  • 49

1 Answers1

1

I suggest you not use the grpc-gateway or any other tool to convert gRPC to HTTP RPC. You are adding unnecessary complexity to your application.

If you have a gRPC service but for whatever reason, your client can not call the gRPC and you need to offer your service through a plain HTTP option... Is that your case?

If that is your case the correct way is to offer an HTTP RPC service and from it, you call your gRPC service.

HTTP RPC is way simpler than REST and you don't need any tool for that.

I have this exact case implemented in GOlang here

    // Creates a new book.
func (h BookStoreService) CreateBook(w http.ResponseWriter, r *http.Request) {
    request := &bookv1.CreateBookRequest{}
    proxy := httputil.GetProxy(w, r, request)
    proxy.SetServiceRequest(func(request proto.Message) (proto.Message, error) {
        return h.client.CreateBook(r.Context(), request.(*bookv1.CreateBookRequest))
    })
    proxy.Call()
}

Proxy struct

func GetProxy(w http.ResponseWriter, r *http.Request, request proto.Message) *ServiceProxy {
    proxy := &ServiceProxy{}
    proxy.SetResponseWriter(w)
    proxy.SetSourceRequest(r)
    proxy.SetDestRequest(request)
    return proxy
}

type ServiceProxy struct {
    err            error
    serviceRequest func(request proto.Message) (proto.Message, error)
    writer         http.ResponseWriter
    destRequest    proto.Message
    sourceRequest  *http.Request
}

func (b *ServiceProxy) SetDestRequest(request proto.Message) {
    b.destRequest = request
}

func (b *ServiceProxy) SetSourceRequest(request *http.Request) {
    b.sourceRequest = request
}

func (b *ServiceProxy) SetServiceRequest(svcRequest func(request proto.Message) (proto.Message, error)) *ServiceProxy {
    b.serviceRequest = svcRequest
    return b
}

func (b *ServiceProxy) Call() {
    b.writer.Header().Set("Content-Type", "application/json; charset=utf-8")
    err := unmarshal(b.writer, b.sourceRequest, b.destRequest)
    if err != nil {
        return
    }
    resp, err := b.serviceRequest(b.destRequest)
    if err != nil {
        handleErrorResp(b.writer, err)
        return
    }
    b.writer.WriteHeader(http.StatusOK)
    json.NewEncoder(b.writer).Encode(resp)
}

func (b *ServiceProxy) SetResponseWriter(w http.ResponseWriter) {
    b.writer = w
}
Alexsandro Souza
  • 806
  • 7
  • 14