Small refactoring

This commit is contained in:
Alexander Kiryukhin 2022-01-31 20:17:31 +03:00
parent 35fee8155b
commit 4cf58de9bb
No known key found for this signature in database
GPG key ID: 6DF7A2910D0699E9
7 changed files with 80 additions and 57 deletions

View file

@ -7,13 +7,15 @@ Go 1.18+ required
## Features:
- [x] Batch request and responses
- [ ] WebSockets
- [ ] WebSocket transport
## Usage
## Usage (http transport)
1. Create JSON-RPC 2.0 server:
1. Create JSON-RPC/HTTP server:
```go
s := jsonrpc2.New()
import "github.com/neonxp/jsonrpc2/http"
...
s := http.New()
```
2. Write handler:
```go
@ -22,15 +24,19 @@ Go 1.18+ required
}
```
Handler must have exact two arguments (context and input of any json serializable type) and exact two return values (output of any json serializable type and error)
3. Wrap handler with `jsonrpc2.Wrap` method and register it in server:
3. Wrap handler with `rpc.Wrap` method and register it in server:
```go
s.Register("multiply", jsonrpc2.Wrap(Multiply))
s.Register("multiply", rpc.Wrap(Multiply))
```
4. Use server as common http handler:
```go
http.ListenAndServe(":8000", s)
```
## Custom transport
See [http/server.go](/http/server.go) for example of transport implementation.
## Complete example
[Full code](/examples/http)
@ -39,18 +45,19 @@ Go 1.18+ required
package main
import (
"context"
"net/http"
"context"
"net/http"
"github.com/neonxp/jsonrpc2"
httpRPC "github.com/neonxp/jsonrpc2/http"
"github.com/neonxp/jsonrpc2/rpc"
)
func main() {
s := jsonrpc2.New()
s.Register("multiply", jsonrpc2.Wrap(Multiply)) // Register handlers
s.Register("divide", jsonrpc2.Wrap(Divide))
s := httpRPC.New()
s.Register("multiply", rpc.Wrap(Multiply))
s.Register("divide", rpc.Wrap(Divide))
http.ListenAndServe(":8000", s)
http.ListenAndServe(":8000", s)
}
func Multiply(ctx context.Context, args *Args) (int, error) {

View file

@ -5,13 +5,15 @@ import (
"errors"
"net/http"
"github.com/neonxp/jsonrpc2"
httpRPC "github.com/neonxp/jsonrpc2/http"
"github.com/neonxp/jsonrpc2/rpc"
)
func main() {
s := jsonrpc2.New()
s.Register("multiply", jsonrpc2.Wrap(Multiply))
s.Register("divide", jsonrpc2.Wrap(Divide))
s := httpRPC.New()
s.Register("multiply", rpc.Wrap(Multiply))
s.Register("divide", rpc.Wrap(Divide))
http.ListenAndServe(":8000", s)
}

33
http/server.go Normal file
View file

@ -0,0 +1,33 @@
package http
import (
"bufio"
"net/http"
"github.com/neonxp/jsonrpc2/rpc"
)
type Server struct {
*rpc.RpcServer
}
func New() *Server {
return &Server{RpcServer: rpc.New()}
}
func (r *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
writer.Header().Set("Content-Type", "application/json")
reader := bufio.NewReader(request.Body)
defer request.Body.Close()
firstByte, err := reader.Peek(1)
if err != nil {
r.Logger.Logf("Can't read body: %v", err)
rpc.WriteError(rpc.ErrCodeParseError, writer)
return
}
if string(firstByte) == "[" {
r.BatchRequest(request.Context(), reader, writer)
return
}
r.SingleRequest(request.Context(), reader, writer)
}

View file

@ -1,4 +1,4 @@
package jsonrpc2
package rpc
import "fmt"
@ -20,7 +20,7 @@ var errorMap = map[int]string{
-32000: "Other error",
}
//-32000 to -32099 Server error Reserved for implementation-defined server-errors.
//-32000 to -32099 RpcServer error Reserved for implementation-defined server-errors.
type Error struct {
Code int `json:"code"`

View file

@ -1,4 +1,4 @@
package jsonrpc2
package rpc
import "log"

View file

@ -1,42 +1,23 @@
package jsonrpc2
package rpc
import (
"bufio"
"context"
"encoding/json"
"io"
"net/http"
"sync"
)
const version = "2.0"
type Server struct {
type RpcServer struct {
Logger Logger
IgnoreNotifications bool
handlers map[string]Handler
mu sync.RWMutex
}
func (r *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
writer.Header().Set("Content-Type", "application/json")
buf := bufio.NewReader(request.Body)
defer request.Body.Close()
firstByte, err := buf.Peek(1)
if err != nil {
r.Logger.Logf("Can't read body: %v", err)
writeError(ErrCodeParseError, writer)
return
}
if string(firstByte) == "[" {
r.batchRequest(writer, request, buf)
return
}
r.singleRequest(writer, request, buf)
}
func New() *Server {
return &Server{
func New() *RpcServer {
return &RpcServer{
Logger: nopLogger{},
IgnoreNotifications: true,
handlers: map[string]Handler{},
@ -44,36 +25,36 @@ func New() *Server {
}
}
func (r *Server) Register(method string, handler Handler) {
func (r *RpcServer) Register(method string, handler Handler) {
r.mu.Lock()
defer r.mu.Unlock()
r.handlers[method] = handler
}
func (r *Server) singleRequest(writer http.ResponseWriter, request *http.Request, buf *bufio.Reader) {
func (r *RpcServer) SingleRequest(ctx context.Context, reader io.Reader, writer io.Writer) {
req := new(rpcRequest)
if err := json.NewDecoder(buf).Decode(req); err != nil {
if err := json.NewDecoder(reader).Decode(req); err != nil {
r.Logger.Logf("Can't read body: %v", err)
writeError(ErrCodeParseError, writer)
WriteError(ErrCodeParseError, writer)
return
}
resp := r.callMethod(request.Context(), req)
resp := r.callMethod(ctx, req)
if req.Id == nil && r.IgnoreNotifications {
// notification request
return
}
if err := json.NewEncoder(writer).Encode(resp); err != nil {
r.Logger.Logf("Can't write response: %v", err)
writeError(ErrCodeInternalError, writer)
WriteError(ErrCodeInternalError, writer)
return
}
}
func (r *Server) batchRequest(writer http.ResponseWriter, request *http.Request, buf *bufio.Reader) {
func (r *RpcServer) BatchRequest(ctx context.Context, reader io.Reader, writer io.Writer) {
var req []rpcRequest
if err := json.NewDecoder(buf).Decode(&req); err != nil {
if err := json.NewDecoder(reader).Decode(&req); err != nil {
r.Logger.Logf("Can't read body: %v", err)
writeError(ErrCodeParseError, writer)
WriteError(ErrCodeParseError, writer)
return
}
var responses []*rpcResponse
@ -82,7 +63,7 @@ func (r *Server) batchRequest(writer http.ResponseWriter, request *http.Request,
for _, j := range req {
go func(req rpcRequest) {
defer wg.Done()
resp := r.callMethod(request.Context(), &req)
resp := r.callMethod(ctx, &req)
if req.Id == nil && r.IgnoreNotifications {
// notification request
return
@ -93,11 +74,11 @@ func (r *Server) batchRequest(writer http.ResponseWriter, request *http.Request,
wg.Wait()
if err := json.NewEncoder(writer).Encode(responses); err != nil {
r.Logger.Logf("Can't write response: %v", err)
writeError(ErrCodeInternalError, writer)
WriteError(ErrCodeInternalError, writer)
}
}
func (r *Server) callMethod(ctx context.Context, req *rpcRequest) *rpcResponse {
func (r *RpcServer) callMethod(ctx context.Context, req *rpcRequest) *rpcResponse {
r.mu.RLock()
h, ok := r.handlers[req.Method]
r.mu.RUnlock()
@ -124,7 +105,7 @@ func (r *Server) callMethod(ctx context.Context, req *rpcRequest) *rpcResponse {
}
}
func writeError(code int, w io.Writer) {
func WriteError(code int, w io.Writer) {
_ = json.NewEncoder(w).Encode(rpcResponse{
Jsonrpc: version,
Error: NewError(code),

View file

@ -1,4 +1,4 @@
package jsonrpc2
package rpc
import (
"context"