Skip to content

Commit e8049bf

Browse files
Merge pull request #1518 from roboflow/fix/issue-with-legacy-request-handler
Fix bug with improper parsing multipart-request causing reading the request body stream twice
2 parents c76f59e + 4379548 commit e8049bf

File tree

3 files changed

+40
-25
lines changed

3 files changed

+40
-25
lines changed
Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,26 @@
1-
from typing import Optional
1+
from typing import Optional, Union
22

33
from fastapi import Request
4+
from starlette.datastructures import UploadFile
45

6+
from inference.core.exceptions import InputImageLoadError
57

6-
async def request_body_content_only_to_be_used_in_legacy_request_handler(
8+
9+
async def parse_body_content_for_legacy_request_handler(
710
request: Request,
8-
) -> Optional[bytes]:
11+
) -> Optional[Union[bytes, UploadFile]]:
912
content_type = request.headers.get("Content-Type")
13+
if content_type is None:
14+
return None
1015
image_reference_in_query = request.query_params.get("image")
11-
if (
12-
content_type is None
13-
or "multipart/form-data" in content_type
14-
or image_reference_in_query
15-
):
16+
if "multipart/form-data" in content_type:
17+
form_data = await request.form()
18+
if "file" not in form_data:
19+
raise InputImageLoadError(
20+
message="Expected image to be send in part named 'file' of multipart/form-data request",
21+
public_message="Expected image to be send in part named 'file' of multipart/form-data request",
22+
)
23+
return form_data["file"]
24+
if content_type is None or image_reference_in_query:
1625
return None
1726
return await request.body()

inference/core/interfaces/http/http_api.py

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
Path,
1919
Query,
2020
Request,
21-
UploadFile,
2221
)
2322
from fastapi.responses import JSONResponse, RedirectResponse, Response
2423
from fastapi.staticfiles import StaticFiles
2524
from fastapi_cprofile.profiler import CProfileMiddleware
25+
from starlette.datastructures import UploadFile
2626
from starlette.middleware.base import BaseHTTPMiddleware
2727

2828
from inference.core import logger
@@ -161,12 +161,13 @@
161161
from inference.core.exceptions import (
162162
ContentTypeInvalid,
163163
ContentTypeMissing,
164+
InputImageLoadError,
164165
MissingServiceSecretError,
165166
RoboflowAPINotAuthorizedError,
166167
)
167168
from inference.core.interfaces.base import BaseInterface
168169
from inference.core.interfaces.http.dependencies import (
169-
request_body_content_only_to_be_used_in_legacy_request_handler,
170+
parse_body_content_for_legacy_request_handler,
170171
)
171172
from inference.core.interfaces.http.error_handlers import (
172173
with_route_exceptions,
@@ -2395,10 +2396,8 @@ def legacy_infer_from_request(
23952396
background_tasks: BackgroundTasks,
23962397
request: Request,
23972398
request_body: Annotated[
2398-
Optional[bytes],
2399-
Depends(
2400-
request_body_content_only_to_be_used_in_legacy_request_handler
2401-
),
2399+
Optional[Union[bytes, UploadFile]],
2400+
Depends(parse_body_content_for_legacy_request_handler),
24022401
],
24032402
dataset_id: str = Path(
24042403
description="ID of a Roboflow dataset corresponding to the model to use for inference OR workspace ID"
@@ -2492,7 +2491,6 @@ def legacy_infer_from_request(
24922491
"external",
24932492
description="The detailed source information of the inference request",
24942493
),
2495-
file: Optional[UploadFile] = None,
24962494
):
24972495
"""
24982496
Legacy inference endpoint for object detection, instance segmentation, and classification.
@@ -2511,36 +2509,35 @@ def legacy_infer_from_request(
25112509
f"Reached legacy route /:dataset_id/:version_id with {dataset_id}/{version_id}"
25122510
)
25132511
model_id = f"{dataset_id}/{version_id}"
2514-
25152512
if confidence >= 1:
25162513
confidence /= 100
25172514
elif confidence < 0.01:
25182515
confidence = 0.01
25192516

25202517
if overlap >= 1:
25212518
overlap /= 100
2522-
25232519
if image is not None:
25242520
request_image = InferenceRequestImage(type="url", value=image)
25252521
else:
25262522
if "Content-Type" not in request.headers:
25272523
raise ContentTypeMissing(
25282524
f"Request must include a Content-Type header"
25292525
)
2530-
if file is not None:
2531-
base64_image_str = file.file.read()
2526+
if isinstance(request_body, UploadFile):
2527+
base64_image_str = request_body.file.read()
25322528
base64_image_str = base64.b64encode(base64_image_str)
25332529
request_image = InferenceRequestImage(
25342530
type="base64", value=base64_image_str.decode("ascii")
25352531
)
2536-
elif (
2537-
"application/x-www-form-urlencoded"
2538-
in request.headers["Content-Type"]
2539-
or "application/json" in request.headers["Content-Type"]
2540-
):
2532+
elif isinstance(request_body, bytes):
25412533
request_image = InferenceRequestImage(
25422534
type=image_type, value=request_body
25432535
)
2536+
elif request_body is None:
2537+
raise InputImageLoadError(
2538+
message="Image not found in request body.",
2539+
public_message="Image not found in request body.",
2540+
)
25442541
else:
25452542
raise ContentTypeInvalid(
25462543
f"Invalid Content-Type: {request.headers['Content-Type']}"
@@ -2701,6 +2698,15 @@ def model_add_legacy(
27012698
async def dashboard_guard():
27022699
return Response(status_code=404)
27032700

2701+
@app.exception_handler(InputImageLoadError)
2702+
async def unicorn_exception_handler(request: Request, exc: InputImageLoadError):
2703+
return JSONResponse(
2704+
status_code=400,
2705+
content={
2706+
"message": f"Could not load input image. Cause: {exc.get_public_error_details()}"
2707+
},
2708+
)
2709+
27042710
app.mount(
27052711
"/",
27062712
StaticFiles(directory="./inference/landing/out", html=True),

inference/core/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "0.54.0"
1+
__version__ = "0.54.1"
22

33

44
if __name__ == "__main__":

0 commit comments

Comments
 (0)