PPysolate SpineREAD THE CODE · UNDERSTAND THE SYSTEM
课程与章节
FOUNDATIONS / 09

Web 与发布(可选)

浏览器、HTTP、Pages,以及 noindex 的边界。

本章目录

这章是本项目的可选背景。它只解释阅读和维护站点时会遇到的概念,不要求先学完整 HTML(描述页面结构的文本)、CSS(控制页面样式的规则)或 JavaScript(浏览器可执行的脚本),也不要求先学 Astro(生成静态站的构建工具)。当前站点的目标是公开一组静态讲义,同时明确“不被搜索引擎收录”;公开访问和搜索收录是两件不同的事。

先认页面的三种材料:HTML 用标签描述结构,例如 h1 是标题、a 是链接;CSS 选择这些元素并设置颜色、间距和布局;JavaScript 才是能在浏览器里执行操作的脚本。浏览器会把 HTML 建成内存中的 DOM 节点树,像一个包含标题、段落和链接的目录。它与 Python AST 都有树形结构,但保存的对象和用途不同。

元素的 id 可以标识一个具体位置,class 可以把一组元素归到同类样式。本站的函数跳转是 URL 片段找到对应 id 后滚动;语法着色给源码文字加显示标记,不是在执行 Go/C/Python。当前章节折叠主要使用浏览器原生 details 元素,不需要把每次展开都发给服务器。

1. 浏览器如何拿到一页内容

浏览器是发出网页请求、接收文件并把它们显示出来的客户端。客户端—服务器模型可以先看成点餐:客户端提出请求,服务器根据地址返回响应。浏览器请求文档站时,服务器通常只返回已经生成好的 HTML、CSS、图片或 JavaScript 文件;浏览器再负责解析和显示。静态站不等于没有服务器,而是服务器不必为每位访问者现场运行项目代码来生成页面。

**URL(统一资源定位符)**是网络资源的地址。比如 https://pysolate-spine.pages.dev/guide/ 可以拆成:https 是加密的 HTTP 通信方案,pysolate-spine.pages.dev 是主机名,/guide/ 是资源路径。?x=1 是查询参数,#part 是片段标识;片段通常由浏览器在收到页面后定位,不会作为 HTTP 请求路径发送给服务器。

浏览器先通过 DNS 把主机名查成 IP 地址,IP 地址是网络设备用来寻找目标机器或边缘节点的数字地址。然后它连接一个端口;端口像同一栋楼里的不同窗口,区分不同网络服务。HTTPS 默认使用 443 端口,HTTP 默认使用 80 端口,但实际 URL 或代理配置可以改变端口。127.0.0.1 是回环地址,指向当前机器;本地阅读服务器绑定它,并不等于已发布到公网。程序可以通过 socket(向操作系统申请的通信端点)请求连接一个 IP 和端口。一次请求还会带上 HTTP header(请求头),例如希望的内容类型;服务器响应也有 header,例如内容类型、缓存策略和爬虫指令。

响应的 HTTP 状态码是服务器对这次请求的简短结果:200 常表示成功,301/302 表示重定向,404 表示目标不存在,4xx 通常表示请求方的问题,5xx 通常表示服务器处理失败。状态码不是页面正文;即使服务器返回一张自定义 404 页面,状态仍应表达“找不到”。浏览器开发者工具或 curl -I URL 可以观察响应头,但后者访问外网,属于联网操作;下面的解释是规则,不把一个示意响应冒充现场抓包。

下面是结构示意,不是抓包结果:

状态:200
响应头:Content-Type: text/html; charset=utf-8
响应头:X-Robots-Tag: noindex
正文:HTML 页面内容

HTTPS 是 HTTP 加上 TLS 保护。TLS 在通信双方之间协商加密,并通过证书帮助客户端确认连接的主机身份,还能检测传输内容是否被中途修改。可以把加密理解为用密钥把可读信息变成无法直接阅读的形式,持正确密钥才能恢复。证书是一份把域名与公钥等信息关联起来、带有可信签发者签名的声明;公钥可公开,私钥需要保密。数字签名帮助检查来源和内容是否被改,不是“程序没有 bug”的证明。浏览器检查证书与域名,服务器也要证明持有相应私钥,再协商这次连接的保护。

通信双方仍能看到自己的通信内容。TLS 保护传输过程,不自动把公开站点变成需要登录的私有站点;知道 URL 的人仍可能直接访问公开文件。

2. Astro 静态构建和本地预览

本站的源材料是仓库 Markdown 和明确列出的源码视图,布局由 Astro 文件负责。site/astro.config.mjs 将输出模式设为 static,意思是构建时生成文件,部署时由静态文件服务器直接返回这些文件。它不是在浏览器里启动 Go、CPython Wasm(项目打包的 Python 解释器模块)或 Python 用户程序。

站点的构建命令是 site/package.json 中的 pnpm build:它先准备生成内容,再运行 astro build,最后执行验证脚本。按当前工程的边界,验证会检查 HTML 的 noindex(不加入搜索索引)、站内链接和锚点、敏感机器信息、异常文件、全局响应头以及源码页文本是否被导航标记改写。构建输出放在 site/dist/,只发布这份静态输出,不发布根目录的 dist/spine.wasm。

命令的作用和副作用要分开看:

# 可能联网、有本机 node_modules 变化;首次准备站点依赖时使用
cd site
pnpm install --frozen-lockfile

# 本地生成并检查静态文件;不会自动部署
pnpm build

# 需要先生成内容(如上面的 build);启动本地开发服务器
pnpm dev

# 需要先有构建结果;预览已构建版本
pnpm preview

--frozen-lockfile 要求安装结果服从 site/pnpm-lock.yaml,不允许安装过程悄悄改锁文件。首次安装可能需要访问包仓库;已有缓存时不一定联网。pnpm dev 和 pnpm preview 都会启动持续运行的本地进程,终端通常要用 Ctrl-C 停止。仓库根目录的 make site 等价于进入 site 执行构建;请根据实际命令输出判断构建是否成功。

如果只想阅读已经存在的静态输出,make read 会使用 Python 标准库服务器绑定 127.0.0.1,默认通过 PORT 环境变量或端口 4328 访问;它不安装 npm 依赖。它是本机 HTTP 预览,不是生产部署。每个命令的预期结果必须和实测分开记录:例如“应生成 site/dist/index.html”是预期,“验证脚本报告多少页面”只有实际运行后才能写成测量结果。

3. Cloudflare Pages、CDN 与 Git 仓库不是一件事

Cloudflare Pages 是托管静态站点的发布服务;**CDN(内容分发网络)**把文件复制或缓存到靠近访问者的边缘节点,让不同地点的客户端更快拿到同样的文件。这里的“边缘”是不同地区的服务节点,不是电脑屏幕的边缘;是否更快取决于缓存和网络,不把它当本项目的性能数字。CDN 是文件交付层,不是本仓库的 Git(保存代码历史的版本管理系统)历史。Git 仓库保存源代码、提交和分支;Pages 保存某次发布的静态输出;两者可以关联,也可以完全分开。

本项目的站点 README 说明生产项目是 Cloudflare Pages pysolate-spine,部署脚本会先完整构建和检查,再用既有 Wrangler CLI(Cloudflare 的命令行工具)上传到 production main;它不依赖 GitHub Actions(自动化工作流服务),也不会自动 git push。pnpm run deploy 属于联网且有副作用的操作,会上传公开文件;它需要本机已有的授权设置。API 是程序供调用者使用的接口,API token 是调用时出示、具有特定权限的令牌;账号 ID 只是指明操作哪个账号,不是密码。不要把真实值写入文档、文件或聊天。部署命令成功也不等于访问正确,发布后仍应检查生产 URL 的页面、状态码、资源、X-Robots-Tag 和 404 行为。

缓存是什么

缓存就是保留以前拿到的一份内容,后面有机会复用。HTTP 的 Cache-Control 控制怎样复用:本站 no-cache 不是“绝不保存”,而是要求复用前重新确认;no-store 才是不要存储,max-age 则表示允许直接复用多久。

HTTP 页面缓存与本轮 Future 是不同层的东西。前者保留网页文件,后者保留某次工具工作的结果,并遵守本项目的快照约定。都叫“存一份”,不表示能互相替代。

4. SEO、抓取和“不收录”

SEO 是让搜索系统能理解和处理网页的工作;它不等于“只要能访问就一定会出现在搜索结果中”。搜索引擎的 **crawler(爬虫)**先请求 URL;**crawl(抓取)**是这个读取过程,**index(索引)**是把页面内容纳入搜索系统的数据库。抓到了不必然收录,收录也不等于排名靠前。

robots.txt 是站点根目录提供给遵守规则的爬虫看的抓取说明;它不是密码。页面里的 meta robots(HTML 页面元信息),或响应头 X-Robots-Tag,可以声明 noindex(不要把页面加入索引)、nofollow(不要从此页面跟随链接)、noarchive(不要提供缓存副本)、nosnippet(不要在结果中显示摘要)等展示限制。noindex 不是鉴权:公开链接仍然可以访问,不服从这些规则的爬虫也可以忽略它们。真正限制访问要使用登录、访问策略或其他鉴权机制,而不是只写 robots 指令。

本项目选择 robots.txt 中:

User-agent: *
Allow: /

同时给 HTML 写 noindex, nofollow, noarchive, nosnippet,给静态资源通过 _headers 写同样的 X-Robots-Tag。这样做的原因是让遵守规则的搜索引擎能够读取响应并看到 noindex。如果用 Disallow: / 完全禁止抓取,爬虫可能看不到页面里的 noindex,反而只留下一个只能显示 URL 的搜索条目。Allow 是“可以读取”,noindex 是“读到后不要收录”,它们解决的是不同阶段的问题。

robots.txt 没有提供站点地图(sitemap)。站点布局也给 HTML 设置 noindex;包括 404 页面在内,当前目标都是减少索引,不是隐藏内容。_headers 是 Pages 识别的配置文件;本地 Python HTTP 服务器不会因为目录里有这个文件就自动应用它。因此线上响应头需要实际回读,不能用本地 HTML 通过检查替代。响应头和页面 meta 是两条不同的发送路径:浏览器会收到它们,合规搜索引擎会据此处理;它们都不能替代访问控制。

5. 发布边界:白名单决定公开什么

站点生成过程不是把整个 Git 仓库复制到服务器。它只读取发布清单中列出的文档和源码,生成页面;新增要公开的页面或源码必须显式加入白名单。站点 README 还明确排除根目录 Wasm、测试日志、凭据、完整 Git 仓库、本机配置和 SSH(远程登录协议)配置。本项目的 Wasm 由构建脚本链接/打包得到,再由 Go 中的 wazero 使用;它不随这个静态讲义站发布。构建验证会拒绝 Wasm、日志、环境文件、源码地图等不应发布的文件(source map 可把构建后代码位置对应回原始源码,也可能暴露源码或路径;它不是本课的调用流程图),并检查敏感机器信息、sitemap、断链和缺少 noindex 的 HTML。

因此,源代码发布白名单和“仓库里文件存在”是两种状态:文件可以留在本地仓库,但不出现在公开站点。site/ 下的维护脚本属于站点私有操作,本章只按 site/README.md 解释其边界,不把脚本路径当成公开源码阅读入口。写入本地文件本身不会发布;需要构建、上传并检查线上结果。

6. 这个站点没有做什么

页面是静态文档和选定源码视图。浏览器不会在前端执行本项目的 Python 用户程序,也不会建立实时 LLM provider(模型服务商)连接来生成回答;make demo 和真实 Guest(Wasm 中的运行环境)测试仍在本地工程命令中运行。站点的页面、CDN、Git 仓库、Wasm artifact(构建得到的二进制文件)和凭据分别有不同的生命周期,不能因为某一层可访问,就推断其他层也已公开或具备同样的权限。

本机维护细节保留在仓库的 site/README.md;公开阅读可继续看 构建与来源。