C言語でTwitter UserStreamを受信しよう


拙作のC言語向けTwitterライブラリT4CはREST APIに加えてStreaming APIもサポートしています。
今回はC言語でどのようにしてStreaming APIをサポートするのか、について書こうと思います。

Streaming APIとは

Streaming APIとは簡単に言うと、リアルタイムに(何らからのEventが発生した場合に)TwitterからレスポンスがかえってくるAPIです。
で、リアルタイムに帰ってくる事に加え、無限にレスポンスが帰ってきます。
よって、通常のGETリクエストとして処理することはできません。したがって、異なるアプローチをとることにします。

異なるアプローチといっても単純で、タイムアウトを無くして、レスポンスがかえってくるたびにcallbackが呼ばれるようにすればよいのです。

libcurlでタイムアウトをなくすには

curl_easy_setopt(curl, CURLOPT_TIMEOUT, 0);

とすればよいのです。

また、コールバックには次のようなものを指定すればそれが、レスポンスがかえってくるたびに呼ばれます。

static size_t streaming_callback_sample(void* ptr, size_t size, size_t nmemb, void* data) {
  if (size * nmemb == 0)
    return 0;

  size_t realsize = size * nmemb;
  string* str = (string*)data;
  str->length = realsize + 1;
  str->value  = MALLOC_TN(char, str->length);

  if (str->value != NULL) {
    memcpy(str->value, ptr, realsize);
    strcat(str->value, "\0");

    fprintf(stderr, "RECIEVED: %ld bytes\n", realsize);
    fprintf(stderr, "[STREAMING API] received -> %s\n", str->value);
  }

  return realsize;
}

コード

あとは、以前に書いた記事(C言語でTwitter APIをつかって、ツイートしよう! (自前でOAuthを実装する話))のrequestとほぼ同じような感じで一部、上で書いたことを反映させれば実現できます(重複になるのでstream関数内で用いてる関数などについての説明は行いませんので、詳しくは以前書いた記事を参照してください。)

void stream(T4C* t4c, string url, Parameters* paramsArgument, size_t (*callback)(void*, size_t, size_t, void*)) {

  bool paramsArgumentWasNULL = false;
  if (paramsArgument == NULL) {
    paramsArgument = new_parameters();
    paramsArgumentWasNULL = true;
  }

  Parameters* oauthParams = new_parameters();
  genOAuthParams(t4c, oauthParams);
  Parameters* params = new_parameters();
  buildParams(params, oauthParams, paramsArgument);

  string oauthSignature = signature(t4c->consumerSecret, t4c->accessTokenSecret, GET, url, params);
  string encodedSignature = url_encode(oauthSignature);

  add_parameter(oauthParams, make_string("oauth_signature"), encodedSignature);
  add_parameter(params, make_string("oauth_signature"), encodedSignature);

  string authorize      = new_string();
  string authorizeChild = join_parameters(oauthParams, ",");
  authorize.length      = 21 + authorizeChild.length;
  authorize.value       = MALLOC_TN(char, authorize.length);
  sprintf(authorize.value, "Authorization: OAuth %s", string_get_value(authorizeChild));

  string path = join_parameters(params, "&");

  if (DEBUG) {
    printf("----------------------------\n");
    printf("STREAMING API");
    printf("URL: %s\n", string_get_value(url));
    printf("path: %s\n", string_get_value(path));
    printf("authorize: %s\n", string_get_value(authorize));
    printf("----------------------------\n");
  }

  CURL* curl;
  curl = curl_easy_init();

  string reqURL = new_string();

  reqURL.length = url.length + 1 + path.length;
  reqURL.value  = MALLOC_TN(char, reqURL.length);
  sprintf(reqURL.value, "%s?%s", url.value, path.value);

  curl_easy_setopt(curl, CURLOPT_URL, reqURL.value);

  struct curl_slist *headers = NULL;
  headers = curl_slist_append(headers, string_get_value(authorize));
  curl_easy_setopt(curl, CURLOPT_HEADER, headers);

  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, callback);
  curl_easy_setopt(curl, CURLOPT_TIMEOUT, 0);

  curl_easy_perform(curl);
  curl_easy_cleanup(curl);

  free_string(url);
  free_string(path);
  free_string(authorize);
  free_parameters(oauthParams);
  free_parameters(params);
  if (paramsArgumentWasNULL) {
    free_parameters(paramsArgument);
  }
}

このようにすればC言語でTwitter UserStreamが受信できます。
以下にT4Cを用いてC言語でUserStreamを受信するコードを書きます

#include <t4c/parameters.h>
#include <t4c/string.h>
#include <t4c/t4c.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>

//上に載せたコールバックをここに書く(スペースの関係上省略)
static size_t streaming_callback_sample(void* ptr, size_t size, size_t nmemb, void* data);

int main() {
  T4C t4c = {
    .consumerKey       = make_string("Your Consumer Key"),
    .consumerSecret    = make_string("Your Consumer Secret"),
    .accessToken       = make_string("Your AccessToken"),
    .accessTokenSecret = make_string("Your AccessTokenSecret")
  };

  stream(&t4c, make_string("https://userstream.twitter.com/1.1/user.json"), NULL, streaming_callback_sample);
}

また、T4CのREADMEにも書きましたが、/statuses/filter.jsonのような引数をとるAPIも使うことができます。そちらはT4CのREADMEを参照してください。