构建占位图片服务

构建一个简单的占位图片服务,使用Form来验证图片的尺寸:

class ImageForm(forms.Form):
    # valid height and width
    height = forms.IntegerField(min_value=1, max_value=1000)
    width = forms.IntegerField(min_value=1, max_value=1000)

    def generate(self, image_format='PNG'):
        height = self.cleaned_data['height']
        width = self.cleaned_data['width']
        image = Image.new('RGB', (width, height))
        content = BytesIO()
        image.save(content, image_format)
        content.seek(0)
        return content


def placeholder(request, width, height):
    form = ImageForm({'height': height, 'width': width})
    if form.is_valid():
        image = form.generate()
        return HttpResponse(image, content_type='image/png')
    else:
        return HttpResponseBadRequest('Invalid Image Request')


urlpatterns = (
    url(r'^image/(?P<width>\d+)x(?P<height>\d+)/$', placeholder, name='placeholder'),
)

加入缓存

先设置缓存key,先尝试读取key对应的缓存,如缓存不存在则生成图片并写缓存:

class ImageForm(forms.Form):
    # valid height and width
    height = forms.IntegerField(min_value=1, max_value=1000)
    width = forms.IntegerField(min_value=1, max_value=1000)


    def generate(self, image_format='PNG'):
        height = self.cleaned_data['height']
        width = self.cleaned_data['width']
        key = '{}x{}.{}'.format(height, width, image_format)
        content = cache.get(key)
        if content is None:
            image = Image.new('RGB', (width, height))
            content = BytesIO()
            image.save(content, image_format)
            content.seek(0)
            cache.set(key, content, 60*60)
        return content

设置Etag缓存

上述缓存的操作发生在服务器端,服务器端对缓存进行管理操作。同时可以进一步设置Etag使服务器对相同的资源返回304 Not Modified响应,以此来节约服务器运算资源和带宽。

def generate_etag(request, width, height):
    content = 'placeholder: {} x {}'.format(width, height)
    return hashlib.sha1(content.encode('utf-8')).hexdigest()


@etag(generate_etag)
def placeholder(request, width, height):
    form = ImageForm({'height': height, 'width': width})
    if form.is_valid():
        image = form.generate()
        return HttpResponse(image, content_type='image/png')
    else:
        return HttpResponseBadRequest('Invalid Image Request')

测试代码

启动测试服务器:
python placeholder.py runserver
根据构建的url模式,访问图片占位符地址:http://127.0.0.1:8000/image/200x200/
状态码为200,原始响应头如下:

HTTP/1.0 200 OK
Date: Sun, 29 Aug 2021 07:04:54 GMT
Server: WSGIServer/0.1 Python/2.7.18
Content-Length: 320
ETag: "3720c8007695a4c03152ef519310374e908ff60f"
Content-Type: image/png

再次请求该地址,状态码为304,原始响应头如下:

HTTP/1.0 304 Not Modified
Date: Sun, 29 Aug 2021 07:06:54 GMT
Server: WSGIServer/0.1 Python/2.7.18
Content-Length: 0
ETag: "3720c8007695a4c03152ef519310374e908ff60f"

同时可以注意到第二次的Content-Length为0,服务器只有headers的返回,没有body内容,节约了服务器带宽资源。

完整演示代码文件placeholder.pyplaceholder.py.txt 下载后删除.txt后缀即可。

上述演示代码存在的问题

  • 图片宽度过小是,文字会溢出,可将文字起始位置设置为(0, 0)
  • generate 方法与 ImageForm 耦合到一起了,难以在其他的地方复用
  • 图片缓存部分的逻辑,嵌入到了generate方法中,两者再次产生耦合,后续如果需要修改相关功能,可能比较困难,同时如果也会增加测试该代码的难度
  • 无法从外部(如通过URL参数)对generate方法指定image_format参数,如需实现此功能,需要同时修改urlpatternsgenerate_etag装饰器的代码,同时需要在ImageForm对图片格式参数进行限制。

包含模板文件的完整项目演示文件 placeholder.zip

标签: Python, Django

添加新评论