├── test ├── error_test_server │ ├── .gitignore │ ├── go.mod │ ├── grpcerrors │ │ ├── grpcerrors.proto │ │ ├── grpcerrors.pb.go │ │ └── grpcerrors_grpc.pb.go │ ├── client.go │ ├── server.go │ └── go.sum ├── .gitignore ├── runtests.jl ├── GrpcerrorsClients │ ├── grpcerrors.jl │ ├── GrpcerrorsClients.jl │ └── grpcerrors_pb.jl ├── RouteguideClients │ ├── routeguide.jl │ ├── RouteguideClients.jl │ ├── route_guide.proto │ └── route_guide_pb.jl ├── buildserver.sh ├── runtests_errors.jl ├── runtests_routeguide.jl ├── test_grpcerrors.jl └── test_routeclient.jl ├── .github └── workflows │ ├── TagBot.yml │ └── ci.yml ├── src ├── gRPCClient.jl ├── limitio.jl ├── generate.jl ├── grpc.jl └── curl.jl ├── Project.toml ├── LICENSE.md └── README.md /test/error_test_server/.gitignore: -------------------------------------------------------------------------------- 1 | grpcerrors_* 2 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | grpc-go 2 | server.pid 3 | routeguide_* 4 | grpcerrors_* 5 | testservers 6 | -------------------------------------------------------------------------------- /test/runtests.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | 3 | @testset "gRPCClient" begin 4 | include("runtests_routeguide.jl") 5 | include("runtests_errors.jl") 6 | end 7 | -------------------------------------------------------------------------------- /test/GrpcerrorsClients/grpcerrors.jl: -------------------------------------------------------------------------------- 1 | module grpcerrors 2 | const _ProtoBuf_Top_ = @static isdefined(parentmodule(@__MODULE__), :_ProtoBuf_Top_) ? (parentmodule(@__MODULE__))._ProtoBuf_Top_ : parentmodule(@__MODULE__) 3 | include("grpcerrors_pb.jl") 4 | end 5 | -------------------------------------------------------------------------------- /test/RouteguideClients/routeguide.jl: -------------------------------------------------------------------------------- 1 | module routeguide 2 | const _ProtoBuf_Top_ = @static isdefined(parentmodule(@__MODULE__), :_ProtoBuf_Top_) ? (parentmodule(@__MODULE__))._ProtoBuf_Top_ : parentmodule(@__MODULE__) 3 | include("route_guide_pb.jl") 4 | end 5 | -------------------------------------------------------------------------------- /.github/workflows/TagBot.yml: -------------------------------------------------------------------------------- 1 | name: TagBot 2 | on: 3 | issue_comment: 4 | types: 5 | - created 6 | workflow_dispatch: 7 | jobs: 8 | TagBot: 9 | if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: JuliaRegistries/TagBot@v1 13 | with: 14 | token: ${{ secrets.GITHUB_TOKEN }} 15 | -------------------------------------------------------------------------------- /test/error_test_server/go.mod: -------------------------------------------------------------------------------- 1 | module juliacomputing.com/errortest 2 | 3 | go 1.11 4 | 5 | require ( 6 | github.com/golang/protobuf v1.5.2 7 | golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6 // indirect 8 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 // indirect 9 | google.golang.org/genproto v0.0.0-20210423144448-3a41ef94ed2b // indirect 10 | google.golang.org/grpc v1.37.0 11 | google.golang.org/protobuf v1.26.0 12 | ) 13 | -------------------------------------------------------------------------------- /src/gRPCClient.jl: -------------------------------------------------------------------------------- 1 | module gRPCClient 2 | 3 | using LibCURL 4 | using Downloads 5 | using ProtoBuf 6 | 7 | import Downloads: Curl 8 | import ProtoBuf: call_method 9 | 10 | export gRPCController, gRPCChannel, gRPCException, gRPCServiceCallException, gRPCMessageTooLargeException, gRPCStatus, gRPCCheck, StatusCode 11 | 12 | abstract type gRPCException <: Exception end 13 | 14 | include("limitio.jl") 15 | include("curl.jl") 16 | include("grpc.jl") 17 | include("generate.jl") 18 | 19 | function __init__() 20 | GRPC_STATIC_HEADERS[] = grpc_headers() 21 | end 22 | 23 | end # module 24 | -------------------------------------------------------------------------------- /Project.toml: -------------------------------------------------------------------------------- 1 | name = "gRPCClient" 2 | uuid = "aaca4a50-36af-4a1d-b878-4c443f2061ad" 3 | authors = ["Tanmay K.M. "] 4 | version = "0.1.2" 5 | 6 | [deps] 7 | Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6" 8 | LibCURL = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" 9 | ProtoBuf = "3349acd9-ac6a-5e09-bcdb-63829b23a429" 10 | 11 | [compat] 12 | Downloads = "1.3" 13 | LibCURL = "0.6" 14 | ProtoBuf = "0.11" 15 | julia = "1.3" 16 | 17 | [extras] 18 | Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" 19 | Sockets = "6462fe0b-24de-5631-8697-dd941f90decc" 20 | Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" 21 | 22 | [targets] 23 | test = ["Random", "Sockets", "Test"] 24 | -------------------------------------------------------------------------------- /test/error_test_server/grpcerrors/grpcerrors.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "juliacomputing.com/grpcerrors"; 4 | 5 | package grpcerrors; 6 | 7 | // Interface exported by the server. 8 | service GRPCErrors { 9 | // simple RPC, takes a message and responds with a message 10 | rpc SimpleRPC(Data) returns (Data) {} 11 | 12 | // streaming response, takes a message and responds with a stream 13 | rpc StreamResponse(Data) returns (stream Data) {} 14 | 15 | // streaming request, takes streaming input and responds with a message 16 | rpc StreamRequest(stream Data) returns (Data) {} 17 | 18 | // streaming request and response 19 | rpc StreamRequestResponse(stream Data) returns (stream Data) {} 20 | } 21 | 22 | // Request parameter, dictates how the simulation should behave. 23 | // mode can take values: 24 | // 1: throw an error after seconds provided in `param` 25 | // 2: no error, just wait until seconds provided in `param`, respond with SimulationParams 26 | // 27 | // when sent in a stream as input, the server would consider only the first one in the 28 | // stream to determine the course of action 29 | message Data { 30 | int32 mode = 1; 31 | int32 param = 2; 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The gRPCClient.jl package is licensed under the MIT "Expat" License: 2 | 3 | > Copyright (c) 2021: Julia Computing, Inc. 4 | > 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | > of this software and associated documentation files (the "Software"), to deal 7 | > in the Software without restriction, including without limitation the rights 8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | > copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in all 13 | > copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | > SOFTWARE. 22 | > 23 | -------------------------------------------------------------------------------- /test/error_test_server/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "log" 7 | "time" 8 | 9 | "google.golang.org/grpc" 10 | pb "juliacomputing.com/errortest/grpcerrors" 11 | ) 12 | 13 | var ( 14 | tls = flag.Bool("tls", false, "Connection uses TLS if true, else plain TCP") 15 | caFile = flag.String("ca_file", "", "The file containing the CA root cert file") 16 | serverAddr = flag.String("server_addr", "localhost:10000", "The server address in the format of host:port") 17 | serverHostOverride = flag.String("server_host_override", "x.test.youtube.com", "The server name used to verify the hostname returned by the TLS handshake") 18 | ) 19 | 20 | func simpleRPC(client pb.GRPCErrorsClient, data *pb.Data) { 21 | log.Printf("Calling simpleRPC for data (%d, %d)", data.Mode, data.Param) 22 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 23 | defer cancel() 24 | respdata, err := client.SimpleRPC(ctx, data) 25 | if err != nil { 26 | log.Fatalf("%v.SimpleRPC(_) = _, %v: ", client, err) 27 | } 28 | log.Println(respdata) 29 | } 30 | 31 | func main() { 32 | flag.Parse() 33 | var opts []grpc.DialOption 34 | opts = append(opts, grpc.WithInsecure()) 35 | opts = append(opts, grpc.WithBlock()) 36 | conn, err := grpc.Dial(*serverAddr, opts...) 37 | if err != nil { 38 | log.Fatalf("fail to dial: %v", err) 39 | } 40 | defer conn.Close() 41 | client := pb.NewGRPCErrorsClient(conn) 42 | 43 | // simpel RPC 44 | simpleRPC(client, &pb.Data{Mode: 1, Param: 0}) 45 | } 46 | 47 | -------------------------------------------------------------------------------- /src/limitio.jl: -------------------------------------------------------------------------------- 1 | # limits number of bytes written to an io stream (originally from https://github.com/JuliaDebug/Debugger.jl/blob/master/src/limitio.jl) 2 | # useful to detect messages that would go over limit when converted to bytes. 3 | mutable struct LimitIO{IO_t <: IO} <: IO 4 | io::IO_t 5 | maxbytes::Int 6 | n::Int # max bytes to write 7 | end 8 | LimitIO(io::IO, maxbytes) = LimitIO(io, maxbytes, 0) 9 | 10 | function Base.write(io::LimitIO, v::UInt8) 11 | io.n > io.maxbytes && throw(gRPCMessageTooLargeException(io.maxbytes, io.n)) 12 | nincr = write(io.io, v) 13 | io.n += nincr 14 | nincr 15 | end 16 | 17 | """ 18 | Default maximum gRPC message size 19 | """ 20 | const DEFAULT_MAX_MESSAGE_LENGTH = 1024*1024*4 21 | 22 | """ 23 | struct gRPCMessageTooLargeException 24 | limit::Int 25 | encountered::Int 26 | end 27 | 28 | A `gRPMessageTooLargeException` exception is thrown when a message is 29 | encountered that has a size greater than the limit configured. 30 | Specifically, `max_recv_message_length` while receiving and 31 | `max_send_message_length` while sending. 32 | 33 | A `gRPMessageTooLargeException` has the following members: 34 | 35 | - `limit`: the limit value that was exceeded 36 | - `encountered`: the amount of data that was actually received 37 | or sent before this error was triggered. Note that this may 38 | not correspond to the full size of the data, as error may be 39 | thrown before actually materializing the complete data. 40 | """ 41 | struct gRPCMessageTooLargeException <: gRPCException 42 | limit::Int 43 | encountered::Int 44 | end 45 | 46 | Base.show(io::IO, m::gRPCMessageTooLargeException) = print(io, "gRPMessageTooLargeException($(m.limit), $(m.encountered)) - Encountered message size $(m.encountered) > max configured $(m.limit)") -------------------------------------------------------------------------------- /test/buildserver.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 5 | export PATH="$PATH:$(go env GOPATH)/bin" 6 | mkdir -p ${BASEDIR}/testservers 7 | 8 | # build routeguide server 9 | cd ${BASEDIR} 10 | if [ ! -d "grpc-go" ] 11 | then 12 | git clone -b v1.35.0 https://github.com/grpc/grpc-go 13 | fi 14 | 15 | cd grpc-go/examples/route_guide 16 | protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative routeguide/route_guide.proto 17 | sed 's/localhost/0.0.0.0/g' server/server.go > server/server.go.new 18 | rm server/server.go 19 | mv server/server.go.new server/server.go 20 | 21 | export GOOS=linux 22 | export GOARCH=amd64 23 | echo "building routeguide_${GOOS}_${GOARCH}..." 24 | go build -o routeguide_${GOOS}_${GOARCH} -i server/server.go 25 | export GOARCH=386 26 | echo "building routeguide_${GOOS}_${GOARCH}..." 27 | go build -o routeguide_${GOOS}_${GOARCH} -i server/server.go 28 | export GOOS=windows 29 | export GOARCH=amd64 30 | echo "building routeguide_${GOOS}_${GOARCH}..." 31 | go build -o routeguide_${GOOS}_${GOARCH}.exe -i server/server.go 32 | export GOARCH=386 33 | echo "building routeguide_${GOOS}_${GOARCH}..." 34 | go build -o routeguide_${GOOS}_${GOARCH}.exe -i server/server.go 35 | export GOOS=darwin 36 | export GOARCH=amd64 37 | echo "building routeguide_${GOOS}_${GOARCH}..." 38 | go build -o routeguide_${GOOS}_${GOARCH} -i server/server.go 39 | 40 | cp routeguide_* ${BASEDIR}/testservers/ 41 | 42 | # build grpcerrors server 43 | cd ${BASEDIR} 44 | cd error_test_server 45 | 46 | export GOOS=linux 47 | export GOARCH=amd64 48 | echo "building grpcerrors_${GOOS}_${GOARCH}..." 49 | go build -o grpcerrors_${GOOS}_${GOARCH} -i server.go 50 | export GOARCH=386 51 | echo "building grpcerrors_${GOOS}_${GOARCH}..." 52 | go build -o grpcerrors_${GOOS}_${GOARCH} -i server.go 53 | export GOOS=windows 54 | export GOARCH=amd64 55 | echo "building grpcerrors_${GOOS}_${GOARCH}..." 56 | go build -o grpcerrors_${GOOS}_${GOARCH}.exe -i server.go 57 | export GOARCH=386 58 | echo "building grpcerrors_${GOOS}_${GOARCH}..." 59 | go build -o grpcerrors_${GOOS}_${GOARCH}.exe -i server.go 60 | export GOOS=darwin 61 | export GOARCH=amd64 62 | echo "building grpcerrors_${GOOS}_${GOARCH}..." 63 | go build -o grpcerrors_${GOOS}_${GOARCH} -i server.go 64 | 65 | cp grpcerrors_* ${BASEDIR}/testservers/ 66 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: [main] 5 | tags: ["*"] 6 | pull_request: 7 | jobs: 8 | test: 9 | name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} 10 | runs-on: ${{ matrix.os }} 11 | continue-on-error: ${{ matrix.experimental }} 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | version: 16 | - '1.3' 17 | - '1' # automatically expands to the latest stable 1.x release of Julia 18 | os: 19 | - ubuntu-latest 20 | arch: 21 | - x64 22 | - x86 23 | experimental: [false] 24 | include: 25 | # allow failures on nightlies 26 | - os: ubuntu-latest 27 | arch: x64 28 | version: nightly 29 | experimental: true 30 | - os: ubuntu-latest 31 | arch: x86 32 | version: nightly 33 | experimental: true 34 | # test macOS and Windows with latest Julia only 35 | - os: macOS-latest 36 | arch: x64 37 | version: 1 38 | experimental: true 39 | - os: windows-latest 40 | arch: x64 41 | version: 1 42 | experimental: false 43 | - os: windows-latest 44 | arch: x86 45 | version: 1 46 | experimental: false 47 | steps: 48 | - uses: actions/checkout@v2 49 | - name: setup protoc 50 | uses: arduino/setup-protoc@v1 51 | with: 52 | version: '3.x' 53 | - run: protoc --version 54 | - uses: julia-actions/setup-julia@v1 55 | with: 56 | version: ${{ matrix.version }} 57 | arch: ${{ matrix.arch }} 58 | - uses: actions/cache@v1 59 | env: 60 | cache-name: cache-artifacts 61 | with: 62 | path: ~/.julia/artifacts 63 | key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} 64 | restore-keys: | 65 | ${{ runner.os }}-test-${{ env.cache-name }}- 66 | ${{ runner.os }}-test- 67 | ${{ runner.os }}- 68 | - uses: julia-actions/julia-buildpkg@v1 69 | - uses: julia-actions/julia-runtest@v1 70 | - uses: julia-actions/julia-processcoverage@v1 71 | - uses: codecov/codecov-action@v1 72 | with: 73 | file: lcov.info -------------------------------------------------------------------------------- /test/runtests_errors.jl: -------------------------------------------------------------------------------- 1 | module ErrorTest 2 | 3 | using gRPCClient 4 | using Downloads 5 | using Random 6 | using Sockets 7 | using Test 8 | 9 | const SERVER_RELEASE = "https://github.com/JuliaComputing/gRPCClient.jl/releases/download/testserver_v0.2/" 10 | function server_binary() 11 | arch = (Sys.ARCH === :x86_64) ? "amd64" : "386" 12 | filename = Sys.islinux() ? "grpcerrors_linux_$(arch)" : 13 | Sys.iswindows() ? "grpcerrors_windows_$(arch).exe" : 14 | Sys.isapple() ? "grpcerrors_darwin_$(arch)" : 15 | error("no server binary available for this platform") 16 | source = string(SERVER_RELEASE, filename) 17 | destination = joinpath(@__DIR__, filename) 18 | isfile(destination) || Downloads.download(source, destination) 19 | ((filemode(destination) & 0o777) == 0o777) || chmod(destination, 0o777) 20 | 21 | destination 22 | end 23 | 24 | function start_server() 25 | serverbin = server_binary() 26 | 27 | @info("starting test server", serverbin) 28 | serverproc = run(`$serverbin`; wait=false) 29 | 30 | listening = timedwait(120.0; pollint=5.0) do 31 | try 32 | sock = connect(ip"127.0.0.1", 10000) 33 | close(sock) 34 | true 35 | catch 36 | false 37 | end 38 | end 39 | 40 | if listening !== :ok 41 | @warn("test server did not start, stopping server") 42 | kill(serverproc) 43 | error("test server did not start") 44 | end 45 | 46 | serverproc 47 | end 48 | 49 | function test_generate() 50 | @testset "codegen" begin 51 | dir = joinpath(@__DIR__, "GrpcerrorsClients") 52 | gRPCClient.generate(joinpath(@__DIR__, "error_test_server", "grpcerrors", "grpcerrors.proto"); outdir=dir) 53 | @test isfile(joinpath(dir, "GrpcerrorsClients.jl")) 54 | @test isfile(joinpath(dir, "grpcerrors.jl")) 55 | @test isfile(joinpath(dir, "grpcerrors_pb.jl")) 56 | end 57 | end 58 | 59 | # switch off host verification for tests 60 | if isempty(get(ENV, "JULIA_NO_VERIFY_HOSTS", "")) 61 | ENV["JULIA_NO_VERIFY_HOSTS"] = "**" 62 | end 63 | 64 | server_endpoint = isempty(ARGS) ? "http://localhost:10000/" : ARGS[1] 65 | @info("server endpoint: $server_endpoint") 66 | 67 | @testset "Server Errors" begin 68 | if !Sys.iswindows() 69 | test_generate() 70 | else 71 | @info("skipping code generation on Windows to avoid needing batch file execution permissions") 72 | end 73 | include("test_grpcerrors.jl") 74 | serverproc = start_server() 75 | 76 | @info("testing grpcerrors...") 77 | test_clients(server_endpoint) 78 | 79 | kill(serverproc) 80 | @info("stopped test server") 81 | end 82 | 83 | end # module ErrorTest 84 | -------------------------------------------------------------------------------- /test/RouteguideClients/RouteguideClients.jl: -------------------------------------------------------------------------------- 1 | module RouteguideClients 2 | using gRPCClient 3 | 4 | include("routeguide.jl") 5 | using .routeguide 6 | 7 | import Base: show 8 | 9 | # begin service: routeguide.RouteGuide 10 | 11 | export RouteGuideBlockingClient, RouteGuideClient 12 | 13 | struct RouteGuideBlockingClient 14 | controller::gRPCController 15 | channel::gRPCChannel 16 | stub::RouteGuideBlockingStub 17 | 18 | function RouteGuideBlockingClient(api_base_url::String; kwargs...) 19 | controller = gRPCController(; kwargs...) 20 | channel = gRPCChannel(api_base_url) 21 | stub = RouteGuideBlockingStub(channel) 22 | new(controller, channel, stub) 23 | end 24 | end 25 | 26 | struct RouteGuideClient 27 | controller::gRPCController 28 | channel::gRPCChannel 29 | stub::RouteGuideStub 30 | 31 | function RouteGuideClient(api_base_url::String; kwargs...) 32 | controller = gRPCController(; kwargs...) 33 | channel = gRPCChannel(api_base_url) 34 | stub = RouteGuideStub(channel) 35 | new(controller, channel, stub) 36 | end 37 | end 38 | 39 | show(io::IO, client::RouteGuideBlockingClient) = print(io, "RouteGuideBlockingClient(", client.channel.baseurl, ")") 40 | show(io::IO, client::RouteGuideClient) = print(io, "RouteGuideClient(", client.channel.baseurl, ")") 41 | 42 | import .routeguide: GetFeature 43 | """ 44 | GetFeature 45 | 46 | - input: routeguide.Point 47 | - output: routeguide.Feature 48 | """ 49 | GetFeature(client::RouteGuideBlockingClient, inp::routeguide.Point) = GetFeature(client.stub, client.controller, inp) 50 | GetFeature(client::RouteGuideClient, inp::routeguide.Point, done::Function) = GetFeature(client.stub, client.controller, inp, done) 51 | 52 | import .routeguide: ListFeatures 53 | """ 54 | ListFeatures 55 | 56 | - input: routeguide.Rectangle 57 | - output: Channel{routeguide.Feature} 58 | """ 59 | ListFeatures(client::RouteGuideBlockingClient, inp::routeguide.Rectangle) = ListFeatures(client.stub, client.controller, inp) 60 | ListFeatures(client::RouteGuideClient, inp::routeguide.Rectangle, done::Function) = ListFeatures(client.stub, client.controller, inp, done) 61 | 62 | import .routeguide: RecordRoute 63 | """ 64 | RecordRoute 65 | 66 | - input: Channel{routeguide.Point} 67 | - output: routeguide.RouteSummary 68 | """ 69 | RecordRoute(client::RouteGuideBlockingClient, inp::Channel{routeguide.Point}) = RecordRoute(client.stub, client.controller, inp) 70 | RecordRoute(client::RouteGuideClient, inp::Channel{routeguide.Point}, done::Function) = RecordRoute(client.stub, client.controller, inp, done) 71 | 72 | import .routeguide: RouteChat 73 | """ 74 | RouteChat 75 | 76 | - input: Channel{routeguide.RouteNote} 77 | - output: Channel{routeguide.RouteNote} 78 | """ 79 | RouteChat(client::RouteGuideBlockingClient, inp::Channel{routeguide.RouteNote}) = RouteChat(client.stub, client.controller, inp) 80 | RouteChat(client::RouteGuideClient, inp::Channel{routeguide.RouteNote}, done::Function) = RouteChat(client.stub, client.controller, inp, done) 81 | 82 | # end service: routeguide.RouteGuide 83 | 84 | end # module RouteguideClients 85 | -------------------------------------------------------------------------------- /test/GrpcerrorsClients/GrpcerrorsClients.jl: -------------------------------------------------------------------------------- 1 | module GrpcerrorsClients 2 | using gRPCClient 3 | 4 | include("grpcerrors.jl") 5 | using .grpcerrors 6 | 7 | import Base: show 8 | 9 | # begin service: grpcerrors.GRPCErrors 10 | 11 | export GRPCErrorsBlockingClient, GRPCErrorsClient 12 | 13 | struct GRPCErrorsBlockingClient 14 | controller::gRPCController 15 | channel::gRPCChannel 16 | stub::GRPCErrorsBlockingStub 17 | 18 | function GRPCErrorsBlockingClient(api_base_url::String; kwargs...) 19 | controller = gRPCController(; kwargs...) 20 | channel = gRPCChannel(api_base_url) 21 | stub = GRPCErrorsBlockingStub(channel) 22 | new(controller, channel, stub) 23 | end 24 | end 25 | 26 | struct GRPCErrorsClient 27 | controller::gRPCController 28 | channel::gRPCChannel 29 | stub::GRPCErrorsStub 30 | 31 | function GRPCErrorsClient(api_base_url::String; kwargs...) 32 | controller = gRPCController(; kwargs...) 33 | channel = gRPCChannel(api_base_url) 34 | stub = GRPCErrorsStub(channel) 35 | new(controller, channel, stub) 36 | end 37 | end 38 | 39 | show(io::IO, client::GRPCErrorsBlockingClient) = print(io, "GRPCErrorsBlockingClient(", client.channel.baseurl, ")") 40 | show(io::IO, client::GRPCErrorsClient) = print(io, "GRPCErrorsClient(", client.channel.baseurl, ")") 41 | 42 | import .grpcerrors: SimpleRPC 43 | """ 44 | SimpleRPC 45 | 46 | - input: grpcerrors.Data 47 | - output: grpcerrors.Data 48 | """ 49 | SimpleRPC(client::GRPCErrorsBlockingClient, inp::grpcerrors.Data) = SimpleRPC(client.stub, client.controller, inp) 50 | SimpleRPC(client::GRPCErrorsClient, inp::grpcerrors.Data, done::Function) = SimpleRPC(client.stub, client.controller, inp, done) 51 | 52 | import .grpcerrors: StreamResponse 53 | """ 54 | StreamResponse 55 | 56 | - input: grpcerrors.Data 57 | - output: Channel{grpcerrors.Data} 58 | """ 59 | StreamResponse(client::GRPCErrorsBlockingClient, inp::grpcerrors.Data) = StreamResponse(client.stub, client.controller, inp) 60 | StreamResponse(client::GRPCErrorsClient, inp::grpcerrors.Data, done::Function) = StreamResponse(client.stub, client.controller, inp, done) 61 | 62 | import .grpcerrors: StreamRequest 63 | """ 64 | StreamRequest 65 | 66 | - input: Channel{grpcerrors.Data} 67 | - output: grpcerrors.Data 68 | """ 69 | StreamRequest(client::GRPCErrorsBlockingClient, inp::Channel{grpcerrors.Data}) = StreamRequest(client.stub, client.controller, inp) 70 | StreamRequest(client::GRPCErrorsClient, inp::Channel{grpcerrors.Data}, done::Function) = StreamRequest(client.stub, client.controller, inp, done) 71 | 72 | import .grpcerrors: StreamRequestResponse 73 | """ 74 | StreamRequestResponse 75 | 76 | - input: Channel{grpcerrors.Data} 77 | - output: Channel{grpcerrors.Data} 78 | """ 79 | StreamRequestResponse(client::GRPCErrorsBlockingClient, inp::Channel{grpcerrors.Data}) = StreamRequestResponse(client.stub, client.controller, inp) 80 | StreamRequestResponse(client::GRPCErrorsClient, inp::Channel{grpcerrors.Data}, done::Function) = StreamRequestResponse(client.stub, client.controller, inp, done) 81 | 82 | # end service: grpcerrors.GRPCErrors 83 | 84 | end # module GrpcerrorsClients 85 | -------------------------------------------------------------------------------- /test/runtests_routeguide.jl: -------------------------------------------------------------------------------- 1 | module RouteClientTest 2 | 3 | using gRPCClient 4 | using Downloads 5 | using Random 6 | using Sockets 7 | using Test 8 | 9 | const SERVER_RELEASE = "https://github.com/JuliaComputing/gRPCClient.jl/releases/download/testserver_v0.2/" 10 | function server_binary() 11 | arch = (Sys.ARCH === :x86_64) ? "amd64" : "386" 12 | filename = Sys.islinux() ? "routeguide_linux_$(arch)" : 13 | Sys.iswindows() ? "routeguide_windows_$(arch).exe" : 14 | Sys.isapple() ? "routeguide_darwin_$(arch)" : 15 | error("no server binary available for this platform") 16 | source = string(SERVER_RELEASE, filename) 17 | destination = joinpath(@__DIR__, filename) 18 | isfile(destination) || Downloads.download(source, destination) 19 | ((filemode(destination) & 0o777) == 0o777) || chmod(destination, 0o777) 20 | 21 | destination 22 | end 23 | 24 | function start_server() 25 | serverbin = server_binary() 26 | 27 | @info("starting test server", serverbin) 28 | serverproc = run(`$serverbin`; wait=false) 29 | 30 | listening = timedwait(120.0; pollint=5.0) do 31 | try 32 | sock = connect(ip"127.0.0.1", 10000) 33 | close(sock) 34 | true 35 | catch 36 | false 37 | end 38 | end 39 | 40 | if listening !== :ok 41 | @warn("test server did not start, stopping server") 42 | kill(serverproc) 43 | error("test server did not start") 44 | end 45 | 46 | serverproc 47 | end 48 | 49 | function test_generate() 50 | @testset "codegen" begin 51 | dir = joinpath(@__DIR__, "RouteguideClients") 52 | gRPCClient.generate(joinpath(dir, "route_guide.proto"); outdir=dir) 53 | @test isfile(joinpath(dir, "route_guide_pb.jl")) 54 | @test isfile(joinpath(dir, "routeguide.jl")) 55 | @test isfile(joinpath(dir, "RouteguideClients.jl")) 56 | end 57 | end 58 | 59 | function test_timeout_header_values() 60 | @testset "timeout header" begin 61 | @test "100S" == gRPCClient.grpc_timeout_header_val(100) 62 | @test "100010m" == gRPCClient.grpc_timeout_header_val(100.01) 63 | @test "100000100u" == gRPCClient.grpc_timeout_header_val(100.0001) 64 | @test "100000010000n" == gRPCClient.grpc_timeout_header_val(100.00001) 65 | end 66 | end 67 | 68 | # switch off host verification for tests 69 | if isempty(get(ENV, "JULIA_NO_VERIFY_HOSTS", "")) 70 | ENV["JULIA_NO_VERIFY_HOSTS"] = "**" 71 | end 72 | 73 | server_endpoint = isempty(ARGS) ? "http://localhost:10000/" : ARGS[1] 74 | @info("server endpoint: $server_endpoint") 75 | 76 | @testset "RouteGuide" begin 77 | if !Sys.iswindows() 78 | test_generate() 79 | else 80 | @info("skipping code generation on Windows to avoid needing batch file execution permissions") 81 | end 82 | 83 | test_timeout_header_values() 84 | 85 | include("test_routeclient.jl") 86 | serverproc = start_server() 87 | 88 | @debug("testing routeclinet...") 89 | test_clients(server_endpoint) 90 | 91 | @debug("testing async safety...") 92 | test_task_safety(server_endpoint) 93 | 94 | kill(serverproc) 95 | @info("stopped test server") 96 | end 97 | 98 | end # module RouteClientTest 99 | -------------------------------------------------------------------------------- /test/error_test_server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "flag" 7 | "fmt" 8 | "io" 9 | "log" 10 | "net" 11 | "time" 12 | 13 | "google.golang.org/grpc" 14 | 15 | "google.golang.org/grpc/codes" 16 | "google.golang.org/grpc/credentials" 17 | "google.golang.org/grpc/status" 18 | 19 | pb "juliacomputing.com/errortest/grpcerrors" 20 | ) 21 | 22 | var ( 23 | tls = flag.Bool("tls", false, "Connection uses TLS if true, else plain TCP") 24 | certFile = flag.String("cert_file", "", "The TLS cert file") 25 | keyFile = flag.String("key_file", "", "The TLS key file") 26 | port = flag.Int("port", 10000, "The server port") 27 | ) 28 | 29 | type gRPCErrorsServer struct { 30 | pb.UnimplementedGRPCErrorsServer 31 | } 32 | 33 | func (gRPCErrorsServer) SimpleRPC(ctx context.Context, data *pb.Data) (*pb.Data, error) { 34 | if data.Mode == 1 { 35 | time.Sleep(time.Duration(int64(data.Param)) * time.Second) 36 | return data, errors.New("simulated error mode 1") 37 | } else if data.Mode == 2 { 38 | time.Sleep(time.Duration(int64(data.Param)) * time.Second) 39 | return data, nil 40 | } else { 41 | return nil, status.Errorf(codes.Unimplemented, "mode not implemented") 42 | } 43 | } 44 | 45 | func (gRPCErrorsServer) StreamResponse(data *pb.Data, stream pb.GRPCErrors_StreamResponseServer) error { 46 | if data.Mode == 1 { 47 | time.Sleep(time.Duration(int64(data.Param)) * time.Second) 48 | return errors.New("simulated error mode 1") 49 | } else if data.Mode == 2 { 50 | time.Sleep(time.Duration(int64(data.Param)) * time.Second) 51 | if err := stream.Send(data); err != nil { 52 | return err 53 | } 54 | return nil 55 | } else { 56 | return status.Errorf(codes.Unimplemented, "mode not implemented") 57 | } 58 | } 59 | 60 | func (gRPCErrorsServer) StreamRequest(stream pb.GRPCErrors_StreamRequestServer) error { 61 | data, err := stream.Recv() 62 | if err == io.EOF { 63 | return nil 64 | } 65 | if err != nil { 66 | return err 67 | } 68 | 69 | if data.Mode == 1 { 70 | time.Sleep(time.Duration(int64(data.Param)) * time.Second) 71 | return errors.New("simulated error mode 1") 72 | } else if data.Mode == 2 { 73 | time.Sleep(time.Duration(int64(data.Param)) * time.Second) 74 | return stream.SendAndClose(data) 75 | } else { 76 | return status.Errorf(codes.Unimplemented, "mode not implemented") 77 | } 78 | } 79 | 80 | func (gRPCErrorsServer) StreamRequestResponse(stream pb.GRPCErrors_StreamRequestResponseServer) error { 81 | data, err := stream.Recv() 82 | if err == io.EOF { 83 | return nil 84 | } 85 | if err != nil { 86 | return err 87 | } 88 | 89 | if data.Mode == 1 { 90 | time.Sleep(time.Duration(int64(data.Param)) * time.Second) 91 | return errors.New("simulated error mode 1") 92 | } else if data.Mode == 2 { 93 | time.Sleep(time.Duration(int64(data.Param)) * time.Second) 94 | if err := stream.Send(data); err != nil { 95 | return err 96 | } 97 | return nil 98 | } else { 99 | return status.Errorf(codes.Unimplemented, "mode not implemented") 100 | } 101 | } 102 | 103 | func newServer() *gRPCErrorsServer { 104 | s := &gRPCErrorsServer{} 105 | return s 106 | } 107 | 108 | func main() { 109 | flag.Parse() 110 | lis, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", *port)) 111 | if err != nil { 112 | log.Fatalf("failed to listen: %v", err) 113 | } 114 | var opts []grpc.ServerOption 115 | if *tls { 116 | creds, err := credentials.NewServerTLSFromFile(*certFile, *keyFile) 117 | if err != nil { 118 | log.Fatalf("Failed to generate credentials %v", err) 119 | } 120 | opts = []grpc.ServerOption{grpc.Creds(creds)} 121 | } 122 | grpcServer := grpc.NewServer(opts...) 123 | pb.RegisterGRPCErrorsServer(grpcServer, newServer()) 124 | grpcServer.Serve(lis) 125 | } 126 | -------------------------------------------------------------------------------- /test/RouteguideClients/route_guide.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015 gRPC authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | option go_package = "google.golang.org/grpc/examples/route_guide/routeguide"; 18 | option java_multiple_files = true; 19 | option java_package = "io.grpc.examples.routeguide"; 20 | option java_outer_classname = "RouteGuideProto"; 21 | 22 | package routeguide; 23 | 24 | // Interface exported by the server. 25 | service RouteGuide { 26 | // A simple RPC. 27 | // 28 | // Obtains the feature at a given position. 29 | // 30 | // A feature with an empty name is returned if there's no feature at the given 31 | // position. 32 | rpc GetFeature(Point) returns (Feature) {} 33 | 34 | // A server-to-client streaming RPC. 35 | // 36 | // Obtains the Features available within the given Rectangle. Results are 37 | // streamed rather than returned at once (e.g. in a response message with a 38 | // repeated field), as the rectangle may cover a large area and contain a 39 | // huge number of features. 40 | rpc ListFeatures(Rectangle) returns (stream Feature) {} 41 | 42 | // A client-to-server streaming RPC. 43 | // 44 | // Accepts a stream of Points on a route being traversed, returning a 45 | // RouteSummary when traversal is completed. 46 | rpc RecordRoute(stream Point) returns (RouteSummary) {} 47 | 48 | // A Bidirectional streaming RPC. 49 | // 50 | // Accepts a stream of RouteNotes sent while a route is being traversed, 51 | // while receiving other RouteNotes (e.g. from other users). 52 | rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} 53 | } 54 | 55 | // Points are represented as latitude-longitude pairs in the E7 representation 56 | // (degrees multiplied by 10**7 and rounded to the nearest integer). 57 | // Latitudes should be in the range +/- 90 degrees and longitude should be in 58 | // the range +/- 180 degrees (inclusive). 59 | message Point { 60 | int32 latitude = 1; 61 | int32 longitude = 2; 62 | } 63 | 64 | // A latitude-longitude rectangle, represented as two diagonally opposite 65 | // points "lo" and "hi". 66 | message Rectangle { 67 | // One corner of the rectangle. 68 | Point lo = 1; 69 | 70 | // The other corner of the rectangle. 71 | Point hi = 2; 72 | } 73 | 74 | // A feature names something at a given point. 75 | // 76 | // If a feature could not be named, the name is empty. 77 | message Feature { 78 | // The name of the feature. 79 | string name = 1; 80 | 81 | // The point where the feature is detected. 82 | Point location = 2; 83 | } 84 | 85 | // A RouteNote is a message sent while at a given point. 86 | message RouteNote { 87 | // The location from which the message is sent. 88 | Point location = 1; 89 | 90 | // The message to be sent. 91 | string message = 2; 92 | } 93 | 94 | // A RouteSummary is received in response to a RecordRoute rpc. 95 | // 96 | // It contains the number of individual points received, the number of 97 | // detected features, and the total distance covered as the cumulative sum of 98 | // the distance between each point. 99 | message RouteSummary { 100 | // The number of points received. 101 | int32 point_count = 1; 102 | 103 | // The number of known features passed while traversing the route. 104 | int32 feature_count = 2; 105 | 106 | // The distance covered in metres. 107 | int32 distance = 3; 108 | 109 | // The duration of the traversal in seconds. 110 | int32 elapsed_time = 4; 111 | } 112 | -------------------------------------------------------------------------------- /test/GrpcerrorsClients/grpcerrors_pb.jl: -------------------------------------------------------------------------------- 1 | # syntax: proto3 2 | using ProtoBuf 3 | import ProtoBuf.meta 4 | 5 | mutable struct Data <: ProtoType 6 | __protobuf_jl_internal_meta::ProtoMeta 7 | __protobuf_jl_internal_values::Dict{Symbol,Any} 8 | __protobuf_jl_internal_defaultset::Set{Symbol} 9 | 10 | function Data(; kwargs...) 11 | obj = new(meta(Data), Dict{Symbol,Any}(), Set{Symbol}()) 12 | values = obj.__protobuf_jl_internal_values 13 | symdict = obj.__protobuf_jl_internal_meta.symdict 14 | for nv in kwargs 15 | fldname, fldval = nv 16 | fldtype = symdict[fldname].jtyp 17 | (fldname in keys(symdict)) || error(string(typeof(obj), " has no field with name ", fldname)) 18 | if fldval !== nothing 19 | values[fldname] = isa(fldval, fldtype) ? fldval : convert(fldtype, fldval) 20 | end 21 | end 22 | obj 23 | end 24 | end # mutable struct Data 25 | const __meta_Data = Ref{ProtoMeta}() 26 | function meta(::Type{Data}) 27 | ProtoBuf.metalock() do 28 | if !isassigned(__meta_Data) 29 | __meta_Data[] = target = ProtoMeta(Data) 30 | allflds = Pair{Symbol,Union{Type,String}}[:mode => Int32, :param => Int32] 31 | meta(target, Data, allflds, ProtoBuf.DEF_REQ, ProtoBuf.DEF_FNUM, ProtoBuf.DEF_VAL, ProtoBuf.DEF_PACK, ProtoBuf.DEF_WTYPES, ProtoBuf.DEF_ONEOFS, ProtoBuf.DEF_ONEOF_NAMES) 32 | end 33 | __meta_Data[] 34 | end 35 | end 36 | function Base.getproperty(obj::Data, name::Symbol) 37 | if name === :mode 38 | return (obj.__protobuf_jl_internal_values[name])::Int32 39 | elseif name === :param 40 | return (obj.__protobuf_jl_internal_values[name])::Int32 41 | else 42 | getfield(obj, name) 43 | end 44 | end 45 | 46 | # service methods for GRPCErrors 47 | const _GRPCErrors_methods = MethodDescriptor[ 48 | MethodDescriptor("SimpleRPC", 1, Data, Data), 49 | MethodDescriptor("StreamResponse", 2, Data, Channel{Data}), 50 | MethodDescriptor("StreamRequest", 3, Channel{Data}, Data), 51 | MethodDescriptor("StreamRequestResponse", 4, Channel{Data}, Channel{Data}) 52 | ] # const _GRPCErrors_methods 53 | const _GRPCErrors_desc = ServiceDescriptor("grpcerrors.GRPCErrors", 1, _GRPCErrors_methods) 54 | 55 | GRPCErrors(impl::Module) = ProtoService(_GRPCErrors_desc, impl) 56 | 57 | mutable struct GRPCErrorsStub <: AbstractProtoServiceStub{false} 58 | impl::ProtoServiceStub 59 | GRPCErrorsStub(channel::ProtoRpcChannel) = new(ProtoServiceStub(_GRPCErrors_desc, channel)) 60 | end # mutable struct GRPCErrorsStub 61 | 62 | mutable struct GRPCErrorsBlockingStub <: AbstractProtoServiceStub{true} 63 | impl::ProtoServiceBlockingStub 64 | GRPCErrorsBlockingStub(channel::ProtoRpcChannel) = new(ProtoServiceBlockingStub(_GRPCErrors_desc, channel)) 65 | end # mutable struct GRPCErrorsBlockingStub 66 | 67 | SimpleRPC(stub::GRPCErrorsStub, controller::ProtoRpcController, inp::Data, done::Function) = call_method(stub.impl, _GRPCErrors_methods[1], controller, inp, done) 68 | SimpleRPC(stub::GRPCErrorsBlockingStub, controller::ProtoRpcController, inp::Data) = call_method(stub.impl, _GRPCErrors_methods[1], controller, inp) 69 | 70 | StreamResponse(stub::GRPCErrorsStub, controller::ProtoRpcController, inp::Data, done::Function) = call_method(stub.impl, _GRPCErrors_methods[2], controller, inp, done) 71 | StreamResponse(stub::GRPCErrorsBlockingStub, controller::ProtoRpcController, inp::Data) = call_method(stub.impl, _GRPCErrors_methods[2], controller, inp) 72 | 73 | StreamRequest(stub::GRPCErrorsStub, controller::ProtoRpcController, inp::Channel{Data}, done::Function) = call_method(stub.impl, _GRPCErrors_methods[3], controller, inp, done) 74 | StreamRequest(stub::GRPCErrorsBlockingStub, controller::ProtoRpcController, inp::Channel{Data}) = call_method(stub.impl, _GRPCErrors_methods[3], controller, inp) 75 | 76 | StreamRequestResponse(stub::GRPCErrorsStub, controller::ProtoRpcController, inp::Channel{Data}, done::Function) = call_method(stub.impl, _GRPCErrors_methods[4], controller, inp, done) 77 | StreamRequestResponse(stub::GRPCErrorsBlockingStub, controller::ProtoRpcController, inp::Channel{Data}) = call_method(stub.impl, _GRPCErrors_methods[4], controller, inp) 78 | 79 | export Data, GRPCErrors, GRPCErrorsStub, GRPCErrorsBlockingStub, SimpleRPC, StreamResponse, StreamRequest, StreamRequestResponse 80 | -------------------------------------------------------------------------------- /test/test_grpcerrors.jl: -------------------------------------------------------------------------------- 1 | include("GrpcerrorsClients/GrpcerrorsClients.jl") 2 | using .GrpcerrorsClients 3 | 4 | # GrpcerrorsClients.Data mode values: 5 | # 1: throw an error after seconds provided in `param` 6 | # 2: no error, just wait until seconds provided in `param`, respond with SimulationParams 7 | Base.show(io::IO, data::GrpcerrorsClients.Data) = print(io, string("[", data.mode, ", ", data.param, "]")) 8 | 9 | # single request, single response 10 | function test_simplerpc(client::GRPCErrorsBlockingClient) 11 | data = GrpcerrorsClients.Data(; mode=1, param=0) 12 | try 13 | _, status_future = GrpcerrorsClients.SimpleRPC(client, data) 14 | gRPCCheck(status_future) 15 | error("error not caught") 16 | catch ex 17 | @test isa(ex, gRPCServiceCallException) 18 | @test ex.message == "simulated error mode 1" 19 | @test ex.grpc_status == StatusCode.UNKNOWN.code 20 | end 21 | 22 | data = GrpcerrorsClients.Data(; mode=2, param=5) 23 | try 24 | _, status_future = GrpcerrorsClients.SimpleRPC(client, data) 25 | gRPCCheck(status_future) 26 | error("error not caught") 27 | catch ex 28 | @test isa(ex, gRPCServiceCallException) 29 | @test ex.message == StatusCode.DEADLINE_EXCEEDED.message 30 | @test ex.grpc_status == StatusCode.DEADLINE_EXCEEDED.code 31 | end 32 | end 33 | 34 | # single request, stream response 35 | function test_stream_response(client::GRPCErrorsBlockingClient) 36 | data = GrpcerrorsClients.Data(; mode=1, param=0) 37 | try 38 | _, status_future = GrpcerrorsClients.StreamResponse(client, data) 39 | gRPCCheck(status_future) 40 | error("error not caught") 41 | catch ex 42 | @test isa(ex, gRPCServiceCallException) 43 | @test ex.message == "simulated error mode 1" 44 | @test ex.grpc_status == StatusCode.UNKNOWN.code 45 | end 46 | 47 | data = GrpcerrorsClients.Data(; mode=2, param=25) 48 | try 49 | _, status_future = GrpcerrorsClients.StreamResponse(client, data) 50 | gRPCCheck(status_future) 51 | error("error not caught") 52 | catch ex 53 | @test isa(ex, gRPCServiceCallException) 54 | @test ex.message == StatusCode.DEADLINE_EXCEEDED.message 55 | @test ex.grpc_status == StatusCode.DEADLINE_EXCEEDED.code 56 | end 57 | end 58 | 59 | # stream request, single response 60 | function test_stream_request(client::GRPCErrorsBlockingClient) 61 | try 62 | data = GrpcerrorsClients.Data(; mode=1, param=0) 63 | in = Channel{GrpcerrorsClients.Data}(1) 64 | @async begin 65 | put!(in, data) 66 | close(in) 67 | end 68 | _, status_future = GrpcerrorsClients.StreamRequest(client, in) 69 | gRPCCheck(status_future) 70 | error("error not caught") 71 | catch ex 72 | @test isa(ex, gRPCServiceCallException) 73 | @test ex.message == "simulated error mode 1" 74 | @test ex.grpc_status == StatusCode.UNKNOWN.code 75 | end 76 | 77 | try 78 | data = GrpcerrorsClients.Data(; mode=2, param=5) 79 | in = Channel{GrpcerrorsClients.Data}(1) 80 | @async begin 81 | put!(in, data) 82 | close(in) 83 | end 84 | _, status_future = GrpcerrorsClients.StreamRequest(client, in) 85 | gRPCCheck(status_future) 86 | error("error not caught") 87 | catch ex 88 | @test isa(ex, gRPCServiceCallException) 89 | @test ex.message == StatusCode.DEADLINE_EXCEEDED.message 90 | @test ex.grpc_status == StatusCode.DEADLINE_EXCEEDED.code 91 | end 92 | end 93 | 94 | # stream request, stream response 95 | function test_stream_request_response(client::GRPCErrorsBlockingClient) 96 | try 97 | data = GrpcerrorsClients.Data(; mode=1, param=0) 98 | in = Channel{GrpcerrorsClients.Data}(1) 99 | @async begin 100 | put!(in, data) 101 | close(in) 102 | end 103 | _, status_future = GrpcerrorsClients.StreamRequestResponse(client, in) 104 | gRPCCheck(status_future) 105 | error("error not caught") 106 | catch ex 107 | @test isa(ex, gRPCServiceCallException) 108 | @test ex.message == "simulated error mode 1" 109 | @test ex.grpc_status == StatusCode.UNKNOWN.code 110 | end 111 | 112 | try 113 | data = GrpcerrorsClients.Data(; mode=2, param=5) 114 | in = Channel{GrpcerrorsClients.Data}(1) 115 | @async begin 116 | put!(in, data) 117 | close(in) 118 | end 119 | _, status_future = GrpcerrorsClients.StreamRequestResponse(client, in) 120 | gRPCCheck(status_future) 121 | error("error not caught") 122 | catch ex 123 | @test isa(ex, gRPCServiceCallException) 124 | @test ex.message == StatusCode.DEADLINE_EXCEEDED.message 125 | @test ex.grpc_status == StatusCode.DEADLINE_EXCEEDED.code 126 | end 127 | end 128 | 129 | function test_blocking_client(server_endpoint::String) 130 | client = GRPCErrorsBlockingClient(server_endpoint; verbose=false, request_timeout=3) 131 | @testset "request response" begin 132 | test_simplerpc(client) 133 | end 134 | @testset "streaming recv" begin 135 | test_stream_response(client) 136 | end 137 | @testset "streaming send" begin 138 | test_stream_request(client) 139 | end 140 | @testset "streaming send recv" begin 141 | test_stream_request_response(client) 142 | end 143 | end 144 | 145 | function test_clients(server_endpoint::String) 146 | @info("testing blocking client") 147 | test_blocking_client(server_endpoint) 148 | end -------------------------------------------------------------------------------- /src/generate.jl: -------------------------------------------------------------------------------- 1 | const package_regex = r"package\s(\S*)[\s]*;.*" 2 | const service_regex = r"service\s(\S*)[\s]*.*" 3 | 4 | function write_header(io, generated_module, package, client_module_name) 5 | print(io, """module $(client_module_name) 6 | using gRPCClient 7 | 8 | include("$(generated_module).jl") 9 | using .$(package) 10 | 11 | import Base: show 12 | """) 13 | end 14 | 15 | function write_trailer(io, client_module_name) 16 | print(io, """ 17 | 18 | end # module $(client_module_name) 19 | """) 20 | end 21 | 22 | function write_service(io, package, service, methods) 23 | print(io, """ 24 | 25 | # begin service: $(package).$(service) 26 | 27 | export $(service)BlockingClient, $(service)Client 28 | 29 | struct $(service)BlockingClient 30 | controller::gRPCController 31 | channel::gRPCChannel 32 | stub::$(service)BlockingStub 33 | 34 | function $(service)BlockingClient(api_base_url::String; kwargs...) 35 | controller = gRPCController(; kwargs...) 36 | channel = gRPCChannel(api_base_url) 37 | stub = $(service)BlockingStub(channel) 38 | new(controller, channel, stub) 39 | end 40 | end 41 | 42 | struct $(service)Client 43 | controller::gRPCController 44 | channel::gRPCChannel 45 | stub::$(service)Stub 46 | 47 | function $(service)Client(api_base_url::String; kwargs...) 48 | controller = gRPCController(; kwargs...) 49 | channel = gRPCChannel(api_base_url) 50 | stub = $(service)Stub(channel) 51 | new(controller, channel, stub) 52 | end 53 | end 54 | 55 | show(io::IO, client::$(service)BlockingClient) = print(io, "$(service)BlockingClient(", client.channel.baseurl, ")") 56 | show(io::IO, client::$(service)Client) = print(io, "$(service)Client(", client.channel.baseurl, ")") 57 | """) 58 | 59 | for method in methods 60 | write_service_method(io, package, service, method) 61 | end 62 | 63 | print(io, """ 64 | 65 | # end service: $(package).$(service) 66 | """) 67 | end 68 | 69 | typename(ch::Type{T}) where {T <: Channel} = string("Channel{", typename(eltype(ch)), "}") 70 | typename(T) = last(split(string(T), '.'; limit=2)) 71 | 72 | function write_service_method(io, package, service, method) 73 | method_name = method.name 74 | input_type = typename(method.input_type) 75 | output_type = typename(method.output_type) 76 | 77 | print(io, """ 78 | 79 | import .$(package): $(method_name) 80 | \"\"\" 81 | $(method_name) 82 | 83 | - input: $input_type 84 | - output: $output_type 85 | \"\"\" 86 | $(method_name)(client::$(service)BlockingClient, inp::$(input_type)) = $(method_name)(client.stub, client.controller, inp) 87 | $(method_name)(client::$(service)Client, inp::$(input_type), done::Function) = $(method_name)(client.stub, client.controller, inp, done) 88 | """) 89 | end 90 | 91 | function detect_services(proto::String) 92 | package = "" 93 | services = String[] 94 | 95 | for line in readlines(proto) 96 | line = strip(line) 97 | if startswith(line, "package") 98 | regexmatches = match(package_regex, line) 99 | if (regexmatches !== nothing) && (length(regexmatches.captures) == 1) 100 | package = string(first(regexmatches.captures)) 101 | end 102 | elseif startswith(line, "service") 103 | regexmatches = match(service_regex, line) 104 | if (regexmatches !== nothing) && (length(regexmatches.captures) == 1) 105 | service = string(first(regexmatches.captures)) 106 | push!(services, service) 107 | end 108 | end 109 | end 110 | package, services 111 | end 112 | 113 | function get_generated_method_table(s::String) 114 | T = Main 115 | for t in split(s, ".") 116 | T = Base.eval(T, Symbol(t)) 117 | end 118 | T 119 | end 120 | 121 | """ 122 | generate(proto::String; outdir::String=pwd()) 123 | 124 | Generate a gRPC client from protobuf specification file. 125 | 126 | - `proto`: Path to the protobuf specification to used. 127 | - `outdir`: Directory to write generated code into, created if not present 128 | already. Existing files if any will be overwtitten. 129 | """ 130 | function generate(proto::String; outdir::String=pwd(), includes::Vector{String}=String[]) 131 | if !isfile(proto) 132 | throw(ArgumentError("No such file - $proto")) 133 | end 134 | proto = abspath(proto) 135 | 136 | @info("Generating gRPC client", proto, outdir) 137 | 138 | # determine the package name and service name 139 | package, services = detect_services(proto) 140 | protodir = dirname(proto) 141 | includeflag = `-I=$protodir` 142 | for inc in includes 143 | includeflag = `$includeflag -I=$inc` 144 | end 145 | @info("Detected", package, services, includes) 146 | 147 | # generate protobuf services 148 | mkpath(outdir) 149 | bindir = Sys.BINDIR 150 | pathenv = string(ENV["PATH"], Sys.iswindows() ? ";" : ":", bindir) 151 | withenv("PATH"=>pathenv) do 152 | ProtoBuf.protoc(`$includeflag --julia_out=$outdir $proto`) 153 | end 154 | 155 | # include the generated code and detect service method names 156 | generated_module = first(split(package, '.'; limit=2)) 157 | generated_module_file = joinpath(outdir, string(generated_module, ".jl")) 158 | Main.eval(:(include($generated_module_file))) 159 | 160 | # generate the gRPC client code 161 | client_module_name = string(titlecase(generated_module; strict=false), "Clients") 162 | open(joinpath(outdir, "$(client_module_name).jl"), "w") do grpcservice 163 | write_header(grpcservice, generated_module, package, client_module_name) 164 | for service in services 165 | methods = get_generated_method_table(string(package, "._", service, "_methods")) 166 | write_service(grpcservice, package, service, methods) 167 | end 168 | write_trailer(grpcservice, client_module_name) 169 | end 170 | 171 | @info("Generated", outdir) 172 | end 173 | -------------------------------------------------------------------------------- /test/error_test_server/grpcerrors/grpcerrors.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.26.0 4 | // protoc v3.6.1 5 | // source: grpcerrors.proto 6 | 7 | package grpcerrors 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | // Request parameter, dictates how the simulation should behave. 24 | // mode can take values: 25 | // 1: throw an error after seconds provided in `param` 26 | // 2: no error, just wait until seconds provided in `param`, respond with SimulationParams 27 | // 28 | // when sent in a stream as input, the server would consider only the first one in the 29 | // stream to determine the course of action 30 | type Data struct { 31 | state protoimpl.MessageState 32 | sizeCache protoimpl.SizeCache 33 | unknownFields protoimpl.UnknownFields 34 | 35 | Mode int32 `protobuf:"varint,1,opt,name=mode,proto3" json:"mode,omitempty"` 36 | Param int32 `protobuf:"varint,2,opt,name=param,proto3" json:"param,omitempty"` 37 | } 38 | 39 | func (x *Data) Reset() { 40 | *x = Data{} 41 | if protoimpl.UnsafeEnabled { 42 | mi := &file_grpcerrors_proto_msgTypes[0] 43 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 44 | ms.StoreMessageInfo(mi) 45 | } 46 | } 47 | 48 | func (x *Data) String() string { 49 | return protoimpl.X.MessageStringOf(x) 50 | } 51 | 52 | func (*Data) ProtoMessage() {} 53 | 54 | func (x *Data) ProtoReflect() protoreflect.Message { 55 | mi := &file_grpcerrors_proto_msgTypes[0] 56 | if protoimpl.UnsafeEnabled && x != nil { 57 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 58 | if ms.LoadMessageInfo() == nil { 59 | ms.StoreMessageInfo(mi) 60 | } 61 | return ms 62 | } 63 | return mi.MessageOf(x) 64 | } 65 | 66 | // Deprecated: Use Data.ProtoReflect.Descriptor instead. 67 | func (*Data) Descriptor() ([]byte, []int) { 68 | return file_grpcerrors_proto_rawDescGZIP(), []int{0} 69 | } 70 | 71 | func (x *Data) GetMode() int32 { 72 | if x != nil { 73 | return x.Mode 74 | } 75 | return 0 76 | } 77 | 78 | func (x *Data) GetParam() int32 { 79 | if x != nil { 80 | return x.Param 81 | } 82 | return 0 83 | } 84 | 85 | var File_grpcerrors_proto protoreflect.FileDescriptor 86 | 87 | var file_grpcerrors_proto_rawDesc = []byte{ 88 | 0x0a, 0x10, 0x67, 0x72, 0x70, 0x63, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 89 | 0x74, 0x6f, 0x12, 0x0a, 0x67, 0x72, 0x70, 0x63, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x22, 0x30, 90 | 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x01, 91 | 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x61, 92 | 0x72, 0x61, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x70, 0x61, 0x72, 0x61, 0x6d, 93 | 0x32, 0xf5, 0x01, 0x0a, 0x0a, 0x47, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x12, 94 | 0x31, 0x0a, 0x09, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x50, 0x43, 0x12, 0x10, 0x2e, 0x67, 95 | 0x72, 0x70, 0x63, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x1a, 0x10, 96 | 0x2e, 0x67, 0x72, 0x70, 0x63, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x44, 0x61, 0x74, 0x61, 97 | 0x22, 0x00, 0x12, 0x38, 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 98 | 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x65, 0x72, 0x72, 0x6f, 0x72, 99 | 0x73, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x1a, 0x10, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x65, 0x72, 0x72, 100 | 0x6f, 0x72, 0x73, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x22, 0x00, 0x30, 0x01, 0x12, 0x37, 0x0a, 0x0d, 101 | 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x2e, 102 | 0x67, 0x72, 0x70, 0x63, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x1a, 103 | 0x10, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x44, 0x61, 0x74, 104 | 0x61, 0x22, 0x00, 0x28, 0x01, 0x12, 0x41, 0x0a, 0x15, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 105 | 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 106 | 0x2e, 0x67, 0x72, 0x70, 0x63, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x44, 0x61, 0x74, 0x61, 107 | 0x1a, 0x10, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x44, 0x61, 108 | 0x74, 0x61, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x1f, 0x5a, 0x1d, 0x6a, 0x75, 0x6c, 0x69, 109 | 0x61, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 110 | 0x72, 0x70, 0x63, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 111 | 0x33, 112 | } 113 | 114 | var ( 115 | file_grpcerrors_proto_rawDescOnce sync.Once 116 | file_grpcerrors_proto_rawDescData = file_grpcerrors_proto_rawDesc 117 | ) 118 | 119 | func file_grpcerrors_proto_rawDescGZIP() []byte { 120 | file_grpcerrors_proto_rawDescOnce.Do(func() { 121 | file_grpcerrors_proto_rawDescData = protoimpl.X.CompressGZIP(file_grpcerrors_proto_rawDescData) 122 | }) 123 | return file_grpcerrors_proto_rawDescData 124 | } 125 | 126 | var file_grpcerrors_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 127 | var file_grpcerrors_proto_goTypes = []interface{}{ 128 | (*Data)(nil), // 0: grpcerrors.Data 129 | } 130 | var file_grpcerrors_proto_depIdxs = []int32{ 131 | 0, // 0: grpcerrors.GRPCErrors.SimpleRPC:input_type -> grpcerrors.Data 132 | 0, // 1: grpcerrors.GRPCErrors.StreamResponse:input_type -> grpcerrors.Data 133 | 0, // 2: grpcerrors.GRPCErrors.StreamRequest:input_type -> grpcerrors.Data 134 | 0, // 3: grpcerrors.GRPCErrors.StreamRequestResponse:input_type -> grpcerrors.Data 135 | 0, // 4: grpcerrors.GRPCErrors.SimpleRPC:output_type -> grpcerrors.Data 136 | 0, // 5: grpcerrors.GRPCErrors.StreamResponse:output_type -> grpcerrors.Data 137 | 0, // 6: grpcerrors.GRPCErrors.StreamRequest:output_type -> grpcerrors.Data 138 | 0, // 7: grpcerrors.GRPCErrors.StreamRequestResponse:output_type -> grpcerrors.Data 139 | 4, // [4:8] is the sub-list for method output_type 140 | 0, // [0:4] is the sub-list for method input_type 141 | 0, // [0:0] is the sub-list for extension type_name 142 | 0, // [0:0] is the sub-list for extension extendee 143 | 0, // [0:0] is the sub-list for field type_name 144 | } 145 | 146 | func init() { file_grpcerrors_proto_init() } 147 | func file_grpcerrors_proto_init() { 148 | if File_grpcerrors_proto != nil { 149 | return 150 | } 151 | if !protoimpl.UnsafeEnabled { 152 | file_grpcerrors_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 153 | switch v := v.(*Data); i { 154 | case 0: 155 | return &v.state 156 | case 1: 157 | return &v.sizeCache 158 | case 2: 159 | return &v.unknownFields 160 | default: 161 | return nil 162 | } 163 | } 164 | } 165 | type x struct{} 166 | out := protoimpl.TypeBuilder{ 167 | File: protoimpl.DescBuilder{ 168 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 169 | RawDescriptor: file_grpcerrors_proto_rawDesc, 170 | NumEnums: 0, 171 | NumMessages: 1, 172 | NumExtensions: 0, 173 | NumServices: 1, 174 | }, 175 | GoTypes: file_grpcerrors_proto_goTypes, 176 | DependencyIndexes: file_grpcerrors_proto_depIdxs, 177 | MessageInfos: file_grpcerrors_proto_msgTypes, 178 | }.Build() 179 | File_grpcerrors_proto = out.File 180 | file_grpcerrors_proto_rawDesc = nil 181 | file_grpcerrors_proto_goTypes = nil 182 | file_grpcerrors_proto_depIdxs = nil 183 | } 184 | -------------------------------------------------------------------------------- /test/test_routeclient.jl: -------------------------------------------------------------------------------- 1 | include("RouteguideClients/RouteguideClients.jl") 2 | using .RouteguideClients 3 | 4 | Base.show(io::IO, location::RouteguideClients.Point) = print(io, string("[", location.latitude, ", ", location.longitude, "]")) 5 | Base.show(io::IO, feature::RouteguideClients.Feature) = print(io, string(feature.name, " - ", feature.location)) 6 | Base.show(io::IO, summary::RouteguideClients.RouteSummary) = print(io, string(summary.point_count, " points, ", summary.feature_count, " features, distance=", summary.distance, ", elapsed_time=", summary.elapsed_time)) 7 | Base.show(io::IO, note::RouteguideClients.RouteNote) = print(io, string(note.message, " ", note.location)) 8 | 9 | function randomPoint() 10 | latitude = (abs(rand(Int) % 180) - 90) * 1e7 11 | longitude = (abs(rand(Int) % 360) - 180) * 1e7 12 | RouteguideClients.Point(; latitude=latitude, longitude=longitude) 13 | end 14 | 15 | # single request, single response 16 | function test_get_feature(client::RouteGuideBlockingClient) 17 | # existing feature 18 | point = RouteguideClients.Point(; latitude=409146138, longitude=-746188906) 19 | feature, status_future = RouteguideClients.GetFeature(client, point) 20 | gRPCCheck(status_future) 21 | @test isa(feature, RouteguideClients.Feature) 22 | @debug("existing feature", feature) 23 | 24 | # missing feature 25 | point = RouteguideClients.Point(; latitude=0, longitude=0) 26 | feature, status_future = RouteguideClients.GetFeature(client, point) 27 | gRPCCheck(status_future) 28 | @test isa(feature, RouteguideClients.Feature) 29 | @debug("missing feature", feature) 30 | end 31 | 32 | # single request, streaming response 33 | function test_list_features(client::RouteGuideBlockingClient) 34 | @debug("listing features in an area") 35 | rect = RouteguideClients.Rectangle(; lo=RouteguideClients.Point(; latitude=400000000, longitude=-750000000), hi=RouteguideClients.Point(; latitude=420000000, longitude=-730000000)) 36 | features, status_future = RouteguideClients.ListFeatures(client, rect) 37 | while isopen(features) || isready(features) 38 | try 39 | feature = take!(features) 40 | @debug(feature) 41 | catch ex 42 | (!isa(ex, InvalidStateException) || !fetch(status_future).success) && rethrow(ex) 43 | end 44 | end 45 | gRPCCheck(status_future) 46 | @test isa(features, Channel{RouteguideClients.Feature}) 47 | @test !isopen(features) 48 | end 49 | 50 | # streaming request, single response 51 | function test_record_route(client::RouteGuideBlockingClient) 52 | @sync begin 53 | point_count = abs(rand(Int) % 100) + 2 54 | @debug("recording a route", point_count) 55 | points_channel = Channel{RouteguideClients.Point}(1) 56 | @async begin 57 | for idx in 1:point_count 58 | put!(points_channel, randomPoint()) 59 | end 60 | close(points_channel) 61 | end 62 | route_summary, status_future = RouteguideClients.RecordRoute(client, points_channel) 63 | gRPCCheck(status_future) 64 | @test isa(route_summary, RouteguideClients.RouteSummary) 65 | @test !isopen(points_channel) 66 | @debug("route summary: $route_summary") 67 | end 68 | end 69 | 70 | # streaming request, streaming response 71 | function test_route_chat(client::RouteGuideBlockingClient) 72 | @sync begin 73 | notes = RouteguideClients.RouteNote[ 74 | RouteguideClients.RouteNote(;location=RouteguideClients.Point(;latitude=0, longitude=1), message="First message"), 75 | RouteguideClients.RouteNote(;location=RouteguideClients.Point(;latitude=0, longitude=2), message="Second message"), 76 | RouteguideClients.RouteNote(;location=RouteguideClients.Point(;latitude=0, longitude=3), message="Third message"), 77 | RouteguideClients.RouteNote(;location=RouteguideClients.Point(;latitude=0, longitude=1), message="Fourth message"), 78 | RouteguideClients.RouteNote(;location=RouteguideClients.Point(;latitude=0, longitude=2), message="Fifth message"), 79 | RouteguideClients.RouteNote(;location=RouteguideClients.Point(;latitude=0, longitude=3), message="Sixth message"), 80 | ] 81 | @debug("route chat") 82 | in_channel = Channel{RouteguideClients.RouteNote}(1) 83 | @async begin 84 | for note in notes 85 | put!(in_channel, note) 86 | end 87 | close(in_channel) 88 | end 89 | out_channel, status_future = RouteguideClients.RouteChat(client, in_channel) 90 | nreceived = 0 91 | for note in out_channel 92 | nreceived += 1 93 | @debug("received note $note") 94 | end 95 | gRPCCheck(status_future) 96 | @test nreceived > 0 97 | @test isa(out_channel, Channel{RouteguideClients.RouteNote}) 98 | @test !isopen(out_channel) 99 | @test !isopen(in_channel) 100 | end 101 | end 102 | 103 | function test_exception() 104 | client = RouteGuideBlockingClient("https://localhost:30000"; verbose=false) 105 | point = RouteguideClients.Point(; latitude=409146138, longitude=-746188906) 106 | @test_throws gRPCServiceCallException RouteguideClients.GetFeature(client, point) 107 | 108 | @test_throws ArgumentError RouteGuideBlockingClient("https://localhost:30000"; maxage=-1) 109 | end 110 | 111 | function test_message_length_limit(server_endpoint) 112 | point = RouteguideClients.Point(; latitude=409146138, longitude=-746188906) 113 | 114 | # test send message length limit 115 | client = RouteGuideBlockingClient(server_endpoint; max_message_length=1, verbose=false) 116 | @test_throws gRPCMessageTooLargeException RouteguideClients.GetFeature(client, point) 117 | 118 | # test recv message length limit 119 | client = RouteGuideBlockingClient(server_endpoint; max_recv_message_length=1, verbose=false) 120 | @test_throws gRPCMessageTooLargeException RouteguideClients.GetFeature(client, point) 121 | 122 | iob = IOBuffer() 123 | show(iob, gRPCMessageTooLargeException(1, 2)) 124 | @test String(take!(iob)) == "gRPMessageTooLargeException(1, 2) - Encountered message size 2 > max configured 1" 125 | show(iob, gRPCServiceCallException(0, "test error")) 126 | @test String(take!(iob)) == "gRPCServiceCallException: 0, test error" 127 | end 128 | 129 | function test_async_get_feature(client::RouteGuideClient) 130 | # existing feature 131 | point = RouteguideClients.Point(; latitude=409146138, longitude=-746188906) 132 | results = Channel{Any}(1) 133 | RouteguideClients.GetFeature(client, point, result->put!(results, result)) 134 | feature, status_future = take!(results) 135 | gRPCCheck(status_future) 136 | @test isa(feature, RouteguideClients.Feature) 137 | @debug("existing feature", feature) 138 | end 139 | 140 | function test_async_client(server_endpoint::String) 141 | client = RouteGuideClient(server_endpoint; verbose=false) 142 | @testset "GetFeature" begin 143 | test_async_get_feature(client) 144 | end 145 | end 146 | 147 | function test_blocking_client(server_endpoint::String) 148 | client = RouteGuideBlockingClient(server_endpoint; verbose=false) 149 | @testset "request response" begin 150 | test_get_feature(client) 151 | end 152 | @testset "streaming recv" begin 153 | test_list_features(client) 154 | end 155 | @testset "streaming send" begin 156 | test_record_route(client) 157 | end 158 | @testset "streaming send recv" begin 159 | test_route_chat(client) 160 | end 161 | @testset "error handling" begin 162 | test_exception() 163 | end 164 | @testset "message length limits" begin 165 | test_message_length_limit(server_endpoint) 166 | end 167 | end 168 | 169 | function test_clients(server_endpoint::String) 170 | @info("testing blocking client") 171 | test_blocking_client(server_endpoint) 172 | @info("testing async client") 173 | test_async_client(server_endpoint) 174 | end 175 | 176 | function test_task_safety(server_endpoint::String) 177 | @testset "async safety" begin 178 | client = RouteGuideBlockingClient(server_endpoint; verbose=false) 179 | @sync begin 180 | for taskid in 1:5 181 | @async begin 182 | test_get_feature(client) 183 | test_list_features(client) 184 | test_record_route(client) 185 | test_route_chat(client) 186 | test_exception() 187 | test_message_length_limit(server_endpoint) 188 | end 189 | end 190 | end 191 | end 192 | end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gRPCClient.jl 2 | 3 | A Julia gRPC Client. 4 | 5 | [![Build Status](https://github.com/JuliaComputing/gRPCClient.jl/workflows/CI/badge.svg)](https://github.com/JuliaComputing/gRPCClient.jl/actions?query=workflow%3ACI+branch%3Amain) 6 | [![codecov.io](http://codecov.io/github/JuliaComputing/gRPCClient.jl/coverage.svg?branch=main)](http://codecov.io/github/JuliaComputing/gRPCClient.jl?branch=main) 7 | 8 | 9 | ## Generating gRPC Service Client 10 | 11 | gRPC services are declared in `.proto` files. Use `gRPCClient.generate` to generate client code from specification files. 12 | 13 | gRPC code generation uses `protoc` and the `ProtoBuf.jl` package. To be able to generate gRPC client code, `ProtoBuf` package must be installed along with `gRPCClient`. 14 | 15 | The `protoc` file must have service generation turned on for at least one of C++, python or Java, e.g. one of: 16 | 17 | ``` 18 | option cc_generic_services = true; 19 | option py_generic_services = true; 20 | option java_generic_services = true; 21 | ``` 22 | 23 | The Julia code generated can be improper if the `package` name declared in the proto specification has `.`. Set a suitable `package` name without `.`. 24 | 25 | ```julia 26 | julia> using Pkg 27 | 28 | julia> Pkg.add("ProtoBuf") 29 | ... 30 | Installed ProtoBuf ──── v0.11.0 31 | Downloading artifact: protoc 32 | ... 33 | julia> Pkg.add("gRPCClient") 34 | ... 35 | julia> # or Pkg.develop(PackageSpec(url="https://github.com/JuliaComputing/gRPCClient.jl")) 36 | 37 | julia> using gRPCClient 38 | 39 | julia> gRPCClient.generate("route_guide.proto") 40 | ┌ Info: Generating gRPC client 41 | │ proto = "RouteguideClients/route_guide.proto" 42 | └ outdir = "RouteguideClients" 43 | ┌ Info: Detected 44 | │ package = "routeguide" 45 | └ service = "RouteGuide" 46 | ┌ Info: Generated 47 | └ outdir = "RouteguideClients" 48 | ``` 49 | 50 | The generated code can either be published as a package or included and used as a module. 51 | 52 | ```julia 53 | julia> using gRPCClient 54 | 55 | julia> include("RouteguideClients/RouteguideClients.jl"); 56 | 57 | julia> using .RouteguideClients 58 | 59 | julia> import .RouteguideClients: Point, Feature, GetFeature 60 | 61 | julia> Base.show(io::IO, location::Point) = 62 | print(io, string("[", location.latitude, ", ", location.longitude, "]")) 63 | 64 | julia> Base.show(io::IO, feature::Feature) = 65 | print(io, string(feature.name, " - ", feature.location)) 66 | 67 | julia> client = RouteGuideBlockingClient("https://server:10000/"); 68 | 69 | julia> point = Point(; latitude=409146138, longitude=-746188906); # request param 70 | 71 | julia> feature, status_future = GetFeature(client, point); 72 | 73 | julia> gRPCCheck(status_future) # check status of request 74 | true 75 | 76 | julia> feature # this is the API return value 77 | Berkshire Valley Management Area Trail, Jefferson, NJ, USA - [409146138, -746188906] 78 | ``` 79 | 80 | The generated module is named after the package declared in the proto file. 81 | And for each service, a pair of clients are generated in the form of 82 | `Client` and `BlockingClient`. 83 | 84 | The service methods generated for `Client` are identical to the 85 | ones generated for `BlockingClient`, except that they spawn off 86 | the actual call into a task and accept a callback method that is invoked with 87 | the results. The `BlockingClient` may however be more intuitive 88 | to use. 89 | 90 | Each service method returns (or calls back with, in the case of non-blocking 91 | clients) two values: 92 | - The result, which can be a Julia struct or a `Channel` for streaming output. 93 | - And, the gRPC status. 94 | 95 | The `gRPCCheck` method checks the status for success or failure. Note that for 96 | methods with streams as input or output, the gRPC status will not be ready 97 | until the method completes. So the status check and stream use must be done 98 | in separate tasks. E.g.: 99 | 100 | ```julia 101 | @sync begin 102 | in_channel = Channel{RouteguideClients.RouteNote}(1) 103 | @async begin 104 | # send inputs 105 | for input in inputs 106 | put!(in_channel, input) 107 | end 108 | close(in_channel) 109 | end 110 | out_channel, status_future = RouteguideClients.RouteChat(client, in_channel) 111 | @async begin 112 | # consume outputs 113 | for output in out_channel 114 | # use output 115 | end 116 | end 117 | @async begin 118 | gRPCCheck(status_future) 119 | end 120 | end 121 | ``` 122 | 123 | ## APIs and Implementation Details 124 | 125 | The generated gRPC client (`RouteGuideBlockingClient` in the example above) 126 | uses a gRPC controller and channel behind the scenes to communicate with 127 | the server. 128 | 129 | ### `gRPCController` 130 | 131 | A `gRPCController` contains settings to control the behavior of gRPC requests. 132 | Each gRPC client holds an instance of the controller created using keyword 133 | arguments passed to its constructor. 134 | 135 | ```julia 136 | gRPCController(; 137 | [ maxage::Int = 0, ] 138 | [ keepalive::Int64 = 60, ] 139 | [ negotiation::Symbol = :http2_prior_knowledge, ] 140 | [ revocation::Bool = true, ] 141 | [ request_timeout::Real = Inf, ] 142 | [ connect_timeout::Real = 0, ] 143 | [ max_message_length = DEFAULT_MAX_MESSAGE_LENGTH, ] 144 | [ max_recv_message_length = 0, ] 145 | [ max_send_message_length = 0, ] 146 | [ verbose::Bool = false, ] 147 | ) 148 | ``` 149 | 150 | - `maxage`: maximum age (seconds) of a connection beyond which it will not 151 | be reused (default 180 seconds, same as setting this to 0). 152 | - `keepalive`: interval (seconds) in which to send TCP keepalive messages on 153 | the connection (default 60 seconds). 154 | - `negotiation`: how to negotiate HTTP2, can be one of `:http2_prior_knowledge` 155 | (no negotiation, the default), `:http2_tls` (http2 upgrade but only over 156 | tls), or `:http2` (http2 upgrade) 157 | - `revocation`: whether to check for certificate recovation (default is true) 158 | - `request_timeout`: request timeout (seconds) 159 | - `connect_timeout`: connect timeout (seconds) (default is 300 seconds, same 160 | as setting this to 0) 161 | - `max_message_length`: maximum message length (default is 4MB) 162 | - `max_recv_message_length`: maximum message length to receive (default is 163 | `max_message_length`, same as setting this to 0) 164 | - `max_send_message_length`: maximum message length to send (default is 165 | `max_message_length`, same as setting this to 0) 166 | - `verbose`: whether to print out verbose communication logs (default false) 167 | 168 | ### `gRPCChannel` 169 | 170 | ```julia 171 | gRPCChannel(baseurl::String) 172 | ``` 173 | 174 | `gRPCChannel` represents a connection to a specific service endpoint 175 | (service `baseurl`) of a gRPC server. 176 | 177 | A channel also usually has a single network connection backing it and 178 | multiple streams of requests can flow through it at any time. The number 179 | of streams that can be multiplexed is negotiated between the client and 180 | the server. 181 | 182 | ### `gRPCStatus` 183 | 184 | `gRPCStatus` represents the status of a request. It has the following fields: 185 | 186 | - `success`: whether the request was completed successfully. 187 | - `grpc_status`: the grpc status code returned 188 | - `message`: any error message if request was not successful 189 | 190 | ### `gRPCCheck` 191 | 192 | ```julia 193 | gRPCCheck(status; throw_error::Bool=true) 194 | ``` 195 | 196 | Method to check the response of a gRPC request and raise a `gRPCException` 197 | if it has failed. If `throw_error` is set to false, returns `true` or `false` 198 | indicating success instead. 199 | 200 | ### `gRPCException` 201 | 202 | Every gRPC request returns the result and a future representing the status 203 | of the gRPC request. Use the `gRPCCheck` method on the status future to check 204 | the request status and throw a `gRPCException` if it is not successful. 205 | 206 | The abstract `gRPCException` type has the following concrete implementations: 207 | 208 | - `gRPCMessageTooLargeException` 209 | - `gRPCServiceCallException` 210 | 211 | ### `gRPCMessageTooLargeException` 212 | 213 | A `gRPMessageTooLargeException` exception is thrown when a message is 214 | encountered that has a size greater than the limit configured. 215 | Specifically, `max_recv_message_length` while receiving and 216 | `max_send_message_length` while sending. 217 | 218 | A `gRPMessageTooLargeException` has the following members: 219 | 220 | - `limit`: the limit value that was exceeded 221 | - `encountered`: the amount of data that was actually received 222 | or sent before this error was triggered. Note that this may 223 | not correspond to the full size of the data, as error may be 224 | thrown before actually materializing the complete data. 225 | 226 | ### `gRPCServiceCallException` 227 | 228 | A `gRPCServiceCallException` is thrown if a gRPC request is not successful. 229 | It has the following members: 230 | 231 | - `grpc_status`: grpc status code for this request 232 | - `message`: any error message if request was not successful 233 | 234 | ## Credits 235 | 236 | This package was originally developed at [Julia Computing](https://juliacomputing.com) 237 | -------------------------------------------------------------------------------- /src/grpc.jl: -------------------------------------------------------------------------------- 1 | """ 2 | struct gRPCStatus 3 | success::Bool 4 | message::String 5 | end 6 | 7 | `gRPCStatus` represents the status of a request. It has the following fields: 8 | 9 | - `success`: whether the request was completed successfully. 10 | - `message`: any error message if request was not successful 11 | """ 12 | struct gRPCStatus 13 | success::Bool 14 | grpc_status::Int 15 | message::String 16 | exception::Union{Nothing,Exception} 17 | end 18 | 19 | gRPCStatus(success::Bool, grpc_status::Int, message::AbstractString) = gRPCStatus(success, grpc_status, string(message), nothing) 20 | function gRPCStatus(status_future) 21 | try 22 | fetch(status_future) 23 | catch ex 24 | task_exception = isa(ex, TaskFailedException) ? ex.task.exception : ex 25 | while isa(task_exception, TaskFailedException) 26 | task_exception = task_exception.task.exception 27 | end 28 | gRPCStatus(false, StatusCode.INTERNAL.code, string(task_exception), task_exception) 29 | end 30 | end 31 | 32 | """ 33 | struct gRPCServiceCallException 34 | message::String 35 | end 36 | 37 | A `gRPCServiceCallException` is thrown if a gRPC request is not successful. 38 | It has the following members: 39 | 40 | - `message`: any error message if request was not successful 41 | """ 42 | struct gRPCServiceCallException <: gRPCException 43 | grpc_status::Int 44 | message::String 45 | end 46 | 47 | Base.show(io::IO, m::gRPCServiceCallException) = print(io, "gRPCServiceCallException: $(m.grpc_status), $(m.message)") 48 | 49 | """ 50 | gRPCCheck(status; throw_error::Bool=true) 51 | 52 | Every gRPC request returns the result and a future representing the status 53 | of the gRPC request. Check the response of a gRPC request and raise a 54 | `gRPCException` if it has failed. If `throw_error` is set to false, this 55 | returns `true` or `false` indicating success instead. 56 | """ 57 | gRPCCheck(status_future; throw_error::Bool=true) = gRPCCheck(gRPCStatus(status_future); throw_error=throw_error) 58 | function gRPCCheck(status::gRPCStatus; throw_error::Bool=true) 59 | if throw_error && !status.success 60 | if status.exception === nothing 61 | throw(gRPCServiceCallException(status.grpc_status, status.message)) 62 | else 63 | throw(status.exception) 64 | end 65 | end 66 | status.success 67 | end 68 | 69 | """ 70 | gRPCController(; 71 | [ maxage::Int = 0, ] 72 | [ keepalive::Int64 = 60, ] 73 | [ negotiation::Symbol = :http2_prior_knowledge, ] 74 | [ revocation::Bool = true, ] 75 | [ request_timeout::Real = Inf, ] 76 | [ connect_timeout::Real = 0, ] 77 | [ max_message_length = DEFAULT_MAX_MESSAGE_LENGTH, ] 78 | [ max_recv_message_length = 0, ] 79 | [ max_send_message_length = 0, ] 80 | [ verbose::Bool = false, ] 81 | ) 82 | 83 | Contains settings to control the behavior of gRPC requests. 84 | - `maxage`: maximum age (seconds) of a connection beyond which it will not 85 | be reused (default 180 seconds, same as setting this to 0). 86 | - `keepalive`: interval (seconds) in which to send TCP keepalive messages on 87 | the connection (default 60 seconds). 88 | - `negotiation`: how to negotiate HTTP2, can be one of `:http2_prior_knowledge` 89 | (no negotiation, the default), `:http2_tls` (http2 upgrade but only over 90 | tls), or `:http2` (http2 upgrade) 91 | - `revocation`: whether to check for certificate recovation (default is true) 92 | - `request_timeout`: request timeout (seconds) 93 | - `connect_timeout`: connect timeout (seconds) (default is 300 seconds, same 94 | as setting this to 0) 95 | - `max_message_length`: maximum message length (default is 4MB) 96 | - `max_recv_message_length`: maximum message length to receive (default is 97 | `max_message_length`, same as setting this to 0) 98 | - `max_send_message_length`: maximum message length to send (default is 99 | `max_message_length`, same as setting this to 0) 100 | - `verbose`: whether to print out verbose communication logs (default false) 101 | """ 102 | struct gRPCController <: ProtoRpcController 103 | maxage::Clong 104 | keepalive::Clong 105 | negotiation::Symbol 106 | revocation::Bool 107 | request_timeout::Real 108 | connect_timeout::Real 109 | max_recv_message_length::Int 110 | max_send_message_length::Int 111 | verbose::Bool 112 | 113 | function gRPCController(; 114 | maxage::Integer = 0, 115 | keepalive::Integer = 60, 116 | negotiation::Symbol = :http2_prior_knowledge, 117 | revocation::Bool = true, 118 | request_timeout::Real = Inf, 119 | connect_timeout::Real = 0, 120 | max_message_length::Integer = DEFAULT_MAX_MESSAGE_LENGTH, 121 | max_recv_message_length::Integer = 0, 122 | max_send_message_length::Integer = 0, 123 | verbose::Bool = false 124 | ) 125 | if maxage < 0 || keepalive < 0 || request_timeout < 0 || connect_timeout < 0 || 126 | max_message_length < 0 || max_recv_message_length < 0 || max_send_message_length < 0 127 | throw(ArgumentError("Invalid gRPCController parameter")) 128 | end 129 | (max_recv_message_length == 0) && (max_recv_message_length = max_message_length) 130 | (max_send_message_length == 0) && (max_send_message_length = max_message_length) 131 | new(maxage, keepalive, negotiation, revocation, request_timeout, connect_timeout, max_recv_message_length, max_send_message_length, verbose) 132 | end 133 | end 134 | 135 | """ 136 | gRPCChannel(baseurl) 137 | 138 | `gRPCChannel` represents a connection to a specific service endpoint 139 | (service `baseurl`) of a gRPC server. 140 | 141 | A channel also usually has a single network connection backing it and 142 | multiple streams of requests can flow through it at any time. The number 143 | of streams that can be multiplexed is negotiated between the client and 144 | the server. 145 | """ 146 | struct gRPCChannel <: ProtoRpcChannel 147 | downloader::Downloader 148 | baseurl::String 149 | 150 | function gRPCChannel(baseurl::String) 151 | downloader = Downloader(; grace=Inf) 152 | Curl.init!(downloader.multi) 153 | Curl.setopt(downloader.multi, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX) 154 | endswith(baseurl, '/') && (baseurl = baseurl[1:end-1]) 155 | new(downloader, baseurl) 156 | end 157 | end 158 | 159 | function to_delimited_message_bytes(msg, max_message_length::Int) 160 | iob = IOBuffer() 161 | limitiob = LimitIO(iob, max_message_length) 162 | write(limitiob, UInt8(0)) # compression 163 | write(limitiob, hton(UInt32(0))) # message length (placeholder) 164 | data_len = writeproto(limitiob, msg) # message bytes 165 | 166 | seek(iob, 1) # seek out the message length placeholder 167 | write(iob, hton(UInt32(data_len))) # fill the message length 168 | take!(iob) 169 | end 170 | 171 | function call_method(channel::gRPCChannel, service::ServiceDescriptor, method::MethodDescriptor, controller::gRPCController, request::T) where T <: ProtoType 172 | inputchannel = Channel{T}(1) 173 | put!(inputchannel, request) 174 | close(inputchannel) 175 | call_method(channel, service, method, controller, inputchannel) 176 | end 177 | call_method(channel::gRPCChannel, service::ServiceDescriptor, method::MethodDescriptor, controller::gRPCController, input::Channel{T}) where T <: ProtoType = call_method(channel, service, method, controller, input, get_response_type(method)) 178 | function call_method(channel::gRPCChannel, service::ServiceDescriptor, method::MethodDescriptor, controller::gRPCController, input::Channel{T1}, ::Type{Channel{T2}}) where {T1 <: ProtoType, T2 <: ProtoType} 179 | call_method(channel, service, method, controller, input, Channel{T2}()) 180 | end 181 | function call_method(channel::gRPCChannel, service::ServiceDescriptor, method::MethodDescriptor, controller::gRPCController, input::Channel{T1}, ::Type{T2}) where {T1 <: ProtoType, T2 <: ProtoType} 182 | outchannel, status_future = call_method(channel, service, method, controller, input, Channel{T2}()) 183 | try 184 | take!(outchannel), status_future 185 | catch ex 186 | gRPCCheck(status_future) # check for core issue 187 | if isa(ex, InvalidStateException) 188 | throw(gRPCServiceCallException("Server closed connection without any response")) 189 | else 190 | rethrow() # throw this error if there's no other issue 191 | end 192 | end 193 | end 194 | function call_method(channel::gRPCChannel, service::ServiceDescriptor, method::MethodDescriptor, controller::gRPCController, input::Channel{T1}, outchannel::Channel{T2}) where {T1 <: ProtoType, T2 <: ProtoType} 195 | url = string(channel.baseurl, "/", service.name, "/", method.name) 196 | status_future = @async grpc_request(channel.downloader, url, input, outchannel; 197 | maxage = controller.maxage, 198 | keepalive = controller.keepalive, 199 | negotiation = controller.negotiation, 200 | revocation = controller.revocation, 201 | request_timeout = controller.request_timeout, 202 | connect_timeout = controller.connect_timeout, 203 | max_recv_message_length = controller.max_recv_message_length, 204 | max_send_message_length = controller.max_send_message_length, 205 | verbose = controller.verbose, 206 | ) 207 | outchannel, status_future 208 | end 209 | -------------------------------------------------------------------------------- /test/error_test_server/grpcerrors/grpcerrors_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | 3 | package grpcerrors 4 | 5 | import ( 6 | context "context" 7 | grpc "google.golang.org/grpc" 8 | codes "google.golang.org/grpc/codes" 9 | status "google.golang.org/grpc/status" 10 | ) 11 | 12 | // This is a compile-time assertion to ensure that this generated file 13 | // is compatible with the grpc package it is being compiled against. 14 | // Requires gRPC-Go v1.32.0 or later. 15 | const _ = grpc.SupportPackageIsVersion7 16 | 17 | // GRPCErrorsClient is the client API for GRPCErrors service. 18 | // 19 | // 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. 20 | type GRPCErrorsClient interface { 21 | // simple RPC, takes a message and responds with a message 22 | SimpleRPC(ctx context.Context, in *Data, opts ...grpc.CallOption) (*Data, error) 23 | // streaming response, takes a message and responds with a stream 24 | StreamResponse(ctx context.Context, in *Data, opts ...grpc.CallOption) (GRPCErrors_StreamResponseClient, error) 25 | // streaming request, takes streaming input and responds with a message 26 | StreamRequest(ctx context.Context, opts ...grpc.CallOption) (GRPCErrors_StreamRequestClient, error) 27 | // streaming request and response 28 | StreamRequestResponse(ctx context.Context, opts ...grpc.CallOption) (GRPCErrors_StreamRequestResponseClient, error) 29 | } 30 | 31 | type gRPCErrorsClient struct { 32 | cc grpc.ClientConnInterface 33 | } 34 | 35 | func NewGRPCErrorsClient(cc grpc.ClientConnInterface) GRPCErrorsClient { 36 | return &gRPCErrorsClient{cc} 37 | } 38 | 39 | func (c *gRPCErrorsClient) SimpleRPC(ctx context.Context, in *Data, opts ...grpc.CallOption) (*Data, error) { 40 | out := new(Data) 41 | err := c.cc.Invoke(ctx, "/grpcerrors.GRPCErrors/SimpleRPC", in, out, opts...) 42 | if err != nil { 43 | return nil, err 44 | } 45 | return out, nil 46 | } 47 | 48 | func (c *gRPCErrorsClient) StreamResponse(ctx context.Context, in *Data, opts ...grpc.CallOption) (GRPCErrors_StreamResponseClient, error) { 49 | stream, err := c.cc.NewStream(ctx, &GRPCErrors_ServiceDesc.Streams[0], "/grpcerrors.GRPCErrors/StreamResponse", opts...) 50 | if err != nil { 51 | return nil, err 52 | } 53 | x := &gRPCErrorsStreamResponseClient{stream} 54 | if err := x.ClientStream.SendMsg(in); err != nil { 55 | return nil, err 56 | } 57 | if err := x.ClientStream.CloseSend(); err != nil { 58 | return nil, err 59 | } 60 | return x, nil 61 | } 62 | 63 | type GRPCErrors_StreamResponseClient interface { 64 | Recv() (*Data, error) 65 | grpc.ClientStream 66 | } 67 | 68 | type gRPCErrorsStreamResponseClient struct { 69 | grpc.ClientStream 70 | } 71 | 72 | func (x *gRPCErrorsStreamResponseClient) Recv() (*Data, error) { 73 | m := new(Data) 74 | if err := x.ClientStream.RecvMsg(m); err != nil { 75 | return nil, err 76 | } 77 | return m, nil 78 | } 79 | 80 | func (c *gRPCErrorsClient) StreamRequest(ctx context.Context, opts ...grpc.CallOption) (GRPCErrors_StreamRequestClient, error) { 81 | stream, err := c.cc.NewStream(ctx, &GRPCErrors_ServiceDesc.Streams[1], "/grpcerrors.GRPCErrors/StreamRequest", opts...) 82 | if err != nil { 83 | return nil, err 84 | } 85 | x := &gRPCErrorsStreamRequestClient{stream} 86 | return x, nil 87 | } 88 | 89 | type GRPCErrors_StreamRequestClient interface { 90 | Send(*Data) error 91 | CloseAndRecv() (*Data, error) 92 | grpc.ClientStream 93 | } 94 | 95 | type gRPCErrorsStreamRequestClient struct { 96 | grpc.ClientStream 97 | } 98 | 99 | func (x *gRPCErrorsStreamRequestClient) Send(m *Data) error { 100 | return x.ClientStream.SendMsg(m) 101 | } 102 | 103 | func (x *gRPCErrorsStreamRequestClient) CloseAndRecv() (*Data, error) { 104 | if err := x.ClientStream.CloseSend(); err != nil { 105 | return nil, err 106 | } 107 | m := new(Data) 108 | if err := x.ClientStream.RecvMsg(m); err != nil { 109 | return nil, err 110 | } 111 | return m, nil 112 | } 113 | 114 | func (c *gRPCErrorsClient) StreamRequestResponse(ctx context.Context, opts ...grpc.CallOption) (GRPCErrors_StreamRequestResponseClient, error) { 115 | stream, err := c.cc.NewStream(ctx, &GRPCErrors_ServiceDesc.Streams[2], "/grpcerrors.GRPCErrors/StreamRequestResponse", opts...) 116 | if err != nil { 117 | return nil, err 118 | } 119 | x := &gRPCErrorsStreamRequestResponseClient{stream} 120 | return x, nil 121 | } 122 | 123 | type GRPCErrors_StreamRequestResponseClient interface { 124 | Send(*Data) error 125 | Recv() (*Data, error) 126 | grpc.ClientStream 127 | } 128 | 129 | type gRPCErrorsStreamRequestResponseClient struct { 130 | grpc.ClientStream 131 | } 132 | 133 | func (x *gRPCErrorsStreamRequestResponseClient) Send(m *Data) error { 134 | return x.ClientStream.SendMsg(m) 135 | } 136 | 137 | func (x *gRPCErrorsStreamRequestResponseClient) Recv() (*Data, error) { 138 | m := new(Data) 139 | if err := x.ClientStream.RecvMsg(m); err != nil { 140 | return nil, err 141 | } 142 | return m, nil 143 | } 144 | 145 | // GRPCErrorsServer is the server API for GRPCErrors service. 146 | // All implementations must embed UnimplementedGRPCErrorsServer 147 | // for forward compatibility 148 | type GRPCErrorsServer interface { 149 | // simple RPC, takes a message and responds with a message 150 | SimpleRPC(context.Context, *Data) (*Data, error) 151 | // streaming response, takes a message and responds with a stream 152 | StreamResponse(*Data, GRPCErrors_StreamResponseServer) error 153 | // streaming request, takes streaming input and responds with a message 154 | StreamRequest(GRPCErrors_StreamRequestServer) error 155 | // streaming request and response 156 | StreamRequestResponse(GRPCErrors_StreamRequestResponseServer) error 157 | mustEmbedUnimplementedGRPCErrorsServer() 158 | } 159 | 160 | // UnimplementedGRPCErrorsServer must be embedded to have forward compatible implementations. 161 | type UnimplementedGRPCErrorsServer struct { 162 | } 163 | 164 | func (UnimplementedGRPCErrorsServer) SimpleRPC(context.Context, *Data) (*Data, error) { 165 | return nil, status.Errorf(codes.Unimplemented, "method SimpleRPC not implemented") 166 | } 167 | func (UnimplementedGRPCErrorsServer) StreamResponse(*Data, GRPCErrors_StreamResponseServer) error { 168 | return status.Errorf(codes.Unimplemented, "method StreamResponse not implemented") 169 | } 170 | func (UnimplementedGRPCErrorsServer) StreamRequest(GRPCErrors_StreamRequestServer) error { 171 | return status.Errorf(codes.Unimplemented, "method StreamRequest not implemented") 172 | } 173 | func (UnimplementedGRPCErrorsServer) StreamRequestResponse(GRPCErrors_StreamRequestResponseServer) error { 174 | return status.Errorf(codes.Unimplemented, "method StreamRequestResponse not implemented") 175 | } 176 | func (UnimplementedGRPCErrorsServer) mustEmbedUnimplementedGRPCErrorsServer() {} 177 | 178 | // UnsafeGRPCErrorsServer may be embedded to opt out of forward compatibility for this service. 179 | // Use of this interface is not recommended, as added methods to GRPCErrorsServer will 180 | // result in compilation errors. 181 | type UnsafeGRPCErrorsServer interface { 182 | mustEmbedUnimplementedGRPCErrorsServer() 183 | } 184 | 185 | func RegisterGRPCErrorsServer(s grpc.ServiceRegistrar, srv GRPCErrorsServer) { 186 | s.RegisterService(&GRPCErrors_ServiceDesc, srv) 187 | } 188 | 189 | func _GRPCErrors_SimpleRPC_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 190 | in := new(Data) 191 | if err := dec(in); err != nil { 192 | return nil, err 193 | } 194 | if interceptor == nil { 195 | return srv.(GRPCErrorsServer).SimpleRPC(ctx, in) 196 | } 197 | info := &grpc.UnaryServerInfo{ 198 | Server: srv, 199 | FullMethod: "/grpcerrors.GRPCErrors/SimpleRPC", 200 | } 201 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 202 | return srv.(GRPCErrorsServer).SimpleRPC(ctx, req.(*Data)) 203 | } 204 | return interceptor(ctx, in, info, handler) 205 | } 206 | 207 | func _GRPCErrors_StreamResponse_Handler(srv interface{}, stream grpc.ServerStream) error { 208 | m := new(Data) 209 | if err := stream.RecvMsg(m); err != nil { 210 | return err 211 | } 212 | return srv.(GRPCErrorsServer).StreamResponse(m, &gRPCErrorsStreamResponseServer{stream}) 213 | } 214 | 215 | type GRPCErrors_StreamResponseServer interface { 216 | Send(*Data) error 217 | grpc.ServerStream 218 | } 219 | 220 | type gRPCErrorsStreamResponseServer struct { 221 | grpc.ServerStream 222 | } 223 | 224 | func (x *gRPCErrorsStreamResponseServer) Send(m *Data) error { 225 | return x.ServerStream.SendMsg(m) 226 | } 227 | 228 | func _GRPCErrors_StreamRequest_Handler(srv interface{}, stream grpc.ServerStream) error { 229 | return srv.(GRPCErrorsServer).StreamRequest(&gRPCErrorsStreamRequestServer{stream}) 230 | } 231 | 232 | type GRPCErrors_StreamRequestServer interface { 233 | SendAndClose(*Data) error 234 | Recv() (*Data, error) 235 | grpc.ServerStream 236 | } 237 | 238 | type gRPCErrorsStreamRequestServer struct { 239 | grpc.ServerStream 240 | } 241 | 242 | func (x *gRPCErrorsStreamRequestServer) SendAndClose(m *Data) error { 243 | return x.ServerStream.SendMsg(m) 244 | } 245 | 246 | func (x *gRPCErrorsStreamRequestServer) Recv() (*Data, error) { 247 | m := new(Data) 248 | if err := x.ServerStream.RecvMsg(m); err != nil { 249 | return nil, err 250 | } 251 | return m, nil 252 | } 253 | 254 | func _GRPCErrors_StreamRequestResponse_Handler(srv interface{}, stream grpc.ServerStream) error { 255 | return srv.(GRPCErrorsServer).StreamRequestResponse(&gRPCErrorsStreamRequestResponseServer{stream}) 256 | } 257 | 258 | type GRPCErrors_StreamRequestResponseServer interface { 259 | Send(*Data) error 260 | Recv() (*Data, error) 261 | grpc.ServerStream 262 | } 263 | 264 | type gRPCErrorsStreamRequestResponseServer struct { 265 | grpc.ServerStream 266 | } 267 | 268 | func (x *gRPCErrorsStreamRequestResponseServer) Send(m *Data) error { 269 | return x.ServerStream.SendMsg(m) 270 | } 271 | 272 | func (x *gRPCErrorsStreamRequestResponseServer) Recv() (*Data, error) { 273 | m := new(Data) 274 | if err := x.ServerStream.RecvMsg(m); err != nil { 275 | return nil, err 276 | } 277 | return m, nil 278 | } 279 | 280 | // GRPCErrors_ServiceDesc is the grpc.ServiceDesc for GRPCErrors service. 281 | // It's only intended for direct use with grpc.RegisterService, 282 | // and not to be introspected or modified (even as a copy) 283 | var GRPCErrors_ServiceDesc = grpc.ServiceDesc{ 284 | ServiceName: "grpcerrors.GRPCErrors", 285 | HandlerType: (*GRPCErrorsServer)(nil), 286 | Methods: []grpc.MethodDesc{ 287 | { 288 | MethodName: "SimpleRPC", 289 | Handler: _GRPCErrors_SimpleRPC_Handler, 290 | }, 291 | }, 292 | Streams: []grpc.StreamDesc{ 293 | { 294 | StreamName: "StreamResponse", 295 | Handler: _GRPCErrors_StreamResponse_Handler, 296 | ServerStreams: true, 297 | }, 298 | { 299 | StreamName: "StreamRequest", 300 | Handler: _GRPCErrors_StreamRequest_Handler, 301 | ClientStreams: true, 302 | }, 303 | { 304 | StreamName: "StreamRequestResponse", 305 | Handler: _GRPCErrors_StreamRequestResponse_Handler, 306 | ServerStreams: true, 307 | ClientStreams: true, 308 | }, 309 | }, 310 | Metadata: "grpcerrors.proto", 311 | } 312 | -------------------------------------------------------------------------------- /src/curl.jl: -------------------------------------------------------------------------------- 1 | const GRPC_STATIC_HEADERS = Ref{Ptr{Nothing}}(C_NULL) 2 | 3 | const StatusCode = ( 4 | OK = (code=0, message="Success"), 5 | CANCELLED = (code=1, message="The operation was cancelled"), 6 | UNKNOWN = (code=2, message="Unknown error"), 7 | INVALID_ARGUMENT = (code=3, message="Client specified an invalid argument"), 8 | DEADLINE_EXCEEDED = (code=4, message="Deadline expired before the operation could complete"), 9 | NOT_FOUND = (code=5, message="Requested entity was not found"), 10 | ALREADY_EXISTS = (code=6, message="Entity already exists"), 11 | PERMISSION_DENIED = (code=7, message="No permission to execute the specified operation"), 12 | RESOURCE_EXHAUSTED = (code=8, message="Resource exhausted"), 13 | FAILED_PRECONDITION = (code=9, message="Operation was rejected because the system is not in a state required for the operation's execution"), 14 | ABORTED = (code=10, message="Operation was aborted"), 15 | OUT_OF_RANGE = (code=11, message="Operation was attempted past the valid range"), 16 | UNIMPLEMENTED = (code=12, message="Operation is not implemented or is not supported/enabled in this service"), 17 | INTERNAL = (code=13, message="Internal error"), 18 | UNAVAILABLE = (code=14, message="The service is currently unavailable"), 19 | DATA_LOSS = (code=15, message="Unrecoverable data loss or corruption"), 20 | UNAUTHENTICATED = (code=16, message="The request does not have valid authentication credentials for the operation") 21 | ) 22 | 23 | grpc_status_info(code) = StatusCode[code+1] 24 | grpc_status_message(code) = (grpc_status_info(code)).message 25 | grpc_status_code_str(code) = string(propertynames(StatusCode)[code+1]) 26 | 27 | #= 28 | const SEND_BUFFER_SZ = 1024 * 1024 29 | function buffer_send_data(input::Channel{T}) where T <: ProtoType 30 | data = nothing 31 | if isready(input) 32 | iob = IOBuffer() 33 | while isready(input) && (iob.size < SEND_BUFFER_SZ) 34 | write(iob, to_delimited_message_bytes(take!(input))) 35 | yield() 36 | end 37 | data = take!(iob) 38 | elseif isopen(input) 39 | data = UInt8[] 40 | end 41 | data 42 | end 43 | =# 44 | 45 | function send_data(easy::Curl.Easy, input::Channel{T}, max_send_message_length::Int) where T <: ProtoType 46 | while true 47 | yield() 48 | data = isready(input) ? to_delimited_message_bytes(take!(input), max_send_message_length) : isopen(input) ? UInt8[] : nothing 49 | easy.input === nothing && break 50 | easy.input = data 51 | Curl.curl_easy_pause(easy.handle, Curl.CURLPAUSE_CONT) 52 | wait(easy.ready) 53 | easy.input === nothing && break 54 | easy.ready = Threads.Event() 55 | end 56 | end 57 | 58 | function grpc_timeout_header_val(timeout::Real) 59 | if round(Int, timeout) == timeout 60 | timeout_secs = round(Int64, timeout) 61 | return "$(timeout_secs)S" 62 | end 63 | timeout *= 1000 64 | if round(Int, timeout) == timeout 65 | timeout_millisecs = round(Int64, timeout) 66 | return "$(timeout_millisecs)m" 67 | end 68 | timeout *= 1000 69 | if round(Int, timeout) == timeout 70 | timeout_microsecs = round(Int64, timeout) 71 | return "$(timeout_microsecs)u" 72 | end 73 | timeout *= 1000 74 | timeout_nanosecs = round(Int64, timeout) 75 | return "$(timeout_nanosecs)n" 76 | end 77 | 78 | function grpc_headers(; timeout::Real=Inf) 79 | headers = C_NULL 80 | headers = LibCURL.curl_slist_append(headers, "User-Agent: $(Curl.USER_AGENT)") 81 | headers = LibCURL.curl_slist_append(headers, "Content-Type: application/grpc+proto") 82 | headers = LibCURL.curl_slist_append(headers, "Content-Length:") 83 | headers = LibCURL.curl_slist_append(headers, "te: trailers") 84 | if timeout !== Inf 85 | headers = LibCURL.curl_slist_append(headers, "grpc-timeout: $(grpc_timeout_header_val(timeout))") 86 | end 87 | headers 88 | end 89 | 90 | function grpc_request_header(request_timeout::Real) 91 | if request_timeout == Inf 92 | GRPC_STATIC_HEADERS[] 93 | else 94 | grpc_headers(; timeout=request_timeout) 95 | end 96 | end 97 | 98 | function easy_handle(maxage::Clong, keepalive::Clong, negotiation::Symbol, revocation::Bool, request_timeout::Real) 99 | easy = Curl.Easy() 100 | http_version = (negotiation === :http2) ? CURL_HTTP_VERSION_2_0 : 101 | (negotiation === :http2_tls) ? CURL_HTTP_VERSION_2TLS : 102 | (negotiation === :http2_prior_knowledge) ? CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE : 103 | throw(ArgumentError("unsupported HTTP2 negotiation mode $negotiation")) 104 | Curl.setopt(easy, CURLOPT_HTTP_VERSION, http_version) 105 | Curl.setopt(easy, CURLOPT_PIPEWAIT, Clong(1)) 106 | Curl.setopt(easy, CURLOPT_POST, Clong(1)) 107 | Curl.setopt(easy, CURLOPT_HTTPHEADER, grpc_request_header(request_timeout)) 108 | if !revocation 109 | Curl.setopt(easy, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_REVOKE) 110 | end 111 | if maxage > 0 112 | Curl.setopt(easy, CURLOPT_MAXAGE_CONN, maxage) 113 | end 114 | if keepalive > 0 115 | Curl.setopt(easy, CURLOPT_TCP_KEEPALIVE, Clong(1)) 116 | Curl.setopt(easy, CURLOPT_TCP_KEEPINTVL, keepalive); 117 | Curl.setopt(easy, CURLOPT_TCP_KEEPIDLE, keepalive); 118 | end 119 | easy 120 | end 121 | 122 | function recv_data(easy::Curl.Easy, output::Channel{T}, max_recv_message_length::Int) where T <: ProtoType 123 | iob = PipeBuffer() 124 | waiting_for_header = true 125 | msgsize = 0 126 | compressed = UInt8(0) 127 | datalen = UInt32(0) 128 | need_more = true 129 | for buf in easy.output 130 | write(iob, buf) 131 | need_more = false 132 | while !need_more 133 | if waiting_for_header 134 | if bytesavailable(iob) >= 5 135 | compressed = read(iob, UInt8) # compression 136 | datalen = ntoh(read(iob, UInt32)) # message length 137 | 138 | if datalen > max_recv_message_length 139 | throw(gRPCMessageTooLargeException(max_recv_message_length, datalen)) 140 | end 141 | 142 | waiting_for_header = false 143 | else 144 | need_more = true 145 | end 146 | end 147 | 148 | if !waiting_for_header 149 | if bytesavailable(iob) >= datalen 150 | msgbytes = IOBuffer(view(iob.data, iob.ptr:(iob.ptr+datalen-1))) 151 | put!(output, readproto(msgbytes, T())) # decode message bytes 152 | iob.ptr += datalen 153 | waiting_for_header = true 154 | else 155 | need_more = true 156 | end 157 | end 158 | end 159 | end 160 | close(output) 161 | end 162 | 163 | function set_connect_timeout(easy::Curl.Easy, timeout::Real) 164 | timeout >= 0 || 165 | throw(ArgumentError("timeout must be positive, got $timeout")) 166 | if timeout ≤ typemax(Clong) ÷ 1000 167 | timeout_ms = round(Clong, timeout * 1000) 168 | Curl.setopt(easy, CURLOPT_CONNECTTIMEOUT_MS, timeout_ms) 169 | else 170 | timeout = timeout ≤ typemax(Clong) ? round(Clong, timeout) : Clong(0) 171 | Curl.setopt(easy, CURLOPT_CONNECTTIMEOUT, timeout) 172 | end 173 | end 174 | 175 | function grpc_request(downloader::Downloader, url::String, input::Channel{T1}, output::Channel{T2}; 176 | maxage::Clong = typemax(Clong), 177 | keepalive::Clong = 60, 178 | negotiation::Symbol = :http2_prior_knowledge, 179 | revocation::Bool = true, 180 | request_timeout::Real = Inf, 181 | connect_timeout::Real = 0, 182 | max_recv_message_length::Int = DEFAULT_MAX_RECV_MESSAGE_LENGTH, 183 | max_send_message_length::Int = DEFAULT_MAX_SEND_MESSAGE_LENGTH, 184 | verbose::Bool = false)::gRPCStatus where {T1 <: ProtoType, T2 <: ProtoType} 185 | Curl.with_handle(easy_handle(maxage, keepalive, negotiation, revocation, request_timeout)) do easy 186 | # setup the request 187 | Curl.set_url(easy, url) 188 | Curl.set_timeout(easy, request_timeout) 189 | set_connect_timeout(easy, connect_timeout) 190 | Curl.set_verbose(easy, verbose) 191 | Curl.add_upload_callbacks(easy) 192 | Downloads.set_ca_roots(downloader, easy) 193 | 194 | # do the request 195 | Curl.add_handle(downloader.multi, easy) 196 | 197 | function cleanup() 198 | Curl.remove_handle(downloader.multi, easy) 199 | # though remove_handle sets easy.handle to C_NULL, it does not close output and progress channels 200 | # we need to close them here to unblock anything waiting on them 201 | close(easy.output) 202 | close(easy.progress) 203 | close(output) 204 | close(input) 205 | nothing 206 | end 207 | 208 | # do send recv data 209 | if VERSION < v"1.5" 210 | cleaned_up = false 211 | exception = nothing 212 | cleanup_once = (ex)->begin 213 | if !cleaned_up 214 | cleaned_up = true 215 | exception = ex 216 | cleanup() 217 | end 218 | end 219 | 220 | @sync begin 221 | @async try 222 | recv_data(easy, output, max_recv_message_length) 223 | catch ex 224 | cleanup_once(ex) 225 | end 226 | @async try 227 | send_data(easy, input, max_send_message_length) 228 | catch ex 229 | cleanup_once(ex) 230 | end 231 | end 232 | 233 | if exception !== nothing 234 | throw(exception) 235 | end 236 | else 237 | try 238 | Base.Experimental.@sync begin 239 | @async recv_data(easy, output, max_recv_message_length) 240 | @async send_data(easy, input, max_send_message_length) 241 | end 242 | finally # ensure handle is removed 243 | cleanup() 244 | end 245 | end 246 | 247 | @debug("response headers", easy.res_hdrs) 248 | 249 | # parse the grpc headers 250 | grpc_status = StatusCode.OK.code 251 | grpc_message = "" 252 | for hdr in easy.res_hdrs 253 | if startswith(hdr, "grpc-status") 254 | grpc_status = parse(Int, strip(last(split(hdr, ':'; limit=2)))) 255 | elseif startswith(hdr, "grpc-message") 256 | grpc_message = string(strip(last(split(hdr, ':'; limit=2)))) 257 | end 258 | end 259 | if (easy.code == CURLE_OPERATION_TIMEDOUT) && (grpc_status == StatusCode.OK.code) 260 | grpc_status = StatusCode.DEADLINE_EXCEEDED.code 261 | end 262 | if (grpc_status != StatusCode.OK.code) && isempty(grpc_message) 263 | grpc_message = grpc_status_message(grpc_status) 264 | end 265 | 266 | if ((easy.code == CURLE_OK) && (grpc_status == StatusCode.OK.code)) 267 | gRPCStatus(true, grpc_status, "") 268 | else 269 | gRPCStatus(false, grpc_status, isempty(grpc_message) ? Curl.get_curl_errstr(easy) : grpc_message) 270 | end 271 | end 272 | end 273 | -------------------------------------------------------------------------------- /test/RouteguideClients/route_guide_pb.jl: -------------------------------------------------------------------------------- 1 | # syntax: proto3 2 | using ProtoBuf 3 | import ProtoBuf.meta 4 | 5 | mutable struct Point <: ProtoType 6 | __protobuf_jl_internal_meta::ProtoMeta 7 | __protobuf_jl_internal_values::Dict{Symbol,Any} 8 | __protobuf_jl_internal_defaultset::Set{Symbol} 9 | 10 | function Point(; kwargs...) 11 | obj = new(meta(Point), Dict{Symbol,Any}(), Set{Symbol}()) 12 | values = obj.__protobuf_jl_internal_values 13 | symdict = obj.__protobuf_jl_internal_meta.symdict 14 | for nv in kwargs 15 | fldname, fldval = nv 16 | fldtype = symdict[fldname].jtyp 17 | (fldname in keys(symdict)) || error(string(typeof(obj), " has no field with name ", fldname)) 18 | if fldval !== nothing 19 | values[fldname] = isa(fldval, fldtype) ? fldval : convert(fldtype, fldval) 20 | end 21 | end 22 | obj 23 | end 24 | end # mutable struct Point 25 | const __meta_Point = Ref{ProtoMeta}() 26 | function meta(::Type{Point}) 27 | ProtoBuf.metalock() do 28 | if !isassigned(__meta_Point) 29 | __meta_Point[] = target = ProtoMeta(Point) 30 | allflds = Pair{Symbol,Union{Type,String}}[:latitude => Int32, :longitude => Int32] 31 | meta(target, Point, allflds, ProtoBuf.DEF_REQ, ProtoBuf.DEF_FNUM, ProtoBuf.DEF_VAL, ProtoBuf.DEF_PACK, ProtoBuf.DEF_WTYPES, ProtoBuf.DEF_ONEOFS, ProtoBuf.DEF_ONEOF_NAMES) 32 | end 33 | __meta_Point[] 34 | end 35 | end 36 | function Base.getproperty(obj::Point, name::Symbol) 37 | if name === :latitude 38 | return (obj.__protobuf_jl_internal_values[name])::Int32 39 | elseif name === :longitude 40 | return (obj.__protobuf_jl_internal_values[name])::Int32 41 | else 42 | getfield(obj, name) 43 | end 44 | end 45 | 46 | mutable struct Rectangle <: ProtoType 47 | __protobuf_jl_internal_meta::ProtoMeta 48 | __protobuf_jl_internal_values::Dict{Symbol,Any} 49 | __protobuf_jl_internal_defaultset::Set{Symbol} 50 | 51 | function Rectangle(; kwargs...) 52 | obj = new(meta(Rectangle), Dict{Symbol,Any}(), Set{Symbol}()) 53 | values = obj.__protobuf_jl_internal_values 54 | symdict = obj.__protobuf_jl_internal_meta.symdict 55 | for nv in kwargs 56 | fldname, fldval = nv 57 | fldtype = symdict[fldname].jtyp 58 | (fldname in keys(symdict)) || error(string(typeof(obj), " has no field with name ", fldname)) 59 | if fldval !== nothing 60 | values[fldname] = isa(fldval, fldtype) ? fldval : convert(fldtype, fldval) 61 | end 62 | end 63 | obj 64 | end 65 | end # mutable struct Rectangle 66 | const __meta_Rectangle = Ref{ProtoMeta}() 67 | function meta(::Type{Rectangle}) 68 | ProtoBuf.metalock() do 69 | if !isassigned(__meta_Rectangle) 70 | __meta_Rectangle[] = target = ProtoMeta(Rectangle) 71 | allflds = Pair{Symbol,Union{Type,String}}[:lo => Point, :hi => Point] 72 | meta(target, Rectangle, allflds, ProtoBuf.DEF_REQ, ProtoBuf.DEF_FNUM, ProtoBuf.DEF_VAL, ProtoBuf.DEF_PACK, ProtoBuf.DEF_WTYPES, ProtoBuf.DEF_ONEOFS, ProtoBuf.DEF_ONEOF_NAMES) 73 | end 74 | __meta_Rectangle[] 75 | end 76 | end 77 | function Base.getproperty(obj::Rectangle, name::Symbol) 78 | if name === :lo 79 | return (obj.__protobuf_jl_internal_values[name])::Point 80 | elseif name === :hi 81 | return (obj.__protobuf_jl_internal_values[name])::Point 82 | else 83 | getfield(obj, name) 84 | end 85 | end 86 | 87 | mutable struct Feature <: ProtoType 88 | __protobuf_jl_internal_meta::ProtoMeta 89 | __protobuf_jl_internal_values::Dict{Symbol,Any} 90 | __protobuf_jl_internal_defaultset::Set{Symbol} 91 | 92 | function Feature(; kwargs...) 93 | obj = new(meta(Feature), Dict{Symbol,Any}(), Set{Symbol}()) 94 | values = obj.__protobuf_jl_internal_values 95 | symdict = obj.__protobuf_jl_internal_meta.symdict 96 | for nv in kwargs 97 | fldname, fldval = nv 98 | fldtype = symdict[fldname].jtyp 99 | (fldname in keys(symdict)) || error(string(typeof(obj), " has no field with name ", fldname)) 100 | if fldval !== nothing 101 | values[fldname] = isa(fldval, fldtype) ? fldval : convert(fldtype, fldval) 102 | end 103 | end 104 | obj 105 | end 106 | end # mutable struct Feature 107 | const __meta_Feature = Ref{ProtoMeta}() 108 | function meta(::Type{Feature}) 109 | ProtoBuf.metalock() do 110 | if !isassigned(__meta_Feature) 111 | __meta_Feature[] = target = ProtoMeta(Feature) 112 | allflds = Pair{Symbol,Union{Type,String}}[:name => AbstractString, :location => Point] 113 | meta(target, Feature, allflds, ProtoBuf.DEF_REQ, ProtoBuf.DEF_FNUM, ProtoBuf.DEF_VAL, ProtoBuf.DEF_PACK, ProtoBuf.DEF_WTYPES, ProtoBuf.DEF_ONEOFS, ProtoBuf.DEF_ONEOF_NAMES) 114 | end 115 | __meta_Feature[] 116 | end 117 | end 118 | function Base.getproperty(obj::Feature, name::Symbol) 119 | if name === :name 120 | return (obj.__protobuf_jl_internal_values[name])::AbstractString 121 | elseif name === :location 122 | return (obj.__protobuf_jl_internal_values[name])::Point 123 | else 124 | getfield(obj, name) 125 | end 126 | end 127 | 128 | mutable struct RouteNote <: ProtoType 129 | __protobuf_jl_internal_meta::ProtoMeta 130 | __protobuf_jl_internal_values::Dict{Symbol,Any} 131 | __protobuf_jl_internal_defaultset::Set{Symbol} 132 | 133 | function RouteNote(; kwargs...) 134 | obj = new(meta(RouteNote), Dict{Symbol,Any}(), Set{Symbol}()) 135 | values = obj.__protobuf_jl_internal_values 136 | symdict = obj.__protobuf_jl_internal_meta.symdict 137 | for nv in kwargs 138 | fldname, fldval = nv 139 | fldtype = symdict[fldname].jtyp 140 | (fldname in keys(symdict)) || error(string(typeof(obj), " has no field with name ", fldname)) 141 | if fldval !== nothing 142 | values[fldname] = isa(fldval, fldtype) ? fldval : convert(fldtype, fldval) 143 | end 144 | end 145 | obj 146 | end 147 | end # mutable struct RouteNote 148 | const __meta_RouteNote = Ref{ProtoMeta}() 149 | function meta(::Type{RouteNote}) 150 | ProtoBuf.metalock() do 151 | if !isassigned(__meta_RouteNote) 152 | __meta_RouteNote[] = target = ProtoMeta(RouteNote) 153 | allflds = Pair{Symbol,Union{Type,String}}[:location => Point, :message => AbstractString] 154 | meta(target, RouteNote, allflds, ProtoBuf.DEF_REQ, ProtoBuf.DEF_FNUM, ProtoBuf.DEF_VAL, ProtoBuf.DEF_PACK, ProtoBuf.DEF_WTYPES, ProtoBuf.DEF_ONEOFS, ProtoBuf.DEF_ONEOF_NAMES) 155 | end 156 | __meta_RouteNote[] 157 | end 158 | end 159 | function Base.getproperty(obj::RouteNote, name::Symbol) 160 | if name === :location 161 | return (obj.__protobuf_jl_internal_values[name])::Point 162 | elseif name === :message 163 | return (obj.__protobuf_jl_internal_values[name])::AbstractString 164 | else 165 | getfield(obj, name) 166 | end 167 | end 168 | 169 | mutable struct RouteSummary <: ProtoType 170 | __protobuf_jl_internal_meta::ProtoMeta 171 | __protobuf_jl_internal_values::Dict{Symbol,Any} 172 | __protobuf_jl_internal_defaultset::Set{Symbol} 173 | 174 | function RouteSummary(; kwargs...) 175 | obj = new(meta(RouteSummary), Dict{Symbol,Any}(), Set{Symbol}()) 176 | values = obj.__protobuf_jl_internal_values 177 | symdict = obj.__protobuf_jl_internal_meta.symdict 178 | for nv in kwargs 179 | fldname, fldval = nv 180 | fldtype = symdict[fldname].jtyp 181 | (fldname in keys(symdict)) || error(string(typeof(obj), " has no field with name ", fldname)) 182 | if fldval !== nothing 183 | values[fldname] = isa(fldval, fldtype) ? fldval : convert(fldtype, fldval) 184 | end 185 | end 186 | obj 187 | end 188 | end # mutable struct RouteSummary 189 | const __meta_RouteSummary = Ref{ProtoMeta}() 190 | function meta(::Type{RouteSummary}) 191 | ProtoBuf.metalock() do 192 | if !isassigned(__meta_RouteSummary) 193 | __meta_RouteSummary[] = target = ProtoMeta(RouteSummary) 194 | allflds = Pair{Symbol,Union{Type,String}}[:point_count => Int32, :feature_count => Int32, :distance => Int32, :elapsed_time => Int32] 195 | meta(target, RouteSummary, allflds, ProtoBuf.DEF_REQ, ProtoBuf.DEF_FNUM, ProtoBuf.DEF_VAL, ProtoBuf.DEF_PACK, ProtoBuf.DEF_WTYPES, ProtoBuf.DEF_ONEOFS, ProtoBuf.DEF_ONEOF_NAMES) 196 | end 197 | __meta_RouteSummary[] 198 | end 199 | end 200 | function Base.getproperty(obj::RouteSummary, name::Symbol) 201 | if name === :point_count 202 | return (obj.__protobuf_jl_internal_values[name])::Int32 203 | elseif name === :feature_count 204 | return (obj.__protobuf_jl_internal_values[name])::Int32 205 | elseif name === :distance 206 | return (obj.__protobuf_jl_internal_values[name])::Int32 207 | elseif name === :elapsed_time 208 | return (obj.__protobuf_jl_internal_values[name])::Int32 209 | else 210 | getfield(obj, name) 211 | end 212 | end 213 | 214 | # service methods for RouteGuide 215 | const _RouteGuide_methods = MethodDescriptor[ 216 | MethodDescriptor("GetFeature", 1, Point, Feature), 217 | MethodDescriptor("ListFeatures", 2, Rectangle, Channel{Feature}), 218 | MethodDescriptor("RecordRoute", 3, Channel{Point}, RouteSummary), 219 | MethodDescriptor("RouteChat", 4, Channel{RouteNote}, Channel{RouteNote}) 220 | ] # const _RouteGuide_methods 221 | const _RouteGuide_desc = ServiceDescriptor("routeguide.RouteGuide", 1, _RouteGuide_methods) 222 | 223 | RouteGuide(impl::Module) = ProtoService(_RouteGuide_desc, impl) 224 | 225 | mutable struct RouteGuideStub <: AbstractProtoServiceStub{false} 226 | impl::ProtoServiceStub 227 | RouteGuideStub(channel::ProtoRpcChannel) = new(ProtoServiceStub(_RouteGuide_desc, channel)) 228 | end # mutable struct RouteGuideStub 229 | 230 | mutable struct RouteGuideBlockingStub <: AbstractProtoServiceStub{true} 231 | impl::ProtoServiceBlockingStub 232 | RouteGuideBlockingStub(channel::ProtoRpcChannel) = new(ProtoServiceBlockingStub(_RouteGuide_desc, channel)) 233 | end # mutable struct RouteGuideBlockingStub 234 | 235 | GetFeature(stub::RouteGuideStub, controller::ProtoRpcController, inp::Point, done::Function) = call_method(stub.impl, _RouteGuide_methods[1], controller, inp, done) 236 | GetFeature(stub::RouteGuideBlockingStub, controller::ProtoRpcController, inp::Point) = call_method(stub.impl, _RouteGuide_methods[1], controller, inp) 237 | 238 | ListFeatures(stub::RouteGuideStub, controller::ProtoRpcController, inp::Rectangle, done::Function) = call_method(stub.impl, _RouteGuide_methods[2], controller, inp, done) 239 | ListFeatures(stub::RouteGuideBlockingStub, controller::ProtoRpcController, inp::Rectangle) = call_method(stub.impl, _RouteGuide_methods[2], controller, inp) 240 | 241 | RecordRoute(stub::RouteGuideStub, controller::ProtoRpcController, inp::Channel{Point}, done::Function) = call_method(stub.impl, _RouteGuide_methods[3], controller, inp, done) 242 | RecordRoute(stub::RouteGuideBlockingStub, controller::ProtoRpcController, inp::Channel{Point}) = call_method(stub.impl, _RouteGuide_methods[3], controller, inp) 243 | 244 | RouteChat(stub::RouteGuideStub, controller::ProtoRpcController, inp::Channel{RouteNote}, done::Function) = call_method(stub.impl, _RouteGuide_methods[4], controller, inp, done) 245 | RouteChat(stub::RouteGuideBlockingStub, controller::ProtoRpcController, inp::Channel{RouteNote}) = call_method(stub.impl, _RouteGuide_methods[4], controller, inp) 246 | 247 | export Point, Rectangle, Feature, RouteNote, RouteSummary, RouteGuide, RouteGuideStub, RouteGuideBlockingStub, GetFeature, ListFeatures, RecordRoute, RouteChat 248 | -------------------------------------------------------------------------------- /test/error_test_server/go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 4 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 5 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 6 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 8 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 9 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 10 | github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 11 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 12 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 13 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 14 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 15 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 16 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 17 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 18 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 19 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 20 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 21 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 22 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 23 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 24 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 25 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 26 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 27 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 28 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 29 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 30 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 31 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 32 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 33 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 34 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 35 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 36 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 37 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 38 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 39 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 40 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 41 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 42 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 43 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 44 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 45 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 46 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 47 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 48 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 49 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 50 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 51 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 52 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 53 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 54 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 55 | golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= 56 | golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6 h1:0PC75Fz/kyMGhL0e1QnypqK2kQMqKt9csD1GnMJR+Zk= 57 | golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= 58 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 59 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 60 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 61 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 62 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 63 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 64 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 65 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 66 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 67 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 68 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 69 | golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 70 | golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 71 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 72 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 h1:iGu644GcxtEcrInvDsQRCwJjtCIOlT2V7IRt6ah2Whw= 73 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 74 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 75 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 76 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 77 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 78 | golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= 79 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 80 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 81 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 82 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 83 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 84 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 85 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 86 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 87 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 88 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 89 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 90 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 91 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 92 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 93 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 94 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 95 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 96 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 97 | google.golang.org/genproto v0.0.0-20210423144448-3a41ef94ed2b h1:Rt15zyw7G2yfLqmsjEa1xICjWEw+topkn7vEAR6bVPk= 98 | google.golang.org/genproto v0.0.0-20210423144448-3a41ef94ed2b/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= 99 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 100 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 101 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 102 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 103 | google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 104 | google.golang.org/grpc v1.37.0 h1:uSZWeQJX5j11bIQ4AJoj+McDBo29cY1MCoC1wO3ts+c= 105 | google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= 106 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 107 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 108 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 109 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 110 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 111 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 112 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 113 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 114 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 115 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 116 | google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= 117 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 118 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 119 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 120 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 121 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 122 | --------------------------------------------------------------------------------