1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586
|
#include <stdio.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <ctype.h> #include <strings.h> #include <string.h> #include <sys/stat.h> #include <pthread.h> #include <sys/wait.h> #include <stdlib.h> #define ISspace(x) isspace((int)(x))
#define SERVER_STRING "Server: jdbhttpd/0.1.0rn" void accept_request(int); void bad_request(int); void cat(int, FILE *); void cannot_execute(int); void error_die(const char *); void execute_cgi(int, const char *, const char *, const char *); int get_line(int, char *, int); void headers(int, const char *); void not_found(int); void serve_file(int, const char *); int startup(u_short *); void unimplemented(int);
void accept_request(int client) { char buf[1024]; int numchars; char method[255]; char url[255]; char path[512]; size_t i, j; struct stat st; int cgi = 0;
char *query_string = NULL;
numchars = get_line(client, buf, sizeof(buf)); i = 0; j = 0; while (!ISspace(buf[j]) && (i < sizeof(method) - 1)) { method[i] = buf[j]; i++; j++; } method[i] = ''; //函数说明:strcasecmp()用来比较参数s1 和s2 字符串,比较时会自动忽略大小写的差异。 //返回值:若参数s1 和s2 字符串相同则返回0。s1 长度大于s2 长度则返回大于0 的值,s1 长度若小于s2 长度则返回小于0 的值。 if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) { //tinyhttp仅仅实现了GET和POST unimplemented(client); return; } //cgi为标志位,置1说明开启cgi解析 if (strcasecmp(method, "POST") == 0) //如果请求方法为POST,需要cgi解析 cgi = 1; i = 0; //将method后面的后边的空白字符略过 while (ISspace(buf[j]) && (j < sizeof(buf))) j++; //继续读取request-URL while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf))) { url[i] = buf[j]; i++; j++; } url[i] = ''; //如果是GET请求,url可能会带有?,有查询参数 if (strcasecmp(method, "GET") == 0) { query_string = url; while ((*query_string != '?') && (*query_string != '')) query_string++; if (*query_string == '?') { //如果带有查询参数,需要执行cgi,解析参数,设置标志位为1 cgi = 1; //将解析参数截取下来 *query_string = ''; query_string++; } } //以上已经将起始行解析完毕 //url中的路径格式化到path sprintf(path, "htdocs%s", url); //学习到这里明天继续TODO //如果path只是一个目录,默认设置为首页index.html if (path[strlen(path) - 1] == '/') strcat(path, "index.html"); //函数定义: int stat(const char *file_name, struct stat *buf); //函数说明: 通过文件名filename获取文件信息,并保存在buf所指的结构体stat中 //返回值: 执行成功则返回0,失败返回-1,错误代码存于errno(需要include <errno.h>) if (stat(path, &st) == -1) { //假如访问的网页不存在,则不断的读取剩下的请求头信息,并丢弃即可 while ((numchars > 0) && strcmp("n", buf)) /* read & discard headers */ numchars = get_line(client, buf, sizeof(buf)); //最后声明网页不存在 not_found(client); } else { //如果访问的网页存在则进行处理 if ((st.st_mode & S_IFMT) == S_IFDIR)//S_IFDIR代表目录 //如果路径是个目录,那就将主页进行显示 strcat(path, "/index.html"); if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH) ) //S_IXUSR:文件所有者具可执行权限 //S_IXGRP:用户组具可执行权限 //S_IXOTH:其他用户具可读取权限 cgi = 1; if (!cgi) //将静态文件返回 serve_file(client, path); else //执行cgi动态解析 execute_cgi(client, path, method, query_string); } close(client);//因为http是面向无连接的,所以要关闭 } /**********************************************************************/ /* Inform the client that a request it has made has a problem. * Parameters: client socket */ /**********************************************************************/ void bad_request(int client) { char buf[1024]; //发送400 sprintf(buf, "HTTP/1.0 400 BAD REQUESTrn"); send(client, buf, sizeof(buf), 0); sprintf(buf, "Content-type: text/htmlrn"); send(client, buf, sizeof(buf), 0); sprintf(buf, "rn"); send(client, buf, sizeof(buf), 0); sprintf(buf, "<P>Your browser sent a bad request, "); send(client, buf, sizeof(buf), 0); sprintf(buf, "such as a POST without a Content-Length.rn"); send(client, buf, sizeof(buf), 0); } /**********************************************************************/ /* Put the entire contents of a file out on a socket. This function * is named after the UNIX "cat" command, because it might have been * easier just to do something like pipe, fork, and exec("cat"). * Parameters: the client socket descriptor * FILE pointer for the file to cat */ /**********************************************************************/ void cat(int client, FILE *resource) { //发送文件的内容 char buf[1024]; //读取文件到buf中 fgets(buf, sizeof(buf), resource); while (!feof(resource))//判断文件是否读取到末尾 { //读取并发送文件内容 send(client, buf, strlen(buf), 0); fgets(buf, sizeof(buf), resource); } } /**********************************************************************/ /* Inform the client that a CGI script could not be executed. * Parameter: the client socket descriptor. */ /**********************************************************************/ void cannot_execute(int client) { char buf[1024]; //发送500 sprintf(buf, "HTTP/1.0 500 Internal Server Errorrn"); send(client, buf, strlen(buf), 0); sprintf(buf, "Content-type: text/htmlrn"); send(client, buf, strlen(buf), 0); sprintf(buf, "rn"); send(client, buf, strlen(buf), 0); sprintf(buf, "<P>Error prohibited CGI execution.rn"); send(client, buf, strlen(buf), 0); } /**********************************************************************/ /* Print out an error message with perror() (for system errors; based * on value of errno, which indicates system call errors) and exit the * program indicating an error. */ /**********************************************************************/ void error_die(const char *sc) { perror(sc); exit(1); } /**********************************************************************/ /* Execute a CGI script. Will need to set environment variables as * appropriate. * Parameters: client socket descriptor * path to the CGI script */ /**********************************************************************/ //执行cgi动态解析 void execute_cgi(int client, const char *path, const char *method, const char *query_string) { char buf[1024]; int cgi_output[2];//声明的读写管道,切莫被名称给忽悠,会给出图进行说明 int cgi_input[2];// pid_t pid; int status; int i; char c; int numchars = 1; int content_length = -1; buf[0] = 'A'; buf[1] = ''; if (strcasecmp(method, "GET") == 0) //如果是GET请求 //读取并且丢弃头信息 while ((numchars > 0) && strcmp("n", buf)) numchars = get_line(client, buf, sizeof(buf)); else { //处理的请求为POST numchars = get_line(client, buf, sizeof(buf)); while ((numchars > 0) && strcmp("n", buf)) {//循环读取头信息找到Content-Length字段的值 buf[15] = '';//目的是为了截取Content-Length: if (strcasecmp(buf, "Content-Length:") == 0) //"Content-Length: 15" content_length = atoi(&(buf[16]));//获取Content-Length的值 numchars = get_line(client, buf, sizeof(buf)); } if (content_length == -1) { //错误请求 bad_request(client); return; } } //返回正确响应码200 sprintf(buf, "HTTP/1.0 200 OKrn"); send(client, buf, strlen(buf), 0); //#include<unistd.h> //int pipe(int filedes[2]); //返回值:成功,返回0,否则返回-1。参数数组包含pipe使用的两个文件的描述符。fd[0]:读管道,fd[1]:写管道。 //必须在fork()中调用pipe(),否则子进程不会继承文件描述符。 //两个进程不共享祖先进程,就不能使用pipe。但是可以使用命名管道。 //pipe(cgi_output)执行成功后,cgi_output[0]:读通道 cgi_output[1]:写通道,这就是为什么说不要被名称所迷惑 if (pipe(cgi_output) < 0) { cannot_execute(client); return; } if (pipe(cgi_input) < 0) { cannot_execute(client); return; } if ( (pid = fork()) < 0 ) { cannot_execute(client); return; } //fork出一个子进程运行cgi脚本 if (pid == 0) /* 子进程: 运行CGI 脚本 */ { char meth_env[255]; char query_env[255]; char length_env[255]; dup2(cgi_output[1], 1);//1代表着stdout,0代表着stdin,将系统标准输出重定向为cgi_output[1] dup2(cgi_input[0], 0);//将系统标准输入重定向为cgi_input[0],这一点非常关键, //cgi程序中用的是标准输入输出进行交互 close(cgi_output[0]);//关闭了cgi_output中的读通道 close(cgi_input[1]);//关闭了cgi_input中的写通道 //CGI标准需要将请求的方法存储环境变量中,然后和cgi脚本进行交互 //存储REQUEST_METHOD sprintf(meth_env, "REQUEST_METHOD=%s", method); putenv(meth_env); if (strcasecmp(method, "GET") == 0) { //存储QUERY_STRING sprintf(query_env, "QUERY_STRING=%s", query_string); putenv(query_env); } else { /* POST */ //存储CONTENT_LENGTH sprintf(length_env, "CONTENT_LENGTH=%d", content_length); putenv(length_env); } // 表头文件#include<unistd.h> // 定义函数 // int execl(const char * path,const char * arg,....); // 函数说明 // execl()用来执行参数path字符串所代表的文件路径,接下来的参数代表执行该文件时传递过去的argv(0)、argv[1]……,最后一个参数必须用空指针(NULL)作结束。 // 返回值 // 如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中。 execl(path, path, NULL);//执行CGI脚本 exit(0); } else { /* 父进程 */ close(cgi_output[1]);//关闭了cgi_output中的写通道,注意这是父进程中cgi_output变量和子进程要区分开 close(cgi_input[0]);//关闭了cgi_input中的读通道 if (strcasecmp(method, "POST") == 0) for (i = 0; i < content_length; i++) { //开始读取POST中的内容 recv(client, &c, 1, 0); //将数据发送给cgi脚本 write(cgi_input[1], &c, 1); } //读取cgi脚本返回数据 while (read(cgi_output[0], &c, 1) > 0) //发送给浏览器 send(client, &c, 1, 0); //运行结束关闭 close(cgi_output[0]); close(cgi_input[1]); //定义函数:pid_t waitpid(pid_t pid, int * status, int options); //函数说明:waitpid()会暂时停止目前进程的执行, 直到有信号来到或子进程结束. //如果在调用wait()时子进程已经结束, 则wait()会立即返回子进程结束状态值. 子进程的结束状态值会由参数status 返回, //而子进程的进程识别码也会一快返回. //如果不在意结束状态值, 则参数status 可以设成NULL. 参数pid 为欲等待的子进程识别码, 其他数值意义如下: //1、pid<-1 等待进程组识别码为pid 绝对值的任何子进程. //2、pid=-1 等待任何子进程, 相当于wait(). //3、pid=0 等待进程组识别码与目前进程相同的任何子进程. //4、pid>0 等待任何子进程识别码为pid 的子进程. waitpid(pid, &status, 0); } } /**********************************************************************/ /* Get a line from a socket, whether the line ends in a newline, * carriage return, or a CRLF combination. Terminates the string read * with a null character. If no newline indicator is found before the * end of the buffer, the string is terminated with a null. If any of * the above three line terminators is read, the last character of the * string will be a linefeed and the string will be terminated with a * null character. * Parameters: the socket descriptor * the buffer to save the data in * the size of the buffer * Returns: the number of bytes stored (excluding null) */ /**********************************************************************/ //解析一行http报文 int get_line(int sock, char *buf, int size) { int i = 0; char c = ''; int n; while ((i < size - 1) && (c != 'n')) { n = recv(sock, &c, 1, 0); if (n > 0) { if (c == 'r') { n = recv(sock, &c, 1, MSG_PEEK); if ((n > 0) && (c == 'n')) recv(sock, &c, 1, 0); else c = 'n'; } buf[i] = c; i++; } else c = 'n'; } buf[i] = ''; return(i); } /**********************************************************************/ /* Return the informational HTTP headers about a file. */ /* Parameters: the socket to print the headers on * the name of the file */ /**********************************************************************/ void headers(int client, const char *filename) { char buf[1024]; (void)filename; /* could use filename to determine file type */ //发送HTTP头 strcpy(buf, "HTTP/1.0 200 OKrn"); send(client, buf, strlen(buf), 0); strcpy(buf, SERVER_STRING); send(client, buf, strlen(buf), 0); sprintf(buf, "Content-Type: text/htmlrn"); send(client, buf, strlen(buf), 0); strcpy(buf, "rn"); send(client, buf, strlen(buf), 0); } /**********************************************************************/ /* Give a client a 404 not found status message. */ /**********************************************************************/ void not_found(int client) { char buf[1024]; //返回404 sprintf(buf, "HTTP/1.0 404 NOT FOUNDrn"); send(client, buf, strlen(buf), 0); sprintf(buf, SERVER_STRING); send(client, buf, strlen(buf), 0); sprintf(buf, "Content-Type: text/htmlrn"); send(client, buf, strlen(buf), 0); sprintf(buf, "rn"); send(client, buf, strlen(buf), 0); sprintf(buf, "<HTML><TITLE>Not Found</TITLE>rn"); send(client, buf, strlen(buf), 0); sprintf(buf, "<BODY><P>The server could not fulfillrn"); send(client, buf, strlen(buf), 0); sprintf(buf, "your request because the resource specifiedrn"); send(client, buf, strlen(buf), 0); sprintf(buf, "is unavailable or nonexistent.rn"); send(client, buf, strlen(buf), 0); sprintf(buf, "</BODY></HTML>rn"); send(client, buf, strlen(buf), 0); } /**********************************************************************/ /* Send a regular file to the client. Use headers, and report * errors to client if they occur. * Parameters: a pointer to a file structure produced from the socket * file descriptor * the name of the file to serve */ /**********************************************************************/ //将请求的文件发送回浏览器客户端 void serve_file(int client, const char *filename) { FILE *resource = NULL; int numchars = 1; char buf[1024]; buf[0] = 'A'; buf[1] = '';//这个赋值不清楚是干什么的 while ((numchars > 0) && strcmp("n", buf)) //将HTTP请求头读取并丢弃 numchars = get_line(client, buf, sizeof(buf)); //打开文件 resource = fopen(filename, "r"); if (resource == NULL) //如果文件不存在,则返回not_found not_found(client); else { //添加HTTP头 headers(client, filename); //并发送文件内容 cat(client, resource); } fclose(resource);//关闭文件句柄 } /**********************************************************************/ /* This function starts the process of listening for web connections * on a specified port. If the port is 0, then dynamically allocate a * port and modify the original port variable to reflect the actual * port. * Parameters: pointer to variable containing the port to connect on * Returns: the socket */ /**********************************************************************/ //启动服务端 int startup(u_short *port) { int httpd = 0; struct sockaddr_in name; //设置http socket httpd = socket(PF_INET, SOCK_STREAM, 0); if (httpd == -1) error_die("socket"); memset(&name, 0, sizeof(name)); name.sin_family = AF_INET; name.sin_port = htons(*port); name.sin_addr.s_addr = htonl(INADDR_ANY); //绑定端口 if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0) error_die("bind"); if (*port == 0) /*动态分配一个端口 */ { int namelen = sizeof(name); if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1) error_die("getsockname"); *port = ntohs(name.sin_port); } //监听连接 if (listen(httpd, 5) < 0) error_die("listen"); return(httpd); } /**********************************************************************/ /* Inform the client that the requested web method has not been * implemented. * Parameter: the client socket */ /**********************************************************************/ void unimplemented(int client) { char buf[1024]; //发送501说明相应方法没有实现 sprintf(buf, "HTTP/1.0 501 Method Not Implementedrn"); send(client, buf, strlen(buf), 0); sprintf(buf, SERVER_STRING); send(client, buf, strlen(buf), 0); sprintf(buf, "Content-Type: text/htmlrn"); send(client, buf, strlen(buf), 0); sprintf(buf, "rn"); send(client, buf, strlen(buf), 0); sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implementedrn"); send(client, buf, strlen(buf), 0); sprintf(buf, "</TITLE></HEAD>rn"); send(client, buf, strlen(buf), 0); sprintf(buf, "<BODY><P>HTTP request method not supported.rn"); send(client, buf, strlen(buf), 0); sprintf(buf, "</BODY></HTML>rn"); send(client, buf, strlen(buf), 0); } /**********************************************************************/ int main(void) { int server_sock = -1; u_short port = 0; int client_sock = -1; struct sockaddr_in client_name; int client_name_len = sizeof(client_name); pthread_t newthread; //启动server socket server_sock = startup(&port); printf("httpd running on port %dn", port); while (1) { //接受客户端连接 client_sock = accept(server_sock, (struct sockaddr *)&client_name, &client_name_len); if (client_sock == -1) error_die("accept"); /*启动线程处理新的连接 */ if (pthread_create(&newthread , NULL, accept_request, client_sock) != 0) perror("pthread_create"); } //关闭server socket close(server_sock); return(0); }
|