C++でHTTPS通信をするサンプル

CodeRunnerや天下一GameBattleContestでは連続してHTTPS通信をし続ける必要があります。サンプルの実装は以下のようになっています。

|CodeRunner| |
|:--|:--|
|Ruby|open-uri|
|Python|urllib.urlopen|
|Java|java.net.HttpURLConnection<br>(javax.net.ssl.HttpsURLConnectionに変更すればSSL対応にできると思います)|
|C++|**popen curl**|

|天下一| |
|:--|:--|
|Python|urllib.request.urlopen|
|C#|System.Net.WebRequest|
|Go|net/http|
|C++|**popened by Python**|

RubyはNet::HTTP、Pythonはhttplib/http.clientを使う方法もあると思いますが、いずれにせよ言えるのは、C++が不遇であるということす。

そこで、以前携わった[controllerclientcpp](https://github.com/mujin/controllerclientcpp)が応用できないかな〜と思って作ってみました(controllerclientcppはboost必須ですがこちらのサンプルはboost不要です、その代わりC++11が必須)。

```cpp:curlsample.cpp
#include <curl.h>
#include <stdio.h>
#include <string>
#include <sstream>
#include <functional>
#include <vector>
#include <memory>

template <typename ... Args>
std::string format(const std::string& fmt, Args ... args ){
	// http://pyopyopyo.hatenablog.com/entry/2019/02/08/102456
	size_t len = std::snprintf( nullptr, 0, fmt.c_str(), args ... );
	std::vector<char> buf(len + 1);
	std::snprintf(&buf[0], len + 1, fmt.c_str(), args ... );
	return std::string(&buf[0], &buf[0] + len);
}

// https://github.com/mujin/controllerclientcpp/blob/master/src/common.h
#define CHECKCURLCODE(code, msg) if (code != CURLE_OK) throw std::runtime_error(::format("[%s:%d] curl function %s with error '%s': %s",(__PRETTY_FUNCTION__),(__LINE__),(msg),curl_easy_strerror(code),_errormessage.c_str()));
// https://github.com/mujin/controllerclientcpp/blob/master/src/controllerclientimpl.h
#define CURL_OPTION_SAVER(curl, curlopt, curvalue) std::shared_ptr<void> __curloptionsaver ## curlopt((void*)0, std::bind(std::function<CURLcode(CURL*, CURLoption, decltype(curvalue))>(curl_easy_setopt), curl, curlopt, curvalue))
#define CURL_OPTION_SETTER(curl, curlopt, newvalue) CHECKCURLCODE(curl_easy_setopt(curl, curlopt, newvalue), "curl_easy_setopt " # curlopt)
#define CURL_OPTION_SAVE_SETTER(curl, curlopt, curvalue, newvalue) CURL_OPTION_SAVER(curl, curlopt, curvalue); CURL_OPTION_SETTER(curl, curlopt, newvalue)
#define CURL_INFO_GETTER(curl, curlinfo, outvalue) CHECKCURLCODE(curl_easy_getinfo(curl, curlinfo, outvalue), "curl_easy_getinfo " # curlinfo)
#define CURL_PERFORM(curl) CHECKCURLCODE(curl_easy_perform(curl), "curl_easy_perform")
#define CURL_FORM_RELEASER(form) std::shared_ptr<void> __curlformreleaser ## form((void*)0, std::bind(std::function<void(decltype(form))>(curl_formfree), form))

class CurlSample{
public:
	CurlSample():_httpheaders(NULL){
		_curl = curl_easy_init();
		_errormessage.resize(CURL_ERROR_SIZE);
		CURL_OPTION_SETTER(_curl, CURLOPT_ERRORBUFFER, &_errormessage[0]);
	}
	~CurlSample(){
		if(!!_httpheaders)curl_slist_free_all(_httpheaders);
		_httpheaders = NULL;
		curl_easy_cleanup(_curl);
	}
	void SetupHeaders(){
		if(!!_httpheaders)curl_slist_free_all(_httpheaders);
		_httpheaders = NULL;
		_httpheaders = curl_slist_append(_httpheaders,::format("Accept-Language: en-us").c_str());
	}
	void Get(const std::string &uri,std::ostream &output,double timeout=5){
		SetupHeaders();
		CURL_OPTION_SAVE_SETTER(_curl, CURLOPT_TIMEOUT_MS, 0L, (long)(timeout * 1000L));
		CURL_OPTION_SAVE_SETTER(_curl, CURLOPT_URL, NULL, uri.c_str());
		CURL_OPTION_SAVE_SETTER(_curl, CURLOPT_HTTPHEADER, NULL, _httpheaders);
		CURL_OPTION_SAVE_SETTER(_curl, CURLOPT_WRITEFUNCTION, NULL, _WriteCallback);
		CURL_OPTION_SAVE_SETTER(_curl, CURLOPT_WRITEDATA, NULL, &output);
		CURL_OPTION_SAVE_SETTER(_curl, CURLOPT_HTTPGET, 0L, 1L);
		CURL_PERFORM(_curl);
		long http_code = 0;
		CURL_INFO_GETTER(_curl, CURLINFO_RESPONSE_CODE, &http_code);
		if(http_code!=200)throw std::runtime_error(::format("[%s:%d] HTTP returned %d",(__PRETTY_FUNCTION__),(__LINE__),http_code));
	}
	void Post(const std::string &uri,const std::string &data,std::ostream &output,double timeout=5){
		SetupHeaders();
		CURL_OPTION_SAVE_SETTER(_curl, CURLOPT_TIMEOUT_MS, 0L, (long)(timeout * 1000L));
		CURL_OPTION_SAVE_SETTER(_curl, CURLOPT_URL, NULL, uri.c_str());
		CURL_OPTION_SAVE_SETTER(_curl, CURLOPT_HTTPHEADER, NULL, _httpheaders);
		CURL_OPTION_SAVE_SETTER(_curl, CURLOPT_WRITEFUNCTION, NULL, _WriteCallback);
		CURL_OPTION_SAVE_SETTER(_curl, CURLOPT_WRITEDATA, NULL, &output);
		CURL_OPTION_SAVE_SETTER(_curl, CURLOPT_POST, 0L, 1L);
		CURL_OPTION_SAVE_SETTER(_curl, CURLOPT_POSTFIELDSIZE, 0, data.size());
		CURL_OPTION_SAVE_SETTER(_curl, CURLOPT_POSTFIELDS, NULL, data.size() > 0 ? data.c_str() : NULL);
		CURL_PERFORM(_curl);
		long http_code = 0;
		CURL_INFO_GETTER(_curl, CURLINFO_RESPONSE_CODE, &http_code);
		if(http_code/100!=2)throw std::runtime_error(::format("[%s:%d] HTTP returned %d",(__PRETTY_FUNCTION__),(__LINE__),http_code));
	}
private:
	static int _WriteCallback(char *data, size_t size, size_t nmemb, std::ostream *writerData){
		if(writerData == NULL){
			return 0;
		}
		writerData->write(data, size*nmemb);
		return size * nmemb;
	}
	CURL *_curl;
	curl_slist *_httpheaders;
	std::string _errormessage;
};

int main(){
	CurlSample curl;
	std::stringstream ss;
	curl.Get("https://www.google.com/",ss,5);
	fputs(ss.str().c_str(),stdout);
}
```

## 終わりに

- 天下一2019OBTの後に作ろうと思って結局天下一2020の後になってしまいました。。まあ賞金取れたから(闇討ちしなくてよかった意味で)結果オーライ^^;;;