I've taken up Go recently, and I've been loving it so far.
Here's a tiny JSON-RPC implementation. (I know that the formatting's not quite idiomatic, and to be honest, I don't care, this is more for myself than anyone else.)
package libaux
import (
"bytes"
"fmt"
"encoding/json"
"net/http"
)
const (
ctJSON = "application/json charset=UTF-8"
)
type RPCError struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
Orig error `json:"-"`
}
func (err RPCError) Error() string {
if err.Orig != nil {
return err.Orig.Error()
}
return fmt.Sprintf("JSON-RPC: (%d) %s", err.Code, err.Message)
}
type RPCCallable func(...interface{}) (interface{}, *RPCError)
type RPCServer struct {
methods map[string]RPCCallable
}
func (rh *RPCServer) Call(name string, args ...interface{}) (result interface{}, err *RPCError) {
defer func() {
if err_ := recover(); err_ != nil {
err = &RPCError{ -32603, "Internal error", "Method call panicked", nil }
switch x := err_.(type) {
case RPCError:
err = &x
case *RPCError:
err = x
case error:
err.Orig = x
}
}
}()
method, ok := rh.methods[name]
if ! ok {
return nil, &RPCError{ -32601, "Method not found", nil, nil }
}
result, err = method(args...)
return
}
func (rh *RPCServer) Register(name string, callable RPCCallable) {
if rh.methods == nil {
rh.methods = make(map[string]RPCCallable)
}
rh.methods[name] = callable
}
func (rh *RPCServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", ctJSON)
if r.Body == nil {
out, _ := json.Marshal(map[string]interface{}{
"jsonrpc": "2.0",
"id": nil,
"error": RPCError{ -32600, "Invalid Request",
"Request didn't have a body.", nil },
})
w.WriteHeader(http.StatusBadRequest)
w.Write(out)
return
}
buf := new(bytes.Buffer)
if _, err := buf.ReadFrom(r.Body); err != nil {
out, _ := json.Marshal(map[string]interface{}{
"jsonrpc": "2.0",
"id": nil,
"error": RPCError{ -32600, "Invalid Request",
"Request didn't have a body.", nil },
})
w.WriteHeader(http.StatusBadRequest)
w.Write(out)
return
}
req := buf.Bytes()
var requests []interface{}
if json.Unmarshal(req, &requests) == nil {
allNil := true
responses := make([]map[string]interface{}, len(requests))
for i, request_ := range requests {
request, ok := request_.(map[string]interface{})
if ! ok {
allNil = false
responses[i] = map[string]interface{}{
"jsonrpc": "2.0",
"id": nil,
"error": RPCError{ -32600, "Invalid Request", nil, nil },
}
continue
}
response := rh.handleRequest(request)
if response != nil {
allNil = false
}
responses[i] = response
}
if allNil {
w.Header().Del("Content-Type")
w.WriteHeader(http.StatusNoContent)
return
}
out, err := json.Marshal(responses)
if err != nil {
out, _ := json.Marshal(map[string]interface{}{
"jsonrpc": "2.0",
"id": nil,
"error": RPCError{ -32000, "Serialization failure", nil, err },
})
w.WriteHeader(http.StatusInternalServerError)
w.Write(out)
return
}
w.Write(out)
return
}
var request map[string]interface{}
if err := json.Unmarshal(req, &request); err != nil {
out, _ := json.Marshal(map[string]interface{}{
"jsonrpc": "2.0",
"id": nil,
"error": RPCError{ -32700, "Parse error", nil, err },
})
w.WriteHeader(http.StatusBadRequest)
w.Write(out)
return
}
response := rh.handleRequest(request)
if response == nil {
w.Header().Del("Content-Type")
w.WriteHeader(http.StatusNoContent)
return
}
out, err := json.Marshal(response)
if err != nil {
out, _ = json.Marshal(map[string]interface{}{
"jsonrpc": "2.0",
"id": request["id"],
"error": RPCError{ -32000, "Serialization failure", nil, err },
})
w.WriteHeader(http.StatusInternalServerError)
w.Write(out)
return
}
w.Write(out)
}
func (rh *RPCServer) handleRequest(rq map[string]interface{}) (out map[string]interface{}) {
out = map[string]interface{}{
"jsonrpc": "2.0",
"id": nil,
}
if rq["jsonrpc"] != "2.0" {
out["error"] = RPCError{ -32600, "Invalid Request",
"This server only supports JSONRPC 2.0.", nil }
return
}
wantsResponse := true
id, ok := rq["id"]
if ! ok {
wantsResponse = false
} else {
out["id"] = id
}
method, ok := rq["method"].(string)
if ! ok {
if ! wantsResponse {
return nil
}
out["error"] = RPCError{ -32600, "Invalid Request",
"No method specified.", nil }
return
}
var params []interface{}
if params_, ok := rq["params"]; ok {
params, ok = params_.([]interface{})
if ! ok {
if paramMap, ok := params_.(map[string]interface{}); ok {
params = []interface{}{ paramMap }
} else {
if ! wantsResponse {
return nil
}
out["error"] = RPCError{ -32600, "Invalid Request",
"Params should either be an array or an object.", nil }
return
}
}
} else {
params = []interface{}{ }
}
result, err := rh.Call(method, params...)
if err != nil {
if ! wantsResponse {
return nil
}
out["error"] = err
return
}
if ! wantsResponse {
return nil
}
out["result"] = result
return
}