习题 50: 你的第一个网站 ******************************* 这节以及后面的习题中,你的任务是把前面创建的游戏做成网页版。这是本书的最后三个\ 章节,这些内容对你来说难度会相当大,你要在上面花些时间才能做出来。在你开始这节\ 练习以前,你必须已经成功地完成过了《习题 46》的内容,正确安装了 ``pip``\,而且\ 学会了如何安装软件包以及如何创建项目骨架。如果你不记得这些内容,就回到《习题 46》\ 重新复习一遍。 安装 lpthw.web ----------------- 在创建你的第一个网页应用程序之前,你需要安装一个“Web 框架”,它的名字叫 ``lpthw.web``\。\ 所谓的“框架”通常是指“让某件事情做起来更容易的软件包”。在网页应用的世界里,人们创建\ 了各种各样的“网页框架”,用来解决他们在创建网站时碰到的问题,然后把这些解决方案用\ 软件包的方式发布出来,这样你就可以利用它们引导创建你自己的项目了。 可选的框架类型有很多很多,不过在这里我们将使用 ``lpthw.web`` 框架。你可以先学会它,\ 等到差不多的时候再去接触其它的框架,不过 ``lpthw.web`` 本身挺不错的,所以就算你一直使用\ 也没关系。 使用 ``pip`` 安装 ``lpthw.web``\: .. code-block:: console $ sudo pip install lpthw.web [sudo] password for zedshaw: Downloading/unpacking lpthw.web Running setup.py egg_info for package lpthw.web Installing collected packages: lpthw.web Running setup.py install for lpthw.web Successfully installed lpthw.web Cleaning up... 以上是 Linux 和 Mac OSX 系统下的安装命令,如果你使用的是 Windows,那你只要把 ``sudo`` 去掉就可以了。如果你无法正常安装,请回到《习题 46》,确认自己学会了\ 里边的内容。 .. warning:: 其他 Python 程序员会警告你说 ``lpthw.web`` 只是另外一个叫做 ``web.py`` 的 Web 框架的代码分支(fork),而 ``web.py`` 又包含了太多的“魔法(magic)”在里边。\ 如果他们这么说的话,你告诉他们 Google App Engine 最早用的就是 ``web.py``\,\ 但没有一个 Python 程序员抱怨过它里边包含了太多的魔法,因为 Google 用它也没\ 啥问题。如果 Google 觉得它可以,那它对你来说也不会差。所以还是回去继续学习\ 吧,他们这些说法与其说是教导你,不如说是拿他们自己的教条束缚你,你还是忽略\ 这些说法好了。 写一个简单的“Hello World”项目 ----------------------------------- 现在你将做一个非常简单的“Hello World”项目出来,首先你要创建一个项目目录: .. code-block:: console $ cd projects $ mkdir gothonweb $ cd gothonweb $ mkdir bin gothonweb tests docs templates $ touch gothonweb/__init__.py $ touch tests/__init__.py 你最终的目的是把《习题 42》中的游戏做成一个 web 应用,所以你的项目名称叫做 ``gothonweb``\,不过在此之前,你需要创建一个最基本的 ``lpthw.web`` 应用,将下面\ 的代码放到 ``bin/app.py`` 中: .. literalinclude:: ex/ex50.py :linenos: 然后使用下面的方法来运行这个 web 程序: .. code-block:: console $ python bin/app.py http://0.0.0.0:8080/ 不过如果你执行下面的命令: .. code-block:: console $ cd bin/ # WRONG! WRONG! WRONG! $ python app.py # WRONG! WRONG! WRONG! 那你就错了。在所有的 python 项目中,你都不需要进到底层目录去运行东西。你应该停留在最上层目录\ 运行,这样才能保证所有的模组和文件能被正常访问到。如果你犯了这个错误,就请回到《习题 46》学习\ 一下关于项目布局的知识。 最后,使用你的网页浏览器,打开 URL ``http://localhost:8080/``\,你应该看到两样\ 东西,首先是浏览器里显示了 ``Hello, world!``\,然后是你的命令行终端显示了如下\ 的输出: .. code-block:: console $ python bin/app.py http://0.0.0.0:8080/ 127.0.0.1:59542 - - [13/Jun/2011 11:44:43] "HTTP/1.1 GET /" - 200 OK 127.0.0.1:59542 - - [13/Jun/2011 11:44:43] "HTTP/1.1 GET /favicon.ico" - 404 Not Found 这些是 ``lpthw.web`` 打印出的 log 信息,从这些信息你可以看出服务器有在运行,而且能\ 了解到程序在浏览器背后做了些什么事情。这些信息还有助于你发现程序的问题。例如在\ 最后一行它告诉你浏览器试图获取 ``/favicon.ico``\,但是这个文件并不存在,因此它\ 返回的状态码是 ``404 Not Found``\。 到这里,我还没有讲到任何 web 相关的工作原理,因为首先你需要完成准备工作,以\ 便后面的学习能顺利进行,接下来的两节习题中会有详细的解释。我会要求你用各种方法\ 把你的 lpthw.web 应用程序弄坏,然后再将其重新构建起来:这样做的目的是让你明白运行\ ``lpthw.web`` 程序需要准备好哪些东西。 发生了什么事情? ------------------ 在浏览器访问到你的网页应用程序时,发生了下面一些事情: 1. 浏览器通过网络连接到你自己的电脑,它的名字叫做 ``localhost``\,这是一个标准\ 称谓,表示的谁就是网络中你自己的这台计算机,不管它实际名字是什么,你都可以使用\ ``localhost`` 来访问。它使用到的网络端口是 ``5000``\。 2. 连接成功以后,浏览器对 ``bin/app.py`` 这个应用程序发出了 HTTP 请求(request),\ 要求访问 URL ``/``\,这通常是一个网站的第一个 URL。 3. 在 ``bin/app.py`` 里,我们有一个列表,里边包含了 URL 和类的匹配关系。我们这里\ 只定义了一组匹配,那就是 ``'/', 'index'`` 的匹配。它的含义是:如果有人使用浏览\ 器访问 ``/`` 这一级目录,\ ``lpthw.web`` 将找到并加载 ``class index``\,从而\ 用它处理这个浏览器请求。 4. 现在 ``lpthw.web`` 找到了 ``class index``\,然后针对这个类的一个实例调用了 ``index.GET`` 这个方法函数。该函数运行后返回了一个字符串,以供 ``lpthw.web`` 将其传递给浏览器。 5. 最后 ``lpthw.web`` 完成了对于浏览器请求的处理,将响应(response)回传给浏览器,\ 于是你就看到了现在的页面。 确定你真的弄懂了这些,你需要画一个示意图,来理清信息是如何从浏览器传递到 ``lpthw.web``\,再到 ``index.GET``\,再回到你的浏览器的。 修正错误 ------------- 第一步,把第 11 行的 ``greeting`` 变量赋值删掉,然后刷新浏览器。你应该会看到一个\ 错误页面,你可以通过这一页丰富的错误信息看出你的程序崩溃的原因是什么。当然你已经\ 知道出错的原因是 ``greeting`` 的赋值丢失了,不过 ``lpthw.web`` 还是会给你一个挺好\ 的错误页面,让你能找到出错的具体位置。试试在这个错误页面上做以下操作: 1. 检查每一段 ``Local vars`` 输出(用鼠标点击它们),追踪里边提到的变量名称,\ 以及它们是在哪些代码文件中用到的。 2. 阅读 ``Request Information`` 一节,看看里边哪些知识是你已经熟悉了的。Request 是浏览器发给你的 ``gothonweb`` 应用程序的信息。这些知识对于日常网页浏览没有\ 什么用处,但现在你要学会这些东西,以便写出 web 应用程序来。 3. 试着把这个小程序的别的位置改错,探索一下会发生什么事情。\``lpthw.web`` 的会把\ 一些错误信息和堆栈跟踪(stack trace)信息显示在命令行终端,所以别忘了检查命令行\ 终端的信息输出。 创建基本的模板文件 ---------------------- 你已经试过用各种方法把这个 lpthw.web 程序改错,不过你有没有注意到“Hello World”不是\ 一个好 HTML 网页呢?这是一个 web 应用,所以需要一个合适的 HTML 响应页面才对。为了\ 达到这个目的,下一步你要做的是将“Hello World”以较大的绿色字体显示出来。 第一步是创建一个 ``templates/index.html`` 文件,内容如下: .. literalinclude:: ex/ex50/gothonweb/templates/index.html :linenos: 如果你学过 HTML 的话,这些内容你看上去应该很熟悉。如果你没学过 HTML,那你应该\ 去研究一下,试着用 HTML 写几个网页,从而知道它的工作原理。不过我们这里的 HTML 文件其实是一个“模板(template)”,如果你向模板提供一些参数,\ ``lpthw.web`` 就会\ 在模板中找到对应的位置,将参数的内容填充到模板中。例如每一个出现 ``$greeting`` 的位置,\ ``$greeting`` 的内容都会被替换成对应这个变量名的参数。 为了让你的 ``bin/app.py`` 处理模板,你需要写一写代码,告诉 ``lpthw.web`` 到哪里去找到模板进行加载,以及如何渲染(render)这个模板,按下面的方式修改你的 ``app.py``\: .. literalinclude:: ex/ex50/gothonweb/bin/app.py :linenos: 特别注意一下 ``render`` 这个新变量名,注意我修改了 ``index.GET`` 的最后一行,\ 让它返回了 ``render.index()`` ,并且将 ``greeting`` 变量作为参数传递给了这个\ 函数。 改好上面的代码后,刷新一下浏览器中的网页,你应该会看到一条和之前不同的绿色信息\ 输出。你还可以在浏览器中通过“查看源文件(View Source)”看到模板被渲染成了标准有效\ 的 HTML 源代码。 这么讲也许有些太快了,我来详细解释一下模板的工作原理吧: 1. 在 ``bin/app.py`` 里面你添加了一个叫做 ``render`` 的新变量,它本身是一个 ``web.template.render`` 对象。 2. 你将 ``templates/`` 作为参数传递给了这个对象,这样就让 ``render`` 知道了\ 从哪里去加载模板文件。 3. 在你后面的代码中,当浏览器一如既往地触发了 ``index.GET`` 以后,它没有再返回\ 简单的 ``greeting`` 字符串,取而代之的是你调用了 ``render.index``\,而且将\ 问候语句作为一个变量传递给它。 4. 这个 ``render_template`` 函数可以说是一个“魔法函数”,它看到了你需要的是 ``index.html``\,于是就跑到 ``templates/`` 目录下,找到名字为 ``index.html`` 的文件,然后就把它渲染(render)一遍(叫“转换一遍”也可以)。 5. 在 ``templates/index.html`` 文件中,你可以看到初始定义一行中说这个模板需要\ 使用一个叫 ``greeting`` 的参数,这和函数定义中的格式差不多。另外和 Python 语法一样,模板文件是缩进敏感的,所以要确认自己弄对了缩进。 6. 最后,你让 ``templates/index.html`` 去检查 ``greeting`` 这个变量,如果这个\ 变量存在的话,就打印出变量的内容,如果不存在的话,就会打印出一个默认的问候信息。 要深入理解这个过程,你可以修改 greeting 变量以及 HTML 模板的内容,看看会有什么\ 效果。然后创建一个叫做 ``templates/foo.html`` 的模板,并且使用一个新的 ``render.foo`` 去渲染它。从这个过程你也可以看出,\ ``render`` 调用的函数名称只要跟 ``templates/`` 下的 ``.html`` 文件名匹配到,这个 HTML 模板就可以被渲染到了。 加分习题 ========== 1. 到 http://webpy.org/ 阅读里边的文档,它其实和 ``lpthw.web`` 是同一个项目。 2. 实验一下你在上述网站看到的所有的东西,包括里边的代码示例。 3. 阅读以下 HTML5 和 CSS3 相关的东西,自己练习着写几个 .html 和 .css 文件。 4. 如果你有一个懂 Django 朋友可以帮你的话,你可以试着使用 Django 完成一下习题 50、51、52,看看结果会是什么样子的。 常见问题回答 ========================== 我没法连接 ``http://localhost:8080/``. 那就试试 ``http://127.0.0.1:8080/`` 。 ``lpthw.web`` 和 ``web.py`` 有啥不同? 一样的。我只不过“锁定”了 ``web.py`` 的某个版本,把它命名为 ``lpthw.web`` ,这样同学们\ 用的版本就都是一样的了。这样就算日后 ``web.py`` 升级升到面目全非,我也无需更新本书了。 我找不到 ``index.html`` (或者别的文件)。 很有可能是你先跑了 ``cd bin/`` 然后才开始做项目的。不要这么做,所有的指令都应该在 ``bin/`` 的上一层完成,所以如果你无法运行 ``python bin/app.py`` 那就说明你不在正确的目录下面。 为什么调用 template 时要写 ``greeting=greeting`` ? 这一句并不是赋值给 ``greeting`` ,而是将一个命名参数传到模板中。这也算是一种赋值,不过\ 只会在模板函数的调用中生效。 端口 8080 无法使用。 也许是哪个杀毒软件占用了这个端口,那就换一个端口好了。 安装 ``lpthw.web`` 时出现 ``ImportError "No module named web"`` 。 很有可能是你在系统中安装了多个版本的 Python,而在这里你用了错误的一个,或者由于 ``pip`` 版本太旧导致安装没有正确完成。试着卸载并重装 ``lpthw.web`` 。如果还不行,那就再仔细检查\ 确认自己用了正确版本的 Python。