httpuv

repository·main·Indexed 42 days ago

https://github.com/rstudio/httpuv

A low-level HTTP and WebSocket server library for R, built on top of libuv and http-parser. It provides a multithreaded architecture to handle socket and protocol support, allowing developers to create HTTP servers via startServer() and manage WebSocket connections with event handlers.

Tokens
55.5K
Snippets
181
Records
249
Agent score
54%

What's inside httpuv

  1. Overview of libuv features

    main

    libuv is a multi-platform asynchronous I/O library. It provides a full-featured event loop and supports the following core capabilities:

    • Networking: Asynchronous TCP and UDP sockets, and asynchronous DNS resolution.
    • File System: Asynchronous file and file system operations, and file system events.
    • Process Management: Child processes and IPC (Inter-Process Communication) via Unix domain sockets or named pipes (Windows).
    • Concurrency: A thread pool, threading and synchronization primitives, and signal handling.
    • System Utilities: ANSI escape code controlled TTY and a high-resolution clock.
    • Event Loop: Backed by epoll, kqueue, IOCP, and event ports.
  2. Use the HTTP Parser for requests and responses

    main

    The http_parser is a high-performance C parser for HTTP messages (both requests and responses). It is designed to be zero-allocation and non-blocking, making it suitable for high-concurrency applications. It handles persistent streams (keep-alive), chunked encoding, and protocol upgrades.

    Key features:

    • No dependencies and no syscalls.
    • Does not buffer data internally; it can be interrupted at any time.
    • Defends against buffer overflow attacks.
    • Extracts header fields, Content-Length, request methods, status codes, Transfer-Encoding, HTTP versions, URLs, and message bodies.
  3. What is an FS Poll handle and when to use it

    main

    An uv_fs_poll_t handle allows you to monitor a specific file system path for changes.

    When to use it: Use FS Poll handles instead of uv_fs_event_t when working on file systems that do not support native file system event handles. FS Poll works by using stat to detect changes, making it more portable across different file systems at the cost of polling overhead.

    Key Behavior:

    • The handle monitors a path and triggers a callback when changes are detected.
    • If the monitored path becomes inaccessible or does not exist, the callback is invoked with status < 0. The watcher remains active and will trigger again once the path becomes accessible or the error state changes.
  4. What is a uv_stream_t and its implementations?

    main

    A uv_stream_t is an abstract handle type that provides an abstraction for a duplex (two-way) communication channel. Because it is an abstract type, you must use one of the three specific stream implementations provided by libuv:

    1. uv_tcp_t: For TCP networking.
    2. uv_pipe_t: For local pipes (IPC).
    3. uv_tty_t: For terminal/TTY communication.
  5. What is a uv_poll_t handle and when to use it

    main

    A uv_poll_t handle is used to watch file descriptors for readability, writability, and disconnection, similar to the standard poll(2) system call.

    When to use it: Use poll handles primarily to integrate external libraries that rely on the event loop to signal socket status changes (e.g., c-ares or libssh2).

    When NOT to use it: Do not use uv_poll_t for standard networking tasks. uv_tcp_t and uv_udp_t provide faster and more scalable implementations, especially on Windows.

    Important Constraints:

    • Platform differences: On Windows, only sockets can be polled. On Unix, any file descriptor accepted by poll(2) can be used. On AIX, watching for disconnection is not supported.
    • Avoid busyloops: Never have multiple active poll handles for the same socket.
    • Lifecycle management: Do not close a file descriptor while it is being polled by an active handle. You should only close the file descriptor immediately after calling uv_poll_stop or uv_close.
  6. Use uv_timer_t to schedule callbacks

    main

    Timer handles (uv_timer_t) allow you to schedule callbacks to be executed in the future. You can create one-shot timers or repeating timers using the uv_timer_start function.

    Timers are managed by an event loop (uv_loop_t). When a timer is started, you specify a timeout (initial delay) and a repeat interval (subsequent delays), both in milliseconds.

    Key behaviors:

    • One-shot timer: Set repeat to 0.
    • Repeating timer: Set repeat to a non-zero value.
    • Immediate execution: Setting timeout to 0 causes the callback to fire on the next event loop iteration.
    • Updating: If a timer is already active, calling uv_timer_start updates its configuration.
    /* Example conceptual usage */
    int uv_timer_init(uv_loop_t* loop, uv_timer_t* handle);
    uv_timer_start(handle, callback, timeout_ms, repeat_ms);
  7. How signal handles work in libuv

    main

    Signal handles (uv_signal_t) implement Unix-style signal handling on a per-event loop basis. They allow an application to register callbacks that are triggered when specific system signals are received.

    Platform-Specific Behavior

    Windows

    Some signals are emulated on Windows:

    • SIGINT: Delivered on CTRL+C (unless terminal raw mode is enabled).
    • SIGBREAK: Delivered on CTRL + BREAK.
    • SIGHUP: Generated when the console window is closed. The program has approximately 10 seconds to perform cleanup before Windows terminates it.
    • SIGWINCH: Raised when the console is resized. Note that in console emulators or 32-bit apps on 64-bit systems, delivery may not be timely. For uv_tty_t handles, detection depends on cursor movement (writable) or raw mode/reading (readable).
    • Unsupported signals: While watchers can be created for SIGILL, SIGABRT, SIGFPE, SIGSEGV, SIGTERM, and SIGKILL, they will never be received. Programmatic calls to raise() or abort() are also not detected.

    Unix

    • SIGKILL and SIGSTOP cannot be caught.
    • Handling SIGBUS, SIGFPE, SIGILL, or SIGSEGV via libuv results in undefined behavior.
    • SIGABRT generated by abort() (e.g., via assert()) will not be caught.
    • Linux Warning: Do not install watchers for SIGRT0 and SIGRT1 (signals 32 and 33), as they are used by NPTL pthreads for thread management. Doing so leads to unpredictable behavior.
  8. How libuv file system operations work

    main

    libuv provides cross-platform synchronous and asynchronous file system operations.

    Execution Model:

    • All file operations are executed on the threadpool.
    • Synchronous vs Asynchronous: Every function accepts a callback. If you pass NULL as the callback, the operation is performed synchronously. If you provide a callback, the operation is performed asynchronously.

    Platform Note:

    • On Windows, uv_fs_* functions use UTF-8 encoding for paths.
  9. Handle errors in libuv 1.0+

    main

    Libuv 1.0 overhauled error handling. Instead of functions returning -1 and requiring a call to uv_last_error, functions and status parameters now return the actual error code directly. Success is 0, and errors are represented by negative numbers.

    // libuv 1.0 error checking
    r = uv_listen((uv_stream_t*) server, 511, NULL);
    if (r < 0) {
      /* r contains the error code, e.g., UV_EADDRINUSE */
    }
  10. Understand libuv handles and requests

    main

    libuv provides two primary abstractions to interact with the event loop:

    • Handles: Long-lived objects capable of performing operations while active. Examples include:
      • Prepare handles: Callbacks are executed once every loop iteration while active.
      • TCP server handles: Callbacks are executed every time a new connection is received.
    • Requests: Typically short-lived operations. These can be performed over a handle (e.g., write requests to send data via a handle) or as standalone operations (e.g., getaddrinfo requests which run directly on the loop without needing a handle).
  11. Manage event loop lifecycle with ref and unref

    main

    The libuv event loop runs as long as there are active handles. You can manually influence the loop's reference count to control when it exits.

    Concepts

    • Active Handles: Every started handle increases the loop's reference count. When the count reaches zero, uv_run exits.
    • uv_ref(uv_handle_t*): Manually increases the reference count of a handle.
    • uv_unref(uv_handle_t*): Decreases the reference count of a handle. An unref'd handle can still be active and run its callbacks, but it will not prevent the event loop from exiting if it is the only remaining watcher.

    Use Case: Background Tasks

    Use uv_unref for background tasks like garbage collection or heartbeat timers. This allows the program to exit automatically once all primary work (e.g., network requests) is complete, without needing to explicitly stop the background timers.

    // Example: Unref a timer so it doesn't keep the loop alive
    uv_timer_init(loop, &timer_req);
    uv_timer_start(&timer_req, callback, 5000, 2000);
    uv_unref((uv_handle_t*)&timer_req);
  12. Use UDP for connectionless communication

    main

    UDP in libuv is connectionless and unreliable. It uses the uv_udp_t handle for receiving and uv_udp_send_t requests for sending.

    Key UDP behaviors:

    • Binding: You can bind to 0.0.0.0 to listen on all interfaces, or use port 0 to let the OS assign a random port. To use a broadcast address, use 255.255.255.255.
    • Broadcast: When sending broadcast packets, you must set the broadcast flag, otherwise you will receive an EACCES error.
    • Read Callbacks: Since UDP is connectionless, the read callback provides an extra parameter containing the sender's address (addr).
    • Data Integrity: If the provided buffer is too small, the flags parameter may contain UV_UDP_PARTIAL. In this case, the OS will discard the data that did not fit.
    • Address Info: If addr is NULL in a read callback, it indicates there is nothing to read (an empty datagram was not received).
    /* UDP Setup Example */
    uv_udp_bind(&udp_handle, (const struct sockaddr*)&addr, UV_UDP_REUSEADDR);
    uv_udp_recv(&udp_handle, alloc_cb, on_read);