初级代码游戏的专栏介绍与文章目录-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\nif (!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,提交到自身。
三、更多讨论
我这里是只是验证功能,正常情形这种功能肯定是和特定需求相关的,除了保存文件肯定还有很多相关处理,以这个为基础,做成特定页面当然是没有问题的。
我这里是接收完整个请求才处理的,因为以前没考虑这个功能。如果文件很大,恐怕这个方式不行,需要专门优化。
(这里是结束)