{"id":50673727,"url":"https://github.com/jkaninda/okapiws","last_synced_at":"2026-06-08T14:01:38.703Z","repository":{"id":355662831,"uuid":"1136333161","full_name":"jkaninda/okapiws","owner":"jkaninda","description":"Okapi WebSocket is a lightweight, framework-agnostic WebSocket package focused on developer experience and clean APIs","archived":false,"fork":false,"pushed_at":"2026-03-04T18:58:30.000Z","size":13,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-04T18:43:24.129Z","etag":null,"topics":["go-websocket","go-websocket-server","websocket"],"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/jkaninda.png","metadata":{"files":{"readme":"README.md","changelog":null,"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-01-17T13:56:34.000Z","updated_at":"2026-03-04T18:56:13.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jkaninda/okapiws","commit_stats":null,"previous_names":["jkaninda/okapi-ws"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/jkaninda/okapiws","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jkaninda%2Fokapiws","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jkaninda%2Fokapiws/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jkaninda%2Fokapiws/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jkaninda%2Fokapiws/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jkaninda","download_url":"https://codeload.github.com/jkaninda/okapiws/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jkaninda%2Fokapiws/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34065354,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-08T02:00:07.615Z","response_time":111,"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":["go-websocket","go-websocket-server","websocket"],"created_at":"2026-06-08T14:01:37.948Z","updated_at":"2026-06-08T14:01:38.698Z","avatar_url":"https://github.com/jkaninda.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Okapi WebSocket\n\n**Okapi WebSocket** is a lightweight, framework-agnostic WebSocket package for Go, providing both **server** and **client** with clean, callback-driven APIs.\nIt integrates seamlessly with the **Okapi Web Framework**, but works with Go's standard `net/http` or any HTTP server.\n\n## Features\n\n### Server\n* Simple WebSocket upgrade API (`Default` or `NewWSUpgrader`)\n* Framework independent — works with `net/http`, Okapi, or any Go HTTP server\n* Optional configuration (`nil` uses sensible defaults)\n* `OnMessage` / `OnError` / `OnClose` callback handlers\n* `Send`, `SendText`, `SendJSON`, `SendEvent`, `SendBinary` methods\n* Text and binary message support\n* Custom response headers on upgrade\n* Context-based lifecycle (inherits request context)\n* Graceful connection closing with close frames\n* Automatic ping/pong keep-alive\n* Configurable buffer sizes, timeouts, max message size, CORS, subprotocols, and compression\n\n### Client\n* Connect to any WebSocket server (`ws://` or `wss://`)\n* Same callback-driven API: `OnMessage` / `OnError` / `OnClose` / `OnConnect`\n* Same send methods: `Send`, `SendText`, `SendJSON`, `SendEvent`, `SendBinary`\n* Auto-reconnect with exponential backoff and jitter\n* Configurable max retries, initial delay, and max delay\n* Custom HTTP headers for handshake\n* Custom TLS configuration\n* Context-based lifecycle and graceful shutdown\n* Thread-safe\n\n### Okapi\n- [https://github.com/jkaninda/okapi](https://github.com/jkaninda/okapi)\n\n## Installation\n\n```sh\ngo get github.com/jkaninda/okapiws\n```\n\n---\n\n## Server\n\n### Using with Go `net/http`\n\n```go\npackage main\n\nimport (\n\t\"log\"\n\t\"net/http\"\n\n\tokapiws \"github.com/jkaninda/okapiws\"\n)\n\nfunc main() {\n\thttp.HandleFunc(\"/ws\", func(w http.ResponseWriter, r *http.Request) {\n\t\tupgrader := okapiws.NewWSUpgrader(nil) // nil = use default config\n\n\t\tws, err := upgrader.Upgrade(w, r, nil)\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"WebSocket upgrade failed\", http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\t\tdefer ws.Close()\n\n\t\tws.OnMessage(func(msg *okapiws.WSMessage) {\n\t\t\tlog.Printf(\"[%d] %s\", msg.Type, msg.Data)\n\t\t\t_ = ws.Send(msg.Data) // Echo\n\t\t})\n\n\t\tws.OnError(func(err error) {\n\t\t\tlog.Printf(\"WebSocket error: %v\", err)\n\t\t})\n\n\t\tws.Start()\n\n\t\t\u003c-ws.Context().Done() // Block until closed\n\t})\n\n\tlog.Println(\"Listening on :8080...\")\n\tlog.Fatal(http.ListenAndServe(\":8080\", nil))\n}\n```\n\n### Using with Okapi\n\n```go\npackage main\n\nimport (\n\t\"log\"\n\t\"net/http\"\n\n\t\"github.com/jkaninda/okapi\"\n\tokapiws \"github.com/jkaninda/okapiws\"\n)\n\n// WebSocket upgrades the HTTP connection to WebSocket.\n// Config is optional; pass nil to use default settings.\nfunc WebSocket(config *okapiws.WSConfig, c *okapi.Context) (*okapiws.WSConnection, error) {\n\tupgrader := okapiws.NewWSUpgrader(config)\n\treturn upgrader.Upgrade(c.Response(), c.Request(), nil)\n}\n\n// WebSocketWithHeaders upgrades with additional response headers.\nfunc WebSocketWithHeaders(config *okapiws.WSConfig, headers http.Header, c okapi.Context) (*okapiws.WSConnection, error) {\n\tupgrader := okapiws.NewWSUpgrader(config)\n\treturn upgrader.Upgrade(c.Response(), c.Request(), headers)\n}\n\nfunc main() {\n\tapp := okapi.Default()\n\n\tapp.Get(\"/\", func(c *okapi.Context) error {\n\t\treturn c.OK(okapi.M{\"message\": \"Hello from Okapi Web Framework!\"})\n\t})\n\n\tapp.Get(\"/ws\", handleWebSocket)\n\n\tif err := app.Start(); err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc handleWebSocket(c *okapi.Context) error {\n\tws, err := WebSocket(nil, c)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := ws.Close(); err != nil {\n\t\t\tlog.Printf(\"error closing WebSocket: %v\", err)\n\t\t}\n\t}()\n\n\tws.OnMessage(func(msg *okapiws.WSMessage) {\n\t\tlog.Printf(\"[%d] %s\", msg.Type, msg.Data)\n\t\t// Echo the message back\n\t\t_ = ws.Send(msg.Data)\n\t})\n\n\tws.OnError(func(err error) {\n\t\tlog.Printf(\"WebSocket error: %v\", err)\n\t})\n\n\tws.Start()\n\n\t// Block until the connection is closed\n\t\u003c-ws.Context().Done()\n\treturn nil\n}\n```\n\n### Custom Response Headers\n\n```go\nheaders := http.Header{}\nheaders.Add(\"X-WebSocket-Version\", \"1.0\")\n\nws, err := upgrader.Upgrade(w, r, headers)\n```\n\n### Sending Messages\n\n```go\n// Text\nws.Send([]byte(\"hello\"))\nws.SendText(\"hello\")\n\n// JSON\nws.SendJSON(map[string]any{\"key\": \"value\"})\n\n// Event (custom protocol with {\"event\": \"...\", \"data\": ...})\nws.SendEvent(\"chat\", map[string]any{\"message\": \"hi\"})\n\n// Binary\nws.SendBinary(binaryData)\n```\n\n---\n\n## Client\n\n### Basic Usage\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\t\"time\"\n\n\tokapiws \"github.com/jkaninda/okapiws\"\n)\n\nfunc main() {\n\tconfig := okapiws.DefaultWSClient()\n\tconfig.AutoReconnect = true\n\tconfig.ReconnectInitial = 1 * time.Second\n\tconfig.ReconnectMax = 30 * time.Second\n\tconfig.MaxRetries = 10\n\n\tclient := okapiws.NewWSClient(\"ws://localhost:8080/ws\", okapiws.WithConfig(config))\n\n\tclient.OnConnect(func() {\n\t\tlog.Println(\"Connected to server\")\n\t\t_ = client.SendText(\"Hello from client!\")\n\t})\n\n\tclient.OnMessage(func(msg *okapiws.WSMessage) {\n\t\tlog.Printf(\"Received [%d]: %s\", msg.Type, msg.Data)\n\t})\n\n\tclient.OnError(func(err error) {\n\t\tlog.Printf(\"Error: %v\", err)\n\t})\n\n\tclient.OnClose(func() {\n\t\tlog.Println(\"Connection closed\")\n\t})\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\n\tif err := client.Connect(ctx); err != nil {\n\t\tlog.Fatalf(\"Failed to connect: %v\", err)\n\t}\n\n\t// Send a message every 5 seconds\n\tgo func() {\n\t\tticker := time.NewTicker(5 * time.Second)\n\t\tdefer ticker.Stop()\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase \u003c-ticker.C:\n\t\t\t\tif err := client.SendText(\"ping from client\"); err != nil {\n\t\t\t\t\tlog.Printf(\"Send error: %v\", err)\n\t\t\t\t}\n\t\t\tcase \u003c-ctx.Done():\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\t// Wait for interrupt signal\n\tsigCh := make(chan os.Signal, 1)\n\tsignal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)\n\t\u003c-sigCh\n\n\tlog.Println(\"Shutting down...\")\n\tcancel()\n\tif err := client.Close(); err != nil {\n\t\tlog.Printf(\"Close error: %v\", err)\n\t}\n}\n```\n\n### Custom Headers and TLS\n\n```go\nconfig := okapiws.DefaultWSClient()\nconfig.Headers = http.Header{\n\t\"Authorization\": []string{\"Bearer my-token\"},\n}\nconfig.TLSConfig = \u0026tls.Config{\n\tInsecureSkipVerify: true,\n}\n\nclient := okapiws.NewWSClient(\"wss://example.com/ws\", okapiws.WithConfig(config))\n```\n\n### Auto-Reconnect\n\nWhen `AutoReconnect` is enabled, the client automatically reconnects on connection loss using exponential backoff with jitter. The `OnConnect` callback fires on each successful reconnection.\n\n```go\nconfig := okapiws.DefaultWSClient()\nconfig.AutoReconnect = true\nconfig.ReconnectInitial = 1 * time.Second  // first retry after 1s\nconfig.ReconnectMax = 30 * time.Second     // cap backoff at 30s\nconfig.MaxRetries = 0                       // 0 = unlimited retries\n```\n\n---\n\n## Configuration\n\n### Server Configuration (`WSConfig`)\n\n| Field              | Default | Description                          |\n|--------------------|---------|--------------------------------------|\n| `ReadBufferSize`   | 1024    | Read buffer size in bytes            |\n| `WriteBufferSize`  | 1024    | Write buffer size in bytes           |\n| `HandshakeTimeout` | 10s     | Handshake timeout                    |\n| `CheckOrigin`      | `true`  | Origin check function                |\n| `Subprotocols`     | `nil`   | Supported subprotocols               |\n| `EnableCompression`| `false` | Enable per-message compression       |\n| `PingInterval`     | 54s     | Interval between pings               |\n| `PongWait`         | 60s     | Timeout waiting for pong             |\n| `WriteWait`        | 10s     | Write deadline timeout               |\n| `MaxMessageSize`   | 512KB   | Maximum incoming message size        |\n\n```go\ncfg := \u0026okapiws.WSConfig{\n\tReadBufferSize:  2048,\n\tWriteBufferSize: 2048,\n\tMaxMessageSize:  1024 * 1024, // 1MB\n}\nupgrader := okapiws.NewWSUpgrader(cfg)\n```\n\nPass `nil` to use defaults:\n\n```go\nws, err := okapiws.Default(w, r, nil)\n```\n\n### Client Configuration (`WSClientConfig`)\n\n| Field              | Default | Description                              |\n|--------------------|---------|------------------------------------------|\n| `ReadBufferSize`   | 1024    | Read buffer size in bytes                |\n| `WriteBufferSize`  | 1024    | Write buffer size in bytes               |\n| `HandshakeTimeout` | 10s     | Handshake timeout                        |\n| `Headers`          | `nil`   | Custom HTTP headers for handshake        |\n| `TLSConfig`        | `nil`   | Custom TLS configuration                 |\n| `Subprotocols`     | `nil`   | Requested subprotocols                   |\n| `EnableCompression`| `false` | Enable per-message compression           |\n| `PingInterval`     | 54s     | Interval between pings                   |\n| `PongWait`         | 60s     | Timeout waiting for pong                 |\n| `WriteWait`        | 10s     | Write deadline timeout                   |\n| `MaxMessageSize`   | 512KB   | Maximum incoming message size            |\n| `AutoReconnect`    | `false` | Enable auto-reconnect on disconnect      |\n| `ReconnectInitial` | 1s      | Initial reconnect delay                  |\n| `ReconnectMax`     | 30s     | Maximum reconnect delay                  |\n| `MaxRetries`       | 0       | Max reconnect attempts (0 = unlimited)   |\n\n---\n\n## License\n\nThis project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjkaninda%2Fokapiws","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjkaninda%2Fokapiws","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjkaninda%2Fokapiws/lists"}