【Linux】HTTP协议

news2024/11/30 12:38:19

目录

  • 🚀前言
    • 🚃HTTP协议
  • 🚄1、URL网址
  • 🚅2、URL的编码和解码
  • 🚇3、HTTP协议格式
  • 🚈4、HTTP请求
    • 🚉4.1、 HTTP GET和POST方法
    • 🚋4.2、HTTP状态码
    • 🚊4.3、HTTP常见Header

🚀前言

本篇文章进行应用层中HTTP协议的学习!!!


🚃HTTP协议

前言:应用层协议都是人定的,在以前有一些大佬定的好用的应用层协议,供我们直接参考使用,后面就变成了标准,HTTP就在其中

  • 应用层的作用主要是向用户提供应用服务时的通信活动

  • http协议:又名超文本传输协议,是一个简单的网络请求和响应协议,它通常运行在传输层之上的应用层

  • 它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应

  • HTTP协议主要是在网络中传输图片、音频、视频等网络资源,把这些丰富的资源都呈现在一个网站上,正是因为它可以传输任何资源,所以才叫超文本传输协议


🚄1、URL网址

前言:平时我们经常说的访问某某某网址,其实就是说访问某某某URL!!!

在这里插入图片描述

URL中的字段

  • 第一个字段是协议名

  • 第二个字段中的登录信息(用户名和密码)一般不在URL中明文传输,一般都是通过请求正文加密进行传输

  • 第三个字段是网址的域名,域名可以通过DNS服务转换成IP地址,然后通过对应的端口号标定网络中唯一的进程

  • 第四个字段中的第一个斜杠是Web根目录,它可以被设置为根目录,也可以设置为其他路径作为web根目录(相对路径),后面访问的文件是这个网址要在服务器获取的资源(index.html)

  • 查询字符串用于向服务器传递参数,格式为?key=value&key2=value2,其中?key=value表示为第一个参数&用于分隔多个参数GET方法请求可以直接将参数以明文方式添加到URL中

  • 片段标识符用于指定文档中特定的部分,格式为#fragment,其中#表示片段标识符开始,fragment表示文档中特定的部分。主要用于跳转到文档的特定位置

  • 总结:URL就是通过某种协议获取某个主机中的某种资源,拉取到网站本地中,如果是html,浏览器可以自动解析


🚅2、URL的编码和解码

【URL编码解码在线网页工具】

编码
  • 当使用URL进行传参时,如果存在特殊字符或汉字时,会对其进行转义(编码)【url特殊字符】
  • 比如问号和加号等特殊字符,它们已经被URL当做特殊意义理解了,因此这些字符不能随意出现
  • 编码规则:将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式

搜关键字通过utl传参,“+” 被转义成了 %2B

在这里插入图片描述

汉字通过url进行传参后,也会对其进行编码,每个汉字转换成三个16进制的数据

在这里插入图片描述

解码
  • 解码就是编码的逆过程,首先去掉%,然后将16进制数据转换成十进制,然后通过ASCII表转换成对应的特殊字符即可!!!

在这里插入图片描述


🚇3、HTTP协议格式

HTTP请求
  • HTTP请求包含:请求行、请求报头、空行和请求正文(有效载荷)这四个部分,空行是用来分离请求报头和请求正文(有效载荷)分隔符
  • 首行:[方法] + [url] + [HTTP版本]
  • 请求报头(Header):请求报头内的属性可以有多个,请求的属性是冒号分割的键值对;每组属性之间使用“\r\n”分隔;遇到空行(“\r\n”)表示Header部分结束
  • 请求正文(Body):空行后面的内容都是Body,Body允许为空字符串;如果Body存在, 则在Header中会有一个Content-Length属性标识Body的长度

在这里插入图片描述

HTTP响应
  • HTTP响应包含:状态行、响应报头、空行和响应正文(有效载荷)这四个部分,空行是用来分离请求报头和响应正文(有效载荷)分隔符
  • 首行:[HTTP版本号] + [状态码] + [状态码描述]
  • 请求报头(Header):响应报头内的属性可以有多个,响应的属性是冒号分割的键值对;每组属性之间使用“\r\n”分隔;遇到空行(“\r\n”)表示Header部分结束
  • 响应正文(Body):空行后面的内容都是Body,Body允许为空字符串;如果Body存在, 则在Header中会有一个Content-Length属性标识Body的长度;如果服务器返回了一个html页面,那么html页面内容就是在body中

0


HTTP的请求和响应如何保证自己的报头和有效载荷被全部读取呢?

  • 读取完整报头:按行读取,直到读取到空行即读取完了报头

  • 读取完整有效载荷:报头读取完毕后,请求或响应的报头属性中,有一个属性(Content-Length)可以标识有效载荷的数据长度,按照给定的长度进行读取,就能读取到完整的正文

HTTP请求行:GET / HTTP/1.1

  • 请求行中的第二个字段是URL,而URL除了资源路径以外其他的相关字段都省略了

  • 第二个字段下的斜杠,它不是服务器的根目录,而是一个Web根目录,但是它也可以设置成为根目录,一般为服务器内相对路径下的Web根目录

  • 举个例子,我们访问baidu.com时,请求资源路径只写一个斜杠,但是baidu会给我们自动添加主页资源的路径(/index.html),随后通过网络传输到客户端显示给用户

在这里插入图片描述

代码感受一下:首个http服务器代码

  • 该服务器只是简单的构建了http响应报头,多线程的方式进行读取请求和响应请求
#include <iostream>
#include <string>
#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <cassert>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <thread>
#include <fstream>

#define ROOT_PATH "./wwwroot"
#define HOME "index.html"
#define CRLF "\r\n"
#define CRLF_LEN 2
#define SPACE " "
#define SPACE_LEN 1

class HttpServer
{
public:
    HttpServer(uint16_t Port, std::string Ip = "")
        : ServerIp_(Ip), ServerPort_(Port), ListenSockfd_(-1)
    {
    }

    void Init()
    {
        assert(ServerPort_ > 1024);
        ListenSockfd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (ListenSockfd_ < 0)
        {
            std::cout << "have socket error: " << strerror(errno) << std::endl;
            exit(1);
        }

        sockaddr_in ServerData;
        socklen_t len = sizeof(ServerData);
        memset(&ServerData, 0, len);
        ServerData.sin_family = AF_INET;
        ServerData.sin_port = htons(ServerPort_);
        ServerData.sin_addr.s_addr = ServerIp_.empty() ? htonl(INADDR_ANY) : inet_addr(ServerIp_.c_str());

        if (bind(ListenSockfd_, (const sockaddr *)&ServerData, len))
        {
            std::cout << "bind inet data error: " << strerror(errno) << std::endl;
            exit(2);
        }

        if (listen(ListenSockfd_, 2) < 0)
        {
            std::cout << "Set listen status error: " << strerror(errno) << std::endl;
            exit(3);
        }
    }

    void Start()
    {
        while (true)
        {
            int sersockfd = accept(ListenSockfd_, nullptr, nullptr);
            if (sersockfd < 0)
            {
                std::cout << "accept error: " << strerror(errno) << std::endl;
                exit(4);
            }
            // 使用线程并行的去执行服务,不影响获取连接请求
            std::thread Thread(&HttpServer::RunThread, this, sersockfd);
            Thread.detach();
        }
    }

private:
    void RunThread(int sersockfd)
    {
        std::string inbuffer;
        inbuffer.resize(1024, 0);
        // 读http请求
        ssize_t Index = read(sersockfd, (void *)inbuffer.c_str(), inbuffer.size());
        if (Index > 0)
        {
            std::cout << inbuffer << std::endl;
        }

        // 构建响应报文
        std::string RequestPath = ROOT_PATH;
        RequestPath += GetRequestPath(inbuffer);
        std::string resources = ReadFile(RequestPath);

        std::string Response;
        Response += "HTTP/1.1 200 OK\r\n"; // 请求行
        Response += "Content-Type: text/html\r\n";
        Response += ("Content-Length: ") + (std::to_string(resources.size()) + "\r\n");
        Response += "\r\n";
        Response += resources;

		// 发送响应报文
        send(sersockfd, Response.c_str(), Response.size(), 0);

        close(sersockfd); // 服务完成后关闭服务套接字
    }

private:
    std::string ReadFile(const std::string &Path)
    {
    	// 读取请求资源路径下的文件
        std::ifstream in(Path.c_str());
        if (!in.is_open())
        {
            std::cout << "open file error: " << strerror(errno) << std::endl;
            return "404";
        }
        std::string buffer;
        std::string result;
        while (std::getline(in, buffer))
        {
            result += buffer;
        }
        std::cout << result << std::endl;
        return result;
    }

    std::string GetRequestPath(std::string &inbuffer)
    {
        // "GET /a/b/c HTTP/1.1\r\n" --> "/a/b/c"
        size_t pos = inbuffer.find(CRLF);
        if (pos == std::string::npos)
            return "4xx";
        std::string RequestLine = inbuffer.substr(0, pos + CRLF_LEN);
        size_t SpaceOne = RequestLine.find(SPACE);
        size_t SpaceTwo = RequestLine.rfind(SPACE);
        if (SpaceOne == std::string::npos && SpaceTwo == std::string::npos)
            return "4xx";

        // 如果请求为'/'那么自动添加******主页的资源******
        std::string Path = RequestLine.substr(SpaceOne + SPACE_LEN, SpaceTwo - (SpaceOne + SPACE_LEN));
        if (Path.size() == 1 && Path[0] == '/')
        {
            Path += HOME;
        }
        return Path;
    }

private:
    std::string ServerIp_;
    uint16_t ServerPort_;
    int ListenSockfd_;
};

int main(int argc, char *argv[])
{
    if (argc < 2)
    {
        std::cout << "Format: ./可执行程序 [ip] port" << std::endl;
        exit(5);
    }
    std::string ip;
    uint16_t port;
    if (argc == 3)
    {
        ip = argv[1];
        port = std::stoi(argv[2]);
    }
    else
    {
        port = std::stoi(argv[1]);
    }

    HttpServer hs(port, ip);
    hs.Init();
    hs.Start();
    return 0;
}

在这里插入图片描述

运行结果

在这里插入图片描述


🚈4、HTTP请求

HTTP请求行中的请求方法有如下图所示:

在这里插入图片描述

🚉4.1、 HTTP GET和POST方法

我们的网络行为无非有两种:

  • 我想把远端服务器某种资源拿到你的本地(GET /index.html)

  • 我们想把我们的属性字段(比如搜索关键字或用户名和密码等等),提交到远端服务器

  • 传参时,是使用html中一个表单的东西进行传递参数的


GET方法

  • GET方法是用于获取服务器中某个路径下的某种资源,可以向服务器进行传参

  • 我们想把我们的属性字段提交到远端,可以使用GET方法进行传参

  • HTTP协议中GET方法会以明文的方式将我们对应的参数信息拼接到URL中传参

  • 前面说的URL中查询字符串字段,GET方法就是把参数拼接到这里,格式为?key=value&key2=value2,&为参数间的分隔符参数的格式是以键值对的方式进行传参


POST方法

  • POST方法也可以获取服务器中某个路径下的某种资源,也可以向服务器进行传参

  • POST方法传参时,会将参数以明文的方式拼接到http请求正文中进行提交

  • 提交参数时也是以?key=value&key2=value2这样的格式进行传参,&为参数间的分隔符参数的格式是以键值对的方式进行传参


GET vs POST

  • GET方法通过URL进行传参

  • POST通过请求正文(有效载荷)进行传参

  • GET方法传参不私密,因为懂些网络的人,就知道你传的参数是什么了

  • POST方法通过正文传参相对比较私密一些(有些人不会抓包)

  • GET通过URL传参,POST通过正文传参,一般传一些比较大的内容都是通过POST的方式进行传参的,因为正文有报头属性(Content-Type)来标定正文数据类型是什么

  • GET方法以前是会限定传参内容大小的,现在已经可以无限制了,但是传输的数据类型只能是文本(text/html),所以还是使用POST以正文的方式进行传参,还能识别数据类型


验证是否正确:

当前路径下wwwroot下的index.html文件

  • 这里使用html语法中的表单进行传参,不会的可以学一下html的语法
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>测试网页</title>
</head>

<body>
    <h3>hello my server!</h3>
    <p>我终于测试完了我的代码</p>
    <form action="/a/b/c.html" method="post">
        Username: <input type="text" name="user"><br>
        Password: <input type="password" name="passwd"><br>
        <input type="submit" value="Submit">
    </form>
    <!-- <img border="0" src="https://img1.baidu.com/it/u=1691233364,820181697&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500" alt="Pulpit rock" width="304" height="228"> -->
</body>

</html>

HttpServer.cc

#include "Util.h"
#include <thread>
#include <fstream>

#define ROOT_PATH "./wwwroot"
#define HOME "index.html"
#define CRLF "\r\n"
#define CRLF_LEN 2
#define SPACE " "
#define SPACE_LEN 1

class HttpServer
{
public:
    HttpServer(uint16_t Port, std::string Ip = "")
        : ServerIp_(Ip), ServerPort_(Port), ListenSockfd_(-1)
    {
    }

    void Init()
    {
        assert(ServerPort_ > 1024);
        ListenSockfd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (ListenSockfd_ < 0)
        {
            std::cout << "have socket error: " << strerror(errno) << std::endl;
            exit(1);
        }

        sockaddr_in ServerData;
        socklen_t len = sizeof(ServerData);
        memset(&ServerData, 0, len);
        ServerData.sin_family = AF_INET;
        ServerData.sin_port = htons(ServerPort_);
        ServerData.sin_addr.s_addr = ServerIp_.empty() ? htonl(INADDR_ANY) : inet_addr(ServerIp_.c_str());

        if (bind(ListenSockfd_, (const sockaddr *)&ServerData, len))
        {
            std::cout << "bind inet data error: " << strerror(errno) << std::endl;
            exit(2);
        }

        if (listen(ListenSockfd_, 4) < 0)
        {
            std::cout << "Set listen status error: " << strerror(errno) << std::endl;
            exit(3);
        }
    }

    void Start()
    {
        while (true)
        {
            int sersockfd = accept(ListenSockfd_, nullptr, nullptr);
            if (sersockfd < 0)
            {
                std::cout << "accept error: " << strerror(errno) << std::endl;
                exit(4);
            }
            // 使用线程并行的去执行服务,不影响获取连接请求
            std::thread Thread(&HttpServer::RunThread, this, sersockfd);
            Thread.detach();
        }
    }

private:
    void RunThread(int sersockfd)
    {
        // char inbuffer[1024];
        std::string inbuffer;
        inbuffer.resize(1024, 0);
        ssize_t Index = read(sersockfd, (void *)inbuffer.c_str(), inbuffer.size());
        if (Index > 0)
        {
            std::cout << inbuffer << std::endl;
        }

        // 发响应报文
        std::string RequestPath = ROOT_PATH;
        RequestPath += GetRequestPath(inbuffer);
        std::string resources = ReadFile(RequestPath);

        std::string Response;
        Response += "HTTP/1.1 200 OK\r\n";
        Response += "Host: " + (ServerIp_.empty() ? "0.0.0.0" : ServerIp_) + ":" + \
                    std::to_string(ServerPort_) + "\r\n";
        Response += "Content-Type: text/html\r\n";
        Response += ("Content-Length: ") + (std::to_string(resources.size()) + "\r\n");
        Response += "\r\n";
        Response += resources;

        send(sersockfd, Response.c_str(), Response.size(), 0);

        close(sersockfd);
    }

private:
    std::string ReadFile(const std::string &Path)
    {
        std::ifstream in(Path.c_str());
        if (!in.is_open())
        {
            std::cout << "open file error: " << strerror(errno) << std::endl;
            return "404";
        }
        std::string buffer;
        std::string result;
        while (std::getline(in, buffer))
        {
            result += buffer;
        }
        return result;
    }

    std::string GetRequestPath(std::string &inbuffer)
    {
        // "GET /a/b/c HTTP/1.1\r\n" --> "/a/b/c"
        size_t pos = inbuffer.find(CRLF);
        if (pos == std::string::npos)
            return "4xx";
        std::string RequestLine = inbuffer.substr(0, pos + CRLF_LEN);
        size_t SpaceOne = RequestLine.find(SPACE);
        size_t SpaceTwo = RequestLine.rfind(SPACE);
        if (SpaceOne == std::string::npos && SpaceTwo == std::string::npos)
            return "4xx";

        // 如果请求为'/'那么自动添加主页的资源
        std::string Path = RequestLine.substr(SpaceOne + SPACE_LEN, SpaceTwo - (SpaceOne + SPACE_LEN));
        if (Path.size() == 1 && Path[0] == '/')
        {
            Path += HOME;
        }
        return Path;
    }

private:
    std::string ServerIp_;
    uint16_t ServerPort_;
    int ListenSockfd_;
};

int main(int argc, char *argv[])
{
    if (argc < 2)
    {
        std::cout << "Format: ./可执行程序 [ip] port" << std::endl;
        exit(5);
    }
    std::string ip;
    uint16_t port;
    if (argc == 3)
    {
        ip = argv[1];
        port = std::stoi(argv[2]);
    }
    else
    {
        port = std::stoi(argv[1]);
    }

    HttpServer hs(port, ip);
    hs.Init();
    hs.Start();
    return 0;
}

GET方法

在这里插入图片描述

在这里插入图片描述

POST方法

在这里插入图片描述

在这里插入图片描述


其他方法

【摘至☞developer.mozilla.org】

  • PUT:PUT请求方法是一种将数据添加到指定URL中的操作,可以用于更新服务器上的数据或创建新的资源

  • HEAD:HEAD请求方法用于获取服务端的请求行和报头

  • DELETE:DELETE请求方法用于删除服务端的指定资源

  • OPTIONS:OPTIONS请求方法用于获取服务端所支持的请求方法

  • TRACE:TRACE请求方法沿着通往目标资源的路径进行信息回环测试,提供一个有用的调试机制

  • CONNECT:CONNECT请求方法可以开启与所请求资源之间的双向沟通的通道;它可以用来创建隧道(tunnel)


🚋4.2、HTTP状态码

【HTT状态码 – 百度百科】
在这里插入图片描述

  • 状态码:状态码是服务器响应客户端请求时在状态行填充的字段,包含状态码和状态码描述

  • 1xx:该状态码表示接收的请求报文正在处理中稍后会响应一个完整的响应报文100的状态码描述为Continue101状态码描述为Switching Protocols

  • 2xx:该状态码表示请求已成功被服务器接收、理解、并接受(但不代表已经处理)200状态码描述为OK201状态码描述为Created202状态码描述为Accepted

  • 3xx:该状态码需要客户端采取进一步的操作才能完成请求,用于进行重定向(请求的URL可能临时进行维护或永久失效了),后续通过请求报头属性中的Location可以重定向到新的URL300状态码描述为Multiple Choices(临时重定向)301状态码描述为Moved Permanently(永久重定向)

  • 4xx:该状态码表示客户端发送请求有问题,可能请求报文没有按照要求进行编写请求的资源不存在等等。403状态码描述为Forbidden(有权限访问此网站)404状态码描述为Not Found(请求资源不存在)

  • 5xx:该状态码表示服务器在处理请求的过程中有错误或者异常状态发生(内存不足或程序异常等)500状态码描述为Internal Server Error502状态码描述为Bad Gateway

响应报文中状态行格式如下:

在这里插入图片描述


🚊4.3、HTTP常见Header

HTTP常见报头:

  • Content-Type:用于标定正文的数据类型(text/html等)【Content-Type对照表】

  • Content-Length:用于标定正文(Body)的长度,方便正确的读取正文数据大小

  • Host:客户端告知服务器,所请求的资源是在哪个主机的哪个端口上(格式ip:port)

  • User-Agent:声明(获取)用户的操作系统和浏览器版本信息

  • Referer:当前页面是从哪个页面跳转过来的

  • Location:搭配3xx状态码使用,告诉客户端接下来要去哪里访问重定向后的url

  • Cookie: 用于在客户端存储少量信息(用户名和密码),通常用于实现会话(session)保持的功能

状态码3xx和响应报头属性Location字段的关系

  • 状态码告诉用户要访问的网站正在进行维护或永久下架了

  • 客户端收到响应报头属性Location字段后,会根据Location字段的值进行重定向或跳转操作,如果需要进行重定向操作,客户端会发送新的请求,请求的URL地址为Location字段的值

  • 注意:Location字段的值必须是完整的URL,不能是baidu.com这样,必须携带使用的协议名(https://www.baidu.com/)

代码看构建响应报文的部分即可

#include "Util.h"
#include <thread>
#include <fstream>

#define ROOT_PATH "./wwwroot"
#define HOME "index.html"
#define CRLF "\r\n"
#define CRLF_LEN 2
#define SPACE " "
#define SPACE_LEN 1

class HttpServer
{
public:
    HttpServer(uint16_t Port, std::string Ip = "")
        : ServerIp_(Ip), ServerPort_(Port), ListenSockfd_(-1)
    {
    }

    void Init()
    {
        assert(ServerPort_ > 1024);
        ListenSockfd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (ListenSockfd_ < 0)
        {
            std::cout << "have socket error: " << strerror(errno) << std::endl;
            exit(1);
        }

        sockaddr_in ServerData;
        socklen_t len = sizeof(ServerData);
        memset(&ServerData, 0, len);
        ServerData.sin_family = AF_INET;
        ServerData.sin_port = htons(ServerPort_);
        ServerData.sin_addr.s_addr = ServerIp_.empty() ? htonl(INADDR_ANY) : inet_addr(ServerIp_.c_str());

        if (bind(ListenSockfd_, (const sockaddr *)&ServerData, len))
        {
            std::cout << "bind inet data error: " << strerror(errno) << std::endl;
            exit(2);
        }

        if (listen(ListenSockfd_, 4) < 0)
        {
            std::cout << "Set listen status error: " << strerror(errno) << std::endl;
            exit(3);
        }
    }

    void Start()
    {
        while (true)
        {
            int sersockfd = accept(ListenSockfd_, nullptr, nullptr);
            if (sersockfd < 0)
            {
                std::cout << "accept error: " << strerror(errno) << std::endl;
                exit(4);
            }
            // 使用线程并行的去执行服务,不影响获取连接请求
            std::thread Thread(&HttpServer::RunThread, this, sersockfd);
            Thread.detach();
        }
    }

private:
    void RunThread(int sersockfd)
    {
        // char inbuffer[1024];
        std::string inbuffer;
        inbuffer.resize(1024, 0);
        ssize_t Index = read(sersockfd, (void *)inbuffer.c_str(), inbuffer.size());
        if (Index > 0)
        {
            std::cout << inbuffer << std::endl;
        }

        // 发响应报文
        std::string RequestPath = ROOT_PATH;
        RequestPath += GetRequestPath(inbuffer);
        std::string resources = ReadFile(RequestPath);

        std::string Response;
        Response += "HTTP/1.1 300 Multiple Choices\r\n";
        Response += "Host: " + (ServerIp_.empty() ? "0.0.0.0" : ServerIp_) + ":" + \
                    std::to_string(ServerPort_) + "\r\n";
        Response += "Content-Type: text/html\r\n";
        Response += ("Content-Length: ") + (std::to_string(resources.size()) + "\r\n");
        Response += "Location: https://www.baidu.com/\r\n";
        Response += "\r\n";
        Response += resources;

        send(sersockfd, Response.c_str(), Response.size(), 0);

        close(sersockfd);
    }

private:
    std::string ReadFile(const std::string &Path)
    {
        std::ifstream in(Path.c_str());
        if (!in.is_open())
        {
            std::cout << "open file error: " << strerror(errno) << std::endl;
            return "404";
        }
        std::string buffer;
        std::string result;
        while (std::getline(in, buffer))
        {
            result += buffer;
        }
        return result;
    }

    std::string GetRequestPath(std::string &inbuffer)
    {
        // "GET /a/b/c HTTP/1.1\r\n" --> "/a/b/c"
        size_t pos = inbuffer.find(CRLF);
        if (pos == std::string::npos)
            return "4xx";
        std::string RequestLine = inbuffer.substr(0, pos + CRLF_LEN);
        size_t SpaceOne = RequestLine.find(SPACE);
        size_t SpaceTwo = RequestLine.rfind(SPACE);
        if (SpaceOne == std::string::npos && SpaceTwo == std::string::npos)
            return "4xx";

        // 如果请求为'/'那么自动添加主页的资源
        std::string Path = RequestLine.substr(SpaceOne + SPACE_LEN, SpaceTwo - (SpaceOne + SPACE_LEN));
        if (Path.size() == 1 && Path[0] == '/')
        {
            Path += HOME;
        }
        return Path;
    }

private:
    std::string ServerIp_;
    uint16_t ServerPort_;
    int ListenSockfd_;
};

int main(int argc, char *argv[])
{
    if (argc < 2)
    {
        std::cout << "Format: ./可执行程序 [ip] port" << std::endl;
        exit(5);
    }
    std::string ip;
    uint16_t port;
    if (argc == 3)
    {
        ip = argv[1];
        port = std::stoi(argv[2]);
    }
    else
    {
        port = std::stoi(argv[1]);
    }

    HttpServer hs(port, ip);
    hs.Init();
    hs.Start();
    return 0;
}

关于Cookie字段和Session详解

  • HTTP协议的特定之一:它是无状态的客户端发送请求和服务器响应请求时的报文都不会被保存起来,发完后就直接丢弃了

  • 如果我们要看电影,访问了一个网站,这些电影要登录账号才能免费观看,如果HTTP是无状态的话,请求报文中传参的用户名和密码都会被丢弃,那用户每请求一个新的电影资源都要进行重新登录!!

  • 解法方法就是在这个网站中保持用户的登录状态(会话保持),但是保持登录数据就要将请求的报文存储起来,每次请求和响应都要保存,会很费内存

  • Cookie:它是一个在客户端存储数据的小型文本文件,由服务器发给客户端(响应属性字段Set-Cookie),然后客户端将其存储在本地(浏览器维护,可能存储在磁盘或内存)。每次客户端向服务器发送请求时,Cookie会随请求一起发送到服务器

  • Session:由于Cookie存储在客户端,因此很容易被篡改和盗取,而Session是一种在服务端存储数据的机制,当用户第一次访问网站时,会为其创建一个唯一的Session ID,并将其存储在Cookie中(Cookie其他数据都保存在服务器端中,后面通过Session ID进行校验),后面客户端向服务器发送请求时,服务器会根据Session ID查找对应的Session数据

看代码中的构建报文部分

#include "Util.h"
#include <thread>
#include <fstream>

#define ROOT_PATH "./wwwroot"
#define HOME "index.html"
#define CRLF "\r\n"
#define CRLF_LEN 2
#define SPACE " "
#define SPACE_LEN 1

class HttpServer
{
public:
    HttpServer(uint16_t Port, std::string Ip = "")
        : ServerIp_(Ip), ServerPort_(Port), ListenSockfd_(-1)
    {
    }

    void Init()
    {
        assert(ServerPort_ > 1024);
        ListenSockfd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (ListenSockfd_ < 0)
        {
            std::cout << "have socket error: " << strerror(errno) << std::endl;
            exit(1);
        }

        sockaddr_in ServerData;
        socklen_t len = sizeof(ServerData);
        memset(&ServerData, 0, len);
        ServerData.sin_family = AF_INET;
        ServerData.sin_port = htons(ServerPort_);
        ServerData.sin_addr.s_addr = ServerIp_.empty() ? htonl(INADDR_ANY) : inet_addr(ServerIp_.c_str());

        if (bind(ListenSockfd_, (const sockaddr *)&ServerData, len))
        {
            std::cout << "bind inet data error: " << strerror(errno) << std::endl;
            exit(2);
        }

        if (listen(ListenSockfd_, 4) < 0)
        {
            std::cout << "Set listen status error: " << strerror(errno) << std::endl;
            exit(3);
        }
    }

    void Start()
    {
        while (true)
        {
            int sersockfd = accept(ListenSockfd_, nullptr, nullptr);
            if (sersockfd < 0)
            {
                std::cout << "accept error: " << strerror(errno) << std::endl;
                exit(4);
            }
            // 使用线程并行的去执行服务,不影响获取连接请求
            std::thread Thread(&HttpServer::RunThread, this, sersockfd);
            Thread.detach();
        }
    }

private:
    void RunThread(int sersockfd)
    {
        // char inbuffer[1024];
        std::string inbuffer;
        inbuffer.resize(1024, 0);
        ssize_t Index = read(sersockfd, (void *)inbuffer.c_str(), inbuffer.size());
        if (Index > 0)
        {
            std::cout << inbuffer << std::endl;
        }

        // 发响应报文
        std::string RequestPath = ROOT_PATH;
        RequestPath += GetRequestPath(inbuffer);
        std::string resources = ReadFile(RequestPath);

        std::string Response;
        Response += "HTTP/1.1 200 OK\r\n";
        Response += "Host: " + (ServerIp_.empty() ? "0.0.0.0" : ServerIp_) + ":" + \
                    std::to_string(ServerPort_) + "\r\n";
        Response += "Content-Type: text/html\r\n";
        Response += ("Content-Length: ") + (std::to_string(resources.size()) + "\r\n");
        Response += "Location: https://www.baidu.com/\r\n";
        Response += "Set-Cookie: This is my cookie\r\n";
        Response += "\r\n";
        Response += resources;

        send(sersockfd, Response.c_str(), Response.size(), 0);

        close(sersockfd);
    }

private:
    std::string ReadFile(const std::string &Path)
    {
        std::ifstream in(Path.c_str());
        if (!in.is_open())
        {
            std::cout << "open file error: " << strerror(errno) << std::endl;
            return "404";
        }
        std::string buffer;
        std::string result;
        while (std::getline(in, buffer))
        {
            result += buffer;
        }
        return result;
    }

    std::string GetRequestPath(std::string &inbuffer)
    {
        // "GET /a/b/c HTTP/1.1\r\n" --> "/a/b/c"
        size_t pos = inbuffer.find(CRLF);
        if (pos == std::string::npos)
            return "4xx";
        std::string RequestLine = inbuffer.substr(0, pos + CRLF_LEN);
        size_t SpaceOne = RequestLine.find(SPACE);
        size_t SpaceTwo = RequestLine.rfind(SPACE);
        if (SpaceOne == std::string::npos && SpaceTwo == std::string::npos)
            return "4xx";

        // 如果请求为'/'那么自动添加主页的资源
        std::string Path = RequestLine.substr(SpaceOne + SPACE_LEN, SpaceTwo - (SpaceOne + SPACE_LEN));
        if (Path.size() == 1 && Path[0] == '/')
        {
            Path += HOME;
        }
        return Path;
    }

private:
    std::string ServerIp_;
    uint16_t ServerPort_;
    int ListenSockfd_;
};

int main(int argc, char *argv[])
{
    if (argc < 2)
    {
        std::cout << "Format: ./可执行程序 [ip] port" << std::endl;
        exit(5);
    }
    std::string ip;
    uint16_t port;
    if (argc == 3)
    {
        ip = argv[1];
        port = std::stoi(argv[2]);
    }
    else
    {
        port = std::stoi(argv[1]);
    }

    HttpServer hs(port, ip);
    hs.Init();
    hs.Start();
    return 0;
}

服务器发送Cookie后,会在网站上的cookie中找到相关数据

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/625828.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

redis架构设计: redis-server的启动(硬核分析)

怎么在windows上用clion搭建redis的源码阅读环境 请看我的上一篇文章 redis启动之后都干了什么呢? 我们知道&#xff0c;redis的服务端对应的源码位置是server.c main函数是程序启动的入口 &#xff0c;下面我来一行一行的分析server.c的源码 1、定义时间函数变量 struct …

chatgpt赋能python:Python多种输出格式详解

Python多种输出格式详解 对于Python程序员来说&#xff0c;输出是非常重要的。无论是在开发阶段还是在生产环境中&#xff0c;输出都是我们调试程序和确认程序运行是否正常的重要手段。Python标准库提供了丰富的输出格式&#xff0c;本文介绍了几种常见的输出格式及其使用方法…

因为写不出拖拽移动效果,我恶补了一下Dom中的各种距离

目录 背景 JS Dom各种距离释义 第一个发现 window.devicePixelRatio 的存在 document.body、document.documentElement和window.screen的宽高区别 scrollWidth, scrollLeft, clientWidth关系 元素自身和父级元素的scrollWidth和scrollLeft关系? offsetWidth和clientWid…

【FDA】图像通过傅里叶变换改变光谱风格,实现域自适应

FDA: Fourier Domain Adaptation for Semantic Segmentation, CVPR2020 翻译&#xff1a;CVF2020邻域自适应/语义分割&#xff1a;FDA: Fourier Domain Adaptation for Semantic SegmentationFDA&#xff1a;用于语义分割的傅立叶域自适应算法_傅里叶域适应_HheeFish的博客-CS…

【TCP/IP】多进程服务器的实现(进阶) - 进程和僵尸进程

目录 僵尸(Zombie)进程 僵尸进程的产生机制 僵尸进程的危害 僵尸进程的销毁 wait函数 waitpid函数 进程管理在网络编程中十分重要&#xff0c;如果未处理好&#xff0c;将会导致出现“僵尸进程”&#xff0c;进而影响服务器端对进程的管控。 僵尸(Zombie)进程 第一次听到…

数据类型

常见的数据类型&#xff1a; int&#xff0c;整数类型&#xff08;整形&#xff09;bool&#xff0c;布尔类型str&#xff0c;字符串类型list&#xff0c;列表类型tuple&#xff0c;元组类型dict&#xff0c;字典类型set&#xff0c;集合类型float&#xff0c;浮点类型&#x…

python Web开发 flask轻量级Web框架实战项目--学生管理系统

上次发的一篇文章&#xff0c;有很多朋友私信我要后面的部分&#xff0c;那咱们就今天来一起学习一下吧&#xff0c;因为我的数据库这门课选中的课题是学生管理系统&#xff0c;所以今天就以这个课题为例子&#xff0c;从0到1去实现一个管理系统。数据库设计部分我会专门出一个…

《Java 核心技术面试》课程笔记(十二)

Java 有几种文件拷贝方式&#xff1f;哪一种最高效&#xff1f; 典型回答 Java 有多种比较典型的文件拷贝实现方式&#xff0c;比如&#xff1a;利用java.io 类库&#xff0c;直接为源文件构建一个 FileInputStream 读取&#xff0c;然后再为目标文件构建一个 FileOutputStre…

chatgpt赋能python:Python模块(Module)是什么?

Python模块&#xff08;Module&#xff09;是什么&#xff1f; Python模块&#xff08;Module&#xff09;是指一些预先编写好的代码&#xff0c;这些代码可以在程序中被引入和使用。它们可以包含可以复用的函数、常量和类。Python模块是一种封装程序代码的方法。 下载Python…

领取的AWS亚马逊云服务器到期会扣费的问题解决办法。

本篇文章主要讲解&#xff0c;领取的AWS亚马逊服务器到期后会持续扣费问题的解决办法。 作者&#xff1a;任聪聪 日期&#xff1a;2023年6月8日 关于aws服务器一年免费期限到期后扣费的问题&#xff0c;网络上的文章并不是很全&#xff0c;故此我通过个人的经验进行了如下的教程…

chatgpt赋能python:Python怎么print换行?

Python怎么print换行&#xff1f; 如果你是一个Python开发者&#xff0c;你可能遇到过需要在Python中打印输出换行的情况。本文将分享几种方式&#xff0c;让你学会如何在Python中print换行。 1. 使用"\n" 您可以在print语句中使用"\n"来表示换行。这个…

陈丹琦团队新作:单卡A100可训300亿参数模型啦!

夕小瑶科技说 原创 作者 | 智商掉了一地、ZenMoore 近年来&#xff0c;随着大模型的涌现&#xff0c;微调语言模型已经在各种下游任务上展现出了卓越的性能。然而&#xff0c;这些庞大模型的参数量常常达到数十亿甚至上百亿的级别&#xff0c;训练这样规模的模型需要消耗大量…

chatgpt赋能python:Python视图(View)在SEO中的重要性

Python视图&#xff08;View&#xff09;在SEO中的重要性 什么是Python视图&#xff1f; Python视图是指&#xff0c;在Web应用程序中&#xff0c;将业务逻辑与显示逻辑分开处理&#xff0c;并以代码的形式定义的可重用组件。它们是与URL相对应的函数或方法。Python视图可以生…

uniapp:uni-app-base 项目基础配置,开箱可用

目前&#xff08;20230605&#xff09;uni-app最新版本&#xff08;3.8.4.20230531&#xff09; 一、官网文档 uni-app官网 二、创建项目 项目目标&#xff1a;vue3tsvitevscode 创建以 typescript 开发的工程&#xff08;如命令行创建失败&#xff0c;请直接访问 gitee 下…

《面试1v1》JVM调优

我是 javapub&#xff0c;一名 Markdown 程序员从&#x1f468;‍&#x1f4bb;&#xff0c;八股文种子选手。 《面试1v1》 连载中… 面试官&#xff1a; 小伙子,说听说你JVM调优挺在行? 候选人&#xff1a; 谢谢夸奖,我对JVM调优还在学习中,远未达到在行的程度。不过日常工作…

springboot项目外卖管理 day04-文件的上传下载与菜品的新增与修改

文章目录 1、文件上传下载1.1、文件上传介绍与实现1.2、文件下载介绍与实现 2、新增菜品2.1、需求分析2.2、代码开发-梳理交互过程2.2.1、菜品分类下拉框&#xff1a;在CategoryController添加 2.2.2、新增保存 3、菜品信息分页查询3.1、代码开发-梳理交互过程对象拷贝BeanUtil…

chatgpt赋能python:Python输出方法详解:从基础print()到高级logging模块

Python输出方法详解&#xff1a;从基础print()到高级logging模块 在Python编程中&#xff0c;输出是一个必不可少的步骤。然而&#xff0c;Python提供了多种输出方法&#xff0c;如何选择最适合的方法呢&#xff1f;本文将详细介绍Python输出的不同方法&#xff0c;并给出实际…

只给大模型LeetCode编号,也能解题!大模型表现好是源于对训练数据的记忆吗?请不要迷信大模型

夕小瑶科技说 原创 作者 | Python 自从推出以来&#xff0c;ChatGPT这款智能高效的人机对话平台迅速风靡全球。人们开始广泛尝试使用ChatGPT来解决各种问题&#xff0c;无论是医学检测报告的解释&#xff0c;还是公众号文章的取名&#xff0c;甚至是论文修改润色和rebuttal撰…

微服务治理框架- - -Spring Cloud

前言&#xff1a;最近微服务很是火热&#xff0c;那么什么是微服务&#xff1f;相信小伙伴们对此也是一知半解&#xff0c;那么今天叶秋学长带领大家一起学习微服务治理框架Spring Cloud&#xff0c;快来跟着学长一起学习吧~~ 目录 对SpringCloud了解多少&#xff1f; 什么是…

【动态规划】NK刷题之DP7 连续子数组的最大乘积

【动态规划】NK刷题之DP7 连续子数组的最大乘积 1.题目2.题解3.代码部分法一&#xff1a;动态规划3.1.1 创建变量n&#xff0c;并读入数据3.1.2 创建动态数组&#xff0c;并初始化3.1.3 对动态数组断言3.1.4 读入原整形数组的数据3.1.5 创建变量ret&#xff0c;并赋初值3.1.6 循…