Home > hello httpd
hello httpd Archive
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以外のファイル応答
あたりの機能を追加していきます。
乞うご期待・・・!?
Home > hello httpd
- Search
-
Loading
- Feeds
- Links
- スポンサードリンク