构建占位图片服务
构建一个简单的占位图片服务,使用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.py
:placeholder.py.txt 下载后删除.txt
后缀即可。
上述演示代码存在的问题
- 图片宽度过小是,文字会溢出,可将文字起始位置设置为
(0, 0)
generate
方法与 ImageForm
耦合到一起了,难以在其他的地方复用- 图片缓存部分的逻辑,嵌入到了
generate
方法中,两者再次产生耦合,后续如果需要修改相关功能,可能比较困难,同时如果也会增加测试该代码的难度 - 无法从外部(如通过URL参数)对
generate
方法指定image_format
参数,如需实现此功能,需要同时修改urlpatterns
及 generate_etag
装饰器的代码,同时需要在ImageForm
对图片格式参数进行限制。
包含模板文件的完整项目演示文件 placeholder.zip