Using an app built using build.phonegap.com I'm finding that the FileUploadOptions.params values aren't being posted - iOS (8.4.1) only posts the file multipart data. This isn't a server side error as I've watched the POST using tcpdump/Wireshark and netcat.
Using:
This is the code that is used to upload a test file. It generates a temporary file and attempts to upload it. The question is, have I mucked something up, or is this a bona fide bug?
$scope.doUploadTest = function() {
var q = $q.defer();
var promise = q.promise;
var text = "Test file content: " + new Date();
$window.requestFileSystem(LocalFileSystem.TEMPORARY, text.length, function(fs) {
q.resolve(fs);
}, function(err) {
q.reject(err);
});
promise
.then(function(fs) {
var qFile = $q.defer();
fs.root.getFile("upload.tmp", {create: true, exclusive: false}, function(entry) {
qFile.resolve(entry);
}, function(err) {
qFile.reject(err);
});
return qFile.promise;
})
.then(function(entry) {
console.log("got file: " + entry.nativeURL);
var qWriter = $q.defer();
entry.createWriter(function(writer) {
qWriter.resolve({writer: writer, entry: entry});
}, function(err) {
qWriter.reject(err);
});
return qWriter.promise;
})
.then(function(response) {
var qWrite = $q.defer();
response.writer.onwriteend = function(evt) {
qWrite.resolve(response.entry);
};
response.writer.write(text);
return qWrite.promise;
})
.then(function(entry) {
$scope.error = entry;
var qUpload = $q.defer();
var options = new FileUploadOptions();
options.params = {
Value: {
list: [1, 2, 3],
text: "foo bar baz",
number: 1234
}
};
options.fileKey = "thefile";
options.fileName = entry.name;
options.mimeType = "text/plain";
options.chunkedMode = false;
options.headers = {
"X-Upload": (new Date()).toString()
};
var ft = new FileTransfer();
ft.upload(entry.nativeURL, "http://192.168.0.30:9100/", function(success) {
entry.remove();
qUpload.resolve(success);
}, function(err) {
entry.remove();
qUpload.reject(err);
}, options);
// Abort after 3 seconds
$timeout(function() {
console.log("Aborting upload - timed out");
ft.abort();
}, 3000);
return qUpload.promise;
})
.then(function(response) {
console.log("Upload succeeded");
$scope.error = response;
})
.catch(function(err) {
console.log("Upload failed");
$scope.error = err;
});
;
};
The request made on an iPhone 6 (iOS v.8.4.1) is as follows:
POST / HTTP/1.1 Host: 192.168.0.30:9100 Content-Type: multipart/form-data; boundary=+++++org.apache.cordova.formBoundary Accept-Encoding: gzip, deflate X-Upload: Tue Sep 01 2015 12:12:49 GMT+0100 (BST) Connection: keep-alive Accept: */* User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 8_4_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Mobile/12H321 (5348809648) Content-Length: 261 Accept-Language: en-us X-Requested-With: XMLHttpRequest --+++++org.apache.cordova.formBoundary Content-Disposition: form-data; name="thefile"; filename="upload.tmp" Content-Type: text/plain Content-Length: 58 Test file content: Tue Sep 01 2015 12:12:49 GMT+0100 (BST) --+++++org.apache.cordova.formBoundary--
On a Motorola Moto X (2nd gen, Android 5.1) the request is this:
POST / HTTP/1.1 Content-Type: multipart/form-data; boundary=+++++ X-Upload: Tue Sep 01 2015 12:52:24 GMT+0100 (BST) User-Agent: Dalvik/2.1.0 (Linux; U; Android 5.1; XT1092 Build/LPE23.32-25.1) Host: 192.168.0.30:9100 Connection: Keep-Alive Accept-Encoding: gzip Content-Length: 289 --+++++ Content-Disposition: form-data; name="Value" {"list":[1,2,3],"text":"foo bar baz","number":1234} --+++++ Content-Disposition: form-data; name="thefile"; filename="upload.tmp" Content-Type: text/plain Test file content: Tue Sep 01 2015 12:52:23 GMT+0100 (BST) --+++++--
It turns out that the params object can only contain literal properties, and not objects. The documentation only uses string values, but doesn't explicitly say that objects are unsupported.
This is further complicated by the fact that on Android the plugin serialises the object as a JSON string, whereas on iOS the plugin silently dumps it.
So, the solution is simply to serialise the data first:
var options = new FileUploadOptions();
options.params = {
Value: JSON.stringify({
list: [1, 2, 3],
text: "foo bar baz",
number: 1234
})
};