Proxying Web Servers

Preview

Positron Run App

Positron provides a simplified method for running interactive apps within the IDE. We recommend using Positron’s Run App feature instead of running an app from the Terminal yourself. See Positron’s Run interactive apps documentation for more information on how to use the feature.

Before running your app in a Positron Pro session, please make the applicable framework-specific modifications to your application to ensure that the app can be proxied appropriately. The Workbench Extension supports most of the proxied servers created by the Run App feature.

Dash

The Positron Run App feature manages path-related Dash environment variables on your behalf. Therefore, avoid setting DASH_URL_BASE_PATHNAME, DASH_REQUESTS_PREFIX_PATHNAME, DASH_ROUTES_PATHNAME_PREFIX, and other Dash environment variables which modify the application path.

Dash applications started from the Positron Run App feature are not supported in the Proxied Servers view in the Workbench Extension. To utilize the Proxied Servers view, run the Dash application from the Terminal. See the Running apps in the Terminal section below for more information.

If you’ve previously opened your project directory in a VS Code session in Workbench, delete the generated .env file or remove the PORT and DASH_REQUESTS_PATHNAME_PREFIX variables from your .env file to avoid conflicts with Positron’s Run App feature.

FastAPI

When using the Uvicorn ASGI server, Workbench sets the environment variable UVICORN_ROOT_PATH to the necessary value for the default FastAPI port 8000 in Positron Pro sessions. Therefore, avoid setting the UVICORN_ROOT_PATH environment variable.

Positron displays the interactive API documentation in the Viewer.

Flask

Since Workbench runs an Nginx HTTP proxy server, Flask applications must include additional logic to tell Flask it is running behind a proxy. Flask recommends using the Werkzeug middleware to do this. From Werkzeug:

Middlewares wrap applications to dispatch between them or provide additional request handling

This module provides a middleware that adjusts the WSGI environ based on X-Forwarded- headers that proxies in front of an application may set. When an application is running behind a proxy server, WSGI may see the request as coming from that server rather than the real client. Proxies set various headers to track where the request actually came from. This middleware should only be used if the application is actually behind such a proxy, and should be configured with the number of proxies that are chained in front of it. Not all proxies set all the headers. Since incoming headers can be faked, you must set how many proxies are setting each header so the middleware knows what to trust.

The Workbench Extension provides the following Posit Workbench Flask Interface code snippet so that users can quickly access this middleware:

from flask import Flask
import os
app = Flask(__name__)
if 'RS_SERVER_URL' in os.environ and os.environ['RS_SERVER_URL']:
  from werkzeug.middleware.proxy_fix import ProxyFix
  app.wsgi_app = ProxyFix(app.wsgi_app, x_prefix=1)

Trigger this snippet by typing from flask import within a python file.

The line if 'RS_SERVER_URL' in os.environ and os.environ['RS_SERVER_URL']: tests if the RS_SERVER_URL environment variable is set before implementing the middleware fix and ensures that this middleware only runs when running the application from within Workbench.

Running apps in the Terminal

We recommend using Positron’s Run App feature to run your app because it simplifies the application proxying process. See Positron Run App for more information.

The Posit Workbench Extension includes a Proxied Servers view when selecting the extension from the Activity Bar. This view contains a list of currently running web servers. Each item in this list includes the web server’s name and the port the process is running on. Selecting an item opens the server in a new browser tab.

The extension determines which servers to display by searching through your currently running processes for those with open and listening sockets. The extension excludes processes it expects and non-development servers such as R and Jupyter sessions. For R and Python processes, the extension represents the server’s name as the directory name.

We have ensured the following server types can be proxied when using the extension in Positron Pro sessions (other application types may work as expected):

Dash

Requirements

  • Minimum Dash version of 1.10.0 (earlier versions require the user to manually set the port variable in the run call)
  • The server must be run with the debug argument set to True e.g., app.run(debug=True)
  • The python-dotenv package – can be installed with pip install python-dotenv

Environment variables

The following environment variables must be exported in the Terminal or set in a .env file before running the Dash app:

  • PORT
  • DASH_REQUESTS_PREFIX_PATHNAME

In the Terminal, run the following commands to get the proxied server pathname and to set the environment variables:

Terminal
# Export the port number, such as 8050
export PORT=8050

# Use the rserver-url binary to get the proxied server address
/usr/lib/rstudio-server/bin/rserver-url -l $PORT
# Example output for /usr/lib/rstudio-server/bin/rserver-url -l $PORT:
# https://workbench-server/s/0976a6ae9dc1b15c17c63/p/4624f968/

# Export the DASH_REQUESTS_PREFIX_PATHNAME variable with the path part
# of the output from the rserver-url command, such as
# /s/0976a6ae9dc1b15c17c63/p/4624f968/
export DASH_REQUESTS_PREFIX_PATHNAME=/s/0976a6ae9dc1b15c17c63/p/4624f968/

Alternatively, set the environment variables in a .env file in the root of your project directory. For example:

.env
PORT=8050
DASH_REQUESTS_PREFIX_PATHNAME=/s/0976a6ae9dc1b15c17c63/p/4624f968/

Then, proceed to run your Dash app in the Terminal.

Dash doesn’t always respect the port provided by python-dotenv, even when it is properly reading other variables from the file, such as DASH_REQUESTS_PREFIX_PATHNAME. Posit is looking into a long-term fix for this issue, but there are manual steps that can be taken to force Dash to use the provided port.

One way to force Dash to use the provided port is to run your code using the Python extension and Positron’s debugger. Alternatively, update your main application file to load the python-dotenv file before importing Dash, as in the following:

from dotenv import load_dotenv
load_dotenv()
import dash

Manually setting a port number

To force Workbench to use a specific port, set the desired port in the run call or add a comment to the top of the main app file containing the port number in the following format:

# .run(port = 'your_port_num_here').

FastAPI

Since Workbench sessions run remotely behind a proxy, FastAPI applications on Workbench must use the ASGI root_path mechanism. For more information, see FastAPI’s Behind a Proxy documentation.

The following instructions describe how to set the appropriate application port and root path for an app run with the Uvicorn ASGI server. Uvicorn is the default Asynchronous Server Gateway Interface for FastAPI applications.

Using the default port

When using the default FastAPI port, 8000, no additional configuration is required by the user. This is because Workbench sets the environment variable UVICORN_ROOT_PATH to the necessary value for port 8000 in all Positron Pro sessions.

However, we recommend specifying the port and root_path directly instead of falling back on the default port.

Using a specific port and root_path

To specify a port, use one of the following methods:

  • Start Uvicorn in the Terminal and pass the desired port and root path as command line arguments
  • Import the Uvicorn module into your Python code and pass a port to the uvicorn.run function

The examples in this section use port 8050. This value should be replaced with your desired port.

To determine the root path, use the rserver-url binary as shown in the following examples. See URL Prefixes page for more information.

Specify a port and root path in the Terminal

When specifying a port that is not 8000, such as port 8050, users must pass the root-path argument to Uvicorn. Use the rserver-url binary with the -l flag to set the root_path variable. This looks like the following:

uvicorn main:app --port=8050 --root-path=$(/usr/lib/rstudio-server/bin/rserver-url -l 8050)

If this root path is not specified and a port has been passed to Uvicorn, the server displays in the Proxied Servers view with a warning:

Screenshot of the Posit Workbench Proxied Servers view with an alert next to a Uvicorn server running on port 8050

Access the server by clicking on it as usual, but all features may not work as expected.

Note

When accessing a running FastAPI application, it typically displays a JSON response by default. To get to the interactive API documentation, add /docs or /redoc to the end of the URL. For more information, see the FastAPI: User Guide.

Import the Uvicorn module

Users can programmatically specify a port to run their application on by passing the port number to the Uvicorn constructor. Typically, this would look like the following code:

from fastapi import FastAPI
import uvicorn

app = FastAPI()

if __name__ == '__main__':
    uvicorn.run(app, port = 8050)

When running in Workbench, users should call the rserver-url binary with the -l flag to set the root_path variable. Posit Workbench provides the Posit Workbench FastAPI Uvicorn Root Path code snippet for users to easily retrieve this required code. This snippet can be retrieved by typing from fastapi import FastAPI:

from fastapi import FastAPI
import uvicorn

app = FastAPI()

if __name__ == '__main__':
    path, port = '', 8050
  
    if 'RS_SERVER_URL' in os.environ and os.environ['RS_SERVER_URL']:
        path = subprocess.run(f'echo $(/usr/lib/rstudio-server/bin/rserver-url -l {port})',
                             stdout=subprocess.PIPE, shell=True).stdout.decode().strip()
    uvicorn.run(app, port = port, root_path = path)

The path variable is set to the URL prefix for the current Workbench session and the requested port. It will look like the URL that your Positron Pro session is running at but with an additional /p/<port-id> suffix. Because this value will vary per session, it should not be hard-coded. Always use rserver-url to generate the value.

The path variable is only set when the environment variable RS_SERVER_URL is set. This ensures that the path variable is not set when this code is run outside of Workbench. If you want to configure path to a different value when run outside of Workbench, replace the empty string that path is initialized to with your desired path:

    path, port = '<default-path>', 8050

Flask

Since Workbench runs an Nginx HTTP proxy server, Flask applications must include additional logic to tell Flask it is running behind a proxy. Flask recommends using the Werkzeug middleware to do this. From Werkzeug:

Middlewares wrap applications to dispatch between them or provide additional request handling

This module provides a middleware that adjusts the WSGI environ based on X-Forwarded- headers that proxies in front of an application may set. When an application is running behind a proxy server, WSGI may see the request as coming from that server rather than the real client. Proxies set various headers to track where the request actually came from. This middleware should only be used if the application is actually behind such a proxy, and should be configured with the number of proxies that are chained in front of it. Not all proxies set all the headers. Since incoming headers can be faked, you must set how many proxies are setting each header so the middleware knows what to trust.

The Workbench Extension provides the following Posit Workbench Flask Interface code snippet so that users can quickly access this middleware:

from flask import Flask
import os
app = Flask(__name__)
if 'RS_SERVER_URL' in os.environ and os.environ['RS_SERVER_URL']:
  from werkzeug.middleware.proxy_fix import ProxyFix
  app.wsgi_app = ProxyFix(app.wsgi_app, x_prefix=1)

Trigger this snippet by typing from flask import within a python file.

The line if 'RS_SERVER_URL' in os.environ and os.environ['RS_SERVER_URL']: tests if the RS_SERVER_URL environment variable is set before implementing the middleware fix and ensures that this middleware only runs when running the application from within Workbench.

Back to top