diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 28fb729..3e8a681 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,8 +4,6 @@ on: branches: [main] tags: ["*"] pull_request: -env: - GO111MODULE: on jobs: test: name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} @@ -22,39 +20,24 @@ jobs: arch: - x64 - x86 - # include: - # # test macOS and Windows with latest Julia only - # - os: macOS-latest - # arch: x64 - # version: 1 - # - os: windows-latest - # arch: x64 - # version: 1 - # - os: windows-latest - # arch: x86 - # version: 1 + include: + # test macOS and Windows with latest Julia only + - os: macOS-latest + arch: x64 + version: 1 + - os: windows-latest + arch: x64 + version: 1 + - os: windows-latest + arch: x86 + version: 1 steps: - uses: actions/checkout@v2 - - name: setup go - uses: actions/setup-go@v2 - with: - go-version: '1.12.9' - - run: go version - name: setup protoc uses: arduino/setup-protoc@v1 with: version: '3.x' - run: protoc --version - - name: generate test certificates - run: test/certgen/certgen.sh - shell: bash - - name: install protoc-gen-go - run: | - go get google.golang.org/protobuf/cmd/protoc-gen-go google.golang.org/grpc/cmd/protoc-gen-go-grpc - shell: bash - - name: start test server - run: test/runserver.sh - shell: bash - uses: julia-actions/setup-julia@v1 with: version: ${{ matrix.version }} @@ -74,7 +57,4 @@ jobs: - uses: julia-actions/julia-processcoverage@v1 - uses: codecov/codecov-action@v1 with: - file: lcov.info - - name: shutdown test server - run: kill `cat test/grpc-go/examples/route_guide/server.pid` - shell: bash \ No newline at end of file + file: lcov.info \ No newline at end of file diff --git a/Project.toml b/Project.toml index bb8930c..d62fcb5 100644 --- a/Project.toml +++ b/Project.toml @@ -16,7 +16,8 @@ julia = "1.3" [extras] Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +Sockets = "6462fe0b-24de-5631-8697-dd941f90decc" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Test", "Random"] +test = ["Random", "Sockets", "Test"] diff --git a/README.md b/README.md index 7094a5b..00f9a53 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,7 @@ A Julia gRPC Client. -GitHub Actions : [![Build Status](https://github.com/JuliaComputing/gRPCClient.jl/workflows/CI/badge.svg)](https://github.com/JuliaComputing/gRPCClient.jl/actions?query=workflow%3ACI+branch%3Amain) - -[![Coverage Status](https://coveralls.io/repos/JuliaComputing/gRPCClient.jl/badge.svg?branch=main)](https://coveralls.io/r/JuliaComputing/gRPCClient.jl?branch=main) +[![Build Status](https://github.com/JuliaComputing/gRPCClient.jl/workflows/CI/badge.svg)](https://github.com/JuliaComputing/gRPCClient.jl/actions?query=workflow%3ACI+branch%3Amain) [![codecov.io](http://codecov.io/github/JuliaComputing/gRPCClient.jl/coverage.svg?branch=main)](http://codecov.io/github/JuliaComputing/gRPCClient.jl?branch=main) @@ -72,6 +70,8 @@ arguments passed to its constructor. gRPCController(; [ maxage::Int = 0, ] [ keepalive::Int64 = 60, ] + [ negotiation::Symbol = :http2_prior_knowledge, ] + [ revocation::Bool = true, ] [ request_timeout::Real = Inf, ] [ connect_timeout::Real = 0, ] [ verbose::Bool = false, ] @@ -82,6 +82,10 @@ gRPCController(; be reused (default 180 seconds, same as setting this to 0). - `keepalive`: interval (seconds) in which to send TCP keepalive messages on the connection (default 60 seconds). +- `negotiation`: how to negotiate HTTP2, can be one of `:http2_prior_knowledge` + (no negotiation, the default), `:http2_tls` (http2 upgrade but only over + tls), or `:http2` (http2 upgrade) +- `revocation`: whether to check for certificate recovation (default is true) - `request_timeout`: request timeout (seconds) - `connect_timeout`: connect timeout (seconds) (default is 300 seconds, same as setting this to 0) diff --git a/src/curl.jl b/src/curl.jl index 4261897..bd04229 100644 --- a/src/curl.jl +++ b/src/curl.jl @@ -20,6 +20,7 @@ end function send_data(easy::Curl.Easy, input::Channel{T}) where T <: ProtoType while true + yield() data = isready(input) ? to_delimited_message_bytes(take!(input)) : isopen(input) ? UInt8[] : nothing easy.input === nothing && break easy.input = data @@ -38,12 +39,19 @@ function grpc_headers() headers end -function easy_handle(maxage::Clong, keepalive::Clong) +function easy_handle(maxage::Clong, keepalive::Clong, negotiation::Symbol, revocation::Bool) easy = Curl.Easy() - Curl.setopt(easy, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0) + http_version = (negotiation === :http2) ? CURL_HTTP_VERSION_2_0 : + (negotiation === :http2_tls) ? CURL_HTTP_VERSION_2TLS : + (negotiation === :http2_prior_knowledge) ? CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE : + throw(ArgumentError("unsupported HTTP2 negotiation mode $negotiation")) + Curl.setopt(easy, CURLOPT_HTTP_VERSION, http_version) Curl.setopt(easy, CURLOPT_PIPEWAIT, Clong(1)) Curl.setopt(easy, CURLOPT_POST, Clong(1)) Curl.setopt(easy, CURLOPT_HTTPHEADER, GRPC_STATIC_HEADERS[]) + if !revocation + Curl.setopt(easy, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_REVOKE) + end if maxage > 0 Curl.setopt(easy, CURLOPT_MAXAGE_CONN, maxage) end @@ -106,10 +114,12 @@ end function grpc_request(downloader::Downloader, url::String, input::Channel{T1}, output::Channel{T2}; maxage::Clong = typemax(Clong), keepalive::Clong = 60, + negotiation::Symbol = :http2_prior_knowledge, + revocation::Bool = true, request_timeout::Real = Inf, connect_timeout::Real = 0, verbose::Bool = false)::gRPCStatus where {T1 <: ProtoType, T2 <: ProtoType} - Curl.with_handle(easy_handle(maxage, keepalive)) do easy + Curl.with_handle(easy_handle(maxage, keepalive, negotiation, revocation)) do easy # setup the request Curl.set_url(easy, url) Curl.set_timeout(easy, request_timeout) @@ -133,4 +143,4 @@ function grpc_request(downloader::Downloader, url::String, input::Channel{T1}, o (easy.code == CURLE_OK) ? gRPCStatus(true, "") : gRPCStatus(false, Curl.get_curl_errstr(easy)) end -end \ No newline at end of file +end diff --git a/src/generate.jl b/src/generate.jl index afd0733..5120d94 100644 --- a/src/generate.jl +++ b/src/generate.jl @@ -125,7 +125,11 @@ function generate(proto::String; outdir::String=pwd()) # generate protobuf service mkpath(outdir) - ProtoBuf.protoc(`-I=$protodir --julia_out=$outdir $proto`) + bindir = Sys.BINDIR + pathenv = string(ENV["PATH"], Sys.iswindows() ? ";" : ":", bindir) + withenv("PATH"=>pathenv) do + ProtoBuf.protoc(`-I=$protodir --julia_out=$outdir $proto`) + end # include the generated code and detect service method names generated_module = joinpath(outdir, "$(package).jl") diff --git a/src/grpc.jl b/src/grpc.jl index 11aa010..9bcaaa8 100644 --- a/src/grpc.jl +++ b/src/grpc.jl @@ -51,6 +51,8 @@ end gRPCController(; [ maxage::Int = 0, ] [ keepalive::Int64 = 60, ] + [ negotiation::Symbol = :http2_prior_knowledge, ] + [ revocation::Bool = true, ] [ request_timeout::Real = Inf, ] [ connect_timeout::Real = 0, ] [ verbose::Bool = false, ] @@ -61,6 +63,10 @@ Contains settings to control the behavior of gRPC requests. be reused (default 180 seconds, same as setting this to 0). - `keepalive`: interval (seconds) in which to send TCP keepalive messages on the connection (default 60 seconds). +- `negotiation`: how to negotiate HTTP2, can be one of `:http2_prior_knowledge` + (no negotiation, the default), `:http2_tls` (http2 upgrade but only over + tls), or `:http2` (http2 upgrade) +- `revocation`: whether to check for certificate recovation (default is true) - `request_timeout`: request timeout (seconds) - `connect_timeout`: connect timeout (seconds) (default is 300 seconds, same as setting this to 0) @@ -69,6 +75,8 @@ Contains settings to control the behavior of gRPC requests. struct gRPCController <: ProtoRpcController maxage::Clong keepalive::Clong + negotiation::Symbol + revocation::Bool request_timeout::Real connect_timeout::Real verbose::Bool @@ -76,11 +84,13 @@ struct gRPCController <: ProtoRpcController function gRPCController(; maxage::Integer = 0, keepalive::Integer = 60, + negotiation::Symbol = :http2_prior_knowledge, + revocation::Bool = true, request_timeout::Real = Inf, connect_timeout::Real = 0, verbose::Bool = false ) - new(maxage, keepalive, request_timeout, connect_timeout, verbose) + new(maxage, keepalive, negotiation, revocation, request_timeout, connect_timeout, verbose) end end @@ -141,6 +151,8 @@ function call_method(channel::gRPCChannel, service::ServiceDescriptor, method::M status_future = @async grpc_request(channel.downloader, url, input, outchannel; maxage = controller.maxage, keepalive = controller.keepalive, + negotiation = controller.negotiation, + revocation = controller.revocation, request_timeout = controller.request_timeout, connect_timeout = controller.connect_timeout, verbose = controller.verbose, diff --git a/test/.gitignore b/test/.gitignore index 1a7d908..3513e3f 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -1,2 +1,3 @@ grpc-go server.pid +runserver_* diff --git a/test/RouteGuideClients/.gitignore b/test/RouteGuideClients/.gitignore deleted file mode 100644 index 27d013c..0000000 --- a/test/RouteGuideClients/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.jl diff --git a/test/RouteGuideClients/RouteGuideClients.jl b/test/RouteGuideClients/RouteGuideClients.jl new file mode 100644 index 0000000..864fcdc --- /dev/null +++ b/test/RouteGuideClients/RouteGuideClients.jl @@ -0,0 +1,79 @@ +module RouteGuideClients +using gRPCClient + +include("routeguide.jl") +using .routeguide + +import Base: show +export RouteGuideBlockingClient, RouteGuideClient + +struct RouteGuideBlockingClient + controller::gRPCController + channel::gRPCChannel + stub::RouteGuideBlockingStub + + function RouteGuideBlockingClient(api_base_url::String; kwargs...) + controller = gRPCController(; kwargs...) + channel = gRPCChannel(api_base_url) + stub = RouteGuideBlockingStub(channel) + new(controller, channel, stub) + end +end + +struct RouteGuideClient + controller::gRPCController + channel::gRPCChannel + stub::RouteGuideStub + + function RouteGuideClient(api_base_url::String; kwargs...) + controller = gRPCController(; kwargs...) + channel = gRPCChannel(api_base_url) + stub = RouteGuideStub(channel) + new(controller, channel, stub) + end +end + +show(io::IO, client::RouteGuideBlockingClient) = print(io, "RouteGuideBlockingClient(", client.channel.baseurl, ")") +show(io::IO, client::RouteGuideClient) = print(io, "RouteGuideClient(", client.channel.baseurl, ")") + +import .routeguide: GetFeature +""" + GetFeature + +- input: routeguide.Point +- output: routeguide.Feature +""" +GetFeature(client::RouteGuideBlockingClient, inp::routeguide.Point) = GetFeature(client.stub, client.controller, inp) +GetFeature(client::RouteGuideClient, inp::routeguide.Point, done::Function) = GetFeature(client.stub, client.controller, inp, done) + +import .routeguide: ListFeatures +""" + ListFeatures + +- input: routeguide.Rectangle +- output: Channel{routeguide.Feature} +""" +ListFeatures(client::RouteGuideBlockingClient, inp::routeguide.Rectangle) = ListFeatures(client.stub, client.controller, inp) +ListFeatures(client::RouteGuideClient, inp::routeguide.Rectangle, done::Function) = ListFeatures(client.stub, client.controller, inp, done) + +import .routeguide: RecordRoute +""" + RecordRoute + +- input: Channel{routeguide.Point} +- output: routeguide.RouteSummary +""" +RecordRoute(client::RouteGuideBlockingClient, inp::Channel{routeguide.Point}) = RecordRoute(client.stub, client.controller, inp) +RecordRoute(client::RouteGuideClient, inp::Channel{routeguide.Point}, done::Function) = RecordRoute(client.stub, client.controller, inp, done) + +import .routeguide: RouteChat +""" + RouteChat + +- input: Channel{routeguide.RouteNote} +- output: Channel{routeguide.RouteNote} +""" +RouteChat(client::RouteGuideBlockingClient, inp::Channel{routeguide.RouteNote}) = RouteChat(client.stub, client.controller, inp) +RouteChat(client::RouteGuideClient, inp::Channel{routeguide.RouteNote}, done::Function) = RouteChat(client.stub, client.controller, inp, done) + +end # module RouteGuideClients diff --git a/test/RouteGuideClients/route_guide_pb.jl b/test/RouteGuideClients/route_guide_pb.jl new file mode 100644 index 0000000..b61f48d --- /dev/null +++ b/test/RouteGuideClients/route_guide_pb.jl @@ -0,0 +1,237 @@ +# syntax: proto3 +using ProtoBuf +import ProtoBuf.meta + +mutable struct Point <: ProtoType + __protobuf_jl_internal_meta::ProtoMeta + __protobuf_jl_internal_values::Dict{Symbol,Any} + __protobuf_jl_internal_defaultset::Set{Symbol} + + function Point(; kwargs...) + obj = new(meta(Point), Dict{Symbol,Any}(), Set{Symbol}()) + values = obj.__protobuf_jl_internal_values + symdict = obj.__protobuf_jl_internal_meta.symdict + for nv in kwargs + fldname, fldval = nv + fldtype = symdict[fldname].jtyp + (fldname in keys(symdict)) || error(string(typeof(obj), " has no field with name ", fldname)) + values[fldname] = isa(fldval, fldtype) ? fldval : convert(fldtype, fldval) + end + obj + end +end # mutable struct Point +const __meta_Point = Ref{ProtoMeta}() +function meta(::Type{Point}) + ProtoBuf.metalock() do + if !isassigned(__meta_Point) + __meta_Point[] = target = ProtoMeta(Point) + allflds = Pair{Symbol,Union{Type,String}}[:latitude => Int32, :longitude => Int32] + 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) + end + __meta_Point[] + end +end +function Base.getproperty(obj::Point, name::Symbol) + if name === :latitude + return (obj.__protobuf_jl_internal_values[name])::Int32 + elseif name === :longitude + return (obj.__protobuf_jl_internal_values[name])::Int32 + else + getfield(obj, name) + end +end + +mutable struct Rectangle <: ProtoType + __protobuf_jl_internal_meta::ProtoMeta + __protobuf_jl_internal_values::Dict{Symbol,Any} + __protobuf_jl_internal_defaultset::Set{Symbol} + + function Rectangle(; kwargs...) + obj = new(meta(Rectangle), Dict{Symbol,Any}(), Set{Symbol}()) + values = obj.__protobuf_jl_internal_values + symdict = obj.__protobuf_jl_internal_meta.symdict + for nv in kwargs + fldname, fldval = nv + fldtype = symdict[fldname].jtyp + (fldname in keys(symdict)) || error(string(typeof(obj), " has no field with name ", fldname)) + values[fldname] = isa(fldval, fldtype) ? fldval : convert(fldtype, fldval) + end + obj + end +end # mutable struct Rectangle +const __meta_Rectangle = Ref{ProtoMeta}() +function meta(::Type{Rectangle}) + ProtoBuf.metalock() do + if !isassigned(__meta_Rectangle) + __meta_Rectangle[] = target = ProtoMeta(Rectangle) + allflds = Pair{Symbol,Union{Type,String}}[:lo => Point, :hi => Point] + 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) + end + __meta_Rectangle[] + end +end +function Base.getproperty(obj::Rectangle, name::Symbol) + if name === :lo + return (obj.__protobuf_jl_internal_values[name])::Point + elseif name === :hi + return (obj.__protobuf_jl_internal_values[name])::Point + else + getfield(obj, name) + end +end + +mutable struct Feature <: ProtoType + __protobuf_jl_internal_meta::ProtoMeta + __protobuf_jl_internal_values::Dict{Symbol,Any} + __protobuf_jl_internal_defaultset::Set{Symbol} + + function Feature(; kwargs...) + obj = new(meta(Feature), Dict{Symbol,Any}(), Set{Symbol}()) + values = obj.__protobuf_jl_internal_values + symdict = obj.__protobuf_jl_internal_meta.symdict + for nv in kwargs + fldname, fldval = nv + fldtype = symdict[fldname].jtyp + (fldname in keys(symdict)) || error(string(typeof(obj), " has no field with name ", fldname)) + values[fldname] = isa(fldval, fldtype) ? fldval : convert(fldtype, fldval) + end + obj + end +end # mutable struct Feature +const __meta_Feature = Ref{ProtoMeta}() +function meta(::Type{Feature}) + ProtoBuf.metalock() do + if !isassigned(__meta_Feature) + __meta_Feature[] = target = ProtoMeta(Feature) + allflds = Pair{Symbol,Union{Type,String}}[:name => AbstractString, :location => Point] + 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) + end + __meta_Feature[] + end +end +function Base.getproperty(obj::Feature, name::Symbol) + if name === :name + return (obj.__protobuf_jl_internal_values[name])::AbstractString + elseif name === :location + return (obj.__protobuf_jl_internal_values[name])::Point + else + getfield(obj, name) + end +end + +mutable struct RouteNote <: ProtoType + __protobuf_jl_internal_meta::ProtoMeta + __protobuf_jl_internal_values::Dict{Symbol,Any} + __protobuf_jl_internal_defaultset::Set{Symbol} + + function RouteNote(; kwargs...) + obj = new(meta(RouteNote), Dict{Symbol,Any}(), Set{Symbol}()) + values = obj.__protobuf_jl_internal_values + symdict = obj.__protobuf_jl_internal_meta.symdict + for nv in kwargs + fldname, fldval = nv + fldtype = symdict[fldname].jtyp + (fldname in keys(symdict)) || error(string(typeof(obj), " has no field with name ", fldname)) + values[fldname] = isa(fldval, fldtype) ? fldval : convert(fldtype, fldval) + end + obj + end +end # mutable struct RouteNote +const __meta_RouteNote = Ref{ProtoMeta}() +function meta(::Type{RouteNote}) + ProtoBuf.metalock() do + if !isassigned(__meta_RouteNote) + __meta_RouteNote[] = target = ProtoMeta(RouteNote) + allflds = Pair{Symbol,Union{Type,String}}[:location => Point, :message => AbstractString] + 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) + end + __meta_RouteNote[] + end +end +function Base.getproperty(obj::RouteNote, name::Symbol) + if name === :location + return (obj.__protobuf_jl_internal_values[name])::Point + elseif name === :message + return (obj.__protobuf_jl_internal_values[name])::AbstractString + else + getfield(obj, name) + end +end + +mutable struct RouteSummary <: ProtoType + __protobuf_jl_internal_meta::ProtoMeta + __protobuf_jl_internal_values::Dict{Symbol,Any} + __protobuf_jl_internal_defaultset::Set{Symbol} + + function RouteSummary(; kwargs...) + obj = new(meta(RouteSummary), Dict{Symbol,Any}(), Set{Symbol}()) + values = obj.__protobuf_jl_internal_values + symdict = obj.__protobuf_jl_internal_meta.symdict + for nv in kwargs + fldname, fldval = nv + fldtype = symdict[fldname].jtyp + (fldname in keys(symdict)) || error(string(typeof(obj), " has no field with name ", fldname)) + values[fldname] = isa(fldval, fldtype) ? fldval : convert(fldtype, fldval) + end + obj + end +end # mutable struct RouteSummary +const __meta_RouteSummary = Ref{ProtoMeta}() +function meta(::Type{RouteSummary}) + ProtoBuf.metalock() do + if !isassigned(__meta_RouteSummary) + __meta_RouteSummary[] = target = ProtoMeta(RouteSummary) + allflds = Pair{Symbol,Union{Type,String}}[:point_count => Int32, :feature_count => Int32, :distance => Int32, :elapsed_time => Int32] + 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) + end + __meta_RouteSummary[] + end +end +function Base.getproperty(obj::RouteSummary, name::Symbol) + if name === :point_count + return (obj.__protobuf_jl_internal_values[name])::Int32 + elseif name === :feature_count + return (obj.__protobuf_jl_internal_values[name])::Int32 + elseif name === :distance + return (obj.__protobuf_jl_internal_values[name])::Int32 + elseif name === :elapsed_time + return (obj.__protobuf_jl_internal_values[name])::Int32 + else + getfield(obj, name) + end +end + +# service methods for RouteGuide +const _RouteGuide_methods = MethodDescriptor[ + MethodDescriptor("GetFeature", 1, routeguide.Point, routeguide.Feature), + MethodDescriptor("ListFeatures", 2, routeguide.Rectangle, Channel{routeguide.Feature}), + MethodDescriptor("RecordRoute", 3, Channel{routeguide.Point}, routeguide.RouteSummary), + MethodDescriptor("RouteChat", 4, Channel{routeguide.RouteNote}, Channel{routeguide.RouteNote}) + ] # const _RouteGuide_methods +const _RouteGuide_desc = ServiceDescriptor("routeguide.RouteGuide", 1, _RouteGuide_methods) + +RouteGuide(impl::Module) = ProtoService(_RouteGuide_desc, impl) + +mutable struct RouteGuideStub <: AbstractProtoServiceStub{false} + impl::ProtoServiceStub + RouteGuideStub(channel::ProtoRpcChannel) = new(ProtoServiceStub(_RouteGuide_desc, channel)) +end # mutable struct RouteGuideStub + +mutable struct RouteGuideBlockingStub <: AbstractProtoServiceStub{true} + impl::ProtoServiceBlockingStub + RouteGuideBlockingStub(channel::ProtoRpcChannel) = new(ProtoServiceBlockingStub(_RouteGuide_desc, channel)) +end # mutable struct RouteGuideBlockingStub + +GetFeature(stub::RouteGuideStub, controller::ProtoRpcController, inp::routeguide.Point, done::Function) = call_method(stub.impl, _RouteGuide_methods[1], controller, inp, done) +GetFeature(stub::RouteGuideBlockingStub, controller::ProtoRpcController, inp::routeguide.Point) = call_method(stub.impl, _RouteGuide_methods[1], controller, inp) + +ListFeatures(stub::RouteGuideStub, controller::ProtoRpcController, inp::routeguide.Rectangle, done::Function) = call_method(stub.impl, _RouteGuide_methods[2], controller, inp, done) +ListFeatures(stub::RouteGuideBlockingStub, controller::ProtoRpcController, inp::routeguide.Rectangle) = call_method(stub.impl, _RouteGuide_methods[2], controller, inp) + +RecordRoute(stub::RouteGuideStub, controller::ProtoRpcController, inp::Channel{routeguide.Point}, done::Function) = call_method(stub.impl, _RouteGuide_methods[3], controller, inp, done) +RecordRoute(stub::RouteGuideBlockingStub, controller::ProtoRpcController, inp::Channel{routeguide.Point}) = call_method(stub.impl, _RouteGuide_methods[3], controller, inp) + +RouteChat(stub::RouteGuideStub, controller::ProtoRpcController, inp::Channel{routeguide.RouteNote}, done::Function) = call_method(stub.impl, _RouteGuide_methods[4], controller, inp, done) +RouteChat(stub::RouteGuideBlockingStub, controller::ProtoRpcController, inp::Channel{routeguide.RouteNote}) = call_method(stub.impl, _RouteGuide_methods[4], controller, inp) + +export Point, Rectangle, Feature, RouteNote, RouteSummary, RouteGuide, RouteGuideStub, RouteGuideBlockingStub, GetFeature, ListFeatures, RecordRoute, RouteChat diff --git a/test/RouteGuideClients/routeguide.jl b/test/RouteGuideClients/routeguide.jl new file mode 100644 index 0000000..6dbe92d --- /dev/null +++ b/test/RouteGuideClients/routeguide.jl @@ -0,0 +1,4 @@ +module routeguide + const _ProtoBuf_Top_ = @static isdefined(parentmodule(@__MODULE__), :_ProtoBuf_Top_) ? (parentmodule(@__MODULE__))._ProtoBuf_Top_ : parentmodule(@__MODULE__) + include("route_guide_pb.jl") +end diff --git a/test/buildserver.sh b/test/buildserver.sh new file mode 100755 index 0000000..6c44953 --- /dev/null +++ b/test/buildserver.sh @@ -0,0 +1,36 @@ +#!/bin/bash +set -e + +BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd ${BASEDIR} + +export PATH="$PATH:$(go env GOPATH)/bin" + +git clone -b v1.35.0 https://github.com/grpc/grpc-go +cd grpc-go/examples/route_guide +protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative routeguide/route_guide.proto +sed 's/localhost/0.0.0.0/g' server/server.go > server/server.go.new +rm server/server.go +mv server/server.go.new server/server.go + +export GOOS=linux +export GOARCH=amd64 +echo "building runserver_${GOOS}_${GOARCH}..." +go build -o runserver_${GOOS}_${GOARCH} -i server/server.go +export GOARCH=386 +echo "building runserver_${GOOS}_${GOARCH}..." +go build -o runserver_${GOOS}_${GOARCH} -i server/server.go +export GOOS=windows +export GOARCH=amd64 +echo "building runserver_${GOOS}_${GOARCH}..." +go build -o runserver_${GOOS}_${GOARCH}.exe -i server/server.go +export GOARCH=386 +echo "building runserver_${GOOS}_${GOARCH}..." +go build -o runserver_${GOOS}_${GOARCH}.exe -i server/server.go +export GOOS=darwin +export GOARCH=amd64 +echo "building runserver_${GOOS}_${GOARCH}..." +go build -o runserver_${GOOS}_${GOARCH} -i server/server.go + +mkdir -p ${BASEDIR}/runserver +cp runserver_* ${BASEDIR}/runserver/ diff --git a/test/certgen/.gitignore b/test/certgen/.gitignore deleted file mode 100644 index 3d9e703..0000000 --- a/test/certgen/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -*.crt -*.key -*.pem -*.csr -*.srl diff --git a/test/certgen/certgen.sh b/test/certgen/certgen.sh deleted file mode 100755 index 8aa16fc..0000000 --- a/test/certgen/certgen.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash - -BASEDIR=$(dirname $0) -cd $BASEDIR - -HOSTNAME=`hostname -f` -# HOSTNAME=localhost - -# Generate self signed root CA cert -openssl req -nodes -x509 -newkey rsa:2048 -keyout ca.key -out ca.crt -subj "/C=IN/ST=KA/L=Bangalore/O=JuliaComputing/OU=gRPCClient/CN=${HOSTNAME}/emailAddress=ca@examplegrpcclient.com" - -# Generate server cert to be signed -openssl req -nodes -newkey rsa:2048 -keyout server.key -out server.csr -subj "/C=IN/ST=KA/L=Bangalore/O=JuliaComputing/OU=server/CN=${HOSTNAME}/emailAddress=server@examplegrpcclient.com" - -# Sign the server cert -openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt - -# Create server PEM file -cat server.key server.crt > server.pem - - -# Generate client cert to be signed -openssl req -nodes -newkey rsa:2048 -keyout client.key -out client.csr -subj "/C=IN/ST=KA/L=Bangalore/O=JuliaComputing/OU=client/CN=${HOSTNAME}/emailAddress=client@examplegrpcclient.com" - -# Sign the client cert -openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAserial ca.srl -out client.crt - -# Create client PEM file -cat client.key client.crt > client.pem diff --git a/test/runserver.sh b/test/runserver.sh deleted file mode 100755 index 87e9691..0000000 --- a/test/runserver.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash -set -e - -BASEDIR=$(dirname $0) -cd ${BASEDIR} - -export PATH="$PATH:$(go env GOPATH)/bin" -CERT_FILE=../../../certgen/server.pem -KEY_FILE=../../../certgen/server.key -HOSTNAME=`hostname -f` - -git clone -b v1.35.0 https://github.com/grpc/grpc-go -cd grpc-go/examples/route_guide -protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative routeguide/route_guide.proto -sed 's/localhost/0.0.0.0/g' server/server.go > server/server.go.new -rm server/server.go -mv server/server.go.new server/server.go -go build -o runserver -i server/server.go - -./runserver --tls=true --cert_file=$CERT_FILE --key_file=$KEY_FILE & -echo $! > server.pid -echo "server pid `cat server.pid`" - -NEXT_WAIT_TIME=0 -until [ $NEXT_WAIT_TIME -eq 10 ] || nc -z 127.0.0.1 10000; do - sleep $(( NEXT_WAIT_TIME++ )) -done -[ $NEXT_WAIT_TIME -lt 5 ] - -echo "server listening" diff --git a/test/runtests.jl b/test/runtests.jl index 51a121b..814f90c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,7 +1,49 @@ using gRPCClient +using Downloads using Random +using Sockets using Test +const SERVER_RELEASE = "https://github.com/JuliaComputing/gRPCClient.jl/releases/download/testserver_v0.1/" +function server_binary() + arch = (Sys.ARCH === :x86_64) ? "amd64" : "386" + filename = Sys.islinux() ? "runserver_linux_$(arch)" : + Sys.iswindows() ? "runserver_windows_$(arch).exe" : + Sys.isapple() ? "runserver_darwin_$(arch)" : + error("no server binary available for this platform") + source = string(SERVER_RELEASE, filename) + destination = joinpath(@__DIR__, filename) + isfile(destination) || Downloads.download(source, destination) + ((filemode(destination) & 0o777) == 0o777) || chmod(destination, 0o777) + + destination +end + +function start_server() + serverbin = server_binary() + + @info("starting test server", serverbin) + serverproc = run(`$serverbin`; wait=false) + + listening = timedwait(120.0; pollint=5.0) do + try + sock = connect(ip"127.0.0.1", 10000) + close(sock) + true + catch + false + end + end + + if listening !== :ok + @warn("test server did not start, stopping server") + kill(serverproc) + error("test server did not start") + end + + serverproc +end + function test_generate() @testset "codegen" begin dir = joinpath(@__DIR__, "RouteGuideClients") @@ -12,22 +54,27 @@ function test_generate() end end -# e.g.: SSL_CERT_FILE=/path/to/gRPCClient/test/certgen/ca.crt julia runtests.jl https://hostname:10000/ -if isempty(get(ENV, "SSL_CERT_FILE", "")) - ENV["SSL_CERT_FILE"] = joinpath(@__DIR__, "certgen", "ca.crt") -end - # switch off host verification for tests if isempty(get(ENV, "JULIA_NO_VERIFY_HOSTS", "")) ENV["JULIA_NO_VERIFY_HOSTS"] = "**" end -server_endpoint = isempty(ARGS) ? "https://$(strip(read(`hostname -f`, String))):10000/" : ARGS[1] +server_endpoint = isempty(ARGS) ? "http://localhost:10000/" : ARGS[1] @info("server endpoint: $server_endpoint") @testset "gRPCClient" begin - test_generate() + if !Sys.iswindows() + test_generate() + else + @info("skipping code generation on Windows to avoid needing batch file execution permissions") + end include("test_routeclient.jl") + + serverproc = start_server() + @info("testing routeclinet...") test_clients(server_endpoint) -end \ No newline at end of file + + kill(serverproc) + @info("stopped test server") +end