LCOV - code coverage report
Current view: top level - zeroeq/http - server.cpp (source / functions) Hit Total Coverage
Test: ZeroEQ Lines: 313 337 92.9 %
Date: 2017-12-01 01:44:57 Functions: 76 79 96.2 %

          Line data    Source code
       1             : 
       2             : /* Copyright (c) 2016-2017, Human Brain Project
       3             :  *                          Stefan.Eilemann@epfl.ch
       4             :  *                          Daniel.Nachbaur@epfl.ch
       5             :  *                          Pawel.Podhajski@epfl.ch
       6             :  *                          Raphael.Dumusc@epfl.ch
       7             :  */
       8             : 
       9             : #include "server.h"
      10             : 
      11             : #include "helpers.h"
      12             : #include "requestHandler.h"
      13             : 
      14             : #include "../detail/common.h"
      15             : #include "../detail/sender.h"
      16             : #include "../detail/socket.h"
      17             : #include "../log.h"
      18             : 
      19             : #include "jsoncpp/json/json.h"
      20             : 
      21             : #include <servus/serializable.h>
      22             : 
      23             : // for NI_MAXHOST
      24             : #ifdef _WIN32
      25             : #include <Ws2tcpip.h>
      26             : #else
      27             : #include <netdb.h>
      28             : #include <unistd.h>
      29             : #endif
      30             : 
      31             : #include <algorithm>
      32             : #include <array>
      33             : #include <future>
      34             : #include <thread>
      35             : 
      36             : namespace
      37             : {
      38             : // Transform camelCase to hyphenated-notation, e.g.
      39             : // lexis/render/LookOut -> lexis/render/look-out
      40             : // Inspiration:
      41             : // https://gist.github.com/rodamber/2558e25d4d8f6b9f2ffdf7bd49471340
      42          68 : std::string _camelCaseToHyphenated(const std::string& camelCase)
      43             : {
      44          68 :     if (camelCase.empty())
      45           0 :         return camelCase;
      46             : 
      47         136 :     std::string str(1, tolower(camelCase[0]));
      48         544 :     for (auto it = camelCase.begin() + 1; it != camelCase.end(); ++it)
      49             :     {
      50         476 :         if (isupper(*it) && *(it - 1) != '-' && islower(*(it - 1)))
      51           0 :             str += "-";
      52         476 :         str += *it;
      53             :     }
      54             : 
      55          68 :     std::transform(str.begin(), str.end(), str.begin(), ::tolower);
      56          68 :     return str;
      57             : }
      58             : 
      59             : // http://stackoverflow.com/questions/5343190
      60          68 : std::string _replaceAll(std::string subject, const std::string& search,
      61             :                         const std::string& replace)
      62             : {
      63          68 :     size_t pos = 0;
      64         204 :     while ((pos = subject.find(search, pos)) != std::string::npos)
      65             :     {
      66          68 :         subject.replace(pos, search.length(), replace);
      67          68 :         pos += replace.length();
      68             :     }
      69          68 :     return subject;
      70             : }
      71             : 
      72             : // convert name to lowercase with '/' separators instead of '::'
      73          68 : std::string _convertEndpointName(const std::string& endpoint)
      74             : {
      75          68 :     return _camelCaseToHyphenated(_replaceAll(endpoint, "::", "/"));
      76             : }
      77             : 
      78          16 : const std::string JSON_TYPE = "application/json";
      79          16 : const std::string REQUEST_REGISTRY = "registry";
      80          16 : const std::string REQUEST_SCHEMA = "schema";
      81          16 : const std::string HTTP_SERVER_SERVICE = "_http._tcp"; // Standard HTTP service
      82             : 
      83         326 : void _checkEndpointName(const std::string& endpoint)
      84             : {
      85         326 :     if (endpoint == REQUEST_REGISTRY)
      86           4 :         ZEROEQTHROW(
      87             :             std::runtime_error("'registry' not allowed as endpoint name"));
      88         322 : }
      89             : 
      90         136 : bool _endsWithSchema(const std::string& uri)
      91             : {
      92         136 :     if (uri.length() < REQUEST_SCHEMA.length())
      93          30 :         return false;
      94         106 :     return uri.compare(uri.length() - REQUEST_SCHEMA.length(),
      95         106 :                        std::string::npos, REQUEST_SCHEMA) == 0;
      96             : }
      97             : 
      98         120 : std::string _removeEndpointFromPath(const std::string& endpoint,
      99             :                                     const std::string& path)
     100             : {
     101         120 :     if (endpoint == "/")
     102           8 :         return path;
     103             : 
     104         112 :     if (endpoint.size() >= path.size())
     105          72 :         return std::string();
     106             : 
     107          40 :     return path.substr(endpoint.size());
     108             : }
     109             : 
     110          72 : std::string _getHost(const zeroeq::URI& uri)
     111             : {
     112             :     // INADDR_ANY translation: zmq -> boost.asio
     113          72 :     if (uri.getHost().empty() || uri.getHost() == "*")
     114          68 :         return "0.0.0.0";
     115           4 :     return uri.getHost();
     116             : }
     117             : 
     118         392 : bool _isCorsRequest(const zeroeq::http::Message& message)
     119             : {
     120         392 :     return !message.origin.empty();
     121             : }
     122             : 
     123         208 : bool _isCorsPreflightRequest(const zeroeq::http::Message& message)
     124             : {
     125         244 :     return _isCorsRequest(message) &&
     126         234 :            message.request.method == zeroeq::http::Method::OPTIONS &&
     127         234 :            message.accessControlRequestMethod != zeroeq::http::Method::ALL;
     128             : }
     129             : } // anonymous namespace
     130             : 
     131             : namespace zeroeq
     132             : {
     133             : namespace http
     134             : {
     135             : class Server::Impl : public detail::Sender
     136             : {
     137             : public:
     138          72 :     Impl(const URI& uri_, const std::string& session)
     139         216 :         : detail::Sender(URI(_getInprocURI()), ZMQ_PAIR, HTTP_SERVER_SERVICE,
     140         144 :                          session == DEFAULT_SESSION ? getDefaultPubSession()
     141             :                                                     : session)
     142         144 :         , _requestHandler(_getInprocURI())
     143             :         , _httpOptions(_requestHandler)
     144         144 :         , _httpServer(_httpOptions.address(_getHost(uri_))
     145         216 :                           .port(std::to_string(int(uri_.getPort())))
     146          72 :                           .protocol_family(HTTPServer::options::ipv4)
     147         576 :                           .reuse_address(true))
     148             :     {
     149          72 :         if (::zmq_bind(socket.get(), _getInprocURI().c_str()) == -1)
     150             :         {
     151           0 :             ZEROEQTHROW(
     152             :                 std::runtime_error("Cannot bind HTTPServer to inproc socket"));
     153             :         }
     154             : 
     155             :         try
     156             :         {
     157          72 :             _httpServer.listen();
     158         288 :             _httpThread.reset(new std::thread([&] {
     159             :                 try
     160             :                 {
     161          72 :                     _httpServer.run();
     162             :                 }
     163           0 :                 catch (const std::exception& e)
     164             :                 {
     165             :                     ZEROEQERROR
     166           0 :                         << "Error during HTTPServer::run(): " << e.what()
     167           0 :                         << std::endl;
     168             :                 }
     169         216 :             }));
     170             :         }
     171           0 :         catch (const std::exception& e)
     172             :         {
     173           0 :             ZEROEQTHROW(std::runtime_error(
     174             :                 std::string("Error while starting HTTP server: ") + e.what()));
     175             :         }
     176             : 
     177          72 :         uri = URI();
     178          72 :         uri.setHost(_httpServer.address());
     179          72 :         uri.setPort(std::stoi(_httpServer.port()));
     180             : 
     181          72 :         if (uri.getHost() == "0.0.0.0")
     182             :         {
     183          68 :             char hostname[NI_MAXHOST + 1] = {0};
     184          68 :             gethostname(hostname, NI_MAXHOST);
     185          68 :             hostname[NI_MAXHOST] = '\0';
     186          68 :             uri.setHost(hostname);
     187             :         }
     188             : 
     189          72 :         if (uri_.getHost() != uri.getHost() || uri_.getPort() == 0)
     190          72 :             ZEROEQINFO << "HTTP server bound to " << uri.getHost() << ":"
     191         144 :                        << uri.getPort() << std::endl;
     192             : 
     193          72 :         if (!servus::Servus::isAvailable())
     194           0 :             return;
     195             : 
     196          72 :         announce();
     197             :     }
     198             : 
     199          72 :     ~Impl()
     200          72 :     {
     201          72 :         if (_httpThread)
     202             :         {
     203          72 :             _httpServer.stop();
     204          72 :             _httpThread->join();
     205             :         }
     206          72 :     }
     207             : 
     208          68 :     void registerSchema(const std::string& endpoint, const std::string& schema)
     209             :     {
     210         136 :         const std::string exist = getSchema(endpoint);
     211          68 :         if (exist.empty())
     212          48 :             _schemas[endpoint] = schema;
     213          20 :         else if (schema != exist)
     214           4 :             ZEROEQTHROW(std::runtime_error(
     215             :                 "Schema registered for endpoint differs: " + endpoint));
     216          64 :     }
     217             : 
     218          78 :     std::string getSchema(const std::string& endpoint) const
     219             :     {
     220          78 :         const auto& i = _schemas.find(endpoint);
     221          78 :         return i != _schemas.end() ? i->second : std::string();
     222             :     }
     223             : 
     224          14 :     bool remove(const servus::Serializable& serializable)
     225             :     {
     226          28 :         const auto endpoint = _convertEndpointName(serializable.getTypeName());
     227          14 :         _schemas.erase(endpoint);
     228          14 :         const bool foundPUT = _methods[int(Method::PUT)].erase(endpoint) != 0;
     229          14 :         const bool foundGET = _methods[int(Method::GET)].erase(endpoint) != 0;
     230          28 :         return foundPUT || foundGET;
     231             :     }
     232             : 
     233          18 :     bool remove(const std::string& endpoint)
     234             :     {
     235          18 :         _schemas.erase(endpoint);
     236          18 :         bool foundMethod = false;
     237         126 :         for (auto& method : _methods)
     238         108 :             if (method.erase(endpoint) != 0)
     239          22 :                 foundMethod = true;
     240          18 :         return foundMethod;
     241             :     }
     242             : 
     243         214 :     bool handle(const Method method, const std::string& endpoint, RESTFunc func)
     244             :     {
     245         214 :         _checkEndpointName(endpoint);
     246             : 
     247         214 :         if (_methods[int(method)].count(endpoint) != 0)
     248          28 :             return false;
     249             : 
     250         186 :         _methods[int(method)][endpoint] = func;
     251         186 :         return true;
     252             :     }
     253             : 
     254          22 :     bool handlePUT(const std::string& endpoint,
     255             :                    servus::Serializable& serializable)
     256             :     {
     257          10 :         const auto func = [&serializable](const std::string& json) {
     258             :             return serializable.fromJSON(json);
     259          32 :         };
     260          22 :         return handlePUT(endpoint, serializable.getSchema(), func);
     261             :     }
     262             : 
     263          50 :     bool handlePUT(const std::string& endpoint, const std::string& schema,
     264             :                    const PUTPayloadFunc& func)
     265             :     {
     266          50 :         _checkEndpointName(endpoint);
     267             : 
     268         340 :         const auto futureFunc = [func](const Request& request) {
     269          20 :             const auto code = func(request.body) ? Code::OK : Code::BAD_REQUEST;
     270          20 :             return make_ready_response(code);
     271          96 :         };
     272          48 :         if (!handle(Method::PUT, endpoint, futureFunc))
     273           8 :             return false;
     274             : 
     275          40 :         if (!schema.empty())
     276          28 :             registerSchema(endpoint, schema);
     277          38 :         return true;
     278             :     }
     279             : 
     280          30 :     bool handleGET(const std::string& endpoint,
     281             :                    const servus::Serializable& serializable)
     282             :     {
     283          44 :         const auto func = [&serializable] { return serializable.toJSON(); };
     284          30 :         return handleGET(endpoint, serializable.getSchema(), func);
     285             :     }
     286             : 
     287          62 :     bool handleGET(const std::string& endpoint, const std::string& schema,
     288             :                    const GETFunc& func)
     289             :     {
     290          62 :         _checkEndpointName(endpoint);
     291             : 
     292         428 :         const auto futureFunc = [func](const Request&) {
     293          46 :             return make_ready_response(Code::OK, func(), JSON_TYPE);
     294         164 :         };
     295          60 :         if (!handle(Method::GET, endpoint, futureFunc))
     296           8 :             return false;
     297             : 
     298          52 :         if (!schema.empty())
     299          40 :             registerSchema(endpoint, schema);
     300             : 
     301          50 :         return true;
     302             :     }
     303             : 
     304     1799436 :     void addSockets(std::vector<detail::Socket>& entries)
     305             :     {
     306             :         detail::Socket entry;
     307     1799436 :         entry.socket = socket.get();
     308     1799436 :         entry.events = ZMQ_POLLIN;
     309     1799436 :         entries.push_back(entry);
     310     1799436 :     }
     311             : 
     312           6 :     std::string getRegistry() const
     313             :     {
     314          12 :         Json::Value body(Json::objectValue);
     315          12 :         for (const auto& i : _methods[int(Method::GET)])
     316           6 :             body[i.first].append("GET");
     317           8 :         for (const auto& i : _methods[int(Method::POST)])
     318           2 :             body[i.first].append("POST");
     319          12 :         for (const auto& i : _methods[int(Method::PUT)])
     320           6 :             body[i.first].append("PUT");
     321           8 :         for (const auto& i : _methods[int(Method::PATCH)])
     322           2 :             body[i.first].append("PATCH");
     323           8 :         for (const auto& i : _methods[int(Method::DELETE)])
     324           2 :             body[i.first].append("DELETE");
     325           8 :         for (const auto& i : _methods[int(Method::OPTIONS)])
     326           2 :             body[i.first].append("OPTIONS");
     327          12 :         return body.toStyledString();
     328             :     }
     329             : 
     330          52 :     std::string getAllowedMethods(const std::string& endpoint) const
     331             :     {
     332          52 :         std::string methods;
     333          52 :         if (_methods[int(Method::GET)].count(endpoint))
     334          16 :             methods.append(methods.empty() ? "GET" : ", GET");
     335          52 :         if (_methods[int(Method::POST)].count(endpoint))
     336           0 :             methods.append(methods.empty() ? "POST" : ", POST");
     337          52 :         if (_methods[int(Method::PUT)].count(endpoint))
     338          16 :             methods.append(methods.empty() ? "PUT" : ", PUT");
     339          52 :         if (_methods[int(Method::PATCH)].count(endpoint))
     340           0 :             methods.append(methods.empty() ? "PATCH" : ", PATCH");
     341          52 :         if (_methods[int(Method::DELETE)].count(endpoint))
     342           0 :             methods.append(methods.empty() ? "DELETE" : ", DELETE");
     343          52 :         if (_methods[int(Method::OPTIONS)].count(endpoint))
     344           0 :             methods.append(methods.empty() ? "OPTIONS" : ", OPTIONS");
     345          52 :         return methods;
     346             :     }
     347             : 
     348         184 :     std::future<Response> respondTo(Request& request) const
     349             :     {
     350         184 :         const auto method = request.method;
     351         368 :         const auto path = request.path.substr(1); // remove leading '/'
     352             : 
     353         184 :         if (method == Method::GET)
     354             :         {
     355         118 :             if (path == REQUEST_REGISTRY)
     356           6 :                 return make_ready_response(Code::OK, getRegistry(), JSON_TYPE);
     357             : 
     358         112 :             if (_endsWithSchema(path))
     359             :             {
     360          32 :                 const auto endpoint = path.substr(0, path.find_last_of('/'));
     361          20 :                 const auto it = _schemas.find(endpoint);
     362          20 :                 if (it != _schemas.end())
     363           8 :                     return make_ready_response(Code::OK, it->second, JSON_TYPE);
     364             :             }
     365             :         }
     366             : 
     367         404 :         const auto beginsWithPath = [&path](const FuncMap::value_type& pair) {
     368         202 :             const auto& endpoint = pair.first;
     369         202 :             return path.find(endpoint) == 0;
     370         170 :         };
     371         170 :         const auto& funcMap = _methods[int(method)];
     372             :         const auto it =
     373         170 :             std::find_if(funcMap.begin(), funcMap.end(), beginsWithPath);
     374         170 :         if (it != funcMap.end())
     375             :         {
     376         120 :             const auto& endpoint = it->first;
     377         120 :             const auto& func = it->second;
     378             : 
     379         130 :             const auto pathStripped = _removeEndpointFromPath(endpoint, path);
     380         120 :             if (pathStripped.empty() || *endpoint.rbegin() == '/')
     381             :             {
     382         112 :                 request.path = pathStripped;
     383         112 :                 return func(request);
     384             :             }
     385             :         }
     386             : 
     387             :         // if "/" is registered as an endpoint it should be passed all
     388             :         // unhandled requests.
     389          58 :         if (funcMap.count("/"))
     390             :         {
     391          10 :             const auto& func = funcMap.at("/");
     392          10 :             request.path = path;
     393          10 :             return func(request);
     394             :         }
     395             : 
     396             :         // return informative error 405 "Method Not Allowed" if possible
     397          96 :         const auto allowedMethods = getAllowedMethods(path);
     398          48 :         if (!allowedMethods.empty())
     399             :         {
     400             :             using Headers = std::map<Header, std::string>;
     401          28 :             Headers headers{{Header::ALLOW, allowedMethods}};
     402          28 :             return make_ready_response(Code::NOT_SUPPORTED, std::string(),
     403          28 :                                        std::move(headers));
     404             :         }
     405             : 
     406          34 :         return make_ready_response(Code::NOT_FOUND);
     407             :     }
     408             : 
     409          24 :     void processCorsPreflightRequest(Message& message) const
     410             :     {
     411             :         // In a typical situation, user agents discover via a preflight request
     412             :         // whether a cross-origin resource is prepared to accept requests.
     413             :         // The current implementation accepts all sources for all requests.
     414             :         // More information can be found here: https://www.w3.org/TR/cors
     415             : 
     416             :         // remove leading '/'
     417          30 :         const auto path = message.request.path.substr(1);
     418             : 
     419          24 :         const bool isSchemaRequest = _isSchemaRequest(message);
     420          44 :         if (!_methods[int(message.accessControlRequestMethod)].count(path) &&
     421          20 :             !isSchemaRequest)
     422             :         {
     423          18 :             message.response = make_ready_response(Code::NOT_SUPPORTED);
     424          18 :             return;
     425             :         }
     426             : 
     427             :         message.corsResponseHeaders = {
     428             :             {CorsResponseHeader::access_control_allow_headers, "Content-Type"},
     429             :             {CorsResponseHeader::access_control_allow_methods,
     430          12 :              isSchemaRequest ? "GET" : getAllowedMethods(path)},
     431          18 :             {CorsResponseHeader::access_control_allow_origin, "*"}};
     432           6 :         message.response = make_ready_response(Code::OK);
     433             :     }
     434             : 
     435             : private:
     436          24 :     bool _isSchemaRequest(Message& message) const
     437             :     {
     438          48 :         const auto path = message.request.path.substr(1);
     439          24 :         if (!_endsWithSchema(path))
     440          12 :             return false;
     441             : 
     442          24 :         const auto endpoint = path.substr(0, path.find_last_of('/'));
     443          12 :         if (_schemas.find(endpoint) == _schemas.end())
     444           0 :             return false;
     445             : 
     446          12 :         return message.accessControlRequestMethod == Method::GET;
     447             :     }
     448             : 
     449             :     // key stores endpoints of Serializable objects lower-case, hyphenated,
     450             :     // with '/' separators
     451             :     // must be an ordered map in order to iterate from the most specific path
     452             :     typedef std::map<std::string, RESTFunc, std::greater<std::string>> FuncMap;
     453             :     typedef std::map<std::string, std::string> SchemaMap;
     454             : 
     455             :     SchemaMap _schemas;
     456             :     std::array<FuncMap, size_t(Method::ALL)> _methods;
     457             :     RequestHandler _requestHandler;
     458             :     HTTPServer::options _httpOptions;
     459             :     HTTPServer _httpServer;
     460             :     std::unique_ptr<std::thread> _httpThread;
     461             : 
     462         216 :     std::string _getInprocURI() const
     463             :     {
     464         432 :         std::ostringstream inprocURI;
     465             : // No socket notifier possible on inproc ZMQ sockets,
     466             : // (https://github.com/zeromq/libzmq/issues/1434).
     467             : // Use inproc on Windows as ipc is not supported there, which means
     468             : // we do not support notifications on Windows...
     469             : #ifdef _MSC_VER
     470             :         inprocURI << "inproc://#" << static_cast<const void*>(this);
     471             : #else
     472         216 :         inprocURI << "ipc:///tmp/" << static_cast<const void*>(this);
     473             : #endif
     474         432 :         return inprocURI.str();
     475             :     }
     476             : };
     477             : 
     478             : namespace
     479             : {
     480          12 : std::string _getServerParameter(const int argc, const char* const* argv)
     481             : {
     482          24 :     for (int i = 0; i < argc; ++i)
     483             :     {
     484          20 :         if (std::string(argv[i]) == "--zeroeq-http-server")
     485             :         {
     486           8 :             if (i == argc - 1 || argv[i + 1][0] == '-')
     487           4 :                 return "tcp://";
     488           4 :             return argv[i + 1];
     489             :         }
     490             :     }
     491           4 :     return std::string();
     492             : }
     493             : }
     494             : 
     495           4 : Server::Server(const URI& uri, Receiver& shared)
     496             :     : Receiver(shared)
     497           4 :     , _impl(new Impl(uri, DEFAULT_SESSION))
     498             : {
     499           4 : }
     500             : 
     501           8 : Server::Server(const URI& uri)
     502             :     : Receiver()
     503           8 :     , _impl(new Impl(uri, DEFAULT_SESSION))
     504             : {
     505           8 : }
     506             : 
     507           4 : Server::Server(Receiver& shared)
     508             :     : Receiver(shared)
     509           4 :     , _impl(new Impl(URI(), DEFAULT_SESSION))
     510             : {
     511           4 : }
     512             : 
     513          56 : Server::Server()
     514             :     : Receiver()
     515          56 :     , _impl(new Impl(URI(), DEFAULT_SESSION))
     516             : {
     517          56 : }
     518             : 
     519          80 : Server::~Server()
     520             : {
     521          80 : }
     522             : 
     523           6 : std::unique_ptr<Server> Server::parse(const int argc, const char* const* argv)
     524             : {
     525          12 :     const std::string& param = _getServerParameter(argc, argv);
     526           6 :     if (param.empty())
     527           2 :         return nullptr;
     528             : 
     529           4 :     return std::unique_ptr<Server>(new Server(URI(param)));
     530             : }
     531             : 
     532           6 : std::unique_ptr<Server> Server::parse(const int argc, const char* const* argv,
     533             :                                       Receiver& shared)
     534             : {
     535          12 :     const std::string& param = _getServerParameter(argc, argv);
     536           6 :     if (param.empty())
     537           2 :         return nullptr;
     538             : 
     539           4 :     return std::unique_ptr<Server>(new Server(URI(param), shared));
     540             : }
     541             : 
     542          82 : const URI& Server::getURI() const
     543             : {
     544          82 :     return _impl->uri;
     545             : }
     546             : 
     547           8 : SocketDescriptor Server::getSocketDescriptor() const
     548             : {
     549             : #ifdef _MSC_VER
     550             :     ZEROEQTHROW(std::runtime_error(
     551             :         std::string("HTTP server socket descriptor not available")));
     552             : #else
     553           8 :     SocketDescriptor fd = 0;
     554           8 :     size_t fdLength = sizeof(fd);
     555           8 :     if (::zmq_getsockopt(_impl->socket.get(), ZMQ_FD, &fd, &fdLength) == -1)
     556             :     {
     557           0 :         ZEROEQTHROW(
     558             :             std::runtime_error(std::string("Could not get socket descriptor")));
     559             :     }
     560           8 :     return fd;
     561             : #endif
     562             : }
     563             : 
     564           0 : bool Server::handle(const std::string& endpoint, servus::Serializable& object)
     565             : {
     566           0 :     return handlePUT(endpoint, object) && handleGET(endpoint, object);
     567             : }
     568             : 
     569         106 : bool Server::handle(const Method action, const std::string& endpoint,
     570             :                     RESTFunc func)
     571             : {
     572         106 :     return _impl->handle(action, endpoint, func);
     573             : }
     574             : 
     575          14 : bool Server::remove(const servus::Serializable& object)
     576             : {
     577          14 :     return _impl->remove(object);
     578             : }
     579             : 
     580          18 : bool Server::remove(const std::string& endpoint)
     581             : {
     582          18 :     return _impl->remove(endpoint);
     583             : }
     584             : 
     585          20 : bool Server::handlePUT(servus::Serializable& object)
     586             : {
     587          20 :     return _impl->handlePUT(_convertEndpointName(object.getTypeName()), object);
     588             : }
     589             : 
     590           2 : bool Server::handlePUT(const std::string& endpoint,
     591             :                        servus::Serializable& object)
     592             : {
     593           2 :     return _impl->handlePUT(endpoint, object);
     594             : }
     595             : 
     596          10 : bool Server::handlePUT(const std::string& endpoint, const PUTFunc& func)
     597             : {
     598          20 :     return _impl->handlePUT(endpoint, "",
     599         116 :                             [func](const std::string&) { return func(); });
     600             : }
     601             : 
     602           8 : bool Server::handlePUT(const std::string& endpoint, const std::string& schema,
     603             :                        const PUTFunc& func)
     604             : {
     605          16 :     return _impl->handlePUT(endpoint, schema,
     606         102 :                             [func](const std::string&) { return func(); });
     607             : }
     608             : 
     609           6 : bool Server::handlePUT(const std::string& endpoint, const PUTPayloadFunc& func)
     610             : {
     611           6 :     return _impl->handlePUT(endpoint, "", func);
     612             : }
     613             : 
     614           4 : bool Server::handlePUT(const std::string& endpoint, const std::string& schema,
     615             :                        const PUTPayloadFunc& func)
     616             : {
     617           4 :     return _impl->handlePUT(endpoint, schema, func);
     618             : }
     619             : 
     620          30 : bool Server::handleGET(const servus::Serializable& object)
     621             : {
     622          30 :     return _impl->handleGET(_convertEndpointName(object.getTypeName()), object);
     623             : }
     624             : 
     625           0 : bool Server::handleGET(const std::string& endpoint,
     626             :                        const servus::Serializable& object)
     627             : {
     628           0 :     return _impl->handleGET(endpoint, object);
     629             : }
     630             : 
     631          16 : bool Server::handleGET(const std::string& endpoint, const GETFunc& func)
     632             : {
     633          16 :     return _impl->handleGET(endpoint, "", func);
     634             : }
     635             : 
     636          16 : bool Server::handleGET(const std::string& endpoint, const std::string& schema,
     637             :                        const GETFunc& func)
     638             : {
     639          16 :     return _impl->handleGET(endpoint, schema, func);
     640             : }
     641             : 
     642           4 : std::string Server::getSchema(const servus::Serializable& object) const
     643             : {
     644           8 :     const auto endpoint = _convertEndpointName(object.getTypeName());
     645           8 :     return _impl->getSchema(endpoint);
     646             : }
     647             : 
     648           6 : std::string Server::getSchema(const std::string& endpoint) const
     649             : {
     650           6 :     return _impl->getSchema(endpoint);
     651             : }
     652             : 
     653         184 : std::future<Response> Server::respondTo(Request& request) const
     654             : {
     655         184 :     return _impl->respondTo(request);
     656             : }
     657             : 
     658     1799436 : void Server::addSockets(std::vector<detail::Socket>& entries)
     659             : {
     660     1799436 :     _impl->addSockets(entries);
     661     1799436 : }
     662             : 
     663         208 : bool Server::process(detail::Socket&)
     664             : {
     665         208 :     Message* message = nullptr;
     666         208 :     ::zmq_recv(_impl->socket.get(), &message, sizeof(message), 0);
     667         208 :     if (!message)
     668           0 :         ZEROEQTHROW(std::runtime_error(
     669             :             "Could not receive HTTP request from HTTP server"));
     670             : 
     671         208 :     if (_isCorsPreflightRequest(*message))
     672             :     {
     673          24 :         _impl->processCorsPreflightRequest(*message);
     674             :     }
     675             :     else
     676             :     {
     677             :         try
     678             :         {
     679         184 :             message->response = respondTo(message->request);
     680             :         }
     681           4 :         catch (const std::exception& e)
     682             :         {
     683           2 :             message->response =
     684           4 :                 make_ready_response(Code::INTERNAL_SERVER_ERROR,
     685           4 :                                     std::string("Request handler exception: ") +
     686           4 :                                         e.what());
     687             :         }
     688           0 :         catch (...)
     689             :         {
     690           0 :             message->response =
     691           0 :                 make_ready_response(Code::INTERNAL_SERVER_ERROR,
     692           0 :                                     "An unknown exception occured");
     693             :         }
     694             : 
     695             :         // When a client makes a CORS request (by setting an 'Origin' header) it
     696             :         // expects an 'Access-Control-Allow-Origin' response header. See:
     697             :         // https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
     698             :         // https://www.html5rocks.com/en/tutorials/cors/
     699         184 :         if (_isCorsRequest(*message))
     700             :         {
     701          12 :             message->corsResponseHeaders = {
     702          24 :                 {CorsResponseHeader::access_control_allow_origin, "*"}};
     703             :         }
     704             :     }
     705             : 
     706         208 :     bool done = true;
     707         208 :     ::zmq_send(_impl->socket.get(), &done, sizeof(done), 0);
     708         208 :     return true;
     709             : }
     710             : }
     711          48 : }

Generated by: LCOV version 1.11