http2 対応の curl をビルドする

curl の http2 は nghttp2 を利用しているので、あらかじめインストールしておく。

$ curl -O http://curl.haxx.se/download/curl-7.45.0.tar.gz
$ tar zxf curl-7.45.0.tar.gz
$ cd curl-7.45.0
$ ./configure --prefix=/usr/local/curl-http2 --with-nghttp2
(途中省略)
configure: Configured to build curl/libcurl:

  curl version:     7.45.0
  Host setup:       x86_64-pc-linux-gnu
  Install prefix:   /home/vagrant/opt/curl-7.45.0
  Compiler:         gcc
  SSL support:      enabled (OpenSSL)
  SSH support:      no      (--with-libssh2)
  zlib support:     enabled
  GSS-API support:  no      (--with-gssapi)
  TLS-SRP support:  enabled
  resolver:         default (--enable-ares / --enable-threaded-resolver)
  IPv6 support:     enabled
  Unix sockets support: enabled
  IDN support:      enabled
  Build libcurl:    Shared=yes, Static=yes
  Built-in manual:  enabled
  --libcurl option: enabled (--disable-libcurl-option)
  Verbose errors:   enabled (--disable-verbose)
  SSPI support:     no      (--enable-sspi)
  ca cert bundle:   /etc/ssl/certs/ca-certificates.crt
  ca cert path:     no
  LDAP support:     enabled (OpenLDAP)
  LDAPS support:    enabled
  RTSP support:     enabled
  RTMP support:     enabled (librtmp)
  metalink support: no      (--with-libmetalink)
  HTTP2 support:    enabled (nghttp2)
  Protocols:        DICT FILE FTP FTPS GOPHER HTTP HTTPS IMAP IMAPS LDAP LDAPS POP3 POP3S RTMP RTSP SMB SMBS SMTP SMTPS TELNET TFTP

$ make
$ sudo make install

libcurl で http レスポンス ボディを受け取る

curl はコマンドではよくお世話になっていたが、C から使ったことはなくこの度 libcurl を使うコードを書いてみた。
curl はいろんなプロトコルに対応しているが、そのほとんどが option や callback 関数を設定するだけでなんとかなりそう。

それと、--libcurl というコマンドラインオプションがあり、curl コマンドで実行した内容の C ソースコードを出力できる。
普段使い慣れているコマンドラインから、C ソースコードでの実装に入っていけるから、実装が少しだけ楽になりそう。

コード

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "curl/curl.h"

typedef struct {
    char *m;
    size_t size;
} Memory;

size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata);

int main(int argc, char **argv)
{
    CURL *curl;
    CURLcode ret;
    Memory body;

    curl_global_init(CURL_GLOBAL_ALL);

    curl = curl_easy_init();
    if (curl == NULL) {
        fprintf(stderr, "Error: curl_easy_init() failed\n");
        return 1;
    }

    body.m = (char *)malloc(1);
    body.m[0] = '\0';
    body.size = 0;

    curl_easy_setopt(curl, CURLOPT_URL, "http://localhost/");
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &body);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0");

    ret = curl_easy_perform(curl);
    if (ret != CURLE_OK) {
        fprintf(stderr, "Error: curl_easy_perform() failed: %s\n", curl_easy_strerror(ret));
        return 1;
    }

    printf("response: \n%s\n", body.m);

    free(body.m);

    curl_easy_cleanup(curl);

    curl_global_cleanup();

    return 0;
}

size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
{
    char *p;
    Memory *mem = (Memory *)userdata;
    size_t append_size = size * nmemb;

    p = (char *)realloc(mem->m, mem->size + append_size + 1);
    if (p == NULL) {
        return 0;
    }
    memcpy(p + mem->size, ptr, append_size);

    mem->m = p;
    mem->size += append_size;
    mem->m[mem->size] = '\0';

    return size * nmemb;
}

コマンド一発で mrbgems 込みの mruby を作る

mruby の開発を楽にしようと mrb *1 というツールを作っています。
今回の 0.0.5 では、設定に合わせて mrbgems 込みの mruby を、コマンド一発でビルドできるようになりました。

以下のような YAML 形式で mruby のリポジトリの場所や一緒にビルドする mrbgems や toochain を設定し、mrb build <YAML ファイル名> とコマンドを実行します。
これだけの手順で mruby が出来上がります。設定ファイルの基本的なパラメーター名や階層は build_config.rb が元になっていますので、build_config.rb を使ったことがあるのなら比較的楽に作成できると思います。

mruby:
  github: 'mruby/mruby'
build:
  host:
    toolchain: gcc
    gembox: default
    gem:
      -
        github: 'iij/mruby-io'
      -
        github: 'iij/mruby-dir'

以下、コマンドの実行結果です。

$ mrb build my-mruby.yaml
Cloning into 'mruby'...
remote: Counting objects: 32345, done.
remote: Compressing objects: 100% (109/109), done.
remote: Total 32345 (delta 56), reused 0 (delta 0), pack-reused 32234
Receiving objects: 100% (32345/32345), 9.56 MiB | 1.94 MiB/s, done.
Resolving deltas: 100% (19729/19729), done.
Checking connectivity... done.
Buildding ...
Creating build_config.rb
ar: creating archive /Users/foo/tmp/mruby/build/host/lib/libmruby_core.a
ar: creating archive /Users/foo/tmp/mruby/build/host/lib/libmruby.a

あらかじめ決まった mruby を作成したい時やちょっとだけビルドの手間を省きたい時などに使えるかな、と思います。

次は、rbenv のように作成した mruby の切り替えができたらなと思っています。 あと設定ファイルのドキュメントも・・・。

*1:インストールは gem install mrb

mruby の拡張モジュールを作る際の準備を楽にするツールを作った

きっかけ

mruby の mrbgems を作りたいと思ったら、mruby のソースコードを取得して、mrbgems の example ををコピーして、モジュール名をリネームして、build_config.rb を編集して、ビルドしなければならないと思います。

一つ一つは容易な作業でも最初にこれらを用意しないといけないのは面倒なので、これらの作業をコマンドでできるようにしてみました。それくらいなら Rake でできるとは思ったのですが、今後拡張していくなら、コマンドラインツールを作った方がいいかなと思い、作りました。

mrbgems のテンプレートを作るなら、既に 人間とウェブの未来 - mrubyの拡張モジュールであるmrbgemのテンプレートを自動で生成するmrbgem作った があるのですが、そのためには mruby を取得して、ビルドして、・・・。

やはり gem install 一発で用意できるのはすごく楽かなと。mruby のビルドには ruby が必要になるので、gem であれば導入が楽になるはず。

インストール

以下のコマンドでインストールできます。

$ gem install mrb

mruby を取得する

最新の mruby をカレントディレクトリに保存する。

$ mrb get

build_config.rb を生成する

カレントディレクトリに build_config.rb を作成する。

$ mrb config

mrbgems のひな形を生成する

foobar のモジュールのひな形を生成する。
mruby/examples/mrbgems/c_extension_example at master · mruby/mruby を参考にしています。

$ mrb gem foobar

今後

まだ最小構成程度の build_config.rb や mrbgems のひな形しか生成できていませんが、build_config.rb に任意の設定を差し込んだし、人間とウェブの未来 - mrubyの拡張モジュールであるmrbgemのテンプレートを自動で生成するmrbgem作った のような高機能のひな形が生成できたらいいなと思います。

あとは、複数の mrbgems を開発していると mruby が増えていくので、それらを管理できたらなと思います。 rbenv みたいな感じで。

mruby-odbc を作った

mruby でそれなりにデータベースにアクセスできるようにしたいなーと思って、作った。

qtkmz/mruby-odbc

ODBC API っぽさを残しつつ、基本的なインタフェースでデータベースとやり取りができるようにしています。今の所、インタフェースは最低限実装しています。この後は、prepared statement など実装していく予定です。

env = ODBC::Env.new
env.set_attr ODBC::Env::ODBC_VERSION, ODBC::Env::VERSION_3

conn = ODBC::Conn.new env
conn.connect 'DSN=myodbc'

stmt = ODBC::Stmt.new conn
stmt.execute 'DROP TABLE IF EXISTS foo'
stmt.execute <<END_OF_SQL
CREATE TABLE foo (
  id int,
  name text
)
END_OF_SQL

stmt = ODBC::Stmt.new conn
stmt.execute "insert into foo values(1, 'aaa')"
stmt.execute "insert into foo values(2, 'bbb')"
stmt.execute "insert into foo values(3, 'ccc')"
stmt.execute "insert into foo values(4, 'ddd')"
stmt.execute "insert into foo values(5, 'eee')"

rs = stmt.execute 'SELECT * FROM foo'
rs.each do |row|
  d = []
  rs.num_result_cols.times do |i|
    d << row[i + 1]
  end
  puts d.join(',')
end

conn.close
env.close

これで、ODBC API を持つデータベースに mruby でアクセスすることができるようになります。
例えば、SQLite3, MySQL, PostgreSQL などにアクセスできます。

あと、SQL Server にもアクセスできるぽいです。

Linux の Microsoft ODBC Driver 11 for SQL Server へようこそ.aspx)

TOTP(Time-based One-time Password) のサンプルコードを mruby で実装してみた

TOTP(Time-based One-time Password) のサンプルコードが RFC 6238 - TOTP: Time-Based One-Time Password Algorithm にあるのだが、それを mruby で実装してみた。

必要な mrbgems は二つ。iij/mruby-digestiij/mruby-pack 。 もう少し実装して、二段階認証に対応しているアプリと連携できるところまでやってみるようかな。

ngx_mruby で Basic 認証を実装する

ngx_mruby で Basic 認証を実装したみた。
使い方は、ユーザー情報はファイルに (user.list) に保存しておいて、そのファイルのパスと realm 、mruby スクリプトを nginx.conf に設定する。

こういう小さな ngx_mruby の機能を組み合わせて構築したい場合、一つの機能を component 化した方がいいんだろうなとは思う。component 化したものが ngx_mruby_mrblib なのかな。これと同じようにというか、既存の mruby と同じく mrblib にしてしまえば良さそう。

設定やスクリプト、nginx のビルドスクリプトは以下のあります。 ユーザー情報は暗号化したり、パスワードをハッシュしているわけではないです (手抜き)