Linux——应用层协议HTTP

news2024/9/23 5:43:30

目录

前言

一HTTP协议

1认识URL 

2urlencode 和 urldecode

3协议请求与响应格式 

 4代码实现

二HTTP常见Header

1Connection的报头

2Cookie和Session

2.1Cookie定义

2.2Cookie使用

2.3Session定义

2.4Session实现

四HTTP响应起始行

1状态码

2永久重定向与临时重定向

2.1从技术角度上

2.2从理解角度上 

2.3实现

五HTTP方法 

 1GET与POST

​编辑

 2实现


前言

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

它是一个至关重要的协议:定义了客户端(如浏览器)与服务器之间如何通信,以交换或传输超文本(如 HTML 文档)。而且:HTTP 协议是一个无连接、 无状态的协议, 即每次请求都需要建立新的连接, 且服务器不会保存客户端的状态信息。


一HTTP协议

1认识URL 

平时我们俗称的 "网址" 其实就是说的 URL(Uniform Resource Locator, 统一资源定位器

我们在访问服务器时,不是要有IP和端口号吗?怎么在URL上看不到?

那是因为域名使用时,会自动转化成IP地址(DNS);而端口号是跟协议名强关联的(如现实生活中110与报警电话是一样的类似):当服务器发起请求时,会自动拼接上端口号(80)

因此:在http中端口号是默认被忽略的 

我们平时在逛淘宝时的看到的:网页,视频,图片,音频...叫做超文本资源;那么是谁来完成资源处理的呢?

这些工作是由http进行资源获取后进行推送给进行访问的用户;在没有被访问时,这些资源都是在服务器端储存的; 而我们学习Linux的都知道:Linux操作系统很适合用来作为后端服务器:在LInux中一切皆文件:http在服务器找资源时,怎么准确无误找到对应的资源?

        通过路径(路径标识唯一性):在域名后的字符串就是对应访问服务器资源的路径;但这是LInux中的根目录吗?

        不是:这就做web根目录(如何一个文件都是web根目录的起点);

        现在:我们用了域名:标识唯一主机;端口号(忽略):表示唯一服务进程;路径:该主机上的唯一的文件资源:这三者共同组成了在互联网中唯一的文件资源!!

注:http与https两者是有很强的联系的(只不过https多了一层安全层),在学习时我们就把它们进行统一成http来学习

2urlencode 和 urldecode

像 / ? : 等这样的字符, 已经被 url 当做特殊意义理解了. 因此这些字符不能随意出现;
比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义;
转义的规则如下:

将需要转码的字符转为 16 进制, 然后从右到左, 取 4 位(不足 4 位直接处理), 每 2 位做一位, 前面加上%, 编码成%XY 格式

"+" 被转义成了 "%2B"
urldecode 就是 urlencode 的逆过程;

转化工具:UrlEncode编码/UrlDecode解码 -工具

3协议请求与响应格式 

请求格式  

报文与有效载荷进行分离(封装)

空行(\r\n)

在请求头部中有描述请求正文长度的属性Content-Length:XXX

响应格式也类似

 4代码实现

HTTP协议在网络传输时也是要进行序列化与反序列化,但不同的是:它没有用第三方库(json...):它有着自己的一套格式(也就是我们上面的格式)

现在让我们用代码的形式来进行序列化与反序列化

注:以下代码时从:Linux——应用层自定义协议与序列化-CSDN博客中的实现网络计算器代码来进行修改使用的~

//Socket.hpp
#pragma once
#include <iostream>
#include <cstring>
#include <functional>
#include <unistd.h>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <pthread.h>

#include "Log.hpp"
#include "InetAddr.hpp"

namespace socket_ns
{
    using namespace log_ns;
    class Socket; // 声明
    using SockPtr = std::shared_ptr<Socket>;

    enum
    {
        SOCK_ERROR = 1,
        BIND_ERROR,
        LISTEN_ERROR,
    };

    const static int gbacklog = 8;

    // 模板方法模式
    class Socket
    {
    public:
        virtual void CreateSocket() = 0;
        virtual void InitSocket(uint16_t port, int backlog = gbacklog) = 0;
        virtual SockPtr AcceptSocket(InetAddr *addr) = 0;                     // 对象/变量
        virtual bool ConnectSocket(uint16_t port, const std::string &ip) = 0; // clinet连接成功与失败

        virtual int Sockfd() = 0;
        virtual void Close() = 0;
        virtual ssize_t Recv(std::string *out) = 0;
        virtual ssize_t Send(const std::string &in) = 0;

    public:
        void Tcp_ServerSocket(uint16_t port)
        {
            CreateSocket();
            InitSocket(port);
        }
        bool Tcp_ClientSocket(uint16_t port, const std::string &ip)
        {
            CreateSocket();
            return ConnectSocket(port, ip);
        }
    };

    class TcpSocket : public Socket
    {
    public:
        TcpSocket()
        {
        }
        TcpSocket(int sockfd)
            : _sockfd(sockfd)
        {
        }

        ~TcpSocket()
        {
        }
        virtual void CreateSocket() override
        {
            _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
            if (_sockfd < 0)
            {
                LOG(FATAL, "socket fail\n");
                exit(SOCK_ERROR);
            }
            LOG(INFO, "socket sucess sockfd: %d\n", _sockfd);
        }
        virtual void InitSocket(uint16_t port, int backlog) override
        {
            struct sockaddr_in perr;
            memset(&perr, 0, sizeof(perr));
            perr.sin_family = AF_INET;
            perr.sin_port = htons(port);
            perr.sin_addr.s_addr = INADDR_ANY;
            if (::bind(_sockfd, (struct sockaddr *)&perr, sizeof(perr)) < 0)
            {
                LOG(FATAL, "bind fail\n");
                exit(BIND_ERROR);
            }
            LOG(INFO, "bind sucess\n");
            if (::listen(_sockfd, backlog) < 0)
            {
                LOG(ERROR, "listen fail\n");
                exit(LISTEN_ERROR);
            }
            LOG(INFO, "listen sucess\n");
        }
        virtual SockPtr AcceptSocket(InetAddr *addr) override
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = ::accept(_sockfd, (struct sockaddr *)&client, &len);
            if (sockfd < 0)
            {
                LOG(ERROR, "accept fail\n");
                return nullptr;
            }
            *addr = client;
            LOG(INFO, "get a new link %s sockfd: %d\n", addr->User().c_str(), sockfd);
            return std::make_shared<TcpSocket>(sockfd); // c++14
        }

        virtual bool ConnectSocket(uint16_t port, const std::string &ip) override
        {
            struct sockaddr_in server;
            memset(&server, 0, sizeof(server));
            server.sin_family = AF_INET;
            server.sin_port = htons(port);
            inet_pton(AF_INET, ip.c_str(), &server.sin_addr);
            int n = connect(_sockfd, (struct sockaddr *)&server, sizeof(server));
            if (n < 0)
            {
                return false;
            }
            return true;
        }

        virtual int Sockfd() override
        {
            return _sockfd;
        }
        virtual void Close() override
        {
            if (_sockfd > 0)
            {
                ::close(_sockfd);
            }
        }
        virtual ssize_t Recv(std::string *out) override
        {
            char buffer[4096];
            ssize_t n = ::recv(_sockfd, buffer, sizeof(buffer) - 1, 0);
            if (n > 0)
            {
                buffer[n] = 0;
                *out += buffer; // 细节
            }
            return n;
        }
        virtual ssize_t Send(const std::string &in) override
        {
            return ::send(_sockfd, in.c_str(), in.size(), 0);
        }

    private:
        int _sockfd; // 两个角色
    };
    //class UdpServer:public Socket
    //{}
}

//TcpServer.hpp
#pragma once
#include <functional>
#include "Socket.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"

using namespace socket_ns;

static const int gport = 8888;

using service_io_t = std::function<std::string(std::string)>;

class TcpServer
{
public:
    TcpServer(service_io_t server, uint16_t port = gport)
        : _server(server), _port(port), _listensockfd(std::make_shared<TcpSocket>()), _isrunning(false)
    {
        _listensockfd->Tcp_ServerSocket(_port); // socket bind listen
    }

    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            InetAddr client;
            SockPtr newsock = _listensockfd->AcceptSocket(&client);
            if (newsock == nullptr)
                continue; // 断开连接
            // 进行服务

            // version 2 -- 多线程 -- 不能关fd -- 共享
            pthread_t pid;

            PthreadDate *date = new PthreadDate(newsock, this, client);
            pthread_create(&pid, nullptr, Excute, date);
        }
        _isrunning = false;
    }

    struct PthreadDate
    {
        SockPtr _sockfd;
        TcpServer *_self;
        InetAddr _addr;
        PthreadDate(SockPtr sockfd, TcpServer *self, const InetAddr &addr)
            : _sockfd(sockfd),
              _self(self),
              _addr(addr)
        {
        }
    };
    static void *Excute(void *args)
    {
        pthread_detach(pthread_self());
        PthreadDate *date = static_cast<PthreadDate *>(args);

        std::string requeststr;
        date->_sockfd->Recv(&requeststr);

        std::string reponsestr=date->_self->_server(requeststr); // 进行回调
        date->_sockfd->Send(reponsestr);
        date->_sockfd->Close(); // 关闭 sockfd

        delete date;
        return nullptr;
    }

private:
    service_io_t _server;
    uint16_t _port;

    SockPtr _listensockfd;
    bool _isrunning;
};

//Log.hpp
#pragma once
#include <pthread.h>
#include <fstream>
#include <syscall.h>
#include <stdarg.h>
#include <unistd.h>
#include <cstring>

namespace log_ns
{
    enum
    {
        DEBUG = 1,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };

    std::string Getlevel(int level)
    {
        switch (level)
        {
        case DEBUG:
            return "DEBUG";
            break;
        case INFO:
            return "INFO";
            break;
        case WARNING:
            return "WARNING";
            break;
        case ERROR:
            return "ERROR";
            break;
        case FATAL:
            return "FATAL";
            break;
        default:
            return "";
            break;
        }
    }

    std::string Gettime()
    {
        time_t now = time(nullptr);
        struct tm *time = localtime(&now);
        char buffer[128];
        snprintf(buffer, sizeof(buffer), "%d-%02d-%02d %02d:%02d:%02d",
                 time->tm_year + 1900,
                 time->tm_mon + 1,
                 time->tm_mday,
                 time->tm_hour,
                 time->tm_min,
                 time->tm_sec);
        return buffer;
    }

    struct log_message
    {
        std::string _level;
        int _id;
        std::string _filename;
        int _filenumber;
        std::string _cur_time;
        std::string _message;
    };

#define SCREAM 1
#define FILE 2

#define DEVELOP 3
#define OPERATION 4

    const std::string gpath = "./log.txt";
    pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER;

    class log
    {
    public:
        log(const std::string &path = gpath, const int status = DEVELOP)
            : _mode(SCREAM), _path(path), _status(status)
        {
        }
        void SelectMode(int mode)
        {
            _mode = mode;
        }
        void SelectStatus(int status)
        {
            _status = status;
        }

        void PrintScream(const log_message &le)
        {
            printf("[%s][%d][%s][%d][%s] %s",
                   le._level.c_str(),
                   le._id,
                   le._filename.c_str(),
                   le._filenumber,
                   le._cur_time.c_str(),
                   le._message.c_str());
        }
        void PrintFile(const log_message &le)
        {
            std::fstream in(_path, std::ios::app);
            if (!in.is_open())
                return;
            char buffer[1024];
            snprintf(buffer, sizeof(buffer), "[%s][%d][%s][%d][%s] %s",
                     le._level.c_str(),
                     le._id,
                     le._filename.c_str(),
                     le._filenumber,
                     le._cur_time.c_str(),
                     le._message.c_str());
            in.write(buffer, strlen(buffer)); // 不用sizeof
            in.close();
        }
        void PrintLog(const log_message &le)
        {
            // 过滤
            if (_status == OPERATION)
                return;

            // 线程安全
            pthread_mutex_lock(&gmutex);

            switch (_mode)
            {
            case SCREAM:
                PrintScream(le);
                break;
            case FILE:
                PrintFile(le);
                break;
            default:
                break;
            }

            pthread_mutex_unlock(&gmutex);
        }
        void logmessage(const std::string &filename, int filenumber,int level, const char *message, ...)
        {
            log_message le;
            le._level = Getlevel(level);
            le._id = syscall(SYS_gettid);
            le._filename = filename;
            le._filenumber = filenumber;
            le._cur_time = Gettime();

            va_list vt;
            va_start(vt, message);
            char buffer[128];
            vsnprintf(buffer, sizeof(buffer), message, vt);
            va_end(vt);
            le._message = buffer;

            // 打印日志
            PrintLog(le);
        }
        ~log()
        {
        }

    private:
        int _mode;
        std::string _path;
        int _status;
    };
    // 方便上层调用
    log lg;

// ##不传时可忽略参数
#define LOG(level, message, ...)                                          \
    do                                                                    \
    {                                                                     \
        lg.logmessage(__FILE__, __LINE__, level,message, ##__VA_ARGS__); \
    } while (0)

#define SleftScream()          \
    do                         \
    {                          \
        lg.SelectMode(SCREAM_TYPE); \
    } while (0)
#define SleftFile()          \
    do                       \
    {                        \
        lg.SelectMode(FILE_TYPE); \
    } while (0)
    
}

//InetAddr.hpp
#pragma once
#include<iostream>
#include<string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

class InetAddr
{
    void ToHost()
    {
        //_ip=inet_ntoa(_addr.sin_addr);//4字节地址->char*
        _port=ntohs(_addr.sin_port);

        char buffer[124];
        inet_ntop(AF_INET,&_addr.sin_addr,buffer,sizeof(buffer));
        _ip=buffer;
    }
public:
    InetAddr()
    {
        
    }
    InetAddr(const struct sockaddr_in& addr)
    :_addr(addr)
    {
        ToHost();
    }

    std::string Ip()
    {
        return _ip;
    }

    uint16_t Port()
    {
        return _port;
    }

    struct sockaddr_in Addr()
    {
        return _addr;
    }

    bool operator==(const InetAddr& ad)
    {
        return (this->_ip==ad._ip&&this->_port==ad._port);
    }

    std::string User()
    {
        std::string tmp=_ip+" "+std::to_string(_port)+":";
        return tmp;
    } 
    
private:
    std::string _ip;
    uint16_t _port;
    struct sockaddr_in _addr;
};

//Http.hpp
#pragma once

#include<iostream>
#include<string>
#include<sstream>
#include<vector>
#include<unordered_map>

const std::string base_sep("\r\n");
const std::string line_sep(": ");

class HttpRequest
{
private:
    std::string Getline(std::string &requeststr)
    {
        size_t pos = requeststr.find(base_sep);
        if (pos == std::string::npos) return "";

        std::string line = requeststr.substr(0, pos);
        requeststr.erase(0, line.size() + base_sep.size());//内容和\r\n都清除

        return line.empty() ? base_sep : line;//空行使line为空要特殊判断
    }

    void LineDeserialize()
    {
        std::stringstream ss(_request_line);
        ss >> _method >> _url >> _version;//stringstream插入会省略空格
    }

    void HeadDeserialize()
    {
        for (auto &head : _request_header)
        {
            size_t pos = head.find(line_sep);
            if (pos == std::string::npos)
                continue;
            std::string k = head.substr(0, pos);
            std::string v = head.substr(pos + line_sep.size());
            if (k.empty() || v.empty())
                continue;
            _header.insert(std::make_pair(k, v));
        }
    }

public:
    HttpRequest() : _null_line(base_sep)
    {}

    void Deserialize(std::string &requeststr)//完整请求字符串进行反序列化
    {
        _request_line = Getline(requeststr);
        std::string line;
        do
        {
            line = Getline(requeststr);
            if (line.empty() ||  line == base_sep)
                break;
            _request_header.push_back(line);
        } while (true);
        if (!requeststr.empty())
            _request_main = requeststr;
        
        //更具体的反序列化
        LineDeserialize();
        HeadDeserialize();
    }

    void print()
    {
        std::cout << "------------------------------" << std::endl;
        std::cout << "@@@" << _request_line << std::endl;
        for (auto &header : _request_header)
        {
            std::cout << "###" << header << std::endl;
        }
        std::cout << "---" << _null_line;
        std::cout << ">>>" << _request_main << std::endl;

        std::cout << "更具体进行解析" << std::endl;
        std::cout << "method: " << _method << std::endl;
        std::cout << "url: " << _url << std::endl;
        std::cout << "version: " << _version << std::endl;
        for (auto &header : _header)
        {
            std::cout << header.first << "->" << header.second << std::endl;
        }
    }

private:
    // 基本格式
    std::string _request_line;
    std::vector<std::string> _request_header;
    std::string _null_line;
    std::string _request_main;

    // 具体解析
    std::string _method;
    std::string _url;
    std::string _version;
    std::unordered_map<std::string, std::string> _header;
};

class Http
{
public:
    Http()
    {}
    ~Http()
    {}
    std::string Server(std::string requeststr)
    {
        HttpRequest rt;
        rt.Deserialize(requeststr);
        rt.print();
        return "";
    }
};

接着我们来写HttpReponse:给浏览器进行应答(获取资源(wwwroot中)) 通过path来进行获取

#pragma once

#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <unordered_map>
#include <sys/stat.h>

const std::string base_sep = "\r\n";
const std::string line_sep = ": ";
const std::string profixpath="wwwroot";
const std::string homepage = "index.html";
const std::string space_sep = " ";

class HttpRequest
{
private:
    std::string Getline(std::string &requeststr)
    {
        size_t pos = requeststr.find(base_sep);
        if (pos == std::string::npos)
            return "";

        std::string line = requeststr.substr(0, pos);
        requeststr.erase(0, line.size() + base_sep.size()); // 内容和\r\n都清除

        return line.empty() ? base_sep : line; // 空行使line为空要特殊判断
    }

    void LineDeserialize()
    {
        std::stringstream ss(_request_line);
        ss >> _method >> _url >> _version; // stringstream插入会省略空格

        //url进行解析
        _path+=_url;
        if(_url=="/")
        {
            _path+=homepage;
        }
    }

    void HeadDeserialize()
    {
        for (auto &head : _request_header)
        {
            size_t pos = head.find(line_sep);
            if (pos == std::string::npos)
                continue;
            std::string k = head.substr(0, pos);
            std::string v = head.substr(pos + line_sep.size());
            if (k.empty() || v.empty())
                continue;
            _header.insert(std::make_pair(k, v));
        }
    }

public:
    HttpRequest() : _null_line(base_sep),_path(profixpath)
    {
    }
    std::string Path()
    {
        return _path;
    }
    void Deserialize(std::string &requeststr) // 完整请求字符串进行反序列化
    {
        _request_line = Getline(requeststr);
        std::string line;
        do
        {
            line = Getline(requeststr);
            if (line.empty() || line == base_sep)
                break;
            _request_header.push_back(line);
        } while (true);
        if (!requeststr.empty())
            _request_main = requeststr;

        // 更具体的反序列化
        LineDeserialize();
        HeadDeserialize();
    }

    void print()
    {
        std::cout << "------------------------------" << std::endl;
        std::cout << "@@@" << _request_line << std::endl;
        for (auto &header : _request_header)
        {
            std::cout << "###" << header << std::endl;
        }
        std::cout << "---" << _null_line;
        std::cout << ">>>" << _request_main << std::endl;
        std::cout << "更具体进行解析" << std::endl;
        std::cout << "method: " << _method << std::endl;
        std::cout << "url: " << _url << std::endl;
        std::cout << "version: " << _version << std::endl;
        for (auto &header : _header)
        {
            std::cout << header.first << "->" << header.second << std::endl;
        }
    }

private:
    // 基本格式
    std::string _request_line;
    std::vector<std::string> _request_header;
    std::string _null_line;
    std::string _request_main;

    // 具体解析
    std::string _method;
    std::string _url;
    std::string _version;
    std::unordered_map<std::string, std::string> _header;

    std::string _path;   // 访问资源路径
};

class HttpReponse
{
public:
    HttpReponse():_null_line(base_sep),_version("HTTP/1.1")
    {}
    void AddCode(int code)
    {
        _code=code;
        _des="OK";
    }
    void AddHeader(std::string k,std::string v)
    {
        _header.insert({k,v});
    }
    void AddMain(std::string main)
    {
        _request_main=main;
    }
    std::string Serialize()
    {
        _request_line=_version+space_sep+_code+space_sep+_des+base_sep;
        for(auto& header:_header)
        {
            std::string key = header.first;
            std::string value=header.second;
            _request_header.push_back(key+line_sep+value+base_sep);
        }
        //拼接格式
        std::string jsonstr;
        jsonstr+=_request_line;
        for(auto &header:_request_header)
        {
            jsonstr+=header;
        }
        jsonstr+=_null_line;
        jsonstr+=_request_main;

        return jsonstr;
    }
private:
    //基本属性
    std::string _version;
    std::string _code;
    std::string _des;
    std::unordered_map<std::string,std::string> _header;

    //基本格式
    std::string _request_line;
    std::vector<std::string> _request_header;
    std::string _null_line;
    std::string _request_main;

};

class Http
{
public:
    Http()
    {
    }
    ~Http()
    {
    }
    std::string DealPath(std::string path)
    {
        std::ifstream in(path);
        if(!in.is_open()) return "";
        // in.seekg(0,in.end);
        // int filesize=in.tellg();
        // in.seekg(0,in.beg);
        struct stat buff;
        int n=::stat(path.c_str(),&buff);
        int filesize=buff.st_size;

        std::string content;
        content.resize(filesize);
        in.read((char*)content.c_str(),filesize);
        in.close();
        return content;
    }
    std::string Server(std::string requeststr)
    {
        //请求
        HttpRequest req;
        req.Deserialize(requeststr);
        req.print();

        //上层处理
        std::string content=DealPath(req.Path());

        //应答
        HttpReponse rep;
        rep.AddCode(20);
        rep.AddHeader("Content-Length",std::to_string(content.size()));
        rep.AddMain(content);

        return rep.Serialize();
    }
};

//wwwroot文件夹->给浏览器应答时获取的资源都在这里面
//index.html
<!DOCTYPE html>
<html>

<head>
    <title>测试网站</title>
    <meta charset="UTF-8">
</head>

<body>
    <div id="container" style="width:800px">
        <div id="header" style="background-color:#FFA500;">
            <h1 style="margin-bottom:0;">网页的主标题</h1>
        </div>
        <div id="menu" style="background-color:#FFD700;height:200px;width:100px;float:left;">
            <b>Menu</b><br>
            HTML<br>
            CSS<br>
            JavaScript
        </div>
        <div id="content" style="background-color:#EEEEEE;height:200px;width:700px;float:left;">
            内容就在这里</div>
        <div id="footer" style="background-color:#FFA500;clear:both;text-align:center;">
            Copyright © w3cschool.cn</div>
    </div>
    <div>
        <a href="/register.html">登录页面</a>
        <a href="/a.html">404</a>
        <a href="/redir">重定向</a>
    </div>
    <div>
        <form action="/inexit" method="GET">
            用户名: <input type="text" name="user" value="."><br>
            密码: <input type="password" name="password" value=""><br>
            <input type="submit" value="提交">
        </form>
    </div>
    <div>
        <img src="/image/1.jpg" alt="图片">
    </div>


</body>

</html>

获得一个完整的网页,浏览器要先得到html;根据html的标签,检测出我们还要获取其它资源,浏览器会继续发起请求!!

二HTTP常见Header

字段名含义样例
Connection请求完后是关闭
还是保持连接
Connection: keep-alive 或
Connection: close
Cookie客户端发送给服
务器的 HTTP
cookie 信息
Cookie: session_id=abcdefg12345;
user_id=123
Content-Type实体主体的媒体
类型
Content-Type: application/x-www
form-urlencoded (对于表单提交) 或
Content-Type: application/json (对于
JSON 数据)
Content-Length实体主体的字节
大小
Content-Length: 150
Accept客户端可接受的
响应内容类型
Accept:
text/html,application/xhtml+xml,app
lication/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
Authorization认证信息, 如用
户名和密码
Authorization: Basic
QWxhZGRpbjpvcGVuIHNlc2FtZQ== (Base64
编码后的用户名:密码)
Cache-Control缓存控制指令请求时: Cache-Control: no-cache 或
Cache-Control: max-age=3600; 响应
时: Cache-Control: public, max
age=3600
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
Referer请求的来源 URLReferer:
http://www.example.com/previous_pag
e.html

1Connection的报头

它主要用于控制和管理客户端与服务器之间的连接状态

在HTTP 1.1中它默认是长连接:允许客户端和服务器在请求/响应完成后不立即关闭 TCP 连接, 以便在同一个连接上发送多个请求和接收多个响应

这也就节省了资源:每次想客户端请求时不用再重新建立tcp连接了

而在上面的代码中,我们写的服务器是不是长连接(当然想改的话可以按照写网络版本计算机的思路:根据Content-Type来判断需不需要处理,向客户端进行返回(这个过程是循环的))

2Cookie和Session

在我们平时访问某些网站时(如b站),有些视频不用登录能观看,而有些视频要进行登录才能观看;然而我们在前面说了:HTTP协议是无连接,无状态的:网站要想识别用户身份怎么办?

登录完成后,即使后面我重新打开该网页,网站也还是能够识别我的身份?

这些问题都通过Cookie和Session技术来进行登录会话管理进行解决!

2.1Cookie定义

Cookie(也称为 Web Cookie) 是服务器发送到用户浏览器并保存在浏览器上的一小块数据(文件/内存存储), 它会在浏览器之后向同一服务器再次发起请求时被携带并发送到服务器

2.2Cookie使用

在 HTTP 响应头中添加Cookie,客户端(如浏览器) 就能获取并自行设置并保存

Set-Cookie: <name>=<value>
//其中 <name> 是 Cookie 的名称, <value> 是 Cookie 的值
//一个完整的Cookie
Set-Cookie: username=peter; expires=Thu, 18 Dec 2024 12:00:00 UTC; 
path=/; domain=.example.com; secure; HttpOnly
属性描述
username(代码验证)peter这是 Cookie 的名称和值, 标识用户
名为"peter"。
expires(代码验证)Thu, 18 Dec
2024 12:00:00
UTC
指定 Cookie 的过期时间。 在这个例
子中, Cookie 将在 2024 年 12 月 18
日 12:00:00 UTC 后过期。
path(代码验证)/定义 Cookie 的作用范围。 这里设置
为根路径/, 意味着 Cookie
对.example.com 域名下的所有路径
都可用。
domain.example.com指定哪些域名可以接收这个
Cookie。 点前缀(.) 表示包括所有
子域名。
secure-指示 Cookie 只能通过 HTTPS 协议
发送, 不能通过 HTTP 协议发送, 增
加安全性。
HttpOnly-阻止客户端脚本(如 JavaScript) 访
问此 Cookie, 有助于防止跨站脚本
攻击(XSS) 。

Cookie报头使用(从上面的代码进行修改)

//Http.hpp
#pragma once

#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <unordered_map>
#include <functional>
#include <sys/stat.h>

const std::string base_sep = "\r\n";
const std::string line_sep = ": ";
const std::string profixpath = "wwwroot";
const std::string homepage = "index.html";
const std::string httpversion = "HTTP/1.1";
const std::string space_sep = " ";
const std::string profix_sep = ".";
const std::string arg_sep = "?";

class HttpRequest
{
private:
    std::string Getline(std::string &requeststr)
    {
        size_t pos = requeststr.find(base_sep);
        if (pos == std::string::npos)
            return "";

        std::string line = requeststr.substr(0, pos);
        requeststr.erase(0, line.size() + base_sep.size()); // 内容和\r\n都清除

        return line.empty() ? base_sep : line; // 空行使line为空要特殊判断
    }

    void LineDeserialize()
    {
        std::stringstream ss(_request_line);
        ss >> _method >> _url >> _version; // stringstream插入会省略空格

        if (strcasecmp(_method.c_str(), "GET") == 0)
        {
            // GET获取资源放在正文 /login?user=zhangsan&password=123456
            auto pos = _url.find(arg_sep);
            if (pos != std::string::npos)
            {
                _request_main = _url.substr(pos + arg_sep.size());
                _url.resize(pos);
            }
        }

        // url进行解析
        _path += _url;
        if (_path[_path.size() - 1] == '/')
        {
            _path += homepage;
        }

        // wwwroot/1.jpg
        auto pos = _path.rfind(profix_sep);
        if (pos != std::string::npos)
        {
            _profix = _path.substr(pos);
        }
    }

    void HeadDeserialize()
    {
        for (auto &head : _request_header)
        {
            size_t pos = head.find(line_sep);
            if (pos == std::string::npos)
                continue;
            std::string k = head.substr(0, pos);
            std::string v = head.substr(pos + line_sep.size());
            if (k.empty() || v.empty())
                continue;
            _header.insert({k, v});
        }
    }

public:
    HttpRequest() : _null_line(base_sep), _path(profixpath)
    {
    }
    std::string Path()
    {
        return _path;
    }
    std::string Profix()
    {
        return _profix;
    }
    std::string Main()
    {
        std::cout << "method: " << _method << " path: " << _path << " args: " << _request_main << std::endl;
        return _request_main;
    }
    void Deserialize(std::string &requeststr) // 完整请求字符串进行反序列化
    {
        _request_line = Getline(requeststr);
        std::string line;
        do
        {
            line = Getline(requeststr);
            if (line.empty() || line == base_sep)
                break;
            _request_header.push_back(line);
        } while (true);
        if (!requeststr.empty())
            _request_main = requeststr;

        // 更具体的反序列化
        LineDeserialize();
        HeadDeserialize();
    }

    void print()
    {
        std::cout << "---------------request------------" << std::endl;
        std::cout << "@@@" << _request_line << std::endl;
        for (auto &header : _request_header)
        {
            std::cout << "###" << header << std::endl;
        }
        std::cout << "---" << _null_line;
        std::cout << ">>>" << _request_main << std::endl;
        std::cout << "更具体进行解析" << std::endl;
        std::cout << "method: " << _method << std::endl;
        std::cout << "url: " << _url << std::endl;
        std::cout << "version: " << _version << std::endl;
        for (auto &header : _header)
        {
            std::cout << header.first << "->" << header.second << std::endl;
        }
    }

private:
    // 基本格式
    std::string _request_line;
    std::vector<std::string> _request_header;
    std::string _null_line;
    std::string _request_main;

    // 具体解析
    std::string _method;
    std::string _url;
    std::string _version;
    std::unordered_map<std::string, std::string> _header;

    std::string _path;   // 访问资源路径
    std::string _profix; // 资源后缀
};

class HttpReponse
{
public:
    HttpReponse() : _null_line(base_sep), _version(httpversion)
    {
    }
    void AddCode(int code, const std::string &des)
    {
        _code = code;
        _des = des;
    }
    void AddHeader(std::string k, std::string v)
    {
        _header.insert({k, v});
    }
    void AddMain(const std::string &main)
    {
        _reponse_main = main;
    }
    std::string Serialize()
    {
        _reponse_line = _version + space_sep + std::to_string(_code) + space_sep + _des + base_sep;
        for (auto &header : _header)
        {
            std::string key = header.first;
            std::string value = header.second;
            _reponse_header.push_back(key + line_sep + value + base_sep);
        }
        // 拼接格式
        std::string jsonstr;
        jsonstr += _reponse_line;
        for (auto &header : _reponse_header)
        {
            jsonstr += header;
        }
        jsonstr += _null_line;
        jsonstr += _reponse_main;

        return jsonstr;
    }

private:
    // 基本属性
    std::string _version;
    int _code;
    std::string _des;
    std::unordered_map<std::string, std::string> _header;

    // 基本格式
    std::string _reponse_line;
    std::vector<std::string> _reponse_header;
    std::string _null_line;
    std::string _reponse_main;
};

using fun_t = std::function<HttpReponse(HttpRequest &)>;

class Http
{
public:
    Http()
    {
        _ContentType_Table.insert({".html", "text/html"});
        _ContentType_Table.insert({".jpg", "image/jpeg"});

        _CodeDes_Table.insert({302, "Found"});
        _CodeDes_Table.insert({401, "Not Fund"});
    }
    ~Http()
    {
    }
    std::string DealPath(std::string path)
    {
        std::ifstream in(path);
        if (!in.is_open())
            return "";
        // in.seekg(0,in.end);
        // int filesize=in.tellg();
        // in.seekg(0,in.beg);
        struct stat buff;
        int n = ::stat(path.c_str(), &buff);
        int filesize = buff.st_size;

        std::string content;
        content.resize(filesize);
        in.read((char *)content.c_str(), filesize);
        in.close();
        return content;
    }
    // #define TEST
    std::string Server(std::string &requeststr)
    {
#ifdef TEST
        std::cout << "-------------------------------" << std::endl;
        std::cout << requeststr;
        std::string jsonstr = "HTTP/1.1 200 OK\r\n";
        jsonstr += "Content-Type: text/html\r\n";
        jsonstr += "\r\n";
        jsonstr += "<html><h1> Hello Http </h1></html>";
        return jsonstr;
#else
        // 请求
        HttpRequest req;
        req.Deserialize(requeststr);
        req.print();

        req.Main();

        // 应答
        HttpReponse rep;
        if (req.Path() == "wwwroot/redir")
        {
            std::string redir_path = "https://www.qq.com";
            rep.AddCode(302, _CodeDes_Table[302]);
            rep.AddHeader("Location", redir_path);
        }
    
        std::string content=DealPath(req.Path());

        rep.AddCode(20, "OK");
        rep.AddHeader("Content-Length", std::to_string(content.size()));
        rep.AddHeader("Content-Type", _ContentType_Table[req.Profix()]);

        rep.AddHeader("Set-Cookie", "username=zhangsan; path=/a/b;");

        rep.AddMain(content);
        
        return rep.Serialize();
#endif
    }
    void InsertServer(const std::string &path, fun_t server)
    {
        // path: /login
        std::string s = profixpath + path;
        _Server[s] = server;
    }
    bool IsServerExist(std::string path)
    {
        auto pos = _Server.find(path);
        if (pos == _Server.end())
            return false;
        return true;
    }

    std::string GetMon(int pos)
    {
        std::vector<std::string> Mon = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
        return Mon[pos];
    }
    std::string GetDay(int pos)
    {
        std::vector<std::string> Week = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
        return Week[pos];
    }

    std::string ExpireTime(int t)
    {
        // Thu, 18 Dec 2024 12:00:00 UTC
        time_t timout = time(nullptr) + t; // 超时时间
        struct tm *tm = gmtime(&timout);
        char timebuffer[1024];
        snprintf(timebuffer, sizeof(timebuffer), "%s, %02d %s %d %02d:%02d:%02d UTC",
                 GetDay(tm->tm_wday).c_str(),
                 tm->tm_mday,
                 GetMon(tm->tm_mon).c_str(),
                 tm->tm_year + 1900,
                 tm->tm_hour,
                 tm->tm_min,
                 tm->tm_sec);
        return timebuffer;
    }

private:
    std::unordered_map<std::string, std::string> _ContentType_Table;
    std::unordered_map<int, std::string> _CodeDes_Table;

    std::unordered_map<std::string, fun_t> _Server;
};


请求/时

请求/a/b时 

如果你想往Cookie添加用户和密码:就需要添加两个Cookie(一个用户名,一个密码)

我们写入的是测试数据, 如果写入的是用户的私密数据呢? 比如, 用户名密码,浏览痕迹等。

Cookie会在浏览器(用户端)保存,非常容易被人盗取,更重要的是用户的私密数据;为了解决问题,我们引入HTTP Session

2.3Session定义

HTTP Session 是服务器用来跟踪用户与服务器交互期间用户状态的机制。 由于 HTTP协议是无状态的(每个请求都是独立的) , 因此服务器需要通过 Session 来记住用户的信息

2.4Session实现

原理:

a.当用户首次访问网站时, 服务器会为用户创建一个唯一的 Session ID, 并通过Cookie 将其发送到客户端。
b.客户端在之后的请求中会携带这个 Session ID, 服务器通过 Session ID 来识别用户, 从而获取用户的会话信息。
c.服务器通常会将 Session 信息存储在内存、 数据库或缓存中。(如Redis)

 

//主要代码

//Httprotocol.hpp
#pragma once

#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <memory>
#include <ctime>
#include <functional>
#include "TcpServer.hpp"
#include "Session.hpp" // 引入session

const std::string HttpSep = "\r\n";
// 可以配置的
const std::string homepage = "index.html";
const std::string wwwroot = "./wwwroot";

class HttpRequest
{
public:
    HttpRequest() : _req_blank(HttpSep), _path(wwwroot)
    {
    }
    bool GetLine(std::string &str, std::string *line)
    {
        auto pos = str.find(HttpSep);
        if (pos == std::string::npos)
            return false;
        *line = str.substr(0, pos); // \r\n
        str.erase(0, pos + HttpSep.size());
        return true;
    }
    void Parse()
    {
        // 解析出来url
        std::stringstream ss(_req_line);
        ss >> _method >> _url >> _http_version;

        // 查找cookie
        std::string prefix = "Cookie: "; // 写入: Set-Cookie: sessionid=1234 提交: Cookie: sessionid=1234
        for (auto &line : _req_header)
        {
            std::string cookie;
            if (strncmp(line.c_str(), prefix.c_str(), prefix.size()) == 0) // 找到了
            {
                cookie = line.substr(prefix.size()); // 截取"Cookie: "之后的就行了
                _cookies.emplace_back(cookie);
                break;
            }
        }

        // 查找sessionid
        // sessionid=1234
        prefix = "sessionid=";
        for (const auto &cookie : _cookies)
        {
            if (strncmp(cookie.c_str(), prefix.c_str(), prefix.size()) == 0)
            {
                _sessionid = cookie.substr(prefix.size()); // 截取"sessionid="之后的就行了
                // std::cout << "_sessionid: " << _sessionid << std::endl;
            }
        }
    }
    std::string Url()
    {
        return _url;
    }
    std::string SessionId()
    {
        return _sessionid;
    }
    bool Deserialize(std::string &request)
    {
        std::string line;
        bool ok = GetLine(request, &line);
        if (!ok)
            return false;
        _req_line = line;

        while (true)
        {
            bool ok = GetLine(request, &line);
            if (ok && line.empty())
            {
                _req_content = request;
                break;
            }
            else if (ok && !line.empty())
            {
                _req_header.push_back(line);
            }
            else
            {
                break;
            }
        }

        return true;
    }
    void DebugHttp()
    {
        std::cout << "_req_line: " << _req_line << std::endl;
        for (auto &line : _req_header)
        {
            std::cout << "---> " << line << std::endl;
        }
    }
    ~HttpRequest()
    {
    }

private:
    // http报文自动
    std::string _req_line; // method url http_version
    std::vector<std::string> _req_header;
    std::string _req_blank;
    std::string _req_content;

    // 解析之后的内容
    std::string _method;
    std::string _url; // /   /dira/dirb/x.html     /dira/dirb/XX?usrname=100&&password=1234 /dira/dirb
    std::string _http_version;
    std::string _path;                 // "./wwwroot"
    std::string _suffix;               // 请求资源的后缀
    std::vector<std::string> _cookies; // 其实cookie可以有多个,因为Set-Cookie可以被写多条,测试,一条够了。
    std::string _sessionid;            // 请求携带的sessionid,仅仅用来测试
};

const std::string BlankSep = " ";
const std::string LineSep = "\r\n";

class HttpResponse
{
public:
    HttpResponse() : _http_version("HTTP/1.0"), _status_code(200), _status_code_desc("OK"), _resp_blank(LineSep)
    {
    }
    void SetCode(int code)
    {
        _status_code = code;
    }
    void SetDesc(const std::string &desc)
    {
        _status_code_desc = desc;
    }
    void MakeStatusLine()
    {
        _status_line = _http_version + BlankSep + std::to_string(_status_code) + BlankSep + _status_code_desc + LineSep;
    }
    void AddHeader(const std::string &header)
    {
        _resp_header.push_back(header + LineSep);
    }
    void AddContent(const std::string &content)
    {
        _resp_content = content;
    }
    std::string Serialize()
    {
        MakeStatusLine();
        std::string response_str = _status_line;
        for (auto &header : _resp_header)
        {
            response_str += header;
        }
        response_str += _resp_blank;
        response_str += _resp_content;

        return response_str;
    }
    ~HttpResponse() {}

private:
    std::string _status_line;
    std::vector<std::string> _resp_header;
    std::string _resp_blank;
    std::string _resp_content; // body

    // httpversion StatusCode StatusCodeDesc
    std::string _http_version;
    int _status_code;
    std::string _status_code_desc;
};

class Http
{
private:
    std::string GetMonthName(int month)
    {
        std::vector<std::string> months = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
        return months[month];
    }
    std::string GetWeekDayName(int day)
    {
        std::vector<std::string> weekdays = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
        return weekdays[day];
    }
    std::string ExpireTimeUseRfc1123(int t) // 秒级别的未来UTC时间
    {
        time_t timeout = time(nullptr) + t;
        struct tm *tm = gmtime(&timeout); // 这里不能用localtime,因为localtime是默认带了时区的. gmtime获取的就是UTC统一时间
        char timebuffer[1024];
        // 时间格式如: expires=Thu, 18 Dec 2024 12:00:00 UTC
        snprintf(timebuffer, sizeof(timebuffer), "%s, %02d %s %d %02d:%02d:%02d UTC",
                 GetWeekDayName(tm->tm_wday).c_str(),
                 tm->tm_mday,
                 GetMonthName(tm->tm_mon).c_str(),
                 tm->tm_year + 1900,
                 tm->tm_hour,
                 tm->tm_min,
                 tm->tm_sec);
        return timebuffer;
    }

public:
    Http(uint16_t port)
    {
        _tsvr = std::make_unique<TcpServer>(port, std::bind(&Http::HandlerHttp, this, std::placeholders::_1));
        _tsvr->Init();

        _session_manager = std::make_unique<SessionManager>();
    }
    std::string ProveCookieWrite() // 证明cookie能被写入浏览器
    {
        return "Set-Cookie: username=zhangsan;";
    }
    std::string ProveCookieTimeOut()
    {
        return "Set-Cookie: username=zhangsan; expires=" + ExpireTimeUseRfc1123(60) + ";"; // 让cookie 1min后过期
    }
    std::string ProvePath()
    {
        return "Set-Cookie: username=zhangsan; path=/a/b;";
    }
    std::string ProveSession(const std::string &session_id)
    {
        return "Set-Cookie: sessionid=" + session_id + ";";
    }
    std::string HandlerHttp(std::string request)
    {
        HttpRequest req;
        HttpResponse resp;

        req.Deserialize(request);
        req.Parse();
        // req.DebugHttp();
        // std::cout << req.Url() << std::endl;

        // 下面的代码就用来测试,如果你想更优雅,可以回调出去处理
        static int number = 0;
        if (req.Url() == "/login") // 用/login path向指定浏览器写入sessionid,并在服务器维护对应的session对象
        {
            std::string sessionid = req.SessionId();
            if (sessionid.empty()) // 说明历史没有登陆过
            {
                std::string user = "user-" + std::to_string(number++);
                session_ptr s = std::make_shared<Session>(user, "logined");
                std::string sessionid = _session_manager->AddSession(s);
                lg.LogMessage(Debug, "%s 被添加, sessionid是: %s\n", user.c_str(), sessionid.c_str());
                resp.AddHeader(ProveSession(sessionid));
            }
        }
        else
        {
            // 当浏览器在本站点任何路径中活跃,都会自动提交sessionid, 我们就能知道谁活跃了.
            std::string sessionid = req.SessionId();
            if (!sessionid.empty())
            {
                session_ptr s = _session_manager->GetSession(sessionid);
                // 这个地方有坑,一定要判断服务器端session对象是否存在,因为可能测试的时候
                // 浏览器还有历史sessionid,但是服务器重启之后,session对象没有了.
                if(s != nullptr)
                    lg.LogMessage(Debug, "%s 正在活跃.\n", s->_username.c_str());
                else
                    lg.LogMessage(Debug, "cookie : %s 已经过期, 需要清理\n", sessionid.c_str()); 
            }
        }

        resp.SetCode(200);
        resp.SetDesc("OK");
        resp.AddHeader("Content-Type: text/html");
        // resp.AddHeader(ProveCookieWrite()); //测试cookie被写入与自动提交
        // resp.AddHeader(ProveCookieTimeOut()); //测试过期时间的写入
        //  resp.AddHeader(ProvePath()); // 测试路径
        resp.AddContent("<html><h1>helloworld</h1></html>");
        return resp.Serialize();
    }
    void Run()
    {
        _tsvr->Start();
    }
    ~Http()
    {
    }

private:
    std::unique_ptr<TcpServer> _tsvr;
    std::unique_ptr<SessionManager> _session_manager;
};
//Session.hpp
#pragma once

#include <iostream>
#include <string>
#include <memory>
#include <ctime>
#include <unistd.h>
#include <unordered_map>

// 用来进行测试说明
class Session
{
public:
    Session(const std::string &username, const std::string &status)
    :_username(username), _status(status)
    {
        _create_time = time(nullptr); // 获取时间戳就行了,后面实际需要,就转化就转换一下
    }
    ~Session()
    {}
public:
    std::string _username;
    std::string _status;
    uint64_t _create_time;
    uint64_t _time_out; // 60*5
    std::string vip; // vip
    int active; // 
    std::string pos;
    //当然还可以再加任何其他信息,看你的需求
};

using session_ptr = std::shared_ptr<Session>;

class SessionManager
{
public:
    SessionManager()
    {
        srand(time(nullptr) ^ getpid());
    }
    std::string AddSession(session_ptr s)
    {
        uint32_t randomid = rand() + time(nullptr); // 随机数+时间戳,实际有形成sessionid的库,比如boost uuid库,或者其他第三方库等
        std::string sessionid = std::to_string(randomid);
        _sessions.insert(std::make_pair(sessionid, s));
        return sessionid;
    }
    session_ptr GetSession(const std::string sessionid)
    {
        if(_sessions.find(sessionid) == _sessions.end()) return nullptr;
        return _sessions[sessionid];
    }
    ~SessionManager()
    {}
private:
    std::unordered_map<std::string, session_ptr> _sessions;
};

 

虽然Session比起Cookie来说相对安全,但还是有可能被盗取!(没有绝对的安全)

所以在服务器端就要设置一些方案来减低身份被冒人的风险:通过位置或者活跃度来判断用户身份,如果身份不对就要让服务器储存的对应sessionid失效即可!(通过具体业务来实现) 

四HTTP响应起始行

1状态码

状态码含义应用样例
100Continue上传大文件时, 服务器告诉客户端可以
继续上传
200OK访问网站首页, 服务器返回网页内容
201Created发布新文章, 服务器返回文章创建成功
的信息
204No Content删除文章后, 服务器返回“无内容”表示操
作成功
400Bad Request填写表单时, 格式不正确导致提交失败
401Unauthorized访问需要登录的页面时, 未登录或认证
失败
403Forbidden尝试访问你没有权限查看的页面
404Not Found访问不存在的网页链接
500Internal Server
Error
服务器崩溃或数据库错误导致页面无法
加载
502Bad Gateway使用代理服务器时, 代理服务器无法从
上游服务器获取有效响应
503Service
Unavailable
服务器维护或过载, 暂时无法处理请求

以下是重定向相关状态码的表格:使用时要搭配Header的Location一起使用!

状态码含义是否为临时重定向应用样例
301Moved
Permanently
否(永久重定向)网站换域名后, 自
动跳转到新域名;
搜索引擎更新网站
链接时使用
302Found 或 See
Other
是(临时重定向)用户登录成功后,
重定向到用户首页
307Temporary
Redirect
是(临时重定向)临时重定向资源到
新的位置(较少使
用)
308Permanent
Redirect
否(永久重定向)永久重定向资源到
新的位置(较少使
用)

2永久重定向与临时重定向

2.1从技术角度上

client端访问server端时:如果server端识别client所访问的资源要进行跳转,它就会把状态码(301/302)与要跳转的链接作为响应发给用户,让用户能够跳转到该地址处

2.2从理解角度上 

举例子来理解:

在你们学校的南门,有家xxx鱼店;每次中午你都会去那里吃;但由于这家店附件的马路最近在施工,在店门口贴了张告示:店临时搬到学校北门;所以接下来你要到那里去吃的话就直接去学校北门吃鱼;过了两个月后,你是会去南门(老店地址)还是北门(临时地址)去用餐呢?

        因为你不确定路是路是修没修好的,所以你会先去南门看看,没开就去北门那里用餐(临时重定向)

        如果路是一直修不好的,店老板在店门口贴了:本店永久搬到学校北门;接下来你要去吃的话就直接到北门的店不取南门的店了(永久重定向)

但在实际中,临时重定向我们是见过的:页面跳转;但永久重定向我们没见过:正常,因为永久重定向是给搜索引擎看的!

你实现处理的站点:www.hello.com:xxx搜索引擎要想获取到;它就会通过爬虫等方式获取该站点的信息从而保存到自己的数据库中:用户在进行搜索时就能看到该网站从而进行跳转访问到;此时由于某种原因你需要更换网址为:www.world.com;那这时怎么告诉用户我更换了网址呢?

这就需要xxx搜索引擎下一次爬取该网站信息时,你需要告诉它网站要进行永久重定向(301),它就会更新网站信息,让用户下一次访问时直接就跳转到新网站了!

2.3实现

#pragma once

#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <unordered_map>
#include <sys/stat.h>

const std::string base_sep = "\r\n";
const std::string line_sep = ": ";
const std::string profixpath = "wwwroot";
const std::string homepage = "index.html";
const std::string httpversion = "HTTP/1.1";
const std::string space_sep = " ";
const std::string profix_sep = ".";

class HttpRequest
{
private:
    std::string Getline(std::string &requeststr)
    {
        size_t pos = requeststr.find(base_sep);
        if (pos == std::string::npos)
            return "";

        std::string line = requeststr.substr(0, pos);
        requeststr.erase(0, line.size() + base_sep.size()); // 内容和\r\n都清除

        return line.empty() ? base_sep : line; // 空行使line为空要特殊判断
    }

    void LineDeserialize()
    {
        std::stringstream ss(_request_line);
        ss >> _method >> _url >> _version; // stringstream插入会省略空格

        // url进行解析
        _path += _url;
        if (_url == "/")
        {
            _path += homepage;
        }
        // wwwroot/1.jpg
        auto pos = _path.rfind(profix_sep);
        if (pos != std::string::npos)
        {
            _profix = _path.substr(pos);
        }
    }

    void HeadDeserialize()
    {
        for (auto &head : _request_header)
        {
            size_t pos = head.find(line_sep);
            if (pos == std::string::npos)
                continue;
            std::string k = head.substr(0, pos);
            std::string v = head.substr(pos + line_sep.size());
            if (k.empty() || v.empty())
                continue;
            _header.insert(std::make_pair(k, v));
        }
    }

public:
    HttpRequest() : _null_line(base_sep), _path(profixpath)
    {
    }
    std::string Path()
    {
        return _path;
    }
    std::string Profix()
    {
        return _profix;
    }
    void Deserialize(std::string &requeststr) // 完整请求字符串进行反序列化
    {
        _request_line = Getline(requeststr);
        std::string line;
        do
        {
            line = Getline(requeststr);
            if (line.empty() || line == base_sep)
                break;
            _request_header.push_back(line);
        } while (true);
        if (!requeststr.empty())
            _request_main = requeststr;

        // 更具体的反序列化
        LineDeserialize();
        HeadDeserialize();
    }

    void print()
    {
        std::cout << "------------------------------" << std::endl;
        std::cout << "@@@" << _request_line << std::endl;
        for (auto &header : _request_header)
        {
            std::cout << "###" << header << std::endl;
        }
        std::cout << "---" << _null_line;
        std::cout << ">>>" << _request_main << std::endl;
        std::cout << "更具体进行解析" << std::endl;
        std::cout << "method: " << _method << std::endl;
        std::cout << "url: " << _url << std::endl;
        std::cout << "version: " << _version << std::endl;
        for (auto &header : _header)
        {
            std::cout << header.first << "->" << header.second << std::endl;
        }
    }

private:
    // 基本格式
    std::string _request_line;
    std::vector<std::string> _request_header;
    std::string _null_line;
    std::string _request_main;

    // 具体解析
    std::string _method;
    std::string _url;
    std::string _version;
    std::unordered_map<std::string, std::string> _header;

    std::string _path;   // 访问资源路径
    std::string _profix; // 资源后缀
};

class HttpReponse
{
public:
    HttpReponse() : _null_line(base_sep), _version(httpversion)
    {
    }
    void AddCode(int code, const std::string &des)
    {
        _code = code;
        _des = des;
    }
    void AddHeader(std::string k, std::string v)
    {
        _header.insert({k, v});
    }
    void AddMain(std::string main)
    {
        _request_main = main;
    }
    std::string Serialize()
    {
        _request_line = _version + space_sep + std::to_string(_code) + space_sep + _des + base_sep;
        for (auto &header : _header)
        {
            std::string key = header.first;
            std::string value = header.second;
            _request_header.push_back(key + line_sep + value + base_sep);
        }
        // 拼接格式
        std::string jsonstr;
        jsonstr += _request_line;
        for (auto &header : _request_header)
        {
            jsonstr += header;
        }
        jsonstr += _null_line;
        jsonstr += _request_main;

        return jsonstr;
    }

private:
    // 基本属性
    std::string _version;
    int _code;
    std::string _des;
    std::unordered_map<std::string, std::string> _header;

    // 基本格式
    std::string _request_line;
    std::vector<std::string> _request_header;
    std::string _null_line;
    std::string _request_main;
};

class Http
{
public:
    Http()
    {
        _ContentType_Table.insert({".html", "text/html"});
        _ContentType_Table.insert({".jpg", "image/jpeg"});

        _CodeDes_Table.insert({302, "Found"});
        _CodeDes_Table.insert({401, "Not Fund"});
    }
    ~Http()
    {
    }
    std::string DealPath(std::string path)
    {
        std::ifstream in(path);
        if (!in.is_open())
            return "";
        // in.seekg(0,in.end);
        // int filesize=in.tellg();
        // in.seekg(0,in.beg);
        struct stat buff;
        int n = ::stat(path.c_str(), &buff);
        int filesize = buff.st_size;

        std::string content;
        content.resize(filesize);
        in.read((char *)content.c_str(), filesize);
        in.close();
        return content;
    }
    // #define TEST
    std::string Server(std::string requeststr)
    {
#ifdef TEST
        std::cout << "-------------------------------" << std::endl;
        std::cout << requeststr;
        std::string jsonstr = "HTTP/1.1 200 OK\r\n";
        jsonstr += "Content-Type: text/html\r\n";
        jsonstr += "\r\n";
        jsonstr += "<html><h1> Hello Http </h1></html>";
        return jsonstr;
#else
        // 请求
        HttpRequest req;
        req.Deserialize(requeststr);
        req.print();

        // 应答
        HttpReponse rep;
        if (req.Path() == "wwwroot/redir")
        {
            std::string redir_path = "https://www.qq.com";
            rep.AddCode(302, _CodeDes_Table[302]);
            rep.AddHeader("Location", redir_path);
        }
        else
        {
            // 上层处理
            std::string content = DealPath(req.Path());
            if (content.empty())
            {
                content = DealPath("wwwroot/404.html");
                rep.AddCode(404, _CodeDes_Table[404]);
                rep.AddHeader("Content-Length", std::to_string(content.size()));
                rep.AddHeader("Content-Type", _ContentType_Table[req.Profix()]);
                rep.AddMain(content);
            }
            else
            {
                rep.AddCode(20, "OK");
                rep.AddHeader("Content-Length", std::to_string(content.size()));
                rep.AddHeader("Content-Type", _ContentType_Table[req.Profix()]);
                rep.AddMain(content);
            }
        }
        return rep.Serialize();
#endif
    }

private:
    std::unordered_map<std::string, std::string> _ContentType_Table;
    std::unordered_map<int, std::string> _CodeDes_Table;
};

五HTTP方法 

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

 1GET与POST

两者之间的对比我们要借助form表单来完成(前端)

 <form action="/login" method="POST">
            用户名: <input type="text" name="user" value="."><br>
            密码: <input type="password" name="password" value=""><br>
            <input type="submit" value="提交">
        </form>

现象 

1.GET一般用来获取静态资源,也可以通过url来传递参数

2.POST可以通过htrp request的正文来传递参数

3.url传递参数:体量一般不大;正文传递参数:体量可以很大

4.用POST方法传递参数更私密,但是都不安全! 

 2实现

实现:表单中的数据填完后进行跳转到另一个页面中(我们要通过上层做处理)

(如果时GET方法:要把url中的我们向form表单填的数据给截取到request正文中再处理!)

//ServerMain.cc

#include "TcpServer.hpp"
#include "Http.hpp"

HttpReponse Login(HttpRequest& req)
{
    req.Main();

    HttpReponse rep;
    rep.AddCode(200,"OK");
    rep.AddMain("<html><h1> Hello Http </h1></html>");
    return rep;
    //fork,dup2,pipe,exec* ->pthon,Jova,PHP
    //接下来的写业务就交给其它语言了,c++已经完成使命了
}

int main(int args, char *argv[])
{
    if (args != 2)
    {
        std::cerr << "./Server Localport" << std::endl;
        exit(0);
    }
    uint16_t port = std::stoi(argv[1]);

    Http http;

    http.InsertServer("/login",Login);

    std::unique_ptr<TcpServer> svr=std::make_unique<TcpServer>(
        std::bind(&Http::Server, &http,
        std::placeholders::_1),
        port);
    svr->Start();
    return 0;
}

//Http.hpp
#pragma once

#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <unordered_map>
#include <functional>
#include <sys/stat.h>

const std::string base_sep = "\r\n";
const std::string line_sep = ": ";
const std::string profixpath = "wwwroot";
const std::string homepage = "index.html";
const std::string httpversion = "HTTP/1.1";
const std::string space_sep = " ";
const std::string profix_sep = ".";
const std::string arg_sep = "?";

class HttpRequest
{
private:
    std::string Getline(std::string &requeststr)
    {
        size_t pos = requeststr.find(base_sep);
        if (pos == std::string::npos)
            return "";

        std::string line = requeststr.substr(0, pos);
        requeststr.erase(0, line.size() + base_sep.size()); // 内容和\r\n都清除

        return line.empty() ? base_sep : line; // 空行使line为空要特殊判断
    }

    void LineDeserialize()
    {
        std::stringstream ss(_request_line);
        ss >> _method >> _url >> _version; // stringstream插入会省略空格

        if (strcasecmp(_method.c_str(), "GET") == 0)
        {
            // GET获取资源放在正文 /login?user=zhangsan&password=123456
            auto pos = _url.find(arg_sep);
            if (pos != std::string::npos)
            {
                _request_main = _url.substr(pos + arg_sep.size());
                _url.resize(pos);
            }
        }

        // url进行解析
        _path += _url;
        if (_path[_path.size()-1] == '/')
        {
            _path += homepage;
        }

        // wwwroot/1.jpg
        auto pos = _path.rfind(profix_sep);
        if (pos != std::string::npos)
        {
            _profix = _path.substr(pos);
        }
    }

    void HeadDeserialize()
    {
        for (auto &head : _request_header)
        {
            size_t pos = head.find(line_sep);
            if (pos == std::string::npos)
                continue;
            std::string k = head.substr(0, pos);
            std::string v = head.substr(pos + line_sep.size());
            if (k.empty() || v.empty())
                continue;
            _header.insert({k,v});
        }
    }

public:
    HttpRequest() : _null_line(base_sep), _path(profixpath)
    {
    }
    std::string Path()
    {
        return _path;
    }
    std::string Profix()
    {
        return _profix;
    }
    std::string Main()
    {
        std::cout << "method: " << _method << " path: " << _path << " args: " << _request_main << std::endl;
        return _request_main;
    }
    void Deserialize(std::string &requeststr) // 完整请求字符串进行反序列化
    {
        _request_line = Getline(requeststr);
        std::string line;
        do
        {
            line = Getline(requeststr);
            if (line.empty() || line == base_sep)
                break;
            _request_header.push_back(line);
        } while (true);
        if (!requeststr.empty())
            _request_main = requeststr;

        // 更具体的反序列化
        LineDeserialize();
        HeadDeserialize();
    }

    void print()
    {
        std::cout << "------------------------------" << std::endl;
        std::cout << "@@@" << _request_line << std::endl;
        for (auto &header : _request_header)
        {
            std::cout << "###" << header << std::endl;
        }
        std::cout << "---" << _null_line;
        std::cout << ">>>" << _request_main << std::endl;
        std::cout << "更具体进行解析" << std::endl;
        std::cout << "method: " << _method << std::endl;
        std::cout << "url: " << _url << std::endl;
        std::cout << "version: " << _version << std::endl;
        for (auto &header : _header)
        {
            std::cout << header.first << "->" << header.second << std::endl;
        }
    }

private:
    // 基本格式
    std::string _request_line;
    std::vector<std::string> _request_header;
    std::string _null_line;
    std::string _request_main;

    // 具体解析
    std::string _method;
    std::string _url;
    std::string _version;
    std::unordered_map<std::string, std::string> _header;

    std::string _path;   // 访问资源路径
    std::string _profix; // 资源后缀
};

class HttpReponse
{
public:
    HttpReponse() : _null_line(base_sep), _version(httpversion)
    {
    }
    void AddCode(int code, const std::string &des)
    {
        _code = code;
        _des = des;
    }
    void AddHeader(std::string k, std::string v)
    {
        _header.insert({k, v});
    }
    void AddMain(const std::string &main)
    {
        _request_main = main;
    }
    std::string Serialize()
    {
        _request_line = _version + space_sep + std::to_string(_code) + space_sep + _des + base_sep;
        for (auto &header : _header)
        {
            std::string key = header.first;
            std::string value = header.second;
            _request_header.push_back(key + line_sep + value + base_sep);
        }
        // 拼接格式
        std::string jsonstr;
        jsonstr += _request_line;
        for (auto &header : _request_header)
        {
            jsonstr += header;
        }
        jsonstr += _null_line;
        jsonstr += _request_main;

        return jsonstr;
    }

private:
    // 基本属性
    std::string _version;
    int _code;
    std::string _des;
    std::unordered_map<std::string, std::string> _header;

    // 基本格式
    std::string _request_line;
    std::vector<std::string> _request_header;
    std::string _null_line;
    std::string _request_main;
};

using fun_t = std::function<HttpReponse(HttpRequest&)>;

class Http
{
public:
    Http()
    {
        _ContentType_Table.insert({".html", "text/html"});
        _ContentType_Table.insert({".jpg", "image/jpeg"});

        _CodeDes_Table.insert({302, "Found"});
        _CodeDes_Table.insert({401, "Not Fund"});
    }
    ~Http()
    {
    }
    std::string DealPath(std::string path)
    {
        std::ifstream in(path);
        if (!in.is_open())
            return "";
        // in.seekg(0,in.end);
        // int filesize=in.tellg();
        // in.seekg(0,in.beg);
        struct stat buff;
        int n = ::stat(path.c_str(), &buff);
        int filesize = buff.st_size;

        std::string content;
        content.resize(filesize);
        in.read((char *)content.c_str(), filesize);
        in.close();
        return content;
    }
    // #define TEST
    std::string Server(std::string &requeststr)
    {
#ifdef TEST
        std::cout << "-------------------------------" << std::endl;
        std::cout << requeststr;
        std::string jsonstr = "HTTP/1.1 200 OK\r\n";
        jsonstr += "Content-Type: text/html\r\n";
        jsonstr += "\r\n";
        jsonstr += "<html><h1> Hello Http </h1></html>";
        return jsonstr;
#else
        // 请求
        HttpRequest req;
        req.Deserialize(requeststr);
        req.print();

        req.Main();

        // 应答
        HttpReponse rep;
        if (req.Path() == "wwwroot/redir")
        {
            std::string redir_path = "https://www.qq.com";
            rep.AddCode(302, _CodeDes_Table[302]);
            rep.AddHeader("Location", redir_path);
        }
        // 处理动态资源
        else if (!req.Main().empty())
        {
            if (IsServerExist(req.Path()))
            {
                // 回调
                rep = _Server[req.Path()](req);
            }
        }
        else
        {
            // 处理静态资源
            std::string content = DealPath(req.Path());
            if (content.empty())
            {
                content = DealPath("wwwroot/404.html");
                rep.AddCode(404, _CodeDes_Table[404]);
                rep.AddHeader("Content-Length", std::to_string(content.size()));
                rep.AddHeader("Content-Type", _ContentType_Table[req.Profix()]);
                rep.AddMain(content);
            }
            else
            {
                rep.AddCode(20, "OK");
                rep.AddHeader("Content-Length", std::to_string(content.size()));
                rep.AddHeader("Content-Type", _ContentType_Table[req.Profix()]);
                rep.AddMain(content);
            }
        }
        return rep.Serialize();
#endif
    }
    void InsertServer(const std::string &path, fun_t server)
    {
        // path: /login
        std::string s = profixpath + path;
        _Server[s]=server;
    }
    bool IsServerExist(std::string path)
    {
        auto pos = _Server.find(path);
        if (pos == _Server.end())
            return false;
        return true;
    }

private:
    std::unordered_map<std::string, std::string> _ContentType_Table;
    std::unordered_map<int, std::string> _CodeDes_Table;

    std::unordered_map<std::string, fun_t> _Server;
};

以上便是全部内容:有错误欢迎指正,感谢观看! 

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

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

相关文章

# wps必须要登录激活才能使用吗?

WPS 必须登录激活才能使用吗&#xff1f; 如下图&#xff0c;当我们使用WPS时&#xff0c;不登录会显示工具栏灰色不可用状态。 答&#xff1a;WPS 不一定要登录激活才能使用。 一、免费使用的情况 1、基础功能 在不登录的情况下&#xff0c;用户可以使用 WPS 的一些基础功…

模板:JDBC 连接数据库并实现 CRUD

目录 前期准备&#xff1a; 1. 连接数据库 1.1 第一种 1.2 第二种 2. 增加 3. 修改 4. 删除 5. 查询 5.1 查询某个记录 5.2 查询单列数据 使用时&#xff0c;直接复制再修改一些数据即可&#xff1b; 声明&#xff1a;在对文件/变量命名时&#xff0c;没有做到见名知…

鸿蒙设置,修改APP图标和名称

1、先看默认的图标和名称 2、打开项目开始设置自己需要的图标和名称 2.1找到 路径src\main\module.json5&#xff0c; 找到 abilities&#xff0c;下的&#xff0c;图标icon、名称label&#xff0c;label可以按住ctrl鼠标左键点击跳转 2.2先修改APP名称 1、ctrl鼠标左键点击…

Redis 字符串类型的典型应用场景

目录 1. 缓存功能 2. 计数功能 3. 共享会话&#xff08;Session&#xff09; 4. 手机验证码 前言 这里将详细介绍 Redis 字符串类型在实际开发中的几个典型应用场景&#xff0c;并提供相应的伪代码示例。 1. 缓存功能 场景描述 在许多Web应用中&#xff0c;数据通常需要…

【在Linux世界中追寻伟大的One Piece】验证TCP

目录 1 -> 验证TCP-windows作为client访问Linux 1.1 -> TCP client样例代码 1 -> 验证TCP-windows作为client访问Linux 1.1 -> TCP client样例代码 #include <winsock2.h> #include <iostream> #include <string>#pragma warning(disable : …

什么是Rspack?

Rspack 是一个基于 Rust 编写的高性能 JavaScript 打包工具&#xff0c;旨在提供与 webpack 生态系统的强兼容性&#xff0c;允许无缝替换 webpack&#xff0c;并提供极快的构建速度。 介绍 - Rspack 它由字节跳动 Web Infra 团队孵化&#xff0c;具有以下特点&#xff1a; 高…

JS渲染锻炼输入表单

前言 上篇文章为大家展现了好看的信息窗口&#xff0c;接下来我们跟着流程图看下一步 之前我们的带点击事件已经添加完毕&#xff0c;下一步就是当用户点击的时候&#xff0c;渲染锻炼形式&#xff0c;当然这是一个标签&#xff0c;可以提供给用户输入锻炼形式 实例 ● 我…

Codeforces Round 969 (Div. 1) C. Eri and Expanded Sets(线段树维护差分数组gcd+双指针+尺取)

题目 转化一下题意就是&#xff0c; 给定一个n(n<4e5)&#xff0c;代表数组a的长度&#xff0c; 求有多少区间&#xff0c;满足区间内两两差分后得到的新数组的gcd∈{0,1} 实际t(t<1e4)组样例&#xff0c;保证sumn不超过4e5 思路来源 乱搞acjiangly代码 题解 一个…

C/C++内存管理 ——

目录 五、C/C内存管理 1、C/C内存分布 2、C语言中动态内存管理方式&#xff1a;malloc/calloc/realloc/free 3、C内存管理方式 1.new/delete操作内置类型 2.new和delete操作自定义类型 4、operator new与operator delete函数 5、new和delete的实现原理 1.内置类…

SSM框架学习(四、SpringMVC实战:构建高效表述层框架)

目录 一、SpringMVC简介和体验 1.介绍 2.主要作用 3.核心组件和调用流程理解 4.快速体验 二、SpringMVC接收数据 1.访问路径设置 &#xff08;1&#xff09;精准路径匹配 &#xff08;2&#xff09;模糊路径匹配 &#xff08;3&#xff09;类和方法上添加 RequestMapp…

CSP-J 2024 入门组初赛第一轮初赛试题及答案解析

CSP-J 2024 入门组初赛第一轮初赛试题及答案解析 一、 单项选择题&#xff08;共15题&#xff0c;每题2分&#xff0c;共计30分&#xff1a;每题有且仅有一个正确选项&#xff09; 1 32 位 int 类型的存储范围是&#xff08; &#xff09; A -2147483647 ~ 2147483647 B -21…

【QGIS入门实战精品教程】6.1:QGIS根据属性条件查询数据(SQL表达式)

文章目录 一、字段过滤二、高级过滤(表达式)一、字段过滤 对于单个字段的查询,可以采用字段过滤,例如,从县区数据中,根据NAME字段,查找出县级市玉门市。操作为:右键县区→打开属性表: 点击左下角,选择name字段。 输入玉门市,回车,选择查找除的属性表记录,此时图斑…

汽车总线之----FlexRay总线

Introduction 随着汽车智能化发展&#xff0c;车辆开发的ECU数量不断增加&#xff0c;人们对汽车系统的各个性能方面提出了更高的需求&#xff0c;比如更多的数据交互&#xff0c;更高的传输带宽等。现如今人们广泛接受电子功能来提高驾驶安全性&#xff0c;像ABS防抱死系统&a…

计算机毕业设计之:基于深度学习的路面检测系统(源码+部署文档+讲解)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

【redis-02】深入理解redis中RBD和AOF的持久化

redis系列整体栏目 内容链接地址【一】redis基本数据类型和使用场景https://zhenghuisheng.blog.csdn.net/article/details/142406325【二】redis的持久化机制和原理https://zhenghuisheng.blog.csdn.net/article/details/142441756 如需转载&#xff0c;请输入&#xff1a;htt…

2025校招内推-招联金融

【投递方式】 直接扫下方二维码&#xff0c;或点击内推官网https://wecruit.hotjob.cn/SU61025e262f9d247b98e0a2c2/mc/position/campus&#xff0c;使用内推码 igcefb 投递&#xff09; 【招聘岗位】 后台开发 前端开发 数据开发 数据运营 算法开发 技术运维 软件测试 产品策…

CentOS Stream 9部署docker,并开启API

1、安装docker &#xff08;1&#xff09;安装Docker的依赖项和存储库 sudo dnf install -y dnf-plugins-core sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo &#xff08;2&#xff09;安装Docker sudo dnf install -y docke…

0基础学习PyTorch——最小Demo

大纲 环境准备安装依赖 训练和推理训练生成数据加载数据TensorDatasetDataLoader 定义神经网络定义损失函数和优化器训练模型 推理 参考代码 PyTorch以其简洁直观的API、动态计算图和强大的社区支持&#xff0c;在学术界和工业界都享有极高的声誉&#xff0c;成为许多深度学习爱…

yum 集中式安装 LNMP

目录 安装 nginx 安装 mysql 安装 php 配置lnmp 配置 nginx 支持 PHP 解析 安装 nginx 修改yum源 将原本的yum源备份 vim /etc/yum.repos.d/nginx.repo [nginx-stable] namenginx stable repo baseurlhttp://nginx.org/packages/centos/7/$basearch/ gpgcheck0 enable…

黎巴嫩BP机爆炸事件启示录:我国应加快供应链安全立法

据报道&#xff0c;当地时间9月17日下午&#xff0c;黎巴嫩首都贝鲁特以及黎巴嫩东南部和东北部多地都发生了BP机爆炸事件。当时的统计数据显示&#xff0c;爆炸造成9人死亡&#xff0c;约2800人受伤。9月18日&#xff0c;死亡人数上升到11人&#xff0c;受伤人数超过4000。 目…