静态页的地址指向一个文件,Web 应用的地址指向一段会算东西的程序。
同一个 /calc,参数不同结果就不同,参数不合法就回 400。
上一节那棵树是「结果」。 这一节看「结果是怎么算出来的」——服务器那边跑的不再是「取文件」,而是一段程序。
一、从「给你文件」到「帮你算」
早期的 Web 服务器只干一件事:把路径映射成磁盘上的文件,读出来发走。/index.html 就是那个文件。
Web 应用不是这样。
/calc 这个路径在磁盘上根本不存在,它是程序里的一条路由:收到请求,取参数,算,
把结果当正文发回去。
Demo 里四次请求就能看出来:/calc?a=3&b=4 回 7,/calc?a=100&b=-58 回 42,
/calc?a=x&b=4 回 400,而 POST /calc 的路径里一个参数都没有,结果照样算出来了 15。
这条分界线就是「静态」和「动态」的分界:同一个地址,不同的时候可以给不同的东西。
二、参数放哪,后果不一样
GET 把参数拼在 URL 里,POST 放在请求正文里。技术上都行,但位置不同带来三个后果。
一、长度。
URL 会进服务器日志、进浏览器历史、进 Referer 首部,所以它有实际的长度上限,各家不同但都不大。
正文没有这个问题。
二、编码。URL 里不能直接放非 ASCII 字符,中文要变成百分号编码。
Demo 量了一下:「客户」两个字本来是 6 字节,编码后 18 字节,
正好 3 倍——因为每个字节都变成「百分号加两位十六进制」。
三、会不会被记住。URL 里的东西会被存下来、会被分享、会留在日志里;正文不会。
所以密码、令牌这类东西只能放正文,还得配上 HTTPS。
三、无状态带来的麻烦
HTTP 本身不保存状态(2.3)。Web 应用偏偏需要——登录过的用户,下一个请求怎么还认得? 答案在 2.8 和 8.5.2:用 cookie 带一个会话 ID,服务端拿这个 ID 去查会话表。 所以「Web 应用」这套东西是 HTTP 之上搭出来的,它借了 HTTP 的传输,状态得自己想办法存。 这也解释了为什么 Web 应用特别怕「会话固定」(8.5.2)和「CSRF」(第十一章)——身份全靠那一个 ID, 而 ID 是能被摆弄的。
四、动手 Demo:一个会算加法的地址
本机起一个服务端,/calc 不是文件,是一条路由:取 a 和 b 两个参数,算完把结果当正文发回去。
分别用 GET 和 POST 各试几次,再量一下中文参数编码前后的字节数。
qs = u''
if u'?' in target:
target, qs = target.split(u'?', 1)
if method == u'POST':
qs = body.decode('utf-8') # 参数在正文里
q = parse_qs(qs)
a, b = int(q[u'a'][0]), int(q['b'][0])
return 200, u'%d + %d = %d' % (a, b, a + b)
实跑输出:
--------------------------------------------------------------------
同一个地址,参数不同,结果不同
--------------------------------------------------------------------
GET /calc?a=3&b=4 → 200 3 + 4 = 7
GET /calc?a=100&b=-58 → 200 100 + -58 = 42
GET /calc?a=x&b=4 → 400 参数不是整数
POST /calc → 200 7 + 8 = 15
四次请求的路径 两个不同,还有两个相同
POST 那次路径 /calc(参数不在 URL 里)
这就是「Web 应用」和「静态页」的分界:地址本身不指向一个文件,而是指向一段**会算东西的程序**。
同一个 `/calc`,参数不同结果就不同;参数不合法,回 400。
参数值「客户」本身 6 字节
百分号编码之后 %E5%AE%A2%E6%88%B7 → 18 字节
膨胀 3 倍
每个非 ASCII 字节变成三个字节(`%` 加两位十六进制),所以中文在 URL 里
正好是三倍。这也是为什么 GET 不适合传长参数——除了膨胀,URL 本身还有长度上限。
POST 那次的正文 a=7&b=8,7 字节
它在哪 请求正文里,不在 URL 里
三次 GET 分别回 200/200/400。
第三个参数 a=x 不是整数,服务端直接拒绝——这就是「程序」和「文件」的区别:它会判断。
POST /calc 的路径里没有问号、没有参数,正文是 a=7&b=8,结果 15 照样算出来了。
参数放哪,程序自己读就行。
「客户」两个字 6 字节,百分号编码后 18 字节,正好 3 倍。
所以 GET 传中文参数,长度一旦上去,URL 很快就顶到上限。
POST 那次的正文是 7 字节。
它不进 URL、不进日志、不进 Referer——这三条是「敏感数据只能走正文」的直接理由。
30 秒实验
- 把
a传成一个超长字符串(比如一千个字符),看服务端和浏览器谁先拒绝。 - 用
curl对比GET和POST同一个接口,看-v输出里请求行长差多少。 - 想一想:为什么登录表单一定要用 POST,不能因为「方便分享」用 GET?
常见误区
- 「GET 和 POST 的区别只是有没有正文」——还有长度、编码、会不会被记录这些差别。
- 「Web 应用也是取文件」——路径往往是路由,磁盘上不一定有对应文件。
- 「参数放 URL 里也能传密码,反正加密了」——URL 会进日志和历史记录,加密也挡不住。
自测三问(点开对答案)
1. Web 应用和静态页的分界在哪? —— 静态页的地址对应文件,Web 应用的地址对应一段会算东西的程序。 2. 中文放进 URL 会膨胀多少倍?为什么? —— 三倍。每个非 ASCII 字节变成百分号加两位十六进制,共三个字节。 3. 为什么敏感参数不能放 URL? —— URL 会进服务器日志、浏览器历史、Referer 首部,等于泄露到多处。这段「会算东西的程序」在服务器上怎么被叫起来?最早的办法叫 CGI。
- ☐ (本节完,下一节 10.3.2 与Web服务器及程序协作的CGI)