initial commit, functioning grpcurl command-line util
This commit is contained in:
commit
9dfaded6cf
|
|
@ -0,0 +1,13 @@
|
||||||
|
language: go
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- go: 1.6
|
||||||
|
- go: 1.7
|
||||||
|
- go: 1.8
|
||||||
|
- go: 1.9
|
||||||
|
- go: tip
|
||||||
|
|
||||||
|
script:
|
||||||
|
- ./ci.sh
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2017 FullStory, Inc
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
# GRPCurl
|
||||||
|
`grpcurl` is a command-line tool that lets you interact with GRPC servers. It's
|
||||||
|
basically `curl` for GRPC servers.
|
||||||
|
|
||||||
|
The main purpose for this tool is to invoke RPC methods on a GRPC server from the
|
||||||
|
command-line. GRPC servers use a binary encoding on the wire
|
||||||
|
([protocol buffers](https://developers.google.com/protocol-buffers/), or "protobufs"
|
||||||
|
for short). So they are basically impossible to interact with using regular `curl`
|
||||||
|
(and older versions of `curl` that do not support HTTP/2 are of course non-starters).
|
||||||
|
This program accepts messages using JSON encoding, which is much more friendly for both
|
||||||
|
humans and scripts.
|
||||||
|
|
||||||
|
With this tool you can also browse the schema for GRPC services, either by querying
|
||||||
|
a server that supports [service reflection](https://github.com/grpc/grpc/blob/master/src/proto/grpc/reflection/v1alpha/reflection.proto)
|
||||||
|
or by loading in "protoset" files (files that contain encoded file
|
||||||
|
[descriptor protos](https://github.com/google/protobuf/blob/master/src/google/protobuf/descriptor.proto)).
|
||||||
|
In fact, the way the tool transforms JSON request data into a binary encoded protobuf
|
||||||
|
is using that very same schema. So, if the server you interact with does not support
|
||||||
|
reflection, you will need to build "protoset" files that `grpcurl` can use.
|
||||||
|
|
||||||
|
This code for this tool is also a great example of how to use the various packages of
|
||||||
|
the [protoreflect](https://godoc.org/github.com/jhump/protoreflect) library, and shows
|
||||||
|
off what they can do.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
`grpcurl` supports all kinds of RPC methods, including streaming methods. You can even
|
||||||
|
operate bi-directional streaming methods interactively by running `grpcurl` from an
|
||||||
|
interactive terminal and using stdin as the request body!
|
||||||
|
|
||||||
|
`grpcurl` supports both plain-text and TLS servers and has numerous options for TLS
|
||||||
|
configuration. It also supports mutual TLS, where the client is required to present a
|
||||||
|
client certificate.
|
||||||
|
|
||||||
|
As mentioned above, `grpcurl` works seamlessly if the server supports the reflection
|
||||||
|
service. If not, you must use `protoc` to build protoset files and provide those to
|
||||||
|
`grpcurl`.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
Invoking an RPC on a trusted server (e.g. TLS without self-signed key or custom CA)
|
||||||
|
that requires no client certs and supports service reflection is the simplest thing to
|
||||||
|
do with `grpcurl`. This minimal invocation sends an empty request body:
|
||||||
|
```
|
||||||
|
grpcurl grpc.server.com:443 my.custom.server.Service/Method
|
||||||
|
```
|
||||||
|
|
||||||
|
To list all services exposed by a server, use the "list" verb. When using protoset files
|
||||||
|
instead of server reflection, this lists all services defined in the protoset files.
|
||||||
|
```
|
||||||
|
grpcurl localhost:80808 list
|
||||||
|
|
||||||
|
grpcurl -protoset my-protos.bin list
|
||||||
|
```
|
||||||
|
|
||||||
|
The "list" verb also lets you see all methods in a particular service:
|
||||||
|
```
|
||||||
|
grpcurl localhost:80808 list my.custom.server.Service
|
||||||
|
```
|
||||||
|
|
||||||
|
The "describe" verb will print the type of any symbol that the server knows about
|
||||||
|
or that is found in a given protoset file and also print the full descriptor for the
|
||||||
|
symbol, in JSON.
|
||||||
|
```
|
||||||
|
grpcurl localhost:80808 describe my.custom.server.Service.MethodOne
|
||||||
|
|
||||||
|
grpcurl -protoset my-protos.bin describe my.custom.server.Service.MethodOne
|
||||||
|
```
|
||||||
|
|
||||||
|
The usage doc for the tool explains the numerous options:
|
||||||
|
```
|
||||||
|
grpcurl -help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Protoset Files
|
||||||
|
To use `grpcurl` on servers that do not support reflection, you need to compile the
|
||||||
|
`*.proto` files that describe the service into files containing encoded
|
||||||
|
`FileDescriptorSet` protos, also known as "protoset" files.
|
||||||
|
|
||||||
|
```
|
||||||
|
protoc --proto_path=. \
|
||||||
|
--descriptor_set_out=myservice.protoset \
|
||||||
|
--include_imports \
|
||||||
|
my/custom/server/service.proto
|
||||||
|
```
|
||||||
|
|
||||||
|
The `--descriptor_set_out` argument is what tells `protoc` to produce a protoset,
|
||||||
|
and the `--include_imports` arguments is necessary for the protoset to contain
|
||||||
|
everything that `grpcurl` needs to process and understand the schema.
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
fmtdiff="$(gofmt -s -l ./)"
|
||||||
|
if [[ -n "$fmtdiff" ]]; then
|
||||||
|
gofmt -s -l ./ >&2
|
||||||
|
echo "Run gofmt on the above files!" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
go test -v -race ./...
|
||||||
|
|
@ -0,0 +1,456 @@
|
||||||
|
// Command grpcurl makes GRPC requests (a la cURL, but HTTP/2). It can use a supplied descriptor file or
|
||||||
|
// service reflection to translate JSON request data into the appropriate protobuf request data and vice
|
||||||
|
// versa for presenting the response contents.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jhump/protoreflect/desc"
|
||||||
|
"github.com/jhump/protoreflect/grpcreflect"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/keepalive"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
reflectpb "google.golang.org/grpc/reflection/grpc_reflection_v1alpha"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
|
||||||
|
"github.com/fullstorydev/grpcurl"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
exit = os.Exit
|
||||||
|
|
||||||
|
help = flag.Bool("help", false,
|
||||||
|
`Print usage instructions and exit.`)
|
||||||
|
plaintext = flag.Bool("plaintext", false,
|
||||||
|
`Use plain-text HTTP/2 when connecting to server (no TLS).`)
|
||||||
|
insecure = flag.Bool("insecure", false,
|
||||||
|
`Skip server certificate and domain verification. (NOT SECURE!). Not
|
||||||
|
valid with -plaintext option.`)
|
||||||
|
cacert = flag.String("cacert", "",
|
||||||
|
`File containing trusted root certificates for verifying the server.
|
||||||
|
Ignored if -insecure is specified.`)
|
||||||
|
cert = flag.String("cert", "",
|
||||||
|
`File containing client certificate (public key), to present to the
|
||||||
|
server. Not valid with -plaintext option. Must also provide -key option.`)
|
||||||
|
key = flag.String("key", "",
|
||||||
|
`File containing client private key, to present to the server. Not valid
|
||||||
|
with -plaintext option. Must also provide -cert option.`)
|
||||||
|
protoset multiString
|
||||||
|
addlHeaders 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
|
||||||
|
contents should include all such request messages concatenated together
|
||||||
|
(optionally separated by whitespace).`)
|
||||||
|
connectTimeout = flag.String("connect-timeout", "",
|
||||||
|
`The maximum time, in seconds, to wait for connection to be established.
|
||||||
|
Defaults to 10 seconds.`)
|
||||||
|
keepaliveTime = flag.String("keepalive-time", "",
|
||||||
|
`If present, the maximum idle time in seconds, after which a keepalive
|
||||||
|
probe is sent. If the connection remains idle and no keepalive response
|
||||||
|
is received for this same period then the connection is closed and the
|
||||||
|
operation fails.`)
|
||||||
|
maxTime = flag.String("max-time", "",
|
||||||
|
`The maximum total time the operation can take. This is useful for
|
||||||
|
preventing batch jobs that use grpcurl from hanging due to slow or bad
|
||||||
|
network links or due to incorrect stream method usage.`)
|
||||||
|
verbose = flag.Bool("v", false,
|
||||||
|
`Enable verbose output.`)
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.Var(&addlHeaders, "H",
|
||||||
|
`Additional request headers in 'name: value' format. May specify more
|
||||||
|
than one via multiple -H flags.`)
|
||||||
|
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
|
||||||
|
for it from the remote server via the GRPC reflection API. When set: the
|
||||||
|
'list' action lists the services found in the given descriptors (vs.
|
||||||
|
those exposed by the remote server), and the 'describe' action describes
|
||||||
|
symbols found in the given descriptors. May specify more than one via
|
||||||
|
multiple -protoset flags.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
type multiString []string
|
||||||
|
|
||||||
|
func (s *multiString) String() string {
|
||||||
|
return strings.Join(*s, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *multiString) Set(value string) error {
|
||||||
|
*s = append(*s, value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.CommandLine.Usage = usage
|
||||||
|
flag.Parse()
|
||||||
|
if *help {
|
||||||
|
usage()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do extra validation on arguments and figure out what user asked us to do.
|
||||||
|
if *plaintext && *insecure {
|
||||||
|
fail(nil, "The -plaintext and -insecure arguments are mutually exclusive.")
|
||||||
|
}
|
||||||
|
if *plaintext && *cert != "" {
|
||||||
|
fail(nil, "The -plaintext and -cert arguments are mutually exclusive.")
|
||||||
|
}
|
||||||
|
if *plaintext && *key != "" {
|
||||||
|
fail(nil, "The -plaintext and -key arguments are mutually exclusive.")
|
||||||
|
}
|
||||||
|
if (*key == "") != (*cert == "") {
|
||||||
|
fail(nil, "The -cert and -key arguments must be used together and both be present.")
|
||||||
|
}
|
||||||
|
|
||||||
|
args := flag.Args()
|
||||||
|
|
||||||
|
if len(args) == 0 {
|
||||||
|
fail(nil, "Too few arguments.")
|
||||||
|
}
|
||||||
|
var target string
|
||||||
|
if args[0] != "list" && args[0] != "describe" {
|
||||||
|
target = args[0]
|
||||||
|
args = args[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) == 0 {
|
||||||
|
fail(nil, "Too few arguments.")
|
||||||
|
}
|
||||||
|
var list, describe, invoke bool
|
||||||
|
if args[0] == "list" {
|
||||||
|
list = true
|
||||||
|
args = args[1:]
|
||||||
|
} else if args[0] == "describe" {
|
||||||
|
describe = true
|
||||||
|
args = args[1:]
|
||||||
|
} else {
|
||||||
|
invoke = true
|
||||||
|
}
|
||||||
|
|
||||||
|
var symbol string
|
||||||
|
if invoke {
|
||||||
|
if len(args) == 0 {
|
||||||
|
fail(nil, "Too few arguments.")
|
||||||
|
}
|
||||||
|
symbol = args[0]
|
||||||
|
args = args[1:]
|
||||||
|
} else {
|
||||||
|
if *data != "" {
|
||||||
|
fail(nil, "The -d argument is not used with 'list' or 'describe' verb.")
|
||||||
|
}
|
||||||
|
if len(addlHeaders) > 0 {
|
||||||
|
fail(nil, "The -H argument is not used with 'list' or 'describe' verb.")
|
||||||
|
}
|
||||||
|
if len(args) > 0 {
|
||||||
|
symbol = args[0]
|
||||||
|
args = args[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) > 0 {
|
||||||
|
fail(nil, "Too many arguments.")
|
||||||
|
}
|
||||||
|
if invoke && target == "" {
|
||||||
|
fail(nil, "No host:port specified.")
|
||||||
|
}
|
||||||
|
if len(protoset) == 0 && target == "" {
|
||||||
|
fail(nil, "No host:port specified and no protoset specified.")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
if *maxTime != "" {
|
||||||
|
t, err := strconv.ParseFloat(*maxTime, 64)
|
||||||
|
if err != nil {
|
||||||
|
fail(nil, "The -max-time argument must be a valid number.")
|
||||||
|
}
|
||||||
|
timeout := time.Duration(t * float64(time.Second))
|
||||||
|
ctx, _ = context.WithTimeout(ctx, timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
dial := func() *grpc.ClientConn {
|
||||||
|
dialTime := 10 * time.Second
|
||||||
|
if *connectTimeout != "" {
|
||||||
|
t, err := strconv.ParseFloat(*connectTimeout, 64)
|
||||||
|
if err != nil {
|
||||||
|
fail(nil, "The -connect-timeout argument must be a valid number.")
|
||||||
|
}
|
||||||
|
dialTime = time.Duration(t * float64(time.Second))
|
||||||
|
}
|
||||||
|
opts := []grpc.DialOption{grpc.WithTimeout(dialTime), grpc.WithBlock()}
|
||||||
|
if *keepaliveTime != "" {
|
||||||
|
t, err := strconv.ParseFloat(*keepaliveTime, 64)
|
||||||
|
if err != nil {
|
||||||
|
fail(nil, "The -keepalive-time argument must be a valid number.")
|
||||||
|
}
|
||||||
|
timeout := time.Duration(t * float64(time.Second))
|
||||||
|
opts = append(opts, grpc.WithKeepaliveParams(keepalive.ClientParameters{
|
||||||
|
Time: timeout,
|
||||||
|
Timeout: timeout,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
if *plaintext {
|
||||||
|
opts = append(opts, grpc.WithInsecure())
|
||||||
|
} else {
|
||||||
|
creds, err := grpcurl.ClientTransportCredentials(*insecure, *cacert, *cert, *key)
|
||||||
|
if err != nil {
|
||||||
|
fail(err, "Failed to configure transport credentials")
|
||||||
|
}
|
||||||
|
opts = append(opts, grpc.WithTransportCredentials(creds))
|
||||||
|
}
|
||||||
|
cc, err := grpc.Dial(target, opts...)
|
||||||
|
if err != nil {
|
||||||
|
fail(err, "Failed to dial target host %q", target)
|
||||||
|
}
|
||||||
|
return cc
|
||||||
|
}
|
||||||
|
|
||||||
|
var cc *grpc.ClientConn
|
||||||
|
var descSource grpcurl.DescriptorSource
|
||||||
|
var refClient *grpcreflect.Client
|
||||||
|
if len(protoset) > 0 {
|
||||||
|
var err error
|
||||||
|
descSource, err = grpcurl.DescriptorSourceFromProtoSets(protoset...)
|
||||||
|
if err != nil {
|
||||||
|
fail(err, "Failed to process proto descriptor sets")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cc = dial()
|
||||||
|
refClient = grpcreflect.NewClient(ctx, reflectpb.NewServerReflectionClient(cc))
|
||||||
|
descSource = grpcurl.DescriptorSourceFromServer(ctx, refClient)
|
||||||
|
}
|
||||||
|
|
||||||
|
// arrange for the RPCs to be cleanly shutdown
|
||||||
|
reset := func() {
|
||||||
|
if refClient != nil {
|
||||||
|
refClient.Reset()
|
||||||
|
refClient = nil
|
||||||
|
}
|
||||||
|
if cc != nil {
|
||||||
|
cc.Close()
|
||||||
|
cc = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer reset()
|
||||||
|
exit = func(code int) {
|
||||||
|
// since defers aren't run by os.Exit...
|
||||||
|
reset()
|
||||||
|
os.Exit(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
if list {
|
||||||
|
if symbol == "" {
|
||||||
|
svcs, err := grpcurl.ListServices(descSource)
|
||||||
|
if err != nil {
|
||||||
|
fail(err, "Failed to list services")
|
||||||
|
}
|
||||||
|
if len(svcs) == 0 {
|
||||||
|
fmt.Println("(No services)")
|
||||||
|
} else {
|
||||||
|
for _, svc := range svcs {
|
||||||
|
fmt.Printf("%s\n", svc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
methods, err := grpcurl.ListMethods(descSource, symbol)
|
||||||
|
if err != nil {
|
||||||
|
fail(err, "Failed to list methods for service %q", symbol)
|
||||||
|
}
|
||||||
|
if len(methods) == 0 {
|
||||||
|
fmt.Println("(No methods)") // probably unlikely
|
||||||
|
} else {
|
||||||
|
for _, m := range methods {
|
||||||
|
fmt.Printf("%s\n", m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if describe {
|
||||||
|
var symbols []string
|
||||||
|
if symbol != "" {
|
||||||
|
symbols = []string{symbol}
|
||||||
|
} else {
|
||||||
|
// if no symbol given, describe all exposed services
|
||||||
|
svcs, err := descSource.ListServices()
|
||||||
|
if err != nil {
|
||||||
|
fail(err, "Failed to list services")
|
||||||
|
}
|
||||||
|
if len(svcs) == 0 {
|
||||||
|
fmt.Println("Server returned an empty list of exposed services")
|
||||||
|
}
|
||||||
|
symbols = svcs
|
||||||
|
}
|
||||||
|
for _, s := range symbols {
|
||||||
|
dsc, err := descSource.FindSymbol(s)
|
||||||
|
if err != nil {
|
||||||
|
fail(err, "Failed to resolve symbol %q", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
txt, err := grpcurl.GetDescriptorText(dsc, descSource)
|
||||||
|
if err != nil {
|
||||||
|
fail(err, "Failed to describe symbol %q", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch dsc.(type) {
|
||||||
|
case *desc.MessageDescriptor:
|
||||||
|
fmt.Printf("%s is a message:\n", dsc.GetFullyQualifiedName())
|
||||||
|
case *desc.FieldDescriptor:
|
||||||
|
fmt.Printf("%s is a field:\n", dsc.GetFullyQualifiedName())
|
||||||
|
case *desc.OneOfDescriptor:
|
||||||
|
fmt.Printf("%s is a one-of:\n", dsc.GetFullyQualifiedName())
|
||||||
|
case *desc.EnumDescriptor:
|
||||||
|
fmt.Printf("%s is an enum:\n", dsc.GetFullyQualifiedName())
|
||||||
|
case *desc.EnumValueDescriptor:
|
||||||
|
fmt.Printf("%s is an enum value:\n", dsc.GetFullyQualifiedName())
|
||||||
|
case *desc.ServiceDescriptor:
|
||||||
|
fmt.Printf("%s is a service:\n", dsc.GetFullyQualifiedName())
|
||||||
|
case *desc.MethodDescriptor:
|
||||||
|
fmt.Printf("%s is a method:\n", dsc.GetFullyQualifiedName())
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("descriptor has unrecognized type %T", dsc)
|
||||||
|
fail(err, "Failed to describe symbol %q", s)
|
||||||
|
}
|
||||||
|
fmt.Println(txt)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Invoke an RPC
|
||||||
|
if cc == nil {
|
||||||
|
cc = dial()
|
||||||
|
}
|
||||||
|
var dec *json.Decoder
|
||||||
|
if *data == "@" {
|
||||||
|
dec = json.NewDecoder(os.Stdin)
|
||||||
|
} else {
|
||||||
|
dec = json.NewDecoder(strings.NewReader(*data))
|
||||||
|
}
|
||||||
|
|
||||||
|
h := &handler{dec: dec, descSource: descSource}
|
||||||
|
err := grpcurl.InvokeRpc(ctx, descSource, cc, symbol, addlHeaders, h, h.getRequestData)
|
||||||
|
if err != nil {
|
||||||
|
fail(err, "Error invoking method %q", symbol)
|
||||||
|
}
|
||||||
|
reqSuffix := ""
|
||||||
|
respSuffix := ""
|
||||||
|
if h.reqCount != 1 {
|
||||||
|
reqSuffix = "s"
|
||||||
|
}
|
||||||
|
if h.respCount != 1 {
|
||||||
|
respSuffix = "s"
|
||||||
|
}
|
||||||
|
fmt.Printf("Sent %d request%s and received %d response%s\n", h.reqCount, reqSuffix, h.respCount, respSuffix)
|
||||||
|
if h.stat.Code() != codes.OK {
|
||||||
|
fmt.Fprintf(os.Stderr, "ERROR:\n Code: %s\n Message: %s\n", h.stat.Code().String(), h.stat.Message())
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func usage() {
|
||||||
|
fmt.Fprintf(os.Stderr, `Usage:
|
||||||
|
%s [flags] [host:port] [list|describe] [symbol]
|
||||||
|
|
||||||
|
The 'host:port' is only optional when used with 'list' or 'describe' and a
|
||||||
|
protoset flag is provided.
|
||||||
|
|
||||||
|
If 'list' is indicated, the symbol (if present) should be a fully-qualified
|
||||||
|
service name. If present, all methods of that service are listed. If not
|
||||||
|
present, all exposed services are listed, or all services defined in protosets.
|
||||||
|
|
||||||
|
If 'describe' is indicated, the descriptor for the given symbol is shown. The
|
||||||
|
symbol should be a fully-qualified service, enum, or message name. If no symbol
|
||||||
|
is given then the descriptors for all exposed or known services are shown.
|
||||||
|
|
||||||
|
If neither verb is present, the symbol must be a fully-qualified method name in
|
||||||
|
'service/method' or 'service.method' format. In this case, the request body will
|
||||||
|
be used to invoke the named method. If no body is given, an empty instance of
|
||||||
|
the method's request type will be sent.
|
||||||
|
|
||||||
|
`, os.Args[0])
|
||||||
|
flag.PrintDefaults()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func fail(err error, msg string, args ...interface{}) {
|
||||||
|
if err != nil {
|
||||||
|
msg += ": %v"
|
||||||
|
args = append(args, err)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, msg, args...)
|
||||||
|
fmt.Fprintln(os.Stderr)
|
||||||
|
if err != nil {
|
||||||
|
exit(1)
|
||||||
|
} else {
|
||||||
|
// nil error means it was CLI usage issue
|
||||||
|
fmt.Fprintf(os.Stderr, "Try '%s -help' for more details.\n", os.Args[0])
|
||||||
|
exit(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type handler struct {
|
||||||
|
dec *json.Decoder
|
||||||
|
descSource grpcurl.DescriptorSource
|
||||||
|
reqCount int
|
||||||
|
respCount int
|
||||||
|
stat *status.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) OnResolveMethod(md *desc.MethodDescriptor) {
|
||||||
|
if *verbose {
|
||||||
|
txt, err := grpcurl.GetDescriptorText(md, h.descSource)
|
||||||
|
if err == nil {
|
||||||
|
fmt.Printf("\nResolved method descriptor:\n%s\n", txt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*handler) OnSendHeaders(md metadata.MD) {
|
||||||
|
if *verbose {
|
||||||
|
fmt.Printf("\nRequest metadata to send:\n%s\n", grpcurl.MetadataToString(md))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) getRequestData() (json.RawMessage, error) {
|
||||||
|
// we don't use a mutex, though this methods will be called from different goroutine
|
||||||
|
// than other methods for bidi calls, because this method does not share any state
|
||||||
|
// with the other methods.
|
||||||
|
var msg json.RawMessage
|
||||||
|
if err := h.dec.Decode(&msg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
h.reqCount++
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*handler) OnReceiveHeaders(md metadata.MD) {
|
||||||
|
if *verbose {
|
||||||
|
fmt.Printf("\nResponse headers received:\n%s\n", grpcurl.MetadataToString(md))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) OnReceiveResponse(rsp json.RawMessage) {
|
||||||
|
h.respCount++
|
||||||
|
if *verbose {
|
||||||
|
fmt.Print("\nResponse contents:\n")
|
||||||
|
}
|
||||||
|
fmt.Println(string(rsp))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) OnReceiveTrailers(stat *status.Status, md metadata.MD) {
|
||||||
|
h.stat = stat
|
||||||
|
if *verbose {
|
||||||
|
fmt.Printf("\nResponse trailers received:\n%s\n", grpcurl.MetadataToString(md))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,168 @@
|
||||||
|
// Command testserver spins up a test GRPC server.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/grpclog"
|
||||||
|
"google.golang.org/grpc/interop/grpc_testing"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
"google.golang.org/grpc/reflection"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
|
||||||
|
"github.com/fullstorydev/grpcurl"
|
||||||
|
grpcurl_testing "github.com/fullstorydev/grpcurl/testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
help = flag.Bool("help", false, "Print usage instructions and exit.")
|
||||||
|
cacert = flag.String("cacert", "",
|
||||||
|
"File containing trusted root certificates for verifying client certs. Ignored if\n"+
|
||||||
|
" TLS is not in use (e.g. no -cert or -key specified).")
|
||||||
|
cert = flag.String("cert", "",
|
||||||
|
"File containing server certificate (public key). Must also provide -key option.\n"+
|
||||||
|
" Server uses plain-text if no -cert and -key options are given.")
|
||||||
|
key = flag.String("key", "",
|
||||||
|
"File containing server private key. Must also provide -cert option. Server uses\n"+
|
||||||
|
" plain-text if no -cert and -key options are given.")
|
||||||
|
requirecert = flag.Bool("requirecert", false,
|
||||||
|
"Require clients to authenticate via client certs. Must be using TLS (e.g. must also\n"+
|
||||||
|
" provide -cert and -key options).")
|
||||||
|
port = flag.Int("p", 0, "Port on which to listen. Ephemeral port used if not specified.")
|
||||||
|
noreflect = flag.Bool("noreflect", false, "Indicates that server should not support server reflection.")
|
||||||
|
quiet = flag.Bool("q", false, "Suppresses server request and stream logging.")
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *help {
|
||||||
|
flag.PrintDefaults()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
grpclog.SetLoggerV2(grpclog.NewLoggerV2(os.Stdout, os.Stdout, os.Stderr))
|
||||||
|
|
||||||
|
if len(flag.Args()) > 0 {
|
||||||
|
fmt.Fprintln(os.Stderr, "No arguments expected.")
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
if (*cert == "") != (*key == "") {
|
||||||
|
fmt.Fprintln(os.Stderr, "The -cert and -key arguments must be used together and both be present.")
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
if *requirecert && *cert == "" {
|
||||||
|
fmt.Fprintln(os.Stderr, "The -requirecert arg cannot be used without -cert and -key arguments.")
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
var opts []grpc.ServerOption
|
||||||
|
if *cert != "" {
|
||||||
|
creds, err := grpcurl.ServerTransportCredentials(*cacert, *cert, *key, *requirecert)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to configure transport credentials: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
opts = []grpc.ServerOption{grpc.Creds(creds)}
|
||||||
|
}
|
||||||
|
if !*quiet {
|
||||||
|
opts = append(opts, grpc.UnaryInterceptor(unaryLogger), grpc.StreamInterceptor(streamLogger))
|
||||||
|
}
|
||||||
|
|
||||||
|
l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", *port))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to listen on socket: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
p := l.Addr().(*net.TCPAddr).Port
|
||||||
|
fmt.Printf("Listening on 127.0.0.1:%d\n", p)
|
||||||
|
|
||||||
|
svr := grpc.NewServer(opts...)
|
||||||
|
|
||||||
|
grpc_testing.RegisterTestServiceServer(svr, grpcurl_testing.TestServer{})
|
||||||
|
if !*noreflect {
|
||||||
|
reflection.Register(svr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := svr.Serve(l); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "GRPC server returned error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var id int32
|
||||||
|
|
||||||
|
func unaryLogger(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||||
|
i := atomic.AddInt32(&id, 1) - 1
|
||||||
|
grpclog.Printf("start <%d>: %s\n", i, info.FullMethod)
|
||||||
|
start := time.Now()
|
||||||
|
rsp, err := handler(ctx, req)
|
||||||
|
var code codes.Code
|
||||||
|
if stat, ok := status.FromError(err); ok {
|
||||||
|
code = stat.Code()
|
||||||
|
} else {
|
||||||
|
code = codes.Unknown
|
||||||
|
}
|
||||||
|
grpclog.Printf("completed <%d>: %v (%d) %v\n", i, code, code, time.Now().Sub(start))
|
||||||
|
return rsp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func streamLogger(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||||
|
i := atomic.AddInt32(&id, 1) - 1
|
||||||
|
start := time.Now()
|
||||||
|
grpclog.Printf("start <%d>: %s\n", i, info.FullMethod)
|
||||||
|
err := handler(srv, loggingStream{ss: ss, id: i})
|
||||||
|
var code codes.Code
|
||||||
|
if stat, ok := status.FromError(err); ok {
|
||||||
|
code = stat.Code()
|
||||||
|
} else {
|
||||||
|
code = codes.Unknown
|
||||||
|
}
|
||||||
|
grpclog.Printf("completed <%d>: %v(%d) %v\n", i, code, code, time.Now().Sub(start))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type loggingStream struct {
|
||||||
|
ss grpc.ServerStream
|
||||||
|
id int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l loggingStream) SetHeader(md metadata.MD) error {
|
||||||
|
return l.ss.SetHeader(md)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l loggingStream) SendHeader(md metadata.MD) error {
|
||||||
|
return l.ss.SendHeader(md)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l loggingStream) SetTrailer(md metadata.MD) {
|
||||||
|
l.ss.SetTrailer(md)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l loggingStream) Context() context.Context {
|
||||||
|
return l.ss.Context()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l loggingStream) SendMsg(m interface{}) error {
|
||||||
|
err := l.ss.SendMsg(m)
|
||||||
|
if err == nil {
|
||||||
|
grpclog.Printf("stream <%d>: sent message\n", l.id)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l loggingStream) RecvMsg(m interface{}) error {
|
||||||
|
err := l.ss.RecvMsg(m)
|
||||||
|
if err == nil {
|
||||||
|
grpclog.Printf("stream <%d>: received message\n", l.id)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,846 @@
|
||||||
|
// Package grpcurl provides the core functionality exposed by the grpcurl command, for
|
||||||
|
// dynamically connecting to a server, using the reflection service to inspect the server,
|
||||||
|
// and invoking RPCs. The grpcurl command-line tool constructs a DescriptorSource, based
|
||||||
|
// on the command-line parameters, and supplies an InvocationEventHandler to supply request
|
||||||
|
// data (which can come from command-line args or the process's stdin) and to log the
|
||||||
|
// events (to the process's stdout).
|
||||||
|
package grpcurl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/jsonpb"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
"github.com/golang/protobuf/protoc-gen-go/descriptor"
|
||||||
|
"github.com/jhump/protoreflect/desc"
|
||||||
|
"github.com/jhump/protoreflect/dynamic"
|
||||||
|
"github.com/jhump/protoreflect/dynamic/grpcdynamic"
|
||||||
|
"github.com/jhump/protoreflect/grpcreflect"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/credentials"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrReflectionNotSupported = errors.New("server does not support the reflection API")
|
||||||
|
|
||||||
|
// DescriptorSource is a source of protobuf descriptor information. It can be backed by a FileDescriptorSet
|
||||||
|
// proto (like a file generated by protoc) or a remote server that supports the reflection API.
|
||||||
|
type DescriptorSource interface {
|
||||||
|
// ListServices returns a list of fully-qualified service names. It will be all services in a set of
|
||||||
|
// descriptor files or the set of all services exposed by a GRPC server.
|
||||||
|
ListServices() ([]string, error)
|
||||||
|
// FindSymbol returns a descriptor for the given fully-qualified symbol name.
|
||||||
|
FindSymbol(fullyQualifiedName string) (desc.Descriptor, error)
|
||||||
|
// AllExtensionsForType returns all known extension fields that extend the given message type name.
|
||||||
|
AllExtensionsForType(typeName string) ([]*desc.FieldDescriptor, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DescriptorSourceFromProtoSets creates a DescriptorSource that is backed by the named files, whose contents
|
||||||
|
// are encoded FileDescriptorSet protos.
|
||||||
|
func DescriptorSourceFromProtoSets(fileNames ...string) (DescriptorSource, error) {
|
||||||
|
files := &descriptor.FileDescriptorSet{}
|
||||||
|
for _, fileName := range fileNames {
|
||||||
|
b, err := ioutil.ReadFile(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not load protoset file %q: %v", fileName, err)
|
||||||
|
}
|
||||||
|
var fs descriptor.FileDescriptorSet
|
||||||
|
err = proto.Unmarshal(b, &fs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse contents of protoset file %q: %v", fileName, err)
|
||||||
|
}
|
||||||
|
files.File = append(files.File, fs.File...)
|
||||||
|
}
|
||||||
|
|
||||||
|
unresolved := map[string]*descriptor.FileDescriptorProto{}
|
||||||
|
for _, fd := range files.File {
|
||||||
|
unresolved[fd.GetName()] = fd
|
||||||
|
}
|
||||||
|
resolved := map[string]*desc.FileDescriptor{}
|
||||||
|
for _, fd := range files.File {
|
||||||
|
_, err := resolveFileDescriptor(unresolved, resolved, fd.GetName())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &fileSource{files: resolved}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveFileDescriptor(unresolved map[string]*descriptor.FileDescriptorProto, resolved map[string]*desc.FileDescriptor, filename string) (*desc.FileDescriptor, error) {
|
||||||
|
if r, ok := resolved[filename]; ok {
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
fd, ok := unresolved[filename]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("no descriptor found for %q", filename)
|
||||||
|
}
|
||||||
|
deps := make([]*desc.FileDescriptor, len(fd.GetDependency()))
|
||||||
|
for _, dep := range fd.GetDependency() {
|
||||||
|
depFd, err := resolveFileDescriptor(unresolved, resolved, dep)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
deps = append(deps, depFd)
|
||||||
|
}
|
||||||
|
result, err := desc.CreateFileDescriptor(fd, deps...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resolved[filename] = result
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type fileSource struct {
|
||||||
|
files map[string]*desc.FileDescriptor
|
||||||
|
er *dynamic.ExtensionRegistry
|
||||||
|
erInit sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *fileSource) ListServices() ([]string, error) {
|
||||||
|
set := map[string]bool{}
|
||||||
|
for _, fd := range fs.files {
|
||||||
|
for _, svc := range fd.GetServices() {
|
||||||
|
set[svc.GetFullyQualifiedName()] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sl := make([]string, 0, len(set))
|
||||||
|
for svc := range set {
|
||||||
|
sl = append(sl, svc)
|
||||||
|
}
|
||||||
|
return sl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *fileSource) FindSymbol(fullyQualifiedName string) (desc.Descriptor, error) {
|
||||||
|
for _, fd := range fs.files {
|
||||||
|
if dsc := fd.FindSymbol(fullyQualifiedName); dsc != nil {
|
||||||
|
return dsc, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, notFound("Symbol", fullyQualifiedName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *fileSource) AllExtensionsForType(typeName string) ([]*desc.FieldDescriptor, error) {
|
||||||
|
fs.erInit.Do(func() {
|
||||||
|
fs.er = &dynamic.ExtensionRegistry{}
|
||||||
|
for _, fd := range fs.files {
|
||||||
|
fs.er.AddExtensionsFromFile(fd)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return fs.er.AllExtensionsForType(typeName), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DescriptorSourceFromServer creates a DescriptorSource that uses the given GRPC reflection client
|
||||||
|
// to interrogate a server for descriptor information. If the server does not support the reflection
|
||||||
|
// API then the various DescriptorSource methods will return ErrReflectionNotSupported
|
||||||
|
func DescriptorSourceFromServer(ctx context.Context, refClient *grpcreflect.Client) DescriptorSource {
|
||||||
|
return serverSource{client: refClient}
|
||||||
|
}
|
||||||
|
|
||||||
|
type serverSource struct {
|
||||||
|
client *grpcreflect.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss serverSource) ListServices() ([]string, error) {
|
||||||
|
svcs, err := ss.client.ListServices()
|
||||||
|
return svcs, reflectionSupport(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss serverSource) FindSymbol(fullyQualifiedName string) (desc.Descriptor, error) {
|
||||||
|
file, err := ss.client.FileContainingSymbol(fullyQualifiedName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, reflectionSupport(err)
|
||||||
|
}
|
||||||
|
d := file.FindSymbol(fullyQualifiedName)
|
||||||
|
if d == nil {
|
||||||
|
return nil, notFound("Symbol", fullyQualifiedName)
|
||||||
|
}
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss serverSource) AllExtensionsForType(typeName string) ([]*desc.FieldDescriptor, error) {
|
||||||
|
var exts []*desc.FieldDescriptor
|
||||||
|
nums, err := ss.client.AllExtensionNumbersForType(typeName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, reflectionSupport(err)
|
||||||
|
}
|
||||||
|
for _, fieldNum := range nums {
|
||||||
|
ext, err := ss.client.ResolveExtension(typeName, fieldNum)
|
||||||
|
if err != nil {
|
||||||
|
return nil, reflectionSupport(err)
|
||||||
|
}
|
||||||
|
exts = append(exts, ext)
|
||||||
|
}
|
||||||
|
return exts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func reflectionSupport(err error) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if stat, ok := status.FromError(err); ok && stat.Code() == codes.Unimplemented {
|
||||||
|
return ErrReflectionNotSupported
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListServices uses the given descriptor source to return a sorted list of fully-qualified
|
||||||
|
// service names.
|
||||||
|
func ListServices(source DescriptorSource) ([]string, error) {
|
||||||
|
svcs, err := source.ListServices()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Strings(svcs)
|
||||||
|
return svcs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListMethods uses the given descriptor source to return a sorted list of method names
|
||||||
|
// for the specified fully-qualified service name.
|
||||||
|
func ListMethods(source DescriptorSource, serviceName string) ([]string, error) {
|
||||||
|
dsc, err := source.FindSymbol(serviceName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if sd, ok := dsc.(*desc.ServiceDescriptor); !ok {
|
||||||
|
return nil, notFound("Service", serviceName)
|
||||||
|
} else {
|
||||||
|
methods := make([]string, 0, len(sd.GetMethods()))
|
||||||
|
for _, method := range sd.GetMethods() {
|
||||||
|
methods = append(methods, method.GetName())
|
||||||
|
}
|
||||||
|
sort.Strings(methods)
|
||||||
|
return methods, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type notFoundError string
|
||||||
|
|
||||||
|
func notFound(kind, name string) error {
|
||||||
|
return notFoundError(fmt.Sprintf("%s not found: %s", kind, name))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e notFoundError) Error() string {
|
||||||
|
return string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNotFoundError(err error) bool {
|
||||||
|
if grpcreflect.IsElementNotFoundError(err) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
_, ok := err.(notFoundError)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvocationEventHandler is a bag of callbacks for handling events that occur in the course
|
||||||
|
// of invoking an RPC. The handler also provides request data that is sent. The callbacks are
|
||||||
|
// generally called in the order they are listed below.
|
||||||
|
type InvocationEventHandler interface {
|
||||||
|
// OnResolveMethod is called with a descriptor of the method that is being invoked.
|
||||||
|
OnResolveMethod(*desc.MethodDescriptor)
|
||||||
|
// OnSendHeaders is called with the request metadata that is being sent.
|
||||||
|
OnSendHeaders(metadata.MD)
|
||||||
|
// OnReceiveHeaders is called when response headers have been received.
|
||||||
|
OnReceiveHeaders(metadata.MD)
|
||||||
|
// OnReceiveResponse is called for each response message received.
|
||||||
|
OnReceiveResponse(json.RawMessage)
|
||||||
|
// OnReceiveTrailers is called when response trailers and final RPC status have been received.
|
||||||
|
OnReceiveTrailers(*status.Status, metadata.MD)
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequestMessageSupplier func() (json.RawMessage, error)
|
||||||
|
|
||||||
|
// InvokeRpc uses te given GRPC connection to invoke the given method. The given descriptor source
|
||||||
|
// is used to determine the type of method and the type of request and response message. The given
|
||||||
|
// headers are sent as request metadata. Methods on the given event handler are called as the
|
||||||
|
// invocation proceeds.
|
||||||
|
//
|
||||||
|
// The given requestData function supplies the actual data to send. It should return io.EOF when
|
||||||
|
// there is no more request data. If it returns a nil error then the returned JSON message should
|
||||||
|
// not be blank. If the method being invoked is a unary or server-streaming RPC (e.g. exactly one
|
||||||
|
// request message) and there is no request data (e.g. the first invocation of the function returns
|
||||||
|
// io.EOF), then a blank request message is sent, as if the request data were an empty object: "{}".
|
||||||
|
//
|
||||||
|
// If the requestData function and the given event handler coordinate or share any state, they should
|
||||||
|
// be thread-safe. This is because the requestData function may be called from a different goroutine
|
||||||
|
// than the one invoking event callbacks. (This only happens for bi-directional streaming RPCs, where
|
||||||
|
// one goroutine sends request messages and another consumes the response messages).
|
||||||
|
func InvokeRpc(ctx context.Context, source DescriptorSource, cc *grpc.ClientConn, methodName string,
|
||||||
|
headers []string, handler InvocationEventHandler, requestData RequestMessageSupplier) error {
|
||||||
|
|
||||||
|
md := MetadataFromHeaders(headers)
|
||||||
|
|
||||||
|
svc, mth := parseSymbol(methodName)
|
||||||
|
if svc == "" || mth == "" {
|
||||||
|
return fmt.Errorf("given method name %q is not in expected format: 'service/method' or 'service.method'", methodName)
|
||||||
|
}
|
||||||
|
dsc, err := source.FindSymbol(svc)
|
||||||
|
if err != nil {
|
||||||
|
if isNotFoundError(err) {
|
||||||
|
return fmt.Errorf("target server does not expose service %q", svc)
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("failed to query for service descriptor %q: %v", svc, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sd, ok := dsc.(*desc.ServiceDescriptor)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("target server does not expose service %q", svc)
|
||||||
|
}
|
||||||
|
mtd := sd.FindMethodByName(mth)
|
||||||
|
if mtd == nil {
|
||||||
|
return fmt.Errorf("service %q does not include a method named %q", svc, mth)
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.OnResolveMethod(mtd)
|
||||||
|
|
||||||
|
// we also download any applicable extensions so we can provide full support for parsing user-provided data
|
||||||
|
var ext dynamic.ExtensionRegistry
|
||||||
|
alreadyFetched := map[string]bool{}
|
||||||
|
if err = fetchAllExtensions(source, &ext, mtd.GetInputType(), alreadyFetched); err != nil {
|
||||||
|
return fmt.Errorf("error resolving server extensions for message %s: %v", mtd.GetInputType().GetFullyQualifiedName(), err)
|
||||||
|
}
|
||||||
|
if err = fetchAllExtensions(source, &ext, mtd.GetOutputType(), alreadyFetched); err != nil {
|
||||||
|
return fmt.Errorf("error resolving server extensions for message %s: %v", mtd.GetOutputType().GetFullyQualifiedName(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msgFactory := dynamic.NewMessageFactoryWithExtensionRegistry(&ext)
|
||||||
|
req := msgFactory.NewMessage(mtd.GetInputType())
|
||||||
|
|
||||||
|
handler.OnSendHeaders(md)
|
||||||
|
ctx = metadata.NewOutgoingContext(ctx, md)
|
||||||
|
|
||||||
|
stub := grpcdynamic.NewStubWithMessageFactory(cc, msgFactory)
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if mtd.IsClientStreaming() && mtd.IsServerStreaming() {
|
||||||
|
return invokeBidi(ctx, cancel, stub, mtd, handler, requestData, req)
|
||||||
|
} else if mtd.IsClientStreaming() {
|
||||||
|
return invokeClientStream(ctx, stub, mtd, handler, requestData, req)
|
||||||
|
} else if mtd.IsServerStreaming() {
|
||||||
|
return invokeServerStream(ctx, stub, mtd, handler, requestData, req)
|
||||||
|
} else {
|
||||||
|
return invokeUnary(ctx, stub, mtd, handler, requestData, req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func invokeUnary(ctx context.Context, stub grpcdynamic.Stub, md *desc.MethodDescriptor, handler InvocationEventHandler,
|
||||||
|
requestData RequestMessageSupplier, req proto.Message) error {
|
||||||
|
|
||||||
|
data, err := requestData()
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return fmt.Errorf("error getting request data: %v", err)
|
||||||
|
}
|
||||||
|
if len(data) != 0 {
|
||||||
|
err = jsonpb.UnmarshalString(string(data), req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not parse given request body as message of type %q: %v", md.GetInputType().GetFullyQualifiedName(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != io.EOF {
|
||||||
|
// verify there is no second message, which is a usage error
|
||||||
|
_, err := requestData()
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("method %q is a unary RPC, but request data contained more than 1 message", md.GetFullyQualifiedName())
|
||||||
|
} else if err != io.EOF {
|
||||||
|
return fmt.Errorf("error getting request data: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we can actually invoke the RPC!
|
||||||
|
var respHeaders metadata.MD
|
||||||
|
var respTrailers metadata.MD
|
||||||
|
resp, err := stub.InvokeRpc(ctx, md, req, grpc.Trailer(&respTrailers), grpc.Header(&respHeaders))
|
||||||
|
|
||||||
|
stat, ok := status.FromError(err)
|
||||||
|
if !ok {
|
||||||
|
// Error codes sent from the server will get printed differently below.
|
||||||
|
// So just bail for other kinds of errors here.
|
||||||
|
return fmt.Errorf("grpc call for %q failed: %v", md.GetFullyQualifiedName(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.OnReceiveHeaders(respHeaders)
|
||||||
|
|
||||||
|
var respStr string
|
||||||
|
if stat.Code() == codes.OK {
|
||||||
|
jsm := jsonpb.Marshaler{Indent: " "}
|
||||||
|
respStr, err = jsm.MarshalToString(resp)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to generate JSON form of response message: %v", err)
|
||||||
|
}
|
||||||
|
handler.OnReceiveResponse(json.RawMessage(respStr))
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.OnReceiveTrailers(stat, respTrailers)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func invokeClientStream(ctx context.Context, stub grpcdynamic.Stub, md *desc.MethodDescriptor, handler InvocationEventHandler,
|
||||||
|
requestData RequestMessageSupplier, req proto.Message) error {
|
||||||
|
|
||||||
|
// invoke the RPC!
|
||||||
|
str, err := stub.InvokeRpcClientStream(ctx, md)
|
||||||
|
|
||||||
|
// Upload each request message in the stream
|
||||||
|
var resp proto.Message
|
||||||
|
for err == nil {
|
||||||
|
var data json.RawMessage
|
||||||
|
data, err = requestData()
|
||||||
|
if err == io.EOF {
|
||||||
|
resp, err = str.CloseAndReceive()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error getting request data: %v", err)
|
||||||
|
}
|
||||||
|
if len(data) != 0 {
|
||||||
|
err = jsonpb.UnmarshalString(string(data), req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not parse given request body as message of type %q: %v", md.GetInputType().GetFullyQualifiedName(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = str.SendMsg(req)
|
||||||
|
if err == io.EOF {
|
||||||
|
// We get EOF on send if the server says "go away"
|
||||||
|
// We have to use CloseAndReceive to get the actual code
|
||||||
|
resp, err = str.CloseAndReceive()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally, process response data
|
||||||
|
stat, ok := status.FromError(err)
|
||||||
|
if !ok {
|
||||||
|
// Error codes sent from the server will get printed differently below.
|
||||||
|
// So just bail for other kinds of errors here.
|
||||||
|
return fmt.Errorf("grpc call for %q failed: %v", md.GetFullyQualifiedName(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if respHeaders, err := str.Header(); err == nil {
|
||||||
|
handler.OnReceiveHeaders(respHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
var respStr string
|
||||||
|
if stat.Code() == codes.OK {
|
||||||
|
jsm := jsonpb.Marshaler{Indent: " "}
|
||||||
|
respStr, err = jsm.MarshalToString(resp)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to generate JSON form of response message: %v", err)
|
||||||
|
}
|
||||||
|
handler.OnReceiveResponse(json.RawMessage(respStr))
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.OnReceiveTrailers(stat, str.Trailer())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func invokeServerStream(ctx context.Context, stub grpcdynamic.Stub, md *desc.MethodDescriptor, handler InvocationEventHandler,
|
||||||
|
requestData RequestMessageSupplier, req proto.Message) error {
|
||||||
|
|
||||||
|
data, err := requestData()
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return fmt.Errorf("error getting request data: %v", err)
|
||||||
|
}
|
||||||
|
if len(data) != 0 {
|
||||||
|
err = jsonpb.UnmarshalString(string(data), req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not parse given request body as message of type %q: %v", md.GetInputType().GetFullyQualifiedName(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != io.EOF {
|
||||||
|
// verify there is no second message, which is a usage error
|
||||||
|
_, err := requestData()
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("method %q is a server-streaming RPC, but request data contained more than 1 message", md.GetFullyQualifiedName())
|
||||||
|
} else if err != io.EOF {
|
||||||
|
return fmt.Errorf("error getting request data: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we can actually invoke the RPC!
|
||||||
|
str, err := stub.InvokeRpcServerStream(ctx, md, req)
|
||||||
|
|
||||||
|
if respHeaders, err := str.Header(); err == nil {
|
||||||
|
handler.OnReceiveHeaders(respHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download each response message
|
||||||
|
for err == nil {
|
||||||
|
var resp proto.Message
|
||||||
|
resp, err = str.RecvMsg()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
jsm := jsonpb.Marshaler{Indent: " "}
|
||||||
|
respStr, err := jsm.MarshalToString(resp)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to generate JSON form of response message: %v", err)
|
||||||
|
}
|
||||||
|
handler.OnReceiveResponse(json.RawMessage(respStr))
|
||||||
|
}
|
||||||
|
|
||||||
|
stat, ok := status.FromError(err)
|
||||||
|
if !ok {
|
||||||
|
// Error codes sent from the server will get printed differently below.
|
||||||
|
// So just bail for other kinds of errors here.
|
||||||
|
return fmt.Errorf("grpc call for %q failed: %v", md.GetFullyQualifiedName(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.OnReceiveTrailers(stat, str.Trailer())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func invokeBidi(ctx context.Context, cancel context.CancelFunc, stub grpcdynamic.Stub, md *desc.MethodDescriptor, handler InvocationEventHandler,
|
||||||
|
requestData RequestMessageSupplier, req proto.Message) error {
|
||||||
|
|
||||||
|
// invoke the RPC!
|
||||||
|
str, err := stub.InvokeRpcBidiStream(ctx, md)
|
||||||
|
|
||||||
|
// mutex protects access to handler and sendErr since we'll have two goroutines sharing them
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var sendErr atomic.Value
|
||||||
|
|
||||||
|
defer wg.Wait()
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
// Concurrently upload each request message in the stream
|
||||||
|
var err error
|
||||||
|
var data json.RawMessage
|
||||||
|
for err == nil {
|
||||||
|
data, err = requestData()
|
||||||
|
|
||||||
|
if err == io.EOF {
|
||||||
|
err = str.CloseSend()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("error getting request data: %v", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if len(data) != 0 {
|
||||||
|
err = jsonpb.UnmarshalString(string(data), req)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("could not parse given request body as message of type %q: %v", md.GetInputType().GetFullyQualifiedName(), err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = str.SendMsg(req)
|
||||||
|
|
||||||
|
req.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
sendErr.Store(err)
|
||||||
|
// signals error to other goroutine
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
if respHeaders, err := str.Header(); err == nil {
|
||||||
|
handler.OnReceiveHeaders(respHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download each response message
|
||||||
|
for err == nil {
|
||||||
|
var resp proto.Message
|
||||||
|
resp, err = str.RecvMsg()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
jsm := jsonpb.Marshaler{Indent: " "}
|
||||||
|
respStr, err := jsm.MarshalToString(resp)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to generate JSON form of response message: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.OnReceiveResponse(json.RawMessage(respStr))
|
||||||
|
}
|
||||||
|
|
||||||
|
if se, ok := sendErr.Load().(error); ok && se != io.EOF {
|
||||||
|
err = se
|
||||||
|
}
|
||||||
|
|
||||||
|
stat, ok := status.FromError(err)
|
||||||
|
if !ok {
|
||||||
|
// Error codes sent from the server will get printed differently below.
|
||||||
|
// So just bail for other kinds of errors here.
|
||||||
|
return fmt.Errorf("grpc call for %q failed: %v", md.GetFullyQualifiedName(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.OnReceiveTrailers(stat, str.Trailer())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MetadataFromHeaders(headers []string) metadata.MD {
|
||||||
|
md := make(metadata.MD)
|
||||||
|
for _, part := range headers {
|
||||||
|
if part != "" {
|
||||||
|
pieces := strings.SplitN(part, ":", 2)
|
||||||
|
if len(pieces) == 1 {
|
||||||
|
pieces = append(pieces, "") // if no value was specified, just make it "" (maybe the header value doesn't matter)
|
||||||
|
}
|
||||||
|
headerName := strings.ToLower(strings.TrimSpace(pieces[0]))
|
||||||
|
md[headerName] = append(md[headerName], strings.TrimSpace(pieces[1]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return md
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSymbol(svcAndMethod string) (string, string) {
|
||||||
|
pos := strings.LastIndex(svcAndMethod, "/")
|
||||||
|
if pos < 0 {
|
||||||
|
pos = strings.LastIndex(svcAndMethod, ".")
|
||||||
|
if pos < 0 {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return svcAndMethod[:pos], svcAndMethod[pos+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func MetadataToString(md metadata.MD) string {
|
||||||
|
if len(md) == 0 {
|
||||||
|
return "(empty)"
|
||||||
|
}
|
||||||
|
var b bytes.Buffer
|
||||||
|
for k, vs := range md {
|
||||||
|
for _, v := range vs {
|
||||||
|
b.WriteString(k)
|
||||||
|
b.WriteString(": ")
|
||||||
|
b.WriteString(v)
|
||||||
|
b.WriteString("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDescriptorText(dsc desc.Descriptor, descSource DescriptorSource) (string, error) {
|
||||||
|
dscProto := EnsureExtensions(descSource, dsc.AsProto())
|
||||||
|
return (&jsonpb.Marshaler{Indent: " "}).MarshalToString(dscProto)
|
||||||
|
}
|
||||||
|
|
||||||
|
func EnsureExtensions(source DescriptorSource, msg proto.Message) proto.Message {
|
||||||
|
// load any server extensions so we can properly describe custom options
|
||||||
|
dsc, err := desc.LoadMessageDescriptorForMessage(msg)
|
||||||
|
if err != nil {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
var ext dynamic.ExtensionRegistry
|
||||||
|
if err = fetchAllExtensions(source, &ext, dsc, map[string]bool{}); err != nil {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert message into dynamic message that knows about applicable extensions
|
||||||
|
// (that way we can show meaningful info for custom options instead of printing as unknown)
|
||||||
|
msgFactory := dynamic.NewMessageFactoryWithExtensionRegistry(&ext)
|
||||||
|
dm, err := fullyConvertToDynamic(msgFactory, msg)
|
||||||
|
if err != nil {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
return dm
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetchAllExtensions recursively fetches from the server extensions for the given message type as well as
|
||||||
|
// for all message types of nested fields. The extensions are added to the given dynamic registry of extensions
|
||||||
|
// so that all server-known extensions can be correctly parsed by grpcurl.
|
||||||
|
func fetchAllExtensions(source DescriptorSource, ext *dynamic.ExtensionRegistry, md *desc.MessageDescriptor, alreadyFetched map[string]bool) error {
|
||||||
|
msgTypeName := md.GetFullyQualifiedName()
|
||||||
|
if alreadyFetched[msgTypeName] {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
alreadyFetched[msgTypeName] = true
|
||||||
|
if len(md.GetExtensionRanges()) > 0 {
|
||||||
|
fds, err := source.AllExtensionsForType(msgTypeName)
|
||||||
|
for _, fd := range fds {
|
||||||
|
if err = ext.AddExtension(fd); err != nil {
|
||||||
|
return fmt.Errorf("could not register extension %d of type %s: %v", fd.GetFullyQualifiedName(), msgTypeName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// recursively fetch extensions for the types of any message fields
|
||||||
|
for _, fd := range md.GetFields() {
|
||||||
|
if fd.GetMessageType() != nil {
|
||||||
|
err := fetchAllExtensions(source, ext, fd.GetMessageType(), alreadyFetched)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fullConvertToDynamic attempts to convert the given message to a dynamic message as well
|
||||||
|
// as any nested messages it may contain as field values. If the given message factory has
|
||||||
|
// extensions registered that were not known when the given message was parsed, this effectively
|
||||||
|
// allows re-parsing to identify those extensions.
|
||||||
|
func fullyConvertToDynamic(msgFact *dynamic.MessageFactory, msg proto.Message) (proto.Message, error) {
|
||||||
|
if _, ok := msg.(*dynamic.Message); ok {
|
||||||
|
return msg, nil // already a dynamic message
|
||||||
|
}
|
||||||
|
md, err := desc.LoadMessageDescriptorForMessage(msg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newMsg := msgFact.NewMessage(md)
|
||||||
|
dm, ok := newMsg.(*dynamic.Message)
|
||||||
|
if !ok {
|
||||||
|
// if message factory didn't produce a dynamic message, then we should leave msg as is
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dm.ConvertFrom(msg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// recursively convert all field values, too
|
||||||
|
for _, fd := range md.GetFields() {
|
||||||
|
if fd.IsMap() {
|
||||||
|
if fd.GetMapValueType().GetMessageType() != nil {
|
||||||
|
m := dm.GetField(fd).(map[interface{}]interface{})
|
||||||
|
for k, v := range m {
|
||||||
|
// keys can't be nested messages; so we only need to recurse through map values, not keys
|
||||||
|
newVal, err := fullyConvertToDynamic(msgFact, v.(proto.Message))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
dm.PutMapField(fd, k, newVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if fd.IsRepeated() {
|
||||||
|
if fd.GetMessageType() != nil {
|
||||||
|
s := dm.GetField(fd).([]interface{})
|
||||||
|
for i, e := range s {
|
||||||
|
newVal, err := fullyConvertToDynamic(msgFact, e.(proto.Message))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
dm.SetRepeatedField(fd, i, newVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if fd.GetMessageType() != nil {
|
||||||
|
v := dm.GetField(fd)
|
||||||
|
newVal, err := fullyConvertToDynamic(msgFact, v.(proto.Message))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
dm.SetField(fd, newVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientTransportCredentials builds transport credentials for a GRPC client using the
|
||||||
|
// given properties. If cacertFile is blank, only standard trusted certs are used to
|
||||||
|
// verify the server certs. If clientCertFile is blank, the client will not use a client
|
||||||
|
// certificate. If clientCertFile is not blank then clientKeyFile must not be blank.
|
||||||
|
func ClientTransportCredentials(insecureSkipVerify bool, cacertFile, clientCertFile, clientKeyFile string) (credentials.TransportCredentials, error) {
|
||||||
|
var tlsConf tls.Config
|
||||||
|
|
||||||
|
if clientCertFile != "" {
|
||||||
|
// Load the client certificates from disk
|
||||||
|
certificate, err := tls.LoadX509KeyPair(clientCertFile, clientKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not load client key pair: %v", err)
|
||||||
|
}
|
||||||
|
tlsConf.Certificates = []tls.Certificate{certificate}
|
||||||
|
}
|
||||||
|
|
||||||
|
if insecureSkipVerify {
|
||||||
|
tlsConf.InsecureSkipVerify = true
|
||||||
|
} else if cacertFile != "" {
|
||||||
|
// Create a certificate pool from the certificate authority
|
||||||
|
certPool := x509.NewCertPool()
|
||||||
|
ca, err := ioutil.ReadFile(cacertFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not read ca certificate: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append the certificates from the CA
|
||||||
|
if ok := certPool.AppendCertsFromPEM(ca); !ok {
|
||||||
|
return nil, errors.New("failed to append ca certs")
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConf.RootCAs = certPool
|
||||||
|
}
|
||||||
|
|
||||||
|
return credentials.NewTLS(&tlsConf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerTransportCredentials builds transport credentials for a GRPC server using the
|
||||||
|
// given properties. If cacertFile is blank, the server will not request client certs
|
||||||
|
// unless requireClientCerts is true. When requireClientCerts is false and cacertFile is
|
||||||
|
// not blank, the server will verify client certs when presented, but will not require
|
||||||
|
// client certs. The serverCertFile and serverKeyFile must both not be blank.
|
||||||
|
func ServerTransportCredentials(cacertFile, serverCertFile, serverKeyFile string, requireClientCerts bool) (credentials.TransportCredentials, error) {
|
||||||
|
var tlsConf tls.Config
|
||||||
|
|
||||||
|
// Load the server certificates from disk
|
||||||
|
certificate, err := tls.LoadX509KeyPair(serverCertFile, serverKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not load key pair: %v", err)
|
||||||
|
}
|
||||||
|
tlsConf.Certificates = []tls.Certificate{certificate}
|
||||||
|
|
||||||
|
if cacertFile != "" {
|
||||||
|
// Create a certificate pool from the certificate authority
|
||||||
|
certPool := x509.NewCertPool()
|
||||||
|
ca, err := ioutil.ReadFile(cacertFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not read ca certificate: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append the certificates from the CA
|
||||||
|
if ok := certPool.AppendCertsFromPEM(ca); !ok {
|
||||||
|
return nil, errors.New("failed to append ca certs")
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConf.ClientCAs = certPool
|
||||||
|
}
|
||||||
|
|
||||||
|
if requireClientCerts {
|
||||||
|
tlsConf.ClientAuth = tls.RequireAndVerifyClientCert
|
||||||
|
} else if cacertFile != "" {
|
||||||
|
tlsConf.ClientAuth = tls.VerifyClientCertIfGiven
|
||||||
|
} else {
|
||||||
|
tlsConf.ClientAuth = tls.NoClientCert
|
||||||
|
}
|
||||||
|
|
||||||
|
return credentials.NewTLS(&tlsConf), nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,666 @@
|
||||||
|
package grpcurl_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/jsonpb"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
"github.com/jhump/protoreflect/desc"
|
||||||
|
"github.com/jhump/protoreflect/grpcreflect"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/interop/grpc_testing"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
"google.golang.org/grpc/reflection"
|
||||||
|
reflectpb "google.golang.org/grpc/reflection/grpc_reflection_v1alpha"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
|
||||||
|
. "github.com/fullstorydev/grpcurl"
|
||||||
|
grpcurl_testing "github.com/fullstorydev/grpcurl/testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
sourceProtoset DescriptorSource
|
||||||
|
ccProtoset *grpc.ClientConn
|
||||||
|
|
||||||
|
sourceReflect DescriptorSource
|
||||||
|
ccReflect *grpc.ClientConn
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
var err error
|
||||||
|
sourceProtoset, err = DescriptorSourceFromProtoSets("testing/test.protoset")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a server that includes the reflection service
|
||||||
|
svrReflect := grpc.NewServer()
|
||||||
|
grpc_testing.RegisterTestServiceServer(svrReflect, grpcurl_testing.TestServer{})
|
||||||
|
reflection.Register(svrReflect)
|
||||||
|
var portReflect int
|
||||||
|
if l, err := net.Listen("tcp", "127.0.0.1:0"); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
portReflect = l.Addr().(*net.TCPAddr).Port
|
||||||
|
go svrReflect.Serve(l)
|
||||||
|
}
|
||||||
|
defer svrReflect.Stop()
|
||||||
|
|
||||||
|
// And a corresponding client
|
||||||
|
if ccReflect, err = grpc.Dial(fmt.Sprintf("127.0.0.1:%d", portReflect),
|
||||||
|
grpc.WithInsecure(), grpc.WithTimeout(10*time.Second), grpc.WithBlock()); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer ccReflect.Close()
|
||||||
|
refClient := grpcreflect.NewClient(context.Background(), reflectpb.NewServerReflectionClient(ccReflect))
|
||||||
|
defer refClient.Reset()
|
||||||
|
|
||||||
|
sourceReflect = DescriptorSourceFromServer(context.Background(), refClient)
|
||||||
|
|
||||||
|
// Also create a server that does *not* include the reflection service
|
||||||
|
svrProtoset := grpc.NewServer()
|
||||||
|
grpc_testing.RegisterTestServiceServer(svrProtoset, grpcurl_testing.TestServer{})
|
||||||
|
var portProtoset int
|
||||||
|
if l, err := net.Listen("tcp", "127.0.0.1:0"); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
portProtoset = l.Addr().(*net.TCPAddr).Port
|
||||||
|
go svrProtoset.Serve(l)
|
||||||
|
}
|
||||||
|
defer svrProtoset.Stop()
|
||||||
|
|
||||||
|
// And a corresponding client
|
||||||
|
if ccProtoset, err = grpc.Dial(fmt.Sprintf("127.0.0.1:%d", portProtoset),
|
||||||
|
grpc.WithInsecure(), grpc.WithTimeout(10*time.Second), grpc.WithBlock()); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer ccProtoset.Close()
|
||||||
|
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerDoesNotSupportReflection(t *testing.T) {
|
||||||
|
refClient := grpcreflect.NewClient(context.Background(), reflectpb.NewServerReflectionClient(ccProtoset))
|
||||||
|
defer refClient.Reset()
|
||||||
|
|
||||||
|
refSource := DescriptorSourceFromServer(context.Background(), refClient)
|
||||||
|
|
||||||
|
_, err := ListServices(refSource)
|
||||||
|
if err != ErrReflectionNotSupported {
|
||||||
|
t.Errorf("ListServices should have returned ErrReflectionNotSupported; instead got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = ListMethods(refSource, "SomeService")
|
||||||
|
if err != ErrReflectionNotSupported {
|
||||||
|
t.Errorf("ListMethods should have returned ErrReflectionNotSupported; instead got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = InvokeRpc(context.Background(), refSource, ccProtoset, "FooService/Method", nil, nil, nil)
|
||||||
|
// InvokeRpc wraps the error, so we just verify the returned error includes the right message
|
||||||
|
if err == nil || !strings.Contains(err.Error(), ErrReflectionNotSupported.Error()) {
|
||||||
|
t.Errorf("InvokeRpc should have returned ErrReflectionNotSupported; instead got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListServicesProtoset(t *testing.T) {
|
||||||
|
doTestListServices(t, sourceProtoset, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListServicesReflect(t *testing.T) {
|
||||||
|
doTestListServices(t, sourceReflect, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTestListServices(t *testing.T, source DescriptorSource, includeReflection bool) {
|
||||||
|
names, err := ListServices(source)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to list services: %v", err)
|
||||||
|
}
|
||||||
|
var expected []string
|
||||||
|
if includeReflection {
|
||||||
|
// when using server reflection, we see the TestService as well as the ServerReflection service
|
||||||
|
expected = []string{"grpc.reflection.v1alpha.ServerReflection", "grpc.testing.TestService"}
|
||||||
|
} else {
|
||||||
|
// without reflection, we see all services defined in the same test.proto file, which is the
|
||||||
|
// TestService as well as UnimplementedService
|
||||||
|
expected = []string{"grpc.testing.TestService", "grpc.testing.UnimplementedService"}
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(expected, names) {
|
||||||
|
t.Errorf("ListServices returned wrong results: wanted %v, got %v", expected, names)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListMethodsProtoset(t *testing.T) {
|
||||||
|
doTestListMethods(t, sourceProtoset, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListMethodsReflect(t *testing.T) {
|
||||||
|
doTestListMethods(t, sourceReflect, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTestListMethods(t *testing.T, source DescriptorSource, includeReflection bool) {
|
||||||
|
names, err := ListMethods(source, "grpc.testing.TestService")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to list methods for TestService: %v", err)
|
||||||
|
}
|
||||||
|
expected := []string{
|
||||||
|
"EmptyCall",
|
||||||
|
"FullDuplexCall",
|
||||||
|
"HalfDuplexCall",
|
||||||
|
"StreamingInputCall",
|
||||||
|
"StreamingOutputCall",
|
||||||
|
"UnaryCall",
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(expected, names) {
|
||||||
|
t.Errorf("ListMethods returned wrong results: wanted %v, got %v", expected, names)
|
||||||
|
}
|
||||||
|
|
||||||
|
if includeReflection {
|
||||||
|
// when using server reflection, we see the TestService as well as the ServerReflection service
|
||||||
|
names, err = ListMethods(source, "grpc.reflection.v1alpha.ServerReflection")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to list methods for ServerReflection: %v", err)
|
||||||
|
}
|
||||||
|
expected = []string{"ServerReflectionInfo"}
|
||||||
|
} else {
|
||||||
|
// without reflection, we see all services defined in the same test.proto file, which is the
|
||||||
|
// TestService as well as UnimplementedService
|
||||||
|
names, err = ListMethods(source, "grpc.testing.UnimplementedService")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to list methods for ServerReflection: %v", err)
|
||||||
|
}
|
||||||
|
expected = []string{"UnimplementedCall"}
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(expected, names) {
|
||||||
|
t.Errorf("ListMethods returned wrong results: wanted %v, got %v", expected, names)
|
||||||
|
}
|
||||||
|
|
||||||
|
// force an error
|
||||||
|
_, err = ListMethods(source, "FooService")
|
||||||
|
if err != nil && !strings.Contains(err.Error(), "Symbol not found: FooService") {
|
||||||
|
t.Errorf("ListMethods should have returned 'not found' error but instead returned %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDescribeProtoset(t *testing.T) {
|
||||||
|
doTestDescribe(t, sourceProtoset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDescribeReflect(t *testing.T) {
|
||||||
|
doTestDescribe(t, sourceReflect)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTestDescribe(t *testing.T, source DescriptorSource) {
|
||||||
|
sym := "grpc.testing.TestService.EmptyCall"
|
||||||
|
dsc, err := source.FindSymbol(sym)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get descriptor for %q: %v", sym, err)
|
||||||
|
}
|
||||||
|
if _, ok := dsc.(*desc.MethodDescriptor); !ok {
|
||||||
|
t.Fatalf("descriptor for %q was a %T (expecting a MethodDescriptor)", sym, dsc)
|
||||||
|
}
|
||||||
|
txt := proto.MarshalTextString(dsc.AsProto())
|
||||||
|
expected :=
|
||||||
|
`name: "EmptyCall"
|
||||||
|
input_type: ".grpc.testing.Empty"
|
||||||
|
output_type: ".grpc.testing.Empty"
|
||||||
|
`
|
||||||
|
if expected != txt {
|
||||||
|
t.Errorf("descriptor mismatch: expected %s, got %s", expected, txt)
|
||||||
|
}
|
||||||
|
|
||||||
|
sym = "grpc.testing.StreamingOutputCallResponse"
|
||||||
|
dsc, err = source.FindSymbol(sym)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get descriptor for %q: %v", sym, err)
|
||||||
|
}
|
||||||
|
if _, ok := dsc.(*desc.MessageDescriptor); !ok {
|
||||||
|
t.Fatalf("descriptor for %q was a %T (expecting a MessageDescriptor)", sym, dsc)
|
||||||
|
}
|
||||||
|
txt = proto.MarshalTextString(dsc.AsProto())
|
||||||
|
expected =
|
||||||
|
`name: "StreamingOutputCallResponse"
|
||||||
|
field: <
|
||||||
|
name: "payload"
|
||||||
|
number: 1
|
||||||
|
label: LABEL_OPTIONAL
|
||||||
|
type: TYPE_MESSAGE
|
||||||
|
type_name: ".grpc.testing.Payload"
|
||||||
|
json_name: "payload"
|
||||||
|
>
|
||||||
|
`
|
||||||
|
if expected != txt {
|
||||||
|
t.Errorf("descriptor mismatch: expected %s, got %s", expected, txt)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = source.FindSymbol("FooService")
|
||||||
|
if err != nil && !strings.Contains(err.Error(), "Symbol not found: FooService") {
|
||||||
|
t.Errorf("FindSymbol should have returned 'not found' error but instead returned %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// type == COMPRESSABLE, but that is default (since it has
|
||||||
|
// numeric value == 0) and thus doesn't actually get included
|
||||||
|
// on the wire
|
||||||
|
payload1 = `{
|
||||||
|
"payload": {
|
||||||
|
"body": "SXQncyBCdXNpbmVzcyBUaW1l"
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
payload2 = `{
|
||||||
|
"payload": {
|
||||||
|
"type": "RANDOM",
|
||||||
|
"body": "Rm91eCBkdSBGYUZh"
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
payload3 = `{
|
||||||
|
"payload": {
|
||||||
|
"type": "UNCOMPRESSABLE",
|
||||||
|
"body": "SGlwaG9wb3BvdGFtdXMgdnMuIFJoeW1lbm9jZXJvcw=="
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnaryProtoset(t *testing.T) {
|
||||||
|
doTestUnary(t, ccProtoset, sourceProtoset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnaryReflect(t *testing.T) {
|
||||||
|
doTestUnary(t, ccReflect, sourceReflect)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTestUnary(t *testing.T, cc *grpc.ClientConn, source DescriptorSource) {
|
||||||
|
// Success
|
||||||
|
h := &handler{reqMessages: []string{payload1}}
|
||||||
|
err := InvokeRpc(context.Background(), source, cc, "grpc.testing.TestService/UnaryCall", makeHeaders(codes.OK), h, h.getRequestData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error during RPC: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.check(t, "grpc.testing.TestService.UnaryCall", codes.OK, 1, 1) {
|
||||||
|
if h.respMessages[0] != payload1 {
|
||||||
|
t.Errorf("unexpected response from RPC: expecting %s; got %s", payload1, h.respMessages[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Failure
|
||||||
|
h = &handler{reqMessages: []string{payload1}}
|
||||||
|
err = InvokeRpc(context.Background(), source, cc, "grpc.testing.TestService/UnaryCall", makeHeaders(codes.NotFound), h, h.getRequestData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error during RPC: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.check(t, "grpc.testing.TestService.UnaryCall", codes.NotFound, 1, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientStreamProtoset(t *testing.T) {
|
||||||
|
doTestClientStream(t, ccProtoset, sourceProtoset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientStreamReflect(t *testing.T) {
|
||||||
|
doTestClientStream(t, ccReflect, sourceReflect)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTestClientStream(t *testing.T, cc *grpc.ClientConn, source DescriptorSource) {
|
||||||
|
// Success
|
||||||
|
h := &handler{reqMessages: []string{payload1, payload2, payload3}}
|
||||||
|
err := InvokeRpc(context.Background(), source, cc, "grpc.testing.TestService/StreamingInputCall", makeHeaders(codes.OK), h, h.getRequestData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error during RPC: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.check(t, "grpc.testing.TestService.StreamingInputCall", codes.OK, 3, 1) {
|
||||||
|
expected :=
|
||||||
|
`{
|
||||||
|
"aggregatedPayloadSize": 61
|
||||||
|
}`
|
||||||
|
if h.respMessages[0] != expected {
|
||||||
|
t.Errorf("unexpected response from RPC: expecting %s; got %s", expected, h.respMessages[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail fast (server rejects as soon as possible)
|
||||||
|
h = &handler{reqMessages: []string{payload1, payload2, payload3}}
|
||||||
|
err = InvokeRpc(context.Background(), source, cc, "grpc.testing.TestService/StreamingInputCall", makeHeaders(codes.InvalidArgument), h, h.getRequestData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error during RPC: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.check(t, "grpc.testing.TestService.StreamingInputCall", codes.InvalidArgument, -3, 0)
|
||||||
|
|
||||||
|
// Fail late (server waits until stream is complete to reject)
|
||||||
|
h = &handler{reqMessages: []string{payload1, payload2, payload3}}
|
||||||
|
err = InvokeRpc(context.Background(), source, cc, "grpc.testing.TestService/StreamingInputCall", makeHeaders(codes.Internal, true), h, h.getRequestData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error during RPC: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.check(t, "grpc.testing.TestService.StreamingInputCall", codes.Internal, 3, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerStreamProtoset(t *testing.T) {
|
||||||
|
doTestServerStream(t, ccProtoset, sourceProtoset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerStreamReflect(t *testing.T) {
|
||||||
|
doTestServerStream(t, ccReflect, sourceReflect)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTestServerStream(t *testing.T, cc *grpc.ClientConn, source DescriptorSource) {
|
||||||
|
req := &grpc_testing.StreamingOutputCallRequest{
|
||||||
|
ResponseType: grpc_testing.PayloadType_COMPRESSABLE,
|
||||||
|
ResponseParameters: []*grpc_testing.ResponseParameters{
|
||||||
|
{Size: 10}, {Size: 20}, {Size: 30}, {Size: 40}, {Size: 50},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
payload, err := (&jsonpb.Marshaler{}).MarshalToString(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to construct request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success
|
||||||
|
h := &handler{reqMessages: []string{payload}}
|
||||||
|
err = InvokeRpc(context.Background(), source, cc, "grpc.testing.TestService/StreamingOutputCall", makeHeaders(codes.OK), h, h.getRequestData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error during RPC: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.check(t, "grpc.testing.TestService.StreamingOutputCall", codes.OK, 1, 5) {
|
||||||
|
resp := &grpc_testing.StreamingOutputCallResponse{}
|
||||||
|
for i, msg := range h.respMessages {
|
||||||
|
if err := jsonpb.UnmarshalString(msg, resp); err != nil {
|
||||||
|
t.Errorf("failed to parse response %d: %v", i+1, err)
|
||||||
|
}
|
||||||
|
if resp.Payload.GetType() != grpc_testing.PayloadType_COMPRESSABLE {
|
||||||
|
t.Errorf("response %d has wrong payload type; expecting %v, got %v", i, grpc_testing.PayloadType_COMPRESSABLE, resp.Payload.Type)
|
||||||
|
}
|
||||||
|
if len(resp.Payload.Body) != (i+1)*10 {
|
||||||
|
t.Errorf("response %d has wrong payload size; expecting %d, got %d", i, (i+1)*10, len(resp.Payload.Body))
|
||||||
|
}
|
||||||
|
resp.Reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail fast (server rejects as soon as possible)
|
||||||
|
h = &handler{reqMessages: []string{payload}}
|
||||||
|
err = InvokeRpc(context.Background(), source, cc, "grpc.testing.TestService/StreamingOutputCall", makeHeaders(codes.Aborted), h, h.getRequestData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error during RPC: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.check(t, "grpc.testing.TestService.StreamingOutputCall", codes.Aborted, 1, 0)
|
||||||
|
|
||||||
|
// Fail late (server waits until stream is complete to reject)
|
||||||
|
h = &handler{reqMessages: []string{payload}}
|
||||||
|
err = InvokeRpc(context.Background(), source, cc, "grpc.testing.TestService/StreamingOutputCall", makeHeaders(codes.AlreadyExists, true), h, h.getRequestData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error during RPC: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.check(t, "grpc.testing.TestService.StreamingOutputCall", codes.AlreadyExists, 1, 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHalfDuplexStreamProtoset(t *testing.T) {
|
||||||
|
doTestHalfDuplexStream(t, ccProtoset, sourceProtoset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHalfDuplexStreamReflect(t *testing.T) {
|
||||||
|
doTestHalfDuplexStream(t, ccReflect, sourceReflect)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTestHalfDuplexStream(t *testing.T, cc *grpc.ClientConn, source DescriptorSource) {
|
||||||
|
reqs := []string{payload1, payload2, payload3}
|
||||||
|
|
||||||
|
// Success
|
||||||
|
h := &handler{reqMessages: reqs}
|
||||||
|
err := InvokeRpc(context.Background(), source, cc, "grpc.testing.TestService/HalfDuplexCall", makeHeaders(codes.OK), h, h.getRequestData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error during RPC: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.check(t, "grpc.testing.TestService.HalfDuplexCall", codes.OK, 3, 3) {
|
||||||
|
for i, resp := range h.respMessages {
|
||||||
|
if resp != reqs[i] {
|
||||||
|
t.Errorf("unexpected response %d from RPC:\nexpecting %q\ngot %q", i, reqs[i], resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail fast (server rejects as soon as possible)
|
||||||
|
h = &handler{reqMessages: reqs}
|
||||||
|
err = InvokeRpc(context.Background(), source, cc, "grpc.testing.TestService/HalfDuplexCall", makeHeaders(codes.Canceled), h, h.getRequestData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error during RPC: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.check(t, "grpc.testing.TestService.HalfDuplexCall", codes.Canceled, -3, 0)
|
||||||
|
|
||||||
|
// Fail late (server waits until stream is complete to reject)
|
||||||
|
h = &handler{reqMessages: reqs}
|
||||||
|
err = InvokeRpc(context.Background(), source, cc, "grpc.testing.TestService/HalfDuplexCall", makeHeaders(codes.DataLoss, true), h, h.getRequestData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error during RPC: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.check(t, "grpc.testing.TestService.HalfDuplexCall", codes.DataLoss, 3, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFullDuplexStreamProtoset(t *testing.T) {
|
||||||
|
doTestFullDuplexStream(t, ccProtoset, sourceProtoset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFullDuplexStreamReflect(t *testing.T) {
|
||||||
|
doTestFullDuplexStream(t, ccReflect, sourceReflect)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTestFullDuplexStream(t *testing.T, cc *grpc.ClientConn, source DescriptorSource) {
|
||||||
|
reqs := make([]string, 3)
|
||||||
|
req := &grpc_testing.StreamingOutputCallRequest{
|
||||||
|
ResponseType: grpc_testing.PayloadType_RANDOM,
|
||||||
|
}
|
||||||
|
for i := range reqs {
|
||||||
|
req.ResponseParameters = append(req.ResponseParameters, &grpc_testing.ResponseParameters{Size: int32((i + 1) * 10)})
|
||||||
|
payload, err := (&jsonpb.Marshaler{}).MarshalToString(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to construct request %d: %v", i, err)
|
||||||
|
}
|
||||||
|
reqs[i] = payload
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success
|
||||||
|
h := &handler{reqMessages: reqs}
|
||||||
|
err := InvokeRpc(context.Background(), source, cc, "grpc.testing.TestService/FullDuplexCall", makeHeaders(codes.OK), h, h.getRequestData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error during RPC: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.check(t, "grpc.testing.TestService.FullDuplexCall", codes.OK, 3, 6) {
|
||||||
|
resp := &grpc_testing.StreamingOutputCallResponse{}
|
||||||
|
i := 0
|
||||||
|
for j := 1; j < 3; j++ {
|
||||||
|
// three requests
|
||||||
|
for k := 0; k < j; k++ {
|
||||||
|
// 1 response for first request, 2 for second, etc
|
||||||
|
msg := h.respMessages[i]
|
||||||
|
if err := jsonpb.UnmarshalString(msg, resp); err != nil {
|
||||||
|
t.Errorf("failed to parse response %d: %v", i+1, err)
|
||||||
|
}
|
||||||
|
if resp.Payload.GetType() != grpc_testing.PayloadType_RANDOM {
|
||||||
|
t.Errorf("response %d has wrong payload type; expecting %v, got %v", i, grpc_testing.PayloadType_RANDOM, resp.Payload.Type)
|
||||||
|
}
|
||||||
|
if len(resp.Payload.Body) != (k+1)*10 {
|
||||||
|
t.Errorf("response %d has wrong payload size; expecting %d, got %d", i, (k+1)*10, len(resp.Payload.Body))
|
||||||
|
}
|
||||||
|
resp.Reset()
|
||||||
|
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail fast (server rejects as soon as possible)
|
||||||
|
h = &handler{reqMessages: reqs}
|
||||||
|
err = InvokeRpc(context.Background(), source, cc, "grpc.testing.TestService/FullDuplexCall", makeHeaders(codes.PermissionDenied), h, h.getRequestData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error during RPC: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.check(t, "grpc.testing.TestService.FullDuplexCall", codes.PermissionDenied, -3, 0)
|
||||||
|
|
||||||
|
// Fail late (server waits until stream is complete to reject)
|
||||||
|
h = &handler{reqMessages: reqs}
|
||||||
|
err = InvokeRpc(context.Background(), source, cc, "grpc.testing.TestService/FullDuplexCall", makeHeaders(codes.ResourceExhausted, true), h, h.getRequestData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error during RPC: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.check(t, "grpc.testing.TestService.FullDuplexCall", codes.ResourceExhausted, 3, 6)
|
||||||
|
}
|
||||||
|
|
||||||
|
type handler struct {
|
||||||
|
method *desc.MethodDescriptor
|
||||||
|
methodCount int
|
||||||
|
reqHeaders metadata.MD
|
||||||
|
reqHeadersCount int
|
||||||
|
reqMessages []string
|
||||||
|
reqMessagesCount int
|
||||||
|
respHeaders metadata.MD
|
||||||
|
respHeadersCount int
|
||||||
|
respMessages []string
|
||||||
|
respTrailers metadata.MD
|
||||||
|
respStatus *status.Status
|
||||||
|
respTrailersCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) getRequestData() (json.RawMessage, error) {
|
||||||
|
// we don't use a mutex, though this method will be called from different goroutine
|
||||||
|
// than other methods for bidi calls, because this method does not share any state
|
||||||
|
// with the other methods.
|
||||||
|
h.reqMessagesCount++
|
||||||
|
if h.reqMessagesCount > len(h.reqMessages) {
|
||||||
|
return nil, io.EOF
|
||||||
|
}
|
||||||
|
if h.reqMessagesCount > 1 {
|
||||||
|
// insert delay between messages in request stream
|
||||||
|
time.Sleep(time.Millisecond * 50)
|
||||||
|
}
|
||||||
|
return json.RawMessage(h.reqMessages[h.reqMessagesCount-1]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) OnResolveMethod(md *desc.MethodDescriptor) {
|
||||||
|
h.methodCount++
|
||||||
|
h.method = md
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) OnSendHeaders(md metadata.MD) {
|
||||||
|
h.reqHeadersCount++
|
||||||
|
h.reqHeaders = md
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) OnReceiveHeaders(md metadata.MD) {
|
||||||
|
h.respHeadersCount++
|
||||||
|
h.respHeaders = md
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) OnReceiveResponse(msg json.RawMessage) {
|
||||||
|
h.respMessages = append(h.respMessages, string(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) OnReceiveTrailers(stat *status.Status, md metadata.MD) {
|
||||||
|
h.respTrailersCount++
|
||||||
|
h.respTrailers = md
|
||||||
|
h.respStatus = stat
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) check(t *testing.T, expectedMethod string, expectedCode codes.Code, expectedRequestQueries, expectedResponses int) bool {
|
||||||
|
// verify a few things were only ever called once
|
||||||
|
if h.methodCount != 1 {
|
||||||
|
t.Errorf("expected grpcurl to invoke OnResolveMethod once; was %d", h.methodCount)
|
||||||
|
}
|
||||||
|
if h.reqHeadersCount != 1 {
|
||||||
|
t.Errorf("expected grpcurl to invoke OnSendHeaders once; was %d", h.reqHeadersCount)
|
||||||
|
}
|
||||||
|
if h.reqHeadersCount != 1 {
|
||||||
|
t.Errorf("expected grpcurl to invoke OnSendHeaders once; was %d", h.reqHeadersCount)
|
||||||
|
}
|
||||||
|
if h.respHeadersCount != 1 {
|
||||||
|
t.Errorf("expected grpcurl to invoke OnReceiveHeaders once; was %d", h.respHeadersCount)
|
||||||
|
}
|
||||||
|
if h.respTrailersCount != 1 {
|
||||||
|
t.Errorf("expected grpcurl to invoke OnReceiveTrailers once; was %d", h.respTrailersCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check other stuff against given expectations
|
||||||
|
if h.method.GetFullyQualifiedName() != expectedMethod {
|
||||||
|
t.Errorf("wrong method: expecting %v, got %v", expectedMethod, h.method.GetFullyQualifiedName())
|
||||||
|
}
|
||||||
|
if h.respStatus.Code() != expectedCode {
|
||||||
|
t.Errorf("wrong code: expecting %v, got %v", expectedCode, h.respStatus.Code())
|
||||||
|
}
|
||||||
|
if expectedRequestQueries < 0 {
|
||||||
|
// negative expectation means "negate and expect up to that number; could be fewer"
|
||||||
|
if h.reqMessagesCount > -expectedRequestQueries+1 {
|
||||||
|
// the + 1 is because there will be an extra query that returns EOF
|
||||||
|
t.Errorf("wrong number of messages queried: expecting no more than %v, got %v", -expectedRequestQueries, h.reqMessagesCount-1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if h.reqMessagesCount != expectedRequestQueries+1 {
|
||||||
|
// the + 1 is because there will be an extra query that returns EOF
|
||||||
|
t.Errorf("wrong number of messages queried: expecting %v, got %v", expectedRequestQueries, h.reqMessagesCount-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(h.respMessages) != expectedResponses {
|
||||||
|
t.Errorf("wrong number of messages received: expecting %v, got %v", expectedResponses, len(h.respMessages))
|
||||||
|
}
|
||||||
|
|
||||||
|
// also check headers and trailers came through as expected
|
||||||
|
v := h.respHeaders["some-fake-header-1"]
|
||||||
|
if len(v) != 1 || v[0] != "val1" {
|
||||||
|
t.Errorf("wrong request header for %q: %v", "some-fake-header-1", v)
|
||||||
|
}
|
||||||
|
v = h.respHeaders["some-fake-header-2"]
|
||||||
|
if len(v) != 1 || v[0] != "val2" {
|
||||||
|
t.Errorf("wrong request header for %q: %v", "some-fake-header-2", v)
|
||||||
|
}
|
||||||
|
v = h.respTrailers["some-fake-trailer-1"]
|
||||||
|
if len(v) != 1 || v[0] != "valA" {
|
||||||
|
t.Errorf("wrong request header for %q: %v", "some-fake-trailer-1", v)
|
||||||
|
}
|
||||||
|
v = h.respTrailers["some-fake-trailer-2"]
|
||||||
|
if len(v) != 1 || v[0] != "valB" {
|
||||||
|
t.Errorf("wrong request header for %q: %v", "some-fake-trailer-2", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(h.respMessages) == expectedResponses
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeHeaders(code codes.Code, failLate ...bool) []string {
|
||||||
|
if len(failLate) > 1 {
|
||||||
|
panic("incorrect use of makeContext; should be at most one failLate flag")
|
||||||
|
}
|
||||||
|
|
||||||
|
hdrs := append(make([]string, 0, 5),
|
||||||
|
fmt.Sprintf("%s: %s", grpcurl_testing.MetadataReplyHeaders, "some-fake-header-1: val1"),
|
||||||
|
fmt.Sprintf("%s: %s", grpcurl_testing.MetadataReplyHeaders, "some-fake-header-2: val2"),
|
||||||
|
fmt.Sprintf("%s: %s", grpcurl_testing.MetadataReplyTrailers, "some-fake-trailer-1: valA"),
|
||||||
|
fmt.Sprintf("%s: %s", grpcurl_testing.MetadataReplyTrailers, "some-fake-trailer-2: valB"))
|
||||||
|
if code != codes.OK {
|
||||||
|
if len(failLate) > 0 && failLate[0] {
|
||||||
|
hdrs = append(hdrs, fmt.Sprintf("%s: %d", grpcurl_testing.MetadataFailLate, code))
|
||||||
|
} else {
|
||||||
|
hdrs = append(hdrs, fmt.Sprintf("%s: %d", grpcurl_testing.MetadataFailEarly, code))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hdrs
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cd "$(dirname $0)"
|
||||||
|
|
||||||
|
# Run this script to generate files used by tests.
|
||||||
|
|
||||||
|
echo "Creating protoset..."
|
||||||
|
protoc ../../../google.golang.org/grpc/interop/grpc_testing/test.proto \
|
||||||
|
-I../../../ --include_imports \
|
||||||
|
--descriptor_set_out=testing/test.protoset
|
||||||
|
|
||||||
|
|
||||||
|
echo "Creating certs for TLS testing..."
|
||||||
|
if ! hash certstrap 2>/dev/null; then
|
||||||
|
# certstrap not found: try to install it
|
||||||
|
go get github.com/square/certstrap
|
||||||
|
go install github.com/square/certstrap
|
||||||
|
fi
|
||||||
|
|
||||||
|
function cs() {
|
||||||
|
certstrap --depot-path testing/tls "$@" --passphrase ""
|
||||||
|
}
|
||||||
|
|
||||||
|
rm -rf testing/tls
|
||||||
|
|
||||||
|
# Create CA
|
||||||
|
cs init --years 10 --common-name ca
|
||||||
|
|
||||||
|
# Create client cert
|
||||||
|
cs request-cert --common-name client
|
||||||
|
cs sign client --years 10 --CA ca
|
||||||
|
|
||||||
|
# Create server cert
|
||||||
|
cs request-cert --common-name server --ip 127.0.0.1 --domain localhost
|
||||||
|
cs sign server --years 10 --CA ca
|
||||||
|
|
||||||
|
# Create another server cert for error testing
|
||||||
|
cs request-cert --common-name other --ip 1.2.3.4 --domain foobar.com
|
||||||
|
cs sign other --years 10 --CA ca
|
||||||
|
|
||||||
|
# Create another CA and client cert for more
|
||||||
|
# error testing
|
||||||
|
cs init --years 10 --common-name wrong-ca
|
||||||
|
cs request-cert --common-name wrong-client
|
||||||
|
cs sign wrong-client --years 10 --CA wrong-ca
|
||||||
|
|
||||||
|
# Create expired cert
|
||||||
|
cs request-cert --common-name expired --ip 127.0.0.1 --domain localhost
|
||||||
|
cs sign expired --years 0 --CA ca
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,235 @@
|
||||||
|
package testing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/interop/grpc_testing"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
|
||||||
|
"github.com/fullstorydev/grpcurl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestServer struct{}
|
||||||
|
|
||||||
|
// One empty request followed by one empty response.
|
||||||
|
func (TestServer) EmptyCall(ctx context.Context, req *grpc_testing.Empty) (*grpc_testing.Empty, error) {
|
||||||
|
headers, trailers, failEarly, failLate := processMetadata(ctx)
|
||||||
|
grpc.SetHeader(ctx, headers)
|
||||||
|
grpc.SetTrailer(ctx, trailers)
|
||||||
|
if failEarly != codes.OK {
|
||||||
|
return nil, status.Error(failEarly, "fail")
|
||||||
|
}
|
||||||
|
if failLate != codes.OK {
|
||||||
|
return nil, status.Error(failLate, "fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// One request followed by one response.
|
||||||
|
// The server returns the client payload as-is.
|
||||||
|
func (TestServer) UnaryCall(ctx context.Context, req *grpc_testing.SimpleRequest) (*grpc_testing.SimpleResponse, error) {
|
||||||
|
headers, trailers, failEarly, failLate := processMetadata(ctx)
|
||||||
|
grpc.SetHeader(ctx, headers)
|
||||||
|
grpc.SetTrailer(ctx, trailers)
|
||||||
|
if failEarly != codes.OK {
|
||||||
|
return nil, status.Error(failEarly, "fail")
|
||||||
|
}
|
||||||
|
if failLate != codes.OK {
|
||||||
|
return nil, status.Error(failLate, "fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &grpc_testing.SimpleResponse{
|
||||||
|
Payload: req.Payload,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// One request followed by a sequence of responses (streamed download).
|
||||||
|
// The server returns the payload with client desired type and sizes.
|
||||||
|
func (TestServer) StreamingOutputCall(req *grpc_testing.StreamingOutputCallRequest, str grpc_testing.TestService_StreamingOutputCallServer) error {
|
||||||
|
headers, trailers, failEarly, failLate := processMetadata(str.Context())
|
||||||
|
str.SetHeader(headers)
|
||||||
|
str.SetTrailer(trailers)
|
||||||
|
if failEarly != codes.OK {
|
||||||
|
return status.Error(failEarly, "fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
rsp := &grpc_testing.StreamingOutputCallResponse{Payload: &grpc_testing.Payload{}}
|
||||||
|
for _, param := range req.ResponseParameters {
|
||||||
|
if str.Context().Err() != nil {
|
||||||
|
return str.Context().Err()
|
||||||
|
}
|
||||||
|
delayMicros := int64(param.GetIntervalUs()) * int64(time.Microsecond)
|
||||||
|
if delayMicros > 0 {
|
||||||
|
time.Sleep(time.Duration(delayMicros))
|
||||||
|
}
|
||||||
|
sz := int(param.GetSize())
|
||||||
|
buf := make([]byte, sz)
|
||||||
|
for i := 0; i < sz; i++ {
|
||||||
|
buf[i] = byte(i)
|
||||||
|
}
|
||||||
|
rsp.Payload.Type = req.ResponseType
|
||||||
|
rsp.Payload.Body = buf
|
||||||
|
if err := str.Send(rsp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if failLate != codes.OK {
|
||||||
|
return status.Error(failLate, "fail")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A sequence of requests followed by one response (streamed upload).
|
||||||
|
// The server returns the aggregated size of client payload as the result.
|
||||||
|
func (TestServer) StreamingInputCall(str grpc_testing.TestService_StreamingInputCallServer) error {
|
||||||
|
headers, trailers, failEarly, failLate := processMetadata(str.Context())
|
||||||
|
str.SetHeader(headers)
|
||||||
|
str.SetTrailer(trailers)
|
||||||
|
if failEarly != codes.OK {
|
||||||
|
return status.Error(failEarly, "fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
sz := 0
|
||||||
|
for {
|
||||||
|
if str.Context().Err() != nil {
|
||||||
|
return str.Context().Err()
|
||||||
|
}
|
||||||
|
if req, err := str.Recv(); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
sz += len(req.Payload.Body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := str.SendAndClose(&grpc_testing.StreamingInputCallResponse{AggregatedPayloadSize: int32(sz)}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if failLate != codes.OK {
|
||||||
|
return status.Error(failLate, "fail")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A sequence of requests with each request served by the server immediately.
|
||||||
|
// As one request could lead to multiple responses, this interface
|
||||||
|
// demonstrates the idea of full duplexing.
|
||||||
|
func (TestServer) FullDuplexCall(str grpc_testing.TestService_FullDuplexCallServer) error {
|
||||||
|
headers, trailers, failEarly, failLate := processMetadata(str.Context())
|
||||||
|
str.SetHeader(headers)
|
||||||
|
str.SetTrailer(trailers)
|
||||||
|
if failEarly != codes.OK {
|
||||||
|
return status.Error(failEarly, "fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
rsp := &grpc_testing.StreamingOutputCallResponse{Payload: &grpc_testing.Payload{}}
|
||||||
|
for {
|
||||||
|
if str.Context().Err() != nil {
|
||||||
|
return str.Context().Err()
|
||||||
|
}
|
||||||
|
req, err := str.Recv()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, param := range req.ResponseParameters {
|
||||||
|
sz := int(param.GetSize())
|
||||||
|
buf := make([]byte, sz)
|
||||||
|
for i := 0; i < sz; i++ {
|
||||||
|
buf[i] = byte(i)
|
||||||
|
}
|
||||||
|
rsp.Payload.Type = req.ResponseType
|
||||||
|
rsp.Payload.Body = buf
|
||||||
|
if err := str.Send(rsp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if failLate != codes.OK {
|
||||||
|
return status.Error(failLate, "fail")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A sequence of requests followed by a sequence of responses.
|
||||||
|
// The server buffers all the client requests and then serves them in order. A
|
||||||
|
// stream of responses are returned to the client when the server starts with
|
||||||
|
// first request.
|
||||||
|
func (TestServer) HalfDuplexCall(str grpc_testing.TestService_HalfDuplexCallServer) error {
|
||||||
|
headers, trailers, failEarly, failLate := processMetadata(str.Context())
|
||||||
|
str.SetHeader(headers)
|
||||||
|
str.SetTrailer(trailers)
|
||||||
|
if failEarly != codes.OK {
|
||||||
|
return status.Error(failEarly, "fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
var reqs []*grpc_testing.StreamingOutputCallRequest
|
||||||
|
for {
|
||||||
|
if str.Context().Err() != nil {
|
||||||
|
return str.Context().Err()
|
||||||
|
}
|
||||||
|
if req, err := str.Recv(); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
reqs = append(reqs, req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rsp := &grpc_testing.StreamingOutputCallResponse{}
|
||||||
|
for _, req := range reqs {
|
||||||
|
rsp.Payload = req.Payload
|
||||||
|
if err := str.Send(rsp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if failLate != codes.OK {
|
||||||
|
return status.Error(failLate, "fail")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
MetadataReplyHeaders = "reply-with-headers"
|
||||||
|
MetadataReplyTrailers = "reply-with-trailers"
|
||||||
|
MetadataFailEarly = "fail-early"
|
||||||
|
MetadataFailLate = "fail-late"
|
||||||
|
)
|
||||||
|
|
||||||
|
func processMetadata(ctx context.Context) (metadata.MD, metadata.MD, codes.Code, codes.Code) {
|
||||||
|
md, ok := metadata.FromIncomingContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, codes.OK, codes.OK
|
||||||
|
}
|
||||||
|
return grpcurl.MetadataFromHeaders(md[MetadataReplyHeaders]),
|
||||||
|
grpcurl.MetadataFromHeaders(md[MetadataReplyTrailers]),
|
||||||
|
toCode(md[MetadataFailEarly]),
|
||||||
|
toCode(md[MetadataFailLate])
|
||||||
|
}
|
||||||
|
|
||||||
|
func toCode(vals []string) codes.Code {
|
||||||
|
if len(vals) == 0 {
|
||||||
|
return codes.OK
|
||||||
|
}
|
||||||
|
i, err := strconv.Atoi(vals[len(vals)-1])
|
||||||
|
if err != nil {
|
||||||
|
return codes.Code(i)
|
||||||
|
}
|
||||||
|
return codes.Code(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ grpc_testing.TestServiceServer = TestServer{}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
-----BEGIN X509 CRL-----
|
||||||
|
MIICfDBmAgEBMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNVBAMTAmNhFw0xNzA4MjUx
|
||||||
|
NTQ1NTNaFw0yNzA4MjUxNTQ1NTNaMACgIzAhMB8GA1UdIwQYMBaAFM0FLuuYBwuA
|
||||||
|
J+tocRlu+xUuOw6FMA0GCSqGSIb3DQEBCwUAA4ICAQCcN8WJKbvGrunXgRBjSnsM
|
||||||
|
j/sejaX3CCZPmrXeditekSNMatO0JDXOjyoEvv7s9aZrAf3eFOU3Vr5N7PlbLRdj
|
||||||
|
tovuKTeVp3ungqMoT70cFEf/7eMlpWMB2GkfpV9LtF5Tb8dOYT3kllqtMKv4TeZo
|
||||||
|
2adu+GXdeQsqlz9fDEi0ZV4RBruuO0QyLWXpNrUB6fznUDfE4KVBsAIadjsg+Aew
|
||||||
|
6jeTkYuUILWMwBM6MzOG/InTKqXpe4ghMufI9fO+phxY10gz4QQ44ZNOa18OuiJw
|
||||||
|
IH8MoKzhrgUAPLs135hpdGbDePVw5SIKMHUAU2UEKtozAMVfCW45MZHREDdMV3NA
|
||||||
|
w5QWDoBYl4jol08Orbccmhu4fbauXmB5Id4IPVgGEGFPpiH/QVyJgZIv1AD2dlRg
|
||||||
|
Td26iz9I25hyrpEfF1gJMtOsDOklDsUiMo8ncQ3CL+pkKnMjhm54k6OFe0qlGsdO
|
||||||
|
KSavNlEmW/F9h/gs5kaLeFv0v4JxLh12TY28pCE60yoB/UkuB1a+VTHcP3Fa6uUC
|
||||||
|
uyv0T0f5yHujaM1isGjI3XGgVgLyJiFxtKMPsMEwRrsrEafqp7JCeLpnIWt1J0C/
|
||||||
|
Zz3roGcCGj86Oq5zUjdguHS6Ra+uaX+IMJGohWq1cndzVzNfUNkRIyl8IdkCv9o3
|
||||||
|
J8fVwBzN6sAu6zWd3BqT4A==
|
||||||
|
-----END X509 CRL-----
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIE2jCCAsKgAwIBAgIBATANBgkqhkiG9w0BAQsFADANMQswCQYDVQQDEwJjYTAe
|
||||||
|
Fw0xNzA4MjUxNTQ1NTJaFw0yNzA4MjUxNTQ1NTNaMA0xCzAJBgNVBAMTAmNhMIIC
|
||||||
|
IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnjw7iZyn9EtjtK7zT+M59OxS
|
||||||
|
J43a3kMm11Vnh1Fw8oQ7tH0kQW6COyAwlBhAzWGtfDC6jG7A2n+8mPWoinsxoLA5
|
||||||
|
viSZrEkWZ4tGxWWZ5y/xWh5NBrHa3Jsg1rZ5dstAl08gJPwl0v32aYkMdtk16K0k
|
||||||
|
jtdPpKtO98v1N6ea7fvQKdyrAaUHY/BY+onoqmSBGPX6vVV7FGybWDf72J5vcwyA
|
||||||
|
07wdaZ7TrUYbD28nhXuc4Dt2KbZbWvkT4OYZ+4c+eiehRFVGtuXXi0cKG0xF516b
|
||||||
|
DnrSO1/2ZbIB2xmcgKvcay0jLzWqhnsSc+qTqODVLODQAMvrMlY0RUXQPn/WTfAw
|
||||||
|
aQ+u/j7qIR1KFZcLn7uVq8bCM9g+VeyLjq+6XUvgGL9KhvrR/FA4R4DUka+ZVQqh
|
||||||
|
s262Qs7pNFdoIIrTsJyPd7/UYWCcQbkCKw0aRoUfBeZgkg4bylcABygZqY9+apRF
|
||||||
|
NBEhpycAEvWFarr6rosqII9kLm1LpnPNEgSvQ/CIRIHq5z5iKeSvHFYOVxLS44HH
|
||||||
|
M16Mry5UF/jW7Vg8JY5Jrg5YwyOhdGoOSJ3+c/pbLq1TRkwK9bwEJwGr7FdkrjPZ
|
||||||
|
uNJ7HQiFn2IaeLlbtzwJ+q3vGSFEjlujOigwUJVzXz16Q93vYIuK3FcyDuOMzpwW
|
||||||
|
HHgSLH/+rdtd+7hoLwMCAwEAAaNFMEMwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB
|
||||||
|
/wQIMAYBAf8CAQAwHQYDVR0OBBYEFM0FLuuYBwuAJ+tocRlu+xUuOw6FMA0GCSqG
|
||||||
|
SIb3DQEBCwUAA4ICAQBLNuP8ovTgFcYf7Ydgc+aDB2v+qMYVIqJMrnp8DuYy20yv
|
||||||
|
64jYcIxh35IbQWOZxZshJsebRKM9vr6huEo2c/SuHLQ5HZGPxSt++aG+iY4Y1zL5
|
||||||
|
KHtG558lK4S5VsXymMkUjGZtm+ZuJida9ZcV+jz/kePMHpErWPeMvH2jDmD4mWgA
|
||||||
|
YdjipD4cxEn+9O3lBSCkeSjaAd5rQeD9XomV4a2/uL4Y7RDbn9BNt+jdLvfu2pmo
|
||||||
|
O1zcp0f578oFlUIg0H9fb6YzL3MKOXiuh7KE1/W9el5zsN/kLlyWFbopN34A6PlO
|
||||||
|
ZHEvZZcQW06bmy2FRWgqkqWMqBwzWk7JKGp+ozv8IBvimhgjNun068FQAZV9nfKU
|
||||||
|
6U728P6T1USDhgwtpX7/2IaukXcmO2FE9XzKZyYAbmAcOhPLzFO4pdwapU2lPbFE
|
||||||
|
l2HLkYaHLXzMxB30kQQHW2l8+8xr+MAa+bBcD9Jaxaz/t3ZpLt62/1nxT7SWNwH4
|
||||||
|
Sa83BaG3EHBotlBc18hqrFWEKR4KYenqY8xa7kblDI0rXqlXBblUXp0TwIctOmzR
|
||||||
|
coqR8q6/R4VXhD9FZBIW1/uX2KKEPfTM46aQdaTtdzjd3UzwTP0SRwkvZ4oFftW6
|
||||||
|
s1GljfCGsrOpi6O/Uy/IVTE7Xn/oVnlJvGbaP+AHexLytBiBVUBukLBwvpJ8bg==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEGjCCAgKgAwIBAgIRAPt0KCF12GYbCoUj7klj5/AwDQYJKoZIhvcNAQELBQAw
|
||||||
|
DTELMAkGA1UEAxMCY2EwHhcNMTcwODI1MTU0NTUzWhcNMjcwODI1MTU0NTUyWjAR
|
||||||
|
MQ8wDQYDVQQDEwZjbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
|
||||||
|
AQC1JxEPOsZyf8883tlPBEajotyECtrYMZ48FsYEmQ1XvKPoH3eb7+Ev7tRBVAup
|
||||||
|
yB87XQ5PU/oNqAtpo/6WD5JGnKSVs+EAMESXmzEF04T9hK8uSd0cVEEkd0tbVNpX
|
||||||
|
bWMbivHnx5Vp8o2mIx0sVrgGsJW3t+cYbNTp3bOTdmz7LKbiQN2Ix0wH+2/sPXYa
|
||||||
|
cZsgbI0Ydo9KnqykPm2TqBYCL1kzhGlvaAotjdDIm7OgnaGCFe4CbK4QZB4uFw3e
|
||||||
|
M+PmLG0TsaH9CT/ZRrE21iBfg0rqgpKZKMcqYQftXdLqlikuV69F+0L84xRfeVqB
|
||||||
|
1E4j0RwBGWW8EwY4WHK3VE25AgMBAAGjcTBvMA4GA1UdDwEB/wQEAwIDuDAdBgNV
|
||||||
|
HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFMs+/QF/ZJaRu8Wv
|
||||||
|
vcaMC7jGmPwxMB8GA1UdIwQYMBaAFM0FLuuYBwuAJ+tocRlu+xUuOw6FMA0GCSqG
|
||||||
|
SIb3DQEBCwUAA4ICAQB0gPDs86Rjy/O2+l8QyaYfwmmyTMPjNVqKgVP1uuikWErN
|
||||||
|
5hTAlwtDI9FuiMFBqeBdeiT8IQvzEEQPYu69kAX2XYBWBMWDa85co5fJztAzV7Yz
|
||||||
|
VL1byhxd2jgM14usyx6PbzkhYKBNesujHj7wQ0ur+85Kp66HqKCuNCvbj0zv58PH
|
||||||
|
RWkojRPgyTpbLdXXCOWJXp62XfddL1Bf7NJCW5QTyHoHoOsOeoPajb4OOmQehzqv
|
||||||
|
b9FPAHVFBPrU53Xn1CURAzTeBQ2T/OK4nx6EdQgxP9+VVurBQ9N2YBM9VEJmfQK8
|
||||||
|
Lf5/+EJHe5ctOy1Xm4A3A52zZ1kGjfvWUtGJUSnJ5ahhMm6Dx63wk7oYNCTXnPup
|
||||||
|
aVtINWygNlS/dQsWubHaWSFwB9/QwK074+H/4EpDq9HCMMl8yPMktOmv69Hyaju3
|
||||||
|
MvGshz/DLNZf9oYpO+lbU8X124Z6XifEztMiBlUPW75KYv9X4CTbKTdE45QaRMiO
|
||||||
|
ZXcH4HE1/iQ9IOGg7CplMlMcHg+lQ7CpXQjtUUjCEpkj8BAs8YLDodLnjigs56/8
|
||||||
|
75+3cVZu0+dY+9eNt/EIqzjaFwEx72hbLyhk2IeS++7QloJDhYqlq+ng54Ul957A
|
||||||
|
8e7TsSVHlLZVGXw8yqjyxxOwWaFx6mvFzGrcBtvCgK2HwEiYQ9qXJ5VPkdo42w==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
-----BEGIN CERTIFICATE REQUEST-----
|
||||||
|
MIICVjCCAT4CAQAwETEPMA0GA1UEAxMGY2xpZW50MIIBIjANBgkqhkiG9w0BAQEF
|
||||||
|
AAOCAQ8AMIIBCgKCAQEAtScRDzrGcn/PPN7ZTwRGo6LchAra2DGePBbGBJkNV7yj
|
||||||
|
6B93m+/hL+7UQVQLqcgfO10OT1P6DagLaaP+lg+SRpyklbPhADBEl5sxBdOE/YSv
|
||||||
|
LkndHFRBJHdLW1TaV21jG4rx58eVafKNpiMdLFa4BrCVt7fnGGzU6d2zk3Zs+yym
|
||||||
|
4kDdiMdMB/tv7D12GnGbIGyNGHaPSp6spD5tk6gWAi9ZM4Rpb2gKLY3QyJuzoJ2h
|
||||||
|
ghXuAmyuEGQeLhcN3jPj5ixtE7Gh/Qk/2UaxNtYgX4NK6oKSmSjHKmEH7V3S6pYp
|
||||||
|
LlevRftC/OMUX3lagdROI9EcARllvBMGOFhyt1RNuQIDAQABoAAwDQYJKoZIhvcN
|
||||||
|
AQELBQADggEBAFnxmVCuM3J2bt79JcFOqsXNsvGUUT+4kMl3BcfSWaf1pviuhiXT
|
||||||
|
fsKkk1WItvaRQvpNdQoFQDjKHGcd6+0vCz+Q6Nni2Vniz3+f3+h/rOzWGA656Xxm
|
||||||
|
lgByryixnngWZBNLZkLWCz/H1MAlQYu8PTdy0N+JBF/E5SAGfaaXtfTC6tjnnZIm
|
||||||
|
3rjxC7C3EyELpo3X3erTcHpnFvhl6ZSkViVWfhOjxU0n+TGGohczesbHZc8YC37y
|
||||||
|
JrkrnRDrNKnca1XkXWUnbV6rH8cVDnJ0Fvs54RI686Tlv+LxW2xa3D2+pV7Koduj
|
||||||
|
Ru+PguJ3BbaRpieGTxHg7hH/1T5HsZnD2E0=
|
||||||
|
-----END CERTIFICATE REQUEST-----
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpAIBAAKCAQEAtScRDzrGcn/PPN7ZTwRGo6LchAra2DGePBbGBJkNV7yj6B93
|
||||||
|
m+/hL+7UQVQLqcgfO10OT1P6DagLaaP+lg+SRpyklbPhADBEl5sxBdOE/YSvLknd
|
||||||
|
HFRBJHdLW1TaV21jG4rx58eVafKNpiMdLFa4BrCVt7fnGGzU6d2zk3Zs+yym4kDd
|
||||||
|
iMdMB/tv7D12GnGbIGyNGHaPSp6spD5tk6gWAi9ZM4Rpb2gKLY3QyJuzoJ2hghXu
|
||||||
|
AmyuEGQeLhcN3jPj5ixtE7Gh/Qk/2UaxNtYgX4NK6oKSmSjHKmEH7V3S6pYpLlev
|
||||||
|
RftC/OMUX3lagdROI9EcARllvBMGOFhyt1RNuQIDAQABAoIBACE5jRNyADu33VaY
|
||||||
|
uNqZOiuBD1jYdNL6Jr92ndLyD1RsMNO+Eb3z/SVBdISW2ZzGK5RDuQArss0WaSFz
|
||||||
|
BpqXOIji6fzbBQV31NzJhfA/n0CwOUEQIxGzEk+R4axan8ExOuAuV7ffDzRjXD+A
|
||||||
|
aTVcolv3vz326Ne9/j72fp0pN0vJ0b8mk1xmDWNOHhfoWmIGrUZAjqAkA1kh5aLk
|
||||||
|
Q8MCjVyjT+KYDkFT6NscFVxKslDVhb2OFC7oy+9l/hBru12bsi9eBdYpPT9E1cpR
|
||||||
|
U9N8+9XS9d7wgVnmVh8CIrFToLsvSrwD8SG0Indot0C6dsy0PkoMUekVxvM5/wXm
|
||||||
|
YLZnZEECgYEA28JfZxFxO+bjd+zBC+yrusHCVfZK6MZZV0u/V82Bn+gftwgxagI7
|
||||||
|
q2h7m56WBtq9MeLlhicZ+em4BtA3yHwVGhqr+d5CaXjkYype1EditqGx8JYRUwlG
|
||||||
|
9z7W6jCEDJsjrzvGgua1qsyCZFePG78i4rLumK7UVvWEK/OaiZu3Pr0CgYEA0wbT
|
||||||
|
3STBc4THLXR8nx39b6RP+qH8jO9UcD/V7Hi/SWTCcGB8IIlTV2EJVndKHPregcmI
|
||||||
|
dN61uH3d+3UtI/WxEPMcfrSlEwVrjF2m5szYjLIAeFynw7pQY95qIhgKi6OH0Yn6
|
||||||
|
9OCmieL0x1ez5zOXiv+GVjmn9tDCxXvqfsW9CK0CgYEApOd0Y4kpKUQWyPT135bX
|
||||||
|
PqsKwyqwB4BfpiwHB0IE1ROASP5y5hOK5xLePmaAOeCGPBsBFOveiDQjjalNUroZ
|
||||||
|
s570EeoAd9jpuKggxLZUkqs/NUPG+EJr6DhVWSLS1ArOej4mti+dfu87oUQ69R02
|
||||||
|
dlrCw/vdBuvxJHIGMuCQXxkCgYEAinSFVygBgQCSCkHObjuoB7LgAsp7QCDa3tcT
|
||||||
|
TZafstDYPhEf/9z6AG+bR872onL6wF7xF/Tzd7ulhJGJ73kJFtzbSkrNr+AzgyID
|
||||||
|
GpU2U4GKi24HaIT6r7vDGOF7Mck2mIWWUUqAGiH9hjkFwWD5QeqLQlGL4YVw9U9r
|
||||||
|
OIgWkfUCgYAoMub8wHJe9zhq7UCBCa4zPXqWVQcN7ANjL6fESvyK+A6TfwL5j782
|
||||||
|
CNIVl8ewU9TthEY+AdbJeiAevz+pIazSqi0ln1JKO5YpCOC1Y0UxcEpghplBTlPU
|
||||||
|
yoQyTJP81iLynwOM4pC322ptJISXIndL7Ig/9AoRZtAJV4Ot6z9b1Q==
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEODCCAiCgAwIBAgIQP6vqM4GEKFdwrZvr/s/5KTANBgkqhkiG9w0BAQsFADAN
|
||||||
|
MQswCQYDVQQDEwJjYTAeFw0xNzA4MjUxNTQ1NThaFw0xNzA4MjUxNTQ1NThaMBIx
|
||||||
|
EDAOBgNVBAMTB2V4cGlyZWQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
|
||||||
|
AQDDlGu08Ye7IGMnh6zyiVbW64btT2BUfj4fZlyTraLNzVPfULOXezgJRAwAXEnY
|
||||||
|
eYA5j6e7iAO/yGXq+Pxh9rbGlXTVC2BsPIfKzTOcCTxgO4tfrdfSWFRDGgcFtpAA
|
||||||
|
/4b8fYQ8BMw7khVpOqj9n8TyqsN8DGyZTyNhQuc23zmmY1eVYtDIwVvVBp8/YzDT
|
||||||
|
3RzO+FyoRpKgdngX6kdqqZ+vaPxP078JM5zAMnysjSkpQCdAdu4EupvA5xRtOrDt
|
||||||
|
jJoAi4iUf4sG20RiF9XFpiDkyUiOa0ysFC4wrWbXkaT1xHZJJPwwJZ8VyGDRFlFa
|
||||||
|
POEaM9dtLPKtEV2af3G9Q9s/AgMBAAGjgY4wgYswDgYDVR0PAQH/BAQDAgO4MB0G
|
||||||
|
A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQUNX8nKbJ+G0bv
|
||||||
|
k88bTHkULUX1U5AwHwYDVR0jBBgwFoAUzQUu65gHC4An62hxGW77FS47DoUwGgYD
|
||||||
|
VR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEBCwUAA4ICAQBHZsxC
|
||||||
|
GGOUhNi1b0xUYmKwFaRP641R69NydzVMma+2rpLZtL8MQq7lWAgWGE8EiKfTewk8
|
||||||
|
2Gm+rNwReBRkw56zka8WtLomO4GKKJHvfRsbSempsp9MShc3vcsEtzXGjDQi9c4Y
|
||||||
|
BJjWbh666jCKPRJ09RewcTyTYPtZ96lVeYRj9HhXF1EGnURG5sM/zCantlZXxInk
|
||||||
|
1FIOKDuawMbgf3GR6n/bM2oybYCs4Skv3yOp8x3lyhlJ/zmSPGVVkPa7Vki5+sPh
|
||||||
|
/eIZJ9mzEXsu7IXfg1isZ01iB28+6UgpZt/3017PvgopiYrL0gMKO3EL8UqJDweN
|
||||||
|
GzJh5VYOqsjTbsrYYsWGqM66vJmfPvqDyYA1jj/EH+q6TBz0s99rzF44bBKKie6T
|
||||||
|
0KrZT7ohXQ18Vhl2UpqahMqxMeAW/QP5asGuzS5EalUir5mNcOljtq1NnfmYvqok
|
||||||
|
aDC7rURoANwZ2L1oKN0oB6jn36g791pFdKycw+HdsrGgQGPOMak9P32z5kmDsODH
|
||||||
|
6aVrfio2WwSGg+1CIBH0QsclHAUgLsAXjyGbRWxPsvMcsLB6OOymuTP2UfriT05X
|
||||||
|
duabvbEP5IIRehVUfrP5uvoo29xnoPL4UB0C8gwr21IDn7Zew5/ALekN+s6IgsfL
|
||||||
|
9yKTGSD+6Ir3NqBgL8T+uhOAekyLE5S4CcwCHw==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
-----BEGIN CERTIFICATE REQUEST-----
|
||||||
|
MIIChDCCAWwCAQAwEjEQMA4GA1UEAxMHZXhwaXJlZDCCASIwDQYJKoZIhvcNAQEB
|
||||||
|
BQADggEPADCCAQoCggEBAMOUa7Txh7sgYyeHrPKJVtbrhu1PYFR+Ph9mXJOtos3N
|
||||||
|
U99Qs5d7OAlEDABcSdh5gDmPp7uIA7/IZer4/GH2tsaVdNULYGw8h8rNM5wJPGA7
|
||||||
|
i1+t19JYVEMaBwW2kAD/hvx9hDwEzDuSFWk6qP2fxPKqw3wMbJlPI2FC5zbfOaZj
|
||||||
|
V5Vi0MjBW9UGnz9jMNPdHM74XKhGkqB2eBfqR2qpn69o/E/TvwkznMAyfKyNKSlA
|
||||||
|
J0B27gS6m8DnFG06sO2MmgCLiJR/iwbbRGIX1cWmIOTJSI5rTKwULjCtZteRpPXE
|
||||||
|
dkkk/DAlnxXIYNEWUVo84Roz120s8q0RXZp/cb1D2z8CAwEAAaAtMCsGCSqGSIb3
|
||||||
|
DQEJDjEeMBwwGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEB
|
||||||
|
CwUAA4IBAQA0GTIB6PxgmHBa234rSYqIew4qRfY9MeUkVQEFRDwodqxa+LWvZx2T
|
||||||
|
5JmTZYyXBfQwnSye18fDjQuHv1KaI7bnJuMRv9KU8L6ynLkAqrFWRSBjt3eCum01
|
||||||
|
IWZFyWu+dUN2c12C79zUQh8uZc15oDNFrD8ivBbGRpWvR1CSG/DH52kJ8nckgEsh
|
||||||
|
SwxbzSPOXBgLH6ke5z9QGHJMK2rhRFutFOecAId7VBiWqfZJv15+P2ZcyJNQGThs
|
||||||
|
V2sT974YGFkc0Y1MWlCgi3XhyQzIzqV1tEILGSTDE8biuKlm1nX3H0K8oI3hoGcM
|
||||||
|
CBjE3HQ1/rs10IY/WvkpIfAU71D/ExMc
|
||||||
|
-----END CERTIFICATE REQUEST-----
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEowIBAAKCAQEAw5RrtPGHuyBjJ4es8olW1uuG7U9gVH4+H2Zck62izc1T31Cz
|
||||||
|
l3s4CUQMAFxJ2HmAOY+nu4gDv8hl6vj8Yfa2xpV01QtgbDyHys0znAk8YDuLX63X
|
||||||
|
0lhUQxoHBbaQAP+G/H2EPATMO5IVaTqo/Z/E8qrDfAxsmU8jYULnNt85pmNXlWLQ
|
||||||
|
yMFb1QafP2Mw090czvhcqEaSoHZ4F+pHaqmfr2j8T9O/CTOcwDJ8rI0pKUAnQHbu
|
||||||
|
BLqbwOcUbTqw7YyaAIuIlH+LBttEYhfVxaYg5MlIjmtMrBQuMK1m15Gk9cR2SST8
|
||||||
|
MCWfFchg0RZRWjzhGjPXbSzyrRFdmn9xvUPbPwIDAQABAoIBADa1IZuvpCP330SD
|
||||||
|
cyE0wZHEuC1RcsSvu3jVDThR7aRbtwZUcKgC053j5ueC6TUgZ3mycVzHoyTWTYv4
|
||||||
|
scBFXsMVs2SUlhgwpltYIwOWocjZXxcYbbJs+sT6VtSGSKm+0Gd4RLD1NpvDNTIG
|
||||||
|
MpcfRdwLYDsmzonj1SWzrTFwJ5Qe33cHSR8Oi9OarrxzIHWLDNgp8x+5j2l0T3AG
|
||||||
|
RPQMXj7jaK6qEdHGD6geg+ButeyjYyxu64l8Ooax11/jfdYHcK+BmmtmP3Bd/6FQ
|
||||||
|
DCtrXTBv5Txl/T6D/6OsyabSlwGWFDNEAaS47hKCFiYJBR+Az6r7d1QFBIzuqF0+
|
||||||
|
T7EwaKECgYEA/tUMcTXnBRquAtQHW4cSvHH2qoZAhGjXCmj/pk4UCOZDT1DP/K5u
|
||||||
|
m7tOZB4RJBmzg0a1QS89aJ6lhY0Oy1Y9MZTM6ofkBKd8+P0Rgo04UDVeQ8sA9c8x
|
||||||
|
4bFOrOEqe7NoK7Mwxyn5NOmNi/wxy+tpiMH4Jt3y4TLDEZPg0Tsfu90CgYEAxHnc
|
||||||
|
fN3gKeY3SV6nHi+johBirSNazhr4n0fx/TCsy02e5Rrn0rksGVfPij5Y2g8HQLel
|
||||||
|
hdbG7tVyA7UtGgVjwiT+4j0VXmWpCIqfPsibRoy29oPqO4p5pULsz+ueb4biRW2Q
|
||||||
|
tdLqS4OldM8YUFwkS7k3Pp13SAY+Ir9rHL6wv8sCgYEA/AZaYtCrZMnpFNT7XdLt
|
||||||
|
fb+78xQJVKqXGi2TwLbxa4fHQ/cpa75bl9scAToXO7vLZOaWNhxxQDm+e6Fw4zqs
|
||||||
|
FJAURVMV+GBo4ZrvKU1fRzwwuR1ZGsHKlGoV5DZgHKznNmjmseJaG7FsEujdms58
|
||||||
|
tgsXz+Cr53qbn5O/wU4W6WUCgYBA16sB9sPtcBIc/8UNvFE3wkqes4VbciFNiBQA
|
||||||
|
KJlOe26OVCPgMsawEn/nMw5l4QHWxQU2t5xt5Dm9qYSaCt9Sip0oE1rDDbAMpptJ
|
||||||
|
wDEmxnf3wa+DOP9OoFjBghSG4DA7E57nsxUqGOd5NoPiuZYs+5KU8qkUNyM4mo4C
|
||||||
|
LZjtowKBgCWuDPr8MBL1S3ym55VTNUWcuMKUHZkMg0HjRpi8ABXeARoM5FAWykbk
|
||||||
|
hFnI/Waj6EHNGoVVpKttJldP+uRRA+S3wZYKH4gRQcsWI/BVdiBkZgoTG7cwnixs
|
||||||
|
CqMQ4p6In1Q/6EafPqkVQOY1abNKQ7ZGbhksB/fVbf37XfOnDj3H
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEODCCAiCgAwIBAgIRAMcIOg3oRf7n7mR1BUqNnlcwDQYJKoZIhvcNAQELBQAw
|
||||||
|
DTELMAkGA1UEAxMCY2EwHhcNMTcwODI1MTU0NTUzWhcNMjcwODI1MTU0NTUyWjAQ
|
||||||
|
MQ4wDAYDVQQDEwVvdGhlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
||||||
|
AM4lPXHK6YV7GFMeSsHKVq33LDn8Zt0IkY5pGNCuuksPa3vYBFRkwTrrobKQDnmw
|
||||||
|
jLeaQISyxyelhT4aIU34OQpGjOmCzceYPG0uOcecq7noVvbgw0UeQHkEL0p4NOlb
|
||||||
|
9LMNVhKTgQUhZC5mYSIpXNxlE/LlGmqrFX/8peSaJ1oAkkB5FYN0gAbYhd8kpJX3
|
||||||
|
9Nr2A+f88oicSX5K0L73LUFUrxoTcrFP1pWnFgg28vLvvzrW/VZtq4qJPl3GTLbM
|
||||||
|
xHOu000LaHK8TgIJMCQUalh2q2nz+Htmtv+g/b27YEMGcDBW1qBX9oMKgoM/Z1Hv
|
||||||
|
zjVhAm9I649heDUguFXCaoMCAwEAAaOBjzCBjDAOBgNVHQ8BAf8EBAMCA7gwHQYD
|
||||||
|
VR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBSUR1DE/STLJwf2
|
||||||
|
hW4qXPrOSLqlmDAfBgNVHSMEGDAWgBTNBS7rmAcLgCfraHEZbvsVLjsOhTAbBgNV
|
||||||
|
HREEFDASggpmb29iYXIuY29thwQBAgMEMA0GCSqGSIb3DQEBCwUAA4ICAQBG87BU
|
||||||
|
UuDuUCnvcwUNbga8fhe1PR6z2jueKQiI10SxkYG/g6PMQGGYDNO9DZFKu9l/TMTf
|
||||||
|
LnuEozN+Csig+wC+sc32/MdR35XTmkKtNhL0cVgvKP0Q6zNk97/QJErLtDpYb9VR
|
||||||
|
Gm2Ky6FGDp+/EEUvQUKRpGBmWIqOtjxqQu8lLoJlt/TPhxJ0lGDd3c8WwaVFYTbS
|
||||||
|
isBKdHpS2hkn/O1Yd4QtNk06pCpUDQuPumUOBoa+dK3y0jZ+e34h1NoR5EZvfRJy
|
||||||
|
p3n7CLD2eZSNc4oWKb67X0RDao5LD0b51crjgsFYHhCTS+Mgh0YkgukQZ8uBKpUJ
|
||||||
|
IBhz2Nr1QXykrJUhal9MrKukjczEikGxzK1VsDgxYY1kLBURhM9/TfvICmcAaQqv
|
||||||
|
MF9B78lnoJiPZZxD+a5N9MawzN6QBqX8GpvhZoAnj6iAuNwKJVyENpZqravxeq2o
|
||||||
|
buNjgQ+SmfqxQDfMD3lu95yidqD7bcDipJsXEPQzdBjZ1JOJCGi2eiAm50e6bq94
|
||||||
|
CMKmmRjtIbF1hJnHeEFPvXqdPpqcyEvcaDebph/f+54wubTgwFI3VMnhhlv2EPIe
|
||||||
|
rwcbZV3kNpUZWXAZVzYlQcbK+9US8PocOUzmqzA7ZZRO+rCWNxahHjdgrMK3fG6r
|
||||||
|
WudSHHXawj3dkPeWrQde6SILSK/myhGLdjg1fg==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
-----BEGIN CERTIFICATE REQUEST-----
|
||||||
|
MIICgzCCAWsCAQAwEDEOMAwGA1UEAxMFb3RoZXIwggEiMA0GCSqGSIb3DQEBAQUA
|
||||||
|
A4IBDwAwggEKAoIBAQDOJT1xyumFexhTHkrBylat9yw5/GbdCJGOaRjQrrpLD2t7
|
||||||
|
2ARUZME666GykA55sIy3mkCEsscnpYU+GiFN+DkKRozpgs3HmDxtLjnHnKu56Fb2
|
||||||
|
4MNFHkB5BC9KeDTpW/SzDVYSk4EFIWQuZmEiKVzcZRPy5RpqqxV//KXkmidaAJJA
|
||||||
|
eRWDdIAG2IXfJKSV9/Ta9gPn/PKInEl+StC+9y1BVK8aE3KxT9aVpxYINvLy7786
|
||||||
|
1v1WbauKiT5dxky2zMRzrtNNC2hyvE4CCTAkFGpYdqtp8/h7Zrb/oP29u2BDBnAw
|
||||||
|
VtagV/aDCoKDP2dR7841YQJvSOuPYXg1ILhVwmqDAgMBAAGgLjAsBgkqhkiG9w0B
|
||||||
|
CQ4xHzAdMBsGA1UdEQQUMBKCCmZvb2Jhci5jb22HBAECAwQwDQYJKoZIhvcNAQEL
|
||||||
|
BQADggEBABuaL2t2Zcv9R72OH93EpIzgExL37odLUiIjTIIykK2TT/gb1LtnE1WK
|
||||||
|
THdqaLpnPot9IqBofppfkXPMrG7vavJoPlAp0lU2FHYIz64PHou8lj9yiXezDKDn
|
||||||
|
Jia3TOxCu5VTRnYT7Ypt8kSull/jlyBQgTP+P0YXwoYAXJteQr9O6yD75yWOAx3A
|
||||||
|
f/oQS77xoe0jdU4RkEMQRQQUIiaNyH8Bx4CeETmPoJDzEiIvnC5xDoySks1VJK7b
|
||||||
|
w11IANF7zO5UWtYv/i3+Wh5XLMJ0GIIVTpuGkeVaZCjB8goiBJXoaHOKsO5ygJo5
|
||||||
|
N+nxwiDTwUIZM+dU88mtCh0dvJeMMfA=
|
||||||
|
-----END CERTIFICATE REQUEST-----
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpQIBAAKCAQEAziU9ccrphXsYUx5KwcpWrfcsOfxm3QiRjmkY0K66Sw9re9gE
|
||||||
|
VGTBOuuhspAOebCMt5pAhLLHJ6WFPhohTfg5CkaM6YLNx5g8bS45x5yruehW9uDD
|
||||||
|
RR5AeQQvSng06Vv0sw1WEpOBBSFkLmZhIilc3GUT8uUaaqsVf/yl5JonWgCSQHkV
|
||||||
|
g3SABtiF3ySklff02vYD5/zyiJxJfkrQvvctQVSvGhNysU/WlacWCDby8u+/Otb9
|
||||||
|
Vm2riok+XcZMtszEc67TTQtocrxOAgkwJBRqWHarafP4e2a2/6D9vbtgQwZwMFbW
|
||||||
|
oFf2gwqCgz9nUe/ONWECb0jrj2F4NSC4VcJqgwIDAQABAoIBAQCmygix5gwU/KiM
|
||||||
|
r6iqrOx+6sq0y9vqIIGsaKo0RfriukIrvHacVbzl0DpPADFGEit4beyfsQpjsI9i
|
||||||
|
1L93l0uHXderIzMdt7XEXK9RKxjiXPLn4qj7ZmOhxloA9ctRuB3/NN4cP44XOZIV
|
||||||
|
3K3gdvj0NS/zyZwbC/tkR2Vt1a/bJ8DFfaFrSdk/btpVY2BH/uWjMls1BYIs3tEk
|
||||||
|
nroJYb+fyliC+n/QXrLQAPTVLfI3jyVjRYpW5b6mZHQk4SGDde1XbtqRCp/f6PLu
|
||||||
|
H8FQumd+SfrjgTwefphSWCW2H/aMNsZpL9NK/hmglKg3OcGKhwQswdmL9rDVFQmy
|
||||||
|
AqxXwxk5AoGBAPPmijvwg6bKwqeSCdhoYjQVNXdTbdda3arjC/G/vxhsVntXluOX
|
||||||
|
wGF2jInAu+sAShBFdhG4JlL+itlpA/aDDIMUxsF+YnhTOX5pofL8Nr4sstY6wjBf
|
||||||
|
4jVHQKLnaOm/mVpHqWABVv/M085XK7HHZR9e9y32ry1geRSiPF/E5I03AoGBANhf
|
||||||
|
OPL5WWO7TOchWci6qRcv0kZ47iSE1JxSGnXr+KIIBPIAgrwhYkqgrl6noSe3x1qB
|
||||||
|
tP7ZvmFWewxmo3mN2OwPTsxAhnjQK4D1PGJcsvf2A3f1uiwG+emqWpcMshTQnjda
|
||||||
|
Hi2krfMaHwErE+dEbZiilLDRYAMAWlQodXnhllMVAoGAKdDIumYN7DavENO05Glh
|
||||||
|
DNTmCcM//cASaQ3sKlJZjPJmEVd/Ax4tWYhdp/BnR28RQ6DlETylNW12mLesekMV
|
||||||
|
jhOtz9a/QynhnY62uVYMfKZlMt14FZsayU+iAUvzbL/wps3KeC9CnzCaz7GaSCyL
|
||||||
|
Zcl+T18PwZPcrnDyMOks1hkCgYEArrQUE3tpxbER4v12tTCiHuqp6eTyw+HMmXth
|
||||||
|
ih1B3/KBq7Tl2mlKJ9+daygGYz9sY5OfRLcjlQxyxgyJqjfyEog5o4nmCd5rgfCB
|
||||||
|
FRqsFrI5Er8B11K6rwSxqIzDrTLUzPSisU/qdAN/TT4vD+icZUXAsRQdZc7/IDya
|
||||||
|
vhJ7ghECgYEAiZ6v380l2jcEk+tm4UYZ+nXq3c2wq8mZkQcEDVpqvDGL8k/dPEq9
|
||||||
|
xOy7PVooQHQJyC2bgTbEKs6JYvzAYQmohiq7L3y9WxDQFJeImtzuNi0s/dwP+mBh
|
||||||
|
R6htM5JwAO3JnE9V863M6kvtLEIk7XptI5gC0kN3Thi70yT3lnU+emk=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIENzCCAh+gAwIBAgIQSCdvhIY3KZ9w9YRcDH1oRzANBgkqhkiG9w0BAQsFADAN
|
||||||
|
MQswCQYDVQQDEwJjYTAeFw0xNzA4MjUxNTQ1NTNaFw0yNzA4MjUxNTQ1NTJaMBEx
|
||||||
|
DzANBgNVBAMTBnNlcnZlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
||||||
|
ANlVMhHZHjRWiGqmpOse1KZTmdUaiSgl3T88+Mie17UbiLmsOfnkd3PuEnKlXRXM
|
||||||
|
sqOg15k9xfnV6SQebpBfkcwqJVH/USjWY4C1e2vDrva+j95L+uzDMZDF9nxpnjHE
|
||||||
|
uHT9+hnrmB3Xted0tzxRC/77Cht8Kn4gaoljbBoZsRnv0vRRUYKA2OJHJRRCHhzZ
|
||||||
|
AN6A+lWbWyvyvd3UeOLl3oFhk4lS5fwYY6RY8W9ZTJVxetVycvro42kK2jtpqUeF
|
||||||
|
NercfjOg8VBWYjqB3Ey1wHDpjS407TW7RWEGA+3mP8ZFsuoYr7Rs+z8LprbYEPpF
|
||||||
|
ojgvss+vMvjGkrcU8v0MR/sCAwEAAaOBjjCBizAOBgNVHQ8BAf8EBAMCA7gwHQYD
|
||||||
|
VR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBRS8x8oOcBJnrrW
|
||||||
|
jiS0t3qjKee63TAfBgNVHSMEGDAWgBTNBS7rmAcLgCfraHEZbvsVLjsOhTAaBgNV
|
||||||
|
HREEEzARgglsb2NhbGhvc3SHBH8AAAEwDQYJKoZIhvcNAQELBQADggIBAI9t4Pvn
|
||||||
|
o0cW0SrC4EFNJqUaffbqUNd3i+dBn5/EQc8EGYVY3k8E8iUMHzRDyH+/VRp3RUOH
|
||||||
|
oJDDS2uyeJ2InkC193MOpNJDQV5qf3yOmQCeVmmjcXkg1Nc+3oe8ttWiVW0ArFXS
|
||||||
|
oud6V7/6qzIz3850ypi0Zz4KLVhSGzaI8dnzPopqNEG+ZbgIwvQLqWhv2bLpqycY
|
||||||
|
5ANECpjH6QIEp8JNjga9Sp/LspNCqMDmVswBGarySNWZ1+uflg9X30hsdCzVPgX3
|
||||||
|
KMy0wVelT/4y893BCTo2KendGh70jaGxm8nBH7OXkeki2TI6boAvsn2Iash0HSZ4
|
||||||
|
hPacZ4QWFEYW4jZeu3ZNTJ9tc2u2jgpAGueOcWRY75CraLid1V5t1kpGxAtX4NvX
|
||||||
|
X56e/IlmEI6qPsaoPouQ1riAWMdRQUT1FLNPanv4vElDYXBNcFl7knuS14JDCC0M
|
||||||
|
K6ttSb2MxJYfC6J+OJpHQd2GWU5aO2uZcgi9jRMslNwR+R94bxI+q/bjrI31JsDz
|
||||||
|
1pVRnGRWH7cDejA2f+q7X8/uRuA8bfnqBcu0uI9YR64W0VMMLe41+iR0wt5N3yWr
|
||||||
|
/DalWIvmUvE8LoaGDwxV9T3xq1I1dWnASX+Xmb1SQ0CnhEEooZfQYfb3ffciG6mU
|
||||||
|
UvVC0YW1cjOgb193W/N+Dgju7/a/e+XgbsgT
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
-----BEGIN CERTIFICATE REQUEST-----
|
||||||
|
MIICgzCCAWsCAQAwETEPMA0GA1UEAxMGc2VydmVyMIIBIjANBgkqhkiG9w0BAQEF
|
||||||
|
AAOCAQ8AMIIBCgKCAQEA2VUyEdkeNFaIaqak6x7UplOZ1RqJKCXdPzz4yJ7XtRuI
|
||||||
|
uaw5+eR3c+4ScqVdFcyyo6DXmT3F+dXpJB5ukF+RzColUf9RKNZjgLV7a8Ou9r6P
|
||||||
|
3kv67MMxkMX2fGmeMcS4dP36GeuYHde153S3PFEL/vsKG3wqfiBqiWNsGhmxGe/S
|
||||||
|
9FFRgoDY4kclFEIeHNkA3oD6VZtbK/K93dR44uXegWGTiVLl/BhjpFjxb1lMlXF6
|
||||||
|
1XJy+ujjaQraO2mpR4U16tx+M6DxUFZiOoHcTLXAcOmNLjTtNbtFYQYD7eY/xkWy
|
||||||
|
6hivtGz7PwumttgQ+kWiOC+yz68y+MaStxTy/QxH+wIDAQABoC0wKwYJKoZIhvcN
|
||||||
|
AQkOMR4wHDAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEwDQYJKoZIhvcNAQEL
|
||||||
|
BQADggEBAA76r3SIQUER3XyGp4MOrfKGwBE7RnALcxW1XkrhID2bng2hzovrZZNO
|
||||||
|
xutL1zqPFCxClIKUxYAXpMeY8lnS6H8I6FoM6ALCZbK7q9rmMK198LPMo3zC6TsO
|
||||||
|
rEP8HOF9wxaYubg8xaq8iDlaL4e418M0UPOlE75PtkDAjhY++7ZTsjPVr/9WsJpZ
|
||||||
|
MmEZ5kSS59PZbMbyqXn5MxE0iSD0LfM+lmkIBwSvD8rjq3SQ5NKCg6CJkRRq7BVe
|
||||||
|
bujA2pPb6ivS5pujjIxkdUoz6S0G+ewZG16kbBoygWuRVFD8xqR9Pa41KSPhpx85
|
||||||
|
1qSrqR4zHvS3r+RS9UVIXnbh9ejW6Vg=
|
||||||
|
-----END CERTIFICATE REQUEST-----
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpAIBAAKCAQEA2VUyEdkeNFaIaqak6x7UplOZ1RqJKCXdPzz4yJ7XtRuIuaw5
|
||||||
|
+eR3c+4ScqVdFcyyo6DXmT3F+dXpJB5ukF+RzColUf9RKNZjgLV7a8Ou9r6P3kv6
|
||||||
|
7MMxkMX2fGmeMcS4dP36GeuYHde153S3PFEL/vsKG3wqfiBqiWNsGhmxGe/S9FFR
|
||||||
|
goDY4kclFEIeHNkA3oD6VZtbK/K93dR44uXegWGTiVLl/BhjpFjxb1lMlXF61XJy
|
||||||
|
+ujjaQraO2mpR4U16tx+M6DxUFZiOoHcTLXAcOmNLjTtNbtFYQYD7eY/xkWy6hiv
|
||||||
|
tGz7PwumttgQ+kWiOC+yz68y+MaStxTy/QxH+wIDAQABAoIBAEzhDVAw/LVI8wK/
|
||||||
|
JlGh21lm82Dl/SS9mDE5kUvunKGNNuVvXibewb65tb7mbjI68epeCEZGCtVg7RMA
|
||||||
|
zN23YOzW79K8vWnzxMkP6bPqSecw69WYDRBZ0BvFW3cRKYuzagjAmws2Qt4zoz5Y
|
||||||
|
FEV66gJtrVqhpqptLyKgj+n/sp1YiHMjkcJF5PGnAPudYxpiDHiPFked7vZtigyq
|
||||||
|
eE2GCVbaOmRGPMe28JzRmOuFqEN9GccRjlq+AuzYe14lWKa6fZLVke78luByF//M
|
||||||
|
RA+gGoKfHq859wACOEGbqShMWxC++y1HJ2Mu4adUlikzVq8rboinaAxzv56kc1n6
|
||||||
|
EDc/qPECgYEA2x+qxHKSJnQuItZp0CpsDk51yr1fJcWtdEbcgmomiwMwl/Nk0OAB
|
||||||
|
rplrW5gezyVRfMDsoqAnmBS301JEL/7QuEWvedTpptKC503Z+mCINPTZAaJfa7Jb
|
||||||
|
KUCSQHO3hThfOXFMkb9mcJEVVNqtobnK61tC+JNOSd00Z9TdLQAbcokCgYEA/ehf
|
||||||
|
Vq3md8bkQFlnMtFib8SIr8IP5j4JecFSeqa/OnBFfHJr0trUhoMO04uaQOl6lTT7
|
||||||
|
9Ca36WfJsv3EGkMHEFeceOsPswf65bI/qgxgUS7Qmu+NZTjvXL0gCpeHLPzEaLTV
|
||||||
|
CDXoo+YAJzzv9yWwVEvIIru5SJPVud6Gap5L1WMCgYAjfO91PXD6FVrbfYpJknVJ
|
||||||
|
o99j5GOihG9hI5DW9kYjwXJ/SYYMZhsfoe1HOk3TEqIt6Djq5bFD6icTbIFqnIRF
|
||||||
|
M9QFkTv+Lp3QxEUHTdcBbJ4wq5F0qcAl4DVPhu4z/zs83GKgQDVhCb5AreHtDWAV
|
||||||
|
2gPwqjrFr7OrFUh0302SsQKBgQCOHKZn9HNfLOIKJj/9kHYhCoZaoSqW+rgA/rQ0
|
||||||
|
U+oKQlaR/dTdsn9rPiVpP+S5WjSzGHHAyH79U4rv9Nryu/tTKUY5447o7JmAQJEj
|
||||||
|
k0PBjItTfKrOMdy/MlehtggBpQQlerkVnF62hYAmdhP1Z5HWzIea8SkWNzBTlPn0
|
||||||
|
6N6W8wKBgQCDBPXqGii/ur5IBQ+RASIL78RgIGH49XYcq7IrYFOuztGBbqf4qorR
|
||||||
|
BTl/mWFFHr+fAcLHlC/qTSBBflGClElqcg+j+92RAI6HbCFemdSbnsv5FCmciL6e
|
||||||
|
09x+oprKq3/WAVARUBif3RtDq92SFwSBfrx8JFhGIa9kUcsDFSBaDQ==
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
-----BEGIN X509 CRL-----
|
||||||
|
MIICgjBsAgEBMA0GCSqGSIb3DQEBCwUAMBMxETAPBgNVBAMTCHdyb25nLWNhFw0x
|
||||||
|
NzA4MjUxNTQ1NTdaFw0yNzA4MjUxNTQ1NTdaMACgIzAhMB8GA1UdIwQYMBaAFGM6
|
||||||
|
559SRpX6VJlKbIObtrLXvc1rMA0GCSqGSIb3DQEBCwUAA4ICAQA+6zsHCq9YRZ+a
|
||||||
|
fwsZmbGQqDUBVp5TWtDsy+qvKf/084CgTn0sR28HKEONQvX+R1CyzAaCGrkm081k
|
||||||
|
yDUizdyGrVR8zmCc7O3ztPobfZBmQXbR0pcxwweFiELBO1exEQ5IpM4J0KOPc+CB
|
||||||
|
AwVA227Q4oKrKyUtNQ9d3qr0/2E2HE8W0aV7Ax38MvXsUfafWk0SNPDusFiYNsTt
|
||||||
|
v50Gmd2yBlaMzT9Dsze9wuoTvT42lpCby/NSSDYynG9Cra2y0VoIpVxvPqVzn77L
|
||||||
|
1otkaQRatbfktaa1WVufEK81FXEyeYdM/T4bKCbB5oBKxmwwiS8ukvunc9gdrosx
|
||||||
|
/7QIlpr3iBEu+X+GeOdGyPA+a+S566Hil38QCyMUX4fI7xjYt3ek2MI1YeNEv576
|
||||||
|
CwEihc7NvPh5MI0OQq0nIjTcIeEGQag0eAaGwmJ+AmrZ+4bop5QavuwHVh/7sNel
|
||||||
|
rNhFucmx+gEPeS/Ae0cp2BeXjEXHmdfwKDCT1n1Rdd5wfLeFuKlBG8NdZKwZ7HH7
|
||||||
|
vwi+JwIBx/WJ/f1qcPlWAyF4Y/HUJIRRNSXJOUQCWhCvfGtLQ4xs7uo4ViE8CtyO
|
||||||
|
RE/3xHrX9UwJUymw50Efj3PpOxWPvJ9B7A+8ED1kJR29HQz5gVZhEcW6nDTntwDQ
|
||||||
|
5nrvhzi93xzaTQIhEe53uz+rTGs/fw==
|
||||||
|
-----END X509 CRL-----
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIE5jCCAs6gAwIBAgIBATANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDEwh3cm9u
|
||||||
|
Zy1jYTAeFw0xNzA4MjUxNTQ1NTRaFw0yNzA4MjUxNTQ1NTdaMBMxETAPBgNVBAMT
|
||||||
|
CHdyb25nLWNhMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzs91DHKf
|
||||||
|
enUfIPigN6n4CsTSHGMkRpWQw9T5vIqUd3ZgMJLCoEnj0OpVlFVcb9+P2/f5RYg8
|
||||||
|
H0dmM1iHGi5uCoDnJk7K5zaLMgxGtRy8dwZ1nHri3sZwM6Z9AyvktuSVbuElzp08
|
||||||
|
utwB+fstR2METN5Dovp738rdx2q3zYYYcfnEXcYvxBdxVeitFgrXauRDxuqMio+5
|
||||||
|
5bHVUpIWlpu8Fd3CqnMUS4N6McijIn6T2wiyALJDf9xE7edAYl4ExYVKaOyR3Ff3
|
||||||
|
+BhiS+IEPd9AoctU9JYFpDavfaiZz77AukwwfU+W93NTTFQ+rf/ev8XzsgkFyZPw
|
||||||
|
CCfKuet+o2/8MIxv4nwKxv6GGMFbQz1gNw3RqG5m19zppqVzp1vgMcNXSeRPFlQI
|
||||||
|
fXYFN9BY9bvx2L2ZpTn3gsdgzDGNzYU5fGro3YzmtelNBCY3sAz/riG6+wMDHCId
|
||||||
|
5K5NxrJBW3tTvEZQyKVZA1W22/F/Wz2LxA+4ZLhUoUuXTkJxLS75EWLkK2xK+IXv
|
||||||
|
h4s8n8CwfhFV/De7u18Pho0XKTm2IPir1nL0WNhjy4jvBYDN/Jy4fE4QALt4oH9t
|
||||||
|
+GITkDo0Zd/USZSkAOXgEb+Ks5F/fztI9yVp7/nhhj1KnSIrkObBaltlfEOe4vgz
|
||||||
|
3dNFAG5kH5lyG90uffKR7h1Vo7UkYcpJ3IMCAwEAAaNFMEMwDgYDVR0PAQH/BAQD
|
||||||
|
AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFGM6559SRpX6VJlKbIOb
|
||||||
|
trLXvc1rMA0GCSqGSIb3DQEBCwUAA4ICAQCIm4pRbel8V/W1OKSzJXg3F76ORIjz
|
||||||
|
zN4mAtCX8YtFQwawBlES25Ju2IQ9XfvqM0CPO5LEe1v8ZTXeke9Vjf/XGReBCCqy
|
||||||
|
/STzLSBHflQqvybMYH87K5h5e91Ow9T2HjyPtzS3RdyaahU/Y8/EnOTG89uJlpN3
|
||||||
|
0k0/KXfwVKAyjrOaoTeGPM9BjDssNq2S07h5C8sCby3MpR26CIMGbFnotwTjmSww
|
||||||
|
qkDSVd63/ZIB5/dOcOlBd1+rE3LOzYxDiZtKWu0NM+o7N0m4Y+gD4siyxRWuKslz
|
||||||
|
cTwiwnLmzZG5BUvRT2FmzCwejp45+LjrXmUZ8hCznk68hnkilx9XLdkBL/1qyk40
|
||||||
|
I1IUFQtkkcyznwUyKpC0z4VJZAVL8xi6KO60TOYtidxFTJxkPrWcAHvgzItao3XZ
|
||||||
|
C4hLlNk7RD6BJ8oyMtpXFq7MHAAb8MWSLu/rSAhQHoKqlCEK4Iks9nWLmRP0OdAw
|
||||||
|
BcXGMuTIn1jFRM0CQvg68GPFOH3FKv+cyUbjPoXvCBYiXKmxA/WX3rYvDo2paZKU
|
||||||
|
/mDMu+EdAR3Zk/wYXl4738ujqzO88Nw2LBHLKhXytHMaSbfmWf085r0L+fqHLuVM
|
||||||
|
jlpPEi6vQum25j9tvGnp6GyO8lUDAUqk5gtYIp+D67+NG+9eBocA1ADVpeKZHBQV
|
||||||
|
xGgCdjnoP+nDVw==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEJTCCAg2gAwIBAgIQINVcdjlrWhsKxwcXL4vbUDANBgkqhkiG9w0BAQsFADAT
|
||||||
|
MREwDwYDVQQDEwh3cm9uZy1jYTAeFw0xNzA4MjUxNTQ1NThaFw0yNzA4MjUxNTQ1
|
||||||
|
NTZaMBcxFTATBgNVBAMTDHdyb25nLWNsaWVudDCCASIwDQYJKoZIhvcNAQEBBQAD
|
||||||
|
ggEPADCCAQoCggEBANHAXfyT8vuNKc3FeedZH3865bF5PLbRs2R8CaUOdQj/HTM5
|
||||||
|
xSsb5Tr3x6IkWK+5SwtnGdaLY7GktSktXyUNf2uZflXHCLAqiqcBpLNO9mcFAACz
|
||||||
|
pRb7C18ZZ6d9b7UtPA5oK1Vt45iUzI+mdCC0BtRTWeyKdtTF4muD2TtF7RMQwnjf
|
||||||
|
RGV1EkfQ3sKpX3P7daiA/W116NlESpX3J/VAzoQu+3BrDeXrqgEbNVl+/NN/7uA0
|
||||||
|
RY0HxE75RNhL9yuz1VFP4/NaFOdWN3pSMKwcmiIeNC5n0eyW/fodQpOT/dcscfbP
|
||||||
|
3a0XeoZkfB8nuVcGkmtUkuw0jPy+R64vnQvlugsCAwEAAaNxMG8wDgYDVR0PAQH/
|
||||||
|
BAQDAgO4MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQU
|
||||||
|
0pX6qc7YwiehN5AYh+p4JNkFEXwwHwYDVR0jBBgwFoAUYzrnn1JGlfpUmUpsg5u2
|
||||||
|
ste9zWswDQYJKoZIhvcNAQELBQADggIBALH5pFNhbnro2vFE+8RqbRqOZZNoyKqL
|
||||||
|
INY/e0MNPmhp4CE2BQcrxgFcRgJmyh4lOP8gmIHT8q/9kOvYqMmfU1vbVF/XLFsa
|
||||||
|
NJxkQSX9uilV1LDykyRbwlI4McjdTW7rLEkW8YrZueMXnDYQHGx9L2qYWgXzA5yA
|
||||||
|
Mfsgq3pr39sDVDfYg1H+0daA3nIw+OWDsWjORXvzo5TQzjUXLhREp6WuuRKBT1+p
|
||||||
|
VHGAnUcwDEb6L1bWEloG9ogXJfsXuCUxF+/II1RYSKiAmjge1nDOM2USfIKfD5nz
|
||||||
|
tLJn0pn0B5dyceJTgOK6dwCXwn0Gc99qVzBSSHtPe+abSuY5dNoIwtL4R4rDE4U+
|
||||||
|
+y2vQwzum+GhHn/ZEDuYT/0+IDqkVxeWBiZ5IFEkRpBEFpmEJOdKWaSrIQWMpIjf
|
||||||
|
FIlxY3VzUD8H5M65kMSRKXbRJ1zSHMcIFKK2R98SPuYnYmgc4kOh49WkEr6dj2B1
|
||||||
|
0QNZxPg70HP3qWgCxf8F5Mxg5YOtz7gN6N3AutrlYV0KB/OT0h4lhtvW3inxRgID
|
||||||
|
iAHw0A4X/1qbFUeSUpINZaVQFtBh6fT/JfYDDTFFBcoqrLOZpFaS7r6FbGP3FDS4
|
||||||
|
v9MqsYOSA6LrOHMdRop+eDV718iGDcUVtIItRZZV0s4UTo5q0JpGBtVPo/R8ui19
|
||||||
|
eaGxvLJT1Gd+
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
-----BEGIN CERTIFICATE REQUEST-----
|
||||||
|
MIICXDCCAUQCAQAwFzEVMBMGA1UEAxMMd3JvbmctY2xpZW50MIIBIjANBgkqhkiG
|
||||||
|
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0cBd/JPy+40pzcV551kffzrlsXk8ttGzZHwJ
|
||||||
|
pQ51CP8dMznFKxvlOvfHoiRYr7lLC2cZ1otjsaS1KS1fJQ1/a5l+VccIsCqKpwGk
|
||||||
|
s072ZwUAALOlFvsLXxlnp31vtS08DmgrVW3jmJTMj6Z0ILQG1FNZ7Ip21MXia4PZ
|
||||||
|
O0XtExDCeN9EZXUSR9Dewqlfc/t1qID9bXXo2URKlfcn9UDOhC77cGsN5euqARs1
|
||||||
|
WX7803/u4DRFjQfETvlE2Ev3K7PVUU/j81oU51Y3elIwrByaIh40LmfR7Jb9+h1C
|
||||||
|
k5P91yxx9s/drRd6hmR8Hye5VwaSa1SS7DSM/L5Hri+dC+W6CwIDAQABoAAwDQYJ
|
||||||
|
KoZIhvcNAQELBQADggEBAMxscjfVRQ0/0c6f0MWtJJe+vy5Gj26XHVy5EsbH1ofq
|
||||||
|
eWF00CFlVw5CdznGV0NL6LOE+sz5sBKsN2sZU7xPeV5XRHVXpAuECcOcgWK6FkqA
|
||||||
|
wSmwVWZ93o+kJXrUZTyZBMkvQMUUr30JIpXIXJmLWKPBq5KRBJLirHZYw4FmbARv
|
||||||
|
iZdNlQ1rLOZKZl7yUVkAfyfw+ueb0OPFp/fzuCerNB0ySSmYdHzqDFsMLm4Bq/2z
|
||||||
|
FJOgasAU2RvxFVoRp7P/ZUSvtOKACMUHaYBnKZAvkKv3MIS/qLCSkzxNwsclT8uF
|
||||||
|
aKRFZnOkZZHvEaXVAwCfJB7tI3TELb+L2KyqU36Q1Oc=
|
||||||
|
-----END CERTIFICATE REQUEST-----
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEowIBAAKCAQEA0cBd/JPy+40pzcV551kffzrlsXk8ttGzZHwJpQ51CP8dMznF
|
||||||
|
KxvlOvfHoiRYr7lLC2cZ1otjsaS1KS1fJQ1/a5l+VccIsCqKpwGks072ZwUAALOl
|
||||||
|
FvsLXxlnp31vtS08DmgrVW3jmJTMj6Z0ILQG1FNZ7Ip21MXia4PZO0XtExDCeN9E
|
||||||
|
ZXUSR9Dewqlfc/t1qID9bXXo2URKlfcn9UDOhC77cGsN5euqARs1WX7803/u4DRF
|
||||||
|
jQfETvlE2Ev3K7PVUU/j81oU51Y3elIwrByaIh40LmfR7Jb9+h1Ck5P91yxx9s/d
|
||||||
|
rRd6hmR8Hye5VwaSa1SS7DSM/L5Hri+dC+W6CwIDAQABAoIBAHml4JyRTdX4q+sM
|
||||||
|
gcPcG3lVtkt0rfK1sh4wFgPlW5kpJE1GTwTOe+b0N5LhE5Jum4h0djbIxrwLc4n7
|
||||||
|
J3g82M6VygCDm5VYRuvO9y+LNzrOWo8NoUyvsouoF0a7aCMipfcRETjNr7cZbX5O
|
||||||
|
ooEpB+Dyqm+Wao7CaavDXySSTInGHG4AD9HM5nQsVIebS1HOkhI7SmNkZTOd3gzp
|
||||||
|
bR/iZgaYI5eC7Zj7hHNr4gWdRBuefU8wLZZGoqByHRTSrKwICRLIkGyoMRAD9p9r
|
||||||
|
S48lyUmd3BGHRPLmNl4u0kfsVlcCYBNKVZV6kkSVv4Wht/KVr3tl0+2wrNUe17w7
|
||||||
|
vlCsCPECgYEA9AdHq3UjswD6PYITCdrDOGVLMpgL0obA6X2Fi6GsMiRXQgsvxMVY
|
||||||
|
a4D2vtfZvLa8TSA+b8bK6uSMyD3mOHLxBkUPMQZxiHzq/ldK9vSIvzky0woF6suT
|
||||||
|
J8fa2F0QfjlKWhFMwf6JVGyZl+vmYqqF55lxRJSWGZHSSxaxYPU7dbkCgYEA3Aqb
|
||||||
|
YInpCp7zSlWYfXorwnyvsHsTdFbsoGGgMrc5l2B7PcP+Na7lm0dsNCrp7n7TSrE0
|
||||||
|
8iIoqhYq5u7RlGf5QXzXcGgZQMHcxLcrqBPviEktuUifXZEwfw9NXrlt4iF/oVTc
|
||||||
|
++7jqUZ+iH9NIMoxrQPXVdXJSJN9iwc7/yG6j+MCgYA+ehqsWCpSqx5mXwYW0M6I
|
||||||
|
gs6U3n6wYNXFMeDeFf9rOwioHQsW2tu/cl46EDNr8HEXYfj6TzAmoWs13TszGqKA
|
||||||
|
02+HQroQksLraVgFEChupOtRQtCvA33ignWSTYlqd6qEksdPJ6brWX6decUbX8M2
|
||||||
|
v39TaqNfWok3tlClnUOi6QKBgQCIhfQ9g5OZyWE937nLMH/yHZaMMvCxIDWUlL3m
|
||||||
|
eZQ7/dq5Sd9xw2AmZbwW6gFWvk2ubCBjkxoT3ckkm0xhfdlC7ohk79GrQh0N2HA3
|
||||||
|
ypa1wmGiMhLe5PRoAUCJ4xbwVMRxfsvVbDTIlDpxyjo6e/kyVc3HLevDIe+k0QpC
|
||||||
|
k9TC7QKBgDyghEPAM5euQHk2o7cMr0YK52vzoM1FGhrRAF5MhTYJEYBNu0Z+0McB
|
||||||
|
G8kwy4WH5zMuvKj1zZckAzkbpD/iL3XQuzs9pZdNnXzdf25/us0pZ2a/6v5+fpmF
|
||||||
|
JEuwQ1AztPEv4tLd3+xrmE+j+qd3xDqYt8eaWFswcuxchB6nUdqq
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
|
@ -0,0 +1,331 @@
|
||||||
|
package grpcurl_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials"
|
||||||
|
"google.golang.org/grpc/interop/grpc_testing"
|
||||||
|
|
||||||
|
. "github.com/fullstorydev/grpcurl"
|
||||||
|
grpcurl_testing "github.com/fullstorydev/grpcurl/testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPlainText(t *testing.T) {
|
||||||
|
e, err := createTestServerAndClient(nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to setup server and client: %v", err)
|
||||||
|
}
|
||||||
|
defer e.Close()
|
||||||
|
|
||||||
|
simpleTest(t, e.cc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasicTLS(t *testing.T) {
|
||||||
|
serverCreds, err := ServerTransportCredentials("", "testing/tls/server.crt", "testing/tls/server.key", false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create server creds: %v", err)
|
||||||
|
}
|
||||||
|
clientCreds, err := ClientTransportCredentials(false, "testing/tls/ca.crt", "", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create server creds: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
e, err := createTestServerAndClient(serverCreds, clientCreds)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to setup server and client: %v", err)
|
||||||
|
}
|
||||||
|
defer e.Close()
|
||||||
|
|
||||||
|
simpleTest(t, e.cc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInsecureClientTLS(t *testing.T) {
|
||||||
|
serverCreds, err := ServerTransportCredentials("", "testing/tls/server.crt", "testing/tls/server.key", false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create server creds: %v", err)
|
||||||
|
}
|
||||||
|
clientCreds, err := ClientTransportCredentials(true, "", "", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create server creds: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
e, err := createTestServerAndClient(serverCreds, clientCreds)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to setup server and client: %v", err)
|
||||||
|
}
|
||||||
|
defer e.Close()
|
||||||
|
|
||||||
|
simpleTest(t, e.cc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientCertTLS(t *testing.T) {
|
||||||
|
serverCreds, err := ServerTransportCredentials("testing/tls/ca.crt", "testing/tls/server.crt", "testing/tls/server.key", false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create server creds: %v", err)
|
||||||
|
}
|
||||||
|
clientCreds, err := ClientTransportCredentials(false, "testing/tls/ca.crt", "testing/tls/client.crt", "testing/tls/client.key")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create server creds: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
e, err := createTestServerAndClient(serverCreds, clientCreds)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to setup server and client: %v", err)
|
||||||
|
}
|
||||||
|
defer e.Close()
|
||||||
|
|
||||||
|
simpleTest(t, e.cc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequireClientCertTLS(t *testing.T) {
|
||||||
|
serverCreds, err := ServerTransportCredentials("testing/tls/ca.crt", "testing/tls/server.crt", "testing/tls/server.key", true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create server creds: %v", err)
|
||||||
|
}
|
||||||
|
clientCreds, err := ClientTransportCredentials(false, "testing/tls/ca.crt", "testing/tls/client.crt", "testing/tls/client.key")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create server creds: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
e, err := createTestServerAndClient(serverCreds, clientCreds)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to setup server and client: %v", err)
|
||||||
|
}
|
||||||
|
defer e.Close()
|
||||||
|
|
||||||
|
simpleTest(t, e.cc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBrokenTLS_ClientPlainText(t *testing.T) {
|
||||||
|
serverCreds, err := ServerTransportCredentials("", "testing/tls/server.crt", "testing/tls/server.key", false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create server creds: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// client connection succeeds since client is not waiting for TLS handshake
|
||||||
|
e, err := createTestServerAndClient(serverCreds, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to setup server and client: %v", err)
|
||||||
|
}
|
||||||
|
defer e.Close()
|
||||||
|
|
||||||
|
// but request fails because server closes connection upon seeing request
|
||||||
|
// bytes that are not a TLS handshake
|
||||||
|
cl := grpc_testing.NewTestServiceClient(e.cc)
|
||||||
|
_, err = cl.UnaryCall(context.Background(), &grpc_testing.SimpleRequest{})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expecting failure")
|
||||||
|
}
|
||||||
|
// various errors possible when server closes connection
|
||||||
|
if !strings.Contains(err.Error(), "transport is closing") &&
|
||||||
|
!strings.Contains(err.Error(), "connection is unavailable") &&
|
||||||
|
!strings.Contains(err.Error(), "use of closed network connection") {
|
||||||
|
|
||||||
|
t.Fatalf("expecting transport failure, got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBrokenTLS_ServerPlainText(t *testing.T) {
|
||||||
|
clientCreds, err := ClientTransportCredentials(false, "testing/tls/ca.crt", "", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create server creds: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
e, err := createTestServerAndClient(nil, clientCreds)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expecting TLS failure setting up server and client")
|
||||||
|
e.Close()
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "first record does not look like a TLS handshake") {
|
||||||
|
t.Fatalf("expecting TLS handshake failure, got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBrokenTLS_ServerUsesWrongCert(t *testing.T) {
|
||||||
|
serverCreds, err := ServerTransportCredentials("", "testing/tls/other.crt", "testing/tls/other.key", false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create server creds: %v", err)
|
||||||
|
}
|
||||||
|
clientCreds, err := ClientTransportCredentials(false, "testing/tls/ca.crt", "", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create server creds: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
e, err := createTestServerAndClient(serverCreds, clientCreds)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expecting TLS failure setting up server and client")
|
||||||
|
e.Close()
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "certificate is valid for") {
|
||||||
|
t.Fatalf("expecting TLS certificate error, got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBrokenTLS_ClientHasExpiredCert(t *testing.T) {
|
||||||
|
serverCreds, err := ServerTransportCredentials("testing/tls/ca.crt", "testing/tls/server.crt", "testing/tls/server.key", false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create server creds: %v", err)
|
||||||
|
}
|
||||||
|
clientCreds, err := ClientTransportCredentials(false, "testing/tls/ca.crt", "testing/tls/expired.crt", "testing/tls/expired.key")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create server creds: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
e, err := createTestServerAndClient(serverCreds, clientCreds)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expecting TLS failure setting up server and client")
|
||||||
|
e.Close()
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "bad certificate") {
|
||||||
|
t.Fatalf("expecting TLS certificate error, got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBrokenTLS_ServerHasExpiredCert(t *testing.T) {
|
||||||
|
serverCreds, err := ServerTransportCredentials("", "testing/tls/expired.crt", "testing/tls/expired.key", false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create server creds: %v", err)
|
||||||
|
}
|
||||||
|
clientCreds, err := ClientTransportCredentials(false, "testing/tls/ca.crt", "", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create server creds: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
e, err := createTestServerAndClient(serverCreds, clientCreds)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expecting TLS failure setting up server and client")
|
||||||
|
e.Close()
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "certificate has expired or is not yet valid") {
|
||||||
|
t.Fatalf("expecting TLS certificate expired, got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBrokenTLS_ClientNotTrusted(t *testing.T) {
|
||||||
|
serverCreds, err := ServerTransportCredentials("testing/tls/ca.crt", "testing/tls/server.crt", "testing/tls/server.key", true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create server creds: %v", err)
|
||||||
|
}
|
||||||
|
clientCreds, err := ClientTransportCredentials(false, "testing/tls/ca.crt", "testing/tls/wrong-client.crt", "testing/tls/wrong-client.key")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create server creds: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
e, err := createTestServerAndClient(serverCreds, clientCreds)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expecting TLS failure setting up server and client")
|
||||||
|
e.Close()
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "bad certificate") {
|
||||||
|
t.Fatalf("expecting TLS certificate error, got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBrokenTLS_ServerNotTrusted(t *testing.T) {
|
||||||
|
serverCreds, err := ServerTransportCredentials("", "testing/tls/server.crt", "testing/tls/server.key", false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create server creds: %v", err)
|
||||||
|
}
|
||||||
|
clientCreds, err := ClientTransportCredentials(false, "", "testing/tls/client.crt", "testing/tls/client.key")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create server creds: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
e, err := createTestServerAndClient(serverCreds, clientCreds)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expecting TLS failure setting up server and client")
|
||||||
|
e.Close()
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "certificate signed by unknown authority") {
|
||||||
|
t.Fatalf("expecting TLS certificate error, got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBrokenTLS_RequireClientCertButNonePresented(t *testing.T) {
|
||||||
|
serverCreds, err := ServerTransportCredentials("testing/tls/ca.crt", "testing/tls/server.crt", "testing/tls/server.key", true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create server creds: %v", err)
|
||||||
|
}
|
||||||
|
clientCreds, err := ClientTransportCredentials(false, "testing/tls/ca.crt", "", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create server creds: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
e, err := createTestServerAndClient(serverCreds, clientCreds)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expecting TLS failure setting up server and client")
|
||||||
|
e.Close()
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "bad certificate") {
|
||||||
|
t.Fatalf("expecting TLS certificate error, got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func simpleTest(t *testing.T, cc *grpc.ClientConn) {
|
||||||
|
cl := grpc_testing.NewTestServiceClient(cc)
|
||||||
|
_, err := cl.UnaryCall(context.Background(), &grpc_testing.SimpleRequest{})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("simple RPC failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTestServerAndClient(serverCreds, clientCreds credentials.TransportCredentials) (testEnv, error) {
|
||||||
|
var e testEnv
|
||||||
|
completed := false
|
||||||
|
defer func() {
|
||||||
|
if !completed {
|
||||||
|
e.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var svrOpts []grpc.ServerOption
|
||||||
|
if serverCreds != nil {
|
||||||
|
svrOpts = []grpc.ServerOption{grpc.Creds(serverCreds)}
|
||||||
|
}
|
||||||
|
svr := grpc.NewServer(svrOpts...)
|
||||||
|
grpc_testing.RegisterTestServiceServer(svr, grpcurl_testing.TestServer{})
|
||||||
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
return e, err
|
||||||
|
}
|
||||||
|
port := l.Addr().(*net.TCPAddr).Port
|
||||||
|
go svr.Serve(l)
|
||||||
|
|
||||||
|
cliOpts := []grpc.DialOption{grpc.WithTimeout(2 * time.Second), grpc.WithBlock()}
|
||||||
|
if clientCreds != nil {
|
||||||
|
cliOpts = append(cliOpts, grpc.WithTransportCredentials(clientCreds))
|
||||||
|
} else {
|
||||||
|
cliOpts = append(cliOpts, grpc.WithInsecure())
|
||||||
|
}
|
||||||
|
cc, err := grpc.Dial(fmt.Sprintf("127.0.0.1:%d", port), cliOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return e, err
|
||||||
|
}
|
||||||
|
|
||||||
|
e.svr = svr
|
||||||
|
e.cc = cc
|
||||||
|
completed = true
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type testEnv struct {
|
||||||
|
svr *grpc.Server
|
||||||
|
cc *grpc.ClientConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e testEnv) Close() {
|
||||||
|
if e.cc != nil {
|
||||||
|
e.cc.Close()
|
||||||
|
e.cc = nil
|
||||||
|
}
|
||||||
|
if e.svr != nil {
|
||||||
|
e.svr.GracefulStop()
|
||||||
|
e.svr = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue