1package grpcurl 2 3import ( 4 "bufio" 5 "bytes" 6 "encoding/base64" 7 "encoding/json" 8 "fmt" 9 "io" 10 "reflect" 11 "strings" 12 "sync" 13 14 "github.com/golang/protobuf/jsonpb" //lint:ignore SA1019 we have to import this because it appears in exported API 15 "github.com/golang/protobuf/proto" //lint:ignore SA1019 we have to import this because it appears in exported API 16 "github.com/jhump/protoreflect/desc" 17 "github.com/jhump/protoreflect/dynamic" 18 "google.golang.org/grpc/codes" 19 "google.golang.org/grpc/metadata" 20 "google.golang.org/grpc/status" 21) 22 23// RequestParser processes input into messages. 24type RequestParser interface { 25 // Next parses input data into the given request message. If called after 26 // input is exhausted, it returns io.EOF. If the caller re-uses the same 27 // instance in multiple calls to Next, it should call msg.Reset() in between 28 // each call. 29 Next(msg proto.Message) error 30 // NumRequests returns the number of messages that have been parsed and 31 // returned by a call to Next. 32 NumRequests() int 33} 34 35type jsonRequestParser struct { 36 dec *json.Decoder 37 unmarshaler jsonpb.Unmarshaler 38 requestCount int 39} 40 41// NewJSONRequestParser returns a RequestParser that reads data in JSON format 42// from the given reader. The given resolver is used to assist with decoding of 43// google.protobuf.Any messages. 44// 45// Input data that contains more than one message should just include all 46// messages concatenated (though whitespace is necessary to separate some kinds 47// of values in JSON). 48// 49// If the given reader has no data, the returned parser will return io.EOF on 50// the very first call. 51func NewJSONRequestParser(in io.Reader, resolver jsonpb.AnyResolver) RequestParser { 52 return &jsonRequestParser{ 53 dec: json.NewDecoder(in), 54 unmarshaler: jsonpb.Unmarshaler{AnyResolver: resolver}, 55 } 56} 57 58// NewJSONRequestParserWithUnmarshaler is like NewJSONRequestParser but 59// accepts a protobuf jsonpb.Unmarshaler instead of jsonpb.AnyResolver. 60func NewJSONRequestParserWithUnmarshaler(in io.Reader, unmarshaler jsonpb.Unmarshaler) RequestParser { 61 return &jsonRequestParser{ 62 dec: json.NewDecoder(in), 63 unmarshaler: unmarshaler, 64 } 65} 66 67func (f *jsonRequestParser) Next(m proto.Message) error { 68 var msg json.RawMessage 69 if err := f.dec.Decode(&msg); err != nil { 70 return err 71 } 72 f.requestCount++ 73 return f.unmarshaler.Unmarshal(bytes.NewReader(msg), m) 74} 75 76func (f *jsonRequestParser) NumRequests() int { 77 return f.requestCount 78} 79 80const ( 81 textSeparatorChar = '\x1e' 82) 83 84type textRequestParser struct { 85 r *bufio.Reader 86 err error 87 requestCount int 88} 89 90// NewTextRequestParser returns a RequestParser that reads data in the protobuf 91// text format from the given reader. 92// 93// Input data that contains more than one message should include an ASCII 94// 'Record Separator' character (0x1E) between each message. 95// 96// Empty text is a valid text format and represents an empty message. So if the 97// given reader has no data, the returned parser will yield an empty message 98// for the first call to Next and then return io.EOF thereafter. This also means 99// that if the input data ends with a record separator, then a final empty 100// message will be parsed *after* the separator. 101func NewTextRequestParser(in io.Reader) RequestParser { 102 return &textRequestParser{r: bufio.NewReader(in)} 103} 104 105func (f *textRequestParser) Next(m proto.Message) error { 106 if f.err != nil { 107 return f.err 108 } 109 110 var b []byte 111 b, f.err = f.r.ReadBytes(textSeparatorChar) 112 if f.err != nil && f.err != io.EOF { 113 return f.err 114 } 115 // remove delimiter 116 if len(b) > 0 && b[len(b)-1] == textSeparatorChar { 117 b = b[:len(b)-1] 118 } 119 120 f.requestCount++ 121 122 return proto.UnmarshalText(string(b), m) 123} 124 125func (f *textRequestParser) NumRequests() int { 126 return f.requestCount 127} 128 129// Formatter translates messages into string representations. 130type Formatter func(proto.Message) (string, error) 131 132// NewJSONFormatter returns a formatter that returns JSON strings. The JSON will 133// include empty/default values (instead of just omitted them) if emitDefaults 134// is true. The given resolver is used to assist with encoding of 135// google.protobuf.Any messages. 136func NewJSONFormatter(emitDefaults bool, resolver jsonpb.AnyResolver) Formatter { 137 marshaler := jsonpb.Marshaler{ 138 EmitDefaults: emitDefaults, 139 Indent: " ", 140 AnyResolver: resolver, 141 } 142 return marshaler.MarshalToString 143} 144 145// NewTextFormatter returns a formatter that returns strings in the protobuf 146// text format. If includeSeparator is true then, when invoked to format 147// multiple messages, all messages after the first one will be prefixed with the 148// ASCII 'Record Separator' character (0x1E). 149func NewTextFormatter(includeSeparator bool) Formatter { 150 tf := textFormatter{useSeparator: includeSeparator} 151 return tf.format 152} 153 154type textFormatter struct { 155 useSeparator bool 156 numFormatted int 157} 158 159var protoTextMarshaler = proto.TextMarshaler{ExpandAny: true} 160 161func (tf *textFormatter) format(m proto.Message) (string, error) { 162 var buf bytes.Buffer 163 if tf.useSeparator && tf.numFormatted > 0 { 164 if err := buf.WriteByte(textSeparatorChar); err != nil { 165 return "", err 166 } 167 } 168 169 // If message implements MarshalText method (such as a *dynamic.Message), 170 // it won't get details about whether or not to format to text compactly 171 // or with indentation. So first see if the message also implements a 172 // MarshalTextIndent method and use that instead if available. 173 type indentMarshaler interface { 174 MarshalTextIndent() ([]byte, error) 175 } 176 177 if indenter, ok := m.(indentMarshaler); ok { 178 b, err := indenter.MarshalTextIndent() 179 if err != nil { 180 return "", err 181 } 182 if _, err := buf.Write(b); err != nil { 183 return "", err 184 } 185 } else if err := protoTextMarshaler.Marshal(&buf, m); err != nil { 186 return "", err 187 } 188 189 // no trailing newline needed 190 str := buf.String() 191 if len(str) > 0 && str[len(str)-1] == '\n' { 192 str = str[:len(str)-1] 193 } 194 195 tf.numFormatted++ 196 197 return str, nil 198} 199 200type Format string 201 202const ( 203 FormatJSON = Format("json") 204 FormatText = Format("text") 205) 206 207// AnyResolverFromDescriptorSource returns an AnyResolver that will search for 208// types using the given descriptor source. 209func AnyResolverFromDescriptorSource(source DescriptorSource) jsonpb.AnyResolver { 210 return &anyResolver{source: source} 211} 212 213// AnyResolverFromDescriptorSourceWithFallback returns an AnyResolver that will 214// search for types using the given descriptor source and then fallback to a 215// special message if the type is not found. The fallback type will render to 216// JSON with a "@type" property, just like an Any message, but also with a 217// custom "@value" property that includes the binary encoded payload. 218func AnyResolverFromDescriptorSourceWithFallback(source DescriptorSource) jsonpb.AnyResolver { 219 res := anyResolver{source: source} 220 return &anyResolverWithFallback{AnyResolver: &res} 221} 222 223type anyResolver struct { 224 source DescriptorSource 225 226 er dynamic.ExtensionRegistry 227 228 mu sync.RWMutex 229 mf *dynamic.MessageFactory 230 resolved map[string]func() proto.Message 231} 232 233func (r *anyResolver) Resolve(typeUrl string) (proto.Message, error) { 234 mname := typeUrl 235 if slash := strings.LastIndex(mname, "/"); slash >= 0 { 236 mname = mname[slash+1:] 237 } 238 239 r.mu.RLock() 240 factory := r.resolved[mname] 241 r.mu.RUnlock() 242 243 // already resolved? 244 if factory != nil { 245 return factory(), nil 246 } 247 248 r.mu.Lock() 249 defer r.mu.Unlock() 250 251 // double-check, in case we were racing with another goroutine 252 // that resolved this one 253 factory = r.resolved[mname] 254 if factory != nil { 255 return factory(), nil 256 } 257 258 // use descriptor source to resolve message type 259 d, err := r.source.FindSymbol(mname) 260 if err != nil { 261 return nil, err 262 } 263 md, ok := d.(*desc.MessageDescriptor) 264 if !ok { 265 return nil, fmt.Errorf("unknown message: %s", typeUrl) 266 } 267 // populate any extensions for this message, too 268 if exts, err := r.source.AllExtensionsForType(mname); err != nil { 269 return nil, err 270 } else if err := r.er.AddExtension(exts...); err != nil { 271 return nil, err 272 } 273 274 if r.mf == nil { 275 r.mf = dynamic.NewMessageFactoryWithExtensionRegistry(&r.er) 276 } 277 278 factory = func() proto.Message { 279 return r.mf.NewMessage(md) 280 } 281 if r.resolved == nil { 282 r.resolved = map[string]func() proto.Message{} 283 } 284 r.resolved[mname] = factory 285 return factory(), nil 286} 287 288// anyResolverWithFallback can provide a fallback value for unknown 289// messages that will format itself to JSON using an "@value" field 290// that has the base64-encoded data for the unknown message value. 291type anyResolverWithFallback struct { 292 jsonpb.AnyResolver 293} 294 295func (r anyResolverWithFallback) Resolve(typeUrl string) (proto.Message, error) { 296 msg, err := r.AnyResolver.Resolve(typeUrl) 297 if err == nil { 298 return msg, err 299 } 300 301 // Try "default" resolution logic. This mirrors the default behavior 302 // of jsonpb, which checks to see if the given message name is registered 303 // in the proto package. 304 mname := typeUrl 305 if slash := strings.LastIndex(mname, "/"); slash >= 0 { 306 mname = mname[slash+1:] 307 } 308 //lint:ignore SA1019 new non-deprecated API requires other code changes; deferring... 309 mt := proto.MessageType(mname) 310 if mt != nil { 311 return reflect.New(mt.Elem()).Interface().(proto.Message), nil 312 } 313 314 // finally, fallback to a special placeholder that can marshal itself 315 // to JSON using a special "@value" property to show base64-encoded 316 // data for the embedded message 317 return &unknownAny{TypeUrl: typeUrl, Error: fmt.Sprintf("%s is not recognized; see @value for raw binary message data", mname)}, nil 318} 319 320type unknownAny struct { 321 TypeUrl string `json:"@type"` 322 Error string `json:"@error"` 323 Value string `json:"@value"` 324} 325 326func (a *unknownAny) MarshalJSONPB(jsm *jsonpb.Marshaler) ([]byte, error) { 327 if jsm.Indent != "" { 328 return json.MarshalIndent(a, "", jsm.Indent) 329 } 330 return json.Marshal(a) 331} 332 333func (a *unknownAny) Unmarshal(b []byte) error { 334 a.Value = base64.StdEncoding.EncodeToString(b) 335 return nil 336} 337 338func (a *unknownAny) Reset() { 339 a.Value = "" 340} 341 342func (a *unknownAny) String() string { 343 b, err := a.MarshalJSONPB(&jsonpb.Marshaler{}) 344 if err != nil { 345 return fmt.Sprintf("ERROR: %v", err.Error()) 346 } 347 return string(b) 348} 349 350func (a *unknownAny) ProtoMessage() { 351} 352 353var _ proto.Message = (*unknownAny)(nil) 354 355// FormatOptions is a set of flags that are passed to a JSON or text formatter. 356type FormatOptions struct { 357 // EmitJSONDefaultFields flag, when true, includes empty/default values in the output. 358 // FormatJSON only flag. 359 EmitJSONDefaultFields bool 360 361 // AllowUnknownFields is an option for the parser. When true, 362 // it accepts input which includes unknown fields. These unknown fields 363 // are skipped instead of returning an error. 364 // FormatJSON only flag. 365 AllowUnknownFields bool 366 367 // IncludeTextSeparator is true then, when invoked to format multiple messages, 368 // all messages after the first one will be prefixed with the 369 // ASCII 'Record Separator' character (0x1E). 370 // It might be useful when the output is piped to another grpcurl process. 371 // FormatText only flag. 372 IncludeTextSeparator bool 373} 374 375// RequestParserAndFormatter returns a request parser and formatter for the 376// given format. The given descriptor source may be used for parsing message 377// data (if needed by the format). 378// It accepts a set of options. The field EmitJSONDefaultFields and IncludeTextSeparator 379// are options for JSON and protobuf text formats, respectively. The AllowUnknownFields field 380// is a JSON-only format flag. 381// Requests will be parsed from the given in. 382func RequestParserAndFormatter(format Format, descSource DescriptorSource, in io.Reader, opts FormatOptions) (RequestParser, Formatter, error) { 383 switch format { 384 case FormatJSON: 385 resolver := AnyResolverFromDescriptorSource(descSource) 386 unmarshaler := jsonpb.Unmarshaler{AnyResolver: resolver, AllowUnknownFields: opts.AllowUnknownFields} 387 return NewJSONRequestParserWithUnmarshaler(in, unmarshaler), NewJSONFormatter(opts.EmitJSONDefaultFields, anyResolverWithFallback{AnyResolver: resolver}), nil 388 case FormatText: 389 return NewTextRequestParser(in), NewTextFormatter(opts.IncludeTextSeparator), nil 390 default: 391 return nil, nil, fmt.Errorf("unknown format: %s", format) 392 } 393} 394 395// RequestParserAndFormatterFor returns a request parser and formatter for the 396// given format. The given descriptor source may be used for parsing message 397// data (if needed by the format). The flags emitJSONDefaultFields and 398// includeTextSeparator are options for JSON and protobuf text formats, 399// respectively. Requests will be parsed from the given in. 400// This function is deprecated. Please use RequestParserAndFormatter instead. 401// DEPRECATED 402func RequestParserAndFormatterFor(format Format, descSource DescriptorSource, emitJSONDefaultFields, includeTextSeparator bool, in io.Reader) (RequestParser, Formatter, error) { 403 return RequestParserAndFormatter(format, descSource, in, FormatOptions{ 404 EmitJSONDefaultFields: emitJSONDefaultFields, 405 IncludeTextSeparator: includeTextSeparator, 406 }) 407} 408 409// DefaultEventHandler logs events to a writer. This is not thread-safe, but is 410// safe for use with InvokeRPC as long as NumResponses and Status are not read 411// until the call to InvokeRPC completes. 412type DefaultEventHandler struct { 413 Out io.Writer 414 Formatter Formatter 415 // 0 = default 416 // 1 = verbose 417 // 2 = very verbose 418 VerbosityLevel int 419 420 // NumResponses is the number of responses that have been received. 421 NumResponses int 422 // Status is the status that was received at the end of an RPC. It is 423 // nil if the RPC is still in progress. 424 Status *status.Status 425} 426 427// NewDefaultEventHandler returns an InvocationEventHandler that logs events to 428// the given output. If verbose is true, all events are logged. Otherwise, only 429// response messages are logged. 430// 431// Deprecated: NewDefaultEventHandler exists for compatability. 432// It doesn't allow fine control over the `VerbosityLevel` 433// and provides only 0 and 1 options (which corresponds to the `verbose` argument). 434// Use DefaultEventHandler{} initializer directly. 435func NewDefaultEventHandler(out io.Writer, descSource DescriptorSource, formatter Formatter, verbose bool) *DefaultEventHandler { 436 verbosityLevel := 0 437 if verbose { 438 verbosityLevel = 1 439 } 440 return &DefaultEventHandler{ 441 Out: out, 442 Formatter: formatter, 443 VerbosityLevel: verbosityLevel, 444 } 445} 446 447var _ InvocationEventHandler = (*DefaultEventHandler)(nil) 448 449func (h *DefaultEventHandler) OnResolveMethod(md *desc.MethodDescriptor) { 450 if h.VerbosityLevel > 0 { 451 txt, err := GetDescriptorText(md, nil) 452 if err == nil { 453 fmt.Fprintf(h.Out, "\nResolved method descriptor:\n%s\n", txt) 454 } 455 } 456} 457 458func (h *DefaultEventHandler) OnSendHeaders(md metadata.MD) { 459 if h.VerbosityLevel > 0 { 460 fmt.Fprintf(h.Out, "\nRequest metadata to send:\n%s\n", MetadataToString(md)) 461 } 462} 463 464func (h *DefaultEventHandler) OnReceiveHeaders(md metadata.MD) { 465 if h.VerbosityLevel > 0 { 466 fmt.Fprintf(h.Out, "\nResponse headers received:\n%s\n", MetadataToString(md)) 467 } 468} 469 470func (h *DefaultEventHandler) OnReceiveResponse(resp proto.Message) { 471 h.NumResponses++ 472 if h.VerbosityLevel > 1 { 473 fmt.Fprintf(h.Out, "\nEstimated response size: %d bytes\n", proto.Size(resp)) 474 } 475 if h.VerbosityLevel > 0 { 476 fmt.Fprint(h.Out, "\nResponse contents:\n") 477 } 478 if respStr, err := h.Formatter(resp); err != nil { 479 fmt.Fprintf(h.Out, "Failed to format response message %d: %v\n", h.NumResponses, err) 480 } else { 481 fmt.Fprintln(h.Out, respStr) 482 } 483} 484 485func (h *DefaultEventHandler) OnReceiveTrailers(stat *status.Status, md metadata.MD) { 486 h.Status = stat 487 if h.VerbosityLevel > 0 { 488 fmt.Fprintf(h.Out, "\nResponse trailers received:\n%s\n", MetadataToString(md)) 489 } 490} 491 492// PrintStatus prints details about the given status to the given writer. The given 493// formatter is used to print any detail messages that may be included in the status. 494// If the given status has a code of OK, "OK" is printed and that is all. Otherwise, 495// "ERROR:" is printed along with a line showing the code, one showing the message 496// string, and each detail message if any are present. The detail messages will be 497// printed as proto text format or JSON, depending on the given formatter. 498func PrintStatus(w io.Writer, stat *status.Status, formatter Formatter) { 499 if stat.Code() == codes.OK { 500 fmt.Fprintln(w, "OK") 501 return 502 } 503 fmt.Fprintf(w, "ERROR:\n Code: %s\n Message: %s\n", stat.Code().String(), stat.Message()) 504 505 statpb := stat.Proto() 506 if len(statpb.Details) > 0 { 507 fmt.Fprintf(w, " Details:\n") 508 for i, det := range statpb.Details { 509 prefix := fmt.Sprintf(" %d)", i+1) 510 fmt.Fprintf(w, "%s\t", prefix) 511 prefix = strings.Repeat(" ", len(prefix)) + "\t" 512 513 output, err := formatter(det) 514 if err != nil { 515 fmt.Fprintf(w, "Error parsing detail message: %v\n", err) 516 } else { 517 lines := strings.Split(output, "\n") 518 for i, line := range lines { 519 if i == 0 { 520 // first line is already indented 521 fmt.Fprintf(w, "%s\n", line) 522 } else { 523 fmt.Fprintf(w, "%s%s\n", prefix, line) 524 } 525 } 526 } 527 } 528 } 529} 530