How to select random port number in Flask?
Asked Answered
A

5

27

Using Flask, how can I get the current port number that flask is connected to? I want to start a server on a random port using port 0 but I also need to know which port I am on.

Edit

I think I've found a work around for my issue, although it isn't an answer to the question. I can iterate through ports starting with 49152 and attempt to use that port through app.run(port=PORT). I can do this in a try catch block so that if I get an Address already in use error, I can try the next port.

Aviatrix answered 23/2, 2011 at 0:14 Comment(1)
while raúnning a sample Flask program I am getting this error:.....File "/usr/lib/python2.7/socket.py", line 228, in meth return getattr(self._sock,name)(*args) socket.error: [Errno 13] Permission denied please help me how i can change my portJewel
H
46

You can't easily get at the server socket used by Flask, as it's hidden in the internals of the standard library (Flask uses Werkzeug, whose development server is based on the stdlib's BaseHTTPServer).

However, you can create an ephemeral port yourself and then close the socket that creates it, then use that port yourself. For example:

# hello.py
from flask import Flask, request
import socket

app = Flask(__name__)

@app.route('/')
def hello():
    return 'Hello, world! running on %s' % request.host

if __name__ == '__main__':
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(('localhost', 0))
    port = sock.getsockname()[1]
    sock.close()
    app.run(port=port)

will give you the port number to use. An example run:

$ python hello.py 
* Running on http://127.0.0.1:34447/

and, on browsing to http://localhost:34447/, I see

Hello, world! running on localhost:34447

in my browser.

Of course, if something else uses that port between you closing the socket and then Flask opening the socket with that port, you'd get an "Address in use" error, but you may be able to use this technique in your environment.

Halstead answered 23/2, 2011 at 10:59 Comment(1)
Certainly: there is a race condition in this code.Palaearctic
C
3

As pointed by @VinaySajip Flask use standard server socket but it never assign the instance to any variable, just construct it and call serve_forever() in line.

Anyway instead try to extract socket from flask's app as said @ThiefMaster, we can intercept bind_socket() call and read the address without take care of concurrent socket creation. Here is my solution:

from flask import Flask, request
import socketserver

app = Flask("")

original_socket_bind = SocketServer.TCPServer.server_bind
def socket_bind_wrapper(self):
    ret = original_socket_bind(self)
    print("Socket running at {}:{}".format(*self.socket.getsockname()))
    # Recover original implementation
    socketserver.TCPServer.server_bind = original_socket_bind
    return ret

@app.route("/")
def hello():
    return 'Hello, world! running on {}'.format(request.host)

socketserver.TCPServer.server_bind = socket_bind_wrapper   #Hook the wrapper
app.run(port=0, debug=True)
Campobello answered 22/12, 2014 at 8:18 Comment(2)
this is a good approach, but it has one downfall. when using more code that uses TCPServer your call will be called also for that, and you will get multiple calls to your functionIchang
@Ichang you are right, this is just a proof of concept. Maybe is better to use mock.patch in a with context manager to do it in a safe and clean way.Vexillum
B
3

I agree with the accepted answer that

You can't easily get at the server socket used by Flask, as it's hidden in the internals of the standard library (Flask uses Werkzeug, whose development server is based on the stdlib's BaseHTTPServer).

However, I just discovered that Werkzeug exposes an option to pass a file descriptor for use as it's socket when running werkzeug.serving.BaseWSGIServer (which is what flask's run() function ends up doing). Using this functionality it is possible to select a random available port and then tell Werkzeug to use it.

Here is the startup of my application:

import logging
import pprint
import sys
from flask import Flask

applog = logging.getLogger(__name__)
applog.setLevel(logging.INFO)
app = Flask(__name__)

if __name__ == '__main__':

    app.config.from_object('myapp.default_settings')
    try:
        app.config.from_envvar('MYAPP_SETTINGS')
    except RuntimeError:
        applog.warning("MYAPP_SETTINGS environment variable not set or invalid. Using default settings.")

    # SERVER_NAME should be in the form of localhost:5000, 127.0.0.1:0, etc...
    # So, split on : and take the second element as the port
    # If the port is 0, we need to pick a random port and then tell the server to use that socket
    if app.config['SERVER_NAME'] and int(app.config['SERVER_NAME'].split(':')[1]) == 0:
        import socket, os

        # Chose a random available port by binding to port 0
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.bind((app.config['SERVER_NAME'].split(':')[0], 0))
        sock.listen()

        # Tells the underlying WERKZEUG server to use the socket we just created
        os.environ['WERKZEUG_SERVER_FD'] = str(sock.fileno())

        # Update the configuration so it matches with the port we just chose
        # (sock.getsockname will return the actual port being used, not 0)
        app.config['SERVER_NAME'] = '%s:%d' % (sock.getsockname())

    # Optionally write the current port to a file on the filesystem
    if app.config['CREATE_PORT_FILE']:
        with open(app.config['PORT_FILE'], 'w') as port_file:
            port_file.write(app.config['SERVER_NAME'].split(':')[1])

    applog.info("Running app on %s" % (app.config['SERVER_NAME']))
    applog.info("Active Settings:%s" % pprint.pformat(app.config, compact=True))
    app.run()

Contents of myapp.default_settings:

SERVER_NAME = '127.0.0.1:0'
PORT_FILE = 'current_port'
CREATE_PORT_FILE = True

The important bit here is setting os.environ['WERKZEUG_SERVER_FD']. Werkzeug looks at this environment variable during the run_simple function and, if it is defined, passes it as the fd parameter to the make_server function. This eventually is used as the socket for communication.

While I can't guarantee the stability of this approach (I don't know how well supported the WERKZEUG_SERVER_FD environment variable is) I prefer it to the suggested solutions so far because:

  1. I don't have to loop through a range of ports catching exceptions, I just get the first available port straight from the OS.
  2. There is no chance my chosen random port gets taken between the time I bind a random port and when I run the application because the port I am binding is the port my application ends up using.

The code above also logs the port being used and optionally writes the current port being used to a file specified by a configuration option.

Beadroll answered 24/8, 2018 at 21:53 Comment(2)
Doesn't work in 2023. "Port 51893 is in use by another program. Either identify and stop that program, or start the server with a different port." Probably WERKZEUG_SERVER_FD is not supported anymore.Indelible
Ok, adding os.environ['WERKZEUG_RUN_MAIN'] = 'true' to the list of shitty env vars solves the problem, lol.Indelible
E
0

Most operating systems these days have a command called netstat that will give you a list of all ports that are currently in use, as well as which applications are running on them if you ask nicely enough. :)

It should be pretty easy to parse with os.popen or the subprocess module.

Aside from that, you could just track which ports you're using as you start each server...

Also, if you happen to be doing this from inside an http request, you could look at the SERVER_PORT cgi variable from the wsgi environment.

Elevate answered 23/2, 2011 at 3:21 Comment(1)
I feel there must be a better way than this.Aviatrix
K
0

Binding to port 0 is correct. That will make the operating system choose an available port between 1024 and 65535 for you.

To retrieve the chosen port after binding, use your_socket.getsockname()[1].

So all you need to find out is how to access the listening socket Flask uses.

socket.getsockname() docs: http://docs.python.org/library/socket.html#socket.socket.getsockname

Kahler answered 23/2, 2011 at 8:8 Comment(1)
Is not so simple.... Flask doesn't store the HTTPServer instance but just create it and then call serve_forever()Vexillum

© 2022 - 2024 — McMap. All rights reserved.