Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

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

Anna | 24.06.2014 | нет комментариев

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

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

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

Каждый код веб-сервера, тот, что я хочу представить находится в 2-х файлах.
1-й файл именуется 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 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/

либо
https://unblok.us:1111
Испытание на прогроэффект сервер удачно завалил, в чем повод буду разбираться.

 

Источник: programmingmaster.ru

 

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB