cgi 網站開發學習執行個體

來源:互聯網
上載者:User

頁面模板

可能是因為公司裡寫代碼用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寫到磁碟上。

這樣,就完成了從頁面上選擇檔案,到後台儲存的簡單流程。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.