Line data Source code
1 : /**
2 : * @file RestEndpoint.cpp
3 : *
4 : * This is part of the DUNE DAQ Application Framework, copyright 2020.
5 : * Licensing/copyright details are in the COPYING file that you should have
6 : * received with this code.
7 : */
8 : #include "restcmd/RestEndpoint.hpp"
9 :
10 : #include "logging/Logging.hpp"
11 :
12 : #include <chrono>
13 : #include <future>
14 : #include <sstream>
15 : #include <string>
16 : #include <utility>
17 :
18 : using namespace dunedaq::restcmd;
19 : using namespace Pistache;
20 :
21 : void
22 0 : RestEndpoint::init(size_t threads)
23 : {
24 0 : auto opts = Http::Endpoint::options()
25 0 : .threads(static_cast<int>(threads))
26 0 : .maxRequestSize(15728640) // 15MB
27 0 : .maxResponseSize(1048576) // 1MB
28 0 : .flags(Pistache::Tcp::Options::ReuseAddr)
29 0 : .flags(Pistache::Tcp::Options::ReusePort);
30 :
31 0 : http_endpoint_->init(opts);
32 0 : createRouting();
33 0 : http_client_options_ = Http::Client::options().threads(static_cast<int>(threads));
34 0 : http_client_->init(http_client_options_);
35 0 : }
36 :
37 : void
38 0 : RestEndpoint::start()
39 : {
40 0 : http_endpoint_->setHandler(router_.handler());
41 0 : http_endpoint_->serveThreaded();
42 0 : port_ = http_endpoint_->getPort();
43 0 : TLOG() << "REST server started on port " << port_;
44 0 : }
45 :
46 : // void RestEndpoint::serveTask()
47 : // {
48 : // }
49 :
50 : void
51 0 : RestEndpoint::shutdown()
52 : {
53 0 : http_endpoint_->shutdown();
54 : // server_thread_.join();
55 0 : http_client_->shutdown();
56 0 : }
57 :
58 : void
59 0 : RestEndpoint::createRouting()
60 : {
61 0 : using namespace Rest;
62 0 : Routes::Post(router_, "/command", Routes::bind(&RestEndpoint::handle_route_command, this));
63 0 : }
64 :
65 : inline void
66 : extendHeader(Http::Header::Collection& headers)
67 : {
68 : headers.add<Http::Header::AccessControlAllowOrigin>("*");
69 : headers.add<Http::Header::AccessControlAllowMethods>("POST,GET");
70 : headers.add<Http::Header::ContentType>(MIME(Text, Plain));
71 : }
72 :
73 : inline std::string
74 : getClientAddress(const Pistache::Rest::Request& request)
75 : {
76 : const auto xff = request.headers().tryGetRaw("X-Forwarded-For");
77 : if (!xff.isEmpty()) {
78 : // TODO: Strip off after first comma (to handle chained proxies). // NOLINT
79 : return xff.get().value();
80 : }
81 : return request.address().host();
82 : }
83 :
84 : void
85 0 : RestEndpoint::handle_route_command(const Rest::Request& request, Http::ResponseWriter response)
86 : {
87 0 : dunedaq::cmdlib::cmd::CommandReply meta;
88 0 : auto addr = request.address();
89 0 : auto headers = request.headers();
90 0 : auto ct = headers.get<Http::Header::ContentType>();
91 0 : if (ct->mime() != accepted_mime_) {
92 0 : auto res = response.send(Http::Code::Not_Acceptable, "Not a JSON command!\n");
93 0 : } else {
94 0 : auto ansport = headers.getRaw("X-Answer-Port"); // RS: FIXME reply using headers
95 0 : auto anshost = headers.tryGetRaw("X-Answer-Host"); // RS: FIXME reply using headers
96 0 : meta.data["ans-port"] = ansport.value();
97 0 : meta.data["ans-host"] = (!anshost.isEmpty() ? anshost.get().value() : addr.host());
98 0 : command_callback_(nlohmann::json::parse(request.body()), meta); // RS: FIXME parse errors
99 0 : auto res = response.send(Http::Code::Accepted, "Command received\n");
100 0 : }
101 0 : }
102 :
103 : void
104 0 : RestEndpoint::handleResponseCommand(const cmdobj_t& cmd, dunedaq::cmdlib::cmd::CommandReply& meta)
105 : {
106 0 : dunedaq::cmdlib::cmd::Command command = cmd.get<dunedaq::cmdlib::cmd::Command>();
107 0 : std::ostringstream addrstr;
108 0 : addrstr << meta.data["ans-host"].get<std::string>() << ":" << meta.data["ans-port"].get<std::string>() << "/response";
109 0 : meta.data["cmdid"] = command.id;
110 0 : TLOG() << "Sending POST request to " << addrstr.str();
111 :
112 0 : nlohmann::json body_json;
113 0 : dunedaq::cmdlib::cmd::to_json(body_json, meta);
114 0 : auto response = http_client_->post(addrstr.str()).body(body_json.dump()).send();
115 0 : response.then([&](Http::Response response) { TLOG() << "Response code = " << response.code(); },
116 0 : [&](std::exception_ptr exc) {
117 : // handle response failure
118 0 : try {
119 0 : std::rethrow_exception(exc);
120 0 : } catch (const std::exception& e) {
121 0 : TLOG() << "Exception thrown by Http::Client::post() call: \"" << e.what()
122 0 : << "\"; errno = " << errno;
123 0 : }
124 0 : });
125 0 : http_client_responses_.push_back(std::move(response));
126 0 : }
|