diff --git a/examples/echo_ssl/gleam.toml b/examples/echo_ssl/gleam.toml index 0b17d7b..0da32f5 100644 --- a/examples/echo_ssl/gleam.toml +++ b/examples/echo_ssl/gleam.toml @@ -10,10 +10,10 @@ version = "0.1.0" # links = [{ title = "Website", href = "https://gleam.run" }] [dependencies] -gleam_stdlib = "~> 0.36" glisten = { path = "../../" } -gleam_erlang = "~> 0.24" -gleam_otp = "~> 0.9" +gleam_stdlib = ">= 0.68.1 and < 1.0.0" +gleam_erlang = ">= 1.3.0 and < 2.0.0" +gleam_otp = ">= 1.2.0 and < 2.0.0" logging = ">= 1.3.0 and < 2.0.0" [dev-dependencies] diff --git a/examples/echo_ssl/manifest.toml b/examples/echo_ssl/manifest.toml index f38ebbe..26a9d5e 100644 --- a/examples/echo_ssl/manifest.toml +++ b/examples/echo_ssl/manifest.toml @@ -2,19 +2,18 @@ # You typically do not need to edit this file packages = [ - { name = "gleam_erlang", version = "1.0.0-rc1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "6E0CF4E1F66E2C9226B7554589544F00F12CE14858440EB1BF7EFDACDE1BBC64" }, - { name = "gleam_otp", version = "1.0.0-rc2", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "27138425316D9AACC4881CE56A8F2BDE98AC9FB669755BEF712F37CB79090EAF" }, - { name = "gleam_stdlib", version = "0.60.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "621D600BB134BC239CB2537630899817B1A42E60A1D46C5E9F3FAE39F88C800B" }, - { name = "gleeunit", version = "1.5.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "D33B7736CF0766ED3065F64A1EBB351E72B2E8DE39BAFC8ADA0E35E92A6A934F" }, - { name = "glisten", version = "8.0.0-rc1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib", "logging", "telemetry"], source = "local", path = "../.." }, + { name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" }, + { 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.68.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "F7FAEBD8EF260664E86A46C8DBA23508D1D11BB3BCC6EE1B89B3BC3E5C83FF1E" }, + { 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-rc1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib", "logging"], source = "local", path = "../.." }, { name = "logging", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "1098FBF10B54B44C2C7FDF0B01C1253CAFACDACABEFB4B0D027803246753E06D" }, - { name = "telemetry", version = "1.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "telemetry", source = "hex", outer_checksum = "7015FC8919DBE63764F4B4B87A95B7C0996BD539E0D499BE6EC9D7F3875B79E6" }, ] [requirements] -gleam_erlang = { version = "~> 0.24" } -gleam_otp = { version = "~> 0.9" } -gleam_stdlib = { version = "~> 0.36" } +gleam_erlang = { version = ">= 1.3.0 and < 2.0.0" } +gleam_otp = { version = ">= 1.2.0 and < 2.0.0" } +gleam_stdlib = { version = ">= 0.68.1 and < 1.0.0" } gleeunit = { version = "~> 1.0" } glisten = { path = "../../" } logging = { version = ">= 1.3.0 and < 2.0.0" } diff --git a/examples/echo_ssl/src/echo_server.gleam b/examples/echo_ssl/src/echo_server.gleam index 2dfb979..60a08ad 100644 --- a/examples/echo_ssl/src/echo_server.gleam +++ b/examples/echo_ssl/src/echo_server.gleam @@ -36,7 +36,8 @@ pub fn main() { glisten.continue(state) }) |> glisten.with_tls(certfile: "localhost.crt", keyfile: "localhost.key") - |> glisten.start_with_listener_name(0, listener_name) + |> glisten.with_listener_name(listener_name) + |> glisten.start(0) let info = glisten.get_server_info(listener_name, 5000) diff --git a/src/glisten.gleam b/src/glisten.gleam index be1805d..a61228e 100644 --- a/src/glisten.gleam +++ b/src/glisten.gleam @@ -215,6 +215,7 @@ pub opaque type Builder(state, user_message) { factory.Message(Socket, Subject(handler.Message(user_message))), ), ), + active_state: options.ActiveState, ) } @@ -288,6 +289,7 @@ pub fn new( tls_options: None, listener_name: None, connection_factory_name: None, + active_state: options.Once, ) } @@ -354,6 +356,22 @@ pub fn with_tls( Builder(..builder, tls_options: Some(options.CertKeyFiles(cert, key))) } +/// Set the server's `ActiveState` for flow control of received packets. +/// Default is `Once`. Allowed are `Once`, `Active` and `Count(n)` where n > 1. +pub fn with_active_state( + builder: Builder(state, user_message), + active_state: options.ActiveState, +) -> Builder(state, user_message) { + case active_state { + options.Once | options.Active -> + Builder(..builder, active_state: active_state) + options.Count(n) if n > 1 -> Builder(..builder, active_state: active_state) + options.Count(_) -> panic as "Count shall be greater than 1" + options.Passive -> + panic as "You cannot set the server's `ActiveState` to `Passive`" + } +} + @internal pub fn with_listener_name( builder: Builder(state, user_message), @@ -412,6 +430,7 @@ pub fn start( on_init: convert_on_init(builder.on_init), on_close: builder.on_close, transport:, + active_state: builder.active_state, ) |> acceptor.start_pool(transport, port, options, listener_name) } diff --git a/src/glisten/internal/acceptor.gleam b/src/glisten/internal/acceptor.gleam index 4ff4bb9..2b02e9d 100644 --- a/src/glisten/internal/acceptor.gleam +++ b/src/glisten/internal/acceptor.gleam @@ -109,6 +109,7 @@ pub type Pool(data, user_message) { #(data, Option(Selector(user_message))), on_close: Option(fn(data) -> Nil), transport: Transport, + active_state: options.ActiveState, ) } @@ -149,6 +150,7 @@ pub fn start_pool( on_init: pool.on_init, on_close: pool.on_close, transport: pool.transport, + active_state: pool.active_state, )) }) |> factory.named(pool.name) diff --git a/src/glisten/internal/handler.gleam b/src/glisten/internal/handler.gleam index 7b599fb..65ffb07 100644 --- a/src/glisten/internal/handler.gleam +++ b/src/glisten/internal/handler.gleam @@ -1,13 +1,12 @@ import gleam/dynamic.{type Dynamic} -import gleam/dynamic/decode import gleam/erlang/atom import gleam/erlang/process.{type Selector, type Subject} import gleam/option.{type Option, None, Some} import gleam/otp/actor import gleam/result import gleam/string -import glisten/socket.{type Socket} -import glisten/socket/options.{type IpAddress} +import glisten/socket.{type Socket, type SocketReason} +import glisten/socket/options.{type ActiveState, type IpAddress} import glisten/transport.{type Transport} import logging @@ -20,8 +19,9 @@ pub type InternalMessage { Close Ready ReceiveMessage(BitArray) - SslClosed - TcpClosed + Closed + Passive + SocketError(SocketReason) } pub type Message(user_message) { @@ -44,6 +44,7 @@ pub type LoopState(state, user_message) { sender: Subject(Message(user_message)), transport: Transport, state: state, + active_state: ActiveState, ) } @@ -96,6 +97,7 @@ pub type Handler(state, user_message) { #(state, Option(Selector(user_message))), on_close: Option(fn(state) -> Nil), transport: Transport, + active_state: ActiveState, ) } @@ -119,26 +121,24 @@ pub fn start( let selector = process.new_selector() |> process.select_record(atom.create("tcp"), 2, fn(record) { - { - use data <- decode.field(2, decode.bit_array) - decode.success(ReceiveMessage(data)) - } - |> decode.run(record, _) - |> result.unwrap(ReceiveMessage(<<>>)) + ReceiveMessage(socket_data(record)) }) |> process.select_record(atom.create("ssl"), 2, fn(record) { - { - use data <- decode.field(2, decode.bit_array) - decode.success(ReceiveMessage(data)) - } - |> decode.run(record, _) - |> result.unwrap(ReceiveMessage(<<>>)) + ReceiveMessage(socket_data(record)) + }) + |> process.select_record(atom.create("ssl_closed"), 1, fn(_nil) { Closed }) + |> process.select_record(atom.create("tcp_closed"), 1, fn(_nil) { Closed }) + |> process.select_record(atom.create("ssl_passive"), 1, fn(_nil) { + Passive }) - |> process.select_record(atom.create("ssl_closed"), 1, fn(_nil) { - SslClosed + |> process.select_record(atom.create("tcp_passive"), 1, fn(_nil) { + Passive }) - |> process.select_record(atom.create("tcp_closed"), 1, fn(_nil) { - TcpClosed + |> process.select_record(atom.create("tcp_error"), 2, fn(record) { + SocketError(socket_error(record)) + }) + |> process.select_record(atom.create("ssl_error"), 2, fn(record) { + SocketError(socket_error(record)) }) |> process.map_selector(Internal) |> process.merge_selector(base_selector) @@ -155,6 +155,7 @@ pub fn start( sender: subject, transport: handler.transport, state: initial_state, + active_state: handler.active_state, ) |> actor.initialised() |> actor.selecting(selector) @@ -170,7 +171,7 @@ pub fn start( sender: state.sender, ) case msg { - Internal(TcpClosed) | Internal(SslClosed) | Internal(Close) -> + Internal(Closed) | Internal(Close) -> case transport.close(state.transport, state.socket) { Ok(Nil) -> { let _ = case handler.on_close { @@ -194,8 +195,10 @@ pub fn start( logging.log(logging.Warning, err) } } - - let options = [options.ActiveMode(options.Once)] + // Note that the active_state must set to Passive at start of + // Listener/Accept and not changed until the Ready message is + // received. + let options = [options.ActiveMode(state.active_state)] case transport.set_opts(state.transport, state.socket, options) { Ok(_) -> actor.continue(state) Error(_) -> actor.stop_abnormal("Failed to set socket active") @@ -206,16 +209,20 @@ pub fn start( let msg = Custom(msg) let res = rescue(fn() { handler.loop(state.state, msg, connection) }) case res { - Ok(Continue(next_state, _selector)) -> { + Ok(Continue(next_state, _selector)) + if state.active_state == options.Once + -> { case transport.set_opts(state.transport, state.socket, [ options.ActiveMode(options.Once), ]) { Ok(Nil) -> actor.continue(LoopState(..state, state: next_state)) - Error(Nil) -> actor.stop() + Error(_) -> actor.stop() } } + Ok(Continue(next_state, _selector)) -> + actor.continue(LoopState(..state, state: next_state)) Ok(NormalStop) -> actor.stop() Ok(AbnormalStop(reason)) -> actor.stop_abnormal(reason) Error(reason) -> { @@ -231,16 +238,20 @@ pub fn start( let msg = Packet(msg) let res = rescue(fn() { handler.loop(state.state, msg, connection) }) case res { - Ok(Continue(next_state, _selector)) -> { + Ok(Continue(next_state, _selector)) + if state.active_state == options.Once + -> { case transport.set_opts(state.transport, state.socket, [ options.ActiveMode(options.Once), ]) { Ok(Nil) -> actor.continue(LoopState(..state, state: next_state)) - Error(Nil) -> actor.stop() + Error(_) -> actor.stop() } } + Ok(Continue(next_state, _selector)) -> + actor.continue(LoopState(..state, state: next_state)) Ok(NormalStop) -> actor.stop() Ok(AbnormalStop(reason)) -> actor.stop_abnormal(reason) Error(reason) -> { @@ -252,7 +263,24 @@ pub fn start( } } } + Internal(Passive) -> { + let options = [ + options.ActiveMode(state.active_state), + ] + case transport.set_opts(state.transport, state.socket, options) { + Ok(_) -> actor.continue(state) + Error(_) -> actor.stop_abnormal("Failed to set socket active") + } + } + Internal(SocketError(reason)) -> + actor.stop_abnormal("Received socket error " <> string.inspect(reason)) } }) |> actor.start() } + +@external(erlang, "glisten_ffi", "socket_data") +fn socket_data(record: Dynamic) -> BitArray + +@external(erlang, "glisten_ffi", "socket_data") +fn socket_error(record: Dynamic) -> SocketReason diff --git a/src/glisten/socket/options.gleam b/src/glisten/socket/options.gleam index 8489fa0..a27a406 100644 --- a/src/glisten/socket/options.gleam +++ b/src/glisten/socket/options.gleam @@ -1,9 +1,3 @@ -import gleam/dict.{type Dict} -import gleam/dynamic.{type Dynamic} -import gleam/dynamic/decode -import gleam/erlang/atom -import gleam/list - /// Mode for the socket. Currently `list` is not supported pub type SocketMode { Binary @@ -11,9 +5,13 @@ pub type SocketMode { /// Mapping to the `{active, _}` option pub type ActiveState { + /// The server acknowledges every package. Once + /// Not used by server - for use with low level `tcp.receive`. Passive + /// The server will receive `n` packages before activating again. Count(Int) + /// Connection is always active, no flow control. Active } @@ -29,13 +27,20 @@ pub type TlsCerts { /// Options for the TCP socket pub type TcpOption { + /// Default 1024 Backlog(Int) + /// Default True Nodelay(Bool) Linger(#(Bool, Int)) + /// Default 30_000 SendTimeout(Int) + /// Default True SendTimeoutClose(Bool) + /// Default True Reuseaddr(Bool) + /// Default Passive for low level and Once for server. ActiveMode(ActiveState) + /// Default Binary Mode(SocketMode) // TODO: Probably should adjust the type here to only allow this for TLS CertKeyConfig(TlsCerts) @@ -45,48 +50,10 @@ pub type TcpOption { Ip(Interface) } -@external(erlang, "gleam@function", "identity") -fn from(value: a) -> Dynamic - -pub fn to_dict(options: List(TcpOption)) -> Dict(Dynamic, Dynamic) { - let opt_decoder = { - use opt <- decode.field(0, decode.dynamic) - use value <- decode.field(1, decode.dynamic) - decode.success(#(opt, value)) - } +pub type ErlangTcpOption - let active = atom.create("active") - let ip = atom.create("ip") - - options - |> list.map(fn(opt) { - case opt { - ActiveMode(Passive) -> from(#(active, False)) - ActiveMode(Active) -> from(#(active, True)) - ActiveMode(Count(n)) -> from(#(active, n)) - ActiveMode(Once) -> from(#(active, atom.create("once"))) - Ip(Address(IpV4(a, b, c, d))) -> from(#(ip, from(#(a, b, c, d)))) - Ip(Address(IpV6(a, b, c, d, e, f, g, h))) -> - from(#(ip, from(#(a, b, c, d, e, f, g, h)))) - Ip(Any) -> from(#(ip, atom.create("any"))) - Ip(Loopback) -> from(#(ip, atom.create("loopback"))) - Ipv6 -> from(atom.create("inet6")) - CertKeyConfig(CertKeyFiles(certfile, keyfile)) -> { - from( - #(atom.create("certs_keys"), [ - dict.from_list([ - #(atom.create("certfile"), certfile), - #(atom.create("keyfile"), keyfile), - ]), - ]), - ) - } - other -> from(other) - } - }) - |> list.filter_map(decode.run(_, opt_decoder)) - |> dict.from_list -} +@external(erlang, "glisten_ffi", "to_erl_tcp_options") +pub fn to_erl_options(options: List(TcpOption)) -> List(ErlangTcpOption) pub const default_options = [ Backlog(1024), @@ -98,28 +65,13 @@ pub const default_options = [ ActiveMode(Passive), ] -pub fn merge_with_defaults(options: List(TcpOption)) -> List(TcpOption) { - let overrides = to_dict(options) - - let has_ipv6 = list.contains(options, Ipv6) +@external(erlang, "glisten_ffi", "merge_type_list") +pub fn merge_type_list(original: List(a), override: List(a)) -> List(a) - default_options - |> to_dict - |> dict.merge(overrides) - |> dict.to_list - |> list.map(from) - |> fn(opts) { - case has_ipv6 { - True -> [from(atom.create("inet6")), ..opts] - _ -> opts - } - } - |> unsafe_coerce +pub fn merge_with_defaults(options: List(TcpOption)) -> List(TcpOption) { + merge_type_list(default_options, options) } -@external(erlang, "gleam@function", "identity") -fn unsafe_coerce(value: a) -> b - pub type IpAddress { IpV4(Int, Int, Int, Int) IpV6(Int, Int, Int, Int, Int, Int, Int, Int) diff --git a/src/glisten/ssl.gleam b/src/glisten/ssl.gleam index 4ce8a65..e284284 100644 --- a/src/glisten/ssl.gleam +++ b/src/glisten/ssl.gleam @@ -1,9 +1,7 @@ import gleam/bytes_tree.{type BytesTree} -import gleam/dict import gleam/dynamic.{type Dynamic} import gleam/erlang/atom.{type Atom} import gleam/erlang/process.{type Pid} -import gleam/list import glisten/socket.{type ListenSocket, type Socket, type SocketReason} import glisten/socket/options @@ -13,7 +11,7 @@ pub fn controlling_process(socket: Socket, pid: Pid) -> Result(Nil, Atom) @external(erlang, "ssl", "listen") fn do_listen( port: Int, - options: List(options.TcpOption), + options: List(options.ErlangTcpOption), ) -> Result(ListenSocket, SocketReason) @external(erlang, "ssl", "transport_accept") @@ -49,20 +47,18 @@ pub fn shutdown(socket: Socket) -> Result(Nil, SocketReason) { } @external(erlang, "glisten_ssl_ffi", "set_opts") -fn do_set_opts(socket: Socket, opts: List(Dynamic)) -> Result(Nil, Nil) - -@external(erlang, "gleam@function", "identity") -fn from(value: a) -> Dynamic +fn do_set_opts( + socket: Socket, + opts: List(options.ErlangTcpOption), +) -> Result(Nil, SocketReason) /// Update the optons for a socket (mutates the socket) pub fn set_opts( socket: Socket, opts: List(options.TcpOption), -) -> Result(Nil, Nil) { +) -> Result(Nil, SocketReason) { opts - |> options.to_dict - |> dict.to_list - |> list.map(from) + |> options.to_erl_options() |> do_set_opts(socket, _) } @@ -76,6 +72,7 @@ pub fn listen( ) -> Result(ListenSocket, SocketReason) { options |> options.merge_with_defaults + |> options.to_erl_options |> do_listen(port, _) } @@ -83,7 +80,7 @@ pub fn listen( pub fn negotiated_protocol(socket: Socket) -> Result(String, String) @external(erlang, "ssl", "peername") -pub fn peername(socket: Socket) -> Result(#(Dynamic, Int), Nil) +pub fn peername(socket: Socket) -> Result(#(Dynamic, Int), SocketReason) @external(erlang, "ssl", "sockname") pub fn sockname(socket: ListenSocket) -> Result(#(Dynamic, Int), SocketReason) @@ -92,4 +89,4 @@ pub fn sockname(socket: ListenSocket) -> Result(#(Dynamic, Int), SocketReason) pub fn get_socket_opts( socket: Socket, opts: List(Atom), -) -> Result(List(#(Atom, Dynamic)), Nil) +) -> Result(List(#(Atom, Dynamic)), SocketReason) diff --git a/src/glisten/tcp.gleam b/src/glisten/tcp.gleam index 5bd142f..338d1dc 100644 --- a/src/glisten/tcp.gleam +++ b/src/glisten/tcp.gleam @@ -3,7 +3,6 @@ import gleam/dict.{type Dict} import gleam/dynamic.{type Dynamic} import gleam/erlang/atom.{type Atom} import gleam/erlang/process.{type Pid} -import gleam/list import glisten/socket.{type ListenSocket, type Socket, type SocketReason} import glisten/socket/options.{type TcpOption} @@ -13,7 +12,7 @@ pub fn controlling_process(socket: Socket, pid: Pid) -> Result(Nil, Atom) @external(erlang, "gen_tcp", "listen") fn do_listen_tcp( port: Int, - options: List(TcpOption), + options: List(options.ErlangTcpOption), ) -> Result(ListenSocket, SocketReason) @external(erlang, "gen_tcp", "accept") @@ -52,17 +51,18 @@ pub fn shutdown(socket: Socket) -> Result(Nil, SocketReason) { } @external(erlang, "glisten_tcp_ffi", "set_opts") -fn do_set_opts(socket: Socket, opts: List(Dynamic)) -> Result(Nil, Nil) - -@external(erlang, "gleam@function", "identity") -fn from(value: a) -> Dynamic +fn do_set_opts( + socket: Socket, + opts: List(options.ErlangTcpOption), +) -> Result(Nil, SocketReason) /// Update the optons for a socket (mutates the socket) -pub fn set_opts(socket: Socket, opts: List(TcpOption)) -> Result(Nil, Nil) { +pub fn set_opts( + socket: Socket, + opts: List(TcpOption), +) -> Result(Nil, SocketReason) { opts - |> options.to_dict - |> dict.to_list - |> list.map(from) + |> options.to_erl_options() |> do_set_opts(socket, _) } @@ -73,6 +73,7 @@ pub fn listen( ) -> Result(ListenSocket, SocketReason) { opts |> options.merge_with_defaults + |> options.to_erl_options |> do_listen_tcp(port, _) } @@ -84,13 +85,13 @@ pub fn handshake(socket: Socket) -> Result(Socket, Nil) { pub fn negotiated_protocol(socket: Socket) -> a @external(erlang, "inet", "peername") -pub fn peername(socket: Socket) -> Result(#(Dynamic, Int), Nil) +pub fn peername(socket: Socket) -> Result(#(Dynamic, Int), SocketReason) @external(erlang, "inet", "getopts") pub fn get_socket_opts( socket: Socket, opts: List(Atom), -) -> Result(List(#(Atom, Dynamic)), Nil) +) -> Result(List(#(Atom, Dynamic)), SocketReason) @external(erlang, "inet", "sockname") pub fn sockname(socket: ListenSocket) -> Result(#(Dynamic, Int), SocketReason) diff --git a/src/glisten/transport.gleam b/src/glisten/transport.gleam index c5188b4..c742a2b 100644 --- a/src/glisten/transport.gleam +++ b/src/glisten/transport.gleam @@ -120,7 +120,7 @@ pub fn set_opts( transport: Transport, socket: Socket, opts: List(options.TcpOption), -) -> Result(Nil, Nil) { +) -> Result(Nil, SocketReason) { case transport { Tcp -> tcp.set_opts(socket, opts) Ssl -> ssl.set_opts(socket, opts) @@ -177,6 +177,7 @@ pub fn peername( Tcp -> tcp.peername(socket) Ssl -> ssl.peername(socket) } + |> result.replace_error(Nil) |> result.try(fn(pair) { let #(ip_address, port) = pair decode.run(ip_address, decode_ip()) @@ -200,6 +201,7 @@ pub fn get_socket_opts( Tcp -> tcp.get_socket_opts(socket, opts) Ssl -> ssl.get_socket_opts(socket, opts) } + |> result.replace_error(Nil) } pub fn set_buffer_size(transport: Transport, socket: Socket) -> Result(Nil, Nil) { @@ -215,6 +217,7 @@ pub fn set_buffer_size(transport: Transport, socket: Socket) -> Result(Nil, Nil) }) |> result.try(fn(value) { set_opts(transport, socket, [options.Buffer(value)]) + |> result.map_error(fn(_) { Nil }) }) } diff --git a/src/glisten_ffi.erl b/src/glisten_ffi.erl index b457213..175da9a 100644 --- a/src/glisten_ffi.erl +++ b/src/glisten_ffi.erl @@ -1,6 +1,7 @@ -module(glisten_ffi). --export([parse_address/1, rescue/1]). +-export([parse_address/1, rescue/1, to_erl_tcp_options/1, + merge_type_list/2, socket_data/1]). parse_address(Address) -> case inet:parse_address(Address) of @@ -19,3 +20,49 @@ rescue(Func) -> catch Anything -> {error, Anything} end. + +to_erl_tcp_options(Options) -> + lists:map(fun(A) -> to_erl_tcp_option(A) end, Options). + +to_erl_tcp_option({active_mode, once}) -> {active, once}; +to_erl_tcp_option({active_mode, passive}) -> {active, false}; +to_erl_tcp_option({active_mode, active}) -> {active, true}; +to_erl_tcp_option({active_mode, {count, N}}) -> {active, N}; +to_erl_tcp_option({ip, {address, {ip_v4, A, B, C, D}}}) -> + {ip, {A, B, C, D}}; +to_erl_tcp_option({ip, {address, {ip_v6, A, B, C, D, E, F, G, H}}}) -> + {ip, {A, B, C, D, E, F, G, H}}; +to_erl_tcp_option({ip, any}) -> {ip, any}; +to_erl_tcp_option({ip, loopback}) -> {ip, loopback}; +to_erl_tcp_option(ipv6) -> inet6; +to_erl_tcp_option({cert_key_config, {cert_key_files, CertFile, KeyFile}}) -> + {certs_keys, [#{certfile => CertFile, keyfile => KeyFile}]}; +to_erl_tcp_option(Other) -> Other. + +merge_type_list(Original, Override) -> + NewKeys = get_type_keys(Override), + KeepFromOriginal = + lists:foldl( + fun(Value, Accu) -> insert_value(Value, Accu, NewKeys) end, + [], + Original), + KeepFromOriginal ++ Override. + +insert_value(Value, Accu, NewKeys) -> + case lists:member(get_type_key(Value), NewKeys) of + true -> Accu; + false -> [ Value | Accu ] + end. + +get_type_keys(TypeList) -> + lists:map( + fun(Type) -> get_type_key(Type) end, + TypeList). + +get_type_key(Type) when is_atom(Type) -> Type; +get_type_key(Type) when is_tuple(Type) -> element(1, Type). + +socket_data({tcp, _Socket, Data}) -> Data; +socket_data({ssl, _Socket, Data}) -> Data; +socket_data({tcp_error, _Socket, Reason}) -> Reason; +socket_data({ssl_error, _Socket, Reason}) -> Reason. diff --git a/test/glisten_test.gleam b/test/glisten_test.gleam index a4257e8..4cdd8de 100644 --- a/test/glisten_test.gleam +++ b/test/glisten_test.gleam @@ -91,3 +91,25 @@ pub fn ip_address_to_string_test() { assert glisten.ip_address_to_string(ip) == expected } + +pub fn merge_type_lists_test() { + let default_list = options.default_options + let merge_with = [ + options.ActiveMode(options.Count(10)), + options.CertKeyConfig(options.CertKeyFiles( + certfile: "hello.crt", + keyfile: "world.key", + )), + ] + assert [ + options.Mode(options.Binary), + options.Reuseaddr(True), + options.SendTimeoutClose(True), + options.SendTimeout(30_000), + options.Nodelay(True), + options.Backlog(1024), + options.ActiveMode(options.Count(10)), + options.CertKeyConfig(options.CertKeyFiles("hello.crt", "world.key")), + ] + == options.merge_type_list(default_list, merge_with) +}