制作 Cookie 的食谱
绝大多数 URL 都以字母 “http” 开头,它代表 “超文本传输协议 (hypertext transfer protocol)”。正如电子邮件使用 SMTP(简单邮件传输协议)传输,文件通常使用 FTP(文件传输协议)检索一样,用 HTML 编写的文件通常使用 HTTP 传输。
Web 的发明者为什么为传输超文本创建了一个新的协议,而不是坚持使用以前的协议呢?一个答案是,他们有兴趣让服务器能够快速有效地响应来自浏览器的请求。HTTP 事务的客户端(浏览器)侧包含一个文档请求,其中包含几个可选参数,描述文档的内容类型及其最后修改日期。服务器通过描述文档(包括其内容类型)并返回文档来响应请求。文档发送完毕后,服务器关闭连接。通过交换最少的信息然后断开连接,文档以低开销传输,因此速度相对较快。
这种“无状态性”——即每个连接都用于传输单个文档,并且每个事务都在真空中发生——在 Web 的早期是一个非常棒的想法。这意味着浏览器和服务器在传输文档时不必跟踪很少的信息,从而减小了这些程序的大小并提高了速度。
因此,如果我们查看典型 Web 服务器的访问日志,我们会看到文档请求列表以及发起请求的计算机的 IP 地址(即,唯一标识 Internet 上计算机的数字)。但是,我们不知道大约在同一时间从同一台计算机发出的三个请求是由同一个人还是三个不同的人发出的。
在许多情况下,这不会成为问题;毕竟,如果我的网站设置为提供 HTML 页面,那么我可能不在乎是否有 1,000 个不同的人访问了我的网站,或者是否同一个人阅读了 1,000 个文档。对于许多网站来说,无状态性不会造成任何障碍。
然而,许多网站所有者,尤其是商业网站所有者,越来越对 Web 固有的无状态性感到沮丧。当您精确统计访问您网站的人数,而不是每个文档被访问了多少次的列表时,销售广告要容易得多。“点击量”,或服务器收到的单个 HTTP 请求,仅在非营利和个人领域是衡量网站成功的合理指标;商业网站更关心给定数量的个人浏览了多少页面。
即使是小型个人网站有时也喜欢跟踪用户。如果您想个性化用户对您网站的视图,需要一种方法来跟踪每个用户的偏好,而不是应用于所有用户的设置。而且,虽然您当然可以通过 HTML 表单获取用户的姓名(以及必要的密码),但强迫用户在每个页面上输入,甚至在到达您网站的首页时输入,将给用户带来很大的负担。
本月,我们将研究最流行的跟踪用户状态的方法之一,即 HTTP Cookie。Cookie 允许服务器在用户的计算机上存储少量数据,从而跟踪用户在我们网站上的活动。请注意,虽然 Cookie 可以用于跟踪用户的活动,并可能构建对广告商有用的个人资料,但它们无法收集用户未提供的任何信息。对隐私滥用的担忧在某些情况下可能是真实的(设计者应该认识到 Cookie 会冒犯和惹恼一些用户),但 Cookie 可以在您不知情的情况下从您的计算机收集信息的恐惧是不着边际的。Cookie 只是使创建有趣的网站变得更加容易。
Cookie 是由用户的浏览器存储在用户计算机上的小块数据(最多 4KB)。除了名称-值对之外,Cookie 还标有过期日期,限制了它们的存储时间长度,以及最初创建 Cookie 的 Internet 主机或域的指示符。
在处理 Cookie 时要记住的基本规则是,Cookie 的值由服务器使用 HTTP 响应设置,浏览器使用 HTTP 请求返回这些值。以这种方式思考事物有点令人不安;我们不习惯来自服务器的响应包含它们自己的请求。
假设我们有一个 CGI 程序,当被调用时返回一小段 HTML。假设该程序位于 /cgi-bin 目录中,并且名为 sample.pl,我们的浏览器将通过连接到端口 80 上的服务器并发出如下请求来检索它
GET /cgi-bin/sample.pl HTTP/1.0
此请求表示我们正在使用 HTTP 1.0,并希望服务器向我们发送文档 /cgi-bin/sample.pl。服务器由于其配置选项,知道 /cgi-bin 中的任何内容都是程序,因此它执行 sample.pl,返回输出。以下是 sample.pl 可能返回的示例
HTTP/1.0 200 OK Content-type: text/html <HTML> <Head><Title>Test</Title></Head> <Body><P>Test</P></Body> </HTML>以上是现代 HTTP 事务可以达到的最小程度。返回一个状态代码之后和消息体之前的单个标头 (Content-type)。大多数时候,响应标头中包含更多信息,例如服务器名称和版本号以及文档的创建日期。如果服务器想要在浏览器的计算机上设置 Cookie,它必须包含一个额外的标头,名为 Set-cookie。正如 Content-type 标头定义了响应中返回的数据类型一样,Set-cookie 标头定义了应用于响应来源站点的 Cookie 的名称和值。
例如,列表 1 包含一个简短的程序 (cookie-test.pl),该程序在用户的计算机上创建 Cookie。如果我们从 Web 浏览器运行 cookie-test.pl,我们会看到程序生成的 HTML 输出。如果不是程序礼貌地指示它已设置 Cookie,我们永远不会知道,除非我们要求我们的浏览器在每次都警告我们。(我在 Netscape Navigator 3.0 中发现此功能后尝试过,但我很快就将其关闭了,因为我发现此类对话框多么频繁地干扰了我的 Web 浏览,以及它们中的大多数看起来多么无害。)
如果我们使用 telnet 查看程序发送的输出,Set-cookie 标头就会变得很明显。从我运行 Red Hat Linux 4.2 的计算机上,我输入
telnet localhost 80
这会打开与我的计算机上运行的 Apache HTTP 服务器的连接。然后我输入
GET /cgi-bin/cookie-test.pl HTTP/1.0后跟两个换行符,表示我的请求结束。与上面的示例一样,我的服务器知道 /cgi-bin 中的任何内容实际上都应该执行 cookie-test.pl 并将该程序的输出发送到用户的浏览器。当我输入上述请求并按两次回车键(一次结束请求行,另一次表示我们已完成整个请求)时,我得到以下内容
HTTP/1.1 200 OK Date: Tue, 23 Sep 1997 09:15:42 GMT Server: Apache/1.2.4 Set-cookie: counter=1; path=/cgi-bin/ Connection: close Content-Type: text/html <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <HTML><HEAD><TITLE>Cookie set</TITLE> </HEAD><BODY><P>The cookie named "counter" has been set to 1.</P> </BODY></HTML>Connection closed by foreign host.上面的响应比我们上面看到的骨架响应更详细,但其内容仍然应该相当清楚。我们从 Web 服务器收到 200 (“OK”) 消息、文档创建日期、生成响应的服务器、连接类型和内容类型。
Set-cookie 标头告诉我们的浏览器,我们现在应该保留一个名为 counter 的 Cookie,其值应为 1。将来,每当我的浏览器从该主机请求 cgi-bin 路径中的文档时,它都会发送 counter Cookie 的值,该值仍将设置为 1。列表 2 是一个简短的 CGI 程序,用于打印发送给它的所有 Cookie 的名称和值。
请注意,我们的程序只看到一个 Cookie,而我知道我的浏览器已经保留了比这更多的 Cookie。当我在某些我注册过的网站上输入时,我不必输入我的密码,并且在 ~/.netscape/cookies 中有大量 Cookie,Netscape 浏览器将 Cookie 放在该文件中。为什么只出现一个 Cookie?
答案是,当浏览器访问站点时,它只会发送由该站点创建的 Cookie。因此,当我访问我的本地 Web 服务器时,只有我的本地 Web 服务器创建的 Cookie 才可用于那里的 CGI 程序。如果我要访问 纽约时报,则只有 nytimes.com 域设置的 Cookie 才可用于他们的系统。Cookie 规范的基石之一是 Cookie 名称和值对应该仅发送到创建它们的主机或域。
那些担心您的 Web 浏览兴趣信息在站点之间共享(从而侵犯您的隐私)的人可能仍然是对的——但这不会通过 Cookie 自动发生,除非您访问的所有站点都在同一域内。事实上,HotWired 曾经有几个站点具有依赖 Cookie 的共享密码系统。但是,由于这些站点都具有不同的域名,因此我必须在我的系统上为每个子站点设置一个单独的 Cookie。结果是,我必须在我第一次访问这些站点中的每一个站点时输入我的用户名和密码。
现在我们知道如何创建 Cookie 了。我们的 CGI 程序使用 CGI.pm 的 cookie 方法创建一个具有名称和值的 Cookie,然后将其放入返回给浏览器的标头中。我们也知道如何编写可以获取 Cookie 值的程序。要获取所有 Cookie 的列表,请使用相同的 cookie 方法,然后迭代它返回的名称列表。一旦我们有了这些名称,我们就可以使用 cookie 方法检索值,并为其提供特定 Cookie 名称的参数。
存储值没有太多用处,除非我们也可以更改它。我们的下一个任务是将 cookie-test.pl 和 show-cookies.pl 的部分组合到一个程序中,该程序每次我们访问该站点时都会递增 counter Cookie 值,并在每次都显示其值。我们第一次访问此 CGI 程序时,它会将 Cookie 的值设置为 1,我们第二次访问时,它会将值设置为 2,依此类推。
您可以在 列表 3 中看到对此类程序的简陋尝试。如您所见,代码非常简单明了。我们创建一个 CGI 实例,并使用 cookie 方法提取 counter Cookie 的值。我们将该值递增 1,创建一个具有更新值的新 counter Cookie,并将该值作为标头的一部分发送回程序的响应。响应的正文包含系统中每个 Cookie 的名称和值的简短列表。
每次我们调用 update-counter.pl 时,用户的浏览器都将 counter 识别为具有适当主机或域名和路径的 Cookie,因此将 counter 作为 Cookie 与其请求一起发送。Update-counter.pl 获取 Cookie 的值(如果存在),如果不存在则将其设置为 0。然后,它递增 Cookie 的值,并创建一个新的(传出的)Cookie,其中包含 counter 名称和更新后的值。这个新的 Cookie 包含在 update-counter.pl 发送到用户浏览器的标头中,Cookie 的值显示在包含 HTML 格式文本的响应正文中。
这个程序可能看起来不是很有用,但只需稍作修改,它就可以在多种情况下派上用场。例如,您可以确保用户只填写一次问卷,或者跟踪他们通过 Web 而不是通过电话请求技术支持的次数。另一种可能性可能是基于 Web 的问答游戏,一次呈现一个问题。您可以使用 Cookie 跟踪用户的分数。或者,您可以跟踪用户已经看过哪些问题,以免两次问同一个问题。您甚至可以跟踪用户的最高分,并在用户获得新高分时给出特别消息。
如果我们有兴趣跟踪多个值,我们可以简单地创建多个单独的 Cookie。Cookie 规范表明,每个主机或域最多可以存储 20 个 Cookie。除了 CGI.pm 文档中的一个注释表明某些版本的 Netscape Navigator 对此数字施加了低得多的限制之外,实际上,存储多个 Cookie 是很容易实现的。
仅仅因为我们可以做某事并不意味着我们应该这样做,并且使用多个 Cookie 通常是解决问题的错误方法。有兴趣跟踪每个用户的几个变量的站点已开始使用 Cookie 不是为了存储有关用户的数据,而是为了存储关系数据库中存储的表中的唯一索引。(有关关系数据库和 SQL 的更多信息,请参阅 1997 年 9 月、10 月和 11 月的 At the Forge 专栏。)
我们如何做到这一点?首先,我们在我们的关系数据库中创建一个表,该表为表中的每一行提供一个唯一标识符(称为主键)。例如,如果我们想跟踪每个用户的名字和最喜欢的颜色,我们可以使用 SQL 中的以下语句创建一个表
create table user_table (user_id mediumint auto_increment primary key, user_name varchar(60) not null, user_color varchar(10) not null);
在我们的数据库中创建了上述表之后,我们可以创建一个 HTML 表单,用户可以在其中输入他或她的姓名和最喜欢的颜色(请参阅 列表 4)。
现在我们有了一个 HTML 表单,允许用户将其姓名和最喜欢的颜色提交给 CGI 程序,我们需要编写该程序 submit-cookie.pl(请参阅 列表 5)。该程序首先检查用户是否已经有 Cookie;如果有,它只需更新用户表中的现有元素。此程序的更健壮版本将检查表中是否真的存在条目,或者 Cookie 值是否对我们的站点无效。
如果不存在 user_id Cookie,submit-cookie.pl 需要在数据库表中创建一个新条目,并将新的 Cookie 分配返回给用户的浏览器。因此,我们在表中插入一个新行,其值取决于从 HTML 表单提交的值。当我们完成向服务器发送 SQL 查询后,我们向服务器请求在插入我们的行时使用的唯一 ID,该 ID 用作用户 ID 并存储在用户的浏览器中的 user_id Cookie 中。我们通过使用 Mysql insert_id 方法获得此值,该方法准确地告诉我们此信息。获得此信息后,我们创建一个新 Cookie,并将其作为对用户的响应中的 HTTP 标头的一部分返回。
无论在哪种情况下——无论我们是在表中创建一行还是更新现有行——都向用户显示 homepage.pl(请参阅 列表 6)的链接,homepage.pl 是一个个性化主页程序,用于显示我们收集的信息。请记住,这些信息都没有存储在用户计算机上的 Cookie 中。相反,信息存储在我们的关系数据库中的一个表中,索引存储在用户的 Cookie 文件中。
显然,存储用户的姓名和最喜欢的颜色只是示例。站点可以允许用户指示一组偏好,并使用数据库根据这些偏好选择图形、文本甚至超链接。
这就是我们对 Cookie 及其可以为您和您的网站做些什么的温和介绍的全部内容。我没有深入探讨 Cookie 创建和管理的一些要素,例如过期日期和安全性,但在阅读资源中提到的一个或多个规范后,这些要素很容易理解。只需说,任何有兴趣在当前浏览器调用之后保留 Cookie 值的人都必须处理过期日期,因为在没有过期日期的情况下创建的 Cookie 仅持续到用户退出浏览器为止。
正如我们所见,Cookie 为我们提供了一种状态感,否则这种状态感是不可能管理的。不难想到使用 Cookie 的方法,无论是创建个性化主页、用于在 Web 上购买商品的购物车还是确定用户是否以前访问过某个站点。借助这些类型的工具,您的网站可以变得对访问者更具吸引力,而无需付出大量额外的工作。
