在前面的文章中我们自己定义并实现了一个自定义协议,从序列化和反序列化,到封装报文,解析报文等,这就是我们自己实现的一个应用层协议。虽然我们说, 应用层协议是我们程序员自己定的,但实际上,已经有大佬们定义了一些现成的,又非常好用的应用层协议,供我们直接参考使用,
HTTP(超文本传输协议)就是其中之一,所以下面我们就来认识一下
文章目录
- [1. HTTP协议概述](#1. HTTP协议概述)
-
- [1.1 HTTP协议的发展历程](#1.1 HTTP协议的发展历程)
- [1.2 HTTP 的工作流程(一次典型的网页访问)](#1.2 HTTP 的工作流程(一次典型的网页访问))
- [2. URL](#2. URL)
-
- [2.1 什么是URL](#2.1 什么是URL)
- [2.2 URL 的语法结构](#2.2 URL 的语法结构)
- [2.3 URL 编码](#2.3 URL 编码)
- [3. HTTP协议请求与响应格式](#3. HTTP协议请求与响应格式)
- [4. HTTP方法](#4. HTTP方法)
-
- [4.1 常见的 HTTP 方法](#4.1 常见的 HTTP 方法)
- [4.2 GET方法](#4.2 GET方法)
- [4.3 POST方法](#4.3 POST方法)
- [5. HTTP状态码详解](#5. HTTP状态码详解)
-
- [5.1 状态码分类](#5.1 状态码分类)
- [5.2 常见状态码详解](#5.2 常见状态码详解)
- [6. HTTP头部字段](#6. HTTP头部字段)
-
- [6.1 HTTP常见Header](#6.1 HTTP常见Header)
1. HTTP协议概述
HTTP(HyperText Transfer Protocol,超文本传输协议)是互联网中最重要的应用层协议之一。它定义了客户端(如浏览器)与服务器之间进行通信的规则和格式,主要用于传输超文本(如HTML文档)以及其他网络资源。
简单来说,HTTP 定义了客户端(通常是 Web 浏览器)和服务器之间如何通信。它规定了客户端可以发送哪些请求,以及服务器应该返回哪些响应
核心特点
- 无状态
- HTTP 协议本身不会保留之前请求或响应的任何信息。每一个请求都是独立的,服务器不会记住你上一次是谁。
- 基于请求/响应模型
-
通信总是由客户端发起一个请求,然后服务器返回一个响应。
-
没有请求,就没有响应。
- 灵活可扩展
- HTTP 的头部允许自定义字段,使得协议能够不断发展,支持新的功能(如虚拟主机、缓存控制、内容协商等)。
- 明文传输(在 HTTPS 出现之前)
-
经典的 HTTP 协议中,请求和响应的内容都是未经加密的文本。这意味着在传输过程中,数据可能被窃听或篡改。
-
解决方案:HTTPS。它是在 HTTP 之下加入了 SSL/TLS 加密层,对传输的数据进行加密和身份验证。关于HTTPS,我们后面文章也会重点介绍。
- 支持多种数据格式
- 可以传输文本、图片、音频、视频等多种类型的数据
1.1 HTTP协议的发展历程
HTTP协议的发展是一部为了满足日益增长的网络需求而不断自我革新的历史。每一次升级都旨在解决前一版本的性能瓶颈和安全问题。
- HTTP/0.9 - 原型时代
-
时代背景 (1991):
-
万维网的诞生: 由Tim Berners-Lee在欧洲核子研究中心提出,旨在方便研究人员共享文档。
-
网络环境简单: 内容几乎全是超文本,没有图片、样式或脚本。
-
目标单一: 只是为了获取链接的文档。
-
-
核心技术:
-
极简设计: 只有GET一种方法。
-
无状态: 服务器发送完数据后立即关闭连接,不记录任何信息。
-
无头部: 无法传输元数据,因此只能传输纯文本HTML。
-
-
核心问题: 功能过于单一,无法适应复杂应用。
- HTTP/1.0 - 规范化与扩展
-
时代背景 (1996):
-
浏览器战争: Netscape Navigator和Internet Explorer的竞争推动了Web技术的飞速发展。
-
内容多媒体化: 网页开始包含图片、音频、视频等不同格式的资源。
-
商业化萌芽: 出现了在线购物、广告等需要与服务器交互的应用。
-
-
核心技术:
-
引入HTTP头: 在请求和响应中加入了元数据,实现了内容协商、缓存控制、状态码等强大功能。
-
支持新的方法: 引入了POST(用于提交表单)和HEAD方法。
-
支持多种内容类型: 通过Content-Type字段,可以传输非HTML文件。
-
短连接模型: 默认每个TCP连接只处理一个请求,响应完成后立即断开。高延迟下性能极差。
-
-
核心问题: 频繁的TCP连接建立与销毁带来巨大的性能开销。
- HTTP/1.1 - 互联网的基石
-
时代背景 (1997年提出,1999年作为标准):
-
互联网普及: 网民数量激增,网站成为企业和个人的标配。
-
富媒体应用: CSS、JavaScript的广泛使用使得页面资源数量大幅增加。
-
虚拟主机盛行: 需要在一台服务器上托管多个网站。
-
-
核心技术:
-
持久连接: 默认保持TCP连接打开,允许多个请求和响应在同一个连接上顺序进行,大幅减少了延迟和CPU消耗。
-
管道化: 允许客户端在收到上一个响应之前就发送下一个请求,但响应必须按请求顺序返回,容易导致"队头阻塞"。
-
分块传输编码: 支持服务器逐步生成并发送响应内容。
-
强制性 Host 头: 实现了在一台IP服务器上托管多个域名网站的能力,是虚拟主机的技术基础。
-
-
核心问题: 管道化在实践中体验不佳,队头阻塞问题依然存在;头部冗余严重(如Cookie)。
- HTTP/2.0 - 性能革命
-
时代背景 (2015):
-
移动互联网时代: 用户对页面加载速度极其敏感。
-
复杂Web应用: 单页面应用大量出现,一个页面可能包含上百个资源请求。
-
Google的推动: Google开发的SPDY协议证明了性能优化的巨大潜力,并成为HTTP/2的基础。
-
-
核心技术:
-
二进制分帧: 将传输的消息分解为更小的帧(Frame),采用二进制编码,解析更高效、更紧凑。
-
多路复用: 在单个TCP连接上,可以同时交错地发送多个请求和响应帧,彻底解决了HTTP/1.1的队头阻塞问题。
-
头部压缩: 使用HPACK算法对头部进行压缩,大大减少了冗余数据的传输。
-
服务器推送: 服务器可以主动将客户端很可能需要的资源(如CSS、JS)推送给客户端,无需客户端解析后再请求。
-
-
核心问题: 虽然解决了应用层的队头阻塞,但底层仍基于TCP。TCP在丢包时的重传机制会导致所有流(即使未被影响)等待,即TCP层的队头阻塞。
- HTTP/3.0 - 面向未来的传输
-
时代背景 (2022年发布,仍在普及中):
-
5G与万物互联: 网络环境更加复杂多变,对连接建立速度和稳定性要求更高。
-
极致性能追求: 电子商务、在线视频、实时游戏等应用对延迟的容忍度极低。
-
QUIC协议的成熟: Google再次引领,将QUIC从实验性协议发展为HTTP/3的标准。
-
-
核心技术:
-
基于QUIC,弃用TCP: HTTP/3不再使用TCP作为传输层协议,而是使用基于UDP的QUIC协议。
-
解决TCP队头阻塞: QUIC在用户空间实现了独立的流复用,单个流的丢包不会影响其他流。
-
极速连接建立: QUIC将加密和传输握手合并,通常只需1-RTT(甚至0-RTT)即可建立安全连接,而TCP+TLS需要1-3个RTT。
-
连接迁移: 当用户切换网络时(如WiFi切4G),QUIC连接可以无缝迁移,而TCP连接需要重新建立。
-
1.2 HTTP 的工作流程(一次典型的网页访问)
-
建立 TCP 连接:客户端(浏览器)首先与服务器的 80 端口(HTTP)或 443 端口(HTTPS)建立一个 TCP 连接。
-
发送 HTTP 请求:客户端向服务器发送一个 HTTP 请求报文。
-
服务器处理请求:服务器接收、解析请求,并执行相应的操作(例如查找资源、运行程序)。
-
返回 HTTP 响应:服务器将处理结果封装成一个 HTTP 响应报文,发回给客户端。
-
关闭连接:在 HTTP/1.0 中,连接通常会关闭。在 HTTP/1.1 及以后版本中,连接可能被保持以供后续请求复用。
-
客户端渲染:浏览器接收到响应后,解析 HTML、CSS、JavaScript,并渲染出完整的页面。
2. URL
2.1 什么是URL
URL (Uniform Resource Locator,统一资源定位符),通俗地说,它就是我们在互联网上访问一个网站、一张图片、一个视频或其他任何资源时,在浏览器地址栏里输入的"网址"。
它是一个标准化的地址系统,告诉浏览器如何在浩瀚的互联网上精确地找到并访问特定的资源。
URL 是 URI 的一个子集。URI 是一个更广泛的概念,用于标识一个资源,而 URL 不仅标识资源,还提供了定位和访问它的方式。在日常交谈中,两者常常混用。
2.2 URL 的语法结构
一个完整的 URL 由多个部分组成,每个部分都有其特定的含义。其标准格式如下:
[协议] :// [主机名] : [端口] / [路径] ? [查询字符串] # [片段]
让我们用一个具体的例子来分解它:
https://www.example.com:443/products/index.html?category=electronics&page=2#reviews
- 协议
-
例子中的部分:
https: -
说明: 协议指明了访问资源所需要使用的应用程序协议。它定义了客户端和服务器之间通信的规则。
-
常见协议:
-
http:超文本传输协议,未加密。
-
https:安全的 HTTP,经过加密。
-
ftp:文件传输协议,用于上传和下载文件。
-
mailto:用于打开电子邮件客户端。
-
file:用于访问本地计算机上的文件。
-
- 授权机构
这部分通常包括主机名、端口,有时还包含用户名和密码。
-
主机名:
-
例子中的部分:
www.example.com -
说明: 主机名是资源所在服务器的地址。它可以通过 DNS 解析为服务器的 IP 地址。
-
组成部分:
-
子域名:
www -
域名:
example -
顶级域名:
.com
-
-
-
端口:
-
例子中的部分:
:443 -
说明: 端口用于指定服务器上正在运行的特定服务或应用程序。由于 HTTP 的默认端口是 80,HTTPS 的默认端口是 443,所以这个部分在 URL 中通常会被省略。如果服务运行在非标准端口上,则必须明确指定,例如 :8080。
-
- 路径
-
例子中的部分:
/products/index.html -
说明: 路径表示资源在服务器上的具体位置,类似于操作系统中的文件路径。它指向特定的页面、图片或脚本等。
- 查询字符串
-
例子中的部分:
?category=electronics&page=2 -
说明: 查询字符串用于向服务器传递额外的参数。它通常用于搜索、筛选或传递用户输入。
-
以问号 ? 开始。
-
由多个 参数/值对 组成,格式为 参数=值。
-
不同的参数对之间用 & 符号分隔。
-
在上面的例子中,我们向服务器传递了两个参数:category=electronics 和 page=2,可能意味着请求"电子产品"分类下的第"2"页。
-
- 片段
-
例子中的部分:
#reviews -
说明: 片段(也称为"锚点")用于指向资源内部的某个特定部分。浏览器在加载完整个页面后,会自动滚动到该片段所标识的位置。它不会被发送到服务器
2.3 URL 编码
由于 URL 只能使用 ASCII 字符集中的一组有限字符(字母、数字和少数符号),如果 URL 中包含不在此范围内的字符(如中文、空格、特殊符号),就必须进行 URL 编码。
-
规则: 使用百分号 % 后跟两位十六进制数来表示该字符的 ASCII 码。
-
将需要转码的字符转换为16进制
-
从右到左取4位(不足4位直接处理)
-
每2位作为一组,前面加上%
-
格式为:%XY
-
-
常见例子:
-
空格: 编码为
%20或+(编码为+并不是标准的 URL 编码方式,而是 特定上下文中的特殊约定)。 -
中文: 如"
中国"在 UTF-8 编码下会被编码为%E4%B8%AD%E5%9B%BD。 -
符号:
&编码为%26,?编码为%3F。
-
示例 :
一个未编码的 URL:https://example.com/search?q=hello world&lang=zh-CN
编码后的 URL:https://example.com/search?q=hello%20world&lang=zh-CN
例如:

上图中 "+" 被转义成了 "%2B"
注意:URLDecode是URLEncode的逆过程,将编码后的字符恢复为原始字符
下面推荐一个编码解码的网站
3. HTTP协议请求与响应格式
- 请求报文
一个 HTTP 请求由以下部分组成:
-
请求行:包含方法、URL 和 HTTP 版本。
- GET /index.html HTTP/1.1
-
请求头:包含关于请求的元信息,以键值对的形式。
-
Host: www.example.com
-
User-Agent: Mozilla/5.0...
-
Accept: text/html
-
Cookie: name=value
-
-
空行:分隔头部和主体。
-
请求体:可选部分,通常在 POST、PUT 等方法中携带要发送给服务器的数据(如表单数据、JSON 等)。

示例:

- 首行: [方法] + [url] + [版本]
- Header: 请求的属性,冒号分割的键值对;每组属性之间使用 \r\n 分隔;遇到空行表示 Header 部分结束
- Body: 空行后面的内容都是Body,Body允许为空字符串,如果Body存在,则在Header中会有一个Content-Length属性来标识Body的长度
- 响应报文
一个 HTTP 响应由以下部分组成:
-
状态行:包含 HTTP 版本、状态码和状态消息。
- HTTP/1.1 200 OK
-
响应头:包含关于响应的元信息。
-
Server: nginx/1.18.0
-
Content-Type: text/html; charset=utf-8
-
Content-Length: 1234
-
Set-Cookie: sessionId=abc123
-
-
空行:分隔头部和主体。
-
响应体:服务器返回的实际内容,如 HTML 文档、图片数据等。

示例:

- 首行: [版本号] + [状态码] + [状态码解释]
- Header: 请求的属性,冒号分割的键值对;每组属性之间使用 \r\n 分隔;遇到空行表示Header部分结束
- Body: 空行后面的内容都是Body,Body允许为空字符串,如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度;如果服务器返回了一个html页面,那么html页面内容就是在body中.
那怎么将报文中的报头和报文有效分离出来呢?怎么完整读取到一个报文呢?
在HTTP协议中,报文分为请求报文和响应报文。两者格式类似,都由起始行、头部(Header)和主体(Body)组成。头部和主体之间通过一个空行(即连续的\r\n\r\n)来分隔。
要分离报头和报文主体,我们需要:
-
读取整个HTTP报文(可能分多次读取)。
-
在读取的数据中查找空行(\r\n\r\n)的位置。
-
空行之前的部分是头部,之后的部分是主体。
但是,需要注意的是,HTTP报文可能使用分块传输编码(Transfer-Encoding: chunked)或者有Content-Length头部来指示主体长度。因此,完整读取一个报文可能需要根据这些头部信息来确定主体的长度。
以下是一个基本的步骤:
步骤1:读取数据直到遇到空行,这样我们就可以先解析头部。
步骤2:解析头部,获取重要的头部字段,如Content-Length或Transfer-Encoding。
步骤3:根据头部信息,读取主体部分:
如果存在Content-Length,则读取指定长度的字节。
如果是分块编码,则需要按照分块编码的规则读取,直到遇到一个长度为0的块。
如果既没有Content-Length也不是分块编码,那么主体可能直到连接关闭为止(例如,HTTP/1.0的持续连接关闭时表示主体结束)。
4. HTTP方法
4.1 常见的 HTTP 方法
| 方法 | 描述 | 幂等性 | 安全性 |
|---|---|---|---|
| GET | 获取资源。只应用于请求数据,不应产生副作用。 | 是 | 是 |
| POST | 创建新资源或提交数据(如表单、文件上传)。 | 否 | 否 |
| PUT | 更新/替换一个已存在的资源。 | 是 | 否 |
| PATCH | 部分更新一个资源。 | 否 | 否 |
| DELETE | 删除指定的资源。 | 是 | 否 |
| HEAD | 与 GET 类似,但只获取响应头,不获取响应体。用于检查资源是否存在等。 | 是 | 是 |
| OPTIONS | 用于描述目标资源的通信选项(如服务器支持哪些方法)。 | 是 | 是 |
幂等性:同样的请求执行一次与连续执行多次,对服务器资源的状态改变是相同的。
安全性:该方法不应改变服务器资源的状态。
其中最常用的就是GET方法和POST方法.
4.2 GET方法
定义:
- 用于获取资源,从服务器请求数据。
- 是最常用的 HTTP 方法。
特点:
- 幂等性:多次执行相同 GET 请求,结果一致,不会改变服务器状态。
- 安全性:只读操作,不修改数据。
- 参数在 URL 中:通过查询字符串(query string)传递参数,如 ?id=123&name=abc。
- 可被缓存:浏览器或代理服务器可以缓存 GET 响应。
- 长度限制:受限于 URL 最大长度(通常几千字节),不适合大数据传输。
示例:
bash
GET /api/users?id=123 HTTP/1.1
Host: example.com
注意:
- 不适合传输敏感信息(如密码),因为参数暴露在 URL 中,可能被记录在日志、浏览器历史中。
- 不建议用于提交表单数据(除非是搜索、筛选等只读操作)。
4.3 POST方法
定义:
- 用于向服务器发送数据,通常用于创建新资源或更新现有资源。
特点:
- 非幂等性:每次 POST 可能产生新资源(如新建用户、提交订单),多次调用结果不同。
- 数据在报文体中:参数放在请求体(body)里,不会出现在 URL 中。
- 不可缓存:默认不被缓存。
- 无长度限制:理论上可以发送任意大小的数据(受服务器配置限制)。
- 适合提交表单、上传文件、API 数据等。
示例:
bash
POST /api/users HTTP/1.1
Host: example.com
Content-Type: application/json
Content-Length: 38
{"name": "Alice", "email": "alice@example.com"}
适用场景:
- 用户注册、登录
- 提交表单
- 文件上传
- 创建资源(RESTful API 中常用)
5. HTTP状态码详解
5.1 状态码分类

最常见的状态码,比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(BadGateway)
5.2 常见状态码详解
2xx 成功
-
200 OK:请求成功,返回请求的资源
-
201 Created:资源创建成功
-
204 No Content:请求成功,但无内容返回
3xx 重定向
-
301 Moved Permanently:永久重定向
-
302 Found:临时重定向
-
304 Not Modified:资源未修改,使用缓存
4xx 客户端错误
-
400 Bad Request:请求语法错误
-
401 Unauthorized:需要身份验证
-
403 Forbidden:服务器拒绝请求
-
404 Not Found:请求的资源不存在
5xx 服务器错误
-
500 Internal Server Error:服务器内部错误
-
502 Bad Gateway:网关错误
-
503 Service Unavailable:服务不可用
以下是仅包含重定向相关状态码的表格:
| 状态码 | 含义 | 是否为临时重定向 | 应用样例 |
|---|---|---|---|
| 301 | Moved Permanently | 否(永久重定向) | 网站换域名后,自动跳转到新域名;搜索引擎更新网站链接时使用 |
| 302 | Found 或 See Other | 是(临时重定向) | 用户登录成功后,重定向到用户首页 |
| 307 | Temporary Redirect | 是(临时重定向) | 临时重定向资源到新的位置(较少使用) |
| 308 | Permanent Redirect | 否(永久重定向) | 永久重定向资源到新的位置(较少使用) |
关于重定向的验证,以301为代表
HTTP状态码301(永久重定向)和302(临时重定向)都依赖Location选项。以下是关于两者依赖Location选项的详细说明:
HTTP状态码301(永久重定向)
- 当服务器返回HTTP 301状态码时,表示请求的资源已经被永久移动到新的位置。
- 在这种情况下,服务器会在响应中添加一个Location头部,用于指定资源的新位置。这个Location头部包含了新的URL地址,浏览器会自动重定向到该地址。
- 例如,在HTTP响应中,可能会看到类似于以下的头部信息:`
bash
HTTP/1.1 301 Moved Permanently\r\n
Location: https://www.new-url.com\r\n
HTTP状态码302(临时重定向)
- 当服务器返回HTTP 302状态码时,表示请求的资源临时被移动到新的位置。
- 同样地,服务器也会在响应中添加一个Location头部来指定资源的新位置。浏览器会暂时使用新的URL进行后续的请求,但不会缓存这个重定向。
- 例如,在HTTP响应中,可能会看到类似于以下的头部信息:
bash
HTTP/1.1 302 Found\r\n
Location: https://www.new-url.com\r\n
总结:无论是HTTP 301还是HTTP 302重定向,都需要依赖Location选项来指定资源的新位置。这个Location选项是一个标准的HTTP响应头部,用于告诉浏览器应该将请求重定向到哪个新的URL地址。
6. HTTP头部字段
6.1 HTTP常见Header
- Content-Type: 数据类型(text/html等)
- Content-Length: Body的长度
- Host: 客户端告知服务器,所请求的资源是在哪个主机的哪个端口上;
- User-Agent: 声明用户的操作系统和浏览器版本信息;
- Referer: 当前页面是从哪个页面跳转过来的;
- Location: 搭配3xx状态码使用,告诉客户端接下来要去哪里访问;
- Cookie: 用于在客户端存储少量信息,通常用于实现会话(session)的功能;
关于connection报头
HTTP中的 Connection 字段是HTTP报文头的一部分,它主要用于控制和管理客户端与服务器之间的连接状态
核心作用
- 管理持久连接: Connection 字段还用于管理持久连接(也称为长连接)。持久连接允许客户端和服务器在请求/响应完成后不立即关闭TCP连接,以便在同一个连接上发送多个请求和接收多个响应。
持久连接(长连接)
- HTTP/1.1:在HTTP/1.1协议中,默认使用持久连接。当客户端和服务器都不明确指定关闭连接时,连接将保持打开状态,以便后续的请求和响应可以复用同一个连接。
- HTTP/1.0:在HTTP/1.0协议中,默认连接是非持久的。如果希望在HTTP/1.0上实现持久连接,需要在请求头中显式设置
Connection: keep-alive。
语法格式
Connection: keep-alive:表示希望保持连接以复用TCP连接。Connection: close:表示请求/响应完成后,应该关闭TCP连接。
下面附上一张关于HTTP常见header的表格
| 字段名 | 含义 | 样例 |
|---|---|---|
| Accept | 客户端可接受的响应内容类型 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8 |
| Accept-Encoding | 客户端支持的数据压缩格式 | Accept-Encoding: gzip, deflate, br |
| Accept-Language | 客户端可接受的语⾔类型 | Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 |
| Host | 请求的主机名和端口号 | Host: www.example.com:8080 |
| User-Agent | 客户端的软件环境信息 | User-Agent: Mozilla/5.0 (Windows NT 10.0;Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 |
| Cookie | 客户端发送给服务器的HTTP cookie信息 | Cookie: session_id=abcdefg12345; user_id=123 |
| Referer | 请求的来源URL | Referer:http://www.example.com/previous_page.html |
| Content-Type | 实体主体的媒体类型 | Content-Type: application/x-www-formurlencoded (对于表单提交) 或 Content-Type:application/json (对于JSON数据) |
| Content-Length | 实体主体的字节大小 | Content-Length: 150 |
| Authorization | 认证信息,如用户名和密码 | Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== (Base64编码后的用户名:密码) |
| Cache-Control | 缓存控制指令 | 请求时: Cache-Control: no-cache 或 CacheControl: max-age=3600 ;响应时: CacheControl: public, max-age=3600 |
| Connection | 请求完后是关闭还是保持连接 | Connection: keep-alive 或 Connection: close |
| Date | 请求或响应的日期和时间 | Date: Wed, 21 Oct 2023 07:28:00 GMT |
| Location | 重定向的目标URL(与3xx状态码配合使用) | Location:http://www.example.com/new_location.html (与302状态码配合使用) |
| Server | 服务器类型 | Server: Apache/2.4.41 (Unix) |
| Last-Modified | 资源的最后修改时间 | Last-Modified: Wed, 21 Oct 2023 07:20:00 GMT |
| ETag | 资源的唯⼀标识符,用于缓存 | ETag: "3f80f-1b6-5f4e2512a4100" |
| Expires | 响应过期的日期和时间 | Expires: Wed, 21 Oct 2023 08:28:00 GMT |