简介
Tinyhttp是一个轻量型Http Server,使用C语言开发,全部代码只500多行,还包括一个简单Client。
Tinyhttp程序的逻辑为:一个无线循环,一个请求,创建一个线程,之后线程函数处理每个请求,然后解析HTTP请求,做一些判断,之后判断文件是否可执行,不可执行,打开文件,输出给客户端(浏览器),可执行就创建管道,父子进程进行通信。父子进程通信,用到了dup2和execl函数。
模型图
源码剖析
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方式请求
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 }
