在文章《用C++实现类似于JAVA HttpServlet 的编程接口 》中讲了如何用 acl_cpp 的 HttpServlet 等类来实现 WEB CGI 的功能,同时在文章《使用 acl_cpp 的 HttpServlet 类及服务器框架编写WEB服务器程序 》中也举例说明如何将基于 HttpServlet 编写的 CGI 程序快速地转为服务器程序的过程。本文主要讲如何用 acl_cpp 的 WEB 编程类实现 HTTP 文件上传过程。为了实现 HTTP 协议的文件上传过程,引入了两个类:http_mime 和 http_mime_node。
http_mime 类是有关 HTTP 协议中 mime 格式的流式解析器(即每次仅输入部分 HTTP MIME 数据,等数据输入完毕时,该解析器也解析完毕,流式解析的好处是它可以适用于阻塞或非阻塞的IO模式);http_mime_node 类对象表示 http mime 数据中每一个 mime 结点对象,该结点的数据可能是文件内容数据,也可能是参数数据。
一、http_mime 类
该类一般由 HttpServletRequest 类内部自动管理(负责分配与释放 http_mide 类对象),当然用户可以在测试 http_mime 类时,自己创建与释放该类对象。下面是该类的构造函数及常用方法:
1 2 3 4 5 6 7
|
http_mime(const char* boundary, const char* local_charset = "gb2312");
|
尤其需要指出的是 http mime 的 boundary(分隔符)与邮件的 mime 的分隔符规则略有不同,如邮件的相关头部字段为:Content-Type: multipart/mixed; charset=”GB2312”; boundary=”0_11119_1331286082”,HTTP MIME 的相关头部字段为:Content-Type: multipart/form-data; boundary=”–0_11119_1331286082”。其中,最大的区别就是在 HTTP 头中获得的分隔符与 HTTP 数据体的分隔符(除结尾分隔符多了两个 ‘-‘ 后缀)完全相同,而邮件的 mime 的分隔符在头部和 mime 体中是不一样的,mime 体中的分隔符是由头部的分隔符加两个 ‘-‘ 作为前导符(结尾分隔符为头部分隔符前面加两个 ‘-‘,尾部加两个 ‘-‘),一定得注意这些不同。在 acl_cpp 中的 http mime 解析模块原来主要是作邮件 mime 解析的,现在依然支持 HTTP 的 mime 解析,唯一不同就是区分分隔符的不同。(当然,邮件的 MIME 数据体还与 HTTP MIME 数据体有另外一个区别:邮件的 MIME 数据一般都是要经过 BASE64 来编码的,而 HTTP MIME 却很少编码)。
http_mime 的几个常用方法接口如下:
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
|
void set_saved_path(const char* path);
bool update(const char* data, size_t len);
const std::list<http_mime_node*>& get_nodes(void) const;
const http_mime_node* get_node(const char* name) const;
|
二、http_mime_node 类
该类实例存储 HTTP MIME 数据体中每个数据结点,同时该类的实例是由 http_mime 类对象自动维护的,所以您一般不必关心该类对象的创建与销毁;另外,http_mime_node 类的继承关系为:http_mime_node -> mime_attach -> mime_node。
该类的构造函数如下:
1 2 3 4 5 6 7 8 9 10
|
http_mime_node(const char* path, const MIME_NODE* node, bool decodeIt = true, const char* toCharset = "gb2312", off_t off = 0);
|
该类的常用方法为:
1 2 3 4 5 6 7 8 9 10 11 12
|
http_mime_t get_mime_type(void) const;
const char* get_value(void) const;
|
http_mime_t 为枚举类型,如:
1 2 3 4 5
| typedef enum { HTTP_MIME_PARAM, HTTP_MIME_FILE } http_mime_t;
|
加上两个基类的一些方法,有几个方法也是比较常用的,如下:
- mime_node::get_name: 获得该 mime 结点的名称
- mime_attach::get_filename: 当结点为上传文件类型时,此函数获得上传文件的文件名
三、示例
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 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
| #include "lib_acl.hpp"
using namespace acl;
class http_servlet : public HttpServlet { public: http_servlet() { ... }
... bool doPost(HttpServletRequest& req, HttpServletResponse& res) { ... return doUpload(req, res); }
bool doUpload(HttpServletRequest& req, HttpServletResponse& res) { http_mime* mime = req.getHttpMime(); if (mime == NULL) { logger_error("http_mime null"); return false; }
long long int len = req.getContentLength(); if (len <= 0) { logger_error("body empty"); return false; }
istream& in = req.getInputStream(); char buf[8192]; int ret; bool n = false;
const char* filepath = "./var/mime_file"; ofstream out; out.open_write(filepath);
mime->set_saved_path(filepath);
while (len > 0) { ret = in.read(buf, sizeof(buf), false); if (ret == -1) { logger_error("read POST data error"); return false; } out.write(buf, ret); len -= ret;
if (mime->update(buf, ret) == true) { n = true; break; } } out.close();
if (len != 0 || n == false) logger_warn("not read all data from client");
string path;
const std::list<http_mime_node*>& nodes = mime->get_nodes(); std::list<http_mime_node*>::const_iterator cit = nodes.begin(); for (; cit != nodes.end(); ++cit) { const char* name = (*cit)->get_name();
http_mime_t mime_type = (*cit)->get_mime_type(); if (mime_type == HTTP_MIME_FILE) { const char* filename = (*cit)->get_filename(); if (filename == NULL) { logger("filename null"); continue; }
if (strcmp(name, "file1") == 0) file1_ = filename; else if (strcmp(name, "file2") == 0) file2_ = filename; else if (strcmp(name, "file3") == 0) file3_ = filename;
path.format("./var/%s", filename); (void) (*cit)->save(path.c_str()); } }
const http_mime_node* node = mime->get_node("file1"); if (node && node->get_mime_type() == HTTP_MIME_FILE) { const char* ptr = node->get_filename(); if (ptr) { path.format("./var/1_%s", ptr); (void) node->save(path.c_str()); } }
:unlink(filepath);
if (res.sendHeader() == false) return false; if (res.getOutputStream().write("ok") == -1) return false; return true; }
private: const char* file1_; const char* file2_; const char* file3_; };
int main(void) { #ifdef WIN32 acl::acl_cpp_init(); #endif
http_servlet servlet; servlet.doRun("127.0.0.1:11211"); return 0; }
|
与上面例子对应的 HTML 页面如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <html> <head> <meta content="text/html; charset=gb2312" http-equiv="Content-Type"> </head> <body> <form enctype="multipart/form-data" method=POST action="/cgi-bin/test/upload?name1=中国人"> <input type=hidden name="name2" value="美国人"><br> <input type=hidden name="name3" value="英国人"><br> <input type=submit name="submit", value="提交"><br> 文件一:<input type=file name="file1" value=""><br> 文件二:<input type=file name="file2" value=""><br> 文件三:<input type=file name="file3" value=""><br> </form> </body> </html>
|
上面例子比较简单地说明了如果使用 acl_cpp 中的 HttpServlet/http_mime 等类来实现文件上传的功能,完整的例子请参考:acl_cpp/samples/cig_upload。该例子虽然是一个 CGI 程序,但您依然可以不费吹灰之力将其改变成一个服务器程序,转换方法可参考:《使用 acl_cpp 的 HttpServlet 类及服务器框架编写WEB服务器程序 》。