[写真:北海道の有珠山頂(うすざん)から望む洞爺湖(とうやこ)と昭和新山]
先日、出張で訪れている札幌市内の丸善にいってきた。
3階のコンピュータ関係の本棚で「猫語の教科書」という本を発見して反射的に買ってしまった。
コンピュータと猫語という一見何の脈絡も感じない2つの言葉。
これをコンピュータ関係の本棚に並べた理由は並べた人に聞いてみないとわからない。
自分なりに思いつく理由としては
・コンピュータ好きにはネコ好きが多いから。正確にはデスクワークとペットというのは相性が良いため。
・コンピュータ言語と猫語の言語としての共通性。
・冒頭の暗号に関する話。この暗号というアプローチが、コンピュータ(軍事通信)という歴史と重なるため。
いずれにせよ、これはAmazonで本を探していては決して巡り会う事はなかったということ。
ネットの特性として知りたい情報にたどり着く手段としては優秀であるが、
意外性のある情報との出会いという点でリアルの本屋には適わない。
電子書籍が登場したころリアルの書店が潰れると言われたが、心配するほど日本では電子書籍が普及していないように思える。
WEBとリアルの書店。どちらかが完璧でないからこそ、面白いのだと思った。
本題です。
組み込みLinuxでhttpサーバ
Linuxで動いている組み込み機器は多いですが、昨今はRaspberry Piのようにhttpサーバを乗せることも増えてきています。
下位のUDP/IP,TCP/IPはOS(Linuxカーネル)の仕事なので、その辺はカーネルのソースを見てください。
Lighttpd
Apacheサーバも組み込めるかもしれませんが、今回はApacheより軽量のLighttpdを乗せるところまでを紹介します。
Light footprint + httpd = Lighttpd ということで制約の厳しい組み込みでも使えそうです。
[特徴]
・セキュリティ: BASIC、DIGEST認証
・複数ドメイン管理: バーチャルホスト機能
・帯域: コンテンツ圧縮転送
:インターフェイス: FastCGI、CGI、SCGI、SSI
まずは Lighttpd を公式サイトからDLしましょう。
現在(2013.8) は 1.4.32 のようです。
今回はクロスコンパイル環境の準備に関してはすっとばします。
$ ./configure
この時にarm-none-linux-gnueabiなど組み込み用のアーキテクチャを指定します。
インストールします。
$ make
$ make install
設定は /etc/lighttpd.conf で行います。
Lighttpdのプラグイン開発
Lighttpd自体はpoll/epoll/selectというシステムコールを理解することが必要です。
lihgttpdの状態遷移図です。
各状態の説明は以下の通りです。
・connect waiting for a connection ・reqstart init the read-idle timer ・read read http-request-header from network ・reqend parse request ・readpost read http-request-content from network ・handlereq handle the request internally (might result in sub-requests) ・respstart prepare response header ・write write response-header + content to network ・respend cleanup environment, log request ・error reset connection (incl. close()) ・close close connection (handle lingering close)
GET/POSTがどのように処理されているかを見てみます。
connection.cのconnection_handle_read関数でread_queueをbuffer *bに入れています。
// connection.c line 349
b = (NULL != con->read_queue->last) ? con->read_queue->last->mem : NULL;
buffer型はbuffer.hで宣言されています。
b->ptrでリクエストを取得できます。
typedef struct {
char *ptr;
size_t used;
size_t size;
} buffer;
実際にpluginでGETリクエストやPOSTメッセージボディを取得したい場合は一工夫必要です。
hookのエントリポイントは以下16箇所あります。
Serverwide hooks ・init called when the plugin is loaded ・cleanup called when the plugin is unloaded ・set_defaults called when the configuration has to be processed ・handle_trigger called once a second ・handle_sighup called when the server received a SIGHUP ・Connectionwide hooks Most of these hooks are called in http_response_prepare() after some fields in the connection structure are set. ・handle_uri_raw called after uri.path_raw, uri.authority and uri.scheme are set ・handle_uri_clean called after uri.path (a clean URI without .. and %20) is set ・handle_docroot called at the end of the logical path handle to get a docroot ・handle_subrequest_start called if the physical path is set up and checked ・handle_subrequest called at the end of http_response_prepare() ・handle_physical_path called after the physical path is created and no other handler is found for this request ・handle_request_done called when the request is done ・handle_connection_close called if the connection has to be closed ・handle_joblist called after the connection_state_engine is left again and plugin internal handles have to be called ・connection_reset called if the connection structure has to be cleaned up
状態遷移図におけるreqendではGETメソッドを取得できます。
ただし、connection.cでのcon->read_queueでは取得できません。
この時点でリクエストはread_queueでなくrequest.requestに格納されています。
POSTメッセージはreqendでは取得できません。
POSTの場合、状態遷移はreadpostに遷移しメモリか一時ファイルに格納されます。
handle_subrequestでlighttpd本体ではrequest.content_lengthでPOSTメッセージがあるか判定しているようです。
プラグイン開発は実際に mod_skeleton.c を参考にしましょう。
hookエントリを追加するにはint mod_skeleton_plugin_initに追記していきます。
#include "base.h"
#include "log.h"
#include "buffer.h"
#include "plugin.h"
#include
#include
#include
/* plugin config for all request/connections */
typedef struct {
array *match;
} plugin_config;
typedef struct {
PLUGIN_DATA;
buffer *match_buf;
plugin_config **config_storage;
plugin_config conf;
} plugin_data;
typedef struct {
size_t foo;
} handler_ctx;
static handler_ctx * handler_ctx_init() {
handler_ctx * hctx;
hctx = calloc(1, sizeof(*hctx));
return hctx;
}
static void handler_ctx_free(handler_ctx *hctx) {
free(hctx);
}
/* init the plugin data */
INIT_FUNC(mod_skeleton_init) {
plugin_data *p;
p = calloc(1, sizeof(*p));
p->match_buf = buffer_init();
return p;
}
/* detroy the plugin data */
FREE_FUNC(mod_skeleton_free) {
plugin_data *p = p_d;
UNUSED(srv);
if (!p) return HANDLER_GO_ON;
if (p->config_storage) {
size_t i;
for (i = 0; i < srv->config_context->used; i++) {
plugin_config *s = p->config_storage[i];
if (!s) continue;
array_free(s->match);
free(s);
}
free(p->config_storage);
}
buffer_free(p->match_buf);
free(p);
return HANDLER_GO_ON;
}
/* handle plugin config and check values */
SETDEFAULTS_FUNC(mod_skeleton_set_defaults) {
plugin_data *p = p_d;
size_t i = 0;
config_values_t cv[] = {
{ "skeleton.array", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
{ NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
};
if (!p) return HANDLER_ERROR;
p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
for (i = 0; i < srv->config_context->used; i++) {
plugin_config *s;
s = calloc(1, sizeof(plugin_config));
s->match = array_init();
cv[0].destination = s->match;
p->config_storage[i] = s;
if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
return HANDLER_ERROR;
}
}
return HANDLER_GO_ON;
}
#define PATCH(x) \
p->conf.x = s->x;
static int mod_skeleton_patch_connection(server *srv, connection *con, plugin_data *p) {
size_t i, j;
plugin_config *s = p->config_storage[0];
PATCH(match);
/* skip the first, the global context */
for (i = 1; i < srv->config_context->used; i++) {
data_config *dc = (data_config *)srv->config_context->data[i];
s = p->config_storage[i];
/* condition didn't match */
if (!config_check_cond(srv, con, dc)) continue;
/* merge config */
for (j = 0; j < dc->value->used; j++) {
data_unset *du = dc->value->data[j];
if (buffer_is_equal_string(du->key, CONST_STR_LEN("skeleton.array"))) {
PATCH(match);
}
}
}
return 0;
}
#undef PATCH
URIHANDLER_FUNC(mod_skeleton_uri_handler) {
plugin_data *p = p_d;
int s_len;
size_t k, i;
UNUSED(srv);
if (con->mode != DIRECT) return HANDLER_GO_ON;
if (con->uri.path->used == 0) return HANDLER_GO_ON;
mod_skeleton_patch_connection(srv, con, p);
s_len = con->uri.path->used - 1;
for (k = 0; k < p->conf.match->used; k++) {
data_string *ds = (data_string *)p->conf.match->data[k];
int ct_len = ds->value->used - 1;
if (ct_len > s_len) continue;
if (ds->value->used == 0) continue;
if (0 == strncmp(con->uri.path->ptr + s_len - ct_len, ds->value->ptr, ct_len)) {
con->http_status = 403;
return HANDLER_FINISHED;
}
}
/* not found */
return HANDLER_GO_ON;
}
/* this function is called at dlopen() time and inits the callbacks */
int mod_skeleton_plugin_init(plugin *p) {
p->version = LIGHTTPD_VERSION_ID;
p->name = buffer_init_string("skeleton");
p->init = mod_skeleton_init;
p->handle_uri_clean = mod_skeleton_uri_handler;
p->set_defaults = mod_skeleton_set_defaults;
p->cleanup = mod_skeleton_free;
p->data = NULL;
return 0;
}
このコードをビルドしてみます。ダイナミックリンクライブラリが作成されます。(.so .laが作成されます)
makefile.amを編集します。
lib_LTLIBRARIES += mod_counter.la
mod_counter_la_SOURCES = mod_counter.c
mod_counter_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
mod_counter_la_LIBADD = $(common_libadd)
$ ./configure
設定ファイル(/etc/lighttpd/lighttpd.conf)を変更します。
# Errorログ
server.errorlog = "/var/log/lighttpd/error.log"
# コンテンツ圧縮設定
compress.cache-dir = "/var/lib/lighttpd/cache/compress/"
compress.filetype = ("text/css", "text/javascript", "text/plain", "text/html")
# mod_skeletonを追加
server.modules = (
"mod_access",
"mod_accesslog",
"mod_auth",
"mod_skeleton"
)
#フックするエントリの設定
skeleton.array = (".html")
設定したらデーモンを再起動しましょう。
httpクライアントから ローカルホストであるhttps://127.0.0.1/smp.html にアクセスしてみましょう。
443のステータスコードが返ってくれば、このプラグインは正常動作しているはずです。
サーバも組み込めたので、サーバのコンテンツを返してみましょう。
ということで次回はCGI連携などを書きます。
そういえば気づいたらSublimetext2のプラグイン管理が楽になってました。詳しくはここを参考に。
コマンドからもSublimetext2を使えるようにするためシンボリックリンクを張っておきます。
# case : mac
$ sudo ln -s /Applications/Sublime\ Text2.app/Contents/SharedSupport/bin/subl /opt/local/bin/subl
$ subl *.c
# case : centos 64bit /optにinstall
$ sudo mv Sublime\ Text\ 2 /opt
$ sudo ln -s /opt/Sublime\ Text\ 2/sublime_text /usr/bin/subl
$ sudo subl /usr/share/applications/sublime.desktop
Gtk-WARNING **: cannot open display とでたらユーザ間で重なっている可能性があります。