DEV Community

Pierre DEL PERUGIA
Pierre DEL PERUGIA

Posted on • Edited on

Introducing curlev: a highly-efficient asynchronous HTTP client

I recently faced a significant challenge: making a large number of asynchronous HTTP calls to various servers, without waiting for responses but storing the data as it arrived. These requests originated from different parts of the program and were posted at various times.

After evaluating several libraries (Async++ CURL, cpr, curl-multi-asio, curlcpp, restclient-cpp...), I found that none fully met my requirements. While cpr offered the best performance, its CPU usage was too high for me.

So, I developed my own solution: curlev. It's built upon libcurl's multi-interface and libuv's asynchronous I/O capabilities. curlev achieves performance very close to cpr, but with four times less CPU consumption (and half as much memory).

Config Time CPU RSS s·CPU·MB
curlev 2.162 s 109% 14'580 KB 34
cpr 1.722 s 501% 22'016 KB 185
asyncpp-curl 4.101 s 126% 335'744 KB 1694
curlcpp 45.347 s 99% 625'204 KB 27410
curl-multi-asio 100.102 s 99% 538'164 KB 52083
liblifthttp 104.029 s 99% 528'128 KB 53116

curlev offers three modes:

A synchronous mode:

 auto http = HTTP::create( global::async ); auto code = http->GET( "https://api.example.com/get" ) .exec() .get_code(); 
Enter fullscreen mode Exit fullscreen mode

An asynchronous mode (also available with std::future):

 auto http = HTTP::create( global::async ); http->GET( "https://api.example.com/get" ) .start(); ... ... auto code = http->join().get_code(); 
Enter fullscreen mode Exit fullscreen mode

A detached mode, with callback:

 { HTTP::create( global::async ) ->GET( "https://api.example.com/get" ) .start( []( const auto & http ) { auto code = http.get_code(); } ); } 
Enter fullscreen mode Exit fullscreen mode

A std::shared_ptr is returned by the factory function HTTP::create, to ensure that the connection stays alive during the operation, even if the returned value goes out of scope.

It also provides:

  • all standard HTTP methods (GET, POST, PUT, PATCH, DELETE)
  • query parameters, form data, MIME handling, and raw bodies
  • custom headers and authentication
  • received headers and body

Here are 3 examples inspired from real usages:

auto http = HTTP::create( m_async ); http->POST( opr_callback_url ) .add_headers( { { "X-Response-ID", response_id } } ) .start( [ reference_id ]( const auto & http ) { if ( http.get_code() == 200 ) ack_notification( reference_id ); else log_warning( "opr cb failed: " + std::to_string( http.get_code() ) + ", ref=" + reference_id ); } ); 
Enter fullscreen mode Exit fullscreen mode
auto http = HTTP::create( m_async ); auto code = http->POST( m_opr_server + "/user/notify" + user_id ) { { "message_id", "account.created" }, { "data" , data } } ) .exec() .get_code(); // if ( code == 200 && http->get_content_type() == "application/json" ) json = http->get_body(); else return false; 
Enter fullscreen mode Exit fullscreen mode
auto http = HTTP::create( m_async ); auto code = http->GET( m_opr_server + "/user/" + user_id ) .authentication( "mode=bearer,secret=" + token ) .exec() .get_code(); 
Enter fullscreen mode Exit fullscreen mode

It doesn't provide (yet) JSON helper to check and parse the response.

You can find the project on GitHub here github.

Feel free to check it out and let me know your thoughts.

Top comments (0)