筑波大学 システム情報系 情報工学域
新城 靖
<yas@cs.tsukuba.ac.jp>
このページは、次の URL にあります。
https://www.coins.tsukuba.ac.jp/~syspro/2026/2026-07-15
あるいは、次のページから手繰っていくこともできます。
https://www.coins.tsukuba.ac.jp/~syspro/2026/
http://www.coins.tsukuba.ac.jp/~yas/
Emacs fill-paragraph (M-q, Esc q) を使うと、空行を区切りとして、パラグ ラフを折り返してくれる。region を指定することもできる。
Visual Studio Code なら、同じことを Rewrap で可能。標準のキーは、Emacs に習って Alt+Q。
strcat() を使ったプログラム(やめて欲しいプログラム)。
main() {
char buf[BUFFERSIZE];
char *s1 = "world!";
strcpy(buf, "hello ");
strcat(buf, s1);
printf("%s\n", buf);
}
snprintf() を使ったプログラム。
main() {
char buf[BUFFERSIZE];
char *s1 = "world!";
int req;
req = snprintf(buf, BUFFERSIZE, "hello %s", s1);
printf("%s\n", buf);
}
注意: req >= BUFFERSIZE の時は、後ろが切られている。
fprintf() を使えば、snprintf() や strcpy() は多くの場合で不要になる。
画面に表示するプログラム(1)
main() {
printf("hello %s\n", "world!");
}
画面に表示するプログラム(2)
main() {
fprintf(stdout, "hello %s\n", "world!");
}
ネットワークに出力するプログラム
{
fprintf(out, "hello %s\r\n", "world!");
}
strncpy() は、指定されたnバイト、余った部分を全部0で埋める。
これは、固定長の構造体をファイルに保存する時に有用性があるが、
それ以外では strlcpy() の方が良い。
Linux には strlcpy() は、libbsd-dev (Debian, Ubuntu) や
libbsd-devel (Redhat, CentOS) に含まれている。
また、ヘッダファイルは、次のファイルに含まれている。
#include <bsd/string.h>strlcpy() は snprintf() で代用できる。 FreeBSD の man ページ 等にも書いてある。 次の2つは同じである。
n = strlcpy(dst, src, dst_len); n = snprintf(dst, dst_len, "%s", src);大事なことは、dst_len はdstのサイズの固定長にすること。 決して、srcからstrlen()等で計算してはならない。
例題をコピーする時の注意点
プログラムを書く時の目標
| ファイル記述子 | FILE * |
| 0 | stdin |
| 1 | stdout |
| 2 | stderr |
open() は、ファイル記述子を返すが、それは、小さな整数である。 普通、0, 1, 2 は使われているので、open() すると 3 以上の整数が返される。 open() で開いたファイル後で FILE * の機能を使いたくなった時には、 fdopen() というライブラリ関数が使える。
FILE *fdopen(int fd, const char *mode);
逆に、fopen() で開いて得た FILE * からファイル記述子を得たい場合には、 fileno() というライブラリ関数が使える。
int fileno(FILE *stream);
echo-server-nofork-fdopen.c] [coins-syspro.c]
1:
2: /*
3: echo-server-nofork-fdopen.c -- 受け取った文字列をそのまま返すサーバ(fork無し版)
4: ~yas/syspro/ipc/echo-server-nofork-fdopen.c
5: */
6:
7: #include "coins-syspro.c"
8:
9: extern void echo_server( int portno, int ip_version );
10: extern void echo_receive_request_and_send_reply( int com );
11: extern int echo_receive_request( char *line, size_t size, FILE *in );
12: extern void echo_send_reply( char *line, FILE *out );
13:
14: int
15: main( int argc, char *argv[] )
16: {
17: int portno, ip_version;
18:
19: if( !(argc == 2 || argc==3) ) {
20: fprintf(stderr,"Usage: %s portno {ipversion}\n",argv[0] );
21: exit( 1 );
22: }
23: portno = strtol( argv[1],0,10 );
24: if( argc == 3 )
25: ip_version = strtol( argv[2],0,10 );
26: else
27: ip_version = 46; /* Both IPv4 and IPv6 by default */
28: echo_server( portno, ip_version );
29: }
30:
main() は、引数として、ポート番号、IPのバージョン(4か6)を取る。
文字列で受け取ったポート番号を strtol() で int へ変換している。
IPのバージョンが省略されたら IPv6 と IPv4 両方(射影)とする。
最後に、echo_server() を呼ぶ。
31: void
32: echo_server( int portno, int ip_version )
33: {
34: int acc,com ;
35:
36: acc = tcp_acc_port( portno, ip_version );
37: if( acc<0 )
38: exit( 1 );
39: print_my_host_port( portno );
40: tcp_sockaddr_print( acc );
41: while( 1 )
42: {
43: printf("[%d] accepting incoming connections (acc==%d) ...\n",
44: getpid(),acc );
45: if( (com = accept( acc,0,0 )) < 0 )
46: {
47: perror("accept");
48: exit( -1 );
49: }
50: printf("[%d] connection (fd==%d) from ",getpid(),com );
51: tcp_peeraddr_print( com );
52: echo_receive_request_and_send_reply( com );
53: }
54: }
55:
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() を呼び出し、その通信相手 についての処理を行う。
56: #define BUFFERSIZE 1024
57:
58: void
59: echo_receive_request_and_send_reply( int com )
60: {
61: char line[BUFFERSIZE] ;
62: int rcount ;
63: int wcount ;
64: FILE *in, *out ;
65:
66: if( fdopen_sock(com,&in,&out) < 0 )
67: {
68: perror("fdopen");
69: exit( 1 ); /* exit when no memory */
70: }
71: while( (rcount=echo_receive_request(line,BUFFERSIZE,in))>0 )
72: {
73: printf("[%d] received (fd==%d) %d bytes, [%s]\n",
74: getpid(),com,rcount,line );
75: fflush( stdout );
76: echo_send_reply( line,out );
77: }
78: if( rcount < 0 )
79: perror("fgets");
80: printf("[%d] connection (fd==%d) closed.\n",getpid(),com );
81: fclose( in );
82: fclose( out );
83: }
84:
この 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() で受信したメッセージを表
示している。
85: int
86: echo_receive_request( char *line, size_t size, FILE *in )
87: {
88: if( fgets( line,size,in ) )
89: {
90: return( strlen(line) );
91: }
92: else
93: {
94: if( ferror(in) )
95: return( -1 );
96: else
97: return( 0 );
98: }
99: }
100:
echo_receive_request() は、エコー・サービスで、要求メッセージを 受信する関数である。 このプログラムは、fgets() で1行読み出している。 うまく受信できれば、受信したメッセージの長さを返す。 エラーがおきれば、-1、eof なら 0 を返す。
101: void
102: echo_send_reply( char *line, FILE *out )
103: {
104: fprintf(out,"%s",line );
105: }
echo_send_reply() は、エコー・サービスで、応答メッセージを 送信する関数である。 fprintf() を使って送信している。メッセージには行末の \n は含まれている ので、ここでは付加していない。
この fprintf() では、fdopen_sock() でバッファリングを抑止しているので fflush() は不要になっている。
サーバ側。 サーバ・プロセスは、自発的には終了しない。終了させるには、キーボードで ^C を押し、シグナルを発生させる。
注意:同じポート番号 1231 を同じコンピュータで複数人が同時に使おうとす るとbind でエラーが出ることがある。
注意2: Coins のコンピュータで実験する時は、azalea, jelly を使うこと。 violet01, violet03 では、特定のポート番号のサービスしか利用できない ようになっている。
$ 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 .
$ cp -i ~yas/syspro/ipc/coins-syspro.c .
$ ls -l echo-server-nofork-fdopen.c coins-syspro.c
-rw-r--r-- 1 yas prof 9677 7月 2 13:45 coins-syspro.c
-rw-r--r-- 1 yas prof 2163 7月 2 13:46 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 azalea16 1231
[::]:1231
[2095897] accepting incoming connections (acc==3) ...
[2095897] connection (fd==4) from [2001:2f8:3a:1711::231:13]:49726
[2095897] received (fd==4) 5 bytes, [abc
]
[2095897] received (fd==4) 5 bytes, [def
]
[2095897] received (fd==4) 5 bytes, [ghi
]
[2095897] connection (fd==4) closed.
[2095897] accepting incoming connections (acc==3) ...
[2095897] connection (fd==4) from [2001:2f8:3a:1711::231:13]:35724
[2095897] received (fd==4) 5 bytes, [123
]
[2095897] received (fd==4) 5 bytes, [456
]
[2095897] received (fd==4) 5 bytes, [789
]
[2095897] connection (fd==4) closed.
[2095897] accepting incoming connections (acc==3) ...
^C
$
クライアント側
$ telnet azalea16.coins.tsukuba.ac.jp 1231
Trying 2001:2f8:3a:1711::231:16...
Connected to azalea16.coins.tsukuba.ac.jp.
Escape character is '^]'.
abc
abc
def
def
ghi
ghi
^]
telnet> quit
Connection closed.
$ telnet azalea16.coins.tsukuba.ac.jp 1231
Trying 2001:2f8:3a:1711::231:16...
Connected to azalea16.coins.tsukuba.ac.jp.
Escape character is '^]'.
123
123
456
456
780
780
^]
telnet> quit
Connection closed.
$
複数のクライアントに対してサービスを同時に提供するには次のような方法が ある。
http://www.coins.tsukuba.ac.jp/~syspro/index.html
クライアントは、ホスト名とポート番号(省略時には80)を使い、TCP でサーバとの間に
通信路を確立する。
そして、次のような
HTTPの要求メッセージを送る。
要求メッセージは、最低、空行含めて3行である(仮想ホストがない場合は、Host: 行は省略可能)。
GET /syspro/index.html HTTP/1.0←↓
Host: www.coins.tsukuba.ac.jp←↓
←↓
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.15; rv:127.0) Gecko/20100101 Firefox/127.0←↓
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8←↓
Accept-Language: ja,en-US;q=0.7,en;q=0.3←↓
Accept-Encoding: gzip, deflate, br, zstd←↓
Connection: keep-alive←↓
Upgrade-Insecure-Requests: 1←↓
Sec-Fetch-Dest: document←↓
Sec-Fetch-Mode: navigate←↓
Sec-Fetch-Site: cross-site←↓
Priority: u=1←↓
Pragma: no-cache←↓
Cache-Control: no-cache←↓
←↓
空行は、オプションの終了を意味する。
オプションは全部無視しても、最低限の HTTP サーバを作成することはできる。
サーバとしては、送られてきた要求メッセージを全て受け取る必要はある。
(空行が現れるまで受け取り続ける必要がある。)
サーバは、HTTP の応答メッセージを送り返す。 応答メッセージとしては、最低限次のような行が期待される。
HTTP/1.0 200 OK←↓
Content-Type: text/html←↓
←↓ (空行)
<html>省略</html>↓
1行目は、
成功/失敗を示す行(status line)
である。200 は、成功したことを意味する。
2行目は、HTTPの応答の型を意味する。この例では、内容は、HTML であることを意味する。
3行目は、空行であり、HTTP 応答のヘッダの終りを意味する。
空行以降は、内容である。この例では、HTML で記述されている。
実際には次のように、HTTP サーバは、様々なヘッダと本文を送り返す。
HTTP/1.1 200 OK←↓
Date: Mon, 08 Jul 2026 07:40:13 GMT←↓
Server: Apache←↓
Last-Modified: Thu, 27 Jun 2026 09:25:55 GMT←↓
ETag: "6cb-61bdbb7c82549"←↓
Accept-Ranges: bytes←↓
Content-Length: 1739←↓
Connection: close←↓
Content-Type: text/html←↓
←↓ (空行)
<html>省略</html>↓
http-server.c]
[coins-syspro.c]
1:
2: /*
3: http-server-.c -- 常に同じ内容を返す HTTP サーバ(forkなし版)
4: ~yas/syspro/ipc/http-server.c
5: */
6:
7: #include "coins-syspro.c"
8:
9: extern void http_server( int portno, int ip_version );
10: extern void http_receive_request_and_send_reply( int com );
11: extern int http_receive_request( FILE *in );
12: extern void http_send_reply( FILE *out );
13: extern void http_send_reply_bad_request( FILE *out );
14: extern void print_my_host_port_http( int portno );
15:
16: int
17: main( int argc, char *argv[] )
18: {
19: int portno, ip_version;
20:
21: if( !(argc == 2 || argc==3) ) {
22: fprintf(stderr,"Usage: %s portno {ipversion}\n",argv[0] );
23: exit( 1 );
24: }
25: portno = strtol( argv[1],0,10 );
26: if( argc == 3 )
27: ip_version = strtol( argv[2],0,10 );
28: else
29: ip_version = 46; /* Both IPv4 and IPv6 by default */
30: http_server( portno,ip_version );
31: }
32:
main() は、引数として、ポート番号を取る。
文字列で受け取ったポート番号を strtol() で int へ変換している。
最後に、http_server() を呼ぶ。
echo-server-nofork-fdopen.cのmain()とほとんど同じ。
33: void
34: http_server( int portno, int ip_version )
35: {
36: int acc,com ;
37:
38: acc = tcp_acc_port( portno, ip_version );
39: if( acc<0 )
40: exit( -1 );
41: print_my_host_port_http( portno );
42: tcp_sockaddr_print( acc );
43: while( 1 )
44: {
45: printf("[%d] accepting incoming connections (fd==%d) ...\n",getpid(),acc );
46: if( (com = accept( acc,0,0 )) < 0 )
47: {
48: perror("accept");
49: exit( -1 );
50: }
51: printf("[%d] connection (fd==%d) from ",getpid(),com );
52: tcp_peeraddr_print( com );
53: http_receive_request_and_send_reply( com );
54: }
55: }
56:
echo-server-nofork-fdopen.cのecho_server()とほとんど同じ。違いは、次の所だけ。
57: #define BUFFERSIZE 1024
58:
59: void
60: http_receive_request_and_send_reply( int com )
61: {
62: FILE *in, *out ;
63:
64: if( fdopen_sock(com,&in,&out) < 0 )
65: {
66: perror("fdooen()");
67: exit( -1 );
68: }
69: if( http_receive_request( in ) )
70: {
71: http_send_reply( out );
72: }
73: else
74: {
75: http_send_reply_bad_request( out );
76: }
77: printf("[%d] Replied\n",getpid() );
78: fclose( in );
79: fclose( out );
80: }
81:
この関数では、
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() を呼び出し、エラーの 応答を送信する。
82: int
83: http_receive_request( FILE *in )
84: {
85: char requestline[BUFFERSIZE] ;
86: char rheader[BUFFERSIZE] ;
87:
88: if( fgets(requestline,BUFFERSIZE,in) <= 0 )
89: {
90: printf("No request line.\n");
91: return( 0 );
92: }
93: chomp( requestline ); /* remove \r\n */
94: printf("requestline is [%s]\n",requestline );
95: while( fgets(rheader,BUFFERSIZE,in) )
96: {
97: chomp( rheader ); /* remove \r\n */
98: if( strcmp(rheader,"") == 0 )
99: break;
100: printf("Ignored: %s\n",rheader );
101: }
102: if( strchr(requestline,'<') ||
103: strstr(requestline,"..") )
104: {
105: printf("Dangerous request line found.\n");
106: return( 0 );
107: }
108: return( 1 );
109: }
110:
http_receive_request() は、まず、
HTTPの要求の最初の行(request line)
をfgets() で読み込む。次に、chomp() で行末の改行 \n や
\r を取り除く。
要求行の内容に対して簡単なエラーチェックをしている。次のような パタンが含まれるとエラーにしている。
< が現れる
".." が現れる
最初の要求行に続き、オプションの行を読み出している。 オプションの最後は、空行で終わる。 空行が来るまで、オプションの行を読み出す。
このプログラムでは、オプションを一切使用していない。しかし、HTTP の要求 で送られてきた要求を全て読み出している。プロトコル上、必ず読み出す必要 がある。
111: void
112: http_send_reply( FILE *out )
113: {
114: fprintf(out,"HTTP/1.0 200 OK\r\n"
115: "Content-Type: text/html\r\n"
116: "\r\n");
117: fprintf(out,"<html><head></head><body>hello.</body></html>\n");
118: }
119:
http_send_reply() は、HTTPのヘッダと本体を出力している。 ヘッダは、status line に Content-Typeが続く。 空行以降が本体である。
本体では、HTMLの文書を出力している。
120: void
121: http_send_reply_bad_request( FILE *out )
122: {
123: fprintf(out,"HTTP/1.0 400 Bad Request\r\n"
124: "Content-Type: text/html\r\n"
125: "\r\n");
126: fprintf(out,"<html><head></head><body>400 Bad Request</body></html>\n");
127: }
128:
http_send_reply_bad_request() は、HTTPのヘッダと本体を出力している。 ヘッダは、status line (エラー)に Content-Typeが続く。 空行以降が本体である。
本体では、HTMLの文書を出力している。
サーバ側。 サーバは、終了しないので、最後に、^C を押して、割り込みを掛け て終了させる。
注意:全員がポート番号 1231 を使うとプログラムが動かないことがある。
注意2: Coins のコンピュータで実験する時は、azalea, jelly を使うこと。 violet01, violet03 では、特定のポート番号のサービスしか利用できない ようになっている。
$ 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 2955 7月 2 14:08 http-server.c
$ ls -l coins-syspro.c
-rw-r--r-- 1 yas prof 9677 6月 15 16:22 coins-syspro.c
$ make http-server
cc http-server.c -o http-server
$ ./http-server
Usage: ./http-server portno {ipversion}
$ ./http-server 1231
open http://azalea16:1231/index.html
[::]:1231
[3035948] accepting incoming connections (fd==3) ...
[3035948] connection (fd==4) from [2001:2f8:3a:1711::231:28]:35958
No request line.
[3035948] Replied
[3035948] accepting incoming connections (fd==3) ...
[3035948] connection (fd==4) from [2001:2f8:3a:1711::231:28]:35968
requestline is [GET /index.html HTTP/1.1]
Ignored: Host: azalea16:1231
Ignored: User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0
Ignored: Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Ignored: Accept-Language: ja,en-US;q=0.9,en;q=0.8
Ignored: Accept-Encoding: gzip, deflate
Ignored: Connection: keep-alive
Ignored: Upgrade-Insecure-Requests: 1
Ignored: Priority: u=0, i
[3035948] Replied
[3035948] accepting incoming connections (fd==3) ...
[3035948] connection (fd==4) from [2001:2f8:3a:1711::231:28]:48880
requestline is [GET /favicon.ico HTTP/1.1]
Ignored: Host: azalea16:1231
Ignored: User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0
Ignored: Accept: image/avif,image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5
Ignored: Accept-Language: ja,en-US;q=0.9,en;q=0.8
Ignored: Accept-Encoding: gzip, deflate
Ignored: Connection: keep-alive
Ignored: Referer: http://azalea16:1231/index.html
Ignored: Priority: u=6
[3035948] Replied
[3035948] accepting incoming connections (fd==3) ...
^C
$
クライアント側
$ open http://azalea16:1231/index.html
Web ブラウザ Safari や Firefox, Google Chrome で
"http://azalea16:1231/index.html" や
"http://azalea16.coins.tsukuba.ac.jp:1231/index.html"
を開く。
\n や\r を削除する関数である。
requestline is [...])
Ignored: ...)
~yas/syspro/string/string-split.c [string-split.c] [coins-syspro.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)を標準入力から
与えたとする。
すると、次のような結果を標準出力に出力する。
$ cp ~syspro/public_html/2026/2026-07-15/http-good-request.txt .
$ ./http-request-analyze < http-good-request.txt
requestline is [GET /index.html HTTP/1.0]
Ignored: Host: azalea16.coins.tsukuba.ac.jp:1231
Ignored: User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/114.0
Ignored: Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;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
Ignored: Upgrade-Insecure-Requests: 1
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/2026/2026-07-15/http-bad-request-1.txt .
$ ./http-request-analyze < http-bad-request-1.txt
<・・・中略・・・>
Bad Request.
$
$ cp ~syspro/public_html/2026/2026-07-15/http-bad-request-2.txt .
$ ./http-request-analyze < http-bad-request-2.txt
<・・・中略・・・>
Bad Request.
$
$ cp ~syspro/public_html/2026/2026-07-15/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] [coins-syspro.c]
char buf[BUFFERSIZE]; char *s; int req; s = "/index.html"; req = snprintf(buf, BUFFERSIZE, "./%s", s );なお、ファイル名「/」は、複数付けても良い。 ".//index.html" や ".///////index.html" は "./index.html" と同じファイルを開く。
注意: req >= BUFFERSIZE の時は、後ろが切られている。 これを検出してエラーにすることが望ましい(やらなくても良い)。
"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応答メッセージのヘッダとエラーメッセージが表示される)
もしこの課題に取り組む時、 練習問題(704) と 練習問題(705) で作成した関数をそのまま利用するならば、 この課題のレポートには、それら2つの練習問題の結果を含めなくてよい。 利用しないのであれば、 それら2つの練習問題の結果も必ず含めななさい。
また、前回の課題、 練習問題(704) で、バッファ・オーバーフローやメモリ・リーク等の問題を指摘された人は、 その問題を解消した後でレポートを提出すること。
このサーバ・プログラムは、 echo-server-nofork-fdopen.cや http-server.c と同じように、 次のような引数を与えて実行可能にしなさい。
$ ./http-server ポート番号
$ ./http-server ポート番号 IPバージョン
「IPバージョン」としては、4, 6, 46, 64 のいずれかを指定可能にしなさい。
省略された場合は、46 が指定されたものとして扱いなさい。
注意: Coins のコンピュータで実験する時は、azalea, または jelly を使うこと。 violet01 や violet03 では、特定のポート番号のサービスしか利用できない ようになっている。
作成したプログラムが動作することを、次の方法のいずれかで確認しなさい。
クライアントから不正な要求が送られたとしても、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 |
Webブラウザ等で画像等のバイナリを表示できることを確認しなさい。レポー トには、そのことを記載しなさい。サーバの端末表示としてWebブラウザ等か らアクセスされたことを確認できるようなログを表示しなさい。レポートには、 その表示を含めなさい。
補講で示したように、Coins で利用している HTTP サーバ Apache には Webページに対するアクセス制御 の機能がある。 Apache は、IP アドレスによるアクセス制御を行う事ができる。 このことを確認しなさい。
Apache と同様に、 練習問題(708) で、特定のIPアドレスを持つコンピュータ からの接続要求だけを受け付け、HTML や画像を返し、 その他は 403 Forbidden を返すようなさい。 たとえば、クライアントのIPアドレスを getpeername() で調べ、次の IP アド レスの範囲(情報科学類LAN)についてのみアクセスを許可するようにしなさい。
この課題では、IP アドレスを文字列に変換するのではなく、 struct sockaddr_inやstruct sockaddr_in6 のバイナリの形で扱うことが望ましい。 たとえば バイト・オーダー を意識して、上位バイトから比較し、上限と下限の間に含まれていることを確認する方法が考えられる。 IP アドレスは、struct sockaddr_in や struct sockaddr_in6 ではビッグエンディアンで保存されている。 上限下限と比較する方法の他に、ネットマスクと&をとって比較する方法もある。
この課題に取り組む前に、 練習問題(706) と 練習問題(708) を行ないなさい。 この課題のレポートを提出する時、 練習問題(706) と 練習問題(708) の 拡張であれば、これらの課題のレポートを提出する必用はない。 拡張でなければ、これらの課題のレポートも一緒に提出しなさい。
練習問題(708) を、名前ベースの仮想ホストに対応させなさい。HTTP の要求でオプションであ る Host: を読み、それに応じて異なるディレクトリの内容を返すようにしなさ い。
Firefox 等で試す時には、ホスト名として、次のようなものを利用する。
GET /?month=7&year=2026 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つ以上付けなさい。
echo-server-nofork-fdopen.cの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 コマンドでマニュアルを読みなさい。
$ man w3m
$ 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/ 参照。