fixed managment of http message queue on nfproxy

This commit is contained in:
Domingo Dirutigliano
2025-05-17 13:58:13 +02:00
parent d6a4fc1953
commit 5c46a9bf34

View File

@@ -1,7 +1,12 @@
import pyllhttp import pyllhttp
from firegex.nfproxy.internals.exceptions import NotReadyToRun from firegex.nfproxy.internals.exceptions import NotReadyToRun
from firegex.nfproxy.internals.data import DataStreamCtx from firegex.nfproxy.internals.data import DataStreamCtx
from firegex.nfproxy.internals.exceptions import StreamFullDrop, StreamFullReject, RejectConnection, DropPacket from firegex.nfproxy.internals.exceptions import (
StreamFullDrop,
StreamFullReject,
RejectConnection,
DropPacket,
)
from firegex.nfproxy.internals.models import FullStreamAction, ExceptionAction from firegex.nfproxy.internals.models import FullStreamAction, ExceptionAction
from dataclasses import dataclass, field from dataclasses import dataclass, field
from collections import deque from collections import deque
@@ -15,17 +20,21 @@ from websockets.frames import Frame
from websockets.extensions.permessage_deflate import PerMessageDeflate from websockets.extensions.permessage_deflate import PerMessageDeflate
from pyllhttp import PAUSED_H2_UPGRADE, PAUSED_UPGRADE from pyllhttp import PAUSED_H2_UPGRADE, PAUSED_UPGRADE
@dataclass @dataclass
class InternalHTTPMessage: class InternalHTTPMessage:
"""Internal class to handle HTTP messages""" """Internal class to handle HTTP messages"""
url: str|None = field(default=None)
url: str | None = field(default=None)
headers: dict[str, str] = field(default_factory=dict) headers: dict[str, str] = field(default_factory=dict)
lheaders: dict[str, str] = field(default_factory=dict) # lowercase copy of the headers lheaders: dict[str, str] = field(
body: bytes|None = field(default=None) default_factory=dict
) # lowercase copy of the headers
body: bytes | None = field(default=None)
body_decoded: bool = field(default=False) body_decoded: bool = field(default=False)
headers_complete: bool = field(default=False) headers_complete: bool = field(default=False)
message_complete: bool = field(default=False) message_complete: bool = field(default=False)
status: str|None = field(default=None) status: str | None = field(default=None)
total_size: int = field(default=0) total_size: int = field(default=0)
user_agent: str = field(default_factory=str) user_agent: str = field(default_factory=str)
content_encoding: str = field(default=str) content_encoding: str = field(default=str)
@@ -36,15 +45,17 @@ class InternalHTTPMessage:
method: str = field(default=str) method: str = field(default=str)
content_length: int = field(default=0) content_length: int = field(default=0)
stream: bytes = field(default_factory=bytes) stream: bytes = field(default_factory=bytes)
ws_stream: list[Frame] = field(default_factory=list) # Decoded websocket stream ws_stream: list[Frame] = field(default_factory=list) # Decoded websocket stream
upgrading_to_h2: bool = field(default=False) upgrading_to_h2: bool = field(default=False)
upgrading_to_ws: bool = field(default=False) upgrading_to_ws: bool = field(default=False)
@dataclass @dataclass
class InternalHttpBuffer: class InternalHttpBuffer:
"""Internal class to handle HTTP messages""" """Internal class to handle HTTP messages"""
_url_buffer: bytes = field(default_factory=bytes) _url_buffer: bytes = field(default_factory=bytes)
_raw_header_fields: dict[str, str|list[str]] = field(default_factory=dict) _raw_header_fields: dict[str, str | list[str]] = field(default_factory=dict)
_header_fields: dict[str, str] = field(default_factory=dict) _header_fields: dict[str, str] = field(default_factory=dict)
_body_buffer: bytes = field(default_factory=bytes) _body_buffer: bytes = field(default_factory=bytes)
_status_buffer: bytes = field(default_factory=bytes) _status_buffer: bytes = field(default_factory=bytes)
@@ -52,8 +63,8 @@ class InternalHttpBuffer:
_current_header_value: bytes = field(default_factory=bytes) _current_header_value: bytes = field(default_factory=bytes)
_ws_packet_stream: bytes = field(default_factory=bytes) _ws_packet_stream: bytes = field(default_factory=bytes)
class InternalCallbackHandler():
class InternalCallbackHandler:
buffers = InternalHttpBuffer() buffers = InternalHttpBuffer()
msg = InternalHTTPMessage() msg = InternalHTTPMessage()
save_body = True save_body = True
@@ -72,11 +83,11 @@ class InternalCallbackHandler():
self.buffers = InternalHttpBuffer() self.buffers = InternalHttpBuffer()
self.msg = InternalHTTPMessage() self.msg = InternalHTTPMessage()
self.has_begun = True self.has_begun = True
def on_url(self, url): def on_url(self, url):
self.buffers._url_buffer += url self.buffers._url_buffer += url
self.msg.total_size += len(url) self.msg.total_size += len(url)
def on_url_complete(self): def on_url_complete(self):
self.msg.url = self.buffers._url_buffer.decode(errors="ignore") self.msg.url = self.buffers._url_buffer.decode(errors="ignore")
self.buffers._url_buffer = b"" self.buffers._url_buffer = b""
@@ -84,45 +95,50 @@ class InternalCallbackHandler():
def on_status(self, status: bytes): def on_status(self, status: bytes):
self.msg.total_size += len(status) self.msg.total_size += len(status)
self.buffers._status_buffer += status self.buffers._status_buffer += status
def on_status_complete(self): def on_status_complete(self):
self.msg.status = self.buffers._status_buffer.decode(errors="ignore") self.msg.status = self.buffers._status_buffer.decode(errors="ignore")
self.buffers._status_buffer = b"" self.buffers._status_buffer = b""
def on_header_field(self, field): def on_header_field(self, field):
self.msg.total_size += len(field) self.msg.total_size += len(field)
self.buffers._current_header_field += field self.buffers._current_header_field += field
def on_header_field_complete(self): def on_header_field_complete(self):
pass # Nothing to do pass # Nothing to do
def on_header_value(self, value): def on_header_value(self, value):
self.msg.total_size += len(value) self.msg.total_size += len(value)
self.buffers._current_header_value += value self.buffers._current_header_value += value
def on_header_value_complete(self): def on_header_value_complete(self):
if self.buffers._current_header_field: if self.buffers._current_header_field:
k, v = self.buffers._current_header_field.decode(errors="ignore"), self.buffers._current_header_value.decode(errors="ignore") k, v = (
self.buffers._current_header_field.decode(errors="ignore"),
self.buffers._current_header_value.decode(errors="ignore"),
)
old_value = self.buffers._raw_header_fields.get(k, None) old_value = self.buffers._raw_header_fields.get(k, None)
# raw headers are stored as thay were, considering to check changes between headers encoding # raw headers are stored as thay were, considering to check changes between headers encoding
if isinstance(old_value, list): if isinstance(old_value, list):
old_value.append(v) old_value.append(v)
elif isinstance(old_value, str): elif isinstance(old_value, str):
self.buffers._raw_header_fields[k] = [old_value, v] self.buffers._raw_header_fields[k] = [old_value, v]
else: else:
self.buffers._raw_header_fields[k] = v self.buffers._raw_header_fields[k] = v
# Decoding headers normally # Decoding headers normally
kl = k.lower() kl = k.lower()
if kl in self.buffers._header_fields: if kl in self.buffers._header_fields:
self.buffers._header_fields[kl] += f", {v}" # Should be considered as a single list separated by commas as said in the RFC self.buffers._header_fields[kl] += (
f", {v}" # Should be considered as a single list separated by commas as said in the RFC
)
else: else:
self.buffers._header_fields[kl] = v self.buffers._header_fields[kl] = v
self.buffers._current_header_field = b"" self.buffers._current_header_field = b""
self.buffers._current_header_value = b"" self.buffers._current_header_value = b""
def on_headers_complete(self): def on_headers_complete(self):
self.msg.headers = self.buffers._raw_header_fields self.msg.headers = self.buffers._raw_header_fields
self.msg.lheaders = self.buffers._header_fields self.msg.lheaders = self.buffers._header_fields
@@ -170,7 +186,9 @@ class InternalCallbackHandler():
print(f"Error decompressing brotli: {e}: skipping", flush=True) print(f"Error decompressing brotli: {e}: skipping", flush=True)
decode_success = False decode_success = False
break break
elif enc == "gzip" or enc == "x-gzip": #https://datatracker.ietf.org/doc/html/rfc2616#section-3.5 elif (
enc == "gzip" or enc == "x-gzip"
): # https://datatracker.ietf.org/doc/html/rfc2616#section-3.5
try: try:
if "gzip" in self.content_encoding.lower(): if "gzip" in self.content_encoding.lower():
with gzip.GzipFile(fileobj=io.BytesIO(decoding_body)) as f: with gzip.GzipFile(fileobj=io.BytesIO(decoding_body)) as f:
@@ -187,11 +205,11 @@ class InternalCallbackHandler():
decode_success = False decode_success = False
break break
elif enc == "identity": elif enc == "identity":
pass # No need to do anything https://datatracker.ietf.org/doc/html/rfc2616#section-3.5 (it's possible to be found also if it should't be used) pass # No need to do anything https://datatracker.ietf.org/doc/html/rfc2616#section-3.5 (it's possible to be found also if it should't be used)
else: else:
decode_success = False decode_success = False
break break
if decode_success: if decode_success:
self.msg.body = decoding_body self.msg.body = decoding_body
self.msg.body_decoded = True self.msg.body_decoded = True
@@ -200,15 +218,15 @@ class InternalCallbackHandler():
self.has_begun = False self.has_begun = False
if not self._packet_to_stream(): if not self._packet_to_stream():
self.messages.append(self.msg) self.messages.append(self.msg)
@property @property
def user_agent(self) -> str: def user_agent(self) -> str:
return self.msg.lheaders.get("user-agent", "") return self.msg.lheaders.get("user-agent", "")
@property @property
def content_encoding(self) -> str: def content_encoding(self) -> str:
return self.msg.lheaders.get("content-encoding", "") return self.msg.lheaders.get("content-encoding", "")
@property @property
def content_type(self) -> str: def content_type(self) -> str:
return self.msg.lheaders.get("content-type", "") return self.msg.lheaders.get("content-type", "")
@@ -220,14 +238,14 @@ class InternalCallbackHandler():
@property @property
def should_upgrade(self) -> bool: def should_upgrade(self) -> bool:
return self.is_upgrading return self.is_upgrading
@property @property
def http_version(self) -> str: def http_version(self) -> str:
if self.major and self.minor: if self.major and self.minor:
return f"{self.major}.{self.minor}" return f"{self.major}.{self.minor}"
else: else:
return "" return ""
@property @property
def method_parsed(self) -> str: def method_parsed(self) -> str:
return self.method return self.method
@@ -239,17 +257,17 @@ class InternalCallbackHandler():
for msg in self.messages: for msg in self.messages:
tot += msg.total_size tot += msg.total_size
return tot return tot
@property @property
def content_length_parsed(self) -> int: def content_length_parsed(self) -> int:
return self.content_length return self.content_length
def _is_input(self) -> bool: def _is_input(self) -> bool:
raise NotImplementedError() raise NotImplementedError()
def _packet_to_stream(self): def _packet_to_stream(self):
return self.should_upgrade and self.save_body return self.should_upgrade and self.save_body
def _stream_parser(self, data: bytes): def _stream_parser(self, data: bytes):
if self.msg.upgrading_to_ws: if self.msg.upgrading_to_ws:
if self._ws_raised_error: if self._ws_raised_error:
@@ -259,9 +277,14 @@ class InternalCallbackHandler():
self.buffers._ws_packet_stream += data self.buffers._ws_packet_stream += data
while True: while True:
try: try:
new_frame, self.buffers._ws_packet_stream = self._parse_websocket_frame(self.buffers._ws_packet_stream) new_frame, self.buffers._ws_packet_stream = (
self._parse_websocket_frame(self.buffers._ws_packet_stream)
)
except Exception: except Exception:
print("[WARNING] Websocket parsing failed, passing data to stream...", flush=True) print(
"[WARNING] Websocket parsing failed, passing data to stream...",
flush=True,
)
traceback.print_exc() traceback.print_exc()
self._ws_raised_error = True self._ws_raised_error = True
self.msg.stream += self.buffers._ws_packet_stream self.msg.stream += self.buffers._ws_packet_stream
@@ -275,7 +298,7 @@ class InternalCallbackHandler():
if self.msg.upgrading_to_h2: if self.msg.upgrading_to_h2:
self.msg.total_size += len(data) self.msg.total_size += len(data)
self.msg.stream += data self.msg.stream += data
def _parse_websocket_ext(self): def _parse_websocket_ext(self):
ext_ws = [] ext_ws = []
req_ext = [] req_ext = []
@@ -287,14 +310,17 @@ class InternalCallbackHandler():
if ele == "permessage-deflate": if ele == "permessage-deflate":
ext_ws.append(PerMessageDeflate(False, False, 15, 15)) ext_ws.append(PerMessageDeflate(False, False, 15, 15))
return ext_ws return ext_ws
def _parse_websocket_frame(self, data: bytes) -> tuple[Frame|None, bytes]: def _parse_websocket_frame(self, data: bytes) -> tuple[Frame | None, bytes]:
if self._ws_extentions is None: if self._ws_extentions is None:
if self._is_input(): if self._is_input():
self._ws_extentions = [] # Fallback to no options self._ws_extentions = [] # Fallback to no options
else: else:
self._ws_extentions = self._parse_websocket_ext() # Extentions used are choosen by the server response self._ws_extentions = (
self._parse_websocket_ext()
) # Extentions used are choosen by the server response
read_buffering = bytearray() read_buffering = bytearray()
def read_exact(n: int): def read_exact(n: int):
nonlocal read_buffering nonlocal read_buffering
buffer = bytearray(read_buffering) buffer = bytearray(read_buffering)
@@ -307,17 +333,19 @@ class InternalCallbackHandler():
read_buffering = buffer[n:] read_buffering = buffer[n:]
return new_data return new_data
parsing = Frame.parse(read_exact, extensions=self._ws_extentions, mask=self._is_input()) parsing = Frame.parse(
read_exact, extensions=self._ws_extentions, mask=self._is_input()
)
parsing.send(None) parsing.send(None)
try: try:
parsing.send(bytearray(data)) parsing.send(bytearray(data))
except StopIteration as e: except StopIteration as e:
return e.value, read_buffering return e.value, read_buffering
return None, read_buffering return None, read_buffering
def parse_data(self, data: bytes): def parse_data(self, data: bytes):
if self._packet_to_stream(): # This is a websocket upgrade! if self._packet_to_stream(): # This is a websocket upgrade!
self._stream_parser(data) self._stream_parser(data)
else: else:
try: try:
@@ -333,82 +361,93 @@ class InternalCallbackHandler():
except Exception as e: except Exception as e:
self.raised_error = True self.raised_error = True
raise e raise e
def pop_message(self): def pop_message(self):
return self.messages.popleft() return self.messages.popleft()
def pop_all_messages(self):
tmp = self.messages
self.messages = deque()
return tmp
def __repr__(self): def __repr__(self):
return f"<InternalCallbackHandler msg={self.msg} buffers={self.buffers} save_body={self.save_body} raised_error={self.raised_error} has_begun={self.has_begun} messages={self.messages}>" return f"<InternalCallbackHandler msg={self.msg} buffers={self.buffers} save_body={self.save_body} raised_error={self.raised_error} has_begun={self.has_begun} messages={self.messages}>"
class InternalHttpRequest(InternalCallbackHandler, pyllhttp.Request): class InternalHttpRequest(InternalCallbackHandler, pyllhttp.Request):
def __init__(self): def __init__(self):
super(InternalCallbackHandler, self).__init__() super(InternalCallbackHandler, self).__init__()
super(pyllhttp.Request, self).__init__() super(pyllhttp.Request, self).__init__()
def _is_input(self): def _is_input(self):
return True return True
class InternalHttpResponse(InternalCallbackHandler, pyllhttp.Response): class InternalHttpResponse(InternalCallbackHandler, pyllhttp.Response):
def __init__(self): def __init__(self):
super(InternalCallbackHandler, self).__init__() super(InternalCallbackHandler, self).__init__()
super(pyllhttp.Response, self).__init__() super(pyllhttp.Response, self).__init__()
def _is_input(self): def _is_input(self):
return False return False
class InternalBasicHttpMetaClass: class InternalBasicHttpMetaClass:
"""Internal class to handle HTTP requests and responses""" """Internal class to handle HTTP requests and responses"""
def __init__(self, parser: InternalHttpRequest|InternalHttpResponse, msg: InternalHTTPMessage): def __init__(
self,
parser: InternalHttpRequest | InternalHttpResponse,
msg: InternalHTTPMessage,
):
self._parser = parser self._parser = parser
self.raised_error = False self.raised_error = False
self._message: InternalHTTPMessage|None = msg self._message: InternalHTTPMessage | None = msg
self._contructor_hook() self._contructor_hook()
def _contructor_hook(self): def _contructor_hook(self):
pass pass
@property @property
def total_size(self) -> int: def total_size(self) -> int:
"""Total size of the stream""" """Total size of the stream"""
return self._parser.total_size return self._parser.total_size
@property @property
def url(self) -> str|None: def url(self) -> str | None:
"""URL of the message""" """URL of the message"""
return self._message.url return self._message.url
@property @property
def headers(self) -> dict[str, str]: def headers(self) -> dict[str, str]:
"""Headers of the message""" """Headers of the message"""
return self._message.headers return self._message.headers
@property @property
def user_agent(self) -> str: def user_agent(self) -> str:
"""User agent of the message""" """User agent of the message"""
return self._message.user_agent return self._message.user_agent
@property @property
def content_encoding(self) -> str: def content_encoding(self) -> str:
"""Content encoding of the message""" """Content encoding of the message"""
return self._message.content_encoding return self._message.content_encoding
@property @property
def body(self) -> bytes: def body(self) -> bytes:
"""Body of the message""" """Body of the message"""
return self._message.body return self._message.body
@property @property
def headers_complete(self) -> bool: def headers_complete(self) -> bool:
"""If the headers are complete""" """If the headers are complete"""
return self._message.headers_complete return self._message.headers_complete
@property @property
def message_complete(self) -> bool: def message_complete(self) -> bool:
"""If the message is complete""" """If the message is complete"""
return self._message.message_complete return self._message.message_complete
@property @property
def http_version(self) -> str: def http_version(self) -> str:
"""HTTP version of the message""" """HTTP version of the message"""
@@ -425,7 +464,7 @@ class InternalBasicHttpMetaClass:
return self._parser.should_upgrade return self._parser.should_upgrade
@property @property
def content_length(self) -> int|None: def content_length(self) -> int | None:
"""Content length of the message""" """Content length of the message"""
return self._message.content_length return self._message.content_length
@@ -433,7 +472,7 @@ class InternalBasicHttpMetaClass:
def upgrading_to_h2(self) -> bool: def upgrading_to_h2(self) -> bool:
"""If the message is upgrading to HTTP/2""" """If the message is upgrading to HTTP/2"""
return self._message.upgrading_to_h2 return self._message.upgrading_to_h2
@property @property
def upgrading_to_ws(self) -> bool: def upgrading_to_ws(self) -> bool:
"""If the message is upgrading to Websocket""" """If the message is upgrading to Websocket"""
@@ -443,48 +482,63 @@ class InternalBasicHttpMetaClass:
def ws_stream(self) -> list[Frame]: def ws_stream(self) -> list[Frame]:
"""Websocket stream""" """Websocket stream"""
return self._message.ws_stream return self._message.ws_stream
@property @property
def stream(self) -> bytes: def stream(self) -> bytes:
"""Stream of the message""" """Stream of the message"""
return self._message.stream return self._message.stream
def get_header(self, header: str, default=None) -> str: def get_header(self, header: str, default=None) -> str:
"""Get a header from the message without caring about the case""" """Get a header from the message without caring about the case"""
return self._message.lheaders.get(header.lower(), default) return self._message.lheaders.get(header.lower(), default)
@staticmethod @staticmethod
def _before_fetch_callable_checks(internal_data: DataStreamCtx) -> bool: def _before_fetch_callable_checks(internal_data: DataStreamCtx) -> bool:
raise NotImplementedError() raise NotImplementedError()
@staticmethod @staticmethod
def _parser_class() -> str: def _parser_class() -> str:
raise NotImplementedError() raise NotImplementedError()
@classmethod @classmethod
def _fetch_packet(cls, internal_data: DataStreamCtx): def _fetch_packet(cls, internal_data: DataStreamCtx):
if internal_data.current_pkt is None or internal_data.current_pkt.is_tcp is False: if (
internal_data.current_pkt is None
or internal_data.current_pkt.is_tcp is False
):
raise NotReadyToRun() raise NotReadyToRun()
ParserType = InternalHttpRequest if internal_data.current_pkt.is_input else InternalHttpResponse ParserType = (
InternalHttpRequest
if internal_data.current_pkt.is_input
else InternalHttpResponse
)
parser_key = f"{cls._parser_class()}_{'in' if internal_data.current_pkt.is_input else 'out'}" parser_key = f"{cls._parser_class()}_{'in' if internal_data.current_pkt.is_input else 'out'}"
parser = internal_data.data_handler_context.get(parser_key, None) parser = internal_data.data_handler_context.get(parser_key, None)
if parser is None or parser.raised_error: if parser is None or parser.raised_error:
parser: InternalHttpRequest|InternalHttpResponse = ParserType() parser: InternalHttpRequest | InternalHttpResponse = ParserType()
internal_data.data_handler_context[parser_key] = parser internal_data.data_handler_context[parser_key] = parser
if not internal_data.call_mem.get(cls._parser_class(), False): #Need to parse HTTP if not internal_data.call_mem.get(
cls._parser_class(), False
): # Need to parse HTTP
internal_data.call_mem[cls._parser_class()] = True internal_data.call_mem[cls._parser_class()] = True
parser.pop_all_messages() # Delete content on message deque
#Setting websocket options if needed to the client parser
# Setting websocket options if needed to the client parser
if internal_data.current_pkt.is_input: if internal_data.current_pkt.is_input:
ext_opt = internal_data.data_handler_context.get(f"{cls._parser_class()}_ws_options_client") ext_opt = internal_data.data_handler_context.get(
f"{cls._parser_class()}_ws_options_client"
)
if ext_opt is not None and parser._ws_extentions != ext_opt: if ext_opt is not None and parser._ws_extentions != ext_opt:
parser._ws_extentions = ext_opt parser._ws_extentions = ext_opt
# Memory size managment # Memory size managment
if parser.total_size+len(internal_data.current_pkt.data) > internal_data.stream_max_size: if (
parser.total_size + len(internal_data.current_pkt.data)
> internal_data.stream_max_size
):
match internal_data.full_stream_action: match internal_data.full_stream_action:
case FullStreamAction.FLUSH: case FullStreamAction.FLUSH:
# Deleting parser and re-creating it # Deleting parser and re-creating it
@@ -494,7 +548,10 @@ class InternalBasicHttpMetaClass:
parser.msg.total_size -= len(parser.msg.body) parser.msg.total_size -= len(parser.msg.body)
parser.msg.body = b"" parser.msg.body = b""
print("[WARNING] Flushing stream", flush=True) print("[WARNING] Flushing stream", flush=True)
if parser.total_size+len(internal_data.current_pkt.data) > internal_data.stream_max_size: if (
parser.total_size + len(internal_data.current_pkt.data)
> internal_data.stream_max_size
):
parser.reset_data() parser.reset_data()
case FullStreamAction.REJECT: case FullStreamAction.REJECT:
raise StreamFullReject() raise StreamFullReject()
@@ -502,9 +559,11 @@ class InternalBasicHttpMetaClass:
raise StreamFullDrop() raise StreamFullDrop()
case FullStreamAction.ACCEPT: case FullStreamAction.ACCEPT:
raise NotReadyToRun() raise NotReadyToRun()
internal_data.call_mem["headers_were_set"] = parser.msg.headers_complete #This information is usefull for building the real object internal_data.call_mem["headers_were_set"] = (
parser.msg.headers_complete
) # This information is usefull for building the real object
try: try:
parser.parse_data(internal_data.current_pkt.data) parser.parse_data(internal_data.current_pkt.data)
except Exception as e: except Exception as e:
@@ -520,37 +579,51 @@ class InternalBasicHttpMetaClass:
raise NotReadyToRun() raise NotReadyToRun()
if parser.should_upgrade and not internal_data.current_pkt.is_input: if parser.should_upgrade and not internal_data.current_pkt.is_input:
#Creating ws_option for the client # Creating ws_option for the client
if not internal_data.data_handler_context.get(f"{cls._parser_class()}_ws_options_client"): if not internal_data.data_handler_context.get(
f"{cls._parser_class()}_ws_options_client"
):
ext = parser._parse_websocket_ext() ext = parser._parse_websocket_ext()
internal_data.data_handler_context[f"{cls._parser_class()}_ws_options_client"] = ext internal_data.data_handler_context[
f"{cls._parser_class()}_ws_options_client"
#Once the parsers has been triggered, we can return the object if needed ] = ext
# Once the parsers has been triggered, we can return the object if needed
if not cls._before_fetch_callable_checks(internal_data): if not cls._before_fetch_callable_checks(internal_data):
raise NotReadyToRun() raise NotReadyToRun()
messages_tosend:list[InternalHTTPMessage] = [] messages_tosend: list[InternalHTTPMessage] = []
for i in range(len(parser.messages)): for i in range(len(parser.messages)):
messages_tosend.append(parser.pop_message()) messages_tosend.append(parser.pop_message())
if len(messages_tosend) > 0: if len(messages_tosend) > 0:
internal_data.call_mem["headers_were_set"] = False # New messages completed so the current message headers were not set in this case internal_data.call_mem["headers_were_set"] = (
False # New messages completed so the current message headers were not set in this case
if not internal_data.call_mem["headers_were_set"] and parser.msg.headers_complete: )
messages_tosend.append(parser.msg) # Also the current message needs to be sent due to complete headers
if (
not internal_data.call_mem["headers_were_set"]
and parser.msg.headers_complete
):
messages_tosend.append(
parser.msg
) # Also the current message needs to be sent due to complete headers
if parser._packet_to_stream(): if parser._packet_to_stream():
messages_tosend.append(parser.msg) # Also the current message needs to beacase a stream is going on messages_tosend.append(
parser.msg
) # Also the current message needs to beacase a stream is going on
messages_to_call = len(messages_tosend) messages_to_call = len(messages_tosend)
if messages_to_call == 0: if messages_to_call == 0:
raise NotReadyToRun() raise NotReadyToRun()
elif messages_to_call == 1: elif messages_to_call == 1:
return cls(parser, messages_tosend[0]) return cls(parser, messages_tosend[0])
return [cls(parser, ele) for ele in messages_tosend] return [cls(parser, ele) for ele in messages_tosend]
class HttpRequest(InternalBasicHttpMetaClass): class HttpRequest(InternalBasicHttpMetaClass):
""" """
HTTP Request handler HTTP Request handler
@@ -569,10 +642,11 @@ class HttpRequest(InternalBasicHttpMetaClass):
@staticmethod @staticmethod
def _parser_class() -> str: def _parser_class() -> str:
return "full_http" return "full_http"
def __repr__(self): def __repr__(self):
return f"<HttpRequest method={self.method} url={self.url} headers={self.headers} body=[{0 if not self.body else len(self.body)} bytes] http_version={self.http_version} keep_alive={self.keep_alive} should_upgrade={self.should_upgrade} headers_complete={self.headers_complete} message_complete={self.message_complete} content_length={self.content_length} stream={self.stream} ws_stream={self.ws_stream}>" return f"<HttpRequest method={self.method} url={self.url} headers={self.headers} body=[{0 if not self.body else len(self.body)} bytes] http_version={self.http_version} keep_alive={self.keep_alive} should_upgrade={self.should_upgrade} headers_complete={self.headers_complete} message_complete={self.message_complete} content_length={self.content_length} stream={self.stream} ws_stream={self.ws_stream}>"
class HttpResponse(InternalBasicHttpMetaClass): class HttpResponse(InternalBasicHttpMetaClass):
""" """
HTTP Response handler HTTP Response handler
@@ -591,29 +665,31 @@ class HttpResponse(InternalBasicHttpMetaClass):
@staticmethod @staticmethod
def _parser_class() -> str: def _parser_class() -> str:
return "full_http" return "full_http"
def __repr__(self): def __repr__(self):
return f"<HttpResponse status_code={self.status_code} url={self.url} headers={self.headers} body=[{0 if not self.body else len(self.body)} bytes] http_version={self.http_version} keep_alive={self.keep_alive} should_upgrade={self.should_upgrade} headers_complete={self.headers_complete} message_complete={self.message_complete} content_length={self.content_length} stream={self.stream} ws_stream={self.ws_stream}>" return f"<HttpResponse status_code={self.status_code} url={self.url} headers={self.headers} body=[{0 if not self.body else len(self.body)} bytes] http_version={self.http_version} keep_alive={self.keep_alive} should_upgrade={self.should_upgrade} headers_complete={self.headers_complete} message_complete={self.message_complete} content_length={self.content_length} stream={self.stream} ws_stream={self.ws_stream}>"
class HttpRequestHeader(HttpRequest): class HttpRequestHeader(HttpRequest):
""" """
HTTP Request Header handler HTTP Request Header handler
This data handler will be called only once, the headers are complete, the body will be empty and not buffered This data handler will be called only once, the headers are complete, the body will be empty and not buffered
""" """
def _contructor_hook(self): def _contructor_hook(self):
self._parser.save_body = False self._parser.save_body = False
@staticmethod @staticmethod
def _parser_class() -> str: def _parser_class() -> str:
return "header_http" return "header_http"
class HttpResponseHeader(HttpResponse): class HttpResponseHeader(HttpResponse):
""" """
HTTP Response Header handler HTTP Response Header handler
This data handler will be called only once, the headers are complete, the body will be empty and not buffered This data handler will be called only once, the headers are complete, the body will be empty and not buffered
""" """
def _contructor_hook(self): def _contructor_hook(self):
self._parser.save_body = False self._parser.save_body = False