(1)에서 libcurl 정적 라이브러리를 이용하여 실제로 공개되어 있는 API를 사용해보도록 한다.
블록체인 코인 시세 조회하기
https://www.blockchain.com/explorer
위 사이트에서 제공하는 API를 사용해서 실시간 거래 시세를 조회해보도록 한다.
관련 API를 조회해보면 인증 없이 사용될 수 있는 API 중에 /tickers 관련 API를 사용하면 모든 코인 혹은 {symbol}에 해당하는 거래 시세를 조회가능할 것으로 보인다.
Authentication 인증을 요구하는 API의 경우 계정을 생성하여 API 키를 발급받아 사용해야하며 계좌와 연동되어 실제 거래를 가능하게하는 API를 사용하므로 높은 보안을 요구한다.
API URL 주소 (서버 주소)
예시로 사용할 API는 인증 없이 GET 요청만으로도 조회가능하다.
특이사항으로는 HTTP Request의 헤더는 "Accept: application/json"을 담아야한다.
응답으로는 JSON 형태로 symbol, price_24h, volume_24h, last_trade_price의 키를 갖는 데이터들을 받을 수 있다.
libcurl을 사용하여 모든 코인들의 시세를 조회해보자.
#include <cstdio>
#include <curl/curl.h>
#include <curl/curlver.h>
#include <curl/easy.h>
#include <curl/urlapi.h>
static void get_exchange_info_inline()
{
// 전역 curl 라이브러리 초기화
curl_global_init(CURL_GLOBAL_DEFAULT);
// curl 사용 시작
CURL* curl = curl_easy_init();
if (curl == nullptr)
{
std::cout << "init failed" << std::endl;
return;
}
// api URL 설정
static constexpr const char* url = "https://api.blockchain.com/v3/exchange/tickers/";
curl_easy_setopt(curl, CURLOPT_URL, url);
// response 응답 콜백 등록
std::string response;
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_buffer_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
// http request 헤더 등록
curl_slist* slist = nullptr;
slist = curl_slist_append(slist, "Accept: application/json");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
// http GET 전송
CURLcode err_code = curl_easy_perform(curl);
if (err_code != CURLE_OK)
{
std::cout << "curl_easy_perform failed : " << curl_easy_strerror(err_code) << std::endl;
return;
}
// 응답 코드 및 내용 확인
std::size_t response_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
std::cout << "http response code : " << response_code << std::endl;
std::cout << "http response : " << response << std::endl;
// 리소스 해제
curl_slist_free_all(slist);
curl_easy_cleanup(curl);
curl_global_cleanup();
}
순서는 다음과 같다.
1. 초기화 설정
2. api URL 설정
3. 응답 콜백 설정
https://curl.se/libcurl/c/CURLOPT_WRITEFUNCTION.html 공식 libcurl 문서를 보면
응답 콜백으로 등록한 함수는 C 라이브러리 stdio.h에 포함된 fwrite 명세와 같은 함수를 등록해야한다.
// size_t fwrite(void const*, size_t, size_t, FILE*)
// _Check_return_opt_
// _ACRTIMP size_t __cdecl fwrite(
// _In_reads_bytes_(_ElementSize * _ElementCount) void const* _Buffer,
// _In_ size_t _ElementSize,
// _In_ size_t _ElementCount,
// _Inout_ FILE* _Stream
// );
static size_t write_buffer_callback(char* contents, size_t size, size_t nmemb, std::string* response)
{
size_t count = size * nmemb;
if (response != nullptr && count > 0)
{
response->append(contents, count);
}
return count;
}
4. HTTP GET 요청
5. 응답 메시지 확인
6. 리소스 해제
응답 결과로 온 JSON 스트링을 객체로 역직렬화하여 데이터를 사용하도록 한다.
BlockChainInfo.h
#pragma once
#include <nlohmannjson/json.hpp>
#include <string>
#include <cstring>
#include <map>
using namespace nlohmann;
struct exchange_info
{
double price_24h;
double volume_24h;
double last_trade_price;
std::string symbol;
static void from_json(const json& jdata, exchange_info& info)
{
info.price_24h = jdata.at("price_24h").get<double>();
info.volume_24h = jdata.at("volume_24h").get<double>();
info.last_trade_price = jdata.at("last_trade_price").get<double>();
info.symbol = jdata.at("symbol").get<std::string>();
}
std::string to_string()
{
char buffer[256] = {};
snprintf(buffer, std::size(buffer),
"symbol[%s] : price (%.2lf), volume (%.2lf), last_trade_price (%.2lf)",
symbol.c_str(),
price_24h,
volume_24h,
last_trade_price);
return buffer;
}
};
using exchange_infos = std::map<std::string, exchange_info>;
libcurl 함수를 잘 래핑하여 공개한 소스를 사용해도 되지만 여기서 HTTP 프로토콜을 사용하는 기능만 잘 사용하도록 클래스로 래핑해본다.
BlockChainApi.h
#pragma once
#include <iostream>
#include <cstdio>
#include <curl/curl.h>
#include <curl/curlver.h>
#include <curl/easy.h>
#include <curl/urlapi.h>
#include "BlockChainInfo.h"
namespace blockchain
{
static size_t write_buffer_callback(char* contents, size_t size, size_t nmemb, std::string* response)
{
size_t count = size * nmemb;
if (response != nullptr && count > 0)
{
response->append(contents, count);
}
return count;
}
class curl_http_header
{
private:
curl_slist* slist = nullptr;
public:
void append(const char* header_value)
{
slist = curl_slist_append(slist, header_value);
}
curl_slist* data() const { return slist; }
~curl_http_header()
{
if (slist != nullptr)
{
curl_slist_free_all(slist);
}
}
};
class curl_http
{
private:
class curl_init
{
public:
curl_init() { curl_global_init(CURL_GLOBAL_DEFAULT); }
~curl_init() { curl_global_cleanup(); }
};
private:
CURL* curl;
std::string header;
std::string response;
public:
curl_http()
:
curl(curl_easy_init())
{
static curl_init init;
if (curl != nullptr)
{
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_buffer_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
}
}
~curl_http()
{
if (curl != nullptr)
{
curl_easy_cleanup(curl);
}
}
bool check_inited()
{
bool success = curl != nullptr;
if (!success)
{
std::cout << "curl inited failed" << std::endl;
}
return success;
}
void set_request_url(const char* url)
{
curl_easy_setopt(curl, CURLOPT_URL, url);
}
void set_request_header(const curl_http_header& header)
{
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header.data());
}
CURLcode perform()
{
return curl_easy_perform(curl);
}
static bool check_result(const CURLcode& err_code)
{
bool success = err_code == CURLE_OK;
if (!success)
{
std::cout << "curl_easy_perform failed : " << curl_easy_strerror(err_code) << std::endl;
}
return success;
}
const std::string& get_respone() const
{
return response;
}
const std::size_t get_http_response_code() const
{
std::size_t response_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
return response_code;
}
};
static bool get_exchange_infos(exchange_infos& infos)
{
curl_http curl;
if (!curl.check_inited())
{
return false;
}
curl_http_header header;
header.append("Accept: application/json");
curl.set_request_header(header);
curl.set_request_url(ticker_api);
auto err_code = curl.perform();
if (!curl_http::check_result(err_code))
return false;
auto http_response_code = curl.get_http_response_code();
std::stringstream ss(curl.get_respone());
json jdata;
ss >> jdata;
infos.clear();
for (auto iter = jdata.begin(); iter != jdata.end(); ++iter)
{
exchange_info info;
exchange_info::from_json(*iter, info);
infos.emplace(info.symbol, info);
}
return true;
}
static bool get_exchange_info(std::string_view symbol, exchange_info& info)
{
curl_http curl;
if (!curl.check_inited())
{
return false;
}
std::string url(ticker_api);
url.append(symbol);
curl_http_header header;
header.append("Accept: application/json");
curl.set_request_header(header);
curl.set_request_url(url.c_str());
auto err_code = curl.perform();
if (!curl_http::check_result(err_code))
return false;
std::stringstream ss(curl.get_respone());
json jdata;
ss >> jdata;
exchange_info::from_json(jdata, info);
return true;
}
}
위 예제 함수로 사용해보면 다음과 같다.
#include <iostream>
#include "BlockChainApi.h"
using namespace blockchain;
int main()
{
exchange_infos infos;
if (get_exchange_infos(infos))
{
for (auto& pair : infos)
{
std::cout << pair.second.to_string() << std::endl;
}
}
exchange_info info;
if (get_exchange_info("BTC-USD", info))
{
std::cout << info.to_string() << std::endl;
}
}
결과로 CURLCode::CURLE_PEER_FAILED_VERIFICATION를 받는 경우 SSL 기능에 의해 실패한 경우로 생각될 수 있는데,
https의 경우 클라이언트 SSL 인증서가 있어야 가능한 경우도 있어 보안이 중요한 경우 사용하지는 않지만
다음 옵션등으로 보안이 낮은 옵션을 사용하여 시도해볼 수 있다.
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
libcurl 관련 문서
https://curl.se/libcurl/c/
'Advanced C++' 카테고리의 다른 글
[C++] SEHException : 구조적 예외 처리 Strucutred Exception Handling (0) | 2022.04.20 |
---|---|
[C++] libcurl (3) 공개 API를 사용해보자 : HTTP POST (0) | 2022.04.18 |
[C++] libcurl (1) 다운로드 및 설치 (0) | 2022.04.17 |
[C++] 멀티 스레드 응용 프로그램 : 은행 창구 시스템 구현하기 (0) | 2022.03.27 |
[C++] C++20 동시성 컨테이너를 사용하여 ThreadPool 설계하기 (0) | 2022.03.26 |