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 : }
|