grpc_kit 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +0 -1
- data/TODO.md +1 -1
- data/examples/helloworld_client.rb +5 -1
- data/examples/routeguide_client.rb +5 -2
- data/lib/grpc_kit/calls/client_client_streamer.rb +29 -0
- data/lib/grpc_kit/calls/client_request_response.rb +25 -0
- data/lib/grpc_kit/calls/client_server_streamer.rb +29 -0
- data/lib/grpc_kit/calls/server_client_streamer.rb +32 -0
- data/lib/grpc_kit/calls/server_request_response.rb +32 -0
- data/lib/grpc_kit/calls/server_server_streamer.rb +32 -0
- data/lib/grpc_kit/calls.rb +39 -0
- data/lib/grpc_kit/client.rb +17 -17
- data/lib/grpc_kit/grpc_time.rb +100 -0
- data/lib/grpc_kit/interceptors/client_client_streamer.rb +19 -0
- data/lib/grpc_kit/interceptors/client_request_response.rb +39 -0
- data/lib/grpc_kit/interceptors/client_server_streamer.rb +19 -0
- data/lib/grpc_kit/interceptors/server_client_streamer.rb +15 -0
- data/lib/grpc_kit/interceptors/server_request_response.rb +36 -0
- data/lib/grpc_kit/interceptors/server_server_streamer.rb +17 -0
- data/lib/grpc_kit/interceptors.rb +64 -5
- data/lib/grpc_kit/rpc_desc.rb +18 -2
- data/lib/grpc_kit/rpcs/client_bidi_streamer.rb +11 -0
- data/lib/grpc_kit/rpcs/client_client_streamer.rb +23 -0
- data/lib/grpc_kit/rpcs/client_request_response.rb +31 -0
- data/lib/grpc_kit/rpcs/client_server_streamer.rb +19 -0
- data/lib/grpc_kit/rpcs/server_bidi_streamer.rb +10 -0
- data/lib/grpc_kit/rpcs/server_client_streamer.rb +24 -0
- data/lib/grpc_kit/rpcs/server_request_response.rb +26 -0
- data/lib/grpc_kit/rpcs/server_server_streamer.rb +26 -0
- data/lib/grpc_kit/rpcs.rb +21 -5
- data/lib/grpc_kit/server.rb +16 -7
- data/lib/grpc_kit/session/headers.rb +2 -2
- data/lib/grpc_kit/session/stream.rb +5 -0
- data/lib/grpc_kit/{session/client.rb → sessions/client_session.rb} +5 -6
- data/lib/grpc_kit/{session/server.rb → sessions/server_session.rb} +21 -8
- data/lib/grpc_kit/streams/client_stream.rb +146 -0
- data/lib/grpc_kit/streams/server_stream.rb +105 -0
- data/lib/grpc_kit/{streams → transport}/packable.rb +2 -2
- data/lib/grpc_kit/{streams → transport}/send_buffer.rb +1 -1
- data/lib/grpc_kit/transports/client_transport.rb +96 -0
- data/lib/grpc_kit/transports/server_transport.rb +68 -0
- data/lib/grpc_kit/version.rb +1 -1
- metadata +32 -21
- data/lib/grpc_kit/interceptors/client_streamer.rb +0 -31
- data/lib/grpc_kit/interceptors/request_response.rb +0 -70
- data/lib/grpc_kit/interceptors/server_streamer.rb +0 -33
- data/lib/grpc_kit/interceptors/streaming.rb +0 -70
- data/lib/grpc_kit/rpcs/base.rb +0 -30
- data/lib/grpc_kit/rpcs/bidi_streamer.rb +0 -18
- data/lib/grpc_kit/rpcs/call.rb +0 -27
- data/lib/grpc_kit/rpcs/client_streamer.rb +0 -38
- data/lib/grpc_kit/rpcs/error.rb +0 -23
- data/lib/grpc_kit/rpcs/request_response.rb +0 -64
- data/lib/grpc_kit/rpcs/server_streamer.rb +0 -42
- data/lib/grpc_kit/session/duration.rb +0 -97
- data/lib/grpc_kit/stream.rb +0 -120
- data/lib/grpc_kit/streams/client.rb +0 -113
- data/lib/grpc_kit/streams/server.rb +0 -54
@@ -1,64 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'grpc_kit/rpcs/base'
|
4
|
-
require 'grpc_kit/status_codes'
|
5
|
-
|
6
|
-
module GrpcKit
|
7
|
-
module Rpcs
|
8
|
-
module Client
|
9
|
-
class RequestResponse < Base
|
10
|
-
def invoke(session, request, authority:, metadata: {}, timeout: nil, **opts)
|
11
|
-
cs = GrpcKit::Streams::Client.new(config: @config, session: session, authority: authority)
|
12
|
-
|
13
|
-
call = GrpcKit::Rpcs::Call.new(metadata, @config.method_name, @config.service_name, cs)
|
14
|
-
@config.interceptor.intercept(request, call, metadata) do |r, c, m|
|
15
|
-
if timeout
|
16
|
-
# XXX: when timeout.to_timeout is 0
|
17
|
-
Timeout.timeout(timeout.to_timeout, GrpcKit::Errors::DeadlineExceeded) do
|
18
|
-
c.send_msg(r, timeout: timeout.to_s, metadata: m, last: true)
|
19
|
-
c.recv(last: true)
|
20
|
-
end
|
21
|
-
else
|
22
|
-
c.send_msg(r, metadata: m, last: true)
|
23
|
-
c.recv(last: true)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
module Server
|
31
|
-
class RequestResponse < Base
|
32
|
-
def invoke(stream, session)
|
33
|
-
ss = GrpcKit::Streams::Server.new(stream: stream, session: session, config: @config)
|
34
|
-
call = GrpcKit::Rpcs::Call.new(stream.headers.metadata, @config.method_name, @config.service_name, ss)
|
35
|
-
|
36
|
-
begin
|
37
|
-
do_invoke(ss, call)
|
38
|
-
rescue GrpcKit::Errors::BadStatus => e
|
39
|
-
ss.send_status(status: e.code, msg: e.reason, metadata: {}) # TODO: metadata should be set
|
40
|
-
rescue StandardError => e
|
41
|
-
ss.send_status(status: GrpcKit::StatusCodes::UNKNOWN, msg: e.message, metadata: {})
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
private
|
46
|
-
|
47
|
-
def do_invoke(ss, call)
|
48
|
-
request = ss.recv(last: true)
|
49
|
-
|
50
|
-
resp =
|
51
|
-
if @config.interceptor
|
52
|
-
@config.interceptor.intercept(request, call) do |req, c|
|
53
|
-
@handler.send(@config.ruby_style_method_name, req, c)
|
54
|
-
end
|
55
|
-
else
|
56
|
-
@handler.send(@config.ruby_style_method_name, request, call)
|
57
|
-
end
|
58
|
-
|
59
|
-
ss.send_msg(resp, last: true)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
@@ -1,42 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'grpc_kit/rpcs/base'
|
4
|
-
|
5
|
-
module GrpcKit
|
6
|
-
module Rpcs
|
7
|
-
module Client
|
8
|
-
class ServerStreamer < Base
|
9
|
-
def invoke(session, request, authority:, metadata: {}, timeout: nil, **opts)
|
10
|
-
cs = GrpcKit::Streams::Client.new(config: @config, session: session, authority: authority)
|
11
|
-
call = GrpcKit::Rpcs::Call.new(metadata, @config.method_name, @config.service_name, cs)
|
12
|
-
|
13
|
-
@config.interceptor.intercept(call, metadata) do |c, m|
|
14
|
-
c.send_msg(request, metadata: m, last: true)
|
15
|
-
c
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
module Server
|
22
|
-
class ServerStreamer < Base
|
23
|
-
def invoke(stream, session)
|
24
|
-
ss = GrpcKit::Streams::Server.new(stream: stream, session: session, config: @config)
|
25
|
-
call = GrpcKit::Rpcs::Call.new(stream.headers.metadata, @config.method_name, @config.service_name, ss)
|
26
|
-
|
27
|
-
if @config.interceptor
|
28
|
-
@config.interceptor.intercept(call) do |c|
|
29
|
-
request = c.recv(last: true)
|
30
|
-
@handler.send(@config.ruby_style_method_name, request, c)
|
31
|
-
end
|
32
|
-
else
|
33
|
-
request = call.recv(last: true)
|
34
|
-
@handler.send(@config.ruby_style_method_name, request, call)
|
35
|
-
end
|
36
|
-
|
37
|
-
ss.send_trailer
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
@@ -1,97 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module GrpcKit
|
4
|
-
module Session
|
5
|
-
class Duration < Struct.new(:sec, :msec, :usec, :nsec)
|
6
|
-
MAX_TIMEOUT = 10**9 - 1
|
7
|
-
HOUR = 60 * 60
|
8
|
-
MIN = 60
|
9
|
-
MILL_SEC = 10**-3
|
10
|
-
MICRO_SEC = 10**-6
|
11
|
-
NANO_SEC = 10**-9
|
12
|
-
|
13
|
-
# @params val [String]
|
14
|
-
def self.decode(value)
|
15
|
-
value = value.dup
|
16
|
-
size = value.size
|
17
|
-
if size < 2
|
18
|
-
raise "Invalid format: too short #{value}"
|
19
|
-
end
|
20
|
-
|
21
|
-
unit = value.slice!(-1, 1)
|
22
|
-
d = Duration.new(0, 0, 0, 0)
|
23
|
-
n = Integer(value)
|
24
|
-
|
25
|
-
case unit
|
26
|
-
when 'H'
|
27
|
-
d.sec = n * HOUR
|
28
|
-
when 'M'
|
29
|
-
d.sec = n * MIN
|
30
|
-
when 'S'
|
31
|
-
d.sec = n
|
32
|
-
when 'm'
|
33
|
-
d.msec = n
|
34
|
-
when 'u'
|
35
|
-
d.usec = n
|
36
|
-
when 'n'
|
37
|
-
d.nsec = n
|
38
|
-
else
|
39
|
-
raise "Invalid unit `#{unit}`: #{value + unit} "
|
40
|
-
end
|
41
|
-
d
|
42
|
-
end
|
43
|
-
|
44
|
-
def to_timeout
|
45
|
-
v = 0
|
46
|
-
|
47
|
-
if nsec && (nsec != 0)
|
48
|
-
v += (NANO_SEC * nsec)
|
49
|
-
end
|
50
|
-
|
51
|
-
if usec && (usec != 0)
|
52
|
-
v += (MICRO_SEC * usec)
|
53
|
-
end
|
54
|
-
|
55
|
-
if msec && (msec != 0)
|
56
|
-
v += (MILL_SEC * msec)
|
57
|
-
end
|
58
|
-
|
59
|
-
if sec
|
60
|
-
v += sec
|
61
|
-
end
|
62
|
-
|
63
|
-
v
|
64
|
-
end
|
65
|
-
|
66
|
-
# @params val [Numeric]
|
67
|
-
def self.from_numeric(val)
|
68
|
-
case val
|
69
|
-
when nil
|
70
|
-
when Numeric
|
71
|
-
if val < 0
|
72
|
-
Duration.new(MAX_TIMEOUT, 0, 0, 0)
|
73
|
-
elsif val == 0
|
74
|
-
Duration.new(0, 0, 0, 0)
|
75
|
-
else
|
76
|
-
Duration.new(val, 0, 0, 0)
|
77
|
-
end
|
78
|
-
else
|
79
|
-
raise "Cannot make timeout from #{val}"
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
# TODO
|
84
|
-
def to_s
|
85
|
-
if nsec && (nsec != 0)
|
86
|
-
"#{nsec}n"
|
87
|
-
elsif usec && (usec != 0)
|
88
|
-
"#{usec}u"
|
89
|
-
elsif msec && (msec != 0)
|
90
|
-
"#{msec}m"
|
91
|
-
elsif sec
|
92
|
-
"#{sec}S"
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
data/lib/grpc_kit/stream.rb
DELETED
@@ -1,120 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'forwardable'
|
4
|
-
require 'grpc_kit/streams/packable'
|
5
|
-
|
6
|
-
module GrpcKit
|
7
|
-
class Stream
|
8
|
-
include GrpcKit::Streams::Packable
|
9
|
-
|
10
|
-
extend Forwardable
|
11
|
-
|
12
|
-
delegate %i[stream_id end_write end_read end_write? end_read? close_remote? headers] => :@stream
|
13
|
-
|
14
|
-
# @params protobuf [GrpcKit::Protobuffer]
|
15
|
-
# @params session [GrpcKit::Session::Server|GrpcKit::Session::Client]
|
16
|
-
# @params stream [GrpcKit::Session::Stream] primitive H2 stream id
|
17
|
-
def initialize(protobuf:, session:, stream:)
|
18
|
-
@protobuf = protobuf
|
19
|
-
@session = session
|
20
|
-
@stream = stream
|
21
|
-
end
|
22
|
-
|
23
|
-
def each
|
24
|
-
loop do
|
25
|
-
data = recv
|
26
|
-
return if data.nil?
|
27
|
-
|
28
|
-
yield(data)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def send(data, last: false, limit_size: nil)
|
33
|
-
b =
|
34
|
-
begin
|
35
|
-
@protobuf.encode(data)
|
36
|
-
rescue ArgumentError => e
|
37
|
-
raise GrpcKit::Errors::Internal, "Error while encoding: #{e}"
|
38
|
-
end
|
39
|
-
|
40
|
-
req = pack(b)
|
41
|
-
if limit_size && req.bytesize > limit_size
|
42
|
-
raise GrpcKit::Errors::ResourceExhausted, "Sending message is too large: send=#{req.bytesize}, max=#{limit_size}"
|
43
|
-
end
|
44
|
-
|
45
|
-
@stream.write_send_data(req, last: last)
|
46
|
-
end
|
47
|
-
|
48
|
-
def recv(last: false, limit_size: nil)
|
49
|
-
data = unpack(read(last: last))
|
50
|
-
|
51
|
-
return nil unless data
|
52
|
-
|
53
|
-
compressed, size, buf = *data
|
54
|
-
|
55
|
-
unless size == buf.size
|
56
|
-
raise "inconsistent data: #{buf}"
|
57
|
-
end
|
58
|
-
|
59
|
-
if limit_size && size > limit_size
|
60
|
-
raise GrpcKit::Errors::ResourceExhausted, "Receving message is too large: recevied=#{size}, max=#{limit_size}"
|
61
|
-
end
|
62
|
-
|
63
|
-
if compressed
|
64
|
-
raise 'compress option is unsupported'
|
65
|
-
end
|
66
|
-
|
67
|
-
begin
|
68
|
-
@protobuf.decode(buf)
|
69
|
-
rescue ArgumentError => e
|
70
|
-
raise GrpcKit::Errors::Internal, "Error while decoding #{e}"
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
def send_trailer(status: GrpcKit::StatusCodes::OK, msg: nil, metadata: {})
|
75
|
-
trailer = metadata.dup
|
76
|
-
trailer['grpc-status'] = status.to_s
|
77
|
-
if msg
|
78
|
-
trailer['grpc-message'] = msg
|
79
|
-
end
|
80
|
-
|
81
|
-
@stream.write_trailers_data(trailer)
|
82
|
-
@stream.end_write
|
83
|
-
end
|
84
|
-
|
85
|
-
# TODO: use actual data
|
86
|
-
def submit_response(_header = nil, piggyback_trailer: false)
|
87
|
-
headers = { ':status' => '200', 'content-type' => 'application/grpc' }
|
88
|
-
|
89
|
-
# ds9 does not support nthttp2_submit_{response|request} without body
|
90
|
-
# if piggyback_trailer
|
91
|
-
# headers.merge!(@stream.trailer_data)
|
92
|
-
# @stream.need_trailer = false
|
93
|
-
# else
|
94
|
-
headers['accept-encoding'] = 'identity'
|
95
|
-
# end
|
96
|
-
|
97
|
-
@session.submit_response(@stream.stream_id, headers)
|
98
|
-
end
|
99
|
-
|
100
|
-
private
|
101
|
-
|
102
|
-
def read(last: false)
|
103
|
-
loop do
|
104
|
-
data = @stream.read_recv_data(last: last)
|
105
|
-
return data unless data.empty?
|
106
|
-
|
107
|
-
if @stream.close_remote?
|
108
|
-
# it do not receive data which we need, it may receive invalid grpc-status
|
109
|
-
unless @stream.end_read?
|
110
|
-
return nil
|
111
|
-
end
|
112
|
-
|
113
|
-
return nil
|
114
|
-
end
|
115
|
-
|
116
|
-
@session.run_once
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
@@ -1,113 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'grpc_kit/stream'
|
4
|
-
require 'grpc_kit/streams/send_buffer'
|
5
|
-
require 'grpc_kit/status_codes'
|
6
|
-
|
7
|
-
module GrpcKit
|
8
|
-
module Streams
|
9
|
-
class Client
|
10
|
-
def initialize(session:, config:, authority:)
|
11
|
-
@config = config
|
12
|
-
@session = session
|
13
|
-
@stream = nil
|
14
|
-
@authority = authority
|
15
|
-
end
|
16
|
-
|
17
|
-
def send_msg(data, metadata: {}, timeout: nil, last: false)
|
18
|
-
if @stream
|
19
|
-
# unless metadata.empty?
|
20
|
-
# raise 'You can attach metadata at first send_msg' # XXX
|
21
|
-
# end
|
22
|
-
|
23
|
-
unless @stream.end_write?
|
24
|
-
@session.resume_data(@stream.stream_id)
|
25
|
-
end
|
26
|
-
else
|
27
|
-
headers = build_headers(metadata: metadata, timeout: timeout)
|
28
|
-
stream = @session.start_request(GrpcKit::Streams::SendBuffer.new, headers)
|
29
|
-
@stream = GrpcKit::Stream.new(protobuf: @config.protobuf, session: @session, stream: stream)
|
30
|
-
end
|
31
|
-
|
32
|
-
@stream.send(data, last: last, limit_size: @config.max_send_message_size)
|
33
|
-
@session.run_once
|
34
|
-
end
|
35
|
-
|
36
|
-
def each(&block)
|
37
|
-
unless @stream
|
38
|
-
raise 'You should call `send` method to send data'
|
39
|
-
end
|
40
|
-
|
41
|
-
@stream.each(&block)
|
42
|
-
end
|
43
|
-
|
44
|
-
def recv(last: false)
|
45
|
-
unless @stream
|
46
|
-
raise 'You should call `send` method to send data'
|
47
|
-
end
|
48
|
-
|
49
|
-
data = @stream.recv(last: last, limit_size: @config.max_receive_message_size)
|
50
|
-
|
51
|
-
if data.nil?
|
52
|
-
check_status!
|
53
|
-
raise StopIteration
|
54
|
-
end
|
55
|
-
|
56
|
-
data
|
57
|
-
end
|
58
|
-
|
59
|
-
def close_and_recv
|
60
|
-
unless @stream
|
61
|
-
raise 'You should call `send` method to send data'
|
62
|
-
end
|
63
|
-
|
64
|
-
unless @stream.end_write?
|
65
|
-
@session.resume_data(@stream.stream_id)
|
66
|
-
end
|
67
|
-
|
68
|
-
@stream.end_write
|
69
|
-
@session.start(@stream.stream_id)
|
70
|
-
@stream.end_read
|
71
|
-
|
72
|
-
check_status!
|
73
|
-
|
74
|
-
data = []
|
75
|
-
@stream.each { |d| data.push(d) }
|
76
|
-
data
|
77
|
-
end
|
78
|
-
|
79
|
-
private
|
80
|
-
|
81
|
-
def check_status!
|
82
|
-
# XXX: wait until half close (remote) to get grpc-status
|
83
|
-
until @stream.close_remote?
|
84
|
-
@session.run_once
|
85
|
-
end
|
86
|
-
|
87
|
-
if @stream.headers.grpc_status != GrpcKit::StatusCodes::OK
|
88
|
-
raise GrpcKit::Errors.from_status_code(@stream.headers.grpc_status, @stream.headers.status_message)
|
89
|
-
else
|
90
|
-
GrpcKit.logger.debug('request is success')
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
def build_headers(metadata: {}, timeout: nil, **headers)
|
95
|
-
hdrs = metadata.merge(headers).merge(
|
96
|
-
':method' => 'POST',
|
97
|
-
':scheme' => 'http',
|
98
|
-
':path' => @config.path,
|
99
|
-
':authority' => @authority,
|
100
|
-
'te' => 'trailers',
|
101
|
-
'content-type' => 'application/grpc',
|
102
|
-
'user-agent' => "grpc-ruby/#{GrpcKit::VERSION} (grpc_kit)",
|
103
|
-
'grpc-accept-encoding' => 'identity,deflate,gzip',
|
104
|
-
)
|
105
|
-
if timeout
|
106
|
-
hdrs['grpc-timeout'] = timeout
|
107
|
-
end
|
108
|
-
|
109
|
-
hdrs
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
@@ -1,54 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'forwardable'
|
4
|
-
|
5
|
-
require 'grpc_kit/stream'
|
6
|
-
require 'grpc_kit/status_codes'
|
7
|
-
|
8
|
-
module GrpcKit
|
9
|
-
module Streams
|
10
|
-
class Server
|
11
|
-
extend Forwardable
|
12
|
-
|
13
|
-
delegate %i[each recv] => :@stream
|
14
|
-
|
15
|
-
def initialize(stream:, session:, config:)
|
16
|
-
@stream = GrpcKit::Stream.new(protobuf: config.protobuf, session: session, stream: stream)
|
17
|
-
@config = config
|
18
|
-
@sent_first_msg = false
|
19
|
-
end
|
20
|
-
|
21
|
-
def send_msg(data, last: false)
|
22
|
-
if last
|
23
|
-
@stream.send_trailer # TODO: pass trailer metadata
|
24
|
-
end
|
25
|
-
|
26
|
-
@stream.send(data, last: last, limit_size: @config.max_send_message_size)
|
27
|
-
return if @sent_first_msg
|
28
|
-
|
29
|
-
@stream.submit_response
|
30
|
-
@sent_first_msg = true
|
31
|
-
end
|
32
|
-
|
33
|
-
def recv(last: false)
|
34
|
-
data = @stream.recv(last: last, limit_size: @config.max_receive_message_size)
|
35
|
-
raise StopIteration if data.nil?
|
36
|
-
|
37
|
-
data
|
38
|
-
end
|
39
|
-
|
40
|
-
def send_trailer
|
41
|
-
@stream.send_trailer # TODO: pass trailer metadata
|
42
|
-
@stream.end_write
|
43
|
-
end
|
44
|
-
|
45
|
-
def send_status(status: GrpcKit::StatusCodes::INTERNAL, msg: nil, metadata: {})
|
46
|
-
@stream.send_trailer(status: status, msg: msg, metadata: metadata)
|
47
|
-
return if @sent_first_msg
|
48
|
-
|
49
|
-
@stream.submit_response(piggyback_trailer: true)
|
50
|
-
@sent_first_msg = true
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|