starpc 0.0.1 → 0.1.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 (73) hide show
  1. package/LICENSE +19 -0
  2. package/Makefile +140 -0
  3. package/README.md +128 -26
  4. package/dist/echo/echo.d.ts +59 -0
  5. package/dist/echo/echo.js +85 -0
  6. package/dist/srpc/broadcast-channel.d.ts +16 -0
  7. package/dist/srpc/broadcast-channel.js +60 -0
  8. package/dist/srpc/client-rpc.d.ts +31 -0
  9. package/dist/srpc/client-rpc.js +176 -0
  10. package/dist/srpc/client.d.ts +12 -0
  11. package/dist/srpc/client.js +129 -0
  12. package/dist/srpc/conn.d.ts +14 -0
  13. package/dist/srpc/conn.js +38 -0
  14. package/dist/srpc/index.d.ts +3 -0
  15. package/dist/srpc/index.js +2 -0
  16. package/dist/srpc/packet.d.ts +9 -0
  17. package/dist/srpc/packet.js +89 -0
  18. package/dist/srpc/rpcproto.d.ts +194 -0
  19. package/dist/srpc/rpcproto.js +322 -0
  20. package/dist/srpc/stream.d.ts +5 -0
  21. package/dist/srpc/stream.js +1 -0
  22. package/dist/srpc/ts-proto-rpc.d.ts +7 -0
  23. package/dist/srpc/ts-proto-rpc.js +1 -0
  24. package/dist/srpc/websocket.d.ts +7 -0
  25. package/dist/srpc/websocket.js +18 -0
  26. package/e2e/e2e.go +1 -0
  27. package/e2e/e2e_test.go +158 -0
  28. package/echo/echo.go +1 -0
  29. package/echo/echo.pb.go +165 -0
  30. package/echo/echo.proto +19 -0
  31. package/echo/echo.ts +191 -0
  32. package/echo/echo_srpc.pb.go +333 -0
  33. package/echo/echo_vtproto.pb.go +271 -0
  34. package/echo/server.go +73 -0
  35. package/go.mod +50 -0
  36. package/go.sum +210 -0
  37. package/integration/integration.bash +25 -0
  38. package/integration/integration.go +30 -0
  39. package/integration/integration.ts +54 -0
  40. package/integration/tsconfig.json +11 -0
  41. package/package.json +77 -9
  42. package/patches/@libp2p+mplex+1.2.1.patch +22 -0
  43. package/srpc/broadcast-channel.ts +72 -0
  44. package/srpc/client-rpc.go +163 -0
  45. package/srpc/client-rpc.ts +197 -0
  46. package/srpc/client.go +96 -0
  47. package/srpc/client.ts +182 -0
  48. package/srpc/conn.go +7 -0
  49. package/srpc/conn.ts +49 -0
  50. package/srpc/errors.go +20 -0
  51. package/srpc/handler.go +13 -0
  52. package/srpc/index.ts +3 -0
  53. package/srpc/message.go +7 -0
  54. package/srpc/mux.go +76 -0
  55. package/srpc/packet-rw.go +102 -0
  56. package/srpc/packet.go +71 -0
  57. package/srpc/packet.ts +105 -0
  58. package/srpc/rpc-stream.go +76 -0
  59. package/srpc/rpcproto.pb.go +455 -0
  60. package/srpc/rpcproto.proto +46 -0
  61. package/srpc/rpcproto.ts +467 -0
  62. package/srpc/rpcproto_vtproto.pb.go +1094 -0
  63. package/srpc/server-http.go +66 -0
  64. package/srpc/server-pipe.go +26 -0
  65. package/srpc/server-rpc.go +160 -0
  66. package/srpc/server.go +29 -0
  67. package/srpc/stream-pipe.go +86 -0
  68. package/srpc/stream.go +24 -0
  69. package/srpc/stream.ts +11 -0
  70. package/srpc/ts-proto-rpc.ts +29 -0
  71. package/srpc/websocket.go +68 -0
  72. package/srpc/websocket.ts +22 -0
  73. package/srpc/writer.go +9 -0
@@ -0,0 +1,66 @@
1
+ package srpc
2
+
3
+ import (
4
+ "context"
5
+ "io"
6
+ "net/http"
7
+
8
+ "nhooyr.io/websocket"
9
+ )
10
+
11
+ // HTTPServer implements the SRPC server.
12
+ type HTTPServer struct {
13
+ mux Mux
14
+ srpc *Server
15
+ path string
16
+ }
17
+
18
+ // NewHTTPServer builds a http server / handler.
19
+ // if path is empty, serves on all routes.
20
+ func NewHTTPServer(mux Mux, path string) (*HTTPServer, error) {
21
+ return &HTTPServer{
22
+ mux: mux,
23
+ srpc: NewServer(mux),
24
+ path: path,
25
+ }, nil
26
+ }
27
+
28
+ func (s *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
29
+ if s.path != "" && r.URL.Path != s.path {
30
+ return
31
+ }
32
+
33
+ c, err := websocket.Accept(w, r, &websocket.AcceptOptions{})
34
+ if err != nil {
35
+ // TODO: handle / log error?
36
+ _ = err
37
+ return
38
+ }
39
+ defer c.Close(websocket.StatusInternalError, "closed")
40
+
41
+ ctx := r.Context()
42
+ wsConn, err := NewWebSocketConn(ctx, c, true)
43
+ if err != nil {
44
+ // TODO: handle / log error?
45
+ c.Close(websocket.StatusInternalError, err.Error())
46
+ return
47
+ }
48
+
49
+ // handle incoming streams
50
+ for {
51
+ strm, err := wsConn.AcceptStream()
52
+ if err != nil {
53
+ if err != io.EOF && err != context.Canceled {
54
+ // TODO: handle / log error?
55
+ c.Close(websocket.StatusInternalError, err.Error())
56
+ }
57
+ return
58
+ }
59
+ go func() {
60
+ err := s.srpc.HandleConn(ctx, strm)
61
+ _ = err
62
+ // TODO: handle / log error?
63
+ // err != nil && err != io.EOF && err != context.Canceled
64
+ }()
65
+ }
66
+ }
@@ -0,0 +1,26 @@
1
+ package srpc
2
+
3
+ import (
4
+ "context"
5
+ "net"
6
+ )
7
+
8
+ // NewServerPipe constructs a open stream func which creates an in-memory Pipe
9
+ // Stream with the given Server. Starts read pumps for both. Starts the
10
+ // HandleConn function on the server in a separate goroutine.
11
+ func NewServerPipe(server *Server) OpenStreamFunc {
12
+ return func(ctx context.Context, msgHandler func(pkt *Packet) error) (Writer, error) {
13
+ srvPipe, clientPipe := net.Pipe()
14
+ go func() {
15
+ _ = server.HandleConn(ctx, srvPipe)
16
+ }()
17
+ clientPrw := NewPacketReadWriter(clientPipe, msgHandler)
18
+ go func() {
19
+ err := clientPrw.ReadPump()
20
+ if err != nil {
21
+ _ = clientPrw.Close()
22
+ }
23
+ }()
24
+ return clientPrw, nil
25
+ }
26
+ }
@@ -0,0 +1,160 @@
1
+ package srpc
2
+
3
+ import (
4
+ "context"
5
+
6
+ "github.com/pkg/errors"
7
+ )
8
+
9
+ // ServerRPC represents the server side of an on-going RPC call message stream.
10
+ // Not concurrency safe: use a mutex if calling concurrently.
11
+ type ServerRPC struct {
12
+ // ctx is the context, canceled when the rpc ends.
13
+ ctx context.Context
14
+ // ctxCancel is called when the rpc ends.
15
+ ctxCancel context.CancelFunc
16
+ // writer is the writer to write messages to
17
+ writer Writer
18
+ // mux is the mux to handle calls
19
+ mux Mux
20
+ // service is the rpc service
21
+ service string
22
+ // method is the rpc method
23
+ method string
24
+ // dataCh contains queued data packets.
25
+ // closed when the client closes the channel.
26
+ dataCh chan []byte
27
+ // dataChClosed is a flag set after dataCh is closed.
28
+ // controlled by HandlePacket.
29
+ dataChClosed bool
30
+ // clientErr is an error set by the client.
31
+ // before dataCh is closed, managed by HandlePacket.
32
+ // immutable after dataCh is closed.
33
+ clientErr error
34
+ }
35
+
36
+ // NewServerRPC constructs a new ServerRPC session.
37
+ // note: call SetWriter before handling any incoming messages.
38
+ func NewServerRPC(ctx context.Context, mux Mux) *ServerRPC {
39
+ rpc := &ServerRPC{
40
+ dataCh: make(chan []byte, 5),
41
+ mux: mux,
42
+ }
43
+ rpc.ctx, rpc.ctxCancel = context.WithCancel(ctx)
44
+ return rpc
45
+ }
46
+
47
+ // SetWriter sets the writer field.
48
+ func (r *ServerRPC) SetWriter(w Writer) {
49
+ r.writer = w
50
+ }
51
+
52
+ // Context is canceled when the ServerRPC is no longer valid.
53
+ func (r *ServerRPC) Context() context.Context {
54
+ return r.ctx
55
+ }
56
+
57
+ // HandlePacket handles an incoming parsed message packet.
58
+ // Not concurrency safe: use a mutex if calling concurrently.
59
+ func (r *ServerRPC) HandlePacket(msg *Packet) error {
60
+ if err := msg.Validate(); err != nil {
61
+ return err
62
+ }
63
+
64
+ switch b := msg.GetBody().(type) {
65
+ case *Packet_CallStart:
66
+ return r.HandleCallStart(b.CallStart)
67
+ case *Packet_CallData:
68
+ return r.HandleCallData(b.CallData)
69
+ case *Packet_CallStartResp:
70
+ return r.HandleCallStartResp(b.CallStartResp)
71
+ default:
72
+ return nil
73
+ }
74
+ }
75
+
76
+ // HandleCallStart handles the call start packet.
77
+ func (r *ServerRPC) HandleCallStart(pkt *CallStart) error {
78
+ // process start: method and service
79
+ if r.method != "" || r.service != "" {
80
+ return errors.New("call start must be sent only once")
81
+ }
82
+ if r.dataChClosed {
83
+ return ErrCompleted
84
+ }
85
+ r.method, r.service = pkt.GetRpcMethod(), pkt.GetRpcService()
86
+
87
+ // process first data packet, if included
88
+ if data := pkt.GetData(); len(data) != 0 {
89
+ select {
90
+ case r.dataCh <- data:
91
+ default:
92
+ // the channel should be empty w/ a buffer capacity of 5 here.
93
+ return errors.New("data channel was full, expected empty")
94
+ }
95
+ }
96
+
97
+ // invoke the rpc
98
+ go r.invokeRPC()
99
+
100
+ return nil
101
+ }
102
+
103
+ // HandleCallData handles the call data packet.
104
+ func (r *ServerRPC) HandleCallData(pkt *CallData) error {
105
+ if r.dataChClosed {
106
+ return ErrCompleted
107
+ }
108
+
109
+ if data := pkt.GetData(); len(data) != 0 {
110
+ select {
111
+ case <-r.ctx.Done():
112
+ return context.Canceled
113
+ case r.dataCh <- data:
114
+ }
115
+ }
116
+
117
+ complete := pkt.GetComplete()
118
+ if err := pkt.GetError(); len(err) != 0 {
119
+ complete = true
120
+ r.clientErr = errors.New(err)
121
+ }
122
+
123
+ if complete {
124
+ r.dataChClosed = true
125
+ close(r.dataCh)
126
+ }
127
+
128
+ return nil
129
+ }
130
+
131
+ // HandleCallStartResp handles the CallStartResp packet.
132
+ func (r *ServerRPC) HandleCallStartResp(resp *CallStartResp) error {
133
+ // client-side calls not supported
134
+ return errors.Wrap(ErrUnrecognizedPacket, "call start resp packet unexpected")
135
+ }
136
+
137
+ // invoke invokes the RPC after CallStart is received.
138
+ func (r *ServerRPC) invokeRPC() {
139
+ // ctx := r.ctx
140
+ serviceID, methodID := r.service, r.method
141
+ strm := NewRPCStream(r.ctx, r.writer, r.dataCh)
142
+ ok, err := r.mux.InvokeMethod(serviceID, methodID, strm)
143
+ if err == nil && !ok {
144
+ err = ErrUnimplemented
145
+ }
146
+ outPkt := NewCallDataPacket(nil, true, err)
147
+ _ = r.writer.WritePacket(outPkt)
148
+ r.ctxCancel()
149
+ _ = r.writer.Close()
150
+ }
151
+
152
+ // Close releases any resources held by the ServerRPC.
153
+ // not concurrency safe with HandlePacket.
154
+ func (r *ServerRPC) Close() {
155
+ r.ctxCancel()
156
+ if r.service == "" {
157
+ // invokeRPC has not been called
158
+ _ = r.writer.Close()
159
+ }
160
+ }
package/srpc/server.go ADDED
@@ -0,0 +1,29 @@
1
+ package srpc
2
+
3
+ import (
4
+ "context"
5
+ "io"
6
+ )
7
+
8
+ // Server handles incoming RPC streams with a mux.
9
+ type Server struct {
10
+ // mux is the srpc mux
11
+ mux Mux
12
+ }
13
+
14
+ // NewServer constructs a new SRPC server.
15
+ func NewServer(mux Mux) *Server {
16
+ return &Server{
17
+ mux: mux,
18
+ }
19
+ }
20
+
21
+ // HandleConn handles an incoming ReadWriteCloser.
22
+ func (s *Server) HandleConn(ctx context.Context, rwc io.ReadWriteCloser) error {
23
+ subCtx, subCtxCancel := context.WithCancel(ctx)
24
+ defer subCtxCancel()
25
+ serverRPC := NewServerRPC(subCtx, s.mux)
26
+ prw := NewPacketReadWriter(rwc, serverRPC.HandlePacket)
27
+ serverRPC.SetWriter(prw)
28
+ return prw.ReadPump()
29
+ }
@@ -0,0 +1,86 @@
1
+ package srpc
2
+
3
+ import (
4
+ "context"
5
+ "io"
6
+ "sync"
7
+ )
8
+
9
+ // pipeStream implements an in-memory stream.
10
+ // intended for testing
11
+ type pipeStream struct {
12
+ ctx context.Context
13
+ ctxCancel context.CancelFunc
14
+ // other is the other end of the stream.
15
+ other *pipeStream
16
+ // closeOnce ensures we close only once.
17
+ closeOnce sync.Once
18
+ // dataCh is the data channel
19
+ dataCh chan []byte
20
+ }
21
+
22
+ // NewPipeStream constructs a new in-memory stream.
23
+ func NewPipeStream(ctx context.Context) (Stream, Stream) {
24
+ s1 := &pipeStream{dataCh: make(chan []byte, 5)}
25
+ s1.ctx, s1.ctxCancel = context.WithCancel(ctx)
26
+ s2 := &pipeStream{other: s1, dataCh: make(chan []byte, 5)}
27
+ s2.ctx, s2.ctxCancel = context.WithCancel(ctx)
28
+ s1.other = s2
29
+ return s1, s2
30
+ }
31
+
32
+ // Context is canceled when the Stream is no longer valid.
33
+ func (p *pipeStream) Context() context.Context {
34
+ return p.ctx
35
+ }
36
+
37
+ // MsgSend sends the message to the remote.
38
+ func (p *pipeStream) MsgSend(msg Message) error {
39
+ data, err := msg.MarshalVT()
40
+ if err != nil {
41
+ return err
42
+ }
43
+ select {
44
+ case <-p.ctx.Done():
45
+ return context.Canceled
46
+ case p.other.dataCh <- data:
47
+ return nil
48
+ }
49
+ }
50
+
51
+ // MsgRecv receives an incoming message from the remote.
52
+ // Parses the message into the object at msg.
53
+ func (p *pipeStream) MsgRecv(msg Message) error {
54
+ select {
55
+ case <-p.ctx.Done():
56
+ return context.Canceled
57
+ case data, ok := <-p.dataCh:
58
+ if !ok {
59
+ return io.EOF
60
+ }
61
+ return msg.UnmarshalVT(data)
62
+ }
63
+ }
64
+
65
+ // CloseSend signals to the remote that we will no longer send any messages.
66
+ func (p *pipeStream) CloseSend() error {
67
+ p.closeRemote()
68
+ return nil
69
+ }
70
+
71
+ // Close closes the stream.
72
+ func (p *pipeStream) Close() error {
73
+ p.ctxCancel()
74
+ p.closeRemote()
75
+ return nil
76
+ }
77
+
78
+ // closeRemote closes the remote data channel.
79
+ func (p *pipeStream) closeRemote() {
80
+ p.closeOnce.Do(func() {
81
+ close(p.other.dataCh)
82
+ })
83
+ }
84
+
85
+ // _ is a type assertion
86
+ var _ Stream = ((*pipeStream)(nil))
package/srpc/stream.go ADDED
@@ -0,0 +1,24 @@
1
+ package srpc
2
+
3
+ import (
4
+ "context"
5
+ )
6
+
7
+ // Stream is a handle to an on-going bi-directional or one-directional stream RPC handle.
8
+ type Stream interface {
9
+ // Context is canceled when the Stream is no longer valid.
10
+ Context() context.Context
11
+
12
+ // MsgSend sends the message to the remote.
13
+ MsgSend(msg Message) error
14
+
15
+ // MsgRecv receives an incoming message from the remote.
16
+ // Parses the message into the object at msg.
17
+ MsgRecv(msg Message) error
18
+
19
+ // CloseSend signals to the remote that we will no longer send any messages.
20
+ CloseSend() error
21
+
22
+ // Close closes the stream.
23
+ Close() error
24
+ }
package/srpc/stream.ts ADDED
@@ -0,0 +1,11 @@
1
+ import type { Packet } from './rpcproto'
2
+ import type { Duplex } from 'it-stream-types'
3
+
4
+ // PacketHandler handles incoming packets.
5
+ export type PacketHandler = (packet: Packet) => Promise<void>
6
+
7
+ // Stream is an open connection.
8
+ export type Stream = Duplex<Uint8Array>
9
+
10
+ // OpenStreamFunc is a function to start a new RPC by opening a Stream.
11
+ export type OpenStreamFunc = () => Promise<Stream>
@@ -0,0 +1,29 @@
1
+ import type { Observable } from 'rxjs'
2
+
3
+ // TsProtoRpc matches the Rpc interface generated by ts-proto.
4
+ export interface TsProtoRpc {
5
+ // request fires a one-off unary RPC request.
6
+ request(
7
+ service: string,
8
+ method: string,
9
+ data: Uint8Array
10
+ ): Promise<Uint8Array>
11
+ // clientStreamingRequest fires a one-way client->server streaming request.
12
+ clientStreamingRequest(
13
+ service: string,
14
+ method: string,
15
+ data: Observable<Uint8Array>
16
+ ): Promise<Uint8Array>
17
+ // serverStreamingRequest fires a one-way server->client streaming request.
18
+ serverStreamingRequest(
19
+ service: string,
20
+ method: string,
21
+ data: Uint8Array
22
+ ): Observable<Uint8Array>
23
+ // bidirectionalStreamingRequest implements a two-way streaming request.
24
+ bidirectionalStreamingRequest(
25
+ service: string,
26
+ method: string,
27
+ data: Observable<Uint8Array>
28
+ ): Observable<Uint8Array>
29
+ }
@@ -0,0 +1,68 @@
1
+ package srpc
2
+
3
+ import (
4
+ "context"
5
+ "io"
6
+
7
+ "github.com/libp2p/go-libp2p-core/network"
8
+ "github.com/libp2p/go-libp2p/p2p/muxer/mplex"
9
+ "nhooyr.io/websocket"
10
+ )
11
+
12
+ // WebSocketConn implements the p2p multiplexer over a WebSocket.
13
+ type WebSocketConn struct {
14
+ // conn is the websocket conn
15
+ conn *websocket.Conn
16
+ // mconn is the muxed conn
17
+ mconn network.MuxedConn
18
+ }
19
+
20
+ // NewWebSocketConn constructs a new WebSocket connection.
21
+ func NewWebSocketConn(ctx context.Context, conn *websocket.Conn, isServer bool) (*WebSocketConn, error) {
22
+ nc := websocket.NetConn(ctx, conn, websocket.MessageBinary)
23
+ muxedConn, err := mplex.DefaultTransport.NewConn(nc, isServer, network.NullScope)
24
+ if err != nil {
25
+ return nil, err
26
+ }
27
+ return &WebSocketConn{conn: conn, mconn: muxedConn}, nil
28
+ }
29
+
30
+ // GetWebSocket returns the web socket conn.
31
+ func (w *WebSocketConn) GetWebSocket() *websocket.Conn {
32
+ return w.conn
33
+ }
34
+
35
+ // GetOpenStreamFunc returns the OpenStream func.
36
+ func (w *WebSocketConn) GetOpenStreamFunc() OpenStreamFunc {
37
+ return w.OpenStream
38
+ }
39
+
40
+ // AcceptStream accepts an incoming stream.
41
+ func (w *WebSocketConn) AcceptStream() (io.ReadWriteCloser, error) {
42
+ return w.mconn.AcceptStream()
43
+ }
44
+
45
+ // OpenStream tries to open a stream with the remote.
46
+ func (w *WebSocketConn) OpenStream(ctx context.Context, msgHandler func(pkt *Packet) error) (Writer, error) {
47
+ muxedStream, err := w.mconn.OpenStream(ctx)
48
+ if err != nil {
49
+ return nil, err
50
+ }
51
+
52
+ rw := NewPacketReadWriter(muxedStream, msgHandler)
53
+ go func() {
54
+ err := rw.ReadPump()
55
+ if err != nil {
56
+ _ = rw.Close()
57
+ }
58
+ }()
59
+ return rw, nil
60
+ }
61
+
62
+ // Close closes the writer.
63
+ func (w *WebSocketConn) Close() error {
64
+ return w.conn.Close(websocket.StatusGoingAway, "conn closed")
65
+ }
66
+
67
+ // _ is a type assertion
68
+ var _ Conn = ((*WebSocketConn)(nil))
@@ -0,0 +1,22 @@
1
+ import { Conn } from './conn.js'
2
+ import { duplex } from 'it-ws'
3
+ import { pipe } from 'it-pipe'
4
+ import type WebSocket from 'isomorphic-ws'
5
+
6
+ // WebSocketConn implements a connection with a WebSocket.
7
+ export class WebSocketConn extends Conn {
8
+ // socket is the web socket
9
+ private socket: WebSocket
10
+
11
+ constructor(socket: WebSocket) {
12
+ super()
13
+ this.socket = socket
14
+ const socketDuplex = duplex(socket)
15
+ pipe(this.source, socketDuplex, this.sink)
16
+ }
17
+
18
+ // getSocket returns the websocket.
19
+ public getSocket(): WebSocket {
20
+ return this.socket
21
+ }
22
+ }
package/srpc/writer.go ADDED
@@ -0,0 +1,9 @@
1
+ package srpc
2
+
3
+ // Writer is the interface used to write messages to the remote.
4
+ type Writer interface {
5
+ // WritePacket writes a packet to the remote.
6
+ WritePacket(p *Packet) error
7
+ // Close closes the writer.
8
+ Close() error
9
+ }