A lightweight, event-driven WebSocket framework for Go.
Cira provides an ergonomic abstraction over WebSocket connections with event routing, request-response messaging, synchronous RPC-style calls, and long-lived streams.
- Event-driven routing — named events with dot-separated scoping and middleware
- Bidirectional communication — supports both listening and outbound dialing
- Five messaging patterns — Push, Request, Response, Call, and Stream
- Streaming transport — long-lived message streams identified by user-defined IDs
- Connection management — connection lookup, close callbacks, and contextual execution
- Customizable — pluggable codec, ID generator, and WebSocket upgrader
- Lightweight — built on top of gorilla/websocket
go get github.com/AtoriUzawa/cirapackage main
import "github.com/AtoriUzawa/cira"
func main() {
server := cira.New()
server.On("hello", func(c *cira.Context) {
c.Resp(map[string]string{
"message": "world",
})
})
panic(server.Run(":8080"))
}package main
import (
"github.com/AtoriUzawa/cira"
)
func main() {
client := cira.New()
conn, err := client.Dial("ws://localhost:8080/ws")
if err != nil {
panic(err)
}
conn.Do(func(c *cira.Context) {
_ = c.Push("hello", "world")
})
select {}
}| Pattern | Description | Response |
|---|---|---|
| Push | One-way event delivery | No |
| Request | Request event | Optional |
| Response | Reply to a request | Yes |
| Call | Request and wait for response | Yes |
| Stream | Continuous message transport | Multiple |
Send a one-way event.
ctx.Push("chat.message", map[string]string{
"user": "alice",
"text": "hello",
})Send a request event without waiting for a response.
ctx.Req("status.update", map[string]bool{
"online": true,
})Reply to the current request.
server.On("ping", func(c *cira.Context) {
c.Resp("pong")
})Send a request and wait synchronously for a response.
var resp map[string]any
err := ctx.Call(
"user.info",
map[string]string{
"id": "123",
},
&resp,
)
if err != nil {
return
}ctx.Timeout = 5 * time.SecondDefault timeout:
30 * time.SecondStreams provide a long-lived communication channel identified by a stream ID.
stream := ctx.OpenStream("upload.file")
defer ctx.CloseStream()
_ = stream.Send("chunk_1")
_ = stream.Send("chunk_2")
_ = stream.Send("chunk_3")stream := ctx.OpenStream("upload.file")
defer ctx.CloseStream()
for {
var chunk string
err := stream.Recv(&chunk)
if err != nil {
break
}
fmt.Println(chunk)
}err := stream.RecvTimeout(&chunk)Default timeout uses:
ctx.Timeoutfunc Logger(next cira.HandlerFunc) cira.HandlerFunc {
return func(c *cira.Context) {
log.Println(c.Message.Route)
next(c)
}
}
server.Use(Logger)Middleware executes in reverse registration order.
Routes support dot-separated grouping.
api := server.Group("api")
api.On("user.list", handler)Resulting route:
api.user.list
server.On("hello", func(c *cira.Context) {
fmt.Println(c.Conn.ID())
})conn, err := server.Conn(id)
if err != nil {
return
}conn.Close()conn.OnClose(func() {
log.Println("connection closed")
})engine := cira.New(
cira.WithCodec(myCodec),
)engine := cira.New(
cira.WithIDGenerator(myGenerator),
)engine := cira.New(
cira.WithUpgrader(myUpgrader),
)| Example | Description |
|---|---|
| examples/hello | Minimal server |
| examples/push | Push messaging |
| examples/request | Request messaging |
| examples/call | RPC-style call |
| examples/stream | Stream communication |
| examples/client | Client dial and file upload stream |
| examples/middleware | Middleware usage |
| examples/connection | Connection lifecycle |
| examples/chatroom | Multi-client chatroom |
┌─────────┐
│ Engine │
└────┬────┘
│
┌────▼────┐
│ Peer │
└────┬────┘
│
┌────▼────┐
│ Context │
└────┬────┘
│
┌───┴───┐
│Message│
│Stream │
└───────┘
A connection is represented internally as a Peer. Business logic interacts through Conn and Context abstractions.
MIT