開源C++版本CGI庫CGICC入門

原發布在ChinaUnix,但未自動搬遷過來:http://blog.chinaunix.net/uid-20682147-id-4895772.htmlhtml

PDF版本:https://files-cdn.cnblogs.com/files/aquester/%E5%BC%80%E6%BA%90C%E5%8A%A0%E5%8A%A0%E7%89%88%E6%9C%ACCGI%E5%BA%93CGICC%E5%85%A5%E9%97%A8.pdf前端

目錄

目錄 1git

1. 簡介 1github

2. CGICC組成 1web

3. CGI輸入處理子模塊類結構 2cookie

3.1. Cgicc 2app

3.2. CgiEnvironment 2ide

3.3. HTTPCookie 2函數

3.4. CgiInput 3post

3.5. FormFile 3

3.6. FormEntry 3

4. CGI輸入處理子模塊初始化流程 3

5. 編譯和安裝CGICC 4

6. CGICC使用示例 5

6.1. 頁面效果 5

6.2. HTML文件 5

6.3. test.txt文件 6

6.4. CGI文件 6

6.5. 運行效果 8

7. HTML輸出子模塊類圖 10

7.1. HTTPContentHeader 13

7.2. HTMLElement::render()函數 13

8. 問題? 16

 

1. 簡介

CGICC是一個C++語言實現的開源CGI庫,採用LGPL受權協議,使用較爲簡單。

CGICC官網:http://www.gnu.org/software/cgicc/,截止2015/3/14CGICC最新穩定版本爲3.2.16,下載地址是:http://ftp.gnu.org/gnu/cgicc/cgicc-3.2.16.tar.gz,最新更新時間爲2014/12/7(使人驚訝和欣慰的是做爲古老的CGICGICC還在不斷的更新)。

2. CGICC組成

CGICC由兩大部分組成:

1) CGI輸入處理子模塊

2) HTML輸出子模塊

 

本文暫只介紹CGI輸入處理子模塊,對於HTML輸出,推薦Google開源的ctemplatehttps://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

提供訪問HTMLForm中的被上傳文件信息和數據接口。

3.6. FormEntry

提供訪問HTMLForm中的非被上傳文件類的信息和數據接口。取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構造函數觸發的,通常可在CGImain函數中定義一個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.gztar 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>

 

注意,上傳文件時,Formenctype屬性值必須被設定爲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

枚舉類型,定義了兩個枚舉值:eAtomiceBoolean,eAtomic對應的實現類爲HTMLAtomicElement,eBoolean對應的實現爲HTMLBooleanElement。相似於strong類的爲eBoolean類型,而相似於hrbr之類的爲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,什麼是有fEmbeddedeBoolean類型標籤?

下面這行爲無fEmbedded的eBoolean標籤:

 

 

下段這段也是含fEmbedded的eBoolean的標籤,「<title>CGICC</title>」爲標籤head的fEmbedded內容:

<head>

    <title>CGICC</title>

</head>

 

 

n fDataSpecified

也是針對eBoolean類型標籤的,一樣在HTMLElement::render()函數的實現中,會發現到差異(對應於對HTMLElement::dataSpecified()的調用)。下列的a即爲fDataSpecified類型的eBoolean標籤,其中「一見的技術博客」爲標籤aData

<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文件中,定義了htmlbody等類(位於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;

}

 

其中,子類HTTPHTMLHeadergetData()返回「text/html」,子類HTTPPlainHeadergetData()返回「text/plain」,子類HTTPXHTMLHeadergetData()返回「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()取值。

相關文章
相關標籤/搜索