- 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 2 commits
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,13 +88,16 @@ 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. | ||
| | ||
| This populates the flags field, and determines how long the body 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. Can you add a | ||
| | ||
| :param strict: Whether to raise an exception when encountering a frame | ||
| not defined by spec and implemented by hyperframe. | ||
| | ||
| :raises hyperframe.exceptions.UnknownFrameError: If a frame of unknown | ||
| type is received. | ||
| """ | ||
| | @@ -110,7 +113,15 @@ def parse_frame_header(header): | |
| stream_id = fields[4] | ||
| | ||
| if type not in FRAMES: | ||
| raise UnknownFrameError(type, length) | ||
| if strict: | ||
| raise UnknownFrameError(type, length) | ||
| | ||
| frame = ExtensionFrame( | ||
| type=type, | ||
| flag_byte=flags, | ||
| stream_id=stream_id, | ||
| ) | ||
| return (frame, length) | ||
| 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. So, for the sake of clarity, let's actually do this: try: frame = FRAMES[type](stream_id) except KeyError: if strict: raise UnknownFrameError(type, length) frame = ExtensionFrame(type=type, stream_id=stream_id)Then, the flag byte can end up in place using 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. So, I don't think we can actually use 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. So I am not sure that | ||
| | ||
| frame = FRAMES[type](stream_id) | ||
| frame.parse_flags(flags) | ||
| | @@ -732,6 +743,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. | ||
| """ | ||
| 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) | ||
| 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. 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,8 +1,9 @@ | ||
| # -*- coding: utf-8 -*- | ||
| from hyperframe.flags import Flag | ||
| from hyperframe.frame import ( | ||
| Frame, Flags, DataFrame, PriorityFrame, RstStreamFrame, SettingsFrame, | ||
| PushPromiseFrame, PingFrame, GoAwayFrame, WindowUpdateFrame, HeadersFrame, | ||
| ContinuationFrame, AltSvcFrame | ||
| ContinuationFrame, AltSvcFrame, ExtensionFrame | ||
| ) | ||
| from hyperframe.exceptions import ( | ||
| UnknownFrameError, InvalidPaddingError, InvalidFrameError | ||
| | @@ -35,10 +36,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 +50,39 @@ 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 | ||
| 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)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 | ||
| assert isinstance(f, ExtensionFrame) | ||
| | ||
| 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 | ||
| | ||
| 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.