使用Django/Flask返回图像,相对简单很多,但掌握使用Python CGI也是有用的。 以生成一个随机二维码为范例。

使用Python2.7,请直接参考:http://lost-theory.org/python/dynamicimg.html  

Python >= 3.4

Step 1 生成一个随机二维码,并保存到文件。

qrcode 使用PIL或Pillow生成二维码。

  • QRCode.make_image() 创建的Image对象可等同Pillow时Image.new()创建的对象。
#!/usr/bin/env python3
# Imports
import random
import qrcode

qr = qrcode.QRCode(error_correction=qrcode.constants.ERROR_CORRECT_H, border=1)
qr.add_data('INT:%d' % random.randint(4000, 4999))
qr.make(fit=True)
img = qr.make_image(fill_color="white", back_color="black")

img.save("image.PNG", format='PNG')

将文件保存到任意目录并运行,每次执行会产生不同的二维码。  

Step 2 修改为CGI脚本

已经创建了动态的二维码,现在希望每次浏览器刷新都显示不同的图像,但不希望产生在服务器端的文件写入操作,。

  • 需要使用file-like对象代替文件,在Python2.7中使用StringIO.StringIO,在Python 3.4以上版本中,使用io.BytesIO处理二进制文件,使用io.StringIO处理文本。
    • 使用getvalue()获取完整的缓冲区二进制字节。
    • 使用close()关闭对象并丢弃内存缓冲区
  • 参考HTTP/1.1协议
    • 返回图像,Header至少需要Content-typeContent-length字段。
    • header和body之间需要有个空行。
  • 使用sys.stdout.flush 强制先输出header再输出body。
  • 文本使用sys.stdout.writeprint,二进制则需使用sys.stdout.buffer.write 。
#!/usr/bin/env python3
# Imports
import sys
import random
from io import BytesIO
import qrcode

qr = qrcode.QRCode(error_correction=qrcode.constants.ERROR_CORRECT_H, border=1)
qr.add_data('INT:%d' % random.randint(4000, 4999))
qr.make(fit=True)
img = qr.make_image(fill_color="white", back_color="black")

# img.save("image.PNG", format='PNG')
f = BytesIO()
img.save(f, format='PNG')
data = f.getvalue()
f.close()

l = len(data)
sys.stdout.write("Content-type: image/png\n")
sys.stdout.write("Content-length: %d\n\n" % l)
sys.stdout.flush()
sys.stdout.buffer.write(data)

为什么要避免服务器端的文件写入

  • 安全性的要求:执行cgi的用户往往只有极小权限。
  • 性能的要求:IO性能是大多数VPS的性能瓶颈。