cordova-plugin-advanced-http
cordova-plugin-advanced-http copied to clipboard
[Bug] [iOS] formData with blob is not sending http request
Describe the bug It is not sending any request when i am sending blob but it is working fine when i am just sending text like this form.append("name", "indraraj")
System info
- affected HTTP plugin version: [e.g. 2.3.1] HTTP : 2.3.1
- affected platform(s) and version(s): [e.g. iOS 12.2] iOS: 12.1
- affected device(s): [e.g. iPhone 8] iPAD 5th generation
- cordova version: [e.g. 6.5.0] cordova
- cordova platform version(s): [e.g. android 7.0.0, browser 5.0.3]
Are you using ionic-native-wrapper?
- ionic-native-wrapper version: [e.g. 5.8.0] No
- did you check ionic-native issue tracker for your problem? Yes Capacitor CLI : 1.3.0 @capacitor/core : 1.4.0
Minimum viable code to reproduce If applicable, add formatted sample coding to help explain your problem.
e.g.:
import { HTTP } from "@ionic-native/http/ngx";
declare const cordova: any;
const form = new cordova.plugin.http.ponyfills.FormData()
form.append('profile_pic', customerDetail.profile_pic);
form.append('first_name', customerDetail.first_name);
form.append('mobile', customerDetail.mobile);
console.log(form, "formDataforFile")
this._http.setDataSerializer("multipart");
return this._http.post(
`${environment.apiUrl}/saveWalkForm`,
form,
{ "content-type": "application/json" }
);
console before sending actual request
FormData
__items: Array (3)
0 Array (2)
0 "profile_pic"
1 Blob
lastModifiedDate: Thu Jan 23 2020 19:47:12 GMT+0530 (IST)
name: ""
size: 496658
type: "image/jpeg"
Blob Prototype
Array Prototype
1 Array (2)
0 "first_name"
1 "dfff"
Array Prototype
2 Array (2)
0 "mobile"
1 "3434343455"
Array Prototype
Array Prototype
FormData Prototype
Screenshots If applicable, add screenshots to help explain your problem. ionic info
Ionic:
Ionic CLI : 5.2.3 (/usr/local/lib/node_modules/ionic)
Ionic Framework : @ionic/angular 4.11.8
@angular-devkit/build-angular : 0.801.3
@angular-devkit/schematics : 8.1.3
@angular/cli : 8.1.3
@ionic/angular-toolkit : 2.0.0
Capacitor:
Capacitor CLI : 1.3.0
@capacitor/core : 1.4.0
Cordova:
Cordova CLI : 8.1.2 ([email protected])
Cordova Platforms : none
Cordova Plugins : no whitelisted plugins (0 plugins total)
Utility:
cordova-res : not installed
native-run : 0.2.8 (update available: 0.3.0)
System:
ios-deploy : 1.9.4
ios-sim : 8.0.2
NodeJS : v12.13.0 (/usr/local/bin/node)
npm : 6.12.0
OS : macOS High Sierra
Xcode : Xcode 10.1 Build version 10B61
Hi indraraj, please add a „.catch()“ handler in your code and check which error message you receive. And I see that you‘re overriding the multipart mimetype. You are basically telling your server that you gonna transmit a JSON object, but you do transmit a multipart request. This will definitely fail on the server side. Did you also check server logs? Please use StackOverflow for this kind of questions.
Hi silkimen,
When you add blob to request it is not making any request we are temporary logging all the request and saving in temp column, But with blob it is not making any request to backend. I already have catch block i just don't get any error.
Okay btw let me remove the header part { "content-type": "application/json" }
and test
Edit: Tested with blob and changed content-type to multipart it is not making any request, enabled the debug level at Apache. Also I had catch block so there i had no error.
Okay, I see. I've tried to reproduce your problem on my device (iPhone XR running iOS 13.2) and also on a simulator (iPad 5th generation running iOS 12.4). I'm using the e2e specs suite which you can find here: test/e2e-specs.js lines 802-847 being the relevant ones.
The tests are running without any problems. But I don't have an iPad with iOS 12.1 available. Did you also try to pass the third argument during form.append()
? It's the filename argument.
I see you have this in code value.name = filename || '';
as per this it is not compulsory to pass file name. Anyway let me pass filename as well and test. I have tested in ipad 5th generation simulator it is failed to make a request with backend. For the moment i have used upload file interface combine with json post to deliver the project. I may close this issue after testing with filename as it is not reproducible at your side.
Okay, that‘s interesting. Did you try to run exactly the code from my e2e specs? I‘d like to know what‘s going wrong here.
I am facing similar issue,
I followed the same procedure which is in test/e2e-specs.js. I am trying to upload images to aws s3, using blob.
Xcode debugger crashes throwing error NSInvalidArgumentException
@sarathi0333 Can you please attach a Xcode screenshot? I'd like to know where exactly this problem occurs.
Please find below the screenshots.
@sarathi0333 Unfortunately I can't reproduce your problem. Please attach an extract of your JS code. And please try to run EXACTLY the code you can find in the e2e specs file (running against httpbin.org). I'd like to find out if it's related to something received from the server. Which device are you using? Which iOS version?
Same problem. API works from Postman but not in angular. The plugin seems not working when a try to make a put request in multipart and formData. It never return any response or error...
Hi guys, I have the same issue and have traced it to the FormData.append method from new window.top.cordova.plugin.http.ponyfills.FormData().
Some context - I am overriding the uploadData method of dropzone to work using cordova-plugin-advanced-http on my capacitor app.
Regardless of whether I am sending a blob or file object, the append method receives only the type string, e.g. "[Object blob]", so is checking if "[Object blob]" instanceof "Global.Blob", which clearly it isn't.
This returns it as an unknown type and so is rejected as an incorrect data type for dataSerializer multipart.
A sample of my code:
dz_uploadData = function(files, dataBlocks) {
var _this16 = this;
var url = this.resolveOption(this.options.url, files);
var formData = new window.top.cordova.plugin.http.ponyfills.FormData();
var dto = {
uuid: files[0].upload.uuid,
name: files[0].name,
chunkIndex: dataBlocks[0].chunkIndex,
totalChunkCount: files[0].upload.totalChunkCount
};
for (var key in dto) {
var value = dto[key];
formData.append(key, value);
}
for (var i = 0; i < dataBlocks.length; i++) {
var dataBlock = dataBlocks[i];
formData.append(dataBlock.name, dataBlock.data, dataBlock.filename);
}
window.top.cordova.plugin.http.setDataSerializer("multipart");
window.top.cordova.plugin.http.setRequestTimeout(extendedTimeout);
let requestOptions = {
data: formData,
headers: {
"Content-Type": "multipart/form-data;"
}
};
window.plenty_admin.REST.postOne(
url,
requestOptions,
function(response) {
window.top.cordova.plugin.http.setDataSerializer("json");
window.top.cordova.plugin.http.setRequestTimeout(standardTimeout);
console.log(response);
_this16._finishedUploading(files, xhr, e);
},
function(error) {
window.top.cordova.plugin.http.setDataSerializer("json");
window.top.cordova.plugin.http.setRequestTimeout(standardTimeout);
console.error(error);
_this16._handleUploadError(files, xhr);
}
);
};
I hope this can help to identify the problem? Thanks for your help once again silkimen. Jamie
--EDIT--
The reason for this comes from the FormData.append ponyfill for sure:
FormData.prototype.append = function(name, value, filename) {
if (global.File && value instanceof global.File) {
// nothing to do
} else if (global.Blob && value instanceof global.Blob) { // it is an instance of Blob, not Global.Blob...
// mimic File instance by adding missing properties
value.lastModifiedDate = new Date();
value.name = filename || '';
} else {
value = String(value);
}
this.__items.push([ name, value ]);
};
The patch I have working for this file is:
FormData.prototype.append = function(name, value, filename) {
if (global.File && value instanceof global.File) {
// nothing to do
} else if (global.Blob && value instanceof global.Blob || value.constructor.name === "Blob") {
// mimic File instance by adding missing properties
value.lastModifiedDate = new Date();
value.name = filename || '';
} else {
value = String(value);
}
this.__items.push([ name, value ]);
};
plugins/cordova-plugin-advanced-http/www/helpers.js also needs patching to replace:
if (entry.value[1] instanceof global.Blob || entry.value[1] instanceof global.File) {
var reader = new global.FileReader();
reader.onload = function() {
result.buffers.push(base64.fromArrayBuffer(reader.result));
result.names.push(entry.value[0]);
result.fileNames.push(entry.value[1].name || 'blob');
result.types.push(entry.value[1].type || '');
processFormDataIterator(iterator, textEncoder, result, onFinished);
};
return reader.readAsArrayBuffer(entry.value[1]);
}
with
if (
entry.value[1] instanceof global.Blob ||
entry.value[1].constructor.name === "Blob" ||
entry.value[1] instanceof global.File ||
entry.value[1].constructor.name === "File"
) {
var reader = new global.FileReader();
reader.onload = function() {
result.buffers.push(base64.fromArrayBuffer(reader.result));
result.names.push(entry.value[0]);
result.fileNames.push(entry.value[1].name || 'blob');
result.types.push(entry.value[1].type || '');
processFormDataIterator(iterator, textEncoder, result, onFinished);
};
return reader.readAsArrayBuffer(entry.value[1]);
}
If anyone can explain why instanceof no longer works to identify the object I would appreciate an explanation! Cheers Jamie
I'm running into the same issue. Here is some more information:
FormData Append Code | Logs | Promise Fulfills | Server Receives |
---|---|---|---|
formData.append('file', new Blob([fileContent]), fileName); | Nothing | Never | Nothing |
formData.append('file', new Blob([fileContent])); | Nothing | Never | Nothing |
formData.append('file', new File([fileContent], fileName)); | Nothing | Yes | String (pasted below) |
formData.append('file', new File([fileContent], fileName), fileName); | Yes (pasted below) | Never | Nothing |
formData.append('file', new File([fileContent], fileName, {type: 'application/octet-stream'})); | Nothing | Yes | String (pasted below) |
formData.append('file', new File([fileContent], fileName, {type: 'text/html'})); | Nothing | Yes | String (pasted below) |
fileContent is just a plain UTF-8 string.
Expected POST body:
--00content0boundary00
Content-Disposition: form-data; name="file"; filename="fileName.html"
Content-Type: application/octet-stream
<p>File Content</p>
--00content0boundary00--
None send File with filename
and content other than [object Object]
. Seems to be interpretting Files as strings.
formData.append('file', new File([fileContent], fileName)); POST body:
--00content0boundary00
Content-Disposition: form-data; name="file"
[object Object]
--00content0boundary00--
My fileContent string does not contain "[object Object]" lol
formData.append('file', new File([fileContent], fileName), fileName); Exception:
2020-05-10 18:08:52.174 20406-20406/io.ionic.starter E/Capacitor/Console: File: http://localhost/vendor-es2015.js - Line 43427 - Msg: ERROR Error: Uncaught (in promise): TypeError: Failed to execute 'append' on 'FormData': parameter 2 is not of type 'Blob'.
TypeError: Failed to execute 'append' on 'FormData': parameter 2 is not of type 'Blob'.
at SynologyService.<anonymous> (http://localhost/main-es2015.js:1577:22)
at Generator.next (<anonymous>)
at http://localhost/polyfills-es2015.js:3200:71
at new ZoneAwarePromise (http://localhost/polyfills-es2015.js:4226:29)
at Module.__awaiter (http://localhost/polyfills-es2015.js:3196:12)
at SynologyService.upload (http://localhost/main-es2015.js:1550:63)
at HomePage.<anonymous> (http://localhost/main-es2015.js:1121:37)
at Generator.next (<anonymous>)
at http://localhost/polyfills-es2015.js:3200:71
at new ZoneAwarePromise (http://localhost/polyfills-es2015.js:4226:29)
I get that append
taking fileName third is for Blobs and not Files but just trying everything here.
formData.append('file', new File([fileContent], fileName, {type: 'application/octet-stream'})); POST body:
--00content0boundary00
Content-Disposition: form-data; name="file"
[object Object]
--00content0boundary00--
formData.append('file', new File([fileContent], fileName, {type: 'text/html'})); POST body:
--00content0boundary00
Content-Disposition: form-data; name="file"
[object Object]
--00content0boundary00--
How I'm POSTing
let formData = new FormData();
formData.append('file', ...);
this.mobileHttp.setDataSerializer('multipart');
return this.mobileHttp.post(url, formData, {})
The same exact FormData
can instead be passed to Angular's HttpClient and it will serialize and POST to the server just fine. I think the issue is in how the FormData is serialized when using advanced-http. I tried serializing it myself but then I ran into other issues.
System info
- "@ionic-native/http": "^5.25.0"
- "cordova-plugin-advanced-http": "^2.4.1"
- affected platform(s) and version(s): Android 10 (Build number: QQ2A.200405.005)
- affected device(s): Google Pixel 3
- cordova version: "@capacitor/android": "^2.0.1", "@capacitor/core": "2.0.1",
- cordova platform version(s): minSdkVersion = 21 compileSdkVersion = 29 targetSdkVersion = 29 androidxAppCompatVersion = '1.1.0' androidxCoreVersion = '1.2.0' androidxMaterialVersion = '1.1.0-rc02' androidxBrowserVersion = '1.2.0' androidxLocalbroadcastmanagerVersion = '1.0.0' firebaseMessagingVersion = '20.1.2' playServicesLocationVersion = '17.0.0' junitVersion = '4.12' androidxJunitVersion = '1.1.1' androidxEspressoCoreVersion = '3.2.0' cordovaAndroidVersion = '7.0.0'
I wasn't able to get it working tonight but I got a bit further by stepping through things. One of the issues was that I was using FormData
. It passes the dependencyValidator.checkFormDataInstance(data);
check in processFormData
because it has an entries
function, but the iterator that it returns reads the File
as a string
. So when it does the instanceof
check here it's already a string
type with the value [object Object]
. To fix that I switched to the ponyfills.FormData
mentioned in the documentation, even though the FormData
I was using already had an entries
function.
declare var cordova;
...
new cordova.plugin.http.ponyfills.FormData()
The issue I am running into now is that when I am debugging, the File
constructor from cordova-plugin-file is different from the standard one. It doesn't accept the file content as a string
.
/**
* Constructor.
* name {DOMString} name of the file, without path information
* fullPath {DOMString} the full path of the file, including the name
* type {DOMString} mime type
* lastModifiedDate {Date} last modified date
* size {Number} size of the file in bytes
*/
var File = function (name, localURL, type, lastModifiedDate, size) {
this.name = name || '';
this.localURL = localURL || null;
this.type = type || null;
this.lastModified = lastModifiedDate || null;
// For backwards compatibility, store the timestamp in lastModifiedDate as well
this.lastModifiedDate = lastModifiedDate || null;
this.size = size || 0;
// These store the absolute start and end for slicing the file.
this.start = 0;
this.end = this.size;
};
So I tried passing the file as a Blob
again
formData.append('file', new Blob([fileContent]), fileName);
or
formData.append('file', new Blob([fileContent], { type: 'text/html' }), fileName);
or
const textEncoder = new TextEncoder();
formData.append('file', new Blob([textEncoder.encode(fileContent).buffer]), fileName);
but FileReader.onload
is never triggered in processFormDataIterator
when it's a Blob
type so it doesn't push the file entry for serialization.
While debugging I noticed that window.File
has that weird constructor I pasted above but window.Blob
doesn't have a constructor. Not sure if that has to do with it but that is as far as I got tonight.
Same problem for me on Android. I'm currently unable to send a POST request with a Blob or File in the formData. No request is sent to the server (I also checked Apache logs) and the Promise remains pending without throwing any error.
I searched for days how to make it works but I finally surrendered, it seems it is impossible to send a file with this Plugin.
Anyone else facing the same issue ? Does someone has a workaround ?
I am also facing the same issue for the iOS with sending the multipart request with Blob object
@RomanRobot I've checked your app, but that's not what I meant as MVP. 😅
I have a similar issue, but in my case the Blob entry I appended to FormData is already a string type by the time it gets to that check.
Please check MDN reference of FormData/append. It says:
value The field's value. This can be a USVString or Blob (including subclasses such as File). If none of these are specified the value is converted to a string.
So, I think your Blob instance is not recognized as a Blob. That could be the reason why it's serialized as string representation and not read correctly by FileReader in helpers.js#L443. The question is why?
Do you have set up some polyfills or alternative implementations of Blob or FileReader?
@silkimen I believe this is due to an ongoing issue with ionic/capacitor apps and Angular's Zone library. I implemented this suggested solution in your library, and it fixed my issue. Please let me know if you would like me to submit a PR. Thanks
Edit: The fix actually needs to listen to onloadend as well, instead of onload. Otherwise the file result doesn't get saved.
@bsbechtel How would someone apply this fix themselves? I tried updating the code in node_modules\cordova-plugin-advanced-http\www\helpers.js
to
...
var reader = new global.FileReader();
const zoneOriginalInstance = (reader as any)["__zone_symbol__originalInstance"];
reader = zoneOriginalInstance || reader;
reader.onloadend = function() {
result.buffers.push(base64.fromArrayBuffer(reader.result));
...
but while it compiles, I seem to get this in my output window after submitting any HTTP requests:
io.ionic.starter W/Capacitor/Console: File: http://localhost/vendor-es2015.js - Line 103956 - Msg: Native: tried calling HTTP.get, but Cordova is not available. Make sure to include cordova.js or run in a device/simulator
io.ionic.starter E/Capacitor/Console: File: http://localhost/vendor-es2015.js - Line 43427 - Msg: ERROR Error: Uncaught (in promise): cordova_not_available
If I revert those changes to helpers.js
it goes back to working just as it was, submitting HTTP requests just fine, just the formData POST bug.
It's my first time trying to change a library like this. New to Angular.
Something to note, while I am excited to potentially have a working solution to this issue I'm worried about some reports of performance issues reported below that solution you linked:
- https://github.com/ionic-team/capacitor/issues/1564#issuecomment-567857794
- https://github.com/ionic-team/capacitor/issues/1564#issuecomment-623529277
Also, to answer your question @silkimen
Do you have set up some polyfills or alternative implementations of Blob or FileReader?
I don't think so. The only one I see is the default one in the src
folder generated by Ionic I think it was. It just contains
import './zone-flags';
import 'zone.js/dist/zone';
and a bunch of comments.
@RomanRobot I ended up overwriting the prototype method in a bit of a hacky way until the library can be updated. I put this in main.ts:
const originalReadAsArrayBufferMethod = FileReader.prototype.readAsArrayBuffer;
FileReader.prototype.readAsArrayBuffer = function(args) {
const originalFileReader = this['__zone_symbol__originalInstance'];
if (originalFileReader) {
originalFileReader.onloadend = () => {
this.result = originalFileReader.result;
this.onload();
}
originalFileReader.readAsArrayBuffer(args);
} else {
originalReadAsArrayBufferMethod(args);
}
}
@bsbechtel Freakin' awesome! That got me unblocked on that finally. Thank you!
@bsbechtel thanks for the hacky way after I had spent days on it and saw this eventually. It deserves a PR.
I'm having the same issue with Ionic + Capacitor + Angular on an Android device. Sending FormData works well but the second I add a Blob to the FormData, the http request never gets send and i have no error.