doclib_upload_route.py 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
  1. """在 FastAPI 解析 multipart 前限制文档请求流,保护临时磁盘。
  2. 认证仍由 get_current_user 执行;这里只对缺少 Bearer 的请求提前拒绝。
  3. MultiPartException 让 Starlette 关闭已创建的临时文件,再统一转为 413。
  4. """
  5. from fastapi import HTTPException
  6. from fastapi.routing import APIRoute
  7. from starlette.exceptions import HTTPException as StarletteHTTPException
  8. from starlette.formparsers import MultiPartException
  9. from starlette.requests import Request
  10. from app.core.config import settings
  11. class DoclibRoute(APIRoute):
  12. def get_route_handler(self):
  13. handler = super().get_route_handler()
  14. if not self.path.endswith('/web/doclib/upload'):
  15. return handler
  16. async def limited_upload(request: Request):
  17. authorization = request.headers.get('authorization', '')
  18. if not authorization.lower().startswith('bearer ') or not authorization[7:].strip():
  19. raise HTTPException(401, '请先登录', headers={'WWW-Authenticate': 'Bearer'})
  20. if 'multipart/form-data' not in request.headers.get('content-type', '').lower():
  21. raise HTTPException(415, '文档上传仅支持 multipart/form-data')
  22. # 总请求体允许 64 KiB 的 multipart 边界/字段开销;文件本体仍精确校验。
  23. maximum = settings.DOCLIB_MAX_UPLOAD_BYTES + 64 * 1024
  24. length = request.headers.get('content-length')
  25. if length:
  26. try:
  27. declared = int(length)
  28. except ValueError:
  29. raise HTTPException(400, '无效 Content-Length')
  30. if declared < 0:
  31. raise HTTPException(400, '无效 Content-Length')
  32. if declared > maximum:
  33. raise HTTPException(413, '上传内容超过文档大小限制')
  34. received = 0
  35. exceeded = False
  36. async def receive():
  37. nonlocal received, exceeded
  38. message = await request.receive()
  39. if message['type'] == 'http.request':
  40. received += len(message.get('body', b''))
  41. if received > maximum:
  42. exceeded = True
  43. raise MultiPartException('上传内容超过文档大小限制')
  44. return message
  45. bounded = Request(request.scope, receive)
  46. try:
  47. return await handler(bounded)
  48. except StarletteHTTPException:
  49. if exceeded:
  50. raise HTTPException(413, '上传内容超过文档大小限制')
  51. raise
  52. return limited_upload