BlobBuilder ruins binary data - javascript

I have a problem with BlobBuilder (Chrome11)
I try to obtain an image from server with XHR request. Then i try to save it to local FS with BlobBuilder / FileWriter. Every example on the internet is about working with text/plain mime type and these examples work fine. But when i try to write binary data obtained with XHR, file size becomes about 1.5-2 times bigger than the original file size. And it cannot be viewed in Picasa / Eye Of Gnome.
var xhr = new XMLHttpRequest();
var photoOrigUrl = 'http://www.google.ru/images/nav_logo72.png';
xhr.open('GET', photoOrigUrl, true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
var contentType = xhr.getResponseHeader('Content-type');
fsLink.root.getFile('nav_logo72.png', {'create': true}, function(fileEntry) {
fileEntry.createWriter(function(fileWriter) {
var BlobBuilderObj = new (window.BlobBuilder || window.WebKitBlobBuilder)();
BlobBuilderObj.append(xhr.responseText);
fileWriter.write(BlobBuilderObj.getBlob(contentType));
}, function(resultError) {
console.log('writing file to file system failed ( code ' + resultError.code + ')');
});
});
}
}
xhr.send();
fsLink exists, this is extension.

The problem is that BlobBuilder.append(xhr.responseText) is detecting its argument as a UTF-8 string, which is what XHR returns, and not binary data, which is what it really is. There's a couple of tricks to get the BlobBuilder reading it as binary data instead of string data:
var xhr = new XMLHttpRequest();
var photoOrigUrl = 'http://www.google.ru/images/nav_logo72.png';
xhr.open('GET', photoOrigUrl, true);
// CHANGE 1: This stops the browser from parsing the data as UTF-8:
xhr.overrideMimeType('text/plain; charset=x-user-defined');
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
var contentType = xhr.getResponseHeader('Content-type');
fsLink.root.getFile('nav_logo72.png', {'create': true}, function(fileEntry) {
fileEntry.createWriter(function(fileWriter) {
// CHANGE 2: convert string object into a binary object
var byteArray = new Uint8Array(xhr.response.length);
for (var i = 0; i < xhr.response.length; i++) {
byteArray[i] = xhr.response.charCodeAt(i) & 0xff;
}
var BlobBuilderObj = new (window.BlobBuilder || window.WebKitBlobBuilder)();
// CHANGE 3: Pass the BlobBuilder an ArrayBuffer instead of a string
BlobBuilderObj.append(byteArray.buffer);
// CHANGE 4: not sure if it's needed, but keep only the necessary
// part of the Internet Media Type string
fileWriter.write(BlobBuilderObj.getBlob(contentType.split(";")[0]));
}, function(resultError) {
console.log('writing file to file system failed ( code ' + resultError.code + ')');
});
});
}
}
xhr.send();
This gave me a file with the same length as what xhr.getResponseHeader('Content-Length') suggests it should have been.

You can use xhr.responseType='arraybuffer' though:
BlobBuilder = window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder;
var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
xhr.responseType = 'arraybuffer';
xhr.onload = function(e) {
if (this.status == 200) {
var bb = new BlobBuilder();
bb.append(this.response); // Note: not xhr.responseText
var blob = bb.getBlob('image/png');
...
}
};
xhr.send();

I think Stoive is spot on but I want to point out that instead of BlobBuilder there is now Blob constructor available that will do the trick
var b = new Blob([byteArray.buffer], {'type': 'application/type'});
I think this is more in keeping with current standards. Thanks much Stoive, very helpful.

Btw XHR2 sets a better way for implementing my task:
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://www.google.ru/images/nav_logo72.png', true);
xhr.responseType = 'blob';
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
// xhr.responseBlob is needed blob data
}
}
xhr.send();
The only disappointment is that this is still a bug in Chrome: http://code.google.com/p/chromium/issues/detail?id=52486

XMLHttpRequest cannot load http://www.google.ru/images/nav_logo72.png. Origin file:// is not allowed by Access-Control-Allow-Origin.

Related

Byte array from backend is converted to something else on front-end

[Route("encrypted")]
[HttpGet]
public sbyte[] Encrypted()
{
var mm = System.IO.File.ReadAllBytes("C:\\test\\" + "fill.txt");
sbyte[] sbt = new sbyte[mm.Length];
Buffer.BlockCopy(mm, 0, sbt, 0, mm.Length);
return sbt;
}
when I hover over with mouse it shows following bytes (which is correct):
But when I check on the front-end (javascript). It becomes a different arrayBuffer:
Here is the front end code:
var xhr = new XMLHttpRequest();
xhr.open('GET', '/api/encrypted/', true);
xhr.responseType = 'arraybuffer'; //i have tried without this line too
xhr.onload = function (e) {
if (this.status === 200) {
console.log("received from server--------");
console.log(e.currentTarget.response);
console.log("received from server-------");
}
};
xhr.send();
You did not ask a specific question, but I think this might help.
Your controller action is responding with JSON. Dumping json to the console shows the same array values on the front-end as does dumping sbt to the console on the back-end. Here is the front-end code that dumps the values.
var xhr = new XMLHttpRequest();
xhr.open('GET', '/api/values', true);
xhr.responseType = 'json';
xhr.onload = function (e) {
if (this.status === 200) {
console.log("json");
const json = e.currentTarget.response;
console.log(json);
console.log("json");
}
};
So, you're sending a JSON array.
As an aside, here are some links about the arraybuffer response type.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int8Array
https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Sending_and_Receiving_Binary_Data

how to instantiate new file object javascript

I'm having troubles instantiating a new file object in javascript.
Here's the general gist of what I'm trying to do. There is client side code that expecting a "file" type object. I need to access the file that's located on the server (game.smc), download it to the local machine and feed it to the client side code.
I did some research and found that creating a new blob object is the first step. But in the code below the blob object remains null and is never getting populated. Does the path in the xhr.open need to have the entire url? Maybe i'm missing an entire concept here not sure.
var blob = null;
var xhr = new XMLHttpRequest();
xhr.open("GET", "/Roms/game.smc");
xhr.responseType = "blob";
xhr.onload = function()
{
blob = xhr.response;
}
xhr.send();
Once I can get the blob object populated I can then do this to convert it to a file object.
function blobToFile(theBlob, fileName) {
theBlob.lastModifiedDate = new Date();
theBlob.name = fileName;
return theBlob;
}
This is what I ended up doing. Shows how to get the blob object as well as convert it to a file type.
function autoLoadGame(fileName) {
var gameLocation = '/Content/Roms/Snes/' + fileName;
var blob = null;
var xhr = new XMLHttpRequest();
xhr.open("GET", gameLocation, true);
xhr.onreadystatechange = function () {
if (xhr.readyState == XMLHttpRequest.DONE) {
var blob = xhr.response;
var file = new File([blob], fileName, { type: '', lastModified: Date.now() });
snes_readfile(file);
}
}
xhr.responseType = "blob";
xhr.send();
}

Reading XML returned from AJAX

I have defined this XML file
<?xml version="1.0" encoding="UTF-8"?>
<root>
<town>
<area>20000</area>
<population>10000</population>
</town>
</root>
with AJAX I load this and I try to parse it.
<script>
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function(){
if(this.readyState == 4 && this.status == 200){
myFunctionParsing(this);
}};
xhttp.open("GET", "towns.xml", false);
xhttp.send();
function myFunctionParsing(xml)
{
var xmlDoc = xml.responseXML;
var nodes = xmlDoc.getElementsByTagName("town");
var n = nodes[0].childNodes[0];
console.log(n.tagName);
var aDiv = document.getElementById("area");
aDiv.innerHTML = n.nodeValue;
}
With that script I want to write in div value of node "area" in some div with name "area".
Why can not I write this value? Why n.tagName is undefined?
A couple things:
I am using MDN's XMLHttpRequest example, which shows how to override the mime type to force it to parse the response as XML. That way you don't have to do responseXML.
querySelector is great! Use it. No need for getElementsByTagName.
function parseXML(xml) {
var area = xml.querySelector('town area');
console.log(area.textContent); // => 20000
}
var url = 'https://gist.githubusercontent.com/gabrielflorit/c618f26ac367fbaf91846efe73913c23/raw/b3c9c3e8d0f1dc747006103442b3b19bf7c91d9c/town.xml';
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
// If specified, responseType must be empty string or "document"
xhr.responseType = 'document';
// overrideMimeType() can be used to force the response to be parsed as XML
xhr.overrideMimeType('text/xml');
xhr.onload = function () {
if (xhr.readyState === xhr.DONE) {
if (xhr.status === 200) {
parseXML(xhr.response);
}
}
};
xhr.send(null);

Getting blob gives 404 error

could someone prompt me - how to save "blob in memory" to a file using Java Script?
e.g. I have in the page next blob-image:
<img src="blob:https%3A//drive.google.com/851b979c-92e9-4ef2-9152-8935f7793630" class="g-img">
and I need to save this blob to a file (png/jpg).
The next code just gives:
GET blob:https%3A//drive.google.com/851b979c-92e9-4ef2-9152-8935f7793630 404 (Not Found)
so it seems, usual way to get the blobs doesn't work here.
Is there any workaround to save this blob-images from browser memory to a file, or, saying more exactly - to get them as a real blob using only "src" tag value?
Thank you.
var srcEl = evt.srcElement;
var CurI = document.getElementsByClassName('g-img');
[].forEach.call(CurI, function (el) {
var xhr = new XMLHttpRequest();
xhr.open('GET', el.src, true);
xhr.responseType = 'arraybuffer'; // xhr.responseType = 'blob';
xhr.onload = function(e) {
if (this.status == 200) {
var myBlob = this.response;
var reader = new window.FileReader();
reader.readAsDataURL(myBlob);
console.log(myBlob);
}
};
xhr.send();
console.log(el.src);
// saveAs(myBlob, 'my2image.png');
}
p.s. to use mediarecorder?

How to download and then upload the file?

Tried to use the following code, but it doesn't work properly:
// download the file first
var req = new XMLHttpRequest();
req.open('GET', url, false);
req.overrideMimeType('text/plain; charset=x-user-defined');
req.send(null);
if (req.status != 200) return '';
// upload the file
req.open("POST", "http://mysite.com/upload", false);
req.setRequestHeader("Content-Length", req.responseText.length);
req.sendAsBinary(req.responseText); // What should I pass here?
if (req.status != 200) return '';
return req.responseText;
sendAsBinary is firefox function.
Upd. Also I've tried to upload that as part of the form:
var response = req.responseText;
var formData = new FormData();
formData.append("file", response);
req.open("POST", "http://mysite.com/upload", false);
req.send(formData);
But still not full data is received by the server.
Finally I've used the approach with temp file:
var downloadCompleted = false;
// download the file first
var persist = Components.classes["#mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
.createInstance(Components.interfaces.nsIWebBrowserPersist);
// get OS temp folder
var file = Components.classes["#mozilla.org/file/directory_service;1"]
.getService(Components.interfaces.nsIProperties)
.get("TmpD", Components.interfaces.nsIFile);
file.append("temp.ext");
file.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0666);
var fURI = Services.io.newURI(url,null,null);
const nsIWBP = Components.interfaces.nsIWebBrowserPersist;
const flags = nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES;
persist.persistFlags = flags | nsIWBP.PERSIST_FLAGS_FROM_CACHE;
persist.progressListener = {
onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress) {
},
onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {
if (aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP) {
downloadCompleted = true; // file has been downloaded
}
}
}
persist.saveURI(fURI, null, null, null, "", file);
var thread = Components.classes["#mozilla.org/thread-manager;1"]
.getService(Components.interfaces.nsIThreadManager)
.currentThread;
while (!downloadCompleted) // emulate synchronous request, not recommended approach
thread.processNextEvent(true);
// upload the file
var stream = Components.classes["#mozilla.org/network/file-input-stream;1"]
.createInstance(Components.interfaces.nsIFileInputStream);
stream.init(file, 0x04 | 0x08, 0644, 0x04); // file is an nsIFile instance
// try to determine the MIME type of the file
var mimeType = "text/plain";
try {
var mimeService = Components.classes["#mozilla.org/mime;1"]
.getService(Components.interfaces.nsIMIMEService);
mimeType = mimeService.getTypeFromFile(file); // file is an nsIFile instance
}
catch(e) { /* just use text/plain */ }
var req = Components.classes["#mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Components.interfaces.nsIXMLHttpRequest);
req.open('POST', "http://mysite.com/upload", false);
req.setRequestHeader('Content-Type', mimeType);
req.send(stream);
// delete the file
file.remove(false);
You need to store the responseText in an intermediate variable before reusing the req object.
// download the file first
var req = new XMLHttpRequest();
req.open('GET', url, false);
req.overrideMimeType('text/plain; charset=x-user-defined');
req.send(null);
if (req.status != 200) return '';
var response = req.responseText;
// upload the file
req.open("POST", "http://mysite.com/upload", false);
req.setRequestHeader("Content-Length", response.length);
req.sendAsBinary(response);
if (req.status != 200) return '';
return req.responseText;
Update
Per the MDN page Using XMLHttpRequest, it looks like the above code won't work. Following is the proper way to get the binary response. In the end, you will have an array of unsigned integers which you could send back to the server and convert to binary. I think.
//req.responseType is only defined for FF6+
req.responseType = "arraybuffer";
req.send(null);
//req.response is for FF6+, req.mozResponseArrayBuffer is for FF < 6
var buffer = req.mozResponseArrayBuffer || req.response;
if (buffer) {
var byteArray = new Uint8Array(buffer);
}
Update 2
To submit the byteArray to a server, I would try something like the following untested, almost guaranteed not to work code.
req.open("POST", "http://mysite.com/upload", false);
req.setRequestHeader("Content-Length", byteArray.length);
//if this doesn't work, try byteArray.buffer
//if byteArray.buffer works, try skipping 'var byteArray = new Uint8Array(buffer);' altogether and just sending the buffer directly
req.send(byteArray);
Update 3
Could Using XMLHttpRequest from JavaScript modules / XPCOM components have anything to do with your issue?

Categories

Resources