Самый короткий веб-сервер на с++

от автора

В предыдущей статье я рассказал, как написать простой сервер для передачи одного файла по протоколам http и https. Прошло немного времени и я решил сделать из этого кода универсальную библиотеку для быстрого создания серверов.

Полный код библиотеки можно посмотреть на гитхабе, а если в двух словах, то я добавил немного «египетских скобок», новомодных лямбда-функций и шаблонов. На сегодняшний день результатом стала кроссплатформенная библиотека для создания асинхронных серверов, состоящая из 5 файлов с общим размером 22.5 килобайт. Версия библиотеки для Линукс состоит из одного файла размером 18 килобайт (517 строк кода).

В этой статье я коротко расскажу как работает библиотека и покажу, как с ее помощью написать полностью работоспособный веб-сервер для статических сайтов.

Весь код веб-сервера, который я хочу представить находится в двух файлах.
Первый файл называется serv.cpp и содержит минимальное количество кода:

#include "http_server.h" using namespace server;  CServer<CHttpClient> s(8085, 1111);  int main() {return 0;} 

В первой строке включается файл где написан весь остальной код веб-сервера. В четвертой строке инициируется низкоуровневая библиотека о которой я сказал в начале поста.

Как видно, для работы с библиотекой первым делом нужно создать переменную типа CServer, в которую нужно передать имя пользовательского класса и номера портов которые сервер будет слушать.
В приведенном примере библиотека инициируется с классом CHttpClient (о нем речь чуть ниже), с портом 8085 для приема tcp соединений и портом 1111 для ssl.

Работа библиотеки построена на обмене сообщениями с пользовательским классом. На сегодняшний день определены следующие сообщения:

	enum MESSAGE { 		I_READY_EPOLL, 		I_ACCEPTED, 		I_READED, 		I_ALL_WRITED, 		PLEASE_READ, 		PLEASE_WRITE_BUFFER, 		PLEASE_WRITE_FILE, 		PLEASE_STOP 	}; 

Сообщения, начинающиеся на «I_» посылает библиотека, а сообщения начинающиеся на «PLEASE_» можно посылать библиотеке.
Для того, чтобы реализовать веб-сервер достаточно описать такой класс:

	class CHttpClient 	{ 	public: 		const MESSAGE OnAccepted(shared_ptr<vector<unsigned char>> pvBuffer)  		{***} 		const MESSAGE OnWrited(shared_ptr<vector<unsigned char>> pvBuffer) 		{***} 		const MESSAGE OnReaded(shared_ptr<vector<unsigned char>> pvBuffer) 		{***} 	};   

Эти три публичные функции обязательны, поскольку их вызывает библиотека для обмена сообщениями и данными.
Обмен данными происходит через «буфер обмена», который является одновременно входным и выходным параметром функций.

Сначала я хотел поэтапно расписать тут создание класса CHttpClient но потом решил, что при желании хабровчане смогут и без меня разобраться в 115 строчках кода с комментариями. Поэтому просто приведу его здесь целиком:

Исходник 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 OnReadedHeader(const string strHeader, shared_ptr<vector<unsigned char>> pvBuffer) 		{ 			cout << "Header readed\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 OnWrited(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 OnReaded(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; 			} 		} 	}; } 

Немного пояснений:
в первой строке включается моя низкоуровневая микробиблиотека,
потом определяются каталог и страницы по умолчанию для сайта,
потом идут функции для управления состоянием класса и парсинга заголовка запроса,
callback-функции возвращают в библиотеку сообщения в зависимости от текущего состояния класса.

Итак, на гитхабе можно найти готовый проект для Visual Studio 2012.
Для Линукса нужны только файлы serv.cpp, server.h, http_server.h и ca-cert.pem. Компилятор gcc 4.5 и выше: «g++ -std=c++0x -L/usr/lib -lssl -lcrypto serv.cpp»

Если ничего в коде не менять, то чтобы проверить работу сервера нужно еще в каталог ./wwwroot поместить хотя бы файл index.html

Проверить сервер в работе можно по адресу:
http://unblok.us:8085/

или
https://unblok.us:1111

ссылка на оригинал статьи http://habrahabr.ru/post/217087/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *