Skip to content

Commit f53920b

Browse files
authored
net: add jsonrpc module (#26330)
1 parent c629288 commit f53920b

File tree

10 files changed

+1440
-0
lines changed

10 files changed

+1440
-0
lines changed

examples/jsonrpc/client.v

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
module main
2+
3+
import net
4+
import net.jsonrpc
5+
6+
fn main() {
7+
addr := '127.0.0.1:42228'
8+
mut stream := net.dial_tcp(addr)!
9+
mut log_inter := jsonrpc.LoggingInterceptor{}
10+
mut inters := jsonrpc.Interceptors{
11+
event: [log_inter.on_event]
12+
encoded_request: [log_inter.on_encoded_request]
13+
request: [log_inter.on_request]
14+
response: [log_inter.on_response]
15+
encoded_response: [log_inter.on_encoded_response]
16+
}
17+
18+
mut c := jsonrpc.new_client(jsonrpc.ClientConfig{
19+
stream: stream
20+
interceptors: inters
21+
})
22+
23+
println('TCP JSON-RPC client on ${addr}')
24+
25+
d1 := c.request('kv.delete', {
26+
'key': 'foo'
27+
}, 'kv.delete')!
28+
println('RESULT: ${d1}')
29+
30+
res := c.batch([
31+
jsonrpc.new_request('kv.create', {
32+
'key': 'foo'
33+
'value': 'bar'
34+
}, 'kv.create'),
35+
jsonrpc.new_request('kv.create', {
36+
'key': 'bar'
37+
'value': 'foo'
38+
}, 'kv.create'),
39+
])!
40+
println('RESULT: ${res}')
41+
42+
c.notify('kv.create', {
43+
'key': 'bazz'
44+
'value': 'barr'
45+
})!
46+
}

examples/jsonrpc/server.v

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
module main
2+
3+
import net
4+
import sync
5+
import net.jsonrpc
6+
import log
7+
8+
// ---- CRUD domain ----
9+
struct KvItem {
10+
key string
11+
value string
12+
}
13+
14+
struct KvKey {
15+
key string
16+
}
17+
18+
// ---- Handler ----
19+
struct KvStore {
20+
mut:
21+
mu &sync.RwMutex = sync.new_rwmutex()
22+
store map[string]string
23+
}
24+
25+
fn (mut s KvStore) create(key string, value string) bool {
26+
s.mu.@lock()
27+
defer { s.mu.unlock() }
28+
if key in s.store {
29+
return false
30+
}
31+
s.store[key] = value
32+
return true
33+
}
34+
35+
fn (mut s KvStore) get(key string) ?string {
36+
s.mu.@rlock()
37+
defer { s.mu.runlock() }
38+
if value := s.store[key] {
39+
return value
40+
}
41+
return none
42+
}
43+
44+
fn (mut s KvStore) update(key string, value string) bool {
45+
s.mu.@lock()
46+
defer { s.mu.unlock() }
47+
if key in s.store {
48+
s.store[key] = value
49+
return true
50+
}
51+
return false
52+
}
53+
54+
fn (mut s KvStore) delete(key string) bool {
55+
s.mu.@lock()
56+
defer { s.mu.unlock() }
57+
if key in s.store {
58+
s.store.delete(key)
59+
return true
60+
}
61+
return false
62+
}
63+
64+
fn (s KvStore) dump() map[string]string {
65+
return s.store
66+
}
67+
68+
@[heap]
69+
struct KvHandler {
70+
mut:
71+
store KvStore
72+
}
73+
74+
fn (mut h KvHandler) handle_create(req &jsonrpc.Request, mut wr jsonrpc.ResponseWriter) {
75+
p := req.decode_params[KvItem]() or {
76+
wr.write_error(jsonrpc.invalid_params)
77+
return
78+
}
79+
if p.key.len == 0 {
80+
wr.write_error(jsonrpc.invalid_params)
81+
return
82+
}
83+
log.warn('params=${p}')
84+
if !h.store.create(p.key, p.value) {
85+
wr.write_error(jsonrpc.ResponseError{ // custom app-level error code
86+
code: -32010
87+
message: 'Key already exists'
88+
data: p.key
89+
})
90+
return
91+
}
92+
93+
wr.write({
94+
'ok': true
95+
})
96+
}
97+
98+
fn (mut h KvHandler) handle_get(req &jsonrpc.Request, mut wr jsonrpc.ResponseWriter) {
99+
p := req.decode_params[KvKey]() or {
100+
wr.write_error(jsonrpc.invalid_params)
101+
return
102+
}
103+
104+
value := h.store.get(p.key) or {
105+
wr.write_error(jsonrpc.ResponseError{
106+
code: -32004
107+
message: 'Not found'
108+
data: p.key
109+
})
110+
return
111+
}
112+
113+
wr.write(KvItem{ key: p.key, value: value })
114+
}
115+
116+
fn (mut h KvHandler) handle_update(req &jsonrpc.Request, mut wr jsonrpc.ResponseWriter) {
117+
p := req.decode_params[KvItem]() or {
118+
wr.write_error(jsonrpc.invalid_params)
119+
return
120+
}
121+
122+
if !h.store.update(p.key, p.value) {
123+
wr.write_error(jsonrpc.ResponseError{
124+
code: -32004
125+
message: 'Not found'
126+
data: p.key
127+
})
128+
return
129+
}
130+
131+
wr.write({
132+
'ok': true
133+
})
134+
}
135+
136+
fn (mut h KvHandler) handle_delete(req &jsonrpc.Request, mut wr jsonrpc.ResponseWriter) {
137+
p := req.decode_params[KvKey]() or {
138+
wr.write_error(jsonrpc.invalid_params)
139+
return
140+
}
141+
142+
if !h.store.delete(p.key) {
143+
wr.write_error(jsonrpc.ResponseError{
144+
code: -32004
145+
message: 'Not found'
146+
data: p.key
147+
})
148+
return
149+
}
150+
151+
wr.write({
152+
'ok': true
153+
})
154+
}
155+
156+
fn (mut h KvHandler) handle_list(req &jsonrpc.Request, mut wr jsonrpc.ResponseWriter) {
157+
mut items := []KvItem{}
158+
for k, v in h.store.dump() {
159+
items << KvItem{
160+
key: k
161+
value: v
162+
}
163+
}
164+
items.sort(a.key < b.key)
165+
wr.write(items)
166+
}
167+
168+
// ---- Per-connection server loop ----
169+
// The jsonrpc.Server.start() reads from stream and writes to same stream.
170+
fn handle_conn(mut conn net.TcpConn, h jsonrpc.Handler) {
171+
defer { conn.close() or {} }
172+
173+
mut log_inter := jsonrpc.LoggingInterceptor{}
174+
mut inters := jsonrpc.Interceptors{
175+
event: [log_inter.on_event]
176+
encoded_request: [log_inter.on_encoded_request]
177+
request: [log_inter.on_request]
178+
response: [log_inter.on_response]
179+
encoded_response: [log_inter.on_encoded_response]
180+
}
181+
182+
mut srv := jsonrpc.new_server(jsonrpc.ServerConfig{
183+
stream: conn
184+
handler: h
185+
interceptors: inters
186+
})
187+
188+
jsonrpc.dispatch_event(inters.event, 'start', 'server started')
189+
srv.start()
190+
}
191+
192+
fn main() {
193+
mut s := KvStore{}
194+
mut h := KvHandler{
195+
store: s
196+
}
197+
mut r := jsonrpc.Router{}
198+
r.register('kv.create', h.handle_create)
199+
r.register('kv.get', h.handle_get)
200+
r.register('kv.update', h.handle_update)
201+
r.register('kv.delete', h.handle_delete)
202+
r.register('kv.list', h.handle_list)
203+
204+
addr := '127.0.0.1:42228'
205+
mut l := net.listen_tcp(.ip, addr)!
206+
println('TCP JSON-RPC server on ${addr} (Content-Length framing)')
207+
208+
for {
209+
mut c := l.accept()!
210+
println('Accepted')
211+
go handle_conn(mut c, r.handle_jsonrpc)
212+
}
213+
}

vlib/net/jsonrpc/README.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# JSONRPC
2+
[JSON-RPC 2.0](https://www.jsonrpc.org/specification) client+server implementation in pure V.
3+
4+
## Limitaions
5+
- Request/Response use only string id
6+
- JSON-RPC 1.0 incompatible
7+
8+
## Features
9+
- Request/Response single/batch json encoding/decoding
10+
- Server to work with any io.ReaderWriter
11+
- Server automatically manages batch Requests and builds batch Response
12+
- Client to work with any io.ReaderWriter
13+
- Interceptors for custom events, raw Request, Request, Response, raw Response
14+
15+
## Usage
16+
### Request/Response operations
17+
For both Request/Response constructors are provided and must be used for initialization.
18+
```v
19+
import net.jsonrpc
20+
21+
// jsonrpc.new_request(method, params, id)
22+
mut req := jsonrpc.new_request('kv.create', {
23+
'key': 'key'
24+
'value': 'value'
25+
}, 'kv.create.1')
26+
27+
println(req.encode())
28+
// '{"jsonrpc":"2.0","method":"kv.create","params":{"key":"key","value":"value"},"id":"kv.create.1"}'
29+
30+
// jsonrpc.new_response(result, error, id)
31+
mut resp := jsonrpc.new_response({
32+
'key': 'key'
33+
'value': 'value'
34+
}, jsonrpc.ResponseError{}, 'kv.create.1')
35+
36+
println(resp.encode())
37+
// '{"jsonrpc":"2.0","result":{"key":"key","value":"value"},"id":"kv.create.1"}'
38+
```
39+
To create a Notification, pass empty string as `Request.id` (`jsonrpc.Empty{}.str()` or
40+
`jsonrpc.empty.str()` can be used)
41+
(e.g. `jsonrpc.new_reponse('method', 'params', jsonrpc.empty.str())`).
42+
To omit Response.params in encoded json string pass `jsonrpc.Empty{}` or `jsonrpc.empty` as
43+
value in constructor (e.g. `jsonrpc.new_reponse('method', jsonrpc.empty, 'id')`).
44+
For Response only result or error fields can exist at the same time and not both simultaniously.
45+
If error passed to `jsonrpc.new_response()` the result value will be ignored on `Response.encode()`.
46+
The error field is not generated if `jsonrpc.ResponseError{}` provided as error into
47+
`jsonrpc.new_response()` (e.g. `jsonrpc.new_response("result", jsonrpc.ResponseError{}, "id")`).
48+
If the empty string passed as `Result.id` it will use `jsonrpc.null` as id (translates to json null)
49+
50+
### Client
51+
For full usage check client in [example](examples/client.v)
52+
```v
53+
import net
54+
import net.jsonrpc
55+
56+
addr := '127.0.0.1:42228'
57+
mut stream := net.dial_tcp(addr)!
58+
mut c := jsonrpc.new_client(jsonrpc.ClientConfig{
59+
stream: stream
60+
})
61+
62+
c.notify('kv.create', {
63+
'key': 'bazz'
64+
'value': 'barr'
65+
})!
66+
```
67+
Client can work with any `io.ReaderWriter` provided into stream field value.
68+
69+
### Server
70+
For ready key/value im-memory storage realized with server check this [example](examples/main.v)
71+
```v
72+
import net
73+
import net.jsonrpc
74+
75+
fn handle_test(req &jsonrpc.Request, mut wr jsonrpc.ResponseWriter) {
76+
p := req.decode_params[string]() or {
77+
wr.write_error(jsonrpc.invalid_params)
78+
return
79+
}
80+
81+
wr.write(p)
82+
}
83+
84+
fn handle_conn(mut conn net.TcpConn) {
85+
defer { conn.close() or {} }
86+
87+
mut srv := jsonrpc.new_server(jsonrpc.ServerConfig{
88+
stream: conn
89+
handler: handle_test
90+
})
91+
92+
srv.start()
93+
}
94+
95+
addr := '127.0.0.1:42228'
96+
mut l := net.listen_tcp(.ip, addr)!
97+
println('TCP JSON-RPC server on ${addr} (Content-Length framing)')
98+
99+
for {
100+
mut c := l.accept()!
101+
println('Accepted')
102+
go handle_conn(mut c)
103+
}
104+
```
105+
Server can work with any `io.ReaderWriter` provided into stream field value.
106+
Server requires `jsonrpc.Handler = fn(req &jsonrpc.Request, mut wr jsonrpc.ResponseWriter)`
107+
to pass decoded `jsonrpc.Request` and to write `jsonrpc.Response` into `jsonrpc.ResponseWriter`.
108+
On Notification Server does call `jsonrpc.Handler` but it ingores written `jsonrpc.Response`.
109+
110+
### Handler
111+
`jsonrpc.Handler = fn(req &jsonrpc.Request, mut wr jsonrpc.ResponseWriter)` is the function that
112+
operates the decoded `jsonrpc.Request` and writes `jsonrpc.Response` into `jsonrpc.ResponseWriter`.
113+
Before every return `wr.write()` or `wr.write_error()` must be called so the server do not stuck
114+
waiting for `jsonrpc.Response` to be written. Also only `wr.write()` or `wr.write_error()` must
115+
be called before return and not both.
116+
117+
### Router
118+
The simple `jsonrpc.Router` is provided to register `jsonrpc.Handler` to handle specific method.
119+
The `jsonrpc.Router.handle_jsonrpc` must be passed into `jsonrpc.Server.handler` to handle requests.
120+
If `jsonrpc.Request.method` has no registered `jsonrpc.Handler`, the router will respond
121+
with `jsonrpc.method_not_found` error
122+
123+
### Interceptors
124+
Both `jsonrpc.Client` and `jsonrpc.Server` support `jsonrpc.Interceptors` - the collection of
125+
on event handlers. There is implementation of all supported interceptors called
126+
`jsonrpc.LoggingInterceptor`.

0 commit comments

Comments
 (0)