zlib.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. # -*- coding: utf-8 -*-
  2. """
  3. Zlib-Kompressor.
  4. Implementiert Komprimierung mit dem zlib-Algorithmus,
  5. der in Python standardmäßig verfügbar ist.
  6. """
  7. from __future__ import annotations
  8. import time
  9. import zlib
  10. from typing import Any
  11. from trixy_core.network.compression.compressor import (
  12. Compressor,
  13. CompressionLevel,
  14. CompressionError,
  15. DecompressionError,
  16. )
  17. class ZlibCompressor(Compressor):
  18. """
  19. Kompressor basierend auf zlib.
  20. Verwendet den DEFLATE-Algorithmus für Komprimierung.
  21. Gut ausbalanciert zwischen Geschwindigkeit und Komprimierungsrate.
  22. Beispiel:
  23. compressor = ZlibCompressor(level=CompressionLevel.BEST)
  24. # Komprimieren
  25. compressed = compressor.compress(data)
  26. # Dekomprimieren
  27. original = compressor.decompress(compressed)
  28. # Statistiken
  29. print(compressor.stats.to_dict())
  30. """
  31. def __init__(
  32. self,
  33. level: CompressionLevel = CompressionLevel.DEFAULT,
  34. wbits: int = 15,
  35. ) -> None:
  36. """
  37. Initialisiert den Zlib-Kompressor.
  38. Args:
  39. level: Komprimierungsstufe (0-9)
  40. wbits: Fenstergröße für Komprimierung
  41. 15 = max Fenster (32KB)
  42. -15 = raw deflate (ohne Header)
  43. 15+16 = gzip-kompatibel
  44. """
  45. super().__init__(level)
  46. self._wbits = wbits
  47. @property
  48. def name(self) -> str:
  49. return "zlib"
  50. @property
  51. def content_type(self) -> str:
  52. return "application/zlib"
  53. @property
  54. def min_size(self) -> int:
  55. """Zlib hat ~6 Byte Overhead."""
  56. return 50
  57. def compress(self, data: bytes) -> bytes:
  58. """
  59. Komprimiert Daten mit zlib.
  60. Args:
  61. data: Unkomprimierte Daten
  62. Returns:
  63. Komprimierte Daten
  64. Raises:
  65. CompressionError: Bei Komprimierungsfehler
  66. """
  67. if not data:
  68. return b""
  69. start = time.monotonic()
  70. try:
  71. compressed = zlib.compress(data, level=int(self.level))
  72. except zlib.error as e:
  73. self.stats.errors += 1
  74. raise CompressionError(f"Zlib-Komprimierung fehlgeschlagen: {e}") from e
  75. elapsed_ms = (time.monotonic() - start) * 1000
  76. self.stats.update_compression(len(data), len(compressed), elapsed_ms)
  77. return compressed
  78. def decompress(self, data: bytes) -> bytes:
  79. """
  80. Dekomprimiert zlib-Daten.
  81. Args:
  82. data: Komprimierte Daten
  83. Returns:
  84. Ursprüngliche Daten
  85. Raises:
  86. DecompressionError: Bei Dekomprimierungsfehler
  87. """
  88. if not data:
  89. return b""
  90. start = time.monotonic()
  91. try:
  92. decompressed = zlib.decompress(data)
  93. except zlib.error as e:
  94. self.stats.errors += 1
  95. raise DecompressionError(
  96. f"Zlib-Dekomprimierung fehlgeschlagen: {e}"
  97. ) from e
  98. elapsed_ms = (time.monotonic() - start) * 1000
  99. self.stats.update_decompression(len(data), len(decompressed), elapsed_ms)
  100. return decompressed
  101. def compress_with_header(
  102. self,
  103. data: bytes,
  104. include_size: bool = True,
  105. ) -> bytes:
  106. """
  107. Komprimiert mit zusätzlichem Header.
  108. Der Header enthält:
  109. - 4 Bytes: Originalgröße (wenn include_size=True)
  110. - Komprimierte Daten
  111. Args:
  112. data: Zu komprimierende Daten
  113. include_size: Originalgröße einschließen
  114. Returns:
  115. Komprimierte Daten mit Header
  116. """
  117. compressed = self.compress(data)
  118. if include_size:
  119. size_bytes = len(data).to_bytes(4, byteorder="big")
  120. return size_bytes + compressed
  121. return compressed
  122. def decompress_with_header(
  123. self,
  124. data: bytes,
  125. has_size: bool = True,
  126. ) -> bytes:
  127. """
  128. Dekomprimiert Daten mit Header.
  129. Args:
  130. data: Komprimierte Daten mit Header
  131. has_size: Ob Größen-Header vorhanden
  132. Returns:
  133. Dekomprimierte Daten
  134. """
  135. if has_size:
  136. # Größe lesen (4 Bytes)
  137. if len(data) < 4:
  138. raise DecompressionError("Daten zu kurz für Header")
  139. expected_size = int.from_bytes(data[:4], byteorder="big")
  140. compressed = data[4:]
  141. else:
  142. compressed = data
  143. decompressed = self.decompress(compressed)
  144. if has_size and len(decompressed) != expected_size:
  145. raise DecompressionError(
  146. f"Größe stimmt nicht: erwartet {expected_size}, "
  147. f"erhalten {len(decompressed)}"
  148. )
  149. return decompressed
  150. class GzipCompressor(Compressor):
  151. """
  152. Kompressor für gzip-Format.
  153. Verwendet zlib mit gzip-kompatiblem Header.
  154. Kompatibel mit Standard-gzip-Tools.
  155. """
  156. def __init__(
  157. self,
  158. level: CompressionLevel = CompressionLevel.DEFAULT,
  159. ) -> None:
  160. super().__init__(level)
  161. @property
  162. def name(self) -> str:
  163. return "gzip"
  164. @property
  165. def content_type(self) -> str:
  166. return "application/gzip"
  167. @property
  168. def min_size(self) -> int:
  169. """Gzip hat ~18 Byte Overhead."""
  170. return 100
  171. def compress(self, data: bytes) -> bytes:
  172. """Komprimiert im gzip-Format."""
  173. if not data:
  174. return b""
  175. start = time.monotonic()
  176. try:
  177. import gzip
  178. compressed = gzip.compress(data, compresslevel=int(self.level))
  179. except Exception as e:
  180. self.stats.errors += 1
  181. raise CompressionError(f"Gzip-Komprimierung fehlgeschlagen: {e}") from e
  182. elapsed_ms = (time.monotonic() - start) * 1000
  183. self.stats.update_compression(len(data), len(compressed), elapsed_ms)
  184. return compressed
  185. def decompress(self, data: bytes) -> bytes:
  186. """Dekomprimiert gzip-Daten."""
  187. if not data:
  188. return b""
  189. start = time.monotonic()
  190. try:
  191. import gzip
  192. decompressed = gzip.decompress(data)
  193. except Exception as e:
  194. self.stats.errors += 1
  195. raise DecompressionError(
  196. f"Gzip-Dekomprimierung fehlgeschlagen: {e}"
  197. ) from e
  198. elapsed_ms = (time.monotonic() - start) * 1000
  199. self.stats.update_decompression(len(data), len(decompressed), elapsed_ms)
  200. return decompressed