Web 开发中必须要了解的 HTTP 相关知识
本文主要记录与HTTP相关的具体概念和知识,关于 HTTP 协议的诞生和历史发展,不多做介绍,自己但是既然是写HTTP,顺带说两句,上下文也能衔接的上。
CERN(欧洲核子研究组织)的蒂姆 • 伯纳斯 - 李(Tim BernersLee)博士提出了一种能让远隔两地的研究者们共享知识的设想,于是 HTTP 慢慢的诞生了。
另外,HTTP 协议是无状态可以,于是为了保存用户的状态,cookie 诞生了。
HTTP 协议是建立在 TCP 连接之上的,当浏览器输入 URL 进行访问,浏览器冲 URL 中解析出主机名和端口,浏览器建立一条与 Web 服务器的连接,然后才进行 http 请求。
TCP连接的建立与终止
建立TCP连接(三次握手)
在客户端与服务端进行 http 通信之前,需要建立 TCP 连接,这时需要三次握手
- 请求新的 TCP 连接,客户端发送一个小的 TCP 分组,这个分组设置一个特殊的SYN标记,表明是一个客户端请求。
- 如果服务器接受这个连接,就会对一些连接参数进行计算,并向客户端回送一个 TCP 分组,发送 SY 和 ACK 标记,表明连接请求已经被接受
- 最后,客户端向服务器回送一条确认消息,通知服务器连接已经建立。
断开 TCP 连接(四次断开)
建立一个连接需要三次握手,而终止一个连接要经过4次握手。这由TCP的半关闭(half-close)造成的。既然一个TCP连接是全双工(即数据在两个方向上能同时传递),因此每个方向必须单独地进行关闭。这原则就是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向连接。当一端收到一个FIN,它必须通知应用层另一端几经终止了那个方向的数据传送。发送FIN通常是应用层进行关闭的结果。
- 客户端发送 FIN 标记到服务器,表明客户端发起关闭连接
- 服务器接收客户端的 FIN 标记并,向客户端发送 FIN 的 ACK 确认标记
- 服务器发送FIN到客户端,服务器关闭连接
- 服务器端发送一个 FIN 的 ACK 确认标记,确认连接关闭
建立持久连接的请求和响应交互:
使用 wireshark 进行数据抓包:
这里向大家推荐一款抓包软件 Wireshark,可以用来分析 TCP 连接的建立和断开过程,以及抓取 HTTP 请求和相应的信息等,下面是我进行一次客户端和服务端通信的抓包数据截图:
HTTP 报文
HTTP 协议报文是应用程序之间发送的数据块,也就是客户端和服务端用于交互的信息。客户端的报文叫做请求报文,服务器端的报文叫做响应报文。
HTTP 报文组成
HTTP报文由起始行、首部和实体的主体(也称报文主体或主体)组成。起始行和首部以一个回车符和换行符作为结束,主体部分可以是二进制数据,也可以为空。
1. 起始行
请求报文起始行:
请求报文起始行说明了要做什么,由请求方法 、请求 URI 和协议版本构成。
GET /index.html HTTP/1.1
响应报文起始行:
响应报文的起始行,由协议版本、状态码和原因短语构成。
HTTP/1.1 200 OK // OK就是原因短语
2. 首部
首部字段分类
1. 通用首部
客户端和服务端都可以使用的首部
通用首部字段表:
2. 请求首部
请求报文特有的首部,为服务器提供了一些额外的信息,补充了请求的附加内容、客户端信息、响应内容相关的优先级等信息。
3. 响应首部
响应报文特有的字段
响应首部字段表:
4. 实体首部
用于针对请求报文和响应报文主体部分使用的首部
5. 扩展首部
扩展首部是非标准的首部,由应用程序开发者创建,但还未添加到已批准的HTTP标准中去。
HTTP 状态码
状态码的职责是当客户端向服务器端发送请求时,描述返回的请求结果。借助状态码,用户可以知道服务器端是正常处理了请求,还是出现了错误。
状态码分类:
状态码区间 | 类别 |
---|---|
100~199 | 信息性状态码 |
200~299 | 成功状态码 |
300~399 | 重定向状态码 |
400~499 | 客户端错误状态码 |
500~599 | 服务器错误状态码 |
常用状态码列表:
状态码 | 原因短语 | 含义 |
---|---|---|
200 | OK | 表示从客户端发来的请求在服务器端被正常处理了 |
204 | No Content | 该状态码代表服务器接收的请求已成功处理,但在返回的响应报文中不含实体的主体部分。另外,也不允许返回任何实体的主体。 |
301 | Moved Permanently | 永久重定向,该状态码表示请求的资源已被分配了新的 URI,以后应使用资源现在所指的 URI |
302 | Found | 临时性重定向,该状态码表示请求的资源已被分配了新的 URI,希望用户(本次)能使用新的 URI 访问 |
303 | See Other | 303 状态码和 302 Found 状态码有着相同的功能,但 303 状态码明确表示客户端应当采用 GET 方法获取资源,这点与 302 状态码有区别 |
304 | Not Modified | 缓存 |
307 | Temporary Redirect | 临时重定向,和302一样 |
400 | Bad Request | 该状态码表示请求报文中存在语法错误。当错误发生时,需修改请求的内容后再次发送请求。另外,浏览器会像 200 OK 一样对待该状态码 |
401 | Unauthorized | 该状态码表示发送的请求需要有通过 HTTP 认证(BASIC 认证、DIGEST 认证)的认证信息 |
403 | Forbidden | 该状态码表明对请求资源的访问被服务器拒绝了 |
404 | Not Found | 该状态码表明服务器上无法找到请求的资源 |
500 | Internal Server Error | 该状态码表明服务器端在执行请求时发生了错误。也有可能是 Web应用存在的 bug 或某些临时的故障 |
502 | Bad Gateway | 网关错误 |
503 | Service Unavailable | 该状态码表明服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。如果事先得知解除以上状况需要的时间,最好写入RetryAfter 首部字段再返回给客户端 |
HTTP中不同场景下,首部字段的作用
1. CORS 跨域资源共享
跨域资源共享(CORS)是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。
下面使用 nodejs 来搭建一个简单的服务器,来介绍一个跨域问题的解决方法
// index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>CORS</title>
</head>
<body>
Hello World
<script>
fetch('http://127.0.0.1:8081')
</script>
</body>
</html>
// server.js
const http = require('http')
http.createServer(function(req, res) {
res.writeHead('200', {
'Access-Control-Allow-Origin': 'http://localhost:8082'
})
}).listen(8081)
在源地址为 http://localhost:8082 下,请求http://localhost:8081,是跨域请求,浏览器会自动在 request Header 中发送 Origin 首部字段,并把值设置为来自哪个源,本例为 http://localhost:8081。
服务器需要在响应头中设置 Access-Control-Allow-Origin,来告知浏览器可以处理返回的数据。如果响应头中不设置 Access-Control-Allow-Origin 则会报错,但是返回状态码为200,跨域实际上是浏览器本身的一个安全机制。
// server2.js
// 启动8082端口服务,在浏览器中访问http://127.0.0.1:8082,会返回index.html内容
const http = require('http')
const fs = require('fs')
http.createServer(function(req, res) {
var page = fs.readFileSync('index.html', 'utf-8')
res.writeHead(200, {
'Content-Type': 'text/html'
})
res.end(page)
}).listen(8082)
关于CORS跨域请求的分类:
1.简单请求:
需要同时满足以下的条件就是简单请求
1、请求方法:
GET、POST、HEAD
2、请求头不能为以下其他字段之外
Accept
Accept-Language
Content-Language
Content-Type的值必须为application/x-www-form-urlencoded、multipart/form-data、text/plain之一
2.非简单请求:
非简单请求是当请求信息不满足简单请求的条件,浏览器就发送方法为OPTIONS的预请求,包含自己请求的方法及需要使用的请求头字段,在得到服务器响应允许之后,浏览器会按照想要使用的请求方法及头信息再发一次请求。
现在修改以下上面的例子:
// index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>CORS</title>
</head>
<body>
Hello World
<script>
fetch('http://127.0.0.1:8081', {
method: 'PUT',
headers: {
X-Coustom-Head: 'abc'
}
})
</script>
</body>
</html>
// server.js
const http = require('http')
http.createServer(function(req, res) {
res.writeHead('200', {
'Access-Control-Allow-Origin': 'http://localhost:8082'
})
}).listen(8081)
如果服务端不进行相应的设置告诉浏览器允许跨域访问则会报错
但是预请求返回状态码为 200
// server2.js
// 启动8082端口服务,在浏览器中访问http://127.0.0.1:8082,会返回index.html内容
const http = require('http')
const fs = require('fs')
http.createServer(function(req, res) {
var page = fs.readFileSync('index.html', 'utf-8')
res.writeHead(200, {
'Content-Type': 'text/html'
})
res.end(page)
}).listen(8082)
现在我们修改以下 server.js
// server.js
const http = require('http')
http.createServer(function(req, res) {
res.writeHead('200', {
'Access-Control-Allow-Origin': 'http://localhost:8082',
'Access-Control-Allow-Headers': 'X-Coustom-Head',
'Access-Control-Allow-Methods': 'PUT'
})
}).listen(8081)
重新启动 node 服务,访问 http://locaohost:8082,可以看到在发送预请求后,浏览器会继续发送 PUT 请求
关于CORS的其他设置这里就不多做介绍了,这里主要是用一个例子来说明以下http不同字段在跨域场景下的作用。
2. 缓存(Cache-Control 的作用)
本例依旧用node服务来讲解一下Cache-Control的作用,新建三个文件
// index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Cache-Control</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<script src="/script.js"></script>
</body>
</html>
// script.js
console.log('script.js')
// server.js
const http = require('http')
const fs = require('fs')
http.createServer(function(req, res) {
if (req.url === '/') {
let page = fs.readFileSync('index2.html', 'utf-8')
res.writeHead(200, {
'Content-Type': 'text/html'
})
res.end(page)
}
if (req.url === '/script.js') {
let page = fs.readFileSync('script.js', 'utf-8')
res.writeHead(200, {
'Content-Type': 'text/javascript',
'Cache-Control': 'max-age=10'
})
res.end(page)
}
}).listen(8082)
在第一次请求 script.js 资源时,向服务器发送请求
由于服务器返回响应时,设置 Cache-Control: 'max-age=10' 时,修改 script.js 后,在 10 秒内继续请求 script.js 资源,则从缓存中读取,而打印信息依旧是 script.js
// script.js
console.log('script-modify.js')
更多关于缓存的知识在这里也不多介绍了,贴两张cache-control字段在请求和响应时可以设置的值和其表示含义:
1. Cache-Control 缓存请求指令:
2. Cache-Control 缓存响应指令:
3. cookie
指某些网站为了辨别用户身份、进行 session 跟踪而储存在用户本地终端上的数据(通常经过加密),当下次再访问时浏览器会将该网站的cookie发回给服务器端。
cookie如果不设置过期时间,随浏览器关闭而失效,如果有需要可以设置过期时间,继续上代码例子🌰,新建两个文件如下
// index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Cookie</title>
</head>
<body>
Cookie
<script>
console.log(document.cookie)
</script>
</body>
</html>
// server.js
const http = require('http')
const fs = require('fs')
http.createServer(function(req, res) {
if (req.url === '/') {
let page = fs.readFileSync('index.html', 'utf-8')
res.writeHead(200, {
'Content-Type': 'text/html',
'Set-Cookie': ['a=1;max-age:5', 'b=2;HTTPOnly']
})
res.end(page)
}
}).listen(8082)
启动 node 服务,访问 localhost:8082,可以看到成功设置了 cookie
并在响应头信息中设置了 Set-Cookie 字段
另外关注以下打印信息,发现只有 a=1,因为给 b=2 设置了 HttpOnly 属性,不允许 JavaScript 通过脚本来获取到 cookie 信息。
由于当再次请求时,cookie 会在请求头中发送到服务器,由于 cookie a=1 设置了 5 秒后过期,在 5 秒后刷新页面,请求头中的 cookie 只有 a=1
在5秒内发送二次请求,cookie a=1 没有失效,在请求头中 cookie a=1;b=2 都会发送到服务器。
另外对于cookie的其他设置如expires、domain等在这里也不多做介绍了
4. 重定向
当服务端返回 301、302、307 等状态码都代表资源已经被重定向到其他位置,301 表示永久改变 URI,302 和 307 表示临时重定向到某个 URI
本例举一个服务器返回 302 状态码的例子,直接上代码:
// server.js
const http = require('http');
const fs = require('fs')
http.createServer((req, res) => {
if (req.url === '/') {
res.writeHead(302, {
'Location': '/redirect'
})
res.end()
}
if (req.url === '/redirect') {
res.end('redirect')
}
}).listen(8082);
访问 localhost:8082,服务器返回 302 状态码时,在相应头中设置 Location 首部字段,浏览器会继续发送请求到重定向的地址
HTTP 与 HTTPS 的区别
首先说一下什么是 HTTPS
HTTPS(全称:Hyper Text Transfer Protocol over Secure Socket Layer 或 Hypertext Transfer Protocol Secure,超文本传输安全协议),是以安全为目标的 HTTP 通道,简单讲是 HTTP 的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。
HTTPS = HTTP+ 加密 + 认证 + 完整性保护
最主要是在应用层和传输层中间加了一个SSL(安全套阶层),通常,HTTP 直接和 TCP 通信。当使用 SSL 时,则演变成先和 SSL 通信,再由 SSL 和 TCP 通信。
HTTP 与 HTTPS 的区别:
- HTTP是明文传输,HTTPS 是经过 SSL 加密后进行传输,只有客户端和服务端根据公钥和私钥进行加密和解密能看到,中间任何传输环节无法获取传输信息,所以 HTTPS 比 HTTP 安全
- HTTPS需要到数字证书认证机构进行购买
- HTTP服务器默认端口是 80,HTTPS服务器默认端口是 443
本文主要介绍 HTTP,关于 HTTPS 主要就介绍这么多吧。
HTTP2
本想说点 HTTP2 的知识,奈何自己是小白,放个百度百科的链接:HTTP2。
等后续随着不断的学习,再回来更新本文。
另外放一个 HTTP1.1 与 HTTP2 请求与相应对比的 demo 的链接 HTTP/2 is the future of the Web, and it is here!
最后,本文主要介绍了一些HTTP在web开发中的基础知识,关于概念和图解流程的截图基本上都是来自《TCP/IP详解 卷1:协议》、《图解HTTP》、《HTTP权威指南》,可放心参考。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论