编程实战:自己编写HTTP服务器(系列9:上传文件)

初级代码游戏的专栏介绍与文章目录-CSDN博客

我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。

这些代码大部分以Linux为目标但部分代码是纯C++的,可以在任何平台上使用。

本系列的源码位于httpd目录下。系列入口:

编程实战:自己编写HTTP服务器(系列1:概述和应答)-CSDN博客


        通过HTTP上传文件需要服务端支持,html虽然支持类型为“file”的input,能选择文件,但是服务器还必须正确支持才行,毕竟如果写一个html就能往服务器上放文件可就太乱了。

目录

一、html和http的file支持

二、服务端代码

2.1 服务端入口

2.2 实际功能

三、更多讨论


一、html和http的file支持

        一个上传文件的form很简单:

<form id="upload-form" action="/Upload.asp" method="post" enctype="multipart/form-data" >
    <input type="file" id="upload" name="upload" />
    <input type="submit" value="Upload" />
</form>

        确实很简单,最重要的秘密是必须编码为“multipart/form-data”,当然也必须用POST。

        “multipart/form-data”的格式也很简单:

multipart/form-data; boundary=ASDFGHJKL

--ASDFGHJKL
Content-Disposition: form-data; name="upload"; filename="文件名"
Content-Type: text/plain

文件内容
--ASDFGHJKL--

        注意一下格式细节,其中片段分隔符boundary是“ASDFGHJKL”,实际会更长一些,并且可能以几个“-”打头,容易和后面引导的“--”混淆。

        片段以“--”加分隔符开始,是一行,按照http规则,以“\r\n”为行结束。

        然后跟两个头标,格式和http头标完全相同。

        再后面是一个空行表示头标部分结束。

        投标部分的后面就是数据,没有数据长度,要根据结束标志判断,结束标志是“--”加分隔符再加“--”。这个例子中我上传的是txt文件,所以所以内容类型为text/plain,如果上传的是rar文件,则类型为字节流。

        文件内容和结束标志之间一定有“\r\n”分隔,我上传的文本文件最后没有换行,但结束标志前增加了“\r\n”,如果文件最后有换行,会怎么样我没有验证(我猜还是追加换行然后是结束标志)。

二、服务端代码

2.1 服务端入口

        我将上传功能做成了内置页面,页面地址为“/Upload.asp”,主代码中添了这么一段:

				else if ("/Upload.asp" == m_request.GetResource())
				{
					if (doPageUpload(&connectdata))
					{
						m_respond.Flush(m_s);
					}
					else
					{
						m_respond.Flush(m_s);
						m_s.Close();//所有此类页面都可能无法预先确定输出长度
						isKeepAlive = false;
					}
				}

        具体位置在myhttpserver.h的SocketProcess函数中。

2.2 实际功能

        实际功能位于doPageUpload函数:

		bool doPageUpload(CConnectData* pCD)
		{
			CHttpRequest& m_request = pCD->m_request;//请求
			CHttpRespond& m_respond = pCD->m_respond;//应答
			CMySocket& m_s = pCD->m_s;//连接

			m_respond.Init();
			m_respond.AddHeaderNoCache();
			m_respond.AddHeaderContentTypeByFilename("*.html");
			m_respond.AppendBodyHtmlStart("Uplaod");
			m_respond.AppendBody(m_request.RequestHtmlReport());
			m_respond.AppendBody("<HR/>");
			m_respond.AppendBody(m_request.GetFullRequest());
			m_respond.AppendBody("<HR/>");
			m_respond.AppendBody(m_request.GetContent().data());
			m_respond.AppendBody("<HR/>");

			string content_type = m_request.GetContentType();
			string a = "multipart/form-data; boundary=";
			string boundary;
			size_t pos = content_type.find(a);
			if (0 != pos)
			{
				m_respond.AppendBody("未识别的内容类型,仅支持 multipart/form-data<P>");
			}
			else
			{
				boundary = content_type.substr(a.size());
				string boundary_begin = "--" + boundary + "\r\n";
				string boundary_end = "--" + boundary + "--\r\n";
				size_t part_pos;
				size_t pos_next_find = 0;
				while (CBuffer::npos != (part_pos = m_request.GetContent().find(boundary_begin, pos_next_find)))
				{
					size_t part_end = m_request.GetContent().find(boundary_end, part_pos + boundary_begin.size());
					if (CBuffer::npos == part_end)
					{
						m_respond.AppendBody("数据内容不完整<P>");
						break;
					}

					size_t part_header_end = m_request.GetContent().find("\r\n\r\n");
					string part_header = m_request.GetContent().substr(part_pos + boundary_begin.size(), part_header_end - part_pos - boundary_begin.size());
					size_t pos_file_name_begin;
					string filename_head="filename=\"";
					pos_file_name_begin = part_header.find(filename_head);
					if (string::npos == pos_file_name_begin)
					{
						m_respond.AppendBody("没有文件名<P>");
					}
					else
					{
						size_t pos_file_name_end = part_header.find("\"", pos_file_name_begin + filename_head.size());
						if (string::npos == pos_file_name_end)
						{
							m_respond.AppendBody("文件名格式问题<P>");
						}
						else
						{
							//thelog << pos_file_name_begin << " " << pos_file_name_end << " " << pos_file_name_end - pos_file_name_begin - filename_head.size() << endi;
							string filename = part_header.substr(pos_file_name_begin + filename_head.size(), pos_file_name_end - pos_file_name_begin - filename_head.size());
							if (filename.size() == 0)
							{
								m_respond.AppendBody("没有文件名<P>");
							}
							else
							{
								m_respond.AppendBody(filename);
								CEasyFile file;
								size_t pos_filedata = part_header_end + 4;
								long filesize = part_end - pos_filedata - 2;//前面还有\r\n
								if (!file.WriteFile(filename.c_str(), m_request.GetContent().data() + pos_filedata, filesize))
								{
									m_respond.AppendBody("写入失败<P>");
								}
								else
								{
									char buf[256];
									sprintf(buf, "写入成功 字节数%ld<P>", filesize);
									m_respond.AppendBody(buf);
									char path[1024];
									m_respond.AppendBody(getcwd(path, 1024));
								}
							}
						}
					}

					pos_next_find = part_end + boundary_end.size();
					m_respond.AppendBody("<HR />");
				}
			}

			string form = "<form id=\"upload-form\" action=\"/Upload.asp\" method=\"post\" enctype=\"multipart/form-data\" >\
				<input type=\"file\" id=\"upload\" name=\"upload\" /> <br />\
				<input type=\"submit\" value=\"Upload\" />\
				</form>";
			m_respond.AppendBody(form);
	
			m_respond.AppendBodyHtmlEnd();
			m_respond.Flush(m_s);
			return true;
		}

        功能相当的简单。前面是输出了一下请求信息,方便调试。中间提取文件名和内容,然后直接把内容写到文件中,相当的粗暴。最后是输出了form,提交到自身。

三、更多讨论

        我这里是只是验证功能,正常情形这种功能肯定是和特定需求相关的,除了保存文件肯定还有很多相关处理,以这个为基础,做成特定页面当然是没有问题的。

        我这里是接收完整个请求才处理的,因为以前没考虑这个功能。如果文件很大,恐怕这个方式不行,需要专门优化。

(这里是结束)

  • 51
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值