# http_server **Repository Path**: yyx_dev/http_server ## Basic Information - **Project Name**: http_server - **Description**: 从零实现HTTP服务器项目 - **Primary Language**: C++ - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 0 - **Created**: 2022-08-01 - **Last Updated**: 2023-11-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README > 本项目的博客链接: > > https://blog.csdn.net/yourfriendyo/article/details/128010599 # HTTP服务器 ## 1. 项目背景和技术特点 ### 实现目的 > 从移动端到浏览器,HTTP 协议无疑是打开互联网应用窗口的重要协议,其在网络应用层中的地位不可撼动,是能准确区分前后台的重要协议。 完善对HTTP协议的理论学习,从零开始完成WEB服务器开发。 采用 CS 模型,编写支持中小型应用的HTTP服务器,井结合MySQL。 该项目可以帮助我们从技术上理解上网输入网站URL到关闭浏览器到所有操作中的技术细节。 [HTTP基础知识,这里就不再赘述了](https://blog.csdn.net/yourfriendyo/article/details/126188159) ### 技术特点 - Linux 网络编程 TCP/IP协议、socket流式套接、http协议。 - 多线程技术 - cgi 技术 - shell 脚本 - 线程池 ### 开发环境 - centos7 - vim gcc/g++ Makefile   ## 2. 代码结构和实现思路 ### TcpServer 先实现HTTP的底层协议TCP的代码,也就是完成基本的网络套接字代码。实现成单例模式以供上层调用。 ```cpp //TcpServer.hpp #pragma once #include #include #include #include #include #include #include #include #include #include #define BACK_LOG 5 class TcpServer { private: int _port; int _listen_sock; TcpServer(int port) : _port(port), _listen_sock(-1) {} TcpServer(const TcpServer& ts) = delete; TcpServer operator=(const TcpServer& ts) = delete; static TcpServer* _svr; public: static TcpServer* GetInstance(int port); void InitServer(); void Socket(); void Bind(); void Listen(); ~TcpServer() {} }; TcpServer* TcpServer::_svr = nullptr; ``` ### HttpServer HttpServer类实现调用TcpServer单例,并进入事件循环。 ```cpp #pragma once #include #include #include "TcpServer.hpp" #include "Protocol.hpp" #define PORT 8080 class HttpServer { private: int _port; TcpServer* _tcp_svr; bool _stop; public: HttpServer(int port = PORT) : _port(port), _tcp_svr(nullptr), _stop(false) {} void InitServer() { _tcp_svr = TcpServer::GetInstance(_port); } void Loop(); ~HttpServer() {} }; ``` 接下来就是,读取请求,分析请求,构建响应并返回。 ### Protocol 定制HTTP请求和响应的协议。接着就是`EndPoint`类实现HTTP读取解析响应等一系列函数。 ```cpp struct HttpRequest { std::string _request_line; std::vector _request_headers; std::string _blank; std::string _request_body; std::string _method; std::string _uri; std::string _version; std::string _path; std::string _query; struct stat _resoucre_stat; bool _cgi = false; std::unordered_map _header_kvs; }; struct HttpResponse { std::string _status_line; std::vector _response_headers; std::string _blank = LINE_BREAK; std::string response_body; int _status_code = OK; int _resource_fd = -1; }; ``` ```cpp class EndPoint { private: int _sock; HttpRequest _http_request; HttpResponse _http_response; private: void RecvHttpRequestLine(); void RecvHttpRequestHeadler(); void ParseHttpRequestLine(); void ParseHttpRequestHeadler(); void RecvParseHttpRequestBody(); int ProcessCgi(); int ProcessWebPage(); public: EndPoint(int sock) : _sock(sock) {} void RecvHttpRequest() { RecvHttpRequestLine(); RecvHttpRequestHeadler(); } void ParseHttpRequest() { ParseHttpRequestLine(); ParseHttpRequestHeadler(); RecvParseHttpRequestBody(); } void BuildHttpResponse(); void SendHttpResponse(); ~EndPoint() {} }; ``` #### 读取和解析请求 当前请求时,我们以行为单位。 从各大平台发来的数据的行分隔符各有不同,我们要做的是兼容所有情况,也就是我们要自行实现读取一行数据的接口。 #### 构建和返回响应 如果是GET方法获取资源路径,并进行一系列的检查判断。根据请求资源的类型设置CGI处理,如果是POST方法直接设置CGI。 发送响应就是简单的将构建好的响应返回给对端即可。 #### 处理静态和非静态请求 构建普通网页响应。 构建CGI响应。这是本项目的重难点。 线程首先首先创建子进程,将具体执行进程程序替换的任务交给子进程。 其次定制父子进程通信协议。 请求方法,GET方法的请求参数,报头中的正文大小几个变量都用环境变量导给子进程。POST方法的请求体使用管道导给子进程。 中个细节代码中有注释说明。 ### 差错处理 在读取请求构建响应发送响应的过程中,都穿插着错误判断,并以HTTP响应状态码作为返回值。 在适当的地方`goto END;`,直接进行错误处理。错误处理利用得到的状态码构建错误响应,也就是返回错误网页,如404页面。 ```cpp void BuildHttpResponse() { // 排除非法请求 if (_http_request._method != "GET" && _http_request._method != "POST") { LOG(WARNING) << "bad request invaild method\n"; code = BAD_REQUEST; goto END; } //... // 差错处理 END: if (code != OK) { LOG(INFO) << "headler error begin, code: " << code << '\n'; ErrorHelper(); // 构建错误响应 } } private: void ErrorHelper() { _http_request._cgi = false; // 错误处理,返回静态网页 auto& code = _http_response._status_code; switch (code) { case BAD_REQUEST: HeadlerWrong(PAGE_404); break; case NOT_FOUND: HeadlerWrong(PAGE_404); // 单独构建404页面 break; case SVR_ERROR: HeadlerWrong(PAGE_404); break; case SVR_UNAVL: HeadlerWrong(PAGE_404); break; default: LOG(WARNING) << "unkown error code" << std::endl; break; } } void HeadlerWrong(const std::string& wrong_page) { _http_request._path = WEB_ROOT + '/' + wrong_page; stat(_http_request._path.c_str(), &_http_request._resoucre_stat); ProcessWebPage(); // 返回404页面 } ``` 任务队列中的任务类,设置回调方法,使任务体能够自行调用处理任务的函数。 将`HeaderRequest`方法,构建成回调`CallBack`仿函数。 设置线程池,并配备任务队列。 交给外部`HTTPServer`类向任务队列中添加`accept`接受到的任务。 自身设置`TASK_NUM`数量的线程来同步互斥地获取任务队列中的任务。 ### 调用逻辑 httpserver调用逻辑   ## 3. 难点总结和项目扩展 对于CGI机制的理解和实现。 HTTP服务流程 整个HTTP服务就是CGI程序和客户端沟通的桥梁,因为CGI程序与外界的输入输出都由HTTP服务器代理和转发。 通过子进程进程程序替换的方式,能够调用任意程序,且可以获得程序的运行结果和控制其输入输出。 ### 项目扩展 - URL encode decode - 数据库增删查改 - HTTP其他方法 - 配置文件化 - 301302转发 #### 业务层面 - [ ] 实现在线计算器(日期转换等) - [x] 实现在线简历 - [ ] 实现博客系统 #### 技术层面 - [ ] 支持HTTP1.1长连接(链接管理) - [ ] 提高并发量和执行效率 - [ ] 支持redis - [ ] 支持多机器业务转发负载均衡的代理功能