Tinyhttp源码分析
简介
Tinyhttp是一个轻量型Http Server,使用C语言开发,全部代码只500多行,还包括一个简单Client。
Tinyhttp程序的逻辑为:一个无线循环,一个请求,创建一个线程,之后线程函数处理每个请求,然后解析HTTP请求,做一些判断,之后判断文件是否可执行,不可执行,打开文件,输出给客户端(浏览器),可执行就创建管道,父子进程进行通信。父子进程通信,用到了dup2和execl函数。
模型图
源码剖析
1 #include <stdio.h> 2 #include <sys/socket.h> 3 #include <sys/types.h> 4 #include <netinet/in.h> 5 #include <arpa/inet.h> 6 #include <unistd.h> 7 #include <ctype.h> 8 #include <strings.h> 9 #include <string.h> 10 #include <sys/stat.h> 11 #include <pthread.h> 12 #include <sys/wait.h> 13 #include <stdlib.h> 14 15 #define ISspace(x) isspace((int)(x)) 16 17 #define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n" 18 19 void *accept_request(void *); 20 void bad_request(int); 21 void cat(int, FILE *); 22 void cannot_execute(int); 23 void error_die(const char *); 24 void execute_cgi(int, const char *, const char *, const char *); 25 int get_line(int, char *, int); 26 void headers(int, const char *); 27 void not_found(int); 28 void serve_file(int, const char *); 29 int startup(u_short *); 30 void unimplemented(int); 31 32 /**********************************************************************/ 33 /*功能:处理请求 34 *参数:连接到客户端的套接字*/ 35 /**********************************************************************/ 36 void *accept_request(void *arg) 37 { 38 int client = *(int *)arg; //接收客户端的套接字 39 char buf[1024]; 40 int numchars; 41 char method[255]; 42 char url[255]; 43 char path[512]; 44 size_t i, j; 45 struct stat st; 46 int cgi = 0; 47 48 char *query_string = NULL; 49 // "GET /index.html HTTP/1.1\n",'\000' <repeats 319 times>... 50 numchars = get_line(client, buf, sizeof(buf)); //读取一行存放buf中 51 i = 0; 52 j = 0; 53 //判断buf中第一个空格前面的字符串的请求方式 54 while (!ISspace(buf[j]) && (i < sizeof(method) - 1)) 55 { 56 method[i] = buf[j]; //解析出请求的方法放在method中 57 i++; 58 j++; 59 } 60 method[i] = '\0'; 61 //如果是其他的请求方式,除了GET和POST外,如:HEAD、DELETE等回复未实现方法 62 if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) //strcasecmp用忽略大小写比较字符串 63 { 64 unimplemented(client); //回复请求的方法未实现 65 return 0; 66 } 67 68 //如果是POST方式请求,表示执行cgi 69 if (strcasecmp(method, "POST") == 0) 70 cgi = 1; 71 72 i = 0; 73 //从上面的第一个空格后继续开始 74 while (ISspace(buf[j]) && (j < sizeof(buf))) 75 j++; 76 //POST或者GET空格后面的内容 如:"GET /index.html HTTP/1.1\n" 77 while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf))) 78 { 79 url[i] = buf[j]; //解析出url要请求的地址,如:"/index.html" 80 i++; 81 j++; 82 } 83 url[i] = '\0'; 84 85 //如果是GET方式请求,如:/login.cgi?user=123&password=456 86 if (strcasecmp(method, "GET") == 0) 87 { 88 query_string = url; 89 while ((*query_string != '?') && (*query_string != '\0')) 90 query_string++; 91 if (*query_string == '?') //如果遇到了?表示执行cgi 92 { 93 cgi = 1; 94 *query_string = '\0'; 95 query_string++; //?后面的内容是发送的信息(如用户名和密码信息) 96 } 97 } 98 //拼接url地址路径 99 sprintf(path, "htdocs%s", url); //文件的路径放在path中 100 101 if (path[strlen(path) - 1] == '/') //判断是否是根目录 102 strcat(path, "index.html"); // "htdocs/index.html" 103 104 if (stat(path, &st) == -1) 105 { //获取文件信息到st结构体失败 106 while ((numchars > 0) && strcmp("\n", buf)) //读并丢弃标题(浏览器的信息),如果不读,浏览器发送来的数据,会使服务器处于阻塞状态 107 numchars = get_line(client, buf, sizeof(buf)); 108 not_found(client); //给客户端一条404未找到的状态消息 109 } 110 else //成功获得文件信息 111 { 112 if ((st.st_mode & S_IFMT) == S_IFDIR) //是一个目录 113 strcat(path, "/index.html"); 114 115 if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH)) //文件所有者具可执行权限||用户组具可执行权限||其他用户具可执行权限 116 cgi = 1; //是cgi程序 117 118 if (!cgi) //根据cgi的值,为0执行文件 119 serve_file(client, path); 120 else //cgi为1,执行cgi 121 execute_cgi(client, path, method, query_string); 122 } 123 close(client); 124 return 0; 125 } 126 127 /**********************************************************************/ 128 /* 通知客户它提出的请求有问题 129 * 参数: 接收客户端套接字描述符 */ 130 /**********************************************************************/ 131 void bad_request(int client) 132 { 133 char buf[1024]; 134 135 sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n"); 136 send(client, buf, sizeof(buf), 0); 137 sprintf(buf, "Content-type: text/html\r\n"); 138 send(client, buf, sizeof(buf), 0); 139 sprintf(buf, "\r\n"); 140 send(client, buf, sizeof(buf), 0); 141 sprintf(buf, "<P>Your browser sent a bad request, "); 142 send(client, buf, sizeof(buf), 0); 143 sprintf(buf, "such as a POST without a Content-Length.\r\n"); 144 send(client, buf, sizeof(buf), 0); 145 } 146 147 /**********************************************************************/ 148 /*不停的从resource所指的文件中读取内容,发送给客户端 149 *参数:接受客户端套接字,请求的文件的指针*/ 150 /**********************************************************************/ 151 void cat(int client, FILE *resource) 152 { 153 char buf[1024]; 154 155 fgets(buf, sizeof(buf), resource); // 156 while (!feof(resource)) 157 { 158 send(client, buf, strlen(buf), 0); 159 fgets(buf, sizeof(buf), resource); 160 } 161 } 162 163 /**********************************************************************/ 164 /* 通知客户端无法执行CGI脚本。 165 * 参数:客户端套接字描述符。*/ 166 /**********************************************************************/ 167 void cannot_execute(int client) 168 { 169 char buf[1024]; 170 171 sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n"); 172 send(client, buf, strlen(buf), 0); 173 sprintf(buf, "Content-type: text/html\r\n"); 174 send(client, buf, strlen(buf), 0); 175 sprintf(buf, "\r\n"); 176 send(client, buf, strlen(buf), 0); 177 sprintf(buf, "<P>Error prohibited CGI execution.\r\n"); 178 send(client, buf, strlen(buf), 0); 179 } 180 181 /**********************************************************************/ 182 /* 打印带有perror()的错误消息并退出指示错误的程序。 */ 183 /**********************************************************************/ 184 void error_die(const char *sc) 185 { 186 perror(sc); 187 exit(1); 188 } 189 190 /**********************************************************************/ 191 /* 执行一个cgi程序,需要环境的支持 192 * 参数: 接受客户端套接字描述符client,拼接后的地址路径path,请求的方法method,接受的url?后面的内容*/ 193 /**********************************************************************/ 194 void execute_cgi(int client, const char *path, const char *method, const char *query_string) 195 { 196 char buf[1024]; 197 int cgi_output[2]; 198 int cgi_input[2]; 199 pid_t pid; 200 int status; 201 int i; 202 char c; 203 int numchars = 1; 204 int content_length = -1; 205 206 buf[0] = 'A'; 207 buf[1] = '\0'; 208 if (strcasecmp(method, "GET") == 0) 209 while ((numchars > 0) && strcmp("\n", buf)) //读并丢弃标题(浏览器的信息),如果不读,浏览器发送来的数据,会使服务器处于阻塞状态 210 numchars = get_line(client, buf, sizeof(buf)); 211 212 else //POST 213 { 214 numchars = get_line(client, buf, sizeof(buf)); //读取一行到buf中 215 while ((numchars > 0) && strcmp("\n", buf)) 216 { 217 buf[15] = '\0'; 218 if (strcasecmp(buf, "Content-Length:") == 0) //拷贝Content-Length:到字符串中 219 content_length = atoi(&(buf[16])); //获得content_length内容长度 220 numchars = get_line(client, buf, sizeof(buf)); 221 } 222 if (content_length == -1) 223 { 224 bad_request(client); 225 return; 226 } 227 } 228 229 sprintf(buf, "HTTP/1.0 200 OK\r\n"); //给浏览器一个回复 230 send(client, buf, strlen(buf), 0); 231 232 //建立两根管道,分别是输出管道和输入管道 233 if (pipe(cgi_output) < 0) 234 { 235 cannot_execute(client); 236 return; 237 } 238 if (pipe(cgi_input) < 0) 239 { 240 cannot_execute(client); 241 return; 242 } 243 //创建一个进程 244 if ((pid = fork()) < 0) 245 { 246 cannot_execute(client); 247 return; 248 } 249 if (pid == 0) //子进程 250 { 251 char meth_env[255]; 252 char query_env[255]; 253 char length_env[255]; 254 255 dup2(cgi_output[1], 1); //dup2做了重定向,Linux中0是标准输入(键盘),1是标准输出(屏幕) 256 dup2(cgi_input[0], 0); //将cgi_output[1]描述符拷贝到标准输出,原来输出到屏幕的,现在写到cgi_output[1管道中] 257 //cgi_output[1]用来写 258 //cgi_input[0]用来读 259 close(cgi_output[0]); 260 close(cgi_input[1]); 261 262 sprintf(meth_env, "REQUEST_METHOD=%s", method); //将请求的方法加到环境变量 263 putenv(meth_env); //putenv增加环境变量的内容 264 if (strcasecmp(method, "GET") == 0) 265 { 266 sprintf(query_env, "QUERY_STRING=%s", query_string); 267 putenv(query_env); 268 } 269 else 270 { //POST 271 sprintf(length_env, "CONTENT_LENGTH=%d", content_length); 272 putenv(length_env); 273 } 274 execl(path, path, NULL); //execl执行给出的path路径下的path程序(cgi程序),l表示以list方式传参,经过重定向后,path路径中的程序执行后,读数据从cgi_intput[0],中读取,写数据到cgi_output[1]中欧个 275 exit(0); 276 } 277 else 278 { //父进程 279 //cgi_output[0]管道用来读 280 //cgi_input[1]管道用来写 281 close(cgi_output[1]); 282 close(cgi_input[0]); 283 if (strcasecmp(method, "POST") == 0) //如果是POST,循环每次一个字符,写入管道 284 for (i = 0; i < content_length; i++) 285 { 286 recv(client, &c, 1, 0); 287 write(cgi_input[1], &c, 1); 288 } 289 while (read(cgi_output[0], &c, 1) > 0) //循环从管道中读出数据发送给客户端 290 send(client, &c, 1, 0); 291 292 close(cgi_output[0]); 293 close(cgi_input[1]); 294 waitpid(pid, &status, 0); 295 } 296 } 297 298 /**********************************************************************/ 299 /*返回从接受客户端套接字中读取一行的字节数 300 *参数:客户端套接字sock,缓冲区的指针buf,缓冲区大小size*/ 301 /**********************************************************************/ 302 int get_line(int sock, char *buf, int size) 303 { 304 int i = 0; 305 char c = '\0'; 306 int n; 307 308 while ((i < size - 1) && (c != '\n')) 309 { 310 n = recv(sock, &c, 1, 0); 311 312 if (n > 0) 313 { 314 if (c == '\r') 315 { 316 n = recv(sock, &c, 1, MSG_PEEK); 317 318 if ((n > 0) && (c == '\n')) 319 recv(sock, &c, 1, 0); 320 else 321 c = '\n'; 322 } 323 buf[i] = c; 324 i++; 325 } 326 else 327 c = '\n'; 328 } 329 buf[i] = '\0'; 330 331 return (i); 332 } 333 334 /**********************************************************************/ 335 /*发送有关HTTP头文件的信息 336 *参数:客户端接收套接字,文件的名称*/ 337 /**********************************************************************/ 338 void headers(int client, const char *filename) 339 { 340 char buf[1024]; 341 (void)filename; /*可以使用文件名来确定文件类型*/ 342 343 strcpy(buf, "HTTP/1.0 200 OK\r\n"); 344 send(client, buf, strlen(buf), 0); 345 strcpy(buf, SERVER_STRING); 346 send(client, buf, strlen(buf), 0); 347 sprintf(buf, "Content-Type: text/html\r\n"); 348 send(client, buf, strlen(buf), 0); 349 strcpy(buf, "\r\n"); 350 send(client, buf, strlen(buf), 0); 351 } 352 353 /**********************************************************************/ 354 /* 给客户端一条404未找到的状态消息。*/ 355 /**********************************************************************/ 356 void not_found(int client) 357 { 358 char buf[1024]; 359 360 sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n"); 361 send(client, buf, strlen(buf), 0); 362 sprintf(buf, SERVER_STRING); 363 send(client, buf, strlen(buf), 0); 364 sprintf(buf, "Content-Type: text/html\r\n"); 365 send(client, buf, strlen(buf), 0); 366 sprintf(buf, "\r\n"); 367 send(client, buf, strlen(buf), 0); 368 sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n"); 369 send(client, buf, strlen(buf), 0); 370 sprintf(buf, "<BODY><P>The server could not fulfill\r\n"); 371 send(client, buf, strlen(buf), 0); 372 sprintf(buf, "your request because the resource specified\r\n"); 373 send(client, buf, strlen(buf), 0); 374 sprintf(buf, "is unavailable or nonexistent.\r\n"); 375 send(client, buf, strlen(buf), 0); 376 sprintf(buf, "</BODY></HTML>\r\n"); 377 send(client, buf, strlen(buf), 0); 378 } 379 380 /**********************************************************************/ 381 /*向客户端发送一个常规文件。使用标头,并在出现错误时向客户端报告错误。 382 *参数:客户端的接收文件描述符,要服务的文件的名称*/ 383 /**********************************************************************/ 384 void serve_file(int client, const char *filename) 385 { 386 FILE *resource = NULL; 387 int numchars = 1; 388 char buf[1024]; 389 390 buf[0] = 'A'; 391 buf[1] = '\0'; 392 while ((numchars > 0) && strcmp("\n", buf)) //读并丢弃标题(浏览器的信息),如果不读,浏览器发送来的数据,会使服务器处于阻塞状态 393 numchars = get_line(client, buf, sizeof(buf)); 394 395 resource = fopen(filename, "r"); //打开文件 396 if (resource == NULL) 397 not_found(client); 398 else //正常打开 399 { 400 headers(client, filename); //返回一些头文件相关的信息 401 cat(client, resource); //将文件内容发给客户端 402 } 403 fclose(resource); 404 } 405 406 /**********************************************************************/ 407 /*返回创建指定的端口的监听套接字 408 *参数:指定的端口port*/ 409 /**********************************************************************/ 410 int startup(u_short *port) 411 { 412 int httpd = 0; 413 struct sockaddr_in name; 414 415 httpd = socket(PF_INET, SOCK_STREAM, 0); 416 if (httpd == -1) 417 error_die("socket"); 418 memset(&name, 0, sizeof(name)); 419 name.sin_family = AF_INET; 420 name.sin_port = htons(*port); 421 name.sin_addr.s_addr = inet_addr("192.168.137.114"); 422 423 int on = 1; 424 setsockopt(httpd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); 425 426 if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0) 427 error_die("bind"); 428 if (*port == 0) /* if dynamically allocating a port */ 429 { 430 socklen_t namelen = sizeof(name); 431 if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1) 432 error_die("getsockname"); 433 *port = ntohs(name.sin_port); 434 } 435 if (listen(httpd, 5) < 0) 436 error_die("listen"); 437 return (httpd); 438 } 439 440 /**********************************************************************/ 441 /*通知客户端,所请求的web方法尚未实现 442 *参数:接受客户端套接字 */ 443 /**********************************************************************/ 444 void unimplemented(int client) 445 { 446 char buf[1024]; 447 448 sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n"); 449 send(client, buf, strlen(buf), 0); 450 sprintf(buf, SERVER_STRING); 451 send(client, buf, strlen(buf), 0); 452 sprintf(buf, "Content-Type: text/html\r\n"); 453 send(client, buf, strlen(buf), 0); 454 sprintf(buf, "\r\n"); 455 send(client, buf, strlen(buf), 0); 456 sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n"); 457 send(client, buf, strlen(buf), 0); 458 sprintf(buf, "</TITLE></HEAD>\r\n"); 459 send(client, buf, strlen(buf), 0); 460 sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n"); 461 send(client, buf, strlen(buf), 0); 462 sprintf(buf, "</BODY></HTML>\r\n"); 463 send(client, buf, strlen(buf), 0); 464 } 465 466 /**********************************************************************/ 467 /*主函数入口*/ 468 /**********************************************************************/ 469 int main(void) 470 { 471 int server_sock = -1; 472 u_short port = 8080; 473 int client_sock = -1; 474 struct sockaddr_in client_name; 475 socklen_t client_name_len = sizeof(client_name); 476 pthread_t newthread; 477 478 server_sock = startup(&port); 479 printf("httpd running on port %d\n", port); 480 481 while (1) 482 { 483 //父进程每接收一个客户端创建一个线程 484 client_sock = accept(server_sock, (struct sockaddr *)&client_name, &client_name_len); 485 if (client_sock == -1) 486 error_die("accept"); 487 //线程标识符、线程属性、线程运行函数、运行函数参数(接收套接字) 488 if (pthread_create(&newthread, NULL, accept_request, &client_sock) != 0) 489 perror("pthread_create"); 490 } 491 492 close(server_sock); 493 494 return (0); 495 }