筑波大学 システム情報系 情報工学域 新城 靖 <yas@cs.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~syspro/2021/2021-06-02
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~syspro/2021/
http://www.coins.tsukuba.ac.jp/~yas/
strcat() を使ったプログラム(やめて欲しいプログラム)。
main() { char buf[BUFFERSIZE]; strcpy(buf,"hello "); strcat(buf,"world!"); printf("%s\n", buf); }snprintf() を使ったプログラム。
main() { char buf[BUFFERSIZE]; snprintf(buf, BUFFERSIZE, "hello %s", "world!"); printf("%s\n", buf); }
fprintf() を使えば、snprintf() や strcpy() は多くの場合で不要になる。
画面に表示するプログラム(1)
main() { printf("hello %s\n", "world!"); }画面に表示するプログラム(2)
main() { fprintf(stdout, "hello %s\n", "world!"); }ネットワークに出力するプログラム
{ fprintf(out, "hello %s\n", "world!"); }strncpy() は、指定されたnバイト、余った部分を全部0で埋める。 これは、固定長の構造体をファイルに保存する時に有用性があるが、 それ以外では strlcpy() の方が良い。 Linux には strlcpy() は、libbsd-dev (Debian, Ubuntu) や libbsd-devel (Redhat, CentOS) に含まれている。 また、ヘッダファイルは、次のファイルに含まれている。
#include <bsd/string.h>strlcpy() は snprintf() で代用できる。macOS の man にも書いてある。 どちらにしても len はdstのサイズの固定長にする(srcからstrlen()等で計算しない)。
n = strlcpy(dst, src, len); n = snprintf(dst, len, "%s", src);
例題をコピーする時の注意点
echo-server-nofork-fdopen.c
]
1: 2: /* 3: echo-server-nofork-fdopen.c -- 受け取った文字列をそのまま返すサーバ(fork無し版) 4: ~yas/syspro/ipc/echo-server-nofork-fdopen.c 5: */ 6: #include <stdio.h> 7: #include <stdlib.h> /* exit() */ 8: #include <sys/types.h> /* socket(), wait4() */ 9: #include <sys/socket.h> /* socket() */ 10: #include <netinet/in.h> /* struct sockaddr_in */ 11: #include <sys/resource.h> /* wait4() */ 12: #include <sys/wait.h> /* wait4() */ 13: #include <netdb.h> /* getnameinfo() */ 14: #include <string.h> /* strlen() */ 15: #include <unistd.h> /* getpid(), gethostname() */ 16: 17: extern void echo_server( int portno, int ip_version ); 18: extern void echo_receive_request_and_send_reply( int com ); 19: extern int echo_receive_request( char *line, size_t size, FILE *in ); 20: extern void echo_send_reply( char *line, FILE *out ); 21: extern void print_my_host_port( int portno ); 22: extern void tcp_sockaddr_print( int com ); 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 = 46; /* Both IPv4 and IPv6 by default */ 42: echo_server( portno, ip_version ); 43: } 44:main() は、引数として、ポート番号、IPのバージョン(4か6)を取る。 文字列で受け取ったポート番号を strtol() で int へ変換している。 IPのバージョンが省略されたら IPv6 と 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: tcp_sockaddr_print( acc ); 55: while( 1 ) 56: { 57: printf("[%d] accepting incoming connections (acc==%d) ...\n", 58: getpid(),acc ); 59: if( (com = accept( acc,0,0 )) < 0 ) 60: { 61: perror("accept"); 62: exit( -1 ); 63: } 64: tcp_peeraddr_print( com ); 65: echo_receive_request_and_send_reply( com ); 66: } 67: } 68:
tcp_acc_port() は、引数で与えられたポート番号を使って接続要求受付用ポー トを作成し、そのファイル記述子(ソケット)を返す。 このファイル記述子は、クライアント側とは異なり、そのままでは通信に 用いることはできない。 print_my_host_port() は、telnet で接続する時のヒントを表示する。 tcp_sockaddr_print() は、自身の IP アドレスとポート番号を表示する。
サーバのプログラムの特徴は、内部に無限ループを持っていることである。 サーバは、普通の状態では、終了しない。
accept() は、接続要求を待つシステムコールである。クライアントから接続 が来るまで、システムコールを実行したまま止まっているように見える。接続 要求が届くと、TCP/IP通信路が開設され、通信用ポートのファイル記述子が返 される。このファイル記述子は、クライアント側と同様に 標準入出力(0,1,2)や open() システム・コールの結果と同じもので、 ファイルに対する write() システムコールや read() システムコールの第一引数とし て使うことができる。つまり、write() システムコールを使うと、ネットワー クに対してデータを送り出すことができ、read() システムコールを使うとネッ トワークからデータを受け取ることができる。最後に不要になったら close() で解放する。
tcp_peeraddr_print() を呼び出し、通信相手(peer)のIPアドレスとポート番号 を表示する。
最後に、echo_receive_request_and_send_reply() を呼び出し、その通信相手 についての処理を行う。
69: #define BUFFERSIZE 1024 70: 71: void 72: echo_receive_request_and_send_reply( int com ) 73: { 74: char line[BUFFERSIZE] ; 75: int rcount ; 76: int wcount ; 77: FILE *in, *out ; 78: 79: if( fdopen_sock(com,&in,&out) < 0 ) 80: { 81: perror("fdopen"); 82: exit( 1 ); /* exit when no memory */ 83: } 84: while( (rcount=echo_receive_request(line,BUFFERSIZE,in))>0 ) 85: { 86: printf("[%d] received (fd==%d) %d bytes, [%s]\n", 87: getpid(),com,rcount,line ); 88: fflush( stdout ); 89: echo_send_reply( line,out ); 90: } 91: if( rcount < 0 ) 92: perror("fgets"); 93: printf("[%d] connection (fd==%d) closed.\n",getpid(),com ); 94: fclose( in ); 95: fclose( out ); 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のバッファ)に
溜っているデータを書き出すものである。
98: int 99: echo_receive_request( char *line, size_t size, FILE *in ) 100: { 101: if( fgets( line,size,in ) ) 102: { 103: return( strlen(line) ); 104: } 105: else 106: { 107: if( ferror(in) ) 108: return( -1 ); 109: else 110: return( 0 ); 111: } 112: } 113:
echo_receive_request() は、エコー・サービスで、要求メッセージを 受信する関数である。 このプログラムは、fgets() で1行読み出している。 うまく受信できれば、受信したメッセージの長さを返す。 エラーがおきれば、-1、eof なら 0 を返す。
114: void 115: echo_send_reply( char *line, FILE *out ) 116: { 117: fprintf(out,"%s",line ); 118: } 119:
echo_send_reply() は、エコー・サービスで、応答メッセージを 送信する関数である。 fprintf() を使って受信送信している。メッセージには行末の \n は含まれている ので、ここでは付加していない。
この fprintf() では、fdopen_sock() でバッファリングを抑止しているので fflush() は不要になっている。
サーバ側。 サーバ・プロセスは、自発的には終了しない。終了させるには、キーボードで ^C を押し、シグナルを発生させる。
注意:同じポート番号 1231 を同じコンピュータで複数人が同時に使おうとす るとbind でエラーが出ることがある。
注意2: Coins のコンピュータで実験する時は、aloe を使うこと。 viola5, viola12 では、特定のポート番号のサービスしか利用できない ようになっている。
$ 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 6097 6 3 23:48 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 aloe40.local 1231
[83132] accepting (fd==4) to [::]:1231
[83132] accepting incoming connections (acc==4) ...
[83132] connection (fd==5) from [::ffff:130.158.230.40]:49900
[83132] received (fd==5) 5 bytes, [abc
]
[83132] received (fd==5) 5 bytes, [def
]
[83132] received (fd==5) 5 bytes, [ghi
]
[83132] connection (fd==5) closed.
[83132] accepting incoming connections (acc==4) ...
^C
$
クライアント側
$ host aloe40
aloe40.coins.tsukuba.ac.jp has address 130.158.230.40
aloe40.coins.tsukuba.ac.jp has IPv6 address 2001:2f8:3a:1711::230:40
aloe40:~ yas$ telnet aloe40 1231
Trying 130.158.230.40...
Connected to aloe40.
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 2021 06:09:05 GMT←↓Server: Apache←↓
Last-Modified: Wed, 23 Apr 2021 09:25:25 GMT←↓Etag: "2231aca-63f-4f7b24ffc0c10"←↓
Accept-Ranges: bytes←↓
Content-Length: 1599←↓
Connection: close←↓
Content-Type: text/html←↓
←↓
<html>省略</html>↓
http-server.c
]
1: 2: /* 3: http-server-.c -- 常に同じ内容を返す HTTP サーバ(forkなし版) 4: ~yas/syspro/ipc/http-server.c 5: */ 6: #include <stdio.h> 7: #include <stdlib.h> /* exit() */ 8: #include <sys/types.h> /* socket(), wait4() */ 9: #include <sys/socket.h> /* socket() */ 10: #include <netinet/in.h> /* struct sockaddr_in */ 11: #include <sys/resource.h> /* wait4() */ 12: #include <sys/wait.h> /* wait4() */ 13: #include <netdb.h> /* getnameinfo() */ 14: #include <string.h> /* strlen() */ 15: #include <unistd.h> /* getpid(), gethostname() */ 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 void http_send_reply( FILE *out ); 21: extern void http_send_reply_bad_request( FILE *out ); 22: extern void print_my_host_port_http( int portno ); 23: extern char *chomp( char *str ); 24: extern void tcp_sockaddr_print( int com ); 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 = 46; /* Both IPv4 and IPv6 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: tcp_sockaddr_print( acc ); 57: while( 1 ) 58: { 59: printf("[%d] accepting incoming connections (fd==%d) ...\n",getpid(),acc ); 60: if( (com = accept( acc,0,0 )) < 0 ) 61: { 62: perror("accept"); 63: exit( -1 ); 64: } 65: tcp_peeraddr_print( com ); 66: http_receive_request_and_send_reply( com ); 67: } 68: } 69:
echo-server-nofork-fdopen.cのecho_server()とほとんど同じ。違いは、次の所だけ。
70: #define BUFFERSIZE 1024 71: 72: void 73: http_receive_request_and_send_reply( int com ) 74: { 75: FILE *in, *out ; 76: 77: if( fdopen_sock(com,&in,&out) < 0 ) 78: { 79: perror("fdooen()"); 80: exit( -1 ); 81: } 82: if( http_receive_request( in ) ) 83: { 84: http_send_reply( out ); 85: } 86: else 87: { 88: http_send_reply_bad_request( out ); 89: } 90: printf("[%d] Replied\n",getpid() ); 91: fclose( in ); 92: fclose( out ); 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 を使うとプログラムが動かないことがある。
注意2: Coins のコンピュータで実験する時は、aloe を使うこと。 viola5, viola12 では、特定のポート番号のサービスしか利用できない ようになっている。
$ 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://aloe30.local:1231/index.html
[42377] accepting (fd==4) to [::]:1231
[42377] accepting incoming connections (fd==4) ...
[42377] connection (fd==5) from [2001:2f8:3a:1711::230:40]:57396
requestline is [GET /index.html HTTP/1.1]
Ignored: Host: aloe30.coins.tsukuba.ac.jp:1231
Ignored: Upgrade-Insecure-Requests: 1
Ignored: Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Ignored: User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.1 Safari/605.1.15
Ignored: Accept-Language: ja-jp
Ignored: Accept-Encoding: gzip, deflate
Ignored: Connection: keep-alive
[42377] Replied
[42377] accepting incoming connections (fd==4) ...
[42377] connection (fd==5) from [2001:2f8:3a:1711::230:40]:57397
requestline is [GET /favicon.ico HTTP/1.1]
Ignored: Host: aloe30.coins.tsukuba.ac.jp:1231
Ignored: Connection: keep-alive
Ignored: Accept: */*
Ignored: User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.1 Safari/605.1.15
Ignored: Accept-Language: ja-jp
Ignored: Referer: http://aloe30.coins.tsukuba.ac.jp:1231/index.html
Ignored: Accept-Encoding: gzip, deflate
[42377] Replied
[42377] accepting incoming connections (fd==4) ...
^C
$
クライアント側
$ open http://aloe30.coins.tsukuba.ac.jp:1231/index.html
Safari や Firefox で "http://aloe30.coins.tsukuba.ac.jp:1231/index.html"
を開く。
\n
や\r
を削除する関数である。
http-server.c
]。
requestline is [...]
)
Ignored: ...
)
echo-server-nofork-fdopen.c
]。
次のクライアントでアクセスしなさい。
~yas/syspro/string/string-split.c
[string-split.c,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] にどのような値が返されるのかを調べなさ い。ただし、区切り文字としては ' ' (空白) を用いるものとする。
http-good-request.txt
)を標準入力から
与えたとする。
GET /index.html HTTP/1.1 Host: www.coins.tsukuba.ac.jp User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:12.0) Gecko/20100101 Firefox/12.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 Connection: keep-aliveすると、次のような結果を標準出力に出力する。
$ cp ~syspro/public_html/htdocs/2021/2021-06-02/http-good-request.txt .
$ ./http-request-analyze < 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].
$
次のファイルをコピーして修正しても良い。そのまま実行すると「change
this.」(ここを変えなさい)と表示される。この課題では、この部分を修正する。
http-request-analyze.c
]
http-good-request.txt
]
http-bad-request-1.txt
]
http-bad-request-2.txt
]
http-bad-request-3.txt
]
http-request-analyze.c の関数 http_receive_request() で次のような動作を 行う。
''
」を区切り文字として分割する。
string_split() は、内部で malloc() を呼んでメモリを確保している。
クライアントは、不正な要求行を送ってくる可能性がある。そのような場合に は、エラーにしなさい。たとえば、次のようなデータを送ってくる可能性があ る。
$ cp ~syspro/public_html/htdocs/2021/2021-06-02/http-bad-request-1.txt .
$ ./http-request-analyze < http-bad-request-1.txt
<・・・中略・・・>
Bad Request.
$
$ cp ~syspro/public_html/htdocs/2021/2021-06-02/http-bad-request-2.txt .
$ ./http-request-analyze < http-bad-request-2.txt
<・・・中略・・・>
Bad Request.
$
$ cp ~syspro/public_html/htdocs/2021/2021-06-02/http-bad-request-2.txt .
$ ./http-request-analyze < http-bad-request-3.txt
<・・・中略・・・>
Bad Request.
$
なお、このプログラムは、HTTP の要求を標準入力(stdin)から読むが、ネット ワーク通信を一切行わない。
$ ls -l index.html
-rw-r--r-- 1 yas prof 2280 6月 4 22:29 index.html
$ ./http-response-html /index.html
HTTP/1.0 200 OK
Content-Type: text/html
<HTML>
(カレントワーキングディレクトリにあるindex.htmlの内容)
</HTML>
$
次のファイルをコピーして修正しても良い。そのまま実行すると「change
this.」(ここを変えなさい)と表示される。この課題では、この部分を修正する。
http-response-html.c
]
char buf[BUFFERSIZE]; char *s; s = "/index.html"; snprintf(buf, BUFFERSIZE, "./%s", s );なお、ファイル名「/」は、複数付けても良い。 ".//index.html" や ".///////index.html" は "./index.html" と同じファイルを開く。
"HTTP/1.0 200 OK\r\n"
を出力する。
"Content-Type: text/html\r\n"
を出力する。
"\r\n"
を出力する。
なお、この課題では、対象がテキストだけなので、fgets() と fprintf() を使っ てもよい。バイナリを扱う時には、fgets() と fprintf() では不十分である。 fgetc() と fputc() でも動作するが、fread() と fwrite() よりも性能が低い。
この課題は、HTTP の応答を画面(stdout)に出力するが、ネットワーク通 信を一切行わない。 次のようにコマンドラインからデータを与え、プログラムが正しく動作してい ることを確認しなさい。
$ ./http-response-html /existing-file-name.html # 存在するファイルの場合
(HTTP応答メッセージのヘッダと存在するファイルの内容が表示される)
$ ./http-response-html /file-not-found.html # 存在しないファイルの場合
(HTTP応答メッセージのヘッダとエラーメッセージが表示される)
$ ./http-response-html /bad-file-name.txt # 拡張子が .html ではない場合
(HTTP応答メッセージのヘッダとエラーメッセージが表示される)
作成したプログラムが動作することを、次の方法のいずれかで確認しなさい。
クライアントから不正な要求が送られたとしても、exit() でサーバを終了し ないことが望ましい。 この課題で 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 |
この課題では、IP アドレスを文字列に変換するのではなく、 struct sockaddr_inやstruct sockaddr_in6 のバイナリの形で扱うことが望ましい。 バイト・オーダー にも気をつけなさい。
Firefox 等で試す時には、ホスト名として、次のようなものを利用する。
GET /?month=6&year=2021 HTTP/1.0![[←]](../icons/screen-return.gif)
カレンダの出力については、cal コマンドを実行した結果を用いても良い。こ
れに対して、HTTP で結果を返す。Content-Type: としては、text/plain に
するか、text/html で <PRE></PRE>
を使う方法が
考えられる。その他の手法でもよい。
このサーバを実行するための HTML の form を用意しなさい。
実際の Web ブラウザ (Firefoxなど)で、動作を確認しなさい。telnet だけで は、きちんと HTTP のプロトコルに従っているか確認できないので、不十分で ある。
.cgi
のプログラムを CGI と見なし、プログラムを実行し、そ
の結果をクライアントに返しなさい。
実際の Web ブラウザ (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 アドレスを数字で表示していた。 これを、ホスト名で表示するようにしなさい。
実際の Web ブラウザ (Firefoxなど)で、動作を確認しなさい。telnet だけで は、きちんと HTTP のプロトコルに従っているか確認できないので、不十分で ある。
実際の Web ブラウザ (Firefoxなど)で、動作を確認しなさい。telnet だけで は、きちんと HTTP のプロトコルに従っているか確認できないので、不十分で ある。
$ w3m http://www.coins.tsukuba.ac.jp/
$ w3m http://localhost:1231/index.html
次のように打つと、詳しい説明が表示される。
$ w3m --help 2>&1 | lv
w3mコマンドは、標準エラー出力に使い方を表示する。
これを lv で表示するには、2>&1 |
を用いる。
詳しくは、man w3m や次の Web ページを見なさい。
http://w3m.sourceforge.net/
。
coins では、次のように man コマンドで英語のマニュアルを読みなさい。
$ LANG=C man w3m
iTerm などの文字端末で動作する Web ブラウザ lynx を使ってみなさい。
$ lynx -display_charset=utf-8
$ lynx -display_charset=utf-8 http://www.coins.tsukuba.ac.jp/
$ lynx -display_charset=utf-8 http://localhost:1231/index.html
http://lynx.invisible-island.net/
参照。