システムプログラム(第7週): ネットワーク・プログラミング/サーバ側

                                       筑波大学 システム情報工学研究科 
                                       コンピュータサイエンス専攻, 電子・情報工学系
                                       新城 靖
                                       <yas@is.tsukuba.ac.jp>

このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~syspro/2011/2011-06-08
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~syspro/2011/
http://www.coins.tsukuba.ac.jp/~yas/

今日の重要な話

復習

echoサーバ

TCP/IP のポート番号 7 (echo) では、受け取ったデータをそのまま返すサー ビスを提供している。以下は、これと同じような機能を提供するサーバである。

echo-server-nofork-fdopen.c

echo-server-nofork-fdopen.c のmain()

   1:	
   2:	/*
   3:	        echo-server-nofork-fdopen.c -- 受け取った文字列をそのまま返すサーバ(fork無し版)
   4:	        ~yas/syspro/ipc/echo-server-nofork-fdopen.c
   5:	        Created on 2004/05/09 19:08:47
   6:	*/
   7:	#include <stdio.h>
   8:	#include <stdlib.h>     /* exit() */
   9:	#include <sys/types.h>  /* socket(), wait4() */
  10:	#include <sys/socket.h> /* socket() */
  11:	#include <netinet/in.h> /* struct sockaddr_in */
  12:	#include <sys/resource.h> /* wait4() */
  13:	#include <sys/wait.h>   /* wait4() */
  14:	#include <netdb.h>      /* getnameinfo() */
  15:	#include <string.h>     /* strlen() */
  16:	
  17:	extern  void echo_server( int portno, int ip_version );
  18:	extern  void echo_receive_request_and_send_reply( int com );
  19:	extern  void print_my_host_port( int portno );
  20:	extern  void tcp_peeraddr_print( int com );
  21:	extern  void sockaddr_print( struct sockaddr *addrp, socklen_t addr_len );
  22:	extern  tcp_acc_port( int portno, int pf );
  23:	extern  int fdopen_sock( int sock, FILE **inp, FILE **outp );
  24:	
  25:	main( int argc, char *argv[] )
  26:	{
  27:	    int portno, ip_version;
  28:	        if( !(argc == 2 || argc==3) ) {
  29:	            fprintf(stderr,"Usage: %s portno {ipversion}\n",argv[0] );
  30:	            exit( 1 );
  31:	        }
  32:	        portno = strtol( argv[1],0,10 );
  33:	        if( argc == 3 )
  34:	            ip_version = strtol( argv[2],0,10 );
  35:	        else
  36:	            ip_version = 4; /* IPv4 by default */
  37:	        echo_server( portno, ip_version );
  38:	}
  39:	
main() は、引数として、ポート番号、IPのバージョン(4か6)を取る。 文字列で受け取ったポート番号を strtol() で int へ変換している。 IPのバージョンが省略されたら IPv4 とする。 最後に、echo_server() を呼ぶ。

echo-server-nofork-fdopen.c のecho_server()

echo_server() は、echoサーバのメインループである。

  40:	void
  41:	echo_server( int portno, int ip_version )
  42:	{
  43:	    int acc,com ;
  44:	        acc = tcp_acc_port( portno, ip_version );
  45:	        if( acc<0 )
  46:	            exit( -1 );
  47:	        print_my_host_port( portno );
  48:	        while( 1 )
  49:	        {
  50:	            if( (com = accept( acc,0,0 )) < 0 )
  51:	            {
  52:	                perror("accept");
  53:	                exit( -1 );
  54:	            }
  55:	            tcp_peeraddr_print( com );
  56:	            echo_receive_request_and_send_reply( com );
  57:	        }
  58:	}

tcp_acc_port() は、引数で与えられたポート番号を使って接続要求受付用ポー トを作成し、そのファイル記述子(ソケット)を返す。 このファイル記述子は、クライアント側とは異なり、そまままでは通信に 用いることはできない。 print_my_host_port() は、telnet で接続する時のヒントを表示する。

サーバのプログラムの特徴は、内部に無限ループを持っていることである。 サーバは、普通の状態では、終了しない。

accept() は、接続要求を待つシステムコールである。クライアントから接続 が来るまで、システムコールを実行したまま止まっているように見える。接続 要求が届くと、TCP/IP通信路の開設され、通信用ポートのファイル記述子が返 される。このファイル記述子は、クライアント側と同様に 標準入出力(0,1,2)や open() システム・コールの結果と同じもので、 ファイルに対する write() システムコールや read() システムコールの第一引数とし て使うことができる。つまり、write() システムコールを使うと、ネットワー クに対してデータを送り出すことができ、read() システムコールを使うとネッ トワークからデータを受け取ることができる。最後に不要になったら close() で解放する。

tcp_peeraddr_print() を呼び出し、通信相手(peer)のIPアドレスとポート番号 を表示する。

最後に、echo_receive_request_and_send_reply() を呼び出し、その通信相手 についての処理を行う。

echo_receive_request_and_send_reply()

echo_receive_request_and_send_reply() は、1つの通信相手のクライアントに 対してechoサービスを提供する。

  59:	
  60:	#define BUFFERSIZE      1024
  61:	
  62:	void
  63:	echo_receive_request_and_send_reply( int com )
  64:	{
  65:	    char line[BUFFERSIZE] ;
  66:	    int rcount ;
  67:	    int wcount ;
  68:	    FILE *in, *out ;
  69:	
  70:	        if( fdopen_sock(com,&in,&out) < 0 )
  71:	        {
  72:	            fprintf(stderr,"fdooen()\n");
  73:	            exit( 1 );
  74:	        }
  75:	        while( fgets(line,BUFFERSIZE,in) )
  76:	        {
  77:	            rcount = strlen( line );
  78:	            printf("[%d] received (fd==%d) %d bytes, [%s]\n",getpid(),com,rcount,line );
  79:	            fflush( stdout );
  80:	            fprintf(out,"%s",line );
  81:	        }
  82:	        printf("[%d] connection (fd==%d) closed.\n",getpid(),com );
  83:	        fclose( in );
  84:	        fclose( out );
  85:	}
  86:	
この echo_receive_request_and_send_reply() は、特定のクライアント専用の echo サービスを提供する。クライアントからの要求が続く限り、動作する。

このプログラムでは、 クライアント側 と同様に fdopen_sock() を使って、通信可能なファイル記 述子 com から2つの FILE * を作成している。1つは、入力用、1つは出力 用である。その結果、 高水準入出力ライブラリ を使って通信が行えるようになっている。fprintf() で出力用の FILE * に書 き込むと、ネットワークに対してデータが送り出される。入力用の FILE * に fgets() を行うと、ネットワークからデータを受け取ることができる。

クライアントからの要求は、fgets() で読込んでいる。それを、サーバ側の端 末に printf() で表示している。fflush() は、printf() の内部のバッファ (stdoutのバッファ)に溜っているデータを書き出すものである。

クライアントには、fprintf() で結果を送り返している。こちらは、 fdopen_sock() でバッファリングを抑止しているのでfflush() は不要になって いる。

echo-server-nofork-fdopenの実行例

サーバ側。 サーバは、終了しないので、最後に、^C を押して、割り込みを掛け て終了させる。

注意:全員がポート番号 1231 を使うとプログラムが動かないことがある。

$ ls -l echo-server-nofork-fdopen.c [←]
ls: echo-server-nofork-fdopen.c: No such file or directory
                              # ファイルが存在しないことの確認
$ cp ~yas/syspro/ipc/echo-server-nofork-fdopen.c . [←]
$ ls -l echo-server-nofork-fdopen.c [←]
-rw-r--r--  1 yas  prof  5165  5 31 20:41 echo-server-nofork-fdopen.c
$ make echo-server-nofork-fdopen [←]
cc     echo-server-nofork-fdopen.c   -o echo-server-nofork-fdopen
$ ./echo-server-nofork-fdopen  [←]
Usage: ./echo-server-nofork-fdopen portno {ipversion}
$ ./echo-server-nofork-fdopen 1231 [←]
run telnet cosmos10(v6) 1231 
[3058] connection (fd==4) from 130.158.86.150:53256
[3058] received (fd==4) 5 bytes, [123
]
[3058] received (fd==4) 5 bytes, [456
]
[3058] received (fd==4) 5 bytes, [789
]
[3058] connection (fd==4) closed.
^C
$ []
クライアント側
$ telnet cosmos10 1231 [←]
Trying 130.158.86.150...
Connected to cosmos10.coins.tsukuba.ac.jp.
Escape character is '^]'.
123[←]
123
456[←]
456
789[←]
789
^]
telnet> quit[←]
Connection closed.
$ []

複数のクライアントに対するサービスの同時提供

複数のクライアントに対してサービスの同時に提供することが望ましい。 echo-server-nofork-fdopen.c は、1つのクライアントからの接続されると、そのクライアントに掛り切りに なって、他のクライアントにはサービスを提供できないという問題がある。 標準の echo サーバ(ポート番号 7 で動作している) は、複数のクライアント から接続されれた場合、同時にサービスを提供することができる。

複数のクライアントに対してサービスの同時に提供するには次のような方法が ある。

詳しくは、来週 説明する。

HTTP

先週 説明した HTTP を復習しなさい。

HTTPの要求(オプション付き)

HTTPの要求は、 最低次の2行である。
GET /index.html HTTP/1.0←↓
←↓
HTTPの要求は、要求行(request line)から始まり、最後は、空行で終わる。 実際の Web ブラウザは、次のように要求行と空行の間にオプションを含む要求 を送る。
GET /index.html HTTP/1.0←↓
Host: www.coins.tsukuba.ac.jp←↓
User-Agent: Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10_5_8; ja-jp) AppleWebKit/531.22.7 (KHTML, like Gecko) Version/4.0.5 Safari/531.22.7←↓
Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5←↓
Accept-Language: ja-jp←↓
Accept-Encoding: gzip, deflate←↓
Connection: keep-alive←↓
←↓
空行は、オプションの終了を意味する。 オプションは全部無視しても、最低限の HTTP サーバを作成することはできる。 サーバとしては、送られてきた要求メッセージを全て受け取る必要はある。 (空行が現れるまで受け取り続ける必要がある。)

HTTP の応答としては、最低限次のような行が期待される。

HTTP/1.0 200 OK←↓
Content-Type: text/html←↓
←↓
<html><head></head><body>hello.</body></html>↓
1行目は、成功/失敗を示す行(status line)である。200 は、成功したことを意味する。 2行目は、HTTPの応答の型を意味する。この例では、内容は、HTML であることを意味する。 3行目は、空行であり、HTTP 応答のヘッダの終りを意味する。 空行以降は、内容である。この例では、HTML の内容が1行だけ含まれている。

http-server

以下のプログラムは、HTTP で要求を受け取り、 HTTで常に一定の内容を返すサーバである。 echoサーバ と似せて作ってあるので、まずは、echoサーバを読んで理解することを奨める。

http-server.c

http-server.c のmain()

   1:	
   2:	/*
   3:	        http-server-.c -- 常に同じ内容を返す HTTP サーバ(forkなし版)
   4:	        ~yas/syspro/ipc/http-server.c
   5:	        Created on: 2002/06/03 00:48:43
   6:	*/
   7:	#include <stdio.h>
   8:	#include <stdlib.h>     /* exit() */
   9:	#include <sys/types.h>  /* socket(), wait4() */
  10:	#include <sys/socket.h> /* socket() */
  11:	#include <netinet/in.h> /* struct sockaddr_in */
  12:	#include <sys/resource.h> /* wait4() */
  13:	#include <sys/wait.h>   /* wait4() */
  14:	#include <netdb.h>      /* getnameinfo() */
  15:	#include <string.h>     /* strlen() */
  16:	
  17:	extern  void http_server( int portno, int ip_version );
  18:	extern  void http_receive_request_and_send_reply( int com );
  19:	extern  int  http_receive_request( FILE *in );
  20:	extern  int  http_send_reply( FILE *out );
  21:	extern  int  http_send_reply_bad_request( FILE *out );
  22:	extern  void print_my_host_port_http( int portno );
  23:	extern  void tcp_peeraddr_print( int com );
  24:	extern  void sockaddr_print( struct sockaddr *addrp, socklen_t addr_len );
  25:	extern  int  tcp_acc_port( int portno, int ip_version );
  26:	extern  int  fdopen_sock( int sock, FILE **inp, FILE **outp );
  27:	extern  char *chomp( char *str );
  28:	
  29:	main( int argc, char *argv[] )
  30:	{
  31:	    int portno, ip_version;
  32:	        if( !(argc == 2 || argc==3) ) {
  33:	            fprintf(stderr,"Usage: %s portno {ipversion}\n",argv[0] );
  34:	            exit( 1 );
  35:	        }
  36:	        portno = strtol( argv[1],0,10 );
  37:	        if( argc == 3 )
  38:	            ip_version = strtol( argv[2],0,10 );
  39:	        else
  40:	            ip_version = 4; /* IPv4 by default */
  41:	        http_server( portno,ip_version );
  42:	}
  43:	
main() は、引数として、ポート番号を取る。 文字列で受け取ったポート番号を strtol() で int へ変換している。 最後に、http_server() を呼ぶ。 echo-server-nofork-fdopen.cのmain()とほとんど同じ。

http-server.c のhttp_server()

http_server() は、HTTPサーバのメインループである。

  44:	void
  45:	http_server( int portno, int ip_version )
  46:	{
  47:	    int acc,com ;
  48:	        acc = tcp_acc_port( portno, ip_version );
  49:	        if( acc<0 )
  50:	            exit( -1 );
  51:	        print_my_host_port_http( portno );
  52:	        while( 1 )
  53:	        {
  54:	            if( (com = accept( acc,0,0 )) < 0 )
  55:	            {
  56:	                perror("accept");
  57:	                exit( -1 );
  58:	            }
  59:	            tcp_peeraddr_print( com );
  60:	            http_receive_request_and_send_reply( com );
  61:	            printf("Replied.\n");
  62:	        }
  63:	}
  64:	

echo-server-nofork-fdopen.cのecho_server()とほとんど同じ。違いは、次の所だけ。

http-server.c の http_receive_request_and_send_reply()

http_receive_request_and_send_reply() は、 1つの通信相手のクライアントに対して HTTP により要求を受付け、 HTTPにより応答を返す。

  65:	#define BUFFERSIZE      1024
  66:	
  67:	void
  68:	http_receive_request_and_send_reply( int com )
  69:	{
  70:	    FILE *in, *out ;
  71:	    int err ;
  72:	        if( fdopen_sock(com,&in,&out) < 0 )
  73:	        {
  74:	            fprintf(stderr,"fdooen()\n");
  75:	            exit( 1 );
  76:	        }
  77:	        if( http_receive_request( in ) )
  78:	        {
  79:	            http_send_reply( out );
  80:	        }
  81:	        else
  82:	        {
  83:	            http_send_reply_bad_request( out );
  84:	        }
  85:	        fclose( in );
  86:	        fclose( out );
  87:	}
  88:	
この関数では、 echo-server-nofork-fdopen.cのecho_receive_request_and_send_reply() と同様に、 fdopen_sock() を使って、通信可能なファイル記 述子 com から2つの FILE * を作成している。

http_receive_request() を呼び出し、HTTP で要求を受け取っている。 もしエラーがなければ、http_send_reply() を呼び出し、HTTP で OK の応答を送信する。 エラーが有れば、http_send_reply_bad_request() を呼び出し、エラーの 応答を送信する。

http-server.c の http_receive_request()

http_receive_request()は、HTTP の要求を解析し、画面に表示している。
  89:	int
  90:	http_receive_request( FILE *in )
  91:	{
  92:	    char requestline[BUFFERSIZE] ;
  93:	    char rheader[BUFFERSIZE] ;
  94:	        
  95:	        if( fgets(requestline,BUFFERSIZE,in) <= 0 )
  96:	        {
  97:	            printf("No request line.\n");
  98:	            return( 0 );
  99:	        }
 100:	        chomp( requestline ); /* remove \r\n */
 101:	        printf("requestline is [%s]\n",requestline );
 102:	        if( strchr(requestline,'<') ||
 103:	            strstr(requestline,"..") )
 104:	        {
 105:	            printf("Dangerous request line found. exit.\n");
 106:	            return( 0 );
 107:	        }
 108:	
 109:	        while( fgets(rheader,BUFFERSIZE,in) )
 110:	        {
 111:	            chomp( rheader ); /* remove \r\n */
 112:	            if( strcmp(rheader,"") == 0 )
 113:	                break;
 114:	            printf("Ignored: %s\n",rheader );
 115:	        }
 116:	        return( 1 );
 117:	}
 118:	

http_receive_request() は、まず、HTTPの要求の最初の行(request line)を fgets() で読み込む。 次に、chomp() で行末の改行 \n\r を取り除く。

要求行の内容に対して簡単なエラーチェックをしている。次のような パタンが含まれるとエラーにしている。

最初の要求行に続き、オプションの行を読み出している。 オプションの最後は、空行で終わる。 空行が来るまで、オプションの行を読み出す。

このプログラムでは、オプションを一切使用していない。しかし、HTTP の要求 で送られてきた要求を全て読み出している。プロトコル上、必ず読み出す必要 がある。

http-server.c の http_send_reply()

http_send_reply()は、HTTP の応答メッセージを File *out に出力している。
 119:	int
 120:	http_send_reply( FILE *out )
 121:	{
 122:	        fprintf(out,"HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n");
 123:	        fprintf(out,"<html><head></head><body>hello.</body></html>\n");
 124:	}
 125:	

http_send_reply() は、HTTPのヘッダと本体を出力している。 ヘッダは、status line に Content-Typeが続く。 空行以降が本体である。

本体では、HTMLの文書を出力している。

http-server.c の http_send_reply_bad_request()

http_send_reply_bad_request()は、 HTTP の応答メッセージ(エラー)を File *out に出力している。
 126:	int
 127:	http_send_reply_bad_request( FILE *out )
 128:	{
 129:	        fprintf(out,"HTTP/1.0 400 Bad Request\r\nContent-Type: text/html\r\n\r\n");
 130:	        fprintf(out,"<html><head></head><body>400 Bad Request</body></html>\n");
 131:	}
 132:	

http_send_reply_bad_request() は、HTTPのヘッダと本体を出力している。 ヘッダは、status line (エラー)に Content-Typeが続く。 空行以降が本体である。

本体では、HTMLの文書を出力している。

http-serverの実行例

サーバ側。 サーバは、終了しないので、最後に、^C を押して、割り込みを掛け て終了させる。

注意:全員がポート番号 1231 を使うとプログラムが動かないことがある。

$ cp ~yas/syspro/ipc/http-server.c . [←]
$ make http-server [←]
cc     http-server.c   -o http-server
$ ./http-server  [←]
Usage: ./http-server portno {ipversion}
$ ./http-server 1231 4 [←]
open http://cosmos28(v6):1231/index.html
[15557] connection (fd==4) from 130.158.86.168:53547
requestline is [GET /index.html HTTP/1.1]
Ignored: Host: cosmos28:1231
Ignored: User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1
Ignored: Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Ignored: Accept-Language: ja,en-us;q=0.7,en;q=0.3
Ignored: Accept-Encoding: gzip, deflate
Ignored: Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7
Ignored: Keep-Alive: 115
Ignored: Connection: keep-alive
Replied.
[15557] connection (fd==4) from 130.158.86.168:53548
requestline is [GET /favicon.ico HTTP/1.1]
Ignored: Host: cosmos28:1231
Ignored: User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1
Ignored: Accept: image/png,image/*;q=0.8,*/*;q=0.5
Ignored: Accept-Language: ja,en-us;q=0.7,en;q=0.3
Ignored: Accept-Encoding: gzip, deflate
Ignored: Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7
Ignored: Keep-Alive: 115
Ignored: Connection: keep-alive
Replied.
^C
$ []
クライアント側
$ open http://cosmos28:1231/index.html [←]
Safari や Firefox で "http://cosmos10:1231/index.html" を開く。

Javaによるechoサーバ

echoサーバの細かい所

以下の内容は、飛ばしてよい。

print_my_host_port()

print_my_host_port() は、telnet で接続する時のヒントを表示する。

tcp_peeraddr_print()

tcp_peeraddr_print() は、通信相手(peer)のアドレス(TCP/IPの場合、IPアド レスとポート番号)を表示する。

sockaddr_print()

sockaddr_print() は、引数で与えられた struct sockaddr に含まれる IP アドレスとポート番号を画面に表示する関数である。

tcp_acc_port()

tcp_acc_port() は、 通信路の開設 の仕事のうち、サーバ側で接続要求受付用ポートを作る関数である。

fdopen_sock()

fdopen_sock() は、TCP/IP による通信を、fprintf(), fgets(), fread() 等で 行えるようにする関数である。 この関数は、 クライアント側 とまったく同じである。

http-serverの細かい所

以下の内容は、飛ばしてよい。

http-server.c の print_my_host_port_http()

echo-server-nofork-fdopen.cのprint_my_host_port() と同様に、画面にサーバのホスト名 とポート番号を表示している。ただし、Web ブラウザでアクセスする時に便利 なように、URL の形をしている。

http-server.c のtcp_peeraddr_print()

echo-server-nofork-fdopen.cのtcp_peeraddr_print()と同じ。

http-server.c のsockaddr_print()

echo-server-nofork-fdopen.cのsockaddr_print()と同じ。

http-server.c のtcp_acc_port()

echo-server-nofork-fdopen.cのtcp_acc_port()と同じ。

http-server.c のfdopen_sock()

echo-server-nofork-fdopen.cのfdopen_sock()と同じ。

chomp()

chomp() は、行末の \n\r を削除する関数である。

練習問題

練習問題(701) echo-serverのコンパイルと実行

echo-server-nofork-fdopen.c を、コンパイルして実行しなさい。 次のクライアントでアクセスしなさい。

練習問題(702) http-serverのコンパイルと実行

http-server.c を、コンパイルして実行しなさい。 次のクライアントでアクセスしなさい。 画面に現れる次の部分に着目しなさい。

練習問題(703) HTTPサーバの応答(テキスト版)

コマンドラインからファイル名を受け取り、画面に対して HTTPの応答を返すプ ログラムを作成しなさい。
$ cp ~yas/syspro/ipc/http-response.c . [←]
$ emacs http-response.c [←]
$ cc http-response.c -o http-response [←]
$ ./http-response /index.html [←]
HTTP/1.0 200 OK
Content-Type: text/html

<HTML>
中略
</HTML>
$ []
この課題では、セキュリティに気をつけなさい。必ず次の条件を満たすように しなさい。 次のようなプログラムを作成すればよい。
  1. main() の引数 argv[1] をファイル名とする。
  2. 1. のファイル名、環境変数 HOME、"/public_html" からファイル名を作 成する。snprinf() を用いると便利である。(strcat() は用いないこと。)
  3. ファイルを fopen() で開く。成功すれば、次の処理を行う。
    1. "HTTP/1.0 200 OK\r\n" の行を fprintf() で stdout に出力する。
    2. Content-Type: の行を fprintf() で stdout に出力する。 拡張子の取り出しには、strrchr()、比較には、strcmp() を使う方法がある。
    3. 空行を fprintf() で stdout に出力する。
    4. ファイルの内容をを fread() し、stdout に対して fwrite() で出力する。 ファイルの末尾まで繰り返す。 fread() と fwrite() の使い方については、 前半の資料 を参考にしなさい。 なお、この課題では、対象がテキストだけなので、fgets() と fprintf() を 使ってもよい。バイナリを扱う時には、fgets() と fprintf() では不十分である。
    5. fclose() でファイルを閉じる。
  4. ファイルがなければ、"HTTP/1.0 404 Not Found<以下省略>" のエラー メッセージを出力する。
エラーが生じた時、Apache は、Content-Type: text/html で HTML を出力して いる。これを Web ブラウザや telnet コマンドで確認しなさい。そして、それ と類似の結果を stdout に出力しなさい。

なお、この課題は、HTTP の応答を画面(stdout)に出力するが、ネットワーク通 信を一切行わない。

URL としてディレクトリ名が指定された場合、Apache 等の HTTP サーバは、そ のディレクトリにある特定のファイルが指定されたものとして扱う。たとえば、 "/" という URL が与えられた時には "/index.html", "/dir/" ならば "/dir/index.html" が指定されたものとして扱う。この課題では、この機能を 実装しなくてもよい。ディレクトリ名が指定された場合には、エラーにしても よい。また、Apache と同様に特定のファイル名が指定されたものとして扱って もよい。 この課題では、ディレクトリかどうかの判定は不要であるが、判定したい場合 には stat() システム・コールを使う方法がある。

練習問題(704) HTTPサーバの応答(バイナリ版)

練習問題(703) を拡張して、 バイナリ・ファイルを扱えるようにしなさい。 次の型(Content-Type:)を扱えるようにしなさい。
Content-Type: 拡張子
text/html .html
text/plain .txt
image/gif .gif
image/jpeg .jpeg
image/png .png
バイナリの場合、実行結果を端末に表示すると画面には意味不明のデータが出 力されることになる。正しく動作していることを示すには、標準出力をファイ ルに保存して、その一部を示したり、元のファイルや結果のファイルのバイト 数を示すとよい。

$ ./http-response /picture.jpeg > result.data [←]
$ head -3 result.data [←]
(最初の3行が表示される)
$ head -3 result.data | wc [←]
$ ls -l result.data [←]
$ ls -l ~/public_html/picture.jpeg [←]

練習問題(705) HTTPサーバの要求解析

標準入力から HTTP の GET 要求を受け取り、要求行からファイル名を取り出す プログラムを作成しなさい。
$ cp ~yas/syspro/ipc/http-request-analyze.c . [←]
$ emacs http-request-analyze.c [←]
$ cc http-request-analyze.c -o http-request-analyze [←]
$ cat > reqest.data [←]
GET /index.html HTTP/1.0[←]
Accept: image/png,*/*;q=0.5[←]
Accept-Language: ja,en;q=0.7,en-us;q=0.3[←]
[←]
^D
$ ./http-request-analyze < reqest.data [←]
/index.html
$ [←]
このプログラムは、次のような動作を行う。
  1. stdin から fgets() で 1 行読み込む。行末の \r や \n や \r\n を削除する。 chomp()を使うと便利である。
  2. 最初の行を保存する。
  3. 2行目以降、空行が出てくるまで読み捨てる。
  4. 空行が出てくれば、ループを抜け、保存してある最初の行を解析する。
クライアントは、不正な要求行を送ってくる可能性がある。 そのような場合には、エラーにしなさい。 たとえば、次のようなデータを送ってくる可能性がある。 このような場合も、エラーとして扱い、プログラムがクラッシュしたり、バッ ファ・オーバーフローを起こしたりすることがないようにしなさい。

なお、この課題は、HTTP の要求を標準入力(stdin)から読むが、ネットワーク 通信を一切行わない。

この課題を行う場合、次のファイルに含まれる関数を利用してもよい。

このファイルには、次のような関数が含まれている。
int string_split( char *str, char del, int *countp, char ***vecp )
文字列 str を、区切り文字 del で分解する。 結果を、countp と vecp に返す。 ここで、count と vec は、 main() の引数の argc, argv と同じ形式になっている。
void free_string_vector( int count, char **vec )
string_split() で得た vec のメモリを解放する。
これらの関数は、次のようにして使う。
    int count;
    char **vec;
    int i ;
        str = "GET /index.html HTTP/1.0";
	if( string_split( str, ' ', &count, &vec ) < 0 )
	{
	    perror("string_split-malloc");
	    exit( 1 );
	}
	for( i=0 ; i< count; i++ )
	    printf("%d: %s\n", i, vec[i] );
	free_string_vector( count, vec );

練習問題(706) HTTPサーバ

まず、 練習問題(704) を実施しなさい。 次に、 練習問題(705) を実施しなさい。 そして、その時作成した関数を用いて、HTTP サーバを作成しなさい。 この課題を行う場合、http-server.c に含まれる関数を利用してもよい。

実際の WWW ブラウザ (Firefoxなど)で、動作を確認しなさい。telnet だけで は、きちんと HTTP のプロトコルに従っているか確認できないので、不十分で ある。

練習問題(707) IPアドレスによるアクセス制御が可能なHTTPサーバ

練習問題(706) で、学類内の計算機からの接続要求だけを受け付けるようにしなさい。 たとえば、クライアントのIPアドレスを getpeername() で調べ、 130.158.86.0-130.158.87.255 の範囲についてのみアクセスを許可するように することが考えられる。

練習問題(708) ドメイン名によるアクセス制御が可能なHTTPサーバ

練習問題(707) で、ドメイン名 coins.tsukuba.ac.jp を持つホストからのアクセスだけを許す ようにしなさい。

練習問題(709) カレンダを表示するHTTPサーバ

要求された月のカレンダーを表示するような HTTP サーバを作成しなさい。 要求の与え方とては、次のような方法が考えられる。
GET /?month=6&year=2011 HTTP/1.0[←]
[←]
カレンダの出力については、cal コマンドを実行した結果を用いても良い。こ れに対して、HTTP で結果を返す。Content-Type: としては、text/plain に するか、text/html で <PRE></PRE>を使う方法が 考えられる。その他の手法でもよい。

このサーバを実行するための HTML の form を用意しなさい。

実際の WWW ブラウザ (Firefoxなど)で、動作を確認しなさい。telnet だけで は、きちんと HTTP のプロトコルに従っているか確認できないので、不十分で ある。

練習問題(710) CGIが実行可能なHTTPサーバ

練習問題(706) で、CGI により外部のプログラムを実行することができるようにしなさい。拡 張子が .cgi のプログラムを CGI と見なし、プログラムを実行し、そ の結果をクライアントに返しなさい。

実際の WWW ブラウザ (Firefoxなど)で、動作を確認しなさい。telnet だけで は、きちんと HTTP のプロトコルに従っているか確認できないので、不十分で ある。

練習問題(711) spipe-last

クライアントから送られてくるデータを、コマンドの標準入力に接続して実行 するプログラムを作りなさい。たとえば、次のようなコマンドを実行すること を考える。

% ./spipe-last 1231 head -5 [←]
<クライアントから送られてきた文字列のうち、先頭の5行だけがサーバの画面に表示される>
% []
spipe-last は、指定されたポート番号で tcp_acc_port() を行う。 クライアントからの接続要求を受け付けた後、不要なポートを閉じ、標準出力を切 り替えて、コマンドラインから指定されたコマンドを実行する。 すなわち、dup(), dup2(), close() などで、標準出力を切り替えて、 execve() などで、プログラムを実行する。 コマンドを実行したらそのまま終了する。

クライアントとしては、telnet を用いてもよい。 または、以下の spipe-first を用いることもできる。

この課題では、fork() を行わないことを推奨する。 TCP/IP なら本来双方向で使えるが、この練習問題では単方向だけを 使うことで、パイプの機能を代替する。

また、fork() とパイプを使う実現方法もある。コマンドを実行するする時に fork() して実行し、その標準入力をパイプにして、TCP/IP で受け取ったデー タをパイプに書き込んでもよい。

練習問題(712) spipe-first

練習問題(711) spipe-last のクライアントを作成しなさい。 このクライアントは、引数で指定されたホスト名とポート番号のサーバに接続 する。そして、引数で指定されたコマンドを実行する。 たとえば、次のように実行された場合、spipe-first は、ls -l の結果を画面 に表示される代わりにサーバに送られるようにする。
% ./spipe-first hostname port ls -l [←]
spipe-first は、指定されたホスト名とポート番号でtcp_connect() を行う。 不要なポートを閉じ、標準出力を切り替えた後、指定されたコマンドを実行す る。すなわち、dup(), dup2(), close() などで、標準入出力を切り替えて、 execve() などで、プログラムを実行する。 コマンドを実行したらそのまま終了する。

この課題では、spipe-last と同様に、fork() を行わないことを推奨する。

練習問題(713) spipe-middle

練習問題(711) spipe-lastサーバと 練習問題(712) spipe-firstクライアントの 間に入るプロキシ spipe-middle を作成しなさい。 このプロキシは、次のような引数をとる。
% ./spipe-middle myport serverhost serverport sort +4 [←]
spipe-middle は、第2引数と第3引数で指定されたサーバにtcp_connect() で接 続要求を行う。 tcp_acc_port() により、第1引数で指定された ポート番号で接続受付用ポートを作成する。 spipe-first から接続要求を受け取ると、 不要なポートを閉じ、標準入出力を切り替える。そして、第4引数以降で指定さ れたコマンドを実行する。

練習問題(714) spipeでのクライアントとサーバの入れ替え

練習問題(711) spipe-last練習問題(712) spipe-firstで、 TCP/IPのクライアントとサーバを入れ替えてみなさい。 練習問題(713) spipe-middleも クライアントとサーバを入れ替えなさい。

練習問題(715) fingerサーバ

finger サーバを作成しなさい。これは、受け取った文字列を引数にして、 finger コマンドを実行するとよい。そして、実行結果を、接続先に返すよう にするとよい。

finger コマンドのプロセスと finger サーバの間は、パイプで接続しなさい。 今までの例題で利用した方法を組み合わせることで実現することができるはず である(pipe(),fork(),dup(),close(),execve()など)。popen() ライブラリ 関数を利用してもよい。

finger コマンドのプロセスと finger サーバ間をパイプで結ぶ代わりに、 finger コマンドを exec する前に、配管工事をして、標準出力(1)をTCP/IPの ストリームに接続する方法もある。

受け取った文字列をそのまま popen() や system() などに渡すのは危険である。 というのも、これらの引数は、シェル(/bin/sh)が解釈するからである。 popen() や system() を使う場合には、 finger コ マンドが受付けるのに相応しいもの( isalpha() や isdigit() の並び)かどう かを検査しなさい。もし、シェルが解釈する特殊な文字列(|;<> など)が含まれていると、意図しないプログラムが実行させられることがある。

fork() や execve() を用いて直接 /usr/bin/finger を実行する方法はシェル を経由しないので、比較的安全である。

getpwnam() ライブラリ関数や utmp ファイルを用いて、finger コマンドと似たような 動きを実現してもよい。

練習問題(716) HTTP Proxy

HTTP proxy 作りなさい。次の機能を、1つ以上付けなさい。

練習問題(717) サーバ自由課題

その他、HTTPサーバ、または、 HTTP Proxy と同程度以上の複 雑さを持つサーバを作成しなさい。

練習問題(718) IPアドレスをホスト名に逆変換する

上の tcp_peeraddr_print() は、IP アドレスを数字で表示していた。 これを、ホスト名で表示するようにしなさい。

練習問題(719) utmpの内容を表示するHTTPサーバ

utmpの内容を表示するような HTTP サーバを作成しなさい。 HTTPにより要求を受け取り、結果をHTMLの<TABLE>で表現して HTTPで返しなさい。

実際の WWW ブラウザ (Firefoxなど)で、動作を確認しなさい。telnet だけで は、きちんと HTTP のプロトコルに従っているか確認できないので、不十分で ある。

練習問題(720) ディレクトリの内容を表示するHTTPサーバ

opendir() ライブラリ関数を用いて、ディレクトリの内容を表示するような HTTP サーバを作成しなさい。HTTPにより要求を受け取り、結果をHTMLで表現し てHTTPの応答として返しなさい。ディレクトリの内部に子供のディレクトリが 含まれていた場合、それをハイパーリンクとして表現して返し、その子供のディ レクトリについても表示できるようにしなさい。

実際の WWW ブラウザ (Firefoxなど)で、動作を確認しなさい。telnet だけで は、きちんと HTTP のプロトコルに従っているか確認できないので、不十分で ある。


Last updated: 2011/06/15 12:14:55
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>