Uip的Webserver比较复杂,用c语言实现一个简单服务器的所有功能,路由功能,GET传参,动态页面生成等。
要运行Uip的WebServer 只需要:
1. 修改uip-con.h 里的#inlcude "webserver.h" 去除其注释
2. 在User/main.c 里加入 httpd_init(); //初始化服务器
Uip+ stm32移植参见
浏览器访问 Uip WebServer 页面:
分析下 Uip Webserver 的运行过程,Uip WebServer使用到相关文件如下:
httpd.c
httpd.c中定义了一些字符阿斯克码,含义如下
1
|
#define ISO_nl 0x0a // 换行
|
2
|
#define ISO_space 0x20 // 空格
|
3
|
#define ISO_bang 0x21 // !
|
4
|
#define ISO_percent 0x25 // %
|
5
|
#define ISO_period 0x2e // .
|
6
|
#define ISO_slash 0x2f // /
|
7
|
#define ISO_colon 0x3a // :
|
当Uip接收到Uip接收到底层传递的数据,将接收到的数据通过调用http_appcall(),传递给Webserver处理,再通过handle_connection()先后调用handle_input()函数和handle_output()函数
handle_input()主要作用是分析http数据流:
01
|
static PT_THREAD(handle_input(struct httpd_state *s)) //分析http数据流, http数据流格式(eg. "GET /6?user=123 HTTP/ ...")
|
08
|
PSOCK_READTO(&s->sin, ISO_space); //Uip使用这个函数从http数据流中剥离数据
|
11
|
if(strncmp(s->inputbuf, http_get, 4) != 0) { //判断数据流前4位字符是否为"GET ",判断是否为GET传参
|
12
|
PSOCK_CLOSE_EXIT(&s->sin);
|
15
|
PSOCK_READTO(&s->sin, ISO_space);
|
17
|
if(s->inputbuf[0] != ISO_slash) {
|
18
|
PSOCK_CLOSE_EXIT(&s->sin);
|
21
|
if(s->inputbuf[1] == ISO_space) { //请求路径为 "/ " (eg. 192.168.1.15/ ) 更新请求页面filename 为/index.html
|
22
|
strncpy(s->filename, http_index_html, sizeof(s->filename));
|
23
|
} else { //根据请求路径,更新结构体中filename为相应页面名称
|
24
|
s->inputbuf[PSOCK_DATALEN(&s->sin) - 1] = 0;
|
25
|
strncpy(s->filename, &s->inputbuf[0], sizeof(s->filename));
|
28
|
/* httpd_log_file(uip_conn->ripaddr, s->filename);*/
|
30
|
s->state = STATE_OUTPUT;
|
33
|
PSOCK_READTO(&s->sin, ISO_nl);
|
35
|
if(strncmp(s->inputbuf, http_referer, 8) == 0) {
|
36
|
s->inputbuf[PSOCK_DATALEN(&s->sin) - 2] = 0;
|
37
|
/* httpd_log(&s->inputbuf[9]);*/
|
分析数据得出访问页面的名字,存入一个全局的结构体中,handle_output()函数根据这个结构体获得要输出的页面名字,做相应处理:
01
|
static PT_THREAD(handle_output(struct httpd_state *s))
|
05
|
PT_BEGIN(&s->outputpt);
|
07
|
if(!httpd_fs_open(s->filename, &s->file)) { //没有此页面,打开404页面
|
08
|
httpd_fs_open(http_404_html, &s->file);
|
09
|
strcpy(s->filename, http_404_html);
|
10
|
PT_WAIT_THREAD(&s->outputpt,
|
13
|
PT_WAIT_THREAD(&s->outputpt,
|
16
|
PT_WAIT_THREAD(&s->outputpt,
|
19
|
ptr = strchr(s->filename, ISO_period); //查找字符串s->filename中首次出现字符ISO_period的位置,返回首次出现c的位置的指针
|
20
|
if(ptr != NULL && strncmp(ptr, http_shtml, 6) == 0) { //判断是否为 .shtml网页文件
|
21
|
PT_INIT(&s->scriptpt);
|
22
|
PT_WAIT_THREAD(&s->outputpt, handle_script(s)); //若为 .shtml页面,则调用handle_script()生成动态网页
|
23
|
} else { //不是 .shtml(eg. /index.html),输出该页面数据
|
24
|
PT_WAIT_THREAD(&s->outputpt,
|
28
|
PSOCK_CLOSE(&s->sout);
|
httpd_fs_open()定义于httpd-fs.c,用于读取相应页面的数据,将页面数据存入全局结构体中,是实现路由遍历的关键函数:
1
|
int httpd_fs_open(const char *name, struct httpd_fs_file *file)
|
3
|
#if HTTPD_FS_STATISTICS
|
5
|
#endif /* HTTPD_FS_STATISTICS */
|
6
|
struct httpd_fsdata_file_noconst *f;
|
01
|
//遍历所有的页面数据, 方便校验是否存在该页面
|
02
|
for(f = (struct httpd_fsdata_file_noconst *)HTTPD_FS_ROOT; //HTTPD_FS_ROOT 定义于httpd-fsdata.c, 定义了遍历入口
|
04
|
f = (struct httpd_fsdata_file_noconst *)f->next) { //加载下一个页面数据 ,遍历顺序由httpd_fsdata_file结构体中 next决定(见 httpd-fsdata.c)
|
05
|
if(httpd_fs_strcmp(name, f->name) == 0) { //校验请求的页面是否为此页面
|
08
|
#if HTTPD_FS_STATISTICS
|
10
|
#endif /* HTTPD_FS_STATISTICS */
|
13
|
#if HTTPD_FS_STATISTICS
|
15
|
#endif /* HTTPD_FS_STATISTICS */
|
http-fsdata.c 中包含了所有页面的数据。这里的页面数据都转换为ACAll存在数组中,还包括了层叠样式表 (.css文件) 和图片。其数组结构如下:
01
|
static const unsigned char data_404_html[] = {
|
03
|
0x2f, 0x34, 0x30, 0x34, 0x2e, 0x68, 0x74, 0x6d, 0x6c, 0, //文件名 /404.html
|
05
|
0x3c, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 0xa, 0x20, 0x20, 0x3c, //html文件转码为16进制数据(ASCLL)
|
06
|
0x62, 0x6f, 0x64, 0x79, 0x20, 0x62, 0x67, 0x63, 0x6f, 0x6c,
|
07
|
0x6f, 0x72, 0x3d, 0x22, 0x77, 0x68, 0x69, 0x74, 0x65, 0x22,
|
08
|
0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x63, 0x65, 0x6e,
|
09
|
0x74, 0x65, 0x72, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20,
|
10
|
0x20, 0x3c, 0x68, 0x31, 0x3e, 0x34, 0x30, 0x34, 0x20, 0x2d,
|
11
|
0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20,
|
12
|
0x66, 0x6f, 0x75, 0x6e, 0x64, 0x3c, 0x2f, 0x68, 0x31, 0x3e,
|
13
|
0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x68, 0x33,
|
14
|
0x3e, 0x47, 0x6f, 0x20, 0x3c, 0x61, 0x20, 0x68, 0x72, 0x65,
|
15
|
0x66, 0x3d, 0x22, 0x2f, 0x22, 0x3e, 0x68, 0x65, 0x72, 0x65,
|
16
|
0x3c, 0x2f, 0x61, 0x3e, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x65,
|
17
|
0x61, 0x64, 0x2e, 0x3c, 0x2f, 0x68, 0x33, 0x3e, 0xa, 0x20,
|
18
|
0x20, 0x20, 0x20, 0x3c, 0x2f, 0x63, 0x65, 0x6e, 0x74, 0x65,
|
19
|
0x72, 0x3e, 0xa, 0x20, 0x20, 0x3c, 0x2f, 0x62, 0x6f, 0x64,
|
20
|
0x79, 0x3e, 0xa, 0x3c, 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3e,
|
需要注意的是以下的一段程序:
01
|
//结构体格式说明: 下一个页面地址(用于遍历网页) ,网页name地址 ,html数据起始地址 ,html数据长度
|
03
|
const struct httpd_fsdata_file file_processes_shtml[] = {{NULL, data_processes_shtml, data_processes_shtml + 17, sizeof(data_processes_shtml) - 17}};
|
05
|
const struct httpd_fsdata_file file_404_html[] = {{file_processes_shtml, data_404_html, data_404_html + 10, sizeof(data_404_html) - 10}};
|
07
|
const struct httpd_fsdata_file file_files_shtml[] = {{file_404_html, data_files_shtml, data_files_shtml + 13, sizeof(data_files_shtml) - 13}};
|
09
|
const struct httpd_fsdata_file file_footer_html[] = {{file_files_shtml, data_footer_html, data_footer_html + 13, sizeof(data_footer_html) - 13}};
|
11
|
const struct httpd_fsdata_file file_header_html[] = {{file_footer_html, data_header_html, data_header_html + 13, sizeof(data_header_html) - 13}};
|
13
|
const struct httpd_fsdata_file file_index_html[] = {{file_header_html, data_index_html, data_index_html + 12, sizeof(data_index_html) - 12}};
|
15
|
const struct httpd_fsdata_file file_style_css[] = {{file_index_html, data_style_css, data_style_css + 11, sizeof(data_style_css) - 11}};
|
17
|
const struct httpd_fsdata_file file_tcp_shtml[] = {{file_style_css, data_tcp_shtml, data_tcp_shtml + 11,sizeof(data_tcp_shtml) - 11}};
|
19
|
const struct httpd_fsdata_file file_fade_png[] = {{file_tcp_shtml, data_fade_png, data_fade_png + 10, sizeof(data_fade_png) - 10}};
|
21
|
const struct httpd_fsdata_file file_stats_shtml[] = {{file_fade_png, data_stats_shtml, data_stats_shtml + 13, sizeof(data_stats_shtml) - 13}};
|
23
|
#define HTTPD_FS_ROOT file_stats_shtml //设定路由遍历入口页面,一定要保证所有页面都遍历过一次
|
25
|
#define HTTPD_FS_NUMFILES 10 //设定页面数量
|
在 httpd_fs_open() 首先加载 file_stats_shtml页面数据 再加载file_stats_shtml结构体中下一个网页的数据 也就是file_fade_png的数据,同理 file_fade_png加载后一个页面数据 即 file_tcp_shtml数据 。。。 这样循环一次 就会加载所有的页面,实现里有遍历
Uip WebServer的动态网页生成
在uip/apps/Webserver/http-fs/下有Webserver 页面未转码的html文件,其中有很多 %! 和 %!: 字符 :
04
|
09
|
IP errors IP version/header length
|
23
|
Data packets without ACKs
|
26
|
No connection avaliable
|
27
|
Connection attempts to closed ports
|
|
这是实现动态页面的关键。
handle_output()函数中,找到相应页面数据后,若页面为.shtml,则会调用handle_script()函数:
01
|
static PT_THREAD(handle_script(struct httpd_state *s))
|
05
|
PT_BEGIN(&s->scriptpt);
|
08
|
while(s->file.len > 0) {
|
10
|
/* Check if we should start executing a script. */ //检测当前html数据(定义于httpd-fsdata.c)中是否存在字符 %! 和 %!:
|
11
|
if(*s->file.data == ISO_percent &&
|
12
|
*(s->file.data + 1) == ISO_bang) {
|
13
|
s->scriptptr = s->file.data + 3;
|
14
|
s->scriptlen = s->file.len - 3;
|
15
|
if(*(s->scriptptr - 1) == ISO_colon) { //若为 %!: 根据其后变量名,打开并输出相应文件
|
16
|
httpd_fs_open(s->scriptptr + 1, &s->file); //eg. %!: /header.html 打印/header.html
|
17
|
PT_WAIT_THREAD(&s->scriptpt, send_file(s));
|
18
|
} else { //若为 %! 根据其后变量名,调用相应处理程序(定义于httpd-cgi.c)
|
19
|
PT_WAIT_THREAD(&s->scriptpt, //eg. %! file-stats 调用file-stats 绑定的file_stats()函数,打印出相关数据,实现动态网页
|
20
|
httpd_cgi(s->scriptptr)(s, s->scriptptr));
|
24
|
/* The script is over, so we reset the pointers and continue
|
25
|
sending the rest of the file. */
|
26
|
s->file.data = s->scriptptr;
|
27
|
s->file.len = s->scriptlen;
|
28
|
} else { //当前html数据不存在 %! 和 %!
|
29
|
/* See if we find the start of script marker in the block of HTML
|
uip 载入html数据的方法类似网页里的模板引擎的实现方法。当页面输出时,检测到有字符串 %! 和 %!: 时,则调用相应的cgi程序(httpd-cgi.c)处理,在httpd-cgi.c中做相应的数据处理,实现动态网页。
阅读(1353) | 评论(0) | 转发(0) |