http中文名为超文本传输协议,所谓“超文本”就是指传输范围超出了能在UTF8等码表上找到的字符的范围,包含一些图片,特殊格式之类的。
HTTP的发展简介
从图中可以看出到现在已经发展出了HTTP3,但是市面上的主流还是以HTTP1.0为主。
HTTP2.0和3.0都引入了很多新的特性,比如:提高传输效率,提高传输的安全性。而且在HTTP3.0以前,HTTP都是基于TCP实现的,而HTTP3.0则是基于UDP实现的。
HTTP的应用场景
HTTP协议主要应用于网站,如浏览器和服务器之间的数据传输,客户端(手机,PC)和服务器之间的数据传输。
HTTP协议的交互过程是什么典型的“一问一答”类型。
HTTP报文格式简介
要想了解HTTP的报文格式,就要借助抓包软件,这里使用fiddle抓包软件,在使用fiddle之前,我们要将其他代理软件也给关闭(例如翻墙软件),因为fiddle也是一个代理软件,代理软件之间会冲突,而且我们还要将HTTP协议的选项进行勾选,并且安装安全证书(会自动弹出)
在抓取的包中选取一个蓝色的(表示返回的是一个html,往往是返回一个网站的入口请求)。
右上角的请求明细
右下角的响应明细
在请求中我们选择“raw”显示请求的原始数据,然后选择“view in notepad”选择在记事本中打开即可。
http请求协议分为四个部分
1.首行
GET https://interface.bilibili.com/serverdate.js HTTP/1.1
其中首行又分为三个部分:1.“GET”:方法(method)。后面会仔细介绍。
2.“https://interface.bilibili.com/serverdate.js”:url(统一资源定位符)。使用空格分隔。
3.HTTP/1.1:版本号。
2.请求头(header)
从第二行一直到空行都是请求头。
3.空行
请求头下面会有一行空行,这个空行表示结束标记。
4.正文(body)
http的载荷部分,有的http请求有body,有的就没有(例如示例)。
http响应的基本格式
也是分为四个部分
1.首行
HTTP/1.1 200 OK
这里也是包含三个部分,三个部分之间使用空格进行分割。
1.HTTP/1.1:版本号。
2.200:状态码。
3.OK:状态码描述。
2.响应头 键值对
Date: Sun, 02 Mar 2025 13:08:24 GMT
Content-Type: text/html; charset=utf-8
Connection: keep-alive
york-server-zone: sh001
X-Cache-Webcdn: BYPASS from blzone03
Content-Length: 844
也就是从第二行开始,到空行截至。
3.空行
和请求报文相同,也是起到分割作用。
4.响应报文(body)载荷
<!--
BUILT_AT: 2024年12月12日 11:27
-->
<!doctype html><html lang="zh-CN"><head>
<!-- Dejavu Release Version 70436-->
<script>
window._BiliGreyResult = {
method: "direct",
versionId: "70436",
}
</script><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="renderer" content="webkit"/><title>header sync</title><meta name="theme-color" content="#4393E2"/><link rel="icon" href="//www.bilibili.com/favicon.ico"/><link rel="stylesheet" href="https://s1.hdslb.com/bfs/static/studio/static/cc-iconfont-2.11.0/iconfont.css"/></head><body><div id="app"></div><script src="//s1.hdslb.com/bfs/static/blive/blfe-message-navbar/static/js/vendors~b3ee6c4e.0377a2c3.js"></script><script src="//s1.hdslb.com/bfs/static/blive/blfe-message-navbar/static/js/header_sync.0377a2c3.js"></script></body></html>
响应的载荷是html,包含许多信息。
认识URL(统一资源定位符)
一个十分重要的概念,并不是只出现在HTTP协议中,JDBC中也会用到。
还有一个与url类似的概念:uri(唯一资源标识符),严格来说,uri比url的范围更加广一些。
例如: https://www.bilibili.com/ 就是一个最简单的url,其中 https:// 表示协议的名称,www.bilibili.com/ 则表示域名。
https://www.bilibili.com/video/BV1bKA8ewEQF/?spm_id_from=333.1007.tianma.1-3-3
也表示一个url(为了不让它成为一个链接,删除了部分信息),其中https://www.bilibili.com/video表示路径(带有层次结构),
而/BV1bKA8ewEQF/?spm_id_from=333.1007.tianma.1-3-3 表示查询字符串(query string),这里只有编写它的程序员才能看懂是什么意思。但是我们可以知道查询字符串使用键值对的形式,其中使用“&”来分割键值对,使用“=” 来分割键和值。
一个url的完整结构
1.关于登录信息:图中是不安全的写法,现在都是通过“单独的登录页面”来完成身份验证的。
2.服务器地址/域名:也可以是ip地址,后面带有端口号,表示你要访问服务器的哪一个端口。http默认端口为80,https为443。
3.带层次的文件路径:这里可能对于一个真实的硬盘文件,也可能对应一个虚拟的文件。
4.查询字符串:针对请求的内容做的补充说明。
5.片段标识符:用来表示当前页面的某个部分。通过不同的片段标识符可以完成页面内的跳转。
总结:访问网络上的资源:
1)通过ip地址知道服务器在哪。
2)通过端口号知道程序是哪个。
3)通过路径知道是访问哪个资源。
关于URL encode
query string里是自定应的键值对,在url中,本身有些特殊符号是有特殊含义的,例如:/,&,:,@ .......
如果url中的query string中也包含这些特殊符号怎么办?如果直接写进去,可能会使浏览器/服务器解析失败,靠谱的方法就是对上述符号进行转义。对于汉字也要进行转义。因为如果某个汉字的UTF8/gbk等编码值其中某个字节的编码中和某个符号的ascii值相同就麻烦了。
例如:GET https://ntp.msn.cn/edge/ntp?locale=zh-CN&title=%E6%96%B0%E5%BB%BA%E6%A0%87%E7%AD%BE%E9%A1%B5&dsp=1&sp=%E5%BF%85%E5%BA%94&prerender=1&PC=CNNDDB HTTP/1.1中就使用了大量的转义字符,例如:%E6%96%B0%E5%BB%BA%E6等。
在实际开发中,当要构造一个url时,当query string中包含中文或特殊符号时,一定要进行编码。
HTTP报文首行(方法,URL,版本号)
首行中的方法描述了这次请求是想干什么的。
HTTP提供一个标准文档(虽然是一厢情愿,很多程序员并不遵守)
最主要的方法是GET和POST,其他的方法使用的很少。
两个使用POST的典型场景:
1)登录。
2)上传。
一个实例:
body:{"username":"19308046376","password":"jJGHP1vwDm+ieEYaHl2Omg==","uuid":"456493d5f0fc4316b747208d88e6d7a0","status":0}
这里body使用的是json格式,密码则是使用base64进行了编码,base64特征是末尾带有“==”。base64有标准版(透明的,未进行加密),自定义版(自己约定编码规则,这时就可以作为一种加密手段)。
经典面试题:GET和POST有什么区别:
首先总的来说,GET和POST没有本质上的区别,使用GET的场景也可以换成POST,使用POST的场景也可以换成GET。这取决于代码是咋写的,虽然在部分浏览器,部分服务器的某些特定情况下二者不可以替换,但是大部分情况下都是可以互换的。
但是GET和POST在使用习惯上还是有区别的:
1.GET习惯于把数据放到url的query string中,POST习惯于把数据放到body中。
2.语义上的区别。在标准文档中,GET是来获取数据的,POST是给服务器传输数据的。
3.关于幂等性(数学用语,每次输入情况一定,输出情况也一定,称为幂等;每次输入情况一定,输出情况不一定,不是幂等)。标准文档中,建议GET请求实现成幂等性,POST则无要求。
4.GET请求是可以被浏览器收藏夹收藏的,POST请求则不可以。
关于GET和POST某些不太准确的说法
1.POST比GET更加安全。论据:登录时,如果使用GET,用户名密码就会显示在url上,此时就会被人看到,所以不安全。
即使是POST,数据没有显示在url上,黑客也是可以通过抓包来获取到的,真正保证安全的关键在于加密,如果加密等级足够高,即使看见了也没什么关系。
2.GET传输的数据量小(存在上限),POST传输的数据量更大。
这种说法适用于以前,实际上HTTP的标准文档上说了,对于GET URL的长度不做限制,之前老版本的IE浏览器在实现时,URL的长度有限制,而目前则无限制了。
3.GET只能携带文本数据,POST则可以携带二进制数据。
这个说法并不是完全错误,只是有一定局限性。
URL通过query string来携带数据,query string是只能包含文本的,但是可以对二进制数据进行 urldecode,自然就成了文本了,到了服务器那边再进行 urldecode,就可以还原为二进制数据。
POST请求body中是可以携带二进制数据的,但是很多时候是对二进制数据进行urlencode/base64等方式转码。
请求头
1.Host
表示服务器主机的地址和端口,URL中已经有Host了,二者在绝大部分情况下是一样的,少数情况下可能不同。
2.Content-Length
表示body中的数据长度。通过这个长度来明确包之间的界限,处理粘包问题,因为HTTP也是基于TCP实现的,也会存在粘包问题。
3.Content-Type
表示请求的body中的数据格式。body中可以传输很多种格式的,包括程序员也可以自己约定任意的格式,但是有些格式是很常见的:
请求中
1)applicaton/json
2)application/x-www-urlencoded(称为form表单,通过html中的form标签构造出来的一种格式,特点是认为把query string放入到body中了。
响应中
1)text/plain(纯文本)
2)text/html(html)
3)text/css(css)
4)application/javascript(js)
5)application/json
6)image/png
7)image/jpg
。。。。。。。
在浏览器和服务器交互过程中,有时为了提高效率,会将图片等一些固定不变的内容在浏览器本地的机器上的硬盘进行缓存,后续再进行请求时,就减小了网络开销。
2和3和body有关,数据包中如果没有body,也就不会有这两个字段。
4.User-Agent(简称UA)
例如
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36 Edg/133.0.0.0
是一行UA,其中
Mozilla/5.0 (Windows NT 10.0; Win64; x64):操作系统信息。
AppleWebKit/537.36:说明浏览器使用的是 AppleWebKit 渲染引擎,版本号是 537.36。
Chrome/133.0.0.0 Safari/537.36 Edg/133.0.0.0:浏览器信息。
UA应用场景:
1)同时兼容老设备和新设备,根据系统和浏览器信息返回不同信息。
2)很多手机浏览器可以通过手动修改UA来转换为电脑页面。
5.Referer
例如:Referer: https://www.bilibili.com/video/BV1waXSYSEZ8/?spm_id_from=333.1007.tianma.1-1-1.click&vd_source=504824f6b9c030a5560def77dd4cedf1
描述了当前这个页面是从哪里来的。比如上述就是从哔哩哔哩页面来的。
Referer主要应用于搜索广告中统计点击数来算钱。
但是Referer可以被篡改,在14年左右,三大运营商经常会篡改Referer为通过自己的广告页面跳转的来获利,具体方法就是通过在自己提供的通信设备(路由器,交换机等)上部署程序来修改,这个时间产生了很大影响,被称为“运营商劫持”现象。
这一事件也促使了各大网站使用HTTPS来传输数据。因为HTTPS中的SSL加密可以将header(首行)和body都进行加密,网络上传输的都是密文了,而且如果运营商想要破解的话会被用户的浏览器感知。就避免了运营商劫持现象。
6.Cookie
报头属性中非常重要的部分。
Cookie本质上是一个浏览器这边本地持久化存储数据的机制。
浏览器作为电脑上的一个程序,可以通过调用系统提供的API来读写本地磁盘文件。理论上网页也可以,但是为了安全性,浏览器禁止了网页调用API来读写本地磁盘文件。
但是一些网站需要将一信息保存到浏览器这边,例如登录信息等,所以浏览器退而求其次,提供给了网站可以有限度地存储数据(按照键值对的方式,而且是程序员自定应的)的API,不允许网站随意访问文件系统。
例如:Cookie,LocalStorage,IndexDB等,其中LocalStorage和IndexDB较新一些,但是目前Cookie仍然是主流。
HTTP请求中的Cookie字段,就是把本地存储的Cookie信息发送到服务器这边,HTTP响应中会有一个Set-Cookie字段,就是服务器告诉浏览器你要在本地保存什么信息。
关于Cookie的一些重要结论:
1.Cookie从哪里来?
服务器返回给浏览器的,通常是首次访问/登录成功后。
2.Cookie到哪里去?
Cookie会存储到浏览器本地主机(访问浏览器的主机)的硬盘上,后续每次访问服务器都会带上Cookie。不同的客户端,保存的Cookie也是不同的,即使是同一个主机,使用不同的浏览器,Cookie也大概率会不同。
3.Cookie中存什么?
键值对格式的数据,这里的内容都是程序员自定义的,和query string一样外人无法理解。
4.Cookie在浏览器这边如何组织?
在本地硬盘中保存,是按照域名的不同为维度分别存储,例如,访问百度有一组Cookie,访问谷歌,则是另一组Cookie。
5.Cookie的用途是什么?
用来在客户端保存数据,其中最主要的是保存用户的身份标识,服务器可以通过身份标识来区分用户,其他的信息,如账号密码则不会保存在Cookie中,而是浏览器的另一个保存机制。
状态码
用于响应中的,标识响应的结果如何,当我们出现问题时,原因有很多种,就可以使用不同的数字来区分原因,就可以对症下药了。
上述状态码不需要全部记忆,只需要记住几个重要的即可。
1)200 OK:代表一切顺利。
2)404 Not Found:访问的资源没有找到。
3)403 Forbidden:请求的资源没有访问权限。
4)405 Method Not Allowed:服务器只支持GET请求,但是你发了个POST。
5)500 Internal Server Error:服务器内部错误(服务器挂了),自己写服务器时经常遇到。
6)504 Gateway Timeout:访问服务器超时,可能是服务器挂了,也可能是网挂了。
7)302 Move temporarily:重定向(临时重定向),因为有些域名会更换,但是许多老用户会使用老域名来访问,为了使老用户也可以访问,就会把访问老域名的请求重定向到新域名上。分为301 永久重定向,302 临时重定向。如果是永久重定向,浏览器就会把重定向的结果记录下来,后续再次访问,就直接访问重定向的目标地址即可。
在重定向的响应报文中,会有一个特殊的header叫做Location,就描述了要重定向的目标地址在哪里。
如何构造HTTP请求
1.通过代码构造
任何一个语言,只要可以进行网络编程,都可以构造HTTP请求,对于JAVA来说,需要使用ServerSocket/Socket(TCP中的socket API)来编程。
public class HttpClient {
private Socket socket;
private String ip;
private int port;
public HttpClient(String ip, int port) throws IOException {
this.ip = ip;
this.port = port;
socket = new Socket(ip, port);
}
public String get(String url) throws IOException {
StringBuilder request = new StringBuilder();
// 构造首行
request.append("GET " + url + " HTTP/1.1\n");
// 构造 header
request.append("Host: " + ip + ":" + port + "\n");
// 构造 空行
request.append("\n");
// 发送数据
OutputStream outputStream = socket.getOutputStream();
outputStream.write(request.toString().getBytes());
// 读取响应数据
InputStream inputStream = socket.getInputStream();
byte[] buffer = new byte[1024 * 1024];
int n = inputStream.read(buffer);
return new String(buffer, 0, n, "utf-8");
}
public String post(String url, String body) throws IOException {
StringBuilder request = new StringBuilder();
// 构造首行
request.append("POST " + url + " HTTP/1.1\n");
// 构造 header
request.append("Host: " + ip + ":" + port + "\n");
比特就业课
request.append("Content-Length: " + body.getBytes().length + "\n");
request.append("Content-Type: text/plain\n");
// 构造 空行
request.append("\n");
// 构造 body
request.append(body);
// 发送数据
OutputStream outputStream = socket.getOutputStream();
outputStream.write(request.toString().getBytes());
// 读取响应数据
InputStream inputStream = socket.getInputStream();
byte[] buffer = new byte[1024 * 1024];
int n = inputStream.read(buffer);
return new String(buffer, 0, n, "utf-8");
}
public static void main(String[] args) throws IOException {
HttpClient httpClient = new HttpClient("42.192.83.143", 8080);
String getResp = httpClient.get("/AjaxMockServer/info");
System.out.println(getResp);
String postResp = httpClient.post("/AjaxMockServer/info", "this is
body");
System.out.println(postResp);
}
}
2.通过第三方软件构造
这里使用的是postman来构造HTTP请求。
通过设置HTTP数据报的各个内容即可send。
也可以通过form表单和ajax来构造,但是需要一定的前端基础。