一、概述 互联网刚兴起时,很多项目都是用 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 virtual bool doGet (HttpServletRequest&, HttpServletResponse&) ;virtual bool doPost (HttpServletRequest&, HttpServletResponse&) ;virtual bool doPut (HttpServletRequest&, HttpServletResponse&) ;virtual bool doConnect (HttpServletRequest&, HttpServletResponse&) ;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 33 bool doRun (session& session, socket_stream* stream = NULL , bool body_parse = true , int body_limit = 102400 ) ;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 const char * getParameter (const char * name) const ; const char * getCookieValue (const char * name) const ; HttpSession& getSession (void ) ; istream& getInputStream (void ) const ; #ifdef WIN32 __int64 getContentLength (void ) const ;#else long long int getContentLength (void ) const ;#endif http_mime* getHttpMime (void ) const ; http_request_t getRequestType (void ) const ;
以上方法一般都是我们在实际对 HttpServletRequest 类方法使用过程中用得较多的。如:
1 2 3 4 5 6 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 void setContentType (const char * value) ;void setCharacterEncoding (const char * charset) ;void setStatus (int status) ;void addCookie (const char * name, const char * value, const char * domain = NULL , const char * path = NULL , time_t expires = 0 ) ;bool sendHeader (void ) ;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 virtual const char * getAttribute (const char * name) const ;virtual bool setAttribute (const char * name, const char * value) ;
只所以将这两个方法声明为虚方法,是因为 HttpSession 的 session 数据存储目前仅支持 memcached,您如果有精力的话可以实现一个子类用来支持其它的数据存储方式。当然您也可以在您实现的子类中实现自己的产生唯一 session id 的方法,即实现如下虚方法:
1 2 3 4 5 6 7 8 protected : 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 #include "lib_acl.hpp" using namespace acl;class http_servlet : public HttpServlet {public : http_servlet (void ) { } ~http_servlet (void ) { } bool doGet (HttpServletRequest& req, HttpServletResponse& res) { return doPost (req, res); } bool doPost (HttpServletRequest& req, HttpServletResponse& res) { const char * sid = req.getSession ().getAttribute ("sid" ); if (sid == NULL || *sid == 0 ) req.getSession ().setAttribute ("sid" , "xxxxxx" ); sid = req.getSession ().getAttribute ("sid" ); const char * cookie1 = req.getCookieValue ("name1" ); const char * cookie2 = req.getCookieValue ("name2" ); res.addCookie ("name1" , "value1" ); res.addCookie ("name2" , "value2" , ".test.com" , "/" , 3600 * 24 ); 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 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); res.setContentLength (buf.length ()); if (res.sendHeader () == false ) return false ; if (res.write (buf) == false ) return false ; return true ; } };int main (void ) {#ifdef WIN32 acl::acl_cpp_init (); #endif http_servlet servle; servlet.doRun ("127.0.0.1:11211" ); return 0 ; }
经常使用 Java HttpServlet 等类进行 web 编程的用户对上面的代码一定不会感到陌生,但它的的确确是一个 CGI 程序,可以放在 Apache 及支持 CGI 的 Webserver 下运行。当然,大家应该都清楚 CGI 在运行时因进程切换而导致了效率较为低下,在另一篇文章《使用 acl_cpp 的 HttpServlet 类及服务器框架编写WEB服务器程序 》中展示了用上面的 http_servlet 类并结合 acl_cpp 的服务器模型实现的一个WEB服务器的例子,效率比 CGI 要高的多(效率也应比 FCGI高,因为其少了 Webserver 层的过滤);文章《acl_cpp web 编程之文件上传 》中举例讲述了在服务端如何使用 acl_cpp 库处理浏览器上传文件的功能。
acl 下载 github: https://github.com/acl-dev/acl gitee: https://github.com/acl-dev/acl