📜 ⬆️ ⬇️

The shortest web server in c ++

In the previous article I explained how to write a simple server for transferring a single file using the http and https protocols. Some time passed and I decided to make a universal library of this code for the quick creation of servers.

You can look at the full library code on the githaba , and if in a nutshell, I added a bit of “Egyptian brackets”, new-fashioned lambda functions and templates. Today, the result is a cross-platform library for creating asynchronous servers, consisting of 5 files with a total size of 22.5 kilobytes. The Linux version of the library consists of a single file of 18 kilobytes in size (517 lines of code).

In this article, I will briefly describe how the library works and show you how to use it to write a fully functional web server for static sites.

')
All the web server code that I want to submit is in two files.
The first file is called serv.cpp and contains the minimum amount of code:
#include "http_server.h" using namespace server; CServer<CHttpClient> s(8085, 1111); int main() {return 0;} 


The first line includes the file where the rest of the web server code is written. In the fourth line, a low-level library is initiated, which I mentioned at the beginning of the post.

As you can see, to work with the library, first of all you need to create a variable of type CServer, into which you need to transfer the name of the custom class and the port numbers that the server will listen to.
In the above example, the library is initiated with the CHttpClient class (discussed below), with port 8085 for receiving tcp connections and port 1111 for ssl.

The work of the library is built on messaging with a custom class. To date, the following messages have been identified:
  enum MESSAGE { I_READY_EPOLL, I_ACCEPTED, I_READ, I_ALL_WROTE, PLEASE_READ, PLEASE_WRITE_BUFFER, PLEASE_WRITE_FILE, PLEASE_STOP }; 


Messages that begin with “I_” send a library, and messages that begin with “PLEASE_” can be sent to the library.
To implement a web server, it is enough to describe such a class:
  class CHttpClient { public: const MESSAGE OnAccepted(shared_ptr<vector<unsigned char>> pvBuffer) {***} const MESSAGE OnWrote(shared_ptr<vector<unsigned char>> pvBuffer) {***} const MESSAGE OnRead(shared_ptr<vector<unsigned char>> pvBuffer) {***} }; 

These three public functions are required as they are called by the library for exchanging messages and data.
Data exchange occurs through the "clipboard", which is both an input and output parameter of the functions.

At first, I wanted to paint the creation of the CHttpClient class step by step, but then I decided that, if desired, the habrovcans could figure out 115 lines of code with comments without me. So just bring it here in its entirety:
Source http_server.h
 #include "server.h" #define ROOT_PATH "./wwwroot" #define ERROR_PAGE "error.html" #define DEFAULT_PAGE "index.html" namespace server { class CHttpClient { int m_nSendFile; off_t m_nFilePos; unsigned long long m_nFileSize; enum STATES { S_READING_HEADER, S_READING_BODY, S_WRITING_HEADER, S_WRITING_BODY, S_ERROR }; STATES m_stateCurrent; map<string, string> m_mapHeader; void SetState(const STATES state) {m_stateCurrent = state;} const bool ParseHeader(const string strHeader) { m_mapHeader["Method"] = strHeader.substr(0, strHeader.find(" ") > 0 ? strHeader.find(" ") : 0); if (m_mapHeader["Method"] != "GET") return false; const int nPathSize = strHeader.find(" ", m_mapHeader["Method"].length()+1)-m_mapHeader["Method"].length()-1; if (nPathSize < 0) return false; m_mapHeader["Path"] = strHeader.substr(m_mapHeader["Method"].length()+1, nPathSize); return true; } const MESSAGE OnReadHeader(const string strHeader, shared_ptr<vector<unsigned char>> pvBuffer) { cout << "Header read\n"; if (!ParseHeader(strHeader)) m_mapHeader["Path"] = ERROR_PAGE; if (m_mapHeader["Path"] == "/") m_mapHeader["Path"] += DEFAULT_PAGE; cout << "open file" << ROOT_PATH << m_mapHeader["Path"].c_str() << "\n"; if ((m_nSendFile = _open((ROOT_PATH+m_mapHeader["Path"]).c_str(), O_RDONLY|O_BINARY)) == -1) return PLEASE_STOP; struct stat stat_buf; if (fstat(m_nSendFile, &stat_buf) == -1) return PLEASE_STOP; m_nFileSize = stat_buf.st_size; //    http  std::ostringstream strStream; strStream << "HTTP/1.1 200 OK\r\n" << "Content-Length: " << m_nFileSize << "\r\n" << "\r\n"; //  pvBuffer->resize(strStream.str().length()); memcpy(&pvBuffer->at(0), strStream.str().c_str(), strStream.str().length()); return PLEASE_WRITE_BUFFER; } explicit CHttpClient(CHttpClient &client) {} public: CHttpClient() : m_nSendFile(-1), m_nFilePos(0), m_nFileSize(0), m_stateCurrent(S_READING_HEADER) {} ~CHttpClient() { if (m_nSendFile != -1) _close(m_nSendFile); } const MESSAGE OnAccepted(shared_ptr<vector<unsigned char>> pvBuffer) {return PLEASE_READ;} const MESSAGE OnWrote(shared_ptr<vector<unsigned char>> pvBuffer) { switch(m_stateCurrent) { case S_WRITING_HEADER: if (m_nSendFile == -1) return PLEASE_STOP; SetState(S_WRITING_BODY); pvBuffer->resize(sizeof(int)); memcpy(&pvBuffer->at(0), &m_nSendFile, pvBuffer->size()); return PLEASE_WRITE_FILE; default: return PLEASE_STOP; } } const MESSAGE OnRead(shared_ptr<vector<unsigned char>> pvBuffer) { switch(m_stateCurrent) { case S_READING_HEADER: { //  http     const std::string strInputString((const char *)&pvBuffer->at(0)); if (strInputString.find("\r\n\r\n") == strInputString.npos) return PLEASE_READ; switch(OnReadedHeader(strInputString.substr(0, strInputString.find("\r\n\r\n")+4), pvBuffer)) { case PLEASE_READ: SetState(S_READING_BODY); return PLEASE_READ; case PLEASE_WRITE_BUFFER: SetState(S_WRITING_HEADER); return PLEASE_WRITE_BUFFER; default: SetState(S_ERROR); return PLEASE_STOP; } } default: return PLEASE_STOP; } } }; } 



A bit of explanation:
the first line includes my low-level microbiblioteka,
then the directory and default pages for the site are determined,
then come the functions for managing the state of the class and parsing the request header,
Callback functions return messages to the library depending on the current state of the class.

So, on the githaba, you can find a ready project for Visual Studio 2012.
For Linux, only serv.cpp, server.h, http_server.h and ca-cert.pem files are needed. Compiler gcc 4.5 and higher: "g ++ -std = c ++ 0x -L / usr / lib -lssl -lcrypto serv.cpp"

If you don’t change anything in the code, then in order to check the server’s operation you need to place at least the index.html file in the ./wwwroot directory

You can check the server at work at:
http://unblok.us:8085/

or
https://unblok.us:1111
The test for habroeffect server safely filled up, the reason I will understand. sort of figured out - corrected.

Source: https://habr.com/ru/post/217087/


All Articles