1. HTTP
1.1 HTTP是什么
HTTP(超文本传输协议)是常用的应用层协议,基于TCP传输协议实现的,平时我们打开一个网站就是通过HTTP协议传输数据的,我们在浏览器输入网址URL(例如baidu.com)时浏览器给服务器发送一个HTTP请求,然后服务器返回HTTP响应。
1.2 理解HTTP协议工作过程
前面说HTTP基于TCP的,如果说TCP是管通信的,那么HTTP是管说什么的。
| 维度 | HTTP | TCP |
|---|---|---|
| 所属层级 | 应用层 | 传输层 |
| 关心什么 | 数据的格式与语义(说什么) | 数据的可靠传输(怎么送) |
| 理解内容吗 | ✅ 完全理解(知道是网页请求) | ❌ 完全不理解(只是一串字节) |
| 典型动作 | 构造请求、解析响应 | 三次握手、重传、排序、流控 |
| 典型标识 | URL、方法、状态码 | IP + 端口、序列号 |
| 类比 | 你写信的内容 | 邮政系统 |
我们访问一个网站时涉及不止一次HTTP响应,检查网站的network模块就会显示每一次HTTP响应的结果
2. HTTP协议格式
2.1 抓包工具使用
一个文本协议格式,可以通过fiddler等抓包工具分析HTTP请求/响应的细节。
进去fiddler点击一个HTTP,右边弹出上下两个窗口(上面请求,下面响应),点击raw查看数据格式,点击右下角可以在记事本里查看。
2.2 抓包工具原理
客户端请求发给fiddler,fiddler再发给服务器,服务器返回数据,数据先到fiddler,再到客户端。
2.3 抓包结果
(1)请求
POST https://self.events.data.microsoft.com/OneCollector/1.0/ HTTP/1.1 Accept: */* APIKey: d019ee4b0f-a02c-84 Client-Id: NO_AUTH Content-Encoding: deflate Content-Type: application/bond-compact-binary Expect: 100-continue SDK-Version: EVT-Windows-C++-No-3.4.276.7 Upload-Time: 1790732952702 Host: self.events.data.microsoft.com Content-Length: 606 Connection: Keep-Alive Cache-Control: no-cache首行:方法+url+版本。
header:请求的属性,冒号分隔的键值对,每组之间用\n(换行符)分隔,到空行结束。
body:空行后面的内容,body可以为空,如果body存在,header里会有一个content-length来表示body的长度。
注意:如果Content-Encoding字段是gzip表示正文经过了 gzip 压缩——无论正文原本是文本还是二进制。压缩通过消除冗余减少字节数,从而省带宽。
(2)响应
HTTP/1.1 403 Forbidden Content-Type: application/json Server: Microsoft-HTTPAPI/2.0 Strict-Transport-Security: max-age=31536000 Collector-Error: All Events Throttled. Access-Control-Allow-Headers: Strict-Transport-Security,Collector-Error,Content-Type,Content-Length Access-Control-Allow-Methods: POST Access-Control-Allow-Credentials: true Access-Control-Allow-Origin: * Access-Control-Expose-Headers: Collector-Error Date: Wed, 30 Sep 2026 01:49:16 GMT Content-Length: 52 {"acc":0,"rej":1,"efi":{"EventLevelThrottling":[0]}}首行:版本号+状态码+状态码解释。
header:请求的属性,冒号分隔的键值对,每组之间用\n(换行符)分隔,到空行结束。
body:空行后面的内容,body可以为空,如果body存在,header里会有一个content-length来表示body的长度,如果返回一个HTML页面,那么HTML页面就是在body里。
2.4 协议格式总结
思考:为什么HTTP报文里存在空行?
报文没有规定键值对的个数,空行就是报头的结束标记,也是与正文分隔符,HTTP基于TCP进行传输的,TCP是面向字节流传输的,如果没有空行就会出现粘包问题。
我们通过抓包可以判断bug在前端还是后端:
- 浏览器发出的请求错了→前端错误
- 请求正确,响应错误→后端错误
- 请求正确,响应正确,显示错误→前端错误
3. HTTP请求
3.1 认识URL
(1)URL基本格式
https://www.example.com:443/path/to/page?key1=value1&key2=value2#section └─┬─┘ └──────┬──────┘ └┬┘ └─────┬─────┘ └──────────┬─────────┘ └───┬───┘ scheme host port path query fragment 协议 主机名 端口 路径 查询参数 片段https:协议名,常见的有http和https
www.example.com:服务器地址,此处是一个域名,需要通过DNS解析成一个IP地址,我们可以通过ping命令来获取IP地址,如:
端口号:有的URL端口号会被省略,省略时浏览器会根据协议类型⾃动决定使用哪个端口,
例如http协议默认80端口,https默认443端口。
/path/to/page:带层次的文件路径。
?key1=value1&key2=value2:查询字符串,本质是键值对,键值对之间使用&分隔,前面的问号代表查询,这些键值对的含义都是程序员自定义的。
片段标识:主要用于页面内跳转,例如看java官方文档,通过不同的片段标识跳转到不同章节。
URL中可省略的部分:协议名、IP地址、端口号、带层次的文件路径、查询字符串、片段标识。
URL通俗解释:
http://万达茶百道:2/奶茶/杨枝甘露?甜度=五分糖&冰量=去冰&加料=珍珠#杨枝甘露| URL 部件 | 店里对应 |
|---|---|
http:// | 点单方式:柜台口头报(明文)or 小程序加密下单(HTTPS) |
万达茶百道 | IP 地址:定位到这家店 |
:2 | 端口号:2 号取餐窗口 |
/奶茶/杨枝甘露 | path:品类 → 杯名,层层深入 |
?甜度=五分糖&冰量=去冰 | query string:同一杯奶茶,参数不同结果不同 |
#杨枝甘露 | 打开电子菜单时直接跳到那款的位置——只在你的手机里生效,店员永远看不到 |
(2)URL encode
例如 / ? : 等这样的字符不能随意出现(如 ?在前面说是URL里代表查询的含义),这些字符需要进行转义。
转义规则:将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前⾯加上%,编码成%XY格式
'+' │ ① 转16进制 ▼ ASCII 0x2B(十进制 43) │ ② 判断:0x2B < 0x80,单字节,"不到4位直接处理"的情况 ▼ 十六进制就是 2B —— 正好 2 位 = 1 字节,无需切分 │ ③ 前面加 % ▼ %2B 两个加号 → %2B%2B3.2 认识方法
| 方法 | 说明 | 支持的HTTP协议版本 |
|---|---|---|
| GET | 获取资源 | 1.0、1.1 |
| POST | 传输实体主体 | 1.0、1.1 |
| PUT | 传输文件 | 1.0、1.1 |
| HEAD | 获得报文首部 | 1.0、1.1 |
| DELETE | 删除文件 | 1.0、1.1 |
| OPTIONS | 询问支持的方法 | 1.1 |
| TRACE | 追踪路径 | 1.1 |
| CONNECT | 要求用隧道协议连接代理 | 1.1 |
| LINK | 建立和资源之间的联系 | 1.0 |
| UNLINE | 断开连接关系 | 1.0 |
(1)get方法
最常用的http方法,用于获取服务器上某个资源,在浏览器里直接输入URL(www.baidu.com),此时浏览器就会发出get请求。
通过fiddler抓包工具可以看到:
get请求特点:
- 首部第一行为GET
- URL的查询条件可以为空,也可以不为空
- header部分有若干个键值对结构
- body部分为空
(2)post方法
多用于用户输入数据提交给服务器,例如用户登录。
通过HTML中的form标签可以构造POST请求,或者使用JavaScript的ajax也可以构造POST请求。
登录boss直聘(输入账号信息之后)抓包:
post请求特点:
- 首行第一部分为POST
- URL的查询条件一般为空(也可以不为空)
- header部分有若干个键值对
- body部分一般不为空,body里的数据格式一般通过content-type指定的,body长度有content-length指定的。
(3)GET和POST的区别
- get一般用于获取数据,post一般用于提交数据。
- get的body通常为空,需要传输的数据通过query传输,post的query通常为空,需要传输的数据装到body里传输。
- get的请求一般是幂等的,post不是幂等的(多次请求得到的结果一样就是幂等),例如查询10次结果都一样,提交10次订单就产生10个订单。
- get可以被缓存,post不能被缓存(承接幂等性,GET 靠地址找货,缓存可放心复用;POST 每次执行都改变世界,缓存复用就是造假)。
(4)其他方法
- PUT(修改)与POST(新增)相似,只是具有幂等特性,⼀般用于更新。
- DELETE删除服务器指定资源。
- OPTIONS返回服务器所支持的请求方法。
- HEAD类似于GET,只不过响应体不返回,只返回响应头。
- TRACE回显服务器端收到的请求,测试的时候会用到这个。
- CONNECT预留,暂无使用。
3.3 认识请求报头(header)
header的整体格式是键值对结构
(1)Host
表示服务器的地址和端口。地址必须写,端口可省略——省略时按协议补默认值(http → 80,https → 443)。
例如访问 https://www.gitee.com,完整形式是www.gitee.com:443,因 443 是默认端口,实际就写成www.gitee.com
Host = 把 URL 的主机部分抄进 HTTP 报文,让一台服务器(一个 IP 挂多个站)能分辨"你要哪个站"(虚拟主机)。HTTP/1.1 起强制要求携带,缺失返回 400。
注意:www只是一个子域名,不是必须的。gitee.com和www.gitee.com都能访问,有些网站必须加www才能访问,是因为它只配了www的DNS记录,没配裸域名的反过来也有网站必须不加 www才能访问。
(2)Content-Length
表示body中的数据长度,前提是存在body
服务器如何区分一个完整的请求:
- 如果没有body就读到空行就行
- 如果有body则必然存在content-length,找到空行,空行之后就是body,从空行后开始按照content-length长度读取字节数即可
(3)Content-Type
表示请求的body中的数据格式,前提是存在body
| 大类 | 代表类型 | 对应文件 | 一句话用途 |
|---|---|---|---|
| text/* | text/html | .html | 网页文档,浏览器渲染成页面 |
| application/* | application/json | .json | 前后端 API 传 JSON(本类还有表单/上传/二进制,见下) |
| image/* | image/jpeg | .jpg / .jpeg | 图片(注意:标准写法是 jpeg,没有 image/jpg) |
| audio/* | audio/mpeg | .mp3 | 音频 |
| video/* | video/mp4 | .mp4 | 视频 |
| multipart/* | multipart/form-data | — | 文件上传,一个 body 分多段 |
| font/* | font/woff2 | .woff2 | 网页字体 |
如果一个请求/响应,虽然有 body 但是没有 Content-Type 或者 Content-Length,就是一个非法的"请求/响应"。
比如响应中如果没有 Content-Type,浏览器会根据 body 中的数据,"猜"一个格式(有较大的概率能猜对)。
如果没有 Content-Length,浏览器也能猜(按照下一个请求一定是 GET/POST 开头)。
(4)User-Agent(简称UA)
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.0.0 Safari/537.36Windows NT 10.0; Win64; x64表示操作系统信息
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.0.0 Safari/537.36表示浏览器信息
(5)Referer
表示当前这个页面从哪个页面跳转过来的。
浏览器的回退和referer不同,回退不依赖http协议,浏览器自身维护一个“栈”保存访问记录。
(6)Cookie
cookie里存储了一个字符串(键值对的形式表现),这个数据可能是通过客户端(网页)自行通过JS写入的,也可能来自于服务器(服务器在响应的header里通过Set-Cookie字段给浏览器返回数据)。
登录操作:
最初没有Cookie,发出登录请求,服务器响应返回Set-Cookie,用户访问网站其他页面就会带着获取的Cookie信息来访问。
登录成功 → 服务器返回 sessionId(Set-Cookie里键值对之一) → 浏览器存 Cookie → 后续请求自动带 Cookie → 服务器认出你,不用重新登录理解登录过程:
- 左:客户端
- 右:服务器
注意:Cookie存浏览器就是存本地
(7)session和Cookie的联系
Cookie是票据,Session是档案柜,数据存在服务器的档案柜里,Cookie 里只放一张写着柜号(sessionId)的票。
① 你登录成功 ② 服务器生成一份 Session(存你的用户信息)放在自己"档案柜"里 ③ 服务器把这份 Session 的编号(sessionId)放进响应头: Set-Cookie: sessionId=abc123; HttpOnly ④ 浏览器把这条 Cookie 存在本地 ⑤ 之后每次请求,浏览器自动带上:Cookie: sessionId=abc123 ⑥ 服务器凭编号回档案柜查你的信息 → 确认"是你",返回你的数据桌面应用app不依赖Cookie,因为数据可以存本地(文件系统)。
3.4 认识请求正文(body)
数据格式:
| Content-Type | 数据格式 | 示例内容 | 说明 |
|---|---|---|---|
| application/json | JSON | {"key": "value"} | 前后端交互最常用的数据格式 |
| text/html | HTML | <div>hello</div> | 网页源码,浏览器渲染成页面 |
| text/css | CSS | * { font-size: 12px; } | 样式表,控制网页外观 |
| application/javascript | JS | let n = 10; | 脚本代码,控制网页行为 |
| image/png | 二进制 | 二进制内容 | 图片,浏览器解析成图像显示 |
登录网站请求:
POST /wapi/zpCommon/toggle/all HTTP/1.1 Host: www.zhipin.com Connection: keep-alive Content-Length: 39 Content-Type: application/x-www-form-urlencoded Accept: application/json, text/javascript, */*; q=0.01 Accept-Encoding: gzip, deflate, br, zstd Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 Origin: https://www.zhipin.com Referer: https://www.zhipin.com/city/ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) ... Edg/154.0.0.0 X-Requested-With: XMLHttpRequest Sec-Fetch-Site: same-origin Sec-Fetch-Mode: cors Sec-Fetch-Dest: empty sec-ch-ua-platform: "Windows" sec-ch-ua-mobile: ?0 system=9E2145704D3D49648DD85D6DDAC1CF0D4. HTTP响应
4.1 认识状态码
(1)200 OK
场景的状态码,表示响应成功
然后响应的body(经过压缩后的数据):
(2)404 Not Found
输入www.sogou.com/index2.html
fiddler:
浏览器:
(3)403 Forbidden
用户访问被拒绝,例如不登录访问Gitee的私有仓库。
(4)405 Method Not Allowed
HTTP中所支持的方法有 GET、POST、PUT、DELETE 等。
但是对方的服务器不一定都支持所有的方法(或者不允许用户使用一些其他的方法)。
(5)500 Internal Server Error
服务器内部错误,服务器代码执行过程遇到特殊情况(如服务器异常崩溃)。
(6)504 Gateway Timeout
服务器负载较大的时候,处理单条数据消耗的时间很长,可能出现超时的情况。
(7)302 Move temporarily
临时重定向。就相当于手机号码中的"呼叫转移"功能。
比如我本来的手机号是 186-1234-5678,后来换了个新号码 135-1234-5678,那么不需要让我的朋友知道新号码,只要我去办理一个呼叫转移业务,其他人拨打 186-1234-5678,就会自动转移到 135-1234-5678 上。
(8)301 Moved Permanently
永久重定向。当浏览器收到这种响应时,后续的请求都会被自动改成新的地址。
(9)状态码小结
| 类别 | 类别说明 | 原因短语 |
|---|---|---|
| 1XX | Informational(信息性状态码) | 接收的请求正在处理 |
| 2XX | Success(成功状态码) | 请求正常处理完毕 |
| 3XX | Redirection(重定向状态码) | 需要进行附加操作以完成请求 |
| 4XX | Client Error(客户端错误状态码) | 服务器无法处理请求 |
| 5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 |
4.2 认识响应报头
Content-Type ,Content-Length等属性和请求报头一个含义。
(1)content-type
常见取值:
- text/html:body 数据格式是 HTML
- text/css:body 数据格式是 CSS
- application/javascript:body 数据格式是 JavaScript
- application/json:body 数据格式是 JSON
4.3 认识响应正文(body)
(1)text/html
(2)text/css
(3)application/javascript
(4)application/json
5. 构造HTTP请求
通过Java socket构造HTTP请求
package network.HTTP; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; public class HttpClient { private Socket socket=null; public HttpClient(String serverIp,int port) throws IOException { socket=new Socket(serverIp,port); } public void get(String path) throws Exception{ //构造Http结构字符串 try(InputStream inputStream=socket.getInputStream(); OutputStream outputStream=socket.getOutputStream()){ String firstLine ="GET "+path+" HTTP/1.1\r\n"; String header="Host:" +socket.getInetAddress().getHostAddress()+":"+socket.getPort()+"\r\n"; String blankLine="\n"; String httpRequest=firstLine+header+blankLine; outputStream.write(httpRequest.getBytes()); outputStream.flush(); byte[] bytes=new byte[1024*1024]; int n=inputStream.read(bytes); String httpResponse=new String(bytes,0,n); System.out.println(httpResponse); }catch (Exception e){ e.printStackTrace(); } return; } public static void main(String[] args) throws Exception{ HttpClient httpClient=new HttpClient("www.baidu.com",80); httpClient.get("/");//获取首页 } }响应结果(返回HTML):