最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
cgi 网站开发学习实例
时间:2022-06-25 04:48:48 编辑:袖梨 来源:一聚教程网
页面模板
可能是因为公司里写代码用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 //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 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; } |
对应的模板文件:
代码如下 | 复制代码 |
{{#LIST_SECTIONS}} {{/LIST_SECTIONS}} |
最后输出就是刚在Volume对象的name字段。
uri绑定
前面两篇博客讲了从uri到模板输出,还没有提到中间处理参数的部分。
首先,参数绑定简单的放在一个map中,这里用的是boost的unordered_map(也就是hashmap),其实已经可以使用c++11提供的unordered_map了。
boost::unordered::unordered_map
这个map的value,是一个functor,具体定义为:
typedef boost::function
也就是没有返回值,参数是Context的函数。
Context结构非常简单,主要是封装了输入和输出,也就是cgi、ostream、dict等对象,没有进行太多的抽象。
代码如下 | 复制代码 |
struct Context { cgicc::Cgicc &cgi; cgicc::CgiInput &input; std::ostream &ostream; boost::shared_ptr }; |
这里增加了cgiInput,主要是因为cgicc封装了常用cgi环境变量,没法取到自定义环境变量(前一篇博客介绍过)。
绑定函数非常简单,就是直接插入,需要注意的是,unordered_map非线程安全,没有线程类似java的concurrent hashmap(google了下intel tbb库有类似的数据结构),所以采用了boost thread库中的互斥变量:
代码如下 | 复制代码 |
void bind(const std::string &path, RequestHandleFunc func) { boost::unique_lock _mapping.insert(std::pair } |
注意,boost的锁分为unique_lock和shared_lock,这里是“写者”,所以需要独占锁。
处理方法前文已经贴了代码了,同样要注意的是,需要在搜索map的时候加锁。这里是“读者”,所以使用shared_lock即可:
代码如下 | 复制代码 |
boost::shared_lock |
为了方便绑定,写了个宏,直接绑定。
代码如下 | 复制代码 |
#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 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($(''+data.msg+'')); } } }); |
其中的xhr部分稍后会提到,这里还有两个需要特别注意的地方。首先是processData属性,必须设置为false,其次是contentType必须设置为false。其他后端不清楚,cgicc在处理文件上传(既multipart方式post的时候),必须从http头中读取Content-Disposition属性,只有在jquery里面设置了前面提到的两个属性,才能正确的按照标准协议进行封装,让cgicc读取到正确的内容分隔字符串。
5、锦上添花,增加进度条:
代码如下 | 复制代码 |
var progressTag = $("", { 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 fileIter->writeToStream(of); |
这里忽略了错误处理,通过获取表单元素,作为文件名,直接将文件通过标准库文件输出流ofstream写到磁盘上。
这样,就完成了从页面上选择文件,到后台保存的简单流程。
相关文章
- 江南百景图游宴廊怎么样 江南百景图游宴廊建筑介绍 12-26
- 江南百景图碎金泉怎么样 江南百景图碎金泉建筑介绍 12-26
- 炉石传说兑换码大全 12-26
- 重返未来1999趋光性研究夜幕之外怎么玩 趋光性夜幕之外活动介绍 12-26
- 光遇12.26大蜡烛在哪里 光遇12月26日大蜡烛位置攻略 12-26
- 江南百景图金谷园怎么样 江南百景图金谷园建筑介绍 12-26