22import gzip
33import pprint
44import re
5+ import sys
56from collections .abc import AsyncGenerator
67from enum import Enum
7- from io import BytesIO
88from logging import getLogger
99
10+ if sys .version_info >= (3 , 14 ):
11+ import zstd
12+ else :
13+ from backports import zstd
14+
1015from asgi_webdav .config import Config , get_config
1116from asgi_webdav .constants import (
1217 DEFAULT_COMPRESSION_CONTENT_MINIMUM_LENGTH ,
1722from asgi_webdav .helpers import get_data_generator_from_content
1823from asgi_webdav .request import DAVRequest
1924
20- try :
21- import brotli
22- except ImportError :
23- brotli = None
24-
2525logger = getLogger (__name__ )
2626
2727
@@ -32,9 +32,15 @@ class DAVResponseType(Enum):
3232
3333
3434class DAVCompressionMethod (Enum ):
35- NONE = 0
36- GZIP = 1
37- BROTLI = 2
35+ """
36+ Python 3.11 才支持 StrEnum
37+ 然后使用 auto() 生成期望的枚举值
38+ 并可以不使用 .value 做匹配
39+ """
40+
41+ NONE = "none"
42+ GZIP = "gzip"
43+ ZSTD = "zstd"
3844
3945
4046class DAVResponse :
@@ -44,7 +50,7 @@ class DAVResponse:
4450 headers : dict [bytes , bytes ]
4551 compression_method : DAVCompressionMethod
4652
47- def get_content (self ):
53+ def get_content (self ) -> AsyncGenerator :
4854 return self ._content
4955
5056 def set_content (self , value : bytes | AsyncGenerator ):
@@ -144,17 +150,23 @@ async def send_in_one_call(self, request: DAVRequest):
144150 config .compression .content_type_user_rule ,
145151 ):
146152 if (
147- brotli is not None
148- and config .compression .enable_brotli
149- and request .accept_encoding .br
153+ config .compression .enable_zstd
154+ and DAVCompressionMethod .ZSTD .value in request .accept_encoding
150155 ):
151- self .compression_method = DAVCompressionMethod .BROTLI
152- await BrotliSender (self , config .compression .level ).send (request )
156+ self .compression_method = DAVCompressionMethod .ZSTD
157+ await CompressionSenderZstd (self , config .compression .level ).send (
158+ request
159+ )
153160 return
154161
155- if config .compression .enable_gzip and request .accept_encoding .gzip :
162+ if (
163+ config .compression .enable_gzip
164+ and DAVCompressionMethod .GZIP .value in request .accept_encoding
165+ ):
156166 self .compression_method = DAVCompressionMethod .GZIP
157- await GzipSender (self , config .compression .level ).send (request )
167+ await CompressionSenderGzip (self , config .compression .level ).send (
168+ request
169+ )
158170 return
159171
160172 self .compression_method = DAVCompressionMethod .NONE
@@ -228,12 +240,12 @@ class CompressionSenderAbc:
228240
229241 def __init__ (self , response : DAVResponse ):
230242 self .response = response
231- self .buffer = BytesIO ()
243+ # self.buffer = BytesIO()
232244
233- def write (self , body : bytes ):
245+ def compress (self , body : bytes ) -> bytes :
234246 raise NotImplementedError
235247
236- def close (self ):
248+ def flush (self ) -> bytes :
237249 raise NotImplementedError
238250
239251 async def send (self , request : DAVRequest ):
@@ -250,13 +262,10 @@ async def send(self, request: DAVRequest):
250262 first = True
251263 async for body , more_body in self .response .content :
252264 # get and compress body
253- self .write (body )
254- if not more_body :
255- self .close ()
256- body = self .buffer .getvalue ()
257265
258- self .buffer .seek (0 )
259- self .buffer .truncate ()
266+ data = self .compress (body )
267+ if not more_body :
268+ data += self .flush ()
260269
261270 if first :
262271 first = False
@@ -288,16 +297,17 @@ async def send(self, request: DAVRequest):
288297 await request .send (
289298 {
290299 "type" : "http.response.body" ,
291- "body" : body ,
300+ "body" : data ,
292301 "more_body" : more_body ,
293302 }
294303 )
295304
296305
297- class GzipSender (CompressionSenderAbc ):
306+ class CompressionSenderGzip (CompressionSenderAbc ):
298307 """
299308 https://en.wikipedia.org/wiki/Gzip
300309 https://developer.mozilla.org/en-US/docs/Glossary/GZip_compression
310+ https://docs.python.org/3.14/library/gzip.html
301311 """
302312
303313 def __init__ (self , response : DAVResponse , compress_level : DAVCompressLevel ):
@@ -311,23 +321,21 @@ def __init__(self, response: DAVResponse, compress_level: DAVCompressLevel):
311321 level = 4
312322
313323 self .name = b"gzip"
314- self .compressor = gzip .GzipFile (
315- mode = "wb" , compresslevel = level , fileobj = self .buffer
316- )
324+ self ._level = level
317325
318- def write (self , body : bytes ):
319- self . compressor . write (body )
326+ def compress (self , body : bytes ) -> bytes :
327+ return gzip . compress (body , compresslevel = self . _level )
320328
321- def close (self ):
322- self . compressor . close ()
329+ def flush (self ) -> bytes :
330+ return b""
323331
324332
325- class BrotliSender (CompressionSenderAbc ):
333+ class CompressionSenderZstd (CompressionSenderAbc ):
326334 """
327- https://datatracker.ietf .org/doc/html/rfc7932
328- https://github.com/google/brotli
329- https://caniuse.com/brotli
330- https://developer.mozilla .org/en-US/docs/Glossary/brotli_compression
335+ https://en.wikipedia .org/wiki/Zstd
336+ https://facebook. github.io/zstd/
337+ https://developer.mozilla.org/en-US/docs/Glossary/Zstandard_compression
338+ https://docs.python .org/zh-cn/3.14/library/compression.zstd.html
331339 """
332340
333341 def __init__ (self , response : DAVResponse , compress_level : DAVCompressLevel ):
@@ -336,19 +344,18 @@ def __init__(self, response: DAVResponse, compress_level: DAVCompressLevel):
336344 if compress_level == DAVCompressLevel .FAST :
337345 level = 1
338346 elif compress_level == DAVCompressLevel .BEST :
339- level = 11
347+ level = 19
340348 else :
341- level = 4
349+ level = 3 # compression.zstd.COMPRESSION_LEVEL_DEFAULT
342350
343- self .name = b"br "
344- self .compressor = brotli . Compressor ( mode = brotli . MODE_TEXT , quality = level )
351+ self .name = b"zstd "
352+ self ._compressor = zstd . ZstdCompressor ( level = level )
345353
346- def write (self , body : bytes ):
347- # https://github.com/google/brotli/blob/master/python/brotli.py
348- self .buffer .write (self .compressor .process (body ))
354+ def compress (self , body : bytes ) -> bytes :
355+ return self ._compressor .compress (body )
349356
350- def close (self ):
351- self .buffer . write ( self . compressor . finish () )
357+ def flush (self ) -> bytes :
358+ return self ._compressor . flush ( )
352359
353360
354361class DAVHideFileInDir :
0 commit comments