应用层协议 HTTP

发布于:2025-07-15 ⋅ 阅读:(13) ⋅ 点赞:(0)

HTTP 协议


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

在互联网世界中,HTTP(HyperText Transfer Protocol,超文本传输协议)是一个至关重要的协议。它定义了客户端(如浏览器)与服务器之间如何通信,以交换或传输超文本(如 HTML 文档)。
HTTP 协议是客户端与服务器之间通信的基础。客户端通过 HTTP 协议向服务器发送请求,服务器收到请求后处理并返回响应。HTTP 协议是一个无连接、无状态的协议,即每次请求都需要建立新的连接,且服务器不会保存客户端的状态信息。

认识 URL


平时我们俗称的 "网址" 其实就是说的 URL

urlencode 和 urldecode


像 / ? : 等这样的字符, 已经被 url 当做特殊意义理解了. 因此这些字符不能随意出现.比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义.
转义的规则如下:
将需要转码的字符转为 16 进制,然后从右到左,取 4 位(不足 4 位直接处理),每 2 位做一位,前面加上%,编码成%XY 格式
例如:

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

HTTP 协议请求与响应格式

HTTP 请求

首行: [方法] + [url] + [版本]

Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\r\n 分隔;遇到空行表示 Header 部分结束

Body: 空行后面的内容都是 Body. Body 允许为空字符串. 如果 Body 存在, 则在Header 中会有一个 Content-Length 属性来标识 Body 的长度;

基本的应答格式

所以http请求是一个大号的字符串

编写 HTTP 请求的代码 - 验证 http 请求

公共代码

Socket.hpp

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

namespace SocketModule
{
    using namespace LogModule;
    const static int gbacklog =16;
    const  static int defaultfd =-1;
    // 基类socket
    class Socket
    {
    public:
        virtual ~Socket() {}
        virtual void SocketOrDie() = 0;
        virtual void BindOrDie(uint16_t port) = 0;
        virtual void ListenOrDie(int backlog) = 0;
        virtual  std::shared_ptr<Socket> Accept(InetAddr * client)= 0;
        virtual void Close()=0;
        virtual int Recv(std::string * out) = 0;
        virtual int Send(std::string  &message) = 0;
        virtual int Connect(const std::string &server_ip ,uint16_t port) =0;
    public:
    void BuildTcpClientSocketMethod()
        {
            SocketOrDie();
        }
        void BUildTcpLIstenSocketMethod(uint16_t port,int backlog = gbacklog)
        {
            SocketOrDie();
            BindOrDie(port);
            ListenOrDie(backlog);
        }
        // void BUildUdpSocketMethod()
        // {
        //     SocketOrDie();
        //     BindOrDie();
        // }
    };

    class TcpSocket : public Socket
    {
    public:
       TcpSocket()
       :_sockfd(defaultfd)
       {}
       TcpSocket(int fd): _sockfd(fd) {}
        
        ~TcpSocket() {}
        void SocketOrDie() override
        {
            _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
            if (_sockfd < 0)
            {
                LOG(Loglevel::FATAL) << "创建套接字失败!";
                exit(SOCKET_ERR);
            }
            LOG(Loglevel::INIF) << "创建套接字成功!";
        }
        void BindOrDie(uint16_t port) override
        {
            InetAddr localaddr(port);
            int n = ::bind(_sockfd, localaddr.NetAddrPtr(), localaddr.NetAddrLen());
            if (n < 0)
            {
                LOG(Loglevel::FATAL) << "绑定失败!";
                exit(BIND_ERR);
            }
            LOG(Loglevel::INIF) << "绑定成功!";
        }
        void ListenOrDie(int backlog) override
        {
            int n = ::listen(_sockfd, backlog);
            if (n < 0)
            {
                LOG(Loglevel::FATAL) << "监听失败!";
                exit(LISTEN_ERR);
            }
            LOG(Loglevel::INIF) << "监听成功!";
        }
        std::shared_ptr<Socket> Accept(InetAddr * client) override
        {
            struct sockaddr_in peer;
            socklen_t len  = sizeof(peer);
            int fd =::accept(_sockfd,CONV(peer),&len);
            if (fd < 0)
            {
                LOG(Loglevel::WARNING)<<"连接失败!";
                exit(-1);
            }
              LOG(Loglevel::WARNING)<<"连接成功!";
               client->SetAddr(peer);
               return  std::make_shared<TcpSocket>(fd);
        }
        void Close() override
        {
            if(_sockfd >=0)
            {
                ::close(_sockfd);
            }
        }
         int 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;
              }
            return n;

         }
         int Send(std::string  &message) override
         {
           return send(_sockfd,message.c_str(),message.size(),0);
         }
         int Connect(const std::string &server_ip ,uint16_t port) override
         {
           InetAddr server(server_ip,port);
           return ::connect(_sockfd,server.NetAddrPtr(),server.NetAddrLen()) ;    
         }
    private:
        int _sockfd; //
    };

    // class UdpSocket : public Socket
    // {

    // };
}

Mutex.hpp

#pragma once
#include <pthread.h>
#include <iostream>
namespace MutexModule
{ 
    class Mutex
    {
    public:
        Mutex()
        {
            pthread_mutex_init(&_mutex, nullptr);
        }

        void Lock()
        {
            int n = pthread_mutex_lock(&_mutex);
            (void)n;
        }
        void Unlock()
        {
            int n = pthread_mutex_unlock(&_mutex);
            (void)n;
        }
        ~Mutex()
        {
            pthread_mutex_destroy(&_mutex);
        }
        pthread_mutex_t *get()
        {
            return &_mutex;
        }
    private:
        pthread_mutex_t _mutex;
    };
    class LockGuard
    {
        public:
        LockGuard(Mutex &mutex):_mutex(mutex)
        {
        _mutex.Lock();
        }
        ~LockGuard()
        {
            _mutex.Unlock();
        }
        private:
        Mutex &_mutex;
    };
}

Log.hpp

#ifndef __LOG_HPP__
#define __LOG_HPP__

#include <iostream>
#include <string>
#include "Mutex.hpp"
#include <filesystem>
#include <fstream>
#include <memory>
#include <unistd.h>
#include <sstream>
#include<ctime>

namespace LogModule
{
    const std::string sep = "\r\n";
    using namespace  MutexModule ;
    
    // 2.刷新策略
    class LogStrategy
    {
    public:
        ~LogStrategy() = default;
        virtual void SyncLog(const std::string &message) = 0;
    };

    // 显示器刷新日志的策略
    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        ConsoleLogStrategy() {}
        ~ConsoleLogStrategy() {}
        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);
            std::cout << message << sep;
        }

    private:
        Mutex _mutex;
    };

    // 缺省文件路径以及文件本身
    const std::string defaultpath = "./log";
    const std::string defaultfile = "my.log";
    // 文件刷新日志的策略
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile)
            : _path(path), _file(file)
        {
            LockGuard lockguard(_mutex);
            if (std::filesystem::exists(_path)) // 判断路径是否存在
            {
                return;
            }
            try
            {
                std::filesystem::create_directories(_path);
            }
            catch (const std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << '\n';
            }
        }

        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);
            std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file;
            std::ofstream out(filename, std::ios::app); // 追加写入
            if (!out.is_open())
            {
                return;
            }
            out << message << sep;
            out.close();
        }
        ~FileLogStrategy() {}

    private:
        Mutex _mutex;
        std::string _path; // 日志文件的路径
        std::string _file; // 要打印的日志文件
    };

    // 形成日志等级
    enum class Loglevel
    {
        DEBUG,
        INIF,
        WARNING,
        ERROR,
        FATAL
    };
    std::string Level2Str(Loglevel level)
    {
        switch (level)
        {
        case Loglevel::DEBUG:
            return "DEBUG";
        case Loglevel::INIF:
            return "INIF";
        case Loglevel::WARNING:
            return "WARNING";
        case Loglevel::ERROR:
            return "ERROR";
        case Loglevel::FATAL:
            return "FATAL";
        default:
            return "UNKNOWN";
        }
    }

    std::string GetTimeStamp()
    {
       time_t cuur =time(nullptr);
       struct tm curr_tm;
       localtime_r(&cuur,&curr_tm);
       char buffer[128];
       snprintf(buffer,sizeof(buffer),"%4d-%02d-%02d %02d:%02d:%02d",
       curr_tm.tm_year+1900,
       curr_tm.tm_mon+1,
       curr_tm.tm_mday,
       curr_tm.tm_hour,
       curr_tm.tm_min,
       curr_tm.tm_sec
       );
       return buffer;
    }
    class Logger
    {
    public:
        Logger()
        {
            EnableConsoleLogStrategy();
        }
        // 选择某种策略
        // 1.文件
        void EnableFileLogStrategy()
        {
            _ffush_strategy = std::make_unique<FileLogStrategy>();
        }
        // 显示器
        void EnableConsoleLogStrategy()
        {
            _ffush_strategy = std::make_unique<ConsoleLogStrategy>();
        }

        
        // 表示的是未来的一条日志
        class LogMessage
        {
        public:
            LogMessage(Loglevel &level, std::string &src_name, int line_number, Logger &logger)
                : _curr_time(GetTimeStamp())
                , _level(level)
                , _pid(getpid())
                , _src_name(src_name)
                , _line_number(line_number)
                , _logger(logger)
            {
                // 合并左半部分
                std::stringstream ss;
                ss << "[" << _curr_time << "] "
                   << "[" << Level2Str(_level) << "] "
                   << "[" << _pid << "] "
                   << "[" << _src_name << "] "
                   << "[" << _line_number << "] "
                   << "- ";
                _loginfo = ss.str();
            }
            template <typename T>
            LogMessage &operator<<(const T &info)
            {
                // 右半部分,可变
                std::stringstream ss;
                ss << info;
                _loginfo += ss.str();
                return *this;
            }
            ~LogMessage()
            {
                if (_logger._ffush_strategy)
                {
                    _logger._ffush_strategy->SyncLog(_loginfo);
                }
            }
           

        private:
            std::string _curr_time; // 日志时间
            Loglevel _level;        // 日志状态
            pid_t _pid;             // 进程pid
            std::string _src_name;  // 文件名称
            int _line_number;       // 对应的行号
            std::string _loginfo;   // 合并之后的一条完整信息
            Logger &_logger;
        };
         LogMessage operator()(Loglevel level, std::string src_name, int line_number)
            {
                return LogMessage(level, src_name, line_number, *this);
            }
        ~Logger() {}

    private:
        std::unique_ptr<LogStrategy> _ffush_strategy;
    };
    //全局日志对象
    Logger logger;

    //使用宏,简化用户操作,获取文件名和行号
    // __FILE__  一个宏,替换完成后目标文件的文件名
    // __LINE__  一个宏,替换完成后目标文件对应的行号
    #define LOG(level) logger(level,__FILE__,__LINE__) 
    #define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()
    #define Enable_File_Log_Strategy()    logger.EnableFileLogStrategy()
     
}

#endif

Main.cc

#include"Http.hpp"

// http port
int main(int argc,char* argv[])
{
    if(argc != 2)
    {
        std::cout << "Usage: "<<argv[0]<<" port"<<std::endl;
        exit(USAGE_ERR);
    }
    uint16_t port = std::stoi(argv[1]);
    std::unique_ptr<Http> httpsvr = std::make_unique<Http>(port);
    httpsvr->Start();
    return 0;
}

Common.hpp

#pragma once

#include <iostream>
#include<functional>
#include <string>
#include <cstring>
#include <memory>
#include<unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
enum ExitCode
{
    OK = 0,
    USAGE_ERR,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR,
    CONNECT_ERR,
    FORK_ERR
};

class NoCopy
{

public:
NoCopy(){}
~NoCopy(){}
NoCopy(const NoCopy&)=delete;
const NoCopy& operator= (const NoCopy &)=delete;
};

#define CONV(addr) ((struct sockaddr *)&addr)

InetAddr.hpp

#pragma once
#include "Common.hpp"

class InetAddr
{

public:
    InetAddr()
    {
        
    }
    InetAddr(struct sockaddr_in &addr)
    {   
        SetAddr(addr);
    }
    InetAddr(const std::string &ip,uint16_t port)
        :_ip(ip)
        ,_port(port)
    {
        //主机转网络
       memset(&_addr,0,sizeof(_addr));
       _addr.sin_family=AF_INET;
       inet_pton(AF_INET,_ip.c_str(),&_addr.sin_addr);
       _addr.sin_port=htons(_port);
       
    }
    InetAddr(uint16_t port)
        :_port(port),_ip("0")
    {   //端口转
       memset(&_addr,0,sizeof(_addr));
       _addr.sin_family=AF_INET;
       _addr.sin_addr.s_addr=INADDR_ANY;
       _addr.sin_port = htons(_port);
    }
   

    uint16_t Port() { return _port; }

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

    void SetAddr(struct sockaddr_in &addr) 
    {  //网络转主机
        _addr=addr;
        _port = ntohs(addr.sin_port);
        //_ip = inet_ntoa(addr.sin_addr);
        char ipbuffer[64];
        inet_ntop(AF_INET,&_addr.sin_addr,ipbuffer,sizeof(_addr));
        _ip=ipbuffer;
    }
    const struct sockaddr_in &NetAddr() { return _addr; }
    const struct sockaddr *NetAddrPtr() { return CONV(_addr); }

    socklen_t NetAddrLen() { return sizeof(_addr); }
    bool operator==(const InetAddr &addr)
    {
        return addr._ip == _ip && addr._port == _port;
    }
    std::string StringAddr()
    {
        return _ip + ":" + std::to_string(_port);
    }
    ~InetAddr() {}
private:
    struct sockaddr_in _addr;
    std::string _ip;
    uint16_t _port;
};

 Tcpserver.hpp

#include "Socket.hpp"
#include<iostream>
#include<memory>
#include <sys/wait.h>
#include <functional>

using namespace LogModule;
using namespace SocketModule;

using ioservice_t = std::function<void(std::shared_ptr<Socket>&sock,InetAddr &client)>;

class TcpServer
{

public:
TcpServer(uint16_t port)
:_port(port)
,_listensockptr(std::make_unique<TcpSocket>())
,_isrunning(false)
{
    _listensockptr->BUildTcpLIstenSocketMethod(_port);
}
void Start(ioservice_t callback)
{
  _isrunning =true;
  while(_isrunning)
  {
    InetAddr client;
   auto sock = _listensockptr->Accept(&client);
  
    if(sock == nullptr)
    {
        continue;
    }
    LOG(Loglevel::DEBUG)<<"accept is running"<<client.StringAddr();

    //sock && client
    pid_t id =fork();
    if (id < 0)
    {
        LOG(Loglevel::FATAL)<<"创建子进程失败";
        exit(FORK_ERR);
    }
    else if(id == 0)
    {
     //子进程 关闭listensock
     _listensockptr->Close();
     if(fork()>0)
     {
        exit(0);
     }
     //孙子进程是孤儿进程
     callback(sock,client);
     exit(OK);
    }
    else
    {
      //父进程 关闭sock
      sock->Close();
      pid_t rid = ::waitpid(id,nullptr,0);
      (void)rid;
    }

  }
  _isrunning =false;

}
~TcpServer(){}
private:
uint16_t _port;
std::unique_ptr<Socket>_listensockptr;
bool _isrunning;
};

 Makefile

myhttp:Main.cc
	g++ -o $@ $^ -std=c++17
.PHONY:clean
clean:
	rm -f myhttp

Http.hpp

#pragma once
#include "TcpServer.hpp"
#include <memory>
#include <iostream>
#include <string>
#include <unordered_map>

using namespace SocketModule;

const std::string gspace = " ";
const std::string glinespace = "\r\n";
const std::string glinesep = ": ";

class HttpRequest
{
public:
    std::string Serialize()
    {
        return std::string();
    }
    bool Deserialize(std::string &reqstr)
    {
        return true;
    }


    HttpRequest() {}
    ~HttpRequest() {}

private:
    std::string _method;
    std::string _url;
    std::string _version;

    std::unordered_map<std::string, std::string> _headers;
    std::string _blankline;
    std::string _text;
};

class HttpResponse
{
    public:
        std::string Serialize()
    {
        std::string status_line = _version +" "+gspace+std::to_string(_code)+gspace+_desc+glinespace;
        std::string resp_header;
        for(auto &header : _headrs)
        {
            std::string line = header.first+glinesep+header.second+glinespace;
            resp_header+=line;

        }

        return status_line+resp_header+_blankline+_text;
    }
    bool Deserialize(std::string &reqstr)
    {
        return true;
    }
 HttpResponse()
 :_blankline(glinespace)
 {}
 ~HttpResponse(){}


std::string _version; //版本
int _code; //状态码
std::string _desc; //状态码描述
std::unordered_map<std::string,std::string> _headrs;//相应报头
std::string _blankline;//空行
std::string _text;//正文
};

class Http
{
public:
    Http(uint16_t port)
        : tsvrp(std::make_unique<TcpServer>(port))
    {
    }
    void HandlenHttpRequest(std::shared_ptr<Socket> &sock, InetAddr &client)
    {
        #ifndef DEBUG
        #define DEBUG
        std::string httprequest;
        sock->Recv(&httprequest);
        std::cout<<httprequest<<std::endl;
        //构建应答,内存级 + 固定
        HttpResponse resp;
        resp._version = "HTTP?1.1";
        resp._code = 200;//正常
        resp._desc ="OK";
        resp._text ="<!DOCTYPE html>\
            <html lang=\"en\">\
             <head>\
             <meta charset=\"UTF-8\">\
             <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
              <title>Hello World</title>\
            </head>\
            <body>\
            <h1>Hello World</h1>\
            </body>\
            </html>";
            std::string response_str = resp.Serialize();
            sock->Send(response_str);
        #endif
        
    }
    void Start()
    {
        tsvrp->Start([this](std::shared_ptr<Socket> &sock, InetAddr &client)
                     { this->HandlenHttpRequest(sock, client); });
    }
    ~Http() {}

private:
    std::unique_ptr<TcpServer> tsvrp;
};

这是浏览器返回的部分http

respones返回一个html

 HTTP 的方法

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

HTTP 常见方法


1.GET 方法(重点)


用途:用于请求 URL 指定的资源。
示例:GET /index.html HTTP/1.1
特性:指定资源经服务器端解析后返回响应内容。
form 表单:https://www.runoob.com/html/html-forms.html

2.POST 方法(重点)


用途:用于传输实体的主体,通常用于提交表单数据。
示例:POST /submit.cgi HTTP/1.1
特性:可以发送大量的数据给服务器,并且数据包含在请求体中。
form 表单:https://www.runoob.com/html/html-forms.html

3.PUT 方法(不常用)


用途:用于传输文件,将请求报文主体中的文件保存到请求 URL 指定的位置。
示例:PUT /example.html HTTP/1.1
特性:不太常用,但在某些情况下,如 RESTful API 中,用于更新资源。


4. HEAD 方法


用途:与 GET 方法类似,但不返回报文主体部分,仅返回响应头。
示例:HEAD /index.html HTTP/1.1
特性:用于确认 URL 的有效性及资源更新的日期时间等。

5. DELETE 方法(不常用)


用途:用于删除文件,是 PUT 的相反方法。
示例:DELETE /example.html HTTP/1.1
特性:按请求 URL 删除指定的资源。


6. OPTIONS 方法


用途:用于查询针对请求 URL 指定的资源支持的方法。
示例:OPTIONS * HTTP/1.1
特性:返回允许的方法,如 GET、POST 等。

HttpDamon 1

Util.hpp

#pragma once
#include <iostream>
#include <string>
#include <fstream>

class Util
{

    public:
    //打开指定文件
static bool ReadFileContent(const std::string &filename,std::string *out)
    {
        //version1
        std::ifstream in(filename);
        if(!in.is_open())
        {
            return false;
        }
        std::string line;
        while(std::getline(in,line))
        {
            *out+=line;
        }
        in.close();
        return true;
    }
};

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hello World</title>
</head>
<body>
    <h1>Hello World !</h1>
     <h1>Are you OK ?</h1>
</body>
</html>

Common.hpp

#pragma once

#include <iostream>
#include<functional>
#include <string>
#include <cstring>
#include <memory>
#include<unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
enum ExitCode
{
    OK = 0,
    USAGE_ERR,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR,
    CONNECT_ERR,
    FORK_ERR
};

class NoCopy
{

public:
NoCopy(){}
~NoCopy(){}
NoCopy(const NoCopy&)=delete;
const NoCopy& operator= (const NoCopy &)=delete;
};

#define CONV(addr) ((struct sockaddr *)&addr)

Http.hpp

#pragma once
#include "TcpServer.hpp"
#include <memory>
#include <iostream>
#include <string>
#include <unordered_map>
#include"Util.hpp"


using namespace SocketModule;

const std::string gspace = " ";
const std::string glinespace = "\r\n";
const std::string glinesep = ": ";

const std::string webroot = "./wwwroot";
const std::string homepage = "/index.html";
class HttpRequest
{
public:
    std::string Serialize()
    {
        return std::string();
    }
    bool Deserialize(std::string &reqstr)
    {
        return true;
    }


    HttpRequest() {}
    ~HttpRequest() {}

private:
    std::string _method;
    std::string _url;
    std::string _version;

    std::unordered_map<std::string, std::string> _headers;
    std::string _blankline;
    std::string _text;
};

class HttpResponse
{
    public:
        std::string Serialize()
    {
        std::string status_line = _version +" "+gspace+std::to_string(_code)+gspace+_desc+glinespace;
        std::string resp_header;
        for(auto &header : _headrs)
        {
            std::string line = header.first+glinesep+header.second+glinespace;
            resp_header+=line;

        }

        return status_line+resp_header+_blankline+_text;
    }
    bool Deserialize(std::string &reqstr)
    {
        return true;
    }
 HttpResponse()
 :_blankline(glinespace)
 {}
 ~HttpResponse(){}


std::string _version; //版本
int _code; //状态码
std::string _desc; //状态码描述
std::unordered_map<std::string,std::string> _headrs;//相应报头
std::string _blankline;//空行
std::string _text;//正文
};

class Http
{
public:
    Http(uint16_t port)
        : tsvrp(std::make_unique<TcpServer>(port))
    {
    }
    void HandlenHttpRequest(std::shared_ptr<Socket> &sock, InetAddr &client)
    {
        #ifndef DEBUG
        #define DEBUG
        std::string httprequest;
        sock->Recv(&httprequest);
        std::cout<<httprequest<<std::endl;
        //构建应答,内存级 + 固定
        HttpResponse resp;
        resp._version = "HTTP/1.1";
        resp._code = 200;//正常
        resp._desc ="OK";

        std::string filename= webroot+homepage;// ./wwwroot/index.html

        bool res = Util::ReadFileContent(filename,&(resp._text));
        (void)res;
        std::string response_str = resp.Serialize();
        sock->Send(response_str);
        #endif
        
    }
    void Start()
    {
        tsvrp->Start([this](std::shared_ptr<Socket> &sock, InetAddr &client)
                     { this->HandlenHttpRequest(sock, client); });
    }
    ~Http() {}

private:
    std::unique_ptr<TcpServer> tsvrp;
};

InerAddr.hpp

#pragma once
#include "Common.hpp"

class InetAddr
{

public:
    InetAddr()
    {
        
    }
    InetAddr(struct sockaddr_in &addr)
    {   
        SetAddr(addr);
    }
    InetAddr(const std::string &ip,uint16_t port)
        :_ip(ip)
        ,_port(port)
    {
        //主机转网络
       memset(&_addr,0,sizeof(_addr));
       _addr.sin_family=AF_INET;
       inet_pton(AF_INET,_ip.c_str(),&_addr.sin_addr);
       _addr.sin_port=htons(_port);
       
    }
    InetAddr(uint16_t port)
        :_port(port),_ip("0")
    {   //端口转
       memset(&_addr,0,sizeof(_addr));
       _addr.sin_family=AF_INET;
       _addr.sin_addr.s_addr=INADDR_ANY;
       _addr.sin_port = htons(_port);
    }
   

    uint16_t Port() { return _port; }

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

    void SetAddr(struct sockaddr_in &addr) 
    {  //网络转主机
        _addr=addr;
        _port = ntohs(addr.sin_port);
        //_ip = inet_ntoa(addr.sin_addr);
        char ipbuffer[64];
        inet_ntop(AF_INET,&_addr.sin_addr,ipbuffer,sizeof(_addr));
        _ip=ipbuffer;
    }
    const struct sockaddr_in &NetAddr() { return _addr; }
    const struct sockaddr *NetAddrPtr() { return CONV(_addr); }

    socklen_t NetAddrLen() { return sizeof(_addr); }
    bool operator==(const InetAddr &addr)
    {
        return addr._ip == _ip && addr._port == _port;
    }
    std::string StringAddr()
    {
        return _ip + ":" + std::to_string(_port);
    }
    ~InetAddr() {}
private:
    struct sockaddr_in _addr;
    std::string _ip;
    uint16_t _port;
};

Log.hpp

#ifndef __LOG_HPP__
#define __LOG_HPP__

#include <iostream>
#include <string>
#include "Mutex.hpp"
#include <filesystem>
#include <fstream>
#include <memory>
#include <unistd.h>
#include <sstream>
#include<ctime>

namespace LogModule
{
    const std::string sep = "\r\n";
    using namespace  MutexModule ;
    
    // 2.刷新策略
    class LogStrategy
    {
    public:
        ~LogStrategy() = default;
        virtual void SyncLog(const std::string &message) = 0;
    };

    // 显示器刷新日志的策略
    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        ConsoleLogStrategy() {}
        ~ConsoleLogStrategy() {}
        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);
            std::cout << message << sep;
        }

    private:
        Mutex _mutex;
    };

    // 缺省文件路径以及文件本身
    const std::string defaultpath = "./log";
    const std::string defaultfile = "my.log";
    // 文件刷新日志的策略
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile)
            : _path(path), _file(file)
        {
            LockGuard lockguard(_mutex);
            if (std::filesystem::exists(_path)) // 判断路径是否存在
            {
                return;
            }
            try
            {
                std::filesystem::create_directories(_path);
            }
            catch (const std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << '\n';
            }
        }

        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);
            std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file;
            std::ofstream out(filename, std::ios::app); // 追加写入
            if (!out.is_open())
            {
                return;
            }
            out << message << sep;
            out.close();
        }
        ~FileLogStrategy() {}

    private:
        Mutex _mutex;
        std::string _path; // 日志文件的路径
        std::string _file; // 要打印的日志文件
    };

    // 形成日志等级
    enum class Loglevel
    {
        DEBUG,
        INIF,
        WARNING,
        ERROR,
        FATAL
    };
    std::string Level2Str(Loglevel level)
    {
        switch (level)
        {
        case Loglevel::DEBUG:
            return "DEBUG";
        case Loglevel::INIF:
            return "INIF";
        case Loglevel::WARNING:
            return "WARNING";
        case Loglevel::ERROR:
            return "ERROR";
        case Loglevel::FATAL:
            return "FATAL";
        default:
            return "UNKNOWN";
        }
    }

    std::string GetTimeStamp()
    {
       time_t cuur =time(nullptr);
       struct tm curr_tm;
       localtime_r(&cuur,&curr_tm);
       char buffer[128];
       snprintf(buffer,sizeof(buffer),"%4d-%02d-%02d %02d:%02d:%02d",
       curr_tm.tm_year+1900,
       curr_tm.tm_mon+1,
       curr_tm.tm_mday,
       curr_tm.tm_hour,
       curr_tm.tm_min,
       curr_tm.tm_sec
       );
       return buffer;
    }
    class Logger
    {
    public:
        Logger()
        {
            EnableConsoleLogStrategy();
        }
        // 选择某种策略
        // 1.文件
        void EnableFileLogStrategy()
        {
            _ffush_strategy = std::make_unique<FileLogStrategy>();
        }
        // 显示器
        void EnableConsoleLogStrategy()
        {
            _ffush_strategy = std::make_unique<ConsoleLogStrategy>();
        }

        
        // 表示的是未来的一条日志
        class LogMessage
        {
        public:
            LogMessage(Loglevel &level, std::string &src_name, int line_number, Logger &logger)
                : _curr_time(GetTimeStamp())
                , _level(level)
                , _pid(getpid())
                , _src_name(src_name)
                , _line_number(line_number)
                , _logger(logger)
            {
                // 合并左半部分
                std::stringstream ss;
                ss << "[" << _curr_time << "] "
                   << "[" << Level2Str(_level) << "] "
                   << "[" << _pid << "] "
                   << "[" << _src_name << "] "
                   << "[" << _line_number << "] "
                   << "- ";
                _loginfo = ss.str();
            }
            template <typename T>
            LogMessage &operator<<(const T &info)
            {
                // 右半部分,可变
                std::stringstream ss;
                ss << info;
                _loginfo += ss.str();
                return *this;
            }
            ~LogMessage()
            {
                if (_logger._ffush_strategy)
                {
                    _logger._ffush_strategy->SyncLog(_loginfo);
                }
            }
           

        private:
            std::string _curr_time; // 日志时间
            Loglevel _level;        // 日志状态
            pid_t _pid;             // 进程pid
            std::string _src_name;  // 文件名称
            int _line_number;       // 对应的行号
            std::string _loginfo;   // 合并之后的一条完整信息
            Logger &_logger;
        };
         LogMessage operator()(Loglevel level, std::string src_name, int line_number)
            {
                return LogMessage(level, src_name, line_number, *this);
            }
        ~Logger() {}

    private:
        std::unique_ptr<LogStrategy> _ffush_strategy;
    };
    //全局日志对象
    Logger logger;

    //使用宏,简化用户操作,获取文件名和行号
    // __FILE__  一个宏,替换完成后目标文件的文件名
    // __LINE__  一个宏,替换完成后目标文件对应的行号
    #define LOG(level) logger(level,__FILE__,__LINE__) 
    #define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()
    #define Enable_File_Log_Strategy()    logger.EnableFileLogStrategy()
     
}

#endif

Main.cc

#include"Http.hpp"

// http port
int main(int argc,char* argv[])
{
    if(argc != 2)
    {
        std::cout << "Usage: "<<argv[0]<<" port"<<std::endl;
        exit(USAGE_ERR);
    }
    uint16_t port = std::stoi(argv[1]);
    std::unique_ptr<Http> httpsvr = std::make_unique<Http>(port);
    httpsvr->Start();
    return 0;
}

Makefile

myhttp:Main.cc
	g++ -o $@ $^ -std=c++17
.PHONY:clean
clean:
	rm -f myhttp

Mutex.hpp

#pragma once
#include <pthread.h>
#include <iostream>
namespace MutexModule
{ 
    class Mutex
    {
    public:
        Mutex()
        {
            pthread_mutex_init(&_mutex, nullptr);
        }

        void Lock()
        {
            int n = pthread_mutex_lock(&_mutex);
            (void)n;
        }
        void Unlock()
        {
            int n = pthread_mutex_unlock(&_mutex);
            (void)n;
        }
        ~Mutex()
        {
            pthread_mutex_destroy(&_mutex);
        }
        pthread_mutex_t *get()
        {
            return &_mutex;
        }
    private:
        pthread_mutex_t _mutex;
    };
    class LockGuard
    {
        public:
        LockGuard(Mutex &mutex):_mutex(mutex)
        {
        _mutex.Lock();
        }
        ~LockGuard()
        {
            _mutex.Unlock();
        }
        private:
        Mutex &_mutex;
    };
}

Socket.hpp

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

namespace SocketModule
{
    using namespace LogModule;
    const static int gbacklog =16;
    const  static int defaultfd =-1;
    // 基类socket
    class Socket
    {
    public:
        virtual ~Socket() {}
        virtual void SocketOrDie() = 0;
        virtual void BindOrDie(uint16_t port) = 0;
        virtual void ListenOrDie(int backlog) = 0;
        virtual  std::shared_ptr<Socket> Accept(InetAddr * client)= 0;
        virtual void Close()=0;
        virtual int Recv(std::string * out) = 0;
        virtual int Send(std::string  &message) = 0;
        virtual int Connect(const std::string &server_ip ,uint16_t port) =0;
    public:
    void BuildTcpClientSocketMethod()
        {
            SocketOrDie();
        }
        void BUildTcpLIstenSocketMethod(uint16_t port,int backlog = gbacklog)
        {
            SocketOrDie();
            BindOrDie(port);
            ListenOrDie(backlog);
        }
        // void BUildUdpSocketMethod()
        // {
        //     SocketOrDie();
        //     BindOrDie();
        // }
    };

    class TcpSocket : public Socket
    {
    public:
       TcpSocket()
       :_sockfd(defaultfd)
       {}
       TcpSocket(int fd): _sockfd(fd) {}
        
        ~TcpSocket() {}
        void SocketOrDie() override
        {
            _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
            if (_sockfd < 0)
            {
                LOG(Loglevel::FATAL) << "创建套接字失败!";
                exit(SOCKET_ERR);
            }
            LOG(Loglevel::INIF) << "创建套接字成功!";
        }
        void BindOrDie(uint16_t port) override
        {
            InetAddr localaddr(port);
            int n = ::bind(_sockfd, localaddr.NetAddrPtr(), localaddr.NetAddrLen());
            if (n < 0)
            {
                LOG(Loglevel::FATAL) << "绑定失败!";
                exit(BIND_ERR);
            }
            LOG(Loglevel::INIF) << "绑定成功!";
        }
        void ListenOrDie(int backlog) override
        {
            int n = ::listen(_sockfd, backlog);
            if (n < 0)
            {
                LOG(Loglevel::FATAL) << "监听失败!";
                exit(LISTEN_ERR);
            }
            LOG(Loglevel::INIF) << "监听成功!";
        }
        std::shared_ptr<Socket> Accept(InetAddr * client) override
        {
            struct sockaddr_in peer;
            socklen_t len  = sizeof(peer);
            int fd =::accept(_sockfd,CONV(peer),&len);
            if (fd < 0)
            {
                LOG(Loglevel::WARNING)<<"连接失败!";
                exit(-1);
            }
              LOG(Loglevel::WARNING)<<"连接成功!";
               client->SetAddr(peer);
               return  std::make_shared<TcpSocket>(fd);
        }
        void Close() override
        {
            if(_sockfd >=0)
            {
                ::close(_sockfd);
            }
        }
         int 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;
              }
            return n;

         }
         int Send(std::string  &message) override
         {
           return send(_sockfd,message.c_str(),message.size(),0);
         }
         int Connect(const std::string &server_ip ,uint16_t port) override
         {
           InetAddr server(server_ip,port);
           return ::connect(_sockfd,server.NetAddrPtr(),server.NetAddrLen()) ;    
         }
    private:
        int _sockfd; //
    };

    // class UdpSocket : public Socket
    // {

    // };
}

TcpServer.hpp

#include "Socket.hpp"
#include<iostream>
#include<memory>
#include <sys/wait.h>
#include <functional>

using namespace LogModule;
using namespace SocketModule;

using ioservice_t = std::function<void(std::shared_ptr<Socket>&sock,InetAddr &client)>;

class TcpServer
{

public:
TcpServer(uint16_t port)
:_port(port)
,_listensockptr(std::make_unique<TcpSocket>())
,_isrunning(false)
{
    _listensockptr->BUildTcpLIstenSocketMethod(_port);
}
void Start(ioservice_t callback)
{
  _isrunning =true;
  while(_isrunning)
  {
    InetAddr client;
   auto sock = _listensockptr->Accept(&client);
  
    if(sock == nullptr)
    {
        continue;
    }
    LOG(Loglevel::DEBUG)<<"accept is running"<<client.StringAddr();

    //sock && client
    pid_t id =fork();
    if (id < 0)
    {
        LOG(Loglevel::FATAL)<<"创建子进程失败";
        exit(FORK_ERR);
    }
    else if(id == 0)
    {
     //子进程 关闭listensock
     _listensockptr->Close();
     if(fork()>0)
     {
        exit(0);
     }
     //孙子进程是孤儿进程
     callback(sock,client);
     exit(OK);
    }
    else
    {
      //父进程 关闭sock
      sock->Close();
      pid_t rid = ::waitpid(id,nullptr,0);
      (void)rid;
    }

  }
  _isrunning =false;

}
~TcpServer(){}
private:
uint16_t _port;
std::unique_ptr<Socket>_listensockptr;
bool _isrunning;
};

HTTP 的状态码

最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)

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

 以下是仅包含重定向相关状态码的表格:

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

关于重定向的验证,以 301 为代表 ,HTTP 状态码 301(永久重定向)和 302(临时重定向)都依赖 Location 选项。以下是关于两者依赖 Location 选项的详细说明:
HTTP 状态码 301(永久重定向):

当服务器返回 HTTP 301 状态码时,表示请求的资源已经被永久移动到新的位置。在这种情况下,服务器会在响应中添加一个 Location 头部,用于指定资源的新位置。这个 Location 头部包含了新的 URL 地址,浏览器会自动重定向到该地址。例如,在 HTTP 响应中,可能会看到类似于以下的头部信息:

HTTP/1.1 301 Moved Permanently\r\n
Location: https://www.new-url.com\r\n

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


当服务器返回 HTTP 302 状态码时,表示请求的资源临时被移动到新的位置。同样地,服务器也会在响应中添加一个 Location 头部来指定资源的新位置。浏览器会暂时使用新的 URL 进行后续的请求,但不会缓存这个重定向。例如,在 HTTP 响应中,可能会看到类似于以下的头部信息:

 

HTTP/1.1 302 Found\r\n
Location: https://www.new-url.com\r\n

总结:无论是 HTTP 301 还是 HTTP 302 重定向,都需要依赖 Location 选项来指定资源的新位置。这个 Location 选项是一个标准的 HTTP 响应头部,用于告诉浏览器应该将请求重定向到哪个新的 URL 地址。

HTTP 常见 Header


 

  • Content-Type: 数据类型(text/html 等)
  • Content-Length: Body 的长度
  • Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上
  • User-Agent: 声明用户的操作系统和浏览器版本信息
  • referer: 当前页面是从哪个页面跳转过来的
  • Location: 搭配 3xx 状态码使用, 告诉客户端接下来要去哪里访问
  • Cookie: 用于在客户端存储少量信息. 通常用于实现会(session)的功能

 


 HTTPDamon 2


 

  404.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>404 - Page Not Found</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f4f4f4;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            flex-direction: column;
            text-align: center;
        }
        .error-container {
            max-width: 600px;
            text-align: center;
        }
        .error-container h1 {
            font-size: 3em;
            color: #d9534f;
            margin-bottom: 10px;
        }
        .error-container p {
            font-size: 1.2em;
            color: #333;
            margin-bottom: 20px;
        }
        .error-container a {
            display: inline-block;
            padding: 10px 20px;
            background-color: #007bff;
            color: #fff;
            text-decoration: none;
            border-radius: 4px;
            font-size: 1em;
        }
        .error-container a:hover {
            background-color: #0056b3;
        }
    </style>
</head>
<body>
    <div class="error-container">
        <h1>404 - Page Not Found</h1>
        <p>Sorry, the page you are looking for does not exist.</p>
        <a href="/">Go to Home Page</a>
    </div>
</body>
</html>

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Home Page</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f4f4f4;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            flex-direction: column;
        }
        .home-container {
            text-align: center;
        }
        .home-container h1 {
            font-size: 2.5em;
            margin-bottom: 20px;
        }
        .button-group {
            display: flex;
            justify-content: center;
            gap: 20px;
        }
        .button-group a {
            display: inline-block;
            padding: 10px 20px;
            background-color: #007bff;
            color: #fff;
            text-decoration: none;
            border-radius: 4px;
            font-size: 1em;
        }
        .button-group a:hover {
            background-color: #0056b3;
        }
    </style>
</head>
<body>
    <div class="home-container">
        <h1>Welcome to Our Website</h1>
        <div class="button-group">
            <a href="Login.html">Login</a>
            <a href="Register.html">Register</a>
            <img src="/image/1.png" alt="图片1">
             
            <img src="/image/2.jpg" alt="图片2">
        </div>
        
    </div>
</body>
</html>

Login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Login Page</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f4f4f4;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
        }
        .login-container {
            background-color: #fff;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
            width: 300px;
            text-align: center;
        }
        .login-container h2 {
            margin-bottom: 20px;
        }
        .form-group {
            margin-bottom: 15px;
        }
        .form-group label {
            display: block;
            margin-bottom: 5px;
            text-align: left;
        }
        .form-group input {
            width: 100%;
            padding: 10px;
            border: 1px solid #ccc;
            border-radius: 4px;
            box-sizing: border-box;
        }
        .form-group input:focus {
            outline: none;
            border-color: #007bff;
        }
        .form-group button {
            width: 100%;
            padding: 10px;
            border: none;
            background-color: #007bff;
            color: #fff;
            border-radius: 4px;
            cursor: pointer;
        }
        .form-group button:hover {
            background-color: #0056b3;
        }
    </style>
</head>
<body>
    <div class="login-container">
        <h2>Login</h2>
        <form action="#" method="post">
            <div class="form-group">
                <label for="username">Username</label>
                <input type="text" id="username" name="username" required>
            </div>
            <div class="form-group">
                <label for="password">Password</label>
                <input type="password" id="password" name="password" required>
            </div>
            <div class="form-group">
                <button type="submit">Login</button>
            </div>
        </form>
    </div>
</body>
</html>

Register.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Registration Page</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f4f4f4;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
        }
        .register-container {
            background-color: #fff;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
            width: 350px;
            text-align: center;
        }
        .register-container h2 {
            margin-bottom: 20px;
        }
        .form-group {
            margin-bottom: 15px;
        }
        .form-group label {
            display: block;
            margin-bottom: 5px;
            text-align: left;
        }
        .form-group input {
            width: 100%;
            padding: 10px;
            border: 1px solid #ccc;
            border-radius: 4px;
            box-sizing: border-box;
        }
        .form-group input:focus {
            outline: none;
            border-color: #007bff;
        }
        .form-group button {
            width: 100%;
            padding: 10px;
            border: none;
            background-color: #007bff;
            color: #fff;
            border-radius: 4px;
            cursor: pointer;
        }
        .form-group button:hover {
            background-color: #0056b3;
        }
        .form-group .error {
            color: red;
            font-size: 0.8em;
            text-align: left;
            margin-top: 5px;
        }
    </style>
</head>
<body>
    <div class="register-container">
        <h2>Register</h2>
        <form action="#" method="post" id="registrationForm">
            <div class="form-group">
                <label for="username">Username</label>
                <input type="text" id="username" name="username" required>
            </div>
            <div class="form-group">
                <label for="email">Email</label>
                <input type="email" id="email" name="email" required>
            </div>
            <div class="form-group">
                <label for="password">Password</label>
                <input type="password" id="password" name="password" required>
            </div>
            <div class="form-group">
                <label for="confirm-password">Confirm Password</label>
                <input type="password" id="confirm-password" name="confirm-password" required>
                <div class="error" id="passwordError"></div>
            </div>
            <div class="form-group">
                <button type="submit">Register</button>
            </div>
        </form>
    </div>

    <script>
        document.getElementById('registrationForm').addEventListener('submit', function(event) {
            const password = document.getElementById('password').value;
            const confirmPassword = document.getElementById('confirm-password').value;
            const passwordError = document.getElementById('passwordError');

            if (password !== confirmPassword) {
                passwordError.textContent = 'Passwords do not match';
                event.preventDefault(); // Prevent form submission
            } else {
                passwordError.textContent = '';
            }
        });
    </script>
</body>
</html>

Common.hpp

#pragma once

#include <iostream>
#include<functional>
#include <string>
#include <cstring>
#include <memory>
#include<unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
enum ExitCode
{
    OK = 0,
    USAGE_ERR,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR,
    CONNECT_ERR,
    FORK_ERR
};

class NoCopy
{

public:
NoCopy(){}
~NoCopy(){}
NoCopy(const NoCopy&)=delete;
const NoCopy& operator= (const NoCopy &)=delete;
};

#define CONV(addr) ((struct sockaddr *)&addr)

Http.hpp

#pragma once
#include "TcpServer.hpp"
#include <memory>
#include <iostream>
#include <string>
#include <unordered_map>
#include <sstream>
#include "Util.hpp"


using namespace SocketModule;

const std::string gspace = " ";
const std::string glinespace = "\r\n";
const std::string glinesep = ": ";

const std::string webroot = "./wwwroot";
const std::string homepage = "index.html";
const std::string page_404 = "/404.html";


class HttpRequest
{
public:
    std::string Serialize()
    {
        return std::string();
    }
    bool Deserialize(std::string &reqstr)
    {
         //1.提取请求行
        std::string reqline;
        bool res = Util::ReadOneLine(reqstr,&reqline,glinespace);
        LOG(Loglevel::DEBUG)<<"reqline: "<<reqline;
        //2.对请求行进行反序列化 缺省对报文完整性判断
        ParseReqLine(reqline);

        if(_uri=="/")
        _uri = webroot +_uri +homepage; //./wwwroot/index.html
        else
         _uri= webroot+_uri;//./wwwroot/a/b/c
        return true;
    }

    void ParseReqLine(std::string &reqline)
    {
        std::stringstream ss(reqline);
        ss >> _method >> _uri >> _version;
    }
    HttpRequest() {}
    ~HttpRequest() {}
  std::string Uri()
  {
    return _uri;
  }
private:
    std::string _method;
    std::string _uri;
    std::string _version;

    std::unordered_map<std::string, std::string> _headers;
    std::string _blankline;
    std::string _text;
};

class HttpResponse
{
   public:
   HttpResponse()
    :_version("HTTP/1.1")
    ,_blankline(glinespace)
    {}
    public:
        std::string Serialize()
    {
        std::string status_line = _version +" "+gspace+std::to_string(_code)+gspace+_desc+glinespace;
        std::string resp_header;
        for(auto &header : _headrs)
        {
            std::string line = header.first+glinesep+header.second+glinespace;
            resp_header+=line;

        }

        return status_line+resp_header+_blankline+_text;
    }
    void SetHeader(const std::string &key,const std::string &value)
    {
        auto iter = _headrs.find(key);
        if(iter != _headrs.end())
        return ;
        _headrs.insert(std::make_pair(key,value));
    }
    bool MakeResponse()
    {
        if(_targetfile== "./wwwroot/favicon.ico")
        {
            LOG(Loglevel::DEBUG)<<"用户请求:"<<_targetfile<<"忽略他";
            return false;
        }
        int filesize=0;
        bool res = Util::ReadFileContent(_targetfile,&_text);
        if(!res)
        {
            _text = "";
            LOG(Loglevel::WARNING) << "client want get : " << _targetfile << " but not found";
            SetCode(404);
            _targetfile = webroot+page_404;
            filesize = Util::FileSize(_targetfile);
            Util::ReadFileContent(_targetfile,&_text);
            std::string suffix = Uri2Suffix(_targetfile);
            SetHeader("Conent-Type",suffix);
            SetHeader("Content-Length",std::to_string(filesize));
        }
        else
        {
            LOG(Loglevel::DEBUG) << "读取文件: " << _targetfile;
            SetCode(200);
            filesize = Util::FileSize(_targetfile);
            std::string suffix = Uri2Suffix(_targetfile);
            SetHeader("Conent-Type",suffix);
            SetHeader("Content-Length",std::to_string(filesize));
        }
        
         return true;
    }

    std::string Uri2Suffix(const std::string &targetfile)
    {
        // ./wwwroot/a/b/c.html
        auto pos = targetfile.rfind(".");
        if(pos == std::string::npos)
        {
            return "text/html";
        }
        std::string suffix = targetfile.substr(pos);
        if(suffix == ".html"||suffix == ".htm")  
        {
            return "text/html"; 
        }
        else if(suffix ==".png")
        {
            return "image/png";
        }
        else if (suffix == ".jpg")
        {
            return "image/jpeg";
        }
        else
        return "";
    }
    void SetCode(int code)
    {
        _code=code;
        switch (_code)
        {
        case 200:
            _desc="OK";
            break;
        case 404:
            _desc="Not Found";
            break;
        default:
            break;
        }
    }
    void SetTargeFile(const std::string &target)
    {
        _targetfile = target;
    }
    bool Deserialize(std::string &reqstr)
    {
        std::string reqline;
        bool res = Util::ReadOneLine(reqstr,&reqline,glinespace);

       return true;
    }
 ~HttpResponse(){}


std::string _version; //版本
int _code; //状态码
std::string _desc; //状态码描述
std::unordered_map<std::string,std::string> _headrs;//相应报头
std::string _blankline;//空行
std::string _text;//正文

std::string _targetfile;
};

class Http
{
public:
    Http(uint16_t port)
        : tsvrp(std::make_unique<TcpServer>(port))
    {
    }
    void HandlenHttpRequest(std::shared_ptr<Socket> &sock, InetAddr &client)
    {
        std::string httpreqstr;
        //浏览器发给我的是一个大号的http字符串,其实我们的recv也是有问题的。tcp是面向字节流的
        int n = sock->Recv(&httpreqstr);
        if( n > 0)
        {
            HttpRequest req;
            req.Deserialize(httpreqstr);
            HttpResponse resp;
            resp. SetTargeFile(req.Uri());
            if(resp.MakeResponse())
            {
            std::string response_str = resp.Serialize();
            sock->Send(response_str);
            }

            // std::string filename = req.Uri();
            // HttpResponse resp;
            // resp._version = "HTTP/1.1";
            // resp._code = 200;//正常
            // resp._desc ="OK";
            // LOG(Loglevel::DEBUG)<<"用户请求: "<<filename;
            // bool res = Util::ReadFileContent(filename,&(resp._text));
            // (void)res;


        }

        #ifdef DEBUG
        std::string httprequest;
        sock->Recv(&httprequest);
        std::cout<<httprequest<<std::endl;
        //构建应答,内存级 + 固定
        HttpResponse resp;
        resp._version = "HTTP/1.1";
        resp._code = 200;//正常
        resp._desc ="OK";

        std::string filename= webroot+homepage;// ./wwwroot/index.html

        bool res = Util::ReadFileContent(filename,&(resp._text));
        (void)res;
        std::string response_str = resp.Serialize();
        sock->Send(response_str);
        #endif
        
    }
    void Start()
    {
        tsvrp->Start([this](std::shared_ptr<Socket> &sock, InetAddr &client)
                     { this->HandlenHttpRequest(sock, client); });
    }
    ~Http() {}

private:
    std::unique_ptr<TcpServer> tsvrp;
};

InetAddr.hpp

#pragma once
#include "Common.hpp"

class InetAddr
{

public:
    InetAddr()
    {
        
    }
    InetAddr(struct sockaddr_in &addr)
    {   
        SetAddr(addr);
    }
    InetAddr(const std::string &ip,uint16_t port)
        :_ip(ip)
        ,_port(port)
    {
        //主机转网络
       memset(&_addr,0,sizeof(_addr));
       _addr.sin_family=AF_INET;
       inet_pton(AF_INET,_ip.c_str(),&_addr.sin_addr);
       _addr.sin_port=htons(_port);
       
    }
    InetAddr(uint16_t port)
        :_port(port),_ip("0")
    {   //端口转
       memset(&_addr,0,sizeof(_addr));
       _addr.sin_family=AF_INET;
       _addr.sin_addr.s_addr=INADDR_ANY;
       _addr.sin_port = htons(_port);
    }
   

    uint16_t Port() { return _port; }

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

    void SetAddr(struct sockaddr_in &addr) 
    {  //网络转主机
        _addr=addr;
        _port = ntohs(addr.sin_port);
        //_ip = inet_ntoa(addr.sin_addr);
        char ipbuffer[64];
        inet_ntop(AF_INET,&_addr.sin_addr,ipbuffer,sizeof(_addr));
        _ip=ipbuffer;
    }
    const struct sockaddr_in &NetAddr() { return _addr; }
    const struct sockaddr *NetAddrPtr() { return CONV(_addr); }

    socklen_t NetAddrLen() { return sizeof(_addr); }
    bool operator==(const InetAddr &addr)
    {
        return addr._ip == _ip && addr._port == _port;
    }
    std::string StringAddr()
    {
        return _ip + ":" + std::to_string(_port);
    }
    ~InetAddr() {}
private:
    struct sockaddr_in _addr;
    std::string _ip;
    uint16_t _port;
};

Log.hpp

#ifndef __LOG_HPP__
#define __LOG_HPP__

#include <iostream>
#include <string>
#include "Mutex.hpp"
#include <filesystem>
#include <fstream>
#include <memory>
#include <unistd.h>
#include <sstream>
#include<ctime>

namespace LogModule
{
    const std::string sep = "\r\n";
    using namespace  MutexModule ;
    
    // 2.刷新策略
    class LogStrategy
    {
    public:
        ~LogStrategy() = default;
        virtual void SyncLog(const std::string &message) = 0;
    };

    // 显示器刷新日志的策略
    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        ConsoleLogStrategy() {}
        ~ConsoleLogStrategy() {}
        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);
            std::cout << message << sep;
        }

    private:
        Mutex _mutex;
    };

    // 缺省文件路径以及文件本身
    const std::string defaultpath = "./log";
    const std::string defaultfile = "my.log";
    // 文件刷新日志的策略
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile)
            : _path(path), _file(file)
        {
            LockGuard lockguard(_mutex);
            if (std::filesystem::exists(_path)) // 判断路径是否存在
            {
                return;
            }
            try
            {
                std::filesystem::create_directories(_path);
            }
            catch (const std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << '\n';
            }
        }

        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);
            std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file;
            std::ofstream out(filename, std::ios::app); // 追加写入
            if (!out.is_open())
            {
                return;
            }
            out << message << sep;
            out.close();
        }
        ~FileLogStrategy() {}

    private:
        Mutex _mutex;
        std::string _path; // 日志文件的路径
        std::string _file; // 要打印的日志文件
    };

    // 形成日志等级
    enum class Loglevel
    {
        DEBUG,
        INIF,
        WARNING,
        ERROR,
        FATAL
    };
    std::string Level2Str(Loglevel level)
    {
        switch (level)
        {
        case Loglevel::DEBUG:
            return "DEBUG";
        case Loglevel::INIF:
            return "INIF";
        case Loglevel::WARNING:
            return "WARNING";
        case Loglevel::ERROR:
            return "ERROR";
        case Loglevel::FATAL:
            return "FATAL";
        default:
            return "UNKNOWN";
        }
    }

    std::string GetTimeStamp()
    {
       time_t cuur =time(nullptr);
       struct tm curr_tm;
       localtime_r(&cuur,&curr_tm);
       char buffer[128];
       snprintf(buffer,sizeof(buffer),"%4d-%02d-%02d %02d:%02d:%02d",
       curr_tm.tm_year+1900,
       curr_tm.tm_mon+1,
       curr_tm.tm_mday,
       curr_tm.tm_hour,
       curr_tm.tm_min,
       curr_tm.tm_sec
       );
       return buffer;
    }
    class Logger
    {
    public:
        Logger()
        {
            EnableConsoleLogStrategy();
        }
        // 选择某种策略
        // 1.文件
        void EnableFileLogStrategy()
        {
            _ffush_strategy = std::make_unique<FileLogStrategy>();
        }
        // 显示器
        void EnableConsoleLogStrategy()
        {
            _ffush_strategy = std::make_unique<ConsoleLogStrategy>();
        }

        
        // 表示的是未来的一条日志
        class LogMessage
        {
        public:
            LogMessage(Loglevel &level, std::string &src_name, int line_number, Logger &logger)
                : _curr_time(GetTimeStamp())
                , _level(level)
                , _pid(getpid())
                , _src_name(src_name)
                , _line_number(line_number)
                , _logger(logger)
            {
                // 合并左半部分
                std::stringstream ss;
                ss << "[" << _curr_time << "] "
                   << "[" << Level2Str(_level) << "] "
                   << "[" << _pid << "] "
                   << "[" << _src_name << "] "
                   << "[" << _line_number << "] "
                   << "- ";
                _loginfo = ss.str();
            }
            template <typename T>
            LogMessage &operator<<(const T &info)
            {
                // 右半部分,可变
                std::stringstream ss;
                ss << info;
                _loginfo += ss.str();
                return *this;
            }
            ~LogMessage()
            {
                if (_logger._ffush_strategy)
                {
                    _logger._ffush_strategy->SyncLog(_loginfo);
                }
            }
           

        private:
            std::string _curr_time; // 日志时间
            Loglevel _level;        // 日志状态
            pid_t _pid;             // 进程pid
            std::string _src_name;  // 文件名称
            int _line_number;       // 对应的行号
            std::string _loginfo;   // 合并之后的一条完整信息
            Logger &_logger;
        };
         LogMessage operator()(Loglevel level, std::string src_name, int line_number)
            {
                return LogMessage(level, src_name, line_number, *this);
            }
        ~Logger() {}

    private:
        std::unique_ptr<LogStrategy> _ffush_strategy;
    };
    //全局日志对象
    Logger logger;

    //使用宏,简化用户操作,获取文件名和行号
    // __FILE__  一个宏,替换完成后目标文件的文件名
    // __LINE__  一个宏,替换完成后目标文件对应的行号
    #define LOG(level) logger(level,__FILE__,__LINE__) 
    #define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()
    #define Enable_File_Log_Strategy()    logger.EnableFileLogStrategy()
     
}

#endif

Main.cc

#include"Http.hpp"

// http port
int main(int argc,char* argv[])
{
    if(argc != 2)
    {
        std::cout << "Usage: "<<argv[0]<<" port"<<std::endl;
        exit(USAGE_ERR);
    }
    uint16_t port = std::stoi(argv[1]);
    std::unique_ptr<Http> httpsvr = std::make_unique<Http>(port);
    httpsvr->Start();
    return 0;
}

Mutex.hpp

#pragma once
#include <pthread.h>
#include <iostream>
namespace MutexModule
{ 
    class Mutex
    {
    public:
        Mutex()
        {
            pthread_mutex_init(&_mutex, nullptr);
        }

        void Lock()
        {
            int n = pthread_mutex_lock(&_mutex);
            (void)n;
        }
        void Unlock()
        {
            int n = pthread_mutex_unlock(&_mutex);
            (void)n;
        }
        ~Mutex()
        {
            pthread_mutex_destroy(&_mutex);
        }
        pthread_mutex_t *get()
        {
            return &_mutex;
        }
    private:
        pthread_mutex_t _mutex;
    };
    class LockGuard
    {
        public:
        LockGuard(Mutex &mutex):_mutex(mutex)
        {
        _mutex.Lock();
        }
        ~LockGuard()
        {
            _mutex.Unlock();
        }
        private:
        Mutex &_mutex;
    };
}

Socket.hpp

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

namespace SocketModule
{
    using namespace LogModule;
    const static int gbacklog =16;
    const  static int defaultfd =-1;
    // 基类socket
    class Socket
    {
    public:
        virtual ~Socket() {}
        virtual void SocketOrDie() = 0;
        virtual void BindOrDie(uint16_t port) = 0;
        virtual void ListenOrDie(int backlog) = 0;
        virtual  std::shared_ptr<Socket> Accept(InetAddr * client)= 0;
        virtual void Close()=0;
        virtual int Recv(std::string * out) = 0;
        virtual int Send(std::string  &message) = 0;
        virtual int Connect(const std::string &server_ip ,uint16_t port) =0;
    public:
    void BuildTcpClientSocketMethod()
        {
            SocketOrDie();
        }
        void BUildTcpLIstenSocketMethod(uint16_t port,int backlog = gbacklog)
        {
            SocketOrDie();
            BindOrDie(port);
            ListenOrDie(backlog);
        }
        // void BUildUdpSocketMethod()
        // {
        //     SocketOrDie();
        //     BindOrDie();
        // }
    };

    class TcpSocket : public Socket
    {
    public:
       TcpSocket()
       :_sockfd(defaultfd)
       {}
       TcpSocket(int fd): _sockfd(fd) {}
        
        ~TcpSocket() {}
        void SocketOrDie() override
        {
            _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
            if (_sockfd < 0)
            {
                LOG(Loglevel::FATAL) << "创建套接字失败!";
                exit(SOCKET_ERR);
            }
            LOG(Loglevel::INIF) << "创建套接字成功!";
        }
        void BindOrDie(uint16_t port) override
        {
            InetAddr localaddr(port);
            int n = ::bind(_sockfd, localaddr.NetAddrPtr(), localaddr.NetAddrLen());
            if (n < 0)
            {
                LOG(Loglevel::FATAL) << "绑定失败!";
                exit(BIND_ERR);
            }
            LOG(Loglevel::INIF) << "绑定成功!";
        }
        void ListenOrDie(int backlog) override
        {
            int n = ::listen(_sockfd, backlog);
            if (n < 0)
            {
                LOG(Loglevel::FATAL) << "监听失败!";
                exit(LISTEN_ERR);
            }
            LOG(Loglevel::INIF) << "监听成功!";
        }
        std::shared_ptr<Socket> Accept(InetAddr * client) override
        {
            struct sockaddr_in peer;
            socklen_t len  = sizeof(peer);
            int fd =::accept(_sockfd,CONV(peer),&len);
            if (fd < 0)
            {
                LOG(Loglevel::WARNING)<<"连接失败!";
                exit(-1);
            }
              LOG(Loglevel::WARNING)<<"连接成功!";
               client->SetAddr(peer);
               return  std::make_shared<TcpSocket>(fd);
        }
        void Close() override
        {
            if(_sockfd >=0)
            {
                ::close(_sockfd);
            }
        }
         int Recv(std::string * out) override
         {
            //流式读取,不关心读到的是什么
            char buffer[4096*2];
            ssize_t n =::recv(_sockfd,buffer,sizeof(buffer)-1,0);
            if (n >0)
              {
                buffer[n]=0;
                *out+=buffer;
                return n;
              }
            return n;

         }
         int Send(std::string  &message) override
         {
           return send(_sockfd,message.c_str(),message.size(),0);
         }
         int Connect(const std::string &server_ip ,uint16_t port) override
         {
           InetAddr server(server_ip,port);
           return ::connect(_sockfd,server.NetAddrPtr(),server.NetAddrLen()) ;    
         }
    private:
        int _sockfd; //
    };

    // class UdpSocket : public Socket
    // {

    // };
}

   TcpServer.hpp

#include "Socket.hpp"
#include<iostream>
#include<memory>
#include <sys/wait.h>
#include <functional>

using namespace LogModule;
using namespace SocketModule;

using ioservice_t = std::function<void(std::shared_ptr<Socket>&sock,InetAddr &client)>;

class TcpServer
{

public:
TcpServer(uint16_t port)
:_port(port)
,_listensockptr(std::make_unique<TcpSocket>())
,_isrunning(false)
{
    _listensockptr->BUildTcpLIstenSocketMethod(_port);
}
void Start(ioservice_t callback)
{
  _isrunning =true;
  while(_isrunning)
  {
    InetAddr client;
   auto sock = _listensockptr->Accept(&client);
  
    if(sock == nullptr)
    {
        continue;
    }
    LOG(Loglevel::DEBUG)<<"accept is running"<<client.StringAddr();

    //sock && client
    pid_t id =fork();
    if (id < 0)
    {
        LOG(Loglevel::FATAL)<<"创建子进程失败";
        exit(FORK_ERR);
    }
    else if(id == 0)
    {
     //子进程 关闭listensock
     _listensockptr->Close();
     if(fork()>0)
     {
        exit(0);
     }
     //孙子进程是孤儿进程
     callback(sock,client);
     exit(OK);
    }
    else
    {
      //父进程 关闭sock
      sock->Close();
      pid_t rid = ::waitpid(id,nullptr,0);
      (void)rid;
    }

  }
  _isrunning =false;

}
~TcpServer(){}
private:
uint16_t _port;
std::unique_ptr<Socket>_listensockptr;
bool _isrunning;
};

Util.hpp

#pragma once
#include <iostream>
#include <string>
#include <fstream>

class Util
{

    public:
    //打开指定文件
static bool ReadFileContent(const std::string &filename,std::string *out)
    {
       
        //version1 默认文本方式读取
        // std::ifstream in(filename);
        // if(!in.is_open())
        // {
        //     return false;
        // }
        // std::string line;
        // while(std::getline(in,line))
        // {
        //     *out+=line;
        // }
        // in.close();

        //version2  二进制读取 图片是二进制 
        int filesize = FileSize(filename);
        if(filesize > 0)
        {
            std::ifstream in(filename);
            if(!in.is_open())
            {
                return false;
            }
            out->resize(filesize);
            in.read((char *)(out->c_str()),filesize);
            in.close();
        }
        else{
            return false;
        }
        return true;
    }
    static bool ReadOneLine(std::string &bigstr,std::string *out,const std::string &sep)
    {
        auto pos = bigstr.find(sep);
        if(pos == std::string::npos)
        {
            return false;
        }
        *out = bigstr.substr(0,pos);
        bigstr.erase(0,pos+sep.size());
        return true;
    }
static int FileSize(const std::string &filename)
{
    std::ifstream in(filename,std::ios::binary);
    if(!in.is_open()) return -1;
    in.seekg(0,in.end);
    int filesize = in.tellg();
    in.seekg(0,in.beg);
    in.close();
    return filesize;
}

};

Makefile

myhttp:Main.cc
	g++ -o $@ $^ -std=c++17
.PHONY:clean
clean:
	rm -f myhttp


                                   
         游览器首页效果

查看首页


查看图片

查看不存在资源

其他前端效果不展示,主要学习http相关知识


网站公告

今日签到

点亮在社区的每一天
去签到