《采用WinHttp实现HTTP协议Get、Post和文件上传功能》一文中,我已经比較具体地解说了怎样使用WinHttp接口实现各种协议。

在近期的代码梳理中,我认为Post和文件上传模块能够得到简化,于是差点儿重写了这两个功能的代码。由于Get、Post和文件上传功能的基础(父)类基本没有修改,函数调用的流程也基本没有变化,所以本文我将重点解说修改点。

(转载请指明出于breaksoftware的csdn博客)

首先我改动了接口的字符集。之前我都是使用UNICODE作为接口參数类型,当中一个原因是Windows提倡UNICODE编码,其次是由于WinHttp接口仅仅提供了UNICODE接口函数。而我在本次改动中,将字符集改成UTF8。由于在网络传输方便,UTF8格式才是主流。

于是为了使用WinHttp接口,我提供了一个A版本号的转换层——project中WinhttpA.h。

其次。我增强了Post接口。《使用WinHttp接口实现HTTP协议Get、Post和文件上传功能》的读者和我讨论了非常多Post协议,让我感觉非常有必要重视起该功能。本文我们将着重解说Post的实现和測试。

再次,我将Post的实现和文件上传功能的实现合二为一。由于两者代码非常相似。事实上在原理方面也是非常相似的。

最后。我使用前一篇博文中介绍的IMemFileOperation接口,又一次定义了Post和文件上传功能的參数定义。由于IMemFileOperation的特性,我们能够上传文件,或者上传一片内存值,或者上传文件里的内容,而这些操作是同样的。

Get请求没什么好说的了。我们主要关注Post和文件上传。

普通情况下,我们遇到的是“我们须要向http://www.xxx.com:8080/yyyy/zzz地址Post数据”。当中的“数据”是我们问题的重点。可能非常多人觉得Post请求就是将全部參数都Post到server,事实上不然。打个比方。比方我们要求对http://www.xxxx.com/post?

a=b&c=d地址Post一个数据e=f,我们并非将"a=b&c=d&e=f"Post到server,而仅仅是"e=f"Post过去,"a=b&c=d"还是按Get的方式发送。于是我对上一版的设计做了改良,废掉了ParseParams函数。简化了设计,可是要求用户传进来的URL中不包括须要Post过去的数据——须要Post的数据通过SetPostParam方法传递进来。我们想把重点发到这样的发送分离的实现上:

	if ( !WinHttpCrackUrlA_( m_strUrl, strHost, strPath, strExt, nPort ) ) {
break;
} m_hSession = WinHttpOpenA( m_strAgent.empty() ? NULL : m_strAgent.c_str(), WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0 );
if ( NULL == m_hSession ) {
break;
} if ( FALSE == WinHttpSetTimeouts(m_hSession, m_nResolveTimeout, m_nConnectTimeout, m_nSendTimeout, m_nSendTimeout) ) {
break;
} m_hConnect = WinHttpConnectA( m_hSession, strHost.c_str(), nPort, 0 );
if ( NULL == m_hConnect ) {
break;
} m_strRequestData = strPath + strExt;

主要关注最后一行,我将URL路径和URL參数放到m_strRequestData里。

之后

VOID CHttpRequestByWinHttp::TransmiteDataToServerByPost()
{
BOOL bSuc = FALSE;
do {
m_hRequest = WinHttpOpenRequestA(m_hConnect, "Post",
m_strRequestData.c_str(), NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);
if ( NULL == m_hRequest ) {
break;
}

这样。我们便将不须要Post的数据发送了过去。

如今我们再探讨下须要Post过去的数据。

首先我们须要明白下数据的来源:

  • 内存中的数据
  • 文件里的数据

无论数据来源于何处。它都能够成为待Post过去的数据或者待上传的文件的内容。

于是我们借用上一篇博文中的IMemFileOperation接口,定义Post的数据的格式。

typedef struct _FMParam_ {
std::string strkey;
ToolsInterface::LPIMemFileOperation value;
bool postasfile;
struct FileInfo {
char szfilename[128];
}; struct MemInfo{
bool bMulti;
}; union {
FileInfo fileinfo;
MemInfo meminfo;
};
}FMParam, *PFMParam; typedef std::vector<FMParam> FMParams;
typedef FMParams::iterator FMParamsIter;
typedef FMParams::const_iterator FMParamsCIter;

无论是Post数据还是要上传文件,协议中都须要key的存在。strkey是数据的key。

value字段仅仅是一个指针,它是指向一个文件还是内存。已经没有关系了。由于之后我们将使用统一的接口去訪问它。postasfile字段是标志该參数是否以文件内容的形式Post上去。

这儿须要特别说明下,postasfile和value是内存还是文件是没有关系的。

由于value仅仅是指向了数据内容,至于内容上传到server是作为文件的内容还是仅仅是普通Post的数据值是由postasfile决定的。假设postasfile为真。则FileInfo将被利用到。由于它标记了内容上传到server后,server上保存的文件名称。

假设postasfile为假。则我们须要考虑下数据是作为普通数据post,还是作为MultiPart数据Post。这个就取决于MemInfo中的字段了。

至于什么是MultiPart类型,能够简单參考《使用WinHttp接口实现HTTP协议Get、Post和文件上传功能》后半部分关于文件上传的讨论。

对于待上传的数据,之前设计改框架时。框架提供了GetData方法,让继承类提供数据。

由于数据存在延续性。所以导致继承类的书写非常麻烦——须要记录已经上传了哪些数据。这个版本号我将这个设计做了改动,基类暴露一个发送方法。让继承类在须要的时候调用基类的方法,从而不须要基类记录过程的状态。于是曾经一大坨代码被简化到例如以下几行:

DWORD dwUserDataLength = GetUserDataSize();
if ( FALSE == WinHttpSendRequest( m_hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, dwUserDataLength, 0)) {
break;
} DWORD dwSendUserDataLength = SendUserData();
bSuc = (dwUserDataLength == dwSendUserDataLength) ? TRUE : FALSE;

通过GetUserDataSize我们将获得待Post过去的数据的大小。然后调用SendUserData发送数据,返回发送了的数据的大小。通过对照两者大小得知是否整个操作是否成功。

如今我们再看下发送数据的详细实现。首先我们看下一些固定要写死的字段的申明

#define BOUNDARYPART "--MULTI-PARTS-FORM-DATA-BOUNDARY"

#define PARTRETURN  "\r\n"
#define PARTDISPFD "Content-Disposition:form-data;"
#define PARTNAME "name"
#define PARTEQUATE "="
#define PARTQUOTES "\""
#define PARTSPLIT "&"
#define PARTSEMICOLON ";"
#define PARTFILENAME "filename"
#define PARTTYPEOCT "Content-Type:application/octet-stream"

读过《使用WinHttp接口实现HTTP协议Get、Post和文件上传功能》的朋友应该记得当中有非常多繁杂的数据格式化。之前我们讲过。我们须要先获得待Post的数据大小,再发送数据。这意味着繁杂的数据格式化须要做两次。假设以后须要对当中某个发送数据格式化做改动,那么对应的计算数据长度的方法也要做改动。这是非常不利于维护的。于是,我将两者合为一个函数。通过參数推断是须要计算还是须要发送。这样以后改动发送数据时,仅仅要改动一处,减少了维护的成本和难度。

    DWORD CHttpTransByPost::SendUserData() {
return SendOrCalcData();
} DWORD CHttpTransByPost::GetUserDataSize() {
return SendOrCalcData(FALSE);
} DWORD CHttpTransByPost::SendOrCalcData( BOOL bSend /*= TRUE*/ ) {
DWORD dwsize = 0;
for (FMParamsCIter it = m_PostParam.begin(); it != m_PostParam.end(); it++) {
dwsize += SendData(*it, bSend);
}
if (!m_strBlockEnd.empty()) {
dwsize += DataToServer(m_strBlockEnd.c_str(), m_strBlockEnd.length(), bSend);
}
return dwsize;
}

在SendOrCalcData的最后,我们推断m_strBlockEnd是否为空。假设不为空。则我们将BlockEnd格式化数据发送过去,告诉serverMultiPart数据发送结束。假设为空,则代表此次发送数据不须要按MultiPart形式发送。至于是否须要MultiPart,以及其各种格式化则是在以下的代码中推断

BOOL CHttpTransByPost::ModifyRequestHeader( HINTERNET hRequest ) {
bool bMulti = false;
for (FMParamsCIter it = m_PostParam.begin(); it != m_PostParam.end(); it++) {
if (it->postasfile) {
bMulti = true;
break;
}
else {
bMulti = it->meminfo.bMulti;
if (bMulti) {
break;
}
}
} if (bMulti) {
m_strBlockStart = "--";
m_strBlockStart += BOUNDARYPART;
m_strBlockStart += "\r\n"; m_strBlockEnd = "\r\n--";
m_strBlockEnd += BOUNDARYPART;
m_strBlockEnd += "--\r\n"; m_strNewHeader = "Content-Type: multipart/form-data; boundary=";
m_strNewHeader += BOUNDARYPART;
m_strNewHeader += "\r\n";
}
else {
m_strNewHeader = "Content-Type:application/x-www-form-urlencoded";
m_strNewHeader += "\r\n";
} ::WinHttpAddRequestHeadersA(hRequest, m_strNewHeader.c_str(),
m_strNewHeader.length(), WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE) ;
return AddUserRequestHeader(hRequest);
}

最后我们将注意力集中到发送(计算)数据的函数SendData上。

    DWORD CHttpTransByPost::SendData(const FMParam& postparam, BOOL bSend /*= TRUE*/ ) {
DWORD dwsize = 0;
postparam.value->MFSeek(0, SEEK_SET);
if (postparam.postasfile) {
dwsize = SendFileData(postparam, bSend);
}
else {
dwsize = SendMemData(postparam, bSend);
}
return dwsize;
}

首先,我们使用MFSeek将文件(内存)的指针置到起始处。

然后再通过postasfile决定是按文件的形式发送还是按内存的形式发送。

    DWORD CHttpTransByPost::SendFileData(const FMParam& postparam, BOOL bSend) {
DWORD dwsize = 0;
dwsize += DataToServer(PARTRETURN, strlen(PARTRETURN), bSend);
dwsize += DataToServer(m_strBlockStart.c_str(), m_strBlockStart.length(), bSend);
dwsize += DataToServer(PARTDISPFD, strlen(PARTDISPFD), bSend);
dwsize += DataToServer(PARTNAME, strlen(PARTNAME), bSend);
dwsize += DataToServer(PARTEQUATE, strlen(PARTEQUATE), bSend);
dwsize += DataToServer(PARTQUOTES, strlen(PARTQUOTES), bSend);
dwsize += DataToServer(postparam.strkey.c_str(), postparam.strkey.length(), bSend);
dwsize += DataToServer(PARTQUOTES, strlen(PARTQUOTES), bSend);
dwsize += DataToServer(PARTSEMICOLON, strlen(PARTSEMICOLON), bSend);
dwsize += DataToServer(PARTFILENAME, strlen(PARTFILENAME), bSend);
dwsize += DataToServer(PARTEQUATE, strlen(PARTEQUATE), bSend);
dwsize += DataToServer(PARTQUOTES, strlen(PARTQUOTES), bSend);
dwsize += DataToServer(postparam.fileinfo.szfilename, strlen(postparam.fileinfo.szfilename), bSend);
dwsize += DataToServer(PARTQUOTES, strlen(PARTQUOTES), bSend);
dwsize += DataToServer(PARTRETURN, strlen(PARTRETURN), bSend);
dwsize += DataToServer(PARTTYPEOCT, strlen(PARTTYPEOCT), bSend);
dwsize += DataToServer(PARTRETURN, strlen(PARTRETURN), bSend);
dwsize += DataToServer(PARTRETURN, strlen(PARTRETURN), bSend);
while(!postparam.value->MFEof()) {
char buffer[1024] = {0};
size_t size = postparam.value->MFRead(buffer, 1, 1024);
dwsize += DataToServer(buffer, size, bSend);
}
return dwsize;
}

以文件内容形式发送的代码如上。我们关注下最后几行,MFRead读取内容,然后发送(计算)数据。

    DWORD CHttpTransByPost::SendMemData(const FMParam& postparam, BOOL bSend) {
DWORD dwsize = 0;
if (postparam.meminfo.bMulti) {
dwsize += DataToServer(PARTRETURN, strlen(PARTRETURN), bSend);
dwsize += DataToServer(m_strBlockStart.c_str(), m_strBlockStart.length(), bSend);
dwsize += DataToServer(PARTDISPFD, strlen(PARTDISPFD), bSend);
dwsize += DataToServer(PARTNAME, strlen(PARTNAME), bSend);
dwsize += DataToServer(PARTEQUATE, strlen(PARTEQUATE), bSend);
dwsize += DataToServer(PARTQUOTES, strlen(PARTQUOTES), bSend);
dwsize += DataToServer(postparam.strkey.c_str(), postparam.strkey.length(), bSend);
dwsize += DataToServer(PARTQUOTES, strlen(PARTQUOTES), bSend);
dwsize += DataToServer(PARTRETURN, strlen(PARTRETURN), bSend);
while(!postparam.value->MFEof()) {
char buffer[1024] = {0};
size_t size = postparam.value->MFRead(buffer, 1, 1024);
dwsize += DataToServer(buffer, size, bSend);
}
}
else {
dwsize += DataToServer(PARTSPLIT, strlen(PARTSPLIT), bSend);
dwsize += DataToServer(postparam.strkey.c_str(), postparam.strkey.length(), bSend);
dwsize += DataToServer(PARTEQUATE, strlen(PARTEQUATE), bSend);
while(!postparam.value->MFEof()) {
char buffer[1024] = {0};
size_t size = postparam.value->MFRead(buffer, 1, 1024);
dwsize += DataToServer(buffer, size, bSend);
}
} return dwsize;
}

以上是发送普通Post数据的方法。

当中分为是否须要以MultiiPart形式发送,还是以普通形式发送。MultiPart形式之前已经说过。而普通Post数据形式则是无约束的。我将该数据时拼装成Name1=Value1&Name2=Value2的形式发送的。

对于MultiParg类型的Post。我们使用WireShark截取发送包

发送普通Post数据的WireShark截包为

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYnJlYWtzb2Z0d2FyZQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" align="middle" alt="">

最后我们看下使用的样例

    HttpRequestFM::CHttpTransByPost* p = new HttpRequestFM::CHttpTransByPost();
ToolsInterface::IMemFileOperation* pMemOp = new MemFileOperation::CMemOperation(); p->SetOperation(pMemOp);
p->SetProcessCallBack(ProcssCallback);
p->SetUrl(BIGFILEURL); FMParams params; FMParam param1;
param1.postasfile = false;
param1.strkey = "key1";
param1.meminfo.bMulti = false;
MemFileOperation::CMemOperation mem1("value1", strlen("value1"));
param1.value = &mem1;
params.push_back(param1); FMParam param2;
param2.postasfile = false;
param2.strkey = "key2";
param2.meminfo.bMulti = true;
//sprintf_s(param2.fileinfo.szfilename, sizeof(param2.fileinfo.szfilename), "2.bin");
MemFileOperation::CFileOperation file2("F:/2.bin");
param2.value = &file2;
params.push_back(param2); FMParam param3;
param3.strkey = "key3";
//param3.meminfo.bMulti = true;
param3.postasfile = true;
sprintf_s(param3.fileinfo.szfilename, sizeof(param3.fileinfo.szfilename), "3.bin");
MemFileOperation::CFileOperation file3("F:/1.bin");
param3.value = &file3;
params.push_back(param3); p->SetPostParam(params);
p->Start();

param1是以普通Post数据格式传输的參数。param2的value是从F:/2.bin文件里读取的。可是其仅仅是MultiPart形式上传的数据。而非上传文件。param3是要求上传文件F:/1.bin文件到server上为3.bin。

通过不同的组合。我们能够同一时候上传多个文件。比方我们将上例中的param2做略微变化,那就是将文件上传到其各自的能力server。上传多个文件在同一时间实现功能。

版权声明:本文博主原创文章,博客,未经同意不得转载。

达到HTTP合约Get、Post和文件上传功能——采用WinHttp介面的更多相关文章

  1. iOS 的 Safari 文件上传功能详解

    iOS 6 给 Safari 浏览器带来的另外一个功能是文件上传,终于 Safari 终于支持 input 输入框的文件类型了,并且还支持 HTML媒体捕获(HTML Media Capture). ...

  2. [php基础]PHP.INI配置:文件上传功能配置教程

    昨天分享了在PHP网站开发中如何在php.ini中配置实现session功能的PHP教程,今天继续分享在利用PHP实现文件上传功能时几点关键php.ini的配置. 说到在php.ini中的文件上传的配 ...

  3. 学习ASP.NET Core Razor 编程系列十五——文件上传功能(三)

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  4. 学习ASP.NET Core Razor 编程系列十四——文件上传功能(二)

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  5. django之 文件上传功能(缺陷:无法改存放目录)

    百度云盘:django之 文件上传功能(缺陷:无法改存放目录)

  6. 文件/大文件上传功能实现(JS+PHP)全过程

    文件/大文件上传功能实现(JS+PHP) 参考博文:掘金-橙红年代 前端大文件上传 路漫漫 其修远 PHP + JS 实现大文件分割上传 本文是学习文件上传后的学习总结文章,从无到有实现文件上传功能, ...

  7. 使用element的upload组件实现一个完整的文件上传功能(上)

    说到标题就有点心塞了,前段时间项目上需要实现一个文件上传的功能,然后就咔咔的去用了element的upload组件,不用不知道一用吓一跳哇. 在使用的过程中遇到了很多让意想不到的问题,后来也因为时间问 ...

  8. 使用element的upload组件实现一个完整的文件上传功能(下)

    本篇文章是<使用element的upload组件实现一个完整的文件上传功能(上)>的续篇. 话不多说,接着上一篇直接开始 一.功能完善—保存表格中每一列的文件列表状态 1.思路 保存表格中 ...

  9. SpringBoot实现文件上传功能详解

    目录 利用SpirngBoot实现文件上传功能 零.本篇要点 一.SpringBoot对文件处理相关自动配置 二.处理上传文件MultipartFile接口 三.SpringBoot+Thymelea ...

随机推荐

  1. C#之任务,线程和同步

    1 概述 对于所有需要等待 的操作,例 如 ,因 为文件 . 数据库或网络访 问都需要一定 的时间,此 时就可以启 动一个新线程,同时完成其他任务,即使是处理密集型的任务,线程也是有帮助的. 2 Pa ...

  2. CentOS 网络设置修改

    环境: 系统硬件:vmware vsphere (CPU:2*4核,内存2G) 系统版本:Centos-6.5-x86_64 路由器网关:192.168.1.1 步骤: 1.查看网络MAC地址 [ro ...

  3. Kafka学习(一)配置及简单命令使用

    一. Kafka中的相关概念的介绍 Kafka是一个scala实现的分布式消息中间件,当中涉及到的相关概念例如以下: Kafka中传递的内容称为message(消息),message 是通过topic ...

  4. lightoj1038(期望dp)

    给定一个数字d,随机选择一个d的约数,然后让d除以这个约数,形成新的d,不断继续这个步骤,知道d=1为止, 要我们求将d变为1的期望次数 设d1,d2...dj是除以约数后,形成的行的d,且dj==d ...

  5. 发展合作-ASP.Net传递页面之间的值

    在合作开发中,在页面串传值的时候,遇到了一些困难.在网上搜罗了一下,发现好多的传值方式,能够简单地分下面三种. 一. URL传值 原页面的值放到目标页面的URL中.然后通过QueryString方法获 ...

  6. nyoj 228 士兵杀死(五岁以下儿童)【树状数组】

    分析:这个问题问的是,因为它是一个单独的更新.因此,让我们更新,然后在c[i]表现为1~i之间,还原之后看起来像一个. #include <cstdio> #include <cst ...

  7. ZOJ--3631--Watashi&#39;s BG【枚举】

    链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=4777 题意:有n天,告诉你每天的花费,别人给你一笔资金m,你自己也有一部 ...

  8. AM335x(TQ335x)学习笔记——GPIO关键驱动移植

    或按照S5PV210学习秩序.我们首先解决的关键问题.TQ335x有六个用户按钮,每个上.下.剩下.对.Enter和ESC. 我想开始学习S5PV210当同一,写输入子系统驱动器的关键问题要解决,但浏 ...

  9. 怎样配置nginx同一时候执行不同版本号的php-fpm

    在/usr/local/php/etc/php-fpm.conf里找到 listen = 127.0.0.1:9000 将port9000改动为9001 在对应的nginx配置里也做相同的port改动

  10. jquery处理页面元素

    处理父级页面中的元素 $(parent.document).find('#hidSendPerson').val(val);$(parent.document).find('#btnGo').clic ...