Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate Python web apps with Workbench #4769

Closed
Tracked by #1617
seeM opened this issue Sep 20, 2024 · 8 comments
Closed
Tracked by #1617

Integrate Python web apps with Workbench #4769

seeM opened this issue Sep 20, 2024 · 8 comments
Assignees
Labels
area: proxy Issues related to Positron Proxy category. area: run app area: workbench Issues related to Workbench category. enhancement New feature or request theme: app builder

Comments

@seeM
Copy link
Contributor

seeM commented Sep 20, 2024

Following on from #4662, we should update runApplication to detect if we're in Workbench, and if so, find a free port and corresponding proxied URL (possibly related to #4274) and pass them both to getTerminalOptions. Unfortunately, some application frameworks need the proxied URL to be configured as the "URL prefix"/"root path".

@seeM seeM added enhancement New feature or request area: workbench Issues related to Workbench category. theme: app builder labels Sep 20, 2024
@seeM seeM added this to the 2024.10.0 Pre-Release milestone Sep 20, 2024
@seeM seeM assigned seeM and unassigned seeM Sep 24, 2024
@sharon-wang
Copy link
Member

I ran a Streamlit app in Workbench and it's displaying in the Viewer successfully! It'd be nice if the proxied path was reflected in the Terminal output, but ctrl+clicking on the Local URL results in the URL getting resolved to the correct proxied path (the one shown at the top of the Viewer).

image

@sharon-wang
Copy link
Member

The Quarto preview Terminal output shows the proxied path in addition to the localhost URL:

image

The Quarto extension code that outputs "Browse at ": https://github.com/quarto-dev/quarto/blob/49abc46be3dbfd5cbd1eadf3d04cb4a7ac690b5c/apps/vscode/src/providers/preview/preview.ts#L532

@sharon-wang
Copy link
Member

sharon-wang commented Oct 1, 2024

Additional testing of Python web apps on Workbench: #4662 (comment)

  • Summary:
    • Working with caveats: Streamlit, Dash, Flask
    • Not working / partially working: Gradio (styles and images not loading from correct path), FastAPI (request is not proxied properly)

Just to see what would happen, I tried modifying the following to include a call to positron proxy to retrieve the proxied URI. It seemed to fix the issues with Gradio and Fastapi, but Streamlit and Dash stopped working. Flask still remained happy.

if (url) {
// Convert the url to an external URI.
const localBaseUri = vscode.Uri.parse(url.toString());
const localUri = options.urlPath ?
vscode.Uri.joinPath(localBaseUri, options.urlPath) : localBaseUri;
const externalUri = await vscode.env.asExternalUri(localUri);
// Open the server URL in the viewer pane.
positron.window.previewUrl(externalUri);
}

It seems like we're in this situation:

App Framework
👇 please provide proxy url, then I'll tell you the app url
👆 please provide app url, then I'll tell you the proxy url
Positron Proxy

@seeM is it possible to preconfigure the app url (host:port) and pass that information to Positron Proxy? I suspect that may involve a bunch of framework-specific handling which may not be preferred. I'll look into what we can do on the Positron Proxy side to provide a proxy url separately from setting up the proxied connection.

@seeM
Copy link
Contributor Author

seeM commented Oct 3, 2024

@sharon-wang thanks for doing this research! I suspected this and had the following solution in mind:

The app-specific side is already implemented for Python apps here. Note how the various getTerminalOptions implementations use the optional port and urlPrefix to configure the app framework.

What still needs to be done is in the positron-run-app extension. We can detect if we're running in Workbench, and if so, determine a free port (and corresponding URL prefix) and pass that through to getTerminalOptions here:

// TODO: If we're in Posit Workbench find a free port and corresponding URL prefix.
// Some application frameworks need to know the URL prefix when running behind a proxy.
progress.report({ message: vscode.l10n.t('Getting terminal options...') });
const port = undefined;
const urlPrefix = undefined;
const terminalOptions = await options.getTerminalOptions(runtime, document, port, urlPrefix);

@sharon-wang sharon-wang self-assigned this Oct 7, 2024
sharon-wang added a commit that referenced this issue Oct 11, 2024
## Description

- Addresses #4769 (as much
as possible for now)

### Framework Support Summary (based on **_limited_** testing)
| Framework | Positron Server Web | Positron Desktop | Positron on
Workbench | Notes |
|--------|--------|--------|--------|--------|
| Dash | ✅ yes | ✅ yes | ✅ yes-ish | [Workbench extension
issue](rstudio/rstudio-workbench-vscode-ext#262)
to skip Dash framework handling to avoid conflicting with Positron's
Dash framework handling |
| Fastapi | ✅ yes | ✅ yes | 🛑 no | Seems to be a conflict between the
way we run a fastapi app and that Workbench is setting
`UVICORN_ROOT_PATH` |
| Flask | ✅ yes | ✅ yes | ✅ yes | Workbench: works when including the
code snippet from [Workbench
docs](https://docs.posit.co/ide/server-pro/user/vs-code/guide/proxying-web-servers.html#flask).
|
| Gradio | ✅ yes-ish | ✅ yes-ish | ✅ yes-ish | Working when the
following dependency versions are used: `gradio==3.3.1 fastapi==0.85.2
httpx==0.24.1`. See gradio-app/gradio#9529 for
more information on why more recent versions don't work. |
| Streamlit | ✅ yes | ✅ yes | 🟨 partially | Not working on Workbench
when SSL is enabled |
| Shiny | ✅ yes | ✅ yes | ✅ yes | | 

### Implementation Notes

#### Positron Proxy
CC @softwarenerd & @jmcphers
- Add new command `positronProxy.startPendingProxyServer` which starts a
new proxy server, but doesn't set up the middleware right away. The
command will return the `externalUri` of the proxy server (this is the
same as the url shown in the Viewer), the `proxyPath` (which some app
frameworks use as the `urlPrefix`) and `finishProxySetup()` which the
command-caller will invoke, passing the `targetOrigin` (the actual app
url) so that we can finish setting up the middleware to proxy requests
from the `externalUri` (app proxy uri) to the `targetOrigin` (actual app
uri).
- Refactor the proxy code so we can set up a proxy in multiple steps,
not only all-at-once
- existing callers of `startProxyServer()` will continue to use that
method which still does the all-at-once proxy setup, by calling
`startNewProxyServer()` and `finishProxySetup()` in succession
- new option to call `startNewProxyServer()` standalone, which will only
start up the server and then call `finishProxySetup()` later, once the
`targetOrigin` is known
- Move the HTML-rewriting code to the util file

#### Positron Run App
CC @seeM
- expand the `localUrlRegex` to include the path after the url port
`<HOST>:<PORT>/<PATH>`
- execute the command `positronProxy.startPendingProxyServer` before
setting up terminal options or the debug configuration, so we can start
the proxy server and get the `urlPrefix`
- before previewing the url in the Viewer, finish the proxy setup by
passing the `localUri` to the positron proxy via
`proxyInfo.finishProxySetup(localUri.toString())`, so that the proxy
middleware is set up
- remove `port` from types and test files as it is unused
- increase timeout for waiting for the app url to show in the terminal
(in particular, Gradio would take a bit longer on Workbench and we would
timeout)

#### Python Extension
CC @seeM 
- remove `port` from the `webAppCommands` and related test files which
is unused
- update the framework-specific app arguments and environment variables
to work with our proxy server situation

#### Custom `resolveExternalUri`
CC @melissa-barca
- update the `resolvedUri` to inherit the protocol used by the main
window as the uri's scheme, so we can upgrade to `https` if the main
window is running in a secure context
- NOTE: this will need to be upstreamed

### QA Notes

This PR involves refactoring the positron-proxy, which is used by Help,
Interactive Plots and the Viewer. We should not see any regression in
these types of proxied content.

Positron Server Web and Desktop should be working across all app
frameworks in:
-
https://github.com/posit-dev/qa-example-content/tree/main/workspaces/python_apps
- When running the Gradio app, you will need to install these versions
`gradio==3.3.1 fastapi==0.85.2 httpx==0.24.1`. If you can get it working
with a more recent combination of versions, please let me know!
-
https://github.com/posit-dev/qa-example-content/tree/main/workspaces/shiny-py-example
-
https://github.com/posit-dev/qa-example-content/tree/main/workspaces/streamlit-py-example

Positron on Workbench:
- `dash` works if the generated `.env` file is deleted before running.
Once [this
issue](rstudio/rstudio-workbench-vscode-ext#262)
is complete, this extra step won't be needed.
- `fastapi` does not work. The current hunch is that Workbench setting
`UVICORN_ROOT_PATH` at session start is interfering with how we run the
`fastapi` app with `uvicorn`. The `UVICORN_ROOT_PATH` is set based on
the default fastapi port `8000`, however I think we want the proxied app
url as the root path, which is provided by the Positron Proxy. But, when
setting `--root-path` in the fastapi app command to the proxied root
path, it does not work in Server Web or Desktop. TBD if this resolves
the issue on Workbench. More investigation needed.
- `flask` works as long as you include the code snippet from [Workbench
docs](https://docs.posit.co/ide/server-pro/user/vs-code/guide/proxying-web-servers.html#flask).
- `gradio` works when these versions are installed `gradio==3.3.1
fastapi==0.85.2 httpx==0.24.1`. There's [an
issue](gradio-app/gradio#9529) in newer
versions of Gradio when the app is run via a proxy.
- `streamlit` does not work when SSL is set up. More investigation
needed.
- `shiny` no notes!

---------

Co-authored-by: sharon wang <[email protected]>
@sharon-wang
Copy link
Member

Framework Support Summary as of #4978

Framework Positron Server Web Positron Desktop Positron on Workbench Notes
Dash ✅ yes ✅ yes ✅ yes-ish Workbench extension issue to skip Dash framework handling to avoid conflicting with Positron's Dash framework handling
Fastapi ✅ yes ✅ yes 🛑 no Seems to be a conflict between the way we run a fastapi app and that Workbench is setting UVICORN_ROOT_PATH
Flask ✅ yes ✅ yes ✅ yes Workbench: works when including the code snippet from Workbench docs.
Gradio ✅ yes-ish ✅ yes-ish ✅ yes-ish Working when the following dependency versions are used: gradio==3.3.1 fastapi==0.85.2 httpx==0.24.1. See gradio-app/gradio#9529 for more information on why more recent versions don't work.
Streamlit ✅ yes ✅ yes 🟨 partially Not working on Workbench when SSL is enabled
Shiny ✅ yes ✅ yes ✅ yes

@sharon-wang
Copy link
Member

I'll come back to this to investigate Fastapi and Streamlit after looking at the other 2024.11 Workbench-related issues. Moving to Up Next for now.

@sharon-wang
Copy link
Member

isabelizimm pushed a commit that referenced this issue Oct 16, 2024
## Description

- Addresses #4769 (as much
as possible for now)

### Framework Support Summary (based on **_limited_** testing)
| Framework | Positron Server Web | Positron Desktop | Positron on
Workbench | Notes |
|--------|--------|--------|--------|--------|
| Dash | ✅ yes | ✅ yes | ✅ yes-ish | [Workbench extension
issue](rstudio/rstudio-workbench-vscode-ext#262)
to skip Dash framework handling to avoid conflicting with Positron's
Dash framework handling |
| Fastapi | ✅ yes | ✅ yes | 🛑 no | Seems to be a conflict between the
way we run a fastapi app and that Workbench is setting
`UVICORN_ROOT_PATH` |
| Flask | ✅ yes | ✅ yes | ✅ yes | Workbench: works when including the
code snippet from [Workbench
docs](https://docs.posit.co/ide/server-pro/user/vs-code/guide/proxying-web-servers.html#flask).
|
| Gradio | ✅ yes-ish | ✅ yes-ish | ✅ yes-ish | Working when the
following dependency versions are used: `gradio==3.3.1 fastapi==0.85.2
httpx==0.24.1`. See gradio-app/gradio#9529 for
more information on why more recent versions don't work. |
| Streamlit | ✅ yes | ✅ yes | 🟨 partially | Not working on Workbench
when SSL is enabled |
| Shiny | ✅ yes | ✅ yes | ✅ yes | | 

### Implementation Notes

#### Positron Proxy
CC @softwarenerd & @jmcphers
- Add new command `positronProxy.startPendingProxyServer` which starts a
new proxy server, but doesn't set up the middleware right away. The
command will return the `externalUri` of the proxy server (this is the
same as the url shown in the Viewer), the `proxyPath` (which some app
frameworks use as the `urlPrefix`) and `finishProxySetup()` which the
command-caller will invoke, passing the `targetOrigin` (the actual app
url) so that we can finish setting up the middleware to proxy requests
from the `externalUri` (app proxy uri) to the `targetOrigin` (actual app
uri).
- Refactor the proxy code so we can set up a proxy in multiple steps,
not only all-at-once
- existing callers of `startProxyServer()` will continue to use that
method which still does the all-at-once proxy setup, by calling
`startNewProxyServer()` and `finishProxySetup()` in succession
- new option to call `startNewProxyServer()` standalone, which will only
start up the server and then call `finishProxySetup()` later, once the
`targetOrigin` is known
- Move the HTML-rewriting code to the util file

#### Positron Run App
CC @seeM
- expand the `localUrlRegex` to include the path after the url port
`<HOST>:<PORT>/<PATH>`
- execute the command `positronProxy.startPendingProxyServer` before
setting up terminal options or the debug configuration, so we can start
the proxy server and get the `urlPrefix`
- before previewing the url in the Viewer, finish the proxy setup by
passing the `localUri` to the positron proxy via
`proxyInfo.finishProxySetup(localUri.toString())`, so that the proxy
middleware is set up
- remove `port` from types and test files as it is unused
- increase timeout for waiting for the app url to show in the terminal
(in particular, Gradio would take a bit longer on Workbench and we would
timeout)

#### Python Extension
CC @seeM 
- remove `port` from the `webAppCommands` and related test files which
is unused
- update the framework-specific app arguments and environment variables
to work with our proxy server situation

#### Custom `resolveExternalUri`
CC @melissa-barca
- update the `resolvedUri` to inherit the protocol used by the main
window as the uri's scheme, so we can upgrade to `https` if the main
window is running in a secure context
- NOTE: this will need to be upstreamed

### QA Notes

This PR involves refactoring the positron-proxy, which is used by Help,
Interactive Plots and the Viewer. We should not see any regression in
these types of proxied content.

Positron Server Web and Desktop should be working across all app
frameworks in:
-
https://github.com/posit-dev/qa-example-content/tree/main/workspaces/python_apps
- When running the Gradio app, you will need to install these versions
`gradio==3.3.1 fastapi==0.85.2 httpx==0.24.1`. If you can get it working
with a more recent combination of versions, please let me know!
-
https://github.com/posit-dev/qa-example-content/tree/main/workspaces/shiny-py-example
-
https://github.com/posit-dev/qa-example-content/tree/main/workspaces/streamlit-py-example

Positron on Workbench:
- `dash` works if the generated `.env` file is deleted before running.
Once [this
issue](rstudio/rstudio-workbench-vscode-ext#262)
is complete, this extra step won't be needed.
- `fastapi` does not work. The current hunch is that Workbench setting
`UVICORN_ROOT_PATH` at session start is interfering with how we run the
`fastapi` app with `uvicorn`. The `UVICORN_ROOT_PATH` is set based on
the default fastapi port `8000`, however I think we want the proxied app
url as the root path, which is provided by the Positron Proxy. But, when
setting `--root-path` in the fastapi app command to the proxied root
path, it does not work in Server Web or Desktop. TBD if this resolves
the issue on Workbench. More investigation needed.
- `flask` works as long as you include the code snippet from [Workbench
docs](https://docs.posit.co/ide/server-pro/user/vs-code/guide/proxying-web-servers.html#flask).
- `gradio` works when these versions are installed `gradio==3.3.1
fastapi==0.85.2 httpx==0.24.1`. There's [an
issue](gradio-app/gradio#9529) in newer
versions of Gradio when the app is run via a proxy.
- `streamlit` does not work when SSL is set up. More investigation
needed.
- `shiny` no notes!

---------

Co-authored-by: sharon wang <[email protected]>
isabelizimm pushed a commit that referenced this issue Oct 16, 2024
## Description

- Addresses #4769 (as much
as possible for now)

### Framework Support Summary (based on **_limited_** testing)
| Framework | Positron Server Web | Positron Desktop | Positron on
Workbench | Notes |
|--------|--------|--------|--------|--------|
| Dash | ✅ yes | ✅ yes | ✅ yes-ish | [Workbench extension
issue](rstudio/rstudio-workbench-vscode-ext#262)
to skip Dash framework handling to avoid conflicting with Positron's
Dash framework handling |
| Fastapi | ✅ yes | ✅ yes | 🛑 no | Seems to be a conflict between the
way we run a fastapi app and that Workbench is setting
`UVICORN_ROOT_PATH` |
| Flask | ✅ yes | ✅ yes | ✅ yes | Workbench: works when including the
code snippet from [Workbench
docs](https://docs.posit.co/ide/server-pro/user/vs-code/guide/proxying-web-servers.html#flask).
|
| Gradio | ✅ yes-ish | ✅ yes-ish | ✅ yes-ish | Working when the
following dependency versions are used: `gradio==3.3.1 fastapi==0.85.2
httpx==0.24.1`. See gradio-app/gradio#9529 for
more information on why more recent versions don't work. |
| Streamlit | ✅ yes | ✅ yes | 🟨 partially | Not working on Workbench
when SSL is enabled |
| Shiny | ✅ yes | ✅ yes | ✅ yes | | 

### Implementation Notes

#### Positron Proxy
CC @softwarenerd & @jmcphers
- Add new command `positronProxy.startPendingProxyServer` which starts a
new proxy server, but doesn't set up the middleware right away. The
command will return the `externalUri` of the proxy server (this is the
same as the url shown in the Viewer), the `proxyPath` (which some app
frameworks use as the `urlPrefix`) and `finishProxySetup()` which the
command-caller will invoke, passing the `targetOrigin` (the actual app
url) so that we can finish setting up the middleware to proxy requests
from the `externalUri` (app proxy uri) to the `targetOrigin` (actual app
uri).
- Refactor the proxy code so we can set up a proxy in multiple steps,
not only all-at-once
- existing callers of `startProxyServer()` will continue to use that
method which still does the all-at-once proxy setup, by calling
`startNewProxyServer()` and `finishProxySetup()` in succession
- new option to call `startNewProxyServer()` standalone, which will only
start up the server and then call `finishProxySetup()` later, once the
`targetOrigin` is known
- Move the HTML-rewriting code to the util file

#### Positron Run App
CC @seeM
- expand the `localUrlRegex` to include the path after the url port
`<HOST>:<PORT>/<PATH>`
- execute the command `positronProxy.startPendingProxyServer` before
setting up terminal options or the debug configuration, so we can start
the proxy server and get the `urlPrefix`
- before previewing the url in the Viewer, finish the proxy setup by
passing the `localUri` to the positron proxy via
`proxyInfo.finishProxySetup(localUri.toString())`, so that the proxy
middleware is set up
- remove `port` from types and test files as it is unused
- increase timeout for waiting for the app url to show in the terminal
(in particular, Gradio would take a bit longer on Workbench and we would
timeout)

#### Python Extension
CC @seeM 
- remove `port` from the `webAppCommands` and related test files which
is unused
- update the framework-specific app arguments and environment variables
to work with our proxy server situation

#### Custom `resolveExternalUri`
CC @melissa-barca
- update the `resolvedUri` to inherit the protocol used by the main
window as the uri's scheme, so we can upgrade to `https` if the main
window is running in a secure context
- NOTE: this will need to be upstreamed

### QA Notes

This PR involves refactoring the positron-proxy, which is used by Help,
Interactive Plots and the Viewer. We should not see any regression in
these types of proxied content.

Positron Server Web and Desktop should be working across all app
frameworks in:
-
https://github.com/posit-dev/qa-example-content/tree/main/workspaces/python_apps
- When running the Gradio app, you will need to install these versions
`gradio==3.3.1 fastapi==0.85.2 httpx==0.24.1`. If you can get it working
with a more recent combination of versions, please let me know!
-
https://github.com/posit-dev/qa-example-content/tree/main/workspaces/shiny-py-example
-
https://github.com/posit-dev/qa-example-content/tree/main/workspaces/streamlit-py-example

Positron on Workbench:
- `dash` works if the generated `.env` file is deleted before running.
Once [this
issue](rstudio/rstudio-workbench-vscode-ext#262)
is complete, this extra step won't be needed.
- `fastapi` does not work. The current hunch is that Workbench setting
`UVICORN_ROOT_PATH` at session start is interfering with how we run the
`fastapi` app with `uvicorn`. The `UVICORN_ROOT_PATH` is set based on
the default fastapi port `8000`, however I think we want the proxied app
url as the root path, which is provided by the Positron Proxy. But, when
setting `--root-path` in the fastapi app command to the proxied root
path, it does not work in Server Web or Desktop. TBD if this resolves
the issue on Workbench. More investigation needed.
- `flask` works as long as you include the code snippet from [Workbench
docs](https://docs.posit.co/ide/server-pro/user/vs-code/guide/proxying-web-servers.html#flask).
- `gradio` works when these versions are installed `gradio==3.3.1
fastapi==0.85.2 httpx==0.24.1`. There's [an
issue](gradio-app/gradio#9529) in newer
versions of Gradio when the app is run via a proxy.
- `streamlit` does not work when SSL is set up. More investigation
needed.
- `shiny` no notes!

---------

Co-authored-by: sharon wang <[email protected]>
@jonvanausdeln
Copy link
Contributor

All the apps work as expected with all the noted exceptions.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 1, 2024
@sharon-wang sharon-wang added the area: proxy Issues related to Positron Proxy category. label Nov 21, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: proxy Issues related to Positron Proxy category. area: run app area: workbench Issues related to Workbench category. enhancement New feature or request theme: app builder
Projects
None yet
Development

No branches or pull requests

4 participants