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

Commit c651659

Browse files
committed
initial commit
0 parents  commit c651659

18 files changed

+1229
-0
lines changed

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2020 fastjsonrpc contributors
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# fastjsonrpc [![GoDoc](https://godoc.org/github.com/serjvanilla/fastjsonrpc?status.svg)](http://godoc.org/github.com/serjvanilla/fastjsonrpc)
2+
Fast JSON-RPC 2.0 implementation for [fasthttp](https://github.com/valyala/fasthttp) server.
3+
4+
```
5+
$ GOMAXPROCS=1 go test -bench=. -benchmem -benchtime 10s
6+
BenchmarkEchoHandler 20206782 588 ns/op 0 B/op 0 allocs/op
7+
BenchmarkSumHandler 16310700 732 ns/op 0 B/op 0 allocs/op
8+
BenchmarkBatchSumHandler 7480480 1591 ns/op 0 B/op 0 allocs/op
9+
```
10+
11+
## Install
12+
```
13+
go get -u github.com/serjvanilla/fastjsonrpc
14+
```
15+
16+
## TODO
17+
- [ ] Documentation
18+
- [ ] Examples
19+
- [ ] End-to-end benchmarks
20+
- [ ] Migration from https://github.com/osamingo/jsonrpc examples

benchmarks_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package fastjsonrpc_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/serjvanilla/fastjsonrpc"
7+
"github.com/valyala/fasthttp"
8+
)
9+
10+
func BenchmarkEchoHandler(b *testing.B) {
11+
r := fastjsonrpc.NewRepository()
12+
r.Register("echo", func(ctx *fastjsonrpc.Request) {
13+
ctx.Result(ctx.Params())
14+
})
15+
16+
ctx := new(fasthttp.RequestCtx)
17+
ctx.Request.Header.SetMethod(fasthttp.MethodPost)
18+
ctx.Request.SetBodyString(`{"jsonrpc":"2.0","method":"echo","params":"hello","id":1}`)
19+
20+
handler := r.RequestHandler()
21+
22+
for i := 0; i < b.N; i++ {
23+
ctx.Response.ResetBody()
24+
handler(ctx)
25+
}
26+
}
27+
28+
func BenchmarkSumHandler(b *testing.B) {
29+
r := fastjsonrpc.NewRepository()
30+
r.Register("sum", func(req *fastjsonrpc.Request) {
31+
params := req.Params()
32+
33+
a := params.GetInt("a")
34+
b := params.GetInt("b")
35+
36+
req.Result(req.Arena().NewNumberInt(a + b))
37+
})
38+
39+
ctx := new(fasthttp.RequestCtx)
40+
ctx.Request.Header.SetMethod(fasthttp.MethodPost)
41+
ctx.Request.SetBodyString(`{"jsonrpc":"2.0","method":"sum","params":{"a":7,"b":42},"id":1}`)
42+
43+
handler := r.RequestHandler()
44+
45+
b.ResetTimer()
46+
47+
for i := 0; i < b.N; i++ {
48+
ctx.Response.ResetBody()
49+
handler(ctx)
50+
}
51+
}
52+
53+
func BenchmarkBatchSumHandler(b *testing.B) {
54+
r := fastjsonrpc.NewRepository()
55+
r.Register("sum", func(ctx *fastjsonrpc.Request) {
56+
params := ctx.Params()
57+
58+
a := params.GetInt("a")
59+
b := params.GetInt("b")
60+
61+
ctx.Result(ctx.Arena().NewNumberInt(a + b))
62+
})
63+
64+
ctx := new(fasthttp.RequestCtx)
65+
ctx.Request.Header.SetMethod(fasthttp.MethodPost)
66+
ctx.Request.SetBodyString(
67+
`[{"jsonrpc":"2.0","method":"sum","params":{"a":7,"b":42},"id":1},
68+
{"jsonrpc":"2.0","method":"sum","params":{"a":42,"b":7},"id":2}]`)
69+
70+
handler := r.RequestHandler()
71+
72+
b.ResetTimer()
73+
74+
for i := 0; i < b.N; i++ {
75+
ctx.Response.ResetBody()
76+
handler(ctx)
77+
}
78+
}

buffer_pool.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package fastjsonrpc
2+
3+
import "github.com/valyala/bytebufferpool"
4+
5+
var bufferpool bytebufferpool.Pool

doc.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/*
2+
Package fastjsonrpc provides fast JSON-RPC 2.0 server for use with fasthttp server.
3+
*/
4+
package fastjsonrpc

errors.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package fastjsonrpc
2+
3+
type errorCode int
4+
5+
const (
6+
errorCodeMethodNotFound errorCode = -32601
7+
ErrorCodeInvalidParams errorCode = -32602
8+
ErrorCodeInternalError errorCode = -32603
9+
)
10+
11+
var (
12+
renderedParseError = []byte(`{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"},"id":null}`)
13+
renderedInvalidRequest = []byte(`{"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid Request"},"id":null}`)
14+
)

go.mod

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module github.com/serjvanilla/fastjsonrpc
2+
3+
go 1.13
4+
5+
require (
6+
github.com/klauspost/compress v1.10.3 // indirect
7+
github.com/valyala/bytebufferpool v1.0.0
8+
github.com/valyala/fasthttp v1.9.0
9+
github.com/valyala/fastjson v1.5.0
10+
github.com/valyala/quicktemplate v1.4.1
11+
)

go.sum

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
2+
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
3+
github.com/klauspost/compress v1.8.2 h1:Bx0qjetmNjdFXASH02NSAREKpiaDwkO1DRZ3dV2KCcs=
4+
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
5+
github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8=
6+
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
7+
github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
8+
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
9+
github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w=
10+
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
11+
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
12+
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
13+
github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s=
14+
github.com/valyala/fasthttp v1.9.0 h1:hNpmUdy/+ZXYpGy0OBfm7K0UQTzb73W0T0U4iJIVrMw=
15+
github.com/valyala/fasthttp v1.9.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
16+
github.com/valyala/fastjson v1.5.0 h1:DGrb4wEYso2HdGLyLmNoyNCQnCWfjd8yhghPv5/5YQg=
17+
github.com/valyala/fastjson v1.5.0/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
18+
github.com/valyala/quicktemplate v1.4.1 h1:tEtkSN6mTCJlYVT7As5x4wjtkk2hj2thsb0M+AcAVeM=
19+
github.com/valyala/quicktemplate v1.4.1/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4=
20+
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
21+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
22+
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
23+
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
24+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
25+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

repository.go

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package fastjsonrpc
2+
3+
import (
4+
"bytes"
5+
6+
"github.com/valyala/fasthttp"
7+
"github.com/valyala/fastjson"
8+
)
9+
10+
func NewRepository() *Repository {
11+
return &Repository{
12+
handlers: make(map[string]RequestHandler),
13+
}
14+
}
15+
16+
// Repository is a JSON-RPC 2.0 methods repository.
17+
type Repository struct {
18+
contextPool requestPool
19+
parserPool fastjson.ParserPool
20+
21+
handlers map[string]RequestHandler
22+
}
23+
24+
// RequestHandler is suitable for using with fasthttp.
25+
func (r *Repository) RequestHandler() fasthttp.RequestHandler {
26+
return func(ctx *fasthttp.RequestCtx) {
27+
if !ctx.IsPost() {
28+
ctx.SetStatusCode(fasthttp.StatusMethodNotAllowed)
29+
return
30+
}
31+
32+
parser := r.parserPool.Get()
33+
34+
request, err := parser.ParseBytes(ctx.PostBody())
35+
if err != nil {
36+
_, _ = ctx.Write(renderedParseError)
37+
return
38+
}
39+
40+
rCtx := r.contextPool.Get()
41+
rCtx.ctx = ctx
42+
43+
ctx.SetContentType("application/json")
44+
45+
switch request.Type() {
46+
case fastjson.TypeObject:
47+
r.handleRequest(rCtx, request)
48+
case fastjson.TypeArray:
49+
r.handleBatchRequest(rCtx, request)
50+
default:
51+
_, _ = rCtx.response.Write(renderedInvalidRequest)
52+
}
53+
54+
_, _ = rCtx.response.WriteTo(ctx)
55+
56+
r.contextPool.Put(rCtx)
57+
r.parserPool.Put(parser)
58+
}
59+
}
60+
61+
// Register registers new method handler.
62+
func (r *Repository) Register(method string, handler RequestHandler) {
63+
r.handlers[method] = handler
64+
}
65+
66+
func (r *Repository) handleRequest(ctx *Request, request *fastjson.Value) {
67+
jsonrpc := request.GetStringBytes("jsonrpc")
68+
method := request.GetStringBytes("method")
69+
70+
if !bytes.Equal(jsonrpc, []byte(`2.0`)) || len(method) == 0 {
71+
ctx.writeByte(renderedInvalidRequest)
72+
return
73+
}
74+
75+
if id := request.Get("id"); id != nil {
76+
ctx.id = id.MarshalTo(ctx.id)
77+
}
78+
79+
handler, ok := r.handlers[string(method)]
80+
if !ok {
81+
ctx.Error(errorCodeMethodNotFound, "Method not found", nil)
82+
return
83+
}
84+
85+
ctx.method = method
86+
ctx.params = request.Get("params")
87+
88+
if ctx.params != nil {
89+
ctx.paramsBytes.B = ctx.params.MarshalTo(ctx.paramsBytes.B)
90+
}
91+
92+
defer func() {
93+
if recover() != nil {
94+
ctx.response.Reset()
95+
ctx.Error(ErrorCodeInternalError, "Internal error", nil)
96+
}
97+
}()
98+
99+
handler(ctx)
100+
}
101+
102+
func (r *Repository) handleBatchRequest(batchCtx *Request, requests *fastjson.Value) {
103+
requestsArr := requests.GetArray()
104+
if len(requestsArr) == 0 {
105+
batchCtx.writeByte(renderedInvalidRequest)
106+
return
107+
}
108+
109+
var (
110+
opened bool
111+
needComma bool
112+
hasResponse bool
113+
n int
114+
)
115+
116+
for _, request := range requestsArr {
117+
ctx := r.contextPool.Get()
118+
r.handleRequest(ctx, request)
119+
120+
hasResponse = ctx.response.Len() > 0
121+
122+
if !opened && hasResponse {
123+
_ = batchCtx.response.WriteByte('[')
124+
opened = true
125+
}
126+
127+
if needComma && hasResponse {
128+
_ = batchCtx.response.WriteByte(',')
129+
needComma = false
130+
}
131+
132+
n, _ = batchCtx.response.Write(ctx.response.B)
133+
if n != 0 {
134+
needComma = true
135+
}
136+
137+
r.contextPool.Put(ctx)
138+
}
139+
140+
if opened {
141+
_ = batchCtx.response.WriteByte(']')
142+
}
143+
}

0 commit comments

Comments
 (0)