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

S

Sam Roberts

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
 
E

Edwin Eyan Moragas

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.
 
S

Sam Roberts

Quoting (e-mail address removed), 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
 
G

GOTOU Yuuzou

Hi,

In message said:
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
 
G

GOTOU Yuuzou

Hi,

In message said:
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>]
 
S

Sam Roberts

Thank you for your suggestions.

Quoting (e-mail address removed), on Mon, Mar 21, 2005 at 04:46:10AM +0900:
In message said:
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 (e-mail address removed), on Mon, Mar 21, 2005 at 04:36:12AM +0900:
Hi,



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

Not exactly... :)
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!
--- 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
 
G

GOTOU Yuuzou

In message said:
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.
 
S

Sam Roberts

Quoting (e-mail address removed), on Mon, Mar 21, 2005 at 05:11:24PM +0900:
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?
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.
 
S

Sam Roberts

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 <[email protected]>
# 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
 
G

GOTOU Yuuzou

In message said:
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?

--
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
 
S

Sam Roberts

Quoting (e-mail address removed), on Tue, Mar 22, 2005 at 06:45:13PM +0900:
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.
: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

--
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
 
G

GOTOU Yuuzou

In message said:
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.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,768
Messages
2,569,575
Members
45,053
Latest member
billing-software

Latest Threads

Top