{"id":48910022,"url":"https://github.com/catalinfl/adler","last_synced_at":"2026-05-01T23:03:51.986Z","repository":{"id":349862707,"uuid":"1195587312","full_name":"catalinfl/adler","owner":"catalinfl","description":"High performance WebSocket server library built on top of gobwas/ws","archived":false,"fork":false,"pushed_at":"2026-04-25T22:05:09.000Z","size":130,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-04-26T00:18:23.992Z","etag":null,"topics":["broadcast","game-server","go","gobwas-ws","golang","hub","multiplayer","networking","real-time","rooms","session","websocket","websocket-server"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/catalinfl.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-29T20:52:59.000Z","updated_at":"2026-04-25T22:04:18.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/catalinfl/adler","commit_stats":null,"previous_names":["catalinfl/adler"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/catalinfl/adler","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catalinfl%2Fadler","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catalinfl%2Fadler/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catalinfl%2Fadler/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catalinfl%2Fadler/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/catalinfl","download_url":"https://codeload.github.com/catalinfl/adler/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catalinfl%2Fadler/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32515839,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-30T13:12:12.517Z","status":"online","status_checked_at":"2026-05-01T02:00:05.856Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["broadcast","game-server","go","gobwas-ws","golang","hub","multiplayer","networking","real-time","rooms","session","websocket","websocket-server"],"created_at":"2026-04-16T23:03:28.439Z","updated_at":"2026-05-01T23:03:51.977Z","avatar_url":"https://github.com/catalinfl.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Adler\n\nAdler is a lightweight WebSocket server toolkit for Go. It gives you a small, focused API for upgrading HTTP requests, handling sessions, broadcasting messages, and organizing clients into rooms.\n\n## Features\n\n- WebSocket upgrade from `net/http`\n- Session lifecycle hooks\n- Text, binary, and JSON messaging\n- Global broadcast, filtered broadcast, and targeted session sends\n- Rooms with join, leave, and room-level broadcast helpers\n- Per-session key-value storage\n- Optional access to the underlying request, protocol, and connection\n\n## Roadmap\n\n- [x] Add a dedicated matchmaker module for queue creation, queue management, and match orchestration.\n- [ ] Add ELO-based matchmaking with configurable rating buckets and match quality rules.\n\n## Installation\n\n```bash\ngo get github.com/catalinfl/adler\n```\n\n## Quick Start\n\n```go\npackage main\n\nimport (\n    \"log\"\n    \"net/http\"\n\n    \"github.com/catalinfl/adler\"\n)\n\nfunc main() {\n    a := adler.New()\n\n    a.HandleConnect(func(s *adler.Session) {\n        log.Println(\"connected:\", s.RemoteAddr())\n    })\n\n    a.HandleMessage(func(s *adler.Session, msg []byte) {\n        _ = s.WriteText([]byte(\"echo: \" + string(msg)))\n    })\n\n    a.HandleError(func(s *adler.Session, err error) {\n        log.Println(\"ws error:\", err)\n    })\n\n    http.HandleFunc(\"/ws\", func(w http.ResponseWriter, r *http.Request) {\n        if err := a.HandleRequest(w, r); err != nil {\n            log.Println(\"handle request:\", err)\n        }\n    })\n\n    log.Fatal(http.ListenAndServe(\":8080\", nil))\n}\n```\n\n## How The API Works\n\n### 1. Create a server\n\nUse `adler.New()` to build a server instance. You can pass configuration options at construction time:\n\n```go\na := adler.New(\n    adler.WithDispatchAsync(true),\n    adler.WithMessageBufferSize(256),\n    adler.WithPingPeriod(30*time.Second),\n)\n```\n\n### 2. Register handlers before serving requests\n\nHandlers are set on the `Adler` instance and are called during the session lifecycle.\n\n- `HandleConnect` runs after a session is registered.\n- `HandleMessage` receives text frames.\n- `HandleMessageBinary` receives binary frames.\n- `HandlePong` receives pong frames.\n- `HandleClose` receives close code and reason.\n- `HandleSentMessage` and `HandleSentMessageBinary` run after server writes succeed.\n- `HandleError` receives runtime errors from the session loop.\n- `OnRoomJoin` and `OnRoomLeave` receive room membership events.\n\nExample:\n\n```go\na.HandleConnect(func(s *adler.Session) {\n    s.Set(\"userID\", \"123\")\n})\n\na.HandleClose(func(s *adler.Session, code int, reason string) {\n    log.Printf(\"client closed: code=%d reason=%q\", code, reason)\n})\n```\n\n### 3. Serve the websocket endpoint\n\nCall `HandleRequest` from an HTTP handler. It upgrades the connection and blocks until the session ends.\n\n```go\nhttp.HandleFunc(\"/ws\", func(w http.ResponseWriter, r *http.Request) {\n    _ = a.HandleRequest(w, r)\n})\n```\n\n### 4. Work with a Session\n\n`Session` is the object you receive in callbacks. It exposes message writers and a small key-value store.\n\nMessaging helpers:\n\n- `WriteText([]byte)`\n- `WriteTextWithDeadline([]byte, time.Duration)`\n- `WriteBinary([]byte)`\n- `WriteBinaryWithDeadline([]byte, time.Duration)`\n- `WriteJSON(adler.Map)`\n- `WriteJSONWithDeadline(any, time.Duration)`\n- `Close(...[]byte)`\n\nStorage helpers:\n\n- `Set(key, value)`\n- `SetNX(key, value)`\n- `Get(key)`\n- `GetString(key)`\n- `GetInt(key)`\n- `GetInt64(key)`\n- `GetFloat(key)`\n- `GetBool(key)`\n- `Has(key)`\n- `Unset(key)`\n- `Keys()`\n- `Values()`\n- `Clear()`\n- `Incr(key)` and `Decr(key)` for `*int64` counters\n\nMetadata:\n\n- `Request()` returns the original HTTP request\n- `Protocol()` returns the HTTP protocol string used during upgrade\n- `LocalAddr()` and `RemoteAddr()` expose the connection addresses\n- `Room()` returns the current room\n- `UnsafeConn()` exposes the raw network connection when you need low-level control\n\nExample session storage:\n\n```go\na.HandleConnect(func(s *adler.Session) {\n    s.Set(\"role\", \"admin\")\n    s.SetNX(\"seen\", true)\n})\n\na.HandleMessage(func(s *adler.Session, msg []byte) {\n    role, _ := s.GetString(\"role\")\n    _ = s.WriteText([]byte(\"role=\" + role + \" msg=\" + string(msg)))\n})\n```\n\n### 5. Broadcast to all or some clients\n\nUse server-level broadcast helpers when you want to send to multiple sessions.\n\n- `Broadcast([]byte)` sends text to all connected sessions\n- `BroadcastFilter([]byte, func(*Session) bool)` sends text to matching sessions\n- `BroadcastBinary([]byte)` sends binary to all connected sessions\n- `BroadcastBinaryFilter([]byte, func(*Session) bool)` sends binary to matching sessions\n- `BroadcastJSON(adler.Map)` broadcasts JSON\n- `BroadcastJSONFilter(adler.Map, func(*Session) bool)` broadcasts JSON to matching sessions\n- `BroadcastOthers([]byte, *Session)` sends to everyone except the target session\n- `SendTo([]byte, *Session)` sends only to one session\n\nExample:\n\n```go\na.Broadcast([]byte(\"server says hello\"))\n\na.SendTo([]byte(\"private message\"), session)\n\na.BroadcastFilter([]byte(\"admins only\"), func(s *adler.Session) bool {\n    role, _ := s.GetString(\"role\")\n    return role == \"admin\"\n})\n```\n\n### 6. Group clients in rooms\n\nRooms help you manage subsets of sessions.\n\n```go\nroom := a.NewRoom(\"lobby\")\n\na.HandleConnect(func(s *adler.Session) {\n    _ = room.Join(s)\n})\n\nroom.Broadcast([]byte(\"welcome to the lobby\"))\n```\n\nRoom helpers:\n\n- `NewRoom(name)` returns an existing room or creates one\n- `DeleteRoom(name)` removes a room manually when it is empty\n- `Name()` returns the room name\n- `Len()` returns the number of members\n- `Sessions()` returns a snapshot of current members\n- `Join(*Session)` adds a session to the room\n- `Leave(*Session)` removes a session\n- `OpenRoom()` allows joins again\n- `CloseRoom()` blocks new joins\n- `Broadcast`, `BroadcastBinary`, `BroadcastFilter`, `BroadcastJSON`, `BroadcastJSONFilter`\n\n### 7. Matchmaking with the Matchmaker Module\n\nThe `matchmaker` module provides queue-based matchmaking that groups sessions into rooms automatically. It uses a background goroutine to manage queues and ensure fair player distribution.\n\nFeatures:\n\n- **Non-blocking queue operations**: `AddToQueue()` and `RemoveFromQueue()` send commands to a worker goroutine\n- **Main and waiting queues**: Keeps a main queue and an optional waiting queue when the main queue reaches capacity\n- **Automatic room creation**: Creates Adler rooms when enough players are queued\n- **JSON event notifications**: Sends events to sessions as they move through the queue\n\nThe matchmaker sends these JSON messages:\n\n- `\"queue_joined\"` - Session added to main queue\n- `\"wait_queue_joined\"` - Session added to waiting queue (main is full)\n- `\"promoted_to_queue\"` - Session promoted from waiting to main queue\n- `\"match_found\"` - Match room created with `room_id` and player count\n\nExample:\n\n```go\nimport \"github.com/catalinfl/adler/matchmaker\"\n\nmm := matchmaking.NewMatchmaker(a, \n    matchmaking.WithRoomSize(4),\n    matchmaking.WithMaxQueue(20),\n)\n\na.HandleConnect(func(s *adler.Session) {\n    _ = mm.AddToQueue(s)\n})\n\na.HandleMessage(func(s *adler.Session, msg []byte) {\n    if string(msg) == \"leave_queue\" {\n        mm.RemoveFromQueue(s)\n    }\n})\n```\n\n### 8. Close handling\n\nIf the client sends a close frame, `HandleClose` receives the close status code and reason.\n\n```go\na.HandleClose(func(s *adler.Session, code int, reason string) {\n    log.Printf(\"close: code=%d reason=%q\", code, reason)\n})\n```\n\n## Configuration\n\nUse these options with `adler.New(...)`:\n\n- `WithWriteWait(time.Duration)` interprets the argument as seconds; `WithWriteWait(10)` means 10 seconds\n- `WithPongWait(time.Duration)` interprets the argument as seconds; `WithPongWait(60)` means 60 seconds, and `WithPongWait(0)` disables idle disconnects\n- `WithPingPeriod(time.Duration)` interprets the argument as seconds; `WithPingPeriod(54)` means 54 seconds\n- `WithMessageBufferSize(int)` sets the outbound queue size; start with 64-256 and increase only if you hit `ErrBufferFull` under normal bursts\n- `WithDispatchAsync(bool)` switches inbound dispatch to goroutine-per-message when enabled\n- `WithDeleteRoomOnEmpty(bool)` controls automatic room deletion when the last session leaves (default: `true`)\n\n## Notes On Concurrency\n\n- Session storage is protected by an internal mutex.\n- Server broadcast methods are safe for normal concurrent use.\n- `UnsafeConn()` bypasses Adler's internal coordination; only use it if you manage access carefully.\n\n## Minimal Room Example\n\n```go\nroom := a.NewRoom(\"test\")\n\na.HandleConnect(func(s *adler.Session) {\n    _ = room.Join(s)\n})\n\nroom.HandleJoin(func(s *adler.Session) {\n    _ = s.WriteText([]byte(\"joined room\"))\n})\n```\n\n## License\n\nThis project is licensed under the MIT License. See [LICENSE](LICENSE) for the full text.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcatalinfl%2Fadler","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcatalinfl%2Fadler","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcatalinfl%2Fadler/lists"}