MTK网关方案中 Boa Web Server 分析

来源:互联网 发布:如何在淘宝上评价 编辑:程序博客网 时间:2024/06/03 07:14

针对当前MTK WEB机制进行代码分析总结,总体来说当前机制的重点及难点涉及两个部分,一个是请求处理的核心状态机变迁过程,另一个就是ASP动态解析器的实现,这里分为三部分进行描述,分别对核心状态机变迁、ASP词法解析、语法解析、语法树动作的总结,以及当前服务器的源码分析笔记。

 

一、核心状态机变迁



二、词法解析、语法解析、语法树动作。

这部分算是当前WEB服务器比较难的一块,如果理解了这部分的实现机制,基本上理解当前WEB服务器的代码也就没有什么障碍了,鉴于个人对编译原理也并不了解,这部分也不太容易详细的解释,这里会涉及很大一部分lexyacc(或GNU flexGNU bison)工具的使用以及它们的语法编写,所以偷懒一下,仅仅分析针对我们当前项目,利用这些工具及语法文件为我们生成了什么东西,如果对这些机制感兴趣,可以自学这块的技术,再结合当前分析结果,会对WEB服务器整套机制有更清晰的认识。

 

1、基本原理

a、当用户通过浏览器访问WEB服务器,此时WEB服务器通过请求URL了解到用户希望浏览的页面文件名(假设是test.asp),WEB服务器通过文件扩展名知道了用户在请求一个ASP类型的文件,WEB服务器在本地找到该文件并打开,调用语法解析器。

b、语法解析器的实现代码在我们当前项目中是使用yacc(或GNU bison)工具生成,当然前提是需要一个根据当前语法需求编写的一个特定yacc语法文件(比如当前项目为grammar.y),语法解析器的输入对象除了需要yacc语法文件外,还需要调用词法解析器,通常词法解析器可以使用lex(或GNU flex)生成,但当前项目并没有使用lex,而是自己用C代码写了一个词法解析器(gb-lex.c)供语法解析器使用。

c、WEB服务器调用语法解析器时,语法解析器不断调用词法解析器获取标记及标记值,词法解析器通过读取请求URL指定的ASP文件进行分析,找出符合条件的标记及标记值,并提供给语法解析器使用,语法解析器在根据之前用户提供的语法描述进行语法归约,同时对每一个匹配的语法项执行对应的语法动作,这些语法动作创建了一棵关联的语法树。

d、当WEB服务器调用语法解析器完成之后,语法树也就相应的生成了(当前项目生成的语法树保存在do_asp函数的内部变量中reent->root),之后WEB服务器通过对语法树执行指定的处理来完成轻量级的ASP语言解析执行(通常lexyacc仅仅完成语法树的生成,具体针对语法树的使用需要看当前是做什么目的,比如当前项目是实现解释型语言编译器,即完成ASP语法到C语法的转换及运行)。

 

2、词法解析

通常词法解析都是借用lex(GNU flex)工具,及lex词法文件来完成,但目前MTK方案没有借用lex工具,而是自己实现了词法解析器,感兴趣的同事可以通过gb-lex.c文件来了解MTK词法解析器的实现,由于之前也提到语法解析器需要词法解析器获取解析出来的标记及标记值,所以gb-lex.c文件中实现的函数、变量、枚举值有一大部分并不是随意写的,这些值有些是引用语法解析器中的定义,有些需要提供给语法解析器调用(比如当前语法解析器程序需要调用词法解析器程序gb_lex名称的函数来进行核心词法解析, 而当词法解析器解析到当前内容是GB_HTML_BLOCK标记时,又需要将该标记值的内容传给语法解析器指定的yyval->v_text中),具体细节需要感兴趣的同事自行学习lexyacc相关的技术知识,才能理解他们之间的关系引用,这里不进行详细描述。


这里总结一下当前MTK词法解析器生成的解析结果:

是否在ASP语法标记内(*

标记

描述

GB_IF

解析asp文件内容为“If”部分。(不区分大小写)

GB_ELSE

解析asp文件内容为“Else”部分。(不区分大小写)

GB_ELSEIF

解析asp文件内容为“ElseIf”部分。(不区分大小写)

GB_END

解析asp文件内容为“End”部分。(不区分大小写)

GB_THEN

解析asp文件内容为“Then”部分。(不区分大小写)

GB_SCRIPT

解析asp文件内容为“@SCRIPT”部分,通过对yacc语法文件生成的语法树及对应的语法树动作了解到这部分功能没有实现。

GB_STRING

解析asp文件内容包含在双引号中的内容。

=

解析asp文件内容为“=”部分,通过对yacc语法文件分析了解到目前该标记仅用于条件判断是否相等的比较使用,并不能做为赋值符号来使用。

<

解析asp文件内容为“<”部分,通过对yacc语法文件分析了解到,该标记必须和“>”一起使用,用于条件判断是否不等的比较使用,如“1 <> 2”,但并不能做为小于的比较使用,当前语法文件没有实现小于的判断功能。

>

同“<”描述相同。

(

解析asp文件内容为“(”部分,通过对yacc语法文件分析了解到,该标记必须和“)”一起使用,则表示对内部私有定义的函数调用,同时当前语法文件中固定死了函数的参数为0~3个,参数之间使用逗号分隔。如“tcwebApi_get("VoIPBasic_Common","VoIPLine2Enable","h")”。

)

同“(”描述相同。

,

解析asp文件内容为“,”部分,通过yacc语法文件分析了解到,该标记仅用于函数的参数分隔使用。

GB_ID

这个表示当前解析的内容在ASP语法标记内(即表示是在<% %>内),但以上那些标记都不匹配,这部分内容则标记为GB_ID。比如“<%tcwebApi_get(a,b,c)%>”,这个例子含有4GB_ID,分别为tcwebApi_getabc

GB_HTML_BLOCK

读取ASP文件,不包含在ASP语法标记内的部分都归为该标记,通过对语法树动作了解到这部分内容会原封不动地发给浏览器客户端


是否在ASP语法标记内:

我们以前学习ASP语言知道,ASP的语句在web页面文件中通常是包含在<%  %>中,同样在词法解析器中,也根据这个原则来进行词法分析,如果读取的输入内容没有在<%  %>中,则表示是普通的HTMLJS等等其它原静态WEB内容,否则如果读取的输入内容在<% %>中,则内部的词法解析需要按ASP语言的实现来进行对待。


3、语法解析

通常语法解析都是借用yacc(GNU bison)工具,及yacc语法文件来完成,当前MTK方案的实现也是采用这个方法,当前MTK方案的yacc语法文件为grammar.y

由于yacc语法文件的编写不是一两句话就可以描述清楚,建议感兴趣的同事自行学习lexyacc相关的技术知识,这里不进行详细描述,仅针对当前MTK方案的语法文件描述进行总结。

 

//aspfile为语法分析的起点,aspfile代表一个完整的支持asp格式的文件内容语法描述

//一个ASP文件内容语法是由声明语句组构成。

aspfile -> statements

 

//声明语句组 是由多个声明语句构成,注意这里使用了递归嵌套。

statements -> statement

         -> statements statement

 

//每个声明语句可以表示为以下4种类型的任意1种。

//1@SCRIPT xxx = “xxx”,通过分析语法树动作了解到,即使通过语法解析器解析成功,

//但最终执行代码没有实现。

//2、不包含在ASP语法标记内的部分,详见“词法解析”中的描述。

//3、一条普通语句(见后面stmt的描述)

//4、判断语句(见后面cond的描述)

statement -> GB_SCRIPT GB_ID '=' GB_STRING

        -> GB_HTML_BLOCK

        -> stmt

        -> cond

 

//一条普通语句 可以表示为一条函数调用语句,或者一个标签语句。

stmt -> stmt_call

-> val

 

//一条函数调用语句,这里语法文件固定死了,当前私有函数只能支持0~3个参数。

stmt_call -> GB_ID '('')'

        -> GB_ID '(' GB_STRING ')'

        -> GB_ID '(' GB_STRING ',' GB_STRING ')'

        -> GB_ID '(' GB_STRING ',' GB_STRING ',' GB_STRING ')'

 

//标签语句,由以下任意一种表示

//1GB_STRING 这个比较好理解,像<%”xxx”%>中的xxx就是这种类型

//2GB_ID 在语法文件中有两处地方使用,一处是在stmt_call中用来表示私有函数名

//   那么另一种情况比较特殊,如<%hello%>,通过分析语法树动作了解到,这种情况

//   是被忽略的,不做任何处理。

val -> GB_STRING

   -> GB_ID

 

//判断语句,当前语法描述可以构成如下3种判断语句

//1、一条逻辑分支的判断语句

If 条件 Then

声明语句组

End If

//2、二条逻辑分支的判断语句

If 条件 Then

声明语句组

Else

声明语句组

End If

//3、多条逻辑分支的判断语句

If 条件 Then

声明语句组

ElseIf 条件 Then

声明语句组

ElseIf 条件 Then

    声明语句组

End If

 

cond -> cond_list GB_END GB_IF

    -> cond_list cond_else GB_END GB_IF

 

cond_list -> GB_IF stmt_cond GB_THEN statements

        -> cond_list GB_ELSEIF stmt_cond GB_THEN statements

cond_else -> GB_ELSE statements

 

//条件判断语句,目前只支持等于和不等于两种判断条件,通过对语法树动作分析了解到

//当前条件判断的实现只支持字符串的比较,所以等于和不等于已经可以满足此需求。

stmt_cond -> stmt '=' stmt

         -> stmt '<' '>' stmt


4、语法树动作

yacc语法文件中,通常在语法描述的旁边编写了针对当前语法描述的执行动作,最终目地是为了生成一个语法树,以提供给当前程序使用,用于实现某种功能,比如当前项目会使用生成的语法树实现一种用于动态页面的轻量级ASP语言解释器)。

 

如果要弄明白语法树的生成,需要了解yacc相关的技术知识,感兴趣的同事可以自行学习这方面的技术,这里仅针对目前项目构成的语法树动作做一个总结。

 

do_asp函数中,首先调用语法解析器gb_parse (reent)进行当前asp文件的语法解析,之后解析器会将生成的语法树存储于reent->root中,再调用run_stmts (reent, reent->root)来根据语法树完成轻量级ASP语言解释器的功能实现。


语法树动作标识

描述

AST_HTML

该动作标识表明当前的内容是属于非ASP语法的内容,针对这部分内容直接发送给远端的浏览器。比如<BODY>Hello</BODY>这种非ASP语法的内容,不需要进行ASP解释处理,直接发给远端。

AST_CALL

该动作标识是用来处理如下ASP私有函数,当前方案支持11ASP私有函数(asp_Writerequest_FormtcWebApi_SettcWebApi_GettcWebApi_UnsettcWebApi_CommittcWebApi_SavetcWebApi_CurrentDefaultRoutetcWebApi_constSettcWebApi_staticGettcWebApi_CommitWithoutSave),这些函数的实现都比较简单,这里就不在详细描述。

 

<%tcwebApi_get(a,b,c)%>

AST_CALL_ID

残留代码,在语法文件中已经废弃,不再支持。

AST_CALL_ID_1

残留代码,在语法文件中已经废弃,不再支持。

AST_ID

目前该动作标识没有处理,直接忽略,这种动作标识是由<%xxx%>这种格式生成,没有什么意义。

AST_STRING

该动作标识表示当前是一个字符串,目前在该项目中通常用于条件比较。如<%If tcwebApi_get(a,b,c) = “yyy” Then ......%>,这里的“yyy”对应当前语法树动作,仅仅将字符串原样返回。

AST_NULL

目前该动作标识没有处理,直接忽略,这种动作标识是由<%@SCRIPT xxx = “xxx”%>这种格式生成,目前没有实现该功能。

AST_COND

该动作标识表明当前语法树节点是一个判断处理,这部分比较复杂,在下面用语法树来描述判断处理,相信再结合代码就会比较容易理解了。


判断处理的语法树:

1、一条逻辑分支的判断语句

If 左比较标识 操作符 右比较标识 Then 

    声明语句组 

End If



2、二条逻辑分支的判断语句

If 左比较标识 操作符 右比较标识 Then

声明语句组1

Else

    声明语句组2

EndIf



3、多条逻辑分支的判断语句

If 左比较标识操作符右比较标识1 Then

声明语句组1

ElseIf 左比较标识操作符右比较标识2 Then

声明语句组2

Then

    声明语句组3

End If


三、源码分析笔记

//主程序main//初始化支持的ASP私有函数处理链表,链表头为asp_funcs,后续语法解析器解析//出<%xxx(x,y,z)%>格式的语法标记后,会使用xxx与当前私有函数处理链表中的条//目进行比较,找出当前支持的ASP私有函数,并调用当前函数指针进行处理。    init_asp_funcs ();//生成WEB页面映射表,这里一定要和WEB HTML框架上的页面文件名称统一,//当浏览器发出Http请求后,会使用请求URL的请求路径与当前这个WEB页面映射//表的页面文件名进行匹配。//当前该映射表采用了如下格式,其中pageMap数组的外部索引对应HTML框架中主//菜单个数,pageMap数组的内部索引对应HTML框架中指定主菜单下涉及的子菜单//个数,这里的next指针相当于在子菜单下又级联二级子菜单,但当前方案并没使用//二级子菜单。//pageMap[1][1]->page_name = "sta-device.asp";//pageMap[1][3]->next = NULL;init_pageMap();//更切当前程序的根目录,当前程序是通过程序参数传入,目前设置为根目录为//“/boaroot”fixup_server_root();    //从/boaroot/boa.conf文件中读取配置信息,这里使用了lex/yacc技术机制。read_config_files();//建立非阻塞型的套接口,用于处理http请求,当前监听端口为tcp 80,允许最大的//连接数为250server_s = create_server_socket();//初始化信号处理init_signals();//用户权限降低,如果当前是root用户,则降低权限为配置文件中指定的用户等级、//用户组等级,当前配置文件也设置为0,所以这里没有意义。drop_privs();//初始化通用环境变量,存储到common_cgi_env数组中,后续如果用户请求文件为//CGI类型,则需要通过私有环境变量机制将主程序的信息传送给CGI程序。create_common_env();//初始化是否需要字符转义的位图表,这里使用了位图算法来存储哪些字符需要转义//,哪些字符不需要转义。build_needs_escape();//涉及定时器比较的旧值存储使用initTimerStruct();//获取最大能创建的套接口数c = getrlimit(RLIMIT_NOFILE, &rl);max_connections = rl.rlim_cur;//创建守护进程,当前进程退出,留下子进程。if (do_fork)    switch(fork())    case -1:        exit(1);        break;    case 0:        break;    default:        exit(0);        break;status.requests = 0;status.errors = 0;start_time = current_time;//核心主循环处理,下面单独分析。select_loop(server_s);//释放之前用于页面映射的资源free_pageMap();----------------------------------------------------------------------------------------------------------------------//boa的主循环处理select_loop    //当前ka_timeout值从配置文件中读取,当前为10秒req_timeout.tv_sec = (ka_timeout ? ka_timeout : REQUEST_TIMEOUT);req_timeout.tv_usec = 0l;max_fd = -1;while (1)    //收到SIGHUP信号,并进行处理,该信号主要用于重新加载配置文件    if (sighup_flag)        sighup_run();    //收到SIGCHLD信号,进行子进程控制块资源回收        if (sigchld_flag)            sigchld_run();        //收到SIGALRM信号,打印mime_hashtable、passwd_hashtable两个hash表的        //条目个数。        if (sigalrm_flag)            sigalrm_run();        //收到SIGTERM信号,当前处理方式为将TCP 80监听的套接口从block_read_fdset        //中移除,同时关闭该套接口。        if (sigterm_flag)            if (sigterm_flag == 1)                sigterm_stage1_run(server_s);            if (sigterm_flag == 2 && !request_ready && !request_block)                sigterm_stage2_run();        max_fd = -1;        //检查到有阻塞的请求控制块,则更新阻塞请求控制块的相关信息。        //在fdset_update函数内部通常分两个阶段处理:        //1、首先将当前请求控制块的文件描述符放入select监听事件集中,在当前这次主        //循环不会进行任何处理,但由于已经将需要文件描述符放入select监听事件集中,        //所以在下一轮处理中,如果select引擎检测到当前要监听的事件,则送快速的再        //次进入主循环来再调用fdset_update。        //2、当再次调用fdset_update时,由于当前阻塞的请求控制块已经有了select监听        //事件,则走另外一个流程,该流程中将请求控制块从阻塞列表移入准备列表,并        //且将文件描述符从select监听事件集中去除,因为当前已经准备处理了。之后在        //当前这轮主循环中检测到准备列表有请求控制块,则进行请求控制块的处理。        if (request_block)            fdset_update();        //请求处理核心函数,该函数主要处理2件工作:        //1、如果TCP 80监听的套接口检测到有远端的连接,则进行接入处理,同时生成        //请求控制块加入到准备列表中。        //2、处理准备列表中的所有请求控制块,这里处理流程采用了有限状态机机制。        //下面单独分析。        process_requests(server_s);        //如果当前没有收到终止信号,并且当前TCP监听套接口的连接个数未达到上限,        //则将TCP 80监听的套接口加入到block_read_fdset事件集中。        if (!sigterm_flag && total_connections < (max_connections - 10))             BOA_FD_SET(server_s, &block_read_fdset);        //1、如果当前有准备好的请求控制块,则需要立即处理        //2、否则如果有心跳时间,则超时定时器设置为心跳时间的值        //3、否则使用默认的80秒做为超时定时器时间。        req_timeout.tv_sec = (request_ready ? 0 :             (ka_timeout ? ka_timeout : REQUEST_TIMEOUT));        req_timeout.tv_usec = 0l;        //如果有请求控制块(准备好的或者阻塞的都算),则采用req_timeout做时间值,        //,否则将一直等待。        if (select(max_fd + 1, &block_read_fdset, &block_write_fdset, NULL,        (request_ready || request_block ? &req_timeout : NULL)) == -1)        //更新当前时间        time(¤t_time);        //如果TCP 80监听的套接口检测到有远端连接,则标记上,再次进入主循环时,        //process_requests函数会判断该标记,如果为1,则知道当前有远端的连接,则        //进行接入处理,并创建新的请求控制块,加入到准备队列中。        if (FD_ISSET(server_s, &block_read_fdset))            pending_requests = 1;----------------------------------------------------------------------------------------------------------------------//请求处理核心函数process_requests(server_s);//当前检测到TCP 80监听端口有新的客户端接入,则创建请求控制块,初始化请求//控制块部分信息,并将请求控制块加入到准备列表中。if (pending_requests )    get_request(server_s);    pending_requests = 0;current = request_ready;//遍历请求列表中的请求控制块while (current)    time(¤t_time);    //如果当前请求控制块中已经有要输出的数据,同时当前状态不为终止态。则需要    //进行数据输出处理。    if (current->buffer_end &¤t->status != DEAD && current->status != DONE)        //直接将req->buffer中从 req->buffer_start到req->buffer_end中的数据全部        //通过req->fd套接口发送给远端浏览器客户端。        retval = req_flush(current);                //在process_requests最末尾有针对retval返回值的处理。        //1、-1 表明当前请求控制块未完成,需要从准备列表移到阻塞列表中。        //2、0  表明当前请求控制块已经完成,从准备列表中删除该请求控制块        //3、1  表明当前请求控制块未完成,在准备列表中不动,下一轮继续处理        if (retval == -2)            current->status = DEAD;            retval = 0;            else if (retval >= 0)                retval = 1;        //如果buffer_end没有数据需要发送给远端,或者当前请求控制块的状态已经        //切为终止态,则走这里流程。        else            //请求处理的核心状态机            switch (current->status)            case READ_HEADER:  //初始状态            case ONE_CR:         //收到了“\r”则切为ONE_CR            case ONE_LF:         //收到了“\n”则切为ONE_LF            case TWO_CR:        //在ONE_LF态,又收到“\r”,则切为TWO_CR                //进行HTTP头部分读取处理,下面单独分析                retval = read_header(current);                        //当前POST请求的BODY内容需要写入临时文件,此时读取BUFFER            //已经为空,需要继续从远端套接口中读取后续BODY内容            case BODY_READ:                retval = read_body(current);            //对于一个WEB请求,通常POST方法会先进入此状态,这里总结几种请求            //处理进入状态的说明:            //1、对于GET请求,如果是静态文件,并且文件小于100K,如果请求控制块            //的缓冲可以读取输出完,则直接切为DONE态。            //2、对于GET请求,如果是静态文件,并且文件小于100K,如果请求控制块            //的缓冲一次性无法输出完,则切到WRITE态。            //3、对于GET请求,如果是静态文件,文件大于100K,则切到PIPE_READ            //态。            //4、对于GET请求,如果是CGI扩展文件,则切到PIPE_READ态。            //5、对于GET请求,如果是ASP扩展文件,通常在ASP处理中已经完成所            //有处理,所以切到DONE态。            //6、对于POST请求,则首先切到BODY_WRITE态,之后切到BODY_READ            //态进行POST请求的内容部分读取,再执行后续处理。            case BODY_WRITE:                retval = write_body(current);                        //当前为GET请求,并且请求内容为静态文件,并且该文件的大小小于            //100K,之前已经通过小型缓冲区发送了一部分数据,进入该状态将剩余            //部分发送完成。            case WRITE:                //处理本地静态文件的GET请求                retval = process_get(current);            //两种情况会切到此状态            //1、当前为GET请求,请求文件为普通静态文件,并且该文件长度大于            //100K则切为此态。            //2、当前为GET请求,并且当前请求文件为扩展名为CGI的文件,则也            //切为此态。            case PIPE_READ:                retval = read_from_pipe(current);                        //在PIPE_READ态读取完成或者当前缓冲区满了,则切到该态进行输出            case PIPE_WRITE:                retval = write_from_pipe(current);            //正常处理完成,如果发送缓冲区还有数据,则进行发送,直到发送缓冲            //区没有任何数据了,才返回0,将此请求控制块销毁。            case DONE:                retval = req_flush(current);                if (retval == -2)                    current->status = DEAD;                    retval = 0;                else if (retval > 0)                    retval = 1;                         //异常结束,切换到此状态            case DEAD:                retval = 0;                current->buffer_end = 0;                //请求控制块需要销毁,不再需要使用keepalive机制保存当前会话信息                //req->keepalive=KA_STOPPED                SQUASH_KA(current);             default:                retval = 0;                current->status = DEAD;        //收到终止信号,将req->keepalive=KA_STOPPED        //请求控制块需要销毁,不再需要使用keepalive机制保存当前会话信息        if (sigterm_flag)            SQUASH_KA(current);        //这一步有意义吗?当前机制是单线程,在上面如果已经处理,这里是一定        //不会进入的,除非上面那个ORIGINAL_BEHAVIOR宏被放开。        if (pending_requests)            //TCP80端口又收到远端接入            get_request(server_s);        //根据上面返回结果做处理        //1、如果返回为-1,表示当前因为读、写受到系统缓冲区空或满等影响,需要将        //当前请求控制块移入阻塞队列,并将当前需要监听的事件加入到select事件集中        //2、如果返回为0,表示当前处理成功,可以进行请示控制块销毁,但并不一定会        //销毁,详见free_request的分析。        //3、如果返回为1,表示当前处理了一部分,该请求控制块在下一轮还需要继续        //被处理。        switch (retval)        case -1:            trailer = current;            current = current->next;            block_request(trailer);                 case 0:            current->time_last = current_time;            trailer = current;            current = current->next;            //对当前请求控制块进行释放处理,但这里有两种情况比较特殊            //1、如果当前请求控制块状态并不是DEAD出错状态,并且当前发送BUFFER            //中还有数据,则该请求控制块暂时不进行销毁,同时该请求控制块状态切为            //DONE,待发送BUFFER发送完了才进行销毁。            //2、如果该请求控制块之前与客户端进行交互时,客户端设置了            //CONNECTION: Keep-Alive头域值,则新建一个请求控制块,并将老的请求            //控制块中与连接相关的信息复制到新的请求控制块中,同时将老的请求控制            //块销毁。            free_request(&request_ready, trailer);                case 1:            current->time_last = current_time;            current = current->next;        default:            current = current->next;            break;----------------------------------------------------------------------------------------------------------------------//进行HTTP头部分读取处理read_header(current)//check表示当前已经解析处理在哪个点//client_stream_pos表示已从远端读取到buffer的位置//那么从check到bytes之间的部分即还未进行解析处理的部分。check = req->client_stream + req->parse_pos;buffer = req->client_stream;bytes = req->client_stream_pos;//如果还有未进行解析的数据,则进行处理while (check < (buffer + bytes))    switch (req->status)    //当前在初始态,收到“\r”则切为 ONE_CR 态,收到“\n”则切为 ONE_LF    //态,同时标记当前HTTP头尾为当前正在检测的位置。    case READ_HEADER:        if (*check == '\r')            req->status = ONE_CR;            req->header_end = check;        else if (*check == '\n')            req->status = ONE_LF;            req->header_end = check;        //在ONE_CR态,如果收到“\n”则切为 ONE_LF 态,如果收到“\r”则为    //非法格式,当前ONE_CR态已经收到过“\r”了,此时切为初始态 READ_HEADER    case ONE_CR:        if (*check == '\n')            req->status = ONE_LF;        else if (*check != '\r')            req->status = READ_HEADER;         //在ONE_LF态,收到“\r”则切为 TWO_CR 态,收到“\n”则已经收到两个     //换行符,此时后面内容为HTTP BODY,则切为BODY_READ态,其它情况为     //非法格式,切回初始态。     case ONE_LF:         if (*check == '\r')             req->status = TWO_CR;         else if (*check == '\n')             req->status = BODY_READ;         else             req->status = READ_HEADER;     //收到“\n”则表示已经收到两个“\r\n”,此时后面内容为BODY,则切为     //BODY_READ态,如果收到“\r”则非法格式,恢复为初始态。     case TWO_CR:         if (*check == '\n')             req->status = BODY_READ;         else if (*check != '\r')             req->status = READ_HEADER;       //当前buffer索引值已经解析,位置索引递增       req->parse_pos++;       check++;              //收到HTTP头的行结束符       if (req->status == ONE_LF)           *req->header_end = '\0';                      //进行HTTP请求行、头部字段的解析           //如果对端支持的HTTP版本为0.9,则是一个简单版本,此时           //req->simple为true,直接使用 process_header_end进行处理。           if (req->logline)               if (process_option_line(req) == 0)                   return 0;           else               if (process_logline(req) == 0)                   return 0;               if (req->simple)                   return process_header_end(req);                      //切换到新行的位置           req->header_line = check;       //当前正在进行HTTP BODY内容读取       else if (req->status == BODY_READ)           //当前HTTP头部已经读取完成,准备进行HTTP BODY内容处理,           //在进行BODY内容处理前,先进行核心请求处理。           int retval = process_header_end(req);           //处理POST请求           if (retval && req->method == M_POST)               //header_line指向当前解析处理位置               //header_end指向当前已接收数据尾               req->header_line = check;               req->header_end =req->client_stream + req->client_stream_pos;               //切为BODY_WRITE态,在当前头部读取处理函数中,虽然切               //为BODY_WRITE态,并不表示已经开始进行响应内容的写处理               //,进入该态后即执行write_body函数调用,该函数是检测当前               //HTTP头记载的内容长度,如果指定内容长度的数据没有读取完,               //则会从BODY_WRITE态切为BODY_READ态再继续读取。               req->status = BODY_WRITE;               //当前HTTP头部字段有标记内容长度,则进行记载。               //如果POST请求不含内容长度字段则为非法,返回错误。               if (req->content_length)                   content_length = boa_atoi(req->content_length);                   req->filesize = content_length;                   req->filepos = 0;                   else                   send_r_bad_request(req);                   return 0;                         return retval;//如果当前请求控制块状态还在处理HTTP头部阶段if (req->status < BODY_READ)    //在头部读取阶段,头部数据太长,超出接收BUFFER,切到 DEAD 态。    buf_bytes_left = CLIENT_STREAM_SIZE - req->client_stream_pos;    if (buf_bytes_left < 1)        log_error_time();        req->status = DEAD;        return 0;    //为了解析HTTP头,继续从远端读取数据    bytes = read(req->fd, buffer + req->client_stream_pos, buf_bytes_left);        //1、如果因信号中断,则返回1,下一轮继续处理    //2、如果当前远端没有发送数据,则返回-1,暂时将该请求移到阻塞队列    //3、出错时或远端断开则返回0,不再继续处理。    if (bytes < 0)        if (errno == EINTR)            return 1;        if (errno == EAGAIN || errno == EWOULDBLOCK)            return -1;        return 0;    else if (bytes == 0)        return 0;        //将当前已接收的位置标记递增,同时返回1,下一轮继续处理    req->client_stream_pos += bytes;    return 1;//当前已经到达BODY处理阶段,返回1,下一轮进行BODY相关状态处理return 1;----------------------------------------------------------------------------------------------------------------------//处理本地静态文件的GET请求,之前已经通过小型缓冲区发送了一部分数据,这里将//剩余部分发送完成。process_get(current)    bytes_to_write = req->filesize - req->filepos;if (bytes_to_write > SOCKETBUF_SIZE)    bytes_to_write = SOCKETBUF_SIZE;if (sigsetjmp(env, 1) == 0)    handle_sigbus = 1;        //将剩余部分发送给远端,这里data_mem是当前请求的静态文件做的文件内存映    //射。    bytes_written = write(req->fd, req->data_mem + req->filepos,bytes_to_write);    handle_sigbus = 0;//执行write过程中收到信号错误,切为DEAD态else    req->status = DEAD;    return 0;//1、如果写缓存区满,则将当前请求控制块移到阻塞队列//2、如果写执行错误,则切为DEAD态if (bytes_written < 0)    if (errno == EWOULDBLOCK || errno == EAGAIN)        return -1;    else        req->status = DEAD;        return 0;//已写入指示值递增req->filepos += bytes_written;//整个文件发送完成,返回0,否则返回1在下一轮继续处理if (req->filepos == req->filesize)    return 0;else    return 1;----------------------------------------------------------------------------------------------------------------------//准备开始进行PIPE处理,这里并不一定是在对管道进行处理,因为有两种情况会//进入。//1、当前为GET请求,请求文件为普通静态文件,并且该文件长度大于100K,则会进入,//此时仅仅是对静态文件进行读取,之后发送给远端。//2、当前为GET请求,并且当前请求文件为扩展名为CGI的文件,则会进入,这种情况才//是管理处理,此时子进程调用CGI脚本,将输出指向写管道,当前父进程进行读管道处//理。read_from_pipe(request * req)    //缓冲区剩余空间bytes_to_read = BUFFER_SIZE - (req->header_end - req->buffer);//缓冲区已经满了,先进行一部分处理if (bytes_to_read == 0)    //通常第2种进入情况,首先是将cgi_status设置为CGI_PARSE的,目的就是    //对CGI程序返回的结果可能需要做一些处理,当前仅执行一次就去除了    //CGI_PARSE,所以这种情况的特殊处理通常是写在CGI脚本文件的开头部分    if (req->cgi_status == CGI_PARSE)        req->cgi_status = CGI_BUFFER;        *req->header_end = '\0';        //对CGI程序返回的结果进行处理。        //1、进行起始的合法校验        //2、对Status:输出对特殊处理        //3、对Location:输出对特殊处理        //4、普通处理,将CGI程序返回结果发送给远端        return  process_cgi_header(req);        //当已经处理过一次CGI程序返回结果的解析后,后续遇到缓冲区满,则直接切    //到PIPE_WRITE态进行输出处理    req->status = PIPE_WRITE;    return 1;//从GET静态文件或者读管道获取数据bytes_read = read(req->data_fd, req->header_end, bytes_to_read);//1、收到信号中断了read,返回1,下一轮继续处理//2、读处理阻塞,则返回-1,将当前请求控制块加入到阻塞队列//3、其它错误情况,切到DEAD态if (bytes_read == -1)    if (errno == EINTR)        return 1;    else if (errno == EWOULDBLOCK || errno == EAGAIN)        return -1;    else        req->status = DEAD;        return 0;//已经读取完成else if (bytes_read == 0)    //切到PIPE_WRITE态准备输出    req->status = PIPE_WRITE;    //通常第2种进入情况,首先是将cgi_status设置为CGI_PARSE的,目的就是    //对CGI程序返回的结果可能需要做一些处理,当前仅执行一次就去除了    //CGI_PARSE,所以这种情况的特殊处理通常是写在CGI脚本文件的开头部分    if (req->cgi_status == CGI_PARSE)        req->cgi_status = CGI_DONE;        *req->header_end = '\0';        return  process_cgi_header(req);    //当已经处理过一次CGI程序返回结果的解析后,后续则直接切到PIPE_WRITE    //态进行输出处理    req->cgi_status = CGI_DONE;    return 1;//当前数据没有读取完,并且缓冲区也没有满,则继续读取。req->header_end += bytes_read;return 1;----------------------------------------------------------------------------------------------------------------------//在PIPE_READ态读取完成或者当前缓冲区满了,则需要进行输出write_from_pipe(current);bytes_to_write = req->header_end - req->header_line;//已经没有可输出的数据,当为CGI_DONE态时,则返回完成,否则切到PIPE_READ//态再继续读取。if (bytes_to_write == 0)    if (req->cgi_status == CGI_DONE)        return 0;        req->status = PIPE_READ;    req->header_end = req->header_line = req->buffer;    return 1;//将BUFFER中的数据发送给远端bytes_written = write(req->fd, req->header_line, bytes_to_write);//1、收到信号中断了write,返回1,下一轮继续处理//2、写处理阻塞,则返回-1,将当前请求控制块加入到阻塞队列//3、其它错误情况,切到DEAD态if (bytes_written == -1)    if (errno == EWOULDBLOCK || errno == EAGAIN)        return -1;    else if (errno == EINTR)        return 1;    else        req->status = DEAD;        send_r_error(req);        log_error_doc(req);        return 0;//继续进行下一轮输出处理req->header_line += bytes_written;req->filepos += bytes_written;return 1;----------------------------------------------------------------------------------------------------------------------//对于一个WEB请求,通常POST方法会先进入此状态write_body(current)    //当前可以用于输出的数据bytes_to_write = req->header_end - req->header_line;if (req->filepos + bytes_to_write > req->filesize)    bytes_to_write = req->filesize - req->filepos;//当前已经没有数据可输出if (bytes_to_write == 0)    req->header_line = req->header_end = req->buffer;    //POST请求的BODY数据已经读取完    if (req->filepos >= req->filesize)        //对于POST请求,HTTP头及HTTP BODY都已经读完了,开始进行        //响应处理。        //从这里可以看到如果是POST请求,则请求内容必须是动态的CGI文件或        //ASP文件,而静态文件只能用于GET请求。        return  init_cgi(req);    //当前POST请求的BODY数据还有,则继续读取。    req->status = BODY_READ;    return 1;//将POST请求BODY内容写入临时文件,用于后续处理使用bytes_written = write(req->post_data_fd, req->header_line, bytes_to_write);//1、收到信号中断了write,返回1,下一轮继续处理//2、写处理阻塞,则返回-1,将当前请求控制块加入到阻塞队列//3、其它错误情况,切到DEAD态if (bytes_written == -1)    if (errno == EWOULDBLOCK || errno == EAGAIN)        return -1;    else if (errno == EINTR)        return 1;    else if (errno == ENOSPC)        return 0;    else        return 0;//继续将POST请求BODY内容写入临时文件req->filepos += bytes_written;req->header_line += bytes_written;return 1;----------------------------------------------------------------------------------------------------------------------//当前POST请求的BODY内容需要写入临时文件,此时读取BUFFER已经为空,需要继//续从远端套接口中读取后续BODY内容read_body(current)    //缓冲区剩余空间bytes_free = BUFFER_SIZE - (req->header_end - req->header_line);//剩余可读取的文件大小bytes_to_read = req->filesize - req->filepos;if (bytes_to_read > bytes_free)    bytes_to_read = bytes_free;//POST请求的BODY内容已经全部读取完成,则切到BODY_WRITE态继续处理if (bytes_to_read <= 0)    req->status = BODY_WRITE;    return 1;//从远端套接口中读取剩余的POST请求的BODY内容bytes_read = read(req->fd, req->header_end, bytes_to_read);//1、收到信号中断了read,返回1,下一轮继续处理//2、读处理阻塞,则返回-1,将当前请求控制块加入到阻塞队列if (bytes_read == -1)    if (errno == EWOULDBLOCK || errno == EAGAIN)        return -1;    else        return 0;//远端已经断开,释放当前控制块else if (bytes_read == 0)    send_r_bad_request(req);    return 0;//每读取一次都切到BODY_WRITE态进行处理。req->status = BODY_WRITE;req->header_end += bytes_read;return 1;----------------------------------------------------------------------------------------------------------------------//当前HTTP头部已经读取完成,准备进行HTTP BODY内容处理,在进行BODY内容处理//前,先进行核心请求处理。process_header_end(req)    //没有远端数据,返回错误if (!req->logline)    send_r_error(req);    return 0;//请求URL转义处理unescape_uri(req->request_uri, &(req->query_string));//特殊路径处理clean_pathname(req->request_uri);//非法请求URL格式if (req->request_uri[0] != '/')    send_r_bad_request(req);    return 0;req->iswan_redirect = 0;if( ( M_GET == req->method ) && checkDevRegisterFlag())    //设备注册、SIM卡功能相关,如果当前HTTP请求目的地不是本地,同时设备    //注册没有成功则将iswan_redirect标记设置为1,待后面鉴权验证失败后,会根    //根据此标记来把浏览器重定向到注册页面。//请求URL转换处理。//1、如果请求URL为“/”,则转换成正确的初始页面//2、根据配置文件中的别名配置进行替换处理,比如当前配置文件中记载//“ScriptAlias /cgi-bin/ /boaroot/cgi-bin/”,则从浏览器请求URL为“/cgi-bin/test.asp”则//替换为“/boaroot/cgi-bin/test.asp”,同时将请求控制块中加入当前为CGI的标记,//req->is_cgi = CGI,以及做一些合理性验证处理等。//3、对请求URL中“/~xxx”中的~做特殊处理,因为在Linux中~表示当前用户目录//4、当配置文件中设置了DocumentRoot,则对上述不满足的情况,在请求URL中增加//根目录处理,比如当前配置文件中设置了“DocumentRoot /boaroot/html”,则对于请求//URL为“/test.html”则替换为“/boaroot/html/test.html”//5、对特定头域标记内容为CGI类型的请求在请求控制块中加入当前为CGI的标记。translate_uri(req);if( is_portal_on() && (!checkDevRegisterFlag()) && req->method == M_GET)    //不太清楚这个是什么功能if(req->method == M_GET)    if (strcmp(req->pathname,"/boaroot/cgi-bin/index2.asp") == 0)        //请求页面为index2.asp,则记载登录IP        tcapi_set("Account_Entry0", "LoginIp", req->remote_ip_addr);    if (strcmp(req->pathname,"/boaroot/cgi-bin/refresh.asp"))        //请求页面为refresh.asp,则记载当前是否是从LAN侧登录,以及使用        //Ipv4还是Ipv6。        //以下4种情况的请求URL出现,则从不进行超时处理    if ( 0 == strcmp(req->pathname, "/boaroot/cgi-bin/net-wanset.asp")    || 0 == strcmp(req->pathname, "/boaroot/cgi-bin/refresh.asp")    || ( NULL == strstr(req->pathname,".asp") &&     NULL == strstr(req->pathname,".ASP")) )        adslchanging = 1;    //配置树中有不能超时的请求    tcapi_get("WebCurSet_Entry", "StatusChanging", xmlValue);    if(!strcmp(xmlValue,"1"))        adslchanging = 1;    for(i = 0 ; i < USR_NUM; i++)        //WEB服务器超时处理,当超时后在配置管理树中记载        //tcapi_set(xmlNode, "Logged", "0"),同时也设置超时标记        //bReq_TimeOut[i] = 1,在鉴权处理中会检测该超时标记并进行超时处理        //对于一些特定请求,进行重定向处理。    if (!strcmp(req->pathname,"/boaroot/html/telnet.asp"))        if (0 == isIPv4_Flag)            sprintf(h_temp, "http://[%s]/cgi-bin/telnet.asp", lan_ip6);        else            sprintf(h_temp, "http://%s/cgi-bin/telnet.asp", lan_ip4);        send_r_moved_temp(req , h_temp, "");        return 0;        ......    //针对ASP请求页面(除了几个特定页面外)进行鉴权校验,如果校验失败    //则给浏览器回应错误,这里在校验失败情况下有针对设备注册、SIM卡功能    //的特定页面重定向处理。    if ((strstr(req->pathname,".asp")||strstr(req->pathname,".ASP")    ||(0 == strcmp(req->pathname,"/boaroot/html/romfile.cfg")))    && (strcmp(req->pathname,"/boaroot/cgi-bin/index2.asp") != 0)    && (strcmp(req->pathname,"/boaroot/cgi-bin/register.asp") != 0)    && (strcmp(req->pathname,"/boaroot/cgi-bin/regstatus.asp") != 0)    && (strcmp(req->pathname,"/boaroot/cgi-bin/registersuccess.asp") != 0)    && (http_authorize(req) == -1))        send_r_web_unauthorized(req);            return 0;for(i = 0 ; i < 3; i++)    //获取当前登录用户的有效页面显示掩码位//排除请求为初始页面,如果当前用户无权访问当前页面,则返回校验错误。//POST请求特定分支处理,因为POST请求还需要读取HTTP BODY内容,所以这里//需要提前返回,至于动态页面的响应处理则在读取BODY内容之后才开始。if (req->method == M_POST)    //SIM卡未注册,重定向处理。    if (strcmp(req->pathname,"/boaroot/cgi-bin/index2.asp") && checkSimCardFlag())        tcapi_set("System_Entry", "reqMethod", "Post");        snprintf(redirectUrl, sizeof(redirectUrl), "http://%s/cgi-bin/InsertSimcardMsg.cgi" ,             req->local_ip_addr);        send_r_moved_temp(req , redirectUrl , "");        return 0;    for(i = 0 ; i < USR_NUM; i++)        //如果当前请求页面为refresh.asp,则进行定时器刷新处理    //当请求页面为index2.asp,则打上该标记,该标记在HTTP鉴权中有调用,如果    //标记为0,则进行会话文件合法的判断    if ((!strcmp(req->pathname,"/boaroot/cgi-bin/index2.asp"))        iNormal_Flag = 1;    //当请求页面为upgrade.asp则表示进行升级image处理    if(1 == uploadDownLoadUrl(req))        //调用脚本进行升级image处理,其中会获取IP层的IP_SKB_MARK选项        //目前该选项主要用于QOS。        releaseMemory2(req);    else    //除了生级页面,及这个net-tr069.asp页面,其它页面请求如果出现POST内容    //为组合方式,则响应错误。        if( 0 != strcmp(req->pathname,"/boaroot/cgi-bin/net-tr069.asp")        && NULL != req->content_type         && NULL != strstr(req->content_type, "multipart"))            send_r_bad_request(req);            return 0;    //当有POST请求,还要把语音进程杀掉......    system("killall -9 sipclient");    if(strcmp(req->pathname,"/boaroot/cgi-bin/tools_update.asp") == 0)        fp = fopen("/etc/restart_voip","w");        fputc('s',fp);        fclose(fp);        sleep(3);    //为存储POST请求的BODY内容,创建临时文件    req->post_data_fd = create_temporary_file(1, NULL, 0);    //提前返回,至于动态页面的响应处理则在读取BODY内容之后才开始    return(1);//如果当前请求文件是动态类型。(包括CGI扩展名、ASP扩展名)//该标记目前通常在translate_uri中设置,当发现请求URL的路径含有CGI路径时,则//设置该标记,目前CGI扩展名程序及ASP扩展名程序都存放在相同目录。if (req->is_cgi)    //进行动态文件处理,后面单独分析。    return init_cgi(req);//进行静态文件处理,后面单独分析。return init_get(req);----------------------------------------------------------------------------------------------------------------------//进行动态文件处理,目前有两处调用//1、当请求方式为GET请求时,则在解析完HTTP头后就开始调用//2、当请求方式为POST请求时,则在读取完POST请求的BODY后才开始调用。init_cgi(req)    //处理ASP扩展名的动态文件,下面单独分析if(strstr(req->pathname,".asp")||strstr(req->pathname,".ASP"))    return  asp_handler(req);    //请求控制块需要销毁,不再需要使用keepalive机制保存当前会话信息    //req->keepalive=KA_STOPPED    SQUASH_KA(req);if (req->is_cgi)    //为CGI处理补充环境变量信息    complete_env(req)    //创建管道,用于后续主进程与CGI子进程通讯use_pipes = 1;pipe(pipes);set_nonblock_fd(pipes[0])    //创建进程child_pid = vfork();switch(child_pid)//子进程case 0:    if (req->is_cgi == CGI || req->is_cgi == NPH)        strcpy(path,req->pathname);        foo = path;        c = strrchr(foo, '/');        if (c)            ++c;            *c = '\0';        //将工作目录切到cgi文件目录        chdir(foo)     if (use_pipes)         //子进程使用写管道,同时将标准输出重定向到写管道         close(pipes[0]);         dup2(pipes[1], STDOUT_FILENO);         close(pipes[1]);         set_nonblock_fd(STDOUT_FILENO)          //如果当前为POST请求,则可以还需要对BODY内容进行读取,所以          //将标准输入重定向到存储BODY内容的临时文件描述符上,BODY内          //容的存储见BODY_WRITE状态机的处理。          if (req->method == M_POST)              lseek(req->post_data_fd, 0,SEEK_SET);              dup2(req->post_data_fd, STDIN_FILENO);              close(req->post_data_fd);          //执行CGI脚本程序          if (req->is_cgi)              create_argv(req, aargv);              execve(req->pathname, aargv, req->cgi_env);    //父进程default:    //存储BODY内容的文件描述符,父进程不再需要,则关闭        if (req->method == M_POST).            close(req->post_data_fd);            req->post_data_fd = 0;        //父进程准备从读管道中获取CGI脚本程序的输出        close(pipes[1]);        req->data_fd = pipes[0];        //切为 PIPE_READ ,准备读取CGI脚本程序的输出        req->status = PIPE_READ;    //首先是将cgi_status设置为CGI_PARSE,目的就是对CGI程序返回的结果可    //能需要做一些处理,当前仅执行一次就去除了CGI_PARSE,所以这种情况的    //特殊处理通常是写在CGI脚本文件的开头部分        if (req->is_cgi == CGI)            req->cgi_status = CGI_PARSE;            req->header_line = req->header_end = (req->buffer + BUFFER_SIZE / 2);        //复位准备读取的BUFFER标记        req->buffer_start = req->buffer_end = 0;        req->filepos = 0;        //准备下一轮开始进行CGI脚本程序的输出处理        return 1;----------------------------------------------------------------------------------------------------------------------//进行静态文件处理,目前只有在请求方式为GET,解析完HTTP头后开始调用,所以//静态文件的访问只能通过GET方法执行。init_get(req)    //打开指定的本地静态文件data_fd = open(req->pathname, O_RDONLY);saved_errno = errno;    //打开本地静态文件失败,根据失败情况进行响应if (data_fd == -1)    if (saved_errno == ENOENT)        send_r_not_found(req);    else if (saved_errno == EACCES)        send_r_forbidden(req);    else        send_r_bad_request(req);    return 0;//获取文件信息fstat(data_fd, &statbuf);if (S_ISDIR(statbuf.st_mode))    //当前请求路径为目录,则进行目录请求的处理//如果浏览器支持缓存功能,则之前接收了服务器回应的当前请求文件的最后修改//时间后就会记下来,那么下次在针对这个文件进行请求时,浏览器就会携带这个//最后修改修改时间,这里服务端判断如果该文件的最后修改时间没有改变,则返回//304响应,来通知浏览器,这次请求的文件没有变动,使用本地缓存的就可以了。if (req->if_modified_since && !modified_since(&(statbuf.st_mtime), req->if_modified_since)    send_r_not_modified(req);    close(data_fd);    return 0;//存储文件大小及最后修改时间req->filesize = statbuf.st_size;req->last_modified = statbuf.st_mtime;//当前请求的文件为空,直接返回成功if (req->method == M_HEAD || req->filesize == 0)    send_r_request_ok(req);    close(data_fd);    return 0;//超过100K的文件,则采用分块发送的机制,这里切到 PIPE_READ 态,在PIPE_READ//态中,会将文件内存读取一部分放到BUFFER中,BUFFER满就向远端输出一次,之//后再读文件剩余部分到BUFFER中,再向远端输出一次,直接将整个静态文件输出//完成。if (req->filesize > MAX_FILE_MMAP)    send_r_request_ok(req);    req->status = PIPE_READ;    req->cgi_status = CGI_BUFFER;    req->data_fd = data_fd;    req_flush(req);    req->header_line = req->header_end = req->buffer;    return 1;//当前请求的文件为空,直接返回成功if (req->filesize == 0)    send_r_request_ok(req);    close(data_fd);    return 1;//对于小于100K的静态文件,则采用文件描述符内存映射机制,这样对于小的静态//文件可以避免再经过一次用户空间内存的复制处理,提高处理效率。req->mmap_entry_var = find_mmap(data_fd, &statbuf);req->data_mem = req->mmap_entry_var->mmap;close(data_fd);//发送HTTP响应头send_r_request_ok(req);bytes = BUFFER_SIZE - req->buffer_end; //输出BUFFER还有空间 if (bytes > 0)    if (bytes > req->filesize)        bytes = req->filesize;    if (sigsetjmp(env, 1) == 0)        handle_sigbus = 1;        //将静态文件内容复制到BUFFER中        memcpy(req->buffer + req->buffer_end, req->data_mem, bytes);        handle_sigbus = 0;    //如果在memcpy过程中出现信号错误,则返回错误响应    else        reset_output_buffer(req);        send_r_error(req);        return 0;    //递增已经读取的BUFFER尾标记,及数据源的偏移    req->buffer_end += bytes;    req->filepos += bytes;        //如果当前文件比较小,当前输出BUFFER可以存储完整个文件,则    //直接将文件进行响应,同时切为完成态 DONE    if (req->filesize == req->filepos)        req_flush(req);        req->status = DONE;//当GET请求的静态文件小于100K,同时输出BUFFER一次性无法装下,则//需要进行分段发送,返回1,继续下一轮分段发送处理。//可以参考如下代码调用,这里返回为1后,当前请求控制块状态已经为//BODY_READ,则在此状态机中进行后继的分段发送处理。//read_header//    req->status = BODY_READ//    int retval = process_header_end(req);//        return init_get(req);    //    return retval;return 1;----------------------------------------------------------------------------------------------------------------------//处理ASP扩展名的动态文件asp_handler(req)for(i = 0 ; i < 3; i++)    //记载当前登录用户    //tcapi_set("WebCurSet_Entry", "CurrentAccess", nodename)fd_out = req->fd;//发送响应头http_header()    //如果当前为POST请求,同时头部字段标明当前BODY有内容,则进行//BODY内容解析处理,将浏览器发送的参数提取出来,存储到g_var_post中if((req->method == M_POST) && req->content_length)    if(req->content_type == NULL)        get_post(req);    else if(strstr(req->content_type,"multipart") == NULL)        get_post(req);    else        get_post_multipart(req);//如果当前为GET请求,则浏览器发送的参数存储到请求URL中,则从请求//URL中提取参数,并存储到g_var_post中else if((req->method == M_GET) && req->query_string)    get_query(req);//执行ASP处理do_asp (NULL, req->pathname);    //为ASP处理创建资源    asp_reent* reent = sys_alloc (sizeof(*reent));    memset (reent, 0, sizeof(*reent));        reent->server_env = env;    reent->mem_pos = reent->mem;    reent->lex_line = 1;    //将请求URL指定的asp格读取到内存中reent->asp_start,后续用于ASP    //语法解析处理。    read_asp (reent, filename);    //ASP处理的核心函数,首先使用gb_parse进行ASP语法解析,构建成语法树    //存储到reent->root中,之后调用run_stmts进行语法树处理,这里使用了特殊    //的lex、yacc词法、语法解析器机制实现,如果感兴趣可以参见    //《词法解析、语法解析、语法树动作》小节中的分析。    gb_parse (reent);    run_stmts (reent, reent->root);    //内存资源释放    if (reent->asp_start)        sys_free(reent->asp_start);        sys_free(reent);//释放刚才获取参数时创建的内存资源free_param_line(g_var_post);//处理完成,将请求控制块切为 CGI_DONE 态,以及复位keepalive标记,使得//只要是ASP的处理,执行完一次就断开连接。req->cgi_status = CGI_DONE;req->status = DONE;close(fd_out);req->keepalive = KA_INACTIVE;return 0;



0 0
原创粉丝点击