Tinyhttpd源码浅读

来源:互联网 发布:马云怎么通过淘宝赚钱 编辑:程序博客网 时间:2024/04/30 10:17

简介

为了加深对APU(UNIX环境高级编程)学习后的理解,粗略地读了下tinyhttpd源码,真得是短小精悍。

代码本身实现了一个接受处理http请求的服务器。http协议是个无状态的传输层协议,有很多种请求类型,tinyhttpd实现了其中最常见的get和post请求类型的处理逻辑。由于http协议的无状态性,代码的可分离性很好。

对于应用unix系统的一些接口,该源码是一个很好的应用例子。作者J. David Blackstone是个有意思的学生,在README里面的内容说,这个就是他学生时自己的一个课程的大作业。

调用关系图

代码一共13个函数,包括main函数在内。其调用关系如下:
这里写图片描述

代码主流程其实并不复杂,大量的代码用来处理主流程之外的异常情况。
a) 服务器启动监听客户端请求
–> b) 客户端请求到达, 服务端创建线程处理该请求
–> c) 读取method字段, 根据get和post来分别处理
–> d) 把处理结果发给客户端

其中, 过程 c) 中需要根据情况进行一些细节来分支处理:
1) url以/结尾, 则指定index.html
2) 获取文件信息,找不到文件,返回404状态
3) GET请求且无参数, 则把文件以http方式返回给客户端
4) GET请求且带参数, 或者POST请求, 文件有执行权限, 则执行cgi代码

除此之外,主进程把 子进程执行cgi代码后 的结果发给客户端。cgi代码做的工作其实只有:从环境变量和标准输入中读取数据、处理数据、向标准输出输出数据。

模块介绍

工作的起点,从这个入口开始。

int main(void);

分为两个部分:建立连接并监听,然后,处理从连接来的请求。

int startup(u_short *port);void accept_request(int client);

这里可以说是处理http请求的主模块。代码只对http最基础的进行了实现和处理。
主要有三种方式处理:

void serve_file(int, const char *);void execute_cgi(int, const char *, const char *, const char *);void unimplemented(int);

这三个模块分别处理:无参get请求,带参get请求和post请求,其他类型的请求。

接下来,是这些处理模块的各种细节的代码,具体可以参考http协议的细节规定:

int get_line(int, char *, int);void headers(int, const char *);void cat(int, FILE *);

分别是:读取一行,构造http头,输出文件.

剩下的是处理异常的代码:

void error_die(const char *);void not_found(int client);void bad_request(int);void cannot_execute(int);

分别作用是: 退出,请求文件不存在, 收到的请求出现错误, 服务端文件不能执行。

代码介绍

1) 主流程模块

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_sock = startup(&port); printf("httpd running on port %d\n", port); while (1) {  client_sock = accept(server_sock,(struct sockaddr *)&client_name, &client_name_len);  if (client_sock == -1)   error_die("accept"); /* accept_request(client_sock); */ if (pthread_create(&newthread , NULL, accept_request, client_sock) != 0)   perror("pthread_create"); } close(server_sock); return(0);}
int startup(u_short *port){ int httpd = 0; struct sockaddr_in name; 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)  /* if dynamically allocating a port */ {  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);}
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;      /* becomes true if server decides this is a CGI                    * program */ 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] = '\0'; if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) {  unimplemented(client);  return; } if (strcasecmp(method, "POST") == 0)  cgi = 1; i = 0; while (ISspace(buf[j]) && (j < sizeof(buf)))  j++; while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf))) {  url[i] = buf[j];  i++; j++; } url[i] = '\0'; if (strcasecmp(method, "GET") == 0) {  query_string = url;  while ((*query_string != '?') && (*query_string != '\0'))   query_string++;  if (*query_string == '?')  {   cgi = 1;   *query_string = '\0';   query_string++;  } }

2) 处理http请求的主模块

void serve_file(int client, const char *filename){ FILE *resource = NULL; int numchars = 1; char buf[1024]; buf[0] = 'A'; buf[1] = '\0'; while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */  numchars = get_line(client, buf, sizeof(buf)); resource = fopen(filename, "r"); if (resource == NULL)  not_found(client); else {  headers(client, filename);  cat(client, resource); } fclose(resource);}
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] = '\0'; if (strcasecmp(method, "GET") == 0)  while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */   numchars = get_line(client, buf, sizeof(buf)); else    /* POST */ {  numchars = get_line(client, buf, sizeof(buf));  while ((numchars > 0) && strcmp("\n", buf))  {   buf[15] = '\0';   if (strcasecmp(buf, "Content-Length:") == 0)    content_length = atoi(&(buf[16]));   numchars = get_line(client, buf, sizeof(buf));  }  if (content_length == -1) {   bad_request(client);   return;  } }
void unimplemented(int client){ char buf[1024]; sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, SERVER_STRING); send(client, buf, strlen(buf), 0); sprintf(buf, "Content-Type: text/html\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, "\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, "</TITLE></HEAD>\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, "</BODY></HTML>\r\n"); send(client, buf, strlen(buf), 0);}

3) 处理模块的各种细节方法

int get_line(int sock, char *buf, int size){ int i = 0; char c = '\0'; int n; while ((i < size - 1) && (c != '\n')) {  n = recv(sock, &c, 1, 0);  /* DEBUG printf("%02X\n", c); */  if (n > 0)  {   if (c == '\r')   {    n = recv(sock, &c, 1, MSG_PEEK);    /* DEBUG printf("%02X\n", c); */    if ((n > 0) && (c == '\n'))     recv(sock, &c, 1, 0);    else     c = '\n';   }   buf[i] = c;   i++;  }  else   c = '\n'; } buf[i] = '\0'; return(i);}
void headers(int client, const char *filename){ char buf[1024]; (void)filename;  /* could use filename to determine file type */ strcpy(buf, "HTTP/1.0 200 OK\r\n"); send(client, buf, strlen(buf), 0); strcpy(buf, SERVER_STRING); send(client, buf, strlen(buf), 0); sprintf(buf, "Content-Type: text/html\r\n"); send(client, buf, strlen(buf), 0); strcpy(buf, "\r\n"); send(client, buf, strlen(buf), 0);}
void cat(int client, FILE *resource){ char buf[1024]; fgets(buf, sizeof(buf), resource); while (!feof(resource)) {  send(client, buf, strlen(buf), 0);  fgets(buf, sizeof(buf), resource); }}

4) 异常处理模块

void error_die(const char *sc){ perror(sc); exit(1);}
void not_found(int client){ char buf[1024]; sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, SERVER_STRING); send(client, buf, strlen(buf), 0); sprintf(buf, "Content-Type: text/html\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, "\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, "<BODY><P>The server could not fulfill\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, "your request because the resource specified\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, "is unavailable or nonexistent.\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, "</BODY></HTML>\r\n"); send(client, buf, strlen(buf), 0);}
void bad_request(int client){ char buf[1024]; sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n"); send(client, buf, sizeof(buf), 0); sprintf(buf, "Content-type: text/html\r\n"); send(client, buf, sizeof(buf), 0); sprintf(buf, "\r\n"); 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.\r\n"); send(client, buf, sizeof(buf), 0);}
void cannot_execute(int client){ char buf[1024]; sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, "Content-type: text/html\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, "\r\n"); send(client, buf, strlen(buf), 0); sprintf(buf, "<P>Error prohibited CGI execution.\r\n"); send(client, buf, strlen(buf), 0);}

5) 其他

#define ISspace(x) isspace((int)(x))#define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n"

引用

[1] HTTP协议详解 http://www.cnblogs.com/EricaMIN1987_IT/p/3837436.html
[2] 源码分析之tinyhttpd-0.1 http://www.cnblogs.com/wanpengcoder/p/5304521.html
[3] tinyhttpd源码详解 http://blog.csdn.net/baiwfg2/article/details/45582723

0 0