【问题标题】:Sending bz2-compressed data as utf-8 string via gunicorn with Python 2.7使用 Python 2.7 通过 gunicorn 将 bz2 压缩数据作为 utf-8 字符串发送
【发布时间】:2015-05-15 05:43:07
【问题描述】:

我正在尝试使用 gunicorn 发送一个 utf-8 编码的字符串,这是 bz2 压缩的结果,作为对获取请求的响应。

这是我在 gunicorn 服务器端的代码:

def app(environ, start_response):
    data = "Hello, World!" * 10
    compressed_data = bz2.compress(data)
    start_response("200 OK", [("Content-Type", "text/plain"),
                              ('charset', 'utf-8'),
                              ("Content-Length", str(len(compressed_data))),
                              ('Access-Control-Allow-Headers', '*'),
                              ('Access-Control-Allow-Origin', '*'),
                              # ('Content-Transfer-Encoding', 'BASE64'),
                          ])
    return iter([compressed_data])

当我尝试使用这样的 Python 请求包从客户端获取请求时

import bz2
import requests
res = requests.get('http://127.0.0.1:8000')
bz2.decompress(res.text)

它引发了一个异常

UnicodeEncodeError: 'ascii' codec can't encode character u'\xab' in position 11: ordinal not in range(128)

说无法解码响应 尝试打印响应文本时

print(res.text)
>>u'BZh91AY&SYy\xabm\x99\x00\x00\x13\x97\x80`\x04\x00@\x00\x80\x06\x04\x90\x00 \x00\xa5P\xd0\xda\x10\x03\x0e\xd3\xd4\xdai4\x9bO\x93\x13\x13\xc2b~\x9c\x17rE8P\x90y\xabm\x99'

打印编码文本时

import bz2
print(bz2.compress("Hello, World!" * 10))
>> 'BZh91AY&SYy\xabm\x99\x00\x00\x13\x97\x80`\x04\x00@\x00\x80\x06\x04\x90\x00 \x00\xa5P\xd0\xda\x10\x03\x0e\xd3\xd4\xdai4\x9bO\x93\x13\x13\xc2b~\x9c\x17rE8P\x90y\xabm\x99'

唯一的区别是 unicode 符号,我通过在客户端调整数据以使响应字符串可解码来解决此问题,但我想知道如何在服务器端解决此问题?

【问题讨论】:

    标签: python python-2.7 unicode gunicorn


    【解决方案1】:

    问题是字符串以 unicode 形式出现。您不应该尝试将 bz2 压缩数据解释为文本。

    请参阅request docs,了解如何将数据解释为原始数据而不是文本:

    res.content  # not res.text
    

    此外,数据首先不应以text/plain 发送。 BZ2 压缩数据不是文本,应以application/octet-stream(即字节流)形式发送。

    快速将文本重新解释为字节流(由于默认的 ascii 编解码器无法处理 0-127 范围之外的字节,我们使用 ISO-8859-1 对数据进行编码。

    >>> text = u'BZh91AY&SYy\xabm\x99\x00\x00\x13\x97\x80`\x04\x00@\x00\x80\x06\x04\x90\x00 \x00 \xa5P\xd0\xda\x10\x03\x0e\xd3\xd4\xdai4\x9bO\x93\x13\x13\xc2b~\x9c\x17rE8P\x90y\xabm\x99'
    >>> byte_string = text.encode('ISO-8859-1')
    >>> byte_string
    'BZh91AY&SYy\xabm\x99\x00\x00\x13\x97\x80`\x04\x00@\x00\x80\x06\x04\x90\x00 \x00 \xa5P\xd0\xda\x10\x03\x0e\xd3\xd4\xdai4\x9bO\x93\x13\x13\xc2b~\x9c\x17rE8P\x90y\xabm\x99'
    >>> bz2.decompress(byte_string)
    'Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!'
    

    但理想情况下,您应该修复您的数据类型。

    【讨论】:

      【解决方案2】:

      您不能以 utf-8 格式发送 bzip2 压缩数据。它是二进制数据,不是文本。

      如果您的 http 客户端接受 bzip2 内容编码 (bzip2 is not standard),那么您可以发送使用 bzip2 压缩的 utf-8 编码文本:

      #!/usr/bin/env python
      import bz2
      
      def app(environ, start_response):
          status = '200 OK'
          headers = [('Content-type', 'text/plain; charset=utf-8')]
          data = (u'Hello \N{SNOWMAN}\n' * 10).encode('utf-8')
      
          if 'bzip2' in environ.get('HTTP_ACCEPT_ENCODING', ''): # use bzip2 only if requested
              data = bz2.compress(data)
              headers.append(('Content-Encoding', 'bzip2'))
      
          headers.append(('Content-Length', str(len(data))))
          start_response(status, headers)
          return data
      

      示例

      未压缩的响应:

      $ http -v 127.0.0.1:8000
      GET / HTTP/1.1
      Accept: */*
      Accept-Encoding: gzip, deflate
      Connection: keep-alive
      Host: 127.0.0.1:8000
      User-Agent: HTTPie/0.9.2
      
      
      
      HTTP/1.1 200 OK
      Connection: close
      Content-Length: 100
      Content-type: text/plain; charset=utf-8
      Date: Sun, 17 May 2015 18:47:50 GMT
      Server: gunicorn/19.3.0
      
      Hello ☃
      Hello ☃
      Hello ☃
      Hello ☃
      Hello ☃
      Hello ☃
      Hello ☃
      Hello ☃
      Hello ☃
      Hello ☃
      

      如果客户端指定它接受 bzip2,则为 bzip2 压缩响应:

      $ http -v 127.0.0.1:8000 Accept-Encoding:bzip2 
      GET / HTTP/1.1
      Accept: */*
      Accept-Encoding: bzip2
      Connection: keep-alive
      Host: 127.0.0.1:8000
      User-Agent: HTTPie/0.9.2
      
      
      
      HTTP/1.1 200 OK
      Connection: close
      Content-Encoding: bzip2
      Content-Length: 65
      Content-type: text/plain; charset=utf-8
      Date: Sun, 17 May 2015 18:48:23 GMT
      Server: gunicorn/19.3.0
      
      
      
      +-----------------------------------------+
      | NOTE: binary data not shown in terminal |
      +-----------------------------------------+
      

      下面是对应的使用requests库的http客户端:

      #!/usr/bin/env python
      from __future__ import print_function
      import bz2
      import requests # $ pip install requests
      
      r = requests.get('http://localhost:8000', headers={'Accept-Encoding': 'gzip, deflate, bzip2'})
      content = r.content
      print(len(content))
      if r.headers['Content-Encoding'].endswith('bzip2'): # requests doesn't understand bzip2
          content = bz2.decompress(content)
      print(len(content))
      text = content.decode(r.encoding)
      print(len(text))
      print(text, end='')
      

      输出

      65
      100
      80
      Hello ☃
      Hello ☃
      Hello ☃
      Hello ☃
      Hello ☃
      Hello ☃
      Hello ☃
      Hello ☃
      Hello ☃
      Hello ☃
      

      否则(没有非标准的接受编码),您应该将您的数据作为application/octet-stream 发送为@icedtrees suggested

      #!/usr/bin/env python
      import bz2
      
      def app(environ, start_response):
          status = '200 OK'
          headers = [('Content-type', 'application/octet-stream')]
          data = bz2.compress((u'Hello \N{SNOWMAN}\n' * 10).encode('utf-8'))
      
          headers.append(('Content-Length', str(len(data))))
          start_response(status, headers)
          return data
      

      示例

      $ http 127.0.0.1:8000 
      HTTP/1.1 200 OK
      Connection: close
      Content-Length: 65
      Content-type: application/octet-stream
      Date: Sun, 17 May 2015 18:53:55 GMT
      Server: gunicorn/19.3.0
      
      
      
      +-----------------------------------------+
      | NOTE: binary data not shown in terminal |
      +-----------------------------------------+
      

      bzcat 接受 bzip2 内容:

      $ http 127.0.0.1:8000 | bzcat
      Hello ☃
      Hello ☃
      Hello ☃
      Hello ☃
      Hello ☃
      Hello ☃
      Hello ☃
      Hello ☃
      Hello ☃
      Hello ☃
      

      数据显示正确,因为终端使用utf-8编码。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-02-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-10-24
        相关资源
        最近更新 更多