Say I start a server like so:

srv := http.Server{
    Addr:    ":0",
    Handler: http.FileServer(http.Dir(".")),
}
go srv.ListenAndServe()
log.Printf("Listening on ???")

How can I tell which port was picked? :0 is designed to pick a random ephemeral port by the OS, but I need to know which one was picked.

NOTE: I would prefer not to create my own listener and use srv.Listen(ln), since srv.ListenAndServe() has a good default (but unexported) listener that I want to use.

asked Jul 26, 2015 at 0:44

chowey's user avatar

I would prefer not to create my own listener and use srv.Listen(ln), since srv.ListenAndServe() has a good default (but unexported) listener that I want to use.

Why not? ListenAndServe() is extremely simple to implement yourself. You can read the source code for it yourself:

func (srv *Server) ListenAndServe() error {
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return err
    }
    return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

The answer is that you will need to use write your own ListenAndServe() that gives you the information you want. It is much easier than you believe however. The entire thing isn't more than 20 lines (assuming you want to set keep-alives with tcpKeepAliveListener).

Graham Miln's user avatar

Graham Miln

2,8123 gold badges35 silver badges34 bronze badges

answered Jul 26, 2015 at 2:15

Stephen Weinberg's user avatar

4 Comments

My preference is just a preference, I'm aware it is not difficult to go the srv.Listen() route. I assume that because you avoid the problem, you also can't figure out a way to get the port number?

Correct, it is actually not possible to do that without things much much worse than creating your own listener. ListenAndServe is meant to be a convenience function. As with most such functions, the second you need something that special, you need to use a lower level function.

Note that this uses the unexported tcpKeepAliveListener so if you want TCP keep-alives you'll also need to copy that source thankfully it's only a dozen lines of trivial code but it makes it a little more annoying to do this.

As of Go 1.14.1, net/http no longer wraps the TCP listener!

Looking at the source code, it is now:

func (srv *Server) ListenAndServe() error {
    if srv.shuttingDown() {
        return ErrServerClosed
    }
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return err
    }
    return srv.Serve(ln)
}

This is due to this commit, where the "keepalive" part of the listener was moved to the standard net package and is now turned on by default.

So, don't worry about it! Just create your own listener.


Example

ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
    return err
}
go srv.Serve(ln)
log.Printf("Listening on %s", ln.Addr().String())

answered Apr 8, 2020 at 17:01

chowey's user avatar

chowey

10k6 gold badges60 silver badges87 bronze badges

Comments

You can select a free port before configuring the server address. The simpler way to get a free port is to create a listener, so you can obtain the same result following Stephen suggestion.

func GetFreePort() (int, error) {
    ln, err := net.Listen("tcp", ":0")
    if err != nil {
        return 0, err
    }
    err = ln.Close()
    if err != nil {
        return 0, err
    }
    return ln.Addr().(*net.TCPAddr).Port, nil
}

Here a complete example https://play.golang.org/p/bG4OpmQbz9s

answered Jan 16, 2018 at 14:12

Massimo Zerbini's user avatar

2 Comments

What if the OS assigns that port to another process in the meanwhile?

In that case you get an error starting the server, so you need to test if the error happens and repeat the operation. Choose another port calling the method GetFreePort again and restart the server.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.