3 ==================================================================================
4 Copyright (c) 2022 Alexandre Huff
6 Licensed under the Apache License, Version 2.0 (the "License");
7 you may not use this file except in compliance with the License.
8 You may obtain a copy of the License at
10 http://www.apache.org/licenses/LICENSE-2.0
12 Unless required by applicable law or agreed to in writing, software
13 distributed under the License is distributed on an "AS IS" BASIS,
14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 See the License for the specific language governing permissions and
16 limitations under the License.
17 ==================================================================================
21 Mnemonic: restclient.cpp
22 Abstract: Implements a tiny wrapper on top of libcurl to handle with rest requests.
25 Author: Alexandre Huff
29 #include "restclient.hpp"
35 namespace restclient {
37 // callback to handle http responses
38 static size_t http_response_callback( const char *in, size_t size, size_t num, std::string *out ) {
42 const size_t totalBytes( size * num );
43 out->append( in, totalBytes );
48 Create a RestClient instance to exchange messages with
49 a given rest api available on baseUrl, which consists of
50 scheme://domain[:port]
52 RestClient::RestClient( std::string baseUrl ) {
53 this->baseUrl = baseUrl;
58 RestClient::~RestClient( ) {
59 curl_slist_free_all( headers );
60 curl_easy_cleanup( curl );
63 std::string RestClient::getBaseUrl( ) {
67 void RestClient::init( ) {
68 static std::mutex curl_mutex;
70 { // scoped mutex to make curl_global_init thread-safe
71 const std::lock_guard<std::mutex> lock( curl_mutex );
72 CURLcode code = curl_global_init( CURL_GLOBAL_DEFAULT );
75 ss << "curl_global_init returned error code " << code << ", unable to proceed";
76 std::string s = std::to_string(code);
77 throw RestClientException( ss.str() );
82 curl = curl_easy_init();
84 throw RestClientException( "CURL did not return a handler, unable to proceed" );
87 // curl_easy_setopt( curl, CURLOPT_VERBOSE, 1L );
88 if( curl_easy_setopt( curl, CURLOPT_TIMEOUT, 5 ) != CURLE_OK ) {
89 throw RestClientException( "unable to set CURLOPT_TIMEOUT" );
92 /* provide a buffer to store errors in */
93 if( curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf) != CURLE_OK ) {
94 throw RestClientException( "unable to set CURLOPT_ERRORBUFFER" );
98 if( curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, http_response_callback ) != CURLE_OK ) {
99 throw RestClientException( "unable to set CURLOPT_WRITEFUNCTION" );
102 headers = curl_slist_append( headers, "Accept: application/json" );
103 headers = curl_slist_append( headers, "Content-Type: application/json" );
104 if( curl_easy_setopt( curl, CURLOPT_HTTPHEADER, headers ) != CURLE_OK ) {
105 throw RestClientException( "unable to set CURLOPT_HTTPHEADER" );
108 } catch( const RestClientException &e ) { // avoid memory leakage
109 if( headers != NULL ) {
110 curl_slist_free_all( headers );
113 curl_easy_cleanup( curl );
116 std::stringstream ss;
117 ss << "Failed to initialize RestClient: " << e.what();
118 throw RestClientException( ss.str() );
124 Executes a GET request at the path of this RestClient instance.
125 Returns the HTTP status code and the correspoding message body.
127 response_t RestClient::do_get( std::string path ) {
128 response_t response = { 0, "" };
130 const std::string endpoint = baseUrl + path;
132 if( curl_easy_setopt( curl, CURLOPT_WRITEDATA, &response.body ) != CURLE_OK ) {
133 throw RestClientException( "unable to set CURLOPT_WRITEDATA" );
136 if( curl_easy_setopt( curl, CURLOPT_URL, endpoint.c_str() ) != CURLE_OK ) {
137 throw RestClientException( "unable to set CURLOPT_URL" );
139 if( curl_easy_setopt( curl, CURLOPT_HTTPGET, 1L ) != CURLE_OK ) {
140 throw RestClientException( "unable to set CURLOPT_HTTPGET" );
143 CURLcode res = curl_easy_perform( curl );
144 if( res == CURLE_OK ) {
145 if( curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &response.status_code ) != CURLE_OK ) {
146 throw RestClientException( std::string("unable to get CURLINFO_RESPONSE_CODE. ") + errbuf );
149 size_t len = strlen( errbuf );
150 std::stringstream ss;
151 ss << "unable to complete the request at " << endpoint.c_str();
153 ss << ". " << errbuf;
155 ss << ". " << curl_easy_strerror( res );
157 throw RestClientException( ss.str() );
164 Executes a POST request of a json message at the path of this RestClient instance.
165 Returns the HTTP status code and the correspoding message body.
167 response_t RestClient::do_post( std::string path, std::string json ) {
168 response_t response = { 0, "" };
170 const std::string endpoint = baseUrl + path;
172 if( curl_easy_setopt( curl, CURLOPT_WRITEDATA, &response.body ) != CURLE_OK ) {
173 throw RestClientException( "unable to set CURLOPT_WRITEDATA" );
176 if( curl_easy_setopt( curl, CURLOPT_URL, endpoint.c_str() ) != CURLE_OK ) {
177 throw RestClientException( "unable to set CURLOPT_URL" );
179 if( curl_easy_setopt( curl, CURLOPT_POST, 1L ) != CURLE_OK ) {
180 throw RestClientException( "unable to set CURLOPT_POST" );
182 if( curl_easy_setopt( curl, CURLOPT_POSTFIELDS, json.c_str() ) != CURLE_OK ) {
183 throw RestClientException( "unable to set CURLOPT_POSTFIELDS" );
186 CURLcode res = curl_easy_perform( curl );
187 if( res == CURLE_OK ) {
188 if( curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &response.status_code ) != CURLE_OK ) {
189 throw RestClientException( std::string("unable to get CURLINFO_RESPONSE_CODE. ") + errbuf );
192 size_t len = strlen( errbuf );
193 std::stringstream ss;
194 ss << "unable to complete the request at " << endpoint.c_str();
196 ss << ". " << errbuf;
198 ss << ". " << curl_easy_strerror( res );
200 throw RestClientException( ss.str() );