小皮博客 | Xiaopi's Blog

10-micro-httpd源代码解析

1. Web容器实现机制分析

1.1 micro httpd在linux平台下运行,依赖于xinet,具体安装方式请《4-参考最简洁的Web中间件micro_httpd安装指南》

2. micro httpd源代码解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
/* micro_httpd */

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <dirent.h>
#include <ctype.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>

#define SERVER_NAME "micro_httpd"
#define SERVER_URL "http://www.acme.com/software/micro_httpd/"
/** 定义支持的HTTP协议 */
#define PROTOCOL "HTTP/1.0"
/** 定义时间格式 */
#define RFC1123FMT "%a, %d %b %Y %H:%M:%S GMT"

/* 前置进行函数定义,对于非编译型语言开发者来说,需要理解编译/解释等基本的编译器原理 */
static void file_details( char* dir, char* name );
static void send_error( int status, char* title, char* extra_header, char* text );
static void send_headers( int status, char* title, char* extra_header, char* mime_type, off_t length, time_t mod );
static char* get_mime_type( char* name );
static void strdecode( char* to, char* from );
static int hexit( char c );
static void strencode( char* to, size_t tosize, const char* from );

int main( int argc, char** argv ) {
char line[10000], method[10000], path[10000], protocol[10000], idx[20000], location[20000];
char* file;
size_t len;
int ich;
struct stat sb;
FILE* fp;
struct dirent **dl;
int i, n;

if ( argc != 2) /*需要指明根目录,在/etc/xinetd.d/micro_httpd配置参数server_args 取值可以为对应apache的'/var/www/html' */
send_error( 500, "Internal Error", (char*) 0, "Config error - no dir specified.配置出错了,没有指定路径" );
if ( chdir( argv[1] ) < 0)
send_error( 500, "Internal Error", (char*) 0, "Config error - couldn't chdir().配置出错了,无法chdir()" );
if ( fgets(line, sizeof(line), stdin ) == (char*) 0)
send_error( 400, "Bad Request", (char*) 0, "No request found. 没有找到请求" );
/** 提取路径`方法`协议 */
if ( sscanf( line, "%[^ ] %[^ ] %[^ ]", method, path, protocol ) != 3 )
send_error( 400, "Bad Request", (char*) 0, "Can't parse request. 无法解析请求" );

/** 读取到请求头结束 */
while ( fgets( line, sizeof(line), stdin ) != (char*) 0) {
if ( strcmp( line, "\n" ) == 0 || strcmp( line, "\r\n" ) == 0)
break;
}

/** micro_httpd只支持get请求 */
if (strcasecmp( method, "get" ) != 0)
send_error(501, "Not Implemanted", (char*) 0, "该方法尚未实现." );
if (path[0] != '/') /** 一切从简,路径名称以斜杠开头 */
send_error(400, "Bad Request", (char*) 0, "Bad filename.");
file = &(path[1]);
/** 浏览器传递过来的请求字符串是经过UrlEncoding的,所以这里要解析一下 */
strdecode(file, file);
if (file[0] == '\0')
file = "./";
len = strlen( file );
/** 检查路径是否会超过定义的根目录,超过根目录则存在越界风险: Web容器的基本功能吧! 实现非常简单,否则跟直接访问文件没区别了 */
if ( file[0] == '/' ||
strcmp( file, "..") == 0 ||
strncmp( file, "../", 3) == 0 ||
strstr( file, "/../") != (char*) 0 ||
strcmp( &(file[len-3]), "/.." ) == 0)
send_error(400, "Bad Request", (char*) 0, "Illegal filename." );
/** 接下来就是各种报错了,比如404神马的 */
if(stat( file, &sb) < 0 )
send_error(404, "Not Found", (char*) 0, "File not found.");
if (S_ISDIR( sb.st_mode) ) {
/** 目录名要以斜杠结尾 */
if ( file[len-1] != '/') {
(void) snprintf(
location, sizeof(location), "Location: %s/", path);
send_error( 302, "Found", location, "Directories must end with a slash." );
}
(void) snprintf(idx, sizeof(idx), "%sindex.html", file);
/** 目录名称下有index.html则返回index.html */
if (stat(idx, &sb) >= 0) {
file = idx;
goto do_file;
}
send_headers(200, "OK", (char*)0, "text/html", -1, sb.st_mtime);
(void) printf("\
<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n\
<html>\n\
<head>\n\
<meta http-equiv=\"Content-type\" content=\"text/html;charset=UTF-8\">\n\
<title>Index Of %s</title>\n\
</head>\n\
<body bgcolor=\"#99cc99\">\n\
<h4>Index of %s</h4>\n\
<pre>\n", file, file);
n = scandir(file, &dl, NULL, alphasort);
if (n < 0)
perror( "scandir");
else
for (i = 0; i < n; ++i)
file_details(file, dl[i]->d_name);
(void) printf("\
</pre>\n\
<hr>\n\
<address><a href=\"%s\">%s</a></address>\n\
</body>\n\
</html>\n", SERVER_URL, SERVER_NAME );
}
else {
do_file:/** 读取文件内容并且写入到端口对应的通道当中 */
fp = fopen(file, "r");
if (fp == (FILE*) 0)
send_error( 403, "Forbidden", (char*) 0, "File is protected. " );
send_headers(200, "OK", (char*)0, get_mime_type(file), sb.st_size, sb.st_mtime );
while( ( ich = getc( fp )) != EOF )
putchar( ich );
} /* end else */

(void) fflush( stdout);
exit(0);
}

/** 获取文件的详细信息,并且响应超链接 */
static void
file_details(char* dir, char* name) {
static char encoded_name[1000];
static char path[2000];
struct stat sb;
char timestr[16];

strencode( encoded_name, sizeof(encoded_name), name );
(void) snprintf( path, sizeof(path), "%s/%s", dir, name);
if (lstat( path, &sb) < 0)
(void) printf("<a href=\"%s\">%-32.32s</a> ???\n", encoded_name, name);
else {
(void) strftime( timestr, sizeof(timestr), "%d%b%Y %H:%M", localtime( &sb.st_mtime));
(void) printf( "<a href=\"%s\">%-32.32s</a> %15s %14lld\n", encoded_name, name, timestr, (long long) sb.st_size);
}
}

/** 工具方法: 出错时,发送错误响应信息,传递值过来就会生成相对应的报错页面,这里采用的是字符串替换的方法 */
static void
send_error(int status, char* title, char* extra_header, char* text) {
send_headers( status, title, extra_header, "text/html", -1, -1);
(void) printf("\
<!DOCTYPE>\n\
<html>\n\
<head>\n\
<meta http-equiv=\"Content-type\" content=\"text/html;charset=UTF-8\">\n\
<title>%d %s</title>\n\
</head>\n\
<body bgcolor=\"#cc9999\">\n\
<h4>%d %s</h4>\n", status, title, status, title);
(void)printf("%s\n", text);
(void)printf("\
<hr>\n\
<address><a href=\"%s\">%s</a></address>\n\
</body>\n\
</html>\n", SERVER_URL, SERVER_NAME);
(void) fflush(stdout);
exit( 1 );
}

/** 发送http响应头部信息, 因为本质上也是和拿Socket套接字拼接文件流是一样一样的 */
static void
send_headers(int status, char* title, char* extra_header, char* mime_type, off_t length, time_t mod) {
time_t now;
char timebuf[100];

(void)printf("%s %d %s\015\012", PROTOCOL, status, title); // 协议,状态,标题
(void)printf("Server: %s\015\012", SERVER_NAME); // Server字段
now = time( (time_t*) 0);
(void) strftime( timebuf, sizeof(timebuf), RFC1123FMT, gmtime(&now));
(void) printf( "Date: %s\015\012", timebuf);
if (extra_header != (char*) 0)
(void)printf("%s\015\012", extra_header);
if (mime_type != (char*)0)
(void)printf("Content-Type: %s\015\012", mime_type);
if (length >=0 )
(void)printf("Content-Length: %lld\015\012", (long long)length);
if (mod != (time_t) - 1) {
(void)strftime( timebuf, sizeof(timebuf), RFC1123FMT, gmtime(&mod));
(void)printf("Last-Modified: %s\015\012", timebuf);
}
(void) printf("Connection: close\015\012");
(void) printf("\015\012");
}

/** 分析文件名后缀,获取mime类型 */
static char*
get_mime_type( char* name) {
char* dot;

dot = strrchr(name, '.');
if (dot == (char*)0)
return "text/plain; charset=UTF-8";
if (strcmp(dot, ".html" ) == 0 || strcmp(dot, ".htm") == 0)
return "text/html;charset=UTF-8";
if (strcmp(dot, ".xhtml") == 0 || strcmp(dot, ".xht") == 0)
return "application/xhtml+xml; charset=UTF-8";
if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)
return "image/jpeg";
if (strcmp(dot, ".gif") == 0)
return "image/gif";
if (strcmp(dot, ".png") == 0)
return "image/png";
if (strcmp(dot, ".css") == 0)
return "text/css";
if (strcmp(dot, ".xml") == 0 || strcmp(dot, ".xsl") == 0)
return "text/xml; charset=UTF-8";
if (strcmp(dot, ".json") == 0)
return "text/json; charset=UTF-8";
if (strcmp(dot, ".au") == 0)
return "audio/basic";
if (strcmp(dot, ".wav") == 0)
return "audio/wav";
if (strcmp(dot, ".avi") == 0)
return "video/x-msvideo";
if (strcmp(dot, ".mov") == 0 || strcmp(dot, ".qt") == 0)
return "video/quicktime";
if (strcmp(dot, ".mpeg") == 0 || strcmp(dot, ".mpe") == 0)
return "video/mpeg";
if (strcmp(dot, ".vrml") == 0 || strcmp(dot, ".wrl") == 0)
return "model/vrml";
if (strcmp(dot, ".midi") == 0 || strcmp(dot, ".mid") == 0)
return "audio/midi";
if (strcmp(dot, ".mp3") == 0)
return "audio/mpeg";
if (strcmp(dot, ".ogg") == 0)
return "application/ogg";
if (strcmp(dot, ".pac") == 0)
return "application/x-ns-proxy-autoconfig";
return "text/plain; charset=UTF-8"; /**其他一律认为是字节流*/

}

/** 对路径名进行解码URLDecoding */
static void
strdecode( char* to, char* from) {
for (; *from != '\0'; ++to, ++from) {
if (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2])) {
*to = hexit(from[1]) * 16 + hexit(from[2]);
from += 2;/** 指针 */
}
else
*to = * from;
}
*to = '\0';
}

static int
hexit( char c ){
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return 0; /** Should never happen.*/
}

/** URL Encoding */
static void
strencode(char* to, size_t tosize, const char* from) {
int tolen;

for (tolen = 0; *from != '\0' && tolen + 4 < tosize; ++from){
if (isalnum(*from) || strchr( "/_.-~", *from) != (char*) 0) {
*to = * from;
++to;
++tolen;
} else {
(void)sprintf(to, "%%%02x", (int) *from & 0xff);
to += 3;
tolen += 3;
}
}
*to = '\0';
}

版权声明

本文标题:10-micro-httpd源代码解析

文章作者:盛领

发布时间:2015年02月01日 - 14:55:41

原始链接:http://blog.xiaoyuyu.net/post/2d1cc700.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

如您有任何商业合作或者授权方面的协商,请给我留言:sunsetxiao@126.com

盛领 wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
坚持原创技术分享,您的支持将鼓励我继续创作!