I am building a simple http server using Plug.Cowboy (no Phoenix pls!) to serve a json API for a customer. A microservice.
The problem is the customer has a complex environment with multiple services of many sorts, containers, microservices etc. that are serving on various ports in his server. Therefore we were requested to develop the service to look dynamically and launch itself on a free/unused port.
When we build the Elixir app it is requested to declare the port at Plug initialization or it defaults to 4000/4040:
def start(_type, _args) do
children = [
{Plug.Cowboy, scheme: :http, plug: MyApp, port: 1234}
]
...
How can I re-factory this to search the first available unused port in a certain range ?
If you put port: 0 it seems to look for an available port and use it. I’m not sure if this is safe to be counted on though. I tried it out because in erlang gen_tcp.listen finds an available port when you set port to 0.
This seems to be a Unix convention of getting a random available port. If you then need to know what port you got, possibly you can dig at the Cowboy socket to gain that information.
You cannot limit range, but as other have said, using port 0 will give you random port from ephemeral range (configured by the OS). The problem though is how to later extract that port from within your application (it is not done automatically for you).
However using ephemeral ports for listening with your services is a little bit weird, what exactly you are trying to accomplish?
Indeed I need the port number to be registered and reported by the app. Otherwise it is totally useless.
Moreover I don’t need a random port, the requirement is to find a port in a given range.
I need something like this in Java:
public int nextFreePort(int from, int to) {
int port = randPort(from, to);
while (true) {
if (isLocalPortFree(port)) {
return port;
} else {
port = ThreadLocalRandom.current().nextInt(from, to);
}
}
}
private boolean isLocalPortFree(int port) {
try {
new ServerSocket(port).close();
return true;
} catch (IOException e) {
return false;
}
}
def start(_type, _args) do
children = [
{Plug.Cowboy, scheme: :http, plug: MyApp, port: get_free_port(4000)}
]
...
end
defp get_free_port(start) do
case :gen_tcp.listen(start, [:binary]) do
{:ok, socket} ->
:ok = :gen_tcp.close(socket)
start
{:error, :eaddrinuse} ->
get_free_port(start + 1)
end
end
Another thing you could do is create your own module that initializes the server using the command below and then supervise that instead. Your start_link function would call this command and loop over the port until you get one that works for you.
Plug.Cowboy.http MyPlug, [], port: 80
One thing I thought about your method though, if your app crashes or you do a new deploy or your cowboy process crashes then you can get a new port every time. It seems like your client should be reserving a port for your app on their machines.
@hauleth, please read my original post to understand why. This is the customer’s request. It is not my job to question it.
You’re right, it might be there a better solution. This is the reason I opened this discussion.
I think I’d be tempted to solve this using a launch script that uses netstat or similar to find a suitable available port, then setting an ENV variable before launching the server. It might be possible to do something similar directly in Elixir, before launching the Phoenix process, but I suspect the launch script approach would be quicker & easier to implement.
This should have ended with a question mark. I’m trying to learn from this thread and from your tone it seems you have taken my post as an attack. So good luck
Yes, there may in fact be several seconds’ delay, as the application needs to boot before the socket is opened. I think most solutions will suffer from this to some extent. Hopefully the deployment environment will have a service manager (e.g. systemd, k8s, …) that is capable of restarting services that exit unexpectedly, so this condition should not be too difficult to handle.