一个几百行代码实现的http服务器tinyhttpd
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 #include <stdint.h> 29 30 #define ISspace(x) isspace((int)(x)) 31 32 #define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n" 33 #define STDIN 0 34 #define STDOUT 1 35 #define STDERR 2 36 37 void accept_request(void *); 38 void bad_request(int); 39 void cat(int, FILE *); 40 void cannot_execute(int); 41 void error_die(const char *); 42 void execute_cgi(int, const char *, const char *, const char *); 43 int get_line(int, char *, int); 44 void headers(int, const char *); 45 void not_found(int); 46 void serve_file(int, const char *); 47 int startup(u_short *); 48 void unimplemented(int); 49 50 /**********************************************************************/ 51 /* A request has caused a call to accept() on the server port to 52 * return. Process the request appropriately. 53 * Parameters: the socket connected to the client */ 54 /**********************************************************************/ 55 void accept_request(void *arg) 56 { 57 int client = (intptr_t)arg; 58 char buf[1024]; 59 size_t numchars; 60 char method[255]; 61 char url[255]; 62 char path[512]; 63 size_t i, j; 64 struct stat st; 65 int cgi = 0; /* becomes true if server decides this is a CGI 66 * program */ 67 char *query_string = NULL; 68 69 numchars = get_line(client, buf, sizeof(buf)); 70 i = 0; j = 0; 71 while (!ISspace(buf[i]) && (i < sizeof(method) - 1)) 72 { 73 method[i] = buf[i]; 74 i++; 75 } 76 j=i; 77 method[i] = '\0'; 78 79 if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) 80 { 81 unimplemented(client); 82 return; 83 } 84 85 if (strcasecmp(method, "POST") == 0) 86 cgi = 1; 87 88 i = 0; 89 while (ISspace(buf[j]) && (j < numchars)) 90 j++; 91 while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < numchars)) 92 { 93 url[i] = buf[j]; 94 i++; j++; 95 } 96 url[i] = '\0'; 97 98 if (strcasecmp(method, "GET") == 0) 99 { 100 query_string = url; 101 while ((*query_string != '?') && (*query_string != '\0')) 102 query_string++; 103 if (*query_string == '?') 104 { 105 cgi = 1; 106 *query_string = '\0'; 107 query_string++; 108 } 109 } 110 111 sprintf(path, "htdocs%s", url); 112 if (path[strlen(path) - 1] == '/') 113 strcat(path, "index.html"); 114 if (stat(path, &st) == -1) { 115 while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ 116 numchars = get_line(client, buf, sizeof(buf)); 117 not_found(client); 118 } 119 else 120 { 121 if ((st.st_mode & S_IFMT) == S_IFDIR) 122 strcat(path, "/index.html"); 123 if ((st.st_mode & S_IXUSR) || 124 (st.st_mode & S_IXGRP) || 125 (st.st_mode & S_IXOTH) ) 126 cgi = 1; 127 if (!cgi) 128 serve_file(client, path); 129 else 130 execute_cgi(client, path, method, query_string); 131 } 132 133 close(client); 134 } 135 136 /**********************************************************************/ 137 /* Inform the client that a request it has made has a problem. 138 * Parameters: client socket */ 139 /**********************************************************************/ 140 void bad_request(int client) 141 { 142 char buf[1024]; 143 144 sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n"); 145 send(client, buf, sizeof(buf), 0); 146 sprintf(buf, "Content-type: text/html\r\n"); 147 send(client, buf, sizeof(buf), 0); 148 sprintf(buf, "\r\n"); 149 send(client, buf, sizeof(buf), 0); 150 sprintf(buf, "<P>Your browser sent a bad request, "); 151 send(client, buf, sizeof(buf), 0); 152 sprintf(buf, "such as a POST without a Content-Length.\r\n"); 153 send(client, buf, sizeof(buf), 0); 154 } 155 156 /**********************************************************************/ 157 /* Put the entire contents of a file out on a socket. This function 158 * is named after the UNIX "cat" command, because it might have been 159 * easier just to do something like pipe, fork, and exec("cat"). 160 * Parameters: the client socket descriptor 161 * FILE pointer for the file to cat */ 162 /**********************************************************************/ 163 void cat(int client, FILE *resource) 164 { 165 char buf[1024]; 166 167 fgets(buf, sizeof(buf), resource); 168 while (!feof(resource)) 169 { 170 send(client, buf, strlen(buf), 0); 171 fgets(buf, sizeof(buf), resource); 172 } 173 } 174 175 /**********************************************************************/ 176 /* Inform the client that a CGI script could not be executed. 177 * Parameter: the client socket descriptor. */ 178 /**********************************************************************/ 179 void cannot_execute(int client) 180 { 181 char buf[1024]; 182 183 sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n"); 184 send(client, buf, strlen(buf), 0); 185 sprintf(buf, "Content-type: text/html\r\n"); 186 send(client, buf, strlen(buf), 0); 187 sprintf(buf, "\r\n"); 188 send(client, buf, strlen(buf), 0); 189 sprintf(buf, "<P>Error prohibited CGI execution.\r\n"); 190 send(client, buf, strlen(buf), 0); 191 } 192 193 /**********************************************************************/ 194 /* Print out an error message with perror() (for system errors; based 195 * on value of errno, which indicates system call errors) and exit the 196 * program indicating an error. */ 197 /**********************************************************************/ 198 void error_die(const char *sc) 199 { 200 perror(sc); 201 exit(1); 202 } 203 204 /**********************************************************************/ 205 /* Execute a CGI script. Will need to set environment variables as 206 * appropriate. 207 * Parameters: client socket descriptor 208 * path to the CGI script */ 209 /**********************************************************************/ 210 void execute_cgi(int client, const char *path, 211 const char *method, const char *query_string) 212 { 213 char buf[1024]; 214 int cgi_output[2]; 215 int cgi_input[2]; 216 pid_t pid; 217 int status; 218 int i; 219 char c; 220 int numchars = 1; 221 int content_length = -1; 222 223 buf[0] = 'A'; buf[1] = '\0'; 224 if (strcasecmp(method, "GET") == 0) 225 while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ 226 numchars = get_line(client, buf, sizeof(buf)); 227 else if (strcasecmp(method, "POST") == 0) /*POST*/ 228 { 229 numchars = get_line(client, buf, sizeof(buf)); 230 while ((numchars > 0) && strcmp("\n", buf)) 231 { 232 buf[15] = '\0'; 233 if (strcasecmp(buf, "Content-Length:") == 0) 234 content_length = atoi(&(buf[16])); 235 numchars = get_line(client, buf, sizeof(buf)); 236 } 237 if (content_length == -1) { 238 bad_request(client); 239 return; 240 } 241 } 242 else/*HEAD or other*/ 243 { 244 } 245 246 247 if (pipe(cgi_output) < 0) { 248 cannot_execute(client); 249 return; 250 } 251 if (pipe(cgi_input) < 0) { 252 cannot_execute(client); 253 return; 254 } 255 256 if ( (pid = fork()) < 0 ) { 257 cannot_execute(client); 258 return; 259 } 260 sprintf(buf, "HTTP/1.0 200 OK\r\n"); 261 send(client, buf, strlen(buf), 0); 262 if (pid == 0) /* child: CGI script */ 263 { 264 char meth_env[255]; 265 char query_env[255]; 266 char length_env[255]; 267 268 dup2(cgi_output[1], STDOUT); 269 dup2(cgi_input[0], STDIN); 270 close(cgi_output[0]); 271 close(cgi_input[1]); 272 sprintf(meth_env, "REQUEST_METHOD=%s", method); 273 putenv(meth_env); 274 if (strcasecmp(method, "GET") == 0) { 275 sprintf(query_env, "QUERY_STRING=%s", query_string); 276 putenv(query_env); 277 } 278 else { /* POST */ 279 sprintf(length_env, "CONTENT_LENGTH=%d", content_length); 280 putenv(length_env); 281 } 282 execl(path, NULL); 283 exit(0); 284 } else { /* parent */ 285 close(cgi_output[1]); 286 close(cgi_input[0]); 287 if (strcasecmp(method, "POST") == 0) 288 for (i = 0; i < content_length; i++) { 289 recv(client, &c, 1, 0); 290 write(cgi_input[1], &c, 1); 291 } 292 while (read(cgi_output[0], &c, 1) > 0) 293 send(client, &c, 1, 0); 294 295 close(cgi_output[0]); 296 close(cgi_input[1]); 297 waitpid(pid, &status, 0); 298 } 299 } 300 301 /**********************************************************************/ 302 /* Get a line from a socket, whether the line ends in a newline, 303 * carriage return, or a CRLF combination. Terminates the string read 304 * with a null character. If no newline indicator is found before the 305 * end of the buffer, the string is terminated with a null. If any of 306 * the above three line terminators is read, the last character of the 307 * string will be a linefeed and the string will be terminated with a 308 * null character. 309 * Parameters: the socket descriptor 310 * the buffer to save the data in 311 * the size of the buffer 312 * Returns: the number of bytes stored (excluding null) */ 313 /**********************************************************************/ 314 int get_line(int sock, char *buf, int size) 315 { 316 int i = 0; 317 char c = '\0'; 318 int n; 319 320 while ((i < size - 1) && (c != '\n')) 321 { 322 n = recv(sock, &c, 1, 0); 323 /* DEBUG printf("%02X\n", c); */ 324 if (n > 0) 325 { 326 if (c == '\r') 327 { 328 n = recv(sock, &c, 1, MSG_PEEK); 329 /* DEBUG printf("%02X\n", c); */ 330 if ((n > 0) && (c == '\n')) 331 recv(sock, &c, 1, 0); 332 else 333 c = '\n'; 334 } 335 buf[i] = c; 336 i++; 337 } 338 else 339 c = '\n'; 340 } 341 buf[i] = '\0'; 342 343 return(i); 344 } 345 346 /**********************************************************************/ 347 /* Return the informational HTTP headers about a file. */ 348 /* Parameters: the socket to print the headers on 349 * the name of the file */ 350 /**********************************************************************/ 351 void headers(int client, const char *filename) 352 { 353 char buf[1024]; 354 (void)filename; /* could use filename to determine file type */ 355 356 strcpy(buf, "HTTP/1.0 200 OK\r\n"); 357 send(client, buf, strlen(buf), 0); 358 strcpy(buf, SERVER_STRING); 359 send(client, buf, strlen(buf), 0); 360 sprintf(buf, "Content-Type: text/html\r\n"); 361 send(client, buf, strlen(buf), 0); 362 strcpy(buf, "\r\n"); 363 send(client, buf, strlen(buf), 0); 364 } 365 366 /**********************************************************************/ 367 /* Give a client a 404 not found status message. */ 368 /**********************************************************************/ 369 void not_found(int client) 370 { 371 char buf[1024]; 372 373 sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n"); 374 send(client, buf, strlen(buf), 0); 375 sprintf(buf, SERVER_STRING); 376 send(client, buf, strlen(buf), 0); 377 sprintf(buf, "Content-Type: text/html\r\n"); 378 send(client, buf, strlen(buf), 0); 379 sprintf(buf, "\r\n"); 380 send(client, buf, strlen(buf), 0); 381 sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n"); 382 send(client, buf, strlen(buf), 0); 383 sprintf(buf, "<BODY><P>The server could not fulfill\r\n"); 384 send(client, buf, strlen(buf), 0); 385 sprintf(buf, "your request because the resource specified\r\n"); 386 send(client, buf, strlen(buf), 0); 387 sprintf(buf, "is unavailable or nonexistent.\r\n"); 388 send(client, buf, strlen(buf), 0); 389 sprintf(buf, "</BODY></HTML>\r\n"); 390 send(client, buf, strlen(buf), 0); 391 } 392 393 /**********************************************************************/ 394 /* Send a regular file to the client. Use headers, and report 395 * errors to client if they occur. 396 * Parameters: a pointer to a file structure produced from the socket 397 * file descriptor 398 * the name of the file to serve */ 399 /**********************************************************************/ 400 void serve_file(int client, const char *filename) 401 { 402 FILE *resource = NULL; 403 int numchars = 1; 404 char buf[1024]; 405 406 buf[0] = 'A'; buf[1] = '\0'; 407 while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ 408 numchars = get_line(client, buf, sizeof(buf)); 409 410 resource = fopen(filename, "r"); 411 if (resource == NULL) 412 not_found(client); 413 else 414 { 415 headers(client, filename); 416 cat(client, resource); 417 } 418 fclose(resource); 419 } 420 421 /**********************************************************************/ 422 /* This function starts the process of listening for web connections 423 * on a specified port. If the port is 0, then dynamically allocate a 424 * port and modify the original port variable to reflect the actual 425 * port. 426 * Parameters: pointer to variable containing the port to connect on 427 * Returns: the socket */ 428 /**********************************************************************/ 429 int startup(u_short *port) 430 { 431 int httpd = 0; 432 int on = 1; 433 struct sockaddr_in name; 434 435 httpd = socket(PF_INET, SOCK_STREAM, 0); 436 if (httpd == -1) 437 error_die("socket"); 438 memset(&name, 0, sizeof(name)); 439 name.sin_family = AF_INET; 440 name.sin_port = htons(*port); 441 name.sin_addr.s_addr = htonl(INADDR_ANY); 442 if ((setsockopt(httpd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) < 0) 443 { 444 error_die("setsockopt failed"); 445 } 446 if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0) 447 error_die("bind"); 448 if (*port == 0) /* if dynamically allocating a port */ 449 { 450 socklen_t namelen = sizeof(name); 451 if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1) 452 error_die("getsockname"); 453 *port = ntohs(name.sin_port); 454 } 455 if (listen(httpd, 5) < 0) 456 error_die("listen"); 457 return(httpd); 458 } 459 460 /**********************************************************************/ 461 /* Inform the client that the requested web method has not been 462 * implemented. 463 * Parameter: the client socket */ 464 /**********************************************************************/ 465 void unimplemented(int client) 466 { 467 char buf[1024]; 468 469 sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n"); 470 send(client, buf, strlen(buf), 0); 471 sprintf(buf, SERVER_STRING); 472 send(client, buf, strlen(buf), 0); 473 sprintf(buf, "Content-Type: text/html\r\n"); 474 send(client, buf, strlen(buf), 0); 475 sprintf(buf, "\r\n"); 476 send(client, buf, strlen(buf), 0); 477 sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n"); 478 send(client, buf, strlen(buf), 0); 479 sprintf(buf, "</TITLE></HEAD>\r\n"); 480 send(client, buf, strlen(buf), 0); 481 sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n"); 482 send(client, buf, strlen(buf), 0); 483 sprintf(buf, "</BODY></HTML>\r\n"); 484 send(client, buf, strlen(buf), 0); 485 } 486 487 /**********************************************************************/ 488 489 int main(void) 490 { 491 int server_sock = -1; 492 u_short port = 4000; 493 int client_sock = -1; 494 struct sockaddr_in client_name; 495 socklen_t client_name_len = sizeof(client_name); 496 pthread_t newthread; 497 498 server_sock = startup(&port); 499 printf("httpd running on port %d\n", port); 500 501 while (1) 502 { 503 client_sock = accept(server_sock, 504 (struct sockaddr *)&client_name, 505 &client_name_len); 506 if (client_sock == -1) 507 error_die("accept"); 508 /* accept_request(&client_sock); */ 509 if (pthread_create(&newthread , NULL, (void *)accept_request, (void *)(intptr_t)client_sock) != 0) 510 perror("pthread_create"); 511 } 512 513 close(server_sock); 514 515 return(0); 516 }
其实就是建立tcp连接,通过对数据包的解析是否有http的头字段来判断是不是http的,wireshark就是这样
下面是一个别人总结的图
这个我自己写了一下但是没实现到cgi那里,贴一个别人用windows实现的地址
https://blog.csdn.net/magictong/article/details/53201038