https://github.com/floatpane/go-uds-jsonrpc
Tiny newline-delimited JSON-RPC over Unix domain sockets, for Go.
https://github.com/floatpane/go-uds-jsonrpc
background-service daemon go golang golang-library ipc json-lines json-rpc jsonrpc ndjson pidfile pubsub rpc server-push single-instance uds unix-domain-sockets unix-sockets xdg zero-dependencies
Last synced: about 3 hours ago
JSON representation
Tiny newline-delimited JSON-RPC over Unix domain sockets, for Go.
- Host: GitHub
- URL: https://github.com/floatpane/go-uds-jsonrpc
- Owner: floatpane
- License: mit
- Created: 2026-05-30T14:57:19.000Z (23 days ago)
- Default Branch: master
- Last Pushed: 2026-06-19T10:51:25.000Z (3 days ago)
- Last Synced: 2026-06-19T12:27:21.515Z (3 days ago)
- Topics: background-service, daemon, go, golang, golang-library, ipc, json-lines, json-rpc, jsonrpc, ndjson, pidfile, pubsub, rpc, server-push, single-instance, uds, unix-domain-sockets, unix-sockets, xdg, zero-dependencies
- Language: Go
- Homepage: https://udsrpc.floatpane.com
- Size: 104 KB
- Stars: 2
- Watchers: 0
- Forks: 0
- Open Issues: 9
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- Funding: .github/FUNDING.yml
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
- Security: SECURITY.md
Awesome Lists containing this project
README
# go-uds-jsonrpc
**Tiny newline-delimited JSON-RPC over Unix domain sockets, for Go.**
[](https://golang.org)
[](https://pkg.go.dev/github.com/floatpane/go-uds-jsonrpc)
[](https://github.com/floatpane/go-uds-jsonrpc/releases)
[](https://github.com/floatpane/go-uds-jsonrpc/actions/workflows/ci.yml)
[](LICENSE)
`go-uds-jsonrpc` is what `net/rpc` should be in 2026: JSON on the wire, Unix-socket framing, server-pushed events, no gob, no reflection. It sits between `net/rpc` (too rigid) and gRPC (too heavy) for the very common case of *one daemon, several local clients, one machine*.
## Features
- **Three message shapes** — Request, Response, Event (server-pushed) — on one TCP-ish stream of `\n`-terminated JSON.
- **Cross-platform PID file + IsRunning** — Unix uses signal-0; Windows uses OpenProcess + GetExitCodeProcess.
- **XDG-aware socket paths** — `$XDG_RUNTIME_DIR//` on Linux, `~/Library/Caches//` on macOS, sensible fallback on others.
- **Server scaffolding** — handler registry, panic recovery, broadcast helpers, OnConnect / OnDisconnect hooks, context-driven shutdown.
- **Signal handler** — SIGTERM/SIGINT → shutdown, SIGHUP → reload, both wired in one call.
- **Zero dependencies.** stdlib-only.
## Install
```bash
go get github.com/floatpane/go-uds-jsonrpc
```
Requires Go 1.26+.
## Usage
### Server
```go
package main
import (
"context"
"encoding/json"
"log"
"net"
"os"
"os/signal"
"syscall"
udsrpc "github.com/floatpane/go-uds-jsonrpc"
)
func main() {
const app = "myd"
if err := udsrpc.EnsureRuntimeDir(app); err != nil {
log.Fatal(err)
}
if pid, running := udsrpc.IsRunning(udsrpc.PIDPath(app)); running {
log.Fatalf("already running (PID %d)", pid)
}
if err := udsrpc.WritePID(udsrpc.PIDPath(app)); err != nil {
log.Fatal(err)
}
defer udsrpc.RemovePID(udsrpc.PIDPath(app))
_ = os.Remove(udsrpc.SocketPath(app))
l, err := net.Listen("unix", udsrpc.SocketPath(app))
if err != nil {
log.Fatal(err)
}
defer l.Close()
s := udsrpc.NewServer()
s.Handle("Ping", func(_ context.Context, _ *udsrpc.Conn, _ json.RawMessage) (any, error) {
return map[string]bool{"pong": true}, nil
})
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer cancel()
log.Println(s.Serve(ctx, l))
}
```
### Client
```go
package main
import (
"encoding/json"
"fmt"
"log"
"net"
udsrpc "github.com/floatpane/go-uds-jsonrpc"
)
func main() {
conn, err := net.Dial("unix", udsrpc.SocketPath("myd"))
if err != nil {
log.Fatal(err)
}
c := udsrpc.NewConn(conn)
defer c.Close()
if err := c.Send(&udsrpc.Request{ID: 1, Method: "Ping"}); err != nil {
log.Fatal(err)
}
msg, err := c.ReceiveMessage()
if err != nil {
log.Fatal(err)
}
var result map[string]bool
json.Unmarshal(msg.Response.Result, &result)
fmt.Println("pong:", result["pong"])
}
```
### Push events from server to all clients
```go
go func() {
for range time.Tick(5 * time.Second) {
s.Broadcast("Tick", map[string]int64{"unix": time.Now().Unix()})
}
}()
```
Clients receive these as `Event` messages when they call `ReceiveMessage()`.
## Wire format
Every message is one JSON object followed by `\n`:
```
{"id":1,"method":"Ping","params":{}}
{"id":1,"result":{"pong":true}}
{"type":"Tick","data":{"unix":1748000000}}
```
`DecodeMessage` discriminates by inspecting the keys:
- has `"type"` → `Event`
- has `"method"` → `Request`
- otherwise → `Response`
Standard error codes (borrowed from JSON-RPC 2.0):
| Code | Constant | Meaning |
|----------|-----------------------|--------------------------|
| `-32700` | `ErrCodeParse` | Invalid JSON received |
| `-32600` | `ErrCodeInvalidReq` | Not a valid Request |
| `-32601` | `ErrCodeNotFound` | Method not registered |
| `-32602` | `ErrCodeInvalidParams`| Method exists, bad params|
| `-32603` | `ErrCodeInternal` | Handler error |
Return an `*Error` from a handler to forward a specific code/message to the client. Any other non-nil error becomes `ErrCodeInternal` with the message.
## Documentation
Full API reference: [pkg.go.dev/github.com/floatpane/go-uds-jsonrpc](https://pkg.go.dev/github.com/floatpane/go-uds-jsonrpc)
## Contributing
PRs welcome. See [CONTRIBUTING.md](CONTRIBUTING.md).
## Security
Report vulnerabilities privately via [SECURITY.md](SECURITY.md).
## License
MIT. See [LICENSE](LICENSE).