C++ CGICC Web 编程
C++ 也是可以用来写 Web 应用程序的,在众所周知的大厂 腾讯,据说还有很多 Web 应用程序是用 C++ 写的
上一章节我们使用 std:cout
配合 Python CGI 容器简单的写了一些 CGI 脚本
如果我们还继续写下去,也不是不可以,但是,要解析参数啦,要处理文件和 Cookie 信息啦,就非常复杂了
还好 GNU
提供了一个开源的 CGI 库 cgicc 可以用来简化开发
你可以点击 ftp://ftp.gnu.org/gnu/cgicc/ 下载最新的版本,截止今天,最新的版本为
下载解压后按照下面的命令来安装这个库
$ tar xzf cgicc-3.2.19.tar.gz $ cd cgicc-3.2.19 $ ./configure --prefix=/usr/local $ make $ sudo make install
经过漫长的编译等待,终于好了
在练习的过程中,如果有哪些地方不懂,可以查阅 C++ CGI Lib Documentation
其实它有一篇入门文章挺好的 A Tutorial Example
使用 GET 方法传递信息
GET 方法发送已编码的用户信息追加到页面请求中
页面和已编码信息通过 ?
字符分隔开
http://www.twle.cn/cgi-bin/cpp.cgi?name=yufei&base=pek
GET 方法是默认的从浏览器向 Web 服务器传信息的方法,它会在浏览器的地址栏中生成一串很长的字符串
我们日常浏览网页点击链接使用的方法就是 GET
比如我们随便点击 淘宝 中的某个宝贝,就能看到地址栏里长长的字符串
https://detail.tmall.com/item.htm?spm=a230r.1.14.6.231e485aq3hNYE&id=2224961843&cm_id=140105335569ed55e27b&abbucket=17
问号(?) 后面的那些信息就是要发送的信息
因为放在 URL 谁都可以看到,所以要向服务器传密码或其它一些敏感信息时,不要使用 GET 方法
而且 GET
方法有大小限制,在一个请求字符串中最多可以传 1024 个字符
当使用 GET
方法时, CGI 程序中可使用 QUERY_STRING
环境变量来访问
现在我们开发一个 demo 把 URl 上传递的信息都打印出来
我们把淘宝宝贝的链接换成我们自己的,比如下面这样
http://localhost:8080/cgi-bin/item.cgi?spm=a230r.1.14.6.231e485aq3hNYE&id=2224961843&cm_id=140105335569ed55e27b&abbucket=17
那么解析出来就会得到
spm=a230r.1.14.6.231e485aq3hNYE id=2224961843 cm_id=140105335569ed55e27b abbucket=17
在 cgi-bin
目录下创建一个文件 item.cpp
并复制以下内容
/** * file: index.cpp * author: 简单教程(www.twle.cn) * * Copyright © 2015-2065 www.twle.cn. All rights reserved. */ #include <iostream> #include <cgicc/CgiDefs.h> #include <cgicc/Cgicc.h> #include <cgicc/HTTPHTMLHeader.h> #include <cgicc/HTMLClasses.h> using namespace cgicc; int main () { Cgicc formData; std::cout << "Content-type:text/html;charset=utf-8\r\n\r\n"; std::cout << "<!DOCTYPE html>\n"; std::cout << "<title>使用 GET 传递参数</title>\n"; form_iterator fi = formData.getElement("spm"); if( !fi->isEmpty() && fi != (*formData).end()) { std::cout << "spm:" << **fi << std::endl; } std::cout << "<br/>\n"; fi = formData.getElement("id"); if( !fi->isEmpty() && fi != (*formData).end()) { std::cout << "id:" << **fi << std::endl; } std::cout << "<br/>\n"; fi = formData.getElement("cm_id"); if( !fi->isEmpty() && fi != (*formData).end()) { std::cout << "cm_id:" << **fi << std::endl; } std::cout << "<br/>\n"; fi = formData.getElement("abbucket"); if( !fi->isEmpty() && fi != (*formData).end()) { std::cout << "abbucket:" << **fi << std::endl; } return 0; }
然后使用下面的命令编译我们的 item.cpp
g++ -o item.cgi item.cpp -lcgicc
可以看到显示如下内容
是不是跟淘宝的一样,哈哈
GET 表单
当然了,要是每次都要输入拷贝这么长长的网址到浏览器肯定不太现实,所以要不我们懒到底,做一个 HTML 网页来提交我们的数据
创建一个 item.html
的文件和 cgi-bin
目录放在一起 (同一级),然后输入以下内容
<!DOCTYPE html> <meta charset="utf-8" /> <title>GET 表单 - C++ 基础教程 - 简单教程(www.twle.cn)</title> <p>GET 表单 - C++ 基础教程 - 简单教程(www.twle.cn)</p> <form action="/cgi-bin/item.cgi" method="get"> <p><label>spm: </label><input size="40" type="text" name="spm" value="a230r.1.14.6.231e485aq3hNYE" /></p> <p><label>id: </label><input size="40" type="text" name="id" value="2224961843" /></p> <p><label>cm_id: </label><input size="40" type="text" name="cm_id" value="140105335569ed55e27b"/></p> <p><label>abbucket: </label><input size="40" type="text" name="abbucket" value="17" /></p> <p><input type="submit" value="提交" /></p> </form>
然后点击 http://localhost:8080/item_get.html
就可以打开我们的 GET 表单了
点击提交,是不是到了我们上面做的那个页面
使用 POST 方法传递信息
一个更可靠的向 CGI 程序传递信息的方法是 POST 方法
这种方法打包信息的方式与 GET 方法相同,不同的是,它不是把信息以文本字符串形式放在 URL 中的 ? 之后进行传递,而是把它以单独的消息形式进行传递
POST 方法是以标准输入的形式传给 CGI 脚本的
我们可以复用 item.cgi
程序来处理 POST 方法
然后我们也可以拷贝复用我们的 item_get.html
到 item_post.html
然后把 <form>
中的 method="get"
改成 method="post"
即可
<!DOCTYPE html> <meta charset="utf-8" /> <title>POST 表单 - C++ 基础教程 - 简单教程(www.twle.cn)</title> <p>POST 表单 - C++ 基础教程 - 简单教程(www.twle.cn)</p> <form action="/cgi-bin/item.cgi" method="post"> <p><label>spm: </label><input size="40" type="text" name="spm" value="a230r.1.14.6.231e485aq3hNYE" /></p> <p><label>id: </label><input size="40" type="text" name="id" value="2224961843" /></p> <p><label>cm_id: </label><input size="40" type="text" name="cm_id" value="140105335569ed55e27b"/></p> <p><label>abbucket: </label><input size="40" type="text" name="abbucket" value="17" /></p> <p><input type="submit" value="提交" /></p> </form>
然后点击 http://localhost:8080/item_post.html
就可以打开我们的 POST 表单了
点击提交,我们可以发现地址栏中那长长的一串没有了,但数据还是那个数据
向 CGI 程序传递复选框数据
如果需要让用户从一组数据中选择多项,可以使用复选框
我们先创建一个表单 checkbox.html
带有四个复选框
<!DOCTYPE html> <meta charset="utf-8" /> <title>POST 表单 - C++ 基础教程 - 简单教程(www.twle.cn)</title> <h3>POST 表单 - C++ 基础教程 - 简单教程(www.twle.cn)</h3> <form action="/cgi-bin/checkbox.cgi" method="post"> <p>请选择曾经用过的语言</p> <p> <label><input type="checkbox" name="python" value="on" />Python</label> <label><input type="checkbox" name="c" value="on" />C</label> <label><input type="checkbox" name="java" value="on"/>Java</label> <label><input type="checkbox" name="php" value="on" />PHP</label> </p> <p><input type="submit" value="提交" /></p> </form>
然后再开发一个 checkbox.cgi
来接收复选框里的数据
在 cgi-bin
目录下新建一个文件 checkbox.cpp
然后复制以下内容
/** * file: index.cpp * author: 简单教程(www.twle.cn) * * Copyright © 2015-2065 www.twle.cn. All rights reserved. */ #include <iostream> #include <cgicc/CgiDefs.h> #include <cgicc/Cgicc.h> #include <cgicc/HTTPHTMLHeader.h> #include <cgicc/HTMLClasses.h> using namespace cgicc; int main () { Cgicc formData; bool maths_flag, physics_flag; std::cout << "Content-type:text/html;charset=utf-8\r\n\r\n"; std::cout << "<!DOCTYPE html>\n"; std::cout << "<title>CGI 程序接收复选框数据</title>\n"; std::cout << "你曾经使用过的语言有: "; maths_flag = formData.queryCheckbox("python"); if( maths_flag ) { std::cout << "Python "; } maths_flag = formData.queryCheckbox("c"); if( maths_flag ) { std::cout << "C "; } maths_flag = formData.queryCheckbox("java"); if( maths_flag ) { std::cout << "Java "; } maths_flag = formData.queryCheckbox("php"); if( maths_flag ) { std::cout << "PHP"; } return 0; }
然后使用下面的命令编译我们的 checkbox.cpp
g++ -o checkbox.cgi checkbox.cpp -lcgicc
然后点击 http://localhost:8080/checkbox.html
选择曾经用过的语言,然后点击提交
向 CGI 程序传递单选按钮数据
如果需要用户从一组数据中只能选择一项,可以使用单选框
我们先创建一个表单 radio.html
带有四个单选框
<!DOCTYPE html> <meta charset="utf-8" /> <title>POST 表单 - C++ 基础教程 - 简单教程(www.twle.cn)</title> <h3>POST 表单 - C++ 基础教程 - 简单教程(www.twle.cn)</h3> <form action="/cgi-bin/radio.cgi" method="post"> <p>请选择你喜欢的语言</p> <p> <label><input type="radio" name="language" value="Python" />Python</label> <label><input type="radio" name="language" value="C" />C</label> <label><input type="radio" name="language" value="Java"/>Java</label> <label><input type="radio" name="language" value="PHP" />PHP</label> </p> <p><input type="submit" value="提交" /></p> </form>
然后再开发一个 radio.cgi
来接收单选框的数据
在 cgi-bin
目录下新建一个文件 radio.cpp
然后复制以下内容
/** * file: index.cpp * author: 简单教程(www.twle.cn) * * Copyright © 2015-2065 www.twle.cn. All rights reserved. */ #include <iostream> #include <cgicc/CgiDefs.h> #include <cgicc/Cgicc.h> #include <cgicc/HTTPHTMLHeader.h> #include <cgicc/HTMLClasses.h> using namespace cgicc; int main () { Cgicc formData; std::cout << "Content-type:text/html;charset=utf-8\r\n\r\n"; std::cout << "<!DOCTYPE html>\n"; std::cout << "<title>CGI 程序接收单选框数据</title>\n"; form_iterator fi = formData.getElement("language"); if( !fi->isEmpty() && fi != (*formData).end()) { std::cout << "你最喜欢的语言是:" << **fi; } else { std::cout << "这么多语言里都没有一个你最喜欢的?"; } return 0; }
然后使用下面的命令编译我们的 radio.cpp
g++ -o radio.cgi radio.cpp -lcgicc
然后点击 http://localhost:8080/radio.html
选择你最喜欢的语言,然后点击提交
向 CGI 程序传递文本框数据
当用户可能输入多行文本时,可以使用文本框 <texarea>
元素
我们先创建一个表单 textarea.html
带有一个文本框
<!DOCTYPE html> <meta charset="utf-8" /> <title>POST 表单 - C++ 基础教程 - 简单教程(www.twle.cn)</title> <h3>POST 表单 - C++ 基础教程 - 简单教程(www.twle.cn)</h3> <form action="/cgi-bin/textarea.cgi" method="post"> <p>请输入你最想说的话</p> <p><textarea name="words">简单教程,简单编程</textarea></p> <p><input type="submit" value="提交" /></p> </form>
然后再开发一个 textarea.cgi
来接收单选框的数据
在 cgi-bin
目录下新建一个文件 textarea.cpp
然后复制以下内容
/** * file: index.cpp * author: 简单教程(www.twle.cn) * * Copyright © 2015-2065 www.twle.cn. All rights reserved. */ #include <iostream> #include <cgicc/CgiDefs.h> #include <cgicc/Cgicc.h> #include <cgicc/HTTPHTMLHeader.h> #include <cgicc/HTMLClasses.h> int main () { cgicc::Cgicc formData; std::cout << "Content-type:text/html;charset=utf-8\r\n\r\n"; std::cout << "<!DOCTYPE html>\n"; std::cout << "<title>CGI 程序接收文本框数据</title>\n"; cgicc:: form_iterator fi = formData.getElement("words"); if( !fi->isEmpty() && fi != (*formData).end()) { std::cout << "你说了:<br/>" << **fi; }else{ std::cout << "说点什么好啊吗?"; } return 0; }
然后使用下面的命令编译我们的 textarea.cpp
g++ -o textarea.cgi textarea.cpp -lcgicc
然后点击 http://localhost:8080/textarea.html
选择你最喜欢的语言,然后点击提交
向 CGI 程序传递下拉框数据
如果只有三四个选项让用户从中选择一个,用单选框就好了,但如果有八九个,用单选框就太占页面了
这时候可以使用下拉选择框 <select>
我们先创建一个表单 select.html
带有一个下拉选择框
<!DOCTYPE html> <meta charset="utf-8" /> <title>POST 表单 - C++ 基础教程 - 简单教程(www.twle.cn)</title> <h3>POST 表单 - C++ 基础教程 - 简单教程(www.twle.cn)</h3> <form action="/cgi-bin/select.cgi" method="post"> <p>请选择你最喜欢的语言</p> <p> <select name="love"> <option value="Python">Python</option> <option value="PHP">PHP</option> <option value="Perl">Perl</option> <option value="Ruby">Ruby</option> <option value="Scala">Scala</option> <option value="C#">C#</option> <option value="C++">C++</option> <option value="Go">Go</option> </select> </p> <p><input type="submit" value="提交" /></p> </form>
然后再开发一个 select.cgi
来接收单选框的数据
在 cgi-bin
目录下新建一个文件 select.cpp
然后复制以下内容
#include <iostream> #include <cgicc/CgiDefs.h> #include <cgicc/Cgicc.h> #include <cgicc/HTTPHTMLHeader.h> #include <cgicc/HTMLClasses.h> int main () { cgicc::Cgicc formData; std::cout << "Content-type:text/html;charset=utf-8\r\n\r\n"; std::cout << "<!DOCTYPE html>\n"; std::cout << "<title>CGI 程序接收下拉框数据</title>\n"; cgicc::form_iterator fi = formData.getElement("love"); if( !fi->isEmpty() && fi != (*formData).end()) { std::cout << "你最喜欢的语言是: " << **fi; } else { std::cout << "难道这里面没有你喜欢的语言?"; } return 0; }
然后使用下面的命令编译我们的 select.cpp
g++ -o select.cgi select.cpp -lcgicc
然后点击 http://localhost:8080/select.html
选择你最喜欢的语言,然后点击提交
在 CGI 中使用 Cookies
HTTP 协议是一种无状态的协议
但现代任何一个网页,都需要在不同页面间保持会话信息
例如,一个用户在完成多个页面的步骤之后结束注册
但是,如何在所有网页中保持用户的会话信息
使用 cookies 是记忆和跟踪有关用户喜好、购买、佣金以及其他为追求更好的游客体验或网站统计所需信息的最有效的方法
Cookie 是如何工作的
服务器以 cookie 的形式向访客的浏览器发送一些数据
如果浏览器接受了 cookie,则 cookie 会以纯文本记录的形式存储在访客的硬盘上
当访客访问网站上的另一个页面时,会检索 cookie。一旦找到 cookie,服务器就知道存储了什么
cookie 是一种纯文本的数据记录,带有 5 个可变长度的字段
字段 | 说明 |
---|---|
Expires | cookie 的过期日期。如果此字段未设置,cookie 会在访客退出浏览器时过期 |
Domain | 网站的域名 |
Path | 设置 cookie 的目录或网页的路径。如果让一个域名下的所有网页都可以检索 cookie,此字段可以留空 |
Secure | 如果此字段包含单词 "secure",那么 cookie 只能通过安全服务器进行检索。如果此字段留空,则不存在该限制 |
Name=Value | cookie 以键值对的形式被设置和获取 |
设置 Cookies
向浏览器发送 cookies 是非常简单的
cookies 会在 Content-type 字段之前,与 HTTP 头一起被发送
下面的代码设置了两个 cookie ,分别是 site=www.twle.cn
和 user=yufei
在 cgi-bin
目录下创建一个文件 set_cookie.cpp
并复制以下内容
#include <iostream> int main () { std::cout << "Set-Cookie:site=www.twle.cn;\r\n"; std::cout << "Set-Cookie:user=yufei;"; std::cout << "Domain=localhost;"; std::cout << "Path=/;\r\n"; std::cout << "Content-type:text/html;charset=utf-8\r\n\r\n"; std::cout << "<!DOCTYPE html>\n"; std::cout << "<meta charset=\"utf-8\" />"; std::cout << "<title>CGI 中的 Cookies</title>\n"; std::cout << "设置 cookies"; return 0; }
使用下面的命令编译我们的 set_cookie.cpp
g++ -o set_cookie.cgi set_cookie.cpp
然后点击 http://localhost:8080/cgi-bin/set_cookie.cgi
可以看到如下内容
打开 Chrome 浏览器的开发者模式,切换到 Application
,然后选择 Cookie
可以看到如下内容
获取 Cookies
cookies 存储在 CGI 环境变量 HTTP_COOKIE
中,且它们的形式如下
key1=value1;key2=value2;key3=value3....
在 cgi-bin
目录下创建一个文件 get_cookie.cpp
并复制以下内容
#include <iostream> #include <cgicc/CgiDefs.h> #include <cgicc/Cgicc.h> #include <cgicc/HTTPHTMLHeader.h> #include <cgicc/HTMLClasses.h> using namespace cgicc; int main () { Cgicc cgi; const_cookie_iterator cci; std::cout << "Content-type:text/html;charset=utf-8\r\n\r\n"; std::cout << "<!DOCTYPE html>\n"; std::cout << "<title>CGI 中的 Cookies</title>\n"; std::cout << "<table border = \"0\" cellspacing = \"2\">"; // 获取环境变量 const CgiEnvironment& env = cgi.getEnvironment(); for( cci = env.getCookieList().begin(); cci != env.getCookieList().end(); ++cci ) { std::cout << "<tr><td>" << cci->getName() << "</td><td>"; std::cout << cci->getValue(); std::cout << "</td></tr>\n"; } std::cout << "</table>\n"; return 0; }
使用下面的命令编译我们的 get_cookie.cpp
g++ -o get_cookie.cgi get_cookie.cpp -lcgicc
然后点击 http://localhost:8080/cgi-bin/get_cookie.cgi
可以看到如下内容
文件上传
上传一个文件,需要把 HTML 表单的 enctype
属性设置为 multipart/form-data
我们先创建一个表单 upload.html
带有一个下拉选择框
<!DOCTYPE html> <meta charset="utf-8"> <title>上传表单 - C++ 基础教程 - 简单教程(www.twle.cn)</title> <h3>上传表单 - C++ 基础教程 - 简单教程(www.twle.cn)</h3> <form action="/cgi-bin/upload.cgi" method="post" enctype="multipart/form-data"> <p>文件:<input type="file" name="userfile" /></p> <p><input type="submit" value="上传" /></p> </form>
然后再开发一个 upload.cgi
来接收上传的文件
在 cgi-bin
目录下新建一个文件 upload.cpp
然后复制以下内容
#include <iostream> #include <cgicc/CgiDefs.h> #include <cgicc/Cgicc.h> #include <cgicc/HTTPHTMLHeader.h> #include <cgicc/HTMLClasses.h> using namespace std; using namespace cgicc; int main () { Cgicc cgi; std::cout << "Content-type:text/html;charset=utf-8\r\n\r\n"; std::cout << "<!DOCTYPE html>\n"; std::cout << "<title>CGI 中的文件上传</title>\n"; std::cout << "上传文件的内容是:<br/>"; // 获取要被上传的文件列表 const_file_iterator file = cgi.getFile("userfile"); if(file != cgi.getFiles().end()) { // 在 cout 中发送数据类型 std::cout << HTTPContentHeader(file->getDataType()); // 在 cout 中写入内容 file->writeToStream(cout); } std::cout << "<br/>文件上传成功\n"; return 0; }
然后使用下面的命令编译我们的 upload.cpp
g++ -o upload.cgi upload.cpp -lcgicc
然后点击 http://localhost:8080/upload.html
选择上传的文件,然后点击上传
范例是在 cout 流中写入内容,一般情况是打开文件流,并把上传的文件内容保存在目标位置的某个文件中