1: 2: /- 3: http-simple.c -- 簡単な WWW サーバ (HTTP サーバ) 4: $Header: /home/h1/yas/slab-info2-inet/2-tcp/RCS/httpd-simple.c,v 1.6 1995/09/11 13:35:12 yas Exp $ 5: Start: 1995/09/03 00:27:22 6: -/ 7: 8: #include 9: #include 10: #include 11: #include 12: #include 13: #include "inet.h" 14: 15: /- 16: fdopen2() は、標準のライブラリ関数 fdopen() を拡張したものである。これ 17: は、TCP/IPの通信用ポートに対応しているのように、入出力可能なファイル記 18: 述子について、2つの入出用と出力用の FILE - (fdopen() のマニュアルでは、 19: stream)を返す。引数の fd には、initport() や connect(), accept() で作 20: られた入出力可能(read()システム・コールもwrite() システム・コールも使 21: える)ファイル記述子を指定する。結果として作られた FILE - は、引数とし 22: て与えられた番地 finp, foutp にセットされる。エラーが起きた時には、0 23: が返される。成功した時には、1 が返される。 24: 25: fdopen2() のソース・プログラムは、 26: /usr/open/classes/slab/info2/lib/fdopen2.c にある。 27: -/ 28: extern int fdopen2(/- int fd, FILE --finp, FILE --foutp -/); 29: 30: void 31: usage() 32: { 33: fprintf( stdout,"Usage: httpd-simple [-p port] directory\n" ); 34: exit( -1 ); 35: } 36: 37: main( argc,argv ) 38: int argc ; 39: char -argv[] ; 40: { 41: extern char -optarg; /- getopt() -/ 42: extern int optind; /- getopt() -/ 43: int c ; 44: char -dir ; 45: int port ; 46: 47: /- 48: 実験では、大勢の人が同時にプログラムを作る。よってね各自重ならないよう 49: なポート番号を使わなければならない。この実験では、getuid() + 10000 を 50: 使うことにする。もしそのポート番号が、使われていたら、initport() の中 51: の bind() システム・コールで次のエラーが出る。 52: ---------------------------------------------------------------------- 53: #define EADDRINUSE 48 // Address already in use 54: ---------------------------------------------------------------------- 55: その時は、前に走らせた自分のプロセスがいないか、調べなさい。もし自分の 56: プロセスが、そのポートを使っていれば、kill コマンドなどで殺しなさい。 57: もし、他の人がそのポート番号を使っていたら、-p オプションで空いている 58: ポート番号を指定してつかうか、他のホストにログインしてこのプログラムを 59: 走らせるようにしなさい。 60: -/ 61: 62: port = getuid() + 10000 ; 63: 64: /- 65: 66: getopt() は、オプションを解析するためのライブラリ関数。引数 "p" は、 67: -p オプションを受け付けるという意味である。続くコロンは、-p string の 68: 形で、文字列を受け付けるという意味である。オプションを見つけると、リター 69: ン・バリューとして返す。その時、受け付ける文字列は、大域変数 optarg に 70: セットされる。 71: -/ 72: while( (c = getopt(argc,argv,"p:")) != EOF ) 73: { 74: switch( c ) 75: { 76: /- atoi() は、文字列(ascii)を、整数に変えるライブラリ関数。-/ 77: case 'p': port = atoi( optarg ); break; 78: default: 79: usage(); 80: } 81: } 82: /- 83: getopt() によるオプションの解析が終了した時、optind に、最初の引数(オ 84: プションではなく必須)の添え字がセットされている。これを、argc と比べ 85: て、引数の数が得られる。ここでは、ディレクトリ名が1つ必要である。 86: -/ 87: if( argc - optind != 1 ) 88: usage(); 89: dir = argv[optind+0] ; 90: httpd_simple( port, dir ); 91: } 92: 93: httpd_simple( port,dir ) 94: int port ; 95: char -dir ; 96: { 97: int client ; /- 通信用ポート -/ 98: int fd; /- 要求受付用ポート -/ 99: fd_set fds; /- select_server_stream() のための変数 -/ 100: 101: /- カレント・ワーキング・ディレクトリを、指定されたディレクトリに移す。-/ 102: if( chdir(dir) < 0 ) 103: { 104: perror( dir ); 105: exit( -1 ); 106: } 107: 108: /- initport() で、サーバ側の要求受付用ポートを作る。そのポートには、 109: portで指定されたポート番号が付けられる。-/ 110: fd = initport( PORT_NUMBER(port),SERVER,SOCK_STREAM ); 111: if( fd < 0 ) 112: { 113: fprintf( stderr,"initport() returns %d\n",fd ); 114: exit( -1 ); 115: } 116: /- 受付可能な URL を表示する。-/ 117: print_my_url( port ); 118: 119: /- select_server_stream() のための変数の初期化 -/ 120: FD_ZERO(&fds); 121: 122: /- 123: サーバのメイン・ループ。サーバ・プロセスは、普通、このように決して終了 124: することがないプログラムである。サーバ・プロセスには、このように無限ルー 125: プが1つある。これを、メイン・ループという。 126: -/ 127: while( 1 ) 128: { 129: /- 130: select_server_stream() は、内部で自動的にクライアントからの接続要求を 131: 受け付ける(accept())。このとき、通信用ポートが1つ作られる。 132: select_server_stream() は、クライアントから要求データが送られてきた通 133: 信ポート(のファイル記述子)を返す。 134: -/ 135: client = select_server_stream( fd,&fds ); 136: 137: /-1つのクライアントについて要求を処理する関数を呼ぶ。-/ 138: perform( client ); 139: 140: /-TCP/IPの通信路は、perform() により切断される。HTTPでは、1つの要求の 141: 完了ごとに通信路が切断される。-/ 142: } 143: } 144: 145: #define BUFFSIZE 1024 146: 147: /- 148: 1つのクライアントについて要求を処理する関数。引数は、通信用ポートのファ 149: イル記述子。read(), write() により、クライアントと通信を行うことができ 150: る。 151: -/ 152: perform( client ) 153: int client ; 154: { 155: FILE -fin, -fout ; 156: 157: char buff[BUFFSIZE+1] ; 158: char filename[BUFFSIZE] ; 159: 160: /- クライアントの情報を表示。-/ 161: print_client( client ); 162: 163: /- 164: fdopen2() により、ファイル記述子から、FILE -を得る。これにより、 165: fgets(), fprintf() といった標準入出力ライブラリ関数を利用して、プロセ 166: ス間通信を行うことが可能になる。 167: -/ 168: if( fdopen2(client,&fin,&fout) == 0 ) 169: { 170: fprintf( stderr,"fdopen2() faild.\n" ); 171: fprintf( fout,"HTTP/1.0 500 Internal Server Error\r\n" ); 172: return( -1 ); 173: } 174: /- 175: クライアントからの要求の最初の行を読む。これに一番大事な情報であるファ 176: イル名が含まれている。 177: -/ 178: if( fgets( buff,BUFFSIZE,fin ) == 0 ) 179: { 180: fprintf( stderr,"request line read faild.\n" ); 181: fprintf( fout,"HTTP/1.0 500 Internal Server Error\r\n" ); 182: goto error ; 183: } 184: 185: /- sscanf() により、ファイル名を抜き出す。-/ 186: if( sscanf( buff,"GET %s HTTP/1.0", filename ) != 1 ) 187: { 188: fprintf( stderr,"no filename in the request line\n" ); 189: fprintf( fout, "HTTP/1.0 400 Bad Request\r\n" ); 190: goto error; 191: } 192: /- 193: ファイル名に、".." が含まれていたり、"/" で始まっていない場合は、エラー 194: にする。 195: -/ 196: if( strstr(filename,"/../" ) || filename[0] != '/' ) 197: { 198: fprintf( stderr,"Dangerous file name [%s] \n",filename ); 199: fprintf( fout, "HTTP/1.0 404 Not Found\r\n" ); 200: goto error; 201: } 202: 203: fprintf( fout,"HTTP/1.0 200 Document follows\r\n" ); 204: fprintf( fout,"Content-Type: text/plain\r\n" ); 205: fprintf( fout,"\r\n" ); 206: fprintf( fout,"request[%s]\n",filename ); 207: 208: /- 209: もし実際にファイルの内容を返す時には、ここでそのファイルを、fopen() し 210: て、fread() するとよい。ただし、アクセスするファイルの先頭には、"./" 211: を付けて、カレント・ワーキング・ディレクトリ以下のファイルしかアクセス 212: を許さないようにしなさい。また、ファイルのによって、Content-Type: を変 213: えること。 214: -/ 215: 216: /- 2つの fclose() により通信路が切断される。-/ 217: fclose( fin ); 218: fclose( fout ); 219: return( 0 ); 220: 221: error: 222: /- エラーの時は、内容として、HTML でエラーメッセージを返す。-/ 223: fprintf( fout,"Content-Type: text/html\r\n" ); 224: fprintf( fout, "\r\n" ); 225: fprintf( fout, "

Error.

httpd-simple
\n"); 226: fclose( fin ); 227: fclose( fout ); 228: return( -1 ); 229: } 230: 231: /- 自分自身がサービスできる URL を表示する関数。-/ 232: print_my_url( port ) 233: int port ; 234: { 235: char hostname[BUFFSIZE+1] ; 236: /- gethostname() は、ホスト名を得るシステム・コール。-/ 237: gethostname( hostname,BUFFSIZE ); 238: hostname[BUFFSIZE] = 0 ; 239: printf("http://%s:%d/\n",hostname, port ); 240: } 241: 242: /- 243: クライアントの情報を返す関数。引数は、通信用ポートに対応したファイル記 244: 述子。 245: -/ 246: print_client( client ) 247: int client ; 248: { 249: struct sockaddr_in addr; 250: int addrlen; 251: /- 252: getpeername() システム・コールは、通信相手の名前を返す。名前とは、 253: TCP/IP では、ホストのIPアドレスとポート番号である。それは、sockaddr_in 254: 構造体の sin_addr.s_addr と sin_port に設定される。 255: -/ 256: addrlen = sizeof(addr); 257: if( getpeername(client, &addr, &addrlen) < 0 ) 258: { 259: perror("getpeername"); 260: return( -1 ); 261: } 262: printf("host: "); 263: print_ip_address( addr.sin_addr.s_addr ); 264: printf(", port: %d\n",addr.sin_port ); 265: } 266: 267: /-IPアドレスを、点つき10進数で表示する関数。-/ 268: print_ip_address( ipaddr ) 269: unsigned long ipaddr ; 270: { 271: /- 272: この共用体を使えば、32ビットの整数を、8ビットずつ区切ってアクセスする 273: ことができる。 274: -/ 275: union 276: { 277: unsigned long l ; 278: struct { unsigned char c1, c2, c3, c4 } b; 279: } x ; 280: x.l = ipaddr ; 281: printf("%d.%d.%d.%d", x.b.c1, x.b.c2, x.b.c3, x.b.c4 ); 282: } 283: 284: /- --------------------- http-simple.c --------------------- -/ 285: static char rcsid[] = 286: "$Header: /home/h1/yas/slab-info2-inet/2-tcp/RCS/httpd-simple.c,v 1.6 1995/09/11 13:35:12 yas Exp $" ; -----------------------------------------------------------------------