i'm working with angular 4, I'm using an api that return an image with content-type : img/png
The http method :
return this.http.get('URL', this.options)
.map((res: Response) => res.text());
// can be also : res.arrayBuffer() // res.blob()
The http get response (in text and in ARC ) is like that :
�PNG IHDR��"�W�W��zؽ�|+q%� ��Y������M缥{��U��H�ݏ)L�L�~�6/'6Q�}���:��l'���� �R�L�&�~Lw?�
I tried different methods to convert it and display it :
getting response as blob and convert it using :
new Uint8Array(response)
Getting the image as arrayBuffer and then convert it using :
arrayBufferToBase64(buffer) {
let binary = '';
let bytes = new Uint8Array(buffer);
let len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary);
}
Both of them didnt worked for me and the image is not displaying.
My question so is , what is the real format of the response (blob, arraybuffer or text ) and how to display it ?
You can achieve this using the fetch API.
Let's first return the response as a blob.
And then you can use URL.createObjectURL() to create an file object.
fetch(URL)
.then(res=>{return res.blob()})
.then(blob=>{
var img = URL.createObjectURL(blob);
// Do whatever with the img
document.getElementById('img').setAttribute('src', img);
})
Demo
Related
I built a simple project using javascript and FastAPI. I would like to POST canvas image data in javascript to the server(FastAPI).
But, I get the 422 Unprocessable Entity error
# FastAPI
#app.post("/post")
async def upload_files(input_data: UploadFile = File(...)):
return JSONResponse(status_code = 200, content={'result' : 'success'})
// javascript
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
imageData = ctx.getImageData(0, 0, 112, 112);
fetch("http://{IP}/post", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
input_data: imageData,
}),
})
.then((response) => response.json())
here is my code. I am new one in javascript. so I don't know how can post data to server.
How can I do? Thanks in advance.
MatsLindh's way is correct but you can try upload image by formdata so you haven't convert to base64 then convert back to image
const dataURItoBlob = function(dataURI : string) {
// convert base64/URLEncoded data component to raw binary data held in a string
var byteString : string;
if (dataURI.split(',')[0].indexOf('base64') >= 0)
byteString = atob(dataURI.split(',')[1]);
else
byteString = unescape(dataURI.split(',')[1]);
// separate out the mime component
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
// write the bytes of the string to a typed array
var ia = new Uint8Array(byteString.length);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
return new Blob([ia], {type:mimeString});
}
const url = canvas.toDataURL()
const file = dataURItoBlob(url)
//add file to formdata
const form = new FormData()
form.append('file', item.file)
//post this form in body
UploadFile expects a file being posted to the endpoint in the multipart/form-data format - i.e. as a regular POST with a bit of added metadata.
Since you're retrieving the image data and submitting it as JSON body, that won't match the expected format. You also want to use canvas.toDataURL() to convert the content to base64 before trying to submit it through your API to avoid any UTF-8 issues with JSON.
const pngData = canvas.toDataURL();
// then in your request:
body: JSON.stringify({
input_data: pngData,
}),
To handle this request change your view function signature to receive an input_data parameter (and use a better endpoint name than post):
#app.post("/images")
async def upload_files(input_data: str = Body(...)):
# check the expected format (content-type;<base64 content>)
# .. and decode it with base64.b64decode()
return {'content': input_data}
I'm using the ASP.NET WebApi with ReactJs application in the front, I'm creating a Get method to download file from the server, and I trying to set both Content-Type and Content-Length in the response headers :
var result = new HttpResponseMessage(HttpStatusCode.OK);
result.Content = new StreamContent(new MemoryStream(bytes));
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
result.Content.Headers.ContentLength = bytes.Length;
When I'm calling this method from the ReactJs Application using fetch method like below:
await fetch(`someservice/${clientid}/download/${fileName}`, { responseType: "arraybuffer" })
.then((response) => {
const reader = response.body.getReader();
//get total length
const contentLength = response.headers.get('Content-Length');
console.log(response.headers);
//read the data
let receivedLength = 0; // received that many bytes at the moment
let chunks = []; // array of received binary chunks (comprises the body)
while (true) {
const { done, value } = reader.read();
if (done) {
break;
}
console.log(value);
chunks.push(value);
receivedLength += value.length;
console.log(`Received ${receivedLength} of ${contentLength}`)
}
//concatenate chunks into single Uint8Array
let chunksAll = new Uint8Array(receivedLength);
let position = 0;
for (let chunk of chunks) {
chunksAll.set(chunk, position);
position += chunk.length;
}
});
I got a response without Content-Type and Content-Length headers:
But isn't Content-Type and Content-Length a valid headers ?
response headers in fetch api JavaScript has been implemented as an iterator rather than an object , you could try below to access the headers :-
for (var header of respone.headers.entries()) {
console.log(header);
}
each header item in the loop will be an array of string where the first element is the key and 2nd element is value.
I am getting corrupted image icon while displaying b64 encoded png image response from rest API.
javascript-
function getcap(){
var http = new XMLHttpRequest()
http.open("GET", "http://localhost:8888/newcaptcha",true)
http.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
http.setRequestHeader("Access-Control-Allow-Origin", "http://localhost:8888");
http.send()
http.onload = () => {
var resp=unescape(encodeURIComponent(http.responseText));
var b64Response = window.btoa(resp);
console.log('data:image/png;base64,'+b64Response);
document.getElementById("capimg").src = 'data:image/png;base64,'+b64Response;
}
}
html -
<div id="newCaptcha" onClick="getcap()" ><h5>new captcha:</h5><img id="capimg" width="30" height ="30"/></div>
b64 encoded response-
server code -
#CrossOrigin(origins = "http://localhost:8080")
#RequestMapping(value = "/newcaptcha", method = RequestMethod.GET, produces = "image/png")
public #ResponseBody byte[] getnewCaptcha() {
try {
Random random = new Random();
imgkey= random.nextInt(3);
InputStream is = this.getClass().getResourceAsStream("/"+captcheMap.get(imgkey)+".png");
BufferedImage img = ImageIO.read(is);
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ImageIO.write(img, "png", bao);
return bao.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
The base 64 response attached doesn't seem to actually load the image, if I open it in browser.
Secondly, I can see that that one problem that can cause this is reloading of DOM element img, if its not handled by any framework, you may have to manually intervene. To check this, you can test using a local image and load that. If it doesn't work, then you got your root cause. And if it does, then this base64 response is an issue.
Also, check the console for any errors and do update here.
As I pointed out in comments, probably you don't need b64. However, if you really want, read this.
There are tons on questions on Stackoverflow on this subject, and few answers. I have put together all pieces.
The point is that btoa() badly supports binary data.
Here: convert binary data to base-64 javaScript you find the suggestion to use arraybuffers as responseType, instead of just text.
Here: ArrayBuffer to base64 encoded string you find a function that converts arraybuffers to b64.
Putting all togheter:
function getcap(){
var http = new XMLHttpRequest();
http.open("GET", "/newcaptcha",true);
http.responseType = 'arraybuffer';
http.send();
http.onload = () => {
console.log(http.response);
var b64Response = _arrayBufferToBase64(http.response);
document.getElementById("capimg").src = 'data:image/png;base64,'+b64Response;
}
}
function _arrayBufferToBase64( buffer ) {
var binary = '';
var bytes = new Uint8Array( buffer );
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode( bytes[ i ] );
}
return window.btoa( binary );
}
I am using the Office Javascript API to write an Add-in for Word using Angular.
I want to retrieve the Word document through the API, then convert it to a file and upload it via POST to a server.
The code I am using is nearly identical to the documentation code that Microsoft provides for this use case: https://dev.office.com/reference/add-ins/shared/document.getfileasync#example---get-a-document-in-office-open-xml-compressed-format
The server endpoint requires uploads to be POSTed through a multipart form, so I create a FormData object on which I append the file (a blob) as well as some metadata, when creating the $http call.
The file is being transmitted to the server, but when I open it, it has become corrupted and it can no longer be opened by Word.
According to the documentation, the Office.context.document.getFileAsync function returns a byte array. However, the resulting fileContent variable is a string. When I console.log this string it seems to be compressed data, like it should be.
My guess is I need to do some preprocessing before turning the string into a Blob. But which preprocessing? Base64 encoding through atob doesn't seem to be doing anything.
let sendFile = ( fileContent ) => {
let blob = new Blob([fileContent], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }),
fd = new FormData();
blob.lastModifiedDate = new Date();
fd.append('file', blob, 'uploaded_file_test403.docx');
fd.append('case_id', caseIdReducer.data());
$http.post('/file/create', fd, {
transformRequest: angular.identity,
headers: { 'Content-Type': undefined }
})
.success( ( ) => {
console.log('upload succeeded');
})
.error(( ) => {
console.log('upload failed');
});
};
function onGotAllSlices(docdataSlices) {
let docdata = [];
for (let i = 0; i < docdataSlices.length; i++) {
docdata = docdata.concat(docdataSlices[i]);
}
let fileContent = new String();
for (let j = 0; j < docdata.length; j++) {
fileContent += String.fromCharCode(docdata[j]);
}
// Now all the file content is stored in 'fileContent' variable,
// you can do something with it, such as print, fax...
sendFile(fileContent);
}
function getSliceAsync(file, nextSlice, sliceCount, gotAllSlices, docdataSlices, slicesReceived) {
file.getSliceAsync(nextSlice, (sliceResult) => {
if (sliceResult.status === 'succeeded') {
if (!gotAllSlices) { // Failed to get all slices, no need to continue.
return;
}
// Got one slice, store it in a temporary array.
// (Or you can do something else, such as
// send it to a third-party server.)
docdataSlices[sliceResult.value.index] = sliceResult.value.data;
if (++slicesReceived === sliceCount) {
// All slices have been received.
file.closeAsync();
onGotAllSlices(docdataSlices);
} else {
getSliceAsync(file, ++nextSlice, sliceCount, gotAllSlices, docdataSlices, slicesReceived);
}
} else {
gotAllSlices = false;
file.closeAsync();
console.log(`getSliceAsync Error: ${sliceResult.error.message}`);
}
});
}
// User clicks button to start document retrieval from Word and uploading to server process
ctrl.handleClick = ( ) => {
Office.context.document.getFileAsync(Office.FileType.Compressed, { sliceSize: 65536 /*64 KB*/ },
(result) => {
if (result.status === 'succeeded') {
// If the getFileAsync call succeeded, then
// result.value will return a valid File Object.
let myFile = result.value,
sliceCount = myFile.sliceCount,
slicesReceived = 0, gotAllSlices = true, docdataSlices = [];
// Get the file slices.
getSliceAsync(myFile, 0, sliceCount, gotAllSlices, docdataSlices, slicesReceived);
} else {
console.log(`Error: ${result.error.message}`);
}
}
);
};
I ended up doing this with the fileContent string:
let bytes = new Uint8Array(fileContent.length);
for (let i = 0; i < bytes.length; i++) {
bytes[i] = fileContent.charCodeAt(i);
}
I then proceed to build the Blob with these bytes:
let blob = new Blob([bytes], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
If I then send this via a POST request, the file isn't mangled and can be opened correctly by Word.
I still get the feeling this can be achieved with less hassle / less steps. If anyone has a better solution, I'd be very interested to learn.
thx for your answer, Uint8Array was the solution. Just a little improvement, to avoid creating the string:
let bytes = new Uint8Array(docdata.length);
for (var i = 0; i < docdata.length; i++) {
bytes[i] = docdata[i];
}
Pff! what is wrong with a getting a instance of File and not using FileReader api? c'mon Microsoft!
You should take the byte array and throw it into the blob constructor, turning a binary blob to string in javascript is a bad idea that can lead to "out of range" error or incorrect encoding
just do something along with this
var byteArray = new Uint8Array(3)
byteArray[0] = 97
byteArray[1] = 98
byteArray[2] = 99
new Blob([byteArray])
if the chunk is an instance of a typed arrays or a instance of blob/file. in that case you can just do:
blob = new Blob([blob, chunk])
And please... don't base64 encode it (~3x larger + slower)
I'm working on a project in browser that receives a multipart response from a server.
The first part of the response is a bit of JSON metadata
The second part of the response is a binary MP3 file.
I need a way to extract that MP3 file from the multipart response and play it in an HTML 5 audio element.
Has anyone encountered this or something similar?
I ended up solving my problem. I'm providing the issue and my solution to anyone needing to do this in the future.
Background info:
I'm using AngularJS when I make this ajax request, but the idea is the same for both that, jquery ajax, and regular xhr.
Code:
//creating a form object and assigning everything
//to it is so that XHR can automatically
//generate proper multipart formatting
var form = new FormData();
var data = {};
data['messageHeader'] = {};
var jsonData = JSON.stringify(data);
var jsonBlob = new Blob([jsonData],{type: "application/json"});
//assign json metadata blob and audio blob to the form
form.append("request", jsonData);
form.append("audio",response); //Response is the audio blob
//make the post request
//Notes:
//content-type set to undefined so angular can auto assign type
//transformRequest: angular.identity allows for angular to create multipart
//response: arraybuffer so untouched binary data can be received
$http({method:"POST",
url: endpoint + path,
headers: {
'Authorization': 'Bearer ' + $cookies.get('token'),
'Content-Type': undefined
},
transformRequest: angular.identity,
data: form,
responseType: "arraybuffer"
})
.success(function(data){
//data: ArrayBuffer of multipart response
//toss ArrayBuffer into Uint8Array
//lets you iterate over the bytes
var audioArray = new Uint8Array(data);
//toss a UTF-8 version of the response into
//a variable. Used to extract metadata
var holder = "";
for (var i = 0; i < audioArray.length; i++){
holder += String.fromCharCode(audioArray[i]);
}
//get the boundary from the string. Eg contents of first line
var boundary = holder.substr(0, holder.indexOf("\n"));
//break response into array at each boundary string
var temp = holder.split(boundary);
var parts = [];
//loop through array to remove empty parts
for (var i = 0; i < temp.length; i++){
if (temp[i] != ""){
parts.push(temp[i]);
}
}
//PARSE FIRST PART
//get index of first squiggly, indicator of start of JSON
var jsonStart = parts[0].indexOf('{');
//string to JSON on { index to end of part substring
var JSONResponse = JSON.parse(parts[0].substring(jsonStart));
//PARSE SECOND PART
var audioStart = holder.indexOf('mpeg') + 8;
//get an ArrayBuffer from UInt8Buffer from the audio
//start point to the end of the array
var audio = audioArray.buffer.slice(audioStart);
//hand off audio to AudioContext for automatic decoding
audio_context.decodeAudioData(audio, function(buffer) {
var audioBuffer = buffer;
//create a sound source
var source = audio_context.createBufferSource();
//attach audioBuffer to sound source
source.buffer = audioBuffer;
//wire source to speakers
source.connect(audio_context.destination);
//on audio completion, re-enable mic button
source.onended = function() {
console.log("ended");
$scope.$apply(function(){
$scope.playing = false;
});
}
//start playing audio
source.start(0);
}, function (){
//callback for when there is an error
console.log("error decoding audio");
});
})
Overview:
You need to accept the response as pure binary data (ArrayBuffer). Most libraries will give it to you as a string, which is cool for normal requests but bad for binary data.
You then step through the data to find the multipart boundaries.
You then split at the boundaries.
Get the index of the boundary you know is binary data
and then retrieve the original binary from the ArrayBuffer.
In my case I send that binary into the speakers, however if its an image you can build a blob, get a url from FileReader and then set that as a source of an image.