Access开发培训
网站公告
·Access专家课堂QQ群号:151711184    ·Access快速开发平台下载地址及教程    ·欢迎加入Access专家课堂微信群!    ·如何快速搜索本站文章|示例|资料    
您的位置: 首页 > 技术文章 > Access365/SharePoint/互联网

VBA http post 上传 multipart/form-data 附件

时 间:2012-12-15 07:10:00
作 者:dbaseIIIer   ID:22003  城市:深圳
摘 要:寻遍世界各个网站,各个关键字都没有的情况下,自己终于测试出来了!也回复了google上2006年以来发表都还没有解决的问题!
正 文:

上传附件,原来逻辑也很简单,但是因为中文的问题,unicode 的问题,让上传变得复杂了!


方法一:我们可以用 WebBrower 控件来处理网页的提交动作的。我们可以通过 WebBrowser.Document.form(n).submit() 来提交网页表单的内容。不过这个方法的缺点是:

1. 这个控件是IE提供的原因,就有着浏览器是IE对的限制,譬如 user-agent 就一定是 ie ,

2. 交互的服务器也只能是 html 的,不能是wap 服务器(ie 不支持 WML 标识);而且 

3. WebBrowser 控件的内存耗用也比较大,对于同时多开的操作,就会让程序变慢,内存耗用激增!


所以,我针对的研究是用 XMLHttp 直接与服务器交互的 方法二。

这个方法的好处是,原理通用于任何标准的 网页服务器,不论 IIS, Apache, tomcat,或不论服务器的开发语言是 jsp , php , asp, asp.net。 我们开发的客户端也可以是 js, Access/VBA, Excel/VBA, php ... 都能正确使用!


对于上传 multipart/form-data 格式的附件,找了很久找到很多段的代码,标准就是要输出这样的结果:

--boundary
Content-Disposition: form-data; name="varname";

<变量值>
--boundary
Content-Disposition: form-data; name="uploadName"; filename="上传文件名称"
Content-Type:<文件内容>
--boundary--

看了下这个标准,会编程的人都会生成文本数据的了,不消一会就写好了,

Set http = New MSXML2.ServerXMLHTTP 
http.Open "POST", URL, False
postData = ......
http.setRequestHeader "Content-Type", "multipart/form-data; boundary=" & boundary
http.setRequestHeader "Content-Length", len( postData)
http.Send postData http.WaitForResponse
Debug.Print http.responseText

可是问题出现了,怎么服务器没有响应我的提交有变量提交的呢?测试了 iis, apache 都是失败!


打开了 Chrome 浏览器,用网页打开,提交后,在调试页面看到提交的内容,跟我提交的内容都是一模一样的呢,怎么我用 xmlhttp 提交的就没有结果的呢?玩了几6个小时,左对右对,都没有答案!


第二天,安装了一个http封包截取器(fiddler 挺好用的!)看看我提交的跟 Chrome 提交的区别在哪里!发现了重大问题!


重点1:我们提交的 postData 文本,都是用程序生成的,原来没经过处理,就是 Unicode 文本!但是 multipart/form-data 的标准就不该是 unicode 文本!所以我们需要转义!


在生成的 postData 文本,用了一个 strConv( postData, vbFromUnicode),去测试,还是不行!玩来玩去,把附件去掉,服务器就对我的提交,终于感受到有变量了!但是显示结果怎么还是有问题的???又玩了一段时间,发现重点2了。


重点2:我们查看结果一般会用 http.responseText ,可是如果我们输出网页的内容包含中文字,这就牵涉 unicode 问题了!所以我们看结果需要用  StrConv(http.responseBody, vbUnicode)


现在变量可以顺利提交了,但是附件呢?还是不行,每次加上附件的那几行来提交就出错了!所以我就怀疑 StrConv 把 binary 的数据都改变了,我写了几句话去测试:


    cFile = "e:\qq\0.jpg"
    Dim b() As Byte
    ReDim b(FileLen(cFile))
    ff = FreeFile
    Open cFile For Binary As #ff
    Get #ff, , b
    Close #ff
    
    Dim s1 As String, s2 As String
    s1 = b
    s2 = StrConv(s1, vbUnicode)
    s2 = StrConv(s2, vbFromUnicode)
    Debug.Print Len(s1), Len(s2)
结果发现, StrConv 转换为 vbUnicode 然后又转回来的话,是不会一样的! 连长度都不一样,我就没有比较内容了!所以:



重点3: 千万不要让 StrConv 去处理 postData文本 中 附件的部分!


其实当中,我也花了不少时间去研究怎么把二进制数据与这个提交文本合并在一起的,其中我用了不同的方式来合并文本和二进制数据,不过经过测试后,也知道重点1 和 重点3 之后,就引刃而解了!


重点4:附件读入为文本,不能当字符串处理!


找到有一段错误的代码是


nFile = FreeFile
Open strFileName For Binary As #nFile
strFile = String(LOF(nFile), " ")
Get #nFile, , strFile
Close #nFile
看似很正确的做法,不过,其实对于不同年代的 Basic 语言来说曾经是正确的。因为现在是 Unicode 年代,用 String函数生成的 字符串都是 Unicode的,所以 String( LOF(nFile), " ") 会生成了比原来文件大了一倍的空间!因为Unicode的字符是双字节的,读入文件的大小是描述 Byte 的。根据这个方法读进来的数据,在没有 Unicode 年代的 VB, VBA, Basic 都是对的!



这个年代正确的代码应该是:

nFile = FreeFile
Open strFileName For Binary As #nFile
strFile = String(LOF(nFile)\2+1, " ") '预留足够空间
Get #nFile, , strFile
strFile = LeftB( strFile, LOF(nFile)) '截取 正确的字节数
Close #nFile
Dim b() as Byte
nFile = FreeFile
Open strFileName For Binary As #nFile
ReDim b( LOF(nFile))
Get #nFile, , b
Close #nFile
strFile = b  '把数组变回字符串 才可以与其他字符串相加,或进行字符串函数处理
strFile = LeftB( strFile, UBound( b))  '去掉 VBA Chr(0) 的字串结尾标识
注意这两种方法最后的 LeftB,有别于Left 的!最后这个 strFile 就是读入文件的二进制文本版了!



重点5:注意回车换行符!

multipart/form-data 除了那个 boundary 字符串来识别变量,其实换行符也是特别需要 来辨识 变量名称和 附件内容的分隔的!里面的换行符,千万不要用 vbCrLf ,这是 Unicode 四个字节的,而 multipart/form-data 格式的文本都是 非Unicode 的 就需要两个字节的 换行符 (0x0D 0x0A)。 或者可以用 StrConv( vbCrLf, vbFromUnicode) 的结果都可以。


最后的调试代码就是:


    cFile = "E:\qq\test1.gif"
    Dim http As MSXML2.ServerXMLHTTP
    Set http = New MSXML2.ServerXMLHTTP
    
    http.Open "POST", URL, False
    
    Dim b() As Byte, strFile As String
            
    boundary = "WebKitFormBoundary" & RandomString(16)
    postData = "--" & boundary & vbCrLf
    postData = postData & "Content-Disposition: form-data; name=""albumid""" & vbCrLf
    postData = postData & vbCrLf & "天aaaaa123" & vbCrLf
            
    postData = postData & "--" & boundary & vbCrLf
    postData = postData & "Content-Disposition: form-data; name=""photo[]""; filename=""" & cFile & """" & vbCrLf
    postData = postData & "Content-Type: image/gif" & vbCrLf
    ReDim b(FileLen(cFile))
    ff = FreeFile
    Open cFile For Binary As #ff
    Get #ff, , b
    Close #ff
    strFile = b
    postData = StrConv(postData & vbCrLf, vbFromUnicode) & LeftB(s, UBound(b)) & ChrB(13) & ChrB(10)
            
    postData = postData & StrConv("--" & boundary & "--" & vbCrLf, vbFromUnicode)
            
    http.setRequestHeader "Content-Type", "multipart/form-data; boundary=" & boundary
    
    ReDim b(LenB(postData))
    b = postData
    'HexStr = ""         '这一段是我显示提交的数据
    'For i = 0 To UBound(b)
    '    Debug.Print IIf(b(i) < 16, "0", "") & Hex(b(i)); " ";
    '    HexStr = HexStr & IIf(b(i) < 32 or b(i)>127, ".", Chr(b(i)))
    '    If i Mod 16 = 15 Then Debug.Print "  " & HexStr: HexStr = ""
    'Next

    http.setRequestHeader "Content-Length", UBound(b)
    http.send b
    http.waitForResponse
    
    Debug.Print http.getAllResponseHeaders
    Debug.Print StrConv(http.responseBody, vbUnicode)
重点6:这个非常重要的是所有服务器的POST数据(包括multipart/form-data 这个格式)都是支持 Unicode 数据的提交的(我测试的是 Apache 服务器 2.03),但是这是自动检测的,意思是 如果你整个提交的内容里面都没有一个中文字,或任何双字节的内容,这个检测过程就判定这不是 Unicode 数据,那就变成你提交的文本是 ANSI 单字节文本,也就会造成你的 boundary 文本都检测不出来!意思是一个变量都检测不到!



重点7:最后我的提交 http.send 也是用了 byte array ,也就是免得 VBA 对字符串的处理又多了一个 ChrB(0)在结尾!


没有犯上以上的 7宗罪,必定把多个中文字+附件都能正确提交到任何服务器的了!

今天我就会为这些代码封装成类代码了,有成品再与各位发布!




开发者你们好,这是 地球信息思维开发者 dbaseIIIer (QQ325613888) 
更多的 Access/VBA 互联网交互技术,可以参考 本站内部栏目  互联网技术栏目
更多的思维在  新浪微博   Access贴吧  VisualPHP贴吧



Access软件网QQ交流群 (群号:483923997)       Access源码网店

常见问答:

技术分类:

相关资源:

专栏作家

关于我们 | 服务条款 | 在线投稿 | 友情链接 | 网站统计 | 网站帮助