【网络基础】HTTP协议的基本知识与服务器实现

文章目录

  • [1. 认识URL](#1. 认识URL)
  • [2. urlencode和urldecode](#2. urlencode和urldecode)
  • [3. http 协议格式](#3. http 协议格式)
    • [**3.1 HTTP请求格式**](#3.1 HTTP请求格式)
    • [**3.2 HTTP响应格式**](#3.2 HTTP响应格式)
  • [4. HTTP 常见头部-Header](#4. HTTP 常见头部-Header)
  • [5. HTTP的方法](#5. HTTP的方法)
  • [6. HTTP的状态码](#6. HTTP的状态码)
  • [7. 编写HTTP服务器获取http请求与响应信息](#7. 编写HTTP服务器获取http请求与响应信息)
    • [7.0 Logger.hpp](#7.0 Logger.hpp)
    • [7.1 Socket.hpp](#7.1 Socket.hpp)
    • [7.2 TcpServer.hpp](#7.2 TcpServer.hpp)
    • [7.3 Session.hpp](#7.3 Session.hpp)
    • [7.4 HttpProtocol.hpp](#7.4 HttpProtocol.hpp)
    • [7.5 main.cc](#7.5 main.cc)
    • [7.6 代码链接:](#7.6 代码链接:)

1. 认识URL

URL即我们俗称的网址,一般标注的URL都有以下的几部分,我们以一个示例来举证:

  • 协议部分(Protocol): https://,指示使用HTTPS协议进行通信。
  • 登录信息部分(Username and Password): username:password@,表示需要提供用户名和密码来访问该资源。注意,这种方式传输的密码明文可能会被窃取,因此不建议在实际应用中使用。
  • 域名部分(Domain Name): www.example.com,表示需要访问的服务器的域名或IP地址。
  • 端口号部分(Port Number): 未指定端口号,默认使用协议的默认端口。
  • 路径部分(Path): /path/to/resource,表示需要访问的资源的路径。
  • 查询参数部分(Query Parameters): ?key1=value1&key2=value2,表示需要传递给服务器的额外数据。多个键值对之间使用 & 符号连接。
  • 片段标识部分(Fragment Identifier): #section,表示需要访问的页面内的特定位置(例如HTML文档中的锚点)。

2. urlencode和urldecode

我们可以注意到,URL中会出现如 /, ?, : 等特殊字符,它们在URL中具有特别的意义。如果数据或参数中包含这些特殊字符,就需要进行转义处理。转义规则如下:

转义规则

  1. 空格 :空格通常被转义为%20

  2. 保留字符:以下字符是URL中的保留字符,在某些上下文中具有特殊含义,因此需要进行转义。

    • 冒号(:) 转义为%3A
    • 斜杠(/) 转义为%2F
    • 问号(?) 转义为%3F
    • 井号(#) 转义为%23
    • 方括号([ 和 ]) 转义为%5B%5D
    • 百分号(%) 转义为%25
  3. 非ASCII字符 :非ASCII字符需要进行URL编码。它们将以 %xx 的形式表示,其中 xx 是字符的ASCII码值的十六进制表示。

  4. 其他特殊字符:除了上述情况外,其他特殊字符也可能需要转义,例如各种标点符号。

总结

转义的过程就是 urlencodeurldecode 的过程。urldecodeurlencode 的逆过程。


3. http 协议格式

**HTTP(Hypertext Transfer Protocol)**是一种用于传输超文本的应用层协议,用于在客户端和服务器之间传输信息。下面是一个简单的HTTP请求和响应的格式:

3.1 HTTP请求格式

一个简单的HTTP请求由以下几个部分组成:

  1. 请求行:包含请求方法、请求的URL以及协议版本。

  2. 请求头:包含了关于请求资源、客户端和更多信息的头部信息。

  3. 空行:用于分隔请求头和请求体。

  4. 请求体:包含实际发送给服务器的数据。

示例

cpp 复制代码
GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Accept-Language: en-US

Hello, server!

3.2 HTTP响应格式

一个简单的HTTP响应由以下几个部分组成:

  1. 状态行:包含协议版本、状态码以及状态信息。

  2. 响应头:包含了响应资源、服务器和更多信息的头部信息。

  3. 空行:用于分隔响应头和响应体。

  4. 响应体:包含实际返回给客户端的数据。

示例

c 复制代码
HTTP/1.1 200 OK
Server: Apache
Content-Type: text/html

<html>
<head><title>Hello, client!</title></head>
<body>
<h1>Welcome to my website!</h1>
</body>
</html>

4. HTTP 常见头部-Header

头部名 描述
Content-Type 指定请求或响应中的实体主体的媒体类型。
Content-Length 指定请求或响应中实体主体的长度,以字节为单位。
User-Agent 标识发起请求的用户代理(例如浏览器、爬虫等)的名称和版本号。
Cache-Control 指定请求或响应的缓存机制。
Authorization 用于向服务器提供身份验证凭据。
Cookie 用于在客户端和服务器之间传递会话信息的标头字段。
Accept 指定客户端接受的媒体类型,用于响应内容协商。
Location 指定了重定向的目标位置。
Referer 指定了当前请求来源的URL。
If-None-Match 用于条件请求,指定了请求资源的实体标签(ETag)。
ETag 用于响应中表示实体标签,用于缓存验证。

5. HTTP的方法

方法 描述 幂等性 安全性
GET 获取指定资源的表示形式
POST 向指定资源提交数据进行处理
PUT 将指定的资源用请求的表示替换掉目标资源
DELETE 删除指定的资源
HEAD 获取指定资源的头部信息
OPTIONS 获取当前资源支持的HTTP方法列表和其他通信选项
TRACE 对请求的传输路径进行追踪,主要用于测试和诊断
CONNECT 建立与资源的双向连接,主要用于代理服务器
PATCH 对指定资源进行局部修改
COPY 将指定的资源复制到新的位置
MOVE 将指定的资源从原位置移动到新位置
LINK 将指定资源与另外一个资源进行关联
UNLINK 取消指定资源与另外一个资源的关联

幂等性与安全性解释

幂等性

定义: 幂等性是指对同一个请求执行一次或多次,结果都应该是一样的。也就是说,对于具有幂等性的操作,重复执行多次不会产生副作用。

  • GET: 获取资源的请求。无论请求多少次,资源的状态不会改变,因此GET请求是幂等的。
  • PUT: 替换资源的请求。即使多次执行同样的PUT请求,资源的状态也不会改变,因此PUT请求是幂等的。
  • DELETE: 删除资源的请求。重复删除同一资源不会有额外的副作用,因为资源已经被删除,因此DELETE请求是幂等的。
  • HEAD: 获取资源头部信息。与GET类似,不会改变资源的状态,因此HEAD请求是幂等的。
  • OPTIONS: 获取资源支持的HTTP方法。不会改变资源状态,因此OPTIONS请求是幂等的。
  • TRACE: 追踪请求的传输路径。不会改变资源状态,因此TRACE请求是幂等的。

非幂等操作:

  • POST: 向服务器提交数据进行处理。重复执行POST请求可能会导致不同的结果或副作用,因此POST请求不是幂等的。
  • PATCH: 对资源进行局部修改。重复PATCH请求可能会产生不同的结果,因此PATCH请求不是幂等的。
  • COPY: 复制资源到新位置。重复执行COPY请求可能会创建多个副本,因此COPY请求不是幂等的。
  • MOVE: 移动资源到新位置。重复执行MOVE请求可能会产生不同的结果,因此MOVE请求不是幂等的。
  • LINK: 将资源与另一个资源关联。重复LINK请求可能会导致多个关联,因此LINK请求不是幂等的。
  • UNLINK: 取消资源与另一个资源的关联。重复UNLINK请求可能会产生不同的结果,因此UNLINK请求不是幂等的.
  • CONNECT: 建立与资源的双向连接。重复CONNECT请求可能会导致不同的连接,因此CONNECT请求不是幂等的。

安全性

定义: 安全性是指执行请求不会对服务器资源状态产生影响。安全的操作不会导致资源的状态发生改变。

  • GET: 获取资源的请求。此请求不会修改资源,因此GET请求是安全的。
  • HEAD: 获取资源的头部信息。此请求不会修改资源,因此HEAD请求是安全的.
  • OPTIONS: 获取资源支持的HTTP方法。此请求不会修改资源,因此OPTIONS请求是安全的.
  • TRACE: 追踪请求的传输路径。此请求不会修改资源,因此TRACE请求是安全的。

非安全操作:

  • POST: 向服务器提交数据进行处理。此请求可能会修改服务器上的资源,因此POST请求不是安全的。
  • PUT: 替换资源。此请求会改变资源的状态,因此PUT请求不是安全的。
  • DELETE: 删除资源。此请求会改变资源的状态,因此DELETE请求不是安全的.
  • PATCH: 局部修改资源。此请求会改变资源的状态,因此PATCH请求不是安全的。
  • COPY: 复制资源。此请求会改变资源的状态,因此COPY请求不是安全的.
  • MOVE: 移动资源。此请求会改变资源的状态,因此MOVE请求不是安全的.
  • LINK: 关联资源。此请求会改变资源的状态,因此LINK请求不是安全的.
  • UNLINK: 取消资源的关联。此请求会改变资源的状态,因此UNLINK请求不是安全的.
  • CONNECT: 建立双向连接。此请求可能会改变服务器的状态,因此CONNECT请求不是安全的.

6. HTTP的状态码

HTTP 状态码简介

HTTP状态码是服务器对客户端请求的响应结果的标识,采用三位数字代码的形式。每个状态码表示服务器对请求处理的结果,并帮助客户端了解请求的处理状态。以下是一些常见的HTTP状态码及其含义:

成功状态码

  • 200 OK
    说明 : 请求成功,服务器已成功处理了请求。
    示例: 访问一个网页或获取数据成功。

  • 201 Created
    说明 : 请求已成功处理,并在服务器上创建了一个新的资源。
    示例: 用户注册成功,新账号被创建。

  • 204 No Content
    说明 : 服务器成功处理了请求,但没有返回任何内容。
    示例: 删除操作成功,但没有需要返回的数据。

客户端错误状态码

  • 400 Bad Request
    说明 : 客户端发出的请求有语法错误或无法理解。
    示例: 请求中缺少必需的参数或包含无效的数据。

  • 401 Unauthorized
    说明 : 请求需要用户身份验证。
    示例: 用户尝试访问受保护的资源,但未提供有效的凭证。

  • 403 Forbidden
    说明 : 服务器理解请求,但拒绝执行。通常是因为请求的资源对用户不可用。
    示例: 用户尝试访问不允许的页面或资源。

  • 404 Not Found
    说明 : 服务器无法找到请求的资源。
    示例: 用户访问的网页或文件不存在。

服务器错误状态码

  • 500 Internal Server Error
    说明 : 服务器遇到了意外情况,无法完成请求。
    示例: 服务器内部错误导致请求失败。

  • 502 Bad Gateway
    说明 : 作为网关或代理服务器的服务器从上游服务器收到无效的响应。
    示例: 网关服务器无法从上游服务器获取有效的响应。

  • 503 Service Unavailable
    说明 : 服务器当前无法处理请求,通常用于服务器维护或过载时。
    示例: 服务器正在进行维护,暂时无法提供服务。


7. 编写HTTP服务器获取http请求与响应信息

在了解了http的相关内容后,下面我们编写一个http服务,用来获取http相应与请求信息。

包含以下部分:

  • Socket.hpp:套接字的管理,tcpSocket;
  • TcpServer.hpp:封装了Tcp服务器类
  • Session.hpp:用户会话,以及sessionManager类,管理用户信息
  • HttpProtocol.hpp:http协议的相关操作,如解析
  • wwwroot文件夹:存放html文件、图片文件等
  • main.cc:主文件,用于启动服务器,最后生成可执行文件
  • Logger.hpp: 日志输出类,用于打印错误日志等

7.0 Logger.hpp

cpp 复制代码
#ifndef __M_LOG_H__
#define __M_LOG_H__

#include <iostream>
#include <cstdio>
#include <ctime>

#define DBG_LEVEL 0
#define INF_LEVEL 1
#define ERR_LEVEL 2
#define DEFAULT_LEVEL DBG_LEVEL

#define LOG(level_str, level, format, ...) \
    if (level >= DEFAULT_LEVEL) { \
        time_t t = time(nullptr); \
        struct tm* ptm = localtime(&t); \
        char time_str[32]; \
        strftime(time_str, /*sizeof(time_str)*/31, "%H:%M:%S", ptm); \
        printf("[%s][%s][%s:%d]\t" format "\n", level_str, time_str, __FILE__, __LINE__, ##__VA_ARGS__); \
    } \

#define DLOG(format, ...) LOG("DBG", DBG_LEVEL, format, ##__VA_ARGS__)
#define ILOG(format, ...) LOG("INF", INF_LEVEL, format, ##__VA_ARGS__)
#define ELOG(format, ...) LOG("ERR", ERR_LEVEL, format, ##__VA_ARGS__)

#endif

7.1 Socket.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "Logger.hpp"

#define Convert(addrptr) ((struct sockaddr *)addrptr)

namespace network
{
    const static int defaultsockfd = -1; // 默认socket文件描述符
    const int backlog = 5; // 最大连接请求数

    enum // 错误类型
    {
        SocketError = 1,
        BindError,
        ListenError,
        SendError,
    };

    // 封装Socket接口类(设计模式:模板方法类)
    class Socket
    {
    public:
        virtual ~Socket() {}
        virtual void CreateSocketOrDie() = 0;
        virtual void BindSocketOrDie(uint16_t port) = 0;
        virtual void ListenSocketOrDie(int backlog) = 0;
        virtual Socket *AcceptConnection(std::string *peerip, uint16_t *peerport) = 0;
        virtual bool ConnectServer(std::string &serverip, uint16_t serverport) = 0;
        virtual int GetSockFd() = 0;
        virtual void SetSockFd(int sockfd) = 0;
        virtual void CloseSocket() = 0;
        virtual bool Recv(std::string *buffer, int size) = 0;
        virtual void Send(std::string &send_str) = 0;
        virtual void ReUseAddr() = 0;
    public:
        // 建立监听套接字
        void BuildListenSocketMethod(uint16_t port, int backlog)
        {
            CreateSocketOrDie();
            ReUseAddr();
            BindSocketOrDie(port);
            ListenSocketOrDie(backlog);
        }
        
        // 连接服务器
        bool BuildConnectSocketMethod(std::string &serverip, uint16_t serverport)
        {
            CreateSocketOrDie();
            return ConnectServer(serverip, serverport);
        }

        // 建立普通套接字
        void BuildNormalSocketMethod(int sockfd)
        {
            SetSockFd(sockfd);
        }
    };

    class TcpSocket : public Socket
    {
    public:
        TcpSocket(int sockfd = defaultsockfd) : _sockfd(sockfd) {}
        ~TcpSocket() { 
            // CloseSocket(); 
        }

        void CreateSocketOrDie() override
        {
            _sockfd = socket(AF_INET, SOCK_STREAM, 0);
            if(_sockfd < 0)
            {
                std::cerr << "socket create error" << std::endl;
                exit(SocketError);
            }
        }

        void BindSocketOrDie(uint16_t port) override
        {
            struct sockaddr_in addr;
            memset(&addr, 0, sizeof(addr));
            addr.sin_family = AF_INET;
            addr.sin_port = htons(port);
            addr.sin_addr.s_addr = INADDR_ANY;

            if(::bind(_sockfd, Convert(&addr), sizeof(addr)) < 0) {                
                ELOG("bind error: %s", strerror(errno));
                exit(SocketError);
            }
        }

        void ListenSocketOrDie(int backlog) override
        {
            if(listen(_sockfd, backlog) < 0) {
                std::cerr << "listen error" << std::endl;
                exit(SocketError);
            }
        }

        Socket *AcceptConnection(std::string *peerip, uint16_t *peerport) override
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int newfd = accept(_sockfd, Convert(&peer), &len);
            if(newfd < 0) {
                std::cerr << "accept error" << std::endl;
                exit(SocketError);
            }
            
            *peerport = ntohs(peer.sin_port);
            *peerip = inet_ntoa(peer.sin_addr);

            return new TcpSocket(newfd); // -> Socket *socket = new TcpSocket(newfd);
        }

        bool ConnectServer(std::string &serverip, uint16_t serverport) override
        {
            struct sockaddr_in server;
            server.sin_family = AF_INET;
            server.sin_port = htons(serverport);
            server.sin_addr.s_addr = inet_addr(serverip.c_str());

            if(connect(_sockfd, Convert(&server), sizeof(server) < 0)) {
                std::cerr << "connect error" << std::endl;
                return false;
            }

            return true;
        }

        int GetSockFd() override
        {
            return _sockfd;
        }

        void SetSockFd(int sockfd) override
        {
            _sockfd = sockfd;
        }

        void CloseSocket() override
        {
            if(_sockfd > defaultsockfd) {
                close(_sockfd);
            }
        }

        bool Recv(std::string *buffer, int size) override
        {
            char recvBuffer[size];
            int recvLen = recv(_sockfd, recvBuffer, size, 0);
            if(recvLen < 0) {
                std::cerr << "recv error" << std::endl;
                return false;
            } else if(recvLen == 0) {
                return false;
            }

            recvBuffer[recvLen] = '\0';
            *buffer += recvBuffer;
            return true;
        }

        void Send(std::string &send_str) override
        {
            if(send(_sockfd, send_str.c_str(), send_str.size(), 0))
            {
                std::cerr << "send error" << std::endl;
                exit(SendError);
            }
        }

        void ReUseAddr() override
        {
            int opt = 1;
            setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
        }

    private:
        int _sockfd;
    };
}

在上面整个文件中,我们将socket有关的类与参数都实现在一个命名空间network中,Socket类作为TcpSocket的父类生命了相关的套接字操作函数,后者将这些函数重写实现。


7.2 TcpServer.hpp

TcpServer类基于实现的TcpSocket套接字管理类,通过

cpp 复制代码
#pragma once

#include "Socket.hpp"
#include <iostream>
#include <pthread.h>
#include <functional>

// http协议底层为tcp协议
using func_t = std::function<std::string(std::string &request)>;

class TcpServer;

class ThreadData
{
public:
    ThreadData(TcpServer *tcp_this, network::Socket *sockp) : _this(tcp_this), _sockp(sockp)
    {}

public:
    TcpServer *_this;
    network::Socket *_sockp;
};

class TcpServer
{
public:
    TcpServer(uint16_t port, func_t request_handler) : _port(port), _listen_socket(new network::TcpSocket()), _request_handler(request_handler)
    {
        _listen_socket->BuildListenSocketMethod(_port, network::backlog);
    }

    ~TcpServer()
    {
        delete _listen_socket;
    }

    static void* ThreadRun(void* args)
    {
        pthread_detach(pthread_self()); // 分离线程: 线程结束时自动回收资源
        ThreadData* td = static_cast<ThreadData*>(args);
        std::string http_request; // http请求

        // 接收请求
        if(td->_sockp->Recv(&http_request, 4096)) {
            // 
            std::string http_response = td->_this->_request_handler(http_request);
            if(!http_request.empty()) {
                td->_sockp->Send(http_response); // 发送响应
            }
        }

        td->_sockp->CloseSocket();
        delete td->_sockp;
        delete td;
        return nullptr;
    }

    // 循环监听
    void Loop()
    {
        while(true) 
        {
            std::string peerip;
            uint16_t peerport;
            network::Socket* newSock = _listen_socket->AcceptConnection(&peerip, &peerport);
            if(!newSock) {
                // std::cerr << "accept error" << std::endl;
                continue;
            }

            std::cout << "new connection from " << peerip << ":" << peerport << std::endl;
            ThreadData* td = new ThreadData(this, newSock);
            pthread_t tid;
            pthread_create(&tid, nullptr, ThreadRun, td);
        }
    }

private:
    int _port;
    network::Socket *_listen_socket;
public:
    func_t _request_handler;
};

7.3 Session.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <memory>
#include <unordered_map>
#include "Logger.hpp"

class Session
{
public:
    using ptr = std::shared_ptr<Session>;
    Session(){}
    Session(std::string username, std::string password, std::string status, uint64_t login_time){}
    ~Session(){}


private:
    std::string username;
    std::string password;
    std::string status;
    uint64_t login_time; // 时刻
    // 等等阶段信息、参数
};

class SessionManager
{
public:
    SessionManager(){}
    ~SessionManager(){}

    bool FindSession(std::string& session_id)
    {
        if(sessions.find(session_id) == sessions.end()) {
            DLOG("session_id not found\n");
            return false;
        }

        return true;
    }

    std::string AddSession(std::string username, std::string password, std::string status, uint64_t login_time)
    {
        std::string session_id = username + std::to_string(login_time);
        sessions[session_id] = std::make_shared<Session>(username, password, status, login_time);
        return session_id;
    }

    void DelSession(std::string& session_id)
    {
        if(sessions.find(session_id) == sessions.end()) {
            DLOG("session_id not found\n");
        }
        
        sessions.erase(session_id);
        // return true;
    }

    void ModifySession(std::string& session_id, Session::ptr session)
    {
        if(sessions.find(session_id) == sessions.end()) {
            DLOG("session_id not found\n");
            return ;
        }

        sessions[session_id] = session;
    }

private:
    std::unordered_map<std::string, Session::ptr> sessions;
};

7.4 HttpProtocol.hpp

HttpProtocol.hpp包含对http协议的相关操作,如序列化反序列化,解码格式等

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <fstream>
#include "Session.hpp"

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)
    {}

    ~HttpRequest(){}

    // line: 输出型参数
    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);
        str.erase(0, pos + HTTPSEP.size());
        return true;
    }

    // 反序列化
    bool Deserialize(std::string& request) 
    {
        std::string line;
        if(!GetLine(request, &line))
            return false;
        
        _req_line = line;
        while(true)
        {
            bool flag = GetLine(request, &line);
            if(flag && line.empty()) {
                _req_content = request;
                break;
            } else if (flag && !line.empty()) {
                _req_header.push_back(line);
            } else {
                break;
            }
        }

        return true;
    }

    // 解析请求行
    void ParseReqLine()
    {
        std::stringstream ss(_req_line);
        ss >> _method >> _url >> _http_version;

        if(_url == "/") {
            _path += _url;
            _path += HOMEPAGE;
        } else {
            _path += _url;
        }
    }

    void ParseSuffix() 
    {
        auto pos = _path.rfind(".");
        if(pos == std::string::npos)
            _suffix = ".html";
        else
            _suffix = _path.substr(pos);    
    }

    void Parse()
    {
        // 解析请求行
        ParseReqLine();
        // 解析后缀 wwwroot/A/B/C.html -> .html
        ParseSuffix();
    }

    // 输出http请求信息
    void DebugHttp()
    {
        std::cout << "_req_line: " << _req_line << std::endl;
        for (auto &line : _req_header)
        {
            std::cout << "---> " << line << std::endl;
        }
        std::cout << "_req_blank: " << _req_blank << std::endl;
        std::cout << "_req_content: " << _req_content << std::endl;

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

    // 获取文件内容
    std::string GetFileContent(const std::string& path) 
    {
        std::ifstream in(path, std::ios::binary);
        if (!in.is_open())
            return "";
 
        in.seekg(0, in.end); // 设置文件流的读取位置
        int fSize = in.tellg(); // 获取文件大小
        in.seekg(0, in.beg);

        std::string content; // 将文件内容读取到content
        content.resize(fSize);
        in.read(&content[0], fSize);

        in.close();

        return content;
    }

    std::string GetFileContent()
    {
        return GetFileContent(_path);
    }

    std::string Get_ERROR_404()
    {
        return GetFileContent("./wwwRoot/404.html");
    }

    std::string Url()
    {
        return _url;
    }

    std::string Path()
    {
        return _path;
    }

    std::string Suffix()
    {
        return _suffix;
    }

private:
    // 报文请求内容
    std::string _req_line;
    std::vector<std::string> _req_header;
    std::string _req_blank;
    std::string _req_content;

    // 解析后
    std::string _method; // GET POST
    std::string _url;      // 请求行中的url
    std::string _http_version; // HTTP/1.1
    std::string _path; // "./wwwRoot"
    std::string _suffix; // 请求资源的后缀
};


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)
    {}
    ~HttpResponse(){}

    void SetCode(int code) 
    {
        _status_code = code;
    }

    void SetCodeDesc(const std::string& desc)
    {
        _status_code_desc = desc;
    }

    void SetHttpVersion(const std::string& version)
    {
        _http_version = version;
    }

    void SetContent(const std::string& content)
    {
        _resp_content = content;
    }

    void AddHeader(const std::string& header)
    {
        _resp_header.push_back(header);
    }

    void MakeStatusLine()
    {
        // HTTP/1.1 200 OK\r\n
        _status_line = _http_version + BlankSep + std::to_string(_status_code) + BlankSep + _status_code_desc + LineSep;
    }

    std::string Serialize()
    {
        std::string resp_str = _status_line;
        for (auto &header : _resp_header)
        {
            // resp_str += header + LineSep;
            resp_str += header;
        }

        resp_str += _resp_blank + _resp_content;

        return resp_str;
    }

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

    std::string _http_version;
    int _status_code;
    std::string _status_code_desc;
};

7.5 main.cc

cpp 复制代码
#include <iostream>
#include <memory>
#include <string>
#include <fstream>
#include <unistd.h>
#include "HttpProtocol.hpp"
#include "Session.hpp"
#include "TcpServer.hpp"

std::string CodeToDesc(int code)
{
    switch (code)
    {
    case 200:
        return "OK";
    case 301:
        return "Moved Permanently";
    case 302:
        return "Found";
    case 307:
        return "Temporary Redirect";
    case 400:
        return "Bad Request";
    case 404:
        return "Not Found";
    case 500:
        return "Internal Server Error";
    default:
        return "Unknown";
    }
}

std::string SuffixToType(std::string suffix)
{
    if(suffix == ".html") {
        return "text/html";
    } else if(suffix == ".png") {
        return "img/png";
    } else if(suffix == ".jpg") {
        return "img/jpg";
    } else {
        return "text/html";
    }
}

SessionManager online_user;

std::string HttpRequestHandler(std::string& request) {
    HttpRequest req;
    // 反序列化请求
    req.Deserialize(request);
    req.Parse();
    req.DebugHttp(); // 打印请求信息

    int status_code = 200;

    std::string content = req.GetFileContent();
    if(content.empty()) {
        status_code = 404;
        content = req.Get_ERROR_404(); // 返回404页面
    }

    HttpResponse resp;
    std::string user = "李天所";
    std::string password = "114514";
    std::string status = "logined"; // 示例状态,代表用户状态的值
    uint64_t login_time = static_cast<uint64_t>(std::time(nullptr)); // 当前时间的时间戳,确保使用 uint64_t 类型

    if (req.Url() == "/register") {
        std::string sessionid = online_user.AddSession(user, password, status, login_time);
        // 处理 sessionid,可能需要将其包含在响应中
        std::string session = "Set-Cookie: sessionid=" + sessionid + "\r\n";
        resp.AddHeader(session); // 添加 session 到响应头
    }

    if(req.Url() == "/login") {
        std::string sessionid;
        if(online_user.FindSession(sessionid)) {
            // 处理 sessionid,可能需要将其包含在响应中
            std::string session = "Set-Cookie: sessionid=" + sessionid + "\r\n";
            resp.AddHeader(session); // 添加 session 到响应头
        }
    }

    // 测试
    resp.SetCode(status_code);
    resp.SetCodeDesc(CodeToDesc(status_code));
    resp.MakeStatusLine();
    std::string content_len_str = "Content-Length: " + std::to_string(content.size()) + "\r\n";
    resp.AddHeader(content_len_str);
    std::string content_type_str = "Content-Type: " + SuffixToType(req.Suffix()) + "\r\n"; // 正文的类型
    resp.AddHeader(content_type_str);

    std::string namecookie = "Set-Cookie: username=李天所\r\n";
    resp.AddHeader(namecookie);
    std::string passwdcookie = "Set-Cookie: password=114514\r\n";
    resp.AddHeader(passwdcookie);
    std::string statuscookie = "Set-Cookie: status=logined\r\n";
    resp.AddHeader(statuscookie);

    resp.SetContent(content);

    return resp.Serialize();
}

int main(int argc, char* argv[])
{
    if(argc != 2) {
        std::cerr << "Usage: " << argv[0] << " <port>" << std::endl;
        return -1;
    }

    uint16_t port = std::stoi(argv[1]);

    std::unique_ptr<TcpServer> svr(new TcpServer(port, HttpRequestHandler));

    svr->Loop();

    return 0;
}

结果演示:

7.6 代码链接:

相关推荐
久绊A2 小时前
网络信息系统的整个生命周期
网络
_PowerShell2 小时前
[ DOS 命令基础 3 ] DOS 命令详解-文件操作相关命令
网络·dos命令入门到精通·dos命令基础·dos命令之文件操作命令详解·文件复制命令详解·文件对比命令详解·文件删除命令详解·文件查找命令详解
_.Switch5 小时前
高级Python自动化运维:容器安全与网络策略的深度解析
运维·网络·python·安全·自动化·devops
2401_850410835 小时前
文件系统和日志管理
linux·运维·服务器
qq_254674415 小时前
工作流初始错误 泛微提交流程提示_泛微协同办公平台E-cology8.0版本后台维护手册(11)–系统参数设置
网络
JokerSZ.5 小时前
【基于LSM的ELF文件安全模块设计】参考
运维·网络·安全
一只哒布刘7 小时前
NFS服务器
运维·服务器
小松学前端7 小时前
第六章 7.0 LinkList
java·开发语言·网络
城南vision8 小时前
计算机网络——TCP篇
网络·tcp/ip·计算机网络