Bandit

repository·main·Indexed 23 days ago

https://github.com/mtrudel/bandit

A high-performance, Elixir-native HTTP server for Plug and WebSock applications. Bandit supports HTTP/1.x, HTTP/2, and WebSockets over both HTTP and HTTPS, and serves as the default server for Phoenix since version 1.7.11. It provides a drop-in replacement for Cowboy in Phoenix applications via the Bandit.PhoenixAdapter.

Tokens
3.9K
Snippets
9
Records
20
Agent score
84%

What's inside Bandit

  1. Understand the HTTP/1 process model in Bandit

    main
    In a Bandit server, each HTTP/1 connection is modeled as a single Elixir process. This process is tied to the lifecycle of the underlying TCP connection. If an HTTP client uses the keep-alive feature to send multiple requests over the same connection, all those requests will be serviced by the same process.
  2. How the HTTP/2 process model works in Bandit

    main

    Bandit implements HTTP/2 using a two-tier process model to balance connection management and request concurrency:

    1. Connection Process: One Bandit.HTTP2.Handler process exists per client connection. It implements the ThousandIsland.Handler behaviour and is supervised by Thousand Island. It manages the overall connection state via a Bandit.HTTP2.Connection struct.
    2. Stream Process: One Bandit.HTTP2.StreamProcess exists per HTTP request (stream) within a connection. These are started by the connection process via start_link and are not supervised. They manage stream-specific state via a Bandit.HTTP2.Stream struct.

    Lifecycle & Error Handling:

    • Connection processes live as long as the client is connected.
    • Stream processes live only for the duration of a single stream request (from :init to :closed).
    • If a connection process terminates, all associated stream processes are terminated.
    • If a stream process dies, the connection process handles the exit signal and continues operating, allowing the connection to remain stable even if individual requests fail.
  3. How the WebSocket upgrade mechanism works

    main

    Upgrading an HTTP connection to a WebSocket connection in Bandit involves a coordinated process between Bandit, WebSockAdapter, and Plug.

    1. The HTTP request is first processed as a standard Plug call.
    2. The application decides to upgrade by calling WebSockAdapter.upgrade/4.
    3. WebSockAdapter.upgrade/4 validates the request and calls Plug.Conn.upgrade_adapter/3 to signal Bandit.
    4. At the conclusion of the Plug.call/2 callback, Bandit.Pipeline performs the upgrade.
    5. Bandit.DelegatingHandler switches the connection's handler to Bandit.WebSocket.Handler, handing control to Bandit's WebSocket stack for all future communication.
  4. How HTTP/2 requests are processed with Plugs

    main

    When an HTTP/2 stream is active, Bandit executes the server's configured Plug.

    • State Management: The stream's state is maintained in a Bandit.HTTP2.Stream struct within a Bandit.HTTP2.StreamProcess.
    • Plug Interface: The Plug is called using an instance of the Bandit.Adapter struct.
    • Separation of Concerns:
      • Bandit.Adapter manages HTTP semantics (based on RFC 9110).
      • Bandit.HTTP2.Stream manages transport-specific HTTP/2 concerns (based on RFC 9113).
  5. Understand the Bandit WebSocket process model

    main

    In a Bandit server, every WebSocket connection is modeled as a single process tied to the lifecycle of the underlying connection.

    • Lifecycle: When upgrading from HTTP/1, the existing HTTP/1 handler process transitions into a WebSocket process by changing the delegation target of Bandit.DelegatingHandler.
    • Initialization: During the upgrade, Bandit.DelegatingHandler calls handle_connection/2 to allow the WebSocket handler to initialize startup state.
    • State Management: Connection state is managed using the Bandit.WebSocket.Connection struct and module.
    • Data Flow: Data received via the underlying Thousand Island library is passed to Bandit.WebSocket.Handler.handle_data/3. This method parses the data into WebSocket frames, which are then passed to the configured WebSock handler via the Bandit.WebSocket.Connection.
  6. WebSocket support in Bandit

    main

    Bandit provides full support for WebSockets.

    • Phoenix Users: If you are using Bandit with Phoenix, WebSocket support (for Channels or LiveView) works automatically.
    • Low-level Users: Bandit is compatible with the WebSock and WebSockAdapter libraries, which provide a generic abstraction for WebSockets similar to how Plug works for HTTP.
  7. How HTTP/2 data is read and processed

    main

    The data flow for an HTTP/2 connection follows these steps:

    1. Data Reception: Bytes are received asynchronously via Bandit.HTTP2.Handler.handle_data/3.
    2. Frame Parsing: Bytes are parsed into frames using Bandit.HTTP2.Frame.deserialize/2. Unparsed bytes are buffered to handle fragmented data.
    3. Frame Dispatching: Parsed frames are passed to Bandit.HTTP2.Connection.handle_frame/3 along with a Bandit.HTTP2.Connection struct.
      • Connection-level frames are handled within the connection struct.
      • Stream-level frames are passed to the corresponding Bandit.HTTP2.StreamProcess.
    4. Stream Management: The stream process is responsible for its own state (via Bandit.HTTP2.Stream). Frames sent to streams that are already closed are discarded.
    5. Shutdown: This loop continues until the Bandit.HTTP2.Connection module signals a normal or error-driven connection closure.
  8. How Bandit handles HTTP/1 requests

    main

    The execution flow for an HTTP/1 request follows these steps:

    1. The underlying Thousand Island library calls Bandit.HTTP1.Handler.handle_data/3.
    2. handle_data/3 constructs a Bandit.HTTP1.Socket struct that implements the Bandit.HTTPTransport protocol.
    3. Bandit.Pipeline.run/3 is called to read the request using the Bandit.HTTPTransport protocol.
    4. A Plug.Conn structure is constructed to represent the request.
    5. The Plug.Conn is passed to the configured Plug module.
  9. Set up an HTTPS server with Bandit

    main

    To run Bandit over HTTPS, specify the scheme: :https and provide the paths to your certificate and key files in the options passed to the Bandit child process.

    # lib/my_app/application.ex
    
    defmodule MyApp.Application do
      use Application
    
      def start(_type, _args) do
        children = [
          {
           Bandit,
           plug: MyApp.MyPlug,
           scheme: :https,
           certfile: "/absolute/path/to/cert.pem",
           keyfile: "/absolute/path/to/key.pem"
          }
        ]
    
        opts = [strategy: :one_for_one, name: MyApp.Supervisor]
        Supervisor.start_link(children, opts)
      end
    end
  10. Use Bandit with Plug applications

    main

    You can host a Plug module using Bandit by starting it within your application's supervision tree or by calling Bandit.start_link/1 directly.

    Via Application Supervision Tree

    Add {Bandit, plug: YourPlugModule} to your children list in lib/my_app/application.ex.

    Via Bandit.start_link/1

    For less formal usage, call the function directly:

    # Start an http server on the default port 4000, serving MyApp.MyPlug
    Bandit.start_link(plug: MyPlug)
    # lib/my_app/application.ex
    
    defmodule MyApp.Application do
      use Application
    
      def start(_type, _args) do
        children = [
          {Bandit, plug: MyApp.MyPlug}
        ]
    
        opts = [strategy: :one_for_one, name: MyApp.Supervisor]
        Supervisor.start_link(children, opts)
      end
    end
  11. Use Bandit with Phoenix

    main

    Bandit can be used as a drop-in replacement for Cowboy in Phoenix applications. For Phoenix applications using WebSockets (like Channels or LiveView), ensure you are using Phoenix 1.7 or later.

    1. Add {:bandit, "~> 1.8"} to your mix.exs.
    2. Update your endpoint configuration in config/config.exs to use Bandit.PhoenixAdapter.

    Note: If you have used exotic configuration options in your endpoint, you may need to update them to be compatible with Bandit. Refer to the Bandit.PhoenixAdapter documentation for details.

    # config/config.exs
    
    config :your_app, YourAppWeb.Endpoint,
      adapter: Bandit.PhoenixAdapter, # <---- ADD THIS LINE
      url: [host: "localhost"],
      render_errors: ...