-
Notifications
You must be signed in to change notification settings - Fork 1
Read request bodies lazily, fix wsgi performance regressions #63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,36 +1,82 @@ | ||
| """Utility functions for fastly_compute package.""" | ||
|
|
||
| from io import BufferedReader, RawIOBase | ||
|
|
||
| from wit_world.imports import async_io, http_body | ||
|
|
||
| from fastly_compute.exceptions.types.error import Error | ||
| from fastly_compute.requests.exceptions import RequestException | ||
|
|
||
| class _RawBodyReader(RawIOBase): | ||
| """Raw I/O implementation that reads from a Fastly HTTP body. | ||
|
|
||
| This class provides a streaming interface to read from Fastly HTTP bodies | ||
| without buffering the entire body in memory upfront. | ||
| """ | ||
|
|
||
| def __init__(self, body_handle: async_io.Pollable, chunk_size: int = 4096): | ||
| """Initialize the reader. | ||
|
|
||
| :param body_handle: Fastly HTTP body handle to read from | ||
| :param chunk_size: Size of chunks to read at a time (default: 4096) | ||
| """ | ||
| self._body = body_handle | ||
| self._chunk_size = chunk_size | ||
| self._closed = False | ||
|
|
||
| def readable(self) -> bool: | ||
| """Return whether the stream is readable. | ||
|
|
||
| :return: True if the stream is readable, False otherwise | ||
| """ | ||
| return not self._closed | ||
|
|
||
| def readinto(self, b) -> int: | ||
| """Read up to len(b) bytes into writable buffer b. | ||
|
|
||
| :param b: Writable buffer to read into | ||
| :return: Number of bytes read, or 0 on EOF | ||
| """ | ||
| if self._closed: | ||
| return 0 | ||
|
|
||
| # Read a chunk from host into provided buffer | ||
| chunk_size = min(len(b), self._chunk_size) | ||
| chunk = http_body.read(self._body, chunk_size) | ||
|
|
||
| if not chunk: | ||
| self._closed = True | ||
| return 0 | ||
|
|
||
| # Copy chunk into the provided buffer | ||
| n = len(chunk) | ||
| b[:n] = chunk | ||
| return n | ||
|
|
||
|
|
||
| def create_body_reader( | ||
| body: async_io.Pollable, chunk_size: int = 4096 | ||
| ) -> BufferedReader: | ||
| """Create a file-like reader for streaming a Fastly HTTP body. | ||
|
|
||
| This function returns a BufferedReader that streams the body on-demand, | ||
| avoiding the need to buffer the entire body in memory. The returned reader | ||
| is compatible with the WSGI InputStream Protocol (PEP 3333). | ||
|
|
||
| def read_response_body( | ||
| response_body: async_io.Pollable, chunk_size: int = 4096 | ||
| ) -> bytes: | ||
| """Read the complete response body from a WIT response body object. | ||
| The returned reader supports all standard file operations: read(), readline(), | ||
| readlines(), and iteration. Note that seeking is not supported as the body | ||
| is a forward-only stream. | ||
|
|
||
| Args: | ||
| response_body: WIT response body object to read from | ||
| chunk_size: Size of chunks to read at a time (default: 4096) | ||
| :param body: Fastly HTTP body handle to read from | ||
| :param chunk_size: Size of chunks to read at a time (default: 4096) | ||
| :return: A BufferedReader that streams the body content (WSGI compatible) | ||
|
|
||
| Returns: | ||
| Complete response body as bytes | ||
| Example:: | ||
|
|
||
| Raises: | ||
| RequestException: If there is a problem reading the response body. | ||
| def handle(request, body): | ||
| reader = create_body_reader(body) | ||
| # Read entire body | ||
| full_body = reader.read() | ||
| # Or read line by line | ||
| for line in reader: | ||
| process(line) | ||
| """ | ||
| body_data: bytes = b"" | ||
| while True: | ||
| try: | ||
| chunk = http_body.read(response_body, chunk_size) | ||
| except Error as e: | ||
| raise RequestException.from_fastly_error(e, "http_body.read") from e | ||
|
|
||
| if len(chunk) == 0: | ||
| break | ||
| else: | ||
| body_data += chunk | ||
|
|
||
| return body_data | ||
| return BufferedReader(_RawBodyReader(body, chunk_size)) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I couldn't come up with a type for
beither. :-)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I was following a snippet that didn't have type annotations. Seems like maybe https://docs.python.org/3/library/collections.abc.html#collections.abc.Buffer might be the correct type for the protocol. I'm inclined to just leave it as this matches the definition provided by cpython internally (well, at least in https://github.com/python/cpython/blob/main/Lib/_pyio.py#L636).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Best not to prod the dragons. ;-)
Bufferwas my first choice as well, but it's notlen()-able.