云计算 频道

中文版Google App Engine入门指南(2)

  【IT168 资讯】使用webapp框架

CGI标准是很简单的,但是要把所有的代码一一写出来还是很繁重的。WEB app框架帮你解决了这些具体问题,这样你就可以将你的精力集中在你的程序的功能上了。Google App Engine支持所有用Python写的关于CGI的网站框架(包括使用CGI adaptor的 WSGI-compliant框架),包括 Django,CherryPy, Pylons, 以及 web.py.。你只需要吧这个框架的代码复制到你的程序目录下就可以使用这个框架了。

App Engine包括了一个很简单的web应用框架,叫做webapp。这个webapp框架已经在App Engine开发环境和SDK中安装好了,所以你不需要添加任何代码到你的程序中去,就可以使用这个框架了。在下面的教程中我们将使用webapp框架。

Hello, webapp!

一个 webapp 程序包含三个部分:

一个或多个 RequestHandler类用来处理请求和产生响应。
一个 WSGIApplication 实例用来根据发送请求的URL对应到相应的类
一个主程序用来运行 WSGIApplication(使用CGI adaptor)
下面让我们来把我们的欢迎辞改写成一个 webapp 程序. 编辑 helloworld/helloworld.py 文件,替换为下面的代码:

from google.appengine.ext import webappfrom google.appengine.ext.webapp.util import run_wsgi_appclass MainPage(webapp.RequestHandler): def get(self): self.response.headers['Content-Type'] = 'text/plain' self.response.out.write('Hello, webapp World!')application = webapp.WSGIApplication( [('/', MainPage)], debug=True)def main(): run_wsgi_app(application)if __name__ == "__main__": main()在你的浏览器中重新加载 http://localhost:8080/ ,你将会看到改变。 (如果你关闭了web server,那么可以重新打开,方法详见 "Hello, World!".)

webapp做了些什么呢?

这个 webapp 模块是在 google.appengine.ext 包里面的。这个模块由SDK提供,在发布版的运行环境中也会包括。

上面这段代码定义了一个request handler,MainPage,映射到根目录URL(/)。当webapp接收到一个来自URL/ 的HTTP GET请求后,它就会初始化MainPage类,然后调用这个实例的get方法。在这个方法里面,关于请求的信息可以通过self.request来获得。通常,这个方法都会设置 self.response的属性以进行响应,然后退出方法。webapp将会根据MainPage实例的生命期最后的状态发送出响应。

应用程序本身由一个 webapp.WSGIApplication 实例所代表。 参数 debug=true 将会传递给生产函数,告诉 webapp 如果在程序运行过程中遇到错误,输出堆栈调用的记录。对于产品版的程序,你可能会去掉这个参数。

函数 run_wsgi_app() 接收 WSGIApplication 实例 (或者其他 WSGI-compatible 程序对象),然后将这个程序在App Engine's CGI environment里运行。 run_wsgi_app()和 Python标准库里提供的wsgiref模块中的WSGI-to-CGI adaptor ,但提供了一些额外的功能。比如,它可以自动检测程序是否是运行在调试环境,并且可以在调试环境中输出错误。

我们将会在下面的入门指南中使用很多webapp的功能,想要了解更多关于webapp的内容,访问:the webapp reference。

使用Google帐户服务

Google App Engine提供了很多基于Google框架下的有用的服务,可以通过SDK中提供的类库来调用这些服务。一个很重要的服务就是用户服务,它可以让你的应用程序和Google账户用户集成,有了这个用户服务,你的用户只需要拥有Google帐号就可以登录到你的网站了。

下面我们用用户类服务来个性化我们的欢迎辞:

使用 Users类

编辑 helloworld/helloworld.py , 替换为以下代码:

from google.appengine.api import usersfrom google.appengine.ext import webappfrom google.appengine.ext.webapp.util import run_wsgi_appclass MainPage(webapp.RequestHandler): def get(self): user = users.get_current_user() if user: self.response.headers['Content-Type'] = 'text/plain' self.response.out.write('Hello, ' + user.nickname()) else: self.redirect(users.create_login_url(self.request.uri))application = webapp.WSGIApplication( [('/', MainPage)], debug=True)def main(): run_wsgi_app(application)if __name__ == "__main__": main()重新加载你的网页,你的程序将会重定向到一个本地版的Google登录界面,输入你想要的用户名,那么你的应用程序将会看到这个基于你给的用户名所创建的一个虚拟的User类对象。

当你的应用程序运行在App Engine上之后,用户将会被重定向到Google账户登录页面,然后会返回到成功登陆前或者创建用户之前用户所在的页面。

Users类的 API

让我们仔细来看看这些代码:

user = users.get_current_user()如果用户已经登录了, get_current_user() 将会返回一个 User 对象,否则,将会返回 None。

if user: self.response.headers['Content-Type'] = 'text/plain' self.response.out.write('Hello, ' + user.nickname())如果用户已经登录了,将会根据用户的账户,输出一段含有用户昵称的欢迎辞。

else: self.redirect(users.create_login_url(self.request.uri))如果用户没有登录,则告诉 webapp 让它将页面重定向到Google账户登录页面。 这个重定向包含了用户所在的页面URI (self.request.uri) 所以之后将会返回到成功登陆前或者创建用户之前用户所在的页面。

想了解更多关于Users类的 API,访问the Users reference。

用webapp处理表单

如果你希望用户自己可以留言,那么你需要一种处理用户输入信息的办法。而webapp 让数据处理变得很简单。

用webapp处理Web表单的数据
用下面的代码替换helloworld/helloworld.py:

import cgifrom google.appengine.api import usersfrom google.appengine.ext import webappfrom google.appengine.ext.webapp.util import run_wsgi_appclass MainPage(webapp.RequestHandler): def get(self): self.response.out.write(""" <html> <body> <form action="/sign" method="post"> <div><textarea name="content" rows="3" cols="60"></textarea></div> <div><input type="submit" value="Sign Guestbook"></div> </form> </body> </html>""")class Guestbook(webapp.RequestHandler): def post(self): self.response.out.write('<html><body>You wrote:<pre>') self.response.out.write(cgi.escape(self.request.get('content'))) self.response.out.write('</pre></body></html>')application = webapp.WSGIApplication( [('/', MainPage), ('/sign', Guestbook)], debug=True)def main(): run_wsgi_app(application)if __name__ == "__main__": main()重新加载你的程序页面,你将会看到表单,试着写点东西提交吧。

这个版本的程序有两个handler:MainPage, 映射到 URL /, 用来展示表单. Guestbook, 映射到 URL /sign, 用来展示用户提交表单的内容。

Guestbook handler 有一个 post() 方法(而不是get() 方法)。 这是因为用 MainPage 所展示的页面里用了HTTP POST方法 (method="post")来提交表单里的数据。如果你需要在一个类中同时使用这两个方法(post() get()),只需要各自分别定义在一个类下面就可以了。

post() 方法里的代码可以从self.request中获取表单数据,在将这些数据返回并展示给用户之前,它调用了cgi.escape()方法来去掉用户输入中的一些HTML代码标识符。 cgi是标准Python类库中的一个模块,详见the documentation for cgi。

注意:App Engine编程环境包含了所有Python 2.5的标准类库。但是,不是所有的方法都被允许的。App Engine程序与性在一个受限制的环境中,这样App Engine可以安全地将这些程序规模化。比如,底层的一些对于操作系统,网络操作,以及一些文件系统的操作都是不允许的,如果试图调用这些函数,将引发错误。更多信息,请访问The Python Runtime Environment。

使用数据库存储

对于一个数据量大的网站应用来说数据存储是个很有技巧的的事情。用户可能在一个特定的时间发出了一个数据请求,但是下一个时间又发出了另外一个完全不同的数据请求。所有的WEB服务都需要协调这些相互影响的请求,并且这些请求可能来自世界的各个地方。

由于有了Google App Engine,你再也不需要为这些发愁了。Google App Engine架构将为提供分布式的数据处理,解决负载平衡的问题,并且提供了API来实现所有关于数据存储的问题。

数据存储完整实例

下面是一个最新版的 helloworld/helloworld.py 代码,用来存储用户的留言。下面的所有文字都是用来解释这段代码的。

import cgifrom google.appengine.api import usersfrom google.appengine.ext import webappfrom google.appengine.ext.webapp.util import run_wsgi_appfrom google.appengine.ext import dbclass Greeting(db.Model): author = db.UserProperty() content = db.StringProperty(multiline=True) date = db.DateTimeProperty(auto_now_add=True)class MainPage(webapp.RequestHandler): def get(self): self.response.out.write('<html><body>') greetings = db.GqlQuery("SELECT * FROM Greeting ORDER BY date DESC LIMIT 10") for greeting in greetings: if greeting.author: self.response.out.write('<b>%s</b> wrote:' % greeting.author.nickname()) else: self.response.out.write('An anonymous person wrote:') self.response.out.write('<blockquote>%s</blockquote>' % cgi.escape(greeting.content)) # Write the submission form and the footer of the page self.response.out.write(""" <form action="/sign" method="post"> <div><textarea name="content" rows="3" cols="60"></textarea></div> <div><input type="submit" value="Sign Guestbook"></div> </form> </body> </html>""")class Guestbook(webapp.RequestHandler): def post(self): greeting = Greeting() if users.get_current_user(): greeting.author = users.get_current_user() greeting.content = self.request.get('content') greeting.put() self.redirect('/')application = webapp.WSGIApplication( [('/', MainPage), ('/sign', Guestbook)], debug=True)def main(): run_wsgi_app(application)if __name__ == "__main__": main()将上面的代码替换掉 helloworld/helloworld.py 原有的代码,然后重新加载 http://localhost:8080/ 试着发布一条留言,看看你的留言是否被正确地存储并且正常地显示了。

存储用户提交的留言

App Engine 包含了一个基于Python的数据存储模型. 这个模型类似于 Django's data modelling API, 但是使用了Google自己的存储环境.

对于上一章实现的留言程序,我们想要把用户提交的留言保存起来,每个留言都包含作者名称,消息内容,发布时间等等,并且按照留言的先后将其显示出来。

为了使用data modeling API,在代码顶部添加 google.appengine.ext.db 模块:

from google.appengine.ext import db下面的这段代码定义了一个用来存储用户留言的模块:

class Greeting(db.Model): author = db.UserProperty() content = db.StringProperty(multiline=True) date = db.DateTimeProperty(auto_now_add=True)这段代码定义了 Greeting 模型的三个属性: author 是一个 User 对象, content 是一个字符串对象,anddate 是一个datetime.datetime对象。

其中一些属性包含了默认值:比如db.StringProperty 类型中 multiline=True 表明该字符串中可以包含换行符;db.DateTimeProperty类型中 auto_now_add=True 表明当Greeting对象创建的时候,将使用当前时间初始化这个属性。关于数据模型的属性的更多帮助,请查看 the Datastore reference。

既然我们已经定义了一个数据对象模型,接下来,我们创建一个Greeting对象,并且把它保存起来。编辑 Guestbook handler :

class Guestbook(webapp.RequestHandler): def post(self): greeting = Greeting() if users.get_current_user(): greeting.author = users.get_current_user() greeting.content = self.request.get('content') greeting.put() self.redirect('/')这个新的 Guestbook handler 创建了一个新的 Greeting 对象,然后根据用户提交的数据设置 author 和 content 的属性值。 它并没有甚至 date 的值,所以 date 会自动设成当前时间,因为我们在模型建立的时候已经设置了。

最后一行, greeting.put() 将新创建的对象保存进数据库,如果put()进去的是从数据库中提取的对象,put() 会更新那条数据记录,而现在我们是新创建的一个对象,所以 put() 会添加一条新的记录到数据存储里。

使用 GQL 获取数据记录

App Engine datastore 使用了一套复杂的数据储存系统.但是它并不是一个标准的关系数据库,所以不能使用标准的Sql语句进行查询。作为一个替代,Google准备了一套类Sql的查询语句,称之为GQL.GQL 提供了和SQL基本类似的语法来读取数据。

下面是新版的 MainPage handler 代码,用来查询数据库中的所有留言。

class MainPage(webapp.RequestHandler): def get(self): self.response.out.write('<html><body>') greetings = db.GqlQuery("SELECT * FROM Greeting ORDER BY date DESC LIMIT 10") for greeting in greetings: if greeting.author: self.response.out.write('<b>%s</b> wrote:' % greeting.author.nickname()) else: self.response.out.write('An anonymous person wrote:') self.response.out.write('<blockquote>%s</blockquote>' % cgi.escape(greeting.content)) # Write the submission form and the footer of the page self.response.out.write(""" <form action="/sign" method="post"> <div><textarea name="content" rows="3" cols="60"></textarea></div> <div><input type="submit" value="Sign Guestbook"></div> </form> </body> </html>""")查询语句出现在这一行:

greetings = db.GqlQuery("SELECT * FROM Greeting ORDER BY date DESC LIMIT 10")或者,你也可以在Greeting类里面调用 gql(...) 方法,那样就不必使用 SELECT * FROM Greeting 这样的查询语句了:

greetings = Greeting.gql("ORDER BY date DESC LIMIT 10")和SQL语句类似,关键字 (比如 SELECT) 是大小写无视的,字段名是区分大小写的。

要注意的是,GQL语句总是返回完整的对象,所以GQL查询语句不能指定要查询的字段名。也就是说,所有的GQL语句都是以SELECT * FROM model 开头的。

一个GQL查询语句可以用 WHERE指定查询条件,你可以指定一个或多个条件。 和SQL不同的是,GQL查询不能包含变量值:GQL使用参数绑定查询中所有的变量.例如 , 获取当前登录用户的留言:

if users.get_current_user(): greetings = Greeting.gql("WHERE author = :1 ORDER BY date DESC", users.get_current_user())你也可以使用命名参数代替之:

greetings = Greeting.gql("WHERE author = :author ORDER BY date DESC", author=users.get_current_user())另外, Google datastore API 还提供了另外一种获取数据的方法::

greetings = Greeting.all() greetings.filter("author =", users.get_current_user()) greetings.order("-date")想了解 GQL查询语法的更多内容, 请查看 the Datastore reference。

清空开发版服务器中的数据存储

为了方便你测试自己的应用,GAE开发环境使用了一个临时文件来保存本地的数据,要清空本地开发环境的数据,可以使用如下的命令行:

dev_appserver.py --clear_datastore helloworld/

0
相关文章