Tinyhttp源码分析

简介

Tinyhttp是一个轻量型Http Server,使用C语言开发,全部代码只500多行,还包括一个简单Client。

Tinyhttp程序的逻辑为:一个无线循环,一个请求,创建一个线程,之后线程函数处理每个请求,然后解析HTTP请求,做一些判断,之后判断文件是否可执行,不可执行,打开文件,输出给客户端(浏览器),可执行就创建管道,父子进程进行通信。父子进程通信,用到了dup2和execl函数。

模型图

image

源码剖析

  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 }

 

posted @ 2019-08-13 00:45  WindSun  阅读(1128)  评论(0编辑  收藏  举报
博客已停更,文章已转移,点击访问