Skip to content

Feature Request: WebSocket Upgrade Extension Point/Hook #1106

@romrani-onit

Description

@romrani-onit

Feature Request: WebSocket Upgrade Extension Point / Hook

Summary

Add an extension point that allows custom logic to be executed during WebSocket upgrade requests, similar to how middleware works for regular HTTP requests.

Motivation

Currently, WebSocket upgrade requests bypass the normal request handling pipeline and go directly to handle_upgrade(). This means:

  1. Middleware is not invoked for WebSocket upgrade requests
  2. No way to intercept or modify WebSocket upgrade behavior without patching Crow
  3. Cannot implement custom logic like redirects, authentication, or protocol validation at the upgrade stage

Use Case Examples

1. HTTP to HTTPS WebSocket Redirect

Automatically redirect ws:// connections to wss:// for security:

// Desired usage
app.websocket_upgrade_hook([](const request& req, response& res, bool is_ssl) {
    if (!is_ssl && should_redirect_to_https()) {
        res.code = 308;
        res.set_header("Location", "wss://" + get_https_url(req));
        return false; // Don't proceed with upgrade
    }
    return true; // Proceed with upgrade
});

2. Authentication/Authorization

Validate tokens or permissions before allowing WebSocket upgrade:

app.websocket_upgrade_hook([](const request& req, response& res, bool is_ssl) {
    if (!is_authenticated(req)) {
        res.code = 401;
        res.set_header("WWW-Authenticate", "Bearer");
        return false;
    }
    return true;
});

3. Rate Limiting

Apply rate limits specifically to WebSocket connections:

app.websocket_upgrade_hook([](const request& req, response& res, bool is_ssl) {
    if (rate_limiter.is_exceeded(req.remote_ip_address)) {
        res.code = 429;
        return false;
    }
    return true;
});

4. Protocol Validation

Enforce specific WebSocket subprotocols or extensions:

app.websocket_upgrade_hook([](const request& req, response& res, bool is_ssl) {
    auto protocol = req.get_header_value("Sec-WebSocket-Protocol");
    if (!is_supported_protocol(protocol)) {
        res.code = 400;
        return false;
    }
    return true;
});

Proposed Implementation

Option 1: Hook/Callback Function

Add a simple hook that's called before WebSocket upgrade:

template<typename Adaptor, typename Handler, typename... Middlewares>
class Connection {
    // ...
    void handle() {
        // ... existing code ...
        else if (req_.upgrade) {
            if (req_.get_header_value("upgrade").substr(0, 2) == "h2") {
                // HTTP/2 handling
            }
            else {
                // NEW: Call websocket upgrade hook if registered
                constexpr bool is_ssl = !std::is_same<typename Adaptor::context, void>::value;
                
                if (handler_->has_websocket_upgrade_hook()) {
                    if (!handler_->call_websocket_upgrade_hook(req_, res, is_ssl)) {
                        // Hook returned false, send response and don't upgrade
                        // ... send res and return ...
                        return;
                    }
                }
                
                // Proceed with normal upgrade
                close_connection_ = true;
                handler_->handle_upgrade(req_, res, std::move(adaptor_));
                return;
            }
        }
    }
};

Option 2: Extend Middleware System

Allow middleware to intercept WebSocket upgrades by adding a new middleware method:

struct MyMiddleware {
    struct context {};
    
    void before_handle(request& req, response& res, context& ctx) {
        // Existing middleware for HTTP requests
    }
    
    // NEW: Handle WebSocket upgrade requests
    bool before_websocket_upgrade(request& req, response& res, context& ctx, bool is_ssl) {
        if (!is_ssl) {
            res.code = 308;
            res.set_header("Location", "wss://...");
            return false; // Don't proceed with upgrade
        }
        return true; // Proceed with upgrade
    }
};

Option 3: WebSocket-Specific Middleware

Introduce a new middleware type specifically for WebSocket handling:

struct WebSocketUpgradeMiddleware {
    struct context {};
    
    // Called before WebSocket upgrade
    bool before_upgrade(request& req, response& res, context& ctx, bool is_ssl) {
        // Return true to proceed, false to abort and send res
        return true;
    }
    
    // Optional: Called after successful upgrade
    void after_upgrade(websocket::connection& conn, context& ctx) {
        // Post-upgrade logic
    }
};

// Usage
crow::App<crow::CORSHandler, WebSocketUpgradeMiddleware> app;

Benefits

  1. No library patching needed - Users can implement custom WebSocket upgrade logic without modifying Crow
  2. Consistent with existing patterns - Similar to how middleware works for HTTP requests
  3. Flexible - Supports various use cases (redirects, auth, rate limiting, etc.)
  4. Type-safe - Can provide SSL status information to the hook
  5. Backward compatible - Optional feature that doesn't affect existing code

Current Workaround

Currently, users must patch Crow's Connection::handle() method directly to implement custom WebSocket upgrade logic, which:

  • Makes upgrading Crow difficult
  • Requires maintaining a fork
  • Isn't portable across projects

Related Code Locations

  • Connection::handle() method (around line 9105 in crow_all.h)
  • WebSocket upgrade logic in handle_upgrade()
  • Middleware system implementation

Questions for Maintainers

  1. Which implementation approach would you prefer?
  2. Should this be part of the existing middleware system or a separate mechanism?
  3. Would you like me to submit a PR implementing this feature?

Additional Context

This feature would make Crow more flexible for production deployments where WebSocket connections need custom handling before upgrade, without requiring infrastructure-level solutions (reverse proxies, load balancers, etc.).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions