Skip to content

Instantly share code, notes, and snippets.

@arianvp
Last active March 2, 2026 11:51
Show Gist options
  • Select an option

  • Save arianvp/314113d4309c7d7d3047535e167352ac to your computer and use it in GitHub Desktop.

Select an option

Save arianvp/314113d4309c7d7d3047535e167352ac to your computer and use it in GitHub Desktop.
Varlink HTTP

Keeps varlink connection open for the HTTP 1.1 connection. one varlink call per HTTP POST, and stream back results

$ go run .
2026/03/02 12:45:42 connecting to /run/systemd/userdb/io.systemd.Multiplexer
2026/03/02 12:45:42 io.systemd.UserDatabase.GetUserRecord
2026/03/02 12:45:42 io.systemd.UserDatabase.GetMemberships
2026/03/02 12:45:42 closing /run/systemd/userdb/io.systemd.Multiplexer
curl http://localhost:8080/io.systemd.UserDatabase.{GetUserRecord,GetMemberships}  --data '{"userName": "arian", "service":"io.systemd.NameServiceSwitch"}'
{"parameters":{"record":{"userName":"arian","uid":1000,"gid":100,"homeDirectory":"/home/arian","shell":"/run/current-system/sw/bin/bash","privileged":{"hashedPassword":xxxxx},"passwordChangeNow":false,"lastPasswordChangeUSec":1721001600000000,"status":{"bb9e3c32f25d42fb9996c2554c85a604":{"service":"io.systemd.NameServiceSwitch"}}},"incomplete":false}}
{"parameters":{"userName":"arian","groupName":"wheel"},"continues":true}
{"parameters":{"userName":"arian","groupName":"podman"},"continues":true}
{"parameters":{"userName":"arian","groupName":"nix-trusted-users"},"continues":true}
{"parameters":{"userName":"arian","groupName":"tss"}}
package main
import (
"bufio"
"context"
"encoding/json"
"io"
"log"
"net"
"net/http"
"sync"
)
type handler struct {
mu sync.Mutex
varlinkConns map[net.Conn]net.Conn
}
type varlinkConnKey struct{}
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := context.Cause(r.Context()); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// reuse varlink connection across requests on same HTTP 1.1 / 2 connection
varlinkConn := r.Context().Value(varlinkConnKey{}).(net.Conn)
// map path to varlink method
method := r.PathValue("method")
log.Print(method)
// map request body to varlink parameters
parameterBytes, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
parameters := json.RawMessage(parameterBytes)
req := struct {
Method string `json:"method"`
Parameters json.RawMessage `json:"parameters"`
More bool `json:"more"`
}{
Method: method,
Parameters: parameters,
More: true,
}
reqBytes, err := json.Marshal(req)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// send varlink request
if _, err := varlinkConn.Write(reqBytes); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
varlinkConn.Write([]byte{0})
// stream back (potentially multiple!) varlink responses (separated by nul byte),
// flushing the HTTP stream after each nul byte, mapping nul bytes to \r\n to ease use with HTTP
flusher, _ := w.(http.Flusher)
br := bufio.NewReader(varlinkConn)
for {
msg, err := br.ReadBytes(0)
if len(msg) > 0 {
body := msg[:len(msg)-1]
w.Write(body)
w.Write([]byte("\r\n"))
if flusher != nil {
flusher.Flush()
}
var resp struct {
Continues bool `json:"continues"`
}
json.Unmarshal(body, &resp)
if !resp.Continues {
return
}
}
if err != nil {
if err != io.EOF {
log.Print(err)
}
return
}
}
}
func main() {
h := &handler{
varlinkConns: make(map[net.Conn]net.Conn),
}
mux := http.NewServeMux()
mux.Handle("POST /{method}", h)
server := http.Server{
Addr: ":8080",
Handler: mux,
ConnState: func(conn net.Conn, state http.ConnState) {
if state == http.StateClosed {
h.mu.Lock()
unixConn, ok := h.varlinkConns[conn]
if ok {
delete(h.varlinkConns, conn)
}
h.mu.Unlock()
if ok {
log.Printf("closing %s", unixConn.RemoteAddr())
unixConn.Close()
}
}
},
ConnContext: func(ctx context.Context, c net.Conn) context.Context {
unixConn, err := net.Dial("unix", "/run/systemd/userdb/io.systemd.Multiplexer")
if err != nil {
ctx, cancel := context.WithCancelCause(ctx)
cancel(err)
return ctx
}
h.mu.Lock()
h.varlinkConns[c] = unixConn
h.mu.Unlock()
log.Printf("connecting to %s", unixConn.RemoteAddr())
return context.WithValue(ctx, varlinkConnKey{}, unixConn)
},
}
if err := server.ListenAndServe(); err != nil {
log.Fatal(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment