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

Свой http-сервер менее чем в 40 строк кода на libevent и C 11

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

Просматривая подчас Прогр, я периодично встречаю посты на тему создания собственного веб-сервера на C либо на другом языке. Так как больший интерес для меня представляет C из языков программирования, то данный блог я огромнее каждого и читаю. Если его полистать, то дозволено с легкостью обнаружить как написать свой веб-сервер «на сокетах», с использованием boost.asio либо чего-то еще. Некоторое время назад я так же публиковал свой пост о создании сходственного http-сервера как пример решения тестового задания. Но не ограничившись полученным оным и интереса ради я сделал сопоставления с libevent и boost.asio разработками. А тестовое задание как-таковое отказался исполнять.

Для себя как-то по работе я рассматривал libevent и libev. У всякой есть свои превосходства. Если же есть желание либо надобность в скорой разработке небольшого http-сервера, то для меня огромный интерес представляет libevent, а с учетом некоторых нововведений C 11 код становится гораздо суперкомпактнее и разрешает сделать базовый http-сервер менее, чем в 40 строк.

Материал поста допустимо будет пригоден тем, кто еще не знаком с libevent и есть надобность в скором создании своего http-сервера, а так же материал может заинтересует людей, у которых такой надобности пока нет и даже если они теснее имели навык создания сходственного, увлекательно узнать их суждение и навык. А так как пост не содержит ничего твердо нового, то может быть использован как материал для начала работы в данном направлении, а следственно испробую поставить пометку «обучающий материал».

Чем отлична libevent в различии от, скажем, libev и boost.asio, так это тем, что она имеет свой встроенный http-сервер, и некоторую абстракцию для работы с буферами. А так же имеет большой комплект вспомогательных функций. Дозволено HTTP протокол и самому разобрать, написав простенький финальный автомат либо еще каким-нибудь способом. При работе с libevent это все теснее есть. Эта такая славная плюшка, а дозволено и на больше низкий ярус спуститься и писать свой же парсер для HTTP, при этом работу с сокетами сделать на libevent. ?рус детализации у библиотеки мне понравился тем, что если есть желание сделать что-то стремительно, то дозволено обнаружить в ней больше высокоуровневый интерфейс, тот, что как правило менее эластичен. При происхождении крупных надобностей дозволено понемногу спускаться ярус за ярусом все ниже и ниже. Библиотека разрешает делать многие вещи: асинхронный ввод-итог, работу с сетью, работа с таймерами, rpc, т. д; дозволено с ее поддержкой создавать как серверное, так и клиентское ПО.

Для чего?

Создание собственного небольшого http-сервера может быть обусловлено для всякого его собственными надобностями, желанием либо не желанием применять полнофункциональные готовые сервера по той либо другой причине. Представим у Вас есть некоторое серверное ПО, которое работает по какому-то своему протоколу и решает некоторые задачи и у Вас возникла надобность выдать некоторое API для данного ПО через HTTP протокол. Допустимо каждого несколько маленьких функций по настройке сервера и приобретению его нынешнего состояния по протоколу HTTP. Скажем, организовав обработку запросов GET с параметрами и отдавать маленький xml с результатом либо еще в каком-то формате. В таком случае дозволено с малыми трудозатратами сделать свой http-сервер, тот, что и будет интерфейсом для основного Вашего серверного ПО. Помимо этого если есть надобность сделать свой маленький специфичный сервис по раздаче какого-то комплекта файлов либо даже сделать свое собственное веб-приложение, то дозволено так же воспользоваться таким самописным небольшим сервером. В всеобщем дозволено воспользоваться как для построения самодостаточного серверного ПО, так и для создания вспомогательных сервисов в рамках больше больших систем.

Примитивный http-сервер менее чем в 40 строк

Дабы сделать примитивный однопоточный http-сервер с поддержкой libevent необходимо исполнить следующие несколько бесхитростных шагов:

  • Инициализировать всеобщий объект библиотеки с поддержкой функции event_init. Эта функция может применяться только для однопоточной обработки. Для многопоточной работы на всякий поток должен быть сделан свой объект (об этом ниже).
  • Создание непринужденно http-сервера осуществляется функцией evhttp_start в случае однопоточного сервера с глобальным объектом обработки событий. Объект сделанный с поддержкой evhttp_start в конце следует удалить с поддержкой evhttp_free.
  • Дабы реагировать на входящие запросы необходимо установить функцию обратного вызова с поддержкой evhttp_set_gencb.
  • Позже чего дозволено запускать цикл обработки событий функцией event_dispatch. Эта функция так же рассчитана на работу в одном потоке с глобальным объектом.
  • При обработке запроса дозволено получить буфер для результата функцией evhttp_request_get_output_buffer. В данный буфер добавить какой-то контент. Скажем, для отправки строки дозволено воспользоваться функцией evbuffer_add_printf, а для отправки файла функцией evbuffer_add_file. Позже чего результат на запрос должен быть отправлен, а сделать это дозволено с поддержкой evhttp_send_reply.

Код однопоточного сервера менее чем в 40 строк:

#include <memory>
#include <cstdint>
#include <iostream>
#include <evhttp.h>
int main()
{
  if (!event_init())
  {
    std::cerr << "Failed to init libevent." << std::endl;
    return -1;
  }
  char const SrvAddress[] = "127.0.0.1";
  std::uint16_t SrvPort = 5555;
  std::unique_ptr<evhttp, decltype(&evhttp_free)> Server(evhttp_start(SrvAddress, SrvPort), &evhttp_free);
  if (!Server)
  {
    std::cerr << "Failed to init http server." << std::endl;
    return -1;
  }
  void (*OnReq)(evhttp_request *req, void *) = [] (evhttp_request *req, void *)
  {
    auto *OutBuf = evhttp_request_get_output_buffer(req);
    if (!OutBuf)
      return;
    evbuffer_add_printf(OutBuf, "<html><body><center><h1>Hello Wotld!</h1></center></body></html>");
    evhttp_send_reply(req, HTTP_OK, "", OutBuf);
  };
  evhttp_set_gencb(Server.get(), OnReq, nullptr);
  if (event_dispatch() == -1)
  {
    std::cerr << "Failed to run messahe loop." << std::endl;
    return -1;
  }
  return 0;
}

Получилось менее 40 строк, которые способны обрабатывать http-запросы, отдавая в результат строку «Hello World», а если заменить функцию evbuffer_add_printf на evbuffer_add_file, то дозволено отправлять файлы. Дозволено такой сервер назвать базовой комплектацией. Всякий авто дилер либо риэлтор в большинстве своем мечтают, Дабы их авто и квартиры никогда и ни при каких условиях не уходили в базовой комплектации, а только с дополнительными опциями. А вот необходимы ли такие опции покупателю и в каком объеме…

Что может дать такая базовая комплектация по быстродействию дозволено проверить с поддержкой утилитыab для *nix систем с маленький вариацией параметров.

ab -c 1000 -k -r -t 10 http://127.0.0.1:5555/

Server Software:
Server Hostname: 127.0.0.1
Server Port: 5555Document Path: /
Document Length: 64 bytes

Concurrency Level: 1000
Time taken for tests: 2.289 seconds
Complete requests: 50000
Failed requests: 0
Write errors: 0
Keep-Alive requests: 50000
Total transferred: 8500000 bytes
HTML transferred: 3200000 bytes
Requests per second: 21843.76 [#/sec] (mean)
Time per request: 45.780 [ms] (mean)
Time per request: 0.046 [ms] (mean, across all concurrent requests)
Transfer rate: 3626.41 [Kbytes/sec] received

Connection Times (ms)
min mean[±sd] median max
Connect: 0 3 48.6 0 1001
Processing: 17 42 9.0 43 93
Waiting: 17 42 9.0 43 93
Total: 19 45 49.7 43 1053

ab -c 1000 -r -t 10 http://127.0.0.1:5555/

Server Software:
Server Hostname: 127.0.0.1
Server Port: 5555Document Path: /
Document Length: 64 bytes

Concurrency Level: 1000
Time taken for tests: 5.004 seconds
Complete requests: 50000
Failed requests: 0
Write errors: 0
Total transferred: 6300000 bytes
HTML transferred: 3200000 bytes
Requests per second: 9992.34 [#/sec] (mean)
Time per request: 100.077 [ms] (mean)
Time per request: 0.100 [ms] (mean, across all concurrent requests)
Transfer rate: 1229.53 [Kbytes/sec] received

Connection Times (ms)
min mean[±sd] median max
Connect: 0 61 214.1 20 3028
Processing: 7 34 17.6 31 277
Waiting: 6 28 16.9 25 267
Total: 17 95 219.5 50 3055

Тест проводился на теснее не вовсе новом ноутбуке (2 ядра, 4Гб оперативной памяти) под управлением 32-х битной операционной системы Ubuntu 12.10.

Многопоточный http-сервер

Необходима ли многопоточность? Вопрос риторический… Дозволено все IO и в одном потоке организовать, а запросы складывать в очередь и разгребать ее в несколько потоков. В таком случае вышеприведенный сервер дозволено легко дополнить const SrvAddress[] = “127.0.0.1″; std::uint16_t const SrvPort = 5555; int const SrvThreadCount = 4; try { void (*OnRequest)(evhttp_request *, void *) = [] (evhttp_request *req, void *) { auto *OutBuf = evhttp_request_get_output_buffer(req); if (!OutBuf) return; evbuffer_add_printf(OutBuf, “<html><body><center><h1>Hello Wotld!</h1></center></body></html>”); evhttp_send_reply(req, HTTP_OK, “”, OutBuf); }; std::exception_ptr InitExcept; bool volatile IsRun = true; evutil_socket_t Socket = -1; auto ThreadFunc = [&] () { try { std::unique_ptr<event_base, decltype(&event_base_free)> EventBase(event_base_new(), &event_base_free); if (!EventBase) throw std::runtime_error(“Failed to create new base_event.”); std::unique_ptr<evhttp, decltype(&evhttp_free)> EvHttp(evhttp_new(EventBase.get()), &evhttp_free); if (!EvHttp) throw std::runtime_error(“Failed to create new evhttp.”); evhttp_set_gencb(EvHttp.get(), OnRequest, nullptr); if (Socket == -1) { auto *BoundSock = evhttp_bind_socket_with_handle(EvHttp.get(), SrvAddress, SrvPort); if (!BoundSock) throw std::runtime_error(“Failed to bind server socket.”); if ((Socket = evhttp_bound_socket_get_fd(BoundSock)) == -1) throw std::runtime_error(“Failed to get server socket for next instance.”); } else { if (evhttp_accept_socket(EvHttp.get(), Socket) == -1) throw std::runtime_error(“Failed to bind server socket for new instance.”); } for ( ; IsRun ; ) { event_base_loop(EventBase.get(), EVLOOP_NONBLOCK); std::this_thread::sleep_for(std::chrono::milliseconds(10)); } } catch (…) { InitExcept = std::current_exception(); } }; auto ThreadDeleter = [&] (std::thread *t) { IsRun = false; t->join(); delete t; }; typedef std::unique_ptr<std::thread, decltype(ThreadDeleter)> ThreadPtr; typedef std::vector<ThreadPtr> ThreadPool; ThreadPool Threads; for (int i = 0 ; i < SrvThreadCount ; i) { ThreadPtr Thread(new std::thread(ThreadFunc), ThreadDeleter); std::this_thread::sleep_for(std::chrono::milliseconds(500)); if (InitExcept != std::exception_ptr()) { IsRun = false; std::rethrow_exception(InitExcept); } Threads.push_back(std::move(Thread)); } std::cout << “Press Enter fot quit.” << std::endl; std::cin.get(); IsRun = false; } catch (std::exception const &e) { std::cerr << “Error: ” << e.what() << std::endl; } return 0; }

В коде дозволено подметить, что всякий поток создается позже некоторого внесенного ожидания. Это маленький хак, тот, что теснее будет поправлен в финальной версии сервера. Пока дозволено сказать только, что если этого не сделать, то потоки нужно будет как-то синхронизировать, Дабы они отработали «необычный шаг» по созданию и привязке сокета. Для облегчения пока пускай останется такой хак. Так же в приведенном коде лямбда-функция может показаться спорным решением. Лямбды могут быть отличным решением при применении, скажем, в качестве некоторого предиката при работе со стандартными алгорифмами. В то же время дозволено задуматься об их применении и при написании больше крупных фрагментов кода. В примере выше дозволено было все перенести в обыкновенную функцию, передать все надобные параметры и получить код в жанре C 03. В то же время применение лямбды дало сокращение в объеме кода. На мой взор, когда код невелик, то лямбды могут абсолютно отлично в него вписывать даже с не самым коротким ее оглавлением и не влиять пагубно на качество кода, безусловно не стоит вдаваться в крайности и припоминать студенческие будни с написанием лабораторной работы в 700 строк в исключительной функции main.

Тестирование многопоточного сервера проведено с теми же параметрами, что и предыдущего примера.

ab -c 1000 -k -r -t 10 http://127.0.0.1:5555/

Server Software:
Server Hostname: 127.0.0.1
Server Port: 5555Document Path: /
Document Length: 64 bytes

Concurrency Level: 1000
Time taken for tests: 1.576 seconds
Complete requests: 50000
Failed requests: 0
Write errors: 0
Keep-Alive requests: 50000
Total transferred: 8500000 bytes
HTML transferred: 3200000 bytes
Requests per second: 31717.96 [#/sec] (mean)
Time per request: 31.528 [ms] (mean)
Time per request: 0.032 [ms] (mean, across all concurrent requests)
Transfer rate: 5265.68 [Kbytes/sec] received

 

ab -c 1000 -r -t 10 http://127.0.0.1:5555/

Server Software:
Server Hostname: 127.0.0.1
Server Port: 5555Document Path: /
Document Length: 64 bytes

Concurrency Level: 1000
Time taken for tests: 3.685 seconds
Complete requests: 50000
Failed requests: 0
Write errors: 0
Total transferred: 6300000 bytes
HTML transferred: 3200000 bytes
Requests per second: 13568.41 [#/sec] (mean)
Time per request: 73.701 [ms] (mean)
Time per request: 0.074 [ms] (mean, across all concurrent requests)
Transfer rate: 1669.55 [Kbytes/sec] received

Connection Times (ms)
min mean[±sd] median max
Connect: 0 36 117.2 23 1033
Processing: 3 37 10.0 37 247
Waiting: 3 30 8.7 30 242
Total: 9 73 118.8 61 1089

 

Финальный вариант сервера

Базовая комплектация приведена, комплектация с небольшим комплектом опций так же есть. Сейчас очередь подошла и для создания чего-то больше пригодного и функционального, а так же с небольшим тюнингом.

Наименьший http-сервер:

#include "http_server.h"
#include "http_headers.h"
#include "http_content_type.h"
#include <iostream>
int main()
{
  try
  {
    using namespace Network;
    HttpServer Srv("127.0.0.1", 5555, 4,
      [&] (IHttpRequestPtr req)
      {
        req->SetResponseAttr(Http::Response::Header::Server::Value, "MyTestServer");
        req->SetResponseAttr(Http::Response::Header::ContentType::Value,
                             Http::Content::Type::html::Value);
        req->SetResponseString("<html><body><center><h1>Hello Wotld!</h1></center></body></html>");
      });
    std::cout << "Press Enter for quit." << std::endl;
    std::cin.get();
  }
  catch (std::exception const &e)
  {
    std::cout << e.what() << std::endl;
  }
  return 0;
}

Крайне наименьший объем кода для http-сервера на C . За все есть плата. И в данном случае такая простота клиентского кода по созданию сервера, оплачена больше длинной реализацией, спрятанной в предлагаемой обертке над libevent. На самом же деле ненамного увеличилась реализация. Чуть ниже ее фрагменты будут описаны.

Создание сервера:

  • Нужно сделать объект типа HttpServer. В качестве параметров как минимум передать адрес и порт, на котором будет трудиться сервер, число потоков и функцию для обработки запросов (в данном случае так как обработка запросов минимальна, то дозволено и маленький лямбдой обойтись без создания отдельной функции либо даже целого класса-обработчика). Позже создания объекта сервер будет трудиться до тех пор, пока будет существовать его объект.
  • Обработчик принимает разумный указатель на интерфейс IHttpRequest, реализация которого скрывает всю работу с буфером libevent и отправку результата, а его способы дают вероятность получать данные из входящего запроса и формировать результат.

 

Интерфейс IHttpRequest

namespace Network
{
  DECLARE_RUNTIME_EXCEPTION(HttpRequest)

  struct IHttpRequest
  {
    enum class Type
    {
      HEAD, GET, PUT, POST
    };
    typedef std::unordered_map<std::string, std::string> RequestParams;
    virtual ~IHttpRequest() {}
    virtual Type GetRequestType() const = 0;
    virtual std::string const GetHeaderAttr(char const *attrName) const = 0;
    virtual std::size_t GetContentSize() const = 0;
    virtual void GetContent(void *buf, std::size_t len, bool remove) const = 0;
    virtual std::string const GetPath() const = 0;
    virtual RequestParams const GetParams() const = 0;
    virtual void SetResponseAttr(std::string const &name, std::string const &val) = 0;
    virtual void SetResponseCode(int code) = 0;
    virtual void SetResponseString(std::string const &str) = 0;
    virtual void SetResponseBuf(void const *data, std::size_t bytes) = 0;
    virtual void SetResponseFile(std::string const &fileName) = 0;
  };

  typedef std::shared_ptr<IHttpRequest> IHttpRequestPtr;
}

Данный интерфейс разрешает получать из входящего запроса его тип, некоторые признаки (заголовки), размер тела запроса и само тело запроса при его наличии, а так же формировать результат с вероятностью задать признаки (заголовки), код заключения обработки запроса и тело результата (в данной реализации имеются способы для передачи строки, некоторого буфера либо файла в результат). Всякий способ в его реализации может генерировать исключение типа HttpRequestException.

Если еще раз взглянуть на код сервера, то в коде обработки запросов дозволено подметить такие строки:

req->SetResponseAttr(Http::Response::Header::Server::Value, "MyTestServer");
req->SetResponseAttr(Http::Response::Header::ContentType::Value,
                     Http::Content::Type::html::Value);

Это образование заголовка результата, а данном примере задаются такие поля заголовка, как «Content-Type» и «Server». Не смотря на то, что libevent имеет довольно широкий функционал, выходящий вдалеке за надобности HTTP, списка констант полей заголовков в ней нет; есть только неполный список кодов возврата (особенно Зачастую используемых). Дабы не возиться со строками, определяющими поля заголовков (скажем, во избежании опечаток в пользовательском коде), все константы определены теснее в предлагаемой обертке над libevent.

Пример определения строковых констант

namespace Network
{
  namespace Http
  {
    namespace Request
    {
      namespace Header
      {
        DECLARE_STRING_CONSTANT(Accept, Accept)
        DECLARE_STRING_CONSTANT(AcceptCharset, Accept-Charset)
        // ...
      }

    }

    namespace Response
    {

      namespace Header
      {
        DECLARE_STRING_CONSTANT(AccessControlAllowOrigin, Access-Control-Allow-Origin)
        DECLARE_STRING_CONSTANT(AcceptRanges, Accept-Ranges)
        // ...
      }
    }
  }
}

Строковые константы дозволено определить как примитивными макросами в ветхом жанре чистого C в заголовочных файлах, так и разнести их объявления и определения между .h и .cpp файлами при этом сделав их типизированными теснее в жанре C . Впрочем дозволено обойтись и без разнесения по файлам, а сделать все типизированные определения в жанре C только в заголовочном файле. Для этого дозволено применять определенный подход с образцами и написать такой макрос (макросы, безусловно, признанное C зло, а так же в маленьких дозировках — бальзам; гетерогенные решения владеют большей жизнеспособностью).

DECLARE_STRING_CONSTANT

#define DECLARE_STRING_CONSTANT(name_, value_) 
  namespace Private 
  { 
    template <typename T> 
    struct name_ 
    { 
      static char const Name[]; 
      static char const Value[]; 
    }; 
    template <typename T> 
    char const name_ <T>::Name[] = #name_; 
    template <typename T> 
    char const name_ <T>::Value[] = #value_; 
  } 
  typedef Private:: name_ <void> name_;

Примерно аналогичным образом определены и константы для задания типа контента; имеют небольшую модификацию. Было желание реализовать поиск типа контента по растяжению файла для комфорта при отправке файлов в результат на запрос.

При желании что-то получить из входящего запроса, скажем, с какого хоста и с какой страницы был осуществлен переход на запрашиваемый источник и, скажем, есть ли у пользователя «печеньки», дозволено это все получить из заголовка входящего запроса таким образом:

std::string Host = req->GetHeaderAttr(Http::Request::Header::Host::Value);
std::string Referer = req->GetHeaderAttr(Http::Request::Header::Referer::Value);
std::string Cookie = req->GetHeaderAttr(Http::Request::Header::Cookie::Value);

Аналогичным образом в результате дозволено, скажем, установить пользователю некоторые Cookie, по которым в последующем трудиться с его сессией и отслеживать при желании его блуждания по Вашему источнику (пример работы с заголовками результата приведен в кода сервера).

Если же есть желание организовать некоторое свое API через HTTP, то это так же легко сделать. Представим нужно сделать способы: открытие сессии, приобретение статистической информации о сервере и закрытие сессии. Пускай для этого строки запроса к Вашему серверу будут выглядеть приблизительно так:

http://myserver.com/service/login/OpenSession?user=nym&pwd=kakoyto

http://myserver.com/service/login/CliseSession?sessionId=nym1234567890


http://myserver.com/service/stat/GetInfo?sessionId=nym1234567890

Результатом на эти строки запросов сервер пользователя может сгенерировать какой-то результат, скажем, в формате xml. Это дело разработчика сервера. А вот как трудиться с такими запросами, получать из них параметры приведено ниже:

auto Path = req->GetPath();
auto Params = req->GetParams();

Один из путей для примеров выше будет таким /service/login/OpenSession, а параметры это карта из переданных пар ключ / значение. Тип карты параметров:

typedef std::unordered_map<std::string, std::string> RequestParams;

Позже разбора каждого того, что дозволено реализовать с поддержкой предлагаемой финальной версии обертки над libevent дозволено заглянуть и под капот этой самой обертки.

Класс HttpServer

namespace Network
{
  DECLARE_RUNTIME_EXCEPTION(HttpServer)

  class HttpServer final
    : private Common::NonCopyable
  {
  public:
    typedef std::vector<IHttpRequest::Type> MethodPool;
    typedef std::function<void (IHttpRequestPtr)> OnRequestFunc;
    enum { MaxHeaderSize = static_cast<std::size_t>(-1), MaxBodySize = MaxHeaderSize };

    HttpServer(std::string const &address, std::uint16_t port,
               std::uint16_t threadCount, OnRequestFunc const &onRequest,
               MethodPool const &allowedMethods = {IHttpRequest::Type::GET },
               std::size_t maxHeadersSize = MaxHeaderSize,
               std::size_t maxBodySize = MaxBodySize);

  private:
    volatile bool IsRun = true;
    void (*ThreadDeleter)(std::thread *t) = [] (std::thread *t) { t->join(); delete t; };;
    typedef std::unique_ptr<std::thread, decltype(ThreadDeleter)> ThreadPtr;
    typedef std::vector<ThreadPtr> ThreadPool;
    ThreadPool Threads;
    Common::BoolFlagInvertor RunFlag;
  }; 
}
</source</spoiler>
<spoiler title="Реализация класса HttpServer"><source lang="cpp">
namespace Network
{
  HttpServer::HttpServer(std::string const &address, std::uint16_t port,
              std::uint16_t threadCount, OnRequestFunc const &onRequest,
              MethodPool const &allowedMethods,
              std::size_t maxHeadersSize, std::size_t maxBodySize)
    : RunFlag(&IsRun)
  {
    int AllowedMethods = -1;
    for (auto const i : allowedMethods)
      AllowedMethods |= HttpRequestTypeToAllowedMethod(i);
    bool volatile DoneInitThread = false;
    std::exception_ptr Except;
    evutil_socket_t Socket = -1;
    auto ThreadFunc = [&] ()
    {
      try
      {
        bool volatile ProcessRequest = false;
        RequestParams ReqPrm;
        ReqPrm.Func = onRequest;
        ReqPrm.Process = &ProcessRequest;
        typedef std::unique_ptr<event_base, decltype(&event_base_free)> EventBasePtr;
        EventBasePtr EventBase(event_base_new(), &event_base_free);
        if (!EventBase)
          throw HttpServerException("Failed to create new base_event.");
        typedef std::unique_ptr<evhttp, decltype(&evhttp_free)> EvHttpPtr;
        EvHttpPtr EvHttp(evhttp_new(EventBase.get()), &evhttp_free);
        if (!EvHttp)
          throw HttpServerException("Failed to create new evhttp.");
        evhttp_set_allowed_methods(EvHttp.get(), AllowedMethods);
        if (maxHeadersSize != MaxHeaderSize)
          evhttp_set_max_headers_size(EvHttp.get(), maxHeadersSize);
        if (maxBodySize != MaxBodySize)
          evhttp_set_max_body_size(EvHttp.get(), maxBodySize);
        evhttp_set_gencb(EvHttp.get(), &OnRawRequest, &ReqPrm);
        if (Socket == -1)
        {
          auto *BoundSock = evhttp_bind_socket_with_handle(EvHttp.get(), address.c_str(), port);
          if (!BoundSock)
            throw HttpServerException("Failed to bind server socket.");
          if ((Socket = evhttp_bound_socket_get_fd(BoundSock)) == -1)
            throw HttpServerException("Failed to get server socket for next instance.");
        }
        else
        {
          if (evhttp_accept_socket(EvHttp.get(), Socket) == -1)
            throw HttpServerException("Failed to bind server socket for new instance.");
        }
        DoneInitThread = true;
        for ( ; IsRun ; )
        {
          ProcessRequest = false;
          event_base_loop(EventBase.get(), EVLOOP_NONBLOCK);
          if (!ProcessRequest)
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
      }
      catch (...)
      {
        Except = std::current_exception();
      }
    };
    ThreadPool NewThreads;
    for (int i = 0 ; i < threadCount ;   i)
    {
      DoneInitThread = false;
      ThreadPtr Thread(new std::thread(ThreadFunc), ThreadDeleter);
      NewThreads.push_back(std::move(Thread));
      for ( ; ; )
      {
        if (Except != std::exception_ptr())
        {
          IsRun = false;
          std::rethrow_exception(Except);
        }
        if (DoneInitThread)
          break;
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
      }
    }
    Threads = std::move(NewThreads);
  }
}

Функцию обработки запросов дозволено посмотреть в полной версии, скачав начальные файлы примеров, она стала немножко огромнее, чем в ранее приведенных примерах, и перестала претендовать на лямбду без потери читаемости кода. Так же не стал приводить реализацию интерфейса IHttpRequest, так как она немного увлекательна своей рутинной работой с буфером libevent. А в остальном если посмотреть на код итоговой версии, он не крепко-то изменился. Маленькая модификация и добавилось немножко «тюнинга».

Сервер пользователя не обязан обрабатывать все типы http-запросов. Дозволено задать список типов запросов, которые сервер должен обрабабывать и для этого libevent имеет функцию evhttp_set_allowed_methods (а по умолчанию обертка задает только тип запросов GET). При задании списка обрабатываемых запросов на все остальные libevent сама будет информировать о неосуществимости выполнения такого запроса, тем самым избавив пользователя от дополнительных проверок.

Пытливость ума она бывает различной: нацеленной на творчество и на уничтожение. От разорительной пытливости ума с желанием «завалить» сервер послав ему какой-то непомерно для него огромный заголовок http-пакета либо сформировав огромное тело запроса дозволено так же проактивно защититься функциями evhttp_set_max_headers_size и evhttp_set_max_body_size. Безусловно же отправка крупных запросов может быть вызвана не только недобрыми помыслами, а так же и иными причинами. Приведенные способы дозволят немножко сократить неугодные аварийные заключения Вашего сервера. Допустимо еще что-то предусмотреть, а в остальном теснее дозволено реагировать реактивно, что как правило и происходит…

В конце приведу финальную версию, которая отрабатывает запросы GET (отдает файлы из указанной директории) и выводит на экран с какого хоста был сделан запрос и с какой страницы был осуществлен переход на источник, обрабатываемый сервером.

Финальная версия простого http-сервера

#include "http_server.h"
#include "http_headers.h"
#include "http_content_type.h"
#include <iostream>
#include <sstream>
#include <mutex>
int main()
{
  char const SrvAddress[] = "127.0.0.1";
  std::uint16_t SrvPort = 5555;
  std::uint16_t SrvThreadCount = 4;
  std::string const RootDir = "../test_content";
  std::string const DefaultPage = "index.html";
  std::mutex Mtx;
  try
  {
    using namespace Network;
    HttpServer Srv(SrvAddress, SrvPort, SrvThreadCount,
      [&] (IHttpRequestPtr req)
      {
        std::string Path = req->GetPath();
        Path = RootDir   Path   (Path == "/" ? DefaultPage : std::string());
        {
          std::stringstream Io;
          Io << "Path: " << Path << std::endl
             << Http::Request::Header::Host::Name << ": "
                  << req->GetHeaderAttr(Http::Request::Header::Host::Value) << std::endl
             << Http::Request::Header::Referer::Name << ": "
                  << req->GetHeaderAttr(Http::Request::Header::Referer::Value) << std::endl;
          std::lock_guard<std::mutex> Lock(Mtx);
          std::cout << Io.str() << std::endl;
        }
        req->SetResponseAttr(Http::Response::Header::Server::Value, "MyTestServer");
        req->SetResponseAttr(Http::Response::Header::ContentType::Value,
                             Http::Content::TypeFromFileName(Path));
        req->SetResponseFile(Path);
      });
    std::cin.get();
  }
  catch (std::exception const &e)
  {
    std::cout << e.what() << std::endl;
  }
  return 0;
}

Завершение

Помимо рассмотренного функционала libevent еще много содержит пригодных вероятностей. В всеобщем: еще есть чего испробовать написать с поддержкой этой библиотеки и о чем написать. Данный пост показал только ее малую часть, предуготовленную для разработки http-серверов. Конечный пример этого поста взят за основу, в которую на скорую руку добавлено немножко вспомогательного функционала и реализован сервер, на котором и расположены начальные файлы всех приведенных примеров в виде zip-архива. Скачивая архив с примерами с сервера, разработанного на их же основе дозволено посмотреть на жизнеспособность сервера. В конце прошлого года мною был опубликован пост «Система плагинов как упражнение на C 11». В личку были вопросы наличии какой-то еще информации, моем желании развивать план, поддержке и т. д. и в определенный момент я решил организовать маленький информационный источник для описанной системы плагинов. Дизайнер из меня никакой, так что за дизайн крепко умоляю не журить :) Накидал немножко статического контента для этого источника и нужно было его чем-то отдавать. Да, дозволено было поднять что-то из nginx либо apache, а может и еще что-то. Но мне было увлекательно как будут трудиться ранее разработанные мной тестовые примеры http-серверов, которые я описывал в посте о решении тестового задания с написанием «простенького» http-сервера. И на одном из таких примеров, разработанном «на сокетах» (как изредка это любят называть в тестовых заданиях и т. д.) с собственным разбором протокола и т. д. сайт был доступен примерно месяц. Проработал удачно и без падений. Програэффекта безусловно же не было. Да и откуда ему взяться. А с написанием этого поста я перевел выдачу контента о системе плагинов на сервер, разработанный на примерах этого же поста, там же поместил и сами примеры описываемых серверов. Выдержит ли такой сервер програэффекта? Не знаю. Абсолютно может быть, что на ярусе приложения и выдержит, а вот выдержит ли моя VDS’ка (2 ядра, 1Гб оперативной памяти, ОС — Ubuntu 12.04 64bit) теснее затрудняюсь сказать. А допустим ли сам програэффект так же не могу на это верить. Пока предлагаю немножко протестировать полученный сервер с учетом сети и расположения на удаленном виртуальном сервере, а не локально на моей же машине. Итог тестирования:

ab -c 1000 -k -r -t 10 http://t-boss.ru/libevent_test_http_srv.zip

Server Software: t-boss
Server Hostname: t-boss.ru
Server Port: 80Document Path: /libevent_test_http_srv.zip
Document Length: 23756 bytes

Concurrency Level: 1000
Time taken for tests:10.012 seconds
Complete requests: 2293
Failed requests: 0
Write errors: 0
Keep-Alive requests: 2293
Total transferred: 60628847 bytes
HTML transferred: 60328370 bytes
Requests per second: 229.02 [#/sec] (mean)
Time per request: 4366.365 [ms] (mean)
Time per request: 4.366 [ms] (mean, across all concurrent requests)
Transfer rate: 5913.65 [Kbytes/sec] received

Две с небольшим тысячи обработанных запросов на приобретение архива с начальными файлами примеров поста за десять секунд. Помимо самого http-сервера есть еще несколько иных задач: оптимальная организация логилования, кэширование и т. д. Пока этого нет и это дает вероятность еще немножко поэксперементировать с memcached, berkeley db и иными спецтехнологиями по созданию собственного веб-приложения на C и о итогах написать.

Каждому спасибо за внимание!

Материалы

 

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

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