摘要
- 一条 Gopher 菜单项包含类型、显示名称、不透明 selector、host 与 port。读者选择名称,客户端按其余字段发起一次新的 transaction,响应者可能已是另一台服务器。
- 因此,可见层级实际是一张 referral 图。菜单发布者有权推荐路线,却不能借此拥有目的服务器、定义远端 selector,或证明返回内容真实、未变且安全。
点开“课程表”时,机器已经换了
设想一名学生面对学校的信息菜单。他选择“课程表”,界面像是在同一棵目录中进入下一层。这个名称没有告诉他,下一份响应也许来自另一个系、另一台 host,甚至一个非默认 port。但客户端早已得到这些坐标;它们就藏在显示名称后面的同一行里。
RFC 1436 给出的目录项语法很短:第一个字符是 item type,随后是用户可见名称、Tab、selector、Tab、host、Tab、port,最后以 CRLF 结束。通常只有名称出现在屏幕上。客户端保存其余字段,并把它们变成下一步动作。
这套格式让分散发布变得便宜。一个院系不必把另一个院系的文件复制到中央仓库,只需在自己的菜单中放入一行。用户感到目录延伸了,客户端则结束眼前的 transaction,连接另一端并发送那里的 selector。
也正因为如此,展示权与执行权没有合并。菜单编辑者可以选择标题、排序和去留,但远端 operator 才决定 selector 的含义与实际响应。把某个地址写进目录,是一项导航陈述,不是对目的地的所有权证书。
Selector 的价值恰恰在于客户端不懂它
很多示例里的 selector 像文件路径。聪明的客户端很容易产生冲动:拆解它、规范化它,甚至根据斜杠推测层级。RFC 1436 的要求相反:selector 对客户端不应有含义,客户端绝不能修改它。
这项克制保护了服务器自治。某台服务器可以把 selector 当作 pathname,另一台可以把它交给脚本、应用或动态查询。共享协议无需命令所有发布者公开本地文件系统,也不要求他们采用全球统一 object ID。
不透明并不等于可靠。相同字节在两台 host 上可以指向完全不同的对象,同一台 host 也可能在迁移后改变解释。Selector 不是内容 hash、秘密 capability 或永久名称;它只在指定服务的本地语义里生效。
所以,取证应保留原始字节,而不是事后“理解”它。自动改写分隔符、重新编码,或修复看似异常的字符,都可能让请求悄悄落到另一个对象。Gopher 的简洁依赖一种边界意识:该由远端解释的东西,本地就不要替它解释。
所谓 root,只是方便进入的门
Gopher 借用了文件系统层级,因为目录树容易理解。但 RFC 并未要求网络本身是一棵树。菜单可以指向 secondary server,可以指向互联网上任何有用服务,也可以指回先前经过的节点。最终结构是一张任意 graph。
一个 campus 可以经营众所周知的 top-level server,让各部门登记入口,也可以复制这个入口以分担负载。不过,部门仍能在自己的 menu 里直接放入其他 server。Root 提供 discoverability,不因此成为所有下游内容的中央主人。
一次 referral 横跨多个控制面。源菜单 operator 书写 label 与 descriptor;目的服务器 operator 解释 selector;DNS operator 可以把 alias 映射到新地址;port 上的进程决定谁真正响应;client 决定是否支持 item type;reader 只在被展示的选项中作选择。
因此,一行菜单只能证明“这个源在这个时间提供过这条路线”。它可能写错 host,保留过期 selector,或用友好的名称指向不相干服务。DNS 也可能在菜单不变时发生改变。整张图之所以继续可走,是因为多个自治方持续合作,而不是因为中央数据库锁定了所有关系。
连续浏览是客户端拼出来的
基础 transaction 很短。客户端开启 TCP,发送一行 selector;这行可以为空。服务器给出响应,但不会在不同 transaction 之间保存 client state。只发 CRLF,就可以请求 top-level menu。
文本与目录响应用单独一行句点表示结束。如果正文某行本来以句点开头,发送端必须再加一个,接收端再去掉。二进制类型没有这个句点;客户端读到 TCP 关闭为止。
因此,connection close 不能被统一解释成失败。对 binary 来说,它正是正常 framing。反过来,看到正确的终止符也只说明 response 边界完整,不说明内容正确。完整 menu 可以全是 dead link,正确结束的文本也可能是旧文件。
读者感觉自己处于一个 session,是因为客户端在本地维护连续性。RFC 1436 提到可以把访问过的位置压入 stack,也可以 cache 已读目录。不同服务器不需要共享一份用户旅程;浏览历史由一个个独立 exchange 和本地记忆重新组合。
第一个字符选择方法,不授予真相
菜单行开头的 type 决定 client 用哪套动作。0 是文本,1 是目录,7 是 index search。其他字符可以把请求交给 binary、CSO、Telnet 或 TN3270。遇到不认识的非核心类型,客户端可以忽略,也可以显示为 unknown。
这个字符是 dispatch signal,不是 MIME 证明、身份认证或安全渲染许可。若发布者标错类型,客户端可能对一个可达 endpoint 使用错误解释;语法仍然完全合法。
Type 7 尤其能看清权力分工。客户端发送 selector、Tab 与 search string,服务器返回一份 virtual menu。多个 index 或 gateway 可以覆盖不同集合,client 不必知道它们内部如何组织。
搜索结果仍是 referral。Search server 判断哪些行匹配并给出坐标,目的 host 才控制 retrieval。出现在结果里,不证明链接仍活着、不证明匹配判断正确,也不证明最终文档没有变化。
URI 把路线带出菜单,却没有封存对象
RFC 1738 后来把 Gopher 坐标写进 URL:host、可选 port、单字符 gophertype 与 selector。省略 port 时默认 70;空 path 可以表示 type 1 的顶层目录;编码后的 Tab 把 selector 与搜索词分开。
RFC 4266 在 RFC 1738 退出后,把这套 scheme 保留在 Standards Track。路线不再必须依附于原菜单,可以被收藏、复制和传播。
但可携带不等于不可变。Selector 仍由目的服务器解释,host 的 DNS 结果会变化,port 上的 process 也会更换。URI 保存的是可重复发起请求的语法,不是内容 identity 或 version pin。
安全边界也没有因为序列化而消失。RFC 1436 明说没有讨论 security。RFC 4266 后来指出 Gopher 不提供 privacy,涉及 password 时又是 cleartext,以当代标准看极不安全。一条能被正确 parse 的路线,不会自动成为受保护的路线。
五个字段背后是一项制度选择
如何让许多 publisher 共同形成一个可导航的信息空间,又不让他们把 server 和内容交给唯一管理员?Gopher 的答案是把下一位参与者的坐标交给 client。
集中力量并未消失。热门入口可以决定谁被看见,index 可以选择收录范围,client 可以隐藏不支持的类型。但这些权力仍可分开追问:谁编辑目录,谁运行目的服务,谁解析名称,谁决定兼容性,谁能更正错误。
这也是 Gopher 留下的长期经验。目录可以记录路线并协调访问,却不能把记录职能扩张成对所有目的地的支配。地址簿让房子可被找到,不会让地址簿的保管人成为房子的主人。
来源
会员简报
档案背景详情
使用相应会员等级登录,即可解锁完整简报与来源注释。
仅限 Strategic Circle
Strategic Circle
所有读者均可浏览。加入并登录后可解锁档案简报。
加入 Strategic Circle仅限 Leadership Alliance
Leadership Alliance
符合条件的 IP 资产所有者和管理层可登录查看 Leadership Alliance 简报。
加入 Leadership Alliance
