Jquery中文网 www.jquerycn.cn
Jquery中文网 >  脚本编程  >  C语言  >  正文 cgi 网站开发学习实例

cgi 网站开发学习实例

发布时间:2018-09-15   编辑:www.jquerycn.cn
jquery中文网为您提供cgi 网站开发学习实例等资源,欢迎您收藏本站,我们将为您提供最新的cgi 网站开发学习实例资源
cgi网站开发虽然老了点,不过有时确还是有点用的,本文我们就来学习一下cgi开发,开发语言是C ,本文的内容有:页面模板,uri绑定,文件上传和cgicc。

页面模板

可能是因为公司里写代码用velocity习惯了,找了google的ctemplate来实现类似的功能。ctemplate相对velocity比较简单,只有变量替换,简单的引用、循环。这里有ctemplate的详细文档,相对来说,就是不能在模板上有太多的逻辑。
首先,前一篇里面贴了dispatcher,通过apache中设置环境变量,设置模板跟路径:
   

<table width="620" align="center" border="0" cellpadding="1" cellspacing="1" style="background:#FB7"> <tr> <td width="464" height="27" bgcolor="#FFE7CE"> 代码如下</td> <td width="109" align="center" bgcolor="#FFE7CE" style="cursor:pointer;" onclick="doCopy('copy3735')">复制代码</td> </tr> <tr> <td height="auto" colspan="2" valign="top" bgcolor="#FFFFFF" style="padding:10px;" class="copyclass" id=copy3735>ctemplate::Template::SetTemplateRootDirectory(input.getenv("TEMPLATE_PATH"));</td></tr></table>



然后,通过绑定的处理函数,调用真正的处理uri的函数后,将输出的dictionary对象,和模板合并:

<table width="620" align="center" border="0" cellpadding="1" cellspacing="1" style="background:#FB7"> <tr> <td width="464" height="27" bgcolor="#FFE7CE"> 代码如下</td> <td width="109" align="center" bgcolor="#FFE7CE" style="cursor:pointer;" onclick="doCopy('copy7464')">复制代码</td> </tr> <tr> <td height="auto" colspan="2" valign="top" bgcolor="#FFFFFF" style="padding:10px;" class="copyclass" id=copy7464>std::string templatePath;
if(context.dict)        //has dict
{
    //get template
    templatePath =  context.dict->name() ".tpl";
    //expend
    std::string out;
    bool expandResult = ctemplate::ExpandTemplate(templatePath, ctemplate::STRIP_WHITESPACE, context.dict.get(), &out);

    if(expandResult)        //good, we expend template success
    {
        context.ostream << cgicc::HTTPHTMLHeader();
        context.ostream << out;
    }
    else                            //oops, we response 500
    {
        context.ostream << cgicc::HTTPStatusHeader(500, "Internal Server Error");
        context.ostream << "fail to expand template: " << templatePath;
    }
}
else
{
    //如果没有字典,由对应的函数自己输出
}</td></tr></table>



这里是有字典输出的逻辑。获取根字典的名字,加上后缀,在根路径中查找对应的模板。然后调用ctemplate的函数,将模板展开。最后,将展开后的内容,通过流输出。
如果没有字典输出,默认当成处理函数中已经自己进行了输出(这里主要是为了让处理函数自己输出json内容)
另外一个分支,如果没有绑定,那么直接输出模板内容:

<table width="620" align="center" border="0" cellpadding="1" cellspacing="1" style="background:#FB7"> <tr> <td width="464" height="27" bgcolor="#FFE7CE"> 代码如下</td> <td width="109" align="center" bgcolor="#FFE7CE" style="cursor:pointer;" onclick="doCopy('copy7871')">复制代码</td> </tr> <tr> <td height="auto" colspan="2" valign="top" bgcolor="#FFFFFF" style="padding:10px;" class="copyclass" id=copy7871>// find if there is a template
std::string templatePath = path.substr(1) ".tpl";
if(ctemplate::mutable_default_template_cache()->LoadTemplate(templatePath, ctemplate::STRIP_WHITESPACE))
{
    //expend
    std::string out;
    bool expandResult = ctemplate::ExpandTemplate(templatePath, ctemplate::STRIP_WHITESPACE,
                                                _emptyDict.get(), &out);
    if(expandResult)        //good, we expend template success
    {
        context.ostream << cgicc::HTTPHTMLHeader();
        context.ostream << out;
    }
    else                            //oops, we response 500
    {
        context.ostream << cgicc::HTTPStatusHeader(500, "Internal Server Error");
        context.ostream << "fail to expand template: " << templatePath;
    }
}
else    //not bind and not find a template file
{
    context.ostream << cgicc::HTTPStatusHeader(404, "not find");
    context.ostream << "not find";
}</td></tr></table>



如果没有绑定,用一个空的字典,展开模板,也就是直接输出模板。如果这都没有找到,那么就返回404。

一个绑定函数的例子:

<table width="620" align="center" border="0" cellpadding="1" cellspacing="1" style="background:#FB7"> <tr> <td width="464" height="27" bgcolor="#FFE7CE"> 代码如下</td> <td width="109" align="center" bgcolor="#FFE7CE" style="cursor:pointer;" onclick="doCopy('copy5143')">复制代码</td> </tr> <tr> <td height="auto" colspan="2" valign="top" bgcolor="#FFFFFF" style="padding:10px;" class="copyclass" id=copy5143>void handle(Context &context)
{
    std::vector<Volume> volumes;
    //FIXME: mock数据
    Volume v1 ("1", "a", "cover_1.png", 5);
    Volume v2 ("2", "b", "cover_2.png", 1);

    volumes.push_back(v1);
    volumes.push_back(v2);

    boost::shared_ptr<ctemplate::TemplateDictionary> listPageDict(new ctemplate::TemplateDictionary("list"));
    for(int i = 0; i < volumes.size(); i)
    {
        ctemplate::TemplateDictionary *listSection = listPageDict->AddSectionDictionary("LIST_SECTIONS");
        listSection->SetIntValue("id", volumes[i].id());
        listSection->SetValue("name", volumes[i].name());
        listSection->SetValue("cover_img_path", volumes[i].cover_path());
    }

    context.dict = listPageDict;
}</td></tr></table>



对应的模板文件:

<table width="620" align="center" border="0" cellpadding="1" cellspacing="1" style="background:#FB7"> <tr> <td width="464" height="27" bgcolor="#FFE7CE"> 代码如下</td> <td width="109" align="center" bgcolor="#FFE7CE" style="cursor:pointer;" onclick="doCopy('copy4956')">复制代码</td> </tr> <tr> <td height="auto" colspan="2" valign="top" bgcolor="#FFFFFF" style="padding:10px;" class="copyclass" id=copy4956><!DOCTYPE html>
<html>
        <head>
                <meta charset="utf-8" />
                <title>title</title>
        </head>
        <body>
                <ol>
        {{#LIST_SECTIONS}}
                        <li>name: {{name}}</li>
        {{/LIST_SECTIONS}}
                </ol>
        </body>
</html></td></tr></table>



最后输出就是刚在Volume对象的name字段。



uri绑定

前面两篇博客讲了从uri到模板输出,还没有提到中间处理参数的部分。
首先,参数绑定简单的放在一个map中,这里用的是boost的unordered_map(也就是hashmap),其实已经可以使用c 11提供的unordered_map了。

boost::unordered::unordered_map<std::string, RequestHandleFunc> _mapping;

这个map的value,是一个functor,具体定义为:

typedef boost::function<void (Context &context)> RequestHandleFunc;

也就是没有返回值,参数是Context的函数。
Context结构非常简单,主要是封装了输入和输出,也就是cgi、ostream、dict等对象,没有进行太多的抽象。

<table width="620" align="center" border="0" cellpadding="1" cellspacing="1" style="background:#FB7"> <tr> <td width="464" height="27" bgcolor="#FFE7CE"> 代码如下</td> <td width="109" align="center" bgcolor="#FFE7CE" style="cursor:pointer;" onclick="doCopy('copy6745')">复制代码</td> </tr> <tr> <td height="auto" colspan="2" valign="top" bgcolor="#FFFFFF" style="padding:10px;" class="copyclass" id=copy6745>struct Context
{
        cgicc::Cgicc &cgi;
        cgicc::CgiInput &input;
        std::ostream &ostream;
        boost::shared_ptr<ctemplate::TemplateDictionary> dict;
};</td></tr></table>



这里增加了cgiInput,主要是因为cgicc封装了常用cgi环境变量,没法取到自定义环境变量(前一篇博客介绍过)。
绑定函数非常简单,就是直接插入,需要注意的是,unordered_map非线程安全,没有线程类似java的concurrent hashmap(google了下intel tbb库有类似的数据结构),所以采用了boost thread库中的互斥变量:

<table width="620" align="center" border="0" cellpadding="1" cellspacing="1" style="background:#FB7"> <tr> <td width="464" height="27" bgcolor="#FFE7CE"> 代码如下</td> <td width="109" align="center" bgcolor="#FFE7CE" style="cursor:pointer;" onclick="doCopy('copy7178')">复制代码</td> </tr> <tr> <td height="auto" colspan="2" valign="top" bgcolor="#FFFFFF" style="padding:10px;" class="copyclass" id=copy7178>void bind(const std::string &path, RequestHandleFunc func)
{
    boost::unique_lock<boost::shared_mutex> lock(_mutex);
    _mapping.insert(std::pair<std::string, RequestHandleFunc>(path, func));
}</td></tr></table>



注意,boost的锁分为unique_lock和shared_lock,这里是“写者”,所以需要独占锁。
处理方法前文已经贴了代码了,同样要注意的是,需要在搜索map的时候加锁。这里是“读者”,所以使用shared_lock即可:

<table width="620" align="center" border="0" cellpadding="1" cellspacing="1" style="background:#FB7"> <tr> <td width="464" height="27" bgcolor="#FFE7CE"> 代码如下</td> <td width="109" align="center" bgcolor="#FFE7CE" style="cursor:pointer;" onclick="doCopy('copy7834')">复制代码</td> </tr> <tr> <td height="auto" colspan="2" valign="top" bgcolor="#FFFFFF" style="padding:10px;" class="copyclass" id=copy7834>boost::shared_lock<boost::shared_mutex> lock(_mutex);</td></tr></table>



为了方便绑定,写了个宏,直接绑定。

<table width="620" align="center" border="0" cellpadding="1" cellspacing="1" style="background:#FB7"> <tr> <td width="464" height="27" bgcolor="#FFE7CE"> 代码如下</td> <td width="109" align="center" bgcolor="#FFE7CE" style="cursor:pointer;" onclick="doCopy('copy3641')">复制代码</td> </tr> <tr> <td height="auto" colspan="2" valign="top" bgcolor="#FFFFFF" style="padding:10px;" class="copyclass" id=copy3641>#define REGISTER_URL(URL, CLASS, METHOD)
        CLASS c##CLASS; Handler::instance().bind(URL, boost::bind(&CLASS::METHOD, &c##CLASS, _1))
No Comments</td></tr></table>


文件上传和cgicc

html中上传文件,只需要表单里面放一个input type=file即可,如果要使用ajax异步上传(下文基于jquery),就需要注意几点(以下操作,部分需要基于html5定义的api):

1、页面上创建一个input元素,type是file。如果一个input需要支持选择多个文件,在标签中增加属性:multiple=”multiple”。(注意,这需要浏览器支持html5,具体文档可以见)

2、获取这个元素绑定的已上传文件:

var files = document.getElementById('xxx').files;

先获取元素,通过读取元素的files属性,获取所有已经选择的文件。

3、组装formdata,因为上传文件可能会比较大,所以即使支持多选文件,这里还是分成不同的请求发送。

<table width="620" align="center" border="0" cellpadding="1" cellspacing="1" style="background:#FB7"> <tr> <td width="464" height="27" bgcolor="#FFE7CE"> 代码如下</td> <td width="109" align="center" bgcolor="#FFE7CE" style="cursor:pointer;" onclick="doCopy('copy7005')">复制代码</td> </tr> <tr> <td height="auto" colspan="2" valign="top" bgcolor="#FFFFFF" style="padding:10px;" class="copyclass" id=copy7005>for(var i=0;i<array.length;i ) {
  var file = files[index];
  var formData = new FormData();
  formData.append("file", file);
  formData.append("id", i 1);
}</td></tr></table>



这里将每个文件单独组装成一个formdata,里面包含文件内容和一个id。

4、通过jquery发起post请求:

<table width="620" align="center" border="0" cellpadding="1" cellspacing="1" style="background:#FB7"> <tr> <td width="464" height="27" bgcolor="#FFE7CE"> 代码如下</td> <td width="109" align="center" bgcolor="#FFE7CE" style="cursor:pointer;" onclick="doCopy('copy1423')">复制代码</td> </tr> <tr> <td height="auto" colspan="2" valign="top" bgcolor="#FFFFFF" style="padding:10px;" class="copyclass" id=copy1423>$.ajax({
      type: 'POST',
      url: "file/new",
      data: formData,
      async: true,
      cache: false,
      processData: false,
      contentType: false,
      xhr: function() {
          myXhr = $.ajaxSettings.xhr();
          if(myXhr.upload){
              myXhr.upload.addEventListener('progress',progressHandlerFunction, false);
          }
          return myXhr;
      },
      success: function(data){
          if(!data.result) {
              progressTag.remove();
              $("#" index).after($('<span style="color:red">' data.msg '</span>'));
          }
      }
  });</td></tr></table>



其中的xhr部分稍后会提到,这里还有两个需要特别注意的地方。首先是processData属性,必须设置为false,其次是contentType必须设置为false。其他后端不清楚,cgicc在处理文件上传(既multipart方式post的时候),必须从http头中读取Content-Disposition属性,只有在jquery里面设置了前面提到的两个属性,才能正确的按照标准协议进行封装,让cgicc读取到正确的内容分隔字符串。

5、锦上添花,增加进度条:

<table width="620" align="center" border="0" cellpadding="1" cellspacing="1" style="background:#FB7"> <tr> <td width="464" height="27" bgcolor="#FFE7CE"> 代码如下</td> <td width="109" align="center" bgcolor="#FFE7CE" style="cursor:pointer;" onclick="doCopy('copy6997')">复制代码</td> </tr> <tr> <td height="auto" colspan="2" valign="top" bgcolor="#FFFFFF" style="padding:10px;" class="copyclass" id=copy6997>var progressTag = $("<progress/>", {
      value: 0,
      min: 0,
      max: file.size
  });
  $("#" index).after(progressTag);

  function progressHandlerFunction(evt) {
      if (evt.lengthComputable) {
        progressTag.attr("max", evt.total);
        progressTag.attr("value", evt.loaded);
      }
  }</td></tr></table>



这里通过jquery动态的增加了一个progress标签(html5中新增),然后定义了一个handler。之前jquery的ajax函数中看见了,通过获取原生xhr对象,在upload事件上增加handler函数回调,在上传的同时,将具体进度反馈给用户。当然,如果需要,这里也可以绑定download事件。

6、cgicc处理文件:

cgicc处理文件和普通表单元素类似,唯一的区别是需要通过cgi.getFile函数来获取,而不是cgi.getElement。

<table width="620" align="center" border="0" cellpadding="1" cellspacing="1" style="background:#FB7"> <tr> <td width="464" height="27" bgcolor="#FFE7CE"> 代码如下</td> <td width="109" align="center" bgcolor="#FFE7CE" style="cursor:pointer;" onclick="doCopy('copy7876')">复制代码</td> </tr> <tr> <td height="auto" colspan="2" valign="top" bgcolor="#FFFFFF" style="padding:10px;" class="copyclass" id=copy7876>cgicc::form_iterator idIter = cgi.getElement("id");
cgicc::file_iterator fileIter = cgi.getFile("file");
if(idIter == cgi.getElements().end())
{
    //handle error
}
int id = (int)idIter->getIntegerValue();

if(fileIter == cgi.getFiles().end())
{
    //handle error
}
std::ofstream of(boost::lexical_cast<std::string>(id));
fileIter->writeToStream(of);</td></tr></table>



这里忽略了错误处理,通过获取表单元素,作为文件名,直接将文件通过标准库文件输出流ofstream写到磁盘上。

这样,就完成了从页面上选择文件,到后台保存的简单流程。

您可能感兴趣的文章:
Ruby CGI 编程
用 C 开发 cgi dispatcher 网页
php除了网站能做什么
php主要是干什么用的
php文件有什么用
Python CGI编程
Python3 CGI编程
php 一般用来干什么?
关于CGI 和 PHP-FPM的对比分析
学会php能做什么工作?

[关闭]