1

Grpc-gateway offers solutions for customizing response body using google.api.HttpBody (non-json content type like text/plain, application/xml, etc), however, this proto message cannot deal with request body.

alter_igel
  • 6,899
  • 3
  • 21
  • 40

1 Answers1

0

Inspired by johanbrandhorst' comment, I came up with a solution with a customized marshaler. I implemented interfaces of runtime.marshaler to create my own marshaler.

Custiomized marshaler

package marshaler

import (
    "errors"
    "io"
    "io/ioutil"
    "log"
    "reflect"

    "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
    "google.golang.org/genproto/googleapis/api/httpbody"
)

type XMLMarshaler struct {
    runtime.Marshaler
}

func (x XMLMarshaler) ContentType(_ interface{}) string {
    return "application/xml"
}

func (x XMLMarshaler) Marshal(v interface{}) ([]byte, error) {
    log.Println("xml marshal")
    log.Printf("param: %+v", v)
    // return xml.Marshal(v)
    if httpBody, ok := v.(*httpbody.HttpBody); ok {
        return httpBody.Data, nil
    }
    return x.Marshaler.Marshal(v)
}

func (x XMLMarshaler) NewDecoder(r io.Reader) runtime.Decoder {
    log.Println("xml new decoder")
    return runtime.DecoderFunc(func(p interface{}) error {
        buffer, err := ioutil.ReadAll(r)
        if err != nil {
            return err
        }

        pType := reflect.TypeOf(p)
        if pType.Kind() == reflect.Ptr {
            v := reflect.Indirect(reflect.ValueOf(p))
            if v.Kind() == reflect.String {
                v.Set(reflect.ValueOf(string(buffer)))
                return nil
            }
        }
        return errors.New("value type error")
    })
}

starting grpc-gateway

func run() error {
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()

    // Register gRPC server endpoint
    // Note: Make sure the gRPC server is running properly and accessible
    mux := runtime.NewServeMux(
        runtime.WithMarshalerOption("application/xml", marshaler.XMLMarshaler{}),
    )
    opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
    err := gw.RegisterBotServiceHandlerFromEndpoint(ctx, mux, *grpcServerEndpoint, opts)
    if err != nil {
        return err
    }

    // Start HTTP server (and proxy calls to gRPC server endpoint)
    return http.ListenAndServe(":8080", mux)
}

Then, for all requests with content type specified as "application/xml", grpc-gateway will assign the XML body data to google.api.HttpBody.Data. And, for non-json responses, grpc-gateway will assign google.api.HttpBody.Data to response body.