starpc 0.52.0 → 0.52.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/README.md +37 -8
  2. package/dist/echo/client-test.d.ts +1 -1
  3. package/dist/echo/client-test.js +110 -2
  4. package/dist/integration/cross-language/tcp-packet-stream.d.ts +3 -0
  5. package/dist/integration/cross-language/tcp-packet-stream.js +112 -0
  6. package/dist/integration/cross-language/tcp-packet-stream.test.d.ts +1 -0
  7. package/dist/integration/cross-language/tcp-packet-stream.test.js +121 -0
  8. package/dist/integration/cross-language/ts-client.js +50 -37
  9. package/dist/integration/cross-language/ts-server.js +1 -36
  10. package/dist/rpcstream/rpcstream.d.ts +5 -1
  11. package/dist/rpcstream/rpcstream.js +75 -28
  12. package/dist/rpcstream/rpcstream.test.d.ts +1 -0
  13. package/dist/rpcstream/rpcstream.test.js +92 -0
  14. package/dist/srpc/client-rpc.d.ts +1 -0
  15. package/dist/srpc/client-rpc.js +9 -0
  16. package/dist/srpc/client-rpc.test.d.ts +1 -0
  17. package/dist/srpc/client-rpc.test.js +50 -0
  18. package/dist/srpc/client.js +18 -4
  19. package/dist/srpc/common-rpc.d.ts +5 -0
  20. package/dist/srpc/common-rpc.js +37 -8
  21. package/dist/srpc/common-rpc.test.js +2 -0
  22. package/dist/srpc/errors.d.ts +2 -0
  23. package/dist/srpc/errors.js +14 -0
  24. package/dist/srpc/index.d.ts +1 -1
  25. package/dist/srpc/index.js +1 -1
  26. package/dist/srpc/packet-codec.test.d.ts +1 -0
  27. package/dist/srpc/packet-codec.test.js +75 -0
  28. package/dist/srpc/packet.d.ts +1 -1
  29. package/dist/srpc/packet.js +11 -1
  30. package/dist/srpc/server.js +19 -6
  31. package/dist/srpc/server.test.js +43 -0
  32. package/dist/srpc/stream.d.ts +4 -1
  33. package/dist/srpc/stream.js +62 -3
  34. package/dist/srpc/stream.test.js +110 -1
  35. package/dist/srpc/termination.d.ts +27 -0
  36. package/dist/srpc/termination.js +56 -0
  37. package/dist/srpc/termination.test.d.ts +1 -0
  38. package/dist/srpc/termination.test.js +24 -0
  39. package/dist/testdata/packet-codec-vectors.json +64 -0
  40. package/echo/Cargo.toml +8 -0
  41. package/echo/client-test.ts +124 -2
  42. package/echo/echo_e2e_test.cpp +542 -985
  43. package/echo/echo_pb2.py +40 -0
  44. package/echo/echo_pb2.pyi +13 -0
  45. package/echo/echo_srpc.pb.cpp +3 -5
  46. package/echo/echo_srpc.pb.go +6 -9
  47. package/echo/echo_srpc.pb.hpp +23 -3
  48. package/echo/echo_srpc.pb.rs +61 -26
  49. package/echo/echo_srpc.py +309 -0
  50. package/echo/echo_srpc.pyi +85 -0
  51. package/echo/half_close_test.rs +178 -0
  52. package/echo/integration_server.rs +14 -21
  53. package/echo/main.rs +28 -48
  54. package/go.mod +3 -3
  55. package/go.sum +6 -8
  56. package/integration/cross-language/cpp-client.cpp +446 -402
  57. package/integration/cross-language/cpp-pipe-server.cpp +139 -0
  58. package/integration/cross-language/cpp-pipe_test.go +114 -0
  59. package/integration/cross-language/cpp-server.cpp +222 -186
  60. package/integration/cross-language/go-client/main.go +79 -3
  61. package/integration/cross-language/python-client.py +146 -0
  62. package/integration/cross-language/python-server.py +140 -0
  63. package/integration/cross-language/run.bash +190 -65
  64. package/integration/cross-language/tcp-packet-stream.test.ts +154 -0
  65. package/integration/cross-language/tcp-packet-stream.ts +121 -0
  66. package/integration/cross-language/ts-client.ts +62 -40
  67. package/integration/cross-language/ts-server.ts +1 -45
  68. package/mock/mock_pb2.py +38 -0
  69. package/mock/mock_pb2.pyi +11 -0
  70. package/mock/mock_srpc.pb.cpp +1 -1
  71. package/mock/mock_srpc.pb.hpp +1 -1
  72. package/mock/mock_srpc.pb.rs +10 -9
  73. package/mock/mock_srpc.py +71 -0
  74. package/mock/mock_srpc.pyi +27 -0
  75. package/package.json +19 -5
  76. package/srpc/CMakeLists.txt +3 -58
  77. package/srpc/README-cpp.md +69 -0
  78. package/srpc/__init__.py +0 -0
  79. package/srpc/accept.go +10 -4
  80. package/srpc/accept_test.go +25 -0
  81. package/srpc/build.rs +129 -34
  82. package/srpc/client-half-close_test.go +152 -0
  83. package/srpc/client-invoker.go +32 -42
  84. package/srpc/client-invoker_test.go +53 -0
  85. package/srpc/client-rpc.cpp +65 -80
  86. package/srpc/client-rpc.go +32 -33
  87. package/srpc/client-rpc.hpp +52 -38
  88. package/srpc/client-rpc.test.ts +67 -0
  89. package/srpc/client-rpc.ts +10 -0
  90. package/srpc/client-rpc_test.go +76 -0
  91. package/srpc/client.cpp +99 -87
  92. package/srpc/client.hpp +46 -35
  93. package/srpc/client.rs +78 -38
  94. package/srpc/client.ts +20 -4
  95. package/srpc/client_half_close_test.cc +331 -0
  96. package/srpc/client_invoker.rs +75 -0
  97. package/srpc/codec.rs +6 -0
  98. package/srpc/common-rpc.cpp +186 -146
  99. package/srpc/common-rpc.go +5 -4
  100. package/srpc/common-rpc.hpp +119 -89
  101. package/srpc/common-rpc.test.ts +2 -0
  102. package/srpc/common-rpc.ts +43 -8
  103. package/srpc/common-rpc_test.go +23 -0
  104. package/srpc/error.rs +19 -2
  105. package/srpc/errors.hpp +80 -50
  106. package/srpc/errors.ts +16 -0
  107. package/srpc/handler.hpp +9 -7
  108. package/srpc/handler.rs +7 -45
  109. package/srpc/index.ts +2 -0
  110. package/srpc/invoker.hpp +82 -55
  111. package/srpc/invoker.rs +14 -52
  112. package/srpc/lib.rs +2 -0
  113. package/srpc/message.hpp +17 -12
  114. package/srpc/msg-stream.go +1 -0
  115. package/srpc/msg-stream.hpp +108 -61
  116. package/srpc/mux.cpp +84 -84
  117. package/srpc/mux.hpp +48 -38
  118. package/srpc/mux.rs +4 -4
  119. package/srpc/net.go +12 -3
  120. package/srpc/packet-codec-vectors_test.go +195 -0
  121. package/srpc/packet-codec.test.ts +139 -0
  122. package/srpc/packet-rw.go +9 -2
  123. package/srpc/packet.cpp +60 -58
  124. package/srpc/packet.hpp +35 -20
  125. package/srpc/packet.rs +8 -27
  126. package/srpc/packet.ts +15 -2
  127. package/srpc/py.typed +0 -0
  128. package/srpc/rpc.rs +332 -115
  129. package/srpc/rpc_lifecycle_test.cc +333 -0
  130. package/srpc/rpcproto_pb2.py +40 -0
  131. package/srpc/rpcproto_pb2.pyi +40 -0
  132. package/srpc/server-cancel_test.go +39 -0
  133. package/srpc/server-rpc.cpp +83 -73
  134. package/srpc/server-rpc.go +21 -19
  135. package/srpc/server-rpc.hpp +56 -37
  136. package/srpc/server.rs +76 -131
  137. package/srpc/server.test.ts +50 -0
  138. package/srpc/server.ts +22 -6
  139. package/srpc/starpc.hpp +4 -2
  140. package/srpc/stream.go +4 -0
  141. package/srpc/stream.hpp +95 -34
  142. package/srpc/stream.rs +7 -2
  143. package/srpc/stream.test.ts +132 -1
  144. package/srpc/stream.ts +65 -9
  145. package/srpc/termination.test.ts +30 -0
  146. package/srpc/termination.ts +70 -0
  147. package/srpc/testing.rs +11 -66
  148. package/srpc/transport.rs +21 -71
  149. package/srpc/websocket.rs +106 -57
  150. package/srpc/writer.hpp +46 -32
  151. package/srpc/yamux.rs +82 -26
  152. package/srpc/tests/integration_test.rs +0 -495
@@ -1,13 +1,13 @@
1
1
  #pragma once
2
2
 
3
- #include <string>
4
-
5
3
  #include "common-rpc.hpp"
6
4
  #include "errors.hpp"
7
5
  #include "msg-stream.hpp"
8
6
  #include "packet.hpp"
9
7
  #include "writer.hpp"
10
8
 
9
+ #include <string>
10
+
11
11
  namespace srpc {
12
12
  class Packet;
13
13
  class CallStart;
@@ -15,54 +15,68 @@ class CallStart;
15
15
 
16
16
  namespace starpc {
17
17
 
18
- // ClientRPC represents the client side of an on-going RPC call message stream.
19
- // Matches Go ClientRPC struct in client-rpc.go
18
+ /*
19
+ * ClientRPC is the caller's side of one call. The transport feeds it packets
20
+ * through HandlePacketData and it writes through the PacketWriter given to
21
+ * Start.
22
+ */
20
23
  class ClientRPC : public CommonRPC, public MsgStreamRw {
21
24
  public:
22
- // Constructor matching NewClientRPC in Go
23
- ClientRPC(const std::string &service, const std::string &method);
24
- ~ClientRPC() override;
25
+ /* ClientRPC opens a call for service and method. */
26
+ ClientRPC(const std::string &service, const std::string &method);
27
+ ~ClientRPC() override;
25
28
 
26
- // Start sets the writer and writes the CallStart message.
27
- // Must only be called once!
28
- // Matches Go Start method in client-rpc.go
29
- Error Start(PacketWriter *writer, bool write_first_msg,
30
- const std::string &first_msg);
29
+ /*
30
+ * Start attaches a borrowed writer and writes CallStart once. The
31
+ * writer must outlive this call and its callbacks. The caller closes
32
+ * a rejected writer.
33
+ */
34
+ Error Start(PacketWriter *writer, bool write_first_msg,
35
+ const std::string &first_msg);
31
36
 
32
- // HandlePacketData handles an incoming unparsed message packet.
33
- // Matches Go HandlePacketData in client-rpc.go
34
- Error HandlePacketData(const std::string &data);
37
+ /* HandlePacketData parses one serialized packet and handles it. */
38
+ Error HandlePacketData(const std::string &data);
35
39
 
36
- // HandleStreamClose handles the stream closing optionally with an error.
37
- // Matches Go HandleStreamClose (overrides CommonRPC)
38
- void HandleStreamClose(Error close_err);
40
+ /* HandleStreamClose records the transport ending with its outcome. */
41
+ void HandleStreamClose(Error close_err);
39
42
 
40
- // HandlePacket handles an incoming parsed message packet.
41
- // Matches Go HandlePacket in client-rpc.go
42
- Error HandlePacket(const srpc::Packet &pkt);
43
+ /* HandlePacket validates and dispatches one parsed packet. */
44
+ Error HandlePacket(const srpc::Packet &pkt);
43
45
 
44
- // HandleCallStart handles the call start packet.
45
- // Matches Go HandleCallStart in client-rpc.go
46
- Error HandleCallStart(const srpc::CallStart &pkt);
46
+ /*
47
+ * HandleCallStart rejects a CallStart: the caller never receives one.
48
+ */
49
+ Error HandleCallStart(const srpc::CallStart &pkt);
47
50
 
48
- // Close releases any resources held by the ClientRPC.
49
- // Matches Go Close in client-rpc.go
50
- void Close();
51
+ /* Close cancels the call. */
52
+ void Close();
51
53
 
52
- // MsgStreamRw interface implementation
53
- Error ReadOne(std::string *out) override { return CommonRPC::ReadOne(out); }
54
- Error WriteCallData(const std::string &data, bool data_is_zero, bool complete,
55
- Error err) override {
56
- return CommonRPC::WriteCallData(data, data_is_zero, complete, err);
57
- }
58
- Error WriteCallCancel() override { return CommonRPC::WriteCallCancel(); }
54
+ /* MsgStreamRw over the shared call state. */
55
+ Error ReadOne(std::string *out) override
56
+ {
57
+ return CommonRPC::ReadOne(out);
58
+ }
59
+ Error WriteCallData(const std::string &data, bool data_is_zero,
60
+ bool complete, Error err) override
61
+ {
62
+ return CommonRPC::WriteCallData(data, data_is_zero, complete,
63
+ err);
64
+ }
65
+ Error WriteCallCancel() override
66
+ {
67
+ return CommonRPC::WriteCallCancel();
68
+ }
69
+ std::string RemoteErrorMessage() const override
70
+ {
71
+ return CommonRPC::RemoteErrorMessage();
72
+ }
59
73
  };
60
74
 
61
- // NewClientRPC constructs a new ClientRPC session.
62
- // Matches Go NewClientRPC function in client-rpc.go
75
+ /* NewClientRPC constructs a ClientRPC for service and method. */
63
76
  inline std::unique_ptr<ClientRPC> NewClientRPC(const std::string &service,
64
- const std::string &method) {
65
- return std::make_unique<ClientRPC>(service, method);
77
+ const std::string &method)
78
+ {
79
+ return std::make_unique<ClientRPC>(service, method);
66
80
  }
67
81
 
68
82
  } // namespace starpc
@@ -0,0 +1,67 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { ClientRPC } from './client-rpc.js'
4
+ import { isClosedBeforeCompletionError } from './errors.js'
5
+ import { Packet } from './rpcproto.pb.js'
6
+
7
+ describe('ClientRPC', () => {
8
+ it('fails a call whose transport ends before the remote completes it', async () => {
9
+ const call = new ClientRPC('svc', 'method')
10
+ await call.sink(packetSource(callDataPacket(new Uint8Array([1]))))
11
+
12
+ const received = new Array<number>()
13
+ const read = async () => {
14
+ for await (const data of call.rpcDataSource) {
15
+ received.push(data[0])
16
+ }
17
+ }
18
+ const err = await read().then(
19
+ () => undefined,
20
+ (err: unknown) => err,
21
+ )
22
+
23
+ expect(received).toEqual([1])
24
+ expect(isClosedBeforeCompletionError(err)).toBe(true)
25
+ expect(isClosedBeforeCompletionError(call.isClosed)).toBe(true)
26
+ })
27
+
28
+ it('ends a call cleanly when the remote completes it before the transport ends', async () => {
29
+ const call = new ClientRPC('svc', 'method')
30
+ await call.sink(
31
+ packetSource(
32
+ callDataPacket(new Uint8Array([1])),
33
+ callDataPacket(undefined, true),
34
+ ),
35
+ )
36
+
37
+ const received = new Array<number>()
38
+ for await (const data of call.rpcDataSource) {
39
+ received.push(data[0])
40
+ }
41
+
42
+ expect(received).toEqual([1])
43
+ expect(call.isClosed).toBe(false)
44
+ })
45
+ })
46
+
47
+ // callDataPacket builds an incoming call-data packet.
48
+ function callDataPacket(data?: Uint8Array, complete = false): Packet {
49
+ return Packet.create({
50
+ body: {
51
+ case: 'callData',
52
+ value: {
53
+ data: data ?? new Uint8Array(0),
54
+ dataIsZero: false,
55
+ complete,
56
+ error: '',
57
+ },
58
+ },
59
+ })
60
+ }
61
+
62
+ // packetSource yields packets and then ends like a closed transport.
63
+ async function* packetSource(...packets: Packet[]): AsyncGenerator<Packet> {
64
+ for (const packet of packets) {
65
+ yield packet
66
+ }
67
+ }
@@ -1,6 +1,7 @@
1
1
  import type { CompleteMessage } from '@aptre/protobuf-es-lite'
2
2
  import type { CallStart } from './rpcproto.pb.js'
3
3
  import { CommonRPC } from './common-rpc.js'
4
+ import { ERR_CLOSED_BEFORE_COMPLETION } from './errors.js'
4
5
 
5
6
  // ClientRPC is an ongoing RPC from the client side.
6
7
  export class ClientRPC extends CommonRPC {
@@ -37,4 +38,13 @@ export class ClientRPC extends CommonRPC {
37
38
  `unexpected server to client rpc: ${packet.rpcService || '<empty>'}/${packet.rpcMethod || '<empty>'}`,
38
39
  )
39
40
  }
41
+
42
+ // handleSourceEnd fails the call when the transport ends before the remote
43
+ // sent a completion or an error, so a truncated stream does not read as a
44
+ // complete one.
45
+ protected override async handleSourceEnd() {
46
+ if (!this.isRemoteCompleted) {
47
+ await this.close(new Error(ERR_CLOSED_BEFORE_COMPLETION))
48
+ }
49
+ }
40
50
  }
@@ -0,0 +1,76 @@
1
+ package srpc
2
+
3
+ import (
4
+ "context"
5
+ "io"
6
+ "testing"
7
+ )
8
+
9
+ // callbackPacketWriter exercises synchronous transport callbacks.
10
+ type callbackPacketWriter struct {
11
+ // write delivers the outgoing packet before returning.
12
+ write func(*Packet) error
13
+ // close interrupts pending writes.
14
+ close func() error
15
+ }
16
+
17
+ // WritePacket invokes the test's synchronous transport.
18
+ func (w *callbackPacketWriter) WritePacket(packet *Packet) error { return w.write(packet) }
19
+
20
+ // Close interrupts the test transport.
21
+ func (w *callbackPacketWriter) Close() error { return w.close() }
22
+
23
+ func TestClientRPCSynchronousTransport(t *testing.T) {
24
+ // A transport may deliver its reply before the request write returns.
25
+ rpc := NewClientRPC(t.Context(), "service", "method")
26
+ writer := &callbackPacketWriter{
27
+ write: func(packet *Packet) error {
28
+ return rpc.HandleCallData(&CallData{Data: []byte("reply"), Complete: true})
29
+ },
30
+ close: func() error {
31
+ rpc.HandleStreamClose(io.EOF)
32
+ return nil
33
+ },
34
+ }
35
+ if err := rpc.Start(writer, false, nil); err != nil {
36
+ t.Fatal(err)
37
+ }
38
+ t.Cleanup(rpc.Close)
39
+
40
+ // Both startup and cleanup callbacks must run outside the state lock.
41
+ data, err := rpc.ReadOne()
42
+ if err != nil {
43
+ t.Fatal(err)
44
+ }
45
+ if string(data) != "reply" {
46
+ t.Fatalf("unexpected response %q", data)
47
+ }
48
+ }
49
+
50
+ func TestClientRPCCancelBlockedStart(t *testing.T) {
51
+ // Cancel while CallStart is blocked on a peer that never reads.
52
+ ctx, cancel := context.WithCancel(t.Context())
53
+ t.Cleanup(cancel)
54
+ rpc := NewClientRPC(ctx, "service", "method")
55
+ entered, closed := make(chan struct{}), make(chan struct{})
56
+ writer := &callbackPacketWriter{
57
+ write: func(*Packet) error {
58
+ close(entered)
59
+ <-closed
60
+ return io.ErrClosedPipe
61
+ },
62
+ close: func() error {
63
+ close(closed)
64
+ return nil
65
+ },
66
+ }
67
+ done := make(chan error, 1)
68
+ go func() { done <- rpc.Start(writer, false, nil) }()
69
+ <-entered
70
+ cancel()
71
+ if err := <-done; err != io.ErrClosedPipe {
72
+ t.Fatalf("blocked start returned %v", err)
73
+ }
74
+ }
75
+
76
+ var _ PacketWriter = (*callbackPacketWriter)(nil)
package/srpc/client.cpp CHANGED
@@ -3,99 +3,111 @@
3
3
  #include "client.hpp"
4
4
 
5
5
  namespace starpc {
6
+ namespace {
7
+
8
+ /*
9
+ * ClientStream is the Stream over one opened call. The transport and the
10
+ * stream share the ClientRPC; destroying the writer joins the transport's
11
+ * readers, so the call outlives every callback.
12
+ */
13
+ class ClientStream final : public Stream {
14
+ public:
15
+ ClientStream(std::shared_ptr<ClientRPC> rpc,
16
+ std::unique_ptr<PacketWriter> writer)
17
+ : rpc(std::move(rpc)),
18
+ writer(std::move(writer)),
19
+ messages(
20
+ this->rpc.get(), [this] { this->rpc->Close(); },
21
+ this->rpc->StopToken())
22
+ {
23
+ }
24
+ ~ClientStream() override
25
+ {
26
+ Close();
27
+ }
28
+
29
+ Error MsgSend(const Message &message) override
30
+ {
31
+ return messages.MsgSend(message);
32
+ }
33
+ Error MsgRecv(Message *message) override
34
+ {
35
+ return messages.MsgRecv(message);
36
+ }
37
+ Error CloseSend() override
38
+ {
39
+ return messages.CloseSend();
40
+ }
41
+ std::stop_token StopToken() const override
42
+ {
43
+ return rpc->StopToken();
44
+ }
45
+ std::string RemoteErrorMessage() const override
46
+ {
47
+ return rpc->RemoteErrorMessage();
48
+ }
49
+ Error Close() override
50
+ {
51
+ rpc->Close();
52
+ return Error::OK;
53
+ }
54
+
55
+ private:
56
+ /* rpc is shared with the transport callbacks. */
57
+ std::shared_ptr<ClientRPC> rpc;
58
+ std::unique_ptr<PacketWriter> writer;
59
+ MsgStream messages;
60
+ };
61
+
62
+ } // namespace
6
63
 
7
64
  Error ClientImpl::ExecCall(const std::string &service,
8
- const std::string &method, const Message &in,
9
- Message *out) {
10
- std::string first_msg;
11
- if (!in.SerializeToString(&first_msg)) {
12
- return Error::InvalidMessage;
13
- }
14
-
15
- auto client_rpc = NewClientRPC(service, method);
16
-
17
- // Open the stream with handlers
18
- auto [writer, err] = open_stream_(
19
- [&client_rpc](const std::string &data) -> Error {
20
- return client_rpc->HandlePacketData(data);
21
- },
22
- [&client_rpc](Error close_err) {
23
- client_rpc->HandleStreamClose(close_err);
24
- });
25
-
26
- if (err != Error::OK) {
27
- return err;
28
- }
29
-
30
- err = client_rpc->Start(writer.get(), true, first_msg);
31
- if (err != Error::OK) {
32
- client_rpc->Close();
33
- return err;
34
- }
35
-
36
- std::string msg;
37
- err = client_rpc->ReadOne(&msg);
38
- if (err != Error::OK) {
39
- client_rpc->Close();
40
- return err;
41
- }
42
-
43
- if (!out->ParseFromString(msg)) {
44
- client_rpc->Close();
45
- return Error::InvalidMessage;
46
- }
47
-
48
- client_rpc->Close();
49
- return Error::OK;
65
+ const std::string &method, const Message &in,
66
+ Message *out)
67
+ {
68
+ auto [stream, err] = NewStream(service, method, &in);
69
+ if (err != Error::OK)
70
+ return err;
71
+ return stream->MsgRecv(out);
50
72
  }
51
73
 
52
74
  std::pair<std::unique_ptr<Stream>, Error>
53
75
  ClientImpl::NewStream(const std::string &service, const std::string &method,
54
- const Message *first_msg) {
55
- std::string first_msg_data;
56
- if (first_msg != nullptr) {
57
- if (!first_msg->SerializeToString(&first_msg_data)) {
58
- return {nullptr, Error::InvalidMessage};
59
- }
60
- }
61
-
62
- auto client_rpc = std::make_shared<ClientRPC>(service, method);
63
-
64
- // Open the stream with handlers
65
- auto [writer, err] = open_stream_(
66
- [client_rpc](const std::string &data) -> Error {
67
- return client_rpc->HandlePacketData(data);
68
- },
69
- [client_rpc](Error close_err) {
70
- client_rpc->HandleStreamClose(close_err);
71
- });
72
-
73
- if (err != Error::OK) {
74
- return {nullptr, err};
75
- }
76
-
77
- err = client_rpc->Start(writer.get(), first_msg != nullptr, first_msg_data);
78
- if (err != Error::OK) {
79
- return {nullptr, err};
80
- }
81
-
82
- // Create MsgStream with close callback
83
- // Note: We need to capture writer to extend its lifetime
84
- auto writer_ptr = writer.release();
85
- auto stream =
86
- std::make_unique<MsgStream>(client_rpc.get(), [client_rpc, writer_ptr]() {
87
- client_rpc->Cancel();
88
- if (writer_ptr) {
89
- writer_ptr->Close();
90
- delete writer_ptr;
91
- }
92
- });
93
-
94
- // We need to keep client_rpc alive - in a real implementation
95
- // we'd use a mechanism to tie its lifetime to the stream.
96
- // For now, the shared_ptr in the lambda captures keeps it alive.
97
-
98
- return {std::move(stream), Error::OK};
76
+ const Message *first_msg)
77
+ {
78
+ if (service.empty())
79
+ return {nullptr, Error::EmptyServiceID};
80
+ if (method.empty())
81
+ return {nullptr, Error::EmptyMethodID};
82
+
83
+ std::string first_data;
84
+ if (first_msg != nullptr && !first_msg->SerializeToString(&first_data))
85
+ return {nullptr, Error::InvalidMessage};
86
+
87
+ /*
88
+ * Transport callbacks share the call, without referring to this stack
89
+ * frame.
90
+ */
91
+ auto rpc = std::make_shared<ClientRPC>(service, method);
92
+ auto [writer, err] = open_stream(
93
+ [rpc](const std::string &data) {
94
+ return rpc->HandlePacketData(data);
95
+ },
96
+ [rpc](Error closed) { rpc->HandleStreamClose(closed); });
97
+ if (err != Error::OK) {
98
+ if (writer)
99
+ (void)writer->Close();
100
+ return {nullptr, err};
101
+ }
102
+
103
+ err = rpc->Start(writer.get(), first_msg != nullptr, first_data);
104
+ if (err != Error::OK) {
105
+ rpc->Close();
106
+ return {nullptr, err};
107
+ }
108
+ return {std::make_unique<ClientStream>(std::move(rpc),
109
+ std::move(writer)),
110
+ Error::OK};
99
111
  }
100
112
 
101
113
  } // namespace starpc
package/srpc/client.hpp CHANGED
@@ -1,10 +1,5 @@
1
1
  #pragma once
2
2
 
3
- #include <functional>
4
- #include <memory>
5
- #include <string>
6
- #include <utility>
7
-
8
3
  #include "client-rpc.hpp"
9
4
  #include "errors.hpp"
10
5
  #include "message.hpp"
@@ -13,54 +8,70 @@
13
8
  #include "stream.hpp"
14
9
  #include "writer.hpp"
15
10
 
11
+ #include <functional>
12
+ #include <memory>
13
+ #include <string>
14
+ #include <utility>
15
+
16
16
  namespace starpc {
17
17
 
18
- // OpenStreamFunc opens a stream with a remote.
19
- // msgHandler must not be called concurrently.
20
- // Matches Go OpenStreamFunc in client.go
18
+ /*
19
+ * OpenStreamFunc opens one stream over the transport. It returns the writer
20
+ * the caller writes packets through; msg_handler and close_handler must not
21
+ * be called concurrently. Matches Go OpenStreamFunc in client.go.
22
+ */
21
23
  using OpenStreamFunc =
22
- std::function<std::pair<std::unique_ptr<PacketWriter>, Error>(
23
- PacketDataHandler msg_handler, CloseHandler close_handler)>;
24
+ std::function<std::pair<std::unique_ptr<PacketWriter>, Error>(
25
+ PacketDataHandler msg_handler, CloseHandler close_handler)>;
24
26
 
25
- // Client implements a SRPC client which can initiate RPC streams.
26
- // Matches Go Client interface in client.go
27
+ /*
28
+ * Client starts RPC streams over a transport. Matches the Go Client
29
+ * interface in client.go.
30
+ */
27
31
  class Client {
28
32
  public:
29
- virtual ~Client() = default;
33
+ virtual ~Client() = default;
30
34
 
31
- // ExecCall executes a request/reply RPC with the remote.
32
- virtual Error ExecCall(const std::string &service, const std::string &method,
33
- const Message &in, Message *out) = 0;
35
+ /* ExecCall runs one request/reply RPC and parses the reply into out. */
36
+ virtual Error ExecCall(const std::string &service,
37
+ const std::string &method, const Message &in,
38
+ Message *out) = 0;
34
39
 
35
- // NewStream starts a streaming RPC with the remote & returns the stream.
36
- // firstMsg is optional (can be nullptr).
37
- virtual std::pair<std::unique_ptr<Stream>, Error>
38
- NewStream(const std::string &service, const std::string &method,
39
- const Message *first_msg) = 0;
40
+ /*
41
+ * NewStream opens a streaming RPC and returns the stream. first_msg,
42
+ * when set, is sent as the call's first message.
43
+ */
44
+ virtual std::pair<std::unique_ptr<Stream>, Error>
45
+ NewStream(const std::string &service, const std::string &method,
46
+ const Message *first_msg) = 0;
40
47
  };
41
48
 
42
- // ClientImpl is the default implementation of Client with a transport.
43
- // Matches Go client struct in client.go
49
+ /*
50
+ * ClientImpl is the default Client over an OpenStreamFunc transport. Matches
51
+ * the Go client struct in client.go.
52
+ */
44
53
  class ClientImpl : public Client {
45
54
  public:
46
- explicit ClientImpl(OpenStreamFunc open_stream)
47
- : open_stream_(std::move(open_stream)) {}
55
+ explicit ClientImpl(OpenStreamFunc open_stream)
56
+ : open_stream(std::move(open_stream))
57
+ {
58
+ }
48
59
 
49
- Error ExecCall(const std::string &service, const std::string &method,
50
- const Message &in, Message *out) override;
60
+ Error ExecCall(const std::string &service, const std::string &method,
61
+ const Message &in, Message *out) override;
51
62
 
52
- std::pair<std::unique_ptr<Stream>, Error>
53
- NewStream(const std::string &service, const std::string &method,
54
- const Message *first_msg) override;
63
+ std::pair<std::unique_ptr<Stream>, Error>
64
+ NewStream(const std::string &service, const std::string &method,
65
+ const Message *first_msg) override;
55
66
 
56
67
  private:
57
- OpenStreamFunc open_stream_;
68
+ OpenStreamFunc open_stream;
58
69
  };
59
70
 
60
- // NewClient constructs a client with a OpenStreamFunc.
61
- // Matches Go NewClient function in client.go
62
- inline std::unique_ptr<Client> NewClient(OpenStreamFunc open_stream) {
63
- return std::make_unique<ClientImpl>(std::move(open_stream));
71
+ /* NewClient constructs a Client over open_stream. */
72
+ inline std::unique_ptr<Client> NewClient(OpenStreamFunc open_stream)
73
+ {
74
+ return std::make_unique<ClientImpl>(std::move(open_stream));
64
75
  }
65
76
 
66
77
  } // namespace starpc