(二)无状态的web应用(单py的Django占位图片服务器)
本文为作者原创,转载请注明出处(http://www.cnblogs.com/mar-q/)by 负赑屃
阅读本文建议了解Django框架的基本工作流程,了解WSGI应用,如果对以上不是很清楚,建议结合《Lightweight Django》(中文为《轻量级Django》)进行阅读。本文结合该书实现了一个占位图片服务器,对深入理解Django框架使用流程,了解无状态web应用有一定帮助。
这里的状态是指是客户端和服务器之间会话的状态(例如写入权限、读取权限、验证身份等等),HTTP本身是一个无状态协议,而Django这样的框架使用cookie、session等等会话机制,把同一个客户端发送的请求进行了特定的状态绑定。但是这样存在的问题比较多,比如分布式服务器架构的请求在这种一致性状态下的读取和写入冲突。所以无状态的应用不必在服务器维持一致的状态,而是根据请求来进行操作,易于扩展、缓存和负载均衡。URL可以传输大多数状态,采取可复用应用和可组合服务两种方式。这里通过图片占位服务器为例进行一个实现:
一、什么是占位图片服务器
占位图片服务器就是指,服务器接收URL传递的图片大小、颜色等信息,并生成图片,一般不需要权限验证,因此是一个很好的无状态应用候选。
二、创建项目(Django版本1.11.3)
通过自建模板进行创建,如果看不懂这里建议先阅读第一篇http://www.cnblogs.com/mar-q/p/7841972.html:
django-admin.py startproject placeholder --template=template
1、修改URL匹配模式:增加参数width和height
urlpatterns=[ url(u'^$',index, name='homepage'), url(u'^image/(?P<width>[0-9]+)x(?P<height>[0-9]+)/$', placeholder, name='placeholder'), ]
2、对setting进行配置,这里我们需要创建一个简单的展示界面,有一个html和对应的css,需要对它们进路径配置。需要注意的是当前的目录结构,setting中的BASE_DIR为相对路径,将BASE_DIR与templates和static目录结合即可得到相应的html和css路径。
DEBUG = os.environ.get('DEBUG','on')=='on' SECRET_KEY = os.environ.get('SECRET_KEY', '&8x8ono))lhdi_6fg!h_9uv3l97w$m$(m6lg&0tttyb2e_lnlv') ALLOWED_HOSTS=['*'] BASE_DIR = os.path.dirname(__file__) settings.configure( DEBUG=DEBUG, SECRET_KEY=SECRET_KEY, ROOT_URLCONF=__name__, ALLOWED_HOSTS=ALLOWED_HOSTS, MIDDLEWARE=[ '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')], # 'APP_DIRS': True, } ], STATICFILES_DIRS=[ os.path.join(BASE_DIR, 'static'), ], STATIC_URL = '/static/' )
3、创建一个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_formate='PNG'): height = self.cleaned_data['height'] width = self.cleaned_data['width'] key = '{}.{}.{}'.format(width, height, image_formate) content = cache.get(key) ##增加服务器缓存 if content is None: image = Image.new('RGB', (width, height)) draw = ImageDraw.Draw(image) text = '{}x{}'.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_formate) content.seek(0) cache.set(key, content, 60*60) return content
这个ImageForm类是一个表单类,用于接收URL传递过来的图片宽高信息,定义了generate方法,用于生成对应尺寸的占位图片,同时增加了cache缓存的设置,检测到对应尺寸的content先从缓存获取(Django默认使用本地过程、内存缓存)。
4、创建视图函数placeholder和index:
def generate_etag(req, width, height): content = 'Placeholder: {0}x{1}'.format(width, height) return hashlib.sha1(content.encode('utf-8')).hexdigest() @etag(generate_etag) def placeholder(req, width, height): form = ImageForm({'width':width, 'height':height}) if form.is_valid(): image = form.generate() return HttpResponse(image, content_type='image/png') else: return HttpResponseBadRequest('图片格式错误’) def index(req): example = reverse('placeholder', kwargs={'width':50, 'height':50})##通过url标签和参数获取地址 context = { 'example': req.build_absolute_uri(example)##把上面形成的地址传递给页面 } return render_to_response('home.html', context)
注意这里使用了etag修饰符,这里使用它的主要目的是通过客户端浏览器进行缓存,这里定义了一个generate_etag的函数,它接收placeholder视图函数的参数,并通过hashlib建立一个基于传入的weigh和height变化的加密值,这个值就是etag值,客户端第一次访问服务器时会向服务器发送占位图片请求,服务器生成并返回图片以及这个etag值,客户端会将其在缓存内配对缓存,当下一次再进行同样参数的访问时,它将会收到304 Not Modified的返回值,浏览器会使用自身的缓存。如图所示:
5、为主页视图创建template和static文件(html及css)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Place Holder</title> {% load staticfiles %} <link rel="stylesheet" href="{% static 'site.css' %} " type="text/css"> </head> <body> <h1>Django Placeholder Images</h1> <p>该服务应用的功能是提供图片占位符</p> <p>请求服务器响应需要提供图片的width和height参数<b>/image/<width>x<height>/</b></p> <pre> < img src="{{ example }}" ></pre> <h2>Example</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>
Html中我们定义了5段文字,其中第4段调用了index函数中对应的example参数,这个参数中保存的是一个示例的url地址构造方式,在index中我们可以看到使用了reverse和req.build_absolute_uri,此时会返回一个完整的链接地址。
body{ text-align: center; } ul{ list-style: none;font-size:50px } li{ display: inline-block; }
6、最后我们添加wsgi服务,并在main函数中写入调用入口:
application = get_wsgi_application() if __name__ == "__main__": from django.core.management import execute_from_command_line execute_from_command_line(sys.argv)
三、运行并显示效果:
python templateHello.py runserver 0.0.0.0:8000 ##或者 gunicorn -w 4 -b 0.0.0.0:8000 hello --log-file=-
完整代码可以参考https://github.com/helloworld77/DjangoPrimer.git。很少在上面放项目,基本都是看别人的代码,水平有限,还请见谅。