我有一个使用XMLHttpRequest下载二进制资源的网页。
在Firefox和Gecko中,我可以使用responseText来获取字节,即使字节流包含二进制零。我可能需要使用overrideMimeType()
强制mimetype来实现这一点。但是,在IE中,responseText不起作用,因为它似乎终止于第一个零。如果读取100,000个字节,而字节7是二进制零,则只能访问7个字节。 IE的XMLHttpRequest公开了responseBody
属性来访问字节。我已经看到一些帖子暗示直接从Javascript以任何有意义的方式访问这个属性是不可能的。这听起来很疯狂。
xhr.responseBody
,因此明显的解决方法是在网页中的VBScript中定义一个方法,然后从Javascript调用该方法。有关示例,请参阅jsdap。 编辑:请勿使用此VBScript !!
var IE_HACK = (/msie/i.test(navigator.userAgent) &&
!/opera/i.test(navigator.userAgent));
// no no no! Don't do this!
if (IE_HACK) document.write('<script type="text/vbscript">\n\
Function BinaryToArray(Binary)\n\
Dim i\n\
ReDim byteArray(LenB(Binary))\n\
For i = 1 To LenB(Binary)\n\
byteArray(i-1) = AscB(MidB(Binary, i, 1))\n\
Next\n\
BinaryToArray = byteArray\n\
End Function\n\
</script>');
var xml = (window.XMLHttpRequest)
? new XMLHttpRequest() // Mozilla/Safari/IE7+
: (window.ActiveXObject)
? new ActiveXObject("MSXML2.XMLHTTP") // IE6
: null; // Commodore 64?
xml.open("GET", url, true);
if (xml.overrideMimeType) {
xml.overrideMimeType('text/plain; charset=x-user-defined');
} else {
xml.setRequestHeader('Accept-Charset', 'x-user-defined');
}
xml.onreadystatechange = function() {
if (xml.readyState == 4) {
if (!binary) {
callback(xml.responseText);
} else if (IE_HACK) {
// call a VBScript method to copy every single byte
callback(BinaryToArray(xml.responseBody).toArray());
} else {
callback(getBuffer(xml.responseText));
}
}
};
xml.send('');
这是真的吗?最好的方法?复制每个字节?对于不太高效的大型二进制流。
还有一种可能的技术使用ADODB.Stream,它是一个与MemoryStream等效的COM。 See here举个例子。它不需要VBScript,但需要单独的COM对象。
if (typeof (ActiveXObject) != "undefined" && typeof (httpRequest.responseBody) != "undefined") {
// Convert httpRequest.responseBody byte stream to shift_jis encoded string
var stream = new ActiveXObject("ADODB.Stream");
stream.Type = 1; // adTypeBinary
stream.Open ();
stream.Write (httpRequest.responseBody);
stream.Position = 0;
stream.Type = 1; // adTypeBinary;
stream.Read.... /// ???? what here
}
但是这样做不会很好 - 这些天大多数机器都禁用了ADODB.Stream。
在IE8开发人员工具中 - 相当于Firebug的IE - 我可以看到responseBody是一个字节数组,我甚至可以看到字节本身。数据就在那里。我不明白为什么我无法达到它。
我可以用responseText读取它吗?
提示吗? (除了定义VBScript方法)
答案 0 :(得分:14)
是的,我在IE中通过XHR读取二进制数据的答案是使用VBScript注入。起初这对我来说是令人讨厌的,但是,我认为它只是一个与浏览器相关的代码。
(常规XHR和responseText在其他浏览器中工作正常;您可能必须使用XMLHttpRequest.overrideMimeType()
强制mime类型。这在IE上不可用。
这就是我在IE中得到像responseText
一样的东西,即使对于二进制数据也是如此。
首先,将一些VBScript作为一次性注入,如下所示:
if(/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) {
var IEBinaryToArray_ByteStr_Script =
"<!-- IEBinaryToArray_ByteStr -->\r\n"+
"<script type='text/vbscript' language='VBScript'>\r\n"+
"Function IEBinaryToArray_ByteStr(Binary)\r\n"+
" IEBinaryToArray_ByteStr = CStr(Binary)\r\n"+
"End Function\r\n"+
"Function IEBinaryToArray_ByteStr_Last(Binary)\r\n"+
" Dim lastIndex\r\n"+
" lastIndex = LenB(Binary)\r\n"+
" if lastIndex mod 2 Then\r\n"+
" IEBinaryToArray_ByteStr_Last = Chr( AscB( MidB( Binary, lastIndex, 1 ) ) )\r\n"+
" Else\r\n"+
" IEBinaryToArray_ByteStr_Last = "+'""'+"\r\n"+
" End If\r\n"+
"End Function\r\n"+
"</script>\r\n";
// inject VBScript
document.write(IEBinaryToArray_ByteStr_Script);
}
我正在使用的JS类读取二进制文件暴露了一个有趣的方法readCharAt(i)
,它在第i个索引处读取字符(实际上是一个字节)。我就是这样设置的:
// see doc on http://msdn.microsoft.com/en-us/library/ms535874(VS.85).aspx
function getXMLHttpRequest()
{
if (window.XMLHttpRequest) {
return new window.XMLHttpRequest;
}
else {
try {
return new ActiveXObject("MSXML2.XMLHTTP");
}
catch(ex) {
return null;
}
}
}
// this fn is invoked if IE
function IeBinFileReaderImpl(fileURL){
this.req = getXMLHttpRequest();
this.req.open("GET", fileURL, true);
this.req.setRequestHeader("Accept-Charset", "x-user-defined");
// my helper to convert from responseBody to a "responseText" like thing
var convertResponseBodyToText = function (binary) {
var byteMapping = {};
for ( var i = 0; i < 256; i++ ) {
for ( var j = 0; j < 256; j++ ) {
byteMapping[ String.fromCharCode( i + j * 256 ) ] =
String.fromCharCode(i) + String.fromCharCode(j);
}
}
// call into VBScript utility fns
var rawBytes = IEBinaryToArray_ByteStr(binary);
var lastChr = IEBinaryToArray_ByteStr_Last(binary);
return rawBytes.replace(/[\s\S]/g,
function( match ) { return byteMapping[match]; }) + lastChr;
};
this.req.onreadystatechange = function(event){
if (that.req.readyState == 4) {
that.status = "Status: " + that.req.status;
//that.httpStatus = that.req.status;
if (that.req.status == 200) {
// this doesn't work
//fileContents = that.req.responseBody.toArray();
// this doesn't work
//fileContents = new VBArray(that.req.responseBody).toArray();
// this works...
var fileContents = convertResponseBodyToText(that.req.responseBody);
fileSize = fileContents.length-1;
if(that.fileSize < 0) throwException(_exception.FileLoadFailed);
that.readByteAt = function(i){
return fileContents.charCodeAt(i) & 0xff;
};
}
if (typeof callback == "function"){ callback(that);}
}
};
this.req.send();
}
// this fn is invoked if non IE
function NormalBinFileReaderImpl(fileURL){
this.req = new XMLHttpRequest();
this.req.open('GET', fileURL, true);
this.req.onreadystatechange = function(aEvt) {
if (that.req.readyState == 4) {
if(that.req.status == 200){
var fileContents = that.req.responseText;
fileSize = fileContents.length;
that.readByteAt = function(i){
return fileContents.charCodeAt(i) & 0xff;
}
if (typeof callback == "function"){ callback(that);}
}
else
throwException(_exception.FileLoadFailed);
}
};
//XHR binary charset opt by Marcus Granado 2006 [http://mgran.blogspot.com]
this.req.overrideMimeType('text/plain; charset=x-user-defined');
this.req.send(null);
}
conversion code由Miskun提供。
非常快,效果很好。
我使用此方法从Javascript读取和提取zip文件,以及在Javascript中读取和显示EPUB文件的类中。表现非常合理。 500kb文件大约半秒钟。
答案 1 :(得分:11)
答案 2 :(得分:3)
我建议另外两个(快速)选项:
首先,你可以使用 ADODB.Recordset 将字节数组转换为字符串。我猜这个对象比ADODB.Stream更常见,ADODB.Stream由于安全原因经常被禁用。对于500kB文件,此选项非常快,小于 30ms 。
其次,如果Recordset组件不可访问,则有一个技巧可以从Javascript 访问字节数组数据。将xhr.responseBody发送到VBScript,将其传递给任何VBScript字符串函数(如CStr)(不花时间),然后将其返回给JS。您将得到一个奇怪的字符串,其字节连接成16位unicode(反向)。然后,您可以通过正则表达式将此字符串快速转换为可用的字节字符串,并使用基于字典的替换。对于500kB,需要 1s 。
为了进行比较,对于同一个500kB文件,通过循环逐字节转换需要几分钟,所以这是一个明智的选择:)在我一直使用的代码下面,插入到你的标题。然后使用xhr.responseBody调用函数 ieGetBytes 。
<!--[if IE]>
<script type="text/vbscript">
'Best case scenario when the ADODB.Recordset object exists
'We will do the existence test in Javascript (see after)
'Extremely fast, about 25ms for a 500kB file
Function ieGetBytesADO(byteArray)
Dim recordset
Set recordset = CreateObject("ADODB.Recordset")
With recordset
.Fields.Append "temp", 201, LenB(byteArray)
.Open
.AddNew
.Fields("temp").AppendChunk byteArray
.Update
End With
ieGetBytesADO = recordset("temp")
recordset.Close
Set recordset = Nothing
End Function
'Trick to return a Javascript-readable string from a VBScript byte array
'Yet the string is not usable as such by Javascript, since the bytes
'are merged into 16-bit unicode characters. Last character missing if odd length.
Function ieRawBytes(byteArray)
ieRawBytes = CStr(byteArray)
End Function
'Careful the last character is missing in case of odd file length
'We Will call the ieLastByte function (below) from Javascript
'Cannot merge directly within ieRawBytes as the final byte would be duplicated
Function ieLastChr(byteArray)
Dim lastIndex
lastIndex = LenB(byteArray)
if lastIndex mod 2 Then
ieLastChr = Chr( AscB( MidB( byteArray, lastIndex, 1 ) ) )
Else
ieLastChr = ""
End If
End Function
</script>
<script type="text/javascript">
try {
// best case scenario, the ADODB.Recordset object exists
// we can use the VBScript ieGetBytes function to transform a byte array into a string
var ieRecordset = new ActiveXObject('ADODB.Recordset');
var ieGetBytes = function( byteArray ) {
return ieGetBytesADO(byteArray);
}
ieRecordset = null;
} catch(err) {
// no ADODB.Recordset object, we will do the conversion quickly through a regular expression
// initializes for once and for all the translation dictionary to speed up our regexp replacement function
var ieByteMapping = {};
for ( var i = 0; i < 256; i++ ) {
for ( var j = 0; j < 256; j++ ) {
ieByteMapping[ String.fromCharCode( i + j * 256 ) ] = String.fromCharCode(i) + String.fromCharCode(j);
}
}
// since ADODB is not there, we replace the previous VBScript ieGetBytesADO function with a regExp-based function,
// quite fast, about 1.3 seconds for 500kB (versus several minutes for byte-by-byte loops over the byte array)
var ieGetBytes = function( byteArray ) {
var rawBytes = ieRawBytes(byteArray),
lastChr = ieLastChr(byteArray);
return rawBytes.replace(/[\s\S]/g, function( match ) {
return ieByteMapping[match]; }) + lastChr;
}
}
</script>
<![endif]-->
答案 3 :(得分:1)
非常感谢这个解决方案。 VbScript中的BinaryToArray()函数非常适合我。
顺便说一下,我需要二进制数据将它提供给Applet。 (不要问我为什么Applet不能用于下载二进制数据。长话短说..奇怪的MS身份验证不能通过applets(URLConn)调用。在用户支持代理的情况下特别奇怪)
Applet需要一个来自这个数据的字节数组,所以这就是我要做的事情:
String[] results = result.toString().split(",");
byte[] byteResults = new byte[results.length];
for (int i=0; i<results.length; i++){
byteResults[i] = (byte)Integer.parseInt(results[i]);
}
然后可以将字节数组转换为bytearrayinputstream以进行进一步处理。
答案 4 :(得分:1)
您还可以创建一个代理脚本,该脚本会转到您要求的地址。 base64就是它。然后,您只需将查询字符串传递给代理脚本,该脚本会告诉它地址。在IE中,您必须在JS中手动执行base64。但是,如果您不想使用VBScript,这是一种方法。
我将此用于my GameBoy Color emulator。
这是执行魔术的PHP脚本:
<?php
//Binary Proxy
if (isset($_GET['url'])) {
try {
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, stripslashes($_GET['url']));
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
curl_setopt($curl, CURLOPT_POST, false);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 30);
$result = curl_exec($curl);
curl_close($curl);
if ($result !== false) {
header('Content-Type: text/plain; charset=ASCII');
header('Expires: '.gmdate('D, d M Y H:i:s \G\M\T', time() + (3600 * 24 * 7)));
echo(base64_encode($result));
}
else {
header('HTTP/1.0 404 File Not Found');
}
}
catch (Exception $error) { }
}
?>
答案 5 :(得分:1)
我尝试下载文件,然后使用CAPICOM.DLL对其进行签名。我做的唯一方法是注入一个执行下载的VBScript函数。这是我的解决方案:
if(/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) {
var VBConteudo_Script =
'<!-- VBConteudo -->\r\n'+
'<script type="text/vbscript">\r\n'+
'Function VBConteudo(url)\r\n'+
' Set objHTTP = CreateObject("MSXML2.XMLHTTP")\r\n'+
' objHTTP.open "GET", url, False\r\n'+
' objHTTP.send\r\n'+
' If objHTTP.Status = 200 Then\r\n'+
' VBConteudo = objHTTP.responseBody\r\n'+
' End If\r\n'+
'End Function\r\n'+
'\<\/script>\r\n';
// inject VBScript
document.write(VBConteudo_Script);
}
答案 6 :(得分:1)
谢谢你的这篇文章。
我发现此链接很有用:
特别是这部分:
</script>
<script language="VBScript">
Function BinaryToString(Binary)
Dim I,S
For I = 1 to LenB(Binary)
S = S & Chr(AscB(MidB(Binary,I,1)))
Next
BinaryToString = S
End Function
</script>
我已将此添加到我的htm页面。 然后我从我的javascript调用此函数:
responseText = BinaryToString(xhr.responseBody);
适用于IE8,IE9,IE10,FF&amp;铬。