Skip to content

hb_http_server.erl - HTTP Server & AO-Core Router

Overview

Purpose: HTTP server routing requests to AO-Core resolver
Module: hb_http_server
Behavior: Cowboy HTTP handler
Pattern: HTTP request → Message → AO-Core → Message → HTTP response

This module implements a Cowboy-based HTTP server that marshals incoming HTTP requests into HyperBEAM messages, passes them to the AO-Core resolver, and converts the results back into HTTP responses. The server configuration is stored in Cowboy's environment and can be dynamically updated.

Core Responsibilities

  • HTTP Server Lifecycle: Start, configure, and manage HTTP listeners
  • Request Marshaling: Convert HTTP requests to HyperBEAM messages
  • AO-Core Integration: Route messages through singleton resolution
  • Response Conversion: Transform message results to HTTP responses
  • Configuration Management: Dynamic server options via Cowboy environment
  • Hook System: Execute startup hooks for configuration modification
  • Greeting Display: ASCII art banner with configuration on startup

Dependencies

  • HyperBEAM: hb_http, hb_ao, hb_util, hb_maps, hb_opts, hb_message, hb_singleton, hb_store, hb_format, hb_private, hb_features
  • Arweave: ar_wallet
  • Hooks: dev_hook
  • HTTP: cowboy, cowboy_req, ranch, gun
  • OTP: kernel, stdlib, inets, ssl, os_mon
  • Metrics: prometheus, prometheus_cowboy
  • Includes: include/hb.hrl

Public Functions Overview

%% Server Lifecycle
-spec start() -> {ok, pid()}.
-spec start(Opts) -> {ok, pid()}.
-spec start_node() -> NodeURL.
-spec start_node(Opts) -> NodeURL.
 
%% Configuration Management
-spec set_opts(Opts) -> ok.
-spec set_opts(Request, Opts) -> {ok, UpdatedOpts}.
-spec get_opts() -> Opts.
-spec get_opts(NodeMsg) -> Opts.
-spec set_default_opts(Opts) -> Opts.
-spec set_proc_server_id(ServerID) -> ok.
 
%% Cowboy Callbacks
-spec init(Req, ServerID) -> {ok, Req, State}.
-spec allowed_methods(Req, State) -> {[Method], Req, State}.

Public Functions

1. start/0, start/1

-spec start() -> {ok, pid()}
    when
        pid() :: pid().
 
-spec start(Opts) -> {ok, pid()}
    when
        Opts :: map(),
        pid() :: pid().

Description: Start the HTTP server with configuration from file or provided options. Initializes store, loads wallet, displays greeting banner, and starts Cowboy listener.

Startup Flow:
Load Config File (config.flat)

Merge with Environment Defaults

Initialize Store

Load Private Wallet

Display Greeting Banner

Execute Startup Hooks

Start Cowboy Listener

Return {ok, ListenerPID}
Test Code:
-module(hb_http_server_start_test).
-include_lib("eunit/include/eunit.hrl").
-include("include/hb.hrl").
 
start_with_opts_test() ->
    Config = #{
        port => 10000 + rand:uniform(10000),
        store => [hb_test_utils:test_store()],
        priv_wallet => ar_wallet:new()
    },
    {ok, Pid} = hb_http_server:start(Config),
    ?assert(is_pid(Pid)).
 
start_returns_listener_test() ->
    Config = #{
        port => 10000 + rand:uniform(10000),
        priv_wallet => ar_wallet:new()
    },
    Result = hb_http_server:start(Config),
    ?assertMatch({ok, _}, Result).

2. start_node/0, start_node/1

-spec start_node() -> NodeURL
    when
        NodeURL :: binary().
 
-spec start_node(Opts) -> NodeURL
    when
        Opts :: map(),
        NodeURL :: binary().

Description: Start a test node with random port and return its URL. Used primarily for testing.

Implementation:
start_node(Opts) ->
    application:ensure_all_started([kernel, stdlib, inets, ssl, ranch, cowboy, gun, os_mon]),
    hb:init(),
    hb_sup:start_link(Opts),
    ServerOpts = set_default_opts(Opts),
    {ok, _Listener, Port} = new_server(ServerOpts),
    <<"http://localhost:", (integer_to_binary(Port))/binary, "/">>.
Test Code:
-module(hb_http_server_node_test).
-include_lib("eunit/include/eunit.hrl").
-include("include/hb.hrl").
 
start_node_test() ->
    Node = hb_http_server:start_node(),
    ?assert(is_binary(Node)),
    ?assert(binary:match(Node, <<"http://localhost:">>) =/= nomatch).
 
start_node_with_opts_test() ->
    Node = hb_http_server:start_node(#{
        <<"test-key">> => <<"test-value">>
    }),
    {ok, Info} = hb_http:get(Node, <<"/~meta@1.0/info">>, #{}),
    ?assertEqual(<<"test-value">>, hb_ao:get(<<"test-key">>, Info, #{})).
 
start_node_returns_url_test() ->
    Node = hb_http_server:start_node(#{}),
    % URL should end with /
    ?assertEqual(<<"/">>, binary:part(Node, byte_size(Node) - 1, 1)).
 
start_node_accessible_test() ->
    Node = hb_http_server:start_node(#{}),
    % Should be able to get info from the node
    {ok, Info} = hb_http:get(Node, <<"/~meta@1.0/info">>, #{}),
    ?assert(is_map(Info)).

3. set_opts/1, set_opts/2

-spec set_opts(Opts) -> ok
    when
        Opts :: map().
 
-spec set_opts(Request, Opts) -> {ok, UpdatedOpts}
    when
        Request :: map(),
        Opts :: map(),
        UpdatedOpts :: map().

Description: Update server configuration dynamically. Single-arity version updates Cowboy environment; two-arity version merges request with options and maintains history.

set_opts/1 - Update Cowboy Environment:
set_opts(Opts) ->
    case hb_opts:get(http_server, no_server_ref, Opts) of
        no_server_ref -> ok;
        ServerRef -> cowboy:set_env(ServerRef, node_msg, Opts)
    end.
set_opts/2 - Merge and Track History:
set_opts(Request, Opts) ->
    PreparedOpts = hb_opts:mimic_default_types(Opts, false, Opts),
    PreparedRequest = hb_opts:mimic_default_types(
        hb_message:uncommitted(Request),
        false,
        Opts
    ),
    MergedOpts = maps:merge(PreparedOpts, PreparedRequest),
    History = hb_opts:get(node_history, [], Opts) ++ [ResetRequest],
    FinalOpts = MergedOpts#{
        http_server => hb_opts:get(http_server, no_server, Opts),
        node_history => History
    },
    {set_opts(FinalOpts), FinalOpts}.
Test Code:
-module(hb_http_server_set_opts_test).
-include_lib("eunit/include/eunit.hrl").
-include("include/hb.hrl").
 
set_opts_merge_test() ->
    Wallet = ar_wallet:new(),
    _Node = hb_http_server:start_node(#{priv_wallet => Wallet}),
    
    % Get initial options
    Opts = hb_http_server:get_opts(#{
        http_server => hb_util:human_id(ar_wallet:to_address(Wallet))
    }),
    
    % Update with new request
    Request = #{<<"hello">> => <<"world">>},
    {ok, UpdatedOpts} = hb_http_server:set_opts(Request, Opts),
    
    % Verify merge happened
    ?assertEqual(<<"world">>, hb_opts:get(<<"hello">>, not_found, UpdatedOpts)).
 
set_opts_history_test() ->
    Wallet = ar_wallet:new(),
    _Node = hb_http_server:start_node(#{priv_wallet => Wallet}),
    
    Opts = hb_http_server:get_opts(#{
        http_server => hb_util:human_id(ar_wallet:to_address(Wallet))
    }),
    
    % First update
    {ok, Opts1} = hb_http_server:set_opts(#{<<"key1">> => <<"val1">>}, Opts),
    History1 = hb_opts:get(node_history, [], Opts1),
    ?assertEqual(1, length(History1)),
    
    % Second update
    {ok, Opts2} = hb_http_server:set_opts(#{<<"key2">> => <<"val2">>}, Opts1),
    History2 = hb_opts:get(node_history, [], Opts2),
    ?assertEqual(2, length(History2)).
 
set_opts_preserves_server_ref_test() ->
    Wallet = ar_wallet:new(),
    _Node = hb_http_server:start_node(#{priv_wallet => Wallet}),
    ServerRef = hb_util:human_id(ar_wallet:to_address(Wallet)),
    
    Opts = hb_http_server:get_opts(#{http_server => ServerRef}),
    {ok, UpdatedOpts} = hb_http_server:set_opts(#{<<"new">> => <<"data">>}, Opts),
    
    ?assertEqual(ServerRef, hb_opts:get(http_server, not_found, UpdatedOpts)).

4. get_opts/0, get_opts/1

-spec get_opts() -> Opts
    when
        Opts :: map().
 
-spec get_opts(NodeMsg) -> Opts
    when
        NodeMsg :: map(),
        Opts :: map().

Description: Retrieve current server configuration from Cowboy environment.

Implementation:
get_opts() ->
    get_opts(#{http_server => get(server_id)}).
 
get_opts(NodeMsg) ->
    ServerRef = hb_opts:get(http_server, no_server_ref, NodeMsg),
    cowboy:get_env(ServerRef, node_msg, no_node_msg).
Test Code:
-module(hb_http_server_get_opts_test).
-include_lib("eunit/include/eunit.hrl").
-include("include/hb.hrl").
 
get_opts_returns_map_test() ->
    Wallet = ar_wallet:new(),
    _Node = hb_http_server:start_node(#{priv_wallet => Wallet}),
    
    Opts = hb_http_server:get_opts(#{
        http_server => hb_util:human_id(ar_wallet:to_address(Wallet))
    }),
    ?assert(is_map(Opts)).
 
get_opts_contains_wallet_test() ->
    Wallet = ar_wallet:new(),
    _Node = hb_http_server:start_node(#{priv_wallet => Wallet}),
    
    Opts = hb_http_server:get_opts(#{
        http_server => hb_util:human_id(ar_wallet:to_address(Wallet))
    }),
    ?assert(maps:is_key(priv_wallet, Opts)).
 
get_opts_contains_port_test() ->
    Wallet = ar_wallet:new(),
    _Node = hb_http_server:start_node(#{priv_wallet => Wallet}),
    
    Opts = hb_http_server:get_opts(#{
        http_server => hb_util:human_id(ar_wallet:to_address(Wallet))
    }),
    Port = hb_opts:get(port, not_found, Opts),
    ?assert(is_integer(Port)).
 
get_opts_contains_store_test() ->
    Wallet = ar_wallet:new(),
    _Node = hb_http_server:start_node(#{priv_wallet => Wallet}),
    
    Opts = hb_http_server:get_opts(#{
        http_server => hb_util:human_id(ar_wallet:to_address(Wallet))
    }),
    ?assert(maps:is_key(store, Opts)).

5. set_default_opts/1

-spec set_default_opts(Opts) -> OptsWithDefaults
    when
        Opts :: map(),
        OptsWithDefaults :: map().

Description: Apply default values to server options: random port (10000-60000), new wallet, test store.

Defaults:
#{
    port => RandomPort,           % 10000-60000
    priv_wallet => NewWallet,     % Fresh RSA-4096 keypair
    store => [TestStore],         % In-memory test store
    address => HumanAddress,      % Base64url address
    force_signed => true          % Require signatures
}
Test Code:
-module(hb_http_server_set_default_opts_test).
-include_lib("eunit/include/eunit.hrl").
-include("include/hb.hrl").
 
set_default_opts_adds_port_test() ->
    Opts = hb_http_server:set_default_opts(#{}),
    Port = maps:get(port, Opts),
    ?assert(is_integer(Port)),
    ?assert(Port >= 10000),
    ?assert(Port =< 60000).
 
set_default_opts_adds_wallet_test() ->
    Opts = hb_http_server:set_default_opts(#{}),
    ?assert(maps:is_key(priv_wallet, Opts)).
 
set_default_opts_adds_store_test() ->
    Opts = hb_http_server:set_default_opts(#{}),
    ?assert(maps:is_key(store, Opts)).
 
set_default_opts_adds_address_test() ->
    Opts = hb_http_server:set_default_opts(#{}),
    Address = maps:get(address, Opts),
    ?assert(is_binary(Address)).
 
set_default_opts_preserves_passed_port_test() ->
    Opts = hb_http_server:set_default_opts(#{port => 9999}),
    ?assertEqual(9999, maps:get(port, Opts)).
 
set_default_opts_preserves_passed_wallet_test() ->
    Wallet = ar_wallet:new(),
    Opts = hb_http_server:set_default_opts(#{priv_wallet => Wallet}),
    ?assertEqual(Wallet, maps:get(priv_wallet, Opts)).
 
set_default_opts_force_signed_test() ->
    Opts = hb_http_server:set_default_opts(#{}),
    ?assertEqual(true, maps:get(force_signed, Opts)).

6. set_proc_server_id/1

-spec set_proc_server_id(ServerID) -> ok
    when
        ServerID :: binary().

Description: Store server ID in process dictionary for current process.

Implementation:
set_proc_server_id(ServerID) ->
    put(server_id, ServerID).
Test Code:
-module(hb_http_server_proc_id_test).
-include_lib("eunit/include/eunit.hrl").
-include("include/hb.hrl").
 
set_proc_server_id_test() ->
    ServerID = <<"test-server-id">>,
    hb_http_server:set_proc_server_id(ServerID),
    ?assertEqual(ServerID, get(server_id)).
 
set_proc_server_id_overwrites_test() ->
    hb_http_server:set_proc_server_id(<<"first">>),
    hb_http_server:set_proc_server_id(<<"second">>),
    ?assertEqual(<<"second">>, get(server_id)).
 
set_proc_server_id_binary_test() ->
    Wallet = ar_wallet:new(),
    ServerID = hb_util:human_id(ar_wallet:to_address(Wallet)),
    hb_http_server:set_proc_server_id(ServerID),
    ?assert(is_binary(get(server_id))).

7. init/2 (Cowboy Callback)

-spec init(Req, ServerID) -> {ok, Req, State}
    when
        Req :: cowboy_req:req(),
        ServerID :: binary(),
        State :: term().

Description: Handle incoming HTTP request. Marshals request to message, resolves through AO-Core, handles errors, and sends response.

Request Flow:
Cowboy Request

Set Process Server ID

Get Node Configuration

Convert to TABM Singleton Messages

Resolve Through AO-Core

Handle Success/Error

Convert to HTTP Response

Send to Client
Error Handling:
try
    Messages = hb_http:req_to_tabm_singleton(Req, undefined, NodeMsg),
    Results = hb_ao:resolve_many(Messages, NodeMsg),
    case dev_monitor:get_error(Results) of
        no_error ->
            hb_http:reply(Req, lists:last(Results), <<>>, NodeMsg);
        ErrorMsg ->
            format_error_response(Req, ErrorMsg, NodeMsg)
    end
catch
    Type:Error:Stack ->
        format_error_response(Req, {Type, Error, Stack}, NodeMsg)
end.
Test Code:
-module(hb_http_server_init_test).
-include_lib("eunit/include/eunit.hrl").
-include("include/hb.hrl").
 
init_handles_get_request_test() ->
    Node = hb_http_server:start_node(#{}),
    {ok, Response} = hb_http:get(Node, <<"/~meta@1.0/info">>, #{}),
    ?assert(is_map(Response)).
 
init_handles_path_request_test() ->
    Node = hb_http_server:start_node(#{}),
    {ok, Response} = hb_http:get(Node, <<"/~meta@1.0/info/port">>, #{}),
    ?assert(is_integer(Response)).
 
init_sets_server_id_test() ->
    Wallet = ar_wallet:new(),
    Node = hb_http_server:start_node(#{priv_wallet => Wallet}),
    {ok, Info} = hb_http:get(Node, <<"/~meta@1.0/info">>, #{}),
    Address = hb_ao:get(<<"address">>, Info, #{}),
    ?assert(is_binary(Address)).
 
init_returns_node_config_test() ->
    Node = hb_http_server:start_node(#{<<"custom-key">> => <<"custom-value">>}),
    {ok, Info} = hb_http:get(Node, <<"/~meta@1.0/info">>, #{}),
    ?assertEqual(<<"custom-value">>, hb_ao:get(<<"custom-key">>, Info, #{})).

8. allowed_methods/2 (Cowboy Callback)

-spec allowed_methods(Req, State) -> {[Method], Req, State}
    when
        Req :: cowboy_req:req(),
        State :: term(),
        Method :: binary().

Description: Return list of allowed HTTP methods.

Implementation:
allowed_methods(Req, State) ->
    {[<<"GET">>, <<"POST">>, <<"PUT">>, <<"DELETE">>, <<"OPTIONS">>, <<"PATCH">>], Req, State}.
Test Code:
-module(hb_http_server_methods_test).
-include_lib("eunit/include/eunit.hrl").
-include("include/hb.hrl").
 
allowed_methods_test() ->
    MockReq = #{},
    MockState = <<"test-state">>,
    {Methods, _Req, _State} = hb_http_server:allowed_methods(MockReq, MockState),
    ?assert(is_list(Methods)),
    ?assert(lists:member(<<"GET">>, Methods)),
    ?assert(lists:member(<<"POST">>, Methods)),
    ?assert(lists:member(<<"PUT">>, Methods)),
    ?assert(lists:member(<<"DELETE">>, Methods)),
    ?assert(lists:member(<<"OPTIONS">>, Methods)),
    ?assert(lists:member(<<"PATCH">>, Methods)).
 
allowed_methods_returns_six_test() ->
    {Methods, _, _} = hb_http_server:allowed_methods(#{}, #{}),
    ?assertEqual(6, length(Methods)).
 
allowed_methods_preserves_state_test() ->
    MockState = <<"my-state">>,
    {_Methods, _Req, ReturnedState} = hb_http_server:allowed_methods(#{}, MockState),
    ?assertEqual(MockState, ReturnedState).

Server Configuration

Configuration File Format

Default location: config.flat

#{
    % Network
    port => 8734,
    host => <<"localhost">>,
    
    % Security
    priv_key_location => <<"hyperbeam-key.json">>,
    force_signed => true,
    
    % Storage
    store => [
        #{type => rocksdb, path => <<"data/rocksdb">>}
    ],
    store_defaults => #{
        create_if_missing => true
    },
    
    % Monitoring
    prometheus => true,
    
    % Timeouts
    idle_timeout => 300000,  % 5 minutes
    
    % Protocol
    protocol => http2 | http3,
    
    % Hooks
    on => #{
        <<"start">> => StartHookMessage
    }
}.

Startup Hooks

Start Hook Execution

HookMsg = #{<<"body">> => NodeMsg},
Result = dev_hook:on(<<"start">>, HookMsg, NodeMsg),
{ok, #{<<"body">> := ModifiedNodeMsg}} = Result.
Use Cases:
  • Modify server configuration dynamically
  • Initialize custom devices
  • Set up monitoring
  • Configure routing
Test Code:
set_node_opts_test() ->
    Node = hb_http_server:start_node(#{
        on => #{
            <<"start">> => #{
                <<"device">> => #{
                    <<"start">> => fun(_, #{<<"body">> := NodeMsg}, _) ->
                        {ok, #{<<"body">> => NodeMsg#{<<"test-success">> => true}}}
                    end
                }
            }
        }
    }),
    {ok, LiveOpts} = hb_http:get(Node, <<"/~meta@1.0/info">>, #{}),
    ?assert(hb_ao:get(<<"test-success">>, LiveOpts, false, #{})).

Greeting Banner

ASCII Art Display

===========================================================
==    ██╗  ██╗██╗   ██╗██████╗ ███████╗██████╗           ==
==    ██║  ██║╚██╗ ██╔╝██╔══██╗██╔════╝██╔══██╗          ==
==    ███████║ ╚████╔╝ ██████╔╝█████╗  ██████╔╝          ==
==    ██╔══██║  ╚██╔╝  ██╔═══╝ ██╔══╝  ██╔══██╗          ==
==    ██║  ██║   ██║   ██║     ███████╗██║  ██║          ==
==    ╚═╝  ╚═╝   ╚═╝   ╚═╝     ╚══════╝╚═╝  ╚═╝          ==
==                                                       ==
==        ██████╗ ███████╗ █████╗ ███╗   ███╗ VERSION:   ==
==        ██╔══██╗██╔════╝██╔══██╗████╗ ████║      v1.0. ==
==        ██████╔╝█████╗  ███████║██╔████╔██║            ==
==        ██╔══██╗██╔══╝  ██╔══██║██║╚██╔╝██║ EAT GLASS, ==
==        ██████╔╝███████╗██║  ██║██║ ╚═╝ ██║ BUILD THE  ==
==        ╚═════╝ ╚══════╝╚═╝  ╚═╝╚═╝     ╚═╝    FUTURE. ==
===========================================================
== Node activate at: http://localhost:8734              ==
== Operator: xABCD...1234                               ==
===========================================================
== Config:                                               ==
===========================================================
   #{port => 8734, ...}
===========================================================

Suppression: Automatically disabled in test mode


Error Handling

Error Response Format

format_error_response(Req, ErrorMsg, NodeMsg) ->
    Singleton = #{
        <<"status">> => 500,
        <<"status-reason">> => <<"Internal Server Error">>,
        <<"content-type">> => <<"text/plain">>
    },
    FormattedErrorMsg = iolist_to_binary([
        "Error: ", hb_util:bin(ErrorMsg), "\n",
        "Path: ", cowboy_req:path(Req), "\n",
        "Method: ", cowboy_req:method(Req)
    ]),
    hb_http:reply(Req, Singleton, FormattedErrorMsg, NodeMsg).
Error Types:
  • {error, Reason} - Standard errors
  • {throw, Exception} - Exceptions
  • {exit, Reason} - Process exits
  • {Type, Error, Stack} - Catches with stack trace

Server Restart

Dynamic Server Updates

% Start first server
Node1 = hb_http_server:start_node(#{
    <<"key">> => <<"value1">>,
    priv_wallet => Wallet
}),
 
% Restart with same wallet (updates configuration)
Node2 = hb_http_server:start_node(#{
    <<"key">> => <<"value2">>,
    priv_wallet => Wallet
}),
 
% Node2 URL reflects updated configuration
{ok, <<"value2">>} = hb_http:get(Node2, <<"/~meta@1.0/info/key">>, #{}).
Test Code:
restart_server_test() ->
    Wallet = ar_wallet:new(),
    BaseOpts = #{
        <<"test-key">> => <<"server-1">>,
        priv_wallet => Wallet,
        protocol => http2
    },
    _Node1 = hb_http_server:start_node(BaseOpts),
    Node2 = hb_http_server:start_node(BaseOpts#{<<"test-key">> => <<"server-2">>}),
    ?assertEqual(
        {ok, <<"server-2">>},
        hb_http:get(Node2, <<"/~meta@1.0/info/test-key">>, #{protocol => http2})
    ).

Common Patterns

%% Start production server
{ok, _} = hb_http_server:start().
 
%% Start with custom configuration
{ok, _} = hb_http_server:start(#{
    port => 9000,
    store => [#{type => rocksdb, path => <<"data">>}],
    priv_key_location => <<"keys/node.json">>
}).
 
%% Start test node
Node = hb_http_server:start_node(),
{ok, Info} = hb_http:get(Node, <<"/~meta@1.0/info">>, #{}).
 
%% Dynamic configuration update
Opts = hb_http_server:get_opts(),
{ok, NewOpts} = hb_http_server:set_opts(#{<<"new-key">> => <<"value">>}, Opts).
 
%% Get current server configuration
Config = hb_http_server:get_opts(),
Port = hb_opts:get(port, 8734, Config).
 
%% Use startup hook
Node = hb_http_server:start_node(#{
    on => #{
        <<"start">> => #{
            <<"device">> => #{
                <<"start">> => fun(_, #{<<"body">> := Msg}, _) ->
                    io:format("Server starting with config: ~p~n", [Msg]),
                    {ok, #{<<"body">> => Msg}}
                end
            }
        }
    }
}).

Node History Tracking

% Initial state
Opts1 = get_opts(),
History1 = hb_opts:get(node_history, [], Opts1),
% []
 
% First update
{ok, Opts2} = set_opts(#{<<"key1">> => <<"val1">>}, Opts1),
History2 = hb_opts:get(node_history, [], Opts2),
% [#{<<"key1">> => <<"val1">>}]
 
% Second update
{ok, Opts3} = set_opts(#{<<"key2">> => <<"val2">>}, Opts2),
History3 = hb_opts:get(node_history, [], Opts3),
% [#{<<"key1">> => <<"val1">>}, #{<<"key2">> => <<"val2">>}]

Protocol Support

HTTP/2 (Default)

ProtoOpts = #{
    protocols => [http2],
    transport => tcp
}.

HTTP/3 (QUIC)

ProtoOpts = #{
    protocols => [http3],
    transport => quicer
}.
Configuration:
#{
    protocol => http3,
    port => 8734
}

Prometheus Integration

Metrics Collection

ProtoOpts = #{
    metrics_callback => fun prometheus_cowboy2_instrumenter:observe/1,
    stream_handlers => [cowboy_metrics_h, cowboy_stream_h]
}
Metrics Tracked:
  • Request count by method/path
  • Response time distribution
  • Status code distribution
  • Connection count
  • Bytes sent/received

Disabled in Tests: Automatically disabled when hb_features:test() returns true


Configuration Precedence

  1. Command Line/Explicit: Options passed to start/1
  2. Configuration File: Loaded from config.flat
  3. Environment Defaults: From hb_opts:default_message_with_env()
  4. Hardcoded Defaults: In set_default_opts/1

References

  • HTTP Client - hb_http.erl
  • AO-Core - hb_ao.erl, hb_singleton.erl
  • Message Handling - hb_message.erl
  • Options - hb_opts.erl
  • Store - hb_store.erl
  • Hooks - dev_hook.erl

Notes

  1. Cowboy Integration: Uses Cowboy as HTTP server framework
  2. Dynamic Configuration: Server options can be updated at runtime
  3. History Tracking: Maintains history of configuration changes
  4. Startup Hooks: Allows modification of configuration before server starts
  5. Test Mode: Greeting banner and Prometheus disabled in tests
  6. Random Ports: Test nodes use random ports (10000-60000)
  7. Default Security: force_signed => true for test nodes
  8. Store Initialization: Automatic test store creation if not provided
  9. Wallet Management: Loads from file or creates new for tests
  10. Server Restart: Same wallet allows configuration updates
  11. Protocol Choice: HTTP/2 default, HTTP/3 optional
  12. CORS Support: Handled by hb_http:reply/4
  13. Error Formatting: Detailed error messages with stack traces
  14. Process Dictionary: Server ID stored per-process
  15. Environment Storage: Configuration stored in Cowboy environment