Current section

Files

Jump to
dream src dream@servers@mist@server.erl
Raw

src/dream@servers@mist@server.erl

-module(dream@servers@mist@server).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/dream/servers/mist/server.gleam").
-export([new/0, context/2, services/2, router/2, bind/2, max_body_size/2, stop/1, listen/2, listen_with_handle/2]).
-export_type([server_handle/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Your Dream app's entry point\n"
"\n"
" This module is where your web application starts. It provides a builder pattern\n"
" for configuring and starting a Dream server using Mist (the BEAM's HTTP server).\n"
"\n"
" ## Quick Start\n"
"\n"
" ```gleam\n"
" import dream/servers/mist/server\n"
" import dream/servers/mist/server.{listen, router}\n"
" \n"
" pub fn main() {\n"
" server.new()\n"
" |> router(create_router())\n"
" |> listen(3000)\n"
" }\n"
" ```\n"
"\n"
" The builder pattern lets you configure your server step by step. Start with `new()`,\n"
" add your router, and optionally set custom context and services before calling `listen()`.\n"
"\n"
" ## Custom Context and Services\n"
"\n"
" By default, Dream uses `EmptyContext` (no per-request data) and `EmptyServices` (no shared\n"
" dependencies). For most production apps, you'll want to define your own types:\n"
"\n"
" ```gleam\n"
" import dream/servers/mist/server\n"
" import dream/servers/mist/server.{context, listen, router, services}\n"
" import gleam/option.{None}\n"
" \n"
" pub type MyContext {\n"
" MyContext(request_id: String, user: option.Option(User), session: Session)\n"
" }\n"
"\n"
" server.new()\n"
" |> context(MyContext(request_id: \"\", user: None, session: empty_session()))\n"
" |> services(initialize_services())\n"
" |> router(create_router())\n"
" |> listen(3000)\n"
" ```\n"
"\n"
" The type system ensures your controllers receive the correct context type.\n"
).
-opaque server_handle() :: {server_handle, gleam@erlang@process:pid_()}.
-file("src/dream/servers/mist/server.gleam", 99).
?DOC(
" Create a new Dream server with defaults\n"
"\n"
" Returns a server configured with `EmptyContext` (no per-request data) and\n"
" `EmptyServices` (no dependencies). For simple applications, you only need to add\n"
" a router. For more complex apps, use `context()` and `services()` to provide your\n"
" own types before calling `listen()`.\n"
"\n"
" ## Simple Example (no context or services)\n"
"\n"
" ```gleam\n"
" import dream/servers/mist/server\n"
" import dream/servers/mist/server.{listen, router}\n"
" \n"
" server.new()\n"
" |> router(my_router)\n"
" |> listen(3000)\n"
" ```\n"
"\n"
" ## With Custom Context and Services\n"
"\n"
" ```gleam\n"
" import dream/servers/mist/server\n"
" import dream/servers/mist/server.{context, listen, router, services}\n"
" import gleam/option.{None}\n"
" \n"
" server.new()\n"
" |> context(MyContext(request_id: \"\", user: None))\n"
" |> services(my_services)\n"
" |> router(my_router)\n"
" |> listen(3000)\n"
" ```\n"
).
-spec new() -> dream@dream:dream(mist:builder(mist@internal@http:connection(), mist:response_data()), dream@context:empty_context(), dream@router:empty_services()).
new() ->
dream@dream:create(mist:new(fun(_) -> _pipe = gleam@http@response:new(404),
gleam@http@response:set_body(
_pipe,
{bytes, gleam@bytes_tree:new()}
) end), none, empty_context, {some, empty_services}, 10000000, none).
-file("src/dream/servers/mist/server.gleam", 140).
?DOC(
" Set a custom context type for your application\n"
"\n"
" Use this to replace `AppContext` with your own context type that holds\n"
" user authentication, session data, or any other per-request information.\n"
" The type system tracks your context through middleware and controllers.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import dream/servers/mist/server\n"
" import dream/servers/mist/server.{context, listen, router, services}\n"
" \n"
" pub type MyContext {\n"
" MyContext(request_id: String, user: Option(User))\n"
" }\n"
"\n"
" server.new()\n"
" |> context(MyContext(request_id: \"\", user: None))\n"
" |> services(my_services)\n"
" |> router(my_router)\n"
" |> listen(3000)\n"
" ```\n"
).
-spec context(
dream@dream:dream(mist:builder(mist@internal@http:connection(), mist:response_data()), any(), AHMF),
AHMJ
) -> dream@dream:dream(mist:builder(mist@internal@http:connection(), mist:response_data()), AHMJ, AHMF).
context(Dream_instance, New_context) ->
dream@dream:create(
dream@dream:get_server(Dream_instance, true),
none,
New_context,
dream@dream:get_services(Dream_instance),
dream@dream:get_max_body_size(Dream_instance),
dream@dream:get_bind_interface(Dream_instance)
).
-file("src/dream/servers/mist/server.gleam", 184).
?DOC(
" Provide your application's services\n"
"\n"
" Services are shared dependencies available to all requests—database connections,\n"
" HTTP clients, caches, etc. Define a type that holds all your services and pass it here.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import dream/servers/mist/server\n"
" import dream/servers/mist/server.{listen, router, services}\n"
" \n"
" pub type Services {\n"
" Services(db: Connection, cache: Cache)\n"
" }\n"
"\n"
" pub fn initialize_services() -> Services {\n"
" let db = connect_to_database()\n"
" let cache = create_cache()\n"
" Services(db: db, cache: cache)\n"
" }\n"
"\n"
" server.new()\n"
" |> services(initialize_services())\n"
" |> router(my_router)\n"
" |> listen(3000)\n"
" ```\n"
).
-spec services(
dream@dream:dream(mist:builder(mist@internal@http:connection(), mist:response_data()), AHMR, dream@router:empty_services()),
AHMV
) -> dream@dream:dream(mist:builder(mist@internal@http:connection(), mist:response_data()), AHMR, AHMV).
services(Dream_instance, Services_instance) ->
dream@dream:create(
dream@dream:get_server(Dream_instance, true),
none,
dream@dream:get_context(Dream_instance),
{some, Services_instance},
dream@dream:get_max_body_size(Dream_instance),
dream@dream:get_bind_interface(Dream_instance)
).
-file("src/dream/servers/mist/server.gleam", 224).
?DOC(
" Provide your application's router\n"
"\n"
" The router defines which controllers handle which requests. It must be configured\n"
" with the same context and services types you've set up, which the type system enforces.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import dream/servers/mist/server\n"
" import dream/servers/mist/server.{listen, router, services}\n"
" \n"
" pub fn create_router() -> Router(MyContext, Services) {\n"
" router()\n"
" |> route(method: Get, path: \"/\", controller: controllers.index, middleware: [])\n"
" |> route(method: Get, path: \"/users/:id\", controller: controllers.show_user, middleware: [])\n"
" }\n"
"\n"
" server.new()\n"
" |> services(initialize_services())\n"
" |> router(create_router())\n"
" |> listen(3000)\n"
" ```\n"
).
-spec router(
dream@dream:dream(mist:builder(mist@internal@http:connection(), mist:response_data()), AHND, AHNE),
dream@router:router(AHND, AHNE)
) -> dream@dream:dream(mist:builder(mist@internal@http:connection(), mist:response_data()), AHND, AHNE).
router(Dream_instance, Router_instance) ->
dream@dream:create(
dream@dream:get_server(Dream_instance, true),
{some, Router_instance},
dream@dream:get_context(Dream_instance),
dream@dream:get_services(Dream_instance),
dream@dream:get_max_body_size(Dream_instance),
dream@dream:get_bind_interface(Dream_instance)
).
-file("src/dream/servers/mist/server.gleam", 260).
?DOC(
" Set the network interface to bind to\n"
"\n"
" Defaults to binding to all interfaces. Use \"localhost\" or \"127.0.0.1\" to only\n"
" accept local connections, or \"0.0.0.0\" to accept connections from any network interface.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import dream/servers/mist/server\n"
" import dream/servers/mist/server.{bind, listen, router, services}\n"
" \n"
" // Only accept local connections\n"
" server.new()\n"
" |> services(my_services)\n"
" |> router(my_router)\n"
" |> bind(\"localhost\")\n"
" |> listen(3000)\n"
" ```\n"
).
-spec bind(
dream@dream:dream(mist:builder(mist@internal@http:connection(), mist:response_data()), AHNR, AHNS),
binary()
) -> dream@dream:dream(mist:builder(mist@internal@http:connection(), mist:response_data()), AHNR, AHNS).
bind(Dream_instance, Interface) ->
dream@dream:create(
mist:bind(dream@dream:get_server(Dream_instance, true), Interface),
dream@dream:get_router(Dream_instance),
dream@dream:get_context(Dream_instance),
dream@dream:get_services(Dream_instance),
dream@dream:get_max_body_size(Dream_instance),
{some, Interface}
).
-file("src/dream/servers/mist/server.gleam", 300).
?DOC(
" Set maximum request body size in bytes\n"
"\n"
" Requests with bodies larger than this will be rejected. The default is\n"
" 10_000_000 bytes (10MB), which is a safe limit for typical JSON and form\n"
" payloads. Streaming routes are not affected by this limit.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import dream/servers/mist/server\n"
" import dream/servers/mist/server.{listen, max_body_size, router, services}\n"
" \n"
" // Limit request bodies to 10MB\n"
" server.new()\n"
" |> services(my_services)\n"
" |> router(my_router)\n"
" |> max_body_size(10_000_000)\n"
" |> listen(3000)\n"
" ```\n"
).
-spec max_body_size(
dream@dream:dream(mist:builder(mist@internal@http:connection(), mist:response_data()), AHOD, AHOE),
integer()
) -> dream@dream:dream(mist:builder(mist@internal@http:connection(), mist:response_data()), AHOD, AHOE).
max_body_size(Dream_instance, Size) ->
dream@dream:create(
dream@dream:get_server(Dream_instance, true),
dream@dream:get_router(Dream_instance),
dream@dream:get_context(Dream_instance),
dream@dream:get_services(Dream_instance),
Size,
dream@dream:get_bind_interface(Dream_instance)
).
-file("src/dream/servers/mist/server.gleam", 415).
?DOC(
" Stop a server that was started with `listen_with_handle()`\n"
"\n"
" Stops the server process. This sends a shutdown signal to the supervisor.\n"
" Note: In tests, you typically don't need to call this - the server will\n"
" be cleaned up automatically when the test process exits.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import dream/servers/mist/server\n"
" import dream/servers/mist/server.{listen_with_handle, router, stop}\n"
"\n"
" pub fn test_server() {\n"
" let assert Ok(handle) =\n"
" server.new()\n"
" |> router(test_router())\n"
" |> listen_with_handle(8080)\n"
" \n"
" // ... use server ...\n"
" \n"
" // Optional - server will be cleaned up automatically when test exits\n"
" stop(handle)\n"
" }\n"
" ```\n"
).
-spec stop(server_handle()) -> nil.
stop(Handle) ->
case Handle of
{server_handle, Process_pid} ->
gleam@erlang@process:send_exit(Process_pid),
nil
end.
-file("src/dream/servers/mist/server.gleam", 430).
?DOC(
" Helper function to update context with request_id\n"
" This is a simple implementation that just returns the template context\n"
" Most applications don't need request_id in their context\n"
" If you need request_id, consider using middleware or logging\n"
).
-spec update_context_with_request_id(AHPD, binary()) -> AHPD.
update_context_with_request_id(Ctx, _) ->
Ctx.
-file("src/dream/servers/mist/server.gleam", 436).
-spec bind_label(gleam@option:option(binary())) -> binary().
bind_label(Interface) ->
gleam@option:unwrap(Interface, <<"localhost"/utf8>>).
-file("src/dream/servers/mist/server.gleam", 440).
-spec port_in_use_error(integer(), binary()) -> gleam@otp@actor:start_error().
port_in_use_error(Port, Bind_label) ->
Message = <<<<<<<<"Port "/utf8, (erlang:integer_to_binary(Port))/binary>>/binary,
" already in use on "/utf8>>/binary,
Bind_label/binary>>/binary,
". Stop the process using that port or choose a different one."/utf8>>,
{init_failed, Message}.
-file("src/dream/servers/mist/server.gleam", 450).
-spec build_handler(
dream@dream:dream(mist:builder(mist@internal@http:connection(), mist:response_data()), AHPH, AHPI),
dream@router:router(AHPH, AHPI),
AHPI
) -> fun((gleam@http@request:request(mist@internal@http:connection())) -> gleam@http@response:response(mist:response_data())).
build_handler(Dream_instance, Router_instance, Services_instance) ->
dream@servers@mist@handler:create(
Router_instance,
dream@dream:get_max_body_size(Dream_instance),
dream@dream:get_context(Dream_instance),
Services_instance,
fun update_context_with_request_id/2
).
-file("src/dream/servers/mist/server.gleam", 469).
-spec build_server(
dream@dream:dream(mist:builder(mist@internal@http:connection(), mist:response_data()), any(), any()),
fun((gleam@http@request:request(mist@internal@http:connection())) -> gleam@http@response:response(mist:response_data())),
integer()
) -> mist:builder(mist@internal@http:connection(), mist:response_data()).
build_server(Dream_instance, Handler_fn, Port) ->
Server_with_handler = mist:new(Handler_fn),
Server_with_interface = case dream@dream:get_bind_interface(Dream_instance) of
{some, Interface} ->
mist:bind(Server_with_handler, Interface);
none ->
Server_with_handler
end,
mist:port(Server_with_interface, Port).
-file("src/dream/servers/mist/server.gleam", 487).
-spec handle_blocking(boolean()) -> nil.
handle_blocking(Block_forever) ->
case Block_forever of
true ->
gleam_erlang_ffi:sleep_forever();
false ->
nil
end.
-file("src/dream/servers/mist/server.gleam", 494).
-spec start_server(
mist:builder(mist@internal@http:connection(), mist:response_data()),
boolean()
) -> {ok, gleam@otp@actor:started(gleam@otp@static_supervisor:supervisor())} |
{error, gleam@otp@actor:start_error()}.
start_server(Server_with_port, Block_forever) ->
_pipe = mist:start(Server_with_port),
gleam@result:map(
_pipe,
fun(Started) ->
handle_blocking(Block_forever),
Started
end
).
-file("src/dream/servers/mist/server.gleam", 505).
-spec panic_on_start_error({ok, any()} | {error, gleam@otp@actor:start_error()}) -> nil.
panic_on_start_error(Result) ->
case Result of
{ok, _} ->
nil;
{error, {init_failed, Message}} ->
erlang:error(#{gleam_error => panic,
message => Message,
file => <<?FILEPATH/utf8>>,
module => <<"dream/servers/mist/server"/utf8>>,
function => <<"panic_on_start_error"/utf8>>,
line => 508});
{error, Err} ->
Message@1 = <<"Failed to start server: "/utf8,
(gleam@string:inspect(Err))/binary>>,
erlang:error(#{gleam_error => panic,
message => Message@1,
file => <<?FILEPATH/utf8>>,
module => <<"dream/servers/mist/server"/utf8>>,
function => <<"panic_on_start_error"/utf8>>,
line => 511})
end.
-file("src/dream/servers/mist/server.gleam", 518).
?DOC(
" Internal function to start the server with configurable blocking behavior\n"
" Returns Result with Started info on success, or Error with StartError on failure\n"
).
-spec listen_internal(
dream@dream:dream(mist:builder(mist@internal@http:connection(), mist:response_data()), any(), any()),
integer(),
boolean()
) -> {ok, gleam@otp@actor:started(gleam@otp@static_supervisor:supervisor())} |
{error, gleam@otp@actor:start_error()}.
listen_internal(Dream_instance, Port, Block_forever) ->
Bind_interface = dream@dream:get_bind_interface(Dream_instance),
Bind_interface_label = bind_label(Bind_interface),
case dream@servers@mist@port_probe:is_in_use(Bind_interface, Port) of
true ->
{error, port_in_use_error(Port, Bind_interface_label)};
false ->
Router_instance@1 = case dream@dream:get_router(Dream_instance) of
{some, Router_instance} -> Router_instance;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"dream/servers/mist/server"/utf8>>,
function => <<"listen_internal"/utf8>>,
line => 534,
value => _assert_fail,
start => 15974,
'end' => 16048,
pattern_start => 15985,
pattern_end => 16013})
end,
Services_instance@1 = case dream@dream:get_services(Dream_instance) of
{some, Services_instance} -> Services_instance;
_assert_fail@1 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"dream/servers/mist/server"/utf8>>,
function => <<"listen_internal"/utf8>>,
line => 535,
value => _assert_fail@1,
start => 16055,
'end' => 16141,
pattern_start => 16066,
pattern_end => 16096})
end,
Handler_fn = build_handler(
Dream_instance,
Router_instance@1,
Services_instance@1
),
Server_with_port = build_server(Dream_instance, Handler_fn, Port),
start_server(Server_with_port, Block_forever)
end.
-file("src/dream/servers/mist/server.gleam", 339).
?DOC(
" Start the server and listen for requests\n"
"\n"
" Starts the server on the specified port and blocks forever. This is what you call\n"
" in your `main()` function. If the server fails to start, it returns `Nil` immediately.\n"
"\n"
" This function will panic if you haven't called `router()` and `services()` first—\n"
" you can't run a web server without defining what it does.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import dream/servers/mist/server\n"
" import dream/servers/mist/server.{listen, router, services}\n"
" \n"
" pub fn main() {\n"
" server.new()\n"
" |> services(initialize_services())\n"
" |> router(create_router())\n"
" |> listen(3000)\n"
" }\n"
" ```\n"
).
-spec listen(
dream@dream:dream(mist:builder(mist@internal@http:connection(), mist:response_data()), any(), any()),
integer()
) -> nil.
listen(Dream_instance, Port) ->
_pipe = listen_internal(Dream_instance, Port, true),
panic_on_start_error(_pipe).
-file("src/dream/servers/mist/server.gleam", 377).
?DOC(
" Start the server and return a handle for stopping it\n"
"\n"
" Like `listen()`, but returns immediately with a `ServerHandle` that can be used\n"
" to stop the server programmatically. Useful for tests and programmatic server control.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import dream/servers/mist/server\n"
" import dream/servers/mist/server.{listen_with_handle, router, stop}\n"
"\n"
" pub fn test_server() {\n"
" // Start server and get handle\n"
" let assert Ok(handle) =\n"
" server.new()\n"
" |> router(test_router())\n"
" |> listen_with_handle(8080)\n"
" \n"
" // Make test requests\n"
" let response = http_client.get(\"http://localhost:8080/test\")\n"
" // ... assertions ...\n"
" \n"
" // Stop the server\n"
" stop(handle)\n"
" }\n"
" ```\n"
).
-spec listen_with_handle(
dream@dream:dream(mist:builder(mist@internal@http:connection(), mist:response_data()), any(), any()),
integer()
) -> {ok, server_handle()} | {error, gleam@otp@actor:start_error()}.
listen_with_handle(Dream_instance, Port) ->
case listen_internal(Dream_instance, Port, false) of
{ok, Started} ->
{ok, {server_handle, erlang:element(2, Started)}};
{error, Err} ->
{error, Err}
end.