Uip WebServer 实现

Uip的Webserver比较复杂,用c语言实现一个简单服务器的所有功能,路由功能,GET传参,动态页面生成等。
要运行Uip的WebServer 只需要:
1. 修改uip-con.h 里的#inlcude “webserver.h”  去除其注释
2. 在User/main.c 里加入      httpd_init();   //初始化服务器
Uip+ stm32移植参见 Uip + Stm32移植问题总结 
浏览器访问 Uip WebServer 页面:
6581bcdegw1dv93b6puvhj.jpg
分析下 Uip Webserver 的运行过程,Uip WebServer使用到相关文件如下:
httpd.c                        
httpd.c中定义了一些字符阿斯克码,含义如下

#define ISO_nl 0x0a // 换行
#define ISO_space 0x20 // 空格
#define ISO_bang 0x21 // !
#define ISO_percent 0x25 // %
#define ISO_period 0x2e // .
#define ISO_slash 0x2f // /
#define ISO_colon 0x3a // :

当Uip接收到Uip接收到底层传递的数据,将接收到的数据通过调用http_appcall(),传递给Webserver处理,再通过handle_connection()先后调用handle_input()函数和handle_output()函数
handle_input()主要作用是分析http数据流:
static  PT_THREAD(handle_input(struct httpd_state *s))        //分析http数据流, http数据流格式(eg. "GET /6?user=123 HTTP/ ...")
{
char * ptr;

PSOCK_BEGIN(&s->sin);

//取出到下一个空格号之前的数据
PSOCK_READTO(&s->sin, ISO_space);                   //Uip使用这个函数从http数据流中剥离数据


if(strncmp(s->inputbuf, http_get, 4) != 0) {             //判断数据流前4位字符是否为"GET ",判断是否为GET传参
    PSOCK_CLOSE_EXIT(&s->sin);
}

PSOCK_READTO(&s->sin, ISO_space);

if(s->inputbuf[0] != ISO_slash) {                  
    PSOCK_CLOSE_EXIT(&s->sin);
}

if(s->inputbuf[1] == ISO_space) {                     //请求路径为 "/ " (eg. 192.168.1.15/ ) 更新请求页面filename 为/index.html
    strncpy(s->filename, http_index_html, sizeof(s->filename));
} else {                                          //根据请求路径,更新结构体中filename为相应页面名称
    s->inputbuf[PSOCK_DATALEN(&s->sin) - 1] = 0;
    strncpy(s->filename, &s->inputbuf[0], sizeof(s->filename));       
}

/*  httpd_log_file(uip_conn->ripaddr, s->filename);*/

s->state = STATE_OUTPUT;

while(1) {
    PSOCK_READTO(&s->sin, ISO_nl);

    if(strncmp(s->inputbuf, http_referer, 8) == 0) {
        s->inputbuf[PSOCK_DATALEN(&s->sin) - 2] = 0;
        /*      httpd_log(&s->inputbuf[9]);*/
    }
}

PSOCK_END(&s->sin);
}
分析数据得出访问页面的名字,存入一个全局的结构体中,handle_output()函数根据这个结构体获得要输出的页面名字,做相应处理:
static PT_THREAD(handle_output(struct httpd_state *s))
{
    char *ptr;

    PT_BEGIN(&s->outputpt);

    if(!httpd_fs_open(s->filename, &s->file)) {            //没有此页面,打开404页面
        httpd_fs_open(http_404_html, &s->file);
        strcpy(s->filename, http_404_html);
        PT_WAIT_THREAD(&s->outputpt,
                send_headers(s,
                http_header_404));
        PT_WAIT_THREAD(&s->outputpt,
                send_file(s));
    } else {                                 //正常打印相应页面
        PT_WAIT_THREAD(&s->outputpt,                         
                send_headers(s,
                http_header_200));
        ptr = strchr(s->filename, ISO_period);                //查找字符串s->filename中首次出现字符ISO_period的位置,返回首次出现c的位置的指针
        if(ptr != NULL && strncmp(ptr, http_shtml, 6) == 0) {     //判断是否为 .shtml网页文件    
            PT_INIT(&s->scriptpt);
            PT_WAIT_THREAD(&s->outputpt, handle_script(s));    //若为 .shtml页面,则调用handle_script()生成动态网页
        } else {                            //不是 .shtml(eg.  /index.html),输出该页面数据
            PT_WAIT_THREAD(&s->outputpt,
                    send_file(s));
        }
    }
    PSOCK_CLOSE(&s->sout);
    PT_END(&s->outputpt);
}

httpd_fs_open()定义于httpd-fs.c,用于读取相应页面的数据,将页面数据存入全局结构体中,是实现路由遍历的关键函数:

int  httpd_fs_open(const char *name, struct httpd_fs_file *file)
{
#if HTTPD_FS_STATISTICS
  u16_t i = 0;
#endif /* HTTPD_FS_STATISTICS */
  struct httpd_fsdata_file_noconst *f;
  //遍历所有的页面数据, 方便校验是否存在该页面
for(f = (struct httpd_fsdata_file_noconst *)HTTPD_FS_ROOT;        //HTTPD_FS_ROOT 定义于httpd-fsdata.c, 定义了遍历入口 
    f != NULL; 
    f = (struct httpd_fsdata_file_noconst *)f->next) {             //加载下一个页面数据 ,遍历顺序由httpd_fsdata_file结构体中 next决定(见 httpd-fsdata.c)                                                          
        if(httpd_fs_strcmp(name, f->name) == 0) {                  //校验请求的页面是否为此页面 
            file->data = f->data; 
            file->len = f->len; 
        #if HTTPD_FS_STATISTICS 
                ++count[i]; 
        #endif /* HTTPD_FS_STATISTICS */ 
            return 1;
         } 
    #if HTTPD_FS_STATISTICS 
        ++i; 
    #endif /* HTTPD_FS_STATISTICS */ 
} 
return 0; 
}

http-fsdata.c 中包含了所有页面的数据。这里的页面数据都转换为ACAll存在数组中,还包括了层叠样式表 (.css文件) 和图片。其数组结构如下:

static const unsigned char data_404_html[] = {
/* /404.html */
0x2f, 0x34, 0x30, 0x34, 0x2e, 0x68, 0x74, 0x6d, 0x6c, 0,//文件名  /404.html
0x3c, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 0xa, 0x20, 0x20, 0x3c,            //html文件转码为16进制数据(ASCLL)
0x62, 0x6f, 0x64, 0x79, 0x20, 0x62, 0x67, 0x63, 0x6f, 0x6c, 
0x6f, 0x72, 0x3d, 0x22, 0x77, 0x68, 0x69, 0x74, 0x65, 0x22, 
0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x63, 0x65, 0x6e, 
0x74, 0x65, 0x72, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 
0x20, 0x3c, 0x68, 0x31, 0x3e, 0x34, 0x30, 0x34, 0x20, 0x2d, 
0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 
0x66, 0x6f, 0x75, 0x6e, 0x64, 0x3c, 0x2f, 0x68, 0x31, 0x3e, 
0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x68, 0x33, 
0x3e, 0x47, 0x6f, 0x20, 0x3c, 0x61, 0x20, 0x68, 0x72, 0x65, 
0x66, 0x3d, 0x22, 0x2f, 0x22, 0x3e, 0x68, 0x65, 0x72, 0x65, 
0x3c, 0x2f, 0x61, 0x3e, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x65, 
0x61, 0x64, 0x2e, 0x3c, 0x2f, 0x68, 0x33, 0x3e, 0xa, 0x20, 
0x20, 0x20, 0x20, 0x3c, 0x2f, 0x63, 0x65, 0x6e, 0x74, 0x65, 
0x72, 0x3e, 0xa, 0x20, 0x20, 0x3c, 0x2f, 0x62, 0x6f, 0x64, 
0x79, 0x3e, 0xa, 0x3c, 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 
0};

需要注意的是以下的一段程序:

//结构体格式说明:      下一个页面地址(用于遍历网页)    ,网页name地址      ,html数据起始地址          ,html数据长度
//其中的加减操作是为了去除文件名的长度
const struct httpd_fsdata_file file_processes_shtml[] = {{NULL, data_processes_shtml, data_processes_shtml + 17, sizeof(data_processes_shtml) - 17}};

const struct httpd_fsdata_file file_404_html[] = {{file_processes_shtml, data_404_html, data_404_html + 10, sizeof(data_404_html) - 10}};

const struct httpd_fsdata_file file_files_shtml[] = {{file_404_html, data_files_shtml, data_files_shtml + 13, sizeof(data_files_shtml) - 13}};

const struct httpd_fsdata_file file_footer_html[] = {{file_files_shtml, data_footer_html, data_footer_html + 13, sizeof(data_footer_html) - 13}};

const struct httpd_fsdata_file file_header_html[] = {{file_footer_html, data_header_html, data_header_html + 13, sizeof(data_header_html) - 13}};

const struct httpd_fsdata_file file_index_html[] = {{file_header_html, data_index_html, data_index_html + 12, sizeof(data_index_html) - 12}};

const struct httpd_fsdata_file file_style_css[] = {{file_index_html, data_style_css, data_style_css + 11, sizeof(data_style_css) - 11}};

const struct httpd_fsdata_file file_tcp_shtml[] = {{file_style_css, data_tcp_shtml, data_tcp_shtml + 11, sizeof(data_tcp_shtml) - 11}};

const struct httpd_fsdata_file file_fade_png[] = {{file_tcp_shtml, data_fade_png, data_fade_png + 10, sizeof(data_fade_png) - 10}};

const struct httpd_fsdata_file file_stats_shtml[] = {{file_fade_png, data_stats_shtml, data_stats_shtml + 13, sizeof(data_stats_shtml) - 13}};

#define HTTPD_FS_ROOT file_stats_shtml      //设定路由遍历入口页面,一定要保证所有页面都遍历过一次

#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文件,其中有很多 %! 和 %!: 字符 :

%!: /header.html
<h1>Network statistics</h1>
<center>
<table width="300" border="0">
<tr><td><pre>
IP           Packets received
             Packets sent
         Packets dropped
IP errors    IP version/header length
             IP length, high byte
             IP length, low byte
             IP fragments
             Header checksum
             Wrong protocol
ICMP         Packets received
             Packets sent
             Packets dropped
             Type errors
TCP          Packets received
             Packets sent
             Packets dropped
             Checksum errors
             Data packets without ACKs
             Resets
             Retransmissions
         No connection avaliable
         Connection attempts to closed ports
</pre></td><td><pre>%! net-stats
</pre></table>
</center>
%!: /footer.html
这是实现动态页面的关键。
handle_output()函数中,找到相应页面数据后,若页面为.shtml,则会调用handle_script()函数:
    static  PT_THREAD(handle_script(struct httpd_state *s))                                   
    {
    char *ptr;
    PT_BEGIN(&s->scriptpt);

    while(s->file.len > 0) {

/* Check if we should start executing a script. */        //检测当前html数据(定义于httpd-fsdata.c)中是否存在字符 %! 和 %!:
if(*s->file.data == ISO_percent &&
   *(s->file.data + 1) == ISO_bang) {                    
  s->scriptptr = s->file.data + 3;                        
  s->scriptlen = s->file.len - 3;
  if(*(s->scriptptr - 1) == ISO_colon) {                //若为 %!:  根据其后变量名,打开并输出相应文件 
httpd_fs_open(s->scriptptr + 1, &s->file);                //eg.  %!: /header.html  打印/header.html
PT_WAIT_THREAD(&s->scriptpt, send_file(s));
  } else {                                    //若为 %!   根据其后变量名,调用相应处理程序(定义于httpd-cgi.c)
PT_WAIT_THREAD(&s->scriptpt,                        //eg. %! file-stats        调用file-stats 绑定的file_stats()函数,打印出相关数据,实现动态网页
           httpd_cgi(s->scriptptr)(s, s->scriptptr));
  }
  next_scriptstate(s);

  /* The script is over, so we reset the pointers and continue
 sending the rest of the file. */
  s->file.data = s->scriptptr;
  s->file.len = s->scriptlen;
} else {                                                //当前html数据不存在 %! 和 %!
  /* See if we find the start of script marker in the block of HTML
 to be sent. */

...略去

uip 载入html数据的方法类似网页里的模板引擎的实现方法。当页面输出时,检测到有字符串 %! 和 %!: 时,则调用相应的cgi程序(httpd-cgi.c)处理,在httpd-cgi.c中做相应的数据处理,实现动态网页。