码迷,mamicode.com
首页 > 编程语言 > 详细

tinyhttpd ------ C 语言实现最简单的 HTTP 服务器

时间:2016-06-23 12:49:53      阅读:308      评论:0      收藏:0      [点我收藏+]

标签:

工作流程:

1>服务器启动,在指定端口或随机选取端口绑定httpd服务。

2>收到一个http请求时(其实就是listen端口accept的时候),派生一个线程运行accept_request函数。

3>取出http请求中method(getpost)url,对于get方法,如果有携带参数,则query_string指针指向url?后面的get参数。

4>格式化urlpath数组,表示浏览器请求的文件路径,在tinyhttpd中服务器文件是在htdocs文件夹下。当url/结尾,或者url是个目录,则默认在path中加上index.thml,表示访问主页。

5>如果文件路径合法,对于无参数的get请求,直接输出服务器文件到浏览器,即用http格式写到套接字上,跳到(10)。其他情况(带参数getpost方法,url为科执行文件),则调用execute_cgi函数执行cgi脚本。

6>读取整个http请求并丢弃,如果是post则找出content-length,把http状态码200写到套接字里面。

7>建立两个管道,cgi_inputcgi_output,fork一个子进程。

8>在子进程中,把stdout重定向到cgi_output的写入端,把stdin重定向到cgi_input的读取端,关闭cgi_input的写入端和cgi_output的读取端,是指request_method的环境变量,get的话设置query_string的环境变量,post的话设置content-length的环境变量,这些环境变量都是为了给cgi脚本调用,接着用execl运行cgi程序。

9>在父进程中,关闭cgi_input的读取端和cgi_output的写入端,如果post的话,把post数据写入到cgo_input,已被重定向到stdin读取cgi_output的管道输出到客户端,等待子进程结束。

10>关闭与浏览器的链接,完成一次http请求与回应,因为http是无连接的。

  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 
 29 #define ISspace(x) isspace((int)(x))
 30 
 31 #define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n"
 32 
 33 void accept_request(int);
 34 //处理从套接字上监听到的一个HTTP请求,在这里可以很大一部分的体现服务器处理请求的流程
 35 void bad_request(int);
 36 //返回给客户端这是个错误请求,HTTP状态码是400 BAD REQUEST
 37 void cat(int, FILE *);
 38 //读取服务器上某个文件写到socket套接字
 39 void cannot_execute(int);
 40 //主要执行在处理cgi程序的处理,也是个主要函数
 41 void error_die(const char *);
 42 //把错误信息写到perror并退出
 43 void execute_cgi(int, const char *, const char *, const char *);
 44 //运行cgi程序的处理,也是哥主函数
 45 int get_line(int, char *, int);
 46 //读取套接字的一行,把回车换行等情况都统一为换行符结束
 47 void headers(int, const char *);
 48 //把HTTP相应头写到套接字
 49 void not_found(int);
 50 //主要处理找不到请求的文件时的情况
 51 void serve_file(int, const char *);
 52 //调用cat把服务器文件返回给浏览器
 53 int startup(u_short *);
 54 //初始化httpd服务,包括建立套接字,绑定端口,进行监听等
 55 void unimplemented(int);
 56 //返回给浏览器表示接收到的http请求所用的method不被支持
 57 
 58 /**********************************************************************/
 59 /* A request has caused a call to accept() on the server port to
 60  * return.  Process the request appropriately.
 61  * Parameters: the socket connected to the client */
 62 /**********************************************************************/
 63 void accept_request(int client)
 64 {
 65     
 66     char buf[1024];
 67     int numchars;
 68     char method[255];
 69     char url[255];
 70     char path[512];
 71     size_t i, j;
 72     struct stat st;
 73     int cgi = 0;      /* becomes true if server decides this is a CGI
 74                     * program */
 75     char *query_string = NULL;
 76 
 77     numchars = get_line(client, buf, sizeof(buf));
 78     //读取 client端发送的数据并且 返回的参数是 numchars
 79     i = 0;
 80     j = 0;
 81     while (!ISspace(buf[j]) && (i < sizeof(method) - 1))
 82     {
 83         method[i] = buf[j];
 84         i++;
 85         j++;
 86     }
 87     //得到传递的参数是post 还是get方法 还是其他
 88     method[i] = \0;
 89 
 90     if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
 91     {
 92     //回给浏览器表明收到的 HTTP 请求所用的 method 不被支持
 93         unimplemented(client);
 94         return;
 95     }
 96 
 97     if (strcasecmp(method, "POST") == 0)
 98         cgi = 1;
 99 
100     i = 0;
101     //http请求行格式是 method urI http-version
102     while (ISspace(buf[j]) && (j < sizeof(buf)))
103         j++;
104     while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))
105     {
106         url[i] = buf[j]; //包含请求行中的URI
107         i++;
108         j++;
109     }
110     url[i] = \0;
111     ///获取发送数据中的URI
112     if (strcasecmp(method, "GET") == 0)
113     {
114     //get方法 是将参数传递在URI中的?后面如果元素多用&进行链接
115         query_string = url;
116         while ((*query_string != ?) && (*query_string != \0))
117             query_string++;
118          // get 请求? 后面为参数
119      if (*query_string == ?)
120         {
121             cgi = 1;
122             *query_string = \0;
123             query_string++;
124         }
125     }
126     //格式url存储在path数组 并且html存储在 htdocs文件中
127     sprintf(path, "htdocs%s", url);
128     if (path[strlen(path) - 1] == /)
129         strcat(path, "index.html");  //字符串进行衔接 + index.html
130    //stat函数 根据path路径 获取文件内容 存储在 st 结构体中  成功返回0 错误返回-1
131     if (stat(path, &st) == -1)
132     {
133         while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
134             numchars = get_line(client, buf, sizeof(buf));
135         not_found(client);
136     }
137     else
138     {
139         if ((st.st_mode & S_IFMT) == S_IFDIR)
140             strcat(path, "/index.html");
141         //文件的权限 属主 属组 其它 三种任一个拥有执行权
142     if ((st.st_mode & S_IXUSR) ||
143                 (st.st_mode & S_IXGRP) ||
144                 (st.st_mode & S_IXOTH)    )
145             cgi = 1;
146     //调用cat 把服务器文件返回给浏览器  post方法或者拥有执行权限
147         if (!cgi)
148             serve_file(client, path);
149         else
150             execute_cgi(client, path, method, query_string)
151     //运行cgi程序的处理,也是个主要函数
152     }
153 
154     close(client);
155 }
156 
157 /**********************************************************************/
158 /* Inform the client that a request it has made has a problem.
159  * Parameters: client socket */
160 /**********************************************************************/
161 void bad_request(int client)
162 {
163     char buf[1024];
164 
165     sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n");
166     send(client, buf, sizeof(buf), 0);
167     sprintf(buf, "Content-type: text/html\r\n");
168     send(client, buf, sizeof(buf), 0);
169     sprintf(buf, "\r\n");
170     send(client, buf, sizeof(buf), 0);
171     sprintf(buf, "<P>Your browser sent a bad request, ");
172     send(client, buf, sizeof(buf), 0);
173     sprintf(buf, "such as a POST without a Content-Length.\r\n");
174     send(client, buf, sizeof(buf), 0);
175 }
176 
177 /**********************************************************************/
178 /* Put the entire contents of a file out on a socket.  This function
179  * is named after the UNIX "cat" command, because it might have been
180  * easier just to do something like pipe, fork, and exec("cat").
181  * Parameters: the client socket descriptor
182  *             FILE pointer for the file to cat */
183 /**********************************************************************/
184 //读取服务器上的某个文件 写到socket套接字上
185 void cat(int client, FILE *resource)
186 {
187     char buf[1024];
188 
189     fgets(buf, sizeof(buf), resource);
190     //检测流上的文件结束符  如果文件结束,则返回非0值,否则返回0,文件结束符只能被clearerr()清除。
191     while (!feof(resource))
192     {
193         send(client, buf, strlen(buf), 0);
194         fgets(buf, sizeof(buf), resource);
195     }
196 }
197 
198 /**********************************************************************/
199 /* Inform the client that a CGI script could not be executed.
200  * Parameter: the client socket descriptor. */
201 /**********************************************************************/
202 void cannot_execute(int client)
203 {
204     char buf[1024];
205 
206     sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n");
207     send(client, buf, strlen(buf), 0);
208     sprintf(buf, "Content-type: text/html\r\n");
209     send(client, buf, strlen(buf), 0);
210     sprintf(buf, "\r\n");
211     send(client, buf, strlen(buf), 0);
212     sprintf(buf, "<P>Error prohibited CGI execution.\r\n");
213     send(client, buf, strlen(buf), 0);
214 }
215 
216 /**********************************************************************/
217 /* Print out an error message with perror() (for system errors; based
218  * on value of errno, which indicates system call errors) and exit the
219  * program indicating an error. */
220 /**********************************************************************/
221 void error_die(const char *sc)
222 {
223     perror(sc);
224     exit(1);
225 }
226 
227 /**********************************************************************/
228 /* Execute a CGI script.  Will need to set environment variables as
229  * appropriate.
230  * Parameters: client socket descriptor
231  *             path to the CGI script */
232 /**********************************************************************/
233 
234 //运行cgi程序  也是个主函数
235 void execute_cgi(int client, const char *path,
236                  const char *method, const char *query_string)
237 {
238     //在父进程中,关闭cgi_input的读入端和cgi_output的写入端,如果post的话
239     //把post数据写入到cgi_input,已被重定向到stdin,读取cgi_output的管道
240     //输出到客户端,该管道输入是stdout,接着关闭所有管道,等待子进程结束。
241     
242     char buf[1024];
243     int cgi_output[2];
244     int cgi_input[2];
245     //cgi_output[1] cgi_input[1] 为输入端
246     //cgi_input[0] cgi_output[0] 为输出端
247     pid_t pid;
248     int status;
249     int i;
250     char c;
251     int numchars = 1;
252     int content_length = -1;
253 
254     buf[0] = A;
255     buf[1] = \0;
256     //读入请求头
257     if (strcasecmp(method, "GET") == 0)
258         while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
259             numchars = get_line(client, buf, sizeof(buf));
260     else    /* POST */
261     {
262         numchars = get_line(client, buf, sizeof(buf));
263         while ((numchars > 0) && strcmp("\n", buf))
264         {
265             buf[15] = \0;
266             if (strcasecmp(buf, "Content-Length:") == 0)
267                 content_length = atoi(&(buf[16]));
268             numchars = get_line(client, buf, sizeof(buf));
269         }
270         if (content_length == -1)
271         {
272             bad_request(client);
273             return;
274         }
275     }
276 
277     sprintf(buf, "HTTP/1.0 200 OK\r\n");
278     send(client, buf, strlen(buf), 0);
279 
280     if (pipe(cgi_output) < 0)
281     {
282         cannot_execute(client);
283         return;
284     }
285     if (pipe(cgi_input) < 0)
286     {
287         cannot_execute(client);
288         return;
289     }
290 
291     if ( (pid = fork()) < 0 )
292     {
293         cannot_execute(client);
294         return;
295     }
296     if (pid == 0)  /* child: CGI script */
297     {
298         char meth_env[255];
299         char query_env[255];
300         char length_env[255];
301     //把stdout重定向到cgi_output的写入端
302         dup2(cgi_output[1], 1);
303     //把stdin重定向到cgi_input的读入端
304         dup2(cgi_input[0], 0);
305     //关闭cgi_output的读入端 和 cgi_input的 写入端
306         close(cgi_output[0]);
307         close(cgi_input[1]);
308     //设置request_method的环境变量
309         sprintf(meth_env, "REQUEST_METHOD=%s", method);
310         putenv(meth_env);
311         if (strcasecmp(method, "GET") == 0)
312         {
313         //设置query_string的环境变量
314             sprintf(query_env, "QUERY_STRING=%s", query_string);
315             putenv(query_env);
316         }
317         else     /* POST */
318         {
319         //设置content_length的环境变量
320             sprintf(length_env, "CONTENT_LENGTH=%d", content_length);
321             putenv(length_env);
322         }
323         //用execl运行cgi程序
324     execl(path, path, NULL);
325         exit(0);
326     }
327     else        /* parent */
328     {
329         close(cgi_output[1]);
330         close(cgi_input[0]);
331         //关闭cgi_output的 写入端 和 cgi_input的读取端
332     if (strcasecmp(method, "POST") == 0)
333        //接受post的数据
334             for (i = 0; i < content_length; i++)
335             {
336                 recv(client, &c, 1, 0);
337                 write(cgi_input[1], &c, 1);
338                 //讲post数据写入cgi_input,并且重定向到stdin中
339              }
340     //读取cgi_output的管道输出到客户端,该管道输入时stdout
341         while (read(cgi_output[0], &c, 1) > 0)
342             send(client, &c, 1, 0);
343     //关闭管道
344         close(cgi_output[0]);
345         close(cgi_input[1]);
346         //等待子进程
347     waitpid(pid, &status, 0);
348     }
349 }
350 
351 /**********************************************************************/
352 /* Get a line from a socket, whether the line ends in a newline,
353  * carriage return, or a CRLF combination.  Terminates the string read
354  * with a null character.  If no newline indicator is found before the
355  * end of the buffer, the string is terminated with a null.  If any of
356  * the above three line terminators is read, the last character of the
357  * string will be a linefeed and the string will be terminated with a
358  * null character.
359  * Parameters: the socket descriptor
360  *             the buffer to save the data in
361  *             the size of the buffer
362  * Returns: the number of bytes stored (excluding null) */
363 /**********************************************************************/
364 int get_line(int sock, char *buf, int size)
365 {
366     int i = 0;
367     char c = \0;
368     int n;
369 
370     while ((i < size - 1) && (c != \n))
371     {
372         //每次接受一个字符
373     n = recv(sock, &c, 1, 0);
374         /* DEBUG printf("%02X\n", c); */
375         if (n > 0)
376         {
377             if (c == \r)
378             {
379                 n = recv(sock, &c, 1, MSG_PEEK);
380                 /* DEBUG printf("%02X\n", c); */
381                 if ((n > 0) && (c == \n))
382                     recv(sock, &c, 1, 0);
383                 else
384                     c = \n;
385             }
386             buf[i] = c;
387             i++;
388         }
389         else
390             c = \n;
391     }
392     buf[i] = \0;
393 
394     return(i);
395 }
396 
397 /**********************************************************************/
398 /* Return the informational HTTP headers about a file. */
399 /* Parameters: the socket to print the headers on
400  *             the name of the file */
401 /**********************************************************************/
402 //http 响应体
403 void headers(int client, const char *filename)
404 {
405     char buf[1024];
406     (void)filename;  /* could use filename to determine file type */
407 
408     strcpy(buf, "HTTP/1.0 200 OK\r\n");//响应的状态行
409     send(client, buf, strlen(buf), 0);
410     strcpy(buf, SERVER_STRING);
411     send(client, buf, strlen(buf), 0);
412     sprintf(buf, "Content-Type: text/html\r\n"); //响应头
413     send(client, buf, strlen(buf), 0);
414     strcpy(buf, "\r\n");        //响应正文段
415     send(client, buf, strlen(buf), 0);
416 }
417 
418 /**********************************************************************/
419 /* Give a client a 404 not found status message. */
420 /**********************************************************************/
421 void not_found(int client)
422 {
423     //客户端发送的请求无法实现
424     char buf[1024];
425    //响应行
426     sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");
427     send(client, buf, strlen(buf), 0);
428     sprintf(buf, SERVER_STRING);
429     send(client, buf, strlen(buf), 0);
430     //响应头
431     sprintf(buf, "Content-Type: text/html\r\n");
432     send(client, buf, strlen(buf), 0);
433     sprintf(buf, "\r\n");
434     //响应头和响应正文段之间有一个空行 表示响应行结束
435     send(client, buf, strlen(buf), 0);
436     //响应正文段 text/html
437     sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");
438     send(client, buf, strlen(buf), 0);
439     sprintf(buf, "<BODY><P>The server could not fulfill\r\n");
440     send(client, buf, strlen(buf), 0);
441     sprintf(buf, "your request because the resource specified\r\n");
442     send(client, buf, strlen(buf), 0);
443     sprintf(buf, "is unavailable or nonexistent.\r\n");
444     send(client, buf, strlen(buf), 0);
445     sprintf(buf, "</BODY></HTML>\r\n");
446     send(client, buf, strlen(buf), 0);
447 }
448 
449 /**********************************************************************/
450 /* Send a regular file to the client.  Use headers, and report
451  * errors to client if they occur.
452  * Parameters: a pointer to a file structure produced from the socket
453  *              file descriptor
454  *             the name of the file to serve */
455 /**********************************************************************/
456 void serve_file(int client, const char *filename)
457 {
458     FILE *resource = NULL;
459     int numchars = 1;
460     char buf[1024];
461 
462     buf[0] = A;
463     buf[1] = \0;
464     while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
465         numchars = get_line(client, buf, sizeof(buf));
466 
467     resource = fopen(filename, "r");
468     if (resource == NULL)
469         not_found(client);
470     else
471     {
472         headers(client, filename);
473         cat(client, resource);
474     }
475     fclose(resource);
476 }
477 
478 /**********************************************************************/
479 /* This function starts the process of listening for web connections
480  * on a specified port.  If the port is 0, then dynamically allo        //响应正文段cate a
481  * port and modify the original port variable to reflect the actual
482  * port.
483  * Parameters: pointer to variable containing the port to connect on
484  * Returns: the socket */
485 /**********************************************************************/
486 int startup(u_short *port)
487 {
488     int httpd = 0;
489     struct sockaddr_in name;
490     //建立套接字
491     httpd = socket(PF_INET, SOCK_STREAM, 0);
492     if (httpd == -1)
493         error_die("socket");
494     memset(&name, 0, sizeof(name));
495     name.sin_family = AF_INET;
496     name.sin_port = htons(*port);
497     name.sin_addr.s_addr = htonl(INADDR_ANY);
498     //绑定端口和ip
499     if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)
500         error_die("bind");
501     if (*port == 0)  /* if dynamically allocating a port */
502     {
503         int namelen = sizeof(name);
504         if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)
505             error_die("getsockname");
506         *port = ntohs(name.sin_port);
507     }
508     //监听
509     if (listen(httpd, 5) < 0)
510         error_die("listen");
511     return(httpd);
512 }
513 
514 /**********************************************************************/
515 /* Inform the client that the requested web method has not been
516  * implemented.
517  * Parameter: the client socket */
518 /**********************************************************************/
519 void unimplemented(int client)
520 {
521     //发回响应信息  http的请求方法不被接受
522     char buf[1024];
523     //返回501 错误 未实现 (Not implemented)是指Web 服务器不理解或不支持发送给它的 HTTP 数据流中找到的 HTTP 方法
524     sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n");
525     send(client, buf, strlen(buf), 0);
526     sprintf(buf, SERVER_STRING);
527     send(client, buf, strlen(buf), 0);
528     sprintf(buf, "Content-Type: text/html\r\n");//响应头
529     send(client, buf, strlen(buf), 0);
530     sprintf(buf, "\r\n");
531     send(client, buf, strlen(buf), 0); //响应正文段
532     sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n");
533     send(client, buf, strlen(buf), 0);
534     sprintf(buf, "</TITLE></HEAD>\r\n");
535     send(client, buf, strlen(buf), 0);
536     sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n");
537     send(client, buf, strlen(buf), 0);
538     sprintf(buf, "</BODY></HTML>\r\n");
539     send(client, buf, strlen(buf), 0);
540 }
541 
542 /**********************************************************************/
543 
544 int main(void)
545 {
546     int server_sock = -1;
547     u_short port = 0;
548     int client_sock = -1;
549     struct sockaddr_in client_name;
550     int client_name_len = sizeof(client_name);
551     pthread_t newthread;
552 
553     server_sock = startup(&port);
554     printf("httpd running on port %d\n", port);
555 
556     while (1)
557     {
558         client_sock = accept(server_sock,
559                              (struct sockaddr *)&client_name,
560                              &client_name_len);
561         //建立链接
562     if (client_sock == -1)
563             error_die("accept");
564         /* accept_request(client_sock); */
565         //多线程进行控制
566     if (pthread_create(&newthread , NULL, accept_request, client_sock) != 0)
567             perror("pthread_create");
568     }
569 
570     close(server_sock);
571 
572     return(0);
573 }

 

tinyhttpd ------ C 语言实现最简单的 HTTP 服务器

标签:

原文地址:http://www.cnblogs.com/chenyang920/p/5610052.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!