-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Add BufferedProtocol support #2033
Changes from all commits
607b47a
0f4c045
3284f97
9bbf2a7
8d25058
12d15dc
4f79f31
6cd7673
9e8520a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,7 +26,7 @@ | |
import socket | ||
import stat | ||
|
||
from asyncio import CancelledError | ||
from asyncio import BufferedProtocol, CancelledError | ||
from asyncio.transports import Transport | ||
from functools import partial | ||
from inspect import isawaitable | ||
|
@@ -102,7 +102,7 @@ def __init__(self, transport: TransportProtocol, unix=None): | |
self.client_port = addr[1] | ||
|
||
|
||
class HttpProtocol(asyncio.Protocol): | ||
class HttpProtocol(BufferedProtocol): | ||
""" | ||
This class provides a basic HTTP implementation of the sanic framework. | ||
""" | ||
|
@@ -132,6 +132,7 @@ class HttpProtocol(asyncio.Protocol): | |
# connection management | ||
"state", | ||
"url", | ||
"_buffer", | ||
"_handler_task", | ||
"_can_write", | ||
"_data_received", | ||
|
@@ -140,6 +141,7 @@ class HttpProtocol(asyncio.Protocol): | |
"_http", | ||
"_exception", | ||
"recv_buffer", | ||
"_buffer", | ||
"_unix", | ||
) | ||
|
||
|
@@ -301,6 +303,9 @@ def connection_made(self, transport): | |
self.transport = transport | ||
self._task = self.loop.create_task(self.connection_task()) | ||
self.recv_buffer = bytearray() | ||
self._buffer = memoryview( | ||
bytearray(self.app.config.REQUEST_BUFFER_SIZE) | ||
) | ||
Comment on lines
+306
to
+308
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a waste of RAM. The buffer should be kept as small as possible, not always the maximum permitted size. This makes a difference when handling a lot of parallel requests. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure where this value is coming from, but it is the size limit that
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The sizehint is hardcoded in libuv (and probably in a similar manner in plain asyncio). We can safely ignore the hint because libuv will then simply call |
||
self.conn_info = ConnInfo(self.transport, unix=self._unix) | ||
except Exception: | ||
error_logger.exception("protocol.connect_made") | ||
|
@@ -320,23 +325,26 @@ def pause_writing(self): | |
def resume_writing(self): | ||
self._can_write.set() | ||
|
||
def data_received(self, data: bytes): | ||
try: | ||
self._time = current_time() | ||
if not data: | ||
return self.close() | ||
self.recv_buffer += data | ||
|
||
if ( | ||
len(self.recv_buffer) > self.app.config.REQUEST_BUFFER_SIZE | ||
and self.transport | ||
): | ||
self.transport.pause_reading() | ||
def get_buffer(self, sizehint=-1): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are ignoring it here. |
||
return self._buffer | ||
|
||
if self._data_received: | ||
self._data_received.set() | ||
except Exception: | ||
error_logger.exception("protocol.data_received") | ||
def buffer_updated(self, nbytes: int) -> None: | ||
data = self._buffer[:nbytes] | ||
|
||
self._time = current_time() | ||
self.recv_buffer += data | ||
Comment on lines
+331
to
+335
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. However, since we are concurrently receiving more data (so cannot alter the buffer) and handling a request (which needs to consume bytes from buffer), this cannot easily work. Which begs the question: why use BufferedProtocol in the first place? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this was just my proof of concept to see if the BufferedProtocol would play more nicely with streaming requests. We probably need to consume the data somehow from the object returned by I am thinking perhaps what we might want to do is somehow sync on the |
||
|
||
if ( | ||
len(self.recv_buffer) > self.app.config.REQUEST_BUFFER_SIZE | ||
and self.transport | ||
): | ||
self.transport.pause_reading() | ||
|
||
if self._data_received: | ||
self._data_received.set() | ||
|
||
def eof_received(self): | ||
return self.close() | ||
|
||
|
||
def trigger_events(events: Optional[Iterable[Callable[..., Any]]], loop): | ||
|
@@ -466,7 +474,7 @@ def serve( | |
unix: Optional[str] = None, | ||
reuse_port: bool = False, | ||
loop=None, | ||
protocol: Type[asyncio.Protocol] = HttpProtocol, | ||
protocol: Type[HttpProtocol] = HttpProtocol, | ||
backlog: int = 100, | ||
register_sys_signals: bool = True, | ||
run_multiple: bool = False, | ||
|
@@ -618,7 +626,7 @@ def serve( | |
|
||
|
||
def _build_protocol_kwargs( | ||
protocol: Type[asyncio.Protocol], config: Config | ||
protocol: Type[HttpProtocol], config: Config | ||
) -> Dict[str, Union[int, float]]: | ||
if hasattr(protocol, "websocket_handshake"): | ||
return { | ||
|
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.
This is still incorrect, and in general you should not recreate the buffer. Use
del self.recv_buffer[:2]
or so to keep the existing buffer but to consume the data that you want removed (note: as discussed earlier, removing allor the first twobytes heremight beis invalid anyway).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 still am not sure I see how. If we do not, and even if we account for the http loop, you end up with
\r\n
as the first two bytes on the next request.