Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions examples/echo_ssl/gleam.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
17 changes: 8 additions & 9 deletions examples/echo_ssl/manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
3 changes: 2 additions & 1 deletion examples/echo_ssl/src/echo_server.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
81 changes: 43 additions & 38 deletions src/glisten/internal/handler.gleam
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
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.{type Socket, type SocketReason}
import glisten/socket/options.{type IpAddress}
import glisten/transport.{type Transport}
import logging

const no_of_packages_before_passive = 16

@external(erlang, "glisten_ffi", "rescue")
fn rescue(func: fn() -> anything) -> Result(anything, Dynamic)

Expand All @@ -20,8 +21,9 @@ pub type InternalMessage {
Close
Ready
ReceiveMessage(BitArray)
SslClosed
TcpClosed
Closed
Passive
SocketError(SocketReason)
}

pub type Message(user_message) {
Expand Down Expand Up @@ -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) {
SslClosed
|> 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("tcp_closed"), 1, fn(_nil) {
TcpClosed
|> process.select_record(atom.create("tcp_passive"), 1, fn(_nil) {
Passive
})
|> 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)
Expand Down Expand Up @@ -170,7 +170,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 {
Expand All @@ -181,6 +181,15 @@ pub fn start(
}
Error(err) -> actor.stop_abnormal(string.inspect(err))
}
Internal(Passive) -> {
let options = [
options.ActiveMode(options.Count(no_of_packages_before_passive)),
]
case transport.set_opts(state.transport, state.socket, options) {
Ok(_) -> actor.continue(state)
Error(_) -> actor.stop_abnormal("Failed to set socket active")
}
}
Internal(Ready) ->
case transport.handshake(state.transport, state.socket) {
Error(_) -> actor.stop_abnormal("Failed to handshake socket")
Expand All @@ -195,7 +204,9 @@ pub fn start(
}
}

let options = [options.ActiveMode(options.Once)]
let options = [
options.ActiveMode(options.Count(no_of_packages_before_passive)),
]
case transport.set_opts(state.transport, state.socket, options) {
Ok(_) -> actor.continue(state)
Error(_) -> actor.stop_abnormal("Failed to set socket active")
Expand All @@ -207,14 +218,7 @@ pub fn start(
let res = rescue(fn() { handler.loop(state.state, msg, connection) })
case res {
Ok(Continue(next_state, _selector)) -> {
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()
}
actor.continue(LoopState(..state, state: next_state))
}
Ok(NormalStop) -> actor.stop()
Ok(AbnormalStop(reason)) -> actor.stop_abnormal(reason)
Expand All @@ -232,14 +236,7 @@ pub fn start(
let res = rescue(fn() { handler.loop(state.state, msg, connection) })
case res {
Ok(Continue(next_state, _selector)) -> {
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()
}
actor.continue(LoopState(..state, state: next_state))
}
Ok(NormalStop) -> actor.stop()
Ok(AbnormalStop(reason)) -> actor.stop_abnormal(reason)
Expand All @@ -252,7 +249,15 @@ pub fn start(
}
}
}
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
73 changes: 7 additions & 66 deletions src/glisten/socket/options.gleam
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -45,48 +39,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),
Expand All @@ -98,28 +54,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)
Expand Down
19 changes: 8 additions & 11 deletions src/glisten/ssl.gleam
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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")
Expand Down Expand Up @@ -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, _)
}

Expand All @@ -76,6 +72,7 @@ pub fn listen(
) -> Result(ListenSocket, SocketReason) {
options
|> options.merge_with_defaults
|> options.to_erl_options
|> do_listen(port, _)
}

Expand Down
Loading