deflate_frame.py 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. import zlib
  2. from extension import Extension
  3. from frame import ControlFrame
  4. class DeflateFrame(Extension):
  5. """
  6. This is an implementation of the "deflate-frame" extension, as defined by
  7. http://tools.ietf.org/html/draft-tyoshino-hybi-websocket-perframe-deflate-06.
  8. Supported parameters are:
  9. - max_window_size: maximum size for the LZ77 sliding window.
  10. - no_context_takeover: disallows usage of LZ77 sliding window from
  11. previously built frames for the current frame.
  12. Note that the deflate and inflate hooks modify the RSV1 bit and payload of
  13. existing `Frame` objects.
  14. """
  15. name = 'deflate-frame'
  16. rsv1 = True
  17. defaults = {'max_window_bits': zlib.MAX_WBITS, 'no_context_takeover': False}
  18. COMPRESSION_THRESHOLD = 64 # minimal payload size for compression
  19. def init(self):
  20. mwb = self.defaults['max_window_bits']
  21. cto = self.defaults['no_context_takeover']
  22. if not isinstance(mwb, int) or mwb < 1 or mwb > zlib.MAX_WBITS:
  23. raise ValueError('"max_window_bits" must be in range 1-15')
  24. if cto is not False and cto is not True:
  25. raise ValueError('"no_context_takeover" must have no value')
  26. class Hook(Extension.Hook):
  27. def init(self, extension):
  28. self.defl = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION,
  29. zlib.DEFLATED, -self.max_window_bits)
  30. other_wbits = extension.request.get('max_window_bits', zlib.MAX_WBITS)
  31. self.dec = zlib.decompressobj(-other_wbits)
  32. def send(self, frame):
  33. # FIXME: this does not seem to work properly on Android
  34. if not frame.rsv1 and not isinstance(frame, ControlFrame) and \
  35. len(frame.payload) > DeflateFrame.COMPRESSION_THRESHOLD:
  36. frame.rsv1 = True
  37. frame.payload = self.deflate(frame)
  38. return frame
  39. def recv(self, frame):
  40. if frame.rsv1:
  41. if isinstance(frame, ControlFrame):
  42. raise ValueError('received compressed control frame')
  43. frame.rsv1 = False
  44. frame.payload = self.inflate(frame.payload)
  45. return frame
  46. def deflate(self, frame):
  47. compressed = self.defl.compress(frame.payload)
  48. if frame.final or self.no_context_takeover:
  49. compressed += self.defl.flush(zlib.Z_FINISH) + '\x00'
  50. self.defl = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION,
  51. zlib.DEFLATED, -self.max_window_bits)
  52. else:
  53. compressed += self.defl.flush(zlib.Z_SYNC_FLUSH)
  54. assert compressed[-4:] == '\x00\x00\xff\xff'
  55. compressed = compressed[:-4]
  56. return compressed
  57. def inflate(self, data):
  58. return self.dec.decompress(data + '\x00\x00\xff\xff') + \
  59. self.dec.flush(zlib.Z_SYNC_FLUSH)
  60. class WebkitDeflateFrame(DeflateFrame):
  61. name = 'x-webkit-deflate-frame'