轻量级Django学习(2)——无状态的Web应用

占位图片服务器

  • 一个典型的占位图片服务器,接收一个指定图片大小的URL并生成该图片。所有创建 请求图片所需的信息的包含在URL,无需进行权限验证,是一个很好的无状态应用的例子。

HTTP本身是个无状态的协议,就是说,每一个到达服务器的请求都独立于之前的请求。如果需要特定的状态,则需要把它加到应用层上

Django这样的框架使用Cookie及其他机制将统一客户端发送的请求绑定在一起。

  • 利用模板创建项目

借助在Django学习1中的模板,创建一个placeholder的新项目

django-admin startproject placeholder --template=project_name
  • 视图与URL模式

    我们用两个视图来生成响应,一个视图用来按照请求的宽度高度渲染占位图像,另一个用来渲染主页面内容。

def placeholder(request,width,height):
   # TODO : Rest of the view will go here
   return HttpResponse('OK')
def index(request):
   return HttpResponse('Hello World')

我们还要一个指向placeholder视图的路由,包含两个参数:widthheight,Django的URL模式使用正则表达式来匹配输入的URL

# 关联URL模式,捕捉的模式组会以位置参数的形式传递给视图,
# 命名的组会以关键字参数的形式传递,通过?P语法来捕获被命名的组,
# 并用[0-9]来匹配任意数字字符
urlpatterns = (
   url(r'^image/(?P<width>[0-9]+)×(?P<height>[0-9]+)/',placeholder,name='placeholder'),
   url(r'^$',index,name='homepage'),
)

完成模式的设置后,像**/image/30×25/的这样的URL会被路由到placeholder中,并传递这些数值(width=30、height=50)。同时以homepage为名称与placeholder视图新的路由一起被加入index**路由上。

  • 占位视图

我们说到占位视图接收两个整数参数来设置图片的高度和宽度。但是尽管正则表达式保证了高度宽度只包含整数,但它们仍是以字符串的形式传递给视图的。为了确保在一个可管理的尺寸内,我们用表单来实现验证。

典型的表单用于验证POSTGET内容,它们也可以用于验证URL和cookie的特定值。

from django.http import HttpResponse,HttpResponseBadRequest
from django.conf.urls import url
from django.core.wsgi import get_wsgi_application
from django import forms
# 视图
class ImageForm(forms.Form):
   height = forms.IntegerField(min_value=1, max_value=2000)
   width = forms.IntegerField(min_value=1, max_value=2000)
def placeholder(request,width,height):
   form = ImageForm({'height':height,'width':width})
   if form.is_valid():
       height = form.cleaned_data['height']
       width = form.cleaned_data['width']
       # TODO : Generate image of requested size
       return  HttpResponse('OK')
   else:
       # 如果表单无效,视图发送错误信息返回客户端
       return HttpResponseBadRequest('Invalid Image Request')
  • 图片处理

要生成实际的图片,我们还需要安装Pillow模块来处理图片:

pip install Pillow

通过Pillow创建图片需要两个参数,一个以元组表示的颜色模式和尺寸。

我们在placeholder.py中的视图使用RGB模式和在表单中处理过的数据尺寸

from io import  BytesIO
from PIL import Image
class ImageForm(forms.Form):
   height = forms.IntegerField(min_value=1, max_value=2000)
   width = forms.IntegerField(min_value=1, max_value=2000)

   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():
       # 视图调用from.generate 来获取创建的图片,图片的字节被用于之后创建响应体
       image = form.generate()
       return  HttpResponse(image,content_type='image/png')
   else:
       return HttpResponseBadRequest('Invalid Image Request')

Image 中加入了一个新的generate方法,用于封装创建图片的逻辑,它接收一个参数来设置图片格式,默认为PNG,并以字节的形式返回图片内容。

表单验证图片尺寸通过后,视图会成功返回一张请求宽度和高度的PNG图片。图片内容不写入磁盘,直接发送给客户端。

我们还可以使用ImageDraw模块在图片中加入文字,如:

from PIL import Image,ImageDraw
 def generate(self,image_format='PNG'):
     height = self.cleaned_data['height']
     width = self.cleaned_data['width']
     image = Image.new('RGB',(width,height))
     # 使用ImageDrawm模块在图片中加入文字
     draw = ImageDraw.Draw(image)
     text = '{}×{}'.format(width,height)
     textwidth,textheight = draw.textsize(text)
     if textwidth < width and textheight < height :
        # // 是整数除法
        texttop = (height - textheight) // 2
        textleft = (width - textwidth) // 2
       # 向尺寸合适的地方加入覆盖文字
        draw.text((textleft,texttop),text,fill=(255,255,255))
     content = BytesIO()
     image.save(content,image_format)
     content.seek(0)
     return content
  • 加入缓存

每次请求视图时,占位图片视图都会重新生成图片。由于图片的宽度和高度是由最初值来设置的,常常会对服务器提出不必要的请求。

缓存是避免这种重复的重要办法,当要确定如何为服务提供缓存时,有两种方案可供考虑:服务器端客户端

对于服务器端缓存,可以方便地使用Django地缓存工具。这会把内存开销转换为缓存存储,同时会节约生成图片所需的CPU周期。

``` python

from django.core.cache import cache def generate(self,image_format='PNG'): height = self.cleaned_data['height'] width = self.cleaned_data['width'] # 通过宽度、高度和图片格式生成一个缓存键值 key = '{}.{}.{}'.format(width,height,image_format) # 新图片创建前,检查缓存是否已经存储了图片 content = cache.get(key) if content is None: image = Image.new('RGB',(width,height)) # 使用ImageDrawm模块在图片中加入文字 draw = ImageDraw.Draw(image) text = '{}×{}'.format(width,height) textwidth,textheight = draw.textsize(text) if textwidth < width and textheight < height : # // 是整数除法 texttop = (height - textheight) // 2 textleft = (width - textwidth) // 2 draw.text((textleft,texttop),text,fill=(255,255,255)) content = BytesIO() image.save(content,image_format) content.seek(0) # 当未找到缓存图片且创建了新图片时,通过键值将图片保存一小时 cache.set(key,content,60 * 60) return content ```

缓存的另一个方案,就是关注客户端性能并使用浏览器內建的缓存。Django引入了了一个创建并使用视图的ETag标头的etag修饰符。该修饰符接收一个参数,一个从请求和视图参数中生成ETag标头的函数。

import os
import hashlib
from django.views.decorators.http import etag
# generate_etag是个新函数,接收placeholder视图中的参数
# 使用hashlib来返回一个基于width和height值变化的不透明的ETag值
def generate_etag(request,width,height):
   content = 'Placeholder:{0}×{1}'.format(width,height)
   return  hashlib.sha1(content.encode('utf-8')).hexdigest()
# generate_etag函数会被发送到placeholder视图的etag修饰符中
@etag(generate_etag)
def placeholder(request,width,height):

使用etag修饰符具有在视图被访问之前进行ETag计算的优势。

  • 主页面视图

完成占位视图后,需要渲染一个基本的HTML模板。首先我们加入StaticTemplate设置。让Django可以按照路径找到模板和静态资源。添加资源后的目录应该是这样的:

placeholder/
   placeholder.py
   templates/
       home.html
   static/
       site.css

手动配置路径时,为了避免对路径的硬性编码,使用Python标准库中的os模块

import os
import hashlib
import sys
# 附加配置
DEBUG = os.environ.get('DEBUG','on') == 'on'
# 随机的秘钥
SECRET_KEY = os.environ.get('SECRET_KEY','p*0c^!*uu70+vqq%y@xp66g*n8i9$au9$nx+d4p))6d_&kl@nb')
BASE_DIR = os.path.dirname(__file__)
# 设置
settings.configure(
   DEBUG = DEBUG,
   SECRET_KEY = SECRET_KEY,
   ROOT_URLCONF = __name__,
   MIDDLEWARE_CLASSES = (
       'django.middleware.common.CommonMiddleware',
       'django.middleware.csrf.CsrfViewMiddleware',
       'django.middleware.clickjacking.XFrameOptionsMiddleware',
   ),
   INSTALLED_APPS=(
     'django.contrib.staticfiles',
   ),
   TEMPLATES=(
       {
           'BACKEND':'django.template.backends.django.DjangoTemplates',
           'DIRS':(os.path.join(BASE_DIR,'templates'),),
       },
   ),
   STATICFILES_DIRS=(
       os.path.join(BASE_DIR,'static'),
   ),
   STATIC_URL = '/static/',
)

最后,我们加入简单的主页面模板和CSS文件,

** home.html**:

{% load staticfiles %}
<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="utf-8">
   <title>Django Placeholder Images</title>
   <link rel="stylesheet" href="{% static 'site.css' %}" type="text/css">
</head>
<body>
   <h1>Django Placeholder Images</h1>
   <p>This server can be used for serving placeholder
   images for any web page.</p>
   <p>To request a placeholder image of a given width and height simply include an image with the source pointing to
   <b>/image/&lt;width&gt;x&lt;height&gt;/</b>
   on this server such as:</p>
   <pre>
       &lt;img src="{{ example }}" &gt;
   </pre>
   <h2>Examples</h2>
   <ul>
       <li><img src="{% url 'placeholder' width=50 height=50 %}"></li>
       <li><img src="{% url 'placeholder' width=100 height=50 %}"></li>
       <li><img src="{% url 'placeholder' width=50 height=100 %}"></li>
   </ul>
</body>
</html>

site.css:

body{
   text-align: center;
}
ul{
   list-type:none;
}
li{
   display: inline-block;
}

当然,我们要在placeholder.py中更新index视图来渲染这个模板。

from django.core.urlresolvers import reverse
from django.shortcuts import render
#更新index视图来渲染模板
def index(request):
   # 更新过的index视图通过翻转placeholder视图来创建一个URL样例
   # 并将它传给模板上下文
   example = reverse('placeholder',kwargs={'width':50,'height':50})
   context = {
       'example':request.build_absolute_uri(example)
   }
   # 通过render快捷方式来渲染home.html模板
   return render(request,'home.html',context)
  • 运行

    最后运行完整的项目查看创建的占位图片服务

python placeholder.py runserver

结果: 输入图片说明

  • 问题总结

应用在python3环境下顺利跑通,在python2.7下会遇到字符编码问题

转载于:https://my.oschina.net/u/3193575/blog/819106

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值