From e2e930ece69706a7d4e76d5b8efac2d06a73d6a8 Mon Sep 17 00:00:00 2001 From: "kacaii.dev" Date: Thu, 13 Aug 2026 11:21:44 -0300 Subject: [PATCH] server: add supervision tree --- gleam.toml | 4 +++- manifest.toml | 16 +++++++++----- src/server.gleam | 32 +++++++++++++++++++++++----- src/server/context.gleam | 12 +++++++++++ src/server/router.gleam | 35 +++++++++++++++++++++++++++++++ src/server/supervision_tree.gleam | 27 +++++++++++------------- src/server/web.gleam | 25 ---------------------- test/server_test.gleam | 14 ------------- 8 files changed, 100 insertions(+), 65 deletions(-) create mode 100644 src/server/context.gleam create mode 100644 src/server/router.gleam delete mode 100644 src/server/web.gleam diff --git a/gleam.toml b/gleam.toml index 4e8b490..bf71c36 100644 --- a/gleam.toml +++ b/gleam.toml @@ -14,13 +14,15 @@ version = "1.0.0" [dependencies] gleam_stdlib = ">= 1.0.0 and < 2.0.0" -ewe = ">= 4.0.1 and < 5.0.0" gleam_http = ">= 4.3.0 and < 5.0.0" gleam_erlang = ">= 1.3.0 and < 2.0.0" gleam_otp = ">= 1.3.0 and < 2.0.0" pog = ">= 4.1.0 and < 5.0.0" youid = ">= 1.6.0 and < 2.0.0" envoy = ">= 1.2.0 and < 2.0.0" +mist = ">= 6.0.3 and < 7.0.0" +wisp = ">= 2.2.2 and < 3.0.0" +filepath = ">= 1.1.2 and < 2.0.0" [dev_dependencies] gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/manifest.toml b/manifest.toml index f0ddb44..c5a57f8 100644 --- a/manifest.toml +++ b/manifest.toml @@ -9,10 +9,9 @@ packages = [ { name = "argv", version = "1.1.0", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "3277D100448BDB4A29B6D58C0F36F631CBC349E8BDD09766C6309DF202831140" }, { name = "backoff", version = "1.1.6", build_tools = ["rebar3"], requirements = [], otp_app = "backoff", source = "hex", outer_checksum = "CF0CFFF8995FB20562F822E5CC47D8CCF664C5ECDC26A684CBE85C225F9D7C39" }, - { name = "compresso", version = "0.1.0", build_tools = ["gleam"], requirements = ["exception", "gleam_erlang", "gleam_stdlib", "gleam_yielder", "logging"], otp_app = "compresso", source = "hex", outer_checksum = "8BE29A1EDA42F70826ED148EAE40C46BB3FC18E78FE472663DB01DD4A38172D4" }, + { name = "directories", version = "1.2.0", build_tools = ["gleam"], requirements = ["envoy", "gleam_stdlib", "platform", "simplifile"], otp_app = "directories", source = "hex", outer_checksum = "D13090CFCDF6759B87217E8DDD73A75903A700148A82C1D33799F333E249BF9E" }, { name = "envoy", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "9C6FBB6BFA02A52798BEEC5977A738CAD6E4A057F4B67FD0C8061AD2502C191A" }, { name = "eval", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "eval", source = "hex", outer_checksum = "264DAF4B49DF807F303CA4A4E4EBC012070429E40BE384C58FE094C4958F9BDA" }, - { name = "ewe", version = "4.0.1", build_tools = ["gleam"], requirements = ["compresso", "exception", "gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "glisten", "logging", "websocks"], otp_app = "ewe", source = "hex", outer_checksum = "1A6CBE2E07B7E784F9B9503C94C0F23CDD644AC5EA4E1B957F3A5550541699DB" }, { name = "exception", version = "2.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "6BDEA95248093599391C3B5DF1835C5C6A86C353C2F99CE539B450E3432FE117" }, { name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" }, { name = "glam", version = "2.0.4", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glam", source = "hex", outer_checksum = "48A043A12091F93D911C316DD8F7FFD03CD9FF6F50848FC56E359D3C168DF0FA" }, @@ -26,17 +25,22 @@ packages = [ { name = "gleam_regexp", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "9C215C6CA84A5B35BB934A9B61A9A306EC743153BE2B0425A0D032E477B062A9" }, { name = "gleam_stdlib", version = "1.0.5", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "CEE5B6C076A85B45F60C585F4316C63EC8B7127C119D5738C3958A9C4D50404E" }, { name = "gleam_time", version = "1.10.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_time", source = "hex", outer_checksum = "56539216E4C4B1748714652AB38F0BD16B9101F61DB62769FDC7CD42A8E5E833" }, - { name = "gleam_yielder", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_yielder", source = "hex", outer_checksum = "8E4E4ECFA7982859F430C57F549200C7749823C106759F4A19A78AEA6687717A" }, { name = "gleeunit", version = "1.11.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "EC31ABA74256AEA531EDF8169931D775BBB384FED0A8A1BDC4DD9354E3E21826" }, { name = "glexer", version = "2.5.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "splitter"], otp_app = "glexer", source = "hex", outer_checksum = "D423CF63B8D4F9654EF53D0AF4904B249EB0679BD41C63440170AC508C9CC65E" }, { name = "glisten", version = "9.0.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib", "logging"], otp_app = "glisten", source = "hex", outer_checksum = "7795AA50830656F3A0316A6B26595F893C83272DA901B3405E31339CAA31A10B" }, + { name = "gramps", version = "6.0.1", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gramps", source = "hex", outer_checksum = "D55636072DEE173F6586A5679D3C02EC7A0DE3F8646B78C351B72908FF223DF7" }, + { name = "houdini", version = "1.2.1", build_tools = ["gleam"], requirements = [], otp_app = "houdini", source = "hex", outer_checksum = "6F8AC2F12974567FB744BEA66AC93CEB76AAEA19AD28564623F76CDA9BC26A85" }, + { name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" }, { name = "justin", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "justin", source = "hex", outer_checksum = "8B1C62269E8607D0A0ED698B7903984CE0BF7B6B3C0DEA3C6C8302B39402837B" }, { name = "logging", version = "1.5.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "BC5F18CE5DD9686100229FE5409BDC3DD5C46D5A7DF2F804AD2D8F0DD6C5060E" }, + { name = "marceau", version = "1.3.0", build_tools = ["gleam"], requirements = [], otp_app = "marceau", source = "hex", outer_checksum = "2D1C27504BEF45005F5DFB18591F8610FB4BFA91744878210BDC464412EC44E9" }, + { name = "mist", version = "6.0.3", build_tools = ["gleam"], requirements = ["exception", "gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "glisten", "gramps", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "1B07F321D5FA0CB162D81496F2DE96AEB6EF8980F4F38230A4CC3F849497E020" }, { name = "mug", version = "3.1.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "mug", source = "hex", outer_checksum = "C01279D98E40371DA23461774B63F0E3581B8F1396049D881B0C7EB32799D93F" }, { name = "non_empty_list", version = "2.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "non_empty_list", source = "hex", outer_checksum = "06F9D3AC751CF7853AD5D24B8139CEB30E42D8799DE8D49F966A6197DF0B01CC" }, { name = "opentelemetry_api", version = "1.5.0", build_tools = ["rebar3", "mix"], requirements = [], otp_app = "opentelemetry_api", source = "hex", outer_checksum = "F53EC8A1337AE4A487D43AC89DA4BD3A3C99DDF576655D071DEED8B56A2D5DDA" }, { name = "pg_types", version = "0.6.0", build_tools = ["rebar3"], requirements = [], otp_app = "pg_types", source = "hex", outer_checksum = "9949A4849DD13408FA249AB7B745E0D2DFDB9532AEE2B9722326E33CD082A778" }, { name = "pgo", version = "0.20.0", build_tools = ["rebar3"], requirements = ["backoff", "opentelemetry_api", "pg_types"], otp_app = "pgo", source = "hex", outer_checksum = "2F11E6649CEB38E569EF56B16BE1D04874AE5B11A02867080A2817CE423C683B" }, + { name = "platform", version = "1.0.0", build_tools = ["gleam"], requirements = [], otp_app = "platform", source = "hex", outer_checksum = "8339420A95AD89AAC0F82F4C3DB8DD401041742D6C3F46132A8739F6AEB75391" }, { name = "pog", version = "4.1.0", build_tools = ["gleam"], requirements = ["exception", "gleam_erlang", "gleam_otp", "gleam_stdlib", "gleam_time", "pgo"], otp_app = "pog", source = "hex", outer_checksum = "E4AFBA39A5FAA2E77291836C9683ADE882E65A06AB28CA7D61AE7A3AD61EBBD5" }, { name = "simplifile", version = "2.7.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "A2727627B063E87351934C7F7F008F2D1FDB16F6DE0B8C79F9E46459CFC9C164" }, { name = "splitter", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "splitter", source = "hex", outer_checksum = "3DFD6B6C49E61EDAF6F7B27A42054A17CFF6CA2135FF553D0CB61C234D281DD0" }, @@ -44,18 +48,20 @@ packages = [ { name = "term_size", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "term_size", source = "hex", outer_checksum = "D00BD2BC8FB3EBB7E6AE076F3F1FF2AC9D5ED1805F004D0896C784D06C6645F1" }, { name = "tom", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_time"], otp_app = "tom", source = "hex", outer_checksum = "DCF04CB7AB35D58CFC598C66EA2E1816D160759802C89B2BA6238780D59BC256" }, { name = "tote", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "tote", source = "hex", outer_checksum = "30CBB77B72867B1DE2D271A7FE61DBE8BB81CA7DC385A0E1FDF7D38E71F3791A" }, - { name = "websocks", version = "3.0.1", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_stdlib"], otp_app = "websocks", source = "hex", outer_checksum = "C70340E5B6C3390383ADA17029DCA6F8903863A7AD8CD8E1520EDCC4FE70D6FD" }, + { name = "wisp", version = "2.2.2", build_tools = ["gleam"], requirements = ["directories", "exception", "filepath", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_json", "gleam_stdlib", "houdini", "logging", "marceau", "mist", "simplifile"], otp_app = "wisp", source = "hex", outer_checksum = "5FF5F1E288C3437252ABB93D8F9CF42FF652CE7AD54480CFE736038DC09C4F22" }, { name = "youid", version = "1.6.0", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_stdlib", "gleam_time"], otp_app = "youid", source = "hex", outer_checksum = "7A3ABA44B1B38BC2BDCB5474C5317AA372BE58DFBC649815EE08B03526DDA18D" }, ] [requirements] envoy = { version = ">= 1.2.0 and < 2.0.0" } -ewe = { version = ">= 4.0.1 and < 5.0.0" } +filepath = { version = ">= 1.1.2 and < 2.0.0" } gleam_erlang = { version = ">= 1.3.0 and < 2.0.0" } gleam_http = { version = ">= 4.3.0 and < 5.0.0" } gleam_otp = { version = ">= 1.3.0 and < 2.0.0" } gleam_stdlib = { version = ">= 1.0.0 and < 2.0.0" } gleeunit = { version = ">= 1.0.0 and < 2.0.0" } +mist = { version = ">= 6.0.3 and < 7.0.0" } pog = { version = ">= 4.1.0 and < 5.0.0" } squirrel = { version = ">= 4.7.0 and < 5.0.0" } +wisp = { version = ">= 2.2.2 and < 3.0.0" } youid = { version = ">= 1.6.0 and < 2.0.0" } diff --git a/src/server.gleam b/src/server.gleam index 49cb74b..6bc2f0b 100644 --- a/src/server.gleam +++ b/src/server.gleam @@ -1,16 +1,38 @@ import envoy +import filepath import gleam/erlang/process +import gleam/result import pog +import server/context +import server/router import server/supervision_tree -import server/web +import wisp pub fn main() -> Nil { + wisp.configure_logger() + + //  Postgres let db_process_name = process.new_name("db_connection_pool") - let assert Ok(database_url) = envoy.get("DATABASE_URL") - let assert Ok(pg_config) = pog.url_config(db_process_name, database_url) + let assert Ok(pg_config) = read_database_connection(db_process_name) + let database = pog.named_connection(db_process_name) - let http_handler = web.handle_request - let assert Ok(_) = supervision_tree.start(http_handler, pg_config) + // others + let assert Ok(static_directory) = static_directory() + let assert Ok(secret_key) = envoy.get("SECRET_KEY") + let context = context.Context(database:, static_directory:, secret_key:) + let handler = router.handle_request(_, context) + // start supervision tree + let assert Ok(_) = supervision_tree.start(handler, pg_config, secret_key) process.sleep_forever() } + +pub fn read_database_connection(name: process.Name(pog.Message)) { + let assert Ok(database_url) = envoy.get("DATABASE_URL") + pog.url_config(name, database_url) +} + +pub fn static_directory() -> Result(String, Nil) { + use priv_directory <- result.map(wisp.priv_directory("server")) + filepath.join(priv_directory, "static") +} diff --git a/src/server/context.gleam b/src/server/context.gleam new file mode 100644 index 0000000..a477217 --- /dev/null +++ b/src/server/context.gleam @@ -0,0 +1,12 @@ +import pog + +pub type Context { + Context( + /// PostgreSQL connection pool + database: pog.Connection, + /// Path to the application's priv directory + static_directory: String, + /// Secret used for hashing cookies and passwords + secret_key: String, + ) +} diff --git a/src/server/router.gleam b/src/server/router.gleam new file mode 100644 index 0000000..00e5494 --- /dev/null +++ b/src/server/router.gleam @@ -0,0 +1,35 @@ +import gleam/http +import gleam/http/request +import gleam/http/response +import server/context +import wisp + +pub fn handle_request( + request: request.Request(wisp.Connection), + context: context.Context, +) -> response.Response(wisp.Body) { + use request <- middleware(request, context) + + case request.method, request.path_segments(request) { + // healthcheck + http.Get, [] -> wisp.ok() + + // fallback + _, _ -> wisp.not_found() + } +} + +fn middleware( + req: request.Request(wisp.Connection), + context: context.Context, + next: fn(request.Request(wisp.Connection)) -> response.Response(wisp.Body), +) -> response.Response(wisp.Body) { + let request = wisp.method_override(req) + use <- wisp.log_request(request) + use <- wisp.rescue_crashes() + use request <- wisp.handle_head(request) + use request <- wisp.csrf_known_header_protection(request) + use <- wisp.serve_static(request, "/static", context.static_directory) + + next(request) +} diff --git a/src/server/supervision_tree.gleam b/src/server/supervision_tree.gleam index 3027c08..5df1370 100644 --- a/src/server/supervision_tree.gleam +++ b/src/server/supervision_tree.gleam @@ -1,34 +1,31 @@ -import ewe import gleam/http/request import gleam/http/response import gleam/otp/actor import gleam/otp/static_supervisor as supervisor +import mist import pog -import server/web +import wisp +import wisp/wisp_mist type Request = - request.Request(ewe.Connection) + request.Request(wisp.Connection) type Response = - response.Response(ewe.ResponseBody) + response.Response(wisp.Body) pub fn start( http_handler: fn(Request) -> Response, database_config: pog.Config, + secret_key: String, ) -> Result(actor.Started(supervisor.Supervisor), actor.StartError) { let http_server = - ewe.new(http_handler) - |> ewe.bind("0.0.0.0") - |> ewe.listening(port: 8000) - |> ewe.on_crash(web.on_crash()) - |> ewe.supervised - - let database_connection = - database_config - |> pog.supervised + wisp_mist.handler(http_handler, secret_key) + |> mist.new + |> mist.bind("0.0.0.0") + |> mist.port(8000) supervisor.new(supervisor.OneForOne) - |> supervisor.add(http_server) - |> supervisor.add(database_connection) + |> supervisor.add(mist.supervised(http_server)) + |> supervisor.add(pog.supervised(database_config)) |> supervisor.start } diff --git a/src/server/web.gleam b/src/server/web.gleam deleted file mode 100644 index 4c89a77..0000000 --- a/src/server/web.gleam +++ /dev/null @@ -1,25 +0,0 @@ -import ewe -import gleam/http -import gleam/http/request -import gleam/http/response - -pub fn handle_request( - request: request.Request(body), -) -> response.Response(ewe.ResponseBody) { - case request.method, request.path_segments(request) { - http.Get, [] -> - response.new(200) - |> response.set_header("content-type", "text/plain; charset=utf-8") - |> response.set_body(ewe.TextData("Ok")) - - _, _ -> - response.new(404) - |> response.set_header("content-type", "text/plain; charset=utf-8") - |> response.set_body(ewe.TextData("Not Found")) - } -} - -pub fn on_crash() -> response.Response(ewe.ResponseBody) { - ewe.TextData("Internal Server Error") - |> response.set_body(response.new(500), _) -} diff --git a/test/server_test.gleam b/test/server_test.gleam index 4580e28..902c4da 100644 --- a/test/server_test.gleam +++ b/test/server_test.gleam @@ -1,19 +1,5 @@ -import ewe -import gleam/http/request import gleeunit -import server pub fn main() -> Nil { gleeunit.main() } - -pub fn handle_request_test() { - let response = - request.new() - |> server.handle_request - - assert response.status == 200 - assert response.body == ewe.TextData("Ok") - - Nil -} -- 2.51.2