文章目录
Hi,大家好,我是半亩花海。 本文主要分享关于 HTTP 协议的相关知识,包含 HTTP 协议介绍、工作过程、协议格式、请求 (Request) 与响应 (Response) 格式、客户端与服务器的请求响应过程等。
一、 HTTP 协议介绍
定义: HTTP(Hyper Text Transfer Protocol)全称为 超文本传输协议,是用于从万维网(WWW: World Wide Web )服务器传输超文本到本地浏览器的传送协议。
HTTP 是一种应用层协议,是基于 TCP/IP 通信协议来传递数据的,其中 HTTP1.0、HTTP1.1、HTTP2.0 均为 TCP 实现,HTTP3.0 基于 UDP 实现。现主流使用 HTTP1.0 和 HTTP3.0。
特征:
- 基于某个特定的传输控制协议(TCP) / 网际互连协议(IP)之上
- 描述业务
- 非OS系统
协议: 为了使数据在网络上从源头到达目的,网络通信的参与方必须遵循相同的规则,这套规则称为协议,它最终体现为在网络上传输的数据包的格式。
注意: 当我们访问一些网页时,是显示通过 HTTPS 来进行通信的,并且当下大多数的网页都是通过 HTTPS 来进行通信的,因为 HTTPS 在 HTTP 的基础上做了一个加密的工作。
二、 HTTP 工作过程
当我们在浏览器输入一个网址,此时浏览器就会给对应的服务器发送一个 HTTP 请求,对应的服务器收到这个请求之后,经过计算处理,就会返回一个 HTTP 响应。并且当我们访问一个网站时,可能涉及不止一次的 HTTP 请求和响应的交互过程。
基础术语:
- 客户端: 主动发起网络请求的一端
- 服务器: 被动接收网络请求的一端
- 请求: 客户端给服务器发送的数据
- 响应: 服务器给客户端返回的数据
形式特点: 一发一收,一问一答
注意: 网络编程中,除了一发一收之外,还有其它的模式:
- 多发一收:例如上传大文件
- 一发多收:例如看直播时,搜索一个词条可以得到多个视频源
- 多发多收:例如串流(steam link、moonlight 等等)
三、 HTTP 协议格式总览
注意: 为什么 HTTP 报文中要存在空行呢?
- 因为 HTTP 协议并没有规定报头部分的键值对有多少个,使用空行就相当于是报文的 结束标记 或 报文和正文之间的分隔符。
- HTTP 在传输层依赖 TCP 协议,TCP 是面向字节流的。如果没有这个空行,就会出现 “粘包问题” 。
2.1 HTTP 请求格式
2.2 HTTP 响应格式
四、 HTTP 请求(Request)格式
4.1 认识 URL
4.1.1 URL 基本介绍
- URL(Uniform Resource Locator),统一资源定位符,即平时我们俗称的 “网址”。
- 互联网上的每个文件都有一个唯一的 URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它。
4.4.2 URL 基本格式
- URL 的标准格式:
协议类型:[//服务器地址[:端口号]][/资源层级 UNIX 文件路径]文件名[?查询字符串][#片段标识符]
- URL 的完整格式:
协议类型:[//[访问资源需要的凭证信息@]服务器地址[:端口号]][/资源层级 UNIX 文件路径]文件名[?查询字符串][#片段标识符]
4.4.3 URL 参数介绍
信息 | 描述 | 可否省略 |
---|---|---|
协议类型 | 常见的协议类型有 http 和 https,访问 mysql 时的协议类型为 jdbc:mysql | 可以省略,省略后默认为 http:// |
访问资源需要的凭证信息 | 一般就是登录信息(用户名、密码等),但是现在的网站进行认证一般不再通过 URL 进行,故一般省略 | 可以省略 |
服务器地址 | 服务器的地址可以是一个 IP 地址,也可以是一个域名(域名会通过 DNS 系统解析成一个具体的 IP 地址,可以使用 ping 域名 来得到该域名的 IP 地址),IP 地址用来描述网络上的一个具体位置,能够用来定位一个具体的主机 | 在 HTML 中可以省略(比如 img、link、script、a 标签的 src 或者 href 属性),省略后表示服务器的 ip 或域名与当前 HTML 所属的 ip 或域名一致 |
端口号 | 端口号的主要作用是表示一台计算机中的特定进程所提供的服务,即用来区分一个主机上的不同程序。每个程序在访问网络的时候,都会关联上一个或多个端口号,通过端口号就能区分出当前的请求要给谁。 | 可以被省略,当端口号省略时,浏览器会根据协议类型自动决定使用哪个端口号(如 http 协议默认使用80端口,https 协议默认使用443端口) |
资源层级 UNIX 文件路径 | 表示访问该服务器程序上某个资源的路径 | 可以省略,省略后相当于 / |
文件名 | 表示访问该服务器上的哪个资源(如 html、图片等等) | 不能省略 |
查询字符串 | 查询字符串(query string)本质是一个键值对结构,且键值对之间使用 & 分割,键和值之间使用 = 分割。表示客户端给服务器传递的参数。该参数是 web 开发的一个重要参数,给前后端交互提供了很多可能性。该参数 key 和 value 的取值和个数,完全都是由程序员自己约定,因此可以通过这样的方式来自定制我们需要的信息给服务器 | 可以省略 |
片段标识符 | 片段标识符主要用于页面内跳转,例如跳转到当前页面的某个部分、章节等等 | 可以省略 |
通过抓包可以我们来看看几个网站的 URL:
- 搜狗首页:
[搜狗搜索引擎 - 上网从搜狗开始 (sogou.com)]
(https://www.sogou.com/)- 稀土掘金首页:
[稀土掘金 (juejin.cn)]
(https://juejin.cn/)- CSDN 首页:
[CSDN - 专业开发者社区]
(https://www.csdn.net/)- 飞桨AI Studio平台:【Hackathon 4th AIGC】当中国水墨山水遇上AIGC
[【Hackathon 4th AIGC】当中国水墨山水遇上AIGC - 飞桨AI Studio (baidu.com)]
(https://aistudio.baidu.com/aistudio/projectdetail/6103948)
注意: 上述几个 URL 都并不完整,因为 URL 中的有些参数是可以省略的。
4.4.4 URLEncode 介绍
定义: URLEncode 是一种将特殊字符转换成百分号编码的方法,以便浏览器和服务器之间能够正确地处理它们。
组成: 给某些字符(包括字母、数字、下划线、连字符、句点以及某些保留字符)编码,替换为由 ‘%’ 和其后面的两个十六进制数字所组成。
目的: 将 URL 或者 HTTP 请求中的非 ASCII 字符编码成可以使用的 ASCII 字符,以保证正确传递和处理。
样例:
例如将空格编码成 “%20”、中文编码成 “%E4%BD%A0%E5%A5%BD” 等。
例如,浏览器中进行百度搜索“你好”时,链接地址会被自动编码:
(编码前)https://www.baidu.com/s?wd=你好
(编码后)https://www.baidu.com/s?wd=%E4%BD%A0%E5%A5%BD
当我们用搜狗搜索蛋糕时,通过抓包,我们会得到下面这个 URL:
https://www.sogou.com/web?query=%E8%9B%8B%E7%B3%95&_asf=www.sogou.com&_ast=&w=01019900&p=40040100&ie=utf8&from=index-nologin&s_from=index&sut=4900&sst0=1691469302084&lkt=0%2C0%2C0&sugsuv=1691469277804051&sugtime=1691469302084 我们会发现 query string 的有些值是%E8%9B%8B%E7%B3%95
、0%2C0%2C0
,通过 URLEncode,知道%E8%9B%8B%E7%B3%95
就是表示蛋糕。
需要 URLEncode 的原因:
- 通常在使用 HTTP GET 请求提交参数时,需要对参数进行 URLEncode 编码,以防止出现特殊字符导致的错误。比如:像 “/”、“?”、“:” 等这样的字符,已经被 URL 当做特殊意义理解了,因此这些字符不能随意出现。如果某个参数中需要带有这些特殊字符,就必须先对特殊字符进行转义,即 URLEncode。
- 一个中文字符由 UTF-8 或者 GBK 这样的编码方式构成,虽然在 URL 中没有特殊含义,但是仍然需要进行转义,否则浏览器可能把 UTF-8/GBK 编码中的某个字节当做 URL 中的特殊符号。
转义的规则:
- 将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成
%XY
格式。- 使用现成的可以进行转码的工具
4.2 认识“方法”(method)
HTTP 中的方法即为 HTTP 请求报文中的首行的第一个部分。
HTTP 中的方法:
方法 | 说明 | 适用版本号 |
---|---|---|
GET | 获取资源 | HTTP 1.0、HTTP 1.1 |
POST | 传输实体主体 | HTTP 1.0、HTTP 1.1 |
PUT | 传输文件 | HTTP 1.0、HTTP 1.1 |
HEAD | 获得报文首部 | HTTP 1.0、HTTP 1.1 |
DELETE | 删除文件 | HTTP 1.0、HTTP 1.1 |
OPTIONS | 访问支持的方法 | HTTP 1.1 |
TRACE | 追踪路径 | HTTP 1.1 |
CONNECT | 要求用隧道协议连接代理 | HTTP 1.1 |
LINK | 建立和资源之间的联系 | HTTP 1.1 |
UNLINE | 断开连接关系 | HTTP 1.1 |
虽然 HTTP 中的方法很多,如下表,但最常用的就是 GET 和 POST。本文主要介绍这两个方法。
4.2.1 GET 方法
基本介绍: GET 是最常用的 HTTP 方法,常用于获取服务器上的某个资源。以下几种方式都会触发 GET 方法的请求:
- 在浏览器中直接输入 URL 回车或点击浏览器收藏夹中的链接,此时浏览器就会发送出一个 GET 请求。
- HTML 中的 link、img、script 等标签的属性中放的 URL,浏览器也会构造出 HTTP GET 请求。
- 使用 Javascript 重点 ajax,也能构造出 HTTP GET 请求。
- 各种编程语言(只要能够访问网络),就都能够构造出 HTTP GER 请求。
GET 请求的特点:
- 首行里面的第一个部分就是 GET。
- URL 里面的 query string 可以为空,也可以不为空。
- GET 请求的 header 有若干个键值对结构。
- GET 请求的 body 一般是空的。
GET 请求示例: 搜狗首页请求
4.2.2 POST 方法
基本介绍: POST 方法也是一种常见的方法,多用于提交用户输入的数据给服务器(如登录页面)。以下几种方法都会触发 POST 方法的请求:
- 通过 HTML 中的 form 标签可以构造 POST 请求
- 使用 JavaScript 的 ajax 可以构造 POST 请求
POST 请求的特点:
- 首行第一个部分就是 POST
- URL 里面的 query string 一般是空的
- POST 请求的 header 里面有若干个键值对
- POST 请求的 body 一般不为空(body 的具体数据格式,由 header 中的 Content-Type 来描述;body 的具体数据长度,由 header 中的 Content-Length 来描述
POST 请求示例: QQ 邮箱登录请求
4.3 认识协议头(header)
header 的整体格式是键值对结构,每个键值对占一行,键和值之间使用 冒号+空格
进行分割。
常见的协议头(“报头”):
key | value |
---|---|
Host | 表示服务器主机的地址和端口 |
Content-Length | 表示 body 的数据长度,长度单位是字节 |
Content-Type | 表示 body 的数据格式 |
User-Agent | 表示浏览器或者操作系统的属性 |
Referer | 表示这个页面是从哪个页面跳转过来的 |
Cookie | 是浏览器提供的一种让程序员在本地存储数据的能力 |
Content-Type(body 的数据格式)
有以下三种请求中的数据格式:
application/x-www-form-urlencoded
:这是 form 表单提交的数据格式,此时 body 的格式就类似于 query string(是键值对的结构,键值对之间使用 & 分割,键与值之间使用等号=
分割。multipart/form-data
:这是 form 表单提交的数据格式(需要在 from 标签上加上 enctyped=“multipart/form-data”),通常用于 HTML 提交图片或者文件。application/json
:此时 body 数据为 json 格式,json 格式就是源自 js 的对象的格式。用一个 { } 括住,里面有多个键值对,键值对之间使用逗号分割,键和值之间使用冒号:
分割。
五、 HTTP 响应(Response)格式
5.1 认识“状态码”(status code)
常见的状态码:
常见状态码 | 说明 |
---|---|
200 OK | 这是一个最常见的状态码, 表示访问成功。抓包抓到的大部分结果都是 200 |
404 Not Found | 没有找到资源。URL 标识的资源不存在, 那么就会出现 404 |
403 Forbidden | 表示访问被拒绝。有的页面通常需要用户具有一定的权限才能访问(登陆后才能访问).。如果用户没有登陆直接访问, 就容易见到 403 |
405 Method Not Allowed | 我们学习了 HTTP 中所支持的方法, 有 GET, POST, PUT, DELETE 等。但是对方的服务器不一定都支持所有的方法(或者不允许用户使用一些其他的方法)。 |
500 Internal Server Error | 服务器出现内部错误. 一般是服务器的代码执行过程中遇到了一些特殊情况(服务器异常崩溃)会产生这个状态码,一般很少见 |
504 Gateway Timeout | 当服务器负载比较大的时候, 服务器处理单条请求的时候消耗的时间就会很长, 就可能会导致出现超时的情况 |
302 Move temporarily | 临时重定向。在登陆页面中经常会见到 302. 用于实现登陆成功后自动跳转到主页 |
301 Moved Permanently | 永久重定向。当浏览器收到这种响应时, 后续的请求都会被自动改成新的地址。301 也是通过 Location 字段来表示要重定向到的新地址 |
总结
类别 | 原因短语 |
---|---|
1XX | Informational (信息性状态码) 接受的请求正在处理 |
2XX | Success (成功状态码) 请求正常处理完毕 |
3XX | Redirection (重定向状态码) 需要进行附加操作以完成请求 |
4XX | Client Error (客户端错误状态码) 服务器无法处理请求 |
5XX | Server Error (服务器错误状态码) 服务器处理请求出错 |
5.2 认识协议头(header)
Content-Type (body 的数据格式)
以下介绍三种响应中的数据格式:
Content-Type | 数据格式 |
---|---|
text/html | HTML |
text/css | CSS |
application/javascript | JavaScript |
application/json | JSON |
六、 手写HTTP
6.1 HTTP 客户端
import java.io.*;
import java.net.Socket;
// HTTP 客户端
public class HTTPClient {
public static void main(String[] args) throws IOException {
// 1. HTTP 客户端,要发送 HTTP 请求,先建立 TCP 连接
// 我们当前进程和www.baidu.com主机上绑定 8080 端口的进程使用 TCP 通信(要求 建立 TCP 连接)
Socket socket = new Socket("localhost", 80);
OutputStream os = socket.getOutputStream();
PrintWriter writer = new PrintWriter(new OutputStreamWriter(os, "UTF-8"));
// 只有请求行,请求头为空,没有请求体
String request = "GET / HTTP/1.0\r\n\r\n";
writer.print(request);
// 冲刷缓冲区
writer.flush();
// 读取百度返回的 HTTP 响应
InputStream is = socket.getInputStream();
byte[] buf = new byte[10240]; // 我们知道响应不会超过 1024 字节的
int n = is.read(buf);
String response = new String(buf, 0, n, "UTF-8");
System.out.println(response);
}
}
import java.io.*;
import java.net.Socket;
// HTTP 客户端
public class HTTPClient {
public static void main(String[] args) throws IOException {
// 1. HTTP 客户端,要发送 HTTP 请求,先建立 TCP 连接
// 我们当前进程和www.baidu.com主机上绑定 8080 端口的进程使用 TCP 通信(要求 建立 TCP 连接)
Socket socket = new Socket("localhost", 80);
OutputStream os = socket.getOutputStream();
PrintWriter writer = new PrintWriter(new OutputStreamWriter(os, "UTF-8"));
// 只有请求行,请求头为空,没有请求体
String request = "GET / HTTP/1.0\r\n\r\n";
writer.print(request);
// 冲刷缓冲区
writer.flush();
// 读取百度返回的 HTTP 响应
InputStream is = socket.getInputStream();
byte[] buf = new byte[10240]; // 我们知道响应不会超过 1024 字节的
int n = is.read(buf);
String response = new String(buf, 0, n, "UTF-8");
System.out.println(response);
}
}
6.2 HTTP 服务端
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
// HTTP 服务端
public class HTTPServer {
public static void main(String[] args) throws IOException {
// 我们使用短连接
ServerSocket serverSocket = new ServerSocket(80);
while (true) {
try {
Socket socket = serverSocket.accept();
// 不管对方发给我们的 请求 是什么,一律使用统一的响应回复对方
OutputStream os = socket.getOutputStream();
PrintWriter writer = new PrintWriter(new OutputStreamWriter(os, "UTF-8"));
String html = "<a href='https://www.baidu.com/'>百度一下</a>"; // 我们的响应体
byte[] bytes = html.getBytes("UTF-8");
int contentLength = bytes.length;
String response = "HTTP/1.0 200 OK\r\n" +
"Content-Type: application/octet-stream; charset=utf-8\r\n" +
"Content-Length: 49\r\n" +
"\r\n" +
html;
writer.print(response);
writer.flush();
socket.close();
} catch (IOException exc) {
exc.printStackTrace();
}
}
}
}
七、 客户端→请求→服务器
7.1 通过 form 表单构造 HTTP 请求
form 是 HTML 中的一个表单标签,可以用于给服务器发送 GET 或者 POST 请求。
form 的重要参数:
- action: 用来构造 HTTP 请求的 URL 是什么
- method: 用来构造 HTTP 请求的方法(form 只支持 GET 或 POST 方法)
参数在 form 标签中的含义:
- type: 表示输入框的类型(text 表示文本、password 表示密码、submit 表示提交按钮)
- name: 表示构造出的 HTTP 请求的 query string 的 key
- value: 表示 input 标签的值(对于 type 为 submit 类型来说,value 就对应了按钮上显示的文本)
- input: 标签的内容:表示 query string 的 value
当我们用 form 表单构造好了 HTTP 请求,点击 submit 提交按钮,就可以将请求发送出去。
7.1.1 发送 GET 请求
示例代码:
<form action="http://aaaaa/myPath" method="get">
<input type="text" name="username">
<input type="text" name="password">
<input type="submit" name="提交">
</form>
在构造的页面中,输入数据后,进行提交,我们再通过抓包,查询到了以下结果:
7.1.2 发送 POST 请求
示例代码:
<form action="http://aaaaa/myPath" method="post">
<input type="text" name="username">
<input type="text" name="password">
<input type="submit" name="提交">
</form>
在构造的页面中,输入数据后,进行提交,我们再通过抓包,查询到了以下结果:
7.2 通过 ajax 构造 HTTP 请求
ajax(Asynchronous Javascript And XML) 是一种用于创建快速动态网页的技术。通过在后台与服务器进行少量数据交换,ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。传统的网页(不使用 ajax)如果需要更新内容,必须重载整个页面。
这里不使用 JavaScript 中原生的 ajax,而是用第三方库中 jQuery 里面提供的对 ajax 封装好的一个更简便的版本。
注意:
- 使用 ajax 不仅可以实现 GET 和 POST 方法的请求,也可以实现其它方法的请求。
- 使用 ajax 不能跨域,即访问的域名和构造的域名需要相同(可以跨域的前提是该服务器允许可以跨域)。
7.2.1 发送 GET 请求
示例代码:
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
$.ajax({
url: 'http://42.192.83.143:8089/AjaxMockServer/info',
method: 'GET',
success: function(data, status){
console.log(data);
console.log(status);
}
});
</script>
7.2.2 发送 POST 请求
示例代码:
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
$.ajax({
url: 'http://42.192.83.143:8089/AjaxMockServer/info',
method: 'POST',
contentType: 'text/plain',
data: 'this is body',
success: function(data, status){
console.log(data);
console.log(status);
}
});
</script>
以上是 HTTP 协议相关知识的学习内容,本文基于字节跳动青训营,借鉴与学习 CSDN 社区里的优秀博主的关于 HTTP 协议的介绍、工作过程、请求与响应格式、客户端与服务器的请求响应过程等文章,并会继续积极探索 HTTP 协议的相关知识。