应用层协议HTTP


🍑个人主页:Jupiter. 🚀 所属专栏:Linux从入门到进阶 欢迎大家点赞收藏评论😊

目录

    • [`HTTP 协议`](#HTTP 协议)
      • [`认识 URL(统一资源定位符)`](#认识 URL(统一资源定位符))
        • [`urlencode 和 urldecode(编码与解码)`](#urlencode 和 urldecode(编码与解码))
      • [`HTTP 协议请求与响应格式`](#HTTP 协议请求与响应格式)
      • [`HTTP 请求`](#HTTP 请求)
      • `HTTP基本的应答格式`
      • [`HTTP 常见 Header`](#HTTP 常见 Header)
            • [`关于 connection 报头`](#关于 connection 报头)
        • [`HTTP 的状态码`](#HTTP 的状态码)
        • [`HTTP 的方法`](#HTTP 的方法)
      • Fiddler抓包工具

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 响应中,可能会看到类似于以下的头部信息:
cpp 复制代码
HTTP/1.1 301 Moved Permanently\r\n
Location: https://www.new-url.com\r\n

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

  • 当服务器返回 HTTP 302 状态码时,表示请求的资源临时被移动到新的位置。
  • 同样地,服务器也会在响应中添加一个 Location 头部来指定资源的新位置。浏览器会暂时使用新的 URL 进行后续的请求,但不会缓存这个重定向。
  • 例如,在 HTTP 响应中,可能会看到类似于以下的头部信息
cpp 复制代码
HTTP/1.1 302 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抓包工具

下载链接:


测试相关代码

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

        std::string line = reqstr.substr(0, pos);   // 第一行
        reqstr.erase(0, pos + sep.size());          // 删掉第一行
        return line.empty() ? sep : line;         
    }
    bool ParseHeaderHelper(const std::string &line, std::string *k, std::string *v)  // 解析一行的内容,将其解析成k-v
    {
        auto pos = line.find(header_sep);
        if (pos == std::string::npos)
            return false;

        *k = line.substr(0, pos);
        *v = line.substr(pos + header_sep.size());
        return true;
    }

public:
    HttpRequest() : _blank_line(sep), _path(wwwroot)
    { }
    void Serialize()
    { }
    void Derialize(std::string &reqstr)   //反序列化reqstr,将其解析成HttpRequest对象
    {
        _req_line = GetOneline(reqstr);
        while (true)
        {
            std::string line = GetOneline(reqstr);
            if (line.empty())
                break;
            else if (line == sep)
            {
                _req_text = reqstr;
                break;
            }
            else
            {
                _req_header.emplace_back(line);
            }
        }

        ParseReqLine();   // 解析请求行
        ParseHeader();    
    }
 
    bool ParseReqLine()   //
    {
        if (_req_line.empty())
            return false;
        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());
        return true;
    }
    bool ParseHeader()   
    {
        for (auto &header : _req_header)
        {
            // Connection: keep-alive
            std::string k, v;
            if(ParseHeaderHelper(header, &k, &v))
            {
                _headers.insert(std::make_pair(k, v));
            }
        }
        return true;
    }
    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中
};

cpp 复制代码
class HttpResponse   // 应答
{
public:
    HttpResponse():_version(httpversion), _blank_line(sep)
    {}
    ~HttpResponse()
    {}
    void AddStatusLine(int code)   // 状态码
    {
        _code = code;
        _desc = "OK"; //TODO
    }
    void AddHeader(const std::string &k, const std::string &v)  // 添加一行报头
    {
        _headers[k] = v;
    }
    void AddText(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;   // 应答正文
};
cpp 复制代码
class Factory   // 工厂类
{
public:
    static std::shared_ptr<HttpRequest> BuildHttpRequest()   // 构建一个请求对象
    {
        return std::make_shared<HttpRequest>();
    }
    static std::shared_ptr<HttpResponse> BuildHttpResponose()   // 构建一个应答对象
    {
        return std::make_shared<HttpResponse>();
    }
};

class HttpServer   // 服务器类
{
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)   // 处理请求
    {
#ifdef TEST
        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;
#else
        auto 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
};

相关推荐
fantasy_arch1 小时前
CPU性能优化-磁盘空间和解析时间
网络·性能优化
njnu@liyong2 小时前
图解HTTP-HTTP报文
网络协议·计算机网络·http
GISer_Jing2 小时前
2025前端面试热门题目——计算机网络篇
前端·计算机网络·面试
ZachOn1y3 小时前
计算机网络:应用层 —— 应用层概述
计算机网络·http·https·应用层·dns
cominglately3 小时前
centos单机部署seata
linux·运维·centos
魏 无羡3 小时前
linux CentOS系统上卸载docker
linux·kubernetes·centos
CircleMouse3 小时前
Centos7, 使用yum工具,出现 Could not resolve host: mirrorlist.centos.org
linux·运维·服务器·centos
是Dream呀3 小时前
Python从0到100(七十八):神经网络--从0开始搭建全连接网络和CNN网络
网络·python·神经网络
木子Linux4 小时前
【Linux打怪升级记 | 问题01】安装Linux系统忘记设置时区怎么办?3个方法教你回到东八区
linux·运维·服务器·centos·云计算
kaixin_learn_qt_ing4 小时前
了解RPC
网络·网络协议·rpc