網際網路剛興起時,很多專案都是用 C /Perl 語言寫的一大堆 CGI,一些老程式設計師可謂是償盡了程式設計的苦,因為那時國內的技術水平普遍比較低,如果你會 CGI 程式設計,就已經算是行業中人了,如果你對 CGI 程式設計比較熟練,則就可以稱得是“專家”了,後來技術不斷進步,各種國外的新技術都進入中國並不斷得到普及,CGI 就逐漸淪為一種落後的技術,後來的 PHP, JSP/Servlet, ASP 逐漸佔領了 WEB 程式設計的技術市場,這個時候如果你說再用 C 寫 CGI,別人會感覺是在和古人對話。
現在主流的 WEB 開發語言一個很大的優勢就是有各種相對成熟的基礎庫和框架,開發效率很高,而 CGI 則就遜色很多。當然,這些語言也得有執行效率相對較低的問題,畢竟它們都是指令碼語言或半編譯語言,需要虛擬機器解釋執行,象 facebook 的 WEB 前端基本都是用 PHP 寫的,他們為了解決執行效率問題,在一位華人的領導下開發了可以將 PHP 程式碼轉成 C++ 程式碼的工具(hiphop),從而使執行效率大大提高,這也從另一個側面反映出技術人員還是希望他們的程式能夠執行的更快些。
本文主要描述了 acl_cpp 庫中有關 WEB 程式設計的方法類,為了使大家容易上手,其中的介面設計及命名儘量模仿 JAVA HttpServlet 等相關的類(希望 Oracle 不會告我侵權,呵呵)。如果您會用C/C++程式設計,同時又有使用 Java Servlet 進行 WEB 程式設計的經驗,則該文您讀起來一點不會費力,當然如果您多年從事 WEB 開發,我想理解這些類的設計及用法也不應該有什麼難度。好了,下面就開始講如何使用 acl_cpp 庫中的 http/ 模組下的類進行 web 程式設計。
在 acl_cpp/src/http 模組下,有幾個類與 WEB 程式設計相關:HttpServlet,HttpServletRequest, HttpServletResponse, HttpSession, http_header, http_mime, http_client。如果您掌握了這幾個類的用法,則進行 WEB 程式設計就不會有什麼問題了,下面一一介紹這幾個類:
一、HttpServlet 類
建構函式及解構函式:
1 2 3 4 5 6 7 8 9 |
/** * 建構函式 */ HttpServlet(void); /** * 純虛解構函式,即該類必須由子類進行例項化 */ virtual ~HttpServlet(void) =0; |
在構建函式中,為了支援 HttpSession 資料的儲存,需要使用者給出 memcached 的伺服器地址(目前僅支援採用 memcached 來儲存 session 資料,將來應該會擴充套件至可以支援 redis 等),同時使用者還需要給出 session 的 cookie ID 識別符號以發給瀏覽器。
四個虛介面,需要子類實現以應對不同的瀏覽器的 HTTP 請求:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
/** * 當 HTTP 請求為 GET 方式時的虛擬函式 */ virtual bool doGet(HttpServletRequest&, HttpServletResponse&); /** * 當 HTTP 請求為 POST 方式時的虛擬函式 */ virtual bool doPost(HttpServletRequest&, HttpServletResponse&); /** * 當 HTTP 請求為 PUT 方式時的虛擬函式 */ virtual bool doPut(HttpServletRequest&, HttpServletResponse&); /** * 當 HTTP 請求為 CONNECT 方式時的虛擬函式 */ virtual bool doConnect(HttpServletRequest&, HttpServletResponse&); /** * 當 HTTP 請求為 PURGE 方式時的虛擬函式,該方法在清除 SQUID 的快取 * 時會用到 */ virtual bool doPurge(HttpServletRequest&, HttpServletResponse&); |
使用者實現的 HttpServlet 子類中可以實現以上幾個虛介面的一個或者幾個,以滿足不同的 HTTP 請求。
下面的函式為 HttpServlet 類開始執行的函式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
/** * HttpServlet 物件開始執行,接收 HTTP 請求,並回撥以下 doXXX 虛擬函式 * @param session {session&} 儲存 session 資料的物件 * @param stream {socket_stream*} 當在 acl_master 伺服器框架控制下 * 執行時,該引數必須非空;當在 apache 下以 CGI 方式執行時,該引數 * 設為 NULL;另外,該函式內部不會關閉流連線,應用應自行處理流物件 * 的關閉情況,這樣可以方便與 acl_master 架構結合 * @param body_parse {bool} 針對 POST 方法,該引數指定是否需要 * 讀取 HTTP 請求資料體並按 n/v 方式進行分析;當為 true 則內 * 部會讀取 HTTP 請求體資料,並進行分析,當使用者呼叫 getParameter * 時,不僅可以獲得 URL 中的引數,同時可以獲得 POST 資料體中 * 的引數;當該引數為 false 時則不讀取資料體 * @param body_limit {int} 針對 POST 方法,當資料體為文字引數 * 型別時,此引數限制資料體的長度;當資料體為資料流或 MIME * 格式或 body_read 為 false,此引數無效 * @return {bool} 返回處理結果 */ bool doRun(session& session, socket_stream* stream = NULL, bool body_parse = true, int body_limit = 102400); /** * HttpServlet 物件開始執行,接收 HTTP 請求,並回撥以下 doXXX 虛擬函式, * 呼叫本函式意味著採用 memcached 來儲存 session 資料 * @param memcached_addr {const char*} memcached 伺服器地址,格式:IP:PORT * @param stream {socket_stream*} 含義同上 * @param body_parse {bool} 含義同上 * @param body_limit {int} 含義同上 * @return {bool} 返回處理結果 */ bool doRun(const char* memcached_addr = "127.0.0.1:11211", socket_stream* stream = NULL, bool body_parse = true, int body_limit = 102400); |
從上面五個虛方法中,可以看到兩個重要的類:HttpServletRequest 和 HttpServletResponse。這兩個類分別表示 http 請求類及 http 響應類,這兩個類都是由 HttpServlet 類物件建立並釋放的,所以使用者不必建立和銷燬這兩個類物件例項。下面分別介紹這兩個類:
二、HttpServletRequest 類
該類主要是與瀏覽器的請求過程相關,您可以通過該類的方法獲得瀏覽器的請求資料。該類的方法比較多(基本上是參照了 java HttpServlet 的功能方法及名稱),所以下面僅介紹幾個主要的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
/** * 獲得 HTTP 請求中的引數值,該值已經被 URL 解碼且 * 轉換成本地要求的字符集;針對 GET 方法,則是獲得 * URL 中 ? 後面的引數值;針對 POST 方法,則可以獲得 * URL 中 ? 後面的引數值或請求體中的引數值 */ const char* getParameter(const char* name) const; /** * 獲得 HTTP 客戶端請求的某個 cookie 值 * @param name {const char*} cookie 名稱,必須非空 * @return {const char*} cookie 值,當返回 NULL 時表示 * cookie 值不存在 */ const char* getCookieValue(const char* name) const; /** * 獲得與該 HTTP 會話相關的 HttpSession 物件引用 * @return {HttpSession&} */ HttpSession& getSession(void); /** * 獲得與 HTTP 客戶端連線關聯的輸入流物件引用 * @return {istream&} */ istream& getInputStream(void) const; /** * 獲得 HTTP 請求資料的資料長度 * @return {acl_int64} 返回 -1 表示可能為 GET 方法, * 或 HTTP 請求頭中沒有 Content-Length 欄位 */ fdef WIN32 __int64 getContentLength(void) const; lse long long int getContentLength(void) const; ndif /** * 當 HTTP 請求頭中的 Content-Type 為 * multipart/form-data; boundary=xxx 格式時,說明為檔案上傳 * 資料型別,則可以通過此函式獲得 http_mime 物件 * @return {const http_mime*} 返回 NULL 則說明沒有 MIME 物件, * 返回的值使用者不能手工釋放,因為在 HttpServletRequest 的析 * 構中會自動釋放 */ http_mime* getHttpMime(void) const; /** * 獲得 HTTP 請求資料的型別 * @return {http_request_t},一般對 POST 方法中的上傳 * 檔案應用而言,需要呼叫該函式獲得是否是上傳資料型別 */ http_request_t getRequestType(void) const; |
以上方法一般都是我們在實際對 HttpServletRequest 類方法使用過程中用得較多的。如:
getParmeter: 用來獲得 http 請求引數
getCookieValue:獲得瀏覽器的 cookie 值
getSession:獲得該 HttpServlet 類物件的 session 會話
getInputStream:獲得 http 連線的輸入流
getContentLength:針對 HTTP POST 請求,此函式獲得 HTTP 請求資料體的長度
getRequestType:針對 HTTP POST 請求,此函式返回 HTTP 請求資料體的傳輸方式(普通的 name=value 方式,multipart 上傳檔案格式以及資料流格式)
三、HttpServletResponse 類
該類主要與將您寫的程式將處理資料結果返回給瀏覽器的過程相關,下面也僅介紹該類的一些常用的函式,如果您需要更多的功能,請引數 HttpServletResponse.hpp 標頭檔案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
/** * 設定 HTTP 響應資料體的 Content-Type 欄位值,可欄位值可以為: * text/html 或 text/html; charset=utf8 格式 * @param value {const char*} 欄位值 */ void setContentType(const char* value); /** * 設定 HTTP 響應資料體中字符集,當已經在 setContentType 設定 * 了字符集,則就不必再呼叫本函式設定字符集 * @param charset {const char*} 響應體資料的字符集 */ void setCharacterEncoding(const char* charset); /** * 設定 HTTP 響應頭中的狀態碼:1xx, 2xx, 3xx, 4xx, 5xx * @param status {int} HTTP 響應狀態碼, 如:200 */ void setStatus(int status); /** * 新增 cookie * @param name {const char*} cookie 名 * @param value {const char*} cookie 值 * @param domain {const char*} cookie 儲存域 * @param path {const char*} cookie 儲存路徑 * @param expires {time_t} cookie 過期時間間隔,噹噹前時間加 * 該值為 cookie 的過期時間截(秒) */ void addCookie(const char* name, const char* value, const char* domain = NULL, const char* path = NULL, time_t expires = 0); /** * 傳送 HTTP 響應頭,使用者應該傳送資料體前呼叫此函式將 HTTP * 響應頭髮送給客戶端 */ bool sendHeader(void); /** * 獲得 HTTP 響應物件的輸出流物件,使用者在呼叫 sendHeader 傳送 * 完 HTTP 響應頭後,通過該輸出流來傳送 HTTP 資料體 * @return {ostream&} */ ostream& getOutputStream(void) const; |
setCharacterEncoding:該方法設定 HTTP 響應頭的 HTTP 資料體的字符集,如果通過該函式設定了字符集,即使您在返回的 html 資料中重新設定了其它的字符集,瀏覽器也會優先使用 HTTP 響應頭中設定的字符集,所以使用者一定得注意這點;
setContentType:該方法用來設定 HTTP 響應頭中的 Content-Type 欄位,對於 xml 資料則設定 text/xml,對 html 資料則設定 text/html,當然您也可以設定 image/jpeg 等資料型別;當然,您也可以直接通過該方法在設定資料型別的同時指定資料的字符集,如可以直接寫:setContentType(“text/html; charset=utf8”),這個用法等同於:setContentType(“text/html”); setCharacterEncoding(“utf8”)
setStatus:設定 HTTP 響應頭的狀態碼(一般不用設定狀態碼,除非是您確實需要單獨設定);
addCookie:在 HTTP 響應頭中新增 cookie 內容;
sendHeader:傳送 HTTP 響應頭;
getOutputStream:該函式返回輸出流物件,您可以向輸出流中直接寫 HTTP 響應的資料體(關於 ostream 類的使用請引數標頭檔案:include/ostream.hpp)。
除了以上三個類外,還有一個類比較重要:HttpSession 類,該類主要實現與 session 會話相關的功能:
四、HttpSession 類
該類物件例項使用者也不必建立與釋放,在 HttpServet 類物件內容自動管理該類物件例項。主要用的方法有:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/** * 獲得客戶端在服務端儲存的對應 session 變數名,子類可以過載該方法 * @param name {const char*} session 名,非空 * @return {const char*} session 值,為空說明不存在或內部 * 查詢失敗 */ virtual const char* getAttribute(const char* name) const; /** * 設定服務端對應 session 名的 session 值,子類可以過載該方法 * @param name {const char*} session 名,非空 * @param name {const char*} session 值,非空 * @return {bool} 返回 false 說明設定失敗 */ virtual bool setAttribute(const char* name, const char* value); |
只所以將這兩個方法宣告為虛方法,是因為 HttpSession 的 session 資料儲存目前僅支援 memcached,您如果有精力的話可以實現一個子類用來支援其它的資料儲存方式。當然您也可以在您實現的子類中實現自己的產生唯一 session id 的方法,即實現如下虛方法:
1 2 3 4 5 6 7 8 |
protected: /** * 建立某個 session 會話的唯一 ID 號,子類可以過載該方法 * @param buf {char*} 儲存結果緩衝區 * @param len {size_t} buf 緩衝區大小,buf 緩衝區大小建議 * 64 位元組左右 */ virtual void createSid(char* buf, size_t len); |
好了,上面說了一大堆類及類函式,下面還是以一個具體的示例來說明這些類的用法:
五、示例
下面的例子是一個 CGI 例子,編譯後可執行程式可以直接放在 apache 的 cgi-bin/ 目錄,使用者可以用瀏覽器訪問。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
// http_servlet.cpp : 定義控制檯應用程式的入口點。 // #include "lib_acl.hpp" using namespace acl; ////////////////////////////////////////////////////////////////////////// class http_servlet : public HttpServlet { public: http_servlet(void) { } ~http_servlet(void) { } // 實現處理 HTTP GET 請求的功能函式 virtual bool doGet(HttpServletRequest& req, HttpServletResponse& res) { return doPost(req, res); } // 實現處理 HTTP POST 請求的功能函式 virtual bool doPost(HttpServletRequest& req, HttpServletResponse& res) { // 獲得某瀏覽器使用者的 session 的某個變數值,如果不存在則設定一個 const char* sid = req.getSession().getAttribute("sid"); if (sid == NULL || *sid == 0) req.getSession().setAttribute("sid", "xxxxxx"); // 再取一次該瀏覽器使用者的 session 的某個屬性值 sid = req.getSession().getAttribute("sid"); // 取得瀏覽器發來的兩個 cookie 值 const char* cookie1 = req.getCookieValue("name1"); const char* cookie2 = req.getCookieValue("name2"); // 開始建立 HTTP 響應頭 // 設定 cookie res.addCookie("name1", "value1"); // 設定具有作用域和過期時間的 cookie res.addCookie("name2", "value2", ".test.com", "/", 3600 * 24); // res.setStatus(400); // 可以設定返回的狀態碼 // 兩種方式都可以設定字符集 if (0) res.setContentType("text/xml; charset=gb2312"); else { // 先設定資料型別 res.setContentType("text/xml"); // 再設定資料字符集 res.setCharacterEncoding("gb2312"); } // 獲得瀏覽器請求的兩個引數值 const char* param1 = req.getParameter("name1"); const char* param2 = req.getParameter("name2"); // 建立 xml 格式的資料體 xml body; body.get_root().add_child("root", true) .add_child("sessions", true) .add_child("session", true) .add_attr("sid", sid ? sid : "null") .get_parent() .get_parent() .add_child("cookies", true) .add_child("cookie", true) .add_attr("name1", cookie1 ? cookie1 : "null") .get_parent() .add_child("cookie", true) .add_attr("name2", cookie2 ? cookie2 : "null") .get_parent() .get_parent() .add_child("params", true) .add_child("param", true) .add_attr("name1", param1 ? param1 : "null") .get_parent() .add_child("param", true) .add_attr("name2", param2 ? param2 : "null"); string buf; body.build_xml(buf); // 在http 響應頭中設定資料體長度 res.setContentLength(buf.length()); // 傳送 http 響應頭 if (res.sendHeader() == false) return false; // 傳送 http 響應體 if (res.write(buf) == false) return false; return true; } protected: private: }; ////////////////////////////////////////////////////////////////////////// int main(void) { #ifdef WIN32 acl::acl_cpp_init(); // win32 環境下需要初始化庫 #endif http_servlet servle; // cgi 開始執行 servlet.doRun("127.0.0.1:11211"); // 開始執行,並設定 memcached 的服務地址為:127.0.0.1:11211 // 執行完畢,程式退出 return 0; } |
參考:
acl網路通訊與伺服器框架下載地址:https://github.com/zhengshuxin/acl