146 lines
3.9 KiB
Go
146 lines
3.9 KiB
Go
package grpcgateway
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
gateway "github.com/cosmos/gogogateway"
|
|
"github.com/cosmos/gogoproto/jsonpb"
|
|
"github.com/gorilla/mux"
|
|
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
|
"github.com/spf13/viper"
|
|
"google.golang.org/grpc"
|
|
|
|
"cosmossdk.io/core/transaction"
|
|
"cosmossdk.io/log"
|
|
serverv2 "cosmossdk.io/server/v2"
|
|
)
|
|
|
|
var _ serverv2.ServerComponent[transaction.Tx] = (*GRPCGatewayServer[transaction.Tx])(nil)
|
|
|
|
const (
|
|
ServerName = "grpc-gateway"
|
|
|
|
// GRPCBlockHeightHeader is the gRPC header for block height.
|
|
GRPCBlockHeightHeader = "x-cosmos-block-height"
|
|
)
|
|
|
|
type GRPCGatewayServer[T transaction.Tx] struct {
|
|
logger log.Logger
|
|
config *Config
|
|
cfgOptions []CfgOption
|
|
|
|
GRPCSrv *grpc.Server
|
|
GRPCGatewayRouter *runtime.ServeMux
|
|
}
|
|
|
|
// New creates a new gRPC-gateway server.
|
|
func New[T transaction.Tx](grpcSrv *grpc.Server, ir jsonpb.AnyResolver, cfgOptions ...CfgOption) *GRPCGatewayServer[T] {
|
|
// The default JSON marshaller used by the gRPC-Gateway is unable to marshal non-nullable non-scalar fields.
|
|
// Using the gogo/gateway package with the gRPC-Gateway WithMarshaler option fixes the scalar field marshaling issue.
|
|
marshalerOption := &gateway.JSONPb{
|
|
EmitDefaults: true,
|
|
Indent: "",
|
|
OrigName: true,
|
|
AnyResolver: ir,
|
|
}
|
|
|
|
return &GRPCGatewayServer[T]{
|
|
GRPCSrv: grpcSrv,
|
|
GRPCGatewayRouter: runtime.NewServeMux(
|
|
// Custom marshaler option is required for gogo proto
|
|
runtime.WithMarshalerOption(runtime.MIMEWildcard, marshalerOption),
|
|
|
|
// This is necessary to get error details properly
|
|
// marshaled in unary requests.
|
|
runtime.WithProtoErrorHandler(runtime.DefaultHTTPProtoErrorHandler),
|
|
|
|
// Custom header matcher for mapping request headers to
|
|
// GRPC metadata
|
|
runtime.WithIncomingHeaderMatcher(CustomGRPCHeaderMatcher),
|
|
),
|
|
cfgOptions: cfgOptions,
|
|
}
|
|
}
|
|
|
|
func (g *GRPCGatewayServer[T]) Name() string {
|
|
return ServerName
|
|
}
|
|
|
|
func (s *GRPCGatewayServer[T]) Config() any {
|
|
if s.config == nil || s.config == (&Config{}) {
|
|
cfg := DefaultConfig()
|
|
// overwrite the default config with the provided options
|
|
for _, opt := range s.cfgOptions {
|
|
opt(cfg)
|
|
}
|
|
|
|
return cfg
|
|
}
|
|
|
|
return s.config
|
|
}
|
|
|
|
func (s *GRPCGatewayServer[T]) Init(appI serverv2.AppI[transaction.Tx], v *viper.Viper, logger log.Logger) error {
|
|
cfg := s.Config().(*Config)
|
|
if v != nil {
|
|
if err := serverv2.UnmarshalSubConfig(v, s.Name(), &cfg); err != nil {
|
|
return fmt.Errorf("failed to unmarshal config: %w", err)
|
|
}
|
|
}
|
|
|
|
// Register the gRPC-Gateway server.
|
|
// appI.RegisterGRPCGatewayRoutes(s.GRPCGatewayRouter, s.GRPCSrv)
|
|
|
|
s.logger = logger
|
|
s.config = cfg
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *GRPCGatewayServer[T]) Start(ctx context.Context) error {
|
|
if !s.config.Enable {
|
|
return nil
|
|
}
|
|
|
|
// TODO start a normal Go http server (and do not leverage comet's like https://github.com/cosmos/cosmos-sdk/blob/9df6019de6ee7999fe9864bac836deb2f36dd44a/server/api/server.go#L98)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *GRPCGatewayServer[T]) Stop(ctx context.Context) error {
|
|
if !s.config.Enable {
|
|
return nil
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Register implements registers a grpc-gateway server
|
|
func (s *GRPCGatewayServer[T]) Register(r mux.Router) error {
|
|
// configure grpc-gatway server
|
|
r.PathPrefix("/").Handler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
// Fall back to grpc gateway server.
|
|
s.GRPCGatewayRouter.ServeHTTP(w, req)
|
|
}))
|
|
|
|
return nil
|
|
}
|
|
|
|
// CustomGRPCHeaderMatcher for mapping request headers to
|
|
// GRPC metadata.
|
|
// HTTP headers that start with 'Grpc-Metadata-' are automatically mapped to
|
|
// gRPC metadata after removing prefix 'Grpc-Metadata-'. We can use this
|
|
// CustomGRPCHeaderMatcher if headers don't start with `Grpc-Metadata-`
|
|
func CustomGRPCHeaderMatcher(key string) (string, bool) {
|
|
switch strings.ToLower(key) {
|
|
case GRPCBlockHeightHeader:
|
|
return GRPCBlockHeightHeader, true
|
|
|
|
default:
|
|
return runtime.DefaultHeaderMatcher(key)
|
|
}
|
|
}
|