指引网

当前位置: 主页 > 编程开发 > C >

cgi 网站开发学习实例

来源:网络 作者:佚名 点击: 时间:2017-07-19 23:07
[摘要]  cgi网站开发虽然老了点,不过有时确还是有点用的,本文我们就来学习一下cgi开发,开发语言是C++,本文的内容有:页面模板,uri绑定,文件上传和cgicc。

页面模板

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

 代码如下 复制代码
ctemplate::Template::SetTemplateRootDirectory(input.getenv("TEMPLATE_PATH"));



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

 代码如下 复制代码
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
{
    //如果没有字典,由对应的函数自己输出
}



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

 代码如下 复制代码
// 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";
}



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

一个绑定函数的例子:

 代码如下 复制代码
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;
}



对应的模板文件:

 代码如下 复制代码
<!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>



最后输出就是刚在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等对象,没有进行太多的抽象。

 代码如下 复制代码
struct Context
{
        cgicc::Cgicc &cgi;
        cgicc::CgiInput &input;
        std::ostream &ostream;
        boost::shared_ptr<ctemplate::TemplateDictionary> dict;
};



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

 代码如下 复制代码
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));
}



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

 代码如下 复制代码
boost::shared_lock<boost::shared_mutex> lock(_mutex);



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

 代码如下 复制代码
#define REGISTER_URL(URL, CLASS, METHOD)
        CLASS c##CLASS; Handler::instance().bind(URL, boost::bind(&CLASS::METHOD, &c##CLASS, _1))
No Comments


文件上传和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,因为上传文件可能会比较大,所以即使支持多选文件,这里还是分成不同的请求发送。

 代码如下 复制代码
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);
}



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

4、通过jquery发起post请求:

 代码如下 复制代码
$.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>'));
          }
      }
  });



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

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

 代码如下 复制代码
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);
      }
  }



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

6、cgicc处理文件:

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

 代码如下 复制代码
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);



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

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

------分隔线----------------------------