`
zsxxsz
  • 浏览: 450481 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

web 编程中实现文件上传的服务端实例

阅读更多

      在文章《用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 类时,自己创建与释放该类对象。下面是该类的构造函数及常用方法:

 

		/**
		 * 构建函数
		 * @param boundary {const char*} 分隔符,不能为空
		 * @param local_charset {const char*} 本地字符集,非空时会自动将
		 *  参数内容转为本地字符集
		 */
		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 的几个常用方法接口如下:

 

		/**
		 * 设置 MIME 数据的存储路径,当分析完 MIME 数据后,如果想要
		 * 从中提取数据,则必须给出该 MIME 的原始数据的存储位置,否则
		 * 无法获得相应数据,即 save_xxx/get_nodes/get_node 函数均无法
		 * 正常使用
		 * @param path {const char*} 文件路径名, 如果该参数为空, 则不能
		 *  获得数据体数据, 也不能调用 save_xxx 相关的接口
		 */
		void set_saved_path(const char* path);

		/**
		 * 调用此函数进行流式方式解析数据体内容
		 * @param data {const char*} 数据体(可能是数据头也可能是数据体, 
		 *  并且不必是完整的数据行)
		 * @param len {size_t} data 数据长度
		 * @return {bool} 针对 multipart 数据, 返回 true 表示解析完毕;
		 *  对于非 multipart 文件, 该返回值永远为 false, 没有任何意义, 
		 *  需要调用者自己判断数据体的结束位置
		 * 注意: 调用完此函数后一定需要调用 update_end 函数通知解析器
		 * 解析完毕
		 */
		bool update(const char* data, size_t len);

		/**
		 * 获得所有的 MIME 结点
		 * @return {const std::list<http_mimde_node*>&}
		 */
		const std::list<http_mime_node*>& get_nodes(void) const;

		/**
		 * 根据变量名取得 HTTP MIME 结点
		 * @param param name {const char*} 变量名
		 * @return {http_mime_node*} 返回空则说明对应变量名的结点
		 *  不存在
		 */
		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。

      该类的构造函数如下:

 

		/**
		 * 原始文件存放路径,不能为空
		 * @param node {MIME_NODE*} 对应的 MIME 结点,非空
		 * @param decodeIt {bool} 是否对 MIME 结点的头部数据
		 *  或数据体数据进行解码
		 * @param toCharset {const char*} 本机的字符集
		 * @param off {off_t} 偏移数据位置
		 */
		http_mime_node(const char* path, const MIME_NODE* node,
			bool decodeIt = true, const char* toCharset = "gb2312",
			off_t off = 0);

 

 

      该类的常用方法为:

 

		/**
		 * 获得该结点的类型
		 * @return {http_mime_t}
		 */
		http_mime_t get_mime_type(void) const;

		/**
		 * 当 get_mime_type 返回的类型为 HTTP_MIME_PARAM 时,可以
		 * 调用此函数获得参数值;参数名可以通过基类的 get_name() 获得
		 * @return {const char*} 返回 NULL 表示参数不存在
		 */
		const char* get_value(void) const;

 

       http_mime_t 为枚举类型,如:

 

	typedef enum
	{
		HTTP_MIME_PARAM,        // http mime 结点为参数类型
		HTTP_MIME_FILE          // http mime 结点为文件类型
	} http_mime_t;

 

      加上两个基类的一些方法,有几个方法也是比较常用的,如下:

 

 mime_node::get_name: 获得该 mime 结点的名称

 mime_attach::get_filename: 当结点为上传文件类型时,此函数获得上传文件的文件名

 

      三、示例

 

#include "lib_acl.hpp"

using namespace acl;

class http_servlet : public HttpServlet
{
public:
	http_servlet()
	{
		...
	}

	...
	// 基类虚方法:HTTP POST 方法接口
	virtual bool doPost(HttpServletRequest& req, HttpServletResponse& res)
	{
		...
		return doUpload(req, res);
	}

	// 处理文件上传的函数
	bool doUpload(HttpServletRequest& req, HttpServletResponse& res)
	{
		// 先获得 Content-Type 对应的 http_ctype 对象
		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);

		// 读取 HTTP 客户端请求数据
		while (len > 0)
		{
			// 从 HTTP 输入流中读取数据
			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;

		// 遍历所有的 MIME 结点,找出其中为文件结点的部分进行转储
		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)
		{
			// HTTP MIME 结点的变量名
			const char* name = (*cit)->get_name();

			// HTTP MIME 结点的类型
			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);

		// 发送 http 响应头
		if (res.sendHeader() == false)
			return false;
		// 发送 http 响应体
		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"); // 开始运行,并假设 memcached 监听于 127.0.0.1:11211
	return 0;
}

 

      与上面例子对应的 HTML 页面如下:

<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服务器程序 》。

 

 

      个人微博:http://weibo.com/zsxxsz

       原文地址

      github: https://github.com/acl-dev/acl

       更多文章

       bbs:http://www.aclfans.com

5
1
分享到:
评论
4 楼 zsxxsz 2012-05-23  
whouseit 写道
用C++来实现 WEB 编程太笨重了吧,现在大家都在用PHP/JSP/ASP了,谁还会用C++写WEB程序?感觉博主是在重复发明轮子,呵呵

其实是否需要发明轮子得看具体需求,这些WEB功能的实现只是为了让用户尽量少地依赖一大堆库去完成自己的需求功能,如果您在用一个库干很多事情,就不必东找西查了,并且 acl_cpp 的主要功能还是集中在服务器编程及网络通信方面,象WEB应用库只是为了说明 acl_cpp 的丰富与多样性。
3 楼 whouseit 2012-05-23  
用C++来实现 WEB 编程太笨重了吧,现在大家都在用PHP/JSP/ASP了,谁还会用C++写WEB程序?感觉博主是在重复发明轮子,呵呵
2 楼 zsxxsz 2012-05-23  
mengyouzhe 写道
如果再能支持网页模板,象JAVA的Velocity就牛了,啥时能支持 FCGI 模式?

确实模板功能是WEB编程中不可或缺的一部分,我也非常想加此功能(10年前写CGI程序时页面参数替换可费了不少力气),可能不久就会加上,也希望大家多出出主意,谁有现成的能直接集成进来就更好了,谢谢
1 楼 mengyouzhe 2012-05-23  
如果再能支持网页模板,象JAVA的Velocity就牛了,啥时能支持 FCGI 模式?

相关推荐

    Web Api 通过文件流 下载文件到本地实例

    通过分析这个项目,你可以看到如何在`Controllers`目录下的某个控制器类中实现文件下载的方法,以及如何配置路由来确保客户端可以通过正确的URL访问这个服务。你还可以学习到如何在`Global.asax.cs`或`Startup.cs`中...

    cxf+spring开发webservice客户端与服务端实例

    本实例将详细阐述如何利用CXF和Spring来构建Web服务的客户端和服务端。 一、CXF简介 CXF是一个开源的Java框架,专门用于构建和消费Web服务。它支持SOAP、RESTful等多种服务模型,并且可以方便地与Spring框架集成,...

    gsoap客户端Jax-ws服务端实例

    本实例主要探讨了如何使用gSOAP客户端来访问由JAX-WS(Java API for XML Web Services)在MyEclipse 8.x环境中创建的Web服务。这个过程涉及到C++编程和.NET框架的使用,特别是Visual Studio 2005。 首先,让我们...

    C# WebSocket使用实例源码,包含服务端和客户端

    WebSocket是Web应用中实现双向通信的一种技术,它允许服务器与客户端之间进行实时、低延迟的数据交换。在C#中,WebSocket的使用主要依赖于.NET Framework或.NET Core的相关库。本实例是一个基于C#的WebSocket应用,...

    Visual C# .NET精彩编程实例集锦

    实例142 如何在Web页中使用多选列表框控件 实例143 如何在Web页中使用正则表达式控件 实例144 如何在Web页中获取浏览器信息 实例145 如何在Web页中浏览图像 实例146 如何在Web页中绘制图形 实例147 如何在Web页间...

    《Visual C# .NET精彩编程实例集锦》配套光盘文件【全】

    《Visual C# .NET精彩编程实例集锦》配套光盘文件【全】 目录回到顶部↑ 前言 第1章 控件操作 实例1 如何使用错误提醒控件 实例2 如何使用信息提示控件 实例3 如何使用菜单控件 实例4 如何使用工具栏控件 实例...

    RemObjects服务端实例

    在"RemObjects服务端实例"中,我们可以推测这可能是一个关于如何使用RemObjects框架来构建服务端应用的教程或示例项目。服务端通常是指在后台运行,处理客户端请求并提供数据或服务的程序。在Web开发中,服务端负责...

    【ASP.NET编程知识】ASP.NET WebAPi(selfhost)实现文件同步或异步上传.docx

    ASP.NET WebAPI(SelfHost)实现文件同步或异步上传主要涉及到两个方面:客户端的HTML/JavaScript处理和服务器端的ASP.NET WebAPI处理。这里我们将深入探讨如何在ASP.NET环境中,利用SelfHost模式来构建一个文件上传...

    web Service 实例教程

    内容中提到了创建服务端的几个关键文件:`IHello.java`(定义服务接口),`Hello.java`(实现接口的方法),以及自动生成的`HelloDelegate.java`(服务的代理实现)。客户端通过这些文件与服务端进行通信。 ### ...

    用VS2017 C# 实现WebSocket的服务端和客户端源码.rar

    完成WebSocket服务端和客户端的开发后,可以将服务端部署到IIS或其他Web服务器上,客户端则可以嵌入到Web应用、桌面应用或者移动应用中,实现实时通信功能,例如聊天、游戏、股票实时更新等。 通过深入理解上述...

    Java Web编程实战宝典(光盘源代码)

    总的来说,《Java Web编程实战宝典》的源代码涵盖了以上各个方面的实例,通过学习和实践这些代码,读者不仅可以掌握Java Web开发的基本技能,还能逐步熟悉实际项目的开发流程,为成为合格的Java Web开发者打下坚实...

    基于axis2实现的webservice简单实现(客户端+服务端)。

    【标题】中的“基于axis2实现的webservice简单实现(客户端+服务端)”表明了本文将探讨如何使用Apache Axis2框架来创建和消费Web服务。Apache Axis2是Apache软件基金会开发的一个Web服务引擎,它提供了高效且灵活的...

    多文件断点上传实例

    这个"多文件断点上传实例"很可能是某种编程示例或教程,用于演示如何在应用程序中实现这种功能。下面我们将深入探讨断点上传的概念、工作原理以及实现多文件断点上传的技术细节。 首先,断点上传是基于HTTP或FTP...

    C#精彩实例源码!包括C#编程基础,界面编程,线程,数据库编程,网络编程,Web应用程序的开发等

    C#是一种广泛应用于桌面应用、游戏开发、移动应用以及Web服务的高级编程语言。这个压缩包包含了一系列精彩的C#实例源码,涵盖了C#编程的基础到高级应用,旨在帮助开发者深入理解和掌握C#的各项功能。 1. **C#编程...

    通过webservice上传和下载文件

    标题中的“通过Web服务上传和下载文件”是指利用Web服务技术来实现在网络环境中传输文件。Web服务是一种基于HTTP协议的通信方式,它允许不同系统间的应用程序之间交换数据。在这个场景下,我们主要关注的是如何使用...

    java上传下载实例

    本实例主要探讨如何在Java环境下实现文件的上传和下载功能,这对于我们理解和掌握Web开发中的IO流处理、多线程技术以及HTTP协议有重要作用。 首先,Java上传文件通常涉及到Servlet API,特别是HttpServletRequest...

    axis2 webservice 实例(包括服务端代码及客户端代码)

    标题中的“Axis2 ...通过这个实例,初学者可以深入理解Web服务开发的全过程,从服务端的编写到客户端的调用,这将对他们的编程技能和对Web服务的理解大有裨益。同时,实战经验也会提高他们解决实际问题的能力。

    【最新】C++ http 服务端,接收来自http post form_data文件(图片)源码

    本文将深入探讨如何在C++中实现一个HTTP服务端,尤其是处理HTTP POST请求,特别是接收form_data类型的文件,如图片。这在现代Web开发中是非常常见的场景,例如用户上传头像或者分享照片。 首先,理解HTTP协议的基础...

    C#编程实例

    实例可能涉及创建一个简单的WebService,实现客户端和服务端的交互。 综上所述,这个"C#编程实例"涵盖了C#语言的广泛领域,从基础语法到高级特性,都是学习和提升C#技能的理想材料。通过实际操作这些实例,你将不仅...

Global site tag (gtag.js) - Google Analytics