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 }