📚 第八章 应用协议
📄 一句话概括

WWW 靠三样东西各管一段:URL 说去哪儿取,HTTP 说怎么取,HTML 说怎么显示。 这三样都是伯纳斯李在同一个项目里定的,所以它们配合得刚好。

📝 本节和前面几节的关系

这一节真起一个本机服务,把 URL 拆解、HTTP 请求、HTML 解析串成一条线。 每一步都用真实的响应头和正文。

一、三样东西各管一段

URL 是地址:它回答「去哪台机器、敲哪个门、要哪个资源」。 HTTP 是运输:它回答「怎么把请求送过去、怎么把结果拿回来」。 HTML 是排版语言:它回答「这段文字里哪部分是标题、哪部分要加粗」。 这三样的分工非常干净:少任何一个,WWW 都不成立。 只有 URL 没有 HTTP,你只能知道地址却取不回内容;只有 HTTP 没有 HTML, 取回来的是一堆看不懂的纯文本;只有 HTML 没有 URL,你没法引用别人的页面。 「超链接」是这套体系的关键发明:它让文档之间能互相指。 而「能互相指」的前提是「每个文档有一个全局唯一的地址」——这正是 URL 的作用。 所以「万维网」这个名字里的「网」,指的是文档之间连成的网,不是机器之间连成的网。

二、URL 的四段怎么映射到一次请求

http://127.0.0.1:8080/html 拆开是四段: scheme 是 http、host 是 127.0.0.1、port 是 8080、path 是 /html。 这四段各自决定一件事:scheme 决定「用哪个协议」, host 决定「连哪台机器」(要解析成 IP), port 决定「敲哪个门」(不写就用 scheme 的默认值),path 决定「要哪个资源」。 注意「path 由服务端解释」这一点:HTTP 只负责把它原样送过去,怎么理解完全看服务端。 所以 /a/b 不一定是「目录 a 下的文件 b」, 它可能是一个查询参数、一个 API 路由、甚至一个不存在的路径。 「URL 里的路径像文件路径」这件事,纯粹是约定,不是协议。

三、HTML 是标记,不是程序

HTML 的作用只有一个:给一段纯文本加上「这段是什么」的标注。 它自己不会算、不会判断、不会循环。 所以浏览器要「解析」它,把它变成一棵树,再按树递归地画。 这就是为什么嵌套错了页面就全乱——树的结构变了,画出来的东西自然也变了。 这里有个容易混的地方:HTML 管「结构」,CSS 管「样式」,JavaScript 管「行为」。 三样分开是为了「同一份内容能换不同样子」, 但这个分离在 1990 年代并没有做到—— 那时候用 <font> 标签直接写字体,结构样式混在一起,改一次配色要动所有页面。

四、动手 Demo:WWW 三件套——URL、HTTP、HTML,缺一不可

起一个返回 HTML 的本机服务,把三件事在真请求里对起来。 第一步拆 URL,第二步发 HTTP,第三步解析 HTML。

import urllib.parse, urllib.request, html.parser

port, srv = start_server()          # 起一个返回 HTML 的服务
url = 'http://127.0.0.1:%d/html' % port
sp = urllib.parse.urlsplit(url)

print('scheme', sp.scheme)          # http
print('host  ', sp.hostname)        # 127.0.0.1
print('port  ', sp.port)            # 端口
print('path  ', sp.path)            # /html

req = urllib.request.Request(url, headers={'User-Agent': 'demo/1.0'})
with urllib.request.urlopen(req, timeout=5) as r:
    print('状态码', r.status)
    for k, v in r.getheaders():
        print(k, ':', v)
    body = r.read().decode('utf-8')

class Cnt(html.parser.HTMLParser):
    def __init__(self):
        html.parser.HTMLParser.__init__(self)
        self.tags, self.depth, self.maxd, self.text = [], 0, 0, []
    def handle_starttag(self, tag, attrs):
        self.tags.append(tag)
        self.depth += 1
        self.maxd = max(self.maxd, self.depth)
    def handle_endtag(self, tag):
        self.depth -= 1
    def handle_data(self, data):
        self.text.append(data)

c = Cnt()
c.feed(body)
print('标签', len(c.tags), '最大嵌套', c.maxd, ''.join(c.text))

实跑输出:

--------------------------------------------------------------------
WWW 三件套——URL、HTTP、HTML,缺一不可
--------------------------------------------------------------------
**WWW 能跑起来,靠的是三样东西各管一段**:
**URL 说「去哪儿取」,HTTP 说「怎么取」,HTML 说「取回来怎么显示」。**
**下面把这三段在真请求里对起来看。**

**第一步:拆 URL**

  部分       这一段的值     它负责回答
  ---------------------------------------------
  scheme     http           **用哪个协议去取**
  host       127.0.0.1      **去哪台机器**
  port       64614          **敲哪个门**
  path       /html          **要哪个资源**

**第二步:HTTP 把这段 URL 变成一次请求,拿回状态码、头、正文**

    状态码 200,响应头 6 个
    Server: BaseHTTP/0.6 Python/3.13.14
    Date: Sat, 24 Oct 2026 17:11:09 GMT
    Content-Type: text/html; charset=utf-8
    Content-Length: 79
    Set-Cookie: sid=abc123; Path=/
    X-Demo: 36c

**第三步:HTML 决定这段正文怎么被画出来**

    <html><body><div><p>你好</p><ul><li>a</li><li>b</li></ul></div></body></html>

**解析结果**:7 个标签,最大嵌套 5 层,可见文字「你好ab」。

**三者串起来才叫 WWW**:**URL 是「地址」,HTTP 是「运输」,HTML 是「排版语言」。**
**少任何一个都不成立**——**只有 URL 没有 HTTP,你只能知道地址却取不回内容;**
**只有 HTTP 没有 HTML,取回来的是一堆看不懂的文本。**
**这三样都是伯纳斯李在同一个项目里定下来的,所以它们配合得刚好。**

第一步拆出四段:http、127.0.0.1、端口、/html。 第二步拿回状态码 200、六个响应头、一段 HTML。 第三步解析出 7 个标签、最大嵌套 5 层、可见文字「你好ab」。 注意「最大嵌套 5 层」是怎么来的: html 第 1 层、body 第 2 层、div 第 3 层、ul 第 4 层、li 第 5 层。 p 和 ul 是兄弟,都挂到 div 下面,所以它们都算第 4 层。 这个「深度」不是数标签个数,是数「它上面有几层父节点」。 「可见文字」这一项也值得看一眼:标签全被剥掉,只剩下「你好ab」。 这就是「HTML 是给文本加标注」这句话的字面含义—— 内容本来就在那儿,标签只是告诉浏览器「这段该怎么对待它」。

30 秒实验

  1. 想一下:URL 的 path 为什么由服务端解释?
  2. 想一下:/a/b 一定是文件路径吗?
  3. 想一下:HTML、CSS、JavaScript 各管什么?

常见误区

  1. 以为「URL 的 path 就是服务器上的文件路径」。 它只是个字符串,怎么解释看服务端。
  2. 以为「HTML 能算东西」。 它只是标记,算的逻辑在 JavaScript 或服务端。
  3. 以为「WWW 就是互联网」。 WWW 只是互联网上的一种应用,邮件、DNS、SSH 都不属于它。
自测三问(点开对答案) 1. WWW 的三件套是什么? —— **URL 定位、HTTP 传输、HTML 描述结构。** 2. URL 的四段各决定什么? —— **scheme 决定协议、host 决定机器、port 决定端口、path 决定资源。** 3. HTML 解析出来是什么结构? —— **一棵树**,每个标签的深度等于父节点深度加一。

下一节单独讲 URI 的语法细节。

相关笔记