Home

Born Neet

TinySegmenterをiPhone(Objective-C)に移植してみました

Objective-Cの勉強がてら、辞書いらずの簡易形態素解析(分かち書き)ソフト、
TinySegmenter」をObjective-Cに移植してみました。

ホントは人工無脳アプリを作ったタイミングでお披露目しようとしてましたが、
飽きた(!)のでライブラリとして先に公開しちゃいます。

tnantoka's TinySegmenter.m at master - GitHub

使い方は簡単です。
CocoaOnigurumaをプロジェクトに組み込んだ後、
TinySegmenter.hとTinySegmenter.mをClassesに放り込んで下さい。

あとは以下のようなコードで分ち書きができます。

#import "TinySegmenter.h"
:
:
TinySegmenter* segmenter = [ [ TinySegmenter alloc ] init ];
NSArray* segs = [ segmenter segment: @"これはテストですよ" ];
NSLog(@"%@", [ segs componentsJoinedByString: @"|" ]);
// これ|は|テスト|です|よ

iPhoneアプリで人工無脳とかを作りたいけど、
MeCabはヘビー(っていうかどうやってiPhoneで動かせばいいのかわからん)
とかいう人は是非使ってみて下さい。
※ 誰かMeCabをiPhone SDKで使う方法教えてください><

ライセンスはnew BSDですので、ご自由に。

ただ、Objective-Cを触り始めてまだ2週間ぐらいなので、
ダメダメなコードになってると思われますので、
突っ込みをいただけるとありがたいです。

以上。このライブラリを使っておもしろいボットが作られれば幸いです。
それでは!

PR

初iPhoneアプリ「JavaScript Anywhere」が公開されました

Objective-Cはもちろん、X-Codeもろくに使ったことのなかった僕が、
試行錯誤しながら作った初のiPhoneアプリが公開されました。

その名も「JavaScript Anywhere

名前の通り(?)JavaScriptの簡易開発環境です。
機能はまだまだ少なく、
HTMLとCSS、JSを1ファイルずつ編集して、内蔵ブラウザで実行できるだけです。

とまぁそれだけのアプリですが『満員電車でもJavaScriptを弄っていたい。』
そんな変態にはオススメかもしれません。

スクリーンショットとかは以下のページに載せてあります。
JSAnywhere紹介ページ

無料ですので、少しでも興味のある方はどうぞ。

機能追加のアイディアも募集しております。

以上、宣伝でした。

Hello, httpd! C言語でWebServerを作ってみよう [第1回目]

さて、久々の勝手に連載企画を始めてみます。
(ホントは先週に始まってる予定だったんですが…忙しすぎました)

お題は最近興味津々のWebサーバの実装です。
特にリリースとかは目指しませんが、
この企画を通してApacheとかlightyのソースを読める力をつけたいと思ってます。

それでは第1回目startです!
今回は最初なので雛形となる超シンプルなサーバを作ります。
今後、このソースを拡張していくことになります。

ということで、完成品がこちら↓
※ ようやく始めたGitHubに置いてます

hello.c

make
./hello 8080

で起動できます。

今回実装したのは、「クライアント(同時接続1)に対してhtmlファイルを応答するだけのサーバ」です。
特に難しいことはやってませんが、一応中身を説明しときます。

ソース解説

     1	#include <stdio.h>
     2	#include <stdlib.h>
     3	#include <string.h>
     4	#include <netinet/in.h>
     5	#include <fcntl.h>
     6	#include <errno.h>
     7	
     8	#define DOCUMENT_ROOT "./htdocs"
     9	#define BUF_SIZE 1024
    10	
    11	#define DEBUG

冒頭ではヘッダファイルの読み込みと、
ドキュメントルート、バッファサイズ、デバッグモードの指定をしています。

    13	int main(int argc, char *argv[]) {
    14	
    15		struct sockaddr_in server;
    16		int sockfd;
    17		int port = (argc == 2) ? atoi(argv[1]) : 80;
    18	
    19		// socket作成
    20		if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
    21			perror("socket");
    22			exit(EXIT_FAILURE);
    23		}

ここからmain関数に入ります。
まず、変数の定義とポートの指定(※)をしています。
※ コマンドライン引数がなければ80番を使用
次にTCPのsocketを作成しています。

    25		// Port, IPアドレスを設定(IPv4)
    26		memset(&server, 0, sizeof(server));
    27		server.sin_family = AF_INET;	
    28		server.sin_addr.s_addr = INADDR_ANY;
    29		server.sin_port = htons(port);
    30		
    31		// socketをすぐ再利用できるように
    32		char opt = 1;
    33		setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt));
    34	
    35		// ディスクリプタとPortを結び付ける
    36		if (bind(sockfd, (struct sockaddr *) &server, sizeof(server)) < 0) {
    37			perror("bind");
    38			exit(EXIT_FAILURE);
    39		}

お次は、作成したsocketをportと結び付けます。(bind)

一度clientから接続されると、プログラムを終了した後も一定時間bindできなくなるので、
setsockoptですぐ再利用できるように指定しています。
(が、効いてないようです。なんでだろ?macのせい…?)

    41		// listen準備
    42		if (listen(sockfd, SOMAXCONN) < 0) {
    43			perror("listen");
    44			exit(EXIT_FAILURE);
    45		}

portをlisten可能な状態にします。
listen()の第2引数は接続待ちできる数です。
SOMAXCONNはデフォルト値で、環境毎に異なります。

さて、これで準備は完了です。

    47		// メインループ
    48		while (1) {
    49			struct sockaddr_in client;
    50			int newfd;
    51	
    52			char buf[BUF_SIZE] = "";
    53			char method[BUF_SIZE] = "";
    54			char url[BUF_SIZE] = "";
    55			char protocol[BUF_SIZE] = "";
    56			int len;
    57	
    58			int filefd;
    59			char file[BUF_SIZE] = "";

いよいよメインループです。
クライアントからの処理を無限ループで処理します。
変数の説明は割愛します。

    61			// clientからの接続を受け付ける
    62			memset(&client, 0, sizeof(client));
    63			len = sizeof(client);
    64			if ((newfd = accept(sockfd, (struct sockaddr *) &client, &len)) < 0) {
    65				perror("accept");
    66				exit(EXIT_FAILURE);
    67			}

clientからの接続を受け付けます。
以降の処理はacceptが返したディスクリプタnewfdに対して行います。

    69			// request line読み込み
    70			if (recv(newfd, buf, sizeof(buf), 0) < 0) {
    71				perror("recv");
    72				exit(EXIT_FAILURE);
    73			}
    74			sscanf(buf, "%s %s %s", method, url, protocol);
    75	
    76			// request headerの終わりまで読み飛ばし
    77			// bodyは無視		
    78			do {
    79				if (strstr(buf, "\r\n\r\n")) {
    80					break;
    81				}
    82				if (strlen(buf) >= sizeof(buf)) {
    83					memset(&buf, 0, sizeof(buf));
    84				}
    85			} while (recv(newfd, buf+strlen(buf), sizeof(buf) - strlen(buf), 0) > 0);

clientからのリクエストを読み込みます。
request lineは「GET / HTTP/1.0」になってるはずなので、ssanfします。

すぐ応答を返してもおそらく処理してくれますが、
一応ヘッダの終わり(\r\n\r\n)まで読み飛ばします。
※ 1024バイトの倍数付近に\r\n\r\nが来るとダメです。
 あと、bodyがあることは想定してません。

    87			// ファイルパス生成(脆弱性有り)		
    88			sprintf(file, DOCUMENT_ROOT);
    89			strcat(file, url);		
    90
    91			// index.html補完
    92			if( file[strlen(file)-1] == '/' ) {
    93				strcat(file, "index.html" );
    94			}

「ドキュメントルート+リクエストファイル名」でファイルパスを作成してます。
末尾が「/」ならindex.htmlを追加します。

    96			// debug
    97			#ifdef DEBUG
    98				sleep(1);
    99			#endif

デバッグ用に1秒sleepさせます。
すぐ応答されると、同時接続数が増えても違いがわかりづらいので…

   101			// (最低限の)header送信
   102			char *header = "HTTP/1.0 200 OK\n"
   103									"Content-type: text/html\n"
   104									"\n";
   105			send(newfd, header, strlen(header), 0);
   106	
   107			//	body送信
   108			if ((filefd = open(file, O_RDONLY)) < 0) {
   109				perror("open");
   110				fprintf(stderr, "file: %s\n", file);
   111			}
   112			else {
   113				while ((len = read(filefd, buf, sizeof(buf))) > 0) {
   114					if (send(newfd, buf, len, 0) < 0) {
   115						perror("send2");
   116					}
   117				}
   118			}
   119	
   120			close(newfd);

最後にヘッダとファイルの中身を送信して、 ディスクリプタをcloseします。

これで1クライアントの処理が終わり、
ループ先頭に戻って次のクライアントを処理します。

まとめ

超適当な説明でしたが、今回のソースはこんな感じです。

C言語は大学でやった程度なので、
たったこれだけのプログラムを作るのにもかなり苦労しました^^

問題点だらけだと思うので、突っ込みいただけると幸いです。
ここどういう意味?とかも大歓迎です。

あ、脆弱性だらけなので、くれぐれも公開サーバ上で起動しないで下さい。
/etc/passwdとか見られちゃいますよ…


というところで、今回は終了です。
今後は、

  • マルチクライアント
  • CGI実行
  • html以外のファイル応答

あたりの機能を追加していきます。
乞うご期待・・・!?

C言語(NW)とWebサーバのお勉強「Portable minimal web servers」を読む

「Web Sockets云々の前にTCPのソケット通信わかってんの?」
「う゛…」

というやりとりが動機ってわけではないんですが、
最近、Webサーバの実装に興味深々で、
C言語でのネットワークプログラミングについて勉強を始めました。

そんな中見つけたIBMの軽量 Web サーバーという記事で、
「Portable minimal web servers」なる論文が紹介されていました。

非常に短くて素敵だったので、手始めにこいつをコードリーディングしてみることにしました。

予習

いきなりコードを見てもC初心者な僕にはさっぱりだと思ったので、
以下の4冊から関連しそうな部分を流し読みして、わかった気になっておきました。
これでもう怖くありません。
(嘘です、まだまだ読み返さないとわからないことだらけです。)

photo
UNIXネットワークプログラミング入門
技術評論社 2003-06-05

by G-Tools , 2010/02/21

photo
C言語によるプログラミング 応用編
システム計画研究所
オーム社 2002-09

by G-Tools , 2010/02/21

photo
Unix/Linuxプログラミング理論と実践
長尾 高弘
アスキー・メディアワークス 2008-04-21

by G-Tools , 2010/02/21

photo
C言語によるTCP/IPネットワークプログラミング
ピアソンエデュケーション 2001-09

by G-Tools , 2010/02/21

論文の内容

翻訳しようかと思いましたが、あんまりコードの説明もなさそうだったのでやめときました。

たぶん、

Apacheとか複雑なのもいいけど、シンプルさも大事でしょ?
ちょっとCとbashで作ってみたから、紹介するよ!
GPLだからご自由にどうぞ。

みたいなことが書いてあるはずです。

動作確認

ソースを読む前に、まずは動くか確認。
…以下の手順で問題なく動きました。

準備
cd /Users/t/c/szerver
mkdir htdocs

echo test > htdocs/index.html

設定変更
vi szerver.c
#define PORT 8080
#define DOCUMENTROOT "/Users/t/c/szerver/htdocs"

コンパイル
gcc szerver.c -o szerver

実行
./szerver.c
NW通信の警告popupが出たら「許可」

ブラウザから
http://localhost:8080/
testと表示されればOK

コードリーディング

元のソースはこちら(FTPです)

一部ハンガリー語があったので、
英訳→日訳しつつ読んでいきました。

最終的には下記のようにコメントをつけまくって理解したつもりになってます。
間違ってる部分があればご指摘いただけると幸いです。

いやぁやっぱ公開するコードの変数名とかは英語で書かないと駄目ですね。
非日本語圏の人が「gokei_keisan」とかに出くわしたら、やっぱテンパるんだろうなぁ…。

/* 
  ヘッダファイルのinclude
  TODO: 各ヘッダを何のために使ってるか
*/
#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>	//syslog出力
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fnmatch.h>
#include <libgen.h>

// デバッグログを表示したい時はdefineする
/*#define DEBUG*/

// listenポートとドキュメントルート
#define PORT 8080
#define DOCUMENTROOT "/Users/t/c/szerver/htdocs"

char		request_file[1024];	//
void		*memory_buffer;	// 応答するファイルの内容を読み込むバッファ
size_t	buffer_size;	// 読み込むファイルのサイズ(バッファのサイズ)
int			sock;	//

/*
  エラーをsyslogに吐く。
  hibaはerrorの意味
*/
void hiba( const char *hiba ){
	syslog( 0, hiba );
	exit(1);
}/*hiba*/

/*
  応答するファイルをmemory_bufferに読み込む
*/
int loadfile( ){
	struct stat	size;	// ファイル情報
	int infile;	// ディスクリプタ

	// ファイルの情報を取得
	if( stat( request_file, &size ) != 0 )
		hiba( "File not found.¥n" );

	// サイズを取得
	buffer_size = size.st_size;
	
	// サイズ文のバッファを確保
	memory_buffer=malloc( buffer_size );
	if( memory_buffer==NULL )
		hiba( "Out of memory.¥n" );

	// ファイルを開いて読み込む
	infile=open( request_file, O_RDONLY );
	if( read( infile, memory_buffer, buffer_size ) != buffer_size )
		hiba( "Unable to read file." );

	// ファイルを閉じる
	close( infile );
}/*sendfile*/	/* loadfileのタイポ */

/*
 	socketのbindまでを行う
 	kapuはport
 	nyitはopen
 	-asaは謎
*/
int kapu_nyitasa( void ){
	int kapu;
	struct sockaddr_in  barki; // サーバが接続待ちするIP、ポート情報(barki=any)

	// socketの作成
	// PF_INET(IPV4)
	// SOCK_STREAM(ストリーム)
	// 0(IPPROTO_TCPが指定される)
	kapu = socket (PF_INET, SOCK_STREAM, 0);
	if(kapu < 0)
		hiba( "Error open socket.¥n" );

	barki.sin_family=AF_INET; // アドレスファミリ(インターネット)
	barki.sin_port=htons(PORT);	// portをNWバイトオーダーで指定
	barki.sin_addr.s_addr=htonl(INADDR_ANY);	// ホスト上の全IP

	// ディスクリプタ(kapu)とポートのム結び付け
	if(bind(kapu, (struct sockaddr *) &barki, sizeof(barki)) < 0)
		hiba( "Error binding socket.¥n" );
	
	return kapu;
}/*kapu_nyitasa*/


/*
	olvasas = read
*/
int olvasas( int kapu ){
	char memoria[1024];  // リクエスト読み込みバッファ(memoria = memory)
	int n;
	
	// request_fileにDOCUMENTROOTを書き込む
	sprintf( request_file, DOCUMENTROOT );

	// memoriaにrequestを読み込む
	n=read(kapu, memoria, 1024);
	if( n < 0 )
		hiba( "Error reading socket.¥n" );

	// リクエストラインからファイル名を取得して、DOCUMENTROOT以降に追記
	sscanf(memoria, "GET %s", 
			&request_file[strlen(DOCUMENTROOT)] );
	
	// スラッシュで終わってたらindex.htmlを付ける
	if( request_file[strlen(request_file)-1]=='/' )
			sprintf(&request_file[strlen(request_file)], "index.html" );
	
	// デバッグログ
	#ifdef DEBUG
	syslog( 0, "File request: %s¥n", request_file );
	#endif

	return( n );
}/*olvasas*/
	
/*
 iras = writeっぽい
*/
int iras( int kapu ){
	// レスポンスのテンプレ
	char head_template[]="HTTP/1.0 200 OK¥r¥n¥
Server: miniserver/0.1¥r¥n¥
Connection: close¥r¥n¥
Content-Type: %s¥r¥n¥r¥n";

char	head[512];	// レスポンスするヘッダ
char  *type;
int		irt;

	// Content-Typeの設定
	type="text/html";
	if( fnmatch("*.gif", basename(request_file), 0) == 0 )
		type="image/gif";
	if( fnmatch("*.jpg", basename(request_file), 0) == 0 )
		type="image/jpeg";
	if( fnmatch("*.png", basename(request_file), 0) == 0 )
		type="image/png";
	
	// ヘッダを作成
	sprintf( head, head_template, type ); 

	// ヘッダの送信
	irt=write( kapu, head, strlen(head));
	
	// データの送信
	if( memory_buffer != NULL )
		irt=write( kapu, memory_buffer, buffer_size );
	else
		syslog( 0, "Error: no data to send.¥n" );
 
	return(1);
}/*iras*/

/*
  メインループ
*/
int  mainloop( void ){
	int new;	// 接続完了後のディスクリプタ
	int i;
	struct sockaddr_in clientname;	// 接続したクライアントの情報
	size_t size;

	// socketの作成
	sock=kapu_nyitasa();
	
	while( 1 ){
		// socketをlisten可能状態に
		if( listen (sock, 1) < 0 )
			hiba( "Error while listening." );

			// クライアントと接続
			size = sizeof (clientname);
			new = accept(sock, (struct sockaddr *) &clientname, &size);

			/*waitpid( WAIT_ANY, NULL, WNOHANG );*/
			// 子プロセスでリクエストを処理
			if( fork() != 0 ){
				olvasas( new );	// リクエストからファイル名取得
				loadfile(); // ファイルの読み込み
				iras( new ); // ヘッダ・データの送信
				shutdown( new, 2 ); // TODO: closeとの差とか
				if( memory_buffer != NULL )
					free(memory_buffer);
				return(0);
			}
	}/*while*/
}/*mainloop*/
 
/*
  終了系のシグナルを受けた時の処理
*/
void signal_handler( int signal ){
	shutdown( sock, 2 );
	hiba("Exiting...¥n");
}/*signal_handler*/	


/*
  main関数
*/
int main( int argc, char *argv[] ){
	// syslog準備&スタートメッセージ
	openlog( "webserver", LOG_PID, LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG) );
	syslog( 0, "Miniserver started." );
	
	// 終了系シグナルの処理
	signal( SIGINT, signal_handler );
	signal( SIGKILL, signal_handler );
	signal( SIGTERM, signal_handler );
	
	daemon( 0, 0 );	// daemon化(TODO: 初めて見た)
	mainloop();	// mainloop実行
	return(0);
}/*main*/

うーん、やっぱ自分でコード書いてみないとダメだなー。
頑張るかー。

mod_websocket for lighttpdをMacで動かしてみる3(ssl,wss)

うちのOS X10.6でも無事動いたmod_websocketですが、
証明書の設定が面倒でwss(SSL)を試してみませんでした。

実際始めてしまえばそんなに面倒じゃないんですが、
やるまでがね…。

ってわけで、手順を残しておきますので、ご自由にどうぞ。

なお、参考にしたのは以下のサイトです。
Webサーバ「lighttpd」でSSLを使うには − @IT
今日も明日もググったー: lighttpd ssl
Rails + lighttpd + SSL - プログラマ 福重 伸太朗 〜基本へ帰ろう〜

作業ディレクトリへ

cd lighty/lighttpd-1.4.26

lighttpdをSSL ONでコンパイルし直し

./configure --prefix=/Users/t/lighty/ --with-openssl (network-opensslがenableの方に表示されればOK) make make install cd ..

lighttpdの設定

vi etc/lighttpd/lighttpd.conf $SERVER["socket"] == ":8443" { ssl.engine = "enable" ssl.pemfile = "/Users/t/lighty/etc/lighttpd/ssl/server.pem" } :wq ※ ここではサボってますが、広く公開する場合等(っていうか原則)は、  ドキュメントルートを分けましょう。

オレオレ証明書の作成

mkdir etc/lighttpd/ssl/ openssl req -new -x509 -keyout server.pem -out server.pem Generating a 1024 bit RSA private key .........++++++ .............................++++++ writing new private key to 'server.pem' Enter PEM pass phrase: Verifying - Enter PEM pass phrase: Verify failure Enter PEM pass phrase:**** Verifying - Enter PEM pass phrase:**** ----- You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [AU]:JP State or Province Name (full name) [Some-State]:Tokyo Locality Name (eg, city) []:Koto Organization Name (eg, company) [Internet Widgits Pty Ltd]:Born Neet Organizational Unit Name (eg, section) []: Common Name (eg, YOUR name) []:localhost Email Address []: ls server.pem cd ~/lighty

起動

lighttpd -f etc/lighttpd/lighttpd.conf Enter PEM pass phrase: ****

HTTPSのテスト(ブラウザから)

https://localhost:8443/ test (証明書の警告は無視)

チャットサンプル(wss版)の準備

cp -p srv/www/htdocs/ws_chat.html srv/www/htdocs/wss_chat.html vi srv/www/htdocs/wss_chat.html var ws = new WebSocket("wss://127.0.0.1:8443/chat"); :wq

chatサーバの起動

mod_websocket/src/sample/ws_chat 9001 ポップアップが出るので許可する

Chatのテスト(Chromeから)

http://localhost:8080/wss_chat.html https://localhost:8443/wss_chat.html (wss通信だけじゃなくwss_chat.html取得の通信も暗号化すべき) 動いた!!

ん…wssってこれでいいんだっけ?

何か間違ってる気がするので突っ込みに期待…
…じゃなく後で勉強しとこう。

nori2048さんに早速突っ込みをいただきました。(2010/2/20 00:10)
コメント欄も合わせてご覧下さい。
(記事にも追記しています。)

Home

Page Top