KLab若手エンジニアの これなぁに?

KLab株式会社の若手エンジニアによる技術ブログです。phpやRubyなどの汎用LLやJavaScript/actionScript等のクライアントサイドの内容、MySQL等のデータベース、その他フレームワークまで幅広く面白い情報を発信します。

2008年11月

KLab男塾を開設!!

お世話になっております、amo-kです。 この度、KLab男塾を開設致しましたので緊急連絡いたします!

KLab男塾開設の背景

若手一の暴君、yoshida-kとKラボラトリーのhamanoが激辛料理好きで サドンデスソースという激辛タバスコにはまっておりました。

↓yoshida-k
yoshida-k


そこで私、amo-kがオフィス近くにある韓国焼肉店の激辛チゲを食べに連れて行きました。 これ、メニューには無いのです。以前私が店員に「チゲ辛さ10倍で!」と言ったら本当に激辛チゲを出してくれたノリのいいお店です。 そこで
yoshida-k:「ヤバイっす、激辛部つくりましょう!」
amo-k: 「馬鹿野郎!男塾だろ!!」
yoshida-k: 「す、すいません、そのとおりです!!」
というやり取りが男塾開設のきっかけです。

KLab男塾開設の活動内容

単に激辛料理を食べるだけですw

ということで、yoshida-kにサドンデスソースについて熱く語っていただきます。 では、yoshida-kよろしく!

Rhinoを使おう

全国1千万人のJavaScriptユーザーの皆様こんにちは。たかだです。 本日は、拡張性とポータビリティにすぐれた JavaScript 処理系 Rhino(ライノー)をご紹介したいと思います。 JavaScript マニアなら、ブラウザ上だけではなく、もっと汎用的なツールとして JavaScript を利用したいと思いますよね? 日常的に利用するちょっとしたスクリプトはもちろん、できれば サーバーサイドのWebアプリや GUIアプリケーションまで JavaScriptで書きたいですよね?(という人が何人いるのかわかりませんが) RhinoはJavaScriptの実行環境としてだけではなく、Javaをインタラクティブに操る処理系としても役立ちます。 Rhino はオープンソースで開発されている JavaScript の処理系です。Mozilla Foundation によって管理され、Java言語で実装されています。 最大の特徴は、Java のクラスやメソッドを JavaScriptから操作できることです。JavaScriptコードを Javaバイトコードに変換し、classファイルを作成することもできます。 このため、処理系自体を拡張しなくても、Javaのほとんど全機能を JavaScriptから利用することができます。 など、ブラウザから独立した JavaScript(JScript)処理系にはいくつかの選択肢がありますが、拡張性に優れ、Java言語の機能を引き出せる Rhino はもっとも有力な選択肢の1つです。

インストール

さっそく使ってみましょう。 公式サイトより最新版を入手してください。 これ以外にJDK 1.4以降の Java実行環境が必要です。Java実行環境のインストールについてはここでは説明しません。 適当な場所に解凍してください。js.jarというファイルがあるはずです。こちらのファイルに、クラスパスを通せばさっそく実行できます。 以下のコマンドを実行するとJavaScriptのインタラクティブシェルが立ち上がります。 ## 以下 /path/to/rhino は解凍したrhinoのディレクトリに合わせて変更してください。
$ java -classpath /path/to/rhino/js.jar org.mozilla.javascript.tools.shell.Main
Rhino 1.7 release 0 0000 00 00
js> print(1+1);
2
js>
終了には quit() という関数を呼び出します。
js> quit()
ファイルに保存したスクリプトを実行することもできます。
$ echo 'print("hello, world");' > hello.js
$ java -classpath /path/to/rhino/js.jar org.mozilla.javascript.tools.shell.Main hello.js
hello, world

ただし、こんなに長いコマンドを覚えるのは大変なので、呼び出し用の短いエイリアスを作成しておきましょう。 Windowsでは、以下のようなファイルをrhino.batという名前で保存し、パスの通った場所に置いておきます。 Windows環境では、日本語の文字化けを防ぐため、-Dfile.encoding=UTF-8というオプションを付ける必要 あります。
@echo off
set CLASSPATH=/path/to/rhino/js.jar
java -Dfile.encoding=UTF-8 org.mozilla.javascript.tools.shell.Main %1 %2 %3 %4 %5 %6 %7 %8 %9
bash(UnixライクOS)では以下の用なファイルをrhinoという名前で保存し、パスの通った場所に置き、実行権限を与えます。
export CLASSPATH=/path/to/rhino/js.jar
java org.mozilla.javascript.tools.shell.Main $*
以上により、rhino というコマンドで、rhino のJavaScriptシェルを呼び出すことができるようになりました。
$ rhino
Rhino 1.7 release 0 0000 00 00
js> 

Javaの機能を呼び出す

rhinoからJavaの機能を呼ぶのはとても簡単です。
js> java.lang.System.out.println("Hello");
Hello
以上のようにパッケージつきで呼び出せば、まるでJavaScriptの関数を呼ぶように簡単にJavaのメソッドにアクセスできます。 パッケージをタイプするのが面倒な場合は、importPackageという関数で、グローバル環境に読み込むことができます。
js> importPackage(java.lang);
js> System.out.println('hello');
hello
パッケージは、JavaScriptのオブジェクトのような扱いになっているので、変数に代入もできます。
js> var $out = java.lang.System.out;
js> $out.println('Hello');
Hello
Javaクラスのインスタンスを作成するのも簡単です。
js> var f = new java.io.File('.');
js> f.exists();
true

便利な標準関数

Javaの機能を呼びださなくても、いくつかの機能は標準で提供されています。 以下の関数などはかなり使いどころがあるのではないでしょうか。
print()
標準出力に文字列を出力する。
readFile(path)
ファイルを読み込み、内容を文字列として返す。
readUrl(url)
URLを読み込み、内容を文字列として返す。
runCommand(cmd)
外部コマンドを実行する。
Rhinoを使えば汎用的スクリプト言語としてJavaScriptを利用できるのはもちろん、Javaアプリのテストなどにも強力な性能を発揮します。

symfony × MySQL × Shift_JIS: 0x5c関連

symfonyでアプリケーションを作成していた際に、文字コード絡みで面白い事象に遭遇したので記事にすることにしたw 携帯用のWebアプリケーションを作っていたのだが、 Webサーバ側での出力データの文字コードをShift_JISに統一するため、 以下のように全ての文字コードをShift_JIS/cp932に統一して実験してみた。 MySQL(my.cnf)
[client]
default-character-set=cp932
[mysqld]
default-character-set=cp932
現在の設定状況を確認。
mysql> show variables like 'character_set%';
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | cp932                      | 
| character_set_connection | cp932                      | 
| character_set_database   | cp932                      | 
| character_set_filesystem | binary                     | 
| character_set_results    | cp932                      | 
| character_set_server     | cp932                      | 
| character_set_system     | utf8                       | 
| character_sets_dir       | /usr/share/mysql/charsets/ | 
+--------------------------+----------------------------+
とここで、、、DBドライバmysqliはmy.cnfを読み込む設定が可能だが、DBドライバmysqlは無理ぽい。。。 要するに、クライアント側(symfony/phpアプリケーション)からはmy.cnfは読み込まれない。 php(php.ini)
[mbstring]
mbstring.language = Japanese
mbstring.internal_encoding = SJIS-WIN
mbstring.http_output = SJIS-WIN
symfony
# config/settings.yml(symfony本体、アプリケーション共に)
charset: Shift_JIS
# databases.yml
encoding: cp932
以上の設定を施したが、0x5c問題が気になったので以下のテストを行った。 symfonyで作成したテスト用アプリケーションで「ソ」一文字を登録。 結果は失敗。 ということでsymfonyが使用しているO/Rマッパや データベースドライバについて調べてみた。 symfonyはデフォルトではPropelというO/Rマッパを用い、 CreoleというDB接続モジュールでDBドライバに"mysql"を利用している。 ということで、CreoleのDBドライバ:mysqlにおける MySQL接続クラス(MySQLConnection.php)を確認してみた。
if ($encoding) {
    $this->executeUpdate("SET NAMES " . $encoding);
}
むむ、発見。 ここでdatabases.ymlに設定した文字コードを指定している。 SET NAMESクエリを発行している。 試しにここをphpの標準関数mysql_set_charset()に置き換えてみた。
if ($encoding) {
    mysql_set_charset($encoding);
}
再度「ソ」一文字登録テスト。 お!成功!! しかし何故?? ということでMySQLのクエリログを確認
# 失敗: $this->executeUpdate("SET NAMES " . $encoding);
081114 19:30:21      22 Connect     db_test@localhost on 
                     22 Init DB     db_test
                     22 Init DB     db_test
                     22 Query       SET NAMES cp932
                     22 Query       SET AUTOCOMMIT=0
                     22 Query       BEGIN
                     22 Init DB     db_test
                     22 Query       INSERT INTO table_test (TITLE,CREATED_AT,UPDATED_AT) VALUES ('ソ\','2008-11-14 20:27:42','2008-11-14 20:27:42')
                     22 Init DB     db_test
                     22 Query       ROLLBACK
                     22 Query       SET AUTOCOMMIT=1
                     22 Quit

# 成功: mysql_set_charset($encoding);
081114 19:30:48      23 Connect     db_test@localhost on 
                     23 Init DB     db_test
                     23 Query       SET NAMES cp932
                     23 Query       SET AUTOCOMMIT=0
                     23 Query       BEGIN
                     23 Init DB     db_test
                     23 Query       INSERT INTO table_test (TITLE,CREATED_AT,UPDATED_AT) VALUES ('ソ','2008-11-14 20:28:09','2008-11-14 20:28:09')
                     23 Init DB     db_test
                     23 Query       COMMIT
                     23 Query       SET AUTOCOMMIT=1

# 失敗: mysql_query("SET NAMES " . $encoding);
081114 19:39:12      27 Connect     db_test@localhost on 
                     27 Init DB     db_test
                     27 Query       SET NAMES cp932
                     27 Query       SET AUTOCOMMIT=0
                     27 Query       BEGIN
                     27 Init DB     db_test
                     27 Query       INSERT INTO table_test (TITLE,CREATED_AT,UPDATED_AT) VALUES ('ソ\','2008-11-14 20:36:33','2008-11-14 20:36:33')
                     27 Init DB     db_test
                     27 Query       ROLLBACK
                     27 Query       SET AUTOCOMMIT=1
ン? 成功/失敗時のクエリを確認すると、文字コード指定部分は全く同じである。 なんと、違いはMySQLサーバがデータを受け取った時点で異なることがわかった。 では、php標準関数mysql_set_charset()では何をしているのか? phpのソースコード(php_mysql.c)を追ってみた。
PHP_FUNCTION(mysql_set_charset)
{
	zval *mysql_link = NULL;
	char *csname;
	int id = -1, csname_len;
	php_mysql_conn *mysql;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|r", &csname, &csname_len, &mysql_link) == FAILURE) {
		return;
	}

	if (ZEND_NUM_ARGS() == 1) {
		id = php_mysql_get_default_link(INTERNAL_FUNCTION_PARAM_PASSTHRU);
		CHECK_LINK(id);
	}

	ZEND_FETCH_RESOURCE2(mysql, php_mysql_conn *, &mysql_link, id, "MySQL-Link", le_link, le_plink);

	if (!mysql_set_character_set(&mysql->conn, csname)) {
		RETURN_TRUE;
	} else {
		RETURN_FALSE;
	}
}
おっと、mysql_set_character_set()関数を使用している。 これはMySQLモジュール、libmysqlの関数である。 では、このmysql_set_character_set()では何をしているのか? MySQLのソースコードに内包されているlibmysqldのclient.cを確認
int STDCALL mysql_set_character_set(MYSQL *mysql, const char *cs_name)
{
  struct charset_info_st *cs;
  const char *save_csdir= charsets_dir;

  if (mysql->options.charset_dir)
    charsets_dir= mysql->options.charset_dir;

  if (strlen(cs_name) < MY_CS_NAME_SIZE &&
     (cs= get_charset_by_csname(cs_name, MY_CS_PRIMARY, MYF(0))))
  {
    char buff[MY_CS_NAME_SIZE + 10];
    charsets_dir= save_csdir;
    /* Skip execution of "SET NAMES" for pre-4.1 servers */
    if (mysql_get_server_version(mysql) < 40100)
      return 0;
    sprintf(buff, "SET NAMES %s", cs_name);
    if (!mysql_real_query(mysql, buff, strlen(buff)))
    {
      mysql->charset= cs;
    }
  }
  else
  {
    char cs_dir_name[FN_REFLEN];
    get_charsets_dir(cs_dir_name);
    mysql->net.last_errno= CR_CANT_READ_CHARSET;
    strmov(mysql->net.sqlstate, unknown_sqlstate);
    my_snprintf(mysql->net.last_error, sizeof(mysql->net.last_error) - 1,
		ER(mysql->net.last_errno), cs_name, cs_dir_name);

  }
  charsets_dir= save_csdir;
  return mysql->net.last_errno;
}
ぉお!
sprintf(buff, "SET NAMES %s", cs_name);
if (!mysql_real_query(mysql, buff, strlen(buff)))
{
    mysql->charset= cs;
}
これだ! SET NAMESクエリを発行した後で クライアント側のmysqlコネクションオブジェクトに文字コードをセットしている。 ただ単にSET NAMESクエリを発行するのとphpのmysql_set_charset()にて文字コードを指定するのとでは この違いがあった。 いくらsymfonyやphpの設定をShift_JISにしても、MySQLクライアント(DBドライバmysql)がShift_JISで無ければ0x5cの2バイト目の5c(\)を2バイト目の5cと認識できず、単にエスケープ文字としてエスケープしてしまう。しかし、MySQLサーバ側はShift_JIS(cp932)で設定されているため2バイト目の5cを2バイト目と認識し、クライアントが付加した5cが余分なエスケープとなってクエリがシンタックスエラーとなる。 Shift_JISの「ソ」を「835c」と表現して以下に当問題の一連の流れを書く。
■ symfony/php
insert into table values '835c';
■ mysql(MySQLクライアント)
# 835cの2バイト目をエスケープ(5c付加)してしまう
# 83 + ここに5cを付加 + 5c ⇒ 835c5c
insert into table values '835c5c';
■ MySQLサーバ
# 835cまでを「ソ」と認識し、残りの5cが余計なエスケープとなり、
# 閉じクオートがエスケープされる。('ソ\')
insert into table values '835c5c';
mysqliなら、my.cnfを読み込むことも可能なのでこの様な事象に遭遇することは少ないのかもしれない。 ん~phpでDBドライバにmysqlを使用する、またはそのような環境においてShift_JISを利用したい場合は mysql_set_charset()で文字コードを設定するべきですね。
 KLab若手エンジニアブログのフッター