{"id":21064728,"url":"https://github.com/ideonate/jhsingle-native-proxy","last_synced_at":"2025-04-08T03:11:51.445Z","repository":{"id":37843303,"uuid":"243550914","full_name":"ideonate/jhsingle-native-proxy","owner":"ideonate","description":"Wrap an arbitrary webapp so it can be used in place of jupyter-singleuser in a JupyterHub setting","archived":false,"fork":false,"pushed_at":"2024-09-25T17:20:53.000Z","size":118,"stargazers_count":63,"open_issues_count":5,"forks_count":26,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-28T06:09:48.596Z","etag":null,"topics":["jupyterhub"],"latest_commit_sha":null,"homepage":"https://cdsdashboards.readthedocs.io/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ideonate.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-02-27T15:32:10.000Z","updated_at":"2025-03-17T06:33:54.000Z","dependencies_parsed_at":"2024-06-21T13:30:20.380Z","dependency_job_id":"3496d844-0eaa-431a-871d-b5fc970d9ea4","html_url":"https://github.com/ideonate/jhsingle-native-proxy","commit_stats":{"total_commits":96,"total_committers":13,"mean_commits":7.384615384615385,"dds":"0.30208333333333337","last_synced_commit":"3e26a4ee0e7318970b7cf6abbd7d88455a9ac621"},"previous_names":[],"tags_count":41,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ideonate%2Fjhsingle-native-proxy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ideonate%2Fjhsingle-native-proxy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ideonate%2Fjhsingle-native-proxy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ideonate%2Fjhsingle-native-proxy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ideonate","download_url":"https://codeload.github.com/ideonate/jhsingle-native-proxy/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247767236,"owners_count":20992548,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["jupyterhub"],"created_at":"2024-11-19T17:51:28.170Z","updated_at":"2025-04-08T03:11:51.426Z","avatar_url":"https://github.com/ideonate.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# jhsingle-native-proxy\n\nWrap an arbitrary webapp so it can be used in place of jupyter-singleuser in a JupyterHub setting.\n\nWithin JupyterHub this allows similar operation to [jupyter-server-proxy](https://github.com/jupyterhub/jupyter-server-proxy) except it also removes the Jupyter notebook itself, so is working directly with the arbitrary web service.\n\nOAuth authentication is enforced based on JUPYTERHUB\\_\\* environment variables.\n\nThis project is used in [ContainDS Dashboards](https://github.com/ideonate/cdsdashboards), which is a user-friendly\nway to launch Jupyter notebooks as shareable dashboards inside JupyterHub. Also works with Streamlit and other\nvisualization frameworks.\n\n## Install and Run\n\nInstall using pip.\n\n```\npip install jhsingle-native-proxy\n```\n\nThe process to start is specified on the command line, for example a [streamlit](https://streamlit.io/) web app:\n\n```\njhsingle-native-proxy streamlit hello\n```\n\nBy default the jhsingle-native-proxy server will listen on port 8888, forwarding to port 8500.\n\nBut you will normally need to tell jhsingle-native-proxy which port the end process will run in, and maybe tell the\nend process which port you want it to use (which you can do with the substitution variable {port}).\n\nNote the use of -- to signal the end of command line options to jhsingle-native-proxy. Then the third party command line\nitself can contain options starting with dashes. An alternative is to use the substitution {--}\n\n```\njhsingle-native-proxy -- streamlit hello --server.port {port} --server.headless True --server.enableCORS False\n```\n\nTo run jhsingle-native-proxy itself listening on a different port use:\n\n```\njhsingle-native-proxy --port 8000 streamlit hello\n```\n\nTo run jhsingle-native-proxy on port 8000, and the end process on 8505:\n\n```\njhsingle-native-proxy --port 8000 --destport 8505 -- streamlit hello --server.port {port} --server.headless True --server.enableCORS False\n```\n\nUse the JUPYTERHUB_SERVICE_PREFIX env var to specify the first part of the URL to listen to (and then strip before forwarding). E.g.\nJUPYTERHUB_SERVICE_PREFIX=/user/dan will mean requests on http://localhost:8888/user/dan/something will forward to http://localhost:8500/something\n\nYou can also specify --ip 0.0.0.0 for the address to listen on.\n\nBelow we use the substitution {--} for the command to run, allowing us to specify --ip to jhsingle-native-proxy instead of the\ncommand being run.\n\n```\njhsingle-native-proxy --port 8000 --destport 8505 streamlit hello {--}server.port {port} {--}server.headless True {--}server.enableCORS False --ip 0.0.0.0\n```\n\nSimilarly, use e.g. {-}m to represent -m in the final command.\n\n### Voila example:\n\nRunning voila at the subfolder URL e.g. /user/dan/:\n\n```\npython -m jhsingle_native_proxy.main --destport 0 voila ./Presentation.ipynb {--}port={port} {--}no-browser {--}Voila.server_url=/ {--}Voila.base_url={base_url}/ {--}debug\n```\n\n'destport 0' above instructs jhsingle-native-proxy to choose a random free port on which to run the sub-process (Voila), and of course substitutes that as {port} in the Voila command line so it knows which port to listen on. destport 0 is the default anyway.\n\nOr specify presentation_path as a substitution instead of hard-coding, which is sometimes easier in your wrapper code:\n\n```\npython -m jhsingle_native_proxy.main --destport 0 voila {presentation_path} {--}port={port} {--}no-browser {--}Voila.server_url=/ {--}Voila.base_url={base_url}/ {--}debug --presentation_path=./Presentation.ipynb\n```\n\nIn addition, if presentation_path is provided, two further substitution variables are available: presentation_dirname and\npresentation_basename. These are computed using Python's os.path.dirname and os.path.basename functions on presentation_path.\n\n## Authentication\n\nThe above examples all assume OAuth will be enforced, as per the JUPYTERHUB\\_\\* env vars.\n\nAlternatives can be specified via the authtype flag:\n\nSame as default:\n\n```\njhsingle-native-proxy --authtype=oauth streamlit hello\n```\n\nNo auth required at all:\n\n```\njhsingle-native-proxy --authtype=none streamlit hello\n```\n\n### Specifying Authorized Users\n\nThe env vars JUPYTERHUB_USER and JUPYTERHUB_GROUP can be used, as typical for any JupyterHub single server, to specify user/groups of\nJupyterHub that should be allowed access via OAuth. There is an additional bespoke env var called JUPYTERHUB_ANYONE which can be set to 1\nto allow any authenticated user access. (i.e. anyone who has an account on the JupyterHub)\n\n### Extra Arguments\n\n--request-timeout=300 specifies the timeout in seconds that it waits for the underlying subprocess to return when proxying normal requests. Default is 300.\n\n{origin_host} in the command argument will be replaced with the first 'host' seen in any request to the jhsingle-native-proxy server.\n\n--last-activity-interval=300 specifies how often in seconds to update the hub to provide the last time any traffic passed through\nthe proxy (default 300). Specify 0 to never update.\n--force-keep-alive or --no-force-keep-alive: the former (default) ensures that the hub is notified of recent activity even if there wasn't any - only works if last-activity-interval is not 0.\n\n--ready-check-path (default /) to change the URL on the subprocess used to poll with an HTTP request to check for readiness.\n\n--repo - use git to check out a repo before running the sub process\n\n--repofolder - the path of a folder (to be created if necessary) to contain the git repo contents\n\n--forward-user-info - forward to the underlying process an X-CDSDASHBOARDS-JH-USER HTTP header containing JupyterHub user info as JSON-encoded string\n\n--query-user-info - add a GET query param named CDSDASHBOARDS_JH_USER when calling underlying process containing JupyterHub user info as JSON-encoded string\n\n--ready-timeout - integer timeout for period of checking the process is running at startup (default 10). Increase if your process is not able to return anything at --ready-check-path until a longer time after it first starts up. Be aware that the process must (once ready) return its HTTP response within 1 second. Note this argument is different from --request-timeout which applies to individual HTTP proxy calls during normal operation (not just at startup).\n\n--websocket-max-message-size - message size in bytes allowed by websocket connections made to the underlying process (default is to rely on the tornado library defaults).\n\n--progressive - flush buffer from underlying service whenever chunks appear (this is useful to see results from Voila sooner)\n\n## Changelog\n\n### v0.8.3 released 25 Sep 2024\n\n- New options for Aiohttp Connector and Timeout: aiohttp-no-ssl-connector and aiohttp-request-timeout. Thanks to [aditipate](https://github.com/aditipate).\n\n### v0.8.2 released 30 Nov 2023\n\n- Better logging output via Tornado. Thanks to [aditipate](https://github.com/aditipate).\n\n### v0.8.1 released 13 Jun 2023\n\n- Pin simpervisor version to avoid conflict with version 1.0. Thanks to [dangercrow](https://github.com/dangercrow).\n\n### v0.8.0 released 8 Nov 2021\n\n- Change to work with JupyterHub 2 (detects port from JUPYTERHUB_SERVICE_URL env var if no --port set)\n\n### v0.7.6 released 20 Apr 2021\n\n- New command-line options --ready-timeout and --websocket-max-message-size\n\n### v0.7.3 released 9 Apr 2021\n\n- New command-line option --progressive to flush buffer from underlying service whenever chunks appear (this is useful to see results from Voila sooner)\n- oauth_callback URL now accessible when running with JUPYTERHUB_BASE_URL of /\n\n### v0.7.1 released 22 Feb 2021\n\n- New command-line option --query-user-info to add a CDSDASHBOARDS_JH_USER GET query param to the http\n  request to the underlying service.\n\n### v0.7.0 released 12 Feb 2021\n\n- New command-line option --forward-user-info to add a X-CDSDASHBOARDS-JH-USER header to the http request to the underlying service.\n  The header value is a JSON-encoded dict containing kind, name, admin, groups fields from the logged-in JupyterHub user if available.\n\n### v0.6.1 released 6 Jan 2021\n\n- Require simpervisor \u003e= 0.4 to ensure Python 3.9 compat.\n\n### v0.6.0 released 20 Nov 2020\n\n- Displays INFO level logs by default, which includes output of the subprocess (turn off with --no-logs) [Issue #7](https://github.com/ideonate/jhsingle-native-proxy/issues/7)\n- Logs from subprocess written out at different level depending on source (stderr -\u003e error, stdout -\u003e info)\n- Long subprocess logs are handled and truncated instead of throwing an error [cdsdashboards issue #44](https://github.com/ideonate/cdsdashboards/issues/44)\n- Different handling of branch checkout when using git repo source, when switching brances compared to what was checked out before\n\n### v0.5.6 released 18 Sep 2020\n\n- Always convert presentation_path to an absolute path (based on CWD) before passing to the sub-command.\n\n### v0.5.5 released 10 Sep 2020\n\n- Also accept URLs at the URL-encoded equivalent of the prefix and redirect to the regular version of the URL.\n\n### v0.5.4 released 3 Sep 2020\n\n- Change working folder to repofolder when specified\n\n### v0.5.2 released 17 Aug 2020\n\n- Require tornado 6.0.4+\n\n### v0.5.1 released 17 Aug 2020\n\n- Fix to ensure both websockets are opened at the same time, to avoid writing to a websocket that's not yet open.\n\n### v0.5.0 released 17 Aug 2020\n\n- Open up underlying process' websocket before connecting our own with the client. This ensures any other GET headers can be passed back to the client. (Fix for Streamlit XSRF problems.)\n\n### v0.4.3 released 30 July 2020\n\n- Added --allow-root option (currently ignored) to avoid errors if this flag is usually passed to jupyter-singleuser\n\n### v0.4.2 released 23 July 2020\n\n- Switch to a Conda env before running subprocess by specifying --conda-env option\n\n### v0.4.1 released 20 July 2020\n\n- fix because subprocess sometimes blocked if too much output generated\n\n### v0.4.0 released 15 July 2020\n\n- repo and repofolder optional arguments added\n\n### v0.3.2 released 25 June 2020\n\n### v0.3.1 released 18 June 2020\n\n- Defaults presentation_path to empty str ('') if not supplied, avoiding error\n\n### v0.3.0 released 17 June 2020\n\n- presentation_path can be provided as a command line argument to become a substitution variable.\n- presentation_basename and presentation_dirname are also available when presentation_path is supplied.\n\n### v0.2.0 released 11 June 2020\n\n- Better websocket handling (subprotocols)\n- {origin_host} variable added\n\n### v0.1.3 released 1 June 2020\n\n- request-timeout added to the proxy call, and the default set to 300 (20 seconds was the httpclient's default previously)\n\n### v0.1.2 released 29 May 2020\n\n- Now allows single-dash placeholder, e.g. {-}m translates to -m in the final subprocess command.\n\n## Development install\n\n```\ngit clone https://github.com/ideonate/jhsingle-native-proxy.git\ncd jhsingle-native-proxy\n\npip install -e .\n```\n\nTo run directly in python: `python -m jhsingle_native_proxy.main \u003crest of command line\u003e`\n\nTesting git puller:\n\npython -m jhsingle_native_proxy.main --authtype=none --destport=0 --port=8888 voila ./sincosfolder/Presentation.ipynb {--}port={port} {--}no-browser {--}Voila.server_url=/ {--}Voila.base_url={base_url}/ --repo=https://github.com/danlester/binder-sincos --repofolder=sincosfolder\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fideonate%2Fjhsingle-native-proxy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fideonate%2Fjhsingle-native-proxy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fideonate%2Fjhsingle-native-proxy/lists"}