nfsense/internal/jsonrpc/handler.go

100 lines
2.7 KiB
Go

package jsonrpc
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"reflect"
"runtime/debug"
"golang.org/x/exp/slog"
"nfsense.net/nfsense/internal/session"
)
type Handler struct {
methods map[string]method
maxRequestSize int64
}
func NewHandler(maxRequestSize int64) *Handler {
return &Handler{
methods: map[string]method{},
maxRequestSize: maxRequestSize,
}
}
func (h *Handler) HandleRequest(ctx context.Context, s *session.Session, r io.Reader, w io.Writer) error {
defer func() {
if r := recover(); r != nil {
slog.Error("Recovered Panic Handling JSONRPC Request", "err", fmt.Errorf("%v", r), "stack", debug.Stack())
}
}()
var req request
bufferedRequest := new(bytes.Buffer)
reqSize, err := bufferedRequest.ReadFrom(io.LimitReader(r, h.maxRequestSize+1))
if err != nil {
return respondError(w, "", ErrInternalError, fmt.Errorf("Reading Request: %w", err))
}
if reqSize > h.maxRequestSize {
return respondError(w, "", ErrParse, fmt.Errorf("Request exceeds Max Request Size"))
}
dec := json.NewDecoder(bufferedRequest)
dec.DisallowUnknownFields()
err = dec.Decode(&req)
if err != nil {
return respondError(w, "", ErrParse, fmt.Errorf("Decodeing Request: %w", err))
}
if req.Jsonrpc != "2.0" {
return respondError(w, req.ID, ErrMethodNotFound, fmt.Errorf("Unsupported Jsonrpc version %v", req.Jsonrpc))
}
if s == nil {
return respondError(w, req.ID, 401, fmt.Errorf("Unauthorized"))
}
method, ok := h.methods[req.Method]
if !ok {
return respondError(w, req.ID, ErrMethodNotFound, fmt.Errorf("Unknown Method %v", req.Method))
}
p := reflect.New(method.inType)
paramPointer := p.Interface()
if len(req.Params) != 0 {
dec = json.NewDecoder(bytes.NewReader(req.Params))
dec.DisallowUnknownFields()
err = dec.Decode(paramPointer)
if err != nil {
return respondError(w, req.ID, ErrInvalidParams, fmt.Errorf("Decoding Parameters: %w", err))
}
}
params := make([]reflect.Value, 3)
params[0] = method.subSystem
params[1] = reflect.ValueOf(ctx)
params[2] = reflect.ValueOf(paramPointer).Elem()
defer func() {
if r := recover(); r != nil {
slog.Error("Recovered Panic Executing API Method", "err", fmt.Errorf("%v", r), "method", req.Method, "params", fmt.Sprintf("%+v", params[2]), "id", req.ID, "stack", debug.Stack())
respondError(w, req.ID, ErrInternalError, fmt.Errorf("%v", r))
}
}()
res := method.handlerFunc.Call(params)
result := res[0].Interface()
if !res[1].IsNil() {
reqerr := res[1].Interface().(error)
slog.Error("API Method", "err", reqerr, "method", req.Method, "id", req.ID, "params", fmt.Sprintf("%+v", params[2]))
respondError(w, req.ID, ErrInternalError, reqerr)
return nil
}
respondResult(w, req.ID, result)
return nil
}