add more control over request metadata; add message templates (#17)
* add more control over request metadata between reflection calls and main RPC invocation * add flag to print a message template (when describing message types) * relax some command-line argument issues to warnings
This commit is contained in:
parent
99a3346b94
commit
f7eb17819d
|
|
@ -14,7 +14,9 @@ import (
|
|||
|
||||
"github.com/golang/protobuf/jsonpb"
|
||||
"github.com/golang/protobuf/proto"
|
||||
descpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
|
||||
"github.com/jhump/protoreflect/desc"
|
||||
"github.com/jhump/protoreflect/dynamic"
|
||||
"github.com/jhump/protoreflect/grpcreflect"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
|
|
@ -49,6 +51,8 @@ var (
|
|||
with -plaintext option. Must also provide -cert option.`)
|
||||
protoset multiString
|
||||
addlHeaders multiString
|
||||
rpcHeaders multiString
|
||||
reflHeaders multiString
|
||||
data = flag.String("d", "",
|
||||
`JSON request contents. If the value is '@' then the request contents are
|
||||
read from stdin. For calls that accept a stream of requests, the
|
||||
|
|
@ -68,16 +72,26 @@ var (
|
|||
network links or due to incorrect stream method usage.`)
|
||||
emitDefaults = flag.Bool("emit-defaults", false,
|
||||
`Emit default values from JSON-encoded responses.`)
|
||||
msgTemplate = flag.Bool("msg-template", false,
|
||||
`When describing messages, show a JSON template for the message type.`)
|
||||
verbose = flag.Bool("v", false,
|
||||
`Enable verbose output.`)
|
||||
)
|
||||
|
||||
func init() {
|
||||
// TODO: Allow separate headers for relflection/invocation
|
||||
flag.Var(&addlHeaders, "H",
|
||||
`Additional request headers in 'name: value' format. May specify more
|
||||
than one via multiple -H flags. These headers will also be included in
|
||||
reflection requests to a server.`)
|
||||
`Additional headers in 'name: value' format. May specify more than one
|
||||
via multiple flags. These headers will also be included in reflection
|
||||
requests requests to a server.`)
|
||||
flag.Var(&rpcHeaders, "rpc-header",
|
||||
`Additional RPC headers in 'name: value' format. May specify more than
|
||||
one via multiple flags. These headers will *only* be used when invoking
|
||||
the requested RPC method. They are excluded from reflection requests.`)
|
||||
flag.Var(&reflHeaders, "reflect-header",
|
||||
`Additional reflection headers in 'name: value' format. May specify more
|
||||
than one via multiple flags. These headers will only be used during
|
||||
reflection requests and will be excluded when invoking the requested RPC
|
||||
method.`)
|
||||
flag.Var(&protoset, "protoset",
|
||||
`The name of a file containing an encoded FileDescriptorSet. This file's
|
||||
contents will be used to determine the RPC schema instead of querying
|
||||
|
|
@ -155,7 +169,10 @@ func main() {
|
|||
args = args[1:]
|
||||
} else {
|
||||
if *data != "" {
|
||||
fail(nil, "The -d argument is not used with 'list' or 'describe' verb.")
|
||||
warn("The -d argument is not used with 'list' or 'describe' verb.")
|
||||
}
|
||||
if len(rpcHeaders) > 0 {
|
||||
warn("The -rpc-header argument is not used with 'list' or 'describe' verb.")
|
||||
}
|
||||
if len(args) > 0 {
|
||||
symbol = args[0]
|
||||
|
|
@ -172,6 +189,9 @@ func main() {
|
|||
if len(protoset) == 0 && target == "" {
|
||||
fail(nil, "No host:port specified and no protoset specified.")
|
||||
}
|
||||
if len(protoset) > 0 && len(reflHeaders) > 0 {
|
||||
warn("The -reflect-header argument is not used when -protoset files are used ")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
if *maxTime != "" {
|
||||
|
|
@ -231,7 +251,7 @@ func main() {
|
|||
fail(err, "Failed to process proto descriptor sets")
|
||||
}
|
||||
} else {
|
||||
md := grpcurl.MetadataFromHeaders(addlHeaders)
|
||||
md := grpcurl.MetadataFromHeaders(append(addlHeaders, reflHeaders...))
|
||||
refCtx := metadata.NewOutgoingContext(ctx, md)
|
||||
cc = dial()
|
||||
refClient = grpcreflect.NewClient(refCtx, reflectpb.NewServerReflectionClient(cc))
|
||||
|
|
@ -329,6 +349,16 @@ func main() {
|
|||
fail(err, "Failed to describe symbol %q", s)
|
||||
}
|
||||
fmt.Println(txt)
|
||||
|
||||
if dsc, ok := dsc.(*desc.MessageDescriptor); ok && *msgTemplate {
|
||||
// for messages, also show a template in JSON, to make it easier to
|
||||
// create a request to invoke an RPC
|
||||
tmpl := makeTemplate(dynamic.NewMessage(dsc))
|
||||
fmt.Println("\nMessage template:")
|
||||
jsm := jsonpb.Marshaler{Indent: " ", EmitDefaults: true}
|
||||
jsm.Marshal(os.Stdout, tmpl)
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
|
@ -344,7 +374,7 @@ func main() {
|
|||
}
|
||||
|
||||
h := &handler{dec: dec, descSource: descSource}
|
||||
err := grpcurl.InvokeRpc(ctx, descSource, cc, symbol, addlHeaders, h, h.getRequestData)
|
||||
err := grpcurl.InvokeRpc(ctx, descSource, cc, symbol, append(addlHeaders, rpcHeaders...), h, h.getRequestData)
|
||||
if err != nil {
|
||||
fail(err, "Error invoking method %q", symbol)
|
||||
}
|
||||
|
|
@ -389,6 +419,11 @@ the method's request type will be sent.
|
|||
|
||||
}
|
||||
|
||||
func warn(msg string, args ...interface{}) {
|
||||
msg = fmt.Sprintf("Warning: %s\n", msg)
|
||||
fmt.Fprintf(os.Stderr, msg, args...)
|
||||
}
|
||||
|
||||
func fail(err error, msg string, args ...interface{}) {
|
||||
if err != nil {
|
||||
msg += ": %v"
|
||||
|
|
@ -466,3 +501,64 @@ func (h *handler) OnReceiveTrailers(stat *status.Status, md metadata.MD) {
|
|||
fmt.Printf("\nResponse trailers received:\n%s\n", grpcurl.MetadataToString(md))
|
||||
}
|
||||
}
|
||||
|
||||
// makeTemplate fleshes out the given message so that it is a suitable template for creating
|
||||
// an instance of that message in JSON. In particular, it ensures that any repeated fields
|
||||
// (which include map fields) are not empty, so they will render with a single element (to
|
||||
// show the types and optionally nested fields). It also ensures that nested messages are
|
||||
// not nil by setting them to a message that is also fleshed out as a template message.
|
||||
func makeTemplate(msg proto.Message) proto.Message {
|
||||
dm, ok := msg.(*dynamic.Message)
|
||||
if !ok {
|
||||
return msg
|
||||
}
|
||||
// for repeated fields, add a single element with default value
|
||||
// and for message fields, add a message with all default fields
|
||||
// that also has non-nil message and non-empty repeated fields
|
||||
for _, fd := range dm.GetMessageDescriptor().GetFields() {
|
||||
if fd.IsRepeated() {
|
||||
switch fd.GetType() {
|
||||
case descpb.FieldDescriptorProto_TYPE_FIXED32,
|
||||
descpb.FieldDescriptorProto_TYPE_UINT32:
|
||||
dm.AddRepeatedField(fd, uint32(0))
|
||||
|
||||
case descpb.FieldDescriptorProto_TYPE_SFIXED32,
|
||||
descpb.FieldDescriptorProto_TYPE_SINT32,
|
||||
descpb.FieldDescriptorProto_TYPE_INT32,
|
||||
descpb.FieldDescriptorProto_TYPE_ENUM:
|
||||
dm.AddRepeatedField(fd, int32(0))
|
||||
|
||||
case descpb.FieldDescriptorProto_TYPE_FIXED64,
|
||||
descpb.FieldDescriptorProto_TYPE_UINT64:
|
||||
dm.AddRepeatedField(fd, uint64(0))
|
||||
|
||||
case descpb.FieldDescriptorProto_TYPE_SFIXED64,
|
||||
descpb.FieldDescriptorProto_TYPE_SINT64,
|
||||
descpb.FieldDescriptorProto_TYPE_INT64:
|
||||
dm.AddRepeatedField(fd, int64(0))
|
||||
|
||||
case descpb.FieldDescriptorProto_TYPE_STRING:
|
||||
dm.AddRepeatedField(fd, "")
|
||||
|
||||
case descpb.FieldDescriptorProto_TYPE_BYTES:
|
||||
dm.AddRepeatedField(fd, []byte{})
|
||||
|
||||
case descpb.FieldDescriptorProto_TYPE_BOOL:
|
||||
dm.AddRepeatedField(fd, false)
|
||||
|
||||
case descpb.FieldDescriptorProto_TYPE_FLOAT:
|
||||
dm.AddRepeatedField(fd, float32(0))
|
||||
|
||||
case descpb.FieldDescriptorProto_TYPE_DOUBLE:
|
||||
dm.AddRepeatedField(fd, float64(0))
|
||||
|
||||
case descpb.FieldDescriptorProto_TYPE_MESSAGE,
|
||||
descpb.FieldDescriptorProto_TYPE_GROUP:
|
||||
dm.AddRepeatedField(fd, makeTemplate(dynamic.NewMessage(fd.GetMessageType())))
|
||||
}
|
||||
} else if fd.GetMessageType() != nil {
|
||||
dm.SetField(fd, makeTemplate(dynamic.NewMessage(fd.GetMessageType())))
|
||||
}
|
||||
}
|
||||
return dm
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue