Tinyhttpd精读解析
Posted nengm
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Tinyhttpd精读解析相关的知识,希望对你有一定的参考价值。
首先,本人刚刚开始开源代码精读,写的不对的地方,大家轻拍,一起进步。本文是对Tinyhttpd的一次精读,大家每天都在用着http服务,很多人也一直活跃在上层,使用IIS、Apache等,大家是否想看看http服务器大概是怎么运作的,通过一个500多行的源码加上完整的注释,和大家逛一逛http服务器。Tinyhttpd真的非常适合阅读尤其是刚入门的,清晰的代码,简单的makefile...其实有很多分析tinyghttpd的,这边抱着人家写的是人家,自己写的才是自己的态度,写的尽量详细,尽量简单,很多都写在代码注释里面,也把学习中的一些坑翻出来和大家一起读读,吸取将近20年前的大神通过500行代码带给我们的财富。不管你是写c的,或者C#,js亦或java,只要用到http的都欢迎读读。
大家看后如果觉得我有写的不对的地方,欢迎指出~~
附上注解代码的github地址:
https://github.com/nengm/Tinyhttpd
1.首先是一张图 全解了Tinyhttp是如何运作的,我觉得图说明比我用文字描述的要清晰,语言功底不太给力。
2. 主要函数简略说明,代码中进行了详细注释。
main 主函数
startup 绑定监听套接字
accept_request 每次收到请求,创建一个线程来处理接受到的请求
serve_file 接读取文件返回给请求的http客户端
execute_cgi 执行cgi文件
3.注意点
注意点1:
index.html必须没有执行权限,否则看不到内容,并且会产生Program received signal SIGPIPE, Broken pipe,因为程序中如果有可执行权限会当cgi脚本处理。所以假如html有执行权限先把它去除了,chmod 600 index.html
color.cgi、date.cgi必须要有执行权限。
注意点2:
color.cgi是用perl写的,相信大家很少接触了。所以可以引用网上一个简单的例子,换成一个shell写的cgi测试
#!/bin/bash
echo "Content-Type: text/html"
echo
echo "<HTML><BODY>"
echo "<CENTER>Today is:</CENTER>"
echo "<CENTER><B>"
date
echo "</B></CENTER>"
echo "</BODY></HTML>"
4.操作
1.执行make,生成成功,./httpd启动成功。
2.如果在当前linux下的firefox下执行。直接在浏览器中输入
3.在windows中测试,
4.测试执行自带的perl写的cgi脚本
5.进入index2.html页面,测试shell写的cgi脚本。
5.代码
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 //宏定义,是否是空格 30 #define ISspace(x) isspace((int)(x)) 31 32 #define SERVER_STRING "Server: jdbhttpd/0.1.0\\r\\n" 33 34 //每次收到请求,创建一个线程来处理接受到的请求 35 //把client_sock转成地址作为参数传入pthread_create 36 void accept_request(void *arg); 37 38 //错误请求 39 void bad_request(int); 40 41 //读取文件 42 void cat(int, FILE *); 43 44 //无法执行 45 void cannot_execute(int); 46 47 //错误输出 48 void error_die(const char *); 49 50 //执行cig脚本 51 void execute_cgi(int, const char *, const char *, const char *); 52 53 //得到一行数据,只要发现c为\\n,就认为是一行结束,如果读到\\r,再用MSG_PEEK的方式读入一个字符,如果是\\n,从socket用读出 54 //如果是下个字符则不处理,将c置为\\n,结束。如果读到的数据为0中断,或者小于0,也视为结束,c置为\\n 55 int get_line(int, char *, int); 56 57 //返回http头 58 void headers(int, const char *); 59 60 //没有发现文件 61 void not_found(int); 62 63 //如果不是CGI文件,直接读取文件返回给请求的http客户端 64 void serve_file(int, const char *); 65 66 //开启tcp连接,绑定端口等操作 67 int startup(u_short *); 68 69 //如果不是Get或者Post,就报方法没有实现 70 void unimplemented(int); 71 72 // Http请求,后续主要是处理这个头 73 // 74 // GET / HTTP/1.1 75 // Host: 192.168.0.23:47310 76 // Connection: keep-alive 77 // Upgrade-Insecure-Requests: 1 78 // User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36 79 // Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*; q = 0.8 80 // Accept - Encoding: gzip, deflate, sdch 81 // Accept - Language : zh - CN, zh; q = 0.8 82 // Cookie: __guid = 179317988.1576506943281708800.1510107225903.8862; monitor_count = 5 83 // 84 85 // POST / color1.cgi HTTP / 1.1 86 // Host: 192.168.0.23 : 47310 87 // Connection : keep - alive 88 // Content - Length : 10 89 // Cache - Control : max - age = 0 90 // Origin : http ://192.168.0.23:40786 91 // Upgrade - Insecure - Requests : 1 92 // User - Agent : Mozilla / 5.0 (Windows NT 6.1; WOW64) AppleWebKit / 537.36 (KHTML, like Gecko) Chrome / 55.0.2883.87 Safari / 537.36 93 // Content - Type : application / x - www - form - urlencoded 94 // Accept : text / html, application / xhtml + xml, application / xml; q = 0.9, image / webp, */*;q=0.8 95 // Referer: http://192.168.0.23:47310/ 96 // Accept-Encoding: gzip, deflate 97 // Accept-Language: zh-CN,zh;q=0.8 98 // Cookie: __guid=179317988.1576506943281708800.1510107225903.8862; monitor_count=281 99 // Form Data 100 // color=gray 101 102 /**********************************************************************/ 103 /* A request has caused a call to accept() on the server port to 104 * return. Process the request appropriately. 105 * Parameters: the socket connected to the client */ 106 /**********************************************************************/ 107 void accept_request(void *arg) 108 { 109 //socket 110 int client = (intptr_t)arg; 111 char buf[1024]; 112 int numchars; 113 char method[255]; 114 char url[255]; 115 char path[512]; 116 size_t i, j; 117 struct stat st; 118 int cgi = 0; /* becomes true if server decides this is a CGI 119 * program */ 120 char *query_string = NULL; 121 //根据上面的Get请求,可以看到这边就是取第一行 122 //这边都是在处理第一条http信息 123 //"GET / HTTP/1.1\\n" 124 numchars = get_line(client, buf, sizeof(buf)); 125 i = 0; j = 0; 126 127 //第一行字符串提取Get 128 while (!ISspace(buf[j]) && (i < sizeof(method) - 1)) 129 { 130 method[i] = buf[j]; 131 i++; j++; 132 } 133 //结束 134 method[i] = \'\\0\'; 135 136 //判断是Get还是Post 137 if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) 138 { 139 unimplemented(client); 140 return; 141 } 142 143 //如果是POST,cgi置为1 144 if (strcasecmp(method, "POST") == 0) 145 cgi = 1; 146 147 i = 0; 148 //跳过空格 149 while (ISspace(buf[j]) && (j < sizeof(buf))) 150 j++; 151 152 //得到 "/" 注意:如果你的http的网址为http://192.168.0.23:47310/index.html 153 // 那么你得到的第一条http信息为GET /index.html HTTP/1.1,那么 154 // 解析得到的就是/index.html 155 while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf))) 156 { 157 url[i] = buf[j]; 158 i++; j++; 159 } 160 url[i] = \'\\0\'; 161 162 //判断Get请求 163 if (strcasecmp(method, "GET") == 0) 164 { 165 query_string = url; 166 while ((*query_string != \'?\') && (*query_string != \'\\0\')) 167 query_string++; 168 if (*query_string == \'?\') 169 { 170 cgi = 1; 171 *query_string = \'\\0\'; 172 query_string++; 173 } 174 } 175 176 //路径 177 sprintf(path, "htdocs%s", url); 178 179 //默认地址,解析到的路径如果为/,则自动加上index.html 180 if (path[strlen(path) - 1] == \'/\') 181 strcat(path, "index.html"); 182 183 //获得文件信息 184 if (stat(path, &st) == -1) { 185 //把所有http信息读出然后丢弃 186 while ((numchars > 0) && strcmp("\\n", buf)) /* read & discard headers */ 187 numchars = get_line(client, buf, sizeof(buf)); 188 189 //没有找到 190 not_found(client); 191 } 192 else 193 { 194 if ((st.st_mode & S_IFMT) == S_IFDIR) 195 strcat(path, "/index.html"); 196 //如果你的文件默认是有执行权限的,自动解析成cgi程序,如果有执行权限但是不能执行,会接受到报错信号 197 if ((st.st_mode & S_IXUSR) || 198 (st.st_mode & S_IXGRP) || 199 (st.st_mode & S_IXOTH) ) 200 cgi = 1; 201 if (!cgi) 202 //接读取文件返回给请求的http客户端 203 serve_file(client, path); 204 else 205 //执行cgi文件 206 execute_cgi(client, path, method, query_string); 207 } 208 //执行完毕关闭socket 209 close(client); 210 } 211 212 /**********************************************************************/ 213 /* Inform the client that a request it has made has a problem. 214 * Parameters: client socket */ 215 /**********************************************************************/ 216 void bad_request(int client) 217 { 218 char buf[1024]; 219 220 sprintf(buf, "HTTP/1.0 400 BAD REQUEST\\r\\n"); 221 send(client, buf, sizeof(buf), 0); 222 sprintf(buf, "Content-type: text/html\\r\\n"); 223 send(client, buf, sizeof(buf), 0); 224 sprintf(buf, "\\r\\n"); 225 send(client, buf, sizeof(buf), 0); 226 sprintf(buf, "<P>Your browser sent a bad request, "); 227 send(client, buf, sizeof(buf), 0); 228 sprintf(buf, "such as a POST without a Content-Length.\\r\\n"); 229 send(client, buf, sizeof(buf), 0); 230 } 231 232 /**********************************************************************/ 233 /* Put the entire contents of a file out on a socket. This function 234 * is named after the UNIX "cat" command, because it might have been 235 * easier just to do something like pipe, fork, and exec("cat"). 236 * Parameters: the client socket descriptor 237 * FILE pointer for the file to cat */ 238 /**********************************************************************/ 239 240 //得到文件内容,发送 241 void cat(int client, FILE *resource) 242 { 243 char buf[1024]; 244 245 fgets(buf, sizeof(buf), resource); 246 //循环读 247 while (!feof(resource)) 248 { 249 send(client, buf, strlen(buf), 0); 250 fgets(buf, sizeof(buf), resource); 251 } 252 } 253 254 /**********************************************************************/ 255 /* Inform the client that a CGI script could not be executed. 256 * Parameter: the client socket descriptor. */ 257 /**********************************************************************/ 258 void cannot_execute(int client) 259 { 260 char buf[1024]; 261 262 sprintf(buf, "HTTP/1.0 500 Internal Server Error\\r\\n"); 263 send(client, buf, strlen(buf), 0); 264 sprintf(buf, "Content-type: text/html\\r\\n"); 265 send(client, buf, strlen(buf), 0); 266 sprintf(buf, "\\r\\n"); 267 send(client, buf, strlen(buf), 0); 268 sprintf(buf, "<P>Error prohibited CGI execution.\\r\\n"); 269 send(client, buf, strlen(buf), 0); 270 } 271 272 /**********************************************************************/ 273 /* Print out an error message with perror() (for system errors; based 274 * on value of errno, which indicates system call errors) and exit the 275 * program indicating an error. */ 276 /**********************************************************************/ 277 void error_die(const char *sc) 278 { 279 perror(sc); 280 exit(1); 281 } 282 283 /**********************************************************************/ 284 /* Execute a CGI script. Will need to set environment variables as 285 * appropriate. 286 * Parameters: client socket descriptor 287 * path to the CGI script */ 288 /**********************************************************************/ 289 void execute_cgi(int client, const char *path, 290 const char *method, const char *query_string) 291 { 292 //缓冲区 293 char buf[1024]; 294 295 //2根管道 296 int cgi_output[2]; 297 int cgi_input[2]; 298 299 //进程pid和状态 300 pid_t pid; 301 int status; 302 303 int i; 304 char c; 305 306 //读取的字符数 307 int numchars = 1; 308 309 //http的content_length 310 int content_length = -1; 311 312 //默认字符 313 buf[0] = \'A\'; buf[1] = \'\\0\'; 314 315 //忽略大小写比较字符串 316 if (strcasecmp(method, "GET") == 0) 317 //读取数据,把整个header都读掉,以为Get写死了直接读取index.html,没有必要分析余下的http信息了 318 while ((numchars > 0) && strcmp("\\n", buf)) /* read & discard headers */ 319 numchars = get_line(client, buf, sizeof(buf)); 320 else /* POST */ 321 { 322 numchars = get_line(client, buf, sizeof(buf)); 323 while ((numchars > 0) && strcmp("\\n", buf)) 324 { 325 //如果是POST请求,就需要得到Content-Length,Content-Length:这个字符串一共长为15位,所以 326 //取出头部一句后,将第16位设置结束符,进行比较 327 //第16位置为结束 328 buf[15] = \'\\0\'; 329 if (strcasecmp(buf, "Content-Length:") == 0) 330 //内存从第17位开始就是长度,将17位开始的所有字符串转成整数就是content_length 331 content_length = atoi(&(buf[16])); 332 numchars = get_line(client, buf, sizeof(buf)); 333 } 334 if (content_length == -1) { 335 bad_request(client); 336 return; 337 } 338 } 339 340 sprintf(buf, "HTTP/1.0 200 OK\\r\\n"); 341 send(client, buf, strlen(buf), 0); 342 //建立output管道 343 if (pipe(cgi_output) < 0) { 344 cannot_execute(client); 345 return; 346 } 347 348 //建立input管道 349 if (pipe(cgi_input) < 0) { 350 cannot_execute(client); 351 return; 352 } 353 // f以上是关于Tinyhttpd精读解析的主要内容,如果未能解决你的问题,请参考以下文章
Tinyhttpd - 超轻量型Http Server,使用C语言开发,全部代码只有502行(包括注释),附带一个简单的Client