XDomainRequest performance issue while receiving base64 image - javascript

I have two applications. One of them is an HTML based web application which works in IE9 mode and which receives scanned documents' images in base64 string format via XDomainRequest.
The other application is a Windows service written in C#. It listens requests on a port via HttpListener. Then it scans document and sends scanned image via HttpListenerResponse.
The scanned images will be processed by the web application on client side and then they will be uploaded to the server. So I have to do all job in javascript. There is not a server side solution (for now).
If I receive just one image it is not a big problem. But if the sender sends multiple images, base64 string becomes too large and it takes too much time.
I use below code to convert image to base64 string:
public static string ImageToBase64(Image image, System.Drawing.Imaging.ImageFormat format)
{
using (MemoryStream ms = new MemoryStream())
{
image.Save(ms, format);
byte[] imageBytes = ms.ToArray();
string base64String = Convert.ToBase64String(imageBytes);
return base64String;
}
}
And the below code is used to receive images:
if (xdrScanning.responseText != "") {
try {
json = JSON.parse(xdrScanning.responseText);
$('#scannedImage').attr('src', 'data:image/png;base64, ' + json[0].BASE64IMAGE);
} catch (e) {
xdrScanningError();
}
}
For all I know XDomainRequest receives string responses only.
So, what can I do to overcome that performance issue?

Related

Saving file with JavaScript File API results wrong encoding

I have a problem (or may be two) with saving files using HTML5 File API.
A files comes from the server as a byte array and I need to save it. I tried several ways described on SO:
creating blob and opening it in a new tab
creating a hidden anchor tag with "data:" in href attribute
using FileSaver.js
All approaches allow to save the file but with breaking it by changing the encoding to UTF-8, while the file (in current test case) is in ANSI. And it seems that I have to problems: at the server side and at the client side.
Server side:
Server side is ASP.NET Web API 2 app, which controller sends the file using HttpResponseMessage with StreamContent. The ContentType is correct and corresponds with actual file type.
But as can be seen on the screenshot below server's answer (data.length) is less then actual file size calculated at upload (file.size). Also here could be seen that HTML5 File object has yet another size (f.size).
If I add CharSet with value "ANSI" to server's response message's ContentType property, file data will be the same as it was uploaded, but on saving result file still has wrong size and become broken:
Client side:
I tried to set charset using the JS File options, but it didn't help. As could be found here and here Eli Grey, the author of FileUplaod.js says that
The encoding/charset in the type is just metadata for the browser, not an encoding directive.
which means, if I understood it right, that it is impossible to change the encoding of the file.
Issue result: at the end I can successfully download broken files which are unable to open.
So I have two questions:
How can I save file "as is" using File API. At present time I cannot use simple way with direct link and 'download' attribute because of serverside check for access_token in request header. May be this is the "bottle neck" of the problem?
How can I avoid setting CharSet at server side and also send byte array "as is"? While this problem could be hacked in some way I guess it's more critical. For example, while "ANSI" charset solves the problem with the current file, WinMerge shows that it's encoding is Cyrillic 'Windows-1251' and also can any other.
P.S. the issue is related to all file types (extensions) except *.txt.
Update
Server side code:
public HttpResponseMessage DownloadAttachment(Guid fileId)
{
var stream = GetFileStream(fileId);
var message = new HttpResponseMessage(HttpStatusCode.OK);
message.Content = new StreamContent(stream);
message.Content.Headers.ContentLength = file.Size;
message.Content.Headers.ContentType = new MediaTypeHeaderValue(file.ContentType)
{
// without this charset files sent with bigger size
// than they are as shown on image 1
CharSet = "ANSI"
};
message.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = file.FileName + file.Extension,
Size = file.Size
};
return message;
}
Client side code (TypeScript):
/*
* Handler for click event on download <a> tag
*/
private downloadFile(file: Models.File) {
var self = this;
this.$service.downloadAttachment(this.entityId, file.fileId).then(
// on success
function (data, status, headers, config) {
var fileName = file.fileName + file.extension;
var clientFile = new File([data], fileName);
// here's the issue ---^
saveAs(clientFile, fileName);
},
// on fail
function (error) {
self.alertError(error);
});
}
My code is almost the same as in answers on related questions on SO: instead of setting direct link in 'a' tag, I handle click on it and download file content via XHR (in my case using Angularjs $http service). Getting the file content I create a Blob object (in my case I use File class that derives from Blob) and then try to save it using FileSaver.js. I also tried approach with encoded URL to Blob in href attribute, but it only opens a new tab with a file broken the same way. I found that the problem is in Blob class - calling it's constructor with 'normal' file data I get an instance with 'wrong' size as could be seen on first two screenshots. So, as I understand, my problem not in the way I try to save my file, but in the way I create it - File API

Browser crashes while downloading large size files

I have a web api that reads a file from azure and downloads it into a byte array. The client receives this byte array and downloads it as pdf. This does not work well with large files.
I am not able to figure out how can I send the bytes in chunks from web api to client.
Below is the web api code which just returns the byte array to client:
CloudBlockBlob blockBlob = container.GetBlockBlobReference(fileName);
blockBlob.FetchAttributes();
byte[] data = new byte[blockBlob.Properties.Length];
blockBlob.DownloadToByteArray(data, 0);
return report;
Client side code gets the data when ajax request completes, creates a hyperlink and set its download attribute which downloads the file:
var a = document.createElement("a");
a.href = 'data:application/pdf;base64,' + data.$value;;
a.setAttribute("download", filename);
The error occurred for a file of 1.86 MB.
The browser displays the message:
Something went wrong while displaying the web page.To continue, reload the webpage.
The issue is most likely your server running out of memory on these large files. Don't load the entire file into a variable only to then send it out as the response. This causes a double download, your server has to download it from azure storage and keep it in memory, then your client has to download it from the server. You can do a stream to stream copy instead so memory is not chewed up. Here is an example from your WebApi Controller.
public async Task<HttpResponseMessage> GetPdf()
{
//normally us a using statement for streams, but if you use one here, the stream will be closed before your client downloads it.
Stream stream;
try
{
//container setup earlier in code
var blockBlob = container.GetBlockBlobReference(fileName);
stream = await blockBlob.OpenReadAsync();
//Set your response as the stream content from Azure Storage
response.Content = new StreamContent(stream);
response.Content.Headers.ContentLength = stream.Length;
//This could change based on your file type
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
}
catch (HttpException ex)
{
//A network error between your server and Azure storage
return this.Request.CreateErrorResponse((HttpStatusCode)ex.GetHttpCode(), ex.Message);
}
catch (StorageException ex)
{
//An Azure storage exception
return this.Request.CreateErrorResponse((HttpStatusCode)ex.RequestInformation.HttpStatusCode, "Error getting the requested file.");
}
catch (Exception ex)
{
//catch all exception...log this, but don't bleed the exception to the client
return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Bad Request");
}
finally
{
stream = null;
}
}
I have used (almost exactly) this code and have been able to download files well over 1GB in size.

IE 9/10/11 not rendering images after some unknown threshold

I am building an app that pulls files from SharePoint 2013 or SharePoint 2010 for view in HTML. In C#, files are pulled out of SharePoint (multipage documents like Word, Excel, PDF, TIFF, etc), then are fed into various 3rd party software (DataLogics and Aspose) - which break the documents down into their individual pages, then streams the individual pages to the browser in PNG format.
So in HTML, we have an img element whose src is set to a specific URL in an ASHX service. The ASHX service grabs the file out of SharePoint and, based on query string params, returns the desired page as a Stream.
Here is how we shoot it back:
[WebService(Namespace = "url")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class FileTransfer : IHttpHandler, IReadOnlySessionState
{
public void ProcessRequest(HttpContext context)
var stream = GetStream(context.Request);
int chunkSize = 2097152; //2MB
byte[] chunk = new byte[chunkSize];
int bytesRead = 0;
do {
bytesRead = stream.Read(chunk, 0, chunkSize);
HttpContext.Current.Response.OutputStream.Write(chunk, 0, bytesRead);
}
while (bytesRead > 0);
}
This works perfectly 100% of the time in any browser when the file we are breaking down comes directly from SharePoint.
We also provide a feature where the user can upload a document. This is where the problem comes in. Uploaded documents are not saved in SharePoint. Instead their data is stored in SessionState until the user chooses to save. Files are uploaded to an ASMX service, then the browser requests their individual pages via the above ASHX.
Files are uploaded like this in an ASMX service:
[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
Public object Upload()
{
var request = HttpContext.Current.Request;
if (request.Files.Count == 1)
{
var uniqueId = request["uniqueId"];
var file = request.Files[0];
using (var memoryStream = new MemoryStream())
{
file.InputStream.CopyTo(memoryStream);
docInfo = UploadItem(uniqueId, pageNum, memoryStream.ToArray());
}
}
}
UploadItem adds the uniqueId and byte[] to SessionState.
Files are sent from javascript like this (FileUpload being tied to the change event of an input of type=file):
this.FileUpload = function (files) {
var upload = new XMLHttpRequest();
upload.onreadystatechange = () => {
if (this._curUploadRequest.readyState == 4) {
// handle response
}
};
UpdateFormDigest((<any>window)._spPageContextInfo.webServerRelativeUrl,(<any>window)._spFormDigestRefreshInterval);
var data = new FormData();
data.append("uniqueId", uniqueId);
data.append("pageNum", pageNum);
data.append("data", files[0]);
upload.open('POST', "myurl");
upload.setRequestHeader("X-RequestDigest", $("#__REQUESTDIGEST").val());
upload.send(data);
};
Now we come to the actual bug.
Images are rendered using:
<img src="url to ASHX service" />
In FireFox and Chrome, page images from uploaded documents always show up just fine. But in IE (9, 10, or 11), it renders only the first portion of them, then shows broken image icons on the image placeholders. For these broken images, the NET tab of IE shows it received 0kb and the error event is hit. But if I put a breakpoint in the ASHX just before it returns the stream, it always has a size.
More interestingly, if you take the url that the src is pointed to, open a new window and paste it in, the image shows up just fine.
I even tried to load the images in javascript first like this:
var img = new Image();
img.onload = function(){
// use jquery to append image to page
};
img.src = "url to ASHX service";
In this scenario, Chrome and Firefox work fine as usual, but IE fails again. Except this way, the NET tab of IE shows it received the correct size kb in response. However, it still shows the broken image icon and won't render images to the screen after some unknown threshold. The first several images come back, but once one breaks, all of the rest break.
I also modified the ASHX service to return base64 data instead of a stream, then bound the base64 to the src. In the debugger you can see the base64 assigned to the src of the img elements that show the broken image icon. So the data is there for sure, but IE just isn't rendering it...
I tried to recreate this problem outside of our SharePoint environment in this fiddle using knockout JS. Basically, I grab a ton of big images and throw them on the screen with each button click. But it works just fine. It works perfectly if I use jQuery too.
http://jsfiddle.net/bsdez92f/
Not sure where to go from here.
Any ideas?
So it turns out that the image size was causing a problem. I scaled the images down to thumbnail size on the server side and returned that to the browser. All is working fine at this point.

Want to send images using node.js and socket.io in android

I am Creating a chat app between two users now I can do Simple text chat with different users using node.js and socket.io. Now problem arises here as I have to send image in chat application and after searching for whole long day I am not able to get perfect node.js in which I can send image in chat app. So I want to know is it possible to send image using node.js. Here is my simple node.js file for sending simple text message from one user to another.
socket.on('privateMessage', function(data) {
socket.get('name', function (err, name) {
if(!err) {
// get the user from list by its name to get its socket,
// then emit event privateMessage
// again here we want to make you clear
// that every single client connection has its own
// unique SOcket Object, we need to get this Socket object
// to communicate with every other client. The socket variable
// in this scope is the client who wants to send the private
// message but the socket of the receiver is not know.
// Get it from the saved list when connectMe handlers gets called
// by each user.
onLine[data.to].emit('newPrivateMessage',{from:name, msg:data.msg, type:'Private Msg'})
}
});
});
You can use the Base64 version of your image and send it like this:
onLine[data.to].emit('newPrivateMessage',{from:name, img:data.img.toString('base64'), type:'Private Msg'})
.. and then on the client side receive it and create an image
socket.on("newPrivateMessage", function(data) {
if (data.img) {
var img = new Image();
img.src = 'data:image/jpeg;base64,' + data.img;
// Do whatever you want with your image.
}
});
UPDATE
The following is a snippet taken from the link I've commented below. As you can see it takes the image from the input, reads it and sends to the server. After that you can send the same data from the server to another client.
For the full example, please read the article.
JavaScript (client)
...
$('#imageFile').on('change', function(e) {
var file = e.originalEvent.target.files[0],
reader = new FileReader();
reader.onload = function(evt) {
var jsonObject = {
'imageData': evt.target.result
}
// send a custom socket message to server
socket.emit('user image', jsonObject);
};
reader.readAsDataURL(file);
});
...
HTML
...
Image file: <input type="file" id="imageFile" /><br/>
...
UPDATE 2
Here is one example I have found:
Java (client)
File file = new File("path/to/the/image");
try {
FileInputStream imageInFile = new FileInputStream(file);
byte imageData[] = new byte[(int) file.length()];
imageInFile.read(imageData);
// Converting Image byte array into Base64 String
String imageDataString = Base64.encodeBase64URLSafeString(imageData);
} catch (...) {
...
}
The above snippet shows how to read the file and encode the data into a base64 string. So then you can send it just like a string (I assume).
Here is the complete example: How to convert Image to String and String to Image in Java?
Also I have found encodeToString function of Base64.Encoder (java.util package), which you can use.
The easiest way I can think of is to simply Base64 encode the image and send it through the text pipe. You would need to distinguish text and image messages with header information (Maybe send a JSON object?).

WebSocket JavaScript: Sending complex objects

I am using WebSockets as the connection between a Node.js server and my client JS code.
I want to send a number of different media types (Text, Audio, Video, Images) through the socket.
This is not difficult of course. message.data instanceof Blob separates text from media files. The problem is, that I want to include several additional attributes to those media files.
F.e.:
Dimension of an image
Name of the image
. . .
Now I could send one message containing these informations in text form and follow it up with another message containing the blob.
I would very much prefer though, to be able to build an object:
imageObject = {
xDimension : '50px',
yDimension : '50px',
name : 'PinkFlowers.jpg'
imageData : fs.readFileSync(".resources/images/PinkFlowers.jpg")
}
And send this object as it is via socket.send(imageObject).
So far so good, this actually works, but how do I collect the object and make its fields accessible in the client again?
I have been tampering with it for a while now and I would be grateful for any ideas.
Best regards,
Sticks
Well I did get it to work using base64.
On the server side I am running this piece of code:
var imageObject = newMessageObject('img', 'flower.png');
imageObject.image = new Buffer(fs.readFileSync('./resources/images/flower.png'), 'binary').toString('base64');
imageObject.datatype = 'png';
connection.send(JSON.stringify(imageObject));
The new Buffer() is necessary to ensure a valid utf encoding. If used without, Chrome(dont know about Firefox and others) throws an error, that invalid utf8 encoding was detected and shuts down the execution after JSON.parse(message).
Note: newMessageObject is just an object construction method with two fields, type and name which I use.
On the client side its really straight forward:
websocketConnection.onmessage = function(evt) {
var message = JSON.parse(evt.data);
... // Some app specific stuff
var image = new Image();
image.onload = function() {
canvas.getContext("2d").drawImage(image, 0, 0);
}
image.src = "data:image/" + message.datatype + ";base64," + message.image;
}
This draws the image on the canvas.
I am not convinced, that this is practicable for audio or video files, but for images it does the job.
I will probably fall back to simply sending an obfuscated URL instead of audio/video data and read the files directly from the server. I dont like the security implications though.

Categories