29
29
NEWLINE : Final = b"\n "
30
30
CARRIAGE : Final = b"\r "
31
31
NULL : Final = b"\x00 "
32
+ BACKSLASH = b"\\ "
33
+ COLON_ = b":"
34
+
32
35
HEADER_ESCAPE_CHARS : Final = {
33
- " \n " : "\\ n" ,
34
- ":" : "\\ c" ,
35
- " \\ " : "\\ \\ " ,
36
- " \r " : "" , # [\r]\n is newline, therefore can't be used in header
36
+ NEWLINE . decode () : "\\ n" ,
37
+ COLON_ . decode () : "\\ c" ,
38
+ BACKSLASH . decode () : "\\ \\ " ,
39
+ CARRIAGE . decode () : "" , # [\r]\n is newline, therefore can't be used in header
37
40
}
38
41
HEADER_UNESCAPE_CHARS : Final = {
39
42
b"n" : NEWLINE ,
40
- b"c" : b":" ,
41
- b" \\ " : b" \\ " ,
43
+ b"c" : COLON_ ,
44
+ BACKSLASH : BACKSLASH ,
42
45
}
43
46
44
47
@@ -89,39 +92,39 @@ def dump_frame(frame: AnyClientFrame | AnyServerFrame) -> bytes:
89
92
90
93
91
94
def unescape_byte (byte : bytes , previous_byte : bytes | None ) -> bytes | None :
92
- if previous_byte == b" \\ " :
95
+ if previous_byte == BACKSLASH :
93
96
return HEADER_UNESCAPE_CHARS .get (byte )
94
- if byte == b" \\ " :
97
+ if byte == BACKSLASH :
95
98
return None
96
99
return byte
97
100
98
101
99
- def parse_header (buffer : list [bytes ]) -> tuple [str , str ] | None :
100
- key_buffer : list [bytes ] = []
102
+ def parse_header (buffer : bytearray ) -> tuple [str , str ] | None :
103
+ key_buffer = bytearray ()
104
+ value_buffer = bytearray ()
101
105
key_parsed = False
102
- value_buffer : list [bytes ] = []
103
106
104
107
previous_byte = None
105
108
just_escaped_line = False
106
109
107
- for byte in buffer :
108
- if byte == b":" :
110
+ for byte in iter_bytes ( buffer ) :
111
+ if byte == COLON_ :
109
112
if key_parsed :
110
113
return None
111
114
key_parsed = True
112
115
elif just_escaped_line :
113
116
just_escaped_line = False
114
- if byte != b" \\ " :
115
- (value_buffer if key_parsed else key_buffer ).append (byte )
117
+ if byte != BACKSLASH :
118
+ (value_buffer if key_parsed else key_buffer ).extend (byte )
116
119
elif unescaped_byte := unescape_byte (byte , previous_byte ):
117
120
just_escaped_line = True
118
- (value_buffer if key_parsed else key_buffer ).append (unescaped_byte )
121
+ (value_buffer if key_parsed else key_buffer ).extend (unescaped_byte )
119
122
120
123
previous_byte = byte
121
124
122
125
if key_parsed :
123
126
with suppress (UnicodeDecodeError ):
124
- return ( b"" . join ( key_buffer ) .decode (), b"" . join ( value_buffer ) .decode () )
127
+ return key_buffer .decode (), value_buffer .decode ()
125
128
126
129
return None
127
130
@@ -135,29 +138,29 @@ def make_frame_from_parts(command: bytes, headers: dict[str, str], body: bytes)
135
138
)
136
139
137
140
138
- def parse_lines_into_frame (lines : deque [list [ bytes ] ]) -> AnyClientFrame | AnyServerFrame :
139
- command = b"" . join (lines .popleft ())
141
+ def parse_lines_into_frame (lines : deque [bytearray ]) -> AnyClientFrame | AnyServerFrame :
142
+ command = bytes (lines .popleft ())
140
143
headers = {}
141
144
142
145
while line := lines .popleft ():
143
146
header = parse_header (line )
144
147
if header and header [0 ] not in headers :
145
148
headers [header [0 ]] = header [1 ]
146
- body = b"" . join ( lines .popleft () ) if lines else b""
149
+ body = lines .popleft () if lines else b""
147
150
return make_frame_from_parts (command = command , headers = headers , body = body )
148
151
149
152
150
153
@dataclass
151
154
class FrameParser :
152
- _lines : deque [list [ bytes ] ] = field (default_factory = deque , init = False )
153
- _current_line : list [ bytes ] = field (default_factory = list , init = False )
155
+ _lines : deque [bytearray ] = field (default_factory = deque , init = False )
156
+ _current_line : bytearray = field (default_factory = bytearray , init = False )
154
157
_previous_byte : bytes = field (default = b"" , init = False )
155
158
_headers_processed : bool = field (default = False , init = False )
156
159
157
160
def _reset (self ) -> None :
158
161
self ._headers_processed = False
159
162
self ._lines .clear ()
160
- self ._current_line = []
163
+ self ._current_line = bytearray ()
161
164
162
165
def parse_frames_from_chunk (self , chunk : bytes ) -> Iterator [AnyClientFrame | AnyServerFrame | HeartbeatFrame ]:
163
166
for byte in iter_bytes (chunk ):
@@ -173,15 +176,15 @@ def parse_frames_from_chunk(self, chunk: bytes) -> Iterator[AnyClientFrame | Any
173
176
self ._current_line .pop ()
174
177
self ._headers_processed = not self ._current_line # extra empty line after headers
175
178
176
- if not self ._lines and self ._current_line not in COMMANDS_BYTES_LISTS :
179
+ if not self ._lines and bytes ( self ._current_line ) not in COMMANDS_TO_FRAMES :
177
180
self ._reset ()
178
181
else :
179
182
self ._lines .append (self ._current_line )
180
- self ._current_line = []
183
+ self ._current_line = bytearray ()
181
184
else :
182
185
yield HeartbeatFrame ()
183
186
184
187
else :
185
- self ._current_line . append ( byte )
188
+ self ._current_line += byte
186
189
187
190
self ._previous_byte = byte
0 commit comments