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 秒实验
- 想一下:URL 的 path 为什么由服务端解释?
- 想一下:
/a/b一定是文件路径吗? - 想一下:HTML、CSS、JavaScript 各管什么?
常见误区
- 以为「URL 的 path 就是服务器上的文件路径」。 它只是个字符串,怎么解释看服务端。
- 以为「HTML 能算东西」。 它只是标记,算的逻辑在 JavaScript 或服务端。
- 以为「WWW 就是互联网」。 WWW 只是互联网上的一种应用,邮件、DNS、SSH 都不属于它。
自测三问(点开对答案)
1. WWW 的三件套是什么? —— **URL 定位、HTTP 传输、HTML 描述结构。** 2. URL 的四段各决定什么? —— **scheme 决定协议、host 决定机器、port 决定端口、path 决定资源。** 3. HTML 解析出来是什么结构? —— **一棵树**,每个标签的深度等于父节点深度加一。下一节单独讲 URI 的语法细节。
- ☐ (本节完,下一节 8.5.3 URI)