FP Perception 0.1.2
Loading...
Searching...
No Matches
rest_base.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <atomic>
4#include <string>
5#include <curl/curl.h>
6#include <nlohmann/json.hpp>
7#include <rclcpp/rclcpp.hpp>
12
13namespace fp_perception
14{
22class RestBase : public virtual DriverBase
23{
24public:
31 {
32 }
33
39 virtual ~RestBase()
40 {
41 }
42
51 virtual void initialize_rest_base(const rclcpp::Node::SharedPtr& node, std::string plugin_name = "RestBase",
52 std::string api_key_name = "")
53 {
54 // Declare the plugin name parameter
55 plugin_name_ = plugin_name;
56
57 // Initialize driver base
58 initialize_base(node);
59
60 // Declare parameters
61 node_->declare_parameter(plugin_name_ + ".rest.uri", "http://localhost:8000/api/v1/perception");
62 node_->declare_parameter(plugin_name_ + ".rest.method", "POST");
63 node_->declare_parameter(plugin_name_ + ".rest.ssl_verify", true);
64 node_->declare_parameter(plugin_name_ + ".rest.auth_type", "Bearer");
65 node_->declare_parameter(plugin_name_ + ".rest.timeout_sec", 60);
66 node_->declare_parameter(plugin_name_ + ".rest.connect_timeout_sec", 10);
67
68 // Get parameters from the parameter server
69
70 uri_ = node_->get_parameter(plugin_name_ + ".rest.uri").as_string();
71 method_ = node_->get_parameter(plugin_name_ + ".rest.method").as_string();
72 ssl_verify_ = node_->get_parameter(plugin_name_ + ".rest.ssl_verify").as_bool();
73 auth_type_ = node_->get_parameter(plugin_name_ + ".rest.auth_type").as_string();
74 timeout_sec_ = node_->get_parameter(plugin_name_ + ".rest.timeout_sec").as_int();
75 connect_timeout_sec_ = node_->get_parameter(plugin_name_ + ".rest.connect_timeout_sec").as_int();
76
77 // Log the parameters
78 RCLCPP_INFO(node_->get_logger(), "Assigned driver URI: %s", uri_.c_str());
79 RCLCPP_INFO(node_->get_logger(), "Assigned driver Method: %s", method_.c_str());
80 RCLCPP_INFO(node_->get_logger(), "Assigned driver SSL Verify: %s", ssl_verify_ ? "true" : "false");
81 RCLCPP_INFO(node_->get_logger(), "Assigned driver Auth Type: %s", auth_type_.c_str());
82 RCLCPP_INFO(node_->get_logger(), "Assigned driver Timeout: %ld sec", timeout_sec_);
83 RCLCPP_INFO(node_->get_logger(), "Assigned driver Connect Timeout: %ld sec", connect_timeout_sec_);
84
85 // Load api key from environment
86 if (!api_key_name.empty())
87 {
88 const char* api_key_env = std::getenv(api_key_name.c_str());
89 if (api_key_env)
90 {
91 api_key_ = api_key_env;
92 RCLCPP_INFO(node_->get_logger(), "API key loaded from environment variables: %s", api_key_name.c_str());
93 }
94 else
95 {
96 RCLCPP_ERROR(node_->get_logger(), "missing env variable: %s", api_key_name.c_str());
97 throw fp_perception::fp_perception_exception("missing env variable: " + api_key_name);
98 api_key_ = "";
99 }
100 }
101 else
102 {
103 RCLCPP_INFO(node_->get_logger(), "An API key is not used for this plugin, using empty string");
104 api_key_ = "";
105 }
106 }
107
118 {
119 // Build JSON body from RESTRequest
120 nlohmann::json body_json = toJson(req); // <- Custom method returning nlohmann::json
121
122 std::string json_body = body_json.dump();
123 std::string response_data;
124
125 CURL* curl = curl_easy_init();
126 if (!curl)
127 {
128 record_rest_result(false, 0, "Failed to initialize libcurl");
129 throw fp_perception::fp_perception_exception("Failed to initialize libcurl");
130 }
131
132 struct curl_slist* headers = nullptr;
133 headers = curl_slist_append(headers, "Content-Type: application/json");
134
135 if (auth_type_ == "Bearer")
136 {
137 std::string auth_header = "Authorization: Bearer " + api_key_;
138 headers = curl_slist_append(headers, auth_header.c_str());
139 }
140 else
141 {
142 curl_slist_free_all(headers);
143 curl_easy_cleanup(curl);
144 record_rest_result(false, 0, "Unsupported auth type: " + auth_type_);
145 throw fp_perception::fp_perception_exception("Unsupported auth type: " + auth_type_);
146 }
147
148 curl_easy_setopt(curl, CURLOPT_URL, uri_.c_str());
149 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
150 curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_body.c_str());
151 curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, json_body.size());
152 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
153 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_data);
154 apply_timeouts(curl);
155
156 if (!ssl_verify_)
157 {
158 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
159 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
160 }
161
162 CURLcode res = curl_easy_perform(curl);
163 if (res != CURLE_OK)
164 {
165 curl_slist_free_all(headers);
166 curl_easy_cleanup(curl);
167 record_rest_result(false, 0, std::string("cURL error: ") + curl_easy_strerror(res));
168 throw fp_perception::fp_perception_exception("cURL error: " + std::string(curl_easy_strerror(res)));
169 }
170
171 long http_code = 0;
172 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
173
174 curl_slist_free_all(headers);
175 curl_easy_cleanup(curl);
176
177 if (http_code != 200)
178 {
179 const std::string msg = build_http_error_message(http_code, response_data);
180 record_rest_result(false, http_code, msg);
182 }
183
184 // Parse the JSON response
185 try
186 {
187 nlohmann::json response_json = nlohmann::json::parse(response_data);
188 auto response = fromJson(response_json); // <- Custom method converting json to RESTResponse
189 record_rest_result(true, http_code, "");
190 return response;
191 }
192 catch (const std::exception& e)
193 {
194 record_rest_result(false, http_code, std::string("JSON parse error: ") + e.what());
195 throw fp_perception::fp_perception_exception("JSON parse error: " + std::string(e.what()));
196 }
197 }
198
209 {
210 CURL* curl = curl_easy_init();
211 if (!curl)
212 {
213 record_rest_result(false, 0, "Failed to initialize libcurl");
214 throw fp_perception_exception("Failed to initialize libcurl");
215 }
216
217 curl_mime* mime = curl_mime_init(curl);
218 if (!mime)
219 {
220 curl_easy_cleanup(curl);
221 record_rest_result(false, 0, "Failed to initialize libcurl MIME form");
222 throw fp_perception_exception("Failed to initialize libcurl MIME form");
223 }
224
225 // Add options as form fields
226 for (const auto& opt : req.options)
227 {
228 curl_mimepart* option_part = curl_mime_addpart(mime);
229 if (!option_part || curl_mime_name(option_part, opt.key.c_str()) != CURLE_OK ||
230 curl_mime_data(option_part, opt.value.c_str(), CURL_ZERO_TERMINATED) != CURLE_OK)
231 {
232 curl_mime_free(mime);
233 curl_easy_cleanup(curl);
234 record_rest_result(false, 0, "Failed to build MIME field for option: " + opt.key);
235 throw fp_perception_exception("Failed to build MIME field for option: " + opt.key);
236 }
237 }
238
239 // Add audio data as a file
240 curl_mimepart* file_part = curl_mime_addpart(mime);
241 if (!file_part || curl_mime_name(file_part, "file") != CURLE_OK ||
242 curl_mime_filename(file_part, "audio.wav") != CURLE_OK ||
243 curl_mime_data(file_part, req.file_stream.data(), req.file_stream.size()) != CURLE_OK ||
244 curl_mime_type(file_part, req.file_type.c_str()) != CURLE_OK)
245 {
246 curl_mime_free(mime);
247 curl_easy_cleanup(curl);
248 record_rest_result(false, 0, "Failed to build MIME audio payload");
249 throw fp_perception_exception("Failed to build MIME audio payload");
250 }
251
252 std::string response_data;
253
254 // Set curl options
255 curl_easy_setopt(curl, CURLOPT_URL, uri_.c_str());
256 curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime);
257 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
258 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_data);
259 apply_timeouts(curl);
260
261 // Handle SSL verification
262 if (!ssl_verify_)
263 {
264 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
265 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
266 }
267
268 // Set authorization header
269 struct curl_slist* headers = nullptr;
270 if (auth_type_ == "Bearer")
271 {
272 std::string auth_header = "Authorization: Bearer " + api_key_;
273 headers = curl_slist_append(headers, auth_header.c_str());
274 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
275 }
276 else
277 {
278 curl_mime_free(mime);
279 curl_easy_cleanup(curl);
280 record_rest_result(false, 0, "Unsupported auth type: " + auth_type_);
281 throw fp_perception_exception("Unsupported auth type: " + auth_type_);
282 }
283
284 // Perform request
285 CURLcode res = curl_easy_perform(curl);
286
287 // Get HTTP response code
288 long http_code = 0;
289 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
290
291 // Cleanup
292 curl_slist_free_all(headers);
293 curl_mime_free(mime);
294 curl_easy_cleanup(curl);
295
296 // Error handling
297 if (res != CURLE_OK)
298 {
299 record_rest_result(false, http_code, std::string("cURL error: ") + curl_easy_strerror(res));
300 throw fp_perception_exception("cURL error: " + std::string(curl_easy_strerror(res)));
301 }
302
303 if (http_code != 200)
304 {
305 const std::string msg = build_http_error_message(http_code, response_data);
306 record_rest_result(false, http_code, msg);
307 throw fp_perception_exception(msg);
308 }
309
310 // Parse and return response
311 try
312 {
313 nlohmann::json json = nlohmann::json::parse(response_data);
314 auto response = fromJson(json); // Assumes fromJson(const nlohmann::json&) is implemented
315 record_rest_result(true, http_code, "");
316 return response;
317 }
318 catch (const std::exception& e)
319 {
320 record_rest_result(false, http_code, std::string("JSON parse error: ") + e.what());
321 throw fp_perception_exception("JSON parse error: " + std::string(e.what()));
322 }
323 }
324
326 {
327 CURL* curl = curl_easy_init();
328 if (!curl)
329 {
330 record_rest_result(false, 0, "Failed to initialize libcurl");
331 throw fp_perception_exception("Failed to initialize libcurl");
332 }
333
334 // Prepare the JSON body
335 nlohmann::json json_body;
336 for (const auto& opt : req.options)
337 {
338 json_body[opt.key] = opt.value;
339 }
340 json_body["input"] = req.prompt; // Set input text explicitly
341
342 std::string body = json_body.dump();
343 std::vector<uint8_t> response_binary;
344
345 // Set headers
346 struct curl_slist* headers = nullptr;
347 headers = curl_slist_append(headers, "Content-Type: application/json");
348 if (auth_type_ == "Bearer")
349 {
350 std::string auth_header = "Authorization: Bearer " + api_key_;
351 headers = curl_slist_append(headers, auth_header.c_str());
352 }
353 else
354 {
355 curl_easy_cleanup(curl);
356 curl_slist_free_all(headers);
357 record_rest_result(false, 0, "Unsupported auth type: " + auth_type_);
358 throw fp_perception_exception("Unsupported auth type: " + auth_type_);
359 }
360
361 curl_easy_setopt(curl, CURLOPT_URL, uri_.c_str());
362 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
363 curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
364 curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, body.size());
365 apply_timeouts(curl);
366
367 // Write binary audio data to vector
368 curl_easy_setopt(
369 curl, CURLOPT_WRITEFUNCTION, +[](void* ptr, size_t size, size_t nmemb, void* userdata) -> size_t {
370 auto* vec = reinterpret_cast<std::vector<uint8_t>*>(userdata);
371 size_t total_size = size * nmemb;
372 vec->insert(vec->end(), (uint8_t*)ptr, (uint8_t*)ptr + total_size);
373 return total_size;
374 });
375 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_binary);
376
377 if (!ssl_verify_)
378 {
379 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
380 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
381 }
382
383 // Perform request
384 CURLcode res = curl_easy_perform(curl);
385 long http_code = 0;
386 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
387
388 curl_slist_free_all(headers);
389 curl_easy_cleanup(curl);
390
391 if (res != CURLE_OK)
392 {
393 record_rest_result(false, http_code, std::string("cURL error: ") + curl_easy_strerror(res));
394 throw fp_perception_exception("cURL error: " + std::string(curl_easy_strerror(res)));
395 }
396
397 if (http_code != 200)
398 {
399 const std::string response_text(response_binary.begin(), response_binary.end());
400 const std::string msg = build_http_error_message(http_code, response_text);
401 record_rest_result(false, http_code, msg);
402 throw fp_perception_exception(msg);
403 }
404
405 // Convert raw PCM to int16_t
406 std::vector<int16_t> samples(response_binary.size() / 2);
407 std::memcpy(samples.data(), response_binary.data(), response_binary.size());
408
410 response.audio_stream = samples; // Assuming audio_stream is a vector<int16_t>
411 record_rest_result(true, http_code, "");
412
413 return response;
414 }
415
416protected:
417 static std::string build_http_error_message(long http_code, const std::string& response_data)
418 {
419 std::string details;
420
421 try
422 {
423 const auto err_json = nlohmann::json::parse(response_data);
424 if (err_json.contains("error"))
425 {
426 const auto& err = err_json["error"];
427 if (err.is_object() && err.contains("message"))
428 {
429 if (err["message"].is_string())
430 details = err["message"].get<std::string>();
431 else
432 details = err["message"].dump();
433 }
434 else
435 {
436 details = err.dump();
437 }
438 }
439 }
440 catch (...) {}
441
442 if (details.empty() && !response_data.empty())
443 {
444 constexpr size_t kMaxLen = 1024;
445 details = response_data.substr(0, std::min(kMaxLen, response_data.size()));
446 }
447
448 std::string msg = "HTTP error: " + std::to_string(http_code);
449 if (!details.empty())
450 msg += " - " + details;
451
452 return msg;
453 }
454
455 void apply_timeouts(CURL* curl)
456 {
457 if (connect_timeout_sec_ > 0)
458 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, connect_timeout_sec_);
459
460 if (timeout_sec_ > 0)
461 curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout_sec_);
462 }
463
464 void record_rest_result(bool success, long http_code, const std::string& error)
465 {
467 if (!success)
469
470 last_rest_success_.store(success);
471 last_rest_http_code_.store(http_code);
472
473 std::lock_guard<std::mutex> lock(rest_status_mutex_);
474 last_rest_error_ = error;
475 }
476
477 static size_t write_callback(void* contents, size_t size, size_t nmemb, std::string* userp)
478 {
479 userp->append(static_cast<char*>(contents), size * nmemb);
480 return size * nmemb;
481 }
482
492 virtual nlohmann::json toJson(const fp_perception::RESTRequest& request) = 0;
493
503 virtual fp_perception::RESTResponse fromJson(const nlohmann::json& object) = 0;
504
505 std::string plugin_name_; // Name of the plugin, used for parameter names
506 std::string uri_; // URI for the REST API
507 std::string method_; // HTTP method (GET, POST, etc.)
508 bool ssl_verify_; // Flag for SSL verification
509 std::string auth_type_; // Type of authentication (e.g., Bearer)
510 std::string api_key_; // API key for authentication
511 long timeout_sec_{ 60 };
513 std::atomic<uint64_t> rest_request_count_{ 0 };
514 std::atomic<uint64_t> rest_failure_count_{ 0 };
515 std::atomic<bool> last_rest_success_{ true };
516 std::atomic<long> last_rest_http_code_{ 0 };
518 std::string last_rest_error_;
519};
520
521} // namespace fp_perception
Definition driver_base.hpp:19
rclcpp::Node::SharedPtr node_
ROS node for the driver.
Definition driver_base.hpp:138
void initialize_base(const rclcpp::Node::SharedPtr &node)
Initializer base driver in place of constructor due to plugin semantics.
Definition driver_base.hpp:95
RestBase.
Definition rest_base.hpp:23
long connect_timeout_sec_
Definition rest_base.hpp:512
std::string method_
Definition rest_base.hpp:507
void apply_timeouts(CURL *curl)
Definition rest_base.hpp:455
virtual fp_perception::RESTResponse call(const fp_perception::RESTRequest &req)
Request data from the REST API.
Definition rest_base.hpp:117
std::atomic< bool > last_rest_success_
Definition rest_base.hpp:515
std::string last_rest_error_
Definition rest_base.hpp:518
static std::string build_http_error_message(long http_code, const std::string &response_data)
Definition rest_base.hpp:417
std::mutex rest_status_mutex_
Definition rest_base.hpp:517
void record_rest_result(bool success, long http_code, const std::string &error)
Definition rest_base.hpp:464
std::string plugin_name_
Definition rest_base.hpp:505
std::atomic< uint64_t > rest_request_count_
Definition rest_base.hpp:513
virtual fp_perception::RESTResponse fromJson(const nlohmann::json &object)=0
Convert a JSON object to a fp_perception response.
std::atomic< long > last_rest_http_code_
Definition rest_base.hpp:516
std::string uri_
Definition rest_base.hpp:506
virtual nlohmann::json toJson(const fp_perception::RESTRequest &request)=0
Convert a prompt request to a JSON object.
virtual fp_perception::RESTResponse call_tts(const fp_perception::RESTRequest &req)
Definition rest_base.hpp:325
bool ssl_verify_
Definition rest_base.hpp:508
RestBase()
Constructor.
Definition rest_base.hpp:30
virtual fp_perception::RESTResponse call_audio(const fp_perception::RESTRequest &req)
Request audio data from the REST API.
Definition rest_base.hpp:208
std::atomic< uint64_t > rest_failure_count_
Definition rest_base.hpp:514
std::string api_key_
Definition rest_base.hpp:510
virtual void initialize_rest_base(const rclcpp::Node::SharedPtr &node, std::string plugin_name="RestBase", std::string api_key_name="")
Initialize the REST base class.
Definition rest_base.hpp:51
std::string auth_type_
Definition rest_base.hpp:509
static size_t write_callback(void *contents, size_t size, size_t nmemb, std::string *userp)
Definition rest_base.hpp:477
virtual ~RestBase()
Destructor.
Definition rest_base.hpp:39
long timeout_sec_
Definition rest_base.hpp:511
Definition audio_buffer.hpp:16
Definition structs.hpp:26
std::string prompt
Definition structs.hpp:27
std::string file_type
Definition structs.hpp:28
std::vector< RESTOption > options
Definition structs.hpp:30
std::vector< char > file_stream
Definition structs.hpp:29
Definition structs.hpp:35
std::vector< int16_t > audio_stream
Definition structs.hpp:38
Base class for driver exceptions.
Definition exceptions.hpp:14