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