Return a human-readable representation of a frame.
(self)
| 148 | MAX_LOG_SIZE = int(os.environ.get("WEBSOCKETS_MAX_LOG_SIZE", "75")) |
| 149 | |
| 150 | def __str__(self) -> str: |
| 151 | """ |
| 152 | Return a human-readable representation of a frame. |
| 153 | |
| 154 | """ |
| 155 | coding = None |
| 156 | length = f"{len(self.data)} byte{'' if len(self.data) == 1 else 's'}" |
| 157 | non_final = "" if self.fin else "continued" |
| 158 | |
| 159 | if self.opcode is OP_TEXT: |
| 160 | # Decoding only the beginning and the end is needlessly hard. |
| 161 | # Decode the entire payload then elide later if necessary. |
| 162 | data = repr(bytes(self.data).decode()) |
| 163 | elif self.opcode is OP_BINARY: |
| 164 | # We'll show at most the first 16 bytes and the last 8 bytes. |
| 165 | # Encode just what we need, plus two dummy bytes to elide later. |
| 166 | binary = self.data |
| 167 | if len(binary) > self.MAX_LOG_SIZE // 3: |
| 168 | cut = (self.MAX_LOG_SIZE // 3 - 1) // 3 # by default cut = 8 |
| 169 | binary = b"".join([binary[: 2 * cut], b"\x00\x00", binary[-cut:]]) |
| 170 | data = " ".join(f"{byte:02x}" for byte in binary) |
| 171 | elif self.opcode is OP_CLOSE: |
| 172 | data = str(Close.parse(self.data)) |
| 173 | elif self.data: |
| 174 | # We don't know if a Continuation frame contains text or binary. |
| 175 | # Ping and Pong frames could contain UTF-8. |
| 176 | # Attempt to decode as UTF-8 and display it as text; fallback to |
| 177 | # binary. If self.data is a memoryview, it has no decode() method, |
| 178 | # which raises AttributeError. |
| 179 | try: |
| 180 | data = repr(bytes(self.data).decode()) |
| 181 | coding = "text" |
| 182 | except (UnicodeDecodeError, AttributeError): |
| 183 | binary = self.data |
| 184 | if len(binary) > self.MAX_LOG_SIZE // 3: |
| 185 | cut = (self.MAX_LOG_SIZE // 3 - 1) // 3 # by default cut = 8 |
| 186 | binary = b"".join([binary[: 2 * cut], b"\x00\x00", binary[-cut:]]) |
| 187 | data = " ".join(f"{byte:02x}" for byte in binary) |
| 188 | coding = "binary" |
| 189 | else: |
| 190 | data = "''" |
| 191 | |
| 192 | if len(data) > self.MAX_LOG_SIZE: |
| 193 | cut = self.MAX_LOG_SIZE // 3 - 1 # by default cut = 24 |
| 194 | data = data[: 2 * cut] + "..." + data[-cut:] |
| 195 | |
| 196 | metadata = ", ".join(filter(None, [coding, length, non_final])) |
| 197 | |
| 198 | return f"{self.opcode.name} {data} [{metadata}]" |
| 199 | |
| 200 | @classmethod |
| 201 | def parse( |