Born Neet
TinySegmenterをiPhone(Objective-C)に移植してみました
- 2010-03-28 (Sun)
- iPhone/iPad/IPod Touch
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週間ぐらいなので、
ダメダメなコードになってると思われますので、
突っ込みをいただけるとありがたいです。
以上。このライブラリを使っておもしろいボットが作られれば幸いです。
それでは!
初iPhoneアプリ「JavaScript Anywhere」が公開されました
- 2010-03-26 (Fri)
- jsany
Objective-Cはもちろん、X-Codeもろくに使ったことのなかった僕が、
試行錯誤しながら作った初のiPhoneアプリが公開されました。
その名も「JavaScript Anywhere」
名前の通り(?)JavaScriptの簡易開発環境です。
機能はまだまだ少なく、
HTMLとCSS、JSを1ファイルずつ編集して、内蔵ブラウザで実行できるだけです。
とまぁそれだけのアプリですが『満員電車でもJavaScriptを弄っていたい。』
そんな変態にはオススメかもしれません。
スクリーンショットとかは以下のページに載せてあります。
JSAnywhere紹介ページ
無料ですので、少しでも興味のある方はどうぞ。
機能追加のアイディアも募集しております。
以上、宣伝でした。
Hello, httpd! C言語でWebServerを作ってみよう [第1回目]
- 2010-03-07 (Sun)
- hello httpd
さて、久々の勝手に連載企画を始めてみます。
(ホントは先週に始まってる予定だったんですが…忙しすぎました)
お題は最近興味津々の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」を読む
- 2010-02-21 (Sun)
- Webサーバ
「Web Sockets云々の前にTCPのソケット通信わかってんの?」
「う゛…」
というやりとりが動機ってわけではないんですが、
最近、Webサーバの実装に興味深々で、
C言語でのネットワークプログラミングについて勉強を始めました。
そんな中見つけたIBMの軽量 Web サーバーという記事で、
「Portable minimal web servers」なる論文が紹介されていました。
非常に短くて素敵だったので、手始めにこいつをコードリーディングしてみることにしました。
予習
いきなりコードを見てもC初心者な僕にはさっぱりだと思ったので、
以下の4冊から関連しそうな部分を流し読みして、わかった気になっておきました。
これでもう怖くありません。
(嘘です、まだまだ読み返さないとわからないことだらけです。)
論文の内容
翻訳しようかと思いましたが、あんまりコードの説明もなさそうだったのでやめときました。
たぶん、
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)
- 2010-02-19 (Fri)
- html5
うちの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)
コメント欄も合わせてご覧下さい。
(記事にも追記しています。)




