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

Posted Elaine

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了tinyhttpd ------ C 语言实现最简单的 HTTP 服务器相关的知识,希望对你有一定的参考价值。

工作流程:

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 = 以上是关于tinyhttpd  ------ C 语言实现最简单的 HTTP 服务器的主要内容,如果未能解决你的问题,请参考以下文章

C开源代码学习1 Tinyhttpd

C 语言网络编程 — 轻量级 HTTP 服务器设计与实现

10个经典的C++源码

分享10个值得关注的C语言开源项目

10个值得程序员关注的C语言开源项目

C 语言网络编程 — 轻量级 HTTP 服务器设计与实现