筑波大学 システム情報系 情報工学域
                                       新城 靖
                                       <yas@cs.tsukuba.ac.jp>
このページは、次の URL にあります。
	http://www.coins.tsukuba.ac.jp/~syspro/2014/2014-07-29
あるいは、次のページから手繰っていくこともできます。
	http://www.coins.tsukuba.ac.jp/~syspro/2014/
	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:	#include <unistd.h>     /* getpid(), gethostname() */
  17:	
  18:	extern  void echo_server( int portno, int ip_version );
  19:	extern  void echo_receive_request_and_send_reply( int com );
  20:	extern  int  echo_receive_request( char *line, size_t size, FILE *in );
  21:	extern  void echo_send_reply( char *line, FILE *out );
  22:	extern  void print_my_host_port( 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 pf );
  26:	extern  int  fdopen_sock( int sock, FILE **inp, FILE **outp );
  27:	
  28:	int
  29:	main( int argc, char *argv[] )
  30:	{
  31:	        int portno, ip_version;
  32:	
  33:	        if( !(argc == 2 || argc==3) ) {
  34:	                fprintf(stderr,"Usage: %s portno {ipversion}\n",argv[0] );
  35:	                exit( 1 );
  36:	        }
  37:	        portno = strtol( argv[1],0,10 );
  38:	        if( argc == 3 )
  39:	                ip_version = strtol( argv[2],0,10 );
  40:	        else
  41:	                ip_version = 4; /* IPv4 by default */
  42:	        echo_server( portno, ip_version );
  43:	}
  44:	
main() は、引数として、ポート番号、IPのバージョン(4か6)を取る。
文字列で受け取ったポート番号を strtol() で int へ変換している。
IPのバージョンが省略されたら IPv4 とする。
最後に、echo_server() を呼ぶ。
  45:	void
  46:	echo_server( int portno, int ip_version )
  47:	{
  48:	        int acc,com ;
  49:	
  50:	        acc = tcp_acc_port( portno, ip_version );
  51:	        if( acc<0 )
  52:	                exit( 1 );
  53:	        print_my_host_port( portno );
  54:	        while( 1 )
  55:	        {
  56:	                printf("[%d] accepting incoming connections (acc==%d) ...\n",
  57:	                       getpid(),acc );
  58:	                if( (com = accept( acc,0,0 )) < 0 )
  59:	                {
  60:	                        perror("accept");
  61:	                        exit( -1 );
  62:	                }
  63:	                tcp_peeraddr_print( com );
  64:	                echo_receive_request_and_send_reply( com );
  65:	        }
  66:	}
  67:	
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() を呼び出し、その通信相手 についての処理を行う。
  68:	#define BUFFERSIZE      1024
  69:	
  70:	void
  71:	echo_receive_request_and_send_reply( int com )
  72:	{
  73:	        char line[BUFFERSIZE] ;
  74:	        int rcount ;
  75:	        int wcount ;
  76:	        FILE *in, *out ;
  77:	
  78:	        if( fdopen_sock(com,&in,&out) < 0 )
  79:	        {
  80:	                perror("fdopen");
  81:	                exit( 1 );  /* exit when no memory */
  82:	        }
  83:	        while( (rcount=echo_receive_request(line,BUFFERSIZE,in))>0 )
  84:	        {
  85:	                printf("[%d] received (fd==%d) %d bytes, [%s]\n",
  86:	                       getpid(),com,rcount,line );
  87:	                fflush( stdout );
  88:	                echo_send_reply( line,out );
  89:	        }
  90:	        if( rcount < 0 )
  91:	                perror("fgets");
  92:	        printf("[%d] connection (fd==%d) closed.\n",getpid(),com );
  93:	        fclose( in );
  94:	        fclose( out );
  95:	}
  96:	
この echo_receive_request_and_send_reply() は、特定のクライアント専用の
echo サービスを提供する。クライアントからの要求が続く限り、動作する。
このプログラムでは、 クライアント側 と同様に fdopen_sock() を使って、通信可能なファイル記 述子 com から2つの FILE * を作成している。1つは、入力用、1つは出力 用である。その結果、 高水準入出力ライブラリ を使って通信が行えるようになっている。fprintf() で出力用の FILE * に書 き込むと、ネットワークに対してデータが送り出される。入力用の FILE * に fgets() を行うと、ネットワークからデータを受け取ることができる。
この関数は、クライアントから届いた要求メッセージを echo_receive_request() で配列 line に受信している。受信したメッセージの 末尾は、NULL で終端されている。 このプログラムは、echo サービスであるので、受信したメッセージをそのまま echo_send_reply() でクライアントに送信している。 この関数は、デバッグのために、端末に printf() で受信したメッセージを表 示している。fflush() は、printf() の内部のバッファ(stdoutのバッファ)に 溜っているデータを書き出すものである。
  97:	int
  98:	echo_receive_request( char *line, size_t size, FILE *in )
  99:	{
 100:	        if( fgets( line,size,in ) )
 101:	        {
 102:	                return( strlen(line) );
 103:	        }
 104:	        else
 105:	        {
 106:	                if( ferror(in) )
 107:	                        return( -1 );
 108:	                else
 109:	                        return( 0 );
 110:	        }
 111:	}
 112:	
echo_receive_request() は、エコー・サービスで、要求メッセージを 受信する関数である。 このプログラムは、fgets() で1行読み出している。 うまく受信できれば、受信したメッセージの長さを返す。 エラーがおきれば、-1、eof なら 0 を返す。
 113:	void
 114:	echo_send_reply( char *line, FILE *out )
 115:	{
 116:	        fprintf(out,"%s",line );
 117:	}
 118:	
echo_send_reply() は、エコー・サービスで、応答メッセージを 送信する関数である。 fprintf() を使って受信している。メッセージには行末の \n は含まれている ので、ここでは付加していない。
この fprintf() では、fdopen_sock() でバッファリングを抑止しているので fflush() は不要になっている。
サーバ側。 サーバ・プロセスは、自発的には終了しない。終了させるには、キーボードで ^C を押し、シグナルを発生させる。
注意:同じポート番号 1231 を同じコンピュータで複数人が同時に使おうとす るとbind でエラーが出ることがある。
$ 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 crocus39.coins.tsukuba.ac.jp 1231
[57167] accepting incoming connections (acc==3) ...
[57167] connection (fd==4) from 130.158.86.250:62468
[57167] received (fd==4) 5 bytes, [abc
]
[57167] received (fd==4) 5 bytes, [def
]
[57167] received (fd==4) 5 bytes, [ghi
]
[57167] connection (fd==4) closed.
[57167] accepting incoming connections (acc==3) ...
^C
$ 
クライアント側
$ telnet crocus39.coins.tsukuba.ac.jp 1231 
Trying 130.158.86.249...
Connected to crocus39.coins.tsukuba.ac.jp.
Escape character is '^]'.
abc
abc
def
def
ghi
ghi
^]
telnet> quit
Connection closed.
$ 
複数のクライアントに対してサービスの同時に提供するには次のような方法が ある。
GET /syspro/index.html HTTP/1.0←↓
←↓
HTTPの要求は、要求行(request line)から始まり、最後は、空行で終わる。
実際の Web ブラウザは、次のように要求行と空行の間にオプションを含む要求
を送る。
GET /~syspro/index.html HTTP/1.1←↓
Host: www.coins.tsukuba.ac.jp←↓
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:30.0) Gecko/20100101 Firefox/30.0←↓
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8←↓
Accept-Language: ja,en-us;q=0.7,en;q=0.3←↓
Accept-Encoding: gzip, deflate←↓
Referer: http://www.coins.tsukuba.ac.jp/←↓
Connection: keep-alive←↓
←↓
空行は、オプションの終了を意味する。
オプションは全部無視しても、最低限の HTTP サーバを作成することはできる。
サーバとしては、送られてきた要求メッセージを全て受け取る必要はある。
(空行が現れるまで受け取り続ける必要がある。)
HTTP の応答としては、最低限次のような行が期待される。
HTTP/1.0 200 OK←↓
Content-Type: text/html←↓
←↓
<html>省略</html>↓
1行目は、
成功/失敗を示す行(status line)
である。200 は、成功したことを意味する。
2行目は、HTTPの応答の型を意味する。この例では、内容は、HTML であることを意味する。
3行目は、空行であり、HTTP 応答のヘッダの終りを意味する。
空行以降は、内容である。この例では、HTML の内容が1行だけ含まれている。
実際には次のように、様々なヘッダが送られる。
HTTP/1.1 200 OK←↓
Date: Mon, 28 Jul 2014 06:09:05 GMT←↓
Server: Apache←↓
Last-Modified: Wed, 23 Apr 2014 09:25:25 GMT←↓
Etag: "2231aca-63f-4f7b24ffc0c10"←↓
Accept-Ranges: bytes←↓
Content-Length: 1599←↓
Connection: close←↓
Content-Type: text/html←↓
←↓
<html>省略</html>↓
   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:	#include <unistd.h>     /* getpid(), gethostname() */
  17:	
  18:	extern  void http_server( int portno, int ip_version );
  19:	extern  void http_receive_request_and_send_reply( int com );
  20:	extern  int  http_receive_request( FILE *in );
  21:	extern  void http_send_reply( FILE *out );
  22:	extern  void http_send_reply_bad_request( FILE *out );
  23:	extern  void print_my_host_port_http( int portno );
  24:	extern  char *chomp( char *str );
  25:	extern  void tcp_peeraddr_print( int com );
  26:	extern  void sockaddr_print( struct sockaddr *addrp, socklen_t addr_len );
  27:	extern  int  tcp_acc_port( int portno, int ip_version );
  28:	extern  int  fdopen_sock( int sock, FILE **inp, FILE **outp );
  29:	
  30:	int
  31:	main( int argc, char *argv[] )
  32:	{
  33:	        int portno, ip_version;
  34:	
  35:	        if( !(argc == 2 || argc==3) ) {
  36:	                fprintf(stderr,"Usage: %s portno {ipversion}\n",argv[0] );
  37:	                exit( 1 );
  38:	        }
  39:	        portno = strtol( argv[1],0,10 );
  40:	        if( argc == 3 )
  41:	                ip_version = strtol( argv[2],0,10 );
  42:	        else
  43:	                ip_version = 4; /* IPv4 by default */
  44:	        http_server( portno,ip_version );
  45:	}
  46:	
main() は、引数として、ポート番号を取る。
文字列で受け取ったポート番号を strtol() で int へ変換している。
最後に、http_server() を呼ぶ。
echo-server-nofork-fdopen.cのmain()とほとんど同じ。
  47:	void
  48:	http_server( int portno, int ip_version )
  49:	{
  50:	        int acc,com ;
  51:	
  52:	        acc = tcp_acc_port( portno, ip_version );
  53:	        if( acc<0 )
  54:	                exit( -1 );
  55:	        print_my_host_port_http( portno );
  56:	        while( 1 )
  57:	        {
  58:	                printf("[%d] accepting incoming connections (fd==%d) ...\n",getpid(),acc );
  59:	                if( (com = accept( acc,0,0 )) < 0 )
  60:	                {
  61:	                        perror("accept");
  62:	                        exit( -1 );
  63:	                }
  64:	                tcp_peeraddr_print( com );
  65:	                http_receive_request_and_send_reply( com );
  66:	        }
  67:	}
  68:	
echo-server-nofork-fdopen.cのecho_server()とほとんど同じ。違いは、次の所だけ。
  69:	#define BUFFERSIZE      1024
  70:	
  71:	void
  72:	http_receive_request_and_send_reply( int com )
  73:	{
  74:	        FILE *in, *out ;
  75:	
  76:	        if( fdopen_sock(com,&in,&out) < 0 )
  77:	        {
  78:	                perror("fdooen()");
  79:	                return;
  80:	        }
  81:	        if( http_receive_request( in ) )
  82:	        {
  83:	                http_send_reply( out );
  84:	        }
  85:	        else
  86:	        {
  87:	                http_send_reply_bad_request( out );
  88:	        }
  89:	        printf("[%d] Replied\n",getpid() );
  90:	        fclose( in );
  91:	        fclose( out );
  92:	        return;
  93:	}
  94:	
この関数では、
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() を呼び出し、エラーの 応答を送信する。
  95:	int
  96:	http_receive_request( FILE *in )
  97:	{
  98:	        char requestline[BUFFERSIZE] ;
  99:	        char rheader[BUFFERSIZE] ;
 100:	        
 101:	        if( fgets(requestline,BUFFERSIZE,in) <= 0 )
 102:	        {
 103:	                printf("No request line.\n");
 104:	                return( 0 );
 105:	        }
 106:	        chomp( requestline ); /* remove \r\n */
 107:	        printf("requestline is [%s]\n",requestline );
 108:	        while( fgets(rheader,BUFFERSIZE,in) )
 109:	        {
 110:	                chomp( rheader ); /* remove \r\n */
 111:	                if( strcmp(rheader,"") == 0 )
 112:	                        break;
 113:	                printf("Ignored: %s\n",rheader );
 114:	        }
 115:	        if( strchr(requestline,'<') ||
 116:	            strstr(requestline,"..") )
 117:	        {
 118:	                printf("Dangerous request line found.\n");
 119:	                return( 0 );
 120:	        }
 121:	        return( 1 );
 122:	}
 123:	
http_receive_request() は、まず、
HTTPの要求の最初の行(request line)
をfgets() で読み込む。次に、chomp() で行末の改行 \n や
\r を取り除く。
要求行の内容に対して簡単なエラーチェックをしている。次のような パタンが含まれるとエラーにしている。
< が現れる
".." が現れる
最初の要求行に続き、オプションの行を読み出している。 オプションの最後は、空行で終わる。 空行が来るまで、オプションの行を読み出す。
このプログラムでは、オプションを一切使用していない。しかし、HTTP の要求 で送られてきた要求を全て読み出している。プロトコル上、必ず読み出す必要 がある。
 124:	void
 125:	http_send_reply( FILE *out )
 126:	{
 127:	        fprintf(out,"HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n");
 128:	        fprintf(out,"<html><head></head><body>hello.</body></html>\n");
 129:	}
 130:	
http_send_reply() は、HTTPのヘッダと本体を出力している。 ヘッダは、status line に Content-Typeが続く。 空行以降が本体である。
本体では、HTMLの文書を出力している。
 131:	void
 132:	http_send_reply_bad_request( FILE *out )
 133:	{
 134:	        fprintf(out,"HTTP/1.0 400 Bad Request\r\nContent-Type: text/html\r\n\r\n");
 135:	        fprintf(out,"<html><head></head><body>400 Bad Request</body></html>\n");
 136:	}
 137:	
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  6330  7 28 15:44 http-server.c
$ make http-server 
cc     http-server.c   -o http-server
$ ./http-server 
Usage: ./http-server portno {ipversion}
$ ./http-server 1231 
open http://crocus39.coins.tsukuba.ac.jp:1231/index.html
[57308] accepting incoming connections (fd==3) ...
[57308] connection (fd==4) from 130.158.86.247:52315
requestline is [GET /index.html HTTP/1.1]
Ignored: Host: crocus39.coins.tsukuba.ac.jp:1231
Ignored: User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:31.0) Gecko/20100101 Firefox/31.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
[57308] Replied
[57308] accepting incoming connections (fd==3) ...
[57308] connection (fd==4) from 130.158.86.247:52316
requestline is [GET /favicon.ico HTTP/1.1]
Ignored: Host: crocus39.coins.tsukuba.ac.jp:1231
Ignored: User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:31.0) Gecko/20100101 Firefox/31.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
[57308] Replied
[57308] accepting incoming connections (fd==3) ...
[57308] connection (fd==4) from 130.158.86.247:52317
requestline is [GET /favicon.ico HTTP/1.1]
Ignored: Host: crocus39.coins.tsukuba.ac.jp:1231
Ignored: User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:31.0) Gecko/20100101 Firefox/31.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
[57308] Replied
[57308] accepting incoming connections (fd==3) ...
^C
$ 
クライアント側
$ open http://crocus39.coins.tsukuba.ac.jp:1231/index.html 
Safari や Firefox で "http://crocus39.coins.tsukuba.ac.jp:1231/index.html" を開く。
\n や\r を削除する関数である。
requestline is [...])
Ignored: ...)
~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 );
これらの関数を使って、次のような文字列を解析し、count、 および、 vec[0], vec[1], ... vec[count-1] にどのような値が返されるのかを調べなさ い。ただし、区切り文字としては ' ' (空白) を用いるものとする。
$ 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/htdocs/2014/2014-07-29/http-good-request.txt 
requestline is [GET /index.html HTTP/1.1]
Ignored: Host: www.coins.tsukuba.ac.jp
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
filename is [/index.html]
$ 
このプログラムは、関数 http_receive_request() で次のような動作を行う。
クライアントは、不正な要求行を送ってくる可能性がある。そのような場合に は、エラーにしなさい。たとえば、次のようなデータを送ってくる可能性があ る。
$ ./http-request-analyze < ~syspro/public_html/htdocs/2014/2014-07-29/http-bad-request-1.txt 
<・・・中略・・・>
Bad Request.
$ 
$ ./http-request-analyze < ~syspro/public_html/htdocs/2014/2014-07-29/http-bad-request-2.txt 
<・・・中略・・・>
Bad Request.
$ 
$ ./http-request-analyze < ~syspro/public_html/htdocs/2014/2014-07-29/http-bad-request-3.txt 
<・・・中略・・・>
Bad Request.
$ 
なお、このプログラムは、HTTP の要求を標準入力(stdin)から読むが、ネット ワーク通信を一切行わない。レポートには、標準入力からデータを与え、プロ グラムが正しく動作していることを示しなさい。
$ cp ~yas/syspro/ipc/http-response-html.c . 
$ emacs http-response-html.c 
$ cc http-response-html.c -o http-response-html 
$ ./http-response-html /index.html 
HTTP/1.0 200 OK
Content-Type: text/html
<HTML>
中略
</HTML>
$ 
このプログラムは、動作する関数が含まれている。残りの部分をうめて完成さ
せない。全体としては、次のようなプログラムになる。
HOME、
"/public_html/htdocs/" から、実際のファイル名を作成する。snprinf() を用
いると便利である。(strcat() は用いないこと。)
環境変数は、ライブラリ関数
getenv() 
を使うと入手できる。
char *home;
     home = getenv("HOME");
     ...
なお、"/public_html/htdocs/" 以外のディレクトリを用いてもよい。
たとえば、"." でもよい。
"HTTP/1.0 200 OK\r\n" を出力する。
"Content-Type: text/html" を出力する。
HOME の変わりに「~」を使うことはできない。
「~」は、open() システム・コールや fopen() ライブラリ関数では使
えない。「~」 は、シェルや emacs 等のプログラムが独自にホーム・
ディレクトリを調べて置き換えている。
なお、この課題は、HTTP の応答を画面(stdout)に出力するが、ネットワーク通 信を一切行わない。 レポートには、コマンドラインからデータを与え、プログラムが正しく動作し ていることを示しなさい。
$ ./http-response-html /existing-file-name.html 
$ ./http-response-html /file-not-found.html 
$ ./http-response-html /bad-file-name.txt 
| Content-Type: | 拡張子 | 
|---|---|
| text/html | .html | 
| text/plain | .txt | 
| text/plain | .text | 
| Content-Type: | 拡張子 | 
|---|---|
| text/html | .html | 
| text/plain | .txt | 
| text/plain | .text | 
| image/gif | .gif | 
| image/jpeg | .jpeg | 
| image/png | .png | 
Firefox 等で試す時には、ホスト名として、次のようなものを利用する。
GET /?month=6&year=2014 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 のプロトコルに従っているか確認できないので、不十分で ある。