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.
- package/LICENSE +19 -0
- package/Makefile +140 -0
- package/README.md +128 -26
- package/dist/echo/echo.d.ts +59 -0
- package/dist/echo/echo.js +85 -0
- package/dist/srpc/broadcast-channel.d.ts +16 -0
- package/dist/srpc/broadcast-channel.js +60 -0
- package/dist/srpc/client-rpc.d.ts +31 -0
- package/dist/srpc/client-rpc.js +176 -0
- package/dist/srpc/client.d.ts +12 -0
- package/dist/srpc/client.js +129 -0
- package/dist/srpc/conn.d.ts +14 -0
- package/dist/srpc/conn.js +38 -0
- package/dist/srpc/index.d.ts +3 -0
- package/dist/srpc/index.js +2 -0
- package/dist/srpc/packet.d.ts +9 -0
- package/dist/srpc/packet.js +89 -0
- package/dist/srpc/rpcproto.d.ts +194 -0
- package/dist/srpc/rpcproto.js +322 -0
- package/dist/srpc/stream.d.ts +5 -0
- package/dist/srpc/stream.js +1 -0
- package/dist/srpc/ts-proto-rpc.d.ts +7 -0
- package/dist/srpc/ts-proto-rpc.js +1 -0
- package/dist/srpc/websocket.d.ts +7 -0
- package/dist/srpc/websocket.js +18 -0
- package/e2e/e2e.go +1 -0
- package/e2e/e2e_test.go +158 -0
- package/echo/echo.go +1 -0
- package/echo/echo.pb.go +165 -0
- package/echo/echo.proto +19 -0
- package/echo/echo.ts +191 -0
- package/echo/echo_srpc.pb.go +333 -0
- package/echo/echo_vtproto.pb.go +271 -0
- package/echo/server.go +73 -0
- package/go.mod +50 -0
- package/go.sum +210 -0
- package/integration/integration.bash +25 -0
- package/integration/integration.go +30 -0
- package/integration/integration.ts +54 -0
- package/integration/tsconfig.json +11 -0
- package/package.json +77 -9
- package/patches/@libp2p+mplex+1.2.1.patch +22 -0
- package/srpc/broadcast-channel.ts +72 -0
- package/srpc/client-rpc.go +163 -0
- package/srpc/client-rpc.ts +197 -0
- package/srpc/client.go +96 -0
- package/srpc/client.ts +182 -0
- package/srpc/conn.go +7 -0
- package/srpc/conn.ts +49 -0
- package/srpc/errors.go +20 -0
- package/srpc/handler.go +13 -0
- package/srpc/index.ts +3 -0
- package/srpc/message.go +7 -0
- package/srpc/mux.go +76 -0
- package/srpc/packet-rw.go +102 -0
- package/srpc/packet.go +71 -0
- package/srpc/packet.ts +105 -0
- package/srpc/rpc-stream.go +76 -0
- package/srpc/rpcproto.pb.go +455 -0
- package/srpc/rpcproto.proto +46 -0
- package/srpc/rpcproto.ts +467 -0
- package/srpc/rpcproto_vtproto.pb.go +1094 -0
- package/srpc/server-http.go +66 -0
- package/srpc/server-pipe.go +26 -0
- package/srpc/server-rpc.go +160 -0
- package/srpc/server.go +29 -0
- package/srpc/stream-pipe.go +86 -0
- package/srpc/stream.go +24 -0
- package/srpc/stream.ts +11 -0
- package/srpc/ts-proto-rpc.ts +29 -0
- package/srpc/websocket.go +68 -0
- package/srpc/websocket.ts +22 -0
- 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