C/C++ 实现CGI程序控制及数据的标准编码

        近来要给用户要写一个控制程序, 服务端就是HTTPD加CGI,CGI再去控制应用, 所以用到协议就是HTTP,  由于用户是一个硬件工程师,软件他是真的不太懂, 所以让我给他写一个框, 所以就有了这篇文章, 大家都是在学习吗. 我一点点的来说:

示例代码下载: https://download.csdn.net/download/wanghaisheng/13091515

1.HTTP协议的数据编码

     http协议的数据传输有两种方法,即POST和GET, 当然还好多, 但常用的就是这两种了。为了保证数据的解析正确定性,在数据传输之前要先对数据进行编码.

     虽然在设置表单信息的传输方式时有POST和GET两种方法,但是不管采取哪种方法,浏览器采取的编码方式却是完全相同的。编码规则如下:

      □ 变量之间使用“&”分开

      □ 变量与其对应值之间使用“=”链接

      □ 空格符使用“+”代替

      □ 保留的控制字符则使用百分号接相应的十六进制ASCII代替

      □ 空格是非法字符

      □ 任意不可打印的ASCII 控制字符都为非法字符

      □ 某些具有特殊意义的字符也用百分号接相应的十六进制ASCII代替 

说明白点就是键值对的形式, 如  cmd=1&err_code=0&response_info=not surport&db_dur=1:40&db_heartbeat=115

但如果里面有特殊的字符就会造成解析出错, 所以就要进行编码, 有了编码就要有解码, 这在RFC里都有规定怎么编,不多说了, 我上代码:


int    DecodeURI(const char* inSrc, int inSrcLen, char* ioDest, int inDestLen)
{
    // return the number of chars written to ioDest
    // or KH_BadURLFormat in the case of any error.

    if( inSrcLen <= 0 || inSrc == NULL || ioDest == NULL || inDestLen <= 0 ) {
        return -1;
    }

    //ASSERT(*inSrc == '/'); //For the purposes of '..' stripping, we assume first char is a /

    int theLengthWritten = 0;
    int tempChar = 0;
    int numDotChars = 0;

    while (inSrcLen > 0)
    {
        if (theLengthWritten == inDestLen) {
            return -1;
        }

        if (*inSrc == '%')
        {
            if (inSrcLen < 3) {
                return -1;
            }

            //if there is a special character in this URL, extract it
            char tempbuff[3];
            inSrc++;
            if (!isxdigit(*inSrc)) {
                return -1;
            }
            tempbuff[0] = *inSrc;
            inSrc++;
            if (!isxdigit(*inSrc)) {
                return -1;
            }
            tempbuff[1] = *inSrc;
            inSrc++;
            tempbuff[2] = '\0';
            sscanf(tempbuff, "%x", &tempChar);
            if( tempChar >= 256 ) {
                return -1;
            }
            //ASSERT(tempChar < 256);
            inSrcLen -= 3;        
        }
        else if (*inSrc == '\0') {
            return -1;
        }
        else
        {
            // Any normal character just gets copied into the destination buffer
            tempChar = *inSrc;
            inSrcLen--;
            inSrc++;
        }

        //
        // If we are in a file system that uses a character besides '/' as a
        // path delimiter, we should not allow this character to appear in the URL.
        // In URLs, only '/' has control meaning.
        //if ((tempChar == kPathDelimiterChar) && (kPathDelimiterChar != '/'))
        if (tempChar == '\\') {
            return -1;
        }

        // Check to see if this character is a path delimiter ('/')
        // If so, we need to further check whether backup is required due to
        // dot chars that need to be stripped
        if ((tempChar == '/') && (numDotChars <= 2) && (numDotChars > 0))
        {
            if( theLengthWritten <= numDotChars ) {
                return -1;
            }
            //ASSERT(theLengthWritten > numDotChars);
            ioDest -= (numDotChars + 1);
            theLengthWritten -= (numDotChars + 1);
        }

        *ioDest = tempChar;

        // Note that because we are doing this dotchar check here, we catch dotchars
        // even if they were encoded to begin with.

        // If this is a . , check to see if it's one of those cases where we need to track
        // how many '.'s in a row we've gotten, for stripping out later on
        if (*ioDest == '.')
        {
            if( theLengthWritten <= 0 ) {
                return -1;
            }
            //ASSERT(theLengthWritten > 0);//first char is always '/', right?
            if ((numDotChars == 0) && (*(ioDest - 1) == '/'))
                numDotChars++;
            else if ((numDotChars > 0) && (*(ioDest - 1) == '.'))
                numDotChars++;
        }
        // If this isn't a dot char, we don't care at all, reset this value to 0.
        else
            numDotChars = 0;

        theLengthWritten++;
        ioDest++;
    }

    // Before returning, "strip" any trailing "." or ".." by adjusting "theLengthWritten
    // accordingly
    if (numDotChars <= 2)
        theLengthWritten -= numDotChars;
    return theLengthWritten;
}

int EncodeURI(const char* inSrc, int inSrcLen, char* ioDest, int inDestLen)
{
    // return the number of chars written to ioDest

    int theLengthWritten = 0;

    while (inSrcLen > 0)
    {
        if (theLengthWritten == inDestLen) {            
            return -1;
        }

        //
        // Always encode 8-bit characters
        if ((unsigned char)*inSrc > 127)
        {
            if (inDestLen - theLengthWritten < 3) {                
                return -1;
            }
            //char iChar = (char)inSrc[0];//(BYTE)*inSrc
            sprintf(ioDest,"%%%X",(char)inSrc[0]);
            ioDest += 3;
            theLengthWritten += 3;

            inSrc++;
            inSrcLen--;
            continue;
        }

        //
        // Only encode certain 7-bit characters
        switch (*inSrc)
        {
            // This is the URL RFC list of illegal characters.
        case (' '):
        case ('\r'):
        case ('\n'):
        case ('\t'):
        case ('<'):
        case ('>'):
        case ('#'):
        case ('%'):
        case ('{'):
        case ('}'):
        case ('|'):
        case ('\\'):
        case ('^'):
        case ('~'):
        case ('['):
        case (']'):
        case ('`'):
        case (';'):
        case ('/'):
        case ('?'):
        case ('@'):
        case ('='):
        case ('&'):
        case ('$'):
        case ('"'):
            {
                if ((inDestLen - theLengthWritten) < 3) {                    
                    return -1;
                }
                //char iChar = (char)inSrc[0];//(BYTE)*inSrc
                sprintf(ioDest,"%%%X",(char)inSrc[0]);
                ioDest += 3;
                theLengthWritten += 3;
                break;
            }
        default:
            {
                *ioDest = *inSrc;
                ioDest++;
                theLengthWritten++;
            }
        }

        inSrc++;
        inSrcLen--;
    }

    return theLengthWritten;
}

   

2.什么是CGI协议

CGI(Common Gateway Interface):是HTTP服务器与你的或其它机器上的程序进行“交谈”的一种工具,其程序须运行在网络服务器上。

2.1 CGI处理步骤:

通过Internet把用户请求送到服务器  ==>  服务器接收用户请求并交给CGI程序处理 ==> CGI程序把处理结果传送给服务器 ==> 服务器把结果送回到用户

2.2 CGI参数获取与结果传递

  1. 参数获取:在通过提前设置环境变量,或者使用管道的方式从标准输入中读取

  2. 结果传递:重定向标准输出到管道的输入端口,通过管道将数据发送给服务器

3.CGI的POST和GET工作方式 

     2.1 POST

     如果在form表单中method使用POST方法,那么服务器会将会把从表单中填入的数据接收,并传给相应的CGI程序(就是action中指定的CGI程序),同时把REQUEST_METHOD环境变量设置为POST,而相应的CGI程序检查该环境变量,以确定其工作在POST接收数据方式,然后读取这个数据。注意使用POST这种方法传输数据时,Http在数据发送完后,并不会发送相应的数据传输完毕提示信息,所以Http服务器提供了另一个环境变量CONTENET_LENGTH,该环境变量记录了传输过来了多少个字节长度的数据(单位为字节),所以在编写CGI程序时,如果method为POST,就需要通过该变量来限定读取的数据的长度(如何实现,下面讲解)。

    2.2 GET

    基本上GET方法和POST方法相同,不同的是,使用GET方法时,数据被存储到一个叫做QUERY_STRING的环境变量中了,具体如何得到该变量里的内容,会在下面的例子中详细讲述。

    说了这么多,通过实例看一下,具体实现时如何编写CGI程序。

int main(){

    map<string, string>  mapInputKV; //存储接收信息
    map<string, string>  mapOutputKV;//存储发送信息
 
  string       strCmd ="";
    kt_cmd_code   e_cmd = cmd_invalid;
    //---------------------------------
    //解析部分 这部分已经完成了的数据的解析
    //---------------------------------
    char *lpMethod = getenv( "REQUEST_METHOD" );
    if (!strcmp( lpMethod, "GET" ))
    {
        char * lpInputStr = getenv( "QUERY_STRING" );
        mapInputKV = ParseString( lpInputStr );
    }
    else if (!strcmp( lpMethod, "POST" ))
    {
        char *lpLenstr = getenv( "CONTENT_LENGTH" );
        if (lpLenstr == NULL){
            //cout << "Error, CONTENT_LENGTH should be entered!" << "<br/>";
            //printf();
            mapOutputKV[KEY_ERR] = ERR_INVALID_CONTENT_LENGTH;
            mapOutputKV[KEY_RESPONE_INFO] = "无效的数据长度";
            goto SEND;
        }
        else{
            int iLen = atoi( lpLenstr );
            char * lpPoststr = new char[iLen + 2];
            memset( lpPoststr, 0, iLen + 2 );
            fgets( lpPoststr, iLen + 1, stdin );
            //cout << "poststr:" << poststr << "<br/>";
            mapInputKV = ParseString( lpPoststr );
            //cout << "m * n = " << atoi( m )*atoi( n ) << "<br/>";            
        }
    }
    else{
        //其它的方法都不支持
        mapOutputKV[KEY_ERR] = ERR_INVALID_METHOD;
        mapOutputKV[KEY_RESPONE_INFO] = "无效的HTTP方法";
        goto SEND;
    }

    //---------------------------------
    //执行部分  把其它的处理过程都加上就可以, 可以把每个过程都放到一个函数里,让MAIN变短.
    //---------------------------------
    //
 
    strCmd = mapInputKV[KEY_CMD];
    e_cmd = strCmd.empty() ? cmd_invalid : (kt_cmd_code)(atoi( strCmd.c_str() ));
 
 
    switch( e_cmd )
    {
        case cmd_get_dashboard_info:
        {
            mapOutputKV[KEY_ERR] = ERR_NOERR;
            mapOutputKV[KEY_RESPONE_INFO] = "成功了, 用于客户端提示用户";


            mapOutputKV[KEY_DASHBOARD_DURATION] = "1:40";
            mapOutputKV[KEY_DASHBOARD_HEARTBEAT] = "115";
            //.....添加其它的信息

            break;
        }
        case cmd_get_network_info:
        {
            mapOutputKV[KEY_ERR] = ERR_NOERR;
            mapOutputKV[KEY_RESPONE_INFO] = "成功了, 用于客户端提示用户";
            //.....添加其它的信息
            mapOutputKV[KEY_NETWORK_IP] = "192.168.1.15";
            mapOutputKV[KEY_NETWORK_MASK] = "255.255.255.0";
            mapOutputKV[KEY_NETWORK_GATEWAY] = "192.168.1.1";
            mapOutputKV[KEY_NETWORK_DNS] = "202.106.0.20";

            break;
        }

        case cmd_set_network_info:
        {
            //进行设置
            string sIP = mapInputKV[KEY_NETWORK_IP];
            string sMask = mapInputKV[KEY_NETWORK_MASK];
            string sGate = mapInputKV[KEY_NETWORK_GATEWAY];


            //根据执行情况设置返回信息
            mapOutputKV[KEY_ERR] = ERR_NOERR;
            mapOutputKV[KEY_RESPONE_INFO] = "成功了, 用于客户端提示用户";

            break;
        }
        case cmd_get_ndi_list: //对于处理代码比较多的情况可以重新定义一个处理函数, 这样会使MAIN函数小一点好维护,这里就是一个示例
        {
            //ndi[1]=DESKTOP-KSLG886 (Test Pattern):192.168.1.191:5961 ndi[2]=DESKTOP-KSLG886 (vMix - External 2):192.168.1.191:5963 ndi[3]=DESKTOP-KSLG886 (vMix - Output):192.168.1.191:5962 
            if (ProGetNdiListCmd( mapInputKV, mapOutputKV ) != 0){
                //输出日志
            }
            break;
        }

            //.................

        default:
            {
                mapOutputKV[KEY_ERR] = ERR_INVALID_CMD;
                mapOutputKV[KEY_RESPONE_INFO] = "失败, 不支持的命令请求";
                break;
            }
    }

 

    //---------------------------------
    //输出部分 , 只要收到请求都是必须要回复的,这是遵循HTTP协议标准, 所以
    //---------------------------------
    
SEND:

    //这个函数已经完成的数据的发送, 这部分已经不用动了
    if (ProResponse( mapOutputKV ) != 0){
        //输出日志
    }
        
    
    return 0;
}

 

为了保证代码的简单好懂, 这里只是贴了主函数. 如何要全部代码请下载资源, 放心只要一分. 

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值