µWebSockets

repository·master·Indexed 12 days ago

https://github.com/unetworking/uwebsockets

A high-performance, secure, and standards-compliant C++17 web server library designed for low-latency, high-throughput applications. It supports HTTP and WebSockets with built-in routing, pub/sub, and TLS 1.3. Optimized for speed and memory footprint, it features a 'one app per thread' scaling model and integrates with event-loops like libuv, ASIO, and GCD via µSockets.

Tokens
5.9K
Snippets
16
Records
24
Agent score
97%

What's inside µWebSockets

  1. Overview of µWebSockets

    master

    µWebSockets is a high-performance, secure, and standards-compliant web server designed for demanding applications. It is optimized for speed and memory footprint, capable of performing encrypted TLS 1.3 messaging faster than many servers perform unencrypted messaging.

    Key features include:

    • Optimized Security: Meticulously optimized for speed and memory; participates in Google's OSS-Fuzz with high coverage.
    • Rapid Scripting: While written in C/C++, it offers seamless integration for Node.js via µWebSockets.js.
    • Standards Compliant: Maintains a perfect Autobahn|Testsuite score.
    • Built-in Features: Includes a convenient URL router with wildcard and parameter support, and efficient pub/sub features for WebSockets.
    • Customizable Architecture: Built on µSockets, allowing control over event-loop integrations (libuv, ASIO, GCD, epoll/kqueue) and cryptography layers via compile-time flags.
  2. Understand the purpose of 'hello world' benchmarks

    master

    For the high-performance applications µWebSockets is designed for, "hello world" benchmarks (such as message echoing of 512 bytes to 16 kB) are the most accurate way to gauge performance. These tests measure the efficiency of the core server plumbing:

    1. Receive
    2. Timeout clear
    3. Emit to app
    4. Timeout set
    5. Send

    This type of benchmarking is highly relevant for latency-sensitive or high-concurrency use cases like:

    • IO-gaming (latency)
    • Signalling (memory overhead)
    • Trading/Finance (latency)
    • Chatting/Notifications (memory overhead)
  3. Handle WebSocket backpressure

    master

    Sending data on a WebSocket can build up backpressure.

    1. Check return values: ws.send() returns an enum: SUCCESS, BACKPRESSURE, or DROPPED.
    2. Handle BACKPRESSURE: If send returns BACKPRESSURE, stop sending data until the .drain event fires.
    3. Handle DROPPED: If you configured maxBackpressure in your context, an attempt to send that exceeds the limit will return DROPPED, meaning the message was canceled and not queued.
    4. Monitor buffer: Use ws.getBufferedAmount() inside the .drain handler to check if the buffer has cleared.
  4. Understand the µWebSockets threading and scaling model

    master

    µWebSockets is designed with minimalism and performance in mind. Key architectural concepts include:

    • Async-only & Single-threaded: The implementation is asynchronous and runs locally to a single thread. It is not thread-safe.
    • Scaling: To scale, you should run multiple individual threads, similar to how Node.js scales using individual processes.
    • Protocol Handling: The library handles boilerplate logic such as heartbeat timeouts, backpressure, and ping/pong automatically, allowing you to focus on business logic.
    • Interface: It follows an ExpressJS-like interface where you attach callbacks to specific URL routes.
  5. Start the server and run the event loop

    master

    After defining routes, use .listen() to bind to a port and .run() to start the event loop.

    • App.listen(port, callback): Starts listening. The callback receives a listenSocket (or nullptr on failure).
    • App.run(): Enters the blocking event loop. This call will only return (fall through) when all async work is finished (e.g., all sockets are closed, timers removed, and the listen socket is stopped).

    Because the App uses RAII, once .run() returns and the App object goes out of scope, all memory is automatically cleaned up.

    int main() {
        uWS::App().get("/*", [](auto *res, auto *req) {
            res->end("Hello World!");
        }).listen(9001, [](auto *listenSocket) {
            if (listenSocket) {
                std::cout << "Listening for connections..." << std::endl;
            }
        }).run();
    
        std::cout << "Shoot! We failed to listen and the App fell through, exiting now!" << std::endl;
    }
  6. Stream large data using res.tryEnd() and corking

    master

    Avoid calling res.end(huge_buffer) as it can cause massive backpressure spikes. Instead, stream data part-by-part using res.tryEnd() in combination with res.onWritable and res.onAborted callbacks.

    Using Corking for Performance

    Corking is critical for efficient data packing and sending. While simple cases are corked by default, you should manually wrap multiple send calls in res->cork() to ensure they are sent in a single syscall/SSL block.

    res->cork([res]() {
        res->end("This Http response will be properly corked and efficient in all cases");
    });
    res->cork([res]() {
        res->end("This Http response will be properly corked and efficient in all cases");
    });
  7. Build µWebSockets with specific SSL and Event-Loop implementations

    master

    µWebSockets allows you to control the compiled composition of the networking stack using build flags. This is useful for choosing between different cryptography libraries (like OpenSSL or WolfSSL) and event-loop integrations (like libuv).

    Common build commands:

    • To build examples using WolfSSL and libuv: WITH_WOLFSSL=1 WITH_LIBUV=1 make examples
    • To build examples using OpenSSL and the native kernel: WITH_OPENSSL=1 make examples
    WITH_WOLFSSL=1 WITH_LIBUV=1 make examples
    WITH_OPENSSL=1 make examples
  8. Initialize a uWS::App or uWS::SSLApp

    master

    To start a µWebSockets server, construct an App (for regular TCP) or an SSLApp (for TLS/SSL). The uWS::SSLApp constructor requires a struct containing SSL options like cert and key. Both app types share the same interface and follow the builder pattern, allowing you to chain method calls.

    Note: The library is single-threaded. A socket created on one thread cannot be used on another. The only thread-safe function is Loop::defer, which schedules a function to run on the loop's specific thread.

    uWS::App().get("/hello", [](auto *res, auto *req) {
        res->end("Hello World!");
    });
  9. Compile µWebSockets

    master

    µWebSockets is a header-only C++17 library that depends on uSockets (a platform-specific C project for Linux, macOS, and Windows).

    On Linux and macOS, you can use the provided Makefile to build the library and its examples. To build all examples with SSL enabled, use the WITH_OPENSSL=1 flag. Note that SSL examples require valid certificate and key paths to listen successfully.

    WITH_OPENSSL=1 make
  10. Use CachingApp for HTTP response caching

    master

    The CachingApp class extends the standard uWS::App (or uWS::SSLApp) to provide built-in HTTP response caching. It allows you to define routes that automatically cache their responses based on the URL and a specified expiration time.

    When a request matches a cached URL and the cache has not yet expired, CachingApp serves the buffered response immediately without re-running the handler. If the cache is expired or missing, the handler is executed, and the resulting response is buffered for future requests.

    // Example conceptual usage of CachingApp
    uWS::CachingApp<false> app;
    
    app.get("/api/data", [](uWS::CachingHttpResponse* res, uWS::HttpRequest* req) {
        res->write("{\"status\": \"cached\"}");
        res->end();
    }, 60); // Cache this response for 60 seconds
    
    app.listen(3000, [](auto* listen_socket) {
        if (listen_socket) {
            std::cout << "Listening on port 3000" << std::endl;
        }
    });
  11. Initialize a uWebSockets App

    master

    To start a server, instantiate uWS::App for non-SSL connections or uWS::SSLApp for SSL connections. The App class uses a builder pattern to configure routes, WebSocket behaviors, and listening parameters. The application operates on an implicit thread-local Loop.

    Basic usage involves configuring routes and then calling .run() to start the event loop.

    #include "App.h"
    
    int main() {
        uWS::App()
            .get("/hello", [](auto *res, auto *req) {
                res->end("Hello World!");
            })
            .listen(3000, [](auto *listen_socket) {
                if (listen_socket)
                    std::cout << "Listening on port 3000" << std::endl;
            })
            .run();
    }
  12. Avoid common benchmarking mistakes with Node.js clients

    master

    When benchmarking µWebSockets, avoid using scripted Node.js clients like autocannon or ws. Because µWebSockets is significantly faster than Node.js (up to 12x), a standard Node.js client will likely become the bottleneck before µWebSockets reaches its limit.

    To ensure an accurate benchmark, you must verify that µWebSockets is being stressed to 100% CPU-time. If the CPU usage of the µWebSockets process is not at 100%, you are benchmarking the performance of your client rather than the server.