開源C++版本CGI庫CGICC入門
原發布在ChinaUnix,但未被自動搬遷過來:http://blog.chinaunix.net/uid-20682147-id-4895772.html
PDF版本:https://download.csdn.net/download/aquester/10827278
目錄
7.2. HTMLElement::render()函式 13
1. 簡介
CGICC是一個C++語言實現的開源CGI庫,採用LGPL授權協議,使用較為簡單。
CGICC官網:http://www.gnu.org/software/cgicc/,截止2015/3/14,CGICC最新穩定版本為3.2.16,下載地址是:http://ftp.gnu.org/gnu/cgicc/cgicc-3.2.16.tar.gz,最新更新時間為2014/12/7(令人驚訝和欣慰的是作為古老的CGI,CGICC還在不斷的更新)。
2. CGICC組成
CGICC由兩大部分組成:
1) CGI輸入處理子模組
2) HTML輸出子模組
本文暫只介紹CGI輸入處理子模組,對於HTML輸出,推薦Google開源的ctemplate(https://github.com/OlafvdSpek/ctemplate)。
3. CGI輸入處理子模組類結構
3.1. Cgicc
CGICC的一類,通常直接在CGI的入口函式,如main函式中定義一個CGICC物件,然後即可使用CGICC提供的各種能力。
3.2. CgiEnvironment
提供get系列方法取各環境變數的值。
3.3. HTTPCookie
提花get系列方法取各Cookie的值,並支援set新增或修改Cookie值。
3.4. CgiInput
CgiEnvironment內部類,僅供CgiEnvironment使用。
3.5. FormFile
提供訪問HTML的Form中的被上傳檔案資訊和資料介面。
3.6. FormEntry
提供訪問HTML的Form中的非被上傳檔案類的資訊和資料介面。取URL引數值示例:
// http://127.0.0.1/?param_name=param_value cgicc::form_iterator iter = cgi.getElement("param_name"); if (iter != cgi.getElements().end()) { std::string param_value = iter->getValue(); }
// 也可以這樣做: std::string param_value = cgi("param_name");
// 除此之外,FormEntry還提供了直接取指定資料型別的引數值,如:getIntegerValue、getDoubleValue |
4. CGI輸入處理子模組初始化流程
初始化流程是由Cgicc建構函式觸發的,一般可在CGI的main函式中定義一個Cgicc物件:
5. 編譯和安裝CGICC
詳細編譯步驟如下:
1) 將CGICC原始碼包(本文下載的是cgicc-3.2.16.tar.gz)上傳到Linux某目錄(本文將CGICC原始碼包cgicc-3.2.16.tar.gz上傳到/tmp目錄);
2) 登入Linux,並進入目錄/tmp;
3) 解壓CGICC原始碼包cgicc-3.2.16.tar.gz:tar xzf cgicc-3.2.16.tar.gz;
4) 解壓後,會在/tmp下產生一個子目錄cgicc-3.2.16,進入到這個子目錄;
5) 然後執行configure命令(本文指定的安裝目錄為/usr/local/cgicc-3.2.16,可以根據需要設定為其它目錄),以生成Makefile編譯檔案,如果要在共享庫中使用CGICC,請使用下列編譯命令:
./configure --prefix=/usr/local/cgicc-3.2.16 CXXFLAGS=-fPIC LDFLAGS=-fPIC |
否則,可按如下命令編譯:
./configure --prefix=/usr/local/cgicc-3.2.16 |
在一些環境上,如果不帶-fPIC編譯靜態庫,使用靜態庫時,就會報連結錯誤。
6) 執行make編譯:make
7) 安裝CGICC庫:make install
8) 為/usr/local/cgicc-3.2.16建立不帶版本號的軟連結:
ln -s /usr/local/cgicc-3.2.16 /usr/local/cgicc |
至此,CGICC庫就安裝好了!
6. CGICC使用示例
6.1. 頁面效果
6.2. HTML檔案
頁面效果對應的HTML檔案內容如下(HTML中的id一般是給前端如js使用的,而name通常是給服務端如CGI使用的):
<html> <head> <title>upload</title> </head>
<body> <p>upload: <div> <form action="/cgi-bin/upload.cgi" method="post" name="formname" enctype="multipart/form-data"> <input type="text" id="id1" name="name1" /> <input type="text" id="id2" name="name2" />
<p> <input type="file" id="fileid" name="filename" /> <input type="submit" value="upload" id="upid" name="upname" /> </form> </div> </body> </html> |
注意,上傳檔案時,Form的enctype屬性值必須被設定為multipart/form-data。
6.3. test.txt檔案
test.txt是一個被上傳的檔案,內容只有一行:0123456789。
6.4. CGI檔案
// 如果是Exe形式的CGI,則使用如下語句編譯: // g++ -g -o upload.cgi upload.cpp -I/usr/local/cgicc/include /usr/local/cgicc/lib/libcgicc.a // 如果是共享庫(Windows平臺叫動態庫)形式的CGI,則使用如下語句編譯: // g++ -g -o upload.cgi upload.cpp -shared -fPIC -I/usr/local/cgicc/include /usr/local/cgicc/lib/libcgicc.a #include <stdio.h> #include <sstream> #include "cgicc/Cgicc.h" #include "cgicc/HTMLClasses.h" #include "cgicc/HTTPHTMLHeader.h"
int main(int argc, char **argv) { try { cgicc::Cgicc cgi;
// Output the HTTP headers for an HTML document, // and the HTML 4.0 DTD info std::cout << cgicc::HTTPHTMLHeader() << cgicc::HTMLDoctype(cgicc::HTMLDoctype::eStrict) << std::endl; std::cout << cgicc::html().set("lang", "en").set("dir", "ltr") << std::endl;
// Set up the page's header and title. std::cout << cgicc::head() << std::endl; std::cout << cgicc::title() << "GNU cgicc v" << cgi.getVersion() << cgicc::title() << std::endl; std::cout << cgicc::head() << std::endl;
// Start the HTML body std::cout << cgicc::body() << std::endl;
// Print out a message std::cout << cgicc::h1("Hello, world from GNU cgicc") << std::endl; const cgicc::CgiEnvironment& env = cgi.getEnvironment();
std::cout << "<p>accept: " << env. getAccept() << std::endl; std::cout << "<p>user agent: " << env.getUserAgent() << std::endl;
std::cout << "<p>cookie: " << std::endl; const std::vector<cgicc::HTTPCookie>& cookies = env.getCookieList(); for (std::vector<cgicc::HTTPCookie>::size_type i=0; i<cookies.size(); ++i) { const cgicc::HTTPCookie& cookie = cookies[i]; std::cout << "<br> cookie[" << cookie.getName() << "] = " << cookie.getValue() << std::endl; }
std::cout << "<p>query string: " << env.getQueryString() << std::endl; std::cout << "<p>remote: " << env.getRemoteAddr() << ":" << env.getServerPort() << std::endl;
std::cout << "<p>form: " << std::endl; const std::vector<cgicc::FormEntry>& form_entries = cgi.getElements(); for (std::vector<cgicc::FormEntry>::size_type i=0; i<form_entries.size(); ++i) { const cgicc::FormEntry& form_entry = form_entries[i]; std::cout << "<br> form[" << form_entry.getName() << "] = " << form_entry.getValue() << std::endl; }
// // 取被上傳的檔案資訊 //
// 使用getFile取得指定的被上傳檔案資訊 cgicc::const_file_iterator file_iter = cgi.getFile("file");
// 使用getFiles可以取得所有被上傳檔案資訊 if (file_iter == cgi.getFiles().end()) { std::cout << "<p>file: " << cgi.getFiles().size() << std::endl; } else { const cgicc::FormFile& file = *file_iter; std::cout << "<p>file: " << std::endl; std::cout << "<br> name: " << file.getName() << std::endl; std::cout << "<br> filename: " << file.getFilename() << std::endl; std::cout << "<br> type: " << file.getDataType() << std::endl; std::cout << "<br> size: " << file.getDataLength() << std::endl; std::cout << "<br> content: " << file.getData() << std::endl; }
// Close the document std::cout << cgicc::body() << cgicc::html(); } catch(const std::exception& e) { // handle error condition }
return 0; } |
6.5. 執行效果
點選HTML頁面的upload按鈕後,頁面變成如下:
Hello, world from GNU cgicc
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
user agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.36 (KHTML, like Gecko) Chrome/39.0.2172.95 Safari/537.36
cookie: cookie[pgv] = 445364884 cookie[ku] = f0ab9e006c7f4d5a4e9b394fc44fafc8afd6df6d373f9ff5f2946047974daf0ef9b00c6a1d7c341b cookie[uid] = zhangshan cookie[post-shareto-guide] = 1 cookie[si] = s4001534976 cookie[info] = ssid=s9175124444 cookie[pvid] = 6963827212 cookie[code_user_name] = A5C9579BE8B7C0E0
query string:
remote: 120.16.82.66:80
form: form[name1] = abc form[name2] = xyz form[upname] = upload
file: name: filename filename: test.txt type: text/plain size: 10 content: 0123456789 |
7. HTML輸出子模組類圖
n HTMLBooleanElement
注意sState是類HTMLBooleanElement的靜態資料成員,sState的資料型別為bool。
n 標籤
對於<html></html>,前者<html>叫開始標籤,後者</html>叫關閉標籤。
n EElementType
列舉型別,定義了兩個列舉值:eAtomic和eBoolean,eAtomic對應的實現類為HTMLAtomicElement,eBoolean對應的實現為HTMLBooleanElement。類似於strong類的為eBoolean型別,而類似於hr、br之類的為eAtomic型別。對於eAtomic型別的HTML標籤,它沒有對應的關閉標籤(也叫結束標籤),觀察以下兩組的差別:
<br /> <strong>This text is strong</strong>
<hr /> <p>This is some text</p> |
可以看到br和hr均是eAtomic型別的標籤,而strong和p均是eBoolean型別的標籤。
n fEmbedded
對於eBoolean型別的標籤,在HTMLElement::render()函式的實現中,會發現還區分是否有fEmbedded,什麼是有fEmbedded的eBoolean型別標籤?
下面這行為無fEmbedded的eBoolean標籤:
下段這段也是含fEmbedded的eBoolean的標籤,“<title>CGICC</title>”為標籤head的fEmbedded內容: <head> <title>CGICC</title> </head>
|
n fDataSpecified
也是針對eBoolean型別標籤的,同樣在HTMLElement::render()函式的實現中,會發現到差別(對應於對HTMLElement::dataSpecified()的呼叫)。下列的a即為fDataSpecified型別的eBoolean標籤,其中“一見的技術部落格”為標籤a的Data:
<a href="http://aquester.cublog.cn">一見的技術部落格</a> |
n 程式碼中的html()究竟是啥?
閱讀示例程式碼,可能會有這樣一個疑問:html()是如何被呼叫的?發現沒法直接找到名叫html的函式。
cout << html().set("lang", "en").set("dir", "ltr") << endl; cout << head() << endl; cout << title() << "GNU cgicc v" << cgi.getVersion() << title() << endl; cout << head() << endl; cout << body() << endl; cout << h1("Hello, world from GNU cgicc") << endl; cout << body() << html(); |
上述呼叫中的html()、head()、title()、h1()和body()等,實際都是呼叫類HTMLBooleanElement的建構函式,演變成呼叫HTMLElement::render(std::ostream& out)。
流函式的定義為:
std::ostream& cgicc::operator <<(std::ostream& out, const cgicc::MStreamable& obj) { obj.render(out); return out; } |
從流函式的定義不難看出,實際上呼叫的是render()。
在HTMLClasses.h檔案中,定義了html、body等類(位於cgicc名字空間內),但是有些隱晦,直接看不出來:
翻譯一下,以便容易看懂這個過程,先看相關的巨集定義:
1) 巨集BOOLEAN_ELEMENT
#define BOOLEAN_ELEMENT(name, tag) \ TAG(name, tag); typedef HTMLBooleanElement<name##Tag> name |
2) 巨集TAG
// 注意區分下面的tag和Tag #define TAG(name, tag) \ class name##Tag \ { public: inline static const char* getName() { return tag; // 注意不是Tag,而是tag } } // 注意,這裡並沒有加分號 |
現在來看HTMLClasses.h檔案中定義的BOOLEAN_ELEMENT(html, "html");,巨集展開後,變成:
class htmlTag { public: inline static const char* getName() { return "html"; } };
typedef HTMLBooleanElement<htmlTag> html; // html是不是就是一個類了? |
html()怎麼來的清楚了,還有一個疑問:對於:cout << html(),怎麼知道是輸出<html>還是</html>的?這個邏輯是在函式HTMLElement::render(std::ostream& out)中完成的。
7.1. HTTPContentHeader
HTTPContentHeader負責輸出HTTP頭中的“Content-Type:”,看它的渲染函式reader()實現:
void cgicc::HTTPContentHeader::render(std::ostream& out) const { out << "Content-Type: " << getData() << std::endl;
std::vector<HTTPCookie>::const_iterator iter; for (iter = getCookies().begin(); iter != getCookies().end(); ++iter) { out << *iter << std::endl; }
out << std::endl; } |
其中,子類HTTPHTMLHeader的getData()返回“text/html”,子類HTTPPlainHeader的getData()返回“text/plain”,子類HTTPXHTMLHeader的getData()返回“application/xhtml+xml”。
7.2. HTMLElement::render()函式
void cgicc::HTMLElement::render(std::ostream& out) const { if (eBoolean == getType() && false == dataSpecified()) { if (0 == fEmbedded) /* no embedded elements */ { // 切換:用來控制是輸入開始標籤,還是關閉標籤 // HTMLBooleanElement::sState為類靜態資料成員, // swapState()的作用就是用來切換它的值。 swapState();
/* getState() == true ===> element is active */ if (true == getState()) { // 輸出開始標籤, out << '<' << getName();
// 開始標籤是可能包含屬性的, // 如:<a href="http://aquester.cublog.cn">, // 這裡的href即為標籤<a>的屬性 if (0 != fAttributes) { out << ' '; // 屬性間使用一個空格分開 fAttributes->render(out); }
out << '>'; } else { // 輸出關閉標籤,如:</head> out << "</" << getName() << '>'; } } else /* embedded elements present */ { // 被嵌入的(embedded)的內容總是整體一次性輸出, // 而不是區分其狀態值HTMLBooleanElement::sState out << '<' << getName();
/* render attributes, if present */ if (0 != fAttributes) { out << ' '; fAttributes->render(out); }
out << '>'; fEmbedded->render(out);
// 輸出關閉標籤,如:</head> out << "</" << getName() << '>'; } } else /* For non-boolean elements */ { if (eAtomic == getType()) { out << '<' << getName(); if (0 != fAttributes) { out << ' '; fAttributes->render(out); }
// eAtomic型別的標籤 out << " />"; } else { out << '<' << getName(); if (0 != fAttributes) { out << ' '; fAttributes->render(out); } out << '>';
if (0 != fEmbedded) { fEmbedded->render(out); } else { // 輸出資料, // 如<a href="http://www.gnu.org/software/cgicc">CGICC</a> // 中的CGICC out << getData(); }
// 輸出關閉標籤,如:</head> out << "</" << getName() << '>'; } } } |
8. 問題?
1) 問題1:怎麼取得不在CgiEnvironment支援範圍內的環境變數值?
答:可直接呼叫C庫函式getenv()取值。