> ## Documentation Index
> Fetch the complete documentation index at: https://docs.go-mizu.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# WebSocket

> WebSocket upgrade middleware for real-time bidirectional communication.

## Overview

The `websocket` middleware handles WebSocket upgrade requests, enabling real-time bidirectional communication between clients and your server.

## Installation

```go theme={null}
import "github.com/go-mizu/mizu/middlewares/websocket"
```

## Quick Start

```go theme={null}
app := mizu.New()

app.Get("/ws", websocket.New(func(c *mizu.Ctx, ws *websocket.Conn) error {
    for {
        msgType, data, err := ws.ReadMessage()
        if err != nil {
            return err
        }

        // Echo message back
        if err := ws.WriteMessage(msgType, data); err != nil {
            return err
        }
    }
}))
```

## Configuration

| Option         | Type                       | Default     | Description             |
| -------------- | -------------------------- | ----------- | ----------------------- |
| `Origins`      | `[]string`                 | All allowed | Allowed origin domains  |
| `Subprotocols` | `[]string`                 | -           | Supported subprotocols  |
| `CheckOrigin`  | `func(*http.Request) bool` | -           | Custom origin validator |

## Examples

### Echo Server

```go theme={null}
app.Get("/ws", websocket.New(func(c *mizu.Ctx, ws *websocket.Conn) error {
    for {
        _, data, err := ws.ReadMessage()
        if err != nil {
            return err // Client disconnected
        }

        if err := ws.WriteText(string(data)); err != nil {
            return err
        }
    }
}))
```

### Chat Application

```go theme={null}
var clients = make(map[*websocket.Conn]bool)
var broadcast = make(chan string)
var mu sync.Mutex

// Broadcast messages to all clients
go func() {
    for msg := range broadcast {
        mu.Lock()
        for client := range clients {
            client.WriteText(msg)
        }
        mu.Unlock()
    }
}()

app.Get("/chat", websocket.New(func(c *mizu.Ctx, ws *websocket.Conn) error {
    // Register client
    mu.Lock()
    clients[ws] = true
    mu.Unlock()

    defer func() {
        mu.Lock()
        delete(clients, ws)
        mu.Unlock()
    }()

    for {
        _, data, err := ws.ReadMessage()
        if err != nil {
            return nil
        }
        broadcast <- string(data)
    }
}))
```

### Origin Validation

```go theme={null}
app.Get("/ws", websocket.WithOptions(
    func(c *mizu.Ctx, ws *websocket.Conn) error {
        // Handler
    },
    websocket.Options{
        Origins: []string{
            "https://example.com",
            "https://app.example.com",
        },
    },
))
```

### Custom Origin Check

```go theme={null}
app.Get("/ws", websocket.WithOptions(
    handler,
    websocket.Options{
        CheckOrigin: func(r *http.Request) bool {
            origin := r.Header.Get("Origin")
            // Custom validation logic
            return strings.HasSuffix(origin, ".example.com")
        },
    },
))
```

### Subprotocols

```go theme={null}
app.Get("/ws", websocket.WithOptions(
    handler,
    websocket.Options{
        Subprotocols: []string{"graphql-ws", "subscriptions-transport-ws"},
    },
))
```

### Binary Messages

```go theme={null}
app.Get("/ws", websocket.New(func(c *mizu.Ctx, ws *websocket.Conn) error {
    for {
        msgType, data, err := ws.ReadMessage()
        if err != nil {
            return err
        }

        switch msgType {
        case websocket.TextMessage:
            ws.WriteText("Received text: " + string(data))
        case websocket.BinaryMessage:
            ws.WriteBinary(processBinary(data))
        }
    }
}))
```

### Ping/Pong

```go theme={null}
app.Get("/ws", websocket.New(func(c *mizu.Ctx, ws *websocket.Conn) error {
    // Send periodic pings
    go func() {
        ticker := time.NewTicker(30 * time.Second)
        defer ticker.Stop()
        for range ticker.C {
            if err := ws.Ping(nil); err != nil {
                return
            }
        }
    }()

    for {
        msgType, data, err := ws.ReadMessage()
        if err != nil {
            return err
        }

        // Handle pong automatically
        if msgType == websocket.PongMessage {
            continue
        }

        ws.WriteMessage(msgType, data)
    }
}))
```

## Message Types

| Constant        | Value | Description     |
| --------------- | ----- | --------------- |
| `TextMessage`   | 1     | UTF-8 text data |
| `BinaryMessage` | 2     | Binary data     |
| `CloseMessage`  | 8     | Close frame     |
| `PingMessage`   | 9     | Ping frame      |
| `PongMessage`   | 10    | Pong frame      |

## Conn Methods

```go theme={null}
func (c *Conn) ReadMessage() (messageType int, data []byte, err error)
func (c *Conn) WriteMessage(messageType int, data []byte) error
func (c *Conn) WriteText(text string) error
func (c *Conn) WriteBinary(data []byte) error
func (c *Conn) Ping(data []byte) error
func (c *Conn) Pong(data []byte) error
func (c *Conn) Close() error
```

## API Reference

```go theme={null}
func New(handler Handler) mizu.Middleware
func WithOptions(handler Handler, opts Options) mizu.Middleware
func IsWebSocketUpgrade(r *http.Request) bool

type Handler func(c *mizu.Ctx, ws *Conn) error
```

## Client Example

```javascript theme={null}
const ws = new WebSocket('ws://localhost:8080/ws');

ws.onopen = () => {
    ws.send('Hello, server!');
};

ws.onmessage = (event) => {
    console.log('Received:', event.data);
};

ws.onclose = () => {
    console.log('Connection closed');
};
```

## Technical Details

### WebSocket Protocol Implementation

The middleware implements the WebSocket protocol (RFC 6455) with the following key components:

#### Connection Upgrade Process

1. **Request Validation**: Checks for `Upgrade: websocket` and `Connection: Upgrade` headers
2. **Origin Validation**: Validates the request origin against configured allowed origins
3. **Key Exchange**: Validates `Sec-WebSocket-Key` and computes the accept key using SHA1 hash with the WebSocket GUID
4. **Subprotocol Negotiation**: Matches requested subprotocols with supported ones
5. **HTTP Hijacking**: Takes over the HTTP connection using Go's `http.Hijacker` interface
6. **101 Switching Protocols**: Sends the upgrade response with `Sec-WebSocket-Accept` header

#### Frame Structure

The implementation handles WebSocket frames with the following structure:

* **Opcode**: Identifies the frame type (text, binary, close, ping, pong)
* **Masking**: Client-to-server messages must be masked; server-to-client messages are unmasked
* **Payload Length**: Supports three length encoding formats:
  * 0-125 bytes: Single byte length
  * 126-65535 bytes: 2-byte extended length
  * 65536+ bytes: 8-byte extended length

#### Connection Management

* **Thread-Safe Writes**: Uses mutex locks to ensure concurrent write safety
* **Buffered I/O**: Utilizes `bufio.Reader` and `bufio.Writer` for efficient data transfer
* **Resource Cleanup**: Automatically closes underlying TCP connections when handlers return
* **Error Handling**: Returns errors for invalid frames, connection failures, and protocol violations

#### Security Features

* **SHA1 for Key Exchange**: Uses SHA1 (required by RFC 6455) for computing the `Sec-WebSocket-Accept` header
* **Origin Validation**: Supports both whitelist-based and custom validation functions
* **Wildcard Origins**: Allows `*` for development but should be restricted in production
* **No Server Masking**: Server messages are sent unmasked per protocol specification

## Best Practices

* Always validate origins in production
* Handle disconnections gracefully
* Use ping/pong for connection health
* Clean up resources when connections close
* Consider using a message broker for scaling

## Testing

The middleware includes comprehensive test coverage for all major functionality:

| Test Case                                             | Description                                       | Expected Behavior                                          |
| ----------------------------------------------------- | ------------------------------------------------- | ---------------------------------------------------------- |
| `TestIsWebSocketUpgrade`                              | Valid WebSocket upgrade headers                   | Returns true for valid upgrade requests                    |
| `TestIsWebSocketUpgrade` (case insensitive)           | Headers with mixed case                           | Correctly identifies WebSocket upgrades regardless of case |
| `TestIsWebSocketUpgrade` (connection with keep-alive) | Multiple connection values                        | Handles Connection header with multiple values             |
| `TestIsWebSocketUpgrade` (missing headers)            | Missing Upgrade or Connection headers             | Returns false when required headers are absent             |
| `TestComputeAcceptKey`                                | RFC 6455 test vector                              | Computes correct Sec-WebSocket-Accept value                |
| `TestNew_NonWebSocket`                                | Regular HTTP request                              | Passes through to next handler without upgrade             |
| `TestWithOptions_ForbiddenOrigin`                     | Origin not in allowed list                        | Returns 403 Forbidden                                      |
| `TestWithOptions_AllowedOrigin`                       | Origin in allowed list                            | Accepts the connection                                     |
| `TestWithOptions_WildcardOrigin`                      | Wildcard (\*) origin                              | Allows any origin                                          |
| `TestWithOptions_MissingKey`                          | Missing Sec-WebSocket-Key header                  | Returns 400 Bad Request                                    |
| `TestConn_WriteText`                                  | Writing text message                              | Successfully writes text frame                             |
| `TestConn_WriteBinary`                                | Writing binary message                            | Successfully writes binary frame                           |
| `TestConn_WriteMessageLengths`                        | Various payload sizes (10, 126, 200, 70000 bytes) | Correctly encodes all payload length formats               |
| `TestConn_Close`                                      | Connection close                                  | Sends close frame and closes underlying connection         |
| `TestConn_Ping`                                       | Ping message                                      | Sends ping frame with payload                              |
| `TestConn_Pong`                                       | Pong message                                      | Sends pong frame with payload                              |
| `TestConn_ReadMessage` (short unmasked text)          | Reading unmasked text frame                       | Correctly parses and returns message                       |
| `TestConn_ReadMessage` (masked text)                  | Reading masked text frame                         | Unmasks and returns correct data                           |
| `TestConn_ReadMessageExtendedLength`                  | 200-byte message (2-byte length)                  | Correctly decodes 126+ byte payloads                       |
| `TestConn_ReadMessageVeryLongLength`                  | 70000-byte message (8-byte length)                | Correctly decodes 65536+ byte payloads                     |
| `TestConn_ReadMessageReadErrors`                      | Incomplete frame data                             | Returns appropriate errors                                 |
| `TestWithOptions_Subprotocols`                        | Matching subprotocol                              | Accepts connection with negotiated subprotocol             |
| `TestWithOptions_SubprotocolNotMatching`              | Non-matching subprotocol                          | Proceeds without subprotocol                               |
| `TestWithOptions_CustomCheckOrigin`                   | Custom origin validation function                 | Calls custom validator and respects result                 |
| `TestWithOptions_NoOriginsAllowsAll`                  | No origins configured                             | Allows all origins by default                              |
| `TestWithOptions_OriginNotInList`                     | Origin not in whitelist                           | Returns 403 Forbidden                                      |
| `TestMessageConstants`                                | Message type constants                            | Verifies correct constant values per RFC 6455              |
| `TestErrors`                                          | Error types                                       | Validates error messages                                   |

## Related Middlewares

* [sse](/middlewares/sse) - Server-Sent Events
* [timeout](/middlewares/timeout) - Request timeout
