Small refactoring

This commit is contained in:
Александр Кирюхин 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: ## Features:
- [x] Batch request and responses - [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 ```go
s := jsonrpc2.New() import "github.com/neonxp/jsonrpc2/http"
...
s := http.New()
``` ```
2. Write handler: 2. Write handler:
```go ```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) 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 ```go
s.Register("multiply", jsonrpc2.Wrap(Multiply)) s.Register("multiply", rpc.Wrap(Multiply))
``` ```
4. Use server as common http handler: 4. Use server as common http handler:
```go ```go
http.ListenAndServe(":8000", s) http.ListenAndServe(":8000", s)
``` ```
## Custom transport
See [http/server.go](/http/server.go) for example of transport implementation.
## Complete example ## Complete example
[Full code](/examples/http) [Full code](/examples/http)
@ -42,13 +48,14 @@ import (
"context" "context"
"net/http" "net/http"
"github.com/neonxp/jsonrpc2" httpRPC "github.com/neonxp/jsonrpc2/http"
"github.com/neonxp/jsonrpc2/rpc"
) )
func main() { func main() {
s := jsonrpc2.New() s := httpRPC.New()
s.Register("multiply", jsonrpc2.Wrap(Multiply)) // Register handlers s.Register("multiply", rpc.Wrap(Multiply))
s.Register("divide", jsonrpc2.Wrap(Divide)) s.Register("divide", rpc.Wrap(Divide))
http.ListenAndServe(":8000", s) http.ListenAndServe(":8000", s)
} }

View file

@ -5,13 +5,15 @@ import (
"errors" "errors"
"net/http" "net/http"
"github.com/neonxp/jsonrpc2" httpRPC "github.com/neonxp/jsonrpc2/http"
"github.com/neonxp/jsonrpc2/rpc"
) )
func main() { func main() {
s := jsonrpc2.New() s := httpRPC.New()
s.Register("multiply", jsonrpc2.Wrap(Multiply))
s.Register("divide", jsonrpc2.Wrap(Divide)) s.Register("multiply", rpc.Wrap(Multiply))
s.Register("divide", rpc.Wrap(Divide))
http.ListenAndServe(":8000", s) 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" import "fmt"
@ -20,7 +20,7 @@ var errorMap = map[int]string{
-32000: "Other error", -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 { type Error struct {
Code int `json:"code"` Code int `json:"code"`

View file

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

View file

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

View file

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