Service + request
Juan Batiz-Benet committed
Sep 11, 2014 at 03:47 UTC
06b651c4545ae67c10ca0b26c30fd329e6b60e2d
7 files changed
+551
net/service/Makefile
new
+8
@@ -0,0 +1,8 @@
1
+
2
+all: request.pb.go
3
+
4
+request.pb.go: request.proto
5
+ protoc --gogo_out=. --proto_path=../../../../../:/usr/local/opt/protobuf/include:. $<
6
+
7
+clean:
8
+ rm request.pb.go
net/service/request.go
new
+127
@@ -0,0 +1,127 @@
1
+package service
2
+
3
+import (
4
+ crand "crypto/rand"
5
+
6
+ msg "github.com/jbenet/go-ipfs/net/message"
7
+ peer "github.com/jbenet/go-ipfs/peer"
8
+
9
+ proto "code.google.com/p/goprotobuf/proto"
10
+)
11
+
12
+const (
13
+ // IDSize is the size of the ID in bytes.
14
+ IDSize int = 4
15
+)
16
+
17
+// RequestID is a field that identifies request-response flows.
18
+type RequestID []byte
19
+
20
+// Request turns a RequestID into a Request (unsetting first bit)
21
+func (r RequestID) Request() RequestID {
22
+ if r == nil {
23
+ return nil
24
+ }
25
+ r2 := make([]byte, len(r))
26
+ copy(r2, r)
27
+ r2[0] = r[0] & 0x7F // unset first bit for request
28
+ return RequestID(r2)
29
+}
30
+
31
+// Response turns a RequestID into a Response (setting first bit)
32
+func (r RequestID) Response() RequestID {
33
+ if r == nil {
34
+ return nil
35
+ }
36
+ r2 := make([]byte, len(r))
37
+ copy(r2, r)
38
+ r2[0] = r[0] | 0x80 // set first bit for response
39
+ return RequestID(r2)
40
+}
41
+
42
+// IsRequest returns whether a RequestID identifies a request
43
+func (r RequestID) IsRequest() bool {
44
+ if r == nil {
45
+ return false
46
+ }
47
+ return !r.IsResponse()
48
+}
49
+
50
+// IsResponse returns whether a RequestID identifies a response
51
+func (r RequestID) IsResponse() bool {
52
+ if r == nil {
53
+ return false
54
+ }
55
+ return bool(r[0]&0x80 == 0x80)
56
+}
57
+
58
+// RandomRequestID creates and returns a new random request ID
59
+func RandomRequestID() (RequestID, error) {
60
+ buf := make([]byte, IDSize)
61
+ _, err := crand.Read(buf)
62
+ return RequestID(buf).Request(), err
63
+}
64
+
65
+// RequestMap is a map of Requests. the key = (peer.ID concat RequestID).
66
+type RequestMap map[string]*Request
67
+
68
+// Request objects are used to multiplex request-response flows.
69
+type Request struct {
70
+
71
+ // ID is the RequestID identifying this Request-Response Flow.
72
+ ID RequestID
73
+
74
+ // PeerID identifies the peer from whom to expect the response.
75
+ PeerID peer.ID
76
+
77
+ // Response is the channel of incoming responses.
78
+ Response chan *msg.Message
79
+}
80
+
81
+// NewRequest creates a request for given peer.ID
82
+func NewRequest(pid peer.ID) (*Request, error) {
83
+ id, err := RandomRequestID()
84
+ if err != nil {
85
+ return nil, err
86
+ }
87
+
88
+ return &Request{
89
+ ID: id,
90
+ PeerID: pid,
91
+ Response: make(chan *msg.Message, 1),
92
+ }, nil
93
+}
94
+
95
+// Key returns the RequestKey for this request. Use with maps.
96
+func (r *Request) Key() string {
97
+ return RequestKey(r.PeerID, r.ID)
98
+}
99
+
100
+// RequestKey is the peer.ID concatenated with the RequestID. Use with maps.
101
+func RequestKey(pid peer.ID, rid RequestID) string {
102
+ return string(pid) + string(rid.Request()[:])
103
+}
104
+
105
+func wrapData(data []byte, rid RequestID) ([]byte, error) {
106
+ // Marshal
107
+ pbm := new(PBRequest)
108
+ pbm.Data = data
109
+ pbm.Tag = rid
110
+ b, err := proto.Marshal(pbm)
111
+ if err != nil {
112
+ return nil, err
113
+ }
114
+
115
+ return b, nil
116
+}
117
+
118
+func unwrapData(data []byte) ([]byte, RequestID, error) {
119
+ // Unmarshal
120
+ pbm := new(PBRequest)
121
+ err := proto.Unmarshal(data, pbm)
122
+ if err != nil {
123
+ return nil, nil, err
124
+ }
125
+
126
+ return pbm.GetData(), pbm.GetTag(), nil
127
+}
net/service/request.pb.go
new
+50
@@ -0,0 +1,50 @@
1
+// Code generated by protoc-gen-gogo.
2
+// source: request.proto
3
+// DO NOT EDIT!
4
+
5
+/*
6
+Package service is a generated protocol buffer package.
7
+
8
+It is generated from these files:
9
+ request.proto
10
+
11
+It has these top-level messages:
12
+ PBRequest
13
+*/
14
+package service
15
+
16
+import proto "code.google.com/p/gogoprotobuf/proto"
17
+import json "encoding/json"
18
+import math "math"
19
+
20
+// Reference proto, json, and math imports to suppress error if they are not otherwise used.
21
+var _ = proto.Marshal
22
+var _ = &json.SyntaxError{}
23
+var _ = math.Inf
24
+
25
+type PBRequest struct {
26
+ Data []byte `protobuf:"bytes,1,req" json:"Data,omitempty"`
27
+ Tag []byte `protobuf:"bytes,3,opt" json:"Tag,omitempty"`
28
+ XXX_unrecognized []byte `json:"-"`
29
+}
30
+
31
+func (m *PBRequest) Reset() { *m = PBRequest{} }
32
+func (m *PBRequest) String() string { return proto.CompactTextString(m) }
33
+func (*PBRequest) ProtoMessage() {}
34
+
35
+func (m *PBRequest) GetData() []byte {
36
+ if m != nil {
37
+ return m.Data
38
+ }
39
+ return nil
40
+}
41
+
42
+func (m *PBRequest) GetTag() []byte {
43
+ if m != nil {
44
+ return m.Tag
45
+ }
46
+ return nil
47
+}
48
+
49
+func init() {
50
+}
net/service/request.proto
new
+6
@@ -0,0 +1,6 @@
1
+package service;
2
+
3
+message PBRequest {
4
+ required bytes Data = 1;
5
+ optional bytes Tag = 3;
6
+}
net/service/request_test.go
new
+41
@@ -0,0 +1,41 @@
1
+package service
2
+
3
+import (
4
+ "bytes"
5
+ "testing"
6
+)
7
+
8
+func TestMarshaling(t *testing.T) {
9
+
10
+ test := func(d1 []byte, rid1 RequestID) {
11
+ d2, err := wrapData(d1, rid1)
12
+ if err != nil {
13
+ t.Error(err)
14
+ }
15
+
16
+ d3, rid2, err := unwrapData(d2)
17
+ if err != nil {
18
+ t.Error(err)
19
+ }
20
+
21
+ d4, err := wrapData(d3, rid1)
22
+ if err != nil {
23
+ t.Error(err)
24
+ }
25
+
26
+ if !bytes.Equal(rid2, rid1) {
27
+ t.Error("RequestID fail")
28
+ }
29
+
30
+ if !bytes.Equal(d1, d3) {
31
+ t.Error("unmarshalled data should be the same")
32
+ }
33
+
34
+ if !bytes.Equal(d2, d4) {
35
+ t.Error("marshalled data should be the same")
36
+ }
37
+ }
38
+
39
+ test([]byte("foo"), []byte{1, 2, 3, 4})
40
+ test([]byte("bar"), nil)
41
+}
net/service/service.go
new
+194
@@ -0,0 +1,194 @@
1
+package service
2
+
3
+import (
4
+ "errors"
5
+ "sync"
6
+
7
+ msg "github.com/jbenet/go-ipfs/net/message"
8
+ u "github.com/jbenet/go-ipfs/util"
9
+
10
+ context "code.google.com/p/go.net/context"
11
+)
12
+
13
+// Handler is an interface that objects must implement in order to handle
14
+// a service's requests.
15
+type Handler interface {
16
+
17
+ // HandleMessage receives an incoming message, and potentially returns
18
+ // a response message to send back.
19
+ HandleMessage(context.Context, *msg.Message) (*msg.Message, error)
20
+}
21
+
22
+// Service is a networking component that protocols can use to multiplex
23
+// messages over the same channel, and to issue + handle requests.
24
+type Service struct {
25
+ // Handler is the object registered to handle incoming requests.
26
+ Handler Handler
27
+
28
+ // Requests are all the pending requests on this service.
29
+ Requests RequestMap
30
+ RequestsLock sync.RWMutex
31
+
32
+ // cancel is the function to stop the Service
33
+ cancel context.CancelFunc
34
+
35
+ // Message Pipe (connected to the outside world)
36
+ *msg.Pipe
37
+}
38
+
39
+// NewService creates a service object with given type ID and Handler
40
+func NewService(ctx context.Context, h Handler) *Service {
41
+ s := &Service{
42
+ Handler: h,
43
+ Requests: RequestMap{},
44
+ Pipe: msg.NewPipe(10),
45
+ }
46
+
47
+ go s.handleIncomingMessages(ctx)
48
+
49
+ return s
50
+}
51
+
52
+// Start kicks off the Service goroutines.
53
+func (s *Service) Start(ctx context.Context) error {
54
+ if s.cancel != nil {
55
+ return errors.New("Service already started.")
56
+ }
57
+
58
+ // make a cancellable context.
59
+ ctx, s.cancel = context.WithCancel(ctx)
60
+
61
+ go s.handleIncomingMessages(ctx)
62
+ return nil
63
+}
64
+
65
+// Stop stops Service activity.
66
+func (s *Service) Stop() {
67
+ s.cancel()
68
+ s.cancel = context.CancelFunc(nil)
69
+}
70
+
71
+// SendMessage sends a message out
72
+func (s *Service) SendMessage(ctx context.Context, m *msg.Message, rid RequestID) error {
73
+
74
+ // serialize ServiceMessage wrapper
75
+ data, err := wrapData(m.Data, rid)
76
+ if err != nil {
77
+ return err
78
+ }
79
+
80
+ // send message
81
+ m2 := &msg.Message{Peer: m.Peer, Data: data}
82
+ select {
83
+ case s.Outgoing <- m2:
84
+ case <-ctx.Done():
85
+ return ctx.Err()
86
+ }
87
+
88
+ return nil
89
+}
90
+
91
+// SendRequest sends a request message out and awaits a response.
92
+func (s *Service) SendRequest(ctx context.Context, m *msg.Message) (*msg.Message, error) {
93
+
94
+ // create a request
95
+ r, err := NewRequest(m.Peer.ID)
96
+ if err != nil {
97
+ return nil, err
98
+ }
99
+
100
+ // register Request
101
+ s.RequestsLock.Lock()
102
+ s.Requests[r.Key()] = r
103
+ s.RequestsLock.Unlock()
104
+
105
+ // defer deleting this request
106
+ defer func() {
107
+ s.RequestsLock.Lock()
108
+ delete(s.Requests, r.Key())
109
+ s.RequestsLock.Unlock()
110
+ }()
111
+
112
+ // check if we should bail after waiting for mutex
113
+ select {
114
+ default:
115
+ case <-ctx.Done():
116
+ return nil, ctx.Err()
117
+ }
118
+
119
+ // Send message
120
+ s.SendMessage(ctx, m, r.ID)
121
+
122
+ // wait for response
123
+ m = nil
124
+ err = nil
125
+ select {
126
+ case m = <-r.Response:
127
+ case <-ctx.Done():
128
+ err = ctx.Err()
129
+ }
130
+
131
+ return m, err
132
+}
133
+
134
+// handleIncoming consumes the messages on the s.Incoming channel and
135
+// routes them appropriately (to requests, or handler).
136
+func (s *Service) handleIncomingMessages(ctx context.Context) {
137
+ for {
138
+ select {
139
+ case m := <-s.Incoming:
140
+ go s.handleIncomingMessage(ctx, m)
141
+
142
+ case <-ctx.Done():
143
+ return
144
+ }
145
+ }
146
+}
147
+
148
+func (s *Service) handleIncomingMessage(ctx context.Context, m *msg.Message) {
149
+
150
+ // unwrap the incoming message
151
+ data, rid, err := unwrapData(m.Data)
152
+ if err != nil {
153
+ u.PErr("de-serializing error: %v\n", err)
154
+ }
155
+ m2 := &msg.Message{Peer: m.Peer, Data: data}
156
+
157
+ // if it's a request (or has no RequestID), handle it
158
+ if rid == nil || rid.IsRequest() {
159
+ r1, err := s.Handler.HandleMessage(ctx, m2)
160
+ if err != nil {
161
+ u.PErr("handled message yielded error %v\n", err)
162
+ return
163
+ }
164
+
165
+ // if handler gave us a response, send it back out!
166
+ if r1 != nil {
167
+ err := s.SendMessage(ctx, r1, rid.Response())
168
+ if err != nil {
169
+ u.PErr("error sending response message: %v\n", err)
170
+ }
171
+ }
172
+ return
173
+ }
174
+
175
+ // Otherwise, it is a response. handle it.
176
+ if !rid.IsResponse() {
177
+ u.PErr("RequestID should identify a response here.\n")
178
+ }
179
+
180
+ key := RequestKey(m.Peer.ID, RequestID(rid))
181
+ s.RequestsLock.RLock()
182
+ r, found := s.Requests[key]
183
+ s.RequestsLock.RUnlock()
184
+
185
+ if !found {
186
+ u.PErr("no request key %v (timeout?)\n", []byte(key))
187
+ return
188
+ }
189
+
190
+ select {
191
+ case r.Response <- m2:
192
+ case <-ctx.Done():
193
+ }
194
+}
net/service/service_test.go
new
+125
@@ -0,0 +1,125 @@
1
+package service
2
+
3
+import (
4
+ "bytes"
5
+ "testing"
6
+ "time"
7
+
8
+ msg "github.com/jbenet/go-ipfs/net/message"
9
+ peer "github.com/jbenet/go-ipfs/peer"
10
+
11
+ context "code.google.com/p/go.net/context"
12
+ mh "github.com/jbenet/go-multihash"
13
+)
14
+
15
+// ReverseHandler reverses all Data it receives and sends it back.
16
+type ReverseHandler struct{}
17
+
18
+func (t *ReverseHandler) HandleMessage(ctx context.Context, m *msg.Message) (
19
+ *msg.Message, error) {
20
+
21
+ d := m.Data
22
+ for i, j := 0, len(d)-1; i < j; i, j = i+1, j-1 {
23
+ d[i], d[j] = d[j], d[i]
24
+ }
25
+
26
+ return &msg.Message{Peer: m.Peer, Data: d}, nil
27
+}
28
+
29
+func newPeer(t *testing.T, id string) *peer.Peer {
30
+ mh, err := mh.FromHexString(id)
31
+ if err != nil {
32
+ t.Error(err)
33
+ return nil
34
+ }
35
+
36
+ return &peer.Peer{ID: peer.ID(mh)}
37
+}
38
+
39
+func TestServiceHandler(t *testing.T) {
40
+ ctx := context.Background()
41
+ h := &ReverseHandler{}
42
+ s := NewService(ctx, h)
43
+ peer1 := newPeer(t, "11140beec7b5ea3f0fdbc95d0dd47f3c5bc275aaaaaa")
44
+
45
+ d, err := wrapData([]byte("beep"), nil)
46
+ if err != nil {
47
+ t.Error(err)
48
+ }
49
+
50
+ m1 := &msg.Message{Peer: peer1, Data: d}
51
+ s.Incoming <- m1
52
+ m2 := <-s.Outgoing
53
+
54
+ d, rid, err := unwrapData(m2.Data)
55
+ if err != nil {
56
+ t.Error(err)
57
+ }
58
+
59
+ if rid != nil {
60
+ t.Error("RequestID should be nil")
61
+ }
62
+
63
+ if !bytes.Equal(d, []byte("peeb")) {
64
+ t.Errorf("service handler data incorrect: %v != %v", d, "oof")
65
+ }
66
+}
67
+
68
+func TestServiceRequest(t *testing.T) {
69
+ ctx := context.Background()
70
+ s1 := NewService(ctx, &ReverseHandler{})
71
+ s2 := NewService(ctx, &ReverseHandler{})
72
+ peer1 := newPeer(t, "11140beec7b5ea3f0fdbc95d0dd47f3c5bc275aaaaaa")
73
+
74
+ // patch services together
75
+ go func() {
76
+ for {
77
+ select {
78
+ case m := <-s1.Outgoing:
79
+ s2.Incoming <- m
80
+ case m := <-s2.Outgoing:
81
+ s1.Incoming <- m
82
+ case <-ctx.Done():
83
+ return
84
+ }
85
+ }
86
+ }()
87
+
88
+ m1 := &msg.Message{Peer: peer1, Data: []byte("beep")}
89
+ m2, err := s1.SendRequest(ctx, m1)
90
+ if err != nil {
91
+ t.Error(err)
92
+ }
93
+
94
+ if !bytes.Equal(m2.Data, []byte("peeb")) {
95
+ t.Errorf("service handler data incorrect: %v != %v", m2.Data, "oof")
96
+ }
97
+}
98
+
99
+func TestServiceRequestTimeout(t *testing.T) {
100
+ ctx, _ := context.WithTimeout(context.Background(), time.Millisecond)
101
+ s1 := NewService(ctx, &ReverseHandler{})
102
+ s2 := NewService(ctx, &ReverseHandler{})
103
+ peer1 := newPeer(t, "11140beec7b5ea3f0fdbc95d0dd47f3c5bc275aaaaaa")
104
+
105
+ // patch services together
106
+ go func() {
107
+ for {
108
+ <-time.After(time.Millisecond)
109
+ select {
110
+ case m := <-s1.Outgoing:
111
+ s2.Incoming <- m
112
+ case m := <-s2.Outgoing:
113
+ s1.Incoming <- m
114
+ case <-ctx.Done():
115
+ return
116
+ }
117
+ }
118
+ }()
119
+
120
+ m1 := &msg.Message{Peer: peer1, Data: []byte("beep")}
121
+ m2, err := s1.SendRequest(ctx, m1)
122
+ if err == nil || m2 != nil {
123
+ t.Error("should've timed out")
124
+ }
125
+}