习题 51: 从浏览器中获取输入 ******************************** 虽然能让浏览器显示“Hello World”是很有趣的一件事情,但是如果能让用户通过表单(form)\ 向你的应用程序提交文本就更有趣了。这节习题中,我们将使用 form 改进你的 web 程序,\ 并且将用户相关的信息保存到他们的“会话(session)”中。 Web 的工作原理 ================ 该学点无趣的东西了。在创建 form 前你需要先多学一点关于 web的工作原理。这里讲\ 并不完整,但是相当准确,在你的程序出错时,它会帮你找到出错的原因。另外,如果你\ 理解了 form 的应用,那么创建 form 对你来说就会更容易了。 我将以一个简单的图示讲起,它向你展示了 web 请求的各个不同的部分,以及信息传递\ 的大致流程: .. figure:: _static/http_request_diagram.* :align: center 为了方便讲述 HTTP 请求(request) 的流程,我在每条线上面加了字母标签以作区别。 1. 你在浏览器中输入网址 ``http://learnpythonthehardway.org/``\,然后浏览器会\ 通过你的电脑的网络设备发出 request(线路 A)。 2. 你的 request 被传送到互联网(线路 B),然后再抵达远端服务器(线路 C),\ 然后我的服务器将接受这个 request。 3. 我的服务器接受 request 后,我的 web 应用程序就去处理这个请求(线路 D),\ 然后我的 Python 代码就会去运行 ``index.GET`` 这个“处理程序(handler)”。 4. 在代码 ``return`` 的时候,我的 Python 服务器就会发出响应(response),这个\ 响应会再通过线路 D 传递到你的浏览器。 5. 这个网站所在的服务器将响应由线路 D 获取,然后通过线路 C 传至互联网。 6. 响应通过互联网由线路 B 传至你的计算机,计算机的网卡再通过线路 A 将响应传给你\ 的浏览器。 7. 最后,你的浏览器显示了这个响应的内容。 这段详解中用到了一些术语。你需要掌握这些术语,以便在谈论你的 web 应用时你能明白\ 而且应用它们: 浏览器(browser) 这是你几乎每天都会用到的软件。大部分人不知道它真正的原理,他们只会把它叫作\ “网”。它的作用其实是接收你输入到地址栏网址(例如 http://learnpythonthehardway.org),\ 然后使用该信息向该网址对应的服务器提出请求(request)。 地址(address) 通常这是一个像 http://learnpythonthehardway.org/ 一样的 URL (Uniform Resource Locator,\ 统一资源定位器),它告诉浏览器该打开哪个网站。前面的 ``http`` 指出了你要使用\ 的协议(protocol),这里我们用的是“超文本传输协议(Hyper-Text Transport Protocol)”。\ 你还可以试试 ftp://ibiblio.org/ ,这是一个“FTP 文件传输协议(File Transport Protocol)”\ 的例子。\ ``learnpythonthehardway.org`` 这部分是“主机名(hostname)”,也就是\ 一个便于人阅读和记忆的字串,主机名会被匹配到一串叫作“IP 地址”的数字上面,\ 这个“IP 地址”就相当于网络中一台计算机的电话号码,通过这个号码可以访问到这台\ 计算机。最后,URL 中还可以尾随一个“路径”,例如 http://learnpythonthehardway.org/book/ 中的 ``/book/``\,它对应的是服务器上的某个文件或者某些资源,通过访问这样的\ 网址,你可以向服务器发出请求,然后获得这些资源。网站地址还有很多别的组成\ 部分,不过这些是最主要的。 连接(connection) 一旦浏览器知道了协议(http)、服务器(learnpythonthehardway.org)、以及要获得\ 的资源,它就要去创建一个连接。这个过程中,浏览器让操作系统(Operating System, OS)打开计算机的一个“端口(port)”(通常是 80 端口),端口准备好以后,操作系统\ 会回传给你的程序一个类似文件的东西,它所做的事情就是通过网络传输和接收数据,\ 让你的计算机和 learnpythonthehardway.org 这个网站所属的服务器之间实现数据交流。 当你使用 http://localhost:8080/ 访问你自己的站点时,发生的事情其实是一样的,\ 只不过这次你告诉了浏览器要访问的是你自己的计算机(localhost),要使用的端口 不是默认的 80,而是 8080。你还可以直接访问 http://learnpythonthehardway.org:80/, 这和不输入端口效果一样,因为 HTTP 的默认端口本来就是 80。 请求(request) 你的浏览器通过你提供的地址建立了连接,现在它需要从远端服务器要到它(或你)\ 想要的资源。如果你在 URL 的结尾加了 ``/book/``\,那你想要的就是 /book/ 对应\ 的文件或资源,大部分的服务器会直接为你调用 /book/index.html 这个文件,不过\ 我们就假装不存在好了。浏览器为了获得服务器上的资源,它需要向服务器发送一个\ “请求”。这里我就不讲细节了,为了得到服务器上的内容,你必须先向服务器发送一个\ 请求才行。有意思的是,“资源”不一定非要是文件。例如当浏览器向你的应用程序提出\ 请求的时候,服务器返回的其实是你的 Python 代码生成的一些东西。 服务器(server) 服务器指的是浏览器另一端连接的计算机,它知道如何回应浏览器请求的文件和资源。\ 大部分的 web 服务器只要发送文件就可以了,这也是服务器流量的主要部分。不过\ 你学的是使用 Python 组建一个服务器,这个服务器知道如何接受请求,然后返回\ 用 Python 处理过的字符串。当你使用这种处理方式时,你其实是假装把文件发给了\ 浏览器,其实你用的都只是代码而已。就像你在《习题 50》中看到的,要构建一个\ “响应”其实也不需要多少代码。 响应(response) 这就是你的服务器回复你的请求,发回至浏览器的 HTML,它里边可能有 css、javascript、\ 或者图像等内容。以文件响应为例,服务器只要从磁盘读取文件,发送给浏览\ 器就可以了,不过它还要将这些内容包在一个特别定义的“头部信息(header)”中,这样\ 浏览器就会知道它获取的是什么类型的内容。以你的 web 应用程序为例,你发送的其实\ 还是一样的东西,包括 header 也一样,只不过这些数据是你用 Python 代码即时生成\ 的。 这个可以算是你能在网上找到的关于浏览器如何访问网站的最快的快速课程了。这节课程\ 应该可以帮你更容易地理解本节的习题,如果你还是不明白,就到处找资料多多了解这方面\ 的信息,知道你明白为止。有一个很好的方法,就是你对照着上面的图示,将你在《习题 50》\ 中创建的 web 程序中的内容分成几个部分,让其中的各部分对应到上面的图示。如果你可以\ 正确地将程序的各部分对应到这个图示,你就大致开始明白它的工作原理了。 表单(form) 的工作原理 ======================= 熟悉“表单”最好的方法就是写一个可以接收表单数据的程序出来,然后看你可以对它做些\ 什么。先将你的 ``bin/app.py`` 修改成下面的样子: .. literalinclude:: ex/ex51/gothonweb/form_test.py :linenos: 重启你的 web 程序(按 CTRL-C 后重新运行),确认它有运行起来,然后使用浏览器访问 ``http://localhost:8080/hello``\,这时浏览器应该会显示“I just wanted to say Hello, Nobody.”,接下来,将浏览器的地址改成 ``http://localhost:8080/hello?name=Frank``\,\ 然后你可以看到页面显示为“Hello, Frank.”,最后将 ``name=Frank`` 修改为你自己的名字,\ 你就可以看到它对你说“Hello”了。 让我们研究一下你的程序里做过的修改。 1. 我们没有直接为 ``greeting`` 赋值,而是使用了 ``web.input`` 从浏览器获取数据。\ 这个函数会将一组 key=value 的表述作为默认参数,解析你提供的 URL 中的 ``?name=Frank`` 部分,然后返回一个对象,你可以通过这个对象方便地访问到表单的值。 2. 然后我通过 ``form`` 对象的 ``form.name`` 属性为 ``greeting`` 赋值,这句你\ 应该已经熟悉了。 3. 其他的内容和以前是一样的,我们就不再分析了。 URL 中该还可以包含多个参数。将本例的 URL 改成这样子: ``http://localhost:8080/hello?name=Frank&greet=Hola``\。然后修改代码,让它去获取\ ``form.name`` 和 ``form.greet``\,如下所示: .. code-block:: python greeting = "%s, %s" % (form.greet, form.name) 修改完毕后,试着访问新的 URL。然后将 ``&greet=Hola`` 部分删除,看看你会得到什么\ 样的错误信息。由于我们在 ``web.input(name="Nobody")`` 中没有为 ``greet`` 设定\ 默认值,这样 ``greet`` 就变成了一个必须的参数,如果没有这个参数程序就会报错。\ 现在修改一下你的程序,在 ``web.input`` 中为 ``greet`` 设一个默认值试试看。另外\ 你还可以设 ``greet=None``\,这样你可以通过程序检查 ``greet`` 的值是否存在,然后\ 提供一个比较好的错误信息出来,例如: .. code-block:: python form = web.input(name="Nobody", greet=None) if form.greet: greeting = "%s, %s" % (form.greet, form.name) return render.index(greeting = greeting) else: return "ERROR: greet is required." 创建 HTML 表单 =================== 你可以通过 URL 参数实现表单提交,不过这样看上去有些丑陋,而且不方便一般人使用,\ 你真正需要的是一个“POST 表单”,这是一种包含了 ``