@@ -88,15 +88,21 @@ def __repr__(self):
8888 )
8989
9090 @staticmethod
91- def parse_frame_header (header ):
91+ def parse_frame_header (header , strict = False ):
9292 """
9393 Takes a 9-byte frame header and returns a tuple of the appropriate
9494 Frame object and the length that needs to be read from the socket.
9595
9696 This populates the flags field, and determines how long the body is.
9797
98+ :param strict: Whether to raise an exception when encountering a frame
99+ not defined by spec and implemented by hyperframe.
100+
98101 :raises hyperframe.exceptions.UnknownFrameError: If a frame of unknown
99102 type is received.
103+
104+ .. versionchanged:: 5.0.0
105+ Added :param:`strict` to accommodate :class:`ExtensionFrame`
100106 """
101107 try :
102108 fields = _STRUCT_HBBBL .unpack (header )
@@ -110,7 +116,15 @@ def parse_frame_header(header):
110116 stream_id = fields [4 ] & 0x7FFFFFFF
111117
112118 if type not in FRAMES :
113- raise UnknownFrameError (type , length )
119+ if strict :
120+ raise UnknownFrameError (type , length )
121+
122+ frame = ExtensionFrame (
123+ type = type ,
124+ flag_byte = flags ,
125+ stream_id = stream_id ,
126+ )
127+ return (frame , length )
114128
115129 frame = FRAMES [type ](stream_id )
116130 frame .parse_flags (flags )
@@ -736,6 +750,46 @@ def parse_body(self, data):
736750 self .body_len = len (data )
737751
738752
753+ class ExtensionFrame (Frame ):
754+ """
755+ ExtensionFrame is used to wrap frames which are not natively interpretable
756+ by hyperframe.
757+
758+ Although certain byte prefixes are ordained by specification to have
759+ certain contextual meanings, frames with other prefixes are not prohibited,
760+ and may be used to communicate arbitrary meaning between HTTP/2 peers.
761+
762+ Thus, hyperframe, rather than raising an exception when such a frame is
763+ encountered, wraps it in a generic frame to be properly acted upon by
764+ upstream consumers which might have additional context on how to use it.
765+
766+ .. versionadded:: 5.0.0
767+ """
768+
769+ stream_association = _STREAM_ASSOC_EITHER
770+
771+ def __init__ (self , type , flag_byte , stream_id , ** kwargs ):
772+ super (ExtensionFrame , self ).__init__ (stream_id , ** kwargs )
773+ self .type = type
774+ self .flag_byte = flag_byte
775+
776+ def assign_flag_mapping (self , flags ):
777+ """
778+ When initially created, an ExtensionFrame has no concept of what flags
779+ might be relevant to it, since a frame's flags are defined in the
780+ specification for that frame. This method allows a frame to be assigned
781+ a flag mapping after the fact, allowing downstream consumers to modify
782+ ExtensionFrame to fit their needs.
783+ """
784+ self .defined_flags = flags
785+ self .flags = Flags (self .defined_flags )
786+ return self .parse_flags (self .flag_byte )
787+
788+ def parse_body (self , data ):
789+ self .body = data .tobytes ()
790+ self .body_len = len (data )
791+
792+
739793_FRAME_CLASSES = [
740794 DataFrame ,
741795 HeadersFrame ,
0 commit comments