LCOV - code coverage report
Current view: top level - zeroeq/http - requestHandler.cpp (source / functions) Hit Total Coverage
Test: ZeroEQ Lines: 102 115 88.7 %
Date: 2017-12-01 01:44:57 Functions: 16 16 100.0 %

          Line data    Source code
       1             : 
       2             : /* Copyright (c) 2016-2017, Human Brain Project
       3             :  *                          Stefan.Eilemann@epfl.ch
       4             :  *                          Daniel.Nachbaur@epfl.ch
       5             :  *                          Raphael.Dumusc@epfl.ch
       6             :  */
       7             : 
       8             : #include "requestHandler.h"
       9             : 
      10             : #include <zeroeq/log.h>
      11             : #include <zeroeq/uri.h>
      12             : 
      13             : #include <zmq.h>
      14             : 
      15             : #include <future>
      16             : #include <memory> // shared_from_this
      17             : #include <stdexcept>
      18             : 
      19             : namespace zeroeq
      20             : {
      21             : namespace http
      22             : {
      23             : namespace
      24             : {
      25          90 : int _getContentLength(const HTTPServer::request& request)
      26             : {
      27         420 :     for (const auto& i : request.headers)
      28             :     {
      29         362 :         if (i.name == "Content-Length")
      30          32 :             return std::stoi(i.value);
      31             :     }
      32          58 :     return 0;
      33             : }
      34             : 
      35         232 : Method _getMethodType(const std::string& methodName)
      36             : {
      37         232 :     if (methodName == "GET")
      38         122 :         return Method::GET;
      39         110 :     if (methodName == "POST")
      40          14 :         return Method::POST;
      41          96 :     if (methodName == "PUT")
      42          36 :         return Method::PUT;
      43          60 :     if (methodName == "PATCH")
      44          12 :         return Method::PATCH;
      45          48 :     if (methodName == "DELETE")
      46          12 :         return Method::DELETE;
      47          36 :     if (methodName == "OPTIONS")
      48          36 :         return Method::OPTIONS;
      49           0 :     throw std::invalid_argument("Method not supported");
      50             : }
      51             : 
      52         118 : std::string _headerEnumToString(const Header header)
      53             : {
      54         118 :     switch (header)
      55             :     {
      56             :     case Header::ALLOW:
      57          26 :         return "Allow";
      58             :     case Header::CONTENT_TYPE:
      59          56 :         return "Content-Type";
      60             :     case Header::LAST_MODIFIED:
      61          12 :         return "Last-Modified";
      62             :     case Header::LOCATION:
      63          12 :         return "Location";
      64             :     case Header::RETRY_AFTER:
      65          12 :         return "Retry-After";
      66             :     default:
      67           0 :         throw std::logic_error("no such header");
      68             :     }
      69             : }
      70             : 
      71          30 : std::string _headerEnumToString(const CorsResponseHeader header)
      72             : {
      73          30 :     switch (header)
      74             :     {
      75             :     case CorsResponseHeader::access_control_allow_headers:
      76           6 :         return "Access-Control-Allow-Headers";
      77             :     case CorsResponseHeader::access_control_allow_methods:
      78           6 :         return "Access-Control-Allow-Methods";
      79             :     case CorsResponseHeader::access_control_allow_origin:
      80          18 :         return "Access-Control-Allow-Origin";
      81             :     default:
      82           0 :         throw std::logic_error("no such header");
      83             :     }
      84             : }
      85             : 
      86             : // The actual handler for each incoming request where the data is read from
      87             : // a dedicated connection to the client.
      88         208 : struct ConnectionHandler : std::enable_shared_from_this<ConnectionHandler>
      89             : {
      90         208 :     ConnectionHandler(const HTTPServer::request& request, void* socket)
      91         208 :         : _request(request)
      92         208 :         , _socket(socket)
      93             :     {
      94         208 :     }
      95             : 
      96         208 :     void operator()(HTTPServer::connection_ptr connection)
      97             :     {
      98             :         try
      99             :         {
     100         208 :             const auto method = _getMethodType(_request.method);
     101         208 :             if (method != Method::GET)
     102             :             {
     103          90 :                 _size = _getContentLength(_request);
     104             :                 // if we have payload, schedule an (async) read of all chunks.
     105             :                 // Will call _handleRequest() after all data has been read.
     106          90 :                 if (_size > 0)
     107             :                 {
     108          32 :                     _readChunk(connection, method);
     109          32 :                     return;
     110             :                 }
     111             :             }
     112         176 :             _handleRequest(method, connection);
     113             :         }
     114           0 :         catch (const std::invalid_argument&)
     115             :         {
     116           0 :             connection->set_status(HTTPServer::connection::not_supported);
     117             :         }
     118             :     }
     119             : 
     120             : private:
     121          32 :     void _readChunk(HTTPServer::connection_ptr connection, const Method method)
     122             :     {
     123             :         namespace pl = std::placeholders;
     124          64 :         connection->read(std::bind(&ConnectionHandler::_handleChunk,
     125          64 :                                    ConnectionHandler::shared_from_this(),
     126          32 :                                    pl::_1, pl::_2, pl::_3, connection, method));
     127          32 :     }
     128             : 
     129          32 :     void _handleChunk(HTTPServer::connection::input_range range,
     130             :                       const boost::system::error_code error, const size_t size,
     131             :                       HTTPServer::connection_ptr connection,
     132             :                       const Method method_)
     133             :     {
     134          32 :         if (error)
     135             :         {
     136             :             ZEROEQERROR << "Error during ConnectionHandler::_handleChunk: "
     137           0 :                         << error.message() << std::endl;
     138           0 :             return;
     139             :         }
     140             : 
     141          32 :         _body.append(&range[0], size);
     142          32 :         _size -= size;
     143          32 :         if (_size > 0)
     144           0 :             _readChunk(connection, method_);
     145             :         else
     146          32 :             _handleRequest(method_, connection);
     147             :     }
     148             : 
     149         208 :     void _handleRequest(const Method method,
     150             :                         HTTPServer::connection_ptr connection)
     151             :     {
     152         416 :         Message message;
     153         208 :         message.request.method = method;
     154         208 :         message.request.source = _request.source;
     155         416 :         const auto uri = URI(_request.destination);
     156         208 :         message.request.path = uri.getPath();
     157         208 :         message.request.query = uri.getQuery();
     158         208 :         message.request.body.swap(_body);
     159         208 :         _parseCorsRequestHeaders(message);
     160             : 
     161         208 :         void* messagePtr = &message;
     162         208 :         zmq_send(_socket, &messagePtr, sizeof(void*), 0);
     163             :         bool done;
     164         208 :         zmq_recv(_socket, &done, sizeof(done), 0);
     165             : 
     166         416 :         Response response;
     167             :         try
     168             :         {
     169         208 :             response = message.response.get();
     170             :         }
     171           0 :         catch (std::future_error& error)
     172             :         {
     173           0 :             response.code = http::Code::INTERNAL_SERVER_ERROR;
     174             :             ZEROEQINFO << "Error during ConnectionHandler::_handleRequest: "
     175           0 :                        << error.what() << std::endl;
     176             :         }
     177             : 
     178         416 :         std::vector<HTTPServer::response_header> headers;
     179         416 :         headers.push_back(
     180         208 :             {"Content-Length", std::to_string(response.body.length())});
     181             : 
     182         238 :         for (const auto& it : message.corsResponseHeaders)
     183          30 :             headers.push_back({_headerEnumToString(it.first), it.second});
     184             : 
     185         326 :         for (const auto& it : response.headers)
     186         118 :             headers.push_back({_headerEnumToString(it.first), it.second});
     187             : 
     188         208 :         const auto status = HTTPServer::connection::status_t(response.code);
     189         208 :         connection->set_status(status);
     190         208 :         connection->set_headers(headers);
     191         208 :         connection->write(response.body);
     192         208 :     }
     193             : 
     194         208 :     void _parseCorsRequestHeaders(Message& message)
     195             :     {
     196        1206 :         for (const auto& header : _request.headers)
     197             :         {
     198         998 :             if (header.name == "Origin")
     199          36 :                 message.origin = header.value;
     200         962 :             else if (header.name == "Access-Control-Request-Headers")
     201          24 :                 message.accessControlRequestHeaders = header.value;
     202         938 :             else if (header.name == "Access-Control-Request-Method")
     203             :             {
     204             :                 try
     205             :                 {
     206          24 :                     message.accessControlRequestMethod =
     207          24 :                         _getMethodType(header.value);
     208             :                 }
     209           0 :                 catch (const std::invalid_argument&)
     210             :                 {
     211             :                 }
     212             :             }
     213             :         }
     214         208 :     }
     215             : 
     216             :     const HTTPServer::request& _request;
     217             :     void* _socket;
     218             :     std::string _body;
     219             :     int _size = 0;
     220             : };
     221             : } // anonymous namespace
     222             : 
     223          72 : RequestHandler::RequestHandler(const std::string& zmqURL)
     224             :     : _context(detail::getContext())
     225          72 :     , _socket(zmq_socket(_context.get(), ZMQ_PAIR))
     226             : {
     227          72 :     if (zmq_connect(_socket, zmqURL.c_str()) == -1)
     228             :     {
     229           0 :         ZEROEQTHROW(std::runtime_error(
     230             :             "Cannot connect RequestHandler to inproc socket"));
     231             :     }
     232          72 : }
     233             : 
     234         144 : RequestHandler::~RequestHandler()
     235             : {
     236          72 :     zmq_close(_socket);
     237          72 : }
     238             : 
     239         208 : void RequestHandler::operator()(const HTTPServer::request& request,
     240             :                                 HTTPServer::connection_ptr connection)
     241             : {
     242             :     // as the underlying cppnetlib http server is asynchronous and payload for
     243             :     // PUT events has to be read in chunks in the cppnetlib thread, create
     244             :     // a shared instance of the handler object that is passed to cppnetlib for
     245             :     // processing the request.
     246             :     std::shared_ptr<ConnectionHandler> connectionHandler(
     247         416 :         new ConnectionHandler(request, _socket));
     248         208 :     (*connectionHandler)(connection);
     249         208 : }
     250             : }
     251          48 : }

Generated by: LCOV version 1.11