Compare commits
195 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
c54eac28fd | |
|
|
6caf0e77fa | |
|
|
1ad1dc15dd | |
|
|
f575e91b2c | |
|
|
ed672b2bc9 | |
|
|
eab6c910a6 | |
|
|
60e53f304f | |
|
|
f093930c85 | |
|
|
7ad93a42d9 | |
|
|
7155fb6211 | |
|
|
f28d506cea | |
|
|
58ccc6321e | |
|
|
b519ffc959 | |
|
|
3a8fa31879 | |
|
|
614b1687cf | |
|
|
30f87c1323 | |
|
|
78655b4786 | |
|
|
d00c28104b | |
|
|
9e3e083f29 | |
|
|
c32936d71e | |
|
|
f3c8ec2564 | |
|
|
7e1a6c9068 | |
|
|
b9a11e9fea | |
|
|
bc5cf811a0 | |
|
|
fb49f049e6 | |
|
|
cdb43b08fa | |
|
|
56181ba330 | |
|
|
46c38b351a | |
|
|
a05d48d6dd | |
|
|
fc63514da1 | |
|
|
e14d9f769a | |
|
|
239dde4a62 | |
|
|
80e833a557 | |
|
|
6fccd7757e | |
|
|
400fa5f2d3 | |
|
|
0e13e85e65 | |
|
|
07361b21ea | |
|
|
8e76884d21 | |
|
|
805ce40c63 | |
|
|
93ea011b36 | |
|
|
5592211a41 | |
|
|
184c8f70b5 | |
|
|
149a93e0ec | |
|
|
252b57fd45 | |
|
|
24b80dfed8 | |
|
|
334e3f56de | |
|
|
f4157743ed | |
|
|
79fb35f680 | |
|
|
7ccaf0a21f | |
|
|
6093b09afa | |
|
|
70c215f7e2 | |
|
|
28c0ee28f0 | |
|
|
bc2944de97 | |
|
|
7a845ca5e9 | |
|
|
c17f0782f7 | |
|
|
42f63028d4 | |
|
|
743e60a4c9 | |
|
|
b7a5d3bba8 | |
|
|
2a29c1e64b | |
|
|
9a59bed1d2 | |
|
|
ae7dadff19 | |
|
|
3961a33e7f | |
|
|
d5b8e4d4ce | |
|
|
0efcfa65f2 | |
|
|
fae58803d9 | |
|
|
1fda47eb90 | |
|
|
031cd3d1e7 | |
|
|
a5037bdf4a | |
|
|
4775fb574b | |
|
|
b96cb4ddd0 | |
|
|
006918123d | |
|
|
66a2405833 | |
|
|
dfd889a44b | |
|
|
a3a5bcd8ab | |
|
|
61a9c00d87 | |
|
|
85f1cbf7ad | |
|
|
dd2f60135c | |
|
|
81c624c41f | |
|
|
3826617999 | |
|
|
8d60b618ac | |
|
|
47b842a805 | |
|
|
898bdad041 | |
|
|
25c896aa59 | |
|
|
0d0992e6a2 | |
|
|
8093376ece | |
|
|
fec466efa6 | |
|
|
7f919e6459 | |
|
|
0df5c93e8d | |
|
|
353e0953cb | |
|
|
c9ac3da95f | |
|
|
ea24a77215 | |
|
|
c3fbeaff64 | |
|
|
7860209a53 | |
|
|
b953ea196a | |
|
|
683a7fb09c | |
|
|
8bb6eeb0d0 | |
|
|
805e231182 | |
|
|
aa5998a119 | |
|
|
b34b13bab3 | |
|
|
b890db745f | |
|
|
57a2cd1a09 | |
|
|
ae7473c7a7 | |
|
|
b3f576068c | |
|
|
8ee6c9423b | |
|
|
4b65b3ee55 | |
|
|
9ac7e3a34d | |
|
|
76bbedeed0 | |
|
|
b9d2d8cfa8 | |
|
|
30b8cd1531 | |
|
|
a8f79c8751 | |
|
|
bf9b13d6c5 | |
|
|
c3bde04cc1 | |
|
|
2e9ba5024e | |
|
|
127194b205 | |
|
|
cd242fe1ed | |
|
|
59a32e5eb0 | |
|
|
7e1cd16164 | |
|
|
de25c89822 | |
|
|
f1d396c31e | |
|
|
1986364acd | |
|
|
bdf97bc934 | |
|
|
2f55ac63a4 | |
|
|
8d7770a962 | |
|
|
1f34448998 | |
|
|
06a970022e | |
|
|
db90ec1160 | |
|
|
9da71fbe53 | |
|
|
9846afccbc | |
|
|
ba5f667e13 | |
|
|
54ffdcacda | |
|
|
ceef817807 | |
|
|
e544b9e66f | |
|
|
41a6ac0479 | |
|
|
f37ec641c5 | |
|
|
8e51c5e2d3 | |
|
|
8376c2f7bb | |
|
|
0162fa9726 | |
|
|
b8c67b7a4e | |
|
|
ff114930fd | |
|
|
5ad5edb29c | |
|
|
44547153b3 | |
|
|
2108c8f0b3 | |
|
|
36008aa111 | |
|
|
50833f1b21 | |
|
|
939766fb42 | |
|
|
b58182a88d | |
|
|
8e2cf9b3c2 | |
|
|
36f9e53dfd | |
|
|
bfbbed1d42 | |
|
|
153d36db8c | |
|
|
2af40876fc | |
|
|
0218a7db67 | |
|
|
96cfd48e32 | |
|
|
d30f3a01b7 | |
|
|
0d669e78d0 | |
|
|
9572bd4525 | |
|
|
ccc9007156 | |
|
|
9248ea0963 | |
|
|
4054d1d115 | |
|
|
5631bba117 | |
|
|
80425d1b17 | |
|
|
7e4045565f | |
|
|
e5b4fc6cc0 | |
|
|
09c3d1d69e | |
|
|
5d6316f470 | |
|
|
f0723c6273 | |
|
|
fe97274a1b | |
|
|
1bbf8dae71 | |
|
|
0fcd3253f6 | |
|
|
4c9c82cec3 | |
|
|
5082a1dc68 | |
|
|
d641a66208 | |
|
|
ce84976d3c | |
|
|
b292d5aef8 | |
|
|
5516a45602 | |
|
|
4a329f3b13 | |
|
|
1c6532c060 | |
|
|
0f9e76c978 | |
|
|
9fa2fce63b | |
|
|
70e9bba1b8 | |
|
|
d86529bb4f | |
|
|
0dea37ee70 | |
|
|
dfa06f4410 | |
|
|
22ce2f04fd | |
|
|
1e8e50f4f8 | |
|
|
7cabe7a9d0 | |
|
|
9a4bbacdd6 | |
|
|
69ea782936 | |
|
|
58cd93280e | |
|
|
a337c1afcf | |
|
|
e00ef3eb7c | |
|
|
397a8c18ca | |
|
|
554e69be2c | |
|
|
2dd771c49e | |
|
|
79a550b858 |
|
|
@ -0,0 +1,40 @@
|
|||
shared_configs:
|
||||
simple_job_steps: &simple_job_steps
|
||||
- checkout
|
||||
- run:
|
||||
name: Run tests
|
||||
command: |
|
||||
make test
|
||||
|
||||
# Use the latest 2.1 version of CircleCI pipeline process engine. See: https://circleci.com/docs/2.0/configuration-reference
|
||||
version: 2.1
|
||||
jobs:
|
||||
build-1-23:
|
||||
working_directory: ~/repo
|
||||
docker:
|
||||
- image: cimg/go:1.23
|
||||
steps: *simple_job_steps
|
||||
|
||||
build-1-24:
|
||||
working_directory: ~/repo
|
||||
docker:
|
||||
- image: cimg/go:1.24
|
||||
steps: *simple_job_steps
|
||||
|
||||
build-1-25:
|
||||
working_directory: ~/repo
|
||||
docker:
|
||||
- image: cimg/go:1.25
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Run tests and linters
|
||||
command: |
|
||||
make ci
|
||||
|
||||
workflows:
|
||||
pr-build-test:
|
||||
jobs:
|
||||
- build-1-23
|
||||
- build-1-24
|
||||
- build-1-25
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
# Check for updates once a week
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
# This workflow was added by CodeSee. Learn more at https://codesee.io/
|
||||
# This is v2.0 of this workflow file
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
name: CodeSee
|
||||
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
codesee:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
name: Analyze the repo with CodeSee
|
||||
steps:
|
||||
- uses: Codesee-io/codesee-action@v2
|
||||
with:
|
||||
codesee-token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
dist/
|
||||
.idea/
|
||||
VERSION
|
||||
.tmp/
|
||||
*.snap
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
builds:
|
||||
- binary: grpcurl
|
||||
main: ./cmd/grpcurl
|
||||
goos:
|
||||
- linux
|
||||
- darwin
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
- 386
|
||||
- arm
|
||||
- arm64
|
||||
- s390x
|
||||
- ppc64le
|
||||
goarm:
|
||||
- 5
|
||||
- 6
|
||||
- 7
|
||||
ignore:
|
||||
- goos: darwin
|
||||
goarch: 386
|
||||
- goos: windows
|
||||
goarch: arm64
|
||||
- goos: darwin
|
||||
goarch: arm
|
||||
- goos: windows
|
||||
goarch: arm
|
||||
- goos: darwin
|
||||
goarch: s390x
|
||||
- goos: windows
|
||||
goarch: s390x
|
||||
- goos: darwin
|
||||
goarch: ppc64le
|
||||
- goos: windows
|
||||
goarch: ppc64le
|
||||
ldflags:
|
||||
- -s -w -X main.version=v{{.Version}}
|
||||
|
||||
archives:
|
||||
- format: tar.gz
|
||||
name_template: >-
|
||||
{{ .Binary }}_{{ .Version }}_
|
||||
{{- if eq .Os "darwin" }}osx{{ else }}{{ .Os }}{{ end }}_
|
||||
{{- if eq .Arch "amd64" }}x86_64
|
||||
{{- else if eq .Arch "386" }}x86_32
|
||||
{{- else }}{{ .Arch }}{{ end }}
|
||||
{{- with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
files:
|
||||
- LICENSE
|
||||
|
||||
nfpms:
|
||||
- vendor: Fullstory
|
||||
homepage: https://github.com/fullstorydev/grpcurl/
|
||||
maintainer: Engineering at Fullstory <fixme@fixme>
|
||||
description: 'Like cURL, but for gRPC: Command-line tool for interacting with gRPC servers'
|
||||
license: MIT
|
||||
id: nfpms
|
||||
formats:
|
||||
- deb
|
||||
- rpm
|
||||
18
.travis.yml
18
.travis.yml
|
|
@ -1,18 +0,0 @@
|
|||
language: go
|
||||
sudo: false
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- go: "1.7"
|
||||
- go: "1.8"
|
||||
- go: "1.9"
|
||||
- go: "1.10"
|
||||
env: VET=1
|
||||
- go: "1.11"
|
||||
env: GO111MODULE=off
|
||||
- go: "1.11"
|
||||
env: GO111MODULE=on
|
||||
- go: tip
|
||||
|
||||
script:
|
||||
- if [[ "$VET" = 1 ]]; then make; else make deps test; fi
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
FROM golang:1.25-alpine AS builder
|
||||
LABEL maintainer="Fullstory Engineering"
|
||||
|
||||
# create non-privileged group and user
|
||||
RUN addgroup -S grpcurl && adduser -S grpcurl -G grpcurl
|
||||
|
||||
WORKDIR /tmp/fullstorydev/grpcurl
|
||||
# copy just the files/sources we need to build grpcurl
|
||||
COPY VERSION *.go go.* /tmp/fullstorydev/grpcurl/
|
||||
COPY cmd /tmp/fullstorydev/grpcurl/cmd
|
||||
# and build a completely static binary (so we can use
|
||||
# scratch as basis for the final image)
|
||||
ENV CGO_ENABLED=0
|
||||
ENV GO111MODULE=on
|
||||
RUN go build -o /grpcurl \
|
||||
-ldflags "-w -extldflags \"-static\" -X \"main.version=$(cat VERSION)\"" \
|
||||
./cmd/grpcurl
|
||||
|
||||
FROM alpine:3 AS alpine
|
||||
WORKDIR /
|
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
COPY --from=builder /etc/passwd /etc/passwd
|
||||
COPY --from=builder /grpcurl /bin/grpcurl
|
||||
USER grpcurl
|
||||
|
||||
ENTRYPOINT ["/bin/grpcurl"]
|
||||
|
||||
# New FROM so we have a nice'n'tiny image
|
||||
FROM scratch
|
||||
WORKDIR /
|
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
COPY --from=builder /etc/passwd /etc/passwd
|
||||
COPY --from=builder /grpcurl /bin/grpcurl
|
||||
USER grpcurl
|
||||
|
||||
ENTRYPOINT ["/bin/grpcurl"]
|
||||
4
LICENSE
4
LICENSE
|
|
@ -1,6 +1,6 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 FullStory, Inc
|
||||
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
|
||||
|
|
@ -18,4 +18,4 @@ 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.
|
||||
SOFTWARE.
|
||||
|
|
|
|||
71
Makefile
71
Makefile
|
|
@ -1,22 +1,60 @@
|
|||
dev_build_version=$(shell git describe --tags --always --dirty)
|
||||
|
||||
export PATH := $(shell pwd)/.tmp/protoc/bin:$(PATH)
|
||||
export PROTOC_VERSION := 22.0
|
||||
# Disable CGO for improved compatibility across distros
|
||||
export CGO_ENABLED=0
|
||||
export GOFLAGS=-trimpath
|
||||
export GOWORK=off
|
||||
|
||||
# TODO: run golint and errcheck, but only to catch *new* violations and
|
||||
# decide whether to change code or not (e.g. we need to be able to whitelist
|
||||
# violations already in the code). They can be useful to catch errors, but
|
||||
# they are just too noisy to be a requirement for a CI -- we don't even *want*
|
||||
# to fix some of the things they consider to be violations.
|
||||
.PHONY: ci
|
||||
ci: deps checkgofmt vet staticcheck unused ineffassign predeclared test
|
||||
ci: deps checkgofmt checkgenerate vet staticcheck ineffassign predeclared test
|
||||
|
||||
.PHONY: deps
|
||||
deps:
|
||||
go get -d -v -t ./...
|
||||
go mod tidy
|
||||
|
||||
.PHONY: updatedeps
|
||||
updatedeps:
|
||||
go get -d -v -t -u -f ./...
|
||||
go mod tidy
|
||||
|
||||
.PHONY: install
|
||||
install:
|
||||
go install ./...
|
||||
go install -ldflags '-X "main.version=dev build $(dev_build_version)"' ./...
|
||||
|
||||
.PHONY: release
|
||||
release:
|
||||
@go install github.com/goreleaser/goreleaser@v1.21.0
|
||||
goreleaser release --clean
|
||||
|
||||
.PHONY: docker
|
||||
docker:
|
||||
@echo $(dev_build_version) > VERSION
|
||||
docker build -t fullstorydev/grpcurl:$(dev_build_version) .
|
||||
@rm VERSION
|
||||
|
||||
.PHONY: generate
|
||||
generate: .tmp/protoc/bin/protoc
|
||||
@go install google.golang.org/protobuf/cmd/protoc-gen-go@a709e31e5d12
|
||||
@go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1.0
|
||||
@go install github.com/jhump/protoreflect/desc/sourceinfo/cmd/protoc-gen-gosrcinfo@v1.14.1
|
||||
go generate ./...
|
||||
go mod tidy
|
||||
|
||||
.PHONY: checkgenerate
|
||||
checkgenerate: generate
|
||||
git status --porcelain -- '**/*.go'
|
||||
@if [ -n "$$(git status --porcelain -- '**/*.go')" ]; then \
|
||||
git diff -- '**/*.go'; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
.PHONY: checkgofmt
|
||||
checkgofmt:
|
||||
|
|
@ -31,36 +69,35 @@ vet:
|
|||
|
||||
.PHONY: staticcheck
|
||||
staticcheck:
|
||||
@go get honnef.co/go/tools/cmd/staticcheck
|
||||
staticcheck ./...
|
||||
|
||||
.PHONY: unused
|
||||
unused:
|
||||
@go get honnef.co/go/tools/cmd/unused
|
||||
unused ./...
|
||||
@go install honnef.co/go/tools/cmd/staticcheck@2025.1.1
|
||||
staticcheck -checks "inherit,-SA1019" ./...
|
||||
|
||||
.PHONY: ineffassign
|
||||
ineffassign:
|
||||
@go get github.com/gordonklaus/ineffassign
|
||||
@go install github.com/gordonklaus/ineffassign@7953dde2c7bf
|
||||
ineffassign .
|
||||
|
||||
.PHONY: predeclared
|
||||
predeclared:
|
||||
@go get github.com/nishanths/predeclared
|
||||
predeclared .
|
||||
@go install github.com/nishanths/predeclared@51e8c974458a0f93dc03fe356f91ae1a6d791e6f
|
||||
predeclared ./...
|
||||
|
||||
# Intentionally omitted from CI, but target here for ad-hoc reports.
|
||||
.PHONY: golint
|
||||
golint:
|
||||
@go get github.com/golang/lint/golint
|
||||
@go install golang.org/x/lint/golint@v0.0.0-20210508222113-6edffad5e616
|
||||
golint -min_confidence 0.9 -set_exit_status ./...
|
||||
|
||||
# Intentionally omitted from CI, but target here for ad-hoc reports.
|
||||
.PHONY: errchack
|
||||
.PHONY: errcheck
|
||||
errcheck:
|
||||
@go get github.com/kisielk/errcheck
|
||||
@go install github.com/kisielk/errcheck@v1.2.0
|
||||
errcheck ./...
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
go test -race ./...
|
||||
test: deps
|
||||
CGO_ENABLED=1 go test -race ./...
|
||||
|
||||
.tmp/protoc/bin/protoc: ./Makefile ./download_protoc.sh
|
||||
./download_protoc.sh
|
||||
|
||||
|
|
|
|||
138
README.md
138
README.md
|
|
@ -1,6 +1,7 @@
|
|||
# gRPCurl
|
||||
[](https://travis-ci.org/fullstorydev/grpcurl/branches)
|
||||
[](https://circleci.com/gh/fullstorydev/grpcurl/tree/master)
|
||||
[](https://goreportcard.com/report/github.com/fullstorydev/grpcurl)
|
||||
[](https://snapcraft.io/grpcurl)
|
||||
|
||||
`grpcurl` is a command-line tool that lets you interact with gRPC servers. It's
|
||||
basically `curl` for gRPC servers.
|
||||
|
|
@ -14,52 +15,98 @@ This program accepts messages using JSON encoding, which is much more friendly f
|
|||
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),
|
||||
by reading proto source files, or by loading in compiled "protoset" files (files that contain encoded file
|
||||
[descriptor protos](https://github.com/google/protobuf/blob/master/src/google/protobuf/descriptor.proto)).
|
||||
a server that supports [server reflection](https://github.com/grpc/grpc/blob/master/src/proto/grpc/reflection/v1/reflection.proto),
|
||||
by reading proto source files, or by loading in compiled "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 either need the proto source files that define the service or need
|
||||
protoset files that `grpcurl` can use.
|
||||
|
||||
[Examples for how to set up server reflection can be found here.](https://github.com/grpc/grpc/blob/master/doc/server-reflection.md#known-implementations)
|
||||
|
||||
This repo also provides a library package, `github.com/fullstorydev/grpcurl`, that has
|
||||
functions for simplifying the construction of other command-line tools that dynamically
|
||||
invoke gRPC endpoints. This code is 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.
|
||||
|
||||
See also the [`grpcurl` talk at GopherCon 2018](https://www.youtube.com/watch?v=dDr-8kbMnaw).
|
||||
|
||||
## 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.
|
||||
`grpcurl` supports both secure/TLS servers _and_ plain-text servers (i.e. no TLS) 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 can supply the `.proto` source files or you can supply protoset
|
||||
files (containing compiled descriptors, produced by `protoc`) to `grpcurl`.
|
||||
|
||||
## Installation
|
||||
You can use the `go` tool to install `grpcurl`:
|
||||
|
||||
### Binaries
|
||||
|
||||
Download the binary from the [releases](https://github.com/fullstorydev/grpcurl/releases) page.
|
||||
|
||||
### Homebrew (macOS)
|
||||
|
||||
On macOS, `grpcurl` is available via Homebrew:
|
||||
```shell
|
||||
go get github.com/fullstorydev/grpcurl
|
||||
go install github.com/fullstorydev/grpcurl/cmd/grpcurl
|
||||
brew install grpcurl
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
For platforms that support Docker, you can download an image that lets you run `grpcurl`:
|
||||
```shell
|
||||
# Download image
|
||||
docker pull fullstorydev/grpcurl:latest
|
||||
# Run the tool
|
||||
docker run fullstorydev/grpcurl api.grpc.me:443 list
|
||||
```
|
||||
Note that there are some pitfalls when using docker:
|
||||
- If you need to interact with a server listening on the host's loopback network, you must specify the host as `host.docker.internal` instead of `localhost` (for Mac or Windows) _OR_ have the container use the host network with `-network="host"` (Linux only).
|
||||
- If you need to provide proto source files or descriptor sets, you must mount the folder containing the files as a volume (`-v $(pwd):/protos`) and adjust the import paths to container paths accordingly.
|
||||
- If you want to provide the request message via stdin, using the `-d @` option, you need to use the `-i` flag on the docker command.
|
||||
|
||||
### Other Packages
|
||||
|
||||
There are numerous other ways to install `grpcurl`, thanks to support from third parties that
|
||||
have created recipes/packages for it. These include other ways to install `grpcurl` on a variety
|
||||
of environments, including Windows and myriad Linux distributions.
|
||||
|
||||
You can see more details and the full list of other packages for `grpcurl` at _repology.org_:
|
||||
https://repology.org/project/grpcurl/information
|
||||
|
||||
### Snap
|
||||
|
||||
You can install `grpcurl` using the snap package:
|
||||
|
||||
`snap install grpcurl`
|
||||
|
||||
### From Source
|
||||
If you already have the [Go SDK](https://golang.org/doc/install) installed, you can use the `go`
|
||||
tool to install `grpcurl`:
|
||||
```shell
|
||||
go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
|
||||
```
|
||||
|
||||
This installs the command into the `bin` sub-folder of wherever your `$GOPATH`
|
||||
environment variable points. If this directory is already in your `$PATH`, then
|
||||
you should be good to go.
|
||||
environment variable points. (If you have no `GOPATH` environment variable set,
|
||||
the default install location is `$HOME/go/bin`). If this directory is already in
|
||||
your `$PATH`, then you should be good to go.
|
||||
|
||||
If you have already pulled down this repo to a location that is not in your
|
||||
`$GOPATH` and want to build from the sources, you can `cd` into the repo and then
|
||||
run `make install`.
|
||||
|
||||
If you encounter compile errors, you could have out-dated versions of `grpcurl`'s
|
||||
dependencies. You can update the dependencies by running `make updatedeps`.
|
||||
If you encounter compile errors and are using a version of the Go SDK older than 1.13,
|
||||
you could have out-dated versions of `grpcurl`'s dependencies. You can update the
|
||||
dependencies by running `make updatedeps`. Or, if you are using Go 1.11 or 1.12, you
|
||||
can add `GO111MODULE=on` as a prefix to the commands above, which will also build using
|
||||
the right versions of dependencies (vs. whatever you may already have in your `GOPATH`).
|
||||
|
||||
## Usage
|
||||
The usage doc for the tool explains the numerous options:
|
||||
|
|
@ -72,10 +119,13 @@ In the sections below, you will find numerous examples demonstrating how to use
|
|||
|
||||
### Invoking RPCs
|
||||
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
|
||||
that requires no client certs and supports server reflection is the simplest thing to
|
||||
do with `grpcurl`. This minimal invocation sends an empty request body:
|
||||
```shell
|
||||
grpcurl grpc.server.com:443 my.custom.server.Service/Method
|
||||
|
||||
# no TLS
|
||||
grpcurl -plaintext grpc.server.com:80 my.custom.server.Service/Method
|
||||
```
|
||||
|
||||
To send a non-empty request, use the `-d` argument. Note that all arguments must come
|
||||
|
|
@ -92,7 +142,7 @@ If you want to include `grpcurl` in a command pipeline, such as when using `jq`
|
|||
create a request body, you can use `-d @`, which tells `grpcurl` to read the actual
|
||||
request body from stdin:
|
||||
```shell
|
||||
grpcurl -d @ grpc.server.com:443 my.custom.server.Service/Method <<<EOM
|
||||
grpcurl -d @ grpc.server.com:443 my.custom.server.Service/Method <<EOM
|
||||
{
|
||||
"id": 1234,
|
||||
"tags": [
|
||||
|
|
@ -102,6 +152,13 @@ grpcurl -d @ grpc.server.com:443 my.custom.server.Service/Method <<<EOM
|
|||
}
|
||||
EOM
|
||||
```
|
||||
### Adding Headers/Metadata to Request
|
||||
Adding of headers / metadata to a rpc request is possible via the `-H name:value` command line option. Multiple headers can be added in a similar fashion.
|
||||
Example :
|
||||
```shell
|
||||
grpcurl -H header1:value1 -H header2:value2 -d '{"id": 1234, "tags": ["foo","bar"]}' grpc.server.com:443 my.custom.server.Service/Method
|
||||
```
|
||||
For more usage guide, check out the help docs via `grpcurl -help`
|
||||
|
||||
### Listing Services
|
||||
To list all services exposed by a server, use the "list" verb. When using `.proto` source
|
||||
|
|
@ -116,6 +173,13 @@ grpcurl -protoset my-protos.bin list
|
|||
|
||||
# Using proto sources
|
||||
grpcurl -import-path ../protos -proto my-stuff.proto list
|
||||
|
||||
# Export proto files (use -proto-out-dir to specify the output directory)
|
||||
grpcurl -plaintext -proto-out-dir "out_protos" "localhost:8787" describe my.custom.server.Service
|
||||
|
||||
# Export protoset file (use -protoset-out to specify the output file)
|
||||
grpcurl -plaintext -protoset-out "out.protoset" "localhost:8787" describe my.custom.server.Service
|
||||
|
||||
```
|
||||
|
||||
The "list" verb also lets you see all methods in a particular service:
|
||||
|
|
@ -125,8 +189,10 @@ grpcurl localhost:8787 list my.custom.server.Service
|
|||
|
||||
### Describing Elements
|
||||
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.
|
||||
or that is found in a given protoset file. It also prints a description of that
|
||||
symbol, in the form of snippets of proto source. It won't necessarily be the
|
||||
original source that defined the element, but it will be equivalent.
|
||||
|
||||
```shell
|
||||
# Server supports reflection
|
||||
grpcurl localhost:8787 describe my.custom.server.Service.MethodOne
|
||||
|
|
@ -138,7 +204,24 @@ grpcurl -protoset my-protos.bin describe my.custom.server.Service.MethodOne
|
|||
grpcurl -import-path ../protos -proto my-stuff.proto describe my.custom.server.Service.MethodOne
|
||||
```
|
||||
|
||||
## Proto Source Files
|
||||
## Descriptor Sources
|
||||
The `grpcurl` tool can operate on a variety of sources for descriptors. The descriptors
|
||||
are required, in order for `grpcurl` to understand the RPC schema, translate inputs
|
||||
into the protobuf binary format as well as translate responses from the binary format
|
||||
into text. The sections below document the supported sources and what command-line flags
|
||||
are needed to use them.
|
||||
|
||||
### Server Reflection
|
||||
|
||||
Without any additional command-line flags, `grpcurl` will try to use [server reflection](https://github.com/grpc/grpc/blob/master/src/proto/grpc/reflection/v1/reflection.proto).
|
||||
|
||||
Examples for how to set up server reflection can be found [here](https://github.com/grpc/grpc/blob/master/doc/server-reflection.md#known-implementations).
|
||||
|
||||
When using reflection, the server address (host:port or path to Unix socket) is required
|
||||
even for "list" and "describe" operations, so that `grpcurl` can connect to the server
|
||||
and ask it for its descriptors.
|
||||
|
||||
### Proto Source Files
|
||||
To use `grpcurl` on servers that do not support reflection, you can use `.proto` source
|
||||
files.
|
||||
|
||||
|
|
@ -151,14 +234,18 @@ location of the standard protos included with `protoc` (which contain various "w
|
|||
types" with a package definition of `google.protobuf`). These files are "known" by `grpcurl`
|
||||
as a snapshot of their descriptors is built into the `grpcurl` binary.
|
||||
|
||||
## Protoset Files
|
||||
When using proto sources, you can omit the server address (host:port or path to Unix socket)
|
||||
when using the "list" and "describe" operations since they only need to consult the proto
|
||||
source files.
|
||||
|
||||
### Protoset Files
|
||||
You can also use compiled protoset files with `grpcurl`. If you are scripting `grpcurl` and
|
||||
need to re-use the same proto sources for many invocations, you will see better performance
|
||||
by using protoset files (since it skips the parsing and compilation steps with each
|
||||
invocation).
|
||||
|
||||
Protoset files contain binary encoded `google.protobuf.FileDescriptorSet` protos. To create
|
||||
a protoset file, invoke `protoc` with the `*.proto` files that describe the service:
|
||||
a protoset file, invoke `protoc` with the `*.proto` files that define the service:
|
||||
```shell
|
||||
protoc --proto_path=. \
|
||||
--descriptor_set_out=myservice.protoset \
|
||||
|
|
@ -169,3 +256,8 @@ protoc --proto_path=. \
|
|||
The `--descriptor_set_out` argument is what tells `protoc` to produce a protoset,
|
||||
and the `--include_imports` argument is necessary for the protoset to contain
|
||||
everything that `grpcurl` needs to process and understand the schema.
|
||||
|
||||
When using protosets, you can omit the server address (host:port or path to Unix socket)
|
||||
when using the "list" and "describe" operations since they only need to consult the
|
||||
protoset files.
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,46 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFlagDocIndent(t *testing.T) {
|
||||
// Tests the prettify() and indent() function. The indent() function
|
||||
// differs by Go version, due to differences in "flags" package across
|
||||
// versions. Run with multiple versions of Go to ensure that doc output
|
||||
// is properly indented, regardless of Go version.
|
||||
|
||||
var fs flag.FlagSet
|
||||
var buf bytes.Buffer
|
||||
fs.SetOutput(&buf)
|
||||
|
||||
fs.String("foo", "", prettify(`
|
||||
This is a flag doc string.
|
||||
It has multiple lines.
|
||||
More than two, actually.`))
|
||||
fs.Int("bar", 100, prettify(`This is a simple flag doc string.`))
|
||||
fs.Bool("baz", false, prettify(`
|
||||
This is another long doc string.
|
||||
It also has multiple lines. But not as long as the first one.`))
|
||||
|
||||
fs.PrintDefaults()
|
||||
|
||||
expected :=
|
||||
` -bar int
|
||||
This is a simple flag doc string. (default 100)
|
||||
-baz
|
||||
This is another long doc string.
|
||||
It also has multiple lines. But not as long as the first one.
|
||||
-foo string
|
||||
This is a flag doc string.
|
||||
It has multiple lines.
|
||||
More than two, actually.
|
||||
`
|
||||
|
||||
actual := buf.String()
|
||||
if actual != expected {
|
||||
t.Errorf("Flag output had wrong indentation.\nExpecting:\n%s\nGot:\n%s", expected, actual)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,11 @@
|
|||
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || windows
|
||||
// +build darwin dragonfly freebsd linux netbsd openbsd solaris windows
|
||||
|
||||
package main
|
||||
|
||||
import "flag"
|
||||
|
||||
var (
|
||||
unix = flag.Bool("unix", false,
|
||||
`Indicates that the server address is the path to a Unix domain socket.`)
|
||||
unix = flags.Bool("unix", false, prettify(`
|
||||
Indicates that the server address is the path to a Unix domain socket.`))
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,369 @@
|
|||
package grpcurl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/golang/protobuf/proto" //lint:ignore SA1019 we have to import these because some of their types appear in exported API
|
||||
"github.com/jhump/protoreflect/desc" //lint:ignore SA1019 same as above
|
||||
"github.com/jhump/protoreflect/desc/protoparse" //lint:ignore SA1019 same as above
|
||||
"github.com/jhump/protoreflect/desc/protoprint"
|
||||
"github.com/jhump/protoreflect/dynamic" //lint:ignore SA1019 same as above
|
||||
"github.com/jhump/protoreflect/grpcreflect"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/descriptorpb"
|
||||
)
|
||||
|
||||
// ErrReflectionNotSupported is returned by DescriptorSource operations that
|
||||
// rely on interacting with the reflection service when the source does not
|
||||
// actually expose the reflection service. When this occurs, an alternate source
|
||||
// (like file descriptor sets) must be used.
|
||||
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 := &descriptorpb.FileDescriptorSet{}
|
||||
for _, fileName := range fileNames {
|
||||
b, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not load protoset file %q: %v", fileName, err)
|
||||
}
|
||||
var fs descriptorpb.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...)
|
||||
}
|
||||
return DescriptorSourceFromFileDescriptorSet(files)
|
||||
}
|
||||
|
||||
// DescriptorSourceFromProtoFiles creates a DescriptorSource that is backed by the named files,
|
||||
// whose contents are Protocol Buffer source files. The given importPaths are used to locate
|
||||
// any imported files.
|
||||
func DescriptorSourceFromProtoFiles(importPaths []string, fileNames ...string) (DescriptorSource, error) {
|
||||
fileNames, err := protoparse.ResolveFilenames(importPaths, fileNames...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := protoparse.Parser{
|
||||
ImportPaths: importPaths,
|
||||
InferImportPaths: len(importPaths) == 0,
|
||||
IncludeSourceCodeInfo: true,
|
||||
}
|
||||
fds, err := p.ParseFiles(fileNames...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse given files: %v", err)
|
||||
}
|
||||
return DescriptorSourceFromFileDescriptors(fds...)
|
||||
}
|
||||
|
||||
// DescriptorSourceFromFileDescriptorSet creates a DescriptorSource that is backed by the FileDescriptorSet.
|
||||
func DescriptorSourceFromFileDescriptorSet(files *descriptorpb.FileDescriptorSet) (DescriptorSource, error) {
|
||||
unresolved := map[string]*descriptorpb.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]*descriptorpb.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, 0, 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
|
||||
}
|
||||
|
||||
// DescriptorSourceFromFileDescriptors creates a DescriptorSource that is backed by the given
|
||||
// file descriptors
|
||||
func DescriptorSourceFromFileDescriptors(files ...*desc.FileDescriptor) (DescriptorSource, error) {
|
||||
fds := map[string]*desc.FileDescriptor{}
|
||||
for _, fd := range files {
|
||||
if err := addFile(fd, fds); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &fileSource{files: fds}, nil
|
||||
}
|
||||
|
||||
func addFile(fd *desc.FileDescriptor, fds map[string]*desc.FileDescriptor) error {
|
||||
name := fd.GetName()
|
||||
if existing, ok := fds[name]; ok {
|
||||
// already added this file
|
||||
if existing != fd {
|
||||
// doh! duplicate files provided
|
||||
return fmt.Errorf("given files include multiple copies of %q", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
fds[name] = fd
|
||||
for _, dep := range fd.GetDependencies() {
|
||||
if err := addFile(dep, fds); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return 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
|
||||
}
|
||||
|
||||
// GetAllFiles returns all of the underlying file descriptors. This is
|
||||
// more thorough and more efficient than the fallback strategy used by
|
||||
// the GetAllFiles package method, for enumerating all files from a
|
||||
// descriptor source.
|
||||
func (fs *fileSource) GetAllFiles() ([]*desc.FileDescriptor, error) {
|
||||
files := make([]*desc.FileDescriptor, len(fs.files))
|
||||
i := 0
|
||||
for _, fd := range fs.files {
|
||||
files[i] = fd
|
||||
i++
|
||||
}
|
||||
return files, 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(_ 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
|
||||
}
|
||||
|
||||
// WriteProtoset will use the given descriptor source to resolve all of the given
|
||||
// symbols and write a proto file descriptor set with their definitions to the
|
||||
// given output. The output will include descriptors for all files in which the
|
||||
// symbols are defined as well as their transitive dependencies.
|
||||
func WriteProtoset(out io.Writer, descSource DescriptorSource, symbols ...string) error {
|
||||
filenames, fds, err := getFileDescriptors(symbols, descSource)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// now expand that to include transitive dependencies in topologically sorted
|
||||
// order (such that file always appears after its dependencies)
|
||||
expandedFiles := make(map[string]struct{}, len(fds))
|
||||
allFilesSlice := make([]*descriptorpb.FileDescriptorProto, 0, len(fds))
|
||||
for _, filename := range filenames {
|
||||
allFilesSlice = addFilesToSet(allFilesSlice, expandedFiles, fds[filename])
|
||||
}
|
||||
// now we can serialize to file
|
||||
b, err := proto.Marshal(&descriptorpb.FileDescriptorSet{File: allFilesSlice})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to serialize file descriptor set: %v", err)
|
||||
}
|
||||
if _, err := out.Write(b); err != nil {
|
||||
return fmt.Errorf("failed to write file descriptor set: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func addFilesToSet(allFiles []*descriptorpb.FileDescriptorProto, expanded map[string]struct{}, fd *desc.FileDescriptor) []*descriptorpb.FileDescriptorProto {
|
||||
if _, ok := expanded[fd.GetName()]; ok {
|
||||
// already seen this one
|
||||
return allFiles
|
||||
}
|
||||
expanded[fd.GetName()] = struct{}{}
|
||||
// add all dependencies first
|
||||
for _, dep := range fd.GetDependencies() {
|
||||
allFiles = addFilesToSet(allFiles, expanded, dep)
|
||||
}
|
||||
return append(allFiles, fd.AsFileDescriptorProto())
|
||||
}
|
||||
|
||||
// WriteProtoFiles will use the given descriptor source to resolve all the given
|
||||
// symbols and write proto files with their definitions to the given output directory.
|
||||
func WriteProtoFiles(outProtoDirPath string, descSource DescriptorSource, symbols ...string) error {
|
||||
filenames, fds, err := getFileDescriptors(symbols, descSource)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// now expand that to include transitive dependencies in topologically sorted
|
||||
// order (such that file always appears after its dependencies)
|
||||
expandedFiles := make(map[string]struct{}, len(fds))
|
||||
allFileDescriptors := make([]*desc.FileDescriptor, 0, len(fds))
|
||||
for _, filename := range filenames {
|
||||
allFileDescriptors = addFilesToFileDescriptorList(allFileDescriptors, expandedFiles, fds[filename])
|
||||
}
|
||||
pr := protoprint.Printer{}
|
||||
// now we can serialize to files
|
||||
for i := range allFileDescriptors {
|
||||
if err := writeProtoFile(outProtoDirPath, allFileDescriptors[i], &pr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeProtoFile(outProtoDirPath string, fd *desc.FileDescriptor, pr *protoprint.Printer) error {
|
||||
outFile := filepath.Join(outProtoDirPath, fd.GetFullyQualifiedName())
|
||||
outDir := filepath.Dir(outFile)
|
||||
if err := os.MkdirAll(outDir, 0777); err != nil {
|
||||
return fmt.Errorf("failed to create directory %q: %w", outDir, err)
|
||||
}
|
||||
|
||||
f, err := os.Create(outFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create proto file %q: %w", outFile, err)
|
||||
}
|
||||
defer f.Close()
|
||||
if err := pr.PrintProtoFile(fd, f); err != nil {
|
||||
return fmt.Errorf("failed to write proto file %q: %w", outFile, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getFileDescriptors(symbols []string, descSource DescriptorSource) ([]string, map[string]*desc.FileDescriptor, error) {
|
||||
// compute set of file descriptors
|
||||
filenames := make([]string, 0, len(symbols))
|
||||
fds := make(map[string]*desc.FileDescriptor, len(symbols))
|
||||
for _, sym := range symbols {
|
||||
d, err := descSource.FindSymbol(sym)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to find descriptor for %q: %v", sym, err)
|
||||
}
|
||||
fd := d.GetFile()
|
||||
if _, ok := fds[fd.GetName()]; !ok {
|
||||
fds[fd.GetName()] = fd
|
||||
filenames = append(filenames, fd.GetName())
|
||||
}
|
||||
}
|
||||
return filenames, fds, nil
|
||||
}
|
||||
|
||||
func addFilesToFileDescriptorList(allFiles []*desc.FileDescriptor, expanded map[string]struct{}, fd *desc.FileDescriptor) []*desc.FileDescriptor {
|
||||
if _, ok := expanded[fd.GetName()]; ok {
|
||||
// already seen this one
|
||||
return allFiles
|
||||
}
|
||||
expanded[fd.GetName()] = struct{}{}
|
||||
// add all dependencies first
|
||||
for _, dep := range fd.GetDependencies() {
|
||||
allFiles = addFilesToFileDescriptorList(allFiles, expanded, dep)
|
||||
}
|
||||
return append(allFiles, fd)
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package grpcurl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/proto" //lint:ignore SA1019 we have to import this because it appears in exported API
|
||||
"google.golang.org/protobuf/types/descriptorpb"
|
||||
)
|
||||
|
||||
func TestWriteProtoset(t *testing.T) {
|
||||
exampleProtoset, err := loadProtoset("./internal/testing/example.protoset")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to load example.protoset: %v", err)
|
||||
}
|
||||
testProtoset, err := loadProtoset("./internal/testing/test.protoset")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to load test.protoset: %v", err)
|
||||
}
|
||||
|
||||
mergedProtoset := &descriptorpb.FileDescriptorSet{
|
||||
File: append(exampleProtoset.File, testProtoset.File...),
|
||||
}
|
||||
|
||||
descSrc, err := DescriptorSourceFromFileDescriptorSet(mergedProtoset)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create descriptor source: %v", err)
|
||||
}
|
||||
|
||||
checkWriteProtoset(t, descSrc, exampleProtoset, "TestService")
|
||||
checkWriteProtoset(t, descSrc, testProtoset, "testing.TestService")
|
||||
checkWriteProtoset(t, descSrc, mergedProtoset, "TestService", "testing.TestService")
|
||||
}
|
||||
|
||||
func loadProtoset(path string) (*descriptorpb.FileDescriptorSet, error) {
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var protoset descriptorpb.FileDescriptorSet
|
||||
if err := proto.Unmarshal(b, &protoset); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &protoset, nil
|
||||
}
|
||||
|
||||
func checkWriteProtoset(t *testing.T, descSrc DescriptorSource, protoset *descriptorpb.FileDescriptorSet, symbols ...string) {
|
||||
var buf bytes.Buffer
|
||||
if err := WriteProtoset(&buf, descSrc, symbols...); err != nil {
|
||||
t.Fatalf("failed to write protoset: %v", err)
|
||||
}
|
||||
|
||||
var result descriptorpb.FileDescriptorSet
|
||||
if err := proto.Unmarshal(buf.Bytes(), &result); err != nil {
|
||||
t.Fatalf("failed to unmarshal written protoset: %v", err)
|
||||
}
|
||||
|
||||
if !proto.Equal(protoset, &result) {
|
||||
t.Fatalf("written protoset not equal to input:\nExpecting: %s\nActual: %s", protoset, &result)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
cd $(dirname $0)
|
||||
|
||||
if [[ -z "$PROTOC_VERSION" ]]; then
|
||||
echo "Set PROTOC_VERSION env var to indicate the version to download" >&2
|
||||
exit 1
|
||||
fi
|
||||
PROTOC_OS="$(uname -s)"
|
||||
PROTOC_ARCH="$(uname -m)"
|
||||
case "${PROTOC_OS}" in
|
||||
Darwin) PROTOC_OS="osx" ;;
|
||||
Linux) PROTOC_OS="linux" ;;
|
||||
*)
|
||||
echo "Invalid value for uname -s: ${PROTOC_OS}" >&2
|
||||
exit 1
|
||||
esac
|
||||
|
||||
# This is for macs with M1 chips. Precompiled binaries for osx/amd64 are not available for download, so for that case
|
||||
# we download the x86_64 version instead. This will work as long as rosetta2 is installed.
|
||||
if [ "$PROTOC_OS" = "osx" ] && [ "$PROTOC_ARCH" = "arm64" ]; then
|
||||
PROTOC_ARCH="x86_64"
|
||||
fi
|
||||
|
||||
PROTOC="${PWD}/.tmp/protoc/bin/protoc"
|
||||
|
||||
if [[ "$(${PROTOC} --version 2>/dev/null)" != "libprotoc 3.${PROTOC_VERSION}" ]]; then
|
||||
rm -rf ./.tmp/protoc
|
||||
mkdir -p .tmp/protoc
|
||||
curl -L "https://github.com/google/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-${PROTOC_OS}-${PROTOC_ARCH}.zip" > .tmp/protoc/protoc.zip
|
||||
pushd ./.tmp/protoc && unzip protoc.zip && popd
|
||||
fi
|
||||
|
||||
|
|
@ -0,0 +1,554 @@
|
|||
package grpcurl
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/golang/protobuf/jsonpb" //lint:ignore SA1019 we have to import these because some of their types appear in exported API
|
||||
"github.com/golang/protobuf/proto" //lint:ignore SA1019 same as above
|
||||
"github.com/jhump/protoreflect/desc" //lint:ignore SA1019 same as above
|
||||
"github.com/jhump/protoreflect/dynamic" //lint:ignore SA1019 same as above
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// RequestParser processes input into messages.
|
||||
type RequestParser interface {
|
||||
// Next parses input data into the given request message. If called after
|
||||
// input is exhausted, it returns io.EOF. If the caller re-uses the same
|
||||
// instance in multiple calls to Next, it should call msg.Reset() in between
|
||||
// each call.
|
||||
Next(msg proto.Message) error
|
||||
// NumRequests returns the number of messages that have been parsed and
|
||||
// returned by a call to Next.
|
||||
NumRequests() int
|
||||
}
|
||||
|
||||
type jsonRequestParser struct {
|
||||
dec *json.Decoder
|
||||
unmarshaler jsonpb.Unmarshaler
|
||||
requestCount int
|
||||
}
|
||||
|
||||
// NewJSONRequestParser returns a RequestParser that reads data in JSON format
|
||||
// from the given reader. The given resolver is used to assist with decoding of
|
||||
// google.protobuf.Any messages.
|
||||
//
|
||||
// Input data that contains more than one message should just include all
|
||||
// messages concatenated (though whitespace is necessary to separate some kinds
|
||||
// of values in JSON).
|
||||
//
|
||||
// If the given reader has no data, the returned parser will return io.EOF on
|
||||
// the very first call.
|
||||
func NewJSONRequestParser(in io.Reader, resolver jsonpb.AnyResolver) RequestParser {
|
||||
return &jsonRequestParser{
|
||||
dec: json.NewDecoder(in),
|
||||
unmarshaler: jsonpb.Unmarshaler{AnyResolver: resolver},
|
||||
}
|
||||
}
|
||||
|
||||
// NewJSONRequestParserWithUnmarshaler is like NewJSONRequestParser but
|
||||
// accepts a protobuf jsonpb.Unmarshaler instead of jsonpb.AnyResolver.
|
||||
func NewJSONRequestParserWithUnmarshaler(in io.Reader, unmarshaler jsonpb.Unmarshaler) RequestParser {
|
||||
return &jsonRequestParser{
|
||||
dec: json.NewDecoder(in),
|
||||
unmarshaler: unmarshaler,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *jsonRequestParser) Next(m proto.Message) error {
|
||||
var msg json.RawMessage
|
||||
if err := f.dec.Decode(&msg); err != nil {
|
||||
return err
|
||||
}
|
||||
f.requestCount++
|
||||
return f.unmarshaler.Unmarshal(bytes.NewReader(msg), m)
|
||||
}
|
||||
|
||||
func (f *jsonRequestParser) NumRequests() int {
|
||||
return f.requestCount
|
||||
}
|
||||
|
||||
const (
|
||||
textSeparatorChar = '\x1e'
|
||||
)
|
||||
|
||||
type textRequestParser struct {
|
||||
r *bufio.Reader
|
||||
err error
|
||||
requestCount int
|
||||
}
|
||||
|
||||
// NewTextRequestParser returns a RequestParser that reads data in the protobuf
|
||||
// text format from the given reader.
|
||||
//
|
||||
// Input data that contains more than one message should include an ASCII
|
||||
// 'Record Separator' character (0x1E) between each message.
|
||||
//
|
||||
// Empty text is a valid text format and represents an empty message. So if the
|
||||
// given reader has no data, the returned parser will yield an empty message
|
||||
// for the first call to Next and then return io.EOF thereafter. This also means
|
||||
// that if the input data ends with a record separator, then a final empty
|
||||
// message will be parsed *after* the separator.
|
||||
func NewTextRequestParser(in io.Reader) RequestParser {
|
||||
return &textRequestParser{r: bufio.NewReader(in)}
|
||||
}
|
||||
|
||||
func (f *textRequestParser) Next(m proto.Message) error {
|
||||
if f.err != nil {
|
||||
return f.err
|
||||
}
|
||||
|
||||
var b []byte
|
||||
b, f.err = f.r.ReadBytes(textSeparatorChar)
|
||||
if f.err != nil && f.err != io.EOF {
|
||||
return f.err
|
||||
}
|
||||
// remove delimiter
|
||||
if len(b) > 0 && b[len(b)-1] == textSeparatorChar {
|
||||
b = b[:len(b)-1]
|
||||
}
|
||||
|
||||
f.requestCount++
|
||||
|
||||
return proto.UnmarshalText(string(b), m)
|
||||
}
|
||||
|
||||
func (f *textRequestParser) NumRequests() int {
|
||||
return f.requestCount
|
||||
}
|
||||
|
||||
// Formatter translates messages into string representations.
|
||||
type Formatter func(proto.Message) (string, error)
|
||||
|
||||
// NewJSONFormatter returns a formatter that returns JSON strings. The JSON will
|
||||
// include empty/default values (instead of just omitted them) if emitDefaults
|
||||
// is true. The given resolver is used to assist with encoding of
|
||||
// google.protobuf.Any messages.
|
||||
func NewJSONFormatter(emitDefaults bool, resolver jsonpb.AnyResolver) Formatter {
|
||||
marshaler := jsonpb.Marshaler{
|
||||
EmitDefaults: emitDefaults,
|
||||
AnyResolver: resolver,
|
||||
}
|
||||
// Workaround for indentation issue in jsonpb with Any messages.
|
||||
// Bug was originally fixed in https://github.com/golang/protobuf/pull/834
|
||||
// but later re-introduced before the module was deprecated and frozen.
|
||||
// If jsonpb is ever replaced with google.golang.org/protobuf/encoding/protojson
|
||||
// this workaround will no longer be needed.
|
||||
formatter := func(message proto.Message) (string, error) {
|
||||
output, err := marshaler.MarshalToString(message)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := json.Indent(&buf, []byte(output), "", " "); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
return formatter
|
||||
}
|
||||
|
||||
// NewTextFormatter returns a formatter that returns strings in the protobuf
|
||||
// text format. If includeSeparator is true then, when invoked to format
|
||||
// multiple messages, all messages after the first one will be prefixed with the
|
||||
// ASCII 'Record Separator' character (0x1E).
|
||||
func NewTextFormatter(includeSeparator bool) Formatter {
|
||||
tf := textFormatter{useSeparator: includeSeparator}
|
||||
return tf.format
|
||||
}
|
||||
|
||||
type textFormatter struct {
|
||||
useSeparator bool
|
||||
numFormatted int
|
||||
}
|
||||
|
||||
var protoTextMarshaler = proto.TextMarshaler{ExpandAny: true}
|
||||
|
||||
func (tf *textFormatter) format(m proto.Message) (string, error) {
|
||||
var buf bytes.Buffer
|
||||
if tf.useSeparator && tf.numFormatted > 0 {
|
||||
if err := buf.WriteByte(textSeparatorChar); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// If message implements MarshalText method (such as a *dynamic.Message),
|
||||
// it won't get details about whether or not to format to text compactly
|
||||
// or with indentation. So first see if the message also implements a
|
||||
// MarshalTextIndent method and use that instead if available.
|
||||
type indentMarshaler interface {
|
||||
MarshalTextIndent() ([]byte, error)
|
||||
}
|
||||
|
||||
if indenter, ok := m.(indentMarshaler); ok {
|
||||
b, err := indenter.MarshalTextIndent()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err := buf.Write(b); err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else if err := protoTextMarshaler.Marshal(&buf, m); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// no trailing newline needed
|
||||
str := buf.String()
|
||||
if len(str) > 0 && str[len(str)-1] == '\n' {
|
||||
str = str[:len(str)-1]
|
||||
}
|
||||
|
||||
tf.numFormatted++
|
||||
|
||||
return str, nil
|
||||
}
|
||||
|
||||
// Format of request data. The allowed values are 'json' or 'text'.
|
||||
type Format string
|
||||
|
||||
const (
|
||||
// FormatJSON specifies input data in JSON format. Multiple request values
|
||||
// may be concatenated (messages with a JSON representation other than
|
||||
// object must be separated by whitespace, such as a newline)
|
||||
FormatJSON = Format("json")
|
||||
|
||||
// FormatText specifies input data must be in the protobuf text format.
|
||||
// Multiple request values must be separated by the "record separator"
|
||||
// ASCII character: 0x1E. The stream should not end in a record separator.
|
||||
// If it does, it will be interpreted as a final, blank message after the
|
||||
// separator.
|
||||
FormatText = Format("text")
|
||||
)
|
||||
|
||||
// AnyResolverFromDescriptorSource returns an AnyResolver that will search for
|
||||
// types using the given descriptor source.
|
||||
func AnyResolverFromDescriptorSource(source DescriptorSource) jsonpb.AnyResolver {
|
||||
return &anyResolver{source: source}
|
||||
}
|
||||
|
||||
// AnyResolverFromDescriptorSourceWithFallback returns an AnyResolver that will
|
||||
// search for types using the given descriptor source and then fallback to a
|
||||
// special message if the type is not found. The fallback type will render to
|
||||
// JSON with a "@type" property, just like an Any message, but also with a
|
||||
// custom "@value" property that includes the binary encoded payload.
|
||||
func AnyResolverFromDescriptorSourceWithFallback(source DescriptorSource) jsonpb.AnyResolver {
|
||||
res := anyResolver{source: source}
|
||||
return &anyResolverWithFallback{AnyResolver: &res}
|
||||
}
|
||||
|
||||
type anyResolver struct {
|
||||
source DescriptorSource
|
||||
|
||||
er dynamic.ExtensionRegistry
|
||||
|
||||
mu sync.RWMutex
|
||||
mf *dynamic.MessageFactory
|
||||
resolved map[string]func() proto.Message
|
||||
}
|
||||
|
||||
func (r *anyResolver) Resolve(typeUrl string) (proto.Message, error) {
|
||||
mname := typeUrl
|
||||
if slash := strings.LastIndex(mname, "/"); slash >= 0 {
|
||||
mname = mname[slash+1:]
|
||||
}
|
||||
|
||||
r.mu.RLock()
|
||||
factory := r.resolved[mname]
|
||||
r.mu.RUnlock()
|
||||
|
||||
// already resolved?
|
||||
if factory != nil {
|
||||
return factory(), nil
|
||||
}
|
||||
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
// double-check, in case we were racing with another goroutine
|
||||
// that resolved this one
|
||||
factory = r.resolved[mname]
|
||||
if factory != nil {
|
||||
return factory(), nil
|
||||
}
|
||||
|
||||
// use descriptor source to resolve message type
|
||||
d, err := r.source.FindSymbol(mname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
md, ok := d.(*desc.MessageDescriptor)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown message: %s", typeUrl)
|
||||
}
|
||||
// populate any extensions for this message, too (if there are any)
|
||||
if exts, err := r.source.AllExtensionsForType(mname); err == nil {
|
||||
if err := r.er.AddExtension(exts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if r.mf == nil {
|
||||
r.mf = dynamic.NewMessageFactoryWithExtensionRegistry(&r.er)
|
||||
}
|
||||
|
||||
factory = func() proto.Message {
|
||||
return r.mf.NewMessage(md)
|
||||
}
|
||||
if r.resolved == nil {
|
||||
r.resolved = map[string]func() proto.Message{}
|
||||
}
|
||||
r.resolved[mname] = factory
|
||||
return factory(), nil
|
||||
}
|
||||
|
||||
// anyResolverWithFallback can provide a fallback value for unknown
|
||||
// messages that will format itself to JSON using an "@value" field
|
||||
// that has the base64-encoded data for the unknown message value.
|
||||
type anyResolverWithFallback struct {
|
||||
jsonpb.AnyResolver
|
||||
}
|
||||
|
||||
func (r anyResolverWithFallback) Resolve(typeUrl string) (proto.Message, error) {
|
||||
msg, err := r.AnyResolver.Resolve(typeUrl)
|
||||
if err == nil {
|
||||
return msg, err
|
||||
}
|
||||
|
||||
// Try "default" resolution logic. This mirrors the default behavior
|
||||
// of jsonpb, which checks to see if the given message name is registered
|
||||
// in the proto package.
|
||||
mname := typeUrl
|
||||
if slash := strings.LastIndex(mname, "/"); slash >= 0 {
|
||||
mname = mname[slash+1:]
|
||||
}
|
||||
//lint:ignore SA1019 new non-deprecated API requires other code changes; deferring...
|
||||
mt := proto.MessageType(mname)
|
||||
if mt != nil {
|
||||
return reflect.New(mt.Elem()).Interface().(proto.Message), nil
|
||||
}
|
||||
|
||||
// finally, fallback to a special placeholder that can marshal itself
|
||||
// to JSON using a special "@value" property to show base64-encoded
|
||||
// data for the embedded message
|
||||
return &unknownAny{TypeUrl: typeUrl, Error: fmt.Sprintf("%s is not recognized; see @value for raw binary message data", mname)}, nil
|
||||
}
|
||||
|
||||
type unknownAny struct {
|
||||
TypeUrl string `json:"@type"`
|
||||
Error string `json:"@error"`
|
||||
Value string `json:"@value"`
|
||||
}
|
||||
|
||||
func (a *unknownAny) MarshalJSONPB(jsm *jsonpb.Marshaler) ([]byte, error) {
|
||||
if jsm.Indent != "" {
|
||||
return json.MarshalIndent(a, "", jsm.Indent)
|
||||
}
|
||||
return json.Marshal(a)
|
||||
}
|
||||
|
||||
func (a *unknownAny) Unmarshal(b []byte) error {
|
||||
a.Value = base64.StdEncoding.EncodeToString(b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *unknownAny) Reset() {
|
||||
a.Value = ""
|
||||
}
|
||||
|
||||
func (a *unknownAny) String() string {
|
||||
b, err := a.MarshalJSONPB(&jsonpb.Marshaler{})
|
||||
if err != nil {
|
||||
return fmt.Sprintf("ERROR: %v", err.Error())
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func (a *unknownAny) ProtoMessage() {
|
||||
}
|
||||
|
||||
var _ proto.Message = (*unknownAny)(nil)
|
||||
|
||||
// FormatOptions is a set of flags that are passed to a JSON or text formatter.
|
||||
type FormatOptions struct {
|
||||
// EmitJSONDefaultFields flag, when true, includes empty/default values in the output.
|
||||
// FormatJSON only flag.
|
||||
EmitJSONDefaultFields bool
|
||||
|
||||
// AllowUnknownFields is an option for the parser. When true,
|
||||
// it accepts input which includes unknown fields. These unknown fields
|
||||
// are skipped instead of returning an error.
|
||||
// FormatJSON only flag.
|
||||
AllowUnknownFields bool
|
||||
|
||||
// IncludeTextSeparator is true then, when invoked to format multiple messages,
|
||||
// all messages after the first one will be prefixed with the
|
||||
// ASCII 'Record Separator' character (0x1E).
|
||||
// It might be useful when the output is piped to another grpcurl process.
|
||||
// FormatText only flag.
|
||||
IncludeTextSeparator bool
|
||||
}
|
||||
|
||||
// RequestParserAndFormatter returns a request parser and formatter for the
|
||||
// given format. The given descriptor source may be used for parsing message
|
||||
// data (if needed by the format).
|
||||
// It accepts a set of options. The field EmitJSONDefaultFields and IncludeTextSeparator
|
||||
// are options for JSON and protobuf text formats, respectively. The AllowUnknownFields field
|
||||
// is a JSON-only format flag.
|
||||
// Requests will be parsed from the given in.
|
||||
func RequestParserAndFormatter(format Format, descSource DescriptorSource, in io.Reader, opts FormatOptions) (RequestParser, Formatter, error) {
|
||||
switch format {
|
||||
case FormatJSON:
|
||||
resolver := AnyResolverFromDescriptorSource(descSource)
|
||||
unmarshaler := jsonpb.Unmarshaler{AnyResolver: resolver, AllowUnknownFields: opts.AllowUnknownFields}
|
||||
return NewJSONRequestParserWithUnmarshaler(in, unmarshaler), NewJSONFormatter(opts.EmitJSONDefaultFields, anyResolverWithFallback{AnyResolver: resolver}), nil
|
||||
case FormatText:
|
||||
return NewTextRequestParser(in), NewTextFormatter(opts.IncludeTextSeparator), nil
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unknown format: %s", format)
|
||||
}
|
||||
}
|
||||
|
||||
// RequestParserAndFormatterFor returns a request parser and formatter for the
|
||||
// given format. The given descriptor source may be used for parsing message
|
||||
// data (if needed by the format). The flags emitJSONDefaultFields and
|
||||
// includeTextSeparator are options for JSON and protobuf text formats,
|
||||
// respectively. Requests will be parsed from the given in.
|
||||
// This function is deprecated. Please use RequestParserAndFormatter instead.
|
||||
// DEPRECATED
|
||||
func RequestParserAndFormatterFor(format Format, descSource DescriptorSource, emitJSONDefaultFields, includeTextSeparator bool, in io.Reader) (RequestParser, Formatter, error) {
|
||||
return RequestParserAndFormatter(format, descSource, in, FormatOptions{
|
||||
EmitJSONDefaultFields: emitJSONDefaultFields,
|
||||
IncludeTextSeparator: includeTextSeparator,
|
||||
})
|
||||
}
|
||||
|
||||
// DefaultEventHandler logs events to a writer. This is not thread-safe, but is
|
||||
// safe for use with InvokeRPC as long as NumResponses and Status are not read
|
||||
// until the call to InvokeRPC completes.
|
||||
type DefaultEventHandler struct {
|
||||
Out io.Writer
|
||||
Formatter Formatter
|
||||
// 0 = default
|
||||
// 1 = verbose
|
||||
// 2 = very verbose
|
||||
VerbosityLevel int
|
||||
|
||||
// NumResponses is the number of responses that have been received.
|
||||
NumResponses int
|
||||
// Status is the status that was received at the end of an RPC. It is
|
||||
// nil if the RPC is still in progress.
|
||||
Status *status.Status
|
||||
}
|
||||
|
||||
// NewDefaultEventHandler returns an InvocationEventHandler that logs events to
|
||||
// the given output. If verbose is true, all events are logged. Otherwise, only
|
||||
// response messages are logged.
|
||||
//
|
||||
// Deprecated: NewDefaultEventHandler exists for compatibility.
|
||||
// It doesn't allow fine control over the `VerbosityLevel`
|
||||
// and provides only 0 and 1 options (which corresponds to the `verbose` argument).
|
||||
// Use DefaultEventHandler{} initializer directly.
|
||||
func NewDefaultEventHandler(out io.Writer, descSource DescriptorSource, formatter Formatter, verbose bool) *DefaultEventHandler {
|
||||
verbosityLevel := 0
|
||||
if verbose {
|
||||
verbosityLevel = 1
|
||||
}
|
||||
return &DefaultEventHandler{
|
||||
Out: out,
|
||||
Formatter: formatter,
|
||||
VerbosityLevel: verbosityLevel,
|
||||
}
|
||||
}
|
||||
|
||||
var _ InvocationEventHandler = (*DefaultEventHandler)(nil)
|
||||
|
||||
func (h *DefaultEventHandler) OnResolveMethod(md *desc.MethodDescriptor) {
|
||||
if h.VerbosityLevel > 0 {
|
||||
txt, err := GetDescriptorText(md, nil)
|
||||
if err == nil {
|
||||
fmt.Fprintf(h.Out, "\nResolved method descriptor:\n%s\n", txt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *DefaultEventHandler) OnSendHeaders(md metadata.MD) {
|
||||
if h.VerbosityLevel > 0 {
|
||||
fmt.Fprintf(h.Out, "\nRequest metadata to send:\n%s\n", MetadataToString(md))
|
||||
}
|
||||
}
|
||||
|
||||
func (h *DefaultEventHandler) OnReceiveHeaders(md metadata.MD) {
|
||||
if h.VerbosityLevel > 0 {
|
||||
fmt.Fprintf(h.Out, "\nResponse headers received:\n%s\n", MetadataToString(md))
|
||||
}
|
||||
}
|
||||
|
||||
func (h *DefaultEventHandler) OnReceiveResponse(resp proto.Message) {
|
||||
h.NumResponses++
|
||||
if h.VerbosityLevel > 1 {
|
||||
fmt.Fprintf(h.Out, "\nEstimated response size: %d bytes\n", proto.Size(resp))
|
||||
}
|
||||
if h.VerbosityLevel > 0 {
|
||||
fmt.Fprint(h.Out, "\nResponse contents:\n")
|
||||
}
|
||||
if respStr, err := h.Formatter(resp); err != nil {
|
||||
fmt.Fprintf(h.Out, "Failed to format response message %d: %v\n", h.NumResponses, err)
|
||||
} else {
|
||||
fmt.Fprintln(h.Out, respStr)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *DefaultEventHandler) OnReceiveTrailers(stat *status.Status, md metadata.MD) {
|
||||
h.Status = stat
|
||||
if h.VerbosityLevel > 0 {
|
||||
fmt.Fprintf(h.Out, "\nResponse trailers received:\n%s\n", MetadataToString(md))
|
||||
}
|
||||
}
|
||||
|
||||
// PrintStatus prints details about the given status to the given writer. The given
|
||||
// formatter is used to print any detail messages that may be included in the status.
|
||||
// If the given status has a code of OK, "OK" is printed and that is all. Otherwise,
|
||||
// "ERROR:" is printed along with a line showing the code, one showing the message
|
||||
// string, and each detail message if any are present. The detail messages will be
|
||||
// printed as proto text format or JSON, depending on the given formatter.
|
||||
func PrintStatus(w io.Writer, stat *status.Status, formatter Formatter) {
|
||||
if stat.Code() == codes.OK {
|
||||
fmt.Fprintln(w, "OK")
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, "ERROR:\n Code: %s\n Message: %s\n", stat.Code().String(), stat.Message())
|
||||
|
||||
statpb := stat.Proto()
|
||||
if len(statpb.Details) > 0 {
|
||||
fmt.Fprintf(w, " Details:\n")
|
||||
for i, det := range statpb.Details {
|
||||
prefix := fmt.Sprintf(" %d)", i+1)
|
||||
fmt.Fprintf(w, "%s\t", prefix)
|
||||
prefix = strings.Repeat(" ", len(prefix)) + "\t"
|
||||
|
||||
output, err := formatter(det)
|
||||
if err != nil {
|
||||
fmt.Fprintf(w, "Error parsing detail message: %v\n", err)
|
||||
} else {
|
||||
lines := strings.Split(output, "\n")
|
||||
for i, line := range lines {
|
||||
if i == 0 {
|
||||
// first line is already indented
|
||||
fmt.Fprintf(w, "%s\n", line)
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s%s\n", prefix, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,309 @@
|
|||
package grpcurl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/jsonpb" //lint:ignore SA1019 we have to import these because some of their types appear in exported API
|
||||
"github.com/golang/protobuf/proto" //lint:ignore SA1019 same as above
|
||||
"github.com/jhump/protoreflect/desc" //lint:ignore SA1019 same as above
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
)
|
||||
|
||||
func TestRequestParser(t *testing.T) {
|
||||
source, err := DescriptorSourceFromProtoSets("internal/testing/example.protoset")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create descriptor source: %v", err)
|
||||
}
|
||||
|
||||
msg, err := makeProto()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create message: %v", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
format Format
|
||||
input string
|
||||
expectedOutput []proto.Message
|
||||
}{
|
||||
{
|
||||
format: FormatJSON,
|
||||
input: "",
|
||||
},
|
||||
{
|
||||
format: FormatJSON,
|
||||
input: messageAsJSON,
|
||||
expectedOutput: []proto.Message{msg},
|
||||
},
|
||||
{
|
||||
format: FormatJSON,
|
||||
input: messageAsJSON + messageAsJSON + messageAsJSON,
|
||||
expectedOutput: []proto.Message{msg, msg, msg},
|
||||
},
|
||||
{
|
||||
// unlike JSON, empty input yields one empty message (vs. zero messages)
|
||||
format: FormatText,
|
||||
input: "",
|
||||
expectedOutput: []proto.Message{&structpb.Value{}},
|
||||
},
|
||||
{
|
||||
format: FormatText,
|
||||
input: messageAsText,
|
||||
expectedOutput: []proto.Message{msg},
|
||||
},
|
||||
{
|
||||
format: FormatText,
|
||||
input: messageAsText + string(textSeparatorChar),
|
||||
expectedOutput: []proto.Message{msg, &structpb.Value{}},
|
||||
},
|
||||
{
|
||||
format: FormatText,
|
||||
input: messageAsText + string(textSeparatorChar) + messageAsText + string(textSeparatorChar) + messageAsText,
|
||||
expectedOutput: []proto.Message{msg, msg, msg},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
name := fmt.Sprintf("#%d, %s, %d message(s)", i+1, tc.format, len(tc.expectedOutput))
|
||||
rf, _, err := RequestParserAndFormatter(tc.format, source, strings.NewReader(tc.input), FormatOptions{})
|
||||
if err != nil {
|
||||
t.Errorf("Failed to create parser and formatter: %v", err)
|
||||
continue
|
||||
}
|
||||
numReqs := 0
|
||||
for {
|
||||
var req structpb.Value
|
||||
err := rf.Next(&req)
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
t.Errorf("%s, msg %d: unexpected error: %v", name, numReqs, err)
|
||||
}
|
||||
if !proto.Equal(&req, tc.expectedOutput[numReqs]) {
|
||||
t.Errorf("%s, msg %d: incorrect message;\nexpecting:\n%v\ngot:\n%v", name, numReqs, tc.expectedOutput[numReqs], &req)
|
||||
}
|
||||
numReqs++
|
||||
}
|
||||
if rf.NumRequests() != numReqs {
|
||||
t.Errorf("%s: factory reported wrong number of requests: expecting %d, got %d", name, numReqs, rf.NumRequests())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handler prints response data (and headers/trailers in verbose mode).
|
||||
// This verifies that we get the right output in both JSON and proto text modes.
|
||||
func TestHandler(t *testing.T) {
|
||||
source, err := DescriptorSourceFromProtoSets("internal/testing/example.protoset")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create descriptor source: %v", err)
|
||||
}
|
||||
d, err := source.FindSymbol("TestService.GetFiles")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to find method 'TestService.GetFiles': %v", err)
|
||||
}
|
||||
md, ok := d.(*desc.MethodDescriptor)
|
||||
if !ok {
|
||||
t.Fatalf("wrong kind of descriptor found: %T", d)
|
||||
}
|
||||
|
||||
reqHeaders := metadata.Pairs("foo", "123", "bar", "456")
|
||||
respHeaders := metadata.Pairs("foo", "abc", "bar", "def", "baz", "xyz")
|
||||
respTrailers := metadata.Pairs("a", "1", "b", "2", "c", "3")
|
||||
rsp, err := makeProto()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create response message: %v", err)
|
||||
}
|
||||
|
||||
for _, format := range []Format{FormatJSON, FormatText} {
|
||||
for _, numMessages := range []int{1, 3} {
|
||||
for verbosityLevel := 0; verbosityLevel <= 2; verbosityLevel++ {
|
||||
name := fmt.Sprintf("%s, %d message(s)", format, numMessages)
|
||||
if verbosityLevel > 0 {
|
||||
name += fmt.Sprintf(", verbosityLevel=%d", verbosityLevel)
|
||||
}
|
||||
|
||||
verbose := verbosityLevel > 0
|
||||
|
||||
_, formatter, err := RequestParserAndFormatter(format, source, nil, FormatOptions{IncludeTextSeparator: !verbose})
|
||||
if err != nil {
|
||||
t.Errorf("Failed to create parser and formatter: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
h := &DefaultEventHandler{
|
||||
Out: &buf,
|
||||
Formatter: formatter,
|
||||
VerbosityLevel: verbosityLevel,
|
||||
}
|
||||
|
||||
h.OnResolveMethod(md)
|
||||
h.OnSendHeaders(reqHeaders)
|
||||
h.OnReceiveHeaders(respHeaders)
|
||||
for i := 0; i < numMessages; i++ {
|
||||
h.OnReceiveResponse(rsp)
|
||||
}
|
||||
h.OnReceiveTrailers(nil, respTrailers)
|
||||
|
||||
expectedOutput := ""
|
||||
if verbose {
|
||||
expectedOutput += verbosePrefix
|
||||
}
|
||||
for i := 0; i < numMessages; i++ {
|
||||
if verbosityLevel > 1 {
|
||||
expectedOutput += verboseResponseSize
|
||||
}
|
||||
if verbose {
|
||||
expectedOutput += verboseResponseHeader
|
||||
}
|
||||
if format == "json" {
|
||||
expectedOutput += messageAsJSON
|
||||
} else {
|
||||
if i > 0 && !verbose {
|
||||
expectedOutput += string(textSeparatorChar)
|
||||
}
|
||||
expectedOutput += messageAsText
|
||||
}
|
||||
}
|
||||
if verbose {
|
||||
expectedOutput += verboseSuffix
|
||||
}
|
||||
|
||||
out := buf.String()
|
||||
if !compare(out, expectedOutput) {
|
||||
t.Errorf("%s: Incorrect output. Expected:\n%s\nGot:\n%s", name, expectedOutput, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// compare checks that actual and expected are equal, returning true if so.
|
||||
// A simple equality check (==) does not suffice because jsonpb formats
|
||||
// structpb.Value strangely. So if that formatting gets fixed, we don't
|
||||
// want this test in grpcurl to suddenly start failing. So we check each
|
||||
// line and compare the lines after stripping whitespace (which removes
|
||||
// the jsonpb format anomalies).
|
||||
func compare(actual, expected string) bool {
|
||||
actualLines := strings.Split(actual, "\n")
|
||||
expectedLines := strings.Split(expected, "\n")
|
||||
if len(actualLines) != len(expectedLines) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(actualLines); i++ {
|
||||
if strings.TrimSpace(actualLines[i]) != strings.TrimSpace(expectedLines[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func makeProto() (proto.Message, error) {
|
||||
var rsp structpb.Value
|
||||
err := jsonpb.UnmarshalString(`{
|
||||
"foo": ["abc", "def", "ghi"],
|
||||
"bar": { "a": 1, "b": 2 },
|
||||
"baz": true,
|
||||
"null": null
|
||||
}`, &rsp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rsp, nil
|
||||
}
|
||||
|
||||
var (
|
||||
verbosePrefix = `
|
||||
Resolved method descriptor:
|
||||
rpc GetFiles ( .TestRequest ) returns ( .TestResponse );
|
||||
|
||||
Request metadata to send:
|
||||
bar: 456
|
||||
foo: 123
|
||||
|
||||
Response headers received:
|
||||
bar: def
|
||||
baz: xyz
|
||||
foo: abc
|
||||
`
|
||||
verboseSuffix = `
|
||||
Response trailers received:
|
||||
a: 1
|
||||
b: 2
|
||||
c: 3
|
||||
`
|
||||
verboseResponseSize = `
|
||||
Estimated response size: 100 bytes
|
||||
`
|
||||
verboseResponseHeader = `
|
||||
Response contents:
|
||||
`
|
||||
messageAsJSON = `{
|
||||
"bar": {
|
||||
"a": 1,
|
||||
"b": 2
|
||||
},
|
||||
"baz": true,
|
||||
"foo": [
|
||||
"abc",
|
||||
"def",
|
||||
"ghi"
|
||||
],
|
||||
"null": null
|
||||
}
|
||||
`
|
||||
messageAsText = `struct_value: <
|
||||
fields: <
|
||||
key: "bar"
|
||||
value: <
|
||||
struct_value: <
|
||||
fields: <
|
||||
key: "a"
|
||||
value: <
|
||||
number_value: 1
|
||||
>
|
||||
>
|
||||
fields: <
|
||||
key: "b"
|
||||
value: <
|
||||
number_value: 2
|
||||
>
|
||||
>
|
||||
>
|
||||
>
|
||||
>
|
||||
fields: <
|
||||
key: "baz"
|
||||
value: <
|
||||
bool_value: true
|
||||
>
|
||||
>
|
||||
fields: <
|
||||
key: "foo"
|
||||
value: <
|
||||
list_value: <
|
||||
values: <
|
||||
string_value: "abc"
|
||||
>
|
||||
values: <
|
||||
string_value: "def"
|
||||
>
|
||||
values: <
|
||||
string_value: "ghi"
|
||||
>
|
||||
>
|
||||
>
|
||||
>
|
||||
fields: <
|
||||
key: "null"
|
||||
value: <
|
||||
null_value: NULL_VALUE
|
||||
>
|
||||
>
|
||||
>
|
||||
`
|
||||
)
|
||||
32
go.mod
32
go.mod
|
|
@ -1,8 +1,32 @@
|
|||
module github.com/fullstorydev/grpcurl
|
||||
|
||||
go 1.24.0
|
||||
|
||||
toolchain go1.24.1
|
||||
|
||||
require (
|
||||
github.com/golang/protobuf v1.1.0
|
||||
github.com/jhump/protoreflect v1.0.0
|
||||
golang.org/x/net v0.0.0-20180530234432-1e491301e022
|
||||
google.golang.org/grpc v1.12.0
|
||||
github.com/golang/protobuf v1.5.4
|
||||
github.com/jhump/protoreflect v1.18.0
|
||||
google.golang.org/grpc v1.66.2
|
||||
google.golang.org/protobuf v1.36.11
|
||||
)
|
||||
|
||||
require (
|
||||
cel.dev/expr v0.15.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
||||
github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b // indirect
|
||||
github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect
|
||||
github.com/jhump/protoreflect/v2 v2.0.0-beta.1 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
golang.org/x/net v0.38.0 // indirect
|
||||
golang.org/x/oauth2 v0.27.0 // indirect
|
||||
golang.org/x/sync v0.12.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect
|
||||
)
|
||||
|
|
|
|||
69
go.sum
69
go.sum
|
|
@ -1,13 +1,56 @@
|
|||
github.com/golang/protobuf v1.1.0 h1:0iH4Ffd/meGoXqF2lSAhZHt8X+cPgkfn/cb6Cce5Vpc=
|
||||
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/jhump/protoreflect v1.0.0 h1:l94KtQ6gRI3ouKVcXNdofCQJWoHATzcI6tDizOgUaf0=
|
||||
github.com/jhump/protoreflect v1.0.0/go.mod h1:kG/zRVeS2M91gYaCvvUbPkMjjtFQS4qqjcPFzFkh2zE=
|
||||
golang.org/x/net v0.0.0-20180530234432-1e491301e022 h1:MVYFTUmVD3/+ERcvRRI+P/C2+WOUimXh+Pd8LVsklZ4=
|
||||
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
google.golang.org/genproto v0.0.0-20170818100345-ee236bd376b0 h1:jgaHBfsPDMBDKsth1hPtI1HcOyecWndWOFSGW21VgaM=
|
||||
google.golang.org/genproto v0.0.0-20170818100345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.12.0 h1:Mm8atZtkT+P6R43n/dqNDWkPPu5BwRVu/1rJnJCeZH8=
|
||||
google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
cel.dev/expr v0.15.0 h1:O1jzfJCQBfL5BFoYktaxwIhuttaQPsVWerH9/EEKx0w=
|
||||
cel.dev/expr v0.15.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg=
|
||||
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
|
||||
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||
github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw=
|
||||
github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c=
|
||||
github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g=
|
||||
github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b h1:ga8SEFjZ60pxLcmhnThWgvH2wg8376yUJmPhEH4H3kw=
|
||||
github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155 h1:IgJPqnrlY2Mr4pYB6oaMKvFvwJ9H+X6CCY5x1vCTcpc=
|
||||
github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155/go.mod h1:5Wkq+JduFtdAXihLmeTJf+tRYIT4KBc2vPXDhwVo1pA=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jhump/protoreflect v1.18.0 h1:TOz0MSR/0JOZ5kECB/0ufGnC2jdsgZ123Rd/k4Z5/2w=
|
||||
github.com/jhump/protoreflect v1.18.0/go.mod h1:ezWcltJIVF4zYdIFM+D/sHV4Oh5LNU08ORzCGfwvTz8=
|
||||
github.com/jhump/protoreflect/v2 v2.0.0-beta.1 h1:Dw1rslK/VotaUGYsv53XVWITr+5RCPXfvvlGrM/+B6w=
|
||||
github.com/jhump/protoreflect/v2 v2.0.0-beta.1/go.mod h1:D9LBEowZyv8/iSu97FU2zmXG3JxVTmNw21mu63niFzU=
|
||||
github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 h1:KPpdlQLZcHfTMQRi6bFQ7ogNO0ltFT4PmtwTLW4W+14=
|
||||
github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
|
||||
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
|
||||
google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo=
|
||||
google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
|||
987
grpcurl.go
987
grpcurl.go
File diff suppressed because it is too large
Load Diff
296
grpcurl_test.go
296
grpcurl_test.go
|
|
@ -1,6 +1,8 @@
|
|||
package grpcurl_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
|
@ -10,21 +12,20 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/jsonpb"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/jhump/protoreflect/desc"
|
||||
"github.com/golang/protobuf/jsonpb" //lint:ignore SA1019 we have to import these because some of their types appear in exported API
|
||||
"github.com/golang/protobuf/proto" //lint:ignore SA1019 same as above
|
||||
"github.com/jhump/protoreflect/desc" //lint:ignore SA1019 same as above
|
||||
"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/credentials/insecure"
|
||||
"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"
|
||||
grpcurl_testing "github.com/fullstorydev/grpcurl/internal/testing"
|
||||
jsonpbtest "github.com/fullstorydev/grpcurl/internal/testing/jsonpb_test_proto"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -44,20 +45,24 @@ type descSourceCase struct {
|
|||
includeRefl bool
|
||||
}
|
||||
|
||||
// NB: These tests intentionally use the deprecated InvokeRpc since that
|
||||
// calls the other (non-deprecated InvokeRPC). That allows the tests to
|
||||
// easily exercise both functions.
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
var err error
|
||||
sourceProtoset, err = DescriptorSourceFromProtoSets("testing/test.protoset")
|
||||
sourceProtoset, err = DescriptorSourceFromProtoSets("internal/testing/test.protoset")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sourceProtoFiles, err = DescriptorSourceFromProtoFiles(nil, "testing/test.proto")
|
||||
sourceProtoFiles, err = DescriptorSourceFromProtoFiles([]string{"internal/testing"}, "test.proto")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Create a server that includes the reflection service
|
||||
svrReflect := grpc.NewServer()
|
||||
grpc_testing.RegisterTestServiceServer(svrReflect, grpcurl_testing.TestServer{})
|
||||
grpcurl_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 {
|
||||
|
|
@ -72,18 +77,18 @@ func TestMain(m *testing.M) {
|
|||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
if ccReflect, err = grpc.DialContext(ctx, fmt.Sprintf("127.0.0.1:%d", portReflect),
|
||||
grpc.WithInsecure(), grpc.WithBlock()); err != nil {
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer ccReflect.Close()
|
||||
refClient := grpcreflect.NewClient(context.Background(), reflectpb.NewServerReflectionClient(ccReflect))
|
||||
refClient := grpcreflect.NewClientAuto(context.Background(), 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{})
|
||||
grpcurl_testing.RegisterTestServiceServer(svrProtoset, grpcurl_testing.TestServer{})
|
||||
var portProtoset int
|
||||
if l, err := net.Listen("tcp", "127.0.0.1:0"); err != nil {
|
||||
panic(err)
|
||||
|
|
@ -97,7 +102,7 @@ func TestMain(m *testing.M) {
|
|||
ctx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
if ccNoReflect, err = grpc.DialContext(ctx, fmt.Sprintf("127.0.0.1:%d", portProtoset),
|
||||
grpc.WithInsecure(), grpc.WithBlock()); err != nil {
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer ccNoReflect.Close()
|
||||
|
|
@ -112,7 +117,7 @@ func TestMain(m *testing.M) {
|
|||
}
|
||||
|
||||
func TestServerDoesNotSupportReflection(t *testing.T) {
|
||||
refClient := grpcreflect.NewClient(context.Background(), reflectpb.NewServerReflectionClient(ccNoReflect))
|
||||
refClient := grpcreflect.NewClientAuto(context.Background(), ccNoReflect)
|
||||
defer refClient.Reset()
|
||||
|
||||
refSource := DescriptorSourceFromServer(context.Background(), refClient)
|
||||
|
|
@ -135,7 +140,7 @@ func TestServerDoesNotSupportReflection(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestProtosetWithImports(t *testing.T) {
|
||||
sourceProtoset, err := DescriptorSourceFromProtoSets("testing/example.protoset")
|
||||
sourceProtoset, err := DescriptorSourceFromProtoSets("internal/testing/example.protoset")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to load protoset: %v", err)
|
||||
}
|
||||
|
|
@ -172,11 +177,11 @@ func doTestListServices(t *testing.T, source DescriptorSource, includeReflection
|
|||
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"}
|
||||
expected = []string{"grpc.reflection.v1.ServerReflection", "grpc.reflection.v1alpha.ServerReflection", "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"}
|
||||
expected = []string{"testing.TestService", "testing.UnimplementedService"}
|
||||
}
|
||||
if !reflect.DeepEqual(expected, names) {
|
||||
t.Errorf("ListServices returned wrong results: wanted %v, got %v", expected, names)
|
||||
|
|
@ -192,17 +197,17 @@ func TestListMethods(t *testing.T) {
|
|||
}
|
||||
|
||||
func doTestListMethods(t *testing.T, source DescriptorSource, includeReflection bool) {
|
||||
names, err := ListMethods(source, "grpc.testing.TestService")
|
||||
names, err := ListMethods(source, "testing.TestService")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to list methods for TestService: %v", err)
|
||||
}
|
||||
expected := []string{
|
||||
"EmptyCall",
|
||||
"FullDuplexCall",
|
||||
"HalfDuplexCall",
|
||||
"StreamingInputCall",
|
||||
"StreamingOutputCall",
|
||||
"UnaryCall",
|
||||
"testing.TestService.EmptyCall",
|
||||
"testing.TestService.FullDuplexCall",
|
||||
"testing.TestService.HalfDuplexCall",
|
||||
"testing.TestService.StreamingInputCall",
|
||||
"testing.TestService.StreamingOutputCall",
|
||||
"testing.TestService.UnaryCall",
|
||||
}
|
||||
if !reflect.DeepEqual(expected, names) {
|
||||
t.Errorf("ListMethods returned wrong results: wanted %v, got %v", expected, names)
|
||||
|
|
@ -210,19 +215,19 @@ func doTestListMethods(t *testing.T, source DescriptorSource, includeReflection
|
|||
|
||||
if includeReflection {
|
||||
// when using server reflection, we see the TestService as well as the ServerReflection service
|
||||
names, err = ListMethods(source, "grpc.reflection.v1alpha.ServerReflection")
|
||||
names, err = ListMethods(source, "grpc.reflection.v1.ServerReflection")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to list methods for ServerReflection: %v", err)
|
||||
}
|
||||
expected = []string{"ServerReflectionInfo"}
|
||||
expected = []string{"grpc.reflection.v1.ServerReflection.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")
|
||||
names, err = ListMethods(source, "testing.UnimplementedService")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to list methods for ServerReflection: %v", err)
|
||||
}
|
||||
expected = []string{"UnimplementedCall"}
|
||||
expected = []string{"testing.UnimplementedService.UnimplementedCall"}
|
||||
}
|
||||
if !reflect.DeepEqual(expected, names) {
|
||||
t.Errorf("ListMethods returned wrong results: wanted %v, got %v", expected, names)
|
||||
|
|
@ -235,6 +240,149 @@ func doTestListMethods(t *testing.T, source DescriptorSource, includeReflection
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetAllFiles(t *testing.T) {
|
||||
expectedFiles := []string{"test.proto"}
|
||||
expectedFilesWithReflection := []string{
|
||||
"grpc/reflection/v1/reflection.proto", "grpc/reflection/v1alpha/reflection.proto", "test.proto",
|
||||
}
|
||||
|
||||
for _, ds := range descSources {
|
||||
t.Run(ds.name, func(t *testing.T) {
|
||||
files, err := GetAllFiles(ds.source)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get all files: %v", err)
|
||||
}
|
||||
names := fileNames(files)
|
||||
match := false
|
||||
var expected []string
|
||||
if ds.includeRefl {
|
||||
expected = expectedFilesWithReflection
|
||||
} else {
|
||||
expected = expectedFiles
|
||||
}
|
||||
match = reflect.DeepEqual(expected, names)
|
||||
if !match {
|
||||
t.Errorf("GetAllFiles returned wrong results: wanted %v, got %v", expected, names)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// try cases with more complicated set of files
|
||||
otherSourceProtoset, err := DescriptorSourceFromProtoSets("internal/testing/test.protoset", "internal/testing/example.protoset")
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
otherSourceProtoFiles, err := DescriptorSourceFromProtoFiles([]string{"internal/testing"}, "test.proto", "example.proto")
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
otherDescSources := []descSourceCase{
|
||||
{"protoset[b]", otherSourceProtoset, false},
|
||||
{"proto[b]", otherSourceProtoFiles, false},
|
||||
}
|
||||
expectedFiles = []string{
|
||||
"example.proto",
|
||||
"example2.proto",
|
||||
"google/protobuf/any.proto",
|
||||
"google/protobuf/descriptor.proto",
|
||||
"google/protobuf/empty.proto",
|
||||
"google/protobuf/timestamp.proto",
|
||||
"test.proto",
|
||||
}
|
||||
for _, ds := range otherDescSources {
|
||||
t.Run(ds.name, func(t *testing.T) {
|
||||
files, err := GetAllFiles(ds.source)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get all files: %v", err)
|
||||
}
|
||||
names := fileNames(files)
|
||||
if !reflect.DeepEqual(expectedFiles, names) {
|
||||
t.Errorf("GetAllFiles returned wrong results: wanted %v, got %v", expectedFiles, names)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpandHeaders(t *testing.T) {
|
||||
inHeaders := []string{"key1: ${value}", "key2: bar", "key3: ${woo", "key4: woo}", "key5: ${TEST}",
|
||||
"key6: ${TEST_VAR}", "${TEST}: ${TEST_VAR}", "key8: ${EMPTY}"}
|
||||
os.Setenv("value", "value")
|
||||
os.Setenv("TEST", "value5")
|
||||
os.Setenv("TEST_VAR", "value6")
|
||||
os.Setenv("EMPTY", "")
|
||||
expectedHeaders := map[string]bool{"key1: value": true, "key2: bar": true, "key3: ${woo": true, "key4: woo}": true,
|
||||
"key5: value5": true, "key6: value6": true, "value5: value6": true, "key8: ": true}
|
||||
|
||||
outHeaders, err := ExpandHeaders(inHeaders)
|
||||
if err != nil {
|
||||
t.Errorf("The ExpandHeaders function generated an unexpected error %s", err)
|
||||
}
|
||||
for _, expandedHeader := range outHeaders {
|
||||
if _, ok := expectedHeaders[expandedHeader]; !ok {
|
||||
t.Errorf("The ExpandHeaders function has returned an unexpected header. Received unexpected header %s", expandedHeader)
|
||||
}
|
||||
}
|
||||
|
||||
badHeaders := []string{"key: ${DNE}"}
|
||||
_, err = ExpandHeaders(badHeaders)
|
||||
if err == nil {
|
||||
t.Errorf("The ExpandHeaders function should return an error for missing environment variables %q", badHeaders)
|
||||
}
|
||||
}
|
||||
|
||||
func fileNames(files []*desc.FileDescriptor) []string {
|
||||
names := make([]string, len(files))
|
||||
for i, f := range files {
|
||||
names[i] = f.GetName()
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
const expectKnownType = `{
|
||||
"dur": "0s",
|
||||
"ts": "1970-01-01T00:00:00Z",
|
||||
"dbl": 0,
|
||||
"flt": 0,
|
||||
"i64": "0",
|
||||
"u64": "0",
|
||||
"i32": 0,
|
||||
"u32": 0,
|
||||
"bool": false,
|
||||
"str": "",
|
||||
"bytes": null,
|
||||
"st": {"google.protobuf.Struct": "supports arbitrary JSON objects"},
|
||||
"an": {"@type": "type.googleapis.com/google.protobuf.Empty", "value": {}},
|
||||
"lv": [{"google.protobuf.ListValue": "is an array of arbitrary JSON values"}],
|
||||
"val": {"google.protobuf.Value": "supports arbitrary JSON"}
|
||||
}`
|
||||
|
||||
func TestMakeTemplateKnownTypes(t *testing.T) {
|
||||
descriptor, err := desc.LoadMessageDescriptorForMessage((*jsonpbtest.KnownTypes)(nil))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to load descriptor: %v", err)
|
||||
}
|
||||
message := MakeTemplate(descriptor)
|
||||
|
||||
jsm := jsonpb.Marshaler{EmitDefaults: true}
|
||||
out, err := jsm.MarshalToString(message)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal to JSON: %v", err)
|
||||
}
|
||||
|
||||
// make sure template JSON matches expected
|
||||
var actual, expected interface{}
|
||||
if err := json.Unmarshal([]byte(out), &actual); err != nil {
|
||||
t.Fatalf("failed to parse actual JSON: %v", err)
|
||||
}
|
||||
if err := json.Unmarshal([]byte(expectKnownType), &expected); err != nil {
|
||||
t.Fatalf("failed to parse expected JSON: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Errorf("template message is not as expected; want:\n%s\ngot:\n%s", expectKnownType, out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDescribe(t *testing.T) {
|
||||
for _, ds := range descSources {
|
||||
t.Run(ds.name, func(t *testing.T) {
|
||||
|
|
@ -244,7 +392,7 @@ func TestDescribe(t *testing.T) {
|
|||
}
|
||||
|
||||
func doTestDescribe(t *testing.T, source DescriptorSource) {
|
||||
sym := "grpc.testing.TestService.EmptyCall"
|
||||
sym := "testing.TestService.EmptyCall"
|
||||
dsc, err := source.FindSymbol(sym)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get descriptor for %q: %v", sym, err)
|
||||
|
|
@ -255,14 +403,14 @@ func doTestDescribe(t *testing.T, source DescriptorSource) {
|
|||
txt := proto.MarshalTextString(dsc.AsProto())
|
||||
expected :=
|
||||
`name: "EmptyCall"
|
||||
input_type: ".grpc.testing.Empty"
|
||||
output_type: ".grpc.testing.Empty"
|
||||
input_type: ".testing.Empty"
|
||||
output_type: ".testing.Empty"
|
||||
`
|
||||
if expected != txt {
|
||||
t.Errorf("descriptor mismatch: expected %s, got %s", expected, txt)
|
||||
}
|
||||
|
||||
sym = "grpc.testing.StreamingOutputCallResponse"
|
||||
sym = "testing.StreamingOutputCallResponse"
|
||||
dsc, err = source.FindSymbol(sym)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get descriptor for %q: %v", sym, err)
|
||||
|
|
@ -278,7 +426,7 @@ field: <
|
|||
number: 1
|
||||
label: LABEL_OPTIONAL
|
||||
type: TYPE_MESSAGE
|
||||
type_name: ".grpc.testing.Payload"
|
||||
type_name: ".testing.Payload"
|
||||
json_name: "payload"
|
||||
>
|
||||
`
|
||||
|
|
@ -334,12 +482,12 @@ func TestUnary(t *testing.T) {
|
|||
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)
|
||||
err := InvokeRpc(context.Background(), source, cc, "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.check(t, "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])
|
||||
}
|
||||
|
|
@ -347,12 +495,12 @@ func doTestUnary(t *testing.T, cc *grpc.ClientConn, source DescriptorSource) {
|
|||
|
||||
// Failure
|
||||
h = &handler{reqMessages: []string{payload1}}
|
||||
err = InvokeRpc(context.Background(), source, cc, "grpc.testing.TestService/UnaryCall", makeHeaders(codes.NotFound), h, h.getRequestData)
|
||||
err = InvokeRpc(context.Background(), source, cc, "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)
|
||||
h.check(t, "testing.TestService.UnaryCall", codes.NotFound, 1, 0)
|
||||
}
|
||||
|
||||
func TestClientStream(t *testing.T) {
|
||||
|
|
@ -366,12 +514,12 @@ func TestClientStream(t *testing.T) {
|
|||
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)
|
||||
err := InvokeRpc(context.Background(), source, cc, "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) {
|
||||
if h.check(t, "testing.TestService.StreamingInputCall", codes.OK, 3, 1) {
|
||||
expected :=
|
||||
`{
|
||||
"aggregatedPayloadSize": 61
|
||||
|
|
@ -383,21 +531,21 @@ func doTestClientStream(t *testing.T, cc *grpc.ClientConn, source DescriptorSour
|
|||
|
||||
// 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)
|
||||
err = InvokeRpc(context.Background(), source, cc, "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)
|
||||
h.check(t, "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)
|
||||
err = InvokeRpc(context.Background(), source, cc, "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)
|
||||
h.check(t, "testing.TestService.StreamingInputCall", codes.Internal, 3, 0)
|
||||
}
|
||||
|
||||
func TestServerStream(t *testing.T) {
|
||||
|
|
@ -409,9 +557,9 @@ func TestServerStream(t *testing.T) {
|
|||
}
|
||||
|
||||
func doTestServerStream(t *testing.T, cc *grpc.ClientConn, source DescriptorSource) {
|
||||
req := &grpc_testing.StreamingOutputCallRequest{
|
||||
ResponseType: grpc_testing.PayloadType_COMPRESSABLE,
|
||||
ResponseParameters: []*grpc_testing.ResponseParameters{
|
||||
req := &grpcurl_testing.StreamingOutputCallRequest{
|
||||
ResponseType: grpcurl_testing.PayloadType_COMPRESSABLE,
|
||||
ResponseParameters: []*grpcurl_testing.ResponseParameters{
|
||||
{Size: 10}, {Size: 20}, {Size: 30}, {Size: 40}, {Size: 50},
|
||||
},
|
||||
}
|
||||
|
|
@ -422,19 +570,19 @@ func doTestServerStream(t *testing.T, cc *grpc.ClientConn, source DescriptorSour
|
|||
|
||||
// Success
|
||||
h := &handler{reqMessages: []string{payload}}
|
||||
err = InvokeRpc(context.Background(), source, cc, "grpc.testing.TestService/StreamingOutputCall", makeHeaders(codes.OK), h, h.getRequestData)
|
||||
err = InvokeRpc(context.Background(), source, cc, "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{}
|
||||
if h.check(t, "testing.TestService.StreamingOutputCall", codes.OK, 1, 5) {
|
||||
resp := &grpcurl_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 resp.Payload.GetType() != grpcurl_testing.PayloadType_COMPRESSABLE {
|
||||
t.Errorf("response %d has wrong payload type; expecting %v, got %v", i, grpcurl_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))
|
||||
|
|
@ -445,21 +593,21 @@ func doTestServerStream(t *testing.T, cc *grpc.ClientConn, source DescriptorSour
|
|||
|
||||
// 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)
|
||||
err = InvokeRpc(context.Background(), source, cc, "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)
|
||||
h.check(t, "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)
|
||||
err = InvokeRpc(context.Background(), source, cc, "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)
|
||||
h.check(t, "testing.TestService.StreamingOutputCall", codes.AlreadyExists, 1, 5)
|
||||
}
|
||||
|
||||
func TestHalfDuplexStream(t *testing.T) {
|
||||
|
|
@ -475,12 +623,12 @@ func doTestHalfDuplexStream(t *testing.T, cc *grpc.ClientConn, source Descriptor
|
|||
|
||||
// Success
|
||||
h := &handler{reqMessages: reqs}
|
||||
err := InvokeRpc(context.Background(), source, cc, "grpc.testing.TestService/HalfDuplexCall", makeHeaders(codes.OK), h, h.getRequestData)
|
||||
err := InvokeRpc(context.Background(), source, cc, "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) {
|
||||
if h.check(t, "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)
|
||||
|
|
@ -490,21 +638,21 @@ func doTestHalfDuplexStream(t *testing.T, cc *grpc.ClientConn, source Descriptor
|
|||
|
||||
// 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)
|
||||
err = InvokeRpc(context.Background(), source, cc, "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)
|
||||
h.check(t, "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)
|
||||
err = InvokeRpc(context.Background(), source, cc, "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)
|
||||
h.check(t, "testing.TestService.HalfDuplexCall", codes.DataLoss, 3, 3)
|
||||
}
|
||||
|
||||
func TestFullDuplexStream(t *testing.T) {
|
||||
|
|
@ -517,11 +665,11 @@ func TestFullDuplexStream(t *testing.T) {
|
|||
|
||||
func doTestFullDuplexStream(t *testing.T, cc *grpc.ClientConn, source DescriptorSource) {
|
||||
reqs := make([]string, 3)
|
||||
req := &grpc_testing.StreamingOutputCallRequest{
|
||||
ResponseType: grpc_testing.PayloadType_RANDOM,
|
||||
req := &grpcurl_testing.StreamingOutputCallRequest{
|
||||
ResponseType: grpcurl_testing.PayloadType_RANDOM,
|
||||
}
|
||||
for i := range reqs {
|
||||
req.ResponseParameters = append(req.ResponseParameters, &grpc_testing.ResponseParameters{Size: int32((i + 1) * 10)})
|
||||
req.ResponseParameters = append(req.ResponseParameters, &grpcurl_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)
|
||||
|
|
@ -531,13 +679,13 @@ func doTestFullDuplexStream(t *testing.T, cc *grpc.ClientConn, source Descriptor
|
|||
|
||||
// Success
|
||||
h := &handler{reqMessages: reqs}
|
||||
err := InvokeRpc(context.Background(), source, cc, "grpc.testing.TestService/FullDuplexCall", makeHeaders(codes.OK), h, h.getRequestData)
|
||||
err := InvokeRpc(context.Background(), source, cc, "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{}
|
||||
if h.check(t, "testing.TestService.FullDuplexCall", codes.OK, 3, 6) {
|
||||
resp := &grpcurl_testing.StreamingOutputCallResponse{}
|
||||
i := 0
|
||||
for j := 1; j < 3; j++ {
|
||||
// three requests
|
||||
|
|
@ -547,8 +695,8 @@ func doTestFullDuplexStream(t *testing.T, cc *grpc.ClientConn, source Descriptor
|
|||
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 resp.Payload.GetType() != grpcurl_testing.PayloadType_RANDOM {
|
||||
t.Errorf("response %d has wrong payload type; expecting %v, got %v", i, grpcurl_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))
|
||||
|
|
@ -562,21 +710,21 @@ func doTestFullDuplexStream(t *testing.T, cc *grpc.ClientConn, source Descriptor
|
|||
|
||||
// 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)
|
||||
err = InvokeRpc(context.Background(), source, cc, "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)
|
||||
h.check(t, "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)
|
||||
err = InvokeRpc(context.Background(), source, cc, "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)
|
||||
h.check(t, "testing.TestService.FullDuplexCall", codes.ResourceExhausted, 3, 6)
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
|
|
@ -1,18 +1,18 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"github.com/golang/protobuf/ptypes/empty"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// bankServer implements the Bank gRPC service.
|
||||
type bankServer struct {
|
||||
UnimplementedBankServer
|
||||
allAccounts *accounts
|
||||
}
|
||||
|
||||
|
|
@ -72,26 +72,29 @@ func (s *bankServer) GetTransactions(req *GetTransactionsRequest, stream Bank_Ge
|
|||
|
||||
var start, end time.Time
|
||||
if req.Start != nil {
|
||||
start, err = ptypes.Timestamp(req.Start)
|
||||
err := req.Start.CheckValid()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
start = req.Start.AsTime()
|
||||
}
|
||||
if req.End != nil {
|
||||
end, err = ptypes.Timestamp(req.End)
|
||||
err := req.End.CheckValid()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
end = req.End.AsTime()
|
||||
} else {
|
||||
end = time.Date(9999, 12, 31, 23, 59, 59, 999999999, time.Local)
|
||||
}
|
||||
|
||||
txns := acct.getTransactions()
|
||||
for _, txn := range txns {
|
||||
t, err := ptypes.Timestamp(txn.Date)
|
||||
err := txn.Date.CheckValid()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t := txn.Date.AsTime()
|
||||
if (t.After(start) || t.Equal(start)) &&
|
||||
(t.Before(end) || t.Equal(end)) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,6 +1,6 @@
|
|||
syntax = "proto3";
|
||||
|
||||
option go_package = "main";
|
||||
option go_package = ".;main";
|
||||
|
||||
import "google/protobuf/empty.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
|
@ -0,0 +1,374 @@
|
|||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
// BankClient is the client API for Bank service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type BankClient interface {
|
||||
// OpenAccount creates an account with the type and given initial deposit
|
||||
// as its balance.
|
||||
OpenAccount(ctx context.Context, in *OpenAccountRequest, opts ...grpc.CallOption) (*Account, error)
|
||||
// CloseAccount closes the indicated account. An account can only be
|
||||
// closed if its balance is zero.
|
||||
CloseAccount(ctx context.Context, in *CloseAccountRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
// GetAccounts lists all accounts for the current customer.
|
||||
GetAccounts(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*GetAccountsResponse, error)
|
||||
// GetTransactions streams all transactions that match the given criteria.
|
||||
// If the given start date is not specified, transactions since beginning
|
||||
// of time are included. Similarly, if the given end date is not specified,
|
||||
// transactions all the way to the presnet are included.
|
||||
GetTransactions(ctx context.Context, in *GetTransactionsRequest, opts ...grpc.CallOption) (Bank_GetTransactionsClient, error)
|
||||
// Deposit increases the balance of an account by depositing funds into it.
|
||||
Deposit(ctx context.Context, in *DepositRequest, opts ...grpc.CallOption) (*BalanceResponse, error)
|
||||
// Withdraw decreases the balance of an account by withdrawing funds from it.
|
||||
Withdraw(ctx context.Context, in *WithdrawRequest, opts ...grpc.CallOption) (*BalanceResponse, error)
|
||||
// Transfer moves money from one account to another. The source and destination
|
||||
// accounts can be with this bank (e.g. "local" account numbers) or can be
|
||||
// external accounts, identified by their ACH routing and account numbers.
|
||||
Transfer(ctx context.Context, in *TransferRequest, opts ...grpc.CallOption) (*TransferResponse, error)
|
||||
}
|
||||
|
||||
type bankClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewBankClient(cc grpc.ClientConnInterface) BankClient {
|
||||
return &bankClient{cc}
|
||||
}
|
||||
|
||||
func (c *bankClient) OpenAccount(ctx context.Context, in *OpenAccountRequest, opts ...grpc.CallOption) (*Account, error) {
|
||||
out := new(Account)
|
||||
err := c.cc.Invoke(ctx, "/Bank/OpenAccount", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *bankClient) CloseAccount(ctx context.Context, in *CloseAccountRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, "/Bank/CloseAccount", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *bankClient) GetAccounts(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*GetAccountsResponse, error) {
|
||||
out := new(GetAccountsResponse)
|
||||
err := c.cc.Invoke(ctx, "/Bank/GetAccounts", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *bankClient) GetTransactions(ctx context.Context, in *GetTransactionsRequest, opts ...grpc.CallOption) (Bank_GetTransactionsClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, &Bank_ServiceDesc.Streams[0], "/Bank/GetTransactions", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &bankGetTransactionsClient{stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type Bank_GetTransactionsClient interface {
|
||||
Recv() (*Transaction, error)
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
type bankGetTransactionsClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *bankGetTransactionsClient) Recv() (*Transaction, error) {
|
||||
m := new(Transaction)
|
||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *bankClient) Deposit(ctx context.Context, in *DepositRequest, opts ...grpc.CallOption) (*BalanceResponse, error) {
|
||||
out := new(BalanceResponse)
|
||||
err := c.cc.Invoke(ctx, "/Bank/Deposit", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *bankClient) Withdraw(ctx context.Context, in *WithdrawRequest, opts ...grpc.CallOption) (*BalanceResponse, error) {
|
||||
out := new(BalanceResponse)
|
||||
err := c.cc.Invoke(ctx, "/Bank/Withdraw", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *bankClient) Transfer(ctx context.Context, in *TransferRequest, opts ...grpc.CallOption) (*TransferResponse, error) {
|
||||
out := new(TransferResponse)
|
||||
err := c.cc.Invoke(ctx, "/Bank/Transfer", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// BankServer is the server API for Bank service.
|
||||
// All implementations must embed UnimplementedBankServer
|
||||
// for forward compatibility
|
||||
type BankServer interface {
|
||||
// OpenAccount creates an account with the type and given initial deposit
|
||||
// as its balance.
|
||||
OpenAccount(context.Context, *OpenAccountRequest) (*Account, error)
|
||||
// CloseAccount closes the indicated account. An account can only be
|
||||
// closed if its balance is zero.
|
||||
CloseAccount(context.Context, *CloseAccountRequest) (*emptypb.Empty, error)
|
||||
// GetAccounts lists all accounts for the current customer.
|
||||
GetAccounts(context.Context, *emptypb.Empty) (*GetAccountsResponse, error)
|
||||
// GetTransactions streams all transactions that match the given criteria.
|
||||
// If the given start date is not specified, transactions since beginning
|
||||
// of time are included. Similarly, if the given end date is not specified,
|
||||
// transactions all the way to the presnet are included.
|
||||
GetTransactions(*GetTransactionsRequest, Bank_GetTransactionsServer) error
|
||||
// Deposit increases the balance of an account by depositing funds into it.
|
||||
Deposit(context.Context, *DepositRequest) (*BalanceResponse, error)
|
||||
// Withdraw decreases the balance of an account by withdrawing funds from it.
|
||||
Withdraw(context.Context, *WithdrawRequest) (*BalanceResponse, error)
|
||||
// Transfer moves money from one account to another. The source and destination
|
||||
// accounts can be with this bank (e.g. "local" account numbers) or can be
|
||||
// external accounts, identified by their ACH routing and account numbers.
|
||||
Transfer(context.Context, *TransferRequest) (*TransferResponse, error)
|
||||
mustEmbedUnimplementedBankServer()
|
||||
}
|
||||
|
||||
// UnimplementedBankServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedBankServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedBankServer) OpenAccount(context.Context, *OpenAccountRequest) (*Account, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method OpenAccount not implemented")
|
||||
}
|
||||
func (UnimplementedBankServer) CloseAccount(context.Context, *CloseAccountRequest) (*emptypb.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method CloseAccount not implemented")
|
||||
}
|
||||
func (UnimplementedBankServer) GetAccounts(context.Context, *emptypb.Empty) (*GetAccountsResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetAccounts not implemented")
|
||||
}
|
||||
func (UnimplementedBankServer) GetTransactions(*GetTransactionsRequest, Bank_GetTransactionsServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method GetTransactions not implemented")
|
||||
}
|
||||
func (UnimplementedBankServer) Deposit(context.Context, *DepositRequest) (*BalanceResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Deposit not implemented")
|
||||
}
|
||||
func (UnimplementedBankServer) Withdraw(context.Context, *WithdrawRequest) (*BalanceResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Withdraw not implemented")
|
||||
}
|
||||
func (UnimplementedBankServer) Transfer(context.Context, *TransferRequest) (*TransferResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Transfer not implemented")
|
||||
}
|
||||
func (UnimplementedBankServer) mustEmbedUnimplementedBankServer() {}
|
||||
|
||||
// UnsafeBankServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to BankServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeBankServer interface {
|
||||
mustEmbedUnimplementedBankServer()
|
||||
}
|
||||
|
||||
func RegisterBankServer(s grpc.ServiceRegistrar, srv BankServer) {
|
||||
s.RegisterService(&Bank_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _Bank_OpenAccount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(OpenAccountRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(BankServer).OpenAccount(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/Bank/OpenAccount",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(BankServer).OpenAccount(ctx, req.(*OpenAccountRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Bank_CloseAccount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(CloseAccountRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(BankServer).CloseAccount(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/Bank/CloseAccount",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(BankServer).CloseAccount(ctx, req.(*CloseAccountRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Bank_GetAccounts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(emptypb.Empty)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(BankServer).GetAccounts(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/Bank/GetAccounts",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(BankServer).GetAccounts(ctx, req.(*emptypb.Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Bank_GetTransactions_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(GetTransactionsRequest)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(BankServer).GetTransactions(m, &bankGetTransactionsServer{stream})
|
||||
}
|
||||
|
||||
type Bank_GetTransactionsServer interface {
|
||||
Send(*Transaction) error
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
type bankGetTransactionsServer struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *bankGetTransactionsServer) Send(m *Transaction) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func _Bank_Deposit_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DepositRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(BankServer).Deposit(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/Bank/Deposit",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(BankServer).Deposit(ctx, req.(*DepositRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Bank_Withdraw_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(WithdrawRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(BankServer).Withdraw(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/Bank/Withdraw",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(BankServer).Withdraw(ctx, req.(*WithdrawRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Bank_Transfer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(TransferRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(BankServer).Transfer(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/Bank/Transfer",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(BankServer).Transfer(ctx, req.(*TransferRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// Bank_ServiceDesc is the grpc.ServiceDesc for Bank service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var Bank_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "Bank",
|
||||
HandlerType: (*BankServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "OpenAccount",
|
||||
Handler: _Bank_OpenAccount_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "CloseAccount",
|
||||
Handler: _Bank_CloseAccount_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetAccounts",
|
||||
Handler: _Bank_GetAccounts_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Deposit",
|
||||
Handler: _Bank_Deposit_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Withdraw",
|
||||
Handler: _Bank_Withdraw_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Transfer",
|
||||
Handler: _Bank_Transfer_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "GetTransactions",
|
||||
Handler: _Bank_GetTransactions_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "bank.proto",
|
||||
}
|
||||
|
|
@ -1,20 +1,21 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
// chatServer implements the Support gRPC service, for providing
|
||||
// a capability to connect customers and support agents in real-time
|
||||
// chat.
|
||||
type chatServer struct {
|
||||
UnimplementedSupportServer
|
||||
chatsBySession map[string]*session
|
||||
chatsAwaitingAgent []string
|
||||
lastSession int32
|
||||
|
|
@ -134,7 +135,7 @@ func (s *chatServer) ChatCustomer(stream Support_ChatCustomerServer) error {
|
|||
}
|
||||
|
||||
entry := &ChatEntry{
|
||||
Date: ptypes.TimestampNow(),
|
||||
Date: timestamppb.Now(),
|
||||
Entry: &ChatEntry_CustomerMsg{
|
||||
CustomerMsg: req.Msg,
|
||||
},
|
||||
|
|
@ -279,7 +280,7 @@ func (s *chatServer) ChatAgent(stream Support_ChatAgentServer) error {
|
|||
}
|
||||
|
||||
entry := &ChatEntry{
|
||||
Date: ptypes.TimestampNow(),
|
||||
Date: timestamppb.Now(),
|
||||
Entry: &ChatEntry_AgentMsg{
|
||||
AgentMsg: &AgentMessage{
|
||||
AgentName: agent,
|
||||
|
|
@ -1,14 +1,13 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/golang/protobuf/jsonpb"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
// In-memory database that is periodically saved to a JSON file.
|
||||
|
|
@ -38,11 +37,12 @@ func (a *accounts) openAccount(customer string, accountType Account_Type, initia
|
|||
a.AccountNumbersByCustomer[customer] = accountNums
|
||||
var acct account
|
||||
acct.AccountNumber = num
|
||||
acct.Type = accountType
|
||||
acct.BalanceCents = initialBalanceCents
|
||||
acct.Transactions = append(acct.Transactions, &Transaction{
|
||||
AccountNumber: num,
|
||||
SeqNumber: 1,
|
||||
Date: ptypes.TimestampNow(),
|
||||
Date: timestamppb.Now(),
|
||||
AmountCents: initialBalanceCents,
|
||||
Desc: "initial deposit",
|
||||
})
|
||||
|
|
@ -129,7 +129,7 @@ func (a *account) newTransaction(amountCents int32, desc string) (newBalance int
|
|||
a.BalanceCents = bal
|
||||
a.Transactions = append(a.Transactions, &Transaction{
|
||||
AccountNumber: a.AccountNumber,
|
||||
Date: ptypes.TimestampNow(),
|
||||
Date: timestamppb.Now(),
|
||||
AmountCents: amountCents,
|
||||
SeqNumber: uint64(len(a.Transactions) + 1),
|
||||
Desc: desc,
|
||||
|
|
@ -138,16 +138,11 @@ func (a *account) newTransaction(amountCents int32, desc string) (newBalance int
|
|||
}
|
||||
|
||||
func (t *Transaction) MarshalJSON() ([]byte, error) {
|
||||
var jsm jsonpb.Marshaler
|
||||
var buf bytes.Buffer
|
||||
if err := jsm.Marshal(&buf, t); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
return protojson.Marshal(t)
|
||||
}
|
||||
|
||||
func (t *Transaction) UnmarshalJSON(b []byte) error {
|
||||
return jsonpb.Unmarshal(bytes.NewReader(b), t)
|
||||
return protojson.Unmarshal(b, t)
|
||||
}
|
||||
|
||||
func (a *accounts) clone() *accounts {
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
package main
|
||||
|
||||
//go:generate protoc --go_out=plugins=grpc:./ bank.proto support.proto
|
||||
//go:generate protoc --go_out=. --go-grpc_out=. bank.proto support.proto
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
|
@ -14,7 +14,6 @@ import (
|
|||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"google.golang.org/grpc/peer"
|
||||
|
|
@ -130,7 +129,7 @@ type svr struct {
|
|||
}
|
||||
|
||||
func (s *svr) load() error {
|
||||
accts, err := ioutil.ReadFile(s.datafile)
|
||||
accts, err := os.ReadFile(s.datafile)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
|
@ -162,7 +161,7 @@ func (s *svr) flush() {
|
|||
|
||||
if b, err := json.Marshal(accounts); err != nil {
|
||||
grpclog.Errorf("failed to save data to %q", s.datafile)
|
||||
} else if err := ioutil.WriteFile(s.datafile, b, 0666); err != nil {
|
||||
} else if err := os.WriteFile(s.datafile, b, 0666); err != nil {
|
||||
grpclog.Errorf("failed to save data to %q", s.datafile)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,6 +1,6 @@
|
|||
syntax = "proto3";
|
||||
|
||||
option go_package = "main";
|
||||
option go_package = ".;main";
|
||||
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
|
|
@ -0,0 +1,217 @@
|
|||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
// SupportClient is the client API for Support service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type SupportClient interface {
|
||||
// ChatCustomer is used by a customer-facing app to send the customer's messages
|
||||
// to a chat session. The customer is how initiates and terminates (via "hangup")
|
||||
// a chat session. Only customers may invoke this method (e.g. requests must
|
||||
// include customer auth credentials).
|
||||
ChatCustomer(ctx context.Context, opts ...grpc.CallOption) (Support_ChatCustomerClient, error)
|
||||
// ChatAgent is used by an agent-facing app to allow an agent to reply to a
|
||||
// customer's messages in a chat session. The agent may accept a chat session,
|
||||
// which defaults to the session awaiting an agent for the longest period of time
|
||||
// (FIFO queue).
|
||||
ChatAgent(ctx context.Context, opts ...grpc.CallOption) (Support_ChatAgentClient, error)
|
||||
}
|
||||
|
||||
type supportClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewSupportClient(cc grpc.ClientConnInterface) SupportClient {
|
||||
return &supportClient{cc}
|
||||
}
|
||||
|
||||
func (c *supportClient) ChatCustomer(ctx context.Context, opts ...grpc.CallOption) (Support_ChatCustomerClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, &Support_ServiceDesc.Streams[0], "/Support/ChatCustomer", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &supportChatCustomerClient{stream}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type Support_ChatCustomerClient interface {
|
||||
Send(*ChatCustomerRequest) error
|
||||
Recv() (*ChatCustomerResponse, error)
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
type supportChatCustomerClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *supportChatCustomerClient) Send(m *ChatCustomerRequest) error {
|
||||
return x.ClientStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *supportChatCustomerClient) Recv() (*ChatCustomerResponse, error) {
|
||||
m := new(ChatCustomerResponse)
|
||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *supportClient) ChatAgent(ctx context.Context, opts ...grpc.CallOption) (Support_ChatAgentClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, &Support_ServiceDesc.Streams[1], "/Support/ChatAgent", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &supportChatAgentClient{stream}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type Support_ChatAgentClient interface {
|
||||
Send(*ChatAgentRequest) error
|
||||
Recv() (*ChatAgentResponse, error)
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
type supportChatAgentClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *supportChatAgentClient) Send(m *ChatAgentRequest) error {
|
||||
return x.ClientStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *supportChatAgentClient) Recv() (*ChatAgentResponse, error) {
|
||||
m := new(ChatAgentResponse)
|
||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// SupportServer is the server API for Support service.
|
||||
// All implementations must embed UnimplementedSupportServer
|
||||
// for forward compatibility
|
||||
type SupportServer interface {
|
||||
// ChatCustomer is used by a customer-facing app to send the customer's messages
|
||||
// to a chat session. The customer is how initiates and terminates (via "hangup")
|
||||
// a chat session. Only customers may invoke this method (e.g. requests must
|
||||
// include customer auth credentials).
|
||||
ChatCustomer(Support_ChatCustomerServer) error
|
||||
// ChatAgent is used by an agent-facing app to allow an agent to reply to a
|
||||
// customer's messages in a chat session. The agent may accept a chat session,
|
||||
// which defaults to the session awaiting an agent for the longest period of time
|
||||
// (FIFO queue).
|
||||
ChatAgent(Support_ChatAgentServer) error
|
||||
mustEmbedUnimplementedSupportServer()
|
||||
}
|
||||
|
||||
// UnimplementedSupportServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedSupportServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedSupportServer) ChatCustomer(Support_ChatCustomerServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method ChatCustomer not implemented")
|
||||
}
|
||||
func (UnimplementedSupportServer) ChatAgent(Support_ChatAgentServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method ChatAgent not implemented")
|
||||
}
|
||||
func (UnimplementedSupportServer) mustEmbedUnimplementedSupportServer() {}
|
||||
|
||||
// UnsafeSupportServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to SupportServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeSupportServer interface {
|
||||
mustEmbedUnimplementedSupportServer()
|
||||
}
|
||||
|
||||
func RegisterSupportServer(s grpc.ServiceRegistrar, srv SupportServer) {
|
||||
s.RegisterService(&Support_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _Support_ChatCustomer_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
return srv.(SupportServer).ChatCustomer(&supportChatCustomerServer{stream})
|
||||
}
|
||||
|
||||
type Support_ChatCustomerServer interface {
|
||||
Send(*ChatCustomerResponse) error
|
||||
Recv() (*ChatCustomerRequest, error)
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
type supportChatCustomerServer struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *supportChatCustomerServer) Send(m *ChatCustomerResponse) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *supportChatCustomerServer) Recv() (*ChatCustomerRequest, error) {
|
||||
m := new(ChatCustomerRequest)
|
||||
if err := x.ServerStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func _Support_ChatAgent_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
return srv.(SupportServer).ChatAgent(&supportChatAgentServer{stream})
|
||||
}
|
||||
|
||||
type Support_ChatAgentServer interface {
|
||||
Send(*ChatAgentResponse) error
|
||||
Recv() (*ChatAgentRequest, error)
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
type supportChatAgentServer struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *supportChatAgentServer) Send(m *ChatAgentResponse) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *supportChatAgentServer) Recv() (*ChatAgentRequest, error) {
|
||||
m := new(ChatAgentRequest)
|
||||
if err := x.ServerStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Support_ServiceDesc is the grpc.ServiceDesc for Support service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var Support_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "Support",
|
||||
HandlerType: (*SupportServer)(nil),
|
||||
Methods: []grpc.MethodDesc{},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "ChatCustomer",
|
||||
Handler: _Support_ChatCustomer_Handler,
|
||||
ServerStreams: true,
|
||||
ClientStreams: true,
|
||||
},
|
||||
{
|
||||
StreamName: "ChatAgent",
|
||||
Handler: _Support_ChatAgent_Handler,
|
||||
ServerStreams: true,
|
||||
ClientStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "support.proto",
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
|
|
@ -9,17 +10,15 @@ import (
|
|||
"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"
|
||||
grpcurl_testing "github.com/fullstorydev/grpcurl/internal/testing"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -96,7 +95,7 @@ func main() {
|
|||
|
||||
svr := grpc.NewServer(opts...)
|
||||
|
||||
grpc_testing.RegisterTestServiceServer(svr, grpcurl_testing.TestServer{})
|
||||
grpcurl_testing.RegisterTestServiceServer(svr, grpcurl_testing.TestServer{})
|
||||
if !*noreflect {
|
||||
reflection.Register(svr)
|
||||
}
|
||||
|
|
@ -120,7 +119,7 @@ func unaryLogger(ctx context.Context, req interface{}, info *grpc.UnaryServerInf
|
|||
} else {
|
||||
code = codes.Unknown
|
||||
}
|
||||
grpclog.Infof("completed <%d>: %v (%d) %v\n", i, code, code, time.Now().Sub(start))
|
||||
grpclog.Infof("completed <%d>: %v (%d) %v\n", i, code, code, time.Since(start))
|
||||
return rsp, err
|
||||
}
|
||||
|
||||
|
|
@ -135,7 +134,7 @@ func streamLogger(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServer
|
|||
} else {
|
||||
code = codes.Unknown
|
||||
}
|
||||
grpclog.Infof("completed <%d>: %v(%d) %v\n", i, code, code, time.Now().Sub(start))
|
||||
grpclog.Infof("completed <%d>: %v(%d) %v\n", i, code, code, time.Since(start))
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package main
|
||||
|
|
@ -3,9 +3,11 @@ syntax = "proto3";
|
|||
import "google/protobuf/descriptor.proto";
|
||||
import "google/protobuf/empty.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
import "example2.proto";
|
||||
|
||||
message TestRequest {
|
||||
repeated string file_names = 1;
|
||||
repeated Extension extensions = 2;
|
||||
}
|
||||
|
||||
message TestResponse {
|
||||
Binary file not shown.
|
|
@ -0,0 +1,8 @@
|
|||
syntax = "proto3";
|
||||
|
||||
import "google/protobuf/any.proto";
|
||||
|
||||
message Extension {
|
||||
uint64 id = 1;
|
||||
google.protobuf.Any data = 2;
|
||||
}
|
||||
|
|
@ -0,0 +1,344 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.25.0-devel
|
||||
// protoc v3.14.0
|
||||
// source: test_objects.proto
|
||||
|
||||
package jsonpb
|
||||
|
||||
import (
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
anypb "google.golang.org/protobuf/types/known/anypb"
|
||||
durationpb "google.golang.org/protobuf/types/known/durationpb"
|
||||
structpb "google.golang.org/protobuf/types/known/structpb"
|
||||
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||
wrapperspb "google.golang.org/protobuf/types/known/wrapperspb"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// This is a compile-time assertion that a sufficiently up-to-date version
|
||||
// of the legacy proto package is being used.
|
||||
const _ = proto.ProtoPackageIsVersion4
|
||||
|
||||
type KnownTypes struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
An *anypb.Any `protobuf:"bytes,14,opt,name=an" json:"an,omitempty"`
|
||||
Dur *durationpb.Duration `protobuf:"bytes,1,opt,name=dur" json:"dur,omitempty"`
|
||||
St *structpb.Struct `protobuf:"bytes,12,opt,name=st" json:"st,omitempty"`
|
||||
Ts *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=ts" json:"ts,omitempty"`
|
||||
Lv *structpb.ListValue `protobuf:"bytes,15,opt,name=lv" json:"lv,omitempty"`
|
||||
Val *structpb.Value `protobuf:"bytes,16,opt,name=val" json:"val,omitempty"`
|
||||
Dbl *wrapperspb.DoubleValue `protobuf:"bytes,3,opt,name=dbl" json:"dbl,omitempty"`
|
||||
Flt *wrapperspb.FloatValue `protobuf:"bytes,4,opt,name=flt" json:"flt,omitempty"`
|
||||
I64 *wrapperspb.Int64Value `protobuf:"bytes,5,opt,name=i64" json:"i64,omitempty"`
|
||||
U64 *wrapperspb.UInt64Value `protobuf:"bytes,6,opt,name=u64" json:"u64,omitempty"`
|
||||
I32 *wrapperspb.Int32Value `protobuf:"bytes,7,opt,name=i32" json:"i32,omitempty"`
|
||||
U32 *wrapperspb.UInt32Value `protobuf:"bytes,8,opt,name=u32" json:"u32,omitempty"`
|
||||
Bool *wrapperspb.BoolValue `protobuf:"bytes,9,opt,name=bool" json:"bool,omitempty"`
|
||||
Str *wrapperspb.StringValue `protobuf:"bytes,10,opt,name=str" json:"str,omitempty"`
|
||||
Bytes *wrapperspb.BytesValue `protobuf:"bytes,11,opt,name=bytes" json:"bytes,omitempty"`
|
||||
}
|
||||
|
||||
func (x *KnownTypes) Reset() {
|
||||
*x = KnownTypes{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_test_objects_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *KnownTypes) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*KnownTypes) ProtoMessage() {}
|
||||
|
||||
func (x *KnownTypes) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_test_objects_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use KnownTypes.ProtoReflect.Descriptor instead.
|
||||
func (*KnownTypes) Descriptor() ([]byte, []int) {
|
||||
return file_test_objects_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *KnownTypes) GetAn() *anypb.Any {
|
||||
if x != nil {
|
||||
return x.An
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *KnownTypes) GetDur() *durationpb.Duration {
|
||||
if x != nil {
|
||||
return x.Dur
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *KnownTypes) GetSt() *structpb.Struct {
|
||||
if x != nil {
|
||||
return x.St
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *KnownTypes) GetTs() *timestamppb.Timestamp {
|
||||
if x != nil {
|
||||
return x.Ts
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *KnownTypes) GetLv() *structpb.ListValue {
|
||||
if x != nil {
|
||||
return x.Lv
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *KnownTypes) GetVal() *structpb.Value {
|
||||
if x != nil {
|
||||
return x.Val
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *KnownTypes) GetDbl() *wrapperspb.DoubleValue {
|
||||
if x != nil {
|
||||
return x.Dbl
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *KnownTypes) GetFlt() *wrapperspb.FloatValue {
|
||||
if x != nil {
|
||||
return x.Flt
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *KnownTypes) GetI64() *wrapperspb.Int64Value {
|
||||
if x != nil {
|
||||
return x.I64
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *KnownTypes) GetU64() *wrapperspb.UInt64Value {
|
||||
if x != nil {
|
||||
return x.U64
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *KnownTypes) GetI32() *wrapperspb.Int32Value {
|
||||
if x != nil {
|
||||
return x.I32
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *KnownTypes) GetU32() *wrapperspb.UInt32Value {
|
||||
if x != nil {
|
||||
return x.U32
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *KnownTypes) GetBool() *wrapperspb.BoolValue {
|
||||
if x != nil {
|
||||
return x.Bool
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *KnownTypes) GetStr() *wrapperspb.StringValue {
|
||||
if x != nil {
|
||||
return x.Str
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *KnownTypes) GetBytes() *wrapperspb.BytesValue {
|
||||
if x != nil {
|
||||
return x.Bytes
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_test_objects_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_test_objects_proto_rawDesc = []byte{
|
||||
0x0a, 0x12, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x6a, 0x73, 0x6f, 0x6e, 0x70, 0x62, 0x1a, 0x19, 0x67, 0x6f,
|
||||
0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e,
|
||||
0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xba, 0x05, 0x0a, 0x0a, 0x4b, 0x6e, 0x6f, 0x77, 0x6e,
|
||||
0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x02, 0x61, 0x6e, 0x18, 0x0e, 0x20, 0x01, 0x28,
|
||||
0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x02, 0x61, 0x6e, 0x12, 0x2b, 0x0a, 0x03, 0x64,
|
||||
0x75, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x52, 0x03, 0x64, 0x75, 0x72, 0x12, 0x27, 0x0a, 0x02, 0x73, 0x74, 0x18, 0x0c,
|
||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x02, 0x73,
|
||||
0x74, 0x12, 0x2a, 0x0a, 0x02, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x02, 0x74, 0x73, 0x12, 0x2a, 0x0a,
|
||||
0x02, 0x6c, 0x76, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
|
||||
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74,
|
||||
0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x02, 0x6c, 0x76, 0x12, 0x28, 0x0a, 0x03, 0x76, 0x61, 0x6c,
|
||||
0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x03,
|
||||
0x76, 0x61, 0x6c, 0x12, 0x2e, 0x0a, 0x03, 0x64, 0x62, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2e, 0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x03,
|
||||
0x64, 0x62, 0x6c, 0x12, 0x2d, 0x0a, 0x03, 0x66, 0x6c, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2e, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x03, 0x66,
|
||||
0x6c, 0x74, 0x12, 0x2d, 0x0a, 0x03, 0x69, 0x36, 0x34, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||
0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2e, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x03, 0x69, 0x36,
|
||||
0x34, 0x12, 0x2e, 0x0a, 0x03, 0x75, 0x36, 0x34, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c,
|
||||
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2e, 0x55, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x03, 0x75, 0x36,
|
||||
0x34, 0x12, 0x2d, 0x0a, 0x03, 0x69, 0x33, 0x32, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b,
|
||||
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2e, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x03, 0x69, 0x33, 0x32,
|
||||
0x12, 0x2e, 0x0a, 0x03, 0x75, 0x33, 0x32, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||
0x55, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x03, 0x75, 0x33, 0x32,
|
||||
0x12, 0x2e, 0x0a, 0x04, 0x62, 0x6f, 0x6f, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a,
|
||||
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x04, 0x62, 0x6f, 0x6f, 0x6c,
|
||||
0x12, 0x2e, 0x0a, 0x03, 0x73, 0x74, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||
0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x03, 0x73, 0x74, 0x72,
|
||||
0x12, 0x31, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||
0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2e, 0x42, 0x79, 0x74, 0x65, 0x73, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x62, 0x79,
|
||||
0x74, 0x65, 0x73, 0x42, 0x0a, 0x5a, 0x08, 0x2e, 0x3b, 0x6a, 0x73, 0x6f, 0x6e, 0x70, 0x62,
|
||||
}
|
||||
|
||||
var (
|
||||
file_test_objects_proto_rawDescOnce sync.Once
|
||||
file_test_objects_proto_rawDescData = file_test_objects_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_test_objects_proto_rawDescGZIP() []byte {
|
||||
file_test_objects_proto_rawDescOnce.Do(func() {
|
||||
file_test_objects_proto_rawDescData = protoimpl.X.CompressGZIP(file_test_objects_proto_rawDescData)
|
||||
})
|
||||
return file_test_objects_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_test_objects_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_test_objects_proto_goTypes = []interface{}{
|
||||
(*KnownTypes)(nil), // 0: jsonpb.KnownTypes
|
||||
(*anypb.Any)(nil), // 1: google.protobuf.Any
|
||||
(*durationpb.Duration)(nil), // 2: google.protobuf.Duration
|
||||
(*structpb.Struct)(nil), // 3: google.protobuf.Struct
|
||||
(*timestamppb.Timestamp)(nil), // 4: google.protobuf.Timestamp
|
||||
(*structpb.ListValue)(nil), // 5: google.protobuf.ListValue
|
||||
(*structpb.Value)(nil), // 6: google.protobuf.Value
|
||||
(*wrapperspb.DoubleValue)(nil), // 7: google.protobuf.DoubleValue
|
||||
(*wrapperspb.FloatValue)(nil), // 8: google.protobuf.FloatValue
|
||||
(*wrapperspb.Int64Value)(nil), // 9: google.protobuf.Int64Value
|
||||
(*wrapperspb.UInt64Value)(nil), // 10: google.protobuf.UInt64Value
|
||||
(*wrapperspb.Int32Value)(nil), // 11: google.protobuf.Int32Value
|
||||
(*wrapperspb.UInt32Value)(nil), // 12: google.protobuf.UInt32Value
|
||||
(*wrapperspb.BoolValue)(nil), // 13: google.protobuf.BoolValue
|
||||
(*wrapperspb.StringValue)(nil), // 14: google.protobuf.StringValue
|
||||
(*wrapperspb.BytesValue)(nil), // 15: google.protobuf.BytesValue
|
||||
}
|
||||
var file_test_objects_proto_depIdxs = []int32{
|
||||
1, // 0: jsonpb.KnownTypes.an:type_name -> google.protobuf.Any
|
||||
2, // 1: jsonpb.KnownTypes.dur:type_name -> google.protobuf.Duration
|
||||
3, // 2: jsonpb.KnownTypes.st:type_name -> google.protobuf.Struct
|
||||
4, // 3: jsonpb.KnownTypes.ts:type_name -> google.protobuf.Timestamp
|
||||
5, // 4: jsonpb.KnownTypes.lv:type_name -> google.protobuf.ListValue
|
||||
6, // 5: jsonpb.KnownTypes.val:type_name -> google.protobuf.Value
|
||||
7, // 6: jsonpb.KnownTypes.dbl:type_name -> google.protobuf.DoubleValue
|
||||
8, // 7: jsonpb.KnownTypes.flt:type_name -> google.protobuf.FloatValue
|
||||
9, // 8: jsonpb.KnownTypes.i64:type_name -> google.protobuf.Int64Value
|
||||
10, // 9: jsonpb.KnownTypes.u64:type_name -> google.protobuf.UInt64Value
|
||||
11, // 10: jsonpb.KnownTypes.i32:type_name -> google.protobuf.Int32Value
|
||||
12, // 11: jsonpb.KnownTypes.u32:type_name -> google.protobuf.UInt32Value
|
||||
13, // 12: jsonpb.KnownTypes.bool:type_name -> google.protobuf.BoolValue
|
||||
14, // 13: jsonpb.KnownTypes.str:type_name -> google.protobuf.StringValue
|
||||
15, // 14: jsonpb.KnownTypes.bytes:type_name -> google.protobuf.BytesValue
|
||||
15, // [15:15] is the sub-list for method output_type
|
||||
15, // [15:15] is the sub-list for method input_type
|
||||
15, // [15:15] is the sub-list for extension type_name
|
||||
15, // [15:15] is the sub-list for extension extendee
|
||||
0, // [0:15] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_test_objects_proto_init() }
|
||||
func file_test_objects_proto_init() {
|
||||
if File_test_objects_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_test_objects_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*KnownTypes); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_test_objects_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 1,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_test_objects_proto_goTypes,
|
||||
DependencyIndexes: file_test_objects_proto_depIdxs,
|
||||
MessageInfos: file_test_objects_proto_msgTypes,
|
||||
}.Build()
|
||||
File_test_objects_proto = out.File
|
||||
file_test_objects_proto_rawDesc = nil
|
||||
file_test_objects_proto_goTypes = nil
|
||||
file_test_objects_proto_depIdxs = nil
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
syntax = "proto2";
|
||||
|
||||
import "google/protobuf/any.proto";
|
||||
import "google/protobuf/duration.proto";
|
||||
import "google/protobuf/struct.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
import "google/protobuf/wrappers.proto";
|
||||
|
||||
package jsonpb;
|
||||
|
||||
option go_package=".;jsonpb";
|
||||
|
||||
message KnownTypes {
|
||||
optional google.protobuf.Any an = 14;
|
||||
optional google.protobuf.Duration dur = 1;
|
||||
optional google.protobuf.Struct st = 12;
|
||||
optional google.protobuf.Timestamp ts = 2;
|
||||
optional google.protobuf.ListValue lv = 15;
|
||||
optional google.protobuf.Value val = 16;
|
||||
|
||||
optional google.protobuf.DoubleValue dbl = 3;
|
||||
optional google.protobuf.FloatValue flt = 4;
|
||||
optional google.protobuf.Int64Value i64 = 5;
|
||||
optional google.protobuf.UInt64Value u64 = 6;
|
||||
optional google.protobuf.Int32Value i32 = 7;
|
||||
optional google.protobuf.UInt32Value u32 = 8;
|
||||
optional google.protobuf.BoolValue bool = 9;
|
||||
optional google.protobuf.StringValue str = 10;
|
||||
optional google.protobuf.BytesValue bytes = 11;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -18,7 +18,9 @@
|
|||
// of unary/streaming requests/responses.
|
||||
syntax = "proto3";
|
||||
|
||||
package grpc.testing;
|
||||
package testing;
|
||||
|
||||
option go_package = ".;testing";
|
||||
|
||||
message Empty {}
|
||||
|
||||
|
|
@ -172,5 +174,5 @@ service TestService {
|
|||
// that case.
|
||||
service UnimplementedService {
|
||||
// A call that no server should implement
|
||||
rpc UnimplementedCall(grpc.testing.Empty) returns (grpc.testing.Empty);
|
||||
rpc UnimplementedCall(Empty) returns (Empty);
|
||||
}
|
||||
Binary file not shown.
|
|
@ -0,0 +1,523 @@
|
|||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
|
||||
package testing
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
// TestServiceClient is the client API for TestService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type TestServiceClient interface {
|
||||
// One empty request followed by one empty response.
|
||||
EmptyCall(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error)
|
||||
// One request followed by one response.
|
||||
// The server returns the client payload as-is.
|
||||
UnaryCall(ctx context.Context, in *SimpleRequest, opts ...grpc.CallOption) (*SimpleResponse, error)
|
||||
// One request followed by a sequence of responses (streamed download).
|
||||
// The server returns the payload with client desired type and sizes.
|
||||
StreamingOutputCall(ctx context.Context, in *StreamingOutputCallRequest, opts ...grpc.CallOption) (TestService_StreamingOutputCallClient, error)
|
||||
// A sequence of requests followed by one response (streamed upload).
|
||||
// The server returns the aggregated size of client payload as the result.
|
||||
StreamingInputCall(ctx context.Context, opts ...grpc.CallOption) (TestService_StreamingInputCallClient, error)
|
||||
// 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.
|
||||
FullDuplexCall(ctx context.Context, opts ...grpc.CallOption) (TestService_FullDuplexCallClient, error)
|
||||
// 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.
|
||||
HalfDuplexCall(ctx context.Context, opts ...grpc.CallOption) (TestService_HalfDuplexCallClient, error)
|
||||
}
|
||||
|
||||
type testServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewTestServiceClient(cc grpc.ClientConnInterface) TestServiceClient {
|
||||
return &testServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *testServiceClient) EmptyCall(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) {
|
||||
out := new(Empty)
|
||||
err := c.cc.Invoke(ctx, "/testing.TestService/EmptyCall", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *testServiceClient) UnaryCall(ctx context.Context, in *SimpleRequest, opts ...grpc.CallOption) (*SimpleResponse, error) {
|
||||
out := new(SimpleResponse)
|
||||
err := c.cc.Invoke(ctx, "/testing.TestService/UnaryCall", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *testServiceClient) StreamingOutputCall(ctx context.Context, in *StreamingOutputCallRequest, opts ...grpc.CallOption) (TestService_StreamingOutputCallClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, &TestService_ServiceDesc.Streams[0], "/testing.TestService/StreamingOutputCall", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &testServiceStreamingOutputCallClient{stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type TestService_StreamingOutputCallClient interface {
|
||||
Recv() (*StreamingOutputCallResponse, error)
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
type testServiceStreamingOutputCallClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *testServiceStreamingOutputCallClient) Recv() (*StreamingOutputCallResponse, error) {
|
||||
m := new(StreamingOutputCallResponse)
|
||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *testServiceClient) StreamingInputCall(ctx context.Context, opts ...grpc.CallOption) (TestService_StreamingInputCallClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, &TestService_ServiceDesc.Streams[1], "/testing.TestService/StreamingInputCall", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &testServiceStreamingInputCallClient{stream}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type TestService_StreamingInputCallClient interface {
|
||||
Send(*StreamingInputCallRequest) error
|
||||
CloseAndRecv() (*StreamingInputCallResponse, error)
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
type testServiceStreamingInputCallClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *testServiceStreamingInputCallClient) Send(m *StreamingInputCallRequest) error {
|
||||
return x.ClientStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *testServiceStreamingInputCallClient) CloseAndRecv() (*StreamingInputCallResponse, error) {
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := new(StreamingInputCallResponse)
|
||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *testServiceClient) FullDuplexCall(ctx context.Context, opts ...grpc.CallOption) (TestService_FullDuplexCallClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, &TestService_ServiceDesc.Streams[2], "/testing.TestService/FullDuplexCall", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &testServiceFullDuplexCallClient{stream}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type TestService_FullDuplexCallClient interface {
|
||||
Send(*StreamingOutputCallRequest) error
|
||||
Recv() (*StreamingOutputCallResponse, error)
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
type testServiceFullDuplexCallClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *testServiceFullDuplexCallClient) Send(m *StreamingOutputCallRequest) error {
|
||||
return x.ClientStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *testServiceFullDuplexCallClient) Recv() (*StreamingOutputCallResponse, error) {
|
||||
m := new(StreamingOutputCallResponse)
|
||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *testServiceClient) HalfDuplexCall(ctx context.Context, opts ...grpc.CallOption) (TestService_HalfDuplexCallClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, &TestService_ServiceDesc.Streams[3], "/testing.TestService/HalfDuplexCall", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &testServiceHalfDuplexCallClient{stream}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type TestService_HalfDuplexCallClient interface {
|
||||
Send(*StreamingOutputCallRequest) error
|
||||
Recv() (*StreamingOutputCallResponse, error)
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
type testServiceHalfDuplexCallClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *testServiceHalfDuplexCallClient) Send(m *StreamingOutputCallRequest) error {
|
||||
return x.ClientStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *testServiceHalfDuplexCallClient) Recv() (*StreamingOutputCallResponse, error) {
|
||||
m := new(StreamingOutputCallResponse)
|
||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// TestServiceServer is the server API for TestService service.
|
||||
// All implementations must embed UnimplementedTestServiceServer
|
||||
// for forward compatibility
|
||||
type TestServiceServer interface {
|
||||
// One empty request followed by one empty response.
|
||||
EmptyCall(context.Context, *Empty) (*Empty, error)
|
||||
// One request followed by one response.
|
||||
// The server returns the client payload as-is.
|
||||
UnaryCall(context.Context, *SimpleRequest) (*SimpleResponse, error)
|
||||
// One request followed by a sequence of responses (streamed download).
|
||||
// The server returns the payload with client desired type and sizes.
|
||||
StreamingOutputCall(*StreamingOutputCallRequest, TestService_StreamingOutputCallServer) error
|
||||
// A sequence of requests followed by one response (streamed upload).
|
||||
// The server returns the aggregated size of client payload as the result.
|
||||
StreamingInputCall(TestService_StreamingInputCallServer) error
|
||||
// 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.
|
||||
FullDuplexCall(TestService_FullDuplexCallServer) error
|
||||
// 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.
|
||||
HalfDuplexCall(TestService_HalfDuplexCallServer) error
|
||||
mustEmbedUnimplementedTestServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedTestServiceServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedTestServiceServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedTestServiceServer) EmptyCall(context.Context, *Empty) (*Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method EmptyCall not implemented")
|
||||
}
|
||||
func (UnimplementedTestServiceServer) UnaryCall(context.Context, *SimpleRequest) (*SimpleResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method UnaryCall not implemented")
|
||||
}
|
||||
func (UnimplementedTestServiceServer) StreamingOutputCall(*StreamingOutputCallRequest, TestService_StreamingOutputCallServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method StreamingOutputCall not implemented")
|
||||
}
|
||||
func (UnimplementedTestServiceServer) StreamingInputCall(TestService_StreamingInputCallServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method StreamingInputCall not implemented")
|
||||
}
|
||||
func (UnimplementedTestServiceServer) FullDuplexCall(TestService_FullDuplexCallServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method FullDuplexCall not implemented")
|
||||
}
|
||||
func (UnimplementedTestServiceServer) HalfDuplexCall(TestService_HalfDuplexCallServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method HalfDuplexCall not implemented")
|
||||
}
|
||||
func (UnimplementedTestServiceServer) mustEmbedUnimplementedTestServiceServer() {}
|
||||
|
||||
// UnsafeTestServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to TestServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeTestServiceServer interface {
|
||||
mustEmbedUnimplementedTestServiceServer()
|
||||
}
|
||||
|
||||
func RegisterTestServiceServer(s grpc.ServiceRegistrar, srv TestServiceServer) {
|
||||
s.RegisterService(&TestService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _TestService_EmptyCall_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Empty)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(TestServiceServer).EmptyCall(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/testing.TestService/EmptyCall",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(TestServiceServer).EmptyCall(ctx, req.(*Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _TestService_UnaryCall_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(SimpleRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(TestServiceServer).UnaryCall(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/testing.TestService/UnaryCall",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(TestServiceServer).UnaryCall(ctx, req.(*SimpleRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _TestService_StreamingOutputCall_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(StreamingOutputCallRequest)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(TestServiceServer).StreamingOutputCall(m, &testServiceStreamingOutputCallServer{stream})
|
||||
}
|
||||
|
||||
type TestService_StreamingOutputCallServer interface {
|
||||
Send(*StreamingOutputCallResponse) error
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
type testServiceStreamingOutputCallServer struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *testServiceStreamingOutputCallServer) Send(m *StreamingOutputCallResponse) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func _TestService_StreamingInputCall_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
return srv.(TestServiceServer).StreamingInputCall(&testServiceStreamingInputCallServer{stream})
|
||||
}
|
||||
|
||||
type TestService_StreamingInputCallServer interface {
|
||||
SendAndClose(*StreamingInputCallResponse) error
|
||||
Recv() (*StreamingInputCallRequest, error)
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
type testServiceStreamingInputCallServer struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *testServiceStreamingInputCallServer) SendAndClose(m *StreamingInputCallResponse) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *testServiceStreamingInputCallServer) Recv() (*StreamingInputCallRequest, error) {
|
||||
m := new(StreamingInputCallRequest)
|
||||
if err := x.ServerStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func _TestService_FullDuplexCall_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
return srv.(TestServiceServer).FullDuplexCall(&testServiceFullDuplexCallServer{stream})
|
||||
}
|
||||
|
||||
type TestService_FullDuplexCallServer interface {
|
||||
Send(*StreamingOutputCallResponse) error
|
||||
Recv() (*StreamingOutputCallRequest, error)
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
type testServiceFullDuplexCallServer struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *testServiceFullDuplexCallServer) Send(m *StreamingOutputCallResponse) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *testServiceFullDuplexCallServer) Recv() (*StreamingOutputCallRequest, error) {
|
||||
m := new(StreamingOutputCallRequest)
|
||||
if err := x.ServerStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func _TestService_HalfDuplexCall_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
return srv.(TestServiceServer).HalfDuplexCall(&testServiceHalfDuplexCallServer{stream})
|
||||
}
|
||||
|
||||
type TestService_HalfDuplexCallServer interface {
|
||||
Send(*StreamingOutputCallResponse) error
|
||||
Recv() (*StreamingOutputCallRequest, error)
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
type testServiceHalfDuplexCallServer struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *testServiceHalfDuplexCallServer) Send(m *StreamingOutputCallResponse) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *testServiceHalfDuplexCallServer) Recv() (*StreamingOutputCallRequest, error) {
|
||||
m := new(StreamingOutputCallRequest)
|
||||
if err := x.ServerStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// TestService_ServiceDesc is the grpc.ServiceDesc for TestService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var TestService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "testing.TestService",
|
||||
HandlerType: (*TestServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "EmptyCall",
|
||||
Handler: _TestService_EmptyCall_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "UnaryCall",
|
||||
Handler: _TestService_UnaryCall_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "StreamingOutputCall",
|
||||
Handler: _TestService_StreamingOutputCall_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
{
|
||||
StreamName: "StreamingInputCall",
|
||||
Handler: _TestService_StreamingInputCall_Handler,
|
||||
ClientStreams: true,
|
||||
},
|
||||
{
|
||||
StreamName: "FullDuplexCall",
|
||||
Handler: _TestService_FullDuplexCall_Handler,
|
||||
ServerStreams: true,
|
||||
ClientStreams: true,
|
||||
},
|
||||
{
|
||||
StreamName: "HalfDuplexCall",
|
||||
Handler: _TestService_HalfDuplexCall_Handler,
|
||||
ServerStreams: true,
|
||||
ClientStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "test.proto",
|
||||
}
|
||||
|
||||
// UnimplementedServiceClient is the client API for UnimplementedService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type UnimplementedServiceClient interface {
|
||||
// A call that no server should implement
|
||||
UnimplementedCall(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error)
|
||||
}
|
||||
|
||||
type unimplementedServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewUnimplementedServiceClient(cc grpc.ClientConnInterface) UnimplementedServiceClient {
|
||||
return &unimplementedServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *unimplementedServiceClient) UnimplementedCall(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) {
|
||||
out := new(Empty)
|
||||
err := c.cc.Invoke(ctx, "/testing.UnimplementedService/UnimplementedCall", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// UnimplementedServiceServer is the server API for UnimplementedService service.
|
||||
// All implementations must embed UnimplementedUnimplementedServiceServer
|
||||
// for forward compatibility
|
||||
type UnimplementedServiceServer interface {
|
||||
// A call that no server should implement
|
||||
UnimplementedCall(context.Context, *Empty) (*Empty, error)
|
||||
mustEmbedUnimplementedUnimplementedServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedUnimplementedServiceServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedUnimplementedServiceServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedUnimplementedServiceServer) UnimplementedCall(context.Context, *Empty) (*Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method UnimplementedCall not implemented")
|
||||
}
|
||||
func (UnimplementedUnimplementedServiceServer) mustEmbedUnimplementedUnimplementedServiceServer() {}
|
||||
|
||||
// UnsafeUnimplementedServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to UnimplementedServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeUnimplementedServiceServer interface {
|
||||
mustEmbedUnimplementedUnimplementedServiceServer()
|
||||
}
|
||||
|
||||
func RegisterUnimplementedServiceServer(s grpc.ServiceRegistrar, srv UnimplementedServiceServer) {
|
||||
s.RegisterService(&UnimplementedService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _UnimplementedService_UnimplementedCall_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Empty)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(UnimplementedServiceServer).UnimplementedCall(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/testing.UnimplementedService/UnimplementedCall",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(UnimplementedServiceServer).UnimplementedCall(ctx, req.(*Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// UnimplementedService_ServiceDesc is the grpc.ServiceDesc for UnimplementedService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var UnimplementedService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "testing.UnimplementedService",
|
||||
HandlerType: (*UnimplementedServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "UnimplementedCall",
|
||||
Handler: _UnimplementedService_UnimplementedCall_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "test.proto",
|
||||
}
|
||||
|
|
@ -1,14 +1,17 @@
|
|||
package testing
|
||||
|
||||
//go:generate protoc --go_out=. --go-grpc_out=. test.proto
|
||||
//go:generate protoc --descriptor_set_out=./test.protoset test.proto
|
||||
//go:generate protoc --descriptor_set_out=./example.protoset --include_imports example.proto
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
|
||||
|
|
@ -16,10 +19,12 @@ import (
|
|||
)
|
||||
|
||||
// TestServer implements the TestService interface defined in example.proto.
|
||||
type TestServer struct{}
|
||||
type TestServer struct {
|
||||
UnimplementedTestServiceServer
|
||||
}
|
||||
|
||||
// EmptyCall accepts one empty request and issues one empty response.
|
||||
func (TestServer) EmptyCall(ctx context.Context, req *grpc_testing.Empty) (*grpc_testing.Empty, error) {
|
||||
func (TestServer) EmptyCall(ctx context.Context, req *Empty) (*Empty, error) {
|
||||
headers, trailers, failEarly, failLate := processMetadata(ctx)
|
||||
grpc.SetHeader(ctx, headers)
|
||||
grpc.SetTrailer(ctx, trailers)
|
||||
|
|
@ -35,7 +40,7 @@ func (TestServer) EmptyCall(ctx context.Context, req *grpc_testing.Empty) (*grpc
|
|||
|
||||
// UnaryCall accepts one request and issues one response. The response includes
|
||||
// the client's payload as-is.
|
||||
func (TestServer) UnaryCall(ctx context.Context, req *grpc_testing.SimpleRequest) (*grpc_testing.SimpleResponse, error) {
|
||||
func (TestServer) UnaryCall(ctx context.Context, req *SimpleRequest) (*SimpleResponse, error) {
|
||||
headers, trailers, failEarly, failLate := processMetadata(ctx)
|
||||
grpc.SetHeader(ctx, headers)
|
||||
grpc.SetTrailer(ctx, trailers)
|
||||
|
|
@ -46,7 +51,7 @@ func (TestServer) UnaryCall(ctx context.Context, req *grpc_testing.SimpleRequest
|
|||
return nil, status.Error(failLate, "fail")
|
||||
}
|
||||
|
||||
return &grpc_testing.SimpleResponse{
|
||||
return &SimpleResponse{
|
||||
Payload: req.Payload,
|
||||
}, nil
|
||||
}
|
||||
|
|
@ -54,7 +59,7 @@ func (TestServer) UnaryCall(ctx context.Context, req *grpc_testing.SimpleRequest
|
|||
// StreamingOutputCall accepts one request and issues a sequence of responses
|
||||
// (streamed download). The server returns the payload with client desired type
|
||||
// and sizes as specified in the request's ResponseParameters.
|
||||
func (TestServer) StreamingOutputCall(req *grpc_testing.StreamingOutputCallRequest, str grpc_testing.TestService_StreamingOutputCallServer) error {
|
||||
func (TestServer) StreamingOutputCall(req *StreamingOutputCallRequest, str TestService_StreamingOutputCallServer) error {
|
||||
headers, trailers, failEarly, failLate := processMetadata(str.Context())
|
||||
str.SetHeader(headers)
|
||||
str.SetTrailer(trailers)
|
||||
|
|
@ -62,7 +67,7 @@ func (TestServer) StreamingOutputCall(req *grpc_testing.StreamingOutputCallReque
|
|||
return status.Error(failEarly, "fail")
|
||||
}
|
||||
|
||||
rsp := &grpc_testing.StreamingOutputCallResponse{Payload: &grpc_testing.Payload{}}
|
||||
rsp := &StreamingOutputCallResponse{Payload: &Payload{}}
|
||||
for _, param := range req.ResponseParameters {
|
||||
if str.Context().Err() != nil {
|
||||
return str.Context().Err()
|
||||
|
|
@ -92,7 +97,7 @@ func (TestServer) StreamingOutputCall(req *grpc_testing.StreamingOutputCallReque
|
|||
// StreamingInputCall accepts a sequence of requests and issues one response
|
||||
// (streamed upload). The server returns the aggregated size of client payloads
|
||||
// as the result.
|
||||
func (TestServer) StreamingInputCall(str grpc_testing.TestService_StreamingInputCallServer) error {
|
||||
func (TestServer) StreamingInputCall(str TestService_StreamingInputCallServer) error {
|
||||
headers, trailers, failEarly, failLate := processMetadata(str.Context())
|
||||
str.SetHeader(headers)
|
||||
str.SetTrailer(trailers)
|
||||
|
|
@ -114,7 +119,7 @@ func (TestServer) StreamingInputCall(str grpc_testing.TestService_StreamingInput
|
|||
sz += len(req.Payload.Body)
|
||||
}
|
||||
}
|
||||
if err := str.SendAndClose(&grpc_testing.StreamingInputCallResponse{AggregatedPayloadSize: int32(sz)}); err != nil {
|
||||
if err := str.SendAndClose(&StreamingInputCallResponse{AggregatedPayloadSize: int32(sz)}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -127,7 +132,7 @@ func (TestServer) StreamingInputCall(str grpc_testing.TestService_StreamingInput
|
|||
// FullDuplexCall accepts 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 {
|
||||
func (TestServer) FullDuplexCall(str TestService_FullDuplexCallServer) error {
|
||||
headers, trailers, failEarly, failLate := processMetadata(str.Context())
|
||||
str.SetHeader(headers)
|
||||
str.SetTrailer(trailers)
|
||||
|
|
@ -135,7 +140,7 @@ func (TestServer) FullDuplexCall(str grpc_testing.TestService_FullDuplexCallServ
|
|||
return status.Error(failEarly, "fail")
|
||||
}
|
||||
|
||||
rsp := &grpc_testing.StreamingOutputCallResponse{Payload: &grpc_testing.Payload{}}
|
||||
rsp := &StreamingOutputCallResponse{Payload: &Payload{}}
|
||||
for {
|
||||
if str.Context().Err() != nil {
|
||||
return str.Context().Err()
|
||||
|
|
@ -170,7 +175,7 @@ func (TestServer) FullDuplexCall(str grpc_testing.TestService_FullDuplexCallServ
|
|||
// responses. The server buffers all the client requests and then serves them
|
||||
// in order. A stream of responses is returned to the client once the client
|
||||
// half-closes the stream.
|
||||
func (TestServer) HalfDuplexCall(str grpc_testing.TestService_HalfDuplexCallServer) error {
|
||||
func (TestServer) HalfDuplexCall(str TestService_HalfDuplexCallServer) error {
|
||||
headers, trailers, failEarly, failLate := processMetadata(str.Context())
|
||||
str.SetHeader(headers)
|
||||
str.SetTrailer(trailers)
|
||||
|
|
@ -178,7 +183,7 @@ func (TestServer) HalfDuplexCall(str grpc_testing.TestService_HalfDuplexCallServ
|
|||
return status.Error(failEarly, "fail")
|
||||
}
|
||||
|
||||
var reqs []*grpc_testing.StreamingOutputCallRequest
|
||||
var reqs []*StreamingOutputCallRequest
|
||||
for {
|
||||
if str.Context().Err() != nil {
|
||||
return str.Context().Err()
|
||||
|
|
@ -192,7 +197,7 @@ func (TestServer) HalfDuplexCall(str grpc_testing.TestService_HalfDuplexCallServ
|
|||
reqs = append(reqs, req)
|
||||
}
|
||||
}
|
||||
rsp := &grpc_testing.StreamingOutputCallResponse{}
|
||||
rsp := &StreamingOutputCallResponse{}
|
||||
for _, req := range reqs {
|
||||
rsp.Payload = req.Payload
|
||||
if err := str.Send(rsp); err != nil {
|
||||
|
|
@ -251,4 +256,4 @@ func toCode(vals []string) codes.Code {
|
|||
return codes.Code(i)
|
||||
}
|
||||
|
||||
var _ grpc_testing.TestServiceServer = TestServer{}
|
||||
var _ TestServiceServer = TestServer{}
|
||||
|
|
@ -0,0 +1,409 @@
|
|||
package grpcurl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/golang/protobuf/jsonpb" //lint:ignore SA1019 we have to import these because some of their types appear in exported API
|
||||
"github.com/golang/protobuf/proto" //lint:ignore SA1019 same as above
|
||||
"github.com/jhump/protoreflect/desc" //lint:ignore SA1019 same as above
|
||||
"github.com/jhump/protoreflect/dynamic" //lint:ignore SA1019 same as above
|
||||
"github.com/jhump/protoreflect/dynamic/grpcdynamic"
|
||||
"github.com/jhump/protoreflect/grpcreflect"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// 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(proto.Message)
|
||||
// OnReceiveTrailers is called when response trailers and final RPC status have been received.
|
||||
OnReceiveTrailers(*status.Status, metadata.MD)
|
||||
}
|
||||
|
||||
// RequestMessageSupplier is a function that is called to retrieve request
|
||||
// messages for a GRPC operation. This type is deprecated and will be removed in
|
||||
// a future release.
|
||||
//
|
||||
// Deprecated: This is only used with the deprecated InvokeRpc. Instead, use
|
||||
// RequestSupplier with InvokeRPC.
|
||||
type RequestMessageSupplier func() ([]byte, error)
|
||||
|
||||
// InvokeRpc uses the given gRPC connection to invoke the given method. This function is deprecated
|
||||
// and will be removed in a future release. It just delegates to the similarly named InvokeRPC
|
||||
// method, whose signature is only slightly different.
|
||||
//
|
||||
// Deprecated: use InvokeRPC instead.
|
||||
func InvokeRpc(ctx context.Context, source DescriptorSource, cc *grpc.ClientConn, methodName string,
|
||||
headers []string, handler InvocationEventHandler, requestData RequestMessageSupplier) error {
|
||||
|
||||
return InvokeRPC(ctx, source, cc, methodName, headers, handler, func(m proto.Message) error {
|
||||
// New function is almost identical, but the request supplier function works differently.
|
||||
// So we adapt the logic here to maintain compatibility.
|
||||
data, err := requestData()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return jsonpb.Unmarshal(bytes.NewReader(data), m)
|
||||
})
|
||||
}
|
||||
|
||||
// RequestSupplier is a function that is called to populate messages for a gRPC operation. The
|
||||
// function should populate the given message or return a non-nil error. If the supplier has no
|
||||
// more messages, it should return io.EOF. When it returns io.EOF, it should not in any way
|
||||
// modify the given message argument.
|
||||
type RequestSupplier func(proto.Message) error
|
||||
|
||||
// InvokeRPC uses the given gRPC channel 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 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 an empty request message is sent.
|
||||
//
|
||||
// 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, ch grpcdynamic.Channel, methodName string,
|
||||
headers []string, handler InvocationEventHandler, requestData RequestSupplier) 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 {
|
||||
// return a gRPC status error if hasStatus is true
|
||||
errStatus, hasStatus := status.FromError(err)
|
||||
switch {
|
||||
case hasStatus && isNotFoundError(err):
|
||||
return status.Errorf(errStatus.Code(), "target server does not expose service %q: %s", svc, errStatus.Message())
|
||||
case hasStatus:
|
||||
return status.Errorf(errStatus.Code(), "failed to query for service descriptor %q: %s", svc, errStatus.Message())
|
||||
case isNotFoundError(err):
|
||||
return fmt.Errorf("target server does not expose service %q", svc)
|
||||
}
|
||||
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(ch, msgFactory)
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
if mtd.IsClientStreaming() && mtd.IsServerStreaming() {
|
||||
return invokeBidi(ctx, 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 RequestSupplier, req proto.Message) error {
|
||||
|
||||
err := requestData(req)
|
||||
if err != nil && err != io.EOF {
|
||||
return fmt.Errorf("error getting request data: %v", err)
|
||||
}
|
||||
if err != io.EOF {
|
||||
// verify there is no second message, which is a usage error
|
||||
err := requestData(req)
|
||||
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)
|
||||
|
||||
if stat.Code() == codes.OK {
|
||||
handler.OnReceiveResponse(resp)
|
||||
}
|
||||
|
||||
handler.OnReceiveTrailers(stat, respTrailers)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func invokeClientStream(ctx context.Context, stub grpcdynamic.Stub, md *desc.MethodDescriptor, handler InvocationEventHandler,
|
||||
requestData RequestSupplier, 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 {
|
||||
err = requestData(req)
|
||||
if err == io.EOF {
|
||||
resp, err = str.CloseAndReceive()
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting request data: %v", 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 str != nil {
|
||||
if respHeaders, err := str.Header(); err == nil {
|
||||
handler.OnReceiveHeaders(respHeaders)
|
||||
}
|
||||
}
|
||||
|
||||
if stat.Code() == codes.OK {
|
||||
handler.OnReceiveResponse(resp)
|
||||
}
|
||||
|
||||
if str != nil {
|
||||
handler.OnReceiveTrailers(stat, str.Trailer())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func invokeServerStream(ctx context.Context, stub grpcdynamic.Stub, md *desc.MethodDescriptor, handler InvocationEventHandler,
|
||||
requestData RequestSupplier, req proto.Message) error {
|
||||
|
||||
err := requestData(req)
|
||||
if err != nil && err != io.EOF {
|
||||
return fmt.Errorf("error getting request data: %v", err)
|
||||
}
|
||||
if err != io.EOF {
|
||||
// verify there is no second message, which is a usage error
|
||||
err := requestData(req)
|
||||
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 str != nil {
|
||||
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
|
||||
}
|
||||
handler.OnReceiveResponse(resp)
|
||||
}
|
||||
|
||||
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 str != nil {
|
||||
handler.OnReceiveTrailers(stat, str.Trailer())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func invokeBidi(ctx context.Context, stub grpcdynamic.Stub, md *desc.MethodDescriptor, handler InvocationEventHandler,
|
||||
requestData RequestSupplier, req proto.Message) error {
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
// invoke the RPC!
|
||||
str, err := stub.InvokeRpcBidiStream(ctx, md)
|
||||
|
||||
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
|
||||
for err == nil {
|
||||
err = requestData(req)
|
||||
|
||||
if err == io.EOF {
|
||||
err = str.CloseSend()
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error getting request data: %v", err)
|
||||
cancel()
|
||||
break
|
||||
}
|
||||
|
||||
err = str.SendMsg(req)
|
||||
|
||||
req.Reset()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
sendErr.Store(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if str != nil {
|
||||
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
|
||||
}
|
||||
handler.OnReceiveResponse(resp)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if str != nil {
|
||||
handler.OnReceiveTrailers(stat, str.Trailer())
|
||||
}
|
||||
|
||||
return 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
|
||||
}
|
||||
|
||||
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:]
|
||||
}
|
||||
|
|
@ -7,14 +7,17 @@ cd "$(dirname $0)"
|
|||
# Run this script to generate files used by tests.
|
||||
|
||||
echo "Creating protosets..."
|
||||
protoc ../../../google.golang.org/grpc/interop/grpc_testing/test.proto \
|
||||
-I../../../ --include_imports \
|
||||
protoc testing/test.proto \
|
||||
--include_imports \
|
||||
--descriptor_set_out=testing/test.protoset
|
||||
|
||||
protoc testing/example.proto \
|
||||
--include_imports \
|
||||
--descriptor_set_out=testing/example.protoset
|
||||
|
||||
protoc testing/jsonpb_test_proto/test_objects.proto \
|
||||
--go_out=paths=source_relative:.
|
||||
|
||||
echo "Creating certs for TLS testing..."
|
||||
if ! hash certstrap 2>/dev/null; then
|
||||
# certstrap not found: try to install it
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
# Releases of gRPCurl
|
||||
|
||||
This document provides instructions for building a release of `grpcurl`.
|
||||
|
||||
The release process consists of a handful of tasks:
|
||||
1. Drop a release tag in git.
|
||||
2. Build binaries for various platforms. This is done using the local `go` tool and uses `GOOS` and `GOARCH` environment variables to cross-compile for supported platforms.
|
||||
3. Creates a release in GitHub, uploads the binaries, and creates provisional release notes (in the form of a change log).
|
||||
4. Build a docker image for the new release.
|
||||
5. Push the docker image to Docker Hub, with both a version tag and the "latest" tag.
|
||||
6. Submits a PR to update the [Homebrew](https://brew.sh/) recipe with the latest version.
|
||||
|
||||
Most of this is automated via a script in this same directory. The main thing you will need is a GitHub personal access token, which will be used for creating the release in GitHub (so you need write access to the fullstorydev/grpcurl repo) and to open a Homebrew pull request.
|
||||
|
||||
## Creating a new release
|
||||
|
||||
So, to actually create a new release, just run the script in this directory.
|
||||
|
||||
First, you need a version number for the new release, following sem-ver format: `v<Major>.<Minor>.<Patch>`. Second, you need a personal access token for GitHub.
|
||||
|
||||
We'll use `v2.3.4` as an example version and `abcdef0123456789abcdef` as an example GitHub token:
|
||||
|
||||
```sh
|
||||
# from the root of the repo
|
||||
GITHUB_TOKEN=abcdef0123456789abcd \
|
||||
./releasing/do-release.sh v2.3.4
|
||||
```
|
||||
|
||||
Wasn't that easy! There is one last step: update the release notes in GitHub. By default, the script just records a change log of commit descriptions. Use that log (and, if necessary, drill into individual PRs included in the release) to flesh out notes in the format of the `RELEASE_NOTES.md` file _in this directory_. Then login to GitHub, go to the new release, edit the notes, and paste in the markdown you just wrote.
|
||||
|
||||
That should be all there is to it! If things go wrong and you have to re-do part of the process, see the sections below.
|
||||
|
||||
----
|
||||
|
||||
### GitHub Releases
|
||||
The GitHub release is the first step performed by the `do-release.sh` script. So generally, if there is an issue with that step, you can re-try the whole script.
|
||||
|
||||
Note, if running the script did something wrong, you may have to first login to GitHub and remove uploaded artifacts for a botched release attempt. In general, this is _very undesirable_. Releases should usually be considered immutable. Instead of removing uploaded assets and providing new ones, it is often better to remove uploaded assets (to make bad binaries no longer available) and then _release a new patch version_. (You can edit the release notes for the botched version explaining why there are no artifacts for it.)
|
||||
|
||||
The steps to do a GitHub-only release (vs. running the entire script) are the following:
|
||||
|
||||
```sh
|
||||
# from the root of the repo
|
||||
git tag v2.3.4
|
||||
GITHUB_TOKEN=abcdef0123456789abcdef \
|
||||
GO111MODULE=on \
|
||||
make release
|
||||
```
|
||||
|
||||
The `git tag ...` step is necessary because the release target requires that the current SHA have a sem-ver tag. That's the version it will use when creating the release.
|
||||
|
||||
This will create the release in GitHub with provisional release notes that just include a change log of commit messages. You still need to login to GitHub and revise those notes to adhere to the recommended format. (See `RELEASE_NOTES.md` in this directory.)
|
||||
|
||||
### Docker Hub Releases
|
||||
|
||||
To re-run only the Docker Hub release steps, you can manually run through each step in the "Docker" section of `do_release.sh`.
|
||||
|
||||
If the `docker push ...` steps fail, you may need to run `docker login`, enter your Docker Hub login credentials, and then try to push again.
|
||||
|
||||
### Homebrew Releases
|
||||
|
||||
The last step is to update the Homebrew recipe to use the latest version. First, we need to compute the SHA256 checksum for the source archive:
|
||||
|
||||
```sh
|
||||
# download the source archive from GitHub
|
||||
URL=https://github.com/fullstorydev/grpcurl/archive/refs/tags/v2.3.4.tar.gz
|
||||
curl -L -o tmp.tgz $URL
|
||||
# and compute the SHA
|
||||
SHA="$(sha256sum < tmp.tgz | awk '{ print $1 }')"
|
||||
```
|
||||
|
||||
To actually create the brew PR, you need your GitHub personal access token again, as well as the URL and SHA from the previous step:
|
||||
|
||||
```sh
|
||||
HOMEBREW_GITHUB_API_TOKEN=abcdef0123456789abcdef \
|
||||
brew bump-formula-pr --url $URL --sha256 $SHA grpcurl
|
||||
```
|
||||
|
||||
This creates a PR to bump the formula to the new version. When this PR is merged by brew maintainers, the new version becomes available!
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
## Changes
|
||||
|
||||
### Command-line tool
|
||||
|
||||
* _In this list, describe the changes to the command-line tool._
|
||||
* _Use one bullet per change. Include both bug-fixes and improvements. Omit this section if there are no changes that impact the command-line tool._
|
||||
|
||||
### Go package "github.com/fullstorydev/grpcurl"
|
||||
|
||||
* _In this list, describe the changes to exported API in the main package in this repo: "github.com/fullstorydev/grpcurl". These will often be closely related to changes to the command-line tool, though not always: changes that only impact the cmd/grpcurl directory of this repo do not impact exported API._
|
||||
* _Use one bullet per change. Include both bug-fixes and improvements. Omit this section if there are no changes that impact the exported API._
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
#!/bin/bash
|
||||
|
||||
# strict mode
|
||||
set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
|
||||
if [[ -z ${DRY_RUN:-} ]]; then
|
||||
PREFIX=""
|
||||
else
|
||||
PREFIX="echo"
|
||||
fi
|
||||
|
||||
# input validation
|
||||
if [[ -z ${GITHUB_TOKEN:-} ]]; then
|
||||
echo "GITHUB_TOKEN environment variable must be set before running." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ $# -ne 1 || $1 == "" ]]; then
|
||||
echo "This program requires one argument: the version number, in 'vM.N.P' format." >&2
|
||||
exit 1
|
||||
fi
|
||||
VERSION=$1
|
||||
|
||||
# Change to root of the repo
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
# GitHub release
|
||||
|
||||
$PREFIX git tag "$VERSION"
|
||||
# make sure GITHUB_TOKEN is exported, for the benefit of this next command
|
||||
export GITHUB_TOKEN
|
||||
GO111MODULE=on $PREFIX make release
|
||||
# if that was successful, it could have touched go.mod and go.sum, so revert those
|
||||
$PREFIX git checkout go.mod go.sum
|
||||
|
||||
# Docker release
|
||||
|
||||
# make sure credentials are valid for later push steps; this might
|
||||
# be interactive since this will prompt for username and password
|
||||
# if there are no valid current credentials.
|
||||
$PREFIX docker login
|
||||
echo "$VERSION" > VERSION
|
||||
|
||||
# Docker Buildx support is included in Docker 19.03
|
||||
# Below step installs emulators for different architectures on the host
|
||||
# This enables running and building containers for below architectures mentioned using --platforms
|
||||
$PREFIX docker run --privileged --rm tonistiigi/binfmt:qemu-v6.1.0 --install all
|
||||
# Create a new builder instance
|
||||
export DOCKER_CLI_EXPERIMENTAL=enabled
|
||||
$PREFIX docker buildx create --use --name multiarch-builder --node multiarch-builder0
|
||||
# push to docker hub, both the given version as a tag and for "latest" tag
|
||||
$PREFIX docker buildx build --platform linux/amd64,linux/s390x,linux/arm64,linux/ppc64le --tag fullstorydev/grpcurl:${VERSION} --tag fullstorydev/grpcurl:latest --push --progress plain --no-cache .
|
||||
$PREFIX docker buildx build --platform linux/amd64,linux/s390x,linux/arm64,linux/ppc64le --tag fullstorydev/grpcurl:${VERSION}-alpine --tag fullstorydev/grpcurl:latest-alpine --push --progress plain --no-cache --target alpine .
|
||||
rm VERSION
|
||||
|
||||
# Homebrew release
|
||||
|
||||
URL="https://github.com/fullstorydev/grpcurl/archive/refs/tags/${VERSION}.tar.gz"
|
||||
curl -L -o tmp.tgz "$URL"
|
||||
SHA="$(sha256sum < tmp.tgz | awk '{ print $1 }')"
|
||||
rm tmp.tgz
|
||||
HOMEBREW_GITHUB_API_TOKEN="$GITHUB_TOKEN" $PREFIX brew bump-formula-pr --url "$URL" --sha256 "$SHA" grpcurl
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# packing and releasing
|
||||
To pack the current branch to a snap package:
|
||||
|
||||
`snapcraft pack`
|
||||
|
||||
To install the package locally:
|
||||
|
||||
`snap install ./grpcurl_v[version tag]_amd64.snap --devmode`
|
||||
|
||||
To upload the snap to the edge channel:
|
||||
|
||||
`snapcraft upload --release edge ./grpcurl_v[version tag]_amd64.snap`
|
||||
|
||||
(you need to own the package name registration for this!)
|
||||
|
||||
# ownership
|
||||
The snap's current owner is `pietro.pasotti@canonical.com`; who is very happy to support with maintaining the snap distribution and/or transfer its ownership to the developers.
|
||||
|
||||
Please reach out to me for questions regarding the snap; including:
|
||||
- adding support for other architectures
|
||||
- automating the release
|
||||
|
||||
Cheers and thanks for the awesome tool!
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
name: grpcurl
|
||||
base: core24
|
||||
# allow grpcurl part to call craftctl set-version
|
||||
adopt-info: grpcurl
|
||||
summary: grpcurl is a command-line tool that lets you interact with gRPC servers.
|
||||
|
||||
description: |
|
||||
grpcurl is a command-line tool that lets you interact with gRPC servers.
|
||||
It's basically curl for gRPC servers.
|
||||
|
||||
grade: stable
|
||||
confinement: strict
|
||||
license: MIT
|
||||
|
||||
platforms:
|
||||
amd64:
|
||||
build-on:
|
||||
- amd64
|
||||
build-for:
|
||||
- amd64
|
||||
arm64:
|
||||
build-on:
|
||||
- amd64
|
||||
- arm64
|
||||
build-for:
|
||||
- arm64
|
||||
|
||||
apps:
|
||||
grpcurl:
|
||||
command: grpcurl
|
||||
plugs:
|
||||
- network
|
||||
|
||||
parts:
|
||||
grpcurl:
|
||||
plugin: go
|
||||
build-snaps: [go/latest/stable]
|
||||
source: https://github.com/fullstorydev/grpcurl
|
||||
source-type: git
|
||||
override-build: |
|
||||
tag="$(git describe --tags --abbrev=0)"
|
||||
craftctl set version="$tag"
|
||||
|
||||
go build -o $CRAFT_PART_INSTALL/grpcurl ./cmd/grpcurl/grpcurl.go
|
||||
|
||||
# adjust the permissions
|
||||
chmod 0755 $CRAFT_PART_INSTALL/grpcurl
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
|
@ -1,19 +1,18 @@
|
|||
package grpcurl_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
grpcurl_testing "github.com/fullstorydev/grpcurl/internal/testing"
|
||||
)
|
||||
|
||||
func TestPlainText(t *testing.T) {
|
||||
|
|
@ -27,11 +26,11 @@ func TestPlainText(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestBasicTLS(t *testing.T) {
|
||||
serverCreds, err := ServerTransportCredentials("", "testing/tls/server.crt", "testing/tls/server.key", false)
|
||||
serverCreds, err := ServerTransportCredentials("", "internal/testing/tls/server.crt", "internal/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", "", "")
|
||||
clientCreds, err := ClientTransportCredentials(false, "internal/testing/tls/ca.crt", "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create server creds: %v", err)
|
||||
}
|
||||
|
|
@ -46,7 +45,7 @@ func TestBasicTLS(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestInsecureClientTLS(t *testing.T) {
|
||||
serverCreds, err := ServerTransportCredentials("", "testing/tls/server.crt", "testing/tls/server.key", false)
|
||||
serverCreds, err := ServerTransportCredentials("", "internal/testing/tls/server.crt", "internal/testing/tls/server.key", false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create server creds: %v", err)
|
||||
}
|
||||
|
|
@ -65,11 +64,11 @@ func TestInsecureClientTLS(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestClientCertTLS(t *testing.T) {
|
||||
serverCreds, err := ServerTransportCredentials("testing/tls/ca.crt", "testing/tls/server.crt", "testing/tls/server.key", false)
|
||||
serverCreds, err := ServerTransportCredentials("internal/testing/tls/ca.crt", "internal/testing/tls/server.crt", "internal/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")
|
||||
clientCreds, err := ClientTransportCredentials(false, "internal/testing/tls/ca.crt", "internal/testing/tls/client.crt", "internal/testing/tls/client.key")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create server creds: %v", err)
|
||||
}
|
||||
|
|
@ -84,11 +83,11 @@ func TestClientCertTLS(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRequireClientCertTLS(t *testing.T) {
|
||||
serverCreds, err := ServerTransportCredentials("testing/tls/ca.crt", "testing/tls/server.crt", "testing/tls/server.key", true)
|
||||
serverCreds, err := ServerTransportCredentials("internal/testing/tls/ca.crt", "internal/testing/tls/server.crt", "internal/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")
|
||||
clientCreds, err := ClientTransportCredentials(false, "internal/testing/tls/ca.crt", "internal/testing/tls/client.crt", "internal/testing/tls/client.key")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create server creds: %v", err)
|
||||
}
|
||||
|
|
@ -103,35 +102,53 @@ func TestRequireClientCertTLS(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestBrokenTLS_ClientPlainText(t *testing.T) {
|
||||
serverCreds, err := ServerTransportCredentials("", "testing/tls/server.crt", "testing/tls/server.key", false)
|
||||
serverCreds, err := ServerTransportCredentials("", "internal/testing/tls/server.crt", "internal/testing/tls/server.key", false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create server creds: %v", err)
|
||||
}
|
||||
|
||||
// client connection (usually) succeeds since client is not waiting for TLS handshake
|
||||
e, err := createTestServerAndClient(serverCreds, nil)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "deadline exceeded") {
|
||||
// It is possible that connection never becomes healthy:
|
||||
// (we try several times, but if we never get a connection and the error message is
|
||||
// a known/expected possibility, we'll just bail)
|
||||
var e testEnv
|
||||
failCount := 0
|
||||
for {
|
||||
e, err = createTestServerAndClient(serverCreds, nil)
|
||||
if err == nil {
|
||||
// success!
|
||||
defer e.Close()
|
||||
break
|
||||
}
|
||||
|
||||
if strings.Contains(err.Error(), "deadline exceeded") ||
|
||||
strings.Contains(err.Error(), "use of closed network connection") {
|
||||
// It is possible that the connection never becomes healthy:
|
||||
// 1) grpc connects successfully
|
||||
// 2) grpc client tries to send HTTP/2 preface and settings frame
|
||||
// 3) server, expecting handshake, closes the connection
|
||||
// 4) in the client, the write fails, so the connection never
|
||||
// becomes ready
|
||||
// More often than not, the connection becomes ready (presumably
|
||||
// the write to the socket succeeds before the server closes the
|
||||
// connection). But when it does not, it is possible to observe
|
||||
// timeouts when setting up the connection.
|
||||
return
|
||||
// The client will attempt to reconnect on transient errors, so
|
||||
// may eventually bump into the connect time limit. This used to
|
||||
// result in a "deadline exceeded" error, but more recent versions
|
||||
// of the grpc library report any underlying I/O error instead, so
|
||||
// we also check for "use of closed network connection".
|
||||
failCount++
|
||||
if failCount > 5 {
|
||||
return // bail...
|
||||
}
|
||||
// we'll try again
|
||||
|
||||
} else {
|
||||
// some other error occurred, so we'll consider that a test failure
|
||||
t.Fatalf("failed to setup server and client: %v", err)
|
||||
}
|
||||
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{})
|
||||
cl := grpcurl_testing.NewTestServiceClient(e.cc)
|
||||
_, err = cl.UnaryCall(context.Background(), &grpcurl_testing.SimpleRequest{})
|
||||
if err == nil {
|
||||
t.Fatal("expecting failure")
|
||||
}
|
||||
|
|
@ -146,15 +163,15 @@ func TestBrokenTLS_ClientPlainText(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestBrokenTLS_ServerPlainText(t *testing.T) {
|
||||
clientCreds, err := ClientTransportCredentials(false, "testing/tls/ca.crt", "", "")
|
||||
clientCreds, err := ClientTransportCredentials(false, "internal/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()
|
||||
t.Fatal("expecting TLS failure setting up server and client")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "first record does not look like a TLS handshake") {
|
||||
t.Fatalf("expecting TLS handshake failure, got: %v", err)
|
||||
|
|
@ -162,19 +179,19 @@ func TestBrokenTLS_ServerPlainText(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestBrokenTLS_ServerUsesWrongCert(t *testing.T) {
|
||||
serverCreds, err := ServerTransportCredentials("", "testing/tls/other.crt", "testing/tls/other.key", false)
|
||||
serverCreds, err := ServerTransportCredentials("", "internal/testing/tls/other.crt", "internal/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", "", "")
|
||||
clientCreds, err := ClientTransportCredentials(false, "internal/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()
|
||||
t.Fatal("expecting TLS failure setting up server and client")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "certificate is valid for") {
|
||||
t.Fatalf("expecting TLS certificate error, got: %v", err)
|
||||
|
|
@ -182,39 +199,39 @@ func TestBrokenTLS_ServerUsesWrongCert(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestBrokenTLS_ClientHasExpiredCert(t *testing.T) {
|
||||
serverCreds, err := ServerTransportCredentials("testing/tls/ca.crt", "testing/tls/server.crt", "testing/tls/server.key", false)
|
||||
serverCreds, err := ServerTransportCredentials("internal/testing/tls/ca.crt", "internal/testing/tls/server.crt", "internal/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")
|
||||
clientCreds, err := ClientTransportCredentials(false, "internal/testing/tls/ca.crt", "internal/testing/tls/expired.crt", "internal/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()
|
||||
t.Fatal("expecting TLS failure setting up server and client")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "bad certificate") {
|
||||
if !strings.Contains(err.Error(), "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)
|
||||
serverCreds, err := ServerTransportCredentials("", "internal/testing/tls/expired.crt", "internal/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", "", "")
|
||||
clientCreds, err := ClientTransportCredentials(false, "internal/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()
|
||||
t.Fatal("expecting TLS failure setting up server and client")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "certificate has expired or is not yet valid") {
|
||||
t.Fatalf("expecting TLS certificate expired, got: %v", err)
|
||||
|
|
@ -222,70 +239,78 @@ func TestBrokenTLS_ServerHasExpiredCert(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestBrokenTLS_ClientNotTrusted(t *testing.T) {
|
||||
serverCreds, err := ServerTransportCredentials("testing/tls/ca.crt", "testing/tls/server.crt", "testing/tls/server.key", true)
|
||||
serverCreds, err := ServerTransportCredentials("internal/testing/tls/ca.crt", "internal/testing/tls/server.crt", "internal/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")
|
||||
clientCreds, err := ClientTransportCredentials(false, "internal/testing/tls/ca.crt", "internal/testing/tls/wrong-client.crt", "internal/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()
|
||||
t.Fatal("expecting TLS failure setting up server and client")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "bad certificate") {
|
||||
t.Fatalf("expecting TLS certificate error, got: %v", err)
|
||||
// Check for either the old error (Go <=1.24) or the new one (Go 1.25+)
|
||||
// Go 1.24: "bad certificate"
|
||||
// Go 1.25: "handshake failure"
|
||||
errMsg := err.Error()
|
||||
if !strings.Contains(errMsg, "bad certificate") && !strings.Contains(errMsg, "handshake failure") {
|
||||
t.Fatalf("expecting a specific TLS certificate or handshake error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBrokenTLS_ServerNotTrusted(t *testing.T) {
|
||||
serverCreds, err := ServerTransportCredentials("", "testing/tls/server.crt", "testing/tls/server.key", false)
|
||||
serverCreds, err := ServerTransportCredentials("", "internal/testing/tls/server.crt", "internal/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")
|
||||
clientCreds, err := ClientTransportCredentials(false, "", "internal/testing/tls/client.crt", "internal/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()
|
||||
t.Fatal("expecting TLS failure setting up server and client")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "certificate signed by unknown authority") {
|
||||
if !strings.Contains(err.Error(), "certificate") {
|
||||
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)
|
||||
serverCreds, err := ServerTransportCredentials("internal/testing/tls/ca.crt", "internal/testing/tls/server.crt", "internal/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", "", "")
|
||||
clientCreds, err := ClientTransportCredentials(false, "internal/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()
|
||||
t.Fatal("expecting TLS failure setting up server and client")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "bad certificate") {
|
||||
t.Fatalf("expecting TLS certificate error, got: %v", err)
|
||||
// Check for either the old error (Go <=1.24) or the new one (Go 1.25+)
|
||||
// Go 1.24: "bad certificate"
|
||||
// Go 1.25: "handshake failure"
|
||||
errMsg := err.Error()
|
||||
if !strings.Contains(errMsg, "bad certificate") && !strings.Contains(errMsg, "handshake failure") {
|
||||
t.Fatalf("expecting a specific TLS certificate or handshake error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func simpleTest(t *testing.T, cc *grpc.ClientConn) {
|
||||
cl := grpc_testing.NewTestServiceClient(cc)
|
||||
cl := grpcurl_testing.NewTestServiceClient(cc)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
_, err := cl.UnaryCall(ctx, &grpc_testing.SimpleRequest{}, grpc.FailFast(false))
|
||||
_, err := cl.UnaryCall(ctx, &grpcurl_testing.SimpleRequest{}, grpc.WaitForReady(true))
|
||||
if err != nil {
|
||||
t.Errorf("simple RPC failed: %v", err)
|
||||
}
|
||||
|
|
@ -305,7 +330,7 @@ func createTestServerAndClient(serverCreds, clientCreds credentials.TransportCre
|
|||
svrOpts = []grpc.ServerOption{grpc.Creds(serverCreds)}
|
||||
}
|
||||
svr := grpc.NewServer(svrOpts...)
|
||||
grpc_testing.RegisterTestServiceServer(svr, grpcurl_testing.TestServer{})
|
||||
grpcurl_testing.RegisterTestServiceServer(svr, grpcurl_testing.TestServer{})
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
return e, err
|
||||
|
|
@ -332,7 +357,7 @@ type testEnv struct {
|
|||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func (e testEnv) Close() {
|
||||
func (e *testEnv) Close() {
|
||||
if e.cc != nil {
|
||||
e.cc.Close()
|
||||
e.cc = nil
|
||||
|
|
|
|||
Loading…
Reference in New Issue