Proxying web servers in the Workbench Extension
Proxying web servers
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 VS Code 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 therun
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 withpip install python-dotenv
Environment variables
Workbench proxies dash servers by managing the PORT
. However, Workbench manages the environmental variables so additional updates to the environment variables in .env
files is not needed. Users should avoid setting these variables in their own code.
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 VS Code’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 VS Code 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:
Access the server by clicking on it as usual, but all features may not work as expected.
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
= FastAPI()
app
if __name__ == '__main__':
= 8050) uvicorn.run(app, port
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
= FastAPI()
app
if __name__ == '__main__':
= '', 8050
path, port
if 'RS_SERVER_URL' in os.environ and os.environ['RS_SERVER_URL']:
= subprocess.run(f'echo $(/usr/lib/rstudio-server/bin/rserver-url -l {port})',
path =subprocess.PIPE, shell=True).stdout.decode().strip()
stdout= port, root_path = path) uvicorn.run(app, port
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 VS Code 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:
= '<default-path>', 8050 path, port
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
= Flask(__name__)
app if 'RS_SERVER_URL' in os.environ and os.environ['RS_SERVER_URL']:
from werkzeug.middleware.proxy_fix import ProxyFix
= ProxyFix(app.wsgi_app, x_prefix=1) app.wsgi_app
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.