http://chaoxz2005.blog.163.com/blog/static/15036542012863405266/

http://www.dajo.com.cn/a/boke/python/2013/1125/146.html

这里我们将会创建一个仿制TinyURL的应用,将URLs存储到一个redis实例。为了这个应用,我们将会使用的库包括,用于模板的Jinja 2、用于数据库层的redis和用于WSGI层的Werkzeug。

你可以使用pip安装需要的库:

[plain] view plaincopy

 
  1. $ pip install Jinja2 redis

你还需要确保在本地机器上正在运行着一个redis服务器。如果你在使用OS X,你可以使用brew来安装它:

[plain] view plaincopy

 
  1. $ brew install redis

如果你使用ubuntu或debian,你可以使用apt-get:

[plain] view plaincopy

 
  1. $ sudo apt-get install redis

redis是针对UNIX系统开发的,且从未真正地为在Windows上运行进行设计。然而为了开发目的,非官方的移植版本也可以很好地工作。你可以从github获得这些库。

[译者:可能还需要添加redis的python支持,在终端执行sudo easy_install redis即可。]

介绍Shortly

本教程中我们将会一起来使用Werkzeug创建一个简单的URL简化服务。请记住Werkzeug不是框架,而是一个可以创建你自己的框架或应用的、非常灵活的库。这里我们使用的方法只是诸多可用方法的一种。

数据存储方面,我们这里将会使用redis来代替关系型数据库,以保持其简洁性。这正是redis擅长的工作类型。

最终结果会如下图这样:

第0步:WSGI基础介绍

Werkzeug是一个WSGI功能库。WSGI本身是一个用来确保你的web应用能够与webserver进行对话,更重要的是,确保web应用之间能够一起配合工作的协议或约定。

在没有Werkzeug帮助下,用WSGI实现的一个基本“Hello World”应用看起来是这样的:

[python] view plaincopy

 
  1. def application(environ, start_response):
  2. start_response(‘200 OK’, [(‘Content-Type’, ‘text/plain’)])
  3. return [‘Hello World!’]

WSGI应用是你可以调用、传递一个environ字典和一个start_response函数的东西。environ包含所有的传入信息,start_response函数可以用来指示response的开始。使用Werkzeug之后,你将不再需要直接处理被提交上来的请求(request)和应答(response)对象。

请求数据获取environ对象,并允许你以一种良好的方式访问environ中的数据。response对象本身也是一个WSGI应用,提供了很多友好的创建response的方法。

下面的代码演示了如何编写带有response对象的应用:

[python] view plaincopy

 
  1. from werkzeug.wrappers import Response
  2. def application(environ, start_response):
  3. response = Response(‘Hello World!’, mimetype=‘text/plain’)
  4. return response(environ, start_response)

下面是一个可以查看URL中查询字符串的扩展版本(不同之处在于,它查找URL中的name参数的值,并替换单词”World”):

[python] view plaincopy

 
  1. from werkzeug.wrappers import Request, Response
  2. def application(environ, start_response):
  3. request = Request(environ)
  4. text = ‘Hello %s!’ % request.args.get(‘name’, ‘World’)
  5. response = Response(text, mimetype=‘text/plain’)
  6. return response(environ, start_response)

以上就是所有关于WSGI的你需知的内容。

第1步:创建文件夹

在开始之前,先创建本应用需要的目录结构:

[plain] view plaincopy

 
  1. /shortly
  2. /static
  3. /templates

shortly文件夹不是一个python包,而仅仅用于放置我们的文件。在这个文件夹中,我们将会使用接下来的步骤直接放置我们的主模块。在static中的文件允许此应用的用户通过HTTP访问,这是存放css和javascript文件的地方。在templates中我们将会创建Jinja2版本的模板代码,在将来教程中创建的模板会保存在此目录之中。

第2步:基本架构

下面直入主题,创建我们应用的一个模块。先在shortly中创建shortly.py文件。在开始时需要一系列导入语句,我会在这里加入所有的import,甚至包括那些当前没有立即使用的,这样可以使得代码更加清晰。

[python] view plaincopy

 
  1. import os
  2. import redis
  3. import urlparse
  4. from werkzeug.wrappers import Request, Response
  5. from werkzeug.routing import Map, Rule
  6. from werkzeug.exceptions import HTTPException, NotFound
  7. from werkzeug.wsgi import SharedDataMiddleware
  8. from werkzeug.utils import redirect
  9. from jinja2 import Environment, FileSystemLoader

接下来可以创建我们应用的基本架构,以及一个创建它的新实例的函数。我们也可以选择带有一个WSGI中间件,并在web上导出所有static中的文件。

[python] view plaincopy

 
  1. class Shortly(object):
  2. def __init__(self, config):
  3. self.redis = redis.Redis(config[‘redis_host’], config[‘redis_port’])
  4. def dispatch_request(self, request):
  5. return Response(‘Hello World!’)
  6. def wsgi_app(self, environ, start_response):
  7. request =  Request(environ)
  8. response = self.dispatch_request(request)
  9. return response(environ, start_response)
  10. def __call__(self, environ, start_response):
  11. return self.wsgi_app(environ, start_response)
  12. def create_app(redis_host=‘localhost’, redis_port=6379, with_static=True):
  13. app = Shortly({
  14. ‘redis_host’:   redis_host,
  15. ‘redis_port’:   redis_port
  16. })
  17. if with_static:
  18. app.wsgi_app = SharedDataMiddleware(app.wsgi_app, {
  19. ‘/static’:  os.path.join(os.path.dirname(__file__), ‘static’)
  20. })
  21. return app

最后,我们可以添加一段启动本地开发服务器的代码,其中包括一个自动代码reloader和一个debugger:

[python] view plaincopy

 
  1. if __name__ == ‘__main__’:
  2. from werkzeug.serving import run_simple
  3. app = create_app()
  4. run_simple(‘127.0.0.1’, 5000, app, use_debugger=True, use_reloader=True)

这里的基本思想是,Shortly类是一个真实的WSGI应用。__call__函数直接分派至wsgi_app。这样便可以像在create_app函数中所作的那样,通过包装wsgi_app的方法来应用中间件。实际的wsgi_app方法接下来创建一个Request对象,并且调用dispatch_request,然后这个方法必须返回一个再次由WSGI应用评估的Response对象。正如你看到的:下面全是乌龟[译注:原文turtles all the way down,这是对由“不动的推动者”悖论提出的宇宙学中无限退化问题的一个诙谐的表述,表示循环往复的意思]。我们创建的Shortly类,以及Werkzeug中的任何请求对象,共同实现了WSGI接口。这样做的效果之一就是你甚至可以从dispatch_request方法中返回另一个WSGI应用。

create_app工厂方法可以用来创建我们的应用的一个新的实例。不仅仅会向应用传递一些如配置信息之类的参数,还可以选择增加一个导出静态文件的WSGI中间件。该方法甚至可以在我们没有设置服务器来提供static中的文件时,对这些文件进行访问。这对于开发来说是非常有帮助的。

插曲:运行应用

现在你应该能够使用python运行这个文件,并可以看到你本地机器上的一个服务:

[plain] view plaincopy

 
  1. $ python shortly.py
  2. * Running on http://127.0.0.1:5000/
  3. * Restarting with reloader: stat() polling

它还会告诉你reloader被激活。它将会一些相关技术来检查是否有哪个磁盘上的文件被修改,并自动重新开始。

现在访问URL将会看到”Hello World!”。

第3步:环境

现在已经拥有了基础的应用类,我们可以让构造函数做一些有用的工作,并在其中提供一些方便使用的帮助函数。我们需要渲染模板并连接到redis,因此下面对这个类进行一些扩展:

[python] view plaincopy

 
  1. def __init__(self, config):
  2. self.redis = redis.Redis(config[‘redis_host’], config[‘redis_port’])
  3. template_path = os.path.join(os.path.dirname(__file__), ‘templates’)
  4. self.jinja_env = Environment(loader=FileSystemLoader(template_path),
  5. autoescape=True)
  6. def render_template(self, template_name, **context):
  7. t = self.jinja_env.get_template(template_name)
  8. return Response(t.render(context), mimetype=’text/html’)

第4步:路由

接下来是路由。路由是匹配并解析URL为我们可用的形式的过程。Werkzeug提供了一个灵活的内嵌路由系统,我们可以用它完成这项工作。它工作的方式是,你创建一个Map实例,并增加一些Rule对象。每个规则包含一个用来尝试针对一个endpoint匹配URL的模式模板。endpoint通常是一个字符串,可以用来唯一识别这个URL。我们还可以用它对URL做自动反转,不过这不是我们在本教程要做的工作。

将下面的代码添加至构造函数:

[python] view plaincopy

 
  1. self.url_map = Map([
  2. Rule(‘/’, endpoint=‘new_url’),
  3. Rule(‘/<short_id>’, endpoint=‘follow_short_link’),
  4. Rule(‘/<short_id>+’, endpoint=’short_link_details’)
  5. ])

这里我们创建了一个带有三个规则的URL map。“/”表示URL空间的根,这里我们将仅仅分派一个实现了创建一个新URL的逻辑的函数。然后的一个规则连接短链接到目标URL,另一个带有同样的规则,只是在短链接之后增加了一个加号(+),将其连接到短链接的细节信息。

所以,我们如何从endpoint找到一个函数呢?这取决于你自己。我们将在此教程中使用的方法是会调用类本身的一个on_加上endpoint的函数。这里是具体的实现:

[python] view plaincopy

 
  1. def dispatch_request(self, request):
  2. adapter = self.url_map.bind_to_environ(request.environ)
  3. try:
  4. endpoint, values = adapter.match()
  5. return getattr(self, ‘on_’ + endpoint)(request, **values)
  6. except HTTPException, e:
  7. return e

我们讲URL map绑定到当前环境,并获得一个URLAdapter。该适配器可以用来匹配请求,但也可以反转URL。其匹配方法将会返回endpoint和一个URL中值的字典。例如对于follow_short_link规则来说,它拥有一个变量部分称为short_id。当我们访问http://localhost:5000/foo时,我们将会得到其伴随的值:

[plain] view plaincopy

 
  1. endpoint = ‘follow_short_link’
  2. values = {‘short_id’: u’foo’}

如果它没能匹配任何东西,则会唤起一个NotFound异常,这是一个HTTPException。所有的HTTP异常本身也都是WSGI应用,她们渲染一个默认的错误页面。因此我们仅需捕获所有这些信息,然后返回错误本身。

如果所有工作正常,我们便调用函数on_ + endpoint,并将请求作为参数传递给它,就好像所有的URL参数作为关键词参数,并返回那个函数返回的应答对象一般。

第5步:第一个视图

让我们开始第一个视图:对于新的URL的视图:

[python] view plaincopy

 
  1. def on_new_url(self, request):
  2. error = None
  3. url = ‘’
  4. if request.method = ‘POST’:
  5. url = request.form[‘url’]
  6. if not is_valid_url(url):
  7. error = ‘Please enter a valid URL’
  8. else:
  9. short_id = self.insert_url(url)
  10. return redirect(‘/%s+’ % short_id)
  11. return self.render_template(‘new_url.html’, error=error, url=url)

这里的逻辑应当非常易于理解。基本上是我们检查请求的方法是一个POST,这是我们验证URL,并在数据库中增加一个新的入口,然后重定位到细节页面。这意味着我们需要编写一个函数和一个帮助方法。对于URL验证,下面的方法就足够了:

[python] view plaincopy

 
  1. def is_valid_url(url):
  2. parts = urlparse.urlparse(url)
  3. return parts.scheme in (‘http’, ‘https’)

为了插入URL,我们所需做的就是在类中增加下面的一个小方法:

[python] view plaincopy

 
  1. def insert_url(self, url):
  2. short_id = self.redis.get(‘reverse-url:’ + url)
  3. if short_id is not None:
  4. return short_id
  5. url_num = self.redis.incr(‘last-url-id’)
  6. short_id = base36_encode(url_num)
  7. self.redis.set(‘url-target:’ + short_id, url)
  8. self.redis.set(‘reverse-url:’ + url, short_id)
  9. return short_id

reverse-url:加上URL会存储short id。如果URL已经被提交了,这肯定不会是None,而是我们需要的short id,我们便可以仅仅返回这个值。否则我们增加last-url-id键,并将其转换为基于base36的形式。然后我们存储这个链接和redis中的反转入口。下面是转换到base 36的函数:

[python] view plaincopy

 
  1. def base36_encode(number):
  2. assert number >= 0, ‘positive integer required’
  3. if number == 0:
  4. return ‘0’
  5. base36 = []
  6. while number != 0:
  7. number, i = divmod(number, 36)
  8. base36.append(‘0123456789abcdefghijklmnopqrstuvwxyz’[i])
  9. return ‘’.join(reversed(base36))

这样只要添加模板,这个视图就可以工作了。我们会在将来创建这个模板,现在先编写其他视图,然后在完成模板工作。

第6步:重定向视图

重定向视图比较简单。所有要做的工作就是在redis中查找链接后重定向。在此之外,我们还增加了一个计数器,这样就可以知道这些链接被点击的情况。

[python] view plaincopy

 
  1. def on_follow_short_link(self, request, short_id):
  2. link_target = self.redis.get(‘url-target:’ + short_id)
  3. if link_target is None:
  4. raise NotFound()
  5. self.redis.incr(‘click-count:’ +short_id)
  6. return redirect(link_target)

在URL不存在的情况下,我们会手工唤起一个NotFound异常,这将会冒泡到dispatch_request函数,并被转换为一个默认的404应答。

第7步:细节视图

链接细节视图非常类似,仅仅是对一个模板的再次渲染。在查询目标之外,我们还询问redis该链接被点击的次数,如果这个键尚不存在则默认是0:

[python] view plaincopy

 
  1. def on_short_link_details(self, request, short_id):
  2. link_target = self.redis.get(‘url-target:’ + short_id)
  3. if link_target is None:
  4. raise NotFound()
  5. click_count = int(self.redis.get(‘click-count:’ + short_id) or 0)
  6. return self.render_template(‘short_link_details.html’,
  7. link_target=link_target,
  8. short_id=short_id,
  9. click_count=click_count
  10. )

请注意redis通常使用字符串,因此你必须手动将click count转变成int。

第8步:模板

下面是所有的模板。只需将它们放置在templates文件夹。Jinja2支持模板继承,因此我们首先要做的是创建一个布局模板,在其中使用块(block)作为占位符。我们还需要设置Jinja2从而可以自动的从HTML规则中分离字符串,这样就不必在这上面花费时间。这样可以防止XSS攻击和渲染错误。

layout.html

[html] view plaincopy

 
  1. <!doctype html>
  2. <title>{% block title %}{% endblock %} | shortly</title>
  3. <link rel=stylesheet href=/static/style.css type=text/css>
  4. <div class=box>
  5. <h1><a href=/>shortly</a></h1>
  6. <p class=tagline>Shortly is a URL shortener written with Werkzeug {% block body %}{% endblock %}
  7. </div>

new_url.html

[html] view plaincopy

 
  1. {% extends "layout.html" %}
  2. {% block title %}Create New Short URL{% endblock %}
  3. {% block body %}
  4. <h2>Submit URL</h2>
  5. <form action="" method=post>
  6. {% if error %}
  7. <p class=error><string>Error:</strong> {{ error }}
  8. {% endif %}
  9. <p>URL:
  10. <input type=text name=url value="{{ url }}" class=urlinput>
  11. <input type=submit value="Shorten">
  12. </form>
  13. {% endblock %}

short_link_details.html

[html] view plaincopy

 
    1. {% extends "layout.html" %}
    2. {% block title %}Details about /{{ short_id }}{% endblock %}
    3. {% block body %}
    4. <h2><a href="/{{ short_id }}">/{{ short_id }}</a></h2>
    5. <dl>
    6. <dt>Full link
    7. <dd class=link><div>{{ link_target }}</div>
    8. <dt>Click count:
    9. <dd>{{ click_count }}
    10. </dl>
    11. {% endblock %}

Werkzeug教程的更多相关文章

  1. Flask 教程

    官方文档 推荐教程 环境 pip install virtualenv cd proj_fold virtualenv venv . venv/bin/activate for *unix or ve ...

  2. windows下python+flask环境配置详细图文教程

    本帖是本人在安装配置python和flask环境时所用到的资源下载及相关的教程进行了整理罗列,来方便后面的人员,省去搜索的时间.如果你在安装配置是存在问题可留言给我. 首先罗列一下python+fla ...

  3. [Python][flask][flask-wtf]关于flask-wtf中API使用实例教程

    简介:简单的集成flask,WTForms,包括跨站请求伪造(CSRF),文件上传和验证码. 一.安装(Install) 此文仍然是Windows操作系统下的教程,但是和linux操作系统下的运行环境 ...

  4. 写给新手看的Flask+uwsgi+Nginx+Ubuntu部署教程

    学习 Flask,写完一个 Flask 应用需要部署的时候,就想着折腾自己的服务器.根据搜索的教程照做,对于原理一知半解,磕磕碰碰,只要运行起来了,谢天谢地然后不再折腾了,到下一次还需要部署时,这样的 ...

  5. Werkzeug——python web开发工具包

    转载请注明原文地址:https://www.cnblogs.com/ygj0930/p/10826062.html 一:Werkzeug是个啥 1)Werkzeug是一个工具包,它封装了很多东西,诸如 ...

  6. Gevent简明教程

    Gevent简明教程  发表于 2015-11-28 |  分类于 技术| |  阅读次数 5159 前述 进程 线程 协程 异步 并发编程(不是并行)目前有四种方式:多进程.多线程.协程和异步. 多 ...

  7. TensorFlow DeepLab教程初稿-tensorflow gpu安装教程

    TensorFlow DeepLab教程初稿-tensorflow gpu安装教程 商务合作,科技咨询,版权转让:向日葵,135-4855__4328,xiexiaokui#qq.com Summar ...

  8. Flask+uwsgi+Nginx+Ubuntu部署教程

    学习 Flask,写完一个 Flask 应用需要部署的时候,就想着折腾自己的服务器.根据搜索的教程照做,对于原理一知半解,磕磕碰碰,只要运行起来了,谢天谢地然后不再折腾了,到下一次还需要部署时,这样的 ...

  9. 超实用的Flask入门基础教程,新手必备!

    Flask入门基础教程 Flask简介 Flask是一个轻量级的可定制框架,使用Python语言编写,较其他同类型框架更为灵活.轻便.安全且容易上手.它可以很好地结合MVC模式进行开发,开发人员分工合 ...

随机推荐

  1. 初步掌握HBase

    1.HBase概述 HBase是hadoop生态系统中的重要组成部分,是一个开源的.面向列.适合存储海量非结构化数据或半结构化数据,具备高可靠性.高性能.可灵活扩展伸缩.支持实时数据读写的分布式存储系 ...

  2. 照片浏览器软件-WTL开发的照片浏览器

    前段时间,为了准备情人节礼物,本人想了做一个照片浏览器送给女友,专门播放我俩的所有照片的一个程序软件,于是,就写了这么一个照片浏览器软件.本程序是基于WTL8.0开发的一个图片/照片浏览器,涉及到XM ...

  3. CI框架篇之预热篇(1)

    CodeIgniter 的基本都了解了,现在就开始预热,如果学习一门语言一样,我们最开始都是输出一个'HELLO WORLD'一样, 现在我们也通过输出这样一个内容,来了解基本的使用. CodeIgn ...

  4. 从source folder 下将其所有子文件夹的*.* 文件拷贝到 target folder (不拷贝文件夹名仅拷贝文件)

    因本人较懒,一直认为电脑能做的就让电脑来做,所以写下这个批处理的小脚本方便工作. 场景:碰到要拷贝一个文件夹(source folder)下的多个子文件夹(sub-folder)的文件到指定文件夹下( ...

  5. LINQ 101——分区、Join、聚合

    一.Partitioning 分区 Take 例1:取前3个数 static void Linq1() { , , , , , , , , , }; ); Console.WriteLine(&quo ...

  6. tomcat上servlet程序的配置与处理servlet请求过程

    手动配置: tomcat服务器下web项目的基本目录结构 |-tomcat根目录 |-webapps |-WebRoot : web应用的根目录 |-静态资源(html+css+js+image+ve ...

  7. Lucene的Query类介绍

    把Lucene的查询当成sql的查询,也许会笼统的明白些query的真相了. 查询分为大致两类,1:精准查询.2,模糊查询. 创建测试数据. private Directory directory; ...

  8. QT UI 如果发现布局之后,button不在父widget的中间

    如果发现布局之后,button不在父widget的中间: 调整父widget的布局参数:

  9. Island of Survival 概率

    #include <cstdio> #include <iostream> #include <cstring> #include <algorithm> ...

  10. CSS3中的background-size(对响应性图片等比例缩放)

    background-size的基本属性 background-size: 可以设定背景图像的尺寸,该属性是css3中的,在移动端使用的地方很多,比如最常见的地方在做响应性布局的时候,比如之前做的项目 ...