大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
Web 基础
这一课你将能做到
- 拆解任意 URL 的协议、主机、端口、路径、查询参数和片段,并写出合法的相对路径
- 解释 GET 和 POST 的区别,理解 MIME 类型如何决定浏览器处理资源的方式
- 理解 URL 编码与解码规则,知道文件上传时 multipart/form-data 的编码原理
为什么要学
HTML 是「写给浏览器的信」,但浏览器怎么送信、送到哪、信里装什么,这些是 HTTP 和 URL 的职责。写 action="/submit" 时不知道路径是相对还是绝对,部署到子目录就 404;写 <a href="page.html"> 时不知道 GET 请求会把参数暴露在 URL,提交密码等于公开广播。理解 Web 基础,HTML 写出去才不会变成「漂流瓶」。
人话:HTML 是信的内容,URL 是收信地址,HTTP 是邮递方式,MIME 是信封上的标签。只学写信不学制式信封,信送不到。
先建立图像
一个 URL 的解剖图:
https:// www.example.com :8080 /path/to/page ?id=123&name=张三 #section2
│ 协议 │ │ 主机域名 │ │端口│ │ 路径 │ │ 查询参数 │ │ 片段 │
└──────┘ └──────────────┘ └────┘ └────────────┘ └───────────────────┘ └──────┘
相对路径的四种基准:
./page.html → 当前目录(与 page.html 等价)
../page.html → 上级目录
/page.html → 域名根目录(不受当前路径影响)
page.html → 当前目录(无 ./ 前缀的简写)
跟着做
步骤 1:URL 拆解
新建 web.html(只用于概念理解,无需打开):
<!-- 同源 vs 跨域示例 -->
<a href="https://developer.mozilla.org/zh-CN/docs/Web/HTML">MDN HTML</a>
<a href="/about.html">站内关于页</a>
<a href="../contact.html">上级目录联系页</a>
<a href="./detail.html?id=100">当前目录详情页带参数</a>
逐层拆解 https://www.example.com:8080/app/users?id=42#profile:
| 组件 | 值 | 说明 |
|---|---|---|
| 协议 | https | HTTP over TLS,加密传输 |
| 主机 | www.example.com | 域名,经 DNS 解析为 IP |
| 端口 | :8080 | HTTP 默认 80,HTTPS 默认 443,非默认需显式写 |
| 路径 | /app/users | 服务器上的资源路径 |
| 查询 | ?id=42 | 键值对参数,用 & 分隔多个 |
| 片段 | #profile | 锚点,不发给服务器,纯客户端定位 |
步骤 2:相对路径实战
假设当前页面是 https://example.com/blog/2026/post.html:
<!-- 相对当前目录 → https://example.com/blog/2026/about.html -->
<a href="about.html">关于</a>
<!-- 相对上级目录 → https://example.com/blog/archive.html -->
<a href="../archive.html">归档</a>
<!-- 相对根目录 → https://example.com/images/logo.png -->
<img src="/images/logo.png" alt="Logo">
<!-- 协议相对 URL → 继承当前协议(http 或 https) -->
<script src="//cdn.example.com/lib.js"></script>
协议相对 URL(// 开头)在混合内容环境(部分页面 http、部分 https)有用,但现代推荐全部用 https,直接写完整 URL。
步骤 3:GET 与 POST
<!-- GET:搜索表单,参数在 URL -->
<form action="/search" method="GET">
<input type="search" name="q" placeholder="搜索文章">
<button type="submit">搜索</button>
</form>
<!-- 提交后 URL:/search?q=html -->
<!-- POST:登录表单,参数在请求体 -->
<form action="/login" method="POST">
<input type="text" name="username" placeholder="用户名">
<input type="password" name="password" placeholder="密码">
<button type="submit">登录</button>
</form>
<!-- 提交后 URL:/login(参数不可见) -->
| 特性 | GET | POST |
|---|---|---|
| 参数位置 | URL 查询字符串 | 请求体(body) |
| 可见性 | 可见(地址栏、历史记录、服务器日志) | 不可见(相对安全) |
| 长度限制 | 约 2KB-8KB(浏览器/服务器限制) | 无明确限制(受服务器配置) |
| 缓存 | 可被浏览器缓存 | 默认不缓存 |
| 幂等性 | 幂等(多次请求结果相同) | 非幂等(多次提交可能创建多条记录) |
| 用途 | 获取资源、搜索、筛选 | 提交数据、创建资源、登录 |
幂等性:GET /users?id=1 执行一百次,结果一样;POST /orders 执行两次,会下两个订单。
步骤 4:MIME 类型
浏览器收到服务器响应后,根据 Content-Type 决定怎么处理:
| MIME 类型 | 处理方式 | 常见扩展名 |
|---|---|---|
| text/html | 解析为网页 | .html, .htm |
| text/css | 解析为样式表 | .css |
| text/javascript | 执行 JS | .js |
| application/json | 交给 JS 解析 | .json |
| image/jpeg | 渲染为图片 | .jpg, .jpeg |
| image/png | 渲染为图片 | .png |
| image/svg+xml | 解析为矢量图 | .svg |
| application/pdf | 打开或下载 PDF | |
| multipart/form-data | 表单文件上传编码 | — |
服务器配置错误示例:把 .js 文件配成 text/plain,浏览器会显示源码而不是执行。排查:F12 → Network → 点击请求 → Response Headers → Content-Type。
步骤 5:URL 编码与解码
<!-- 特殊字符在 URL 中的编码 -->
<a href="/search?q=%E4%B8%AD%E6%96%87">搜索「中文」</a>
<!-- 「中文」编码为 %E4%B8%AD%E6%96%87 -->
<script>
// JS 编码解码
const encoded = encodeURIComponent('中文 & 符号 = 值');
// "%E4%B8%AD%E6%96%87%20%26%20%E7%AC%A6%E5%8F%B7%20%3D%20%E5%80%BC"
const decoded = decodeURIComponent(encoded);
// "中文 & 符号 = 值"
// 只编码路径部分(不编码斜杠)
const path = encodeURI('/路径/中文?a=1');
// "/%E8%B7%AF%E5%BE%84/%E4%B8%AD%E6%96%87?a=1"
</script>
编码规则:
encodeURIComponent:编码所有特殊字符(用于 query 参数值)encodeURI:不编码 URL 保留字符(/、?、&、=),用于完整 URL- 必须编码的字符:中文、空格(
%20或+)、&、=、%、#
步骤 6:文件上传原理
<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="avatar">
<input type="text" name="description" value="头像">
<button type="submit">上传</button>
</form>
提交后浏览器发送的请求体(简化):
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryABC123
------WebKitFormBoundaryABC123
Content-Disposition: form-data; name="avatar"; filename="photo.jpg"
Content-Type: image/jpeg
[二进制文件内容]
------WebKitFormBoundaryABC123
Content-Disposition: form-data; name="description"
头像
------WebKitFormBoundaryABC123--
boundary:分隔符,浏览器自动生成,分割多个字段。- 文件字段带
filename和Content-Type;普通字段只有 name 和值。 - 没有
enctype="multipart/form-data",文件内容不会被发送,后端收不到文件。
讲清楚要点
- URL 片段 # 不发给服务器:
page.html#section的#section在 HTTP 请求中不会出现,Spring Boot 的@RequestParam、Servlet 的request.getParameter都拿不到它。纯客户端行为。 - 相对路径的基准是当前页面 URL:部署到子目录(如
/app/)时,/images/logo.png是绝对根路径,会跳到域名根,而不是子目录根。子目录项目推荐用相对路径./或../。 - GET 不是「短请求」,POST 不是「长请求」:区别在于语义(获取 vs 创建)和参数位置,不是长度。RFC 规范没有限制 GET URL 长度,但浏览器和服务器有实际限制。
- MIME 类型决定浏览器行为:服务器说
Content-Type: text/plain,浏览器就按纯文本显示;说application/octet-stream,浏览器就提示下载。MIME 类型比文件扩展名更权威。 - URL 编码保护特殊字符:
&在 query 里是参数分隔符,如果参数值本身含&,必须编码成%26,否则解析器会把它当成分隔符。
你可能会懵的地方
-
端口不写时默认是多少? HTTP 默认 80,HTTPS 默认 443。浏览器的地址栏会隐藏默认端口,但服务器配置(如 Nginx、Spring Boot)必须监听正确端口。
-
协议相对 URL
//cdn.com/...安全吗? 它会继承当前页面的协议。如果当前是 http,CDN 也走 http,有中间人攻击风险。现代推荐所有外部资源都用https://硬编码。 -
POST 数据真的安全吗? 比 GET 安全(不在 URL 暴露),但 HTTP 明文传输时,POST body 也会被嗅探。真正安全需要 HTTPS 加密通道 + 后端验证 + 不存明文密码。
-
URL 编码和 Base64 什么关系? 没关系。URL 编码是为了让 URL 合法化;Base64 是为了把二进制数据转成 ASCII 文本,用于 data URI、JWT token 等。Base64 结果里的
+、/、=在 URL 中还需二次 URL 编码。 -
multipart/form-data 和 application/json 能混用吗? 不能。文件上传必须用 multipart;纯 JSON 接口用
Content-Type: application/json。现代文件上传方案:前端分片后 base64 放 JSON,或直接用 multipart。Spring Boot 用@RequestPart接收 multipart 中的文件和 JSON。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
action="/submit" 部署到子目录 | 请求跳到域名根,404 | 用相对路径 ./submit 或动态获取 contextPath |
| GET 提交密码 | 密码暴露在 URL 和日志 | 敏感数据一律 POST |
| URL 含中文不编码 | 不同浏览器处理不一致 | encodeURIComponent 编码参数值 |
| 文件上传缺 enctype | 后端只收到文件名,收不到内容 | enctype="multipart/form-data" |
用 # 传参给后端 | 片段不发给服务器 | 用 ?key=value 查询参数 |
| 认为 POST 绝对安全 | HTTP 明文传输可被嗅探 | HTTPS + 后端验证 + 不存明文 |
卡点急救
- 部署后静态资源 404:检查路径是绝对
/还是相对./。子目录项目全部用相对路径,或模板引擎注入 base URL。 - GET 请求中文乱码:Spring Boot 默认 UTF-8,但 Tomcat 对 query 参数可能用 ISO-8859-1。后端
new String(param.getBytes("ISO-8859-1"), "UTF-8")转换,或前端encodeURIComponent。 - 文件上传大小超限:Spring Boot 默认限制 1MB。
application.properties加spring.servlet.multipart.max-file-size=10MB和max-request-size=100MB;Nginx 加client_max_body_size 10m。 - MIME 类型不对导致 JS/CSS 不执行:F12 Network 面板看 Response Headers 的
Content-Type。Spring Boot 默认按扩展名推断,自定义文件类型需注册ContentNegotiationConfigurer或手动设response.setContentType()。
面试常问
-
URL 由哪些部分组成? 协议(scheme)、主机(host)、端口(port)、路径(path)、查询参数(query)、片段(fragment)。协议+主机+端口=同源;路径和查询变化是跨资源请求;片段不发送到服务器。
-
GET 和 POST 的本质区别? ① 语义:GET 获取资源(幂等、安全),POST 提交数据(非幂等);② 参数位置:GET 在 URL,POST 在 body;③ 缓存:GET 可缓存,POST 不缓存;④ 书签:GET URL 可收藏,POST 不能。不是「GET 传小数据 POST 传大数据」这种表面区别。
-
什么是 MIME 类型? Multipurpose Internet Mail Extensions,互联网上标识文件类型的标准。格式
type/subtype,如text/html、image/png。浏览器根据 MIME 类型决定渲染方式,服务器根据 MIME 决定处理逻辑。HTTP 头Content-Type和 HTML 的accept属性都依赖它。 -
为什么文件上传要 multipart/form-data? 默认
application/x-www-form-urlencoded对二进制文件进行 URL 编码会导致文件膨胀、损坏。multipart 用 boundary 分隔每个字段,文件保持原始二进制,普通文本保持可读,是上传混合内容的唯一可行方案。
自测三问
- 写出从
https://example.com/app/page.html到https://example.com/images/logo.png的相对路径。 - 为什么 GET 不适合提交密码?
- 文件上传时 enctype 三个值的区别? multipart/form-data 为什么必须?
小结一句话
Web 基础是 HTML 的传送带:URL 定地址,HTTP 定方式,MIME 定格式,编码定规则——缺少任何一个环节,页面就到不了用户手里。
延伸
- MDN:HTTP 概述
- MDN:MIME 类型
- 下一课:可访问性——语义化、alt 文本、键盘导航、ARIA 与屏幕阅读器。