aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md33
-rw-r--r--examples/http/main.go10
-rw-r--r--http/server.go33
-rw-r--r--rpc/errors.go (renamed from errors.go)4
-rw-r--r--rpc/logger.go (renamed from logger.go)2
-rw-r--r--rpc/server.go (renamed from server.go)53
-rw-r--r--rpc/wrapper.go (renamed from wrapper.go)2
7 files changed, 80 insertions, 57 deletions
diff --git a/README.md b/README.md
index 13ff84b..750c251 100644
--- a/README.md
+++ b/README.md
@@ -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) {
diff --git a/examples/http/main.go b/examples/http/main.go
index 730fc03..5783c05 100644
--- a/examples/http/main.go
+++ b/examples/http/main.go
@@ -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)
}
diff --git a/http/server.go b/http/server.go
new file mode 100644
index 0000000..5fca66a
--- /dev/null
+++ b/http/server.go
@@ -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)
+}
diff --git a/errors.go b/rpc/errors.go
index cd87fbb..1af84ee 100644
--- a/errors.go
+++ b/rpc/errors.go
@@ -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"`
diff --git a/logger.go b/rpc/logger.go
index 7907e4f..25fe2d6 100644
--- a/logger.go
+++ b/rpc/logger.go
@@ -1,4 +1,4 @@
-package jsonrpc2
+package rpc
import "log"
diff --git a/server.go b/rpc/server.go
index 5625f78..12f07e8 100644
--- a/server.go
+++ b/rpc/server.go
@@ -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),
diff --git a/wrapper.go b/rpc/wrapper.go
index 8058a27..0bc0a5c 100644
--- a/wrapper.go
+++ b/rpc/wrapper.go
@@ -1,4 +1,4 @@
-package jsonrpc2
+package rpc
import (
"context"