C ++ shortest web server

    In a previous article, I talked about how to write a simple server to transfer a single file using http and https protocols. Some time passed and I decided to make a universal library out of this code for quickly creating servers.

    The full library code can be viewed on the github , and in a nutshell, I added a few “Egyptian brackets”, new-fangled lambda functions and templates. To date, the result has become a cross-platform library for creating asynchronous servers, consisting of 5 files with a total size of 22.5 kilobytes. The version of the library for Linux consists of one file of 18 kilobytes in size (517 lines of code).

    In this article, I will briefly describe how the library works and show 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 s(8085, 1111);
    int main() {return 0;}
    


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

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

    The library is built on messaging with a custom class. The following messages are currently defined:
    	enum MESSAGE {
    		I_READY_EPOLL,
    		I_ACCEPTED,
    		I_READ,
    		I_ALL_WROTE,
    		PLEASE_READ,
    		PLEASE_WRITE_BUFFER,
    		PLEASE_WRITE_FILE,
    		PLEASE_STOP
    	};
    


    Messages starting with "I_" are sent by the library, and messages starting with "PLEASE_" can be sent to the library.
    In order to implement a web server, it is enough to describe such a class:
    	class CHttpClient
    	{
    	public:
    		const MESSAGE OnAccepted(shared_ptr> pvBuffer) 
    		{***}
    		const MESSAGE OnWrote(shared_ptr> pvBuffer)
    		{***}
    		const MESSAGE OnRead(shared_ptr> pvBuffer)
    		{***}
    	};
    

    These three public functions are required because they are called by the library for exchanging messages and data.
    Data exchange takes place through the “clipboard”, which is both the input and output parameter of the functions.

    At first I wanted to paint the creation of the CHttpClient class step by step here, but then I decided that if desired, the Habrovsk people would be able to figure out 115 lines of code with comments without me. Therefore, I will simply give 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 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> 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> pvBuffer) {return PLEASE_READ;}
    		const MESSAGE OnWrote(shared_ptr> 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> 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 little explanation:
    the first line turns on my low-level micro-library,
    then the directory and default pages for the site
    are determined, then there are functions for controlling 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 github, you can find a ready-made project for Visual Studio 2012.
    For Linux, you only need the files serv.cpp, server.h, http_server.h and ca-cert.pem. The gcc 4.5 compiler and higher: "g ++ -std = c ++ 0x -L / usr / lib -lssl -lcrypto serv.cpp"

    If you do not change anything in the code, you need to put it in the ./wwwroot directory even though to check the server’s operation the index.html file would

    be possible to check the server in operation at:
    Http://unblok.us:8085/

    or
    https://unblok.us:1111
    The test for the Habroeffect server successfully failed, what I will understand the reason. sort of figured out - fixed.

    Also popular now: