Y's note

Web技術・プロダクトマネジメント・そして経営について

本ブログの更新を停止しており、今後は下記Noteに記載していきます。
https://note.com/yutakikuchi/

C++でApache Moduleを書きたい人へのTutorial

C++ Apache Module Tutorial

あどさーばー作っています@yutakikuchi_です。
広告配信等の処理高速化の実現手段としてCを使ってApache/NginxのModuleレイヤーで処理を書く事があります。Apache/NginxのModuleはCを基本としているんですが、char*の処理は面倒でstringにしたい、連想配列でデータを管理し易くしたい、その他C++にしか無いライブラリを使いたいといったC++への欲求が出てきてしまいます。ぐぐってもApache ModuleをC++で実現している人って結構少なく、おそらく一般的にはApache Moduleにそんな複雑な処理を書く要件や期待なんて無いんだろうなと想像はしていますが、このエントリーではC++で書いてみます(笑)。


Apache API C++ Cookbook はてなブックマーク - Apache API C++ Cookbook
Apache 2.x Modules In C++ (Part 1) - CodeProject はてなブックマーク - Apache 2.x Modules In C++ (Part 1) - CodeProject
1つ目のサイトの「A Basic C++ Apache Module Example」にExampleが載っているんですが内容が素晴らしく古くて使い物にならなかったり、2つ目のサイトはMakefileの説明がよく分からないんで、僕がTutorial作ってみます。

C++ Source & Makefile

Tutorial File

CPlus/apache_module/tutorial at master · yutakikuchi/CPlus はてなブックマーク - CPlus/apache_module/tutorial at master · yutakikuchi/CPlus
下で使うTutorialの内容を置いておきました。

Package Install

今回のC++ ApacheModule作成はCentOS6.4 x86_64でやっています。
先に開発に必要なパッケージをInstallしておきましょう。

$ sudo yum install httpd httpd-devel make gcc gcc-c++ -y
$ tree
.
├── Makefile
└── mod_cpphello.cpp

0 directories, 2 files
C++ Source

先にC++のSourceを書きます。拡張子はmod_cpphello.cppのように.cppとします。下のSourceは単純にHTMLの出力に「こんにちは!」というString型を埋め込むだけのものです。C++記述によりstd::stringが使えるようになっているのが分かると思います。ポイントはmoduleの宣言をCでやるよとextern "C"で宣言することで使えるようになります。mod_cpphello.cpp はてなブックマーク - ここのSampleがかなり奇麗にまとまったと思います。

#include "ap_config.h"
#include "apr.h"
#include "apr_lib.h"
#include "apr_strings.h"
#include "apr_network_io.h"
#include "apr_want.h"
#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_request.h"
#include "http_protocol.h"
#include <string>

extern "C" module AP_MODULE_DECLARE_DATA cpphello_module;

typedef struct {
    char *hellomessage;
} cpphello_dir_config;

static void *cpphello_create_dir_config(apr_pool_t *p, char *path)
{
    cpphello_dir_config *cfg = (cpphello_dir_config *)apr_pcalloc(p, sizeof(cpphello_dir_config));
    cfg->hellomessage = (char *)"こんにちは!";
    return cfg;
}

static int cpphello_handler(request_rec *r)
{
    cpphello_dir_config *cfg = (cpphello_dir_config *) ap_get_module_config(r->per_dir_config, &cpphello_module);
    std::string messagetosend = std::string("<html><p>") + std::string(cfg->hellomessage) + std::string("</p></html>\n");
    r->content_type = "text/html";
    if (!r->header_only) {
      ap_rputs(messagetosend.c_str(), r);
    }
    return OK;
}

static void register_hooks(apr_pool_t *p)
{
  ap_hook_fixups(cpphello_handler,NULL,NULL,APR_HOOK_MIDDLE);
}

extern "C" {
    module AP_MODULE_DECLARE_DATA cpphello_module = {
		STANDARD20_MODULE_STUFF,
		cpphello_create_dir_config,
		NULL,
		NULL,
		NULL,
		NULL,
		register_hooks
	};
};
Makefile

Makefileのポイントはg++を使う事と、ap系のheaderファイルを読み込む為の-I/usr/include/apr-1/を記述する事です。下の記述を書いたらsudo gmake reloadと打ち込むと上のmod_cpphello.cppのcompile、install、httpd.confへの書き込み、apache restartの全てをやってくれます。httpd.confへの書き込みは最初の1回だけで問題ありませんが、手動でやると忘れてしまう事が多いので(apxs -aのオプションを忘れる)注意してください。

##
##  Makefile -- Build procedure for fast3lpoad Apache module
##
##  This is a C++ module so things have to be handled a little differently.

#   the used tools
APXS=apxs
APACHECTL=apachectl

# Get all of apxs's internal values.
APXS_CC=`$(APXS) -q CC`   
APXS_TARGET=`$(APXS) -q TARGET`   
APXS_CFLAGS=`$(APXS) -q CFLAGS`   
APXS_SBINDIR=`$(APXS) -q SBINDIR`   
APXS_CFLAGS_SHLIB=`$(APXS) -q CFLAGS_SHLIB`   
APXS_INCLUDEDIR=`$(APXS) -q INCLUDEDIR`   
APXS_LD_SHLIB=`$(APXS) -q LD_SHLIB`
APXS_LIBEXECDIR=`$(APXS) -q LIBEXECDIR`
APXS_LDFLAGS_SHLIB=`$(APXS) -q LDFLAGS_SHLIB`
APXS_SYSCONFDIR=`$(APXS) -q SYSCONFDIR`
APXS_LIBS_SHLIB=`$(APXS) -q LIBS_SHLIB`

#   the default target
all: mod_cpphello.so

# compile the shared object file. use g++ instead of letting apxs call
# ld so we end up with the right c++ stuff. We do this in two steps,
# compile and link.

# compile
mod_cpphello.o: mod_cpphello.cpp
	g++ -c -fPIC -I$(APXS_INCLUDEDIR) -I/usr/include/apr-1/ $(APXS_CFLAGS) $(APXS_CFLAGS_SHLIB) -Wall -o $@ $< 

# link
mod_cpphello.so: mod_cpphello.o 
	g++ -fPIC -shared -o $@ $< $(APXS_LIBS_SHLIB)

# install the shared object file into Apache 
install: all
	$(APXS) -i -a -n 'cpphello' mod_cpphello.so

# display the apxs variables
check_apxs_vars:
	@echo APXS_CC $(APXS_CC);\
	echo APXS_TARGET $(APXS_TARGET);\
	echo APXS_CFLAGS $(APXS_CFLAGS);\
	echo APXS_SBINDIR $(APXS_SBINDIR);\
	echo APXS_CFLAGS_SHLIB $(APXS_CFLAGS_SHLIB);\
	echo APXS_INCLUDEDIR $(APXS_INCLUDEDIR);\
	echo APXS_LD_SHLIB $(APXS_LD_SHLIB);\
	echo APXS_LIBEXECDIR $(APXS_LIBEXECDIR);\
	echo APXS_LDFLAGS_SHLIB $(APXS_LDFLAGS_SHLIB);\
	echo APXS_SYSCONFDIR $(APXS_SYSCONFDIR);\
	echo APXS_LIBS_SHLIB $(APXS_LIBS_SHLIB)

#   cleanup
clean:
	-rm -f *.so *.o *~

#   install and activate shared object by reloading Apache to
#   force a reload of the shared object file
reload: install restart

#   the general Apache start/restart/stop
#   procedures
start:
	$(APACHECTL) start
restart:
	$(APACHECTL) restart
stop:
	$(APACHECTL) stop

最後にApacheの設定ファイルの確認とlocalhostのPathにアクセスしてみて、「こんにちは!」が表示されている事を見てみます。httpd.confについてはLoadModule cpphello_moduleが自動追加、curlを実行してみると「こんにちは!」のHTMLが確かに取得できました。これでstd::stringがちゃんと使えている事が分かりましたね。

これを参考にC++でもApache Moduleを作ってみてください。

$ sudo gmake reload
/usr/lib64/httpd/build/instdso.sh SH_LIBTOOL='/usr/lib64/apr-1/build/libtool' mod_cpphello.so /usr/lib64/httpd/modules
/usr/lib64/apr-1/build/libtool --mode=install cp mod_cpphello.so /usr/lib64/httpd/modules/
libtool: install: cp mod_cpphello.so /usr/lib64/httpd/modules/mod_cpphello.so
Warning!  dlname not found in /usr/lib64/httpd/modules/mod_cpphello.so.
Assuming installing a .so rather than a libtool archive.
chmod 755 /usr/lib64/httpd/modules/mod_cpphello.so
[activating module `cpphello' in /etc/httpd/conf/httpd.conf]
apachectl restart

$ grep "cpphello" /etc/httpd/conf/httpd.conf
LoadModule cpphello_module    /usr/lib64/httpd/modules/mod_cpphello.so

$ sudo touch /var/www/html/hello_world

$ curl -i "http://localhost/hello_world"
HTTP/1.1 200 OK
Date: Thu, 31 Oct 2013 18:17:14 GMT
Server: Apache/2.2.15 (CentOS)
Last-Modified: Thu, 31 Oct 2013 18:03:36 GMT
ETag: "2601eb-0-4ea0d44d23aa2"
Accept-Ranges: bytes
Content-Length: 39
Connection: close
Content-Type: text/html; charset=UTF-8

<html><p>こんにちは!</p></html>