目录
关于HTTP
HTTP (全称为 "超文本传输协议") 是一种应用非常广泛的 应用层协议.
理解HTTP协议工作过程
网络通信中,主要涉及几个核心概念
1.服务器vs客户端
2.请求vs响应
服务器和客户端之间存在多种模型
1.一发一收(web中最常见)
http协议就是这种传输模型
2.多发一收(多个请求对应一个响应)
比如上传大文件
3.一发多收
比如看直播
4.多发多收
比如串流
HTTP协议格式
使用抓包工具Fiddler
抓包工具的原理
对于抓包工具Fiddler来说,相当于是一个代理
传输数据过程中,服务器会以为他是客户端,客户端会以为他是服务器,进行HTTP通信的详细信息,都会被fiddler获取到。
对于客户端来说,是正向代理
HTTP协议格式
(1)HTTP请求
1.首行 first line
2.首部(请求头) header
header就是一堆键值对,键值对和键值对之间用换行分割,键和值之间用冒号加空格来分割
3.空行
通过空行表示header部分结束了
4.正文body
有的请求有正文,有的没有
(2)HTTP响应
1.首行 first line
2.首部(协议头) header
也是一堆键值对,键值对和键值对之间用换行分割,键和值之间用冒号加空格来分割
3.空行
通过空行表示header部分结束了
4.正文body
有的请求有正文,有的没有
HTTP请求
认识URL
URL基本格式
URL中所有部分都可以省略
1.协议方案名
有HTTP,HTTPS,FTP
2.登录信息
现在基本见不到了,现在的登录都是登陆页面
3.服务器地址
域名(本质是ip地址),也可以是ip地址
4.服务器端口号
地址后的冒号+数字表示要访问的服务器的端口,如果不写就是默认
HTTP:80
HTTPS:443
5.带层次的文件路径
表示访问服务器上的具体的哪个的资源
6.查询字符串
URL中的参数,也叫查询字符串(query string)
这些参数以键值对来组织,键值对之间使用&分割,键和值之间使用=分割
整体使用?来作为起始标志
这些键值对表达的意思是程序员自己约定的
7.片段标识符
不常见,表示定位HTML页面的一个具体位置,常见于文档类网站
关于URLencode
URL中用户自定义的query string中,可能存在一些特殊符号
: / ? = & .
这些需要进行转义,就是把特殊字符里面的值,按照十六进制来表示,每个字节在前面放一个%
如 ? 转义为 %3F
认识方法(method)
GET方法
常用于获取服务器上的某个资源.
在浏览器中直接输入 URL, 此时浏览器就会发送出一个 GET 请求.
另外, HTML 中的 link, img, script 等标签, 也会触发 GET 请求
GET请求的特点
1.首行的第一部分为 GET
2.URL的query string一般不为空
3.header 部分有若干个键值对结构.
4.body 部分通常为空.
POST方法
多用于提交用户输入的数据给服务器(例如登陆页面).
通过 HTML 中的 form 标签可以构造 POST 请求, 或者使用 JavaScript 的 ajax 也可以构造 POST 请求.
POST请求的特点
1.首行的第一部分为 POST
2.URL的query string一般为空
3.header 部分有若干个键值对结构.
4.body 部分通常不为空.
GET和POST的区别?
首先没有本质区别
1.数据位置
GET把自定义数据放到query string,POST把自定义数据到body
2.语义区别
GET一般用于获取数据,POST一般用于提交数据
3.幂等性
GET请求一般会设计成幂等(某个请求,执行一次和执行多次,没啥区别,取决于程序员的设计),POST一般不要求设计成幂等
4.可缓存
GET请求一般会被缓存,POST请求一般不能被缓存
GET的长度限制是多少?
RFC标准中没有描述URL的长度,也没有描述body的长度,更何况GET中可以有body,POST也可以有URL
其他方法
PUT,一般用于更新
DELETE,删除服务器特定资源
OPTIONS,返回服务器所支持的请求方法
HEAD,类似于GET,不过只返回响应头,不返回响应体
TRACE,主要用于测试
CONNECT,让服务器代理用户进行访问
认识请求报头(header)
Host
描述了主机的地址/端口号
Content-Length
表示body的长度,单位是字节
Content-Type
表示body中数据格式的类型
User-Agent
描述了浏览器+系统的版本信息
Referer
表示这个页面是从哪个页面跳转过来的
Cookie
Cookie的值是一个字符串,相当于浏览器的本地存储的一种机制
一般用来保存用户的身份信息
认识请求正文
正文中的内容格式和 header 中的 Content-Type 密切相关.
1.application/x-www-form-urlencoded
图片是一个二进制的数据,HTTP协议是文本协议,不能直接传输二进制数据。
此处看到的图片数据,是针对图片内容进行了base64编码(把二进制数据转换成文本数据)
2.multipart/form-data
主要是用来上传文件
3.application/json
json是一种非常常用的数据组织的格式
比如登录
HTTP响应
状态码
200 OK
表示访问成功
404 Not Found
每次请求带有一个URL,URL里面有路径,表示这次请求想要的资源是啥
如果请求的资源服务器没有,那就404
403 Forbidden
访问被拒绝,一般是因为没有权限,比如密码错误
405 Method Not Allowed
HTTP 所支持的方法, 有 GET, POST, PUT, DELETE 等.
但是对方的服务器不一定都支持所有的方法(或者不允许用户使用一些其他的方法).
500 Internal Server Error
表示服务器挂了,一般都是服务器代码执行时崩溃
504 Gateway Timeout
当服务器负载比较大的时候, 服务器处理单条请求的时候消耗的时间就会很长, 就可能会导致出现超时的情况
302 Move temporarily
先来理解重定向
类似于“呼叫转移”,在登陆页面中经常会见到 302. 用于实现登陆成功后自动跳转到主页.
301 Moved Permanently
永久重定向. 当浏览器收到这种响应时, 后续的请求都会被自动改成新的地址
状态码小结
响应报头
响应报头的基本格式和请求报头的格式基本一致.
Content-Type
text/html : body 数据格式是 HTML
text/css : body 数据格式是 CSS
application/javascript : body 数据格式是 JavaScript
application/json : body 数据格式是 JSON
响应正文
正文的具体格式取决于 Content-Type. 观察上面几个抓包结果中的响应部分.
1.text/html
2.text/css
3.application/javascript
4.application/json
构造HTTP请求
如何构造HTTP请求
1.直接在浏览器输入URL(GET)
2.使用form表单(GET和POST)
3.使用ajax(各种请求)
4.socket构造
通过form表单构造HTTP请求
form发送GET请求
<form action="http://abcdef.com/myPath" method="GET">
<input type="text" name="userId">
<input type="text" name="classId">
<input type="submit" value="提交">
</form>
form发送POST请求
<form action="http://abcdef.com/myPath" method="GET">
<input type="text" name="userId">
<input type="text" name="classId">
<input type="submit" value="提交">
</form>
通过ajax构造HTTP请求
当浏览器在发送请求的过程中,同时也是在进行其他工作的异步,当响应回来了之后,浏览器才处理刚才请求的响应
发送GET或者POST请求
// 1. 创建 XMLHttpRequest 对象
let httpRequest = new XMLHttpRequest();
// 2. 默认异步处理响应. 需要挂在处理响应的回调函数.
httpRequest.onreadystatechange = function () {
// readState 表示当前的状态.
// 0: 请求未初始化
// 1: 服务器连接已建立
// 2: 请求已接收
// 3: 请求处理中
// 4: 请求已完成,且响应已就绪
if (httpRequest.readyState == 4) {
// status 属性获取 HTTP 响应状态码
console.log(httpRequest.status);
// responseText 属性获取 HTTP 响应 body
console.log(httpRequest.responseText);
}
}
// 3. 调用 open 方法设置要访问的 url
httpRequest.open('GET', 'http://42.192.83.143:8080/AjaxMockServer/info');
// 4. 调用 send 方法发送 http 请求
httpRequest.send();
这个方法实在是太麻烦了,所以我们建议使用封装ajax方法
封装ajax方法
https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script>
$.ajax({
type: 'GET',
url: 'http://42.192.83.143:8089/AjaxMockServer/info',
success: function(data,status){
console.log(status);
console.log(data);
}
});
</script>
</body>
</html>
效果
ajax中的跨域问题
ajax为了保证安全性,要求发起ajax请求的页面,和接受ajax请求的服务器,应该是在同一个域名下
如果请求发起的页面和接收请求的服务器不在同一域名,就认为是一次跨域请求
也就是说ajax默认是不允许跨域访问的,在响应中,要加上这几个header,才可以允许请求跨域
// 配置跨域
private void setAccess(HttpServletResponse resp) {
resp.setHeader("Access-Control-Allow-Origin", "*");
resp.setHeader("Access-Control-Allow-Headers", "*");
resp.setHeader("Access-Control-Allow-Methods", "*");
}
通过Java socket构造HTTP请求
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
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");
// GET请求不需要body,就构造完毕了
// 发送数据
OutputStream outputStream = socket.getOutputStream();
// outputStream 是一个字节流,以字节为单位进行写入,因此需要把 StringBuilder 转换成 byte[]
outputStream.write(request.toString().getBytes());
// 读取响应数据
InputStream inputStream = socket.getInputStream();
// 创建一个 1M 大小的缓冲区,用来存放响应数据
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", 8089);
String getResp = httpClient.get("/AjaxMockServer/info");
System.out.println(getResp);
String postResp = httpClient.post("/AjaxMockServer/info", "this is body");
System.out.println(postResp);
}
}
效果
HTTPS
HTTPS是什么
HTTP是一个明文传输协议,一旦传输过程中,数据被第三方获取到了,可能会造成一些重要信息的泄露
HTTPS就是在HTTP的基础上,引入了一个加密层(SSL/TLS)
加密是什么
明文:真正要传输的消息
密文:指的就是加密之后的消息
明文=>密文 称为加密
密文=>明文 称为解密
HTTPS工作过程
引入对称加密
加密:明文=>密文 使用同一个密钥
解密:密文=>明文 使用同一个密钥
客户端和服务器需要先约定好密钥是啥,但是密钥如果明文传输,也容易被黑客获取,因此密钥的传输也必须加密传输,这就行不通了,所以引入了非对称加密
引入非对称加密
加密:明文=>密文 使用密钥1
解密:密文=>明文 使用密钥2
通常会把其中一个密钥公开出去,别人就可以使用这个密钥加密,自己再用另一个密钥进行解密。
公开出去的密钥成为公钥
自己保留的密钥成为私钥
对称加密,成本是比较低(机器消耗资源小),速度也是很快的
非对称加密,成本比对称加密高很多(机器消耗资源多),速度也慢
引入证书
但是,如何保证客户端获取到的公钥是真实的,而不是黑客伪造的?
接下来客户端拿着pub2进行加密
所以要引入证书
客户端与服务器连接的时候,客户端直接索要一个证书,公钥就包含在这个证书里面,这个证书不是服务器自己生成的,是第三方机构颁发的,客户端拿到证书后,就可以根据证书中提供的信息(类似于营业执照上的编号),和第三方机构认证(类似于在工商局查营业执照是否合法),来校验证书是否合法,如果合法,就可以信任其中的公钥
完整流程
1.客户端先从服务器获取到证书,证书中包含了公钥
2.客户端对证书进行校验
3.客户端生成了一个对称密钥,使用公钥对对称密钥进行加密,发送给服务器
4.服务器得到这个请求之后,使用私钥解密,得到对称密钥
5.客户端发出后续的请求,后续的请求都是使用这个对称密钥加密的
6.收到的数据也都是使用这个对称密钥解密的
这个过程其实描述的就是SSL/TLS的握手过程
HTTPS和HTTP的区别
1.HTTP协议以明文的方式发送内容,数据都是未加密的,安全性较差,HTTPS数据传输过程是加密的,安全性较好
2.HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是80端口,后者是443端口
3.HTTPS协议需要申请证书,要花钱的
4.HTTP页面响应比HTTPS快,主要因为HTTPS还要经历一个SSL协商的过程