《采用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. JAVA 计算地球上任意两点(经纬度)距离

    /** * 计算地球上任意两点(经纬度)距离 * * @param long1 * 第一点经度 * @param lat1 * 第一点纬度 * @param long2 * 第二点经度 * @para ...

  2. Cordova CLI源码分析(三)——初始化

    本部分主要涉及以下三个文件 1 cli.js 2 cordova.js 3 events.js 通过前一篇package.json的分析,可以知道,当命令行执行cordova相关命令时,首先调用mai ...

  3. vb.net WPF webbrowser window.close 关闭后不触发 WindowClosing 事件 WNDPROC解决方式

     vb.net WPF webbrowser window.close 关闭后不触发 WindowClosing 事件 WNDPROC解决方式 #Region "WPF 当浏览器窗体关闭 ...

  4. NPOI 创建Excel,数据读取与写入

    <1> using System; using System.Collections.Generic; using System.Linq; using System.Web; using ...

  5. 【翻译】Why JavaScript Is and Will Continue to Be the First Choice of Programmers

    花费2半小时,那么最终会被翻译.假设有问题,请提出,毕竟,自己的6不超过级别. 附加链接 Why JavaScript Is and Will Continue to Be the First Cho ...

  6. SAP ABAP计划 SY-REPID与SY-CPROG差异

    首先,它的两个解释   sy-repid is the name of the current program.  "当前程序的程序名                             ...

  7. PHP --字符串编码转换(自动识别原编码)

    /** * 对数据进行编码转换 * @param array/string $data 数组 * @param string $output 转换后的编码 */ function array_icon ...

  8. Cnblogs API

    http://wcf.open.cnblogs.com/news/help http://wcf.open.cnblogs.com/blog/help/ 不支持用户登陆

  9. Cocos2d-x学习笔记(五岁以下儿童) 精灵两种方式播放动画

     这几天在看控件类,临时没有想好实际运用的方向.单纯的创建网上已经有非常多这方面的样例,我就不写了.接下来是学习精灵类.精灵类若是单独学习也是非常easy.于是我加了一些有关动画方面的知识点与精灵 ...

  10. Android 混淆proguard的实现(图文)

    1.  在Eclipse中的project编译执行后,在文件夹bin以下有生成一些文件,当中classes.dex是未经过混淆生成的.而我们要混淆的话,就要又一次生成一个混淆过的classes.dex ...