Hi Sergey,
I believe the static files that are only read should be placed in the static folder. Once you define static folder in the __init__.py you can access static files. I tried your Range request test with curl and it seems like Range requests are indeed not supported by default in flask.
When I try to request even simple png file with Range I get 416 error:
curl -ik -L http://127.0.0.1/flask_test/static/files.png -H "Range: bytes=0-15,1000-1015" --output -
resulting in:
HTTP/1.1 416 REQUESTED RANGE NOT SATISFIABLE
Date: Tue, 15 Apr 2025 08:57:21 GMT
Server: Apache/2.4.58 (Ubuntu)
Content-Range: bytes */86020
Content-Length: 177
Content-Type: text/html; charset=utf-8
<!doctype html>
<html lang=en>
<title>416 Requested Range Not Satisfiable</title>
<h1>Requested Range Not Satisfiable</h1>
<p>The server cannot provide the requested range.</p>
That’s why I tried to manually pipe files (root, png) through /files route with explicitly allowing for Range requests, modyfying __init__.py file:
from flask import Flask, request, render_template, Response, send_file, send_from_directory
from pathlib import Path
import os, re
app = Flask(__name__,
template_folder=f"{Path(__file__).parent.parent.absolute()}/templates",
static_folder=f"{Path(__file__).parent.parent.absolute()}/static" )
app.debug = True
@app.route("/", methods=["GET"])
def root_page():
return render_template("index.html")
def send_file_partial(file_path):
"""
Serve a file with support for HTTP Range Requests.
"""
range_header = request.headers.get('Range', None)
if not range_header:
# If no Range header is present, serve the entire file
return send_file(file_path)
# Get the file size
file_size = os.path.getsize(file_path)
# Parse the Range header
byte1, byte2 = 0, None
match = re.match(r"bytes=(\d+)-(\d*)", range_header)
if match:
byte1 = int(match.group(1))
if match.group(2):
byte2 = int(match.group(2))
# Ensure byte2 is within the file size
byte2 = byte2 if byte2 is not None else file_size - 1
# Validate the range
if byte1 >= file_size or byte2 >= file_size:
return Response(status=416, headers={"Content-Range": f"bytes */{file_size}"})
# Calculate the length of the requested range
length = byte2 - byte1 + 1
# Open the file and read the requested range
with open(file_path, "rb") as f:
f.seek(byte1)
data = f.read(length)
# Create the response with the requested range
response = Response(data, status=206, mimetype="application/octet-stream")
response.headers.add("Content-Range", f"bytes {byte1}-{byte2}/{file_size}")
response.headers.add("Accept-Ranges", "bytes")
response.headers.add("Content-Length", str(length))
return response
@app.route('/files/<path:filename>')
def serve_file(filename):
"""
Serve .root files with range support.
"""
file_path = os.path.join(app.static_folder, filename) # Adjust the path to your files
if not os.path.exists(file_path):
return Response("File not found", status=404)
return send_file_partial(file_path)
However this removes 416 error and now allow for range requests, it still does not work properly with JSROOT. Can this be caused by JSROOT which still evaluates the request as non-multirange and breaks it to small pieces in the request? Now the range request works fine but looks like JSROOT asks for just single small ranges…
Thanks,
Dominik