【Linux】HTTP协议

欢迎来到Cefler的博客😁

🕌博客主页:折纸花满衣

🏠个人专栏:题目解析


目录

👉🏻http概念初识

HTTP(Hypertext Transfer Protocol)是一种用于传输超文本数据(如HTML、CSS、JavaScript等)的应用层协议。它是万维网的基础,用于在客户端和服务器之间传输信息。以下是关于HTTP协议的一些重要信息:

特点:

  1. 无连接性(Connectionless):每个请求/响应对都是独立的,服务器在处理完一个请求后断开连接,等待下一个请求的到来。

  2. 无状态性(Stateless):每个请求之间没有关联,服务器不会保留之前请求的状态信息,每个请求都是独立处理的。

  3. 基于文本(Text-based):HTTP的请求和响应都是以纯文本的形式进行传输,易于阅读和调试。

  4. 灵活性(Flexibility):HTTP支持多种数据类型和方法,可以传输各种类型的数据,并支持多种应用场景。

工作原理:

  1. 客户端-服务器模型:HTTP是一种客户端-服务器模型的协议,客户端发送请求,服务器返回响应。

  2. 请求-响应模式:客户端向服务器发送请求,请求包括方法(GET、POST等)、URL、HTTP版本、请求头部等信息;服务器接收到请求后,返回相应的响应,包括状态码、响应头部、响应体等信息。

请求方法:

  1. GET:请求指定的资源。
  2. POST:向指定资源提交数据进行处理请求。
  3. PUT:上传指定的URI,作为其表示。
  4. DELETE:请求服务器删除指定的资源。
  5. PATCH:对资源进行部分修改。
  6. HEAD:类似于GET请求,只不过服务器在响应中只返回首部,不返回实体的主体部分。

状态码:

  1. 1xx:信息性状态码。
  2. 2xx:成功状态码。
  3. 3xx:重定向状态码。
  4. 4xx:客户端错误状态码。
  5. 5xx:服务器错误状态码。

协议版本:

  1. HTTP/1.0:最初的版本,每次请求/响应建立一个新的连接。
  2. HTTP/1.1:持久连接,复用连接,支持管道化请求。
  3. HTTP/2.0:二进制协议,多路复用,头部压缩,服务器推送等特性。

安全性:

  1. HTTPS:HTTP的安全版本,使用SSL/TLS协议进行加密通信,保护数据的安全性和完整性。

HTTP协议在互联网中扮演着重要的角色,它的简单性和灵活性使得它成为了Web开发中不可或缺的一部分。

👉🏻URL

URL(Uniform Resource Locator)是统一资源定位符的缩写,是用于标识互联网上资源位置的一种标识符。它由多个部分组成,用于定位资源的地址以及指定访问该资源的方式。

💦 URL的结构:

一个标准的URL通常由以下几个部分组成:

  1. 协议(Protocol):指定访问资源所使用的协议或者规则,如 HTTP、HTTPS、FTP 等。

http://表示的是协议名称,表示请求时需要使用的协议,通常使用的是HTTP协议或安全协议HTTPS。HTTPS是以安全为目标的HTTP通道,在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性

  1. 主机名(Host):指定资源所在的主机或者服务器的域名或者IP地址。

  2. 端口号(Port):指定访问资源所使用的端口号。通常,如果使用默认端口号,则可以省略。

  3. 路径(Path):指定服务器上资源的路径。它是资源在服务器上的具体位置。

  4. 查询字符串(Query String) :用于传递参数给服务器。它以 ? 开始,多个参数之间用 & 分隔。

  5. 片段标识符(Fragment Identifier) :标识资源中的特定部分。它以 # 开始,用于定位文档中的特定位置。

💦示例:

考虑以下示例URL:

css 复制代码
https://www.example.com:8080/path/to/resource?param1=value1&param2=value2#section3
  • 协议(Protocol):HTTPS

  • 主机名(Host):www.example.com(服务器地址)

  • 端口号(Port):8080

  • 路径(Path):/path/to/resource(在服务器下的资源路径)

  • 查询字符串(Query String):param1=value1&param2=value2

  • 片段标识符(Fragment Identifier):section3


域名 www.example.com 可以分为以下三个部分:

  1. 子域名(Subdomain):在这个示例中,"www" 是子域名。它位于主域名之前,用于表示特定的服务、部门或者功能。通常,"www" 是用于指示该域名用于网站服务的子域名,但也可以使用其他的子域名,比如 "blog"、"shop" 等。

  2. 主域名(Domain):在示例中,"example.com" 是主域名。它是一个在互联网上唯一的名称,用于表示一个网站或者服务的整体身份。主域名通常由注册商注册,并且需要进行域名解析以映射到相应的IP地址。

  3. 顶级域名(Top-Level Domain,TLD):在示例中,".com" 是顶级域名。顶级域名是域名体系中的最高层次,用于表示域名的类型或者所属的组织、地理位置等信息。常见的顶级域名包括 ".com"、".net"、".org" 等通用顶级域名,以及各个国家/地区的国家顶级域名(如 ".cn"、".uk" 等)。

综上所述,www.example.com 这个域名可以分为 "www"(子域名)、"example"(主域名)和 ".com"(顶级域名)三个部分。

💦用途:

URL是互联网上资源的唯一标识符,用于在Web浏览器中定位和访问网页、图像、文件、视频等资源。通过URL,用户可以直接访问并获取到网络上的各种资源。

总之,URL是互联网上资源的地址标识符,它由多个部分组成,用于指定资源的位置和访问方式。

👉🏻简单实现http协议(版本一)

http协议主要分两个部分:请求和响应,我们需要分别对它们的数据进行反序列化

HttpProtocol.hpp

cpp 复制代码
#pragma once

#include<iostream>
#include<string>
#include<vector>

using namespace std;
const string HttpSep = "\r\n";

class HttpRequest 
{
public:
    HttpRequest():
    _req_blank(HttpSep)
    {

    }
    
    bool Getline(string& str,string* line)//获取请求行的内容
    {
        //1.找到内容停止符的位置
        auto pos = str.find(HttpSep);
        if(pos==string::npos) return false;

        //2.截取正文
        *line = str.substr(0,pos);
        //3.读取完后,将str中已提取的内容清空
        str.erase(0,pos+HttpSep.size());
        return true;
        
    }
	//反序列化
    void Deserialize(string& request)
    {
        //1.先读请求行
        string line;
        bool ok = Getline(request,&line);
        if(!ok) return;//如果读不到\r\n则退出
        _req_line = line;

        //2.再读报文每一行的内容
        while(true)
        {
            bool ok = Getline(request,&line);//再接着往下读取
            if(ok&&line.empty())
            {
                //如果读到了,但是内容为空
                //则剩下的内容为
                _req_content = request;

            }
            else if(ok&&!line.empty())
            {
                _req_header.push_back(line);
            }
            else
                break;
        }
    }
     void DebugHttp()
    {
        std::cout << "_req_line" << _req_line << std::endl;
        for(auto &line : _req_header)
        {
            std::cout << "---> " << line << std::endl;
        }
        std::cout << "_req_blank: " << _req_blank << std::endl;
        std::cout << "_req_content: " << _req_content << std::endl;
    }
private:
    string _req_line;//请求行
    vector<string> _req_header;//用来存储请求数据中所有的请求行数据
    string _req_content;//报文内容
    string _req_blank;//空行

};

最后去网页访问我们的服务器,就可以得到这个

👉🏻http协议(版本二)

此次版本,我们主要给Http协议中增设解析请求行的功能,

将请求行解析为三部分:

  • method:请求方法
  • URL:资源地址
  • http_version:http的版本

而后我们还要再对URL地址进行解析,可以得到

  • path:资源的路径
  • suffix:资源的后缀名

文件目录如下:

HttpProtocol.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <vector>

#include <sstream>

using namespace std;
const string HttpSep = "\r\n";

const string homepage = "index.html";
const string wwwroot = "./wwwroot"; // 根目录

class HttpRequest
{
public:
    HttpRequest() : _req_blank(HttpSep), _path(wwwroot)
    {
    }
    ~HttpRequest()
    {
    }
    bool Getline(string &str, string *line) // 获取请求行的内容
    {
        // 1.找到内容停止符的位置
        auto pos = str.find(HttpSep);
        if (pos == string::npos)
            return false;

        // 2.截取正文
        *line = str.substr(0, pos);
        // 3.读取完后,将str中已提取的内容清空
        str.erase(0, pos + HttpSep.size());
        return true;
    }

    void Deserialize(string &request)
    {
        // 1.先读请求行
        string line;
        bool ok = Getline(request, &line);
        if (!ok)
            return; // 如果读不到\r\n则退出
        _req_line = line;

        // 2.再读报文每一行的内容
        while (true)
        {
            bool ok = Getline(request, &line); // 再接着往下读取
            if (ok && line.empty())
            {
                // 如果读到了,但是内容为空
                // 则剩下的内容为
                _req_content = request;
            }
            else if (ok && !line.empty())
            {
                _req_header.push_back(line);
            }
            else
                break;
        }
    }
    void ParseReqLine()
    {
        stringstream ss(_req_line);

        ss >> _method >> _url >> _http_version;

        if (_url == "/")
        {
            // 如果是在当前下目录,则直接令它的路径资源终点为index.html
            _path += _url;
            _path += homepage;
        }
        else
        {
            _path += _url;
        }
    }
    void ParseSuffix()
    {
        auto pos = _path.rfind(".");
        if (pos == string::npos)
            _suffix = ".html";
        else
            _suffix = _path.substr(pos);
    }
    void Parse()
    {
        // 1.解析请求行
        ParseReqLine();

        // 2.解析资源后缀
        ParseSuffix();
    }
    void DebugHttp()
    {
        std::cout << "_req_line" << _req_line << std::endl;
        for (auto &line : _req_header)
        {
            std::cout << "---> " << line << std::endl;
        }
        std::cout << "_req_blank: " << _req_blank << std::endl;
        std::cout << "_req_content: " << _req_content << std::endl;

        std::cout << "Method: " << _method << std::endl;
        std::cout << "url: " << _url << std::endl;
        std::cout << "http_version: " << _http_version << std::endl;
    }

    // 接下来再整几个返回解析后内容的函数接口
    string Url()
    {
        return _url;
    }
    string Path()
    {
        return _path;
    }
    string Suffix()
    {
        return _suffix;
    }

private:
    string _req_line;           // 请求行
    vector<string> _req_header; // 用来存储请求数据中所有的请求行数据
    string _req_content;        // 报文内容
    string _req_blank;          // 空行

    // 解析之后的内容
    string _method;       // 请求方法
    string _url;          // 资源路径
    string _http_version; // http版本
    string _path;         // 资源的具体文件路径位置,path包含在url中,所以需要从url中解析
    string _suffix;       // 请求资源的后缀
};

Main.cc

cpp 复制代码
#include <iostream>
#include <memory>
#include <string>
#include <unistd.h>
#include <fstream>
#include "TcpServer.hpp"
#include "HttpProtocol.hpp"
string SuffixToType(const std::string &suffix) // 返回后缀名对应的http-type
{
    if (suffix == ".html" || suffix == ".html")
        return "text/html";
    else if (suffix == ".png")
        return "image/png";
    else if (suffix == ".jpg")
        return "image/jpeg";
    else
    {
        return "text/html";
    }
}
string GetFileContent(const string &path)
{
    // 为了中间不出现字符解码出现问题
    // 我们读取文件内容,而是以二进制形式读取
    ifstream in(path, std::ios::binary); // 二进制读写
    if (!in.is_open())
        return "";

    in.seekg(0, in.end);       // 位置移到文件末尾
    int filesize = in.tellg(); // 读取文件当前位置之前的大小
    in.seekg(0, in.beg);       // 读完大小后回到开头

    string content; // 存储读到的二进制内容
    content.resize(filesize);
    in.read((char *)content.c_str(), filesize);

    in.close();

    return content;
}
// request: 我们认为我们已经读到了一个完整的请求了
std::string HandlerHttpRequest(std::string &request)
{
    HttpRequest req;
    // 1.反序列化+解析http信息+打印http信息
    req.Deserialize(request);

    req.Parse();

    req.DebugHttp();

    // 2.获取请求URL中的文件资源,即文件的内容
    string content = GetFileContent(req.Path());
    // 3.打印看看解析后,资源文件的后缀和对应的http格式类型
    std::cout << "suffix: " << req.Suffix() << " Type: " << SuffixToType(req.Suffix()) << std::endl;

    if (!content.empty())
    {
        //返回http状态+请求到的资源信息+Content-Type
        std::string httpstatusline = "Http/1.0 200 OK\r\n";
        std::string httpheader = "Content-Length: " + std::to_string(content.size()) + "\r\n";
        httpheader += "Content-Type: " + SuffixToType(req.Suffix()) + "\r\n"; // 正文的类型
        httpheader += "\r\n";

        std::string httpresponse = httpstatusline + httpheader + content;
        
        return httpresponse;
    }

    return "";
}

// ./server port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cout << "Usage : " << argv[0] << " port" << std::endl;
        return 0;
    }
    uint16_t localport = std::stoi(argv[1]);

    std::unique_ptr<TcpServer> svr(new TcpServer(localport, HandlerHttpRequest));

    svr->Loop();

    return 0;
}

实现效果如下:

补充知识

rz命令

rz 命令是用于在 Linux 系统中接收文件的命令,通常用于在终端下通过串口或者 SSH 连接从另一台计算机发送文件到本地计算机。这个命令通常与 sz 命令一起使用,sz 用于发送文件,rz 用于接收文件。

使用 rz 命令时,通常会通过串口或者 SSH 连接到远程计算机,然后在远程计算机上执行 sz 命令发送文件,最后在本地计算机上执行 rz 命令来接收文件。

要使用 rz 命令接收文件,你需要先确保你的 Linux 系统上已经安装了支持该命令的终端软件,例如 minicom、screen、picocom 等,以及 sz 和 rz 命令所在的软件包。

在使用 rz 命令接收文件时,默认情况下文件会保存到当前工作目录下。如果你想要控制 rz 命令将接收的文件保存到特定的目录下,你可以在执行 rz 命令之前先切换到你想要保存文件的目录。

ifstream的seekg和tellg

在 C++ 中,ifstream 是用于从文件中读取数据的输入流类,seekg 和 tellg 是 ifstream 类的成员函数,用于在文件中定位和查询当前读取位置的偏移量。

  • seekg: seekg 函数用于在文件中定位读取位置。它有多个重载形式,可以设置文件读取位置的绝对位置或相对位置。通常情况下,seekg 函数的参数为一个 streampos 类型的偏移量,表示从文件开始位置的偏移量,但也可以使用 ios_base::seekdir 类型的参数指定相对于当前位置或者文件末尾的偏移量。

  • tellg: tellg 函数用于查询当前读取位置的偏移量。它返回一个 streampos 类型的值,表示当前读取位置相对于文件开始位置的偏移量。

这两个函数通常用于实现文件的随机访问,可以在文件中自由移动读取位置。下面是一个简单的示例:

cpp 复制代码
#include <iostream>
#include <fstream>

int main() {
    std::ifstream file("example.txt", std::ios::binary); // 打开一个二进制文件用于读取

    // 获取当前读取位置的偏移量
    std::streampos currentPosition = file.tellg();
    std::cout << "Current position: " << currentPosition << std::endl;

    // 移动读取位置到文件末尾
    file.seekg(0, std::ios::end);
    
    // 获取文件末尾的偏移量
    std::streampos endPosition = file.tellg();
    std::cout << "End position: " << endPosition << std::endl;

    // 将读取位置移动回文件开头
    file.seekg(0, std::ios::beg);
    
    // 读取文件内容
    // ...

    return 0;
}

这段代码演示了如何使用 seekg 和 tellg 函数在文件中定位和查询读取位置的偏移量。


如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹❤️ 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长


相关推荐
别动我齐刘海2 小时前
C++ 工程化学习
java·linux·前端·c++·学习·机器学习·机器人
玖石书4 小时前
Ubuntu 24.04 UFW 全命令攻略:从入门到端口开放实战
linux·运维·ubuntu
xiaoqiMikko4 小时前
Linux 实战(七):useradd、sudoers、umask、SGID、软链 —— 三台真机实测
linux·centos
拂拉氏6 小时前
【知识讲解】 Linux之动静态库的了解
linux·运维·服务器·动静态库
Fcy6486 小时前
Linux下 五种IO模型的认识与⾮阻塞IO的实现
linux·服务器·io
Mikko77 小时前
Linux 重启后日志没了?进程被 OOM 杀了去哪看?logrotate 之后日志还往旧文件写?三台真机实测日志与性能排查
linux·运维·centos
书生执笔画浮沉7 小时前
分享一个开源小工具CAdapt
linux·c·gnu·glibc·kernel·升级·灵感
mounter6257 小时前
Linux 内核热升级(Live Update)技术详解:PCI、VFIO 与 IOMMU 子系统的演进与实战解析引言
linux·linux kernel·kernel·iommu·vfio
墨神谕8 小时前
RPC框架与HTTP REST的区别
网络协议·http·rpc
砚凝霜8 小时前
【软考信息安全】第十九章 Unix/Linux安全分析与防护
linux·安全·unix