工作流程:
1>服务器启动,在指定端口或随机选取端口绑定httpd服务。
2>收到一个http请求时(其实就是listen端口accept的时候),派生一个线程运行accept_request函数。
3>取出http请求中method(get或post)和url,对于get方法,如果有携带参数,则query_string指针指向url中?后面的get参数。
4>格式化url到path数组,表示浏览器请求的文件路径,在tinyhttpd中服务器文件是在htdocs文件夹下。当url以/结尾,或者url是个目录,则默认在path中加上index.thml,表示访问主页。
5>如果文件路径合法,对于无参数的get请求,直接输出服务器文件到浏览器,即用http格式写到套接字上,跳到(10)。其他情况(带参数get,post方法,url为科执行文件),则调用execute_cgi函数执行cgi脚本。
6>读取整个http请求并丢弃,如果是post则找出content-length,把http状态码200写到套接字里面。
7>建立两个管道,cgi_input和cgi_output,并fork一个子进程。
8>在子进程中,把stdout重定向到cgi_output的写入端,把stdin重定向到cgi_input的读取端,关闭cgi_input的写入端和cgi_output的读取端,是指request_method的环境变量,get的话设置query_string的环境变量,post的话设置content-length的环境变量,这些环境变量都是为了给cgi脚本调用,接着用execl运行cgi程序。
9>在父进程中,关闭cgi_input的读取端和cgi_output的写入端,如果post的话,把post数据写入到cgo_input,已被重定向到stdin读取cgi_output的管道输出到客户端,等待子进程结束。
10>关闭与浏览器的链接,完成一次http请求与回应,因为http是无连接的。
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 //处理从套接字上监听到的一个HTTP请求,在这里可以很大一部分的体现服务器处理请求的流程 35 void bad_request(int); 36 //返回给客户端这是个错误请求,HTTP状态码是400 BAD REQUEST 37 void cat(int, FILE *); 38 //读取服务器上某个文件写到socket套接字 39 void cannot_execute(int); 40 //主要执行在处理cgi程序的处理,也是个主要函数 41 void error_die(const char *); 42 //把错误信息写到perror并退出 43 void execute_cgi(int, const char *, const char *, const char *); 44 //运行cgi程序的处理,也是哥主函数 45 int get_line(int, char *, int); 46 //读取套接字的一行,把回车换行等情况都统一为换行符结束 47 void headers(int, const char *); 48 //把HTTP相应头写到套接字 49 void not_found(int); 50 //主要处理找不到请求的文件时的情况 51 void serve_file(int, const char *); 52 //调用cat把服务器文件返回给浏览器 53 int startup(u_short *); 54 //初始化httpd服务,包括建立套接字,绑定端口,进行监听等 55 void unimplemented(int); 56 //返回给浏览器表示接收到的http请求所用的method不被支持 57 58 /**********************************************************************/ 59 /* A request has caused a call to accept() on the server port to 60 * return. Process the request appropriately. 61 * Parameters: the socket connected to the client */ 62 /**********************************************************************/ 63 void accept_request(int client) 64 { 65 66 char buf[1024]; 67 int numchars; 68 char method[255]; 69 char url[255]; 70 char path[512]; 71 size_t i, j; 72 struct stat st; 73 int cgi = 0; /* becomes true if server decides this is a CGI 74 * program */ 75 char *query_string = NULL; 76 77 numchars = get_line(client, buf, sizeof(buf)); 78 //读取 client端发送的数据并且 返回的参数是 numchars 79 i = 0; 80 j = 0; 81 while (!ISspace(buf[j]) && (i < sizeof(method) - 1)) 82 { 83 method[i] = buf[j]; 84 i++; 85 j++; 86 } 87 //得到传递的参数是post 还是get方法 还是其他 88 method[i] = ‘\0‘; 89 90 if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) 91 { 92 //回给浏览器表明收到的 HTTP 请求所用的 method 不被支持 93 unimplemented(client); 94 return; 95 } 96 97 if (strcasecmp(method, "POST") == 0) 98 cgi = 1; 99 100 i = 0; 101 //http请求行格式是 method urI http-version 102 while (ISspace(buf[j]) && (j < sizeof(buf))) 103 j++; 104 while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf))) 105 { 106 url[i] = buf[j]; //包含请求行中的URI 107 i++; 108 j++; 109 } 110 url[i] = ‘\0‘; 111 ///获取发送数据中的URI 112 if (strcasecmp(method, "GET") == 0) 113 { 114 //get方法 是将参数传递在URI中的?后面如果元素多用&进行链接 115 query_string = url; 116 while ((*query_string != ‘?‘) && (*query_string != ‘\0‘)) 117 query_string++; 118 // get 请求? 后面为参数 119 if (*query_string == ‘?‘) 120 { 121 cgi = 1; 122 *query_string = ‘\0‘; 123 query_string++; 124 } 125 } 126 //格式url存储在path数组 并且html存储在 htdocs文件中 127 sprintf(path, "htdocs%s", url); 128 if (path[strlen(path) - 1] == ‘/‘) 129 strcat(path, "index.html"); //字符串进行衔接 + index.html 130 //stat函数 根据path路径 获取文件内容 存储在 st 结构体中 成功返回0 错误返回-1 131 if (stat(path, &st) == -1) 132 { 133 while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ 134 numchars = get_line(client, buf, sizeof(buf)); 135 not_found(client); 136 } 137 else 138 { 139 if ((st.st_mode & S_IFMT) == S_IFDIR) 140 strcat(path, "/index.html"); 141 //文件的权限 属主 属组 其它 三种任一个拥有执行权 142 if ((st.st_mode & S_IXUSR) || 143 (st.st_mode & S_IXGRP) || 144 (st.st_mode & S_IXOTH) ) 145 cgi = 1; 146 //调用cat 把服务器文件返回给浏览器 post方法或者拥有执行权限 147 if (!cgi) 148 serve_file(client, path); 149 else 150 execute_cgi(client, path, method, query_string) 151 //运行cgi程序的处理,也是个主要函数 152 } 153 154 close(client); 155 } 156 157 /**********************************************************************/ 158 /* Inform the client that a request it has made has a problem. 159 * Parameters: client socket */ 160 /**********************************************************************/ 161 void bad_request(int client) 162 { 163 char buf[1024]; 164 165 sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n"); 166 send(client, buf, sizeof(buf), 0); 167 sprintf(buf, "Content-type: text/html\r\n"); 168 send(client, buf, sizeof(buf), 0); 169 sprintf(buf, "\r\n"); 170 send(client, buf, sizeof(buf), 0); 171 sprintf(buf, "<P>Your browser sent a bad request, "); 172 send(client, buf, sizeof(buf), 0); 173 sprintf(buf, "such as a POST without a Content-Length.\r\n"); 174 send(client, buf, sizeof(buf), 0); 175 } 176 177 /**********************************************************************/ 178 /* Put the entire contents of a file out on a socket. This function 179 * is named after the UNIX "cat" command, because it might have been 180 * easier just to do something like pipe, fork, and exec("cat"). 181 * Parameters: the client socket descriptor 182 * FILE pointer for the file to cat */ 183 /**********************************************************************/ 184 //读取服务器上的某个文件 写到socket套接字上 185 void cat(int client, FILE *resource) 186 { 187 char buf[1024]; 188 189 fgets(buf, sizeof(buf), resource); 190 //检测流上的文件结束符 如果文件结束,则返回非0值,否则返回0,文件结束符只能被clearerr()清除。 191 while (!feof(resource)) 192 { 193 send(client, buf, strlen(buf), 0); 194 fgets(buf, sizeof(buf), resource); 195 } 196 } 197 198 /**********************************************************************/ 199 /* Inform the client that a CGI script could not be executed. 200 * Parameter: the client socket descriptor. */ 201 /**********************************************************************/ 202 void cannot_execute(int client) 203 { 204 char buf[1024]; 205 206 sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n"); 207 send(client, buf, strlen(buf), 0); 208 sprintf(buf, "Content-type: text/html\r\n"); 209 send(client, buf, strlen(buf), 0); 210 sprintf(buf, "\r\n"); 211 send(client, buf, strlen(buf), 0); 212 sprintf(buf, "<P>Error prohibited CGI execution.\r\n"); 213 send(client, buf, strlen(buf), 0); 214 } 215 216 /**********************************************************************/ 217 /* Print out an error message with perror() (for system errors; based 218 * on value of errno, which indicates system call errors) and exit the 219 * program indicating an error. */ 220 /**********************************************************************/ 221 void error_die(const char *sc) 222 { 223 perror(sc); 224 exit(1); 225 } 226 227 /**********************************************************************/ 228 /* Execute a CGI script. Will need to set environment variables as 229 * appropriate. 230 * Parameters: client socket descriptor 231 * path to the CGI script */ 232 /**********************************************************************/ 233 234 //运行cgi程序 也是个主函数 235 void execute_cgi(int client, const char *path, 236 const char *method, const char *query_string) 237 { 238 //在父进程中,关闭cgi_input的读入端和cgi_output的写入端,如果post的话 239 //把post数据写入到cgi_input,已被重定向到stdin,读取cgi_output的管道 240 //输出到客户端,该管道输入是stdout,接着关闭所有管道,等待子进程结束。 241 242 char buf[1024]; 243 int cgi_output[2]; 244 int cgi_input[2]; 245 //cgi_output[1] cgi_input[1] 为输入端 246 //cgi_input[0] cgi_output[0] 为输出端 247 pid_t pid; 248 int status; 249 int i; 250 char c; 251 int numchars = 1; 252 int content_length = -1; 253 254 buf[0] = ‘A‘; 255 buf[1] = ‘\0‘; 256 //读入请求头 257 if (strcasecmp(method, "GET") == 0) 258 while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ 259 numchars = get_line(client, buf, sizeof(buf)); 260 else /* POST */ 261 { 262 numchars = get_line(client, buf, sizeof(buf)); 263 while ((numchars > 0) && strcmp("\n", buf)) 264 { 265 buf[15] = ‘\0‘; 266 if (strcasecmp(buf, "Content-Length:") == 0) 267 content_length = atoi(&(buf[16])); 268 numchars = get_line(client, buf, sizeof(buf)); 269 } 270 if (content_length == -1) 271 { 272 bad_request(client); 273 return; 274 } 275 } 276 277 sprintf(buf, "HTTP/1.0 200 OK\r\n"); 278 send(client, buf, strlen(buf), 0); 279 280 if (pipe(cgi_output) < 0) 281 { 282 cannot_execute(client); 283 return; 284 } 285 if (pipe(cgi_input) < 0) 286 { 287 cannot_execute(client); 288 return; 289 } 290 291 if ( (pid = fork()) < 0 ) 292 { 293 cannot_execute(client); 294 return; 295 } 296 if (pid == 0) /* child: CGI script */ 297 { 298 char meth_env[255]; 299 char query_env[255]; 300 char length_env[255]; 301 //把stdout重定向到cgi_output的写入端 302 dup2(cgi_output[1], 1); 303 //把stdin重定向到cgi_input的读入端 304 dup2(cgi_input[0], 0); 305 //关闭cgi_output的读入端 和 cgi_input的 写入端 306 close(cgi_output[0]); 307 close(cgi_input[1]); 308 //设置request_method的环境变量 309 sprintf(meth_env, "REQUEST_METHOD=%s", method); 310 putenv(meth_env); 311 if (strcasecmp(method, "GET") == 0) 312 { 313 //设置query_string的环境变量 314 sprintf(query_env, "QUERY_STRING=%s", query_string); 315 putenv(query_env); 316 } 317 else /* POST */ 318 { 319 //设置content_length的环境变量 320 sprintf(length_env, "CONTENT_LENGTH=%d", content_length); 321 putenv(length_env); 322 } 323 //用execl运行cgi程序 324 execl(path, path, NULL); 325 exit(0); 326 } 327 else /* parent */ 328 { 329 close(cgi_output[1]); 330 close(cgi_input[0]); 331 //关闭cgi_output的 写入端 和 cgi_input的读取端 332 if (strcasecmp(method, "POST") == 0) 333 //接受post的数据 334 for (i = 0; i < content_length; i++) 335 { 336 recv(client, &c, 1, 0); 337 write(cgi_input[1], &c, 1); 338 //讲post数据写入cgi_input,并且重定向到stdin中 339 } 340 //读取cgi_output的管道输出到客户端,该管道输入时stdout 341 while (read(cgi_output[0], &c, 1) > 0) 342 send(client, &c, 1, 0); 343 //关闭管道 344 close(cgi_output[0]); 345 close(cgi_input[1]); 346 //等待子进程 347 waitpid(pid, &status, 0); 348 } 349 } 350 351 /**********************************************************************/ 352 /* Get a line from a socket, whether the line ends in a newline, 353 * carriage return, or a CRLF combination. Terminates the string read 354 * with a null character. If no newline indicator is found before the 355 * end of the buffer, the string is terminated with a null. If any of 356 * the above three line terminators is read, the last character of the 357 * string will be a linefeed and the string will be terminated with a 358 * null character. 359 * Parameters: the socket descriptor 360 * the buffer to save the data in 361 * the size of the buffer 362 * Returns: the number of bytes stored (excluding null) */ 363 /**********************************************************************/ 364 int get_line(int sock, char *buf, int size) 365 { 366 int i = 0; 367 char c = ‘\0‘; 368 int n; 369 370 while ((i < size - 1) && (c != ‘\n‘)) 371 { 372 //每次接受一个字符 373 n = recv(sock, &c, 1, 0); 374 /* DEBUG printf("%02X\n", c); */ 375 if (n > 0) 376 { 377 if (c == ‘\r‘) 378 { 379 n = recv(sock, &c, 1, MSG_PEEK); 380 /* DEBUG printf("%02X\n", c); */ 381 if ((n > 0) && (c == ‘\n‘)) 382 recv(sock, &c, 1, 0); 383 else 384 c = ‘\n‘; 385 } 386 buf[i] = c; 387 i++; 388 } 389 else 390 c = ‘\n‘; 391 } 392 buf[i] = ‘\0‘; 393 394 return(i); 395 } 396 397 /**********************************************************************/ 398 /* Return the informational HTTP headers about a file. */ 399 /* Parameters: the socket to print the headers on 400 * the name of the file */ 401 /**********************************************************************/ 402 //http 响应体 403 void headers(int client, const char *filename) 404 { 405 char buf[1024]; 406 (void)filename; /* could use filename to determine file type */ 407 408 strcpy(buf, "HTTP/1.0 200 OK\r\n");//响应的状态行 409 send(client, buf, strlen(buf), 0); 410 strcpy(buf, SERVER_STRING); 411 send(client, buf, strlen(buf), 0); 412 sprintf(buf, "Content-Type: text/html\r\n"); //响应头 413 send(client, buf, strlen(buf), 0); 414 strcpy(buf, "\r\n"); //响应正文段 415 send(client, buf, strlen(buf), 0); 416 } 417 418 /**********************************************************************/ 419 /* Give a client a 404 not found status message. */ 420 /**********************************************************************/ 421 void not_found(int client) 422 { 423 //客户端发送的请求无法实现 424 char buf[1024]; 425 //响应行 426 sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n"); 427 send(client, buf, strlen(buf), 0); 428 sprintf(buf, SERVER_STRING); 429 send(client, buf, strlen(buf), 0); 430 //响应头 431 sprintf(buf, "Content-Type: text/html\r\n"); 432 send(client, buf, strlen(buf), 0); 433 sprintf(buf, "\r\n"); 434 //响应头和响应正文段之间有一个空行 表示响应行结束 435 send(client, buf, strlen(buf), 0); 436 //响应正文段 text/html 437 sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n"); 438 send(client, buf, strlen(buf), 0); 439 sprintf(buf, "<BODY><P>The server could not fulfill\r\n"); 440 send(client, buf, strlen(buf), 0); 441 sprintf(buf, "your request because the resource specified\r\n"); 442 send(client, buf, strlen(buf), 0); 443 sprintf(buf, "is unavailable or nonexistent.\r\n"); 444 send(client, buf, strlen(buf), 0); 445 sprintf(buf, "</BODY></HTML>\r\n"); 446 send(client, buf, strlen(buf), 0); 447 } 448 449 /**********************************************************************/ 450 /* Send a regular file to the client. Use headers, and report 451 * errors to client if they occur. 452 * Parameters: a pointer to a file structure produced from the socket 453 * file descriptor 454 * the name of the file to serve */ 455 /**********************************************************************/ 456 void serve_file(int client, const char *filename) 457 { 458 FILE *resource = NULL; 459 int numchars = 1; 460 char buf[1024]; 461 462 buf[0] = ‘A‘; 463 buf[1] = ‘\0‘; 464 while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ 465 numchars = get_line(client, buf, sizeof(buf)); 466 467 resource = fopen(filename, "r"); 468 if (resource == NULL) 469 not_found(client); 470 else 471 { 472 headers(client, filename); 473 cat(client, resource); 474 } 475 fclose(resource); 476 } 477 478 /**********************************************************************/ 479 /* This function starts the process of listening for web connections 480 * on a specified port. If the port is 0, then dynamically allo //响应正文段cate a 481 * port and modify the original port variable to reflect the actual 482 * port. 483 * Parameters: pointer to variable containing the port to connect on 484 * Returns: the socket */ 485 /**********************************************************************/ 486 int startup(u_short *port) 487 { 488 int httpd = 0; 489 struct sockaddr_in name; 490 //建立套接字 491 httpd = socket(PF_INET, SOCK_STREAM, 0); 492 if (httpd == -1) 493 error_die("socket"); 494 memset(&name, 0, sizeof(name)); 495 name.sin_family = AF_INET; 496 name.sin_port = htons(*port); 497 name.sin_addr.s_addr = htonl(INADDR_ANY); 498 //绑定端口和ip 499 if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0) 500 error_die("bind"); 501 if (*port == 0) /* if dynamically allocating a port */ 502 { 503 int namelen = sizeof(name); 504 if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1) 505 error_die("getsockname"); 506 *port = ntohs(name.sin_port); 507 } 508 //监听 509 if (listen(httpd, 5) < 0) 510 error_die("listen"); 511 return(httpd); 512 } 513 514 /**********************************************************************/ 515 /* Inform the client that the requested web method has not been 516 * implemented. 517 * Parameter: the client socket */ 518 /**********************************************************************/ 519 void unimplemented(int client) 520 { 521 //发回响应信息 http的请求方法不被接受 522 char buf[1024]; 523 //返回501 错误 未实现 (Not implemented)是指Web 服务器不理解或不支持发送给它的 HTTP 数据流中找到的 HTTP 方法 524 sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n"); 525 send(client, buf, strlen(buf), 0); 526 sprintf(buf, SERVER_STRING); 527 send(client, buf, strlen(buf), 0); 528 sprintf(buf, "Content-Type: text/html\r\n");//响应头 529 send(client, buf, strlen(buf), 0); 530 sprintf(buf, "\r\n"); 531 send(client, buf, strlen(buf), 0); //响应正文段 532 sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n"); 533 send(client, buf, strlen(buf), 0); 534 sprintf(buf, "</TITLE></HEAD>\r\n"); 535 send(client, buf, strlen(buf), 0); 536 sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n"); 537 send(client, buf, strlen(buf), 0); 538 sprintf(buf, "</BODY></HTML>\r\n"); 539 send(client, buf, strlen(buf), 0); 540 } 541 542 /**********************************************************************/ 543 544 int main(void) 545 { 546 int server_sock = -1; 547 u_short port = 0; 548 int client_sock = -1; 549 struct sockaddr_in client_name; 550 int client_name_len = sizeof(client_name); 551 pthread_t newthread; 552 553 server_sock = startup(&port); 554 printf("httpd running on port %d\n", port); 555 556 while (1) 557 { 558 client_sock = accept(server_sock, 559 (struct sockaddr *)&client_name, 560 &client_name_len); 561 //建立链接 562 if (client_sock == -1) 563 error_die("accept"); 564 /* accept_request(client_sock); */ 565 //多线程进行控制 566 if (pthread_create(&newthread , NULL, accept_request, client_sock) != 0) 567 perror("pthread_create"); 568 } 569 570 close(server_sock); 571 572 return(0); 573 }