rawhat / glisten

A pure gleam TCP library
Apache License 2.0
55 stars 8 forks source link

Expose `inet:port` for socket types #19

Closed bonsairobo closed 3 months ago

bonsairobo commented 3 months ago

For example, I would like to be able to get the port number of a socket like so:

import gleam/io
import gleam/result
import glisten/socket
import glisten/socket/options.{ActiveMode, Passive}
import glisten/tcp

pub fn main() {
  use listener <- result.then(tcp.listen(0, [ActiveMode(Passive)]))
  use port <- result.then(socket.port(listener))
  io.debug(#("port number is ", port))

  use sock <- result.then(tcp.accept(listener))
  use port <- result.then(socket.port(sock))
  io.debug(#("port number is ", port))

  Ok(Nil)
}

Notice that port 0 is used so the OS will allocate a port for us. Without the inet:port function, there is no way to know what the actual port number is.

I did something very simple here to test it out:

@external(erlang, "inet", "port")
pub fn port(socket: ListenSocket) -> Result(Int, SocketReason)

But I'm completely new to gleam, and I'm not sure how to make this work for both ListenSocket and Socket types. I don't know any Erlang, so I'm not sure how those opaque types are related to the Erlang gen_tcp code.

I'm also not sure if SocketReason is the proper error type for this external function.

rawhat commented 3 months ago

Makes sense as well, I can add a wrapper for that. Thanks!

rawhat commented 3 months ago

Cool, so I've added this. Currently just on master... it's going to need a major version bump since I had to update the return signature of the top-level methods to support accessing the OS-assigned port. For the "low-level" stuff, it's accessible at glisten/transport.{port} and it uses the ListenSocket. It probably works with both, but that's annoying to do with the Gleam type system.

I suspect it will be okay in practice to just look it up once the listen socket is obtained. If you have a use-case in mind that needs it otherwise, I can work around it. Will probably just need a separate function.

If you want, you can clone the repo and try it out as a path dependency. If you can, would be helpful to see if I did anything wrong 😅 if not, no worries!

bonsairobo commented 3 months ago

Thanks!

It probably works with both, but that's annoying to do with the Gleam type system.

Understandable, I can't think of a use case for getting the bound port from an accept call. I'm curious what you mean by "annoying" though.

Also OOC, what does the unknown mean here?

pub fn port(socket: ListenSocket) -> Result(Int, unknown)
bonsairobo commented 3 months ago

If you want, you can clone the repo and try it out as a path dependency

I just made a small change to the "low_level" example to see it working:

diff --git a/examples/low_level/src/low_level.gleam b/examples/low_level/src/low_level.gleam
index 5a087ab..20c2b81 100644
--- a/examples/low_level/src/low_level.gleam
+++ b/examples/low_level/src/low_level.gleam
@@ -1,13 +1,17 @@
+import gleam/int
 import gleam/io
 import gleam/result
 import glisten/socket/options.{ActiveMode, Passive}
 import glisten/tcp
+import glisten/transport
 import logging

 pub fn main() {
   logging.configure()

-  use listener <- result.then(tcp.listen(8000, [ActiveMode(Passive)]))
+  use listener <- result.then(tcp.listen(0, [ActiveMode(Passive)]))
+  use port <- result.then(transport.port(listener))
+  io.println("port = " <> int.to_string(port))
   use socket <- result.then(tcp.accept(listener))
   use msg <- result.then(tcp.receive(socket, 0))
   io.debug(#("got a msg", msg))
bonsairobo commented 3 months ago

Oh also the "echo_server" example is already doing this, but via the get_port function.