Skip to content

Commit

Permalink
fix(falcon): Don't exhaust request body stream
Browse files Browse the repository at this point in the history
  • Loading branch information
szokeasaurusrex committed Nov 13, 2024
1 parent 417be9f commit da1b4fd
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 21 deletions.
43 changes: 22 additions & 21 deletions sentry_sdk/integrations/falcon.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@
FALCON3 = False


_FALCON_UNSET = None # type: Optional[object]
with capture_internal_exceptions():
from falcon._typing import _UNSET as _FALCON_UNSET # type: ignore[import-not-found, no-redef]


class FalconRequestExtractor(RequestExtractor):
def env(self):
# type: () -> Dict[str, Any]
Expand Down Expand Up @@ -73,27 +78,23 @@ def raw_data(self):
else:
return None

if FALCON3:

def json(self):
# type: () -> Optional[Dict[str, Any]]
try:
return self.request.media
except falcon.errors.HTTPBadRequest:
return None

else:

def json(self):
# type: () -> Optional[Dict[str, Any]]
try:
return self.request.media
except falcon.errors.HTTPBadRequest:
# NOTE(jmagnusson): We return `falcon.Request._media` here because
# falcon 1.4 doesn't do proper type checking in
# `falcon.Request.media`. This has been fixed in 2.0.
# Relevant code: https://github.com/falconry/falcon/blob/1.4.1/falcon/request.py#L953
return self.request._media
def json(self):
# type: () -> Optional[Dict[str, Any]]
if _FALCON_UNSET is not None:
cached_media = None
with capture_internal_exceptions():
# self.request._media is the cached self.request.media
# value. It is only available if self.request.media
# has already been accessed. Therefore, reading
# self.request._media will not exhaust the raw request
# stream (self.request.bounded_stream) because it has
# already been read if self.request._media is set.
cached_media = self.request._media

if cached_media is not _FALCON_UNSET:
return cached_media

return None


class SentryFalconMiddleware:
Expand Down
41 changes: 41 additions & 0 deletions tests/integrations/falcon/test_falcon.py
Original file line number Diff line number Diff line change
Expand Up @@ -460,3 +460,44 @@ def test_span_origin(sentry_init, capture_events, make_client):
(_, event) = events

assert event["contexts"]["trace"]["origin"] == "auto.http.falcon"


def test_falcon_request_media(sentry_init, make_app):
# test_passed stores whether the test has passed.
test_passed = False

# test_failure_reason stores the reason why the test failed
# if test_passed is False. The value is meaningless when
# test_passed is True.
test_failure_reason = "test endpoint did not get called"

class SentryCaptureMiddleware:
def process_request(self, _req, _resp):
# This capture message forces Falcon event processors to run
# before the request handler runs
sentry_sdk.capture_message("Processing request")

class RequestMediaResource:
def on_post(self, req, _):
nonlocal test_passed, test_failure_reason
raw_data = req.bounded_stream.read()

# If the raw_data is empty, the request body stream
# has been exhausted by the SDK. Test should fail in
# this case.
test_passed = raw_data != b""
test_failure_reason = "request body has been read"

sentry_init(integrations=[FalconIntegration()])

app = make_app()
app.add_middleware(SentryCaptureMiddleware())
app.add_route("/read_body", RequestMediaResource())

client = falcon.testing.TestClient(app)

client.simulate_post("/read_body", json={"foo": "bar"})

# Check that simulate_post actually calls the resource, and
# that the SDK does not exhaust the request body stream.
assert test_passed, test_failure_reason

0 comments on commit da1b4fd

Please sign in to comment.