0


HTTP协议深度解析:构建Web通信的基石


*🍑个人主页:Jupiter.🚀 所属专栏:Linux从入门到进阶欢迎大家点赞收藏评论😊*
在这里插入图片描述

在这里插入图片描述

目录


HTTP 协议

虽然我们说, 应用层协议是我们程序猿自己定的. 但实际上, 已经有大佬们定义了一些现
成的, 又非常好用的应用层协议, 供我们直接参考使用. HTTP(超文本传输协议)就是其
中之一。

在互联网世界中,

HTTP(HyperText Transfer Protocol,超文本传输协议)

是一个至关重要的协议。它定义了客户端(如浏览器)与服务器之间如何通信,以交换或传输超文本(如 HTML 文档)。

HTTP 协议是客户端与服务器之间通信的基础。客户端通过 HTTP 协议向服务器发送请求,服务器收到请求后处理并返回响应。HTTP 协议是

一个无连接、无状态的协议

,即每次请求都需要建立新的连接,且服务器不会保存客户端的状态信息。

HTTP协议底层是基于TCP的

认识 URL(统一资源定位符)

平时我们俗称的

 "网址"

其实就是说的

URL

网络通信的核心在于资源交互,因此准确定位互联网上的资源至关重要。服务器上的资源通常以文件形式存在,要获取文件内容,首要任务是找到该文件。在服务器上,文件定位通过路径实现;而在互联网上,服务器定位则需通过IP地址和端口号(IP+Port)。结合这两者,加上文件路径,即可唯一确定互联网上的文件(资源),这即构成了统一资源定位符(URL)。

URL是在全球网络中定位资源的策略


在这里插入图片描述

urlencode 和 urldecode(编码与解码)
  • / ? : 等这样的字符, 已经被 url 当做特殊意义理解了. 因此这些字符不能随意出现. 比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义. 转义的规则如下: - 将需要转码的字符转为 16 进制,然后从右到左,取 4 位(不足 4 位直接处理),每 2 位做一位,前面加上%,编码成%XY 格式,例如:“+” 被转义成了 “%2B”

  • urldecode 就是 urlencode 的逆过程;

HTTP 协议请求与响应格式

HTTP 请求

  • 其实http请求实际就是一个大的字符串,只不过以\r\n分隔了,打印出来就是很多行而已;
  • 首行: [方法] + [url] + [版本];
  • Header: 请求的属性,冒号分割的键值对,每组属性之间使用\r\n 分隔,遇到空行表示 Header 部分结束;
  • Body: 空行后面的内容都是 Body, Body 允许为空字符串。如果 Body 存在, 则在Header 中会有一个 Content-Length 属性来标识 Body 的长度;
  • 如何将报头和有效载荷进行分离 :空行可以保证将报头读完。
  • 怎么保证将正文读完:通过 Content-Length。
URL

若未明确指定请求的资源,则系统默认访问的URL为根目录,即“/”。在此情况下,HTTP服务器会尝试查找并展示默认首页,这通常是命名为“index.html”或“index.htm”的文件。当然,如果明确指定了路径,则系统会直接访问该路径下的资源。

如图:
在这里插入图片描述
在服务器内部,通常设有一个专门用于存储资源的目录,我们习惯上称之为“Web根目录”(例如命名为wwwroot)。为了访问这些资源,我们需要在URL的路径部分前加上/wwwroot/。这样,服务器上的所有资源只需放置在wwwroot目录或其子目录下,即可通过相应的URL轻松找到。值得注意的是,虽然wwwroot下的每个目录(包括wwwroot自身)可以选择性地包含一个首页文件(如index.html),但这并非强制要求。这样的设计旨在提高资源管理的便捷性和访问效率。
如图:
在这里插入图片描述

telnet命令

Telnet是一种基于文本协议的网络协议,主要用于远程登录到网络设备和服务器上。

  • 定义 - Telnet协议是TCP/IP协议族中的一员,它提供了一种在远程计算机上执行命令的方式。用户可以通过Telnet客户端连接到远程Telnet服务器,然后在远程计算机上执行命令,就像在该计算机上直接操作一样。
  • 操作模式 - Telnet有两种操作模式,即Telnet命令模式和Telnet会话模式。连接到Telnet服务器后,Telnet客户端会自动进入Telnet会话模式。在会话模式下,所有击键将通过网络发送到Telnet服务器,并可在Telnet服务器上由在该处运行的任何程序进行处理。而Telnet命令模式则允许在本地将命令发送到Telnet客户端服务本身,例如打开到远程主机的连接、关闭到远程主机的连接等。
  • 常用命令 - 连接到远程主机:使用“telnet [主机名或IP地址] [端口]”命令可以连接到远程主机。其中,“[主机名或IP地址]”是远程主机的地址,“[端口]”是远程主机上Telnet服务的端口号,默认端口是23。- 登录远程主机:连接成功后,系统会提示输入用户名和密码进行登录。输入正确的用户名和密码后,即可进入远程主机的命令行界面。- 退出Telnet会话:在Telnet会话中,可以使用“logout”或“exit”命令退出当前会话。另外,也可以通过按“Ctrl+]”进入Telnet命令模式,然后输入“quit”命令退出Telnet客户端。在这里插入图片描述在浏览网站时,每一次页面跳转或访问都伴随着一次HTTP请求。网页作为一个复合资源体,内含诸如图片、样式表、脚本等多种元素。浏览器在获取网页时,首先会请求并接收HTML文档,随后对其进行解析和渲染。此过程中,浏览器会根据HTML中的引用,发起额外的HTTP请求以获取网页所需的其他资源,如图片、CSS文件和JavaScript脚本等。只有当所有必需的资源都被成功加载后,浏览器才能构建并呈现出完整的网页。这一过程确保了用户能够浏览到内容丰富、格式正确的网页。

HTTP基本的应答格式

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 的状态码

最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)

HTTP 状态码 301(永久重定向)和 302(临时重定向)都依赖 Location 选项。以下是关于两者依赖 Location 选项的详细说明:

HTTP 状态码 301(永久重定向):

  • 当服务器返回 HTTP 301 状态码时,表示请求的资源已经被永久移动到新的位置。
  • 在这种情况下,服务器会在响应中添加一个 Location 头部,用于指定资源的新位置。这个 Location 头部包含了新的 URL 地址,浏览器会自动重定向到该地址。
  • 例如,在 HTTP 响应中,可能会看到类似于以下的头部信息:
HTTP/1.1301 Moved Permanently\r\n
Location: https://www.new-url.com\r\n

HTTP 状态码 302(临时重定向):

  • 当服务器返回 HTTP 302 状态码时,表示请求的资源临时被移动到新的位置。
  • 同样地,服务器也会在响应中添加一个 Location 头部来指定资源的新位置。浏览器会暂时使用新的 URL 进行后续的请求,但不会缓存这个重定向。
  • 例如,在 HTTP 响应中,可能会看到类似于以下的头部信息
HTTP/1.1302 Found\r\n
Location: https://www.new-url.com\r\n

总结:无论是 HTTP 301 还是 HTTP 302 重定向,都需要依赖 Location 选项来指定资源的新位置。这个 Location 选项是一个标准的 HTTP 响应头部,用于告诉浏览器应该将请求重定向到哪个新的 URL 地址。

HTTP 的方法

其中最常用的就是 GET 方法和 POST 方法.

  • GET 方法(重点) - 用途:用于请求 URL 指定的资源。(主要是获取内容,但是也可以传参)- 示例:GET /index.html HTTP/1.1- 特性:指定资源经服务器端解析后返回响应内容。

在这里插入图片描述

  • POST 方法(重点) - 用途:用于传输实体的主体,通常用于提交表单数据。(主要是上传内容(传参数))- 示例:POST /submit.cgi HTTP/1.1- 特性:可以发送大量的数据给服务器,并且数据包含在请求体中。在这里插入图片描述 POST方法比GET方法更加私密,并且可以传递更大更多的数据,到那时无论是GET还是POST方法,传递参数都不安全。

Fiddler抓包工具

下载链接:


测试相关代码
classHttpRequest// 请求{private:// \r\n正文
    std::string GetOneline(std::string &reqstr)// 从reqstr中,获取一行的内容{if(reqstr.empty())// 空串return reqstr;auto pos = reqstr.find(sep);// 找到第一个\r\nif(pos == std::string::npos)// 没找到\r\nreturn std::string();

        std::string line = reqstr.substr(0, pos);// 第一行
        reqstr.erase(0, pos + sep.size());// 删掉第一行return line.empty()? sep : line;}boolParseHeaderHelper(const std::string &line, std::string *k, std::string *v)// 解析一行的内容,将其解析成k-v{auto pos = line.find(header_sep);if(pos == std::string::npos)returnfalse;*k = line.substr(0, pos);*v = line.substr(pos + header_sep.size());returntrue;}public:HttpRequest():_blank_line(sep),_path(wwwroot){}voidSerialize(){}voidDerialize(std::string &reqstr)//反序列化reqstr,将其解析成HttpRequest对象{
        _req_line =GetOneline(reqstr);while(true){
            std::string line =GetOneline(reqstr);if(line.empty())break;elseif(line == sep){
                _req_text = reqstr;break;}else{
                _req_header.emplace_back(line);}}ParseReqLine();// 解析请求行ParseHeader();}boolParseReqLine()//{if(_req_line.empty())returnfalse;
        std::stringstream ss(_req_line);
        ss >> _method >> _url >> _version;
        _path += _url;// 判断一下是不是请求的/ -- wwwroot/if(_path[_path.size()-1]=='/'){
            _path += homepage;}auto pos = _path.rfind(filesuffixsep);if(pos == std::string::npos){
            _suffix =".html";}else{
            _suffix = _path.substr(pos);}LOG(INFO,"client wang get %s\n", _path.c_str());returntrue;}boolParseHeader(){for(auto&header : _req_header){// Connection: keep-alive
            std::string k, v;if(ParseHeaderHelper(header,&k,&v)){
                _headers.insert(std::make_pair(k, v));}}returntrue;}
    std::string Path(){return _path;}
    std::string Suffix(){return _suffix;}~HttpRequest(){}private:// 原始协议内容
    std::string _req_line;// 请求的第一行
    std::vector<std::string> _req_header;//将报头解析出来,放到vector中
    std::string _blank_line;//空行
    std::string _req_text;//请求正文内容// 期望解析的结果
    std::string _method;//将请求中的方法提取出来
    std::string _url;//将请求中的url提取出来
    std::string _path;// 请求的资源的 路径
    std::string _suffix;// 资源的后缀名
    std::string _version;// 协议版本

    std::unordered_map<std::string, std::string> _headers;// 解析出来的报头,放到kv中};

classHttpResponse// 应答{public:HttpResponse():_version(httpversion),_blank_line(sep){}~HttpResponse(){}voidAddStatusLine(int code)// 状态码{
        _code = code;
        _desc ="OK";//TODO}voidAddHeader(const std::string &k,const std::string &v)// 添加一行报头{
        _headers[k]= v;}voidAddText(const std::string &text)// 添加正文内容{
        _resp_text = text;}
    std::string Serialize()// 将应答序列化{
        std::string _status_line = _version + space + std::to_string(_code)+ space + _desc + sep;for(auto&header : _headers){
            _resp_header.emplace_back(header.first + header_sep + header.second + sep);}// 序列化
        std::string respstr = _status_line;for(auto&header : _resp_header){
            respstr += header;}
        respstr += _blank_line;
        respstr += _resp_text;return respstr;}private:// 构建应答的必要字段
    std::string _version;// 协议版本int _code;// 状态码
    std::string _desc;// 状态描述
    std::unordered_map<std::string, std::string> _headers;// 报头// 应答的结构化字段 
    std::string _status_line;// 状态行
    std::vector<std::string> _resp_header;// 应答报头
    std::string _blank_line;// 空行
    std::string _resp_text;// 应答正文};
classFactory// 工厂类{public:static std::shared_ptr<HttpRequest>BuildHttpRequest()// 构建一个请求对象{return std::make_shared<HttpRequest>();}static std::shared_ptr<HttpResponse>BuildHttpResponose()// 构建一个应答对象{return std::make_shared<HttpResponse>();}};classHttpServer// 服务器类{public:HttpServer(){}HttpServer(){}
    std::string ReadFileContent(const std::string &path,int*size)// 读取文件内容{// 要按照二进制打开
        std::ifstream in(path, std::ios::binary);// 以二进制方式打开if(!in.is_open()){return std::string();}//获取文件的大小
        in.seekg(0, in.end);int filesize = in.tellg();
        in.seekg(0, in.beg);// 读取文件内容
        std::string content;
        content.resize(filesize);
        in.read((char*)content.c_str(), filesize);        
        in.close();*size = filesize;return content;}
    std::string HandlerHttpReqeust(std::string req)// 处理请求{#ifdefTEST
        std::cout <<"---------------------------------------"<< std::endl;
        std::cout << req;

        std::string response ="HTTP/1.0 200 OK\r\n";// 404 NOT Found
        response +="\r\n";
        response +="<html><body><h1>hello world, hello bite!</h1></body></html>";return response;#elseauto request =Factory::BuildHttpRequest();
        request->Derialize(req);int contentsize =0;
        std::string text =ReadFileContent(request->Path(),&contentsize);
        std::string suffix = request->Suffix();auto response =Factory::BuildHttpResponose();
        response->AddStatusLine(200);
        response->AddHeader("Content-Length", std::to_string(contentsize));// http协议已经给我们规定好了不同文件后缀对应的Content-Type
        response->AddHeader("Content-Type", _mime_type[suffix]);
        response->AddText(text);return response->Serialize();#endif}~HttpServer(){}~HttpServer(){}private:
    std::unordered_map<std::string, std::string> _mime_type;// 不同文件后缀对应的Content-Type};


本文转载自: https://blog.csdn.net/2301_77509762/article/details/142302046
版权归原作者 Jupiter· 所有, 如有侵权,请联系我们删除。

“HTTP协议深度解析:构建Web通信的基石”的评论:

还没有评论