Can WEBrick bind to port 0, and then tell me what port was allocated?

I don't want to use a hard-coded port number, I want it to bind to
INADDR_ANY with a port of zero. The system will select a free port.

Then, I can advertise that port using mDNS/DNS-SD.

Is this possible? I can maybe set :Port to 0, but I am searching the
webrick src and can't see a way to get the underlying socket up, so I
can ask it it's port.

My next try will be looping until I register a free port, but that's an
ugly, ugly solution!

Cheers,
Sam

maybe you can bind to port 0 then just query the socket which port it is on?
i'm speaking from a low level perspective. maybe consult the ruby docs
for socket behavior.

···

On Mon, 21 Mar 2005 03:50:06 +0900, Sam Roberts <sroberts@uniserve.com> wrote:

I don't want to use a hard-coded port number, I want it to bind to
INADDR_ANY with a port of zero. The system will select a free port.

--
http://www.eyan.org

Hi,

In message <20050320184914.GB911@ensemble.local>,

INADDR_ANY with a port of zero. The system will select a free port.

Then, I can advertise that port using mDNS/DNS-SD.

Is this possible? I can maybe set :Port to 0, but I am searching the
webrick src and can't see a way to get the underlying socket up, so I
can ask it it's port.

Setting :Port to 0 works like you want. However WEBrick
never report it. I think the log messages should be changed.
How about the following patch?

--- lib/webrick/server.rb 7 Mar 2005 12:32:07 -0000 1.9
+++ lib/webrick/server.rb 20 Mar 2005 19:17:49 -0000
@@ -74,11 +78,16 @@ def listen(address, port)

     def start(&block)
       raise ServerError, "already started." if @status != :Stop
       server_type = @config[:ServerType] || SimpleServer

       server_type.start{
- @logger.info \
- "#{self.class}#start: pid=#{$$} port=#{@config[:Port]}"
+ @logger.info "#{self.class}#start: pid=#{$$}"
+ @listeners.each{|sock|
+ sockaddr = sock.addr
+ addr = sockaddr[3]
+ port = sockaddr[1]
+ @logger.info "#{self.class}#start: addr=#{addr} port=#{port}"
+ }
         call_callback(:StartCallback)

         thgroup = ThreadGroup.new

···

`Sam Roberts <sroberts@uniserve.com>' wrote:

--
gotoyuzo

Quoting eemoragas@gmail.com, on Mon, Mar 21, 2005 at 03:59:32AM +0900:

maybe you can bind to port 0 then just query the socket which port it is on?

Maybe it wasn't clear, I'm asking about how to do this with WEBrick.

I don't create the socket, WEBrick does. I can't find a WEBrick API that
gives me back the socket, so I don't have a way to call
#getsockname on it to find the port.

I also can't find a WEBrick API where I create the TCPSocket and pass it
in, that would work for me, too.

So that's my question... how do I access the underlying socket so I
can call #getsockname and find what port its listening on?

Thanks,
Sam

···

i'm speaking from a low level perspective. maybe consult the ruby docs
for socket behavior.

On Mon, 21 Mar 2005 03:50:06 +0900, Sam Roberts <sroberts@uniserve.com> wrote:
> I don't want to use a hard-coded port number, I want it to bind to
> INADDR_ANY with a port of zero. The system will select a free port.
--
http://www.eyan.org

Hi,

In message <20050320193414.GD911@ensemble.local>,

I don't create the socket, WEBrick does. I can't find a WEBrick API that
gives me back the socket, so I don't have a way to call
#getsockname on it to find the port.

I also can't find a WEBrick API where I create the TCPSocket and pass it
in, that would work for me, too.

WEBrick::GenericServer#listen and #listeners may be useful.

% ruby -r webrick -e '
s=WEBrick::HTTPServer.new(:Port=>0)
p s.listeners
s.listeners << TCPServer.new("127.0.0.1", "8080") # add a socket to listeners
s.listen("127.0.0.1", 8081) # ditto.
p s.listeners
'
[2005-03-21 04:43:58] INFO WEBrick 1.3.1
[2005-03-21 04:43:58] INFO ruby 1.9.0 (2005-03-20) [i386-netbsd]
[#<TCPServer:0x8314264>, #<TCPServer:0x83141c4>]
[#<TCPServer:0x8314264>, #<TCPServer:0x83141c4>, #<TCPServer:0x8314048>, #<TCPServer:0x8313f08>]

···

`Sam Roberts <sroberts@uniserve.com>' wrote:

--
gotoyuzo

Thank you for your suggestions.

Quoting gotoyuzo@notwork.org, on Mon, Mar 21, 2005 at 04:46:10AM +0900:

In message <20050320193414.GD911@ensemble.local>,
> I don't create the socket, WEBrick does. I can't find a WEBrick API that
> gives me back the socket, so I don't have a way to call
> #getsockname on it to find the port.

> I also can't find a WEBrick API where I create the TCPSocket and pass it
> in, that would work for me, too.

WEBrick::GenericServer#listen and #listeners may be useful.

% ruby -r webrick -e '
s=WEBrick::HTTPServer.new(:Port=>0)
p s.listeners
s.listeners << TCPServer.new("127.0.0.1", "8080") # add a socket to listeners
s.listen("127.0.0.1", 8081) # ditto.
p s.listeners
'
[2005-03-21 04:43:58] INFO WEBrick 1.3.1
[2005-03-21 04:43:58] INFO ruby 1.9.0 (2005-03-20) [i386-netbsd]
[#<TCPServer:0x8314264>, #<TCPServer:0x83141c4>]
[#<TCPServer:0x8314264>, #<TCPServer:0x83141c4>, #<TCPServer:0x8314048>, #<TCPServer:0x8313f08>]

If I set :Port=>0, every TCPServer gets a different port:

  w = WEBrick::HTTPServer.new(:Port=>0)
  w.listeners.each do |s| p Socket.unpack_sockaddr_in(s.getsockname) end
  [50101, "0.0.0.0"]
  [50102, "0.0.0.0"]

I don't want that, its unmanageable. And new listeners get added
dynamically, don't they?

Also, if I manually add a TCPServer with a specific port, I have the
same problem, new listeners will have a differerent port, I think.

Can I do something like this:

  w = WEBrick::HTTPServer.new(:Port=>0)
  s = w.listeners.first
  size = w.listeners.size - 1
  w.listeners.empty
  w.config[:Port] = s.addr[1]
  w.listeners << s
  size.times do
    w.listeners << TCPServer(w.config[:BindAddress], w.config[:Port])
  end

If I modify the config on the fly, will it be respected in the future,
all TCPServer sockets be on the same port after this?

Could I request that if :Port is zero (or perhaps :auto) that WEBrick do
this for me? It could create one server socket, determine the port,
reset :Port => the auto port, and then use it for new sockets.

This would allow me to do:

  w=WEBrick::HTTPServer.new(:Port=>:auto)

  s = DNSSD.register('my server', '_http._tcp', w.config[:Port])

What do you think?

Is there really a case when you would want every listener to have a
different port # assigned by the network stack? If not, maybe setting
:Port to 0 should have the above behaviour.

Quoting gotoyuzo@notwork.org, on Mon, Mar 21, 2005 at 04:36:12AM +0900:

Hi,

In message <20050320184914.GB911@ensemble.local>,
> INADDR_ANY with a port of zero. The system will select a free port.
>
> Then, I can advertise that port using mDNS/DNS-SD.
>
> Is this possible? I can maybe set :Port to 0, but I am searching the
> webrick src and can't see a way to get the underlying socket up, so I
> can ask it it's port.

Setting :Port to 0 works like you want. However WEBrick

Not exactly... :slight_smile:

never report it. I think the log messages should be changed.
How about the following patch?

Reporting it as a log message is a good idea, I like it.

For me, it doesn't really help because I need to know the port
programmatically, and I don't want to parse the log messages!

···

`Sam Roberts <sroberts@uniserve.com>' wrote:
`Sam Roberts <sroberts@uniserve.com>' wrote:

--- lib/webrick/server.rb 7 Mar 2005 12:32:07 -0000 1.9
+++ lib/webrick/server.rb 20 Mar 2005 19:17:49 -0000
@@ -74,11 +78,16 @@ def listen(address, port)

     def start(&block)
       raise ServerError, "already started." if @status != :Stop
       server_type = @config[:ServerType] || SimpleServer

       server_type.start{
- @logger.info \
- "#{self.class}#start: pid=#{$$} port=#{@config[:Port]}"
+ @logger.info "#{self.class}#start: pid=#{$$}"
+ @listeners.each{|sock|
+ sockaddr = sock.addr
+ addr = sockaddr[3]
+ port = sockaddr[1]
+ @logger.info "#{self.class}#start: addr=#{addr} port=#{port}"
+ }
         call_callback(:StartCallback)

         thgroup = ThreadGroup.new

--
gotoyuzo

In message <20050320203133.GA1012@ensemble.local>,

Is there really a case when you would want every listener to have a
different port # assigned by the network stack? If not, maybe setting
:Port to 0 should have the above behaviour.

I prefer nil than 0 for this behavior. It the specified port
is nil, listening port will be chosen and tried to bind until
succeed like this:

module WEBrick
  class GenericServer
    def listen(addr, port)
      return auto_listen(addr) unless port
      @listeners += Utils.create_listeners(addr, port, @logger)
    end

    def auto_listen(addr)
      port = 65535
      while port > 1023
        begin
          listen(addr, port)
        rescue Errno::EACCES, Errno::EADDRINUSE
          port -= 1
          retry
        end
        @config[:Port] = port
        return
      end
    end
  end
end

If nobody opposes, I can make it as a default behavior of
WEBrick.

···

`Sam Roberts <sroberts@uniserve.com>' wrote:

--
gotoyuzo

Here's what I'm doing to create a webserver on a system-allocated port.

I think the code should work for dual IPv4/v6 machines, it's based on
how webrick does it.

Full code below.

Cheers,
Sam

···

----------------------

#!/usr/local/bin/ruby18 -w
# Author: Sam Roberts <sroberts@uniserve.com>
# Licence: this file is placed in the public domain
#
# Advertise a webrick server over mDNS.

require 'webrick'
require 'net/dns/mdns-sd'

DNSSD = Net::DNS::MDNSSD

class HelloServlet < WEBrick::HTTPServlet::AbstractServlet
  def do_GET(req, resp)
    resp.body = "hello, world\n"
    resp['content-type'] = 'text/plain'
    raise WEBrick::HTTPStatus::OK
  end
end

# This may seem convoluted... but if there are multiple address families
# available, like AF_INET6 and AF_INET, this should create multiple TCPServer
# sockets for them.
families = Socket.getaddrinfo(nil, 1, Socket::AF_UNSPEC, Socket::SOCK_STREAM, 0, Socket::AI_PASSIVE)

listeners = []
port = 0

families.each do |af, one, dns, addr|
  p port, addr
  listeners << TCPServer.new(addr, port)
  port = listeners.first.addr[1] unless port != 0
end

listeners.each do |s|
  puts "listen on #{s.addr.inspect}"
end

# This will dynamically allocate multiple TCPServers, each on a different port.
server = WEBrick::HTTPServer.new( :Port => 0 )

# So we replace them with our TCPServer sockets which are all on the same
# (dynamically assigned) port.
server.listeners.each do |s| s.close end
server.listeners.replace listeners
server.config[:Port] = port

server.mount( '/hello/', HelloServlet )

handle = DNSSD.register("hello", '_http._tcp', 'local', port, 'path' => '/hello/')

['INT', 'TERM'].each { |signal|
  trap(signal) { server.shutdown; handle.stop; }
}

server.start

Quoting gotoyuzo@notwork.org, on Mon, Mar 21, 2005 at 05:11:24PM +0900:

In message <20050320203133.GA1012@ensemble.local>,
> Is there really a case when you would want every listener to have a
> different port # assigned by the network stack? If not, maybe setting
> :Port to 0 should have the above behaviour.

I prefer nil than 0 for this behavior. It the specified port

I can understand not liking 0, I think a :Port of zero would best be
considered a bug, and raise an error.

is nil, listening port will be chosen and tried to bind until
succeed like this:

I'm not sure about nil.

It is very sensitive to error, it is very common to pass nil when you
failed to initialize something correctly, and now it will silently do
something unexpected, and the caller will waste time trying to figure
out why every port is different, and having to ask on ruby-talk, where
you will tell him he probably passed nil... and everybody will waste
time.

Personally, I would prefer :auto. You don't accidently pass a symbol in
as a port number, doing so is a pretty clear indication to WEBrick, and
to readers of the code that the port is special.

module WEBrick
  class GenericServer
    def listen(addr, port)
      return auto_listen(addr) unless port
      @listeners += Utils.create_listeners(addr, port, @logger)
    end

I don't understand the following code at all. Why are you choosing the
port number, when the network stack will do it for you much more
portably and efficiently?

And don't some net stacks not allow port past the 16K range?

···

`Sam Roberts <sroberts@uniserve.com>' wrote:

    def auto_listen(addr)
      port = 65535
      while port > 1023
        begin
          listen(addr, port)
        rescue Errno::EACCES, Errno::EADDRINUSE
          port -= 1
          retry
        end
        @config[:Port] = port
        return
      end
    end
  end
end

If nobody opposes, I can make it as a default behavior of
WEBrick.

--
gotoyuzo

In message <20050321143558.GA569@ensemble.local>,

It is very sensitive to error, it is very common to pass nil when you
failed to initialize something correctly, and now it will silently do
something unexpected, and the caller will waste time trying to figure
out why every port is different, and having to ask on ruby-talk, where
you will tell him he probably passed nil... and everybody will waste
time.

Hmm, ok. I understand what you mean. I rewrote a patch again
using port 0. (please forget my last post;-)

Personally, I would prefer :auto. You don't accidently pass a symbol in
as a port number, doing so is a pretty clear indication to WEBrick, and
to readers of the code that the port is special.

:auto seems that it could be used for other parameters.
I hesitate to introduce if it isn't useful for elsewhere.
How do you think?

···

`Sam Roberts <sroberts@uniserve.com>' wrote:

--
gotoyuzo

--- lib/webrick/server.rb 7 Mar 2005 12:32:07 -0000 1.9
+++ lib/webrick/server.rb 22 Mar 2005 09:34:10 -0000
@@ -61,6 +61,9 @@ def initialize(config={}, default=Co
           warn(":Listen option is deprecated; use GenericServer#listen")
         end
         listen(@config[:BindAddress], @config[:Port])
+ if @config[:Port] == 0
+ @config[:Port] = @listeners[0].addr[1]
+ end
       end
     end

--- lib/webrick/utils.rb 28 Sep 2003 17:50:52 -0000 1.3
+++ lib/webrick/utils.rb 22 Mar 2005 09:34:10 -0000
@@ -58,8 +58,9 @@ def create_listeners(address, port,
       sockets =
       res.each{|ai|
         begin
- logger.debug("TCPServer.new(#{ai[3]}, #{ai[1]})") if logger
- sock = TCPServer.new(ai[3], ai[1])
+ logger.debug("TCPServer.new(#{ai[3]}, #{port})") if logger
+ sock = TCPServer.new(ai[3], port)
+ port = sock.addr[1] if port == 0
           Utils::set_close_on_exec(sock)
           sockets << sock
         rescue => ex

Quoting gotoyuzo@notwork.org, on Tue, Mar 22, 2005 at 06:45:13PM +0900:

In message <20050321143558.GA569@ensemble.local>,
> It is very sensitive to error, it is very common to pass nil when you
> failed to initialize something correctly, and now it will silently do
> something unexpected, and the caller will waste time trying to figure
> out why every port is different, and having to ask on ruby-talk, where
> you will tell him he probably passed nil... and everybody will waste
> time.

Hmm, ok. I understand what you mean. I rewrote a patch again
using port 0. (please forget my last post;-)

I think that you didn't notice that if nil is passed, it also causes a
port to be chosen, just like 0:

  TCPServer.new('localhost', nil).addr
  => ["AF_INET6", 49505, "localhost", "::1"]

So you need to look for that, too, I modified the patch, below.

> Personally, I would prefer :auto. You don't accidently pass a symbol in
> as a port number, doing so is a pretty clear indication to WEBrick, and
> to readers of the code that the port is special.

:auto seems that it could be used for other parameters.
I hesitate to introduce if it isn't useful for elsewhere.
How do you think?

I wasn't thinking of a new parameter, but of a new value for the :Port
parameter, current this is true:

    :Port => 80 # http
    :Port => 'www' # http
    :Port => 0 # dynamic
    :Port => nil # dynamic

I would prefer to see:

    :Port => 80 # http
    :Port => 'www' # http
    :Port => :auto # dynamic
    :Port => 0 # raise ArgumentError
    :Port => nil # raise ArgumentError

Its just a small suggestion. I worry that people will do this:

    :Port => ARGV[1]

which may result in:

    :Port => nil

and nil will be treated as 0, and it might surprise people.

If you apply your patch to report each TCPServer that is made and the
port that it is assigned, it will be easier to debug, though.

So, that is how I think.

Thanks for the help.

Sam

···

`Sam Roberts <sroberts@uniserve.com>' wrote:

--
gotoyuzo

--- lib/webrick/server.rb 7 Mar 2005 12:32:07 -0000 1.9
+++ lib/webrick/server.rb 22 Mar 2005 09:34:10 -0000
@@ -61,6 +61,9 @@ def initialize(config={}, default=Co
           warn(":Listen option is deprecated; use GenericServer#listen")
         end
         listen(@config[:BindAddress], @config[:Port])
+ if @config[:Port] == 0

                                   >> @config[:Port] == nil

+ @config[:Port] = @listeners[0].addr[1]
+ end

       end
     end

--- lib/webrick/utils.rb 28 Sep 2003 17:50:52 -0000 1.3
+++ lib/webrick/utils.rb 22 Mar 2005 09:34:10 -0000
@@ -58,8 +58,9 @@ def create_listeners(address, port,
       sockets =
       res.each{|ai|
         begin
- logger.debug("TCPServer.new(#{ai[3]}, #{ai[1]})") if logger
- sock = TCPServer.new(ai[3], ai[1])
+ logger.debug("TCPServer.new(#{ai[3]}, #{port})") if logger
+ sock = TCPServer.new(ai[3], port)
+ port = sock.addr[1] if port == 0

                                               >> port == nil

           Utils::set_close_on_exec(sock)
           sockets << sock
         rescue => ex

In message <20050322153421.GA661@ensemble.local>,

I would prefer to see:

    :Port => 80 # http
    :Port => 'www' # http
    :Port => :auto # dynamic
    :Port => 0 # raise ArgumentError
    :Port => nil # raise ArgumentError

I applied the changes. It works as:

    :Port => 80 # http
    :Port => 'www' # http
    :Port => 0 # dynamic
    :Port => nil # raise ArgumentError

I understand :auto clearly means its purpose, however I'd
like to use it when the concept about other symbols or other
parameters is determined. sorry.

If you apply your patch to report each TCPServer that is made and the
port that it is assigned, it will be easier to debug, though.

For now, aServer.start reports the assinged port number.
I think it could let programmers notice what happened.

···

`Sam Roberts <sroberts@uniserve.com>' wrote:

--
gotoyuzo