How to get the current port number in Flask?

32,285

Solution 1

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.

Solution 2

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.

Solution 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)
Share:
32,285

Related videos on Youtube

david4dev
Author by

david4dev

I am an Ubuntu user (since Jaunty) and also a programmer (by hobby - no formal education). I found the community on Ubuntu Forums extremely helpful and so started to return the favour and never stopped :) I am currently involved with the development of the following FOSS projects: DMedia - a distributed media library, to be used by the Novacut video editor. (IRC: #novacut) LaVida - a simulation game for Linux. (IRC: #lavida) Both of these projects actively welcome new contributors so feel free to help out. If you are interested but don't know where to start feel free to contact me.

Updated on July 09, 2022

Comments

  • david4dev
    david4dev almost 2 years

    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.

    • Manish Kumar
      Manish Kumar over 7 years
      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 port
  • david4dev
    david4dev about 13 years
    I feel there must be a better way than this.
  • Michele d'Amico
    Michele d'Amico over 9 years
    Is not so simple.... Flask doesn't store the HTTPServer instance but just create it and then call serve_forever()
  • Fruch
    Fruch over 7 years
    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 function
  • Michele d'Amico
    Michele d'Amico over 7 years
    @Fruch 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.