以 App Engine 的方式进行 IT
Google 于 2008 年首次发布了其 App Engine 技术。这家互联网搜索巨头将在其基础设施上托管您的 Web 应用程序 (webapp),最初是免费的。只有当您的 webapp 变得“繁忙”时,Google 才会开始向您收费。所谓“繁忙”,Google 指的是每月大约五百万的“页面浏览量”。达到这个阈值,Google 就会来索取您的信用卡信息。
Google App Engine 的应用程序是用 Python 编写的(最近加入了 Java)。正如你们大多数人所知,Python 的创建者(Guido van Rossum)在 Google 工作,据报道他将 50% 的时间用于 Python 的持续改进以及围绕这种日益流行的通用编程和脚本技术而建立的开发者生态系统。
与大多数其他 webapp 开发技术和框架不同,后者需要您自己托管您的 webapp(或找到一家友好的、经济高效的 ISP 来为您托管您的 webapp 及其依赖技术),Google 的 App Engine 抽象出了托管部分。只需按照 App Engine 标准构建您的 webapp,将其上传到 Google,然后它就会部署在 Google “云”中。Google 处理备份、负载均衡、访问高峰、部署、缓存等等。您唯一需要担心的就是您的代码,因为不再有部署方面的干扰。而且,对于 App Engine 来说,一切都与代码有关,这正是我们程序员喜欢的方式,不是吗?
在本文中,我将解释如何使用 Python API 构建一个非常简单的 App Engine 项目。当您完成阅读本文时,您应该已经掌握了足够的知识,以便更好地决定 Google 的 App Engine 是否值得您花更多时间学习。
大多数现代 webapp 技术都是围绕模型-视图-控制器模式 (MVC) 组织的。本质上,一个符合 MVC 规范的 webapp 分为三个部分:模型处理与您的 webapp 数据的交互,视图负责用户界面,而控制器提供将所有内容粘合在一起的应用程序或业务逻辑。理论上,对您的 webapp 外观(视图)的更改不应显著影响您的 webapp 存储其数据(模型)的方式,或者至少任何影响都应是最小且局部的。当然,MVC 模式可以应用于任何应用程序领域,但它特别适用于 Web 环境,因为每个组件(通常)在物理上与其他组件是分离的。例如,您的视图代码在您客户端 PC 的浏览器中运行。您的控制器代码在您的 Web 服务器上运行,而您的模型可能部署在某种数据存储中,该数据存储可能也可能不在其自身的硬件上运行。
App Engine 符合 MVC 模式,App Engine 的优点在于,每个 MVC 组件都在代码文件中实现,这些代码文件很容易彼此分离。如果您愿意,您可以将您的代码放在一个大的源代码文件中,但说实话,除了最简单的 webapp 之外,所有 webapp 都受益于将每个 MVC 组件拆分到其自身的源文件中。
当然,App Engine 并不真正在意您如何组织您的代码(只要它是正确的),App Engine 也不在意您的 webapp 如何运行。如果您想编写一个基本的 CGI webapp,您可以。您也可以使用 Python 的 WSGI 标准,这也是我们在这里将要使用的标准。那么,让我们通过使用 Google App Engine 构建一个简单的 MVC 模式的 webapp 来了解其中涉及的内容。
要构建一个 App Engine webapp,您需要两样东西:Google 的 App Engine SDK 和 Python 2.5 版本。
虽然您会将您的 App Engine webapp 部署到 Google 云,但在开发期间,可以在您的本地机器上测试您的 webapp。您只需要一份 App Engine SDK 的副本,这很容易获得。转到 App Engine 下载页面(请参阅资源),然后单击 Python SDK 的链接。在显示的页面中,选择 Linux 下载(在撰写本文时,这是一个 ZIP 文件)。当前的 App Engine 文档以及 Eclipse 的 Google 插件也可供下载(如果这些东西让您感兴趣)。
下载完成后,安装非常轻松。只需将下载的文件解压缩到您选择的目录中即可。我解压缩到我的 HOME 目录,并创建了一个名为 google_appengine 的新目录。
自 2008 年底以来,Python 有三种不同的版本:标准的 2.5 版本(保持与已安装基础的向后兼容性),2.6 版本(旨在桥接从 2.5 版本到未来版本的过渡)和 3 版本(这是新的、向后不兼容的 Python)。App Engine 针对的是在 Google 云中运行的 Python 2.5 版本的自定义和优化版本。您可以尝试在 2.6 下运行您的 webapp,但我的经验不太令人满意,尽管有时它可以工作。Python 3.1 版本目前不受支持,所以不要浪费时间尝试将其与 App Engine 一起使用。
对于本文,我使用的是 Fedora 12。我曾经沉迷于 Red Hat Linux(然后是 Fedora),但像许多其他 Linux 用户一样,我被 Ubuntu 更友好的桌面体验的承诺和后来的交付所吸引。幸运的是,我最近收到了 Mark G. Sobell 的最新版著作(请参阅资源),其中包含 Fedora 12 DVD。正如预期的那样,安装非常简单。但是,Fedora 12 预装了 Python 2.6,而我需要 2.5 版本。
我喜欢 Python 的一个特性是,解释器的多个版本可以在您的系统上愉快地共存。在我的系统(台式机和笔记本电脑)上,我安装了 2.5、2.6 和 3.1 版本。只有一个版本被符号链接到 /usr/bin/python(通常是 2.6 版本),但我可以使用以下命令行之一调用其他版本
python3 python2.5
在 Fedora 上部署 Python 2.5 版本不是问题。它通常是tar -zxvf, configure, make和make-install四个步骤。正如预期的那样,Fedora DVD 预装了所有开发工具,使我可以从源代码构建 Python 2.5,而没有任何问题。
与 Ruby on Rails 或 Django 等技术不同,后者提供了一系列帮助脚本来让您快速启动和运行,App Engine 迫使您自己完成所有工作。值得庆幸的是,这并不是一项巨大的工作。为了演示,让我们创建一个名为 myapp 的新项目,这个名字相当有想象力。使用以下命令在您的 HOME 目录中创建所需的初始目录和文件
mkdir myapp && cd myapp mkdir templates && touch app.yaml
上述命令创建了一些必需的目录(稍后会详细介绍),以及主要的 App Engine 配置文件:app.yaml。您编辑此文件以告诉 App Engine 关于您的 webapp 的所有信息。将以下配置指令添加到您的 YAML 文件
application: myapp version: 1 runtime: python api-version: 1 handlers: - url: /.* script: myapp.py
第application行标识您的 webapp,并且该值需要与您刚刚创建的用于存放您的项目的目录的名称匹配。使用version值来指示此 YAML 文件引用的 webapp 版本(此值也由 Google 云用于引用您的 webapp 的不同版本,如果它们存在)。第runtime行告诉 App Engine 您正在为哪个平台编码,而api-version值指示您正在使用的 API 版本。
剩下的三行告诉 App Engine 如何处理任何发送到您的 webapp 的 Web 请求。将 YAML 文件中的这些处理程序视为高级应用程序“路由指令”很有用。在url之后的位是一个正则表达式,它(正如所有正则表达式大师会告诉您的那样)匹配任何以 / 开头,后跟任何字符串(或什么都没有)的内容。这里发生的事情是,App Engine 代表您的 webapp 收到的任何 URL 都将被重定向到在script> 行上标识的脚本,在本例中该脚本名为 myapp.py。目前,还没有这样的脚本存在,所以让我们修复它。
为了演示如何将 App Engine webapp 组合在一起,让我们构建一个简单的网页,让用户提交他们的电子邮件地址和消息。这两条数据存储在 Google 云中。第二个页面按需显示电子邮件地址和消息。诚然,这并不是一个非常令人兴奋的 webapp,但它足以演示该技术的基础知识,并让您开始使用一些“真实的东西”。当然,可以使用 App Engine 的 CGI 机制(其工作方式与您期望的完全相同)来构建它,但是由于此应用程序注定要取得成功,让我们改为按照 Python 的 WSGI 标准进行编码。我们还按照 MVC 模式构建 webapp。
由于您计划在此 webapp 中存储一些数据,因此您需要一个放置数据的地方,这意味着您需要一个模型。App Engine 提供了 Google “云”数据存储的 API。您需要做的就是定义一个从 db.Model 继承的 Python 类,然后创建所需的数据字段。对于 webapp,您需要一个用于电子邮件地址和相关消息的字段。为了保持可管理性,让我们将您的模型代码放在其自身的文件中,名为 myappDB.py
from google.appengine.ext import db class UserComment(db.Model): cust_email = db.StringProperty() cust_message = db.TextProperty()
这个模型代码没什么特别的。它只是从 App Engine 导入 db 模块,创建一个名为 UserComment 的新类,并为每个数据字段创建类实例变量。由于 App Engine 的 StringProperty 类型限制为 500 字节,因此您需要为用户消息指定 TextProperty,以防有人有很多话要说。
定义模型后,您可以创建代码的其余部分。创建一个名为 myapp.py 的文件,并将以下代码放入其中。请注意,本节中的代码都包含在一个文件中,但此处将其拆分,以便我可以向您描述其功能。从您的导入开始
import wsgiref.handlers from google.appengine.ext import webapp from google.appengine.ext import db from google.appengine.ext.webapp import template import myappDB
在导入 Python 标准 WSGI 参考实现之后,从 App Engine 导入了三个库webapp提供了一个简单的 Web 框架,db提供对 App Engine 数据存储的访问,以及template提供对 App Engine 标准模板系统(该系统基于 Django 构建)的访问。请注意,您还导入了刚刚创建的 myappDB 模块,该模块将您的模型定义引入到此程序中。
每个 webapp 都需要被告知当用户从浏览器向服务器发送默认请求时该怎么做。通常,这是请求索引页或主页。第一个请求处理程序提供了该功能
class IndexHandler(webapp.RequestHandler): def get(self): html = template.render( 'templates/index.html', {} ) self.response.out.write(html)
您创建了一个名为IndexHandler的新类,该类继承自 webapp 的RequestHandler。在该类中,每当处理对默认页面的请求时,都会调用一个名为get的 Python 方法。该方法在 templates 目录中呈现一个名为 index.html 的模板,并将呈现的页面分配给 html 变量,然后将其作为 HTTP 响应发送到标准输出,最终到达浏览器。render() 方法的第二个参数是一个空哈希(或使用正确的 Python 术语“字典”)。可以将数据(模板变量)发送到模板引擎,但在本例中您不必这样做。当没有数据供模板引擎处理时,约定是发送一个空哈希。我将在本文后面解释如何创建模板。现在,请注意,您的 webapp 使用的任何 HTML 都存储在 templates 目录中,这有助于将视图代码与控制器代码隔离。
离开消息所需的功能分为两个部分。第一部分呈现一个小表单,允许用户输入他们的电子邮件地址和消息。第二部分获取提交的表单数据并将其存储在 Google 云中。这是一个请求处理程序类,它实现了这两个部分
class LeaveCommentHandler(webapp.RequestHandler): def get(self): html = template.render( 'templates/comment.html', {} ) self.response.out.write(html) def post(self): comment = myappDB.UserComment( cust_email = self.request.get("c_email"), cust_message = self.request.get("c_message") ) comment.put() self.redirect("/comments")
第get方法基本上与上一个类中的get方法相同,不同之处在于,在此方法中,您正在呈现一个名为 comment.html 的不同模板。在post方法(您在上一个类中没有该方法)响应从浏览器发送的 POST 请求。换句话说,当用户填写由get方法呈现的表单,然后单击提交按钮时,表单上的数据将传递到此请求处理程序的post方法。您的post方法中的代码首先通过从发布的数据中提取两个命名的表单字段(在 HTML 表单中称为 c_email 和 c_message)来创建模型数据的新实例。提交的数据被分配给模型中定义的数据字段,然后通过调用comment.put()存储在 Google 云中。完成后,您的代码立即重定向到另一个 URL (/comments),这将导致另一个请求处理程序的代码被激活。当然,前提是您已经编写了“/comments”处理程序代码。这是它的代码
class DisplayCommentsHandler(webapp.RequestHandler): def get(self): comments_query = myappDB.UserComment.all() comments = comments_query.fetch(1000) html = template.render( 'templates/comments.html', {'comments': comments} ) self.response.out.write(html)
您的DisplayCommentsHandler代码仅提供 GET 功能,因为这是所有需要的。使用 App Engine 到数据存储的功能接口,您的get方法首先创建一个查询,该查询请求所有UserComment数据,然后从查询结果中获取前 1,000 个评论对。然后,您的代码呈现 comments.html 模板,并将(最多 1,000 个)评论对传递给模板引擎。从模板系统返回的呈现的 HTML 然后作为 HTTP 响应发送到标准输出。
1,000 的限制是 App Engine 对所有正在运行的 webapp 施加的,旨在限制“恶意”webapp 如果不加以检查可能会对 App Engine 基础设施造成的潜在危害。通过限制一次可以获取的数据行数,App Engine 可以尝试确保没有一个 webapp 占用其所有资源。这实际上并不是很大的限制。有多少网页尝试一次显示超过几百条数据库记录?显然,如果您的数据存储中有超过 1,000 行,您需要编写一些额外的代码来循环遍历您的数据,一次 1,000 行,直到您用尽所有数据为止。
与之前的两个请求处理程序不同,最新的这个请求处理程序将数据发送到模板系统。评论查询结果(电子邮件地址和消息的集合)被传递到模板以进行进一步处理。
您可能已经注意到,在get方法中没有使用 SQLDisplayCommentsHandler请求处理程序。相反,您使用了 App Engine 的 API 来请求数据存储中的所有数据,然后从中获取了 1,000 行数据。Google 的数据存储技术是 App Engine 的一个组成部分,它不支持 SQL。事实证明,数据存储不是关系数据库。相反,它基于 Google 的 BigTable 技术,这完全是另一种不同的东西。Google 确实以 GQL 的形式提供了一种查询语言,它看起来很像 SQL,但并不能完成您过去能够使用 SQL 完成的所有事情。例如,GQL 中没有连接。查看 App Engine 数据存储文档以获取所有详细信息,以查看没有 SQL 是否会对您的应用程序造成阻碍。
定义请求处理程序后,剩下的就是将您的应用程序的 URL 连接到请求处理程序。回想一下,app.yaml 文件已经安排将定向到您的 App Engine webapp 的每个 URL 请求都由您的 myapp.py 程序处理。所以,问题是,当请求到达您的程序时,接下来会发生什么?答案是您的 myapp.py 程序当然会处理它!当您导入 webapp 时,您继承了允许您将 URL 与您的请求处理程序链接的功能。这是您需要的代码
def main(): app = webapp.WSGIApplication( [ ('/comment', LeaveCommentHandler), ('/comments', DisplayCommentsHandler), ('/.*', IndexHandler)], debug = True ) wsgiref.handlers.CGIHandler().run(app)
第WSGIApplication构造函数接受一个元组列表,详细说明哪个 URL 调用哪个请求处理程序。元组列表中的每个 URL 都是正则表达式模式,用于检查与已传递到您的程序的 URL 是否匹配。如果找到匹配项,则调用请求处理程序。请注意,在此代码中,当传递的 URL 与 URL 模式之一不匹配时,您需要一个“catchall”正则表达式来执行一些明智的操作。在这里,您安排调用IndexHandler当找不到匹配项时。您还打开了调试信息,这在开发期间很有用,但在部署时应将其关闭。URL 路由准备就绪后,调用run方法(由wsgiref.handlers.CGIHandler提供)启动您的 webapp。
URL 路由和应用程序启动代码包含在main函数中。这是故意的,因为 App Engine 在最初加载和重新加载您的 webapp 时会查找此函数。它的存在允许 App Engine 优化和缓存您的 webapp。它还允许 App Engine 避免在每次新请求发生时都必须加载您的应用程序,这是至今仍在困扰 CGI 的一个批评。当然,要本地测试您的 webapp,您需要告诉 Python 如何启动它,这可以通过经典的 Python 习语来完成
if __name__ == '__main__': main()
这就是您的控制器代码的全部内容。您已经创建了在 URL 请求发送到您的 webapp 时执行您所需操作的代码。剩下的就是创建您的视图代码,在这个简单的应用程序中,它是一小组 HTML 模板。
当在路由过程中没有匹配到 URL 或默认 URL 时,将呈现第一个模板。它由IndexHandler呈现,存储在您的 templates 目录中,名为 index.html,看起来像这样
<html> <head><title>Welcome!</title></head> <body> Either <a href="/comment">leave a comment</a> or check out what <a href="/comments">other people are saying</a> about this webapp. </body> </html>
您可能会注意到,index.html 模板是纯 HTML。它显示一个欢迎页面,为您的网站用户提供留下新消息或查看已提交的所有消息的选项(图 1)。诚然,此 HTML 很简单,但至关重要的是,它与您的代码分开存储,这保持了 MVC 模式的目标。
当用户单击“留下评论”链接时,请求将路由到LeaveCommentHandler的get方法,该方法呈现另一个名为 comment.html 的模板,该模板也位于 templates 目录中。这是此模板的 HTML
<html> <head><title>Leave a Comment</title></head> <body> <form action="/comment" method="POST"> <p>Enter your e-mail address: <input type="text" name="c_email"></p> <p>Enter your message (be nice): <textarea name="c_message" rows="5" cols="50"></textarea></p> <p><input type="Submit" value="Submit your comment"></p> </body> </html>
同样,这是标准 HTML。一个表单被呈现到屏幕上(图 2),该表单从用户那里征求电子邮件地址和消息。请注意 HTML 中分配给每个界面元素的名称。另请注意,表单的 action 标记被定向回/commentURL。当用户提交表单时,数据将发布到 App Engine,这将导致post方法在您的LeaveCommentHandler请求处理程序中执行。此代码从提交的数据中创建新的数据行(请注意名称如何匹配)并将其保存到数据存储中。然后,代码重定向到/commentsURL。
当 App Engine 看到/comments请求 URL 时,它会调用DisplayCommentsHandler请求处理程序的get方法,该方法从数据存储中获取 1,000 行数据,然后将数据发送到 comments.html 模板进行呈现。这个最终模板看起来更像一个“真实”的模板
<html> <head><title>Here are the User Comments</title></head> <body> <p>Here are the comments.</p> {% for c in comments %} <p><b>{{ c.cust_email }}</b> said: <i>"{{ c.cust_message }}"</i></p> {% endfor %} </body> </html>
这是包含模板指令的最终模板,这些指令包含用于处理请求处理程序在控制器代码中调用模板时发送到模板引擎的数据。任何用 {% 和 %} 以及 {{ 和 }} 括起来的内容都是模板代码;其他所有内容都是标准 HTML。App Engine 的模板技术基于 Django,Django 是一个流行的基于 Python 的 Web 应用程序框架。在 {% 和 %} 中找到的代码被执行,而在 {{ 和 }} 中找到的代码是值替换。此模板获取传递给模板的评论查询结果,并在一些自定义 HTML 中显示每一行。呈现的结果如图 3 所示。
在本地测试您的应用程序很复杂(只是稍微复杂),因为您必须确保使用正确版本的 Python,即 2.5 版本。如果您一直在按照说明操作,您应该已将 App Engine 安装在其 HOME 目录中的单独目录中,并将您的 webapp 代码安装在名为 myapp 的目录中。要从您的 HOME 目录启动您的 webapp,请打开一个 shell 并使用此命令
python2.5 google_appengine/dev_appserver.py myapp/
shell 中将出现一堆状态消息,如果一切顺利,开发服务器将通知您您的 webapp 正在 http://localhost:8080 上运行。启动您最喜欢的浏览器,浏览到您的 webapp 并试用一下。您应该看到类似于图 1、2 和 3 中所示的行为。
App Engine 包括一个可通过 Web 访问的 webapp 管理员界面。要查看它,请将您的浏览器指向 http://localhost:8080/_ah/admin。图 4 显示了我创建的数据存储条目的界面。该界面允许您检查和编辑数据存储中的每个条目(以及创建新条目)。每个条目都由数据存储自动分配了一个唯一的键和 ID 值。这些值通常用于从数据存储中检索特定条目。
测试完成后,您就可以进行部署了。为此,请按照 App Engine 网站上的部署说明进行操作。这包括注册一个 Google ID(如果您使用 Gmail 或 Wave,您已经拥有一个),为您的 webapp 选择一个唯一的名称,并请求一个七位数的 Google App Engine 代码(您需要激活您的 webapp,该代码通过 SMS 发送到您的手机)。完成所有这些操作后,使用以下命令从您的 HOME 目录将您的代码上传到 Google 云
google_appengin/appcfg.py update myapp/
当然,这并没有结束。App Engine 还有更多功能,包括与 Google 的用户管理和登录系统集成、安全增强、memcached 集成和验证技术等等。我建议阅读 使用 Google App Engine 和 Google App Engine 编程,这两本书都来自 O'Reilly Media(请参阅资源)。前者是使用 Python 对 App Engine 进行扩展教程介绍,后者是针对 Python 和 Java API 的参考书。在撰写本文时,其他技术出版商正在开发处于高级阶段的 App Engine 书籍(最著名的是 Manning)。Apress 也有一系列 Google 书籍。另一个值得关注的项目是(再次)来自 O'Reilly Media 的即将推出的 Google App Engine 视频教程。
正如我之前提到的,Google 让您可以免费开始使用 App Engine。当您的网站变得流行时,Google 会要求您为它提供的托管服务付费。您的网站越繁忙,您支付的费用就越多,并且费用与您对一家规模合理的 ISP 的期望基本一致。如果您的网站流量保持适度,您可能永远不必为 App Engine 的托管服务付费。但是,您是否以其他方式付费呢?考虑以下几点:一旦您的代码上传到 App Engine,您就无法检索它。您 可以 更新它,但如果您希望将嵌入在您的 webapp 中的业务逻辑转移到另一个平台,您最好保留本地副本作为您自己的备份。然后,还有您的数据。它存在于 Google 云中,这实际上意味着什么取决于您问谁。App Engine 将您的数据与其他人隔离开来,但您信任 Google 为您保管它。
App Engine 构建在开源 Linux 之上,具有 Python 和 Java API,这两者也都是开放技术。但是,仅凭这些事实并不能使 App Engine 变得开放。远非如此,这与 Apple iPad 一样是一个垂直封闭的系统。当您决定为这个特定的“免费”Google 平台开发时,请注意您放弃了什么。如果您可以接受供应商锁定,并且如果您信任 Google 管理您的数据和应用程序,那么 Google App Engine 可能适合您。
资源
Google App Engine 下载页面: code.google.com/appengine/downloads.html
Fedora 和 Red Hat Enterprise Linux 实用指南,第 5 版,作者 Mark G. Sobell,Prentice-Hall,PTR,2010 年: www.pearsonhighered.com/educator/product/Practical-Guide-to-Fedora-and-Red-Hat-Enterprise-Linux-A/9780137060887.page
使用 Google App Engine,作者 Charles Severance,O'Reilly Media,2009 年: oreilly.com/catalog/9780596801601
Google App Engine 编程,作者 Dan Sanderson,O'Reilly Media,2009 年: oreilly.com/catalog/9780596522735
Paul Barry (paul.barry@itcarlow.ie) 在爱尔兰卡洛理工学院任教。他最近完成了 Head First Programming,这本书是他与 David Griffiths 合著的。由于他喜欢自讨苦吃,他现在正在撰写 Head First Python,该书将于 2010 年底由 O'Reilly Media 出版。