Werkzeug教程

摘要:
为了这个应用,我们将会使用的库包括,用于模板的Jinja2、用于数据库层的redis和用于WSGI层的Werkzeug。]介绍Shortly本教程中我们将会一起来使用Werkzeug创建一个简单的URL简化服务。请记住Werkzeug不是框架,而是一个可以创建你自己的框架或应用的、非常灵活的库。最终结果会如下图这样:第0步:WSGI基础介绍Werkzeug是一个WSGI功能库。使用Werkzeug之后,你将不再需要直接处理被提交上来的请求和应答对象。

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安装需要的库:

  1. $pipinstallJinja2redis

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

  1. $brewinstallredis

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

  1. $sudoapt-getinstallredis

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

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

介绍Shortly

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

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

最终结果会如下图这样:

Werkzeug教程第1张

第0步:WSGI基础介绍

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

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

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

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

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

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

  1. fromwerkzeug.wrappersimportResponse
  2. defapplication(environ,start_response):
  3. response=Response(‘HelloWorld!’,mimetype=‘text/plain’)
  4. returnresponse(environ,start_response)

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

  1. fromwerkzeug.wrappersimportRequest,Response
  2. defapplication(environ,start_response):
  3. request=Request(environ)
  4. text=‘Hello%s!’%request.args.get(‘name’,‘World’)
  5. response=Response(text,mimetype=‘text/plain’)
  6. returnresponse(environ,start_response)

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

第1步:创建文件夹

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

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

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

第2步:基本架构

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

  1. importos
  2. importredis
  3. importurlparse
  4. fromwerkzeug.wrappersimportRequest,Response
  5. fromwerkzeug.routingimportMap,Rule
  6. fromwerkzeug.exceptionsimportHTTPException,NotFound
  7. fromwerkzeug.wsgiimportSharedDataMiddleware
  8. fromwerkzeug.utilsimportredirect
  9. fromjinja2importEnvironment,FileSystemLoader

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

  1. classShortly(object):
  2. def__init__(self,config):
  3. self.redis=redis.Redis(config[‘redis_host’],config[‘redis_port’])
  4. defdispatch_request(self,request):
  5. returnResponse(‘HelloWorld!’)
  6. defwsgi_app(self,environ,start_response):
  7. request=Request(environ)
  8. response=self.dispatch_request(request)
  9. returnresponse(environ,start_response)
  10. def__call__(self,environ,start_response):
  11. returnself.wsgi_app(environ,start_response)
  12. defcreate_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. ifwith_static:
  18. app.wsgi_app=SharedDataMiddleware(app.wsgi_app,{
  19. ‘/static’:os.path.join(os.path.dirname(__file__),‘static’)
  20. })
  21. returnapp

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

  1. if__name__==‘__main__’:
  2. fromwerkzeug.servingimportrun_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运行这个文件,并可以看到你本地机器上的一个服务:

  1. $pythonshortly.py
  2. *Runningonhttp://127.0.0.1:5000/
  3. *Restartingwithreloader:stat()polling

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

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

第3步:环境

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

  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. defrender_template(self,template_name,**context):
  7. t=self.jinja_env.get_template(template_name)
  8. returnResponse(t.render(context),mimetype=’text/html’)

第4步:路由

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

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

  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的函数。这里是具体的实现:

  1. defdispatch_request(self,request):
  2. adapter=self.url_map.bind_to_environ(request.environ)
  3. try:
  4. endpoint,values=adapter.match()
  5. returngetattr(self,‘on_’+endpoint)(request,**values)
  6. exceptHTTPException,e:
  7. returne

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

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

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

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

第5步:第一个视图

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

  1. defon_new_url(self,request):
  2. error=None
  3. url=‘’
  4. ifrequest.method=‘POST’:
  5. url=request.form[‘url’]
  6. ifnotis_valid_url(url):
  7. error=‘PleaseenteravalidURL’
  8. else:
  9. short_id=self.insert_url(url)
  10. returnredirect(‘/%s+’%short_id)
  11. returnself.render_template(‘new_url.html’,error=error,url=url)

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

  1. defis_valid_url(url):
  2. parts=urlparse.urlparse(url)
  3. returnparts.schemein(‘http’,‘https’)

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

  1. definsert_url(self,url):
  2. short_id=self.redis.get(‘reverse-url:’+url)
  3. ifshort_idisnotNone:
  4. returnshort_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. returnshort_id

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

  1. defbase36_encode(number):
  2. assertnumber>=0,‘positiveintegerrequired’
  3. ifnumber==0:
  4. return‘0’
  5. base36=[]
  6. whilenumber!=0:
  7. number,i=divmod(number,36)
  8. base36.append(‘0123456789abcdefghijklmnopqrstuvwxyz’[i])
  9. return‘’.join(reversed(base36))

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

第6步:重定向视图

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

  1. defon_follow_short_link(self,request,short_id):
  2. link_target=self.redis.get(‘url-target:’+short_id)
  3. iflink_targetisNone:
  4. raiseNotFound()
  5. self.redis.incr(‘click-count:’+short_id)
  6. returnredirect(link_target)

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

第7步:细节视图

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

  1. defon_short_link_details(self,request,short_id):
  2. link_target=self.redis.get(‘url-target:’+short_id)
  3. iflink_targetisNone:
  4. raiseNotFound()
  5. click_count=int(self.redis.get(‘click-count:’+short_id)or0)
  6. returnself.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

  1. <!doctypehtml>
  2. <title>{%blocktitle%}{%endblock%}|shortly</title>
  3. <linkrel=stylesheethref=/static/style.csstype=text/css>
  4. <divclass=box>
  5. <h1><ahref=/>shortly</a></h1>
  6. <pclass=tagline>ShortlyisaURLshortenerwrittenwithWerkzeug{%blockbody%}{%endblock%}
  7. </div>

new_url.html

  1. {%extends"layout.html"%}
  2. {%blocktitle%}CreateNewShortURL{%endblock%}
  3. {%blockbody%}
  4. <h2>SubmitURL</h2>
  5. <formaction=""method=post>
  6. {%iferror%}
  7. <pclass=error><string>Error:</strong>{{error}}
  8. {%endif%}
  9. <p>URL:
  10. <inputtype=textname=urlvalue="{{url}}"class=urlinput>
  11. <inputtype=submitvalue="Shorten">
  12. </form>
  13. {%endblock%}

short_link_details.html

    1. {%extends"layout.html"%}
    2. {%blocktitle%}Detailsabout/{{short_id}}{%endblock%}
    3. {%blockbody%}
    4. <h2><ahref="/{{short_id}}">/{{short_id}}</a></h2>
    5. <dl>
    6. <dt>Fulllink
    7. <ddclass=link><div>{{link_target}}</div>
    8. <dt>Clickcount:
    9. <dd>{{click_count}}
    10. </dl>
    11. {%endblock%}

免责声明:文章转载自《Werkzeug教程》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇GitLab CI/CD 自动部署之 Shell 篇ASP Blob类型转存为Long Raw类型下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

python之高性能网络编程并发框架eventlet实例

http://blog.csdn.net/mingzznet/article/details/38388299 前言: 虽然 eventlet 封装成了非常类似标准线程库的形式,但线程和eventlet在实际并发执行流程仍然有明显区别。在没有出现 I/O 阻塞时,除非显式声明,否则当前正在执行的 eventlet 永远不会把 cpu 交给其他的 event...

Spark python集成

Spark python集成 1、介绍 Spark支持python语言,对于大量的SQL类型的操作,不需要编译,可以直接提交python文件给spark来运行,因此非常简单方便,但是性能要比scala或java慢。对于常规任务,可以使用python来编写,特殊任务还是建议scala编写。 2、使用pyspark启动spark shell(centos) 2...

python程序练习题集

1.#输入a,b,c,d4个整数,计算a+b-c*d的结果 a=input("please input a nimber:") b=input("please input a number:") c=input("please input a number:") d=input("please input a number:") print a+b-c*d...

Protocol buffers--python 实践 简介以及安装与使用

简介: Protocol Buffers以下简称pb,是google开发的一个可以序列化 反序列化object的数据交换格式,类似于xml,但是比xml 更轻,更快,更简单。而且以上的重点突出一个跨平台,和xml json等数据序列化一样,跨平台跨语言。 安装: 前往github:https://github.com/google/protobuf/rel...

比较全面的python类型转换

 前言提示: int(整形):a=1 str(字符串):a="asdsdf地方123" float(浮点):a="3.14150" bytes(字节包):a=b'xe2x82xac20' complex(复数):a=(34567+0j) list(列表):a=[1, 2, 3, '4', '五', 'liu'] set(集合):a={1,2,2,3,"4"...

python爬虫学习笔记(二十七)-Splash的使用

1. Splash介绍 Splash是一个JavaScript渲染服务,是一个带有HTTP API的轻量级浏览器,同时它对接了Python中的Twisted和QT库。利用它,我们同样可以实现动态渲染页面的抓取 2. 安装 2.1 安装docker 2.2 拉取镜像 docker pull scrapinghub/splash 2.3 用docker运行s...