/
barrister.go
1310 lines (1111 loc) · 35.3 KB
/
barrister.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package barrister
import (
"bytes"
"crypto/rand"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"reflect"
"strings"
)
var zeroVal reflect.Value
// randHex generates a random array of bytes and
// returns the value as a hex encoded string
func randHex(bytes int) string {
buf := make([]byte, bytes)
io.ReadFull(rand.Reader, buf)
return fmt.Sprintf("%x", buf)
}
//////////////////////////////////////////////////
// IDL //
/////////
// ParseIdlJsonFile loads the IDL JSON from the given filename
func ParseIdlJsonFile(filename string) (*Idl, error) {
b, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
return ParseIdlJson(b)
}
// ParseIdlJson parses the given IDL JSON
func ParseIdlJson(jsonData []byte) (*Idl, error) {
elems := []IdlJsonElem{}
err := json.Unmarshal(jsonData, &elems)
if err != nil {
return nil, err
}
return NewIdl(elems), nil
}
// MustParseIdlJson calls ParseIdlJson and panics if an error is returned
func MustParseIdlJson(jsonData []byte) *Idl {
idl, err := ParseIdlJson(jsonData)
if err != nil {
panic(err)
}
return idl
}
// NewIdl creates a new Idl struct based on the slice of elements
// parsed from the IDL JSON document
func NewIdl(elems []IdlJsonElem) *Idl {
idl := &Idl{
elems: elems,
interfaces: map[string][]Function{},
methods: map[string]Function{},
structs: map[string]*Struct{},
enums: map[string][]EnumValue{},
}
for _, el := range elems {
if el.Type == "meta" {
idl.Meta = Meta{el.BarristerVersion, el.DateGenerated * 1000000, el.Checksum}
} else if el.Type == "interface" {
funcs := []Function{}
for _, f := range el.Functions {
meth := fmt.Sprintf("%s.%s", el.Name, f.Name)
idl.methods[meth] = f
funcs = append(funcs, f)
}
idl.interfaces[el.Name] = funcs
} else if el.Type == "struct" {
idl.structs[el.Name] = &Struct{Name: el.Name, Extends: el.Extends, Fields: el.Fields}
} else if el.Type == "enum" {
idl.enums[el.Name] = el.Values
}
}
idl.computeAllStructFields()
return idl
}
// A single element in the IDL JSON. This struct is the union of
// all possible fields that may occur on an element. The "type" field
// determines what fields are relevant for a given element.
type IdlJsonElem struct {
// common fields
Type string `json:"type"`
Name string `json:"name"`
Comment string `json:"comment"`
// type=comment
Value string `json:"value"`
// type=struct
Extends string `json:"extends"`
Fields []Field `json:"fields"`
// type=enum
Values []EnumValue `json:"values"`
// type=interface
Functions []Function `json:"functions"`
// type=meta
BarristerVersion string `json:"barrister_version"`
DateGenerated int64 `json:"date_generated"`
Checksum string `json:"checksum"`
}
// Represents a function on an IDL interface
type Function struct {
Name string `json:"name"`
Comment string `json:"comment"`
Params []Field `json:"params"`
Returns Field `json:"returns"`
}
// Represents an IDL struct
type Struct struct {
Name string
Extends string
Fields []Field
// fields in this struct, and its parents
allFields []Field
}
// Represents a single Field on a struct or Function param
type Field struct {
Name string `json:"name"`
Type string `json:"type"`
Optional bool `json:"optional"`
IsArray bool `json:"is_array"`
Comment string `json:"comment"`
}
func (f Field) goType(idl *Idl, optionalToPtr bool, pkgToStrip string) string {
if f.IsArray {
f2 := Field{f.Name, f.Type, false, false, ""}
prefix := "[]"
if f.Optional && optionalToPtr {
prefix = "*[]"
}
return prefix + f2.goType(idl, optionalToPtr, pkgToStrip)
}
_, isStruct := idl.structs[f.Type]
prefix := ""
if f.Optional && (optionalToPtr || isStruct) {
prefix = "*"
}
switch f.Type {
case "string":
return prefix + "string"
case "int":
return prefix + "int64"
case "float":
return prefix + "float64"
case "bool":
return prefix + "bool"
}
return prefix + capitalizeAndStripMatchingPkg(f.Type, pkgToStrip)
}
func (f Field) zeroVal(idl *Idl, optionalToPtr bool, pkgToStrip string) interface{} {
if f.Optional && optionalToPtr {
return "nil"
}
if f.IsArray {
return f.goType(idl, false, pkgToStrip) + "{}"
}
switch f.Type {
case "string":
return `""`
case "int":
return "int64(0)"
case "float":
return "float64(0)"
case "bool":
return "false"
}
s, ok := idl.structs[f.Type]
if ok {
if f.Optional {
return "nil"
} else {
return capitalizeAndStripMatchingPkg(s.Name, pkgToStrip) + "{}"
}
}
e, ok := idl.enums[f.Type]
if ok && len(e) > 0 {
return `""`
}
msg := fmt.Sprintf("Unable to create val for field: %s type: %s",
f.Name, f.Type)
panic(msg)
}
func (f Field) testVal(idl *Idl) interface{} {
return f.testValRecur(idl, make(map[string]interface{}))
}
func (f Field) testValRecur(idl *Idl, seenTypes map[string]interface{}) interface{} {
if f.IsArray {
key := fmt.Sprintf("array %s", f.Type)
_, ok := seenTypes[key]
if ok {
// we've seen this array type, so return
// an empty slice now to avoid cycles
return make([]interface{}, 0)
}
f2 := Field{f.Name, f.Type, f.Optional, false, ""}
arr := make([]interface{}, 1, 1)
seenTypes[key] = arr
arr[0] = f2.testValRecur(idl, seenTypes)
return arr
}
switch f.Type {
case "string":
return "testval"
case "int":
return int64(99)
case "float":
return float64(10.3)
case "bool":
return true
}
s, ok := idl.structs[f.Type]
if ok {
key := fmt.Sprintf("struct %s", f.Type)
seenVal, ok := seenTypes[key]
if ok {
return seenVal
}
val := map[string]interface{}{}
seenTypes[key] = val
for _, f2 := range s.allFields {
val[f2.Name] = f2.testValRecur(idl, seenTypes)
}
return val
}
e, ok := idl.enums[f.Type]
if ok && len(e) > 0 {
return e[0].Value
}
msg := fmt.Sprintf("Unable to create val for field: %s type: %s", f.Name, f.Type)
panic(msg)
}
// Represents a single element in an IDL enum
type EnumValue struct {
Value string `json:"value"`
Comment string `json:"comment"`
}
type Meta struct {
// Version number of the Python barrister translator that produced the
// JSON translation of the IDL
BarristerVersion string
// When the IDL was translated to JSON represented as nanoseconds since epoch (UnixNano)
DateGenerated int64
// Checksum of the IDL
Checksum string
}
// Represents a single Barrister IDL file
type Idl struct {
// raw data from IDL file
elems []IdlJsonElem
// meta information about the contract
Meta Meta
// hashed elements
interfaces map[string][]Function
methods map[string]Function
structs map[string]*Struct
enums map[string][]EnumValue
}
func (idl *Idl) computeAllStructFields() {
for _, s := range idl.structs {
s.allFields = idl.computeStructFields(s, []Field{})
}
}
func (idl *Idl) computeStructFields(toAdd *Struct, allFields []Field) []Field {
if toAdd.Extends != "" {
parent, ok := idl.structs[toAdd.Extends]
if ok {
allFields = idl.computeStructFields(parent, allFields)
}
}
for _, f := range toAdd.Fields {
allFields = append(allFields, f)
}
return allFields
}
// GenerateGo generates Go source code for the given Idl. A map is returned whose keys are
// the Go package names and values are the source code for that package.
//
// Typically you'll use the idl2go binary as a front end to this method, but this method is exposed
// if you wish to write your own code generation tooling.
//
// defaultPkgName - Go package name to use for non-namespaced elements. Since interfaces are never
// namespaced in Barrister, all interfaces will be generated into this package.
//
// baseImport - Base Go import path to use for internal imports. For example, if "myproject" is provided,
// and two packages "usersvc" and "common" are resolved, then "usersvc" will import "myproject/common"
//
// optionalToPtr - If true struct fields marked `[optional]` will be generated as Go pointers. If false,
// they will be generated as non-pointer types with `omitempty` in the JSON tag. Note: due to the
// behavior of `encoding/json`, all nested struct fields marked optional will be generated as
// pointers. Otherwise there is no way to omit those fields from the struct during marshaling.
//
func (idl *Idl) GenerateGo(defaultPkgName string, baseImport string, optionalToPtr bool) map[string][]byte {
pkgNameToGoCode := make(map[string][]byte)
for _, nsIdl := range partitionIdlByNamespace(idl, defaultPkgName) {
g := generateGo{idl,
nsIdl.idl,
nsIdl.pkgName,
optionalToPtr,
nsIdl.imports,
baseImport}
pkgNameToGoCode[nsIdl.pkgName] = g.generate()
}
return pkgNameToGoCode
}
// Method returns the Function related to the given method.
// The method must be fully qualified with the interface name.
// For example: "UserService.save"
func (idl *Idl) Method(name string) Function {
return idl.methods[name]
}
type namespacedIdl struct {
idl *Idl
pkgName string
imports []string
}
func partitionIdlByNamespace(idl *Idl, defaultPkgName string) []namespacedIdl {
var metaElem IdlJsonElem
pkgNameToIdlElems := make(map[string][]IdlJsonElem)
for _, elem := range idl.elems {
if elem.Type == "meta" {
metaElem = elem
} else {
pkg, _ := splitNs(elem.Name)
if pkg == "" {
pkg = defaultPkgName
}
elems, ok := pkgNameToIdlElems[pkg]
if !ok {
elems = make([]IdlJsonElem, 0)
}
pkgNameToIdlElems[pkg] = append(elems, elem)
}
}
nsIdl := make([]namespacedIdl, 0)
for pkg, elems := range pkgNameToIdlElems {
elems = append(elems, metaElem)
idl := NewIdl(elems)
imports := findAllImports(pkg, elems)
nsIdl = append(nsIdl, namespacedIdl{idl, pkg, imports})
}
return nsIdl
}
// splits name at the first period and returns the two parts
// e.g. "hello.World" returns: `"hello", "World"`
//
// If name does not contain a period then: "", name is returned
// e.g. "hello" returns: `"", "hello"`
func splitNs(name string) (string, string) {
i := strings.Index(name, ".")
if i > -1 && i < (len(name)-1) {
return name[0:i], name[i+1:]
}
return "", name
}
// finds all imported IDL namespaces in the given elements
func findAllImports(pkgToIgnore string, elems []IdlJsonElem) []string {
imports := make([]string, 0)
for _, elem := range elems {
ns, _ := splitNs(elem.Extends)
imports = addIfNotInSlice(ns, pkgToIgnore, imports)
for _, f := range elem.Fields {
ns, _ := splitNs(f.Type)
imports = addIfNotInSlice(ns, pkgToIgnore, imports)
}
for _, fx := range elem.Functions {
ns, _ := splitNs(fx.Returns.Type)
imports = addIfNotInSlice(ns, pkgToIgnore, imports)
for _, f := range fx.Params {
ns, _ := splitNs(f.Type)
imports = addIfNotInSlice(ns, pkgToIgnore, imports)
}
}
}
return imports
}
// Adds a string to the given slice if it is not empty, not equal to toIgnore,
// and not already in the slice
func addIfNotInSlice(a string, toIgnore string, list []string) []string {
if a != "" && a != toIgnore && !stringInSlice(a, list) {
return append(list, a)
}
return list
}
// Returns true if a is in the given slice, or false if it is not
func stringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}
//////////////////////////////////////////////////
// Request / Response //
////////////////////////
// JsonRpcError represents a JSON-RPC 2.0 Request
type JsonRpcRequest struct {
// Version of the JSON-RPC protocol. Always "2.0"
Jsonrpc string `json:"jsonrpc"`
// An identifier established by the client that uniquely identifies the request
Id string `json:"id"`
// Name of the method to be invoked
Method string `json:"method"`
// Parameter values to be used during the invocation of the method
Params interface{} `json:"params"`
}
// JsonRpcError represents a JSON-RPC 2.0 Error
type JsonRpcError struct {
// Indicates the error type that occurred
Code int `json:"code"`
// Short description of the error
Message string `json:"message"`
// Optional value that contains additional information about the error
Data interface{} `json:"data,omitempty"`
}
func (e *JsonRpcError) Error() string {
return fmt.Sprintf("JsonRpcError: code=%d message=%s", e.Code, e.Message)
}
// JsonRpcResponse represents a JSON-RPC 2.0 Response
type JsonRpcResponse struct {
// Version of the JSON-RPC protocol. Always "2.0"
Jsonrpc string `json:"jsonrpc"`
// Id will match the related JsonRpcRequest.Id
Id string `json:"id"`
// Error will be nil if the request was successful
Error *JsonRpcError `json:"error,omitempty"`
// Result from a successful request
Result interface{} `json:"result,omitempty"`
}
// RequestResponse holds the request method and params and the
// handler instance that the method resolves to.
//
// This struct is used with the Filter interface and allows
// Filter implementations to inspect the request, mutate the
// handler (e.g. set out of band authentication information),
// and set the result/error (e.g. to terminate an unauthorized request)
type RequestResponse struct {
// from Transport (e.g. HTTP headers)
Headers Headers
// from JsonRpcRequest
Method string
Params []interface{}
// handler instance that will process
// this request method - this is passed
// by value, so only pointer fields in the handler
// may be modified by filters
Handler interface{}
// to JsonRpcResponse
Result interface{}
Err error
}
// GetFirst returns the first value associated with the given
// key, or an empty string if no value is found with that key
func GetFirst(m map[string][]string, key string) string {
return GetFirstDefault(m, key, "")
}
// GetFirstDefault returns the first value associated with the given
// key, or defaultVal if no value is found with that key
func GetFirstDefault(m map[string][]string, key string, defaultVal string) string {
xs, ok := m[key]
if ok && len(xs) > 0 {
return xs[0]
}
return defaultVal
}
// toJsonRpcError returns a JsonRpcError with code -32000 and
// an empty data field.
func toJsonRpcError(method string, err error) *JsonRpcError {
if err == nil {
return nil
}
e, ok := err.(*JsonRpcError)
if ok {
return e
}
msg := fmt.Sprintf("barrister: method '%s' raised unknown error: %v", method, err)
return &JsonRpcError{Code: -32000, Message: msg}
}
//////////////////////////////////////////////////
// Client //
////////////
// EncodeASCII returns the given byte slice with non-ASCII
// runes encoded as unicode escape sequences (e.g. "\uXX" or "\UXXXX")
func EncodeASCII(b []byte) (*bytes.Buffer, error) {
in := bytes.NewBuffer(b)
out := bytes.NewBufferString("")
for {
r, size, err := in.ReadRune()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
if size == 1 {
// TODO: we need to check for characters above 127
out.WriteRune(r)
} else if size == 2 {
out.WriteString(fmt.Sprintf("\\u%04x", r))
} else {
out.WriteString(fmt.Sprintf("\\U%08x", r))
}
}
return out, nil
}
// Serializers encapsulate marshaling bytes to and from Go types.
type Serializer interface {
Marshal(in interface{}) ([]byte, error)
Unmarshal(in []byte, out interface{}) error
// returns true if b represents a JSON-RPC batch request
IsBatch(b []byte) bool
MimeType() string
}
// JsonSerializer implements Serializer using the `encoding/json` package
type JsonSerializer struct {
// If true values will be encoded with the `EncodeASCII` function
// when marshaled
ForceASCII bool
}
func (s *JsonSerializer) Marshal(in interface{}) ([]byte, error) {
b, err := json.Marshal(in)
if err != nil {
return nil, err
}
if s.ForceASCII {
buf, err := EncodeASCII(b)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
return b, nil
}
func (s *JsonSerializer) Unmarshal(in []byte, out interface{}) error {
return json.Unmarshal(in, out)
}
// IsBatch scans b looking for '[' or '{' - if '[' occurs
// first then true is returned.
func (s *JsonSerializer) IsBatch(b []byte) bool {
batch := false
for i := 0; i < len(b); i++ {
if b[i] == '{' {
break
} else if b[i] == '[' {
batch = true
break
}
}
return batch
}
// Returns "application/json"
func (s *JsonSerializer) MimeType() string {
return "application/json"
}
// The Transport interface abstracts sending a serialized byte slice
type Transport interface {
Send(in []byte) ([]byte, error)
}
// HttpTransport sends requests via the Go `http` package
type HttpTransport struct {
// Endpoint of JSON-RPC service to consume
Url string
// Optional hook to invoke before/after requests
Hook HttpHook
// Optional custom HTTP client to be used instead of the default empty one.
Client *http.Client
// Optional CookieJar - useful if endpoint uses session cookies
// Deprecated by custom Client option. If you need to provide CookieJar, provide a &http.Client{Jar: YourCookie}
Jar http.CookieJar
}
// HttpHook is an optional callback interface that can be implemented
// if custom headers need to be added to the request, or if other
// operations are desired (e.g. request timing)
type HttpHook interface {
// Called before the HTTP request is made.
// Request may be altered, typically to add headers
Before(req *http.Request, body []byte)
// Called after the request is made, but before the
// response is deserialized
After(req *http.Request, resp *http.Response, body []byte)
}
func (t *HttpTransport) Send(in []byte) ([]byte, error) {
req, err := http.NewRequest("POST", t.Url, bytes.NewBuffer(in))
if err != nil {
return nil, fmt.Errorf("barrister: HttpTransport NewRequest failed: %s", err)
}
// TODO: need to make mime type plugable
req.Header.Add("Content-Type", "application/json")
if t.Hook != nil {
t.Hook.Before(req, in)
}
client := t.Client
if client == nil {
client = &http.Client{}
}
if t.Jar != nil {
client.Jar = t.Jar
}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("barrister: HttpTransport POST to %s failed: %s", t.Url, err)
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode > 299 {
return nil, fmt.Errorf("barrister: HttpTransport POST to %s returned non-2xx status: %d - %s", t.Url, resp.StatusCode, resp.Status)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("barrister: HttpTransport Unable to read resp.Body: %s", err)
}
if t.Hook != nil {
t.Hook.After(req, resp, body)
}
return body, nil
}
// Client abstracts methods for calling JSON-RPC services. Note that the
// Server type below implements this interface, which allows services to be
// consumed in process without a transport or serializer.
type Client interface {
// Call represents a single JSON-RPC method invocation
Call(method string, params ...interface{}) (interface{}, error)
// CallBatch represents a JSON-RPC batch request
CallBatch(batch []JsonRpcRequest) []JsonRpcResponse
}
// NewRemoteClient creates a RemoteClient with the given Transport using the JsonSerializer
func NewRemoteClient(trans Transport, forceASCII bool) Client {
return &RemoteClient{trans, &JsonSerializer{forceASCII}}
}
// RemoteClient implements Client against the given Transport and Serializer.
type RemoteClient struct {
Trans Transport
Ser Serializer
}
func (c *RemoteClient) CallBatch(batch []JsonRpcRequest) []JsonRpcResponse {
reqBytes, err := c.Ser.Marshal(batch)
if err != nil {
msg := fmt.Sprintf("barrister: CallBatch unable to Marshal request: %s", err)
return []JsonRpcResponse{
JsonRpcResponse{Error: &JsonRpcError{Code: -32600, Message: msg}}}
}
respBytes, err := c.Trans.Send(reqBytes)
if err != nil {
msg := fmt.Sprintf("barrister: CallBatch Transport error during request: %s", err)
return []JsonRpcResponse{
JsonRpcResponse{Error: &JsonRpcError{Code: -32603, Message: msg}}}
}
var batchResp []JsonRpcResponse
err = c.Ser.Unmarshal(respBytes, &batchResp)
if err != nil {
msg := fmt.Sprintf("barrister: CallBatch unable to Unmarshal response: %s", err)
return []JsonRpcResponse{
JsonRpcResponse{Error: &JsonRpcError{Code: -32603, Message: msg}}}
}
return batchResp
}
func (c *RemoteClient) Call(method string, params ...interface{}) (interface{}, error) {
rpcReq := JsonRpcRequest{Jsonrpc: "2.0", Id: randHex(20), Method: method, Params: params}
reqBytes, err := c.Ser.Marshal(rpcReq)
if err != nil {
msg := fmt.Sprintf("barrister: %s: Call unable to Marshal request: %s", method, err)
return nil, &JsonRpcError{Code: -32600, Message: msg}
}
respBytes, err := c.Trans.Send(reqBytes)
if err != nil {
msg := fmt.Sprintf("barrister: %s: Transport error during request: %s", method, err)
return nil, &JsonRpcError{Code: -32603, Message: msg}
}
var rpcResp JsonRpcResponse
err = c.Ser.Unmarshal(respBytes, &rpcResp)
if err != nil {
msg := fmt.Sprintf("barrister: %s: Call unable to Unmarshal response: %s", method, err)
return nil, &JsonRpcError{Code: -32603, Message: msg}
}
if rpcResp.Error != nil {
return nil, rpcResp.Error
}
return rpcResp.Result, nil
}
//////////////////////////////////////////////////
// Server //
////////////
// If a server handler implements Cloneable, it will
// be cloned per JSON-RPC call. This allows you to initialize out
// of band context that your service implementation may need such
// as auth headers.
//
// In addition, by implementing Cloneable
// your services no longer need to be threadsafe, and can safely store
// state locally for the lifetime of the service method invocation.
//
type Cloneable interface {
// CloneForReq is called after the JSON-RPC request is
// decoded, but before the RPC method call is invoked.
//
// A copy of the implementing struct should be returned.
// This copy will be used for a single RPC method call and
// then discarded.
CloneForReq(headers Headers) interface{}
}
// Represents transport request headers/cookies.
// Handler may mutate Response to send headers back to the caller.
type Headers struct {
// Headers from the request. Handlers should not modify
Request map[string][]string
// Convenience property - only set if transport is HTTP
// For other transports this may be nil
// Read-only.
Cookies []*http.Cookie
// Writeable map of headers to return on the response
// Transport implementations are responsible for
// sending values in this map back to the caller.
Response map[string][]string
}
// GetCookie returns the cookie associated with the given
// name, or nil if no cookie is found with that name.
func (me *Headers) GetCookie(name string) *http.Cookie {
if me.Cookies != nil && len(me.Cookies) > 0 {
for _, c := range me.Cookies {
if c.Name == name {
return c
}
}
}
return nil
}
// ReadCookies is taken from the net/http cookie.go standard library
// and populates Headers.Cookies from Headers.Request["Cookie"]
func (me *Headers) ReadCookies() {
cookies := []*http.Cookie{}
lines, ok := me.Request["Cookie"]
if ok {
for _, line := range lines {
parts := strings.Split(strings.TrimSpace(line), ";")
if len(parts) == 1 && parts[0] == "" {
continue
}
// Per-line attributes
parsedPairs := 0
for i := 0; i < len(parts); i++ {
parts[i] = strings.TrimSpace(parts[i])
if len(parts[i]) == 0 {
continue
}
name, val := parts[i], ""
if j := strings.Index(name, "="); j >= 0 {
name, val = name[:j], name[j+1:]
}
if !isCookieNameValid(name) {
continue
}
val, success := parseCookieValue(val)
if !success {
continue
}
cookies = append(cookies, &http.Cookie{Name: name, Value: val})
parsedPairs++
}
}
}
me.Cookies = cookies
}
// Filters allow you to intercept requests before and after the handler method
// is invoked. Filters are useful for implementing cross cutting concerns
// such as authentication, performance measurement, logging, etc.
type Filter interface {
// PreInvoke is called after the handler has been resolved, but prior
// to handler method invocation.
//
// Return value of false terminates the filter chain, and r.result, r.err
// will be used as the response.
// Return value of true continues filter chain execution.
//
PreInvoke(r *RequestResponse) bool
// PostInvoke is called after the handler method has been invoked and
// returns a bool that indicates whether later filters should be called.
//
// Implementations may alter the ReturnVal, which will be later marshaled
// into the JSON-RPC response.
//
// Return value of false terminates the filter chain, and r.result, r.err
// will be used. Return value of true continues filter chain execution.
//
PostInvoke(r *RequestResponse) bool
}
// NewJSONServer creates a Server for the given IDL that uses the JsonSerializer.
// If forceASCII is true, then unicode characters will be escaped
func NewJSONServer(idl *Idl, forceASCII bool) Server {
return NewServer(idl, &JsonSerializer{forceASCII})
}
// NewServer creates a Server for the given IDL and Serializer
func NewServer(idl *Idl, ser Serializer) Server {
return Server{idl, ser, map[string]interface{}{}, make([]Filter, 0)}
}
// Server represents a handler for Barrister IDL file.
// Each Server has one or more handlers (one per interface in the IDL) and
// zero or more Filters.
type Server struct {
idl *Idl
ser Serializer
handlers map[string]interface{}
filters []Filter
}
// AddFilter registers a Filter implementation with the Server.
// Filter.PreInvoke is called in the order of registration.
// Filter.PostInvoke is called in reverse order of registration.
//
func (s *Server) AddFilter(f Filter) {
s.filters = append(s.filters, f)
}
// AddHandler associates the given impl with the IDL interface.
// Typically this method is used with idl2go generated interfaces, so
// any validation issues indicate a programming bug. Consequently this
// method panics instead of returning na error if any IDL mismatches are
// found.
func (s *Server) AddHandler(iface string, impl interface{}) {
ifaceFuncs, ok := s.idl.interfaces[iface]
if !ok {
msg := fmt.Sprintf("barrister: IDL has no interface: %s", iface)
panic(msg)
}
var typeOfError = reflect.TypeOf((*error)(nil)).Elem()
elem := reflect.ValueOf(impl)
for _, idlFunc := range ifaceFuncs {
fname := capitalize(idlFunc.Name)
fn := elem.MethodByName(fname)
if fn == zeroVal {
msg := fmt.Sprintf("barrister: %s impl has no method named: %s",
iface, fname)
panic(msg)
}
fnType := fn.Type()
if fnType.NumIn() != len(idlFunc.Params) {
msg := fmt.Sprintf("barrister: %s impl method: %s accepts %d params but IDL specifies %d", iface, fname, fnType.NumIn(), len(idlFunc.Params))
panic(msg)
}
if fnType.NumOut() != 2 {
msg := fmt.Sprintf("barrister: %s impl method: %s returns %d params but must be 2", iface, fname, fnType.NumOut())
panic(msg)
}
for x, param := range idlFunc.Params {
path := fmt.Sprintf("%s.%s param[%d]", iface, fname, x)
s.validate(param, fnType.In(x), path)
}
path := fmt.Sprintf("%s.%s return value[0]", iface, fname)
s.validate(idlFunc.Returns, fnType.Out(0), path)