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