@@ -360,25 +360,14 @@ async def stream_file(self, root: str, rel: str, range_header: str | None):
360360 if tr :
361361 url = tr
362362 dl_headers = self ._download_headers ()
363-
364- # 预获取大小/是否支持范围
365- total_size : Optional [int ] = None
366- async with httpx .AsyncClient (timeout = self ._timeout , follow_redirects = True ) as client :
367- try :
368- head_resp = await client .head (url , headers = dl_headers )
369- if head_resp .status_code == 200 :
370- cl = head_resp .headers .get ("Content-Length" )
371- if cl and cl .isdigit ():
372- total_size = int (cl )
373- except Exception :
374- pass
363+ file_size = int (it .get ("size" ) or 0 )
375364
376365 mime , _ = mimetypes .guess_type (rel )
377366 content_type = mime or "application/octet-stream"
378367
379368 # 解析 Range
380369 start = 0
381- end : Optional [int ] = None
370+ end : Optional [int ] = file_size - 1 if file_size > 0 else None
382371 status_code = 200
383372 if range_header and range_header .startswith ("bytes=" ):
384373 status_code = 206
@@ -388,35 +377,65 @@ async def stream_file(self, root: str, rel: str, range_header: str | None):
388377 start = int (s )
389378 if e .strip ():
390379 end = int (e )
391-
392- if total_size is not None and end is None and status_code == 206 :
393- end = total_size - 1
394- if end is not None and total_size is not None and end >= total_size :
395- end = total_size - 1
396- if total_size is not None and start >= total_size :
380+ elif file_size > 0 :
381+ end = file_size - 1
382+ if file_size > 0 :
383+ if start >= file_size :
384+ raise HTTPException (416 , detail = "Requested Range Not Satisfiable" )
385+ if end is None or end >= file_size :
386+ end = file_size - 1
387+ if start > end :
388+ raise HTTPException (416 , detail = "Requested Range Not Satisfiable" )
389+ headers = dict (dl_headers )
390+ if status_code == 206 :
391+ headers ["Range" ] = f"bytes={ start } -" if end is None else f"bytes={ start } -{ end } "
392+
393+ client = httpx .AsyncClient (timeout = None , follow_redirects = True )
394+ req = client .build_request ("GET" , url , headers = headers )
395+ resp = await client .send (req , stream = True )
396+ if resp .status_code == 404 :
397+ await resp .aclose ()
398+ await client .aclose ()
399+ raise FileNotFoundError (rel )
400+ if resp .status_code == 416 :
401+ await resp .aclose ()
402+ await client .aclose ()
397403 raise HTTPException (416 , detail = "Requested Range Not Satisfiable" )
404+ try :
405+ resp .raise_for_status ()
406+ except Exception :
407+ await resp .aclose ()
408+ await client .aclose ()
409+ raise
398410
399- resp_headers : Dict [str , str ] = {"Accept-Ranges" : "bytes" , "Content-Type" : content_type }
400- if status_code == 206 and total_size is not None and end is not None :
401- resp_headers ["Content-Range" ] = f"bytes { start } -{ end } /{ total_size } "
402- resp_headers ["Content-Length" ] = str (end - start + 1 )
403- elif total_size is not None :
404- resp_headers ["Content-Length" ] = str (total_size )
411+ resp_headers : Dict [str , str ] = {
412+ "Accept-Ranges" : resp .headers .get ("Accept-Ranges" , "bytes" ),
413+ "Content-Type" : resp .headers .get ("Content-Type" , content_type ),
414+ }
415+ content_range = resp .headers .get ("Content-Range" )
416+ content_length = resp .headers .get ("Content-Length" )
417+ if content_range :
418+ resp_headers ["Content-Range" ] = content_range
419+ elif status_code == 206 and file_size > 0 and end is not None :
420+ resp_headers ["Content-Range" ] = f"bytes { start } -{ end } /{ file_size } "
421+ if content_length :
422+ resp_headers ["Content-Length" ] = content_length
423+ elif file_size > 0 :
424+ if status_code == 206 and end is not None :
425+ resp_headers ["Content-Length" ] = str (end - start + 1 )
426+ elif resp .status_code == 200 :
427+ resp_headers ["Content-Length" ] = str (file_size )
405428
406429 async def iterator ():
407- headers = dict (dl_headers )
408- if status_code == 206 and end is not None :
409- headers ["Range" ] = f"bytes={ start } -{ end } "
410- async with httpx .AsyncClient (timeout = None , follow_redirects = True ) as client :
411- async with client .stream ("GET" , url , headers = headers ) as resp :
412- if resp .status_code in (404 , 416 ):
413- await resp .aclose ()
414- raise HTTPException (resp .status_code , detail = "Upstream not available" )
415- async for chunk in resp .aiter_bytes ():
416- if chunk :
417- yield chunk
418-
419- return StreamingResponse (iterator (), status_code = status_code , headers = resp_headers , media_type = content_type )
430+ try :
431+ async for chunk in resp .aiter_bytes ():
432+ if chunk :
433+ yield chunk
434+ finally :
435+ await resp .aclose ()
436+ await client .aclose ()
437+
438+ return StreamingResponse (iterator (), status_code = resp .status_code , headers = resp_headers , media_type = content_type )
420439
421440 # -----------------
422441 # 上传(大文件分片)
0 commit comments