每个函数的作用:
17 |accept_request: 处理从套接字上监听到的一个 HTTP 请求,在这里可以很大一部分地体现服务器处理请求流程。
18 |bad_request: 返回给客户端这是个错误请求,HTTP 状态吗 400 BAD REQUEST.
19 |cat: 读取服务器上某个文件写到 socket 套接字。
20 |cannot_execute: 主要处理发生在执行 cgi 程序时出现的错误。
21 |error_die: 把错误信息写到 perror 并退出。
22 |execute_cgi: 运行 cgi 程序的处理,也是个主要函数。
23 |get_line: 读取套接字的一行,把回车换行等情况都统一为换行符结束。
24 |headers: 把 HTTP 响应的头部写到套接字。
25 |not_found: 主要处理找不到请求的文件时的情况。
26 |sever_file: 调用 cat 把服务器文件返回给浏览器。
27 |startup: 初始化 httpd 服务,包括建立套接字,绑定端口,进行监听等。
28 |unimplemented: 返回给浏览器表明收到的 HTTP 请求所用的 method 不被支持。
29 |
30 |
建议源码阅读顺序: main -> startup -> accept_request -> execute_cgi, 通晓主要工作流程后再仔细把每个函数的源码看一看。
32 |
33 |
(1) 服务器启动,在指定端口或随机选取端口绑定 httpd 服务。
36 |(2)收到一个 HTTP 请求时(其实就是 listen 的端口 accpet 的时候),派生一个线程运行 accept_request 函数。
37 |(3)取出 HTTP 请求中的 method (GET 或 POST) 和 url,。对于 GET 方法,如果有携带参数,则 query_string 指针指向 url 中 ? 后面的 GET 参数。
38 |(4) 格式化 url 到 path 数组,表示浏览器请求的服务器文件路径,在 tinyhttpd 中服务器文件是在 htdocs 文件夹下。当 url 以 / 结尾,或 url 是个目录,则默认在 path 中加上 index.html,表示访问主页。
39 |(5)如果文件路径合法,对于无参数的 GET 请求,直接输出服务器文件到浏览器,即用 HTTP 格式写到套接字上,跳到(10)。其他情况(带参数 GET,POST 方式,url 为可执行文件),则调用 excute_cgi 函数执行 cgi 脚本。
40 |(6)读取整个 HTTP 请求并丢弃,如果是 POST 则找出 Content-Length. 把 HTTP 200 状态码写到套接字。
41 |(7) 建立两个管道,cgi_input 和 cgi_output, 并 fork 一个进程。
42 |(8) 在子进程中,把 STDOUT 重定向到 cgi_outputt 的写入端,把 STDIN 重定向到 cgi_input 的读取端,关闭 cgi_input 的写入端 和 cgi_output 的读取端,设置 request_method 的环境变量,GET 的话设置 query_string 的环境变量,POST 的话设置 content_length 的环境变量,这些环境变量都是为了给 cgi 脚本调用,接着用 execl 运行 cgi 程序。
43 |(9) 在父进程中,关闭 cgi_input 的读取端 和 cgi_output 的写入端,如果 POST 的话,把 POST 数据写入 cgi_input,已被重定向到 STDIN,读取 cgi_output 的管道输出到客户端,该管道输入是 STDOUT。接着关闭所有管道,等待子进程结束。这一部分比较乱,见下图说明:
44 |
45 |
47 |
图 1 管道初始状态
49 |
50 |
图 2 管道最终状态
53 |
54 |
(10) 关闭与浏览器的连接,完成了一次 HTTP 请求与回应,因为 HTTP 是无连接的。
56 |
57 |
Welcome to J. David's webserver. 5 |
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, "
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, "
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, "\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, "
HTTP request method not supported.\r\n");
482 | send(client, buf, strlen(buf), 0);
483 | sprintf(buf, "\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 | }
517 |
--------------------------------------------------------------------------------
/simpleclient.c:
--------------------------------------------------------------------------------
1 | #include