Automatic port allocation

 

For when you just can't decide...

It’s quite common to want to test a net­work ser­vice from the out­side, as if it was being ac­cessed from a cli­ent. Quite of­ten, people will pick a “well-­known” port to use, eg: port 8080 or 8888 for a HTTP ser­vice. But that means that if you leave a stray ser­vice pro­cess lying around, you’ll need to hunt it down be­fore you can re-test.

An­other common ap­proach is to it­erate through a ran­dom­ized set of ports until you find one you can bind(2) to with a listening socket; then closing it, then passing that dis­covered port number to your ser­vice. Un­for­tu­nately, this ap­proach tends to be some­what racy, as an­other ser­vice might be al­loc­ated that ad­dress between the time of check and the time of use.

It’s pos­sible to run this pro­cess it­er­at­ively, retrying until your ser­vice is able to bind to a pre-spe­cified port, but even then, there’s a chance that your tests may fail for no good reason if the net­work namespace is shared.

One little known fea­ture of the BSD sockets in­ter­face is that whilst port zero isn’t ac­tu­ally a us­able port for TCP or UDP; asking to listen on port zero will mean the TCP/UDP stack will bind to a free port. You can then learn which port that is using get­sock­name(2).

For ex­ample, in one of our in­ternal clo­jure ap­plic­a­tions, we have some­thing like this:

(de­fre­cord JettyServer [port web-app server]
  com­pon­ent/Li­fe­cycle
  (start [com­pon­ent]
    (lo­g/info ::start­ing-jetty :port port)
    (let [server (run­-jetty (ring/re­quest-handler web-app) {:port port :join? false})
          port' (->> server (.­get­Con­nect­ors) (some (memfn get­Loc­al­Port)))]
      (lo­g/info ::star­ted-jetty server :on-­port port')
      (assoc com­ponent :server server :port port')))

  (stop [com­pon­ent]
  ;;;...
  ))

Whilst it’s not en­tirely clear if you don’t read clo­jure, this uses the Server#get­Con­nect­ors() and then Net­work­Con­nector#get­Loc­al­Port() to find, and then log the local listen ad­dress.

Then in your tests, you can con­figure your client to con­nect to the res­ulting port, meaning that there’s one less reason (un­re­lated to what you’re veri­fy­ing) for your tests to fail.