https://github.com/jkaninda/okapiws
Okapi WebSocket is a lightweight, framework-agnostic WebSocket package focused on developer experience and clean APIs
https://github.com/jkaninda/okapiws
go-websocket go-websocket-server websocket
Last synced: 11 days ago
JSON representation
Okapi WebSocket is a lightweight, framework-agnostic WebSocket package focused on developer experience and clean APIs
- Host: GitHub
- URL: https://github.com/jkaninda/okapiws
- Owner: jkaninda
- License: mit
- Created: 2026-01-17T13:56:34.000Z (5 months ago)
- Default Branch: main
- Last Pushed: 2026-03-04T18:58:30.000Z (4 months ago)
- Last Synced: 2026-05-04T18:43:24.129Z (about 2 months ago)
- Topics: go-websocket, go-websocket-server, websocket
- Language: Go
- Homepage:
- Size: 12.7 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Okapi WebSocket
**Okapi WebSocket** is a lightweight, framework-agnostic WebSocket package for Go, providing both **server** and **client** with clean, callback-driven APIs.
It integrates seamlessly with the **Okapi Web Framework**, but works with Go's standard `net/http` or any HTTP server.
## Features
### Server
* Simple WebSocket upgrade API (`Default` or `NewWSUpgrader`)
* Framework independent — works with `net/http`, Okapi, or any Go HTTP server
* Optional configuration (`nil` uses sensible defaults)
* `OnMessage` / `OnError` / `OnClose` callback handlers
* `Send`, `SendText`, `SendJSON`, `SendEvent`, `SendBinary` methods
* Text and binary message support
* Custom response headers on upgrade
* Context-based lifecycle (inherits request context)
* Graceful connection closing with close frames
* Automatic ping/pong keep-alive
* Configurable buffer sizes, timeouts, max message size, CORS, subprotocols, and compression
### Client
* Connect to any WebSocket server (`ws://` or `wss://`)
* Same callback-driven API: `OnMessage` / `OnError` / `OnClose` / `OnConnect`
* Same send methods: `Send`, `SendText`, `SendJSON`, `SendEvent`, `SendBinary`
* Auto-reconnect with exponential backoff and jitter
* Configurable max retries, initial delay, and max delay
* Custom HTTP headers for handshake
* Custom TLS configuration
* Context-based lifecycle and graceful shutdown
* Thread-safe
### Okapi
- [https://github.com/jkaninda/okapi](https://github.com/jkaninda/okapi)
## Installation
```sh
go get github.com/jkaninda/okapiws
```
---
## Server
### Using with Go `net/http`
```go
package main
import (
"log"
"net/http"
okapiws "github.com/jkaninda/okapiws"
)
func main() {
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
upgrader := okapiws.NewWSUpgrader(nil) // nil = use default config
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
http.Error(w, "WebSocket upgrade failed", http.StatusBadRequest)
return
}
defer ws.Close()
ws.OnMessage(func(msg *okapiws.WSMessage) {
log.Printf("[%d] %s", msg.Type, msg.Data)
_ = ws.Send(msg.Data) // Echo
})
ws.OnError(func(err error) {
log.Printf("WebSocket error: %v", err)
})
ws.Start()
<-ws.Context().Done() // Block until closed
})
log.Println("Listening on :8080...")
log.Fatal(http.ListenAndServe(":8080", nil))
}
```
### Using with Okapi
```go
package main
import (
"log"
"net/http"
"github.com/jkaninda/okapi"
okapiws "github.com/jkaninda/okapiws"
)
// WebSocket upgrades the HTTP connection to WebSocket.
// Config is optional; pass nil to use default settings.
func WebSocket(config *okapiws.WSConfig, c *okapi.Context) (*okapiws.WSConnection, error) {
upgrader := okapiws.NewWSUpgrader(config)
return upgrader.Upgrade(c.Response(), c.Request(), nil)
}
// WebSocketWithHeaders upgrades with additional response headers.
func WebSocketWithHeaders(config *okapiws.WSConfig, headers http.Header, c okapi.Context) (*okapiws.WSConnection, error) {
upgrader := okapiws.NewWSUpgrader(config)
return upgrader.Upgrade(c.Response(), c.Request(), headers)
}
func main() {
app := okapi.Default()
app.Get("/", func(c *okapi.Context) error {
return c.OK(okapi.M{"message": "Hello from Okapi Web Framework!"})
})
app.Get("/ws", handleWebSocket)
if err := app.Start(); err != nil {
panic(err)
}
}
func handleWebSocket(c *okapi.Context) error {
ws, err := WebSocket(nil, c)
if err != nil {
return err
}
defer func() {
if err := ws.Close(); err != nil {
log.Printf("error closing WebSocket: %v", err)
}
}()
ws.OnMessage(func(msg *okapiws.WSMessage) {
log.Printf("[%d] %s", msg.Type, msg.Data)
// Echo the message back
_ = ws.Send(msg.Data)
})
ws.OnError(func(err error) {
log.Printf("WebSocket error: %v", err)
})
ws.Start()
// Block until the connection is closed
<-ws.Context().Done()
return nil
}
```
### Custom Response Headers
```go
headers := http.Header{}
headers.Add("X-WebSocket-Version", "1.0")
ws, err := upgrader.Upgrade(w, r, headers)
```
### Sending Messages
```go
// Text
ws.Send([]byte("hello"))
ws.SendText("hello")
// JSON
ws.SendJSON(map[string]any{"key": "value"})
// Event (custom protocol with {"event": "...", "data": ...})
ws.SendEvent("chat", map[string]any{"message": "hi"})
// Binary
ws.SendBinary(binaryData)
```
---
## Client
### Basic Usage
```go
package main
import (
"context"
"log"
"os"
"os/signal"
"syscall"
"time"
okapiws "github.com/jkaninda/okapiws"
)
func main() {
config := okapiws.DefaultWSClient()
config.AutoReconnect = true
config.ReconnectInitial = 1 * time.Second
config.ReconnectMax = 30 * time.Second
config.MaxRetries = 10
client := okapiws.NewWSClient("ws://localhost:8080/ws", okapiws.WithConfig(config))
client.OnConnect(func() {
log.Println("Connected to server")
_ = client.SendText("Hello from client!")
})
client.OnMessage(func(msg *okapiws.WSMessage) {
log.Printf("Received [%d]: %s", msg.Type, msg.Data)
})
client.OnError(func(err error) {
log.Printf("Error: %v", err)
})
client.OnClose(func() {
log.Println("Connection closed")
})
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
if err := client.Connect(ctx); err != nil {
log.Fatalf("Failed to connect: %v", err)
}
// Send a message every 5 seconds
go func() {
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if err := client.SendText("ping from client"); err != nil {
log.Printf("Send error: %v", err)
}
case <-ctx.Done():
return
}
}
}()
// Wait for interrupt signal
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
<-sigCh
log.Println("Shutting down...")
cancel()
if err := client.Close(); err != nil {
log.Printf("Close error: %v", err)
}
}
```
### Custom Headers and TLS
```go
config := okapiws.DefaultWSClient()
config.Headers = http.Header{
"Authorization": []string{"Bearer my-token"},
}
config.TLSConfig = &tls.Config{
InsecureSkipVerify: true,
}
client := okapiws.NewWSClient("wss://example.com/ws", okapiws.WithConfig(config))
```
### Auto-Reconnect
When `AutoReconnect` is enabled, the client automatically reconnects on connection loss using exponential backoff with jitter. The `OnConnect` callback fires on each successful reconnection.
```go
config := okapiws.DefaultWSClient()
config.AutoReconnect = true
config.ReconnectInitial = 1 * time.Second // first retry after 1s
config.ReconnectMax = 30 * time.Second // cap backoff at 30s
config.MaxRetries = 0 // 0 = unlimited retries
```
---
## Configuration
### Server Configuration (`WSConfig`)
| Field | Default | Description |
|--------------------|---------|--------------------------------------|
| `ReadBufferSize` | 1024 | Read buffer size in bytes |
| `WriteBufferSize` | 1024 | Write buffer size in bytes |
| `HandshakeTimeout` | 10s | Handshake timeout |
| `CheckOrigin` | `true` | Origin check function |
| `Subprotocols` | `nil` | Supported subprotocols |
| `EnableCompression`| `false` | Enable per-message compression |
| `PingInterval` | 54s | Interval between pings |
| `PongWait` | 60s | Timeout waiting for pong |
| `WriteWait` | 10s | Write deadline timeout |
| `MaxMessageSize` | 512KB | Maximum incoming message size |
```go
cfg := &okapiws.WSConfig{
ReadBufferSize: 2048,
WriteBufferSize: 2048,
MaxMessageSize: 1024 * 1024, // 1MB
}
upgrader := okapiws.NewWSUpgrader(cfg)
```
Pass `nil` to use defaults:
```go
ws, err := okapiws.Default(w, r, nil)
```
### Client Configuration (`WSClientConfig`)
| Field | Default | Description |
|--------------------|---------|------------------------------------------|
| `ReadBufferSize` | 1024 | Read buffer size in bytes |
| `WriteBufferSize` | 1024 | Write buffer size in bytes |
| `HandshakeTimeout` | 10s | Handshake timeout |
| `Headers` | `nil` | Custom HTTP headers for handshake |
| `TLSConfig` | `nil` | Custom TLS configuration |
| `Subprotocols` | `nil` | Requested subprotocols |
| `EnableCompression`| `false` | Enable per-message compression |
| `PingInterval` | 54s | Interval between pings |
| `PongWait` | 60s | Timeout waiting for pong |
| `WriteWait` | 10s | Write deadline timeout |
| `MaxMessageSize` | 512KB | Maximum incoming message size |
| `AutoReconnect` | `false` | Enable auto-reconnect on disconnect |
| `ReconnectInitial` | 1s | Initial reconnect delay |
| `ReconnectMax` | 30s | Maximum reconnect delay |
| `MaxRetries` | 0 | Max reconnect attempts (0 = unlimited) |
---
## License
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.