筑波大学 システム情報系 情報工学域 新城 靖 <yas@cs.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~syspro/2018/2018-05-30
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~syspro/2018/
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 に受信している。受信したメッセージ
は、'\0'
で終端されている。
このプログラムは、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 2018 06:09:05 GMT←↓Server: Apache←↓
Last-Modified: Wed, 23 Apr 2018 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/2018/2018-05-30/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/2018/2018-05-30/http-bad-request-1.txt
<・・・中略・・・>
Bad Request.
$
$ ./http-request-analyze < ~syspro/public_html/htdocs/2018/2018-05-30/http-bad-request-2.txt
<・・・中略・・・>
Bad Request.
$
$ ./http-request-analyze < ~syspro/public_html/htdocs/2018/2018-05-30/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
補足: クライアントから不正な要求が送られたとしても、 exit() でサーバを 終了しないようにしなさい。
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=2018 HTTP/1.0![[←]](../icons/screen-return.gif)
カレンダの出力については、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 のプロトコルに従っているか確認できないので、不十分で ある。