C++ (17+) için modern, non-blocking HTTP Client kütüphanesi
Github Repository: https://github.com/leventkaragol/libcpp-http-client
Amaç
C++ ekosisteminde, libcurl oldukça güçlü ve kendini kanıtlamış bir kütüphanedir. Ancak ne yazık ki libcurl'ün C-tabanlı sözdizimi, modern C++'ın sunduğu ifade gücü ve zarafetten uzaktır. Bu soruna çözüm bulmak amacıyla sarmalayıcı (wrapper) kütüphaneler mevcut olsa da, bunlar incelendiğinde genellikle bir ikilemle karşılaşılır: ya libcurl'ün zengin fonksiyonelliğinden ödün verirler ya da kullanım kolaylığı ve sezgisel bir arayüz sunmakta yetersiz kalırlar. libcpp-http-client kütüphanesini, bu boşluğu doldurmak amacıyla hazırladım. libcurl'ün kanıtlanmış gücünü, modern C++'ın temiz, okunabilir ve esnek ifade gücüyle birleştirerek geliştiricilere hem güçlü hem de kullanımı keyifli bir HTTP istemci kütüphanesi sunmaya çalıştım.
Projeye Nasıl Eklenir?
Bu header-only bir kütüphanedir. Yani aslında tek yapmanız gereken src klasöründeki libcpp-http-client.hpp dosyasını projenize eklemek ve #include ile kullanmaya başlamaktır.
Ancak bu kütüphane, arka planda Curl'ü kullanan bir wrapper olarak çalışır. Dolayısıyla, kullanmadan önce projenize Curl kütüphanesini eklemeniz gerekir.
Kullanım örneklerini Github Repository'deki "examples" klasöründe bulabilirsiniz. Aşağıda örnek bir CMakeLists.txt dosyası içeriği de bulabilirsiniz.
cmake_minimum_required(VERSION 3.14)
project(myProject)
find_package(CURL CONFIG REQUIRED)
add_executable(myProject main.cpp libcpp-http-client.hpp)
target_link_libraries(myProject PRIVATE CURL::libcurl)
Nasıl Kullanılır? (En Basit Haliyle)
Aşağıda HTTP GET yoluyla bir API'ye QueryString parametrelerini göndermenin en basit kullanım örneğini görebilirsiniz.
Dikkat!
Birden fazla çağrım yapılacaksa lütfen bu şekilde kullanmayın. Non-blocking özelliğini bu şekilde kullanamazsınız. Okumaya devam edin...
#include <fstream>
#include "libcpp-http-client.hpp"
using namespace lklibs;
int main() {
HttpRequest httpRequest("https://api.myproject.com");
// En basit ama birden fazla çağrı yapılacaksa en yavaş yöntem
auto response = httpRequest
.setQueryString("param1=7¶m2=test")
.send()
.get();
std::cout << "Succeed: " << response.succeed << std::endl;
std::cout << "Http Status Code: " << response.statusCode << std::endl;
std::cout << "Data: " << response.textData << std::endl;
return 0;
}
Non-blocking Ne Demek?
Konuyu bir örnek üzerinden ele alalım. Diyelim ki ekranımız açıldığında 5 farklı API metodu çağırıyoruz ve her biri ortalama 500 ms sürüyor. Bu metotları aşağıdaki gibi sırayla çağırırsak, tüm cevapları toplamda 2,5 saniyede alırız.
#include <fstream>
#include "libcpp-http-client.hpp"
using namespace lklibs;
int main() {
HttpRequest httpRequest1("https://api.myproject.com/foo");
HttpRequest httpRequest2("https://api.myproject.com/bar");
HttpRequest httpRequest3("https://api.myproject.com/baz");
HttpRequest httpRequest4("https://api.myproject.com/qux");
HttpRequest httpRequest5("https://api.myproject.com/quux");
auto response1 = httpRequest1.send().get();
auto response2 = httpRequest2.send().get();
auto response3 = httpRequest3.send().get();
auto response4 = httpRequest4.send().get();
auto response5 = httpRequest5.send().get();
// Toplamda 2,5 saniye sürer
return 0;
}
Her satırın sonundaki ".get()" çağrısı, sunucu yanıt verene kadar beklememize neden olur. Ancak, aynı çağrıyı aşağıdaki gibi yaparsak, tüm istekler paralel olarak işlenir ve toplam süre yaklaşık 0,5 saniye olur.
#include <fstream>
#include "libcpp-http-client.hpp"
using namespace lklibs;
int main() {
HttpRequest httpRequest1("https://api.myproject.com/foo");
HttpRequest httpRequest2("https://api.myproject.com/bar");
HttpRequest httpRequest3("https://api.myproject.com/baz");
HttpRequest httpRequest4("https://api.myproject.com/qux");
HttpRequest httpRequest5("https://api.myproject.com/quux");
auto future1 = httpRequest.send();
auto future2 = httpRequest.send();
auto future3 = httpRequest.send();
auto future4 = httpRequest.send();
auto future5 = httpRequest.send();
auto response1 = future1.get();
auto response2 = future2.get();
auto response3 = future3.get();
auto response4 = future4.get();
auto response5 = future5.get();
// Toplamda 0,5 saniye sürer
return 0;
}
Kütüphanedeki "send" fonksiyonu geriye bir future döndürür ve akışı engellemeden bir sonraki satırın çalışmasına izin verir.
Peki "Exception-Free" Ne Demek?
Exception Free, bu kütüphaneye yaptığınız herhangi bir çağrı için herhangi bir istisna oluşturulmayacağı anlamına gelir. URL bulunamazsa, zaman aşımı olursa, yetkilendirme sorunu yaşanırsa veya sunucuda başka bir hata oluşursa, yanıtın bool tipindeki "succeed" alanı false olarak geri döndürülür. Ayrıca, sunucudan döndürülen HTTP Durum Kodu değeri, int tipindeki "statusCode" alanına ve sunucudan döndürülebilecek olası ek hata bilgileri de metin tipindeki "errorMessage" alanına döndürülür.
Aşağıda örnek bir kullanım örneğini görebilirsiniz...
#include <fstream>
#include "libcpp-http-client.hpp"
using namespace lklibs;
int main() {
HttpRequest httpRequest("https://www.myinvalidurl.com");
auto response = httpRequest.send().get();
// Bir istisna fırlatmak yerine, yanıt nesnesinin başarılı alanı false olarak atanır
std::cout << "Succeed: " << response.succeed << std::endl;
// Ve http durum kodu statusCode alanına ayarlanmıştır (bu durumda 404)
std::cout << "Http Status Code: " << response.statusCode << std::endl;
// Ayrıca herhangi bir hata mesajı mevcutsa, errorMessage alanına atanır
std::cout << "Error Message: " << response.errorMessage << std::endl;
return 0;
}
Peki ya Binary Veriler?
Şimdiye kadarki örneklerde, dönen yanıt nesnesinin "textData" özelliğini kullandık. Ancak, resimler gibi binary dosyalara yapılan istekler için binary verilere ihtiyacımız var. Bu gibi durumlarda, aşağıdaki gibi talebi göndermeden önce "returnAsBinary()" metodunu çağırarak, döndürülen verilerin "textData" yerine "std::vector<unsigned char>" tipindeki "binaryData" alanında döndürülmesini sağlayabiliriz.
#include <fstream>
#include "libcpp-http-client.hpp"
using namespace lklibs;
int main() {
HttpRequest httpRequest("https://api.myproject.com/image/7");
// Resim gibi binary verileri almanız gerekiyorsa, talebi göndermeden önce "returnAsBinary" metodunu çağırmanız yeterlidir
auto response = httpRequest
.returnAsBinary()
.send()
.get();
std::cout << "Succeed: " << response.succeed << std::endl;
std::cout << "Http Status Code: " << response.statusCode << std::endl;
// Bu durumda, textData yerine binaryData aracılığıyla veriyi alabilirsiniz.
std::cout << "Data Size: " << response.binaryData.size() << std::endl;
return 0;
}
Özel HTTP Header Gönderme
Talep sırasında özel HTTP HEADER'ları göndermeniz gerekiyorsa, bunları "addHeader()" metoduyla talebe key-value çiftleri olarak ekleyebilirsiniz.
#include <fstream>
#include "libcpp-http-client.hpp"
using namespace lklibs;
int main() {
HttpRequest httpRequest("https://api.myproject.com");
// Özel başlıkları key-value çiftleri olarak gönderebilirsiniz
auto response = httpRequest
.addHeader("Custom-Header1", "value1")
.addHeader("Custom-Header2", "value2")
.send()
.get();
std::cout << "Succeed: " << response.succeed << std::endl;
return 0;
}
Form Verileriyle POST İsteği
Sırada form verilerini HTTP POST aracılığıyla göndermek var. Tek yapmanız gereken HTTP method türünü değiştirmek için "setMethod" metodunu kullanmak. Aşağıdaki örnek kodda görüldüğü gibi form verilerini "setPaylod" yöntemiyle aktarabilirsiniz.
#include <fstream>
#include "libcpp-http-client.hpp"
using namespace lklibs;
int main() {
HttpRequest httpRequest("https://api.myproject.com");
// Form verilerini yükleyerek bir POST isteği gönderebilirsiniz
auto response = httpRequest
.setMethod(HttpMethod::POST)
.setPayload("param1=7¶m2=test")
.send()
.get();
std::cout << "Succeed: " << response.succeed << std::endl;
std::cout << "Http Status Code: " << response.statusCode << std::endl;
std::cout << "Data: " << response.textData << std::endl;
return 0;
}
Peki ya Diğer Metotlar? (PUT, DELETE, PATCH)
Diğer metotların kullanımını da aşağıdaki örnek kodda bulabilirsiniz.
#include <fstream>
#include "libcpp-http-client.hpp"
using namespace lklibs;
int main() {
HttpRequest httpRequest1("https://api.myproject.com");
auto future1 = httpRequest
.setMethod(HttpMethod::PUT)
.setPayload("param1=7¶m2=test")
.send();
HttpRequest httpRequest2("https://api.myproject.com");
auto future2 = httpRequest
.setMethod(HttpMethod::DELETE_)
.setPayload("param1=7¶m2=test")
.send();
HttpRequest httpRequest3("https://api.myproject.com");
auto future3 = httpRequest
.setMethod(HttpMethod::PATCH)
.setQueryString("param1=7¶m2=test")
.send();
auto response1 = future1.get();
auto response2 = future2.get();
auto response3 = future3.get();
return 0;
}
SSL Sertifika Hataları Nasıl Görmezden Gelinir?
Herhangi geçerli bir nedenden dolayı SSL sertifika hatalarını yok saymanız gerekiyorsa, isteği göndermeden önce "ignoreSslErrors" metodunu çağırabilirsiniz.
#include <fstream>
#include "libcpp-http-client.hpp"
using namespace lklibs;
int main() {
HttpRequest httpRequest("https://api.myinvalidssl.com");
// SSL hatalarını yok saymanız gerekiyorsa, talebi göndermeden önce "ignoreSslErrors" metodunu çağırabilirsiniz
auto response = httpRequest
.ignoreSslErrors()
.send()
.get();
return 0;
}
TLS Versiyonunu Ayarlama
Talep sırasında kullanılan TLS sürümünü "setTLSVersion" metoduyla ayarlayabilirsiniz
#include <fstream>
#include "libcpp-http-client.hpp"
using namespace lklibs;
int main() {
HttpRequest httpRequest("https://api.myproject.com");
// Talep için kullanılacak TLS sürümünü "setTLSVersion" yöntemiyle ayarlayabilirsiniz
auto response = httpRequest
.setTLSVersion(TLSVersion::TLSv1_3)
.send()
.get();
return 0;
}
Timeout Değeri Nasıl Ayarlanır?
Talepler sırasında timeout süresini saniye cinsinden ayarlamak için "setTimeout" metodunu kullanabilirsiniz.
#include <fstream>
#include "libcpp-http-client.hpp"
using namespace lklibs;
int main() {
HttpRequest httpRequest("https://api.myproject.com");
// Zaman aşımını saniye cinsinden ayarlayabilirsiniz
auto response = httpRequest
.setTimeout(3) // 3 saniye
.send()
.get();
return 0;
}
User Agent'ın Belirlenmesi
"setUserAgent" metodu ile talep sırasında gönderilecek User Agent bilgisini ayarlayabilirsiniz.
#include <fstream>
#include "libcpp-http-client.hpp"
using namespace lklibs;
int main() {
HttpRequest httpRequest("https://api.myproject.com");
// "setUserAgent" metoduyla talep için kullanılacak User Agent'ı ayarlayabilirsiniz
auto response = httpRequest
.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0")
.send()
.get();
return 0;
}
Download ve Upload Bant Genişliğini Nasıl Sınırlayabilirim?
İndirme ve yükleme işlemleri sırasında bant genişliğinin belirli bir sınırı aşmasını istemiyorsanız, "setDownloadBandwidthLimit" ve "setUploadBandwidthLimit" metotlarıyla Byte cinsinden kullanılabilecek maksimum sınırı belirleyebilirsiniz.
#include <fstream>
#include "libcpp-http-client.hpp"
using namespace lklibs;
int main() {
HttpRequest httpRequest("https://api.myproject.com");
// İndirme ve yükleme bant genişliği sınırını saniye başına bayt cinsinden ayarlayabilirsiniz
auto response = httpRequest
.setDownloadBandwidthLimit(10240) // 10 KB/sn
.setUploadBandwidthLimit(20480) // 20 KB/sn
.send()
.get();
return 0;
}
İsteği curl Komutu Olarak Nasıl Alabilirim?
Eğer isteği, örneğin loglama amacıyla curl komutu olarak almak istiyorsanız gerekli hazırlıkları yaptıktan sonra toCurlCommand metodunu çağırabilirsiniz.
#include <fstream>
#include "libcpp-http-client.hpp"
using namespace lklibs;
int main() {
HttpRequest httpRequest("https://api.myproject.com");
auto request = httpRequest
.setMethod(HttpMethod::POST)
.setPayload(R"({"param1": 7, "param2": "test"})")
.addHeader("Content-Type", "application/json")
.setTimeout(3)
.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0")
.setDownloadBandwidthLimit(10240)
.setUploadBandwidthLimit(20480);
std::string curlCommand = request.toCurlCommand();
return 0;
}
Veriyi Nasıl Stream Edebilirim?
Verilerin tamamını bir kerede almak yerine, "onDataReceived" callback metodunu kullanarak verileri parçalar halinde de alabilirsiniz.
#include <fstream>
#include "libcpp-http-client.hpp"
using namespace lklibs;
int main() {
HttpRequest httpRequest("https://api.myproject.com/image/5000");
// Verileri "onDataReceived" metoduyla aktarabilirsiniz
httpRequest.onDataReceived([&](const unsigned char* chunk, const size_t dataLength)
{
std::cout << "Received chunk of size: " << dataLength << std::endl;
});
auto response = httpRequest
.returnAsBinary()
.send()
.get();
std::cout << "Succeed: " << response.succeed << std::endl;
std::cout << "Http Status Code: " << response.statusCode << std::endl;
return 0;
}
Semantik Versiyonlama
Kütüphanenin sürümlemesi, geleneksel semantik versiyonlama kullanılarak yapılır. Buna göre, MAJOR.MINOR.PATCH biçiminde yapılan sürümlemede;
PATCH: Olası hata düzeltmeleri ve iyileştirmeleri içerir. Bunu mutlaka edinmek istersiniz.
MINOR: Geriye dönük uyumluluk sayesinde eklenen ek işlevler. Muhtemelen bunu edinmek istersiniz, zararı olmaz.
MAJOR: Geriye dönük uyumluluğu bozan ek işlevler. Nelerin değiştiğini anlamanız ve muhtemelen kendi kodunuzda değişiklik yapmanız gerekecek. Böyle bir şey yayınlarsam, geçiş için gereken değişiklikleri kesinlikle dokümana eklerim.
Tam Fonksiyon Listesi
Kütüphanedeki fonksiyonların tam listesini aşağıda bulabilirsiniz. Send ve toCurlCommand hariç tüm metotlar sınıfın kendisini döndürdüğünden, bir zincir gibi birbiri ardına eklenebilirler.
İpucu!
Tüm metot ve parametre açıklamaları IDE'ler için yorum olarak kod içerisinde de mevcuttur.
HttpRequest& setMethod(const HttpMethod& method) noexcept;
HttpRequest& setQueryString(const std::string& queryString) noexcept;
HttpRequest& setPayload(const std::string& payload) noexcept;
HttpRequest& returnAsBinary() noexcept;
HttpRequest& addHeader(const std::string& key, const std::string& value) noexcept;
HttpRequest& setTimeout(const int timeout) noexcept;
HttpRequest& ignoreSslErrors() noexcept;
HttpRequest& setTLSVersion(const TLSVersion version) noexcept;
HttpRequest& setUserAgent(const std::string& userAgent) noexcept;
HttpRequest& setDownloadBandwidthLimit(const int limit) noexcept;
HttpRequest& setUploadBandwidthLimit(const int limit) noexcept;
std::string toCurlCommand() noexcept;
std::future<HttpResult> send() noexcept;
Lisans
Proje MIT lisansı ile lisanslanmıştır. Ticari kullanıma uygundur. Tam metni aşağıda bulabilirsiniz.
MIT License
Copyright (c) 2024 Levent KARAGÖL
Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies
or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
İletişim
Kütüphaneyle ilgili sorun yaşıyorsanız, lütfen GitHub'da bir konu açın. Lütfen isteğinizi, sorununuzu veya sorunuzu olabildiğince ayrıntılı bir şekilde açıklayın ve derleyicinizin, işletim sisteminizin ve kullandığınız kütüphanenin sürümünü de belirtin. Yeni bir konu açmadan önce, lütfen konunun daha önce kapatılmış konularda bulunmadığından emin olun.
Yazar: Levent KARAGÖL