C++とlibcurl(要OpenSSL)でTwitterのOAuth認証する
他の言語で実装されてるから移植するだけで簡単だと思ってた
普通に書いたらHTTP 100-Continueが表示された
POST 時にサーバがデータを受け入れ可能か確認するための一時応答。
HTTP 100-continue - ..たれろぐ..
(中略)
Android やら .net framework のライブラリがデフォルトで 100 を使う設定な一方、Twitter が対応していなかったのでこれに引っかかった人多数。
Twitter 相手に POST するときは無効にしておきましょう。
とりあえず無効にすることで回避できたと思った
chunk = curl_slist_append(chunk, "Expect:");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
いくら待ってもレスポンスが返ってこない(´・ω・`)
何かコードが間違っているんじゃないかと小一時間悩んだが、わからなかった
そこで最初に戻ってHTTP 100-Continueが出た理由を調べた
(今思うと、最初にしておくべきだったと後悔)
curlでPOST送信する場合に注意しておいた方がいいこと(続・CentOS4系と5系のcurlの違い) - tokuhy’s fraction
これによるとcurlでは1024バイト以上のデータを送信する場合に「Expect: 100-continue」ヘッダを自動で付与するらしい(ただしこれはcurl-7.13以降の場合)*1。
1024バイト以上を送るときに勝手につけるらしい
でも送信データはそんなに大きくない……
いろいろ悩んだ結果、ライブラリのバグを疑いはじめていたが、さすがにないと思って
とりあえず公式のExample Resultを見ていたら、POST BODYが空なことに今更気付く
Request URL:
POST https://api.twitter.com/oauth/request_tokenRequest POST Body:
N/AAuthorization Header:
POST oauth/request_token | Twitter Developers
OAuth oauth_nonce="K7ny27JTpKVsTgdyLdDfmQQWVLERj2zAK5BslRsqyw", oauth_callback="http%3A%2F%2Fmyapp.com%3A3005%2Ftwitter%2Fprocess_callback", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1300228849", oauth_consumer_key="OqEqJeafRSF11jBMStrZz", oauth_signature="Pc%2BMLdv028fxCErFyi8KXFM%2BddU%3D", oauth_version="1.0"
そこで明示的にpostデータのサイズを0にしてみたらレスポンスが返ってくるようになった
libcurl公式にきちんと書かれてたけど、0バイト送るときCURLOPT_POSTFIELDS使わないし……
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, 0);
とりあえずoauth_tokenが取得できたのでよかった
参考文献(上記引用元は省略):
OAuthをPHPでイチから書いてみた(その1) | PRESSMAN*TechPRESSMAN*Tech
JavaでTwitterのOAuthを書いてみました - n3104の日記
サンプル
https://api.twitter.com/oauth/request_tokenにPOSTしてトークンを取得
/*VSの場合のみ。そのほかは適宜ライブラリを追加 また、ファイルパスは要変更 */ #ifdef _MSC_VER #ifndef NDEBUG #pragma comment(lib,"curl/lib/libcurld.lib") #pragma comment(lib,"ssleay32MTd.lib") #pragma comment(lib,"libeay32MTd.lib") #else #pragma comment(lib,"curl/lib/libcurl.lib") #pragma comment(lib,"ssleay32MT.lib") #pragma comment(lib,"libeay32MT.lib") #endif #pragma comment(lib,"ws2_32.lib") #pragma comment(lib,"wldap32.lib") #pragma comment(lib,"Crypt32.Lib") #endif #include<string> #include<map> #include<curl\curl.h> #include<openssl\ssl.h> typedef std::map<std::string, std::string> Param; std::string BuildHttpQuery(const Param ¶m, char join_key_and_value = '=', char arg_separator = '&'); std::string HmacSha1(const char* data, const char *key) { unsigned char res[SHA_DIGEST_LENGTH + 1]; size_t res_len; size_t key_len = strlen(key); size_t data_len = strlen(data); HMAC(EVP_sha1(), key, key_len, reinterpret_cast<const unsigned char*>(data), data_len, res, &res_len); return std::string(reinterpret_cast<char*>(res), res_len); } std::string Base64Encode(const char *src) { BIO *encoder = BIO_new(BIO_f_base64()); BIO *bmem = BIO_new(BIO_s_mem()); encoder = BIO_push(encoder, bmem); BIO_write(encoder, src, strlen(src)); BIO_flush(encoder); BUF_MEM *bptr; BIO_get_mem_ptr(encoder, &bptr); std::string buf(bptr->data, bptr->length - 1); BIO_free_all(encoder); return buf; } std::string UrlEncode(const char *url) { char *p = curl_escape(url, 0); std::string ret(p); curl_free(p); return ret; } std::string CreateRondomString(int length) { std::string str; static const char *m = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; for (int i = 0; i < length; i++) { str += m[rand() % (sizeof(m) - 1)]; } return str; } std::string BuildHttpQuery(const Param ¶m, char join_key_and_value, char arg_separator) { std::string query; for (const auto &itr : param) { query += itr.first; query += join_key_and_value; query += UrlEncode(itr.second.c_str()); query += arg_separator; } query.erase(query.size() - 1); return query; } std::string CreateAuthorizationHeader(const Param ¶m) { std::string oauth_str = "Authorization: OAuth "; for (auto& itr : param) { oauth_str += itr.first; oauth_str += "=\""; oauth_str += UrlEncode(itr.second.c_str()); oauth_str += "\","; } oauth_str.erase(oauth_str.size() - 1, 1); return oauth_str; } std::string CreateOAuthSignature(const char *method, const char *url, const Param ¶m) { std::string signature = method; signature += '&'; signature += UrlEncode(url); signature += '&'; signature += UrlEncode(BuildHttpQuery(param).c_str()); return signature; } static size_t CallBackWriteString(char *ptr, size_t size, size_t nmemb, std::string *stream) { size_t dataLength = size * nmemb; stream->append(ptr, dataLength); return dataLength; } std::string TwitterGetRequestToken() { const char *method = "POST"; const char *requet_url = "https://api.twitter.com/oauth/request_token"; const char *consumer_key = "コンシューマーキー"; const char *consumer_key_secret = "コンシューマーキー シークレット"; //nullの場合はoobがセットされ,PINコードの入力画面に飛ぶ const char *callback_url = nullptr; std::map<std::string, std::string> param; srand((unsigned)time(NULL)); param["oauth_nonce"] = CreateRondomString(40); param["oauth_signature_method"] = "HMAC-SHA1"; param["oauth_timestamp"] = std::to_string(time(NULL)); param["oauth_consumer_key"] = consumer_key; param["oauth_version"] = "1.0"; param["oauth_callback"] = callback_url/* && callback_url[0]*/ ? callback_url : "oob"; std::string sig_target = CreateOAuthSignature(method, requet_url, param); std::string sig_key = UrlEncode(consumer_key_secret) + '&'; param["oauth_signature"] = Base64Encode(HmacSha1(sig_target.c_str(), sig_key.c_str()).c_str()); CURL *curl = curl_easy_init(); curl_easy_setopt(curl, CURLOPT_POST, 1L); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, 0); curl_easy_setopt(curl, CURLOPT_URL, requet_url); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); //curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CallBackWriteString); std::string ret; curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ret); struct curl_slist *chunk = NULL; std::string oauth_str = CreateAuthorizationHeader(param); chunk = curl_slist_append(chunk, oauth_str.c_str()); //chunk = curl_slist_append(chunk, "Expect:"); chunk = curl_slist_append(chunk, "Content-type:"); //chunk = curl_slist_append(chunk, "User-Agent: Twitter OAuth Test"); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk); CURLcode res = curl_easy_perform(curl); return ret; }