В предыдущей статье я рассказал, как написать простой сервер для передачи одного файла по протоколам 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 строчках кода с комментариями. Поэтому просто приведу его здесь целиком:
#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 readedn";
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 OKrn"
<< "Content-Length: " << m_nFileSize << "rn" <<
"rn";
//Запоминаем заголовок
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("rnrn") == strInputString.npos)
return PLEASE_READ;
switch(OnReadedHeader(strInputString.substr(0, strInputString.find("rnrn")+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/
Автор: 3s3s