- Notifications
You must be signed in to change notification settings - Fork 35
Add ExtensionFrame object #75
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
Changes from 1 commit
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 |
|---|---|---|
| | @@ -88,7 +88,7 @@ def __repr__(self): | |
| ) | ||
| | ||
| @staticmethod | ||
| def parse_frame_header(header): | ||
| def parse_frame_header(header, strict=False): | ||
| """ | ||
| Takes a 9-byte frame header and returns a tuple of the appropriate | ||
| Frame object and the length that needs to be read from the socket. | ||
| | @@ -110,6 +110,14 @@ def parse_frame_header(header): | |
| stream_id = fields[4] | ||
| | ||
| if type not in FRAMES: | ||
| if not strict: | ||
| ||
| frame = ExtensionFrame( | ||
| type=type, | ||
| flag_byte=flags, | ||
| stream_id=stream_id, | ||
| ) | ||
| return (frame, length) | ||
| | ||
| raise UnknownFrameError(type, length) | ||
| | ||
| frame = FRAMES[type](stream_id) | ||
| | @@ -732,6 +740,44 @@ def parse_body(self, data): | |
| self.body_len = len(data) | ||
| | ||
| | ||
| class ExtensionFrame(Frame): | ||
| """ | ||
| ExtensionFrame is used to wrap frames which are not natively interpretable | ||
| by hyperframe. | ||
| | ||
| Although certain byte prefixes are ordained by specification to have | ||
| certain contextual meanings, frames with other prefixes are not prohibited, | ||
| and may be used to communicate arbitrary meaning between HTTP 2 peers. | ||
| ||
| | ||
| Thus, hyperframe, rather than raising an exception when such a frame is | ||
| encountered, wraps it in a generic frame to be properly acted upon by | ||
| upstream consumers which might have additional context on how to use it. | ||
| """ | ||
| Member 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. Can you add a | ||
| | ||
| stream_association = _STREAM_ASSOC_EITHER | ||
| | ||
| def __init__(self, type, flag_byte, stream_id, **kwargs): | ||
| super(ExtensionFrame, self).__init__(stream_id, **kwargs) | ||
| self.type = type | ||
| self.flag_byte = flag_byte | ||
| | ||
| def assign_flag_mapping(self, flags): | ||
| """ | ||
| When initially created, an ExtensionFrame has no concept of what flags | ||
| might be relevant to it, since a frame's flags are defined in the | ||
| specification for that frame. This method allows a frame to be assigned | ||
| a flag mapping after the fact, allowing downstream consumers to modify | ||
| ExtensionFrame to fit their needs. | ||
| """ | ||
| self.defined_flags = flags | ||
| self.flags = Flags(self.defined_flags) | ||
| return self.parse_flags(self.flag_byte) | ||
| Member 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 don't think we need this method. Contributor Author 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. See comment above. | ||
| | ||
| def parse_body(self, data): | ||
| self.body = data.tobytes() | ||
| self.body_len = len(data) | ||
| | ||
| | ||
| _FRAME_CLASSES = [ | ||
| DataFrame, | ||
| HeadersFrame, | ||
| | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,6 @@ | ||
| # -*- coding: utf-8 -*- | ||
| from hyperframe.flags import Flag | ||
| | ||
| ||
| from hyperframe.frame import ( | ||
| Frame, Flags, DataFrame, PriorityFrame, RstStreamFrame, SettingsFrame, | ||
| PushPromiseFrame, PingFrame, GoAwayFrame, WindowUpdateFrame, HeadersFrame, | ||
| | @@ -35,10 +37,12 @@ def test_base_frame_cant_parse_body(self): | |
| with pytest.raises(NotImplementedError): | ||
| f.parse_body(data) | ||
| | ||
| def test_parse_frame_header_unknown_type(self): | ||
| def test_parse_frame_header_unknown_type_strict(self): | ||
| with pytest.raises(UnknownFrameError) as excinfo: | ||
| Frame.parse_frame_header(b'\x00\x00\x59\xFF\x00\x00\x00\x00\x01') | ||
| | ||
| Frame.parse_frame_header( | ||
| b'\x00\x00\x59\xFF\x00\x00\x00\x00\x01', | ||
| strict=True | ||
| ) | ||
| exception = excinfo.value | ||
| assert exception.frame_type == 0xFF | ||
| assert exception.length == 0x59 | ||
| | @@ -47,6 +51,38 @@ def test_parse_frame_header_unknown_type(self): | |
| "length 89 bytes" | ||
| ) | ||
| | ||
| def test_parse_frame_header_unknown_type(self): | ||
| f, l = Frame.parse_frame_header( | ||
| b'\x00\x00\x59\xFF\x00\x00\x00\x00\x01' | ||
| ) | ||
| assert f.type == 0xFF | ||
| Contributor 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 think we could additionally have assert isinstance(f, ExtensionFrame) Contributor Author 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. Will add. | ||
| assert l == 0x59 | ||
| | ||
| def test_add_flag_options_later_unknown_type(self): | ||
| f, l = Frame.parse_frame_header( | ||
| b'\x00\x00\x59\xFF\x09\x00\x00\x00\x01' | ||
| ) | ||
| assert f.type == 0xFF | ||
| assert l == 0x59 | ||
| new_flags = [ | ||
| Flag('FANCY_FLAG', 0x01), | ||
| Flag('REAL_THING', 0x04), | ||
| Flag('HUUUUUUGE', 0x08), | ||
| ] | ||
| flags = f.assign_flag_mapping(new_flags) | ||
| assert f.defined_flags == new_flags | ||
| assert flags | ||
| assert 'FANCY_FLAG' in flags | ||
| assert 'REAL_THING' not in flags | ||
| assert 'HUUUUUUGE' in flags | ||
| | ||
| def test_parse_body_unknown_type(self): | ||
| f = decode_frame( | ||
| b'\x00\x00\x0C\xFF\x00\x00\x00\x00\x01hello world!' | ||
| ) | ||
| assert f.body == b'hello world!' | ||
| assert f.body_len == 12 | ||
| | ||
| Member 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. Can at least one of these tests validate that the stream id is correct? | ||
| def test_repr(self, monkeypatch): | ||
| f = Frame(stream_id=0) | ||
| monkeypatch.setattr(Frame, "serialize_body", lambda _: b"body") | ||
| | ||
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.
Please add a docstring comment to explain the new parameter.