Django 返回文本/html时Content-Length总是无法设置为分块

3 投票
1 回答
1724 浏览
提问于 2025-04-17 18:35

在我的Django应用的views.py文件中,我在尝试设置一些HTTP头信息后,返回了一个HttpResponse对象:

# Create a Response Object with the content to return 
response = HttpResponse("%s"%(output_display),mimetype='text/html')
response['Cache-Control'] = 'must-revalidate, max-age=20'
response['Vary'] = 'Accept-Encoding'
response['Transfer-Encoding'] = 'gzip'
#response['Content-Encoding'] = 'gzip'
response['Connection'] = 'close'
#response['Content-Type'] = 'text/html'
response['Content-Length'] = '%s'%(len(output_display))
return response

然后我用Firefox的Live HTTP Headers插件捕捉到了输出,结果看起来是这样的:

HTTP/1.1 200 OK
Date: Sun, 10 Mar 2013 14:55:09 GMT
Server: Apache/2.2.22 (Ubuntu)
Transfer-Encoding: gzip, chunked   <---------- Why 'chunked'?
Vary: Accept-Encoding
Connection: close
Cache-Control: must-revalidate, max-age=20
Content-Encoding: gzip
Content-Type: text/html <---------------------- No Content-Length even though I set it?
X-Pad: avoid browser bug

我想使用Apache2的mem_cache来进行缓存,所以我需要设置Content-Length,而且不能使用'chunked'作为Transfer-Encoding。

我的Apache2的mem_cache.conf文件看起来是这样的(大数字只是为了测试):

<IfModule mod_mem_cache.c>
        CacheEnable mem /
        MCacheSize 10000
        MCacheMaxObjectCount 10000000
        MCacheMinObjectSize 1
        MCacheMaxObjectSize 10000000
        MCacheMaxStreamingBuffer 10000000
</IfModule>

但是尽管我在响应代码中明确设置了Content-Length和Transfer-Encoding,'chunked'却是自动插入的,因此我的Content-Length没有被认可。这是为什么呢?我该如何解决这个问题,以获得想要的响应呢?谢谢!

1 个回答

1

我最近遇到了一个类似的问题,涉及到一个mod_wsgi的应用程序。我想更新一个使用内置磁盘缓存的Apache配置,改为使用socache/memcache。

磁盘缓存是可以工作的,但切换到memcache或shmcb就不行了。当我请求一个想要缓存的资源时,它并没有存储到缓存中(CacheDetailHeader这个设置对调试很有帮助)。我查看调试日志时,发现了这样的信息:

[Wed Dec 05 18:52:16.571002 2018] [cache_socache:debug] \
[pid 884:tid 140422596777728] mod_cache_socache.c(389): \
[client 127.0.0.1:56576] AH02346: URL 'http://127.0.1.1:80/cacheme/c?' \
had no explicit size, ignoring, referer: http://127.0.0.1/

看起来socache不喜欢那些没有明确大小的对象。我尝试把mod_memcache的设置更新为socache的对应设置,并把它们的值设置得足够大:CacheSocacheMaxSizeCacheSocacheReadSize

我知道Content-Length这个头部是被设置了的,并且确实传到了某个地方;当我故意算错这个值时,它在mod_wsgi的日志中出现了。

我发现了一些事情:

  1. 不要自己设置Transfer-Encoding这个头部,因为WSGI规范是不允许的:

    谁设置了Transfer-Encoding: chunked头部?

  2. 即使你自己设置了Content-Length,Apache也会对其进行gzip压缩。这会改变长度;当Apache不知道长度时,它会切换到分块传输,并移除Content-Length头部。

我发现,当在python/mod_wsgi应用中设置:

  • Content-Type: text/html

  • Content-Length设置为我的utf-8编码大小

并在Apache配置中设置:

  • SetEnv no-gzip 1

这样对象就成功进入了shmcb缓存。

看起来当Apache对一个对象进行gzip压缩时,它会改变头部,使得socache无法接受。

我查找了一些方法来让它们兼容,但没找到太多相关的信息。在mod_cache的文档中提到了一些关于重新排序缓存/解压缩过滤器的内容:

https://httpd.apache.org/docs/2.4/mod/mod_cache.html#finecontrol

如果我添加了一个指令来重新排序缓存/解压缩过滤器,这样就有效了:

# within a directory
SetOutputFilter CACHE;DEFLATE

有趣的是,在缓存未命中时,服务器返回的是gzip压缩的内容,但在缓存命中时,服务器返回的是未编码的文本/html。这看起来有点奇怪,但我还没有完全理解FilterChain指令,所以没法尝试。

我还在一个与php/content-length相关的问题中发现了一些提及:

https://serverfault.com/questions/183843/content-length-not-sent-when-gzip-compression-enabled-in-apache

那里的答案发现,如果他们把DeflateBufferSize设置为足够大的值,那么Content-Length就会被设置。

但我没能让这个方法奏效。

所以看起来我们在选择缓存和gzip压缩之间陷入了困境。

撰写回答