用C++實現相似於JAVA HttpServlet 的編程接口

       互聯網剛興起時,不少項目都是用 C /Perl 語言寫的一大堆 CGI,一些老程序員可謂是償盡了編程的苦,由於那時國內的技術水平廣泛比較低,若是你會 CGI 編程,就已經算是行業中人了,若是你對 CGI 編程比較熟練,則就能夠稱得是「專家」了,後來技術不斷進步,各類國外的新技術都進入中國並不斷獲得普及,CGI 就逐漸淪爲一種落後的技術,後來的 PHP, JSP/Servlet, ASP 逐漸佔領了 WEB 編程的技術市場,這個時候若是你說再用 C 寫 CGI,別人會感受是在和古人對話。如今主流的 WEB 開發語言一個很大的優點就是有各類相對成熟的基礎庫和框架,開發效率很高,而 CGI 則就遜色不少。固然,這些語言也得有執行效率相對較低的問題,畢竟它們都是腳本語言或半編譯語言,須要虛擬機解釋執行,象 facebook 的 WEB 前端基本都是用 PHP 寫的,他們爲了解決執行效率問題,在一位華人的領導下開發了能夠將 PHP 代碼轉成 C++ 代碼的工具(hiphop),從而使執行效率大大提升,這也從另外一個側面反映出技術人員仍是但願他們的程序可以運行的更快些。html

      本文主要描述了 acl_cpp 庫中有關 WEB 編程的方法類,爲了使你們容易上手,其中的接口設計及命名儘可能模仿 JAVA HttpServlet 等相關的類。若是您會用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 編程就不會有什麼問題了,下面一一介紹這幾個類:java

      1、HttpServlet 類git

      構造函數及析構函數:程序員

                /**
		 * 構造函數
		 */
		HttpServlet(void);

		/**
		 * 純虛析構函數,即該類必須由子類進行實例化
		 */
		virtual ~HttpServlet(void) =0;

        在構建函數中,爲了支持 HttpSession 數據的存儲,須要用戶給出 memcached 的服務器地址(目前僅支持採用 memcached 來存儲 session 數據,未來應該會擴展至能夠支持 redis 等),同時用戶還須要給出 session 的 cookie ID 標識符以發給瀏覽器。github

       四個虛接口,須要子類實現以應對不一樣的瀏覽器的 HTTP 請求:web

                /**
		 * 當 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 請求。redis

       下面的函數爲 HttpServlet 類開始運行的函數:
apache

                /**
		 * 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 類對象建立並釋放的,因此用戶沒必要建立和銷燬這兩個類對象實例。下面分別介紹這兩個類:編程

       2、HttpServletRequest 類

      該類主要是與瀏覽器的請求過程相關,您能夠經過該類的方法得到瀏覽器的請求數據。該類的方法比較多(基本上是參照了 java HttpServlet 的功能方法及名稱),因此下面僅介紹幾個主要的方法:

                /**
		 * 得到 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 字段
		 */
#ifdef WIN32
		__int64 getContentLength(void) const;
#else
		long long int getContentLength(void) const;
#endif

		/**
		 * 當 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 上傳文件格式以及數據流格式)

      3、HttpServletResponse 類

      該類主要與將您寫的程序將處理數據結果返回給瀏覽器的過程相關,下面也僅介紹該類的一些經常使用的函數,若是您須要更多的功能,請參數 HttpServletResponse.hpp 頭文件。

                /**
		 * 設置 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 會話相關的功能:

      4、HttpSession 類

      該類對象實例用戶也沒必要建立與釋放,在 HttpServet 類對象內容自動管理該類對象實例。主要用的方法有:

                /**
		 * 得到客戶端在服務端存儲的對應 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 的方法,即實現以下虛方法:

 

protected:
		/**
		 * 建立某個 session 會話的惟一 ID 號,子類能夠重載該方法
		 * @param buf {char*} 存儲結果緩衝區
		 * @param len {size_t} buf 緩衝區大小,buf 緩衝區大小建議
		 *  64 字節左右
		 */
		virtual void createSid(char* buf, size_t len);

       好了,上面說了一大堆類及類函數,下面仍是以一個具體的示例來講明這些類的用法:

      5、示例

      下面的例子是一個 CGI 例子,編譯後可執行程序能夠直接放在 apache 的 cgi-bin/ 目錄,用戶能夠用瀏覽器訪問。

// 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;
}

       常用 Java HttpServlet 等類進行 web 編程的用戶對上面的代碼必定不會感到陌生,但它的的確確是一個 C++  程序,能夠放在 Apache 及支持 CGI 的 Webserver 下運行(也可作爲獨立的服務器運行)。固然,你們應該都清楚 CGI 在運行時因進程切換而致使了效率較爲低下,在另外一篇文章《使用 acl_cpp 的 HttpServlet 類及服務器框架編寫WEB服務器程序 》中展現了用上面的 http_servlet 類並結合 acl_cpp 的服務器模型實現的一個WEB服務器的例子,效率比 CGI 要高的多(效率也應比 FCGI高,由於其少了 Webserver 層的過濾);文章《acl_cpp web 編程之文件上傳 》中舉例講述了在服務端如何使用 acl_cpp 庫處理瀏覽器上傳文件的功能。

      上面的例子中用到了另一個類:xml 類,有關該類的使用說明請參考博客:《acl_cpp 編程之 xml 流式解析與建立 》;若是您想了解 HTTP 協議,請參考文章:《HTTP 協議簡介

       該示例所在目錄:acl_cpp/samples/cgi

     下載:http://sourceforge.net/projects/acl/     svn:svn checkout svn://svn.code.sf.net/p/acl/code/trunk acl-code     github:https://github.com/zhengshuxin/acl     qq 羣:242722074

相關文章
相關標籤/搜索