2

I'm on Windows 7. When I start a Bottle web server with:

run('0.0.0.0', port=80) 

And then once again run the same Python script, it doesn't fail with a Port already in use error (this should be normal behaviour), but instead successfully starts the Python script again!

Question: How to stop this behaviour, in a simple way?

This is related to Multiple processes listening on the same port?, but how can you prevent this in a Python context?

chuckx
  • 6,484
  • 1
  • 22
  • 23
Basj
  • 41,386
  • 99
  • 383
  • 673

2 Answers2

3

This is a Windows specific behavior that requires the use of the SO_EXCLUSIVEADDRUSE option before binding a network socket.

From the Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE article in the Windows Socket 2 documentation:

Before the SO_EXCLUSIVEADDRUSE socket option was introduced, there was very little a network application developer could do to prevent a malicious program from binding to the port on which the network application had its own sockets bound. In order to address this security issue, Windows Sockets introduced the SO_EXCLUSIVEADDRUSE socket option, which became available on Windows NT 4.0 with Service Pack 4 (SP4) and later.

...

The SO_EXCLUSIVEADDRUSE option is set by calling the setsockopt function with the optname parameter set to SO_EXCLUSIVEADDRUSE and the optval parameter set to a boolean value of TRUE before the socket is bound.


In order to do this using the Bottle module, you have to create a custom backend facilitating access to the socket before it's bound. This gives an opportunity to set the required socket option as documented.

This is briefly described in the Bottle Deployment documentation:

If there is no adapter for your favorite server or if you need more control over the server setup, you may want to start the server manually.


Here's a modified version of the Bottle Hello World example that demonstrates this:

import socket
from wsgiref.simple_server import WSGIServer
from bottle import route, run, template

@route('/hello/<name>')
def index(name):
  return template('<b>Hello {{name}}</b>!', name=name)

class CustomSocketServer(WSGIServer):
  def server_bind(self):
    # This tests if the socket option exists (i.e. only on Windows), then
    # sets it.
    if hasattr(socket, 'SO_EXCLUSIVEADDRUSE'):
      self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1)

    # Everything below this point is a concatenation of the server_bind
    # implementations pulled from each class in the class hierarchy.
    # wsgiref.WSGIServer -> http.HTTPServer -> socketserver.TCPServer

    elif self.allow_reuse_address:
      self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    self.socket.bind(self.server_address)
    self.server_address = self.socket.getsockname()
    host, port = self.server_address[:2]
    self.server_name = socket.getfqdn(host)
    self.server_port = port
    self.setup_environ()

print "Serving..."
run(host='localhost', port=8080, server_class=CustomSocketServer)   

Note that the code copied is required to maintain the expected behavior by the super classes.

All of the super class implementations of server_bind() start by calling their parent classes server_bind(). This means that calling any of them results in the immediate binding of the socket, removing the opportunity to set the required socket option.


I tested this on Windows 10 using Python 2.7.

First instance:

PS C:\Users\chuckx\bottle-test> C:\Python27\python.exe test.py
Serving...

Second instance:

PS C:\Users\chuckx\bottle-test> C:\Python27\python.exe test.py
Traceback (most recent call last):
  File "test.py", line 32, in <module>
    server_class=CustomSocketServer)
  File "C:\Python27\lib\wsgiref\simple_server.py", line 151, in make_server
    server = server_class((host, port), handler_class)
  File "C:\Python27\lib\SocketServer.py", line 417, in __init__
    self.server_bind()
  File "test.py", line 19, in server_bind
    self.socket.bind(self.server_address)
  File "C:\Python27\lib\socket.py", line 228, in meth
    return getattr(self._sock,name)(*args)
socket.error: [Errno 10048] Only one usage of each socket address (protocol/network address/port) is normally permitted
chuckx
  • 6,484
  • 1
  • 22
  • 23
0

An alternative solution is to use @LuisMuñoz's comment: check if the port is already opened before opening it again:

# Bottle web server code here
# ...

import socket
sock = socket.socket()
sock.settimeout(0.2)  # this prevents a 2 second lag when starting the server
if sock.connect_ex(('127.0.0.1', 80)) == 0:
    print "Sorry, port already in use."
    exit()

run(host='0.0.0.0', port=80)  
Basj
  • 41,386
  • 99
  • 383
  • 673