Skip to content

Commit

Permalink
Implement config.baseUrlPath (streamlit#454)
Browse files Browse the repository at this point in the history
* Implement server.baseUrlPath config option.

* Improve st.image pydoc.

* Improve baseUrlPath support for trailing slash

* Implement JS tests

* Add more JS tests

* Fix tests

* Run pyformat on all files
  • Loading branch information
tvst authored Oct 19, 2019
1 parent a3d6cb2 commit 4b1e7c5
Show file tree
Hide file tree
Showing 22 changed files with 342 additions and 41 deletions.
4 changes: 1 addition & 3 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,7 @@
# further. For a list of options available for each theme, see the
# documentation.

html_theme_options = {
"analytics_id": "UA-122023594-3",
}
html_theme_options = {"analytics_id": "UA-122023594-3"}

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
Expand Down
4 changes: 2 additions & 2 deletions e2e/scripts/sidebar.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@
w2 = st.sidebar.date_input("Label 2", datetime(2019, 7, 6, 21, 15))
st.write("Value 2:", w2)

x = st.sidebar.text('overwrite me')
x.text('overwritten')
x = st.sidebar.text("overwrite me")
x.text("overwritten")
4 changes: 2 additions & 2 deletions e2e/scripts/vega_lite_chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@
}
)

#st.write("Putting the `df` inside the spec, as inline `data` (different notation):")
#This fails now, but not a big deal. It's a weird notation.
# st.write("Putting the `df` inside the spec, as inline `data` (different notation):")
# This fails now, but not a big deal. It's a weird notation.

# st.vega_lite_chart({
# 'data': {'values': df},
Expand Down
9 changes: 6 additions & 3 deletions frontend/src/lib/ConnectionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,13 +177,16 @@ export class ConnectionManager {
internalServerIP,
externalServerIP,
serverPort,
serverBasePath,
} = manifest

const parts = { port: serverPort, basePath: serverBasePath }

const baseUriPartsList = configuredServerAddress
? [{ host: configuredServerAddress, port: serverPort }]
? [{ ...parts, host: configuredServerAddress }]
: [
{ host: externalServerIP, port: serverPort },
{ host: internalServerIP, port: serverPort },
{ ...parts, host: externalServerIP },
{ ...parts, host: internalServerIP },
]

return new WebsocketConnection({
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/lib/ForwardMessageCache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ import fetchMock from "fetch-mock"
import { ForwardMsgCache } from "lib/ForwardMessageCache"
import { buildHttpUri } from "lib/UriUtil"

const MOCK_SERVER_URI = { host: "streamlit.mock", port: 80 }
const MOCK_SERVER_URI = {
host: "streamlit.mock",
port: 80,
basePath: "",
}

interface MockCache {
cache: ForwardMsgCache
Expand Down
153 changes: 153 additions & 0 deletions frontend/src/lib/UriUtil.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/**
* @license
* Copyright 2018-2019 Streamlit Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { buildHttpUri, buildWsUri, getWindowBaseUriParts } from "./UriUtil"

const location = {}

global.window = Object.create(window)
Object.defineProperty(window, "location", { value: location })

test("gets all window URI parts", () => {
location.hostname = "the_host"
location.port = "9988"
location.pathname = "foo"

const parts = getWindowBaseUriParts()
expect(parts).toStrictEqual({
host: "the_host",
port: 9988,
basePath: "foo",
})
})

test("gets window URI parts without basePath", () => {
location.hostname = "the_host"
location.port = "9988"
location.pathname = ""

const parts = getWindowBaseUriParts()
expect(parts).toStrictEqual({
host: "the_host",
port: 9988,
basePath: "",
})
})

test("gets window URI parts with long basePath", () => {
location.hostname = "the_host"
location.port = "9988"
location.pathname = "/foo/bar"

const parts = getWindowBaseUriParts()
expect(parts).toStrictEqual({
host: "the_host",
port: 9988,
basePath: "foo/bar",
})
})

test("gets window URI parts with weird basePath", () => {
location.hostname = "the_host"
location.port = "9988"
location.pathname = "///foo/bar//"

const parts = getWindowBaseUriParts()
expect(parts).toStrictEqual({
host: "the_host",
port: 9988,
basePath: "foo/bar",
})
})

test("builds HTTP URI correctly", () => {
location.href = "http://something"
const uri = buildHttpUri(
{
host: "the_host",
port: 9988,
basePath: "foo/bar",
},
"baz"
)
expect(uri).toBe("http://the_host:9988/foo/bar/baz")
})

test("builds HTTPS URI correctly", () => {
location.href = "https://something"
const uri = buildHttpUri(
{
host: "the_host",
port: 9988,
basePath: "foo/bar",
},
"baz"
)
expect(uri).toBe("https://the_host:9988/foo/bar/baz")
})

test("builds HTTP URI with no base path", () => {
location.href = "http://something"
const uri = buildHttpUri(
{
host: "the_host",
port: 9988,
basePath: "",
},
"baz"
)
expect(uri).toBe("http://the_host:9988/baz")
})

test("builds WS URI correctly", () => {
location.href = "http://something"
const uri = buildWsUri(
{
host: "the_host",
port: 9988,
basePath: "foo/bar",
},
"baz"
)
expect(uri).toBe("ws://the_host:9988/foo/bar/baz")
})

test("builds WSS URI correctly", () => {
location.href = "https://something"
const uri = buildWsUri(
{
host: "the_host",
port: 9988,
basePath: "foo/bar",
},
"baz"
)
expect(uri).toBe("wss://the_host:9988/foo/bar/baz")
})

test("builds WS URI with no base path", () => {
location.href = "http://something"
const uri = buildWsUri(
{
host: "the_host",
port: 9988,
basePath: "",
},
"baz"
)
expect(uri).toBe("ws://the_host:9988/baz")
})
42 changes: 33 additions & 9 deletions frontend/src/lib/UriUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,12 @@ import { IS_DEV_ENV, WEBSOCKET_PORT_DEV } from "lib/baseconsts"
export interface BaseUriParts {
host: string
port: number
basePath: string
}

const FINAL_SLASH_RE = /\/+$/
const INITIAL_SLASH_RE = /^\/+/

/**
* Return the BaseUriParts for the global window
*/
Expand All @@ -33,36 +37,56 @@ export function getWindowBaseUriParts(): BaseUriParts {
// server's port 3000.
// If changed, also change config.py
const host = window.location.hostname

// prettier-ignore
const port = IS_DEV_ENV
? WEBSOCKET_PORT_DEV
: window.location.port
? Number(window.location.port)
: isHttps()
? 443
: 80
return { host, port }
? Number(window.location.port)
: isHttps()
? 443
: 80

const basePath = window.location.pathname
.replace(FINAL_SLASH_RE, "")
.replace(INITIAL_SLASH_RE, "")

return { host, port, basePath }
}

/**
* Create a ws:// or wss:// URI for the given path.
*/
export function buildWsUri(
{ host, port }: BaseUriParts,
{ host, port, basePath }: BaseUriParts,
path: string
): string {
const protocol = isHttps() ? "wss" : "ws"
return `${protocol}://${host}:${port}/${path}`
const fullPath = makePath(basePath, path)
return `${protocol}://${host}:${port}/${fullPath}`
}

/**
* Create an HTTP URI for the given path.
*/
export function buildHttpUri(
{ host, port }: BaseUriParts,
{ host, port, basePath }: BaseUriParts,
path: string
): string {
const protocol = isHttps() ? "https" : "http"
return `${protocol}://${host}:${port}/${path}`
const fullPath = makePath(basePath, path)
return `${protocol}://${host}:${port}/${fullPath}`
}

function makePath(basePath: string, subPath: string): string {
basePath = basePath.replace(FINAL_SLASH_RE, "").replace(INITIAL_SLASH_RE, "")
subPath = subPath.replace(FINAL_SLASH_RE, "").replace(INITIAL_SLASH_RE, "")

if (basePath.length === 0) {
return subPath
}

return `${basePath}/${subPath}`
}

/**
Expand Down
3 changes: 2 additions & 1 deletion lib/streamlit/DeltaGenerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -1305,7 +1305,8 @@ def image(
`image[:, :, 2]` is blue. For images coming from libraries like
OpenCV you should set this to 'BGR', instead.
format : 'JPEG' or 'PNG'
This parameter specifies the image format. Defaults to 'JPEG'.
This parameter specifies the image format to use when transferring
the image data. Defaults to 'JPEG'.
Example
-------
Expand Down
12 changes: 11 additions & 1 deletion lib/streamlit/Report.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,16 @@ def get_url(cls, host_ip):
The URL.
"""
port = _get_browser_address_bar_port()
return "http://%(host_ip)s:%(port)s" % {"host_ip": host_ip, "port": port}
base_path = config.get_option("server.baseUrlPath").strip("/")

if base_path:
base_path = "/" + base_path

return "http://%(host_ip)s:%(port)s%(base_path)s" % {
"host_ip": host_ip.strip("/"),
"port": port,
"base_path": base_path,
}

def __init__(self, script_path, command_line):
"""Constructor.
Expand Down Expand Up @@ -243,6 +252,7 @@ def _build_manifest(
# websocket port, not the web server port. (These are the same in
# prod, but different in dev)
manifest.server_port = config.get_option("browser.serverPort")
manifest.server_base_path = config.get_option("server.baseUrlPath")
else:
manifest.num_messages = num_messages

Expand Down
7 changes: 4 additions & 3 deletions lib/streamlit/ReportSession.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,10 @@ def __init__(self, ioloop, script_path, command_line):

self._state = ReportSessionState.REPORT_NOT_RUNNING

self._main_dg = DeltaGenerator(
enqueue=self.enqueue, container=BlockPath.MAIN)
self._main_dg = DeltaGenerator(enqueue=self.enqueue, container=BlockPath.MAIN)
self._sidebar_dg = DeltaGenerator(
enqueue=self.enqueue, container=BlockPath.SIDEBAR)
enqueue=self.enqueue, container=BlockPath.SIDEBAR
)

self._widget_states = WidgetStates()
self._local_sources_watcher = LocalSourcesWatcher(
Expand Down Expand Up @@ -526,6 +526,7 @@ def handle_save_request(self, ws):
The report's websocket handler.
"""

@tornado.gen.coroutine
def progress(percent):
progress_msg = ForwardMsg()
Expand Down
10 changes: 10 additions & 0 deletions lib/streamlit/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,16 @@ def _server_port():
return 8501


_create_option(
"server.baseUrlPath",
description="""
The base path for the URL where Streamlit should be served from.
""",
default_val="",
type_=str,
)


@_create_option("server.enableCORS", type_=bool)
def _server_enable_cors():
"""Enables support for Cross-Origin Request Sharing, for added security.
Expand Down
7 changes: 5 additions & 2 deletions lib/streamlit/hello/demos.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,11 +221,14 @@ def get_UN_data():
try:
df = get_UN_data()
except urllib.error.URLError as e:
st.error("""
st.error(
"""
**This demo requires internet access.**
Connection error: %s
""" % e.reason)
"""
% e.reason
)
return

countries = st.multiselect(
Expand Down
Loading

0 comments on commit 4b1e7c5

Please sign in to comment.