How to get the current port number in Flask?

泪湿孤枕 提交于 2019-12-02 19:20:58

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.

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)

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.

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

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!