1. 簡介:
tinyhttpd是使用c語言開發的超輕量級http服務器,通過代碼流程可以了解http服務器的基本處理流程,
並且涉及了網絡套接字,線程,父子進程,管道等等知識點;
項目地址:http://sourceforge.net/projects/tinyhttpd/
2. 流程介紹:
(1) 服務器啟動,等待客戶端請求到來;
(2) 客戶端請求到來,創建新線程處理該請求;
(3) 讀取httpHeader中的method,截取url,其中GET方法需要記錄url問號之后的參數串;
(4) 根據url構造完整路徑,如果是/結尾,則指定為該目錄下的index.html;
(5) 獲取文件信息,如果找不到文件,返回404,找到文件則判斷文件權限;
(6) 如果是GET請求並且沒有參數,或者文件不可執行,則直接將文件內容構造http信息返回給客戶端;
(7) 如果是GET帶參數,POST,文件可執行,則執行CGI;
(8) GET請求略過httpHeader,POST方法需要記錄httpHeader中的Content-Length:xx;
(9) 創建管道用於父子進程通信,fork產生子進程;
(10) 子進程設置環境變量,將標准輸入和輸出與管道相連,並且通過exec執行CGI;
(11) 如果是POST,父進程將讀到post內容發送給子進程,並且接收子進程的輸出,輸出給客戶端;
3. 管道說明:
4. 代碼注釋:
1 /* J. David's webserver */ 2 /* This is a simple webserver. 3 * Created November 1999 by J. David Blackstone. 4 * CSE 4344 (Network concepts), Prof. Zeigler 5 * University of Texas at Arlington 6 */ 7 /* This program compiles for Sparc Solaris 2.6. 8 * To compile for Linux: 9 * 1) Comment out the #include <pthread.h> line. 10 * 2) Comment out the line that defines the variable newthread. 11 * 3) Comment out the two lines that run pthread_create(). 12 * 4) Uncomment the line that runs accept_request(). 13 * 5) Remove -lsocket from the Makefile. 14 */ 15 #include <stdio.h> 16 #include <sys/socket.h> 17 #include <sys/types.h> 18 #include <netinet/in.h> 19 #include <arpa/inet.h> 20 #include <unistd.h> 21 #include <ctype.h> 22 #include <strings.h> 23 #include <string.h> 24 #include <sys/stat.h> 25 #include <pthread.h> 26 #include <sys/wait.h> 27 #include <stdlib.h> 28 29 #define ISspace(x) isspace((int)(x)) 30 31 #define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n" 32 33 void accept_request(int); 34 void bad_request(int); 35 void cat(int, FILE *); 36 void cannot_execute(int); 37 void error_die(const char *); 38 void execute_cgi(int, const char *, const char *, const char *); 39 int get_line(int, char *, int); 40 void headers(int, const char *); 41 void not_found(int); 42 void serve_file(int, const char *); 43 int startup(u_short *); 44 void unimplemented(int); 45 46 /**********************************************************************/ 47 /* A request has caused a call to accept() on the server port to 48 * return. Process the request appropriately. 49 * Parameters: the socket connected to the client */ 50 /**********************************************************************/ 51 void accept_request(int client) 52 { 53 char buf[1024]; 54 int numchars; 55 char method[255]; 56 char url[255]; 57 char path[512]; 58 size_t i, j; 59 struct stat st; 60 int cgi = 0; /* becomes true if server decides this is a CGI 61 * program */ 62 char *query_string = NULL; 63 64 //讀取第一行數據 65 numchars = get_line(client, buf, sizeof(buf)); 66 i = 0; j = 0; 67 //讀取http的頭部method字段,讀到空白為止 68 while (!ISspace(buf[j]) && (i < sizeof(method) - 1)) 69 { 70 method[i] = buf[j]; 71 i++; j++; 72 } 73 method[i] = '\0'; 74 75 //只支持GET和POST請求,其他請求方式返回未實現 76 if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) 77 { 78 unimplemented(client); 79 return; 80 } 81 //如果是POST請求,設置cgi標志為1 82 if (strcasecmp(method, "POST") == 0) 83 cgi = 1; 84 85 i = 0; 86 //跳過空白字符 87 while (ISspace(buf[j]) && (j < sizeof(buf))) 88 j++; 89 //讀取url字串 90 while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf))) 91 { 92 url[i] = buf[j]; 93 i++; j++; 94 } 95 url[i] = '\0'; 96 //如果是GET請求,需要從url中解析參數 97 if (strcasecmp(method, "GET") == 0) 98 { 99 query_string = url; 100 //找到?位置 101 while ((*query_string != '?') && (*query_string != '\0')) 102 query_string++; 103 //當前字符為?字符 104 if (*query_string == '?') 105 { 106 cgi = 1; //標記cgi字段 107 *query_string = '\0'; //將?替換成\0 108 query_string++; //query_string指向get參數 109 } 110 } 111 //連接url資源路徑 112 sprintf(path, "htdocs%s", url); 113 //如果訪問的是/結尾的目錄,那么指定為目錄下的index.html 114 if (path[strlen(path) - 1] == '/') 115 strcat(path, "index.html"); 116 //獲取文件信息失敗 117 if (stat(path, &st) == -1) { 118 //將header中的信息都丟棄 119 while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ 120 numchars = get_line(client, buf, sizeof(buf)); 121 //返回404 122 not_found(client); 123 } 124 else 125 { 126 //如果訪問的是目錄,那么指定為目錄下的index.html 127 if ((st.st_mode & S_IFMT) == S_IFDIR) 128 strcat(path, "/index.html"); 129 //如果具有可執行權限,標記cgi 130 if ((st.st_mode & S_IXUSR) || 131 (st.st_mode & S_IXGRP) || 132 (st.st_mode & S_IXOTH) ) 133 cgi = 1; 134 //不需要cgi參與的文件直接進行服務 135 if (!cgi) 136 serve_file(client, path); 137 //否則執行cgi 138 else 139 execute_cgi(client, path, method, query_string); 140 } 141 142 close(client); 143 } 144 145 /**********************************************************************/ 146 /* Inform the client that a request it has made has a problem. 147 * Parameters: client socket */ 148 /**********************************************************************/ 149 void bad_request(int client) 150 { 151 char buf[1024]; 152 //發送BAD REQUEST提示信息到客戶端 153 sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n"); 154 send(client, buf, sizeof(buf), 0); 155 sprintf(buf, "Content-type: text/html\r\n"); 156 send(client, buf, sizeof(buf), 0); 157 sprintf(buf, "\r\n"); 158 send(client, buf, sizeof(buf), 0); 159 sprintf(buf, "<P>Your browser sent a bad request, "); 160 send(client, buf, sizeof(buf), 0); 161 sprintf(buf, "such as a POST without a Content-Length.\r\n"); 162 send(client, buf, sizeof(buf), 0); 163 } 164 165 /**********************************************************************/ 166 /* Put the entire contents of a file out on a socket. This function 167 * is named after the UNIX "cat" command, because it might have been 168 * easier just to do something like pipe, fork, and exec("cat"). 169 * Parameters: the client socket descriptor 170 * FILE pointer for the file to cat */ 171 /**********************************************************************/ 172 void cat(int client, FILE *resource) 173 { 174 char buf[1024]; 175 //循環讀取並發送文件內容 176 fgets(buf, sizeof(buf), resource); 177 while (!feof(resource)) 178 { 179 send(client, buf, strlen(buf), 0); 180 fgets(buf, sizeof(buf), resource); 181 } 182 } 183 184 /**********************************************************************/ 185 /* Inform the client that a CGI script could not be executed. 186 * Parameter: the client socket descriptor. */ 187 /**********************************************************************/ 188 void cannot_execute(int client) 189 { 190 char buf[1024]; 191 //發送500服務器內部錯誤到客戶端 192 sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n"); 193 send(client, buf, strlen(buf), 0); 194 sprintf(buf, "Content-type: text/html\r\n"); 195 send(client, buf, strlen(buf), 0); 196 sprintf(buf, "\r\n"); 197 send(client, buf, strlen(buf), 0); 198 sprintf(buf, "<P>Error prohibited CGI execution.\r\n"); 199 send(client, buf, strlen(buf), 0); 200 } 201 202 /**********************************************************************/ 203 /* Print out an error message with perror() (for system errors; based 204 * on value of errno, which indicates system call errors) and exit the 205 * program indicating an error. */ 206 /**********************************************************************/ 207 void error_die(const char *sc) 208 { 209 //打印錯誤信息並退出 210 perror(sc); 211 exit(1); 212 } 213 214 /**********************************************************************/ 215 /* Execute a CGI script. Will need to set environment variables as 216 * appropriate. 217 * Parameters: client socket descriptor 218 * path to the CGI script */ 219 /**********************************************************************/ 220 void execute_cgi(int client, const char *path, 221 const char *method, const char *query_string) 222 { 223 char buf[1024]; 224 int cgi_output[2]; 225 int cgi_input[2]; 226 pid_t pid; 227 int status; 228 int i; 229 char c; 230 int numchars = 1; 231 int content_length = -1; 232 233 buf[0] = 'A'; buf[1] = '\0'; 234 //如果是GET請求則讀取並丟掉頭部信息 235 if (strcasecmp(method, "GET") == 0) 236 while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ 237 numchars = get_line(client, buf, sizeof(buf)); 238 //如果是POST請求 239 else /* POST */ 240 { 241 //讀取一行數據 242 numchars = get_line(client, buf, sizeof(buf)); 243 while ((numchars > 0) && strcmp("\n", buf)) 244 { 245 //截取Content-Length:字段 246 buf[15] = '\0'; 247 //如果找到該字段,將該字段后面的字串轉成整數長度 248 if (strcasecmp(buf, "Content-Length:") == 0) 249 content_length = atoi(&(buf[16])); 250 //讀取頭部內容 251 numchars = get_line(client, buf, sizeof(buf)); 252 } 253 //沒有找到Content-Length,發送bad request 254 if (content_length == -1) { 255 bad_request(client); 256 return; 257 } 258 } 259 //發送http200頭 260 sprintf(buf, "HTTP/1.0 200 OK\r\n"); 261 send(client, buf, strlen(buf), 0); 262 //創建輸出管道,構造父子進程通信 263 if (pipe(cgi_output) < 0) { 264 cannot_execute(client); 265 return; 266 } 267 //創建輸入管道,構造父子進程通信 268 if (pipe(cgi_input) < 0) { 269 cannot_execute(client); 270 return; 271 } 272 //創建子進程 273 if ( (pid = fork()) < 0 ) { 274 cannot_execute(client); 275 return; 276 } 277 //子進程執行CGI腳本 278 if (pid == 0) /* child: CGI script */ 279 { 280 char meth_env[255]; 281 char query_env[255]; 282 char length_env[255]; 283 284 //子進程的標准輸入輸出與管道對接 285 dup2(cgi_output[1], 1); //將標准輸出重定向到cgi輸出管道的寫端 286 dup2(cgi_input[0], 0); //將標准輸入重定向到cgi輸入管道的讀端 287 288 close(cgi_output[0]); //關閉cgi輸出管道的讀端 289 close(cgi_input[1]); //關閉cgi輸入管道的寫端 290 //設置method環境變量 291 sprintf(meth_env, "REQUEST_METHOD=%s", method); 292 putenv(meth_env); 293 //如果是GET方式設置請求參數環境變量 294 if (strcasecmp(method, "GET") == 0) { 295 sprintf(query_env, "QUERY_STRING=%s", query_string); 296 putenv(query_env); 297 } 298 //如果是POST方式設置內容長度環境變量 299 else { /* POST */ 300 sprintf(length_env, "CONTENT_LENGTH=%d", content_length); 301 putenv(length_env); 302 } 303 //執行CGI 304 execl(path, path, NULL); 305 exit(0); 306 } else { /* parent */ 307 close(cgi_output[1]); //關閉cgi輸出管道的寫端 308 close(cgi_input[0]); //關閉cgi輸入管道的讀端 309 //如果是POST請求,循環讀取post內容,並且輸入到cgi子進程 310 if (strcasecmp(method, "POST") == 0) 311 for (i = 0; i < content_length; i++) { 312 recv(client, &c, 1, 0); 313 write(cgi_input[1], &c, 1); 314 } 315 //從cgi中循環讀取輸出內容,發送到客戶端 316 while (read(cgi_output[0], &c, 1) > 0) 317 send(client, &c, 1, 0); 318 319 //關閉管道 320 close(cgi_output[0]); 321 close(cgi_input[1]); 322 //等待子進程結束 323 waitpid(pid, &status, 0); 324 } 325 } 326 327 /**********************************************************************/ 328 /* Get a line from a socket, whether the line ends in a newline, 329 * carriage return, or a CRLF combination. Terminates the string read 330 * with a null character. If no newline indicator is found before the 331 * end of the buffer, the string is terminated with a null. If any of 332 * the above three line terminators is read, the last character of the 333 * string will be a linefeed and the string will be terminated with a 334 * null character. 335 * Parameters: the socket descriptor 336 * the buffer to save the data in 337 * the size of the buffer 338 * Returns: the number of bytes stored (excluding null) */ 339 /**********************************************************************/ 340 int get_line(int sock, char *buf, int size) 341 { 342 int i = 0; 343 char c = '\0'; 344 int n; 345 //接收\n結束的一行數據或接收滿緩沖區 346 while ((i < size - 1) && (c != '\n')) 347 { 348 //接收一個字節 349 n = recv(sock, &c, 1, 0); 350 /* DEBUG printf("%02X\n", c); */ 351 if (n > 0) 352 { 353 //如果接收到了\r符號 354 if (c == '\r') 355 { 356 //將下一個字字符預取出來,注意MSG_PEEK本地接收窗口不滑動,下次讀取仍然可以讀取到該字符 357 n = recv(sock, &c, 1, MSG_PEEK); 358 /* DEBUG printf("%02X\n", c); */ 359 //如果下一個字符是\n的話,那么接收這個字符 360 if ((n > 0) && (c == '\n')) 361 recv(sock, &c, 1, 0); 362 //不是\n的話,那么將\r替換成\n 363 else 364 c = '\n'; 365 } 366 //字符存入buf,繼續讀取 367 buf[i] = c; 368 i++; 369 } 370 else 371 c = '\n'; 372 } 373 //設置buf字符串結束符 374 buf[i] = '\0'; 375 376 return(i); 377 } 378 379 /**********************************************************************/ 380 /* Return the informational HTTP headers about a file. */ 381 /* Parameters: the socket to print the headers on 382 * the name of the file */ 383 /**********************************************************************/ 384 void headers(int client, const char *filename) 385 { 386 char buf[1024]; 387 (void)filename; /* could use filename to determine file type */ 388 //發送http頭 http碼 服務器信息 內容類型等頭部信息 389 strcpy(buf, "HTTP/1.0 200 OK\r\n"); 390 send(client, buf, strlen(buf), 0); 391 strcpy(buf, SERVER_STRING); 392 send(client, buf, strlen(buf), 0); 393 sprintf(buf, "Content-Type: text/html\r\n"); 394 send(client, buf, strlen(buf), 0); 395 strcpy(buf, "\r\n"); 396 send(client, buf, strlen(buf), 0); 397 } 398 399 /**********************************************************************/ 400 /* Give a client a 404 not found status message. */ 401 /**********************************************************************/ 402 void not_found(int client) 403 { 404 char buf[1024]; 405 //發送http頭 http碼 服務器信息 內容類型等頭部信息 html提示信息 406 sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n"); 407 send(client, buf, strlen(buf), 0); 408 sprintf(buf, SERVER_STRING); 409 send(client, buf, strlen(buf), 0); 410 sprintf(buf, "Content-Type: text/html\r\n"); 411 send(client, buf, strlen(buf), 0); 412 sprintf(buf, "\r\n"); 413 send(client, buf, strlen(buf), 0); 414 sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n"); 415 send(client, buf, strlen(buf), 0); 416 sprintf(buf, "<BODY><P>The server could not fulfill\r\n"); 417 send(client, buf, strlen(buf), 0); 418 sprintf(buf, "your request because the resource specified\r\n"); 419 send(client, buf, strlen(buf), 0); 420 sprintf(buf, "is unavailable or nonexistent.\r\n"); 421 send(client, buf, strlen(buf), 0); 422 sprintf(buf, "</BODY></HTML>\r\n"); 423 send(client, buf, strlen(buf), 0); 424 } 425 426 /**********************************************************************/ 427 /* Send a regular file to the client. Use headers, and report 428 * errors to client if they occur. 429 * Parameters: a pointer to a file structure produced from the socket 430 * file descriptor 431 * the name of the file to serve */ 432 /**********************************************************************/ 433 void serve_file(int client, const char *filename) 434 { 435 FILE *resource = NULL; 436 int numchars = 1; 437 char buf[1024]; 438 439 //讀取丟棄所有頭部信息 440 buf[0] = 'A'; buf[1] = '\0'; 441 while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ 442 numchars = get_line(client, buf, sizeof(buf)); 443 444 //打開資源文件 445 resource = fopen(filename, "r"); 446 //打開失敗,發送404 447 if (resource == NULL) 448 not_found(client); 449 else 450 { 451 headers(client, filename); //發送http頭 452 cat(client, resource); //發送資源文件內容 453 } 454 //關閉資源文件 455 fclose(resource); 456 } 457 458 /**********************************************************************/ 459 /* This function starts the process of listening for web connections 460 * on a specified port. If the port is 0, then dynamically allocate a 461 * port and modify the original port variable to reflect the actual 462 * port. 463 * Parameters: pointer to variable containing the port to connect on 464 * Returns: the socket */ 465 /**********************************************************************/ 466 int startup(u_short *port) 467 { 468 int httpd = 0; 469 struct sockaddr_in name; 470 //創建tcp socket 471 httpd = socket(PF_INET, SOCK_STREAM, 0); 472 if (httpd == -1) 473 error_die("socket"); 474 memset(&name, 0, sizeof(name)); 475 //設置sockaddr地址結構 476 name.sin_family = AF_INET; 477 name.sin_port = htons(*port); 478 name.sin_addr.s_addr = htonl(INADDR_ANY); 479 //綁定到本地地址port指定端口 480 if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0) 481 error_die("bind"); 482 //如果沒有指定端口,則由系統指定,此處或得到系統指定的端口 483 if (*port == 0) /* if dynamically allocating a port */ 484 { 485 int namelen = sizeof(name); 486 if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1) 487 error_die("getsockname"); 488 *port = ntohs(name.sin_port); 489 } 490 //服務器開始監聽 491 if (listen(httpd, 5) < 0) 492 error_die("listen"); 493 return(httpd); 494 } 495 496 /**********************************************************************/ 497 /* Inform the client that the requested web method has not been 498 * implemented. 499 * Parameter: the client socket */ 500 /**********************************************************************/ 501 void unimplemented(int client) 502 { 503 char buf[1024]; 504 //發送未實現的請求方法和提示消息給客戶端 505 sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n"); 506 send(client, buf, strlen(buf), 0); 507 sprintf(buf, SERVER_STRING); 508 send(client, buf, strlen(buf), 0); 509 sprintf(buf, "Content-Type: text/html\r\n"); 510 send(client, buf, strlen(buf), 0); 511 sprintf(buf, "\r\n"); 512 send(client, buf, strlen(buf), 0); 513 sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n"); 514 send(client, buf, strlen(buf), 0); 515 sprintf(buf, "</TITLE></HEAD>\r\n"); 516 send(client, buf, strlen(buf), 0); 517 sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n"); 518 send(client, buf, strlen(buf), 0); 519 sprintf(buf, "</BODY></HTML>\r\n"); 520 send(client, buf, strlen(buf), 0); 521 } 522 523 /**********************************************************************/ 524 525 int main(void) 526 { 527 int server_sock = -1; 528 u_short port = 0; 529 int client_sock = -1; 530 struct sockaddr_in client_name; 531 int client_name_len = sizeof(client_name); 532 pthread_t newthread; 533 534 server_sock = startup(&port); 535 printf("httpd running on port %d\n", port); 536 537 while (1) 538 { 539 //等待客戶端連接到來 540 client_sock = accept(server_sock, 541 (struct sockaddr *)&client_name, 542 &client_name_len); 543 if (client_sock == -1) 544 error_die("accept"); 545 /* accept_request(client_sock); */ 546 //開啟一個新線程處理客戶端請求 547 if (pthread_create(&newthread , NULL, accept_request, client_sock) != 0) 548 perror("pthread_create"); 549 } 550 //關閉服務器 551 close(server_sock); 552 553 return(0); 554 }