From 2e30b5b070aaa80f358f4121da8b8cb25db919dc Mon Sep 17 00:00:00 2001 From: Martin Turoci Date: Thu, 22 Sep 2022 10:39:53 +0200 Subject: [PATCH 1/7] feat: Add proper err handling to site.download function. #1627 --- .vscode/launch.json | 26 +++++++++++++------------- py/h2o_wave/core.py | 10 ++++++---- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 75ce4332bc..7f2b4cf052 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -39,19 +39,6 @@ "bdist_wheel", ] }, - { - "name": "Debug PY App", - "type": "python", - "request": "launch", - "program": "${workspaceFolder}/py/venv/bin/wave", - "python": "${workspaceFolder}/py/venv/bin/python", - "cwd": "${workspaceFolder}/py/examples", - "args": [ - "run", - "tour", - "--no-reload", - ], - }, { "name": "Debug Py Tests", "type": "python", @@ -104,6 +91,19 @@ "${workspaceFolder}/tools/wavegen/build/**/*.js" ] }, + { + "name": "Debug Wave App", + "type": "python", + "request": "launch", + "program": "${workspaceFolder}/py/venv/bin/wave", + "python": "${workspaceFolder}/py/venv/bin/python", + "cwd": "${workspaceFolder}/py/examples", + "args": [ + "run", + "tour", + "--no-reload", + ], + }, { "name": "Debug Wave Server", "type": "go", diff --git a/py/h2o_wave/core.py b/py/h2o_wave/core.py index 8344c5ca95..8ab24e7f80 100644 --- a/py/h2o_wave/core.py +++ b/py/h2o_wave/core.py @@ -893,10 +893,12 @@ async def download(self, url: str, path: str) -> str: path = os.path.abspath(path) # If path is a directory, get basename from url filepath = os.path.join(path, os.path.basename(url)) if os.path.isdir(path) else path - - with open(filepath, 'wb') as f: - async with self._http.stream('GET', f'{_config.hub_host_address}{url}') as r: - async for chunk in r.aiter_bytes(): + async with self._http.stream('GET', f'{_config.hub_host_address}{url}') as res: + if res.status_code != 200: + await res.aread() + raise ServiceError(f'Download failed (code={res.status_code}): {res.text}') + with open(filepath, 'wb') as f: + async for chunk in res.aiter_bytes(): f.write(chunk) return filepath From 6bcff44a19fe1c91f939f7cf4edb052ce5b4a6f0 Mon Sep 17 00:00:00 2001 From: Martin Turoci Date: Thu, 22 Sep 2022 11:04:40 +0200 Subject: [PATCH 2/7] feat: Add proper err handling to site.download function - sync API. #1627 --- py/h2o_wave/core.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/py/h2o_wave/core.py b/py/h2o_wave/core.py index 8ab24e7f80..c96ba1e588 100644 --- a/py/h2o_wave/core.py +++ b/py/h2o_wave/core.py @@ -719,9 +719,11 @@ def download(self, url: str, path: str) -> str: # If path is a directory, get basename from url filepath = os.path.join(path, os.path.basename(url)) if os.path.isdir(path) else path - with open(filepath, 'wb') as f: - with self._http.stream('GET', f'{_config.hub_host_address}{url}') as r: - for chunk in r.iter_bytes(): + with self._http.stream('GET', f'{_config.hub_host_address}{url}') as res: + if res.status_code != 200: + raise ServiceError(f'Download failed (code={res.status_code}): {res.text}') + with open(filepath, 'wb') as f: + for chunk in res.iter_bytes(): f.write(chunk) return filepath From 13ee23ff8fca2c7856501e97c26c5432f1bcd9eb Mon Sep 17 00:00:00 2001 From: Martin Turoci Date: Thu, 22 Sep 2022 11:08:16 +0200 Subject: [PATCH 3/7] feat: Add forgotten stream read. #1627 --- py/h2o_wave/core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/py/h2o_wave/core.py b/py/h2o_wave/core.py index c96ba1e588..ce9400e83d 100644 --- a/py/h2o_wave/core.py +++ b/py/h2o_wave/core.py @@ -721,6 +721,7 @@ def download(self, url: str, path: str) -> str: with self._http.stream('GET', f'{_config.hub_host_address}{url}') as res: if res.status_code != 200: + res.read() raise ServiceError(f'Download failed (code={res.status_code}): {res.text}') with open(filepath, 'wb') as f: for chunk in res.iter_bytes(): From 4d5b23bf09e76123df064a17f4ea9021196fd76d Mon Sep 17 00:00:00 2001 From: Martin Turoci Date: Thu, 22 Sep 2022 11:28:28 +0200 Subject: [PATCH 4/7] test: Fix Unit tests. #1627 --- py/tests/test_python_server.py | 2 +- py/tests/test_python_server_async.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/py/tests/test_python_server.py b/py/tests/test_python_server.py index 4ad491141f..8aa5bb1af7 100644 --- a/py/tests/test_python_server.py +++ b/py/tests/test_python_server.py @@ -383,7 +383,7 @@ def test_multipart_server(self): def test_upload_dir(self): upload_path, = site.upload_dir(os.path.join('tests', 'test_folder')) - download_path = site.download(f'{base_url}{upload_path}test.txt', 'test.txt') + download_path = site.download(f'{upload_path}/test.txt', 'test.txt') txt = read_file(download_path) os.remove(download_path) assert len(txt) > 0 diff --git a/py/tests/test_python_server_async.py b/py/tests/test_python_server_async.py index 1f13ad9d10..9a82db62ec 100644 --- a/py/tests/test_python_server_async.py +++ b/py/tests/test_python_server_async.py @@ -68,8 +68,7 @@ async def test_multipart_server(self): async def test_upload_dir(self): upload_path, = await self.site.upload_dir(os.path.join('tests', 'test_folder')) - base_url = os.getenv('H2O_WAVE_BASE_URL', '/') - download_path = await self.site.download(f'{base_url}{upload_path}test.txt', 'test.txt') + download_path = await self.site.download(f'{upload_path}/test.txt', 'test.txt') txt = read_file(download_path) os.remove(download_path) assert len(txt) > 0 From c73d004e62728761e9f6bb871a26202198782fd0 Mon Sep 17 00:00:00 2001 From: Martin Turoci Date: Thu, 22 Sep 2022 11:31:18 +0200 Subject: [PATCH 5/7] chore: Add cleanup TODOs #1627 --- py/tests/test_python_server.py | 2 ++ py/tests/test_python_server_async.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/py/tests/test_python_server.py b/py/tests/test_python_server.py index 8aa5bb1af7..4cebc90270 100644 --- a/py/tests/test_python_server.py +++ b/py/tests/test_python_server.py @@ -22,6 +22,8 @@ base_url = os.getenv('H2O_WAVE_BASE_URL', '/') + +# TODO: Add cleanup (site.unload) to tests that upload files. class TestPythonServer(unittest.TestCase): def test_new_empty_card(self): page = site['/test'] diff --git a/py/tests/test_python_server_async.py b/py/tests/test_python_server_async.py index 9a82db62ec..4c7be5bf97 100644 --- a/py/tests/test_python_server_async.py +++ b/py/tests/test_python_server_async.py @@ -19,7 +19,7 @@ from .utils import read_file - +# TODO: Add cleanup (site.unload) to tests that upload files. class TestPythonServerAsync(unittest.IsolatedAsyncioTestCase): def __init__(self, methodName: str = ...) -> None: super().__init__(methodName) From eafccd36a91595d2b75b16b91acf625a100b51f7 Mon Sep 17 00:00:00 2001 From: Martin Turoci Date: Mon, 28 Nov 2022 13:54:15 +0100 Subject: [PATCH 6/7] feat: Allow extensionless file downloads. #1627 --- file_server.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/file_server.go b/file_server.go index 1c7e39a157..d950544796 100644 --- a/file_server.go +++ b/file_server.go @@ -69,14 +69,16 @@ func (fs *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - if path.Ext(r.URL.Path) == "" { // ignore requests for directories and ext-less files + trimmedPrefix := strings.TrimPrefix(r.URL.Path, fs.baseURL) + // Ignore requests for directories and non-existent / unaccessible files. + if fileInfo, err := os.Stat(filepath.Join(fs.dir, trimmedPrefix)); err != nil || fileInfo.IsDir() { echo(Log{"t": "file_download", "path": r.URL.Path, "error": "not found"}) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } echo(Log{"t": "file_download", "path": r.URL.Path}) - r.URL.Path = strings.TrimPrefix(r.URL.Path, fs.baseURL) // public + r.URL.Path = trimmedPrefix // public fs.handler.ServeHTTP(w, r) case http.MethodPost: From f3c195597fd483a474eab214f8a77d3381ff4ad3 Mon Sep 17 00:00:00 2001 From: Martin Turoci Date: Mon, 28 Nov 2022 14:11:58 +0100 Subject: [PATCH 7/7] feat: Make download paths work cross-platform. #1627 --- file_server.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/file_server.go b/file_server.go index d950544796..f948e0ac7f 100644 --- a/file_server.go +++ b/file_server.go @@ -70,8 +70,9 @@ func (fs *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { } trimmedPrefix := strings.TrimPrefix(r.URL.Path, fs.baseURL) + fsDirPath := path.Join(fs.dir, trimmedPrefix) // Ignore requests for directories and non-existent / unaccessible files. - if fileInfo, err := os.Stat(filepath.Join(fs.dir, trimmedPrefix)); err != nil || fileInfo.IsDir() { + if fileInfo, err := os.Stat(filepath.FromSlash(fsDirPath)); err != nil || fileInfo.IsDir() { echo(Log{"t": "file_download", "path": r.URL.Path, "error": "not found"}) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return