Полный код библиотеки можно посмотреть на гитхабе, а если в двух словах, то я добавил немного «египетских скобок», новомодных лямбда-функций и шаблонов. На сегодняшний день результатом стала кроссплатформенная библиотека для создания асинхронных серверов, состоящая из 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 строчках кода с комментариями. Поэтому просто приведу его здесь целиком:
#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/
ссылка на оригинал статьи http://habrahabr.ru/post/217087/
Добавить комментарий