筑波大学 システム情報系 情報工学域
                                       新城 靖
                                       <yas@cs.tsukuba.ac.jp>
このページは、次の URL にあります。
	http://www.coins.tsukuba.ac.jp/~syspro/2013/2013-05-29
あるいは、次のページから手繰っていくこともできます。
	http://www.coins.tsukuba.ac.jp/~syspro/2013/
	http://www.coins.tsukuba.ac.jp/~yas/
fprintf() を使えば、snprintf() や strcpy() は多くの場合で不要になる。
画面に表示するプログラム(1)
main()
{
       printf("hello %s %d\n","world",100);
}
画面に表示するプログラム(2)
#include <stdio.h>
main()
{
       fprintf(stdout,"hello %s %d\n","world",100);
}
ネットワークに出力するプログラム
{
       fprintf(out,"hello %s %d\n","world",100);
}
   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  int  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() を呼ぶ。
  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:	            printf("[%d] accepting incoming connections (fd==%d) ...\n",getpid(),acc );
  51:	            if( (com = accept( acc,0,0 )) < 0 )
  52:	            {
  53:	                perror("accept");
  54:	                exit( -1 );
  55:	            }
  56:	            tcp_peeraddr_print( com );
  57:	            echo_receive_request_and_send_reply( com );
  58:	        }
  59:	}
  60:	
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() を呼び出し、その通信相手 についての処理を行う。
  61:	#define BUFFERSIZE      1024
  62:	
  63:	void
  64:	echo_receive_request_and_send_reply( int com )
  65:	{
  66:	    char line[BUFFERSIZE] ;
  67:	    int rcount ;
  68:	    int wcount ;
  69:	    FILE *in, *out ;
  70:	
  71:	        if( fdopen_sock(com,&in,&out) < 0 )
  72:	        {
  73:	            fprintf(stderr,"fdooen()\n");
  74:	            exit( 1 );
  75:	        }
  76:	        while( fgets(line,BUFFERSIZE,in) )
  77:	        {
  78:	            rcount = strlen( line );
  79:	            printf("[%d] received (fd==%d) %d bytes, [%s]\n",getpid(),com,rcount,line );
  80:	            fflush( stdout );
  81:	            fprintf(out,"%s",line );
  82:	        }
  83:	        printf("[%d] connection (fd==%d) closed.\n",getpid(),com );
  84:	        fclose( in );
  85:	        fclose( out );
  86:	}
  87:	
この 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() は不要になって いる。
サーバ側。 サーバは、終了しないので、最後に、^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  5246  5 28 15:13 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 cosmos30(v6) 1231 
[3799] accepting incoming connections (fd==3) ...
[3799] connection (fd==4) from 130.158.86.170:49961
[3799] received (fd==4) 5 bytes, [abc
]
[3799] received (fd==4) 5 bytes, [def
]
[3799] received (fd==4) 5 bytes, [ghi
]
[3799] connection (fd==4) closed.
[3799] accepting incoming connections (fd==3) ...
^C
$ 
クライアント側
$ telnet cosmos30 1231 
Trying 130.158.86.170...
Connected to cosmos30.coins.tsukuba.ac.jp.
Escape character is '^]'.
abc
abc
def
def
ghi
ghi
^]
telnet> quit
Connection closed.
$ 
複数のクライアントに対してサービスの同時に提供するには次のような方法が ある。
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行だけ含まれている。
   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()とほとんど同じ。
  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:	            printf("[%d] accepting incoming connections (fd==%d) ...\n",getpid(),acc );
  55:	            if( (com = accept( acc,0,0 )) < 0 )
  56:	            {
  57:	                perror("accept");
  58:	                exit( -1 );
  59:	            }
  60:	            tcp_peeraddr_print( com );
  61:	            http_receive_request_and_send_reply( com );
  62:	        }
  63:	}
  64:	
echo-server-nofork-fdopen.cのecho_server()とほとんど同じ。違いは、次の所だけ。
  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:	        printf("[%d] Replied\n",getpid() );
  86:	        fclose( in );
  87:	        fclose( out );
  88:	}
  89:	
この関数では、
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() を呼び出し、エラーの 応答を送信する。
  90:	int
  91:	http_receive_request( FILE *in )
  92:	{
  93:	    char requestline[BUFFERSIZE] ;
  94:	    char rheader[BUFFERSIZE] ;
  95:	        
  96:	        if( fgets(requestline,BUFFERSIZE,in) <= 0 )
  97:	        {
  98:	            printf("No request line.\n");
  99:	            return( 0 );
 100:	        }
 101:	        chomp( requestline ); /* remove \r\n */
 102:	        printf("requestline is [%s]\n",requestline );
 103:	        if( strchr(requestline,'<') ||
 104:	            strstr(requestline,"..") )
 105:	        {
 106:	            printf("Dangerous request line found.\n");
 107:	            return( 0 );
 108:	        }
 109:	
 110:	        while( fgets(rheader,BUFFERSIZE,in) )
 111:	        {
 112:	            chomp( rheader ); /* remove \r\n */
 113:	            if( strcmp(rheader,"") == 0 )
 114:	                break;
 115:	            printf("Ignored: %s\n",rheader );
 116:	        }
 117:	        return( 1 );
 118:	}
 119:	
http_receive_request() は、まず、HTTPの要求の最初の行(request line)を
fgets() で読み込む。
次に、chomp() で行末の改行 \n や \r を取り除く。
要求行の内容に対して簡単なエラーチェックをしている。次のような パタンが含まれるとエラーにしている。
< が現れる
".." が現れる
最初の要求行に続き、オプションの行を読み出している。 オプションの最後は、空行で終わる。 空行が来るまで、オプションの行を読み出す。
このプログラムでは、オプションを一切使用していない。しかし、HTTP の要求 で送られてきた要求を全て読み出している。プロトコル上、必ず読み出す必要 がある。
 120:	int
 121:	http_send_reply( FILE *out )
 122:	{
 123:	        fprintf(out,"HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n");
 124:	        fprintf(out,"<html><head></head><body>hello.</body></html>\n");
 125:	}
 126:	
http_send_reply() は、HTTPのヘッダと本体を出力している。 ヘッダは、status line に Content-Typeが続く。 空行以降が本体である。
本体では、HTMLの文書を出力している。
 127:	int
 128:	http_send_reply_bad_request( FILE *out )
 129:	{
 130:	        fprintf(out,"HTTP/1.0 400 Bad Request\r\nContent-Type: text/html\r\n\r\n");
 131:	        fprintf(out,"<html><head></head><body>400 Bad Request</body></html>\n");
 132:	}
 133:	
http_send_reply_bad_request() は、HTTPのヘッダと本体を出力している。 ヘッダは、status line (エラー)に Content-Typeが続く。 空行以降が本体である。
本体では、HTMLの文書を出力している。
サーバ側。 サーバは、終了しないので、最後に、^C を押して、割り込みを掛け て終了させる。
注意:全員がポート番号 1231 を使うとプログラムが動かないことがある。
$ ls -l http-server.c 
ls: http-server.c: No such file or directory
$ cp ~yas/syspro/ipc/http-server.c . 
$ ls -l http-server.c 
-rw-r--r--  1 yas  prof  6549  5 28 15:29 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://cosmos30(v6):1231/index.html
[3825] accepting incoming connections (fd==3) ...
[3825] connection (fd==4) from 130.158.83.140:58779
requestline is [GET /index.html HTTP/1.1]
Ignored: Host: cosmos30.coins.tsukuba.ac.jp:1231
Ignored: User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:12.0) Gecko/20100101 Firefox/12.0
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: Connection: keep-alive
[3825] Replied
[3825] accepting incoming connections (fd==3) ...
[3825] connection (fd==4) from 130.158.83.140:58780
requestline is [GET /favicon.ico HTTP/1.1]
Ignored: Host: cosmos30.coins.tsukuba.ac.jp:1231
Ignored: User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:12.0) Gecko/20100101 Firefox/12.0
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: Connection: keep-alive
[3825] Replied
[3825] accepting incoming connections (fd==3) ...
[3825] connection (fd==4) from 130.158.83.140:58781
requestline is [GET /favicon.ico HTTP/1.1]
Ignored: Host: cosmos30.coins.tsukuba.ac.jp:1231
Ignored: User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:12.0) Gecko/20100101 Firefox/12.0
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: Connection: keep-alive
[3825] Replied
[3825] accepting incoming connections (fd==3) ...
^C
$ 
クライアント側
$ open http://cosmos30:1231/index.html 
Safari や Firefox で "http://cosmos30:1231/index.html" を開く。
\n や\r を削除する関数である。
requestline is [...])
Ignored: ...)
$ cp ~yas/syspro/ipc/http-request-analyze.c . 
$ emacs http-request-analyze.c 
$ cc http-request-analyze.c -o http-request-analyze 
$ ./http-request-analyze < ~syspro/public_html/2013/2013-05-29/http-good-request.txt 
filename is [/index.html]
$ 
このプログラムは、次のような動作を行う。
$ ./http-request-analyze < ~syspro/public_html/2013/2013-05-29/http-bad-request-1.txt 
Bad Request.
$ 
$ ./http-request-analyze < ~syspro/public_html/2013/2013-05-29/http-bad-request-2.txt 
Bad Request.
$ 
$ ./http-request-analyze < ~syspro/public_html/2013/2013-05-29/http-bad-request-3.txt 
Bad Request.
$ 
なお、この課題は、HTTP の要求を標準入力(stdin)から読むが、ネットワーク 通信を一切行わない。
この課題を行う場合、次のファイルに含まれる関数を利用してもよい。
~yas/syspro/string/string-split.c
int string_split( char *str, char del, int *countp, char ***vecp  )
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 );
$ 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>
$ 
この課題では、セキュリティに気をつけなさい。必ず次の条件を満たすように
しなさい。
~/public_html)以下のファイルし
かアクセスできないようにすること。それには、ファイルを開く(open(),
fopen())時に、頭に
環境変数 
HOME の値と "/public_html" を
snprintf() 等
で付加する方法がある。環境変数は、
ライブラリ関数
getenv() 
を使うと入手できる。
char *home;
     home = getenv("HOME");
     ...
他の方法としては、プログラムの中に定数として埋め込む方法が考えられる。
なお、「~」 は、open() システム・
コールや fopen() ライブラリ関数では使えない。「~」 は、シェルや 
emacs が独自にホーム・ディレクトリを調べて置き換えている。
"HTTP/1.0 200 OK<以下省略>" を、ファイルが存在しなけれ
ば、"HTTP/1.0 404 Not Found<以下省略>" を返す。
| Content-Type: | 拡張子 | 
|---|---|
| text/html | .html | 
| text/plain | .txt | 
| text/plain | .text | 
Content-Type: とし
て、application/octet-stream を返してよい。
"file.html.txt" の場合は、".txt" を拡張子として扱いなさい。
"HTTP/1.0 200 OK\r\n" の行を fprintf() で stdout に出力する。
Content-Type: の行を fprintf() で stdout に出力する。
拡張子の取り出しには、strrchr()、比較には、strcmp() を使う方法がある。
strstr() では、複数の拡張子に対応できない場合が多いので注意すること。
"HTTP/1.0 404 Not Found\r\n" の行を fprintf() で stdout に出力する。
"Content-Type: text/html"の行を fprintf() で stdout に出力する。
なお、この課題は、HTTP の応答を画面(stdout)に出力するが、ネットワーク通 信を一切行わない。
URL としてディレクトリ名が指定された場合、Apache 等の HTTP サーバは、そ のディレクトリにある特定のファイルが指定されたものとして扱う。たとえば、 "/" という URL が与えられた時には "/index.html", "/dir/" ならば "/dir/index.html" が指定されたものとして扱う。この課題では、この機能を 実装しなくてもよい。ディレクトリ名が指定された場合には、エラーにしても よい。また、Apache と同様に特定のファイル名が指定されたものとして扱って もよい。 この課題では、ディレクトリかどうかの判定は不要であるが、判定したい場合 には stat() システム・コールを使う方法がある。
練習問題(704) を拡張して、 バイナリ・ファイルを扱えるようにしなさい。 次の型(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 
まず、 練習問題(703) を実施しなさい。 次に、 練習問題(704) 、および、 練習問題(705) を実施しなさい。 そして、その時作成した関数を用いて、HTTP サーバを作成しなさい。 この課題を行う場合、http-server.c に含まれる関数を利用してもよい。
実際の WWW ブラウザ (Firefoxなど)で、動作を確認しなさい。telnet だけで は、きちんと HTTP のプロトコルに従っているか確認できないので、不十分で ある。
Firefox 等で試す時には、ホスト名として、次のようなものを利用する。
GET /?month=6&year=2013 HTTP/1.0
カレンダの出力については、cal コマンドを実行した結果を用いても良い。こ
れに対して、HTTP で結果を返す。Content-Type: としては、text/plain に
するか、text/html で <PRE></PRE>を使う方法が
考えられる。その他の手法でもよい。
このサーバを実行するための HTML の form を用意しなさい。
実際の WWW ブラウザ (Firefoxなど)で、動作を確認しなさい。telnet だけで は、きちんと HTTP のプロトコルに従っているか確認できないので、不十分で ある。
.cgi のプログラムを CGI と見なし、プログラムを実行し、そ
の結果をクライアントに返しなさい。
実際の WWW ブラウザ (Firefoxなど)で、動作を確認しなさい。telnet だけで は、きちんと HTTP のプロトコルに従っているか確認できないので、不十分で ある。
クライアントから送られてくるデータを、コマンドの標準入力に接続して実行 するプログラムを作りなさい。たとえば、次のようなコマンドを実行すること を考える。
% ./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 で受け取ったデー タをパイプに書き込んでもよい。
% ./spipe-first hostname port ls -l 
spipe-first は、指定されたホスト名とポート番号でtcp_connect() を行う。
不要なポートを閉じ、標準出力を切り替えた後、指定されたコマンドを実行す
る。すなわち、dup(), dup2(), close() などで、標準入出力を切り替えて、
execve() などで、プログラムを実行する。
コマンドを実行したらそのまま終了する。
この課題では、spipe-last と同様に、fork() を行わないことを推奨する。
% ./spipe-middle myport serverhost serverport sort +4 
spipe-middle は、第2引数と第3引数で指定されたサーバにtcp_connect() で接
続要求を行う。
tcp_acc_port() により、第1引数で指定された
ポート番号で接続受付用ポートを作成する。
spipe-first から接続要求を受け取ると、
不要なポートを閉じ、標準入出力を切り替える。そして、第4引数以降で指定さ
れたコマンドを実行する。
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 コマンドと似たような 動きを実現してもよい。
HTTP proxy 作りなさい。次の機能を、1つ以上付けなさい。
上の tcp_peeraddr_print() は、IP アドレスを数字で表示していた。 これを、ホスト名で表示するようにしなさい。
実際の WWW ブラウザ (Firefoxなど)で、動作を確認しなさい。telnet だけで は、きちんと HTTP のプロトコルに従っているか確認できないので、不十分で ある。
実際の WWW ブラウザ (Firefoxなど)で、動作を確認しなさい。telnet だけで は、きちんと HTTP のプロトコルに従っているか確認できないので、不十分で ある。