From f213f7e349aa3fd3104b3139aabbfe5df1523f54 Mon Sep 17 00:00:00 2001 From: Julien Castiaux Date: Wed, 3 Aug 2022 16:05:18 +0000 Subject: [PATCH] [IMP] core: support partial http response An HTTP Partial Request is a regular GET request with a Range header that indicate what chunk of the data the browser wishes to download. It is useful to peek in a video stream or to resume an interrupted download. The server can respond with a 206 - Partial Content response, set the appropriate Content-Length and Content-Range headers and only send a chunk of the data in the body. Werkzeug's `send_file`, the library function we use to stream files over http, is fully compliant with partial requests and responses. It understands the Range request header and create the corresponding response. NGINX does also comply with the Range header even via X-Accel-Redirect. closes odoo/odoo#97422 Signed-off-by: Julien Castiaux --- odoo/addons/test_http/tests/test_static.py | 23 ++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/odoo/addons/test_http/tests/test_static.py b/odoo/addons/test_http/tests/test_static.py index ee40085f410..c8f31a8e709 100644 --- a/odoo/addons/test_http/tests/test_static.py +++ b/odoo/addons/test_http/tests/test_static.py @@ -25,9 +25,9 @@ class TestHttpStaticCommon(TestHttpBase): cls.placeholder_data = file.read() def assertDownload( - self, url, assert_status_code, assert_headers, assert_content=None + self, url, headers, assert_status_code, assert_headers, assert_content=None ): - res = self.db_url_open(url) + res = self.db_url_open(url, headers=headers) res.raise_for_status() self.assertEqual(res.status_code, assert_status_code) for header_name, header_value in assert_headers.items(): @@ -49,7 +49,7 @@ class TestHttpStaticCommon(TestHttpBase): headers['X-Accel-Redirect'] = f'/web/filestore/{self.cr.dbname}/{sha[:2]}/{sha}' headers['Content-Length'] = '0' - return self.assertDownload(url, 200, headers, b'' if x_sendfile else self.gizeh_data) + return self.assertDownload(url, {}, 200, headers, b'' if x_sendfile else self.gizeh_data) def assertDownloadPlaceholder(self, url): headers = { @@ -57,7 +57,7 @@ class TestHttpStaticCommon(TestHttpBase): 'Content-Type': 'image/png', 'Content-Disposition': 'inline; filename=placeholder.png' } - return self.assertDownload(url, 200, headers, self.placeholder_data) + return self.assertDownload(url, {}, 200, headers, self.placeholder_data) @tagged('post_install', '-at_install') @@ -248,6 +248,21 @@ class TestHttpStatic(TestHttpStaticCommon): res = self.url_open('/web/image/idontexist?download=True') self.assertEqual(res.status_code, 404) + def test_static15_range(self): + self.assertDownload( + url='/web/content/test_http.gizeh_png', + headers={'Range': 'bytes=100-199'}, + + assert_status_code=206, + assert_headers={ + 'Content-Length': '100', + 'Content-Range': 'bytes 100-199/814', + 'Content-Type': 'image/png', + 'Content-Disposition': 'inline; filename=gizeh.png', + }, + assert_content=self.gizeh_data[100:200] + ) + class TestHttpStaticCache(TestHttpStaticCommon): @freeze_time(datetime.utcnow())