add logging and init web module
All checks were successful
Unit Tests / Run Tests (push) Successful in 13s
All checks were successful
Unit Tests / Run Tests (push) Successful in 13s
This commit is contained in:
parent
cad01edab8
commit
ff758b815c
5 changed files with 238 additions and 0 deletions
|
|
@ -23,6 +23,9 @@ spoke_tcp = ">= 2.0.0 and < 3.0.0"
|
|||
gleam_erlang = ">= 1.3.0 and < 2.0.0"
|
||||
gleam_otp = ">= 1.2.0 and < 2.0.0"
|
||||
gleam_json = ">= 3.1.0 and < 4.0.0"
|
||||
mist = ">= 6.0.2 and < 7.0.0"
|
||||
logging = ">= 1.3.0 and < 2.0.0"
|
||||
gleam_http = ">= 4.3.0 and < 5.0.0"
|
||||
|
||||
[dev_dependencies]
|
||||
gleeunit = ">= 1.0.0 and < 2.0.0"
|
||||
|
|
|
|||
|
|
@ -5,13 +5,21 @@ packages = [
|
|||
{ name = "drift", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "drift", source = "hex", outer_checksum = "30FB0312FF93ABC3A71791B4B8C06B357BFC09E8185CB5CA94B3CCFCEF09D54B" },
|
||||
{ name = "drift_actor", version = "2.0.1", build_tools = ["gleam"], requirements = ["drift", "gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "drift_actor", source = "hex", outer_checksum = "6C15D215F3C8A26AE8531CB1ED2EFA16A8816D4946D9E91765D477D66F24BE90" },
|
||||
{ name = "envoy", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "850DA9D29D2E5987735872A2B5C81035146D7FE19EFC486129E44440D03FD832" },
|
||||
{ name = "exception", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "329D269D5C2A314F7364BD2711372B6F2C58FA6F39981572E5CA68624D291F8C" },
|
||||
{ name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" },
|
||||
{ name = "glaml", version = "3.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib", "yamerl"], otp_app = "glaml", source = "hex", outer_checksum = "100CA23F526AB159712A3204D200969571FC43B193736B320C1400D410DEE7AD" },
|
||||
{ name = "gleam_crypto", version = "1.5.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "50774BAFFF1144E7872814C566C5D653D83A3EBF23ACC3156B757A1B6819086E" },
|
||||
{ name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" },
|
||||
{ name = "gleam_http", version = "4.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "82EA6A717C842456188C190AFB372665EA56CE13D8559BF3B1DD9E40F619EE0C" },
|
||||
{ name = "gleam_json", version = "3.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "44FDAA8847BE8FC48CA7A1C089706BD54BADCC4C45B237A992EDDF9F2CDB2836" },
|
||||
{ name = "gleam_otp", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "BA6A294E295E428EC1562DC1C11EA7530DCB981E8359134BEABC8493B7B2258E" },
|
||||
{ name = "gleam_stdlib", version = "0.70.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "86949BF5D1F0E4AC0AB5B06F235D8A5CC11A2DFC33BF22F752156ED61CA7D0FF" },
|
||||
{ name = "gleeunit", version = "1.9.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "DA9553CE58B67924B3C631F96FE3370C49EB6D6DC6B384EC4862CC4AAA718F3C" },
|
||||
{ name = "glisten", version = "9.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib", "logging"], otp_app = "glisten", source = "hex", outer_checksum = "D92808C66F7D3F22F2289CD04CBA8151757AAE9CB3D86992F0C6DE32A41205E1" },
|
||||
{ name = "gramps", version = "6.0.0", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gramps", source = "hex", outer_checksum = "8B7195978FBFD30B43DF791A8A272041B81E45D245314D7A41FC57237AA882A0" },
|
||||
{ name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" },
|
||||
{ name = "logging", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "1098FBF10B54B44C2C7FDF0B01C1253CAFACDACABEFB4B0D027803246753E06D" },
|
||||
{ name = "mist", version = "6.0.2", 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 = "6B03DEEA38A02F276333CB27B53B16D3D45BD741B89599085A601BAF635F2006" },
|
||||
{ name = "mug", version = "3.1.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "mug", source = "hex", outer_checksum = "C01279D98E40371DA23461774B63F0E3581B8F1396049D881B0C7EB32799D93F" },
|
||||
{ name = "simplifile", version = "2.4.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "7C18AFA4FED0B4CE1FA5B0B4BAC1FA1744427054EA993565F6F3F82E5453170D" },
|
||||
{ name = "spoke_core", version = "1.0.1", build_tools = ["gleam"], requirements = ["drift", "gleam_stdlib", "spoke_mqtt", "spoke_packet"], otp_app = "spoke_core", source = "hex", outer_checksum = "B4092B5D0912936E3504AF903324D283D537C5126ED7FF50D8D12597E69224F1" },
|
||||
|
|
@ -26,10 +34,13 @@ packages = [
|
|||
envoy = { version = ">= 1.1.0 and < 2.0.0" }
|
||||
glaml = { version = ">= 3.0.2 and < 4.0.0" }
|
||||
gleam_erlang = { version = ">= 1.3.0 and < 2.0.0" }
|
||||
gleam_http = { version = ">= 4.3.0 and < 5.0.0" }
|
||||
gleam_json = { version = ">= 3.1.0 and < 4.0.0" }
|
||||
gleam_otp = { version = ">= 1.2.0 and < 2.0.0" }
|
||||
gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" }
|
||||
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
|
||||
logging = { version = ">= 1.3.0 and < 2.0.0" }
|
||||
mist = { version = ">= 6.0.2 and < 7.0.0" }
|
||||
simplifile = { version = ">= 2.4.0 and < 3.0.0" }
|
||||
spoke_mqtt = { version = ">= 1.0.0 and < 2.0.0" }
|
||||
spoke_mqtt_actor = { version = ">= 1.1.1 and < 2.0.0" }
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import envoy
|
|||
import glaml
|
||||
import gleam/option.{type Option, None, Some}
|
||||
import gleam/result
|
||||
import logging
|
||||
import simplifile
|
||||
|
||||
pub type Config {
|
||||
|
|
@ -11,6 +12,7 @@ pub type Config {
|
|||
mqtt_pw: String,
|
||||
mqtt_client_id: String,
|
||||
jwt_secret: String,
|
||||
log_level: logging.LogLevel,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -65,12 +67,22 @@ fn compile_config(doc: glaml.Document) -> Config {
|
|||
get_config_value(root, yaml_key, env_var)
|
||||
}
|
||||
|
||||
let log_level = case envoy.get("LOG_LEVEL") {
|
||||
Ok(level) ->
|
||||
case level {
|
||||
"debug" -> logging.Debug
|
||||
_ -> logging.Info
|
||||
}
|
||||
Error(_) -> logging.Info
|
||||
}
|
||||
|
||||
Config(
|
||||
mqtt_host: get(Some("mqtt.host"), None),
|
||||
mqtt_pw: get(None, Some("MQTT_PW")),
|
||||
mqtt_user: get(Some("mqtt.user"), None),
|
||||
jwt_secret: get(Some("jwt_secret"), Some("JWT_SECRET")),
|
||||
mqtt_client_id: get(Some("mqtt.client_id"), Some("MQTT_CLIENT_ID")),
|
||||
log_level:,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
import config
|
||||
import gleam/erlang/process
|
||||
import gleam/io
|
||||
import logging
|
||||
import mqtt
|
||||
import web
|
||||
|
||||
pub fn main() -> Nil {
|
||||
logging.configure()
|
||||
let cfg = config.load_config()
|
||||
io.println("Config loaded successfully!")
|
||||
logging.set_level(cfg.log_level)
|
||||
|
||||
// Start MQTT, which will forward updates to sensor_reader
|
||||
let client = mqtt.start(cfg)
|
||||
|
|
@ -13,6 +17,7 @@ pub fn main() -> Nil {
|
|||
mqtt.subscribe(client, "homeassistant/sensor/bws/node1/state1")
|
||||
echo s
|
||||
|
||||
let assert Ok(_) = web.start()
|
||||
process.sleep_forever()
|
||||
Nil
|
||||
}
|
||||
|
|
|
|||
207
src/web.gleam
Normal file
207
src/web.gleam
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
import gleam/bit_array
|
||||
import gleam/bytes_tree
|
||||
import gleam/erlang/process.{type Subject}
|
||||
import gleam/http/request.{type Request}
|
||||
import gleam/http/response.{type Response}
|
||||
import gleam/int
|
||||
import gleam/io
|
||||
import gleam/list
|
||||
import gleam/option.{None}
|
||||
import gleam/result
|
||||
import gleam/string
|
||||
import logging
|
||||
import mist.{type Connection, type ResponseData}
|
||||
|
||||
// Example takes from: https://hexdocs.pm/mist/index.html
|
||||
|
||||
const index = "<html lang='en'>
|
||||
<head>
|
||||
<title>Mist Example</title>
|
||||
</head>
|
||||
<body>
|
||||
Hello, world!
|
||||
</body>
|
||||
</html>"
|
||||
|
||||
pub fn start() {
|
||||
let not_found =
|
||||
response.new(404)
|
||||
|> response.set_body(mist.Bytes(bytes_tree.new()))
|
||||
|
||||
let assert Ok(_) =
|
||||
fn(req: Request(Connection)) -> Response(ResponseData) {
|
||||
let _ = case mist.get_connection_info(req.body) {
|
||||
Ok(info) -> {
|
||||
logging.log(
|
||||
logging.Info,
|
||||
"Got a request from: " <> mist.connection_info_to_string(info),
|
||||
)
|
||||
}
|
||||
Error(_nil) -> {
|
||||
logging.log(logging.Info, "Failed to get connection info")
|
||||
}
|
||||
}
|
||||
case request.path_segments(req) {
|
||||
[] ->
|
||||
response.new(200)
|
||||
|> response.prepend_header("my-value", "abc")
|
||||
|> response.prepend_header("my-value", "123")
|
||||
|> response.set_body(mist.Bytes(bytes_tree.from_string(index)))
|
||||
["ws"] ->
|
||||
mist.websocket(
|
||||
request: req,
|
||||
on_init: fn(_conn) { #(Nil, None) },
|
||||
on_close: fn(_state) { io.println("goodbye!") },
|
||||
handler: handle_ws_message,
|
||||
)
|
||||
["echo"] -> echo_body(req)
|
||||
["chunk"] ->
|
||||
mist.chunked(
|
||||
req,
|
||||
response.new(200),
|
||||
fn(subj) {
|
||||
process.spawn(fn() { send_chunks(subj) })
|
||||
Nil
|
||||
},
|
||||
fn(state, msg, connection) {
|
||||
case msg {
|
||||
Data(str) -> {
|
||||
let assert Ok(_nil) =
|
||||
mist.send_chunk(connection, bit_array.from_string(str))
|
||||
mist.chunk_continue(state)
|
||||
}
|
||||
Done -> {
|
||||
mist.chunk_stop()
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
["file", ..rest] -> serve_file(req, rest)
|
||||
["stream"] -> handle_stream(req)
|
||||
|
||||
_ -> not_found
|
||||
}
|
||||
}
|
||||
|> mist.new
|
||||
|> mist.bind("localhost")
|
||||
|> mist.with_ipv6
|
||||
|> mist.port(4000)
|
||||
|> mist.start
|
||||
}
|
||||
|
||||
pub type MyMessage {
|
||||
Broadcast(String)
|
||||
}
|
||||
|
||||
fn handle_ws_message(state, message, conn) {
|
||||
case message {
|
||||
mist.Text("ping") -> {
|
||||
let assert Ok(_) = mist.send_text_frame(conn, "pong")
|
||||
mist.continue(state)
|
||||
}
|
||||
mist.Text(msg) -> {
|
||||
logging.log(logging.Info, "Received text frame: " <> msg)
|
||||
mist.continue(state)
|
||||
}
|
||||
mist.Binary(msg) -> {
|
||||
logging.log(
|
||||
logging.Info,
|
||||
"Received binary frame ("
|
||||
<> int.to_string(bit_array.byte_size(msg))
|
||||
<> ")",
|
||||
)
|
||||
mist.continue(state)
|
||||
}
|
||||
mist.Custom(Broadcast(text)) -> {
|
||||
let assert Ok(_) = mist.send_text_frame(conn, text)
|
||||
mist.continue(state)
|
||||
}
|
||||
mist.Closed | mist.Shutdown -> mist.stop()
|
||||
}
|
||||
}
|
||||
|
||||
fn echo_body(request: Request(Connection)) -> Response(ResponseData) {
|
||||
let content_type =
|
||||
request
|
||||
|> request.get_header("content-type")
|
||||
|> result.unwrap("text/plain")
|
||||
|
||||
mist.read_body(request, 1024 * 1024 * 10)
|
||||
|> result.map(fn(req) {
|
||||
response.new(200)
|
||||
|> response.set_body(mist.Bytes(bytes_tree.from_bit_array(req.body)))
|
||||
|> response.set_header("content-type", content_type)
|
||||
})
|
||||
|> result.lazy_unwrap(fn() {
|
||||
response.new(400)
|
||||
|> response.set_body(mist.Bytes(bytes_tree.new()))
|
||||
})
|
||||
}
|
||||
|
||||
type ChunkMessage {
|
||||
Data(data: String)
|
||||
Done
|
||||
}
|
||||
|
||||
fn send_chunks(subject: Subject(ChunkMessage)) {
|
||||
["one", "two", "three"]
|
||||
|> list.each(fn(data) {
|
||||
process.sleep(2000)
|
||||
process.send(subject, Data(data))
|
||||
})
|
||||
process.send(subject, Done)
|
||||
}
|
||||
|
||||
fn serve_file(
|
||||
_req: Request(Connection),
|
||||
path: List(String),
|
||||
) -> Response(ResponseData) {
|
||||
let file_path = string.join(path, "/")
|
||||
|
||||
// Omitting validation for brevity
|
||||
mist.send_file(file_path, offset: 0, limit: None)
|
||||
|> result.map(fn(file) {
|
||||
let content_type = guess_content_type(file_path)
|
||||
response.new(200)
|
||||
|> response.prepend_header("content-type", content_type)
|
||||
|> response.set_body(file)
|
||||
})
|
||||
|> result.lazy_unwrap(fn() {
|
||||
response.new(404)
|
||||
|> response.set_body(mist.Bytes(bytes_tree.new()))
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_stream(req: Request(Connection)) -> Response(ResponseData) {
|
||||
let failed =
|
||||
response.new(400) |> response.set_body(mist.Bytes(bytes_tree.new()))
|
||||
case mist.stream(req) {
|
||||
Ok(consume) -> {
|
||||
case do_handle_stream(consume, <<>>) {
|
||||
Ok(body) ->
|
||||
response.new(200)
|
||||
|> response.set_body(mist.Bytes(bytes_tree.from_bit_array(body)))
|
||||
Error(_reason) -> failed
|
||||
}
|
||||
}
|
||||
Error(_reason) -> {
|
||||
failed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn do_handle_stream(
|
||||
consume: fn(Int) -> Result(mist.Chunk, mist.ReadError),
|
||||
body: BitArray,
|
||||
) -> Result(BitArray, Nil) {
|
||||
case consume(1024) {
|
||||
Ok(mist.Chunk(data, consume)) ->
|
||||
do_handle_stream(consume, <<body:bits, data:bits>>)
|
||||
Ok(mist.Done) -> Ok(body)
|
||||
Error(_reason) -> Error(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
fn guess_content_type(_path: String) -> String {
|
||||
"application/octet-stream"
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue