Skip to content
This repository was archived by the owner on Apr 28, 2024. It is now read-only.

Commit f304a07

Browse files
committed
Improved middlewares
1 parent 281eda8 commit f304a07

13 files changed

+260
-74
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ import (
7878
"context"
7979

8080
"go.neonxp.dev/jsonrpc2/rpc"
81+
"go.neonxp.dev/jsonrpc2/rpc/middleware"
8182
"go.neonxp.dev/jsonrpc2/transport"
8283
)
8384

@@ -90,7 +91,7 @@ func main() {
9091
// Set options after constructor
9192
s.Use(
9293
rpc.WithTransport(&transport.TCP{Bind: ":3000"}), // TCP transport
93-
rpc.WithMiddleware(rpc.LoggerMiddleware(rpc.StdLogger)), // Logger middleware
94+
rpc.WithMiddleware(middleware.Logger(rpc.StdLogger)), // Logger middleware
9495
)
9596

9697
s.Register("multiply", rpc.H(Multiply))

example/main.go

+36-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import (
77
"os"
88
"os/signal"
99

10+
"github.com/qri-io/jsonschema"
1011
"go.neonxp.dev/jsonrpc2/rpc"
12+
"go.neonxp.dev/jsonrpc2/rpc/middleware"
1113
"go.neonxp.dev/jsonrpc2/transport"
1214
)
1315

@@ -17,9 +19,42 @@ func main() {
1719
rpc.WithTransport(&transport.HTTP{Bind: ":8000", CORSOrigin: "*"}),
1820
)
1921
// Set options after constructor
22+
validation, err := middleware.Validation(map[string]middleware.MethodSchema{
23+
"divide": {
24+
Request: *jsonschema.Must(`{
25+
"type": "object",
26+
"properties": {
27+
"a": {
28+
"type": "integer"
29+
},
30+
"b": {
31+
"type": "integer",
32+
"not":{"const":0}
33+
}
34+
},
35+
"required": ["a", "b"]
36+
}`),
37+
Response: *jsonschema.Must(`{
38+
"type": "object",
39+
"properties": {
40+
"quo": {
41+
"type": "integer"
42+
},
43+
"rem": {
44+
"type": "integer"
45+
}
46+
},
47+
"required": ["quo", "rem"]
48+
}`),
49+
},
50+
})
51+
if err != nil {
52+
log.Fatal(err)
53+
}
2054
s.Use(
2155
rpc.WithTransport(&transport.TCP{Bind: ":3000"}),
22-
rpc.WithMiddleware(rpc.LoggerMiddleware(rpc.StdLogger)),
56+
rpc.WithMiddleware(middleware.Logger(rpc.StdLogger)),
57+
rpc.WithMiddleware(validation),
2358
)
2459

2560
s.Register("multiply", rpc.H(Multiply))

example/test.http

+16-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Content-Type: application/json
1919
"method": "divide",
2020
"params": {
2121
"a": 10,
22-
"b": 3
22+
"b": 10
2323
},
2424
"id": 2
2525
}
@@ -44,3 +44,18 @@ Content-Type: application/json
4444
{"foo": "boo"}
4545
{"jsonrpc": "2.0", "method": "foo.get", "params": {"name": "myself"}, "id": "5"}
4646
{"jsonrpc": "2.0", "method": "get_data", "id": "9"}
47+
{
48+
"jsonrpc": "2.0",
49+
"method": "divide",
50+
"params": {
51+
"a": 10,
52+
"b": 0
53+
},
54+
"id": "divide"
55+
}
56+
{
57+
"jsonrpc": "2.0",
58+
"method": "divide",
59+
"params": {},
60+
"id": "divide"
61+
}

go.mod

+6-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,9 @@ module go.neonxp.dev/jsonrpc2
22

33
go 1.18
44

5-
require golang.org/x/sync v0.0.0-20220513210516-0976fa681c29
5+
require (
6+
github.com/qri-io/jsonschema v0.2.1
7+
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29
8+
)
9+
10+
require github.com/qri-io/jsonpointer v0.1.1 // indirect

go.sum

+9
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,11 @@
1+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
3+
github.com/qri-io/jsonpointer v0.1.1 h1:prVZBZLL6TW5vsSB9fFHFAMBLI4b0ri5vribQlTJiBA=
4+
github.com/qri-io/jsonpointer v0.1.1/go.mod h1:DnJPaYgiKu56EuDp8TU5wFLdZIcAnb/uH9v37ZaMV64=
5+
github.com/qri-io/jsonschema v0.2.1 h1:NNFoKms+kut6ABPf6xiKNM5214jzxAhDBrPHCJ97Wg0=
6+
github.com/qri-io/jsonschema v0.2.1/go.mod h1:g7DPkiOsK1xv6T/Ao5scXRkd+yTFygcANPBaaqW+VrI=
7+
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
8+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
9+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
110
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4=
211
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

rpc/contract.go

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//Package rpc provides abstract rpc server
2+
//
3+
//Copyright (C) 2022 Alexander Kiryukhin <[email protected]>
4+
//
5+
//This file is part of go.neonxp.dev/jsonrpc2 project.
6+
//
7+
//This program is free software: you can redistribute it and/or modify
8+
//it under the terms of the GNU General Public License as published by
9+
//the Free Software Foundation, either version 3 of the License, or
10+
//(at your option) any later version.
11+
//
12+
//This program is distributed in the hope that it will be useful,
13+
//but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
//GNU General Public License for more details.
16+
//
17+
//You should have received a copy of the GNU General Public License
18+
//along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
20+
package rpc
21+
22+
import (
23+
"context"
24+
"encoding/json"
25+
)
26+
27+
type RpcHandler func(ctx context.Context, req *RpcRequest) *RpcResponse
28+
29+
type RpcRequest struct {
30+
Jsonrpc string `json:"jsonrpc"`
31+
Method string `json:"method"`
32+
Params json.RawMessage `json:"params"`
33+
Id any `json:"id"`
34+
}
35+
36+
type RpcResponse struct {
37+
Jsonrpc string `json:"jsonrpc"`
38+
Result json.RawMessage `json:"result,omitempty"`
39+
Error error `json:"error,omitempty"`
40+
Id any `json:"id,omitempty"`
41+
}
42+
43+
type Flusher interface {
44+
// Flush sends any buffered data to the client.
45+
Flush()
46+
}

rpc/errors.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ const (
3131
)
3232

3333
var errorMap = map[int]string{
34-
-32700: "Parse error", // Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.
35-
-32600: "Invalid Request", // The JSON sent is not a valid Request object.
36-
-32601: "Method not found", // The method does not exist / is not available.
37-
-32602: "Invalid params", // Invalid method parameter(s).
38-
-32603: "Internal error", // Internal JSON-RPC error.
39-
-32000: "Other error",
34+
ErrCodeParseError: "Parse error", // Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.
35+
ErrCodeInvalidRequest: "Invalid Request", // The JSON sent is not a valid Request object.
36+
ErrCodeMethodNotFound: "Method not found", // The method does not exist / is not available.
37+
ErrCodeInvalidParams: "Invalid params", // Invalid method parameter(s).
38+
ErrCodeInternalError: "Internal error", // Internal JSON-RPC error.
39+
ErrUser: "Other error",
4040
}
4141

4242
//-32000 to -32099 RpcServer error Reserved for implementation-defined server-errors.

rpc/middleware.go

-21
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,4 @@
1919

2020
package rpc
2121

22-
import (
23-
"context"
24-
"strings"
25-
"time"
26-
)
27-
2822
type Middleware func(handler RpcHandler) RpcHandler
29-
30-
type RpcHandler func(ctx context.Context, req *RpcRequest) *RpcResponse
31-
32-
func LoggerMiddleware(logger Logger) Middleware {
33-
return func(handler RpcHandler) RpcHandler {
34-
return func(ctx context.Context, req *RpcRequest) *RpcResponse {
35-
t1 := time.Now().UnixMicro()
36-
resp := handler(ctx, req)
37-
t2 := time.Now().UnixMicro()
38-
args := strings.ReplaceAll(string(req.Params), "\n", "")
39-
logger.Logf("rpc call=%s, args=%s, take=%dμs", req.Method, args, (t2 - t1))
40-
return resp
41-
}
42-
}
43-
}

rpc/middleware/logger.go

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//Package middleware provides middlewares for rpc server
2+
//
3+
//Copyright (C) 2022 Alexander Kiryukhin <[email protected]>
4+
//
5+
//This file is part of go.neonxp.dev/jsonrpc2 project.
6+
//
7+
//This program is free software: you can redistribute it and/or modify
8+
//it under the terms of the GNU General Public License as published by
9+
//the Free Software Foundation, either version 3 of the License, or
10+
//(at your option) any later version.
11+
//
12+
//This program is distributed in the hope that it will be useful,
13+
//but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
//GNU General Public License for more details.
16+
//
17+
//You should have received a copy of the GNU General Public License
18+
//along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
20+
package middleware
21+
22+
import (
23+
"context"
24+
"strings"
25+
"time"
26+
27+
"go.neonxp.dev/jsonrpc2/rpc"
28+
)
29+
30+
func Logger(logger rpc.Logger) rpc.Middleware {
31+
return func(handler rpc.RpcHandler) rpc.RpcHandler {
32+
return func(ctx context.Context, req *rpc.RpcRequest) *rpc.RpcResponse {
33+
t1 := time.Now().UnixMicro()
34+
resp := handler(ctx, req)
35+
t2 := time.Now().UnixMicro()
36+
args := strings.ReplaceAll(string(req.Params), "\n", "")
37+
logger.Logf("rpc call=%s, args=%s, take=%dμs", req.Method, args, (t2 - t1))
38+
return resp
39+
}
40+
}
41+
}

rpc/middleware/validation.go

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//Package middleware provides middlewares for rpc server
2+
//
3+
//Copyright (C) 2022 Alexander Kiryukhin <[email protected]>
4+
//
5+
//This file is part of go.neonxp.dev/jsonrpc2 project.
6+
//
7+
//This program is free software: you can redistribute it and/or modify
8+
//it under the terms of the GNU General Public License as published by
9+
//the Free Software Foundation, either version 3 of the License, or
10+
//(at your option) any later version.
11+
//
12+
//This program is distributed in the hope that it will be useful,
13+
//but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
//GNU General Public License for more details.
16+
//
17+
//You should have received a copy of the GNU General Public License
18+
//along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
20+
package middleware
21+
22+
import (
23+
"context"
24+
"encoding/json"
25+
"fmt"
26+
"strings"
27+
28+
"github.com/qri-io/jsonschema"
29+
30+
"go.neonxp.dev/jsonrpc2/rpc"
31+
)
32+
33+
type MethodSchema struct {
34+
Request jsonschema.Schema
35+
Response jsonschema.Schema
36+
}
37+
38+
func Validation(serviceSchema map[string]MethodSchema) (rpc.Middleware, error) {
39+
return func(handler rpc.RpcHandler) rpc.RpcHandler {
40+
return func(ctx context.Context, req *rpc.RpcRequest) *rpc.RpcResponse {
41+
if rs, ok := serviceSchema[strings.ToLower(req.Method)]; ok {
42+
if errResp := formatError(ctx, req.Id, rs.Request, req.Params); errResp != nil {
43+
return errResp
44+
}
45+
resp := handler(ctx, req)
46+
if errResp := formatError(ctx, req.Id, rs.Response, resp.Result); errResp != nil {
47+
return errResp
48+
}
49+
return resp
50+
}
51+
return handler(ctx, req)
52+
}
53+
}, nil
54+
}
55+
56+
func formatError(ctx context.Context, requestId any, schema jsonschema.Schema, data json.RawMessage) *rpc.RpcResponse {
57+
errs, err := schema.ValidateBytes(ctx, data)
58+
if err != nil {
59+
return rpc.ErrorResponse(requestId, err)
60+
}
61+
if errs != nil && len(errs) > 0 {
62+
messages := []string{}
63+
for _, msg := range errs {
64+
messages = append(messages, fmt.Sprintf("%s: %s", msg.PropertyPath, msg.Message))
65+
}
66+
return rpc.ErrorResponse(requestId, rpc.Error{
67+
Code: rpc.ErrCodeInvalidParams,
68+
Message: strings.Join(messages, "\n"),
69+
})
70+
}
71+
return nil
72+
}

rpc/options.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919

2020
package rpc
2121

22-
import "go.neonxp.dev/jsonrpc2/transport"
22+
import (
23+
"go.neonxp.dev/jsonrpc2/transport"
24+
)
2325

2426
type Option func(s *RpcServer)
2527

0 commit comments

Comments
 (0)