Skip to content

Instantly share code, notes, and snippets.

@vlucas
Last active May 9, 2017 12:34
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vlucas/6796953 to your computer and use it in GitHub Desktop.
Save vlucas/6796953 to your computer and use it in GitHub Desktop.
Titanium File Upload Differences on Android vs. iPhone

Purpose

The purpose of this gist is to point out that iPhone and Android HTTPClient handle HTTP requests differently when combining file uploads with other form data, for example uploading a photo along with a title or message.

iOS / iPhone

The iOS implementation, as usual, is correct and handles this situation perfectly and as expected.

Android

The Android implementation, on the other hand, doesn't seem to put together the MIME envelope correctly. Dumping the contents of both $_POST and $_FILES produce empty arrays, where both $_POST['title'] and $_FILES['file'] should exist, as they do in the iOS implementation. In short, the data never gets to the user's PHP script in a way that the user can use it effectively.

I have also dumped the contents of php://input for examination. This is the RAW post body that remains when PHP can't decode the contents (commonly used for PUT requests with data)

Ticket

I have filed a bug report on this issue with Appcelerator: https://jira.appcelerator.org/browse/TC-3082

(function() {
// URL location of your local PHP server (should be your local network IP address + port 8000)
var form_url = 'http://192.168.1.108:8000';
var win = Ti.UI.createWindow({
layout: 'vertical',
backgroundColor: '#fff'
});
var txtTitle = Ti.UI.createTextField({ top: '80dp', value: 'My Photo Title' });
win.add(txtTitle);
var btnPhoto = Ti.UI.createButton({ title: 'Select Photo' });
btnPhoto.addEventListener('click', function(e) {
// Open photo gallery for user to select photo
Ti.Media.openPhotoGallery({
mediaTypes:[Ti.Media.MEDIA_TYPE_PHOTO],
success:function(e) {
btnPhoto.value = e.media;
btnPhoto.title = '[ Photo Selected ]';
},
cancel:function() {
btnPhoto.value = null;
btnPhoto.title = 'Select Photo...';
},
error:function(err) {
Ti.API.error(err);
btnPhoto.value = null;
btnPhoto.title = 'Select Photo...';
}
});
});
win.add(btnPhoto);
var btnSubmit = Ti.UI.createButton({ title: 'Submit' });
btnSubmit.addEventListener('click', function(e) {
// Send with HTTPClient
var c = Titanium.Network.createHTTPClient();
c.open('POST', form_url);
c.setRequestHeader('Content-Type','multipart/form-data');
c.send({
title: txtTitle.value,
file: btnPhoto.value
});
});
win.add(btnSubmit);
win.open();
})();
<?php
if(strtoupper($_SERVER['REQUEST_METHOD']) === 'POST') {
error_log(var_export($_POST, true), 4);
error_log(var_export($_FILES, true), 4);
error_log(file_get_contents('php://input'), 4);
}
# Start the PHP built-in webserver in terminal/shell:
# The IP address should be your local network IP (get it from 'ifconfig')
php -S 192.168.1.108:8000
#
# ANDROID output
#
# Both $_POST and $_FILES arrays are empty, and the raw php://input has
# content because PHP was unable to decode the MIME content
#
[Wed Oct 2 12:18:13 2013] array (
)
[Wed Oct 2 12:18:13 2013] array (
)
[Wed Oct 2 12:18:13 2013] --RF1V7b5XaO9pz8kgFD3dNdg8Xbl2ba
Content-Disposition: form-data; name="title"
Content-Transfer-Encoding: 8bit
My Photo Title
--RF1V7b5XaO9pz8kgFD3dNdg8Xbl2ba
Content-Disposition: form-data; name="file"; filename="tixhr-2059411955.jpeg"
Content-Type: image/jpeg
Content-Transfer-Encoding: binary
����e�Exif
#
# IPHONE output
#
# Both $_POST and $_FILES arrays are exactly as expected with appropriate content
# Raw php://input is empty because it was all successfully parsed by PHP into the
# $_POST and $_FILES superglobal arrays
#
[Wed Oct 2 12:19:36 2013] array (
'title' => 'My Photo Title',
)
[Wed Oct 2 12:19:36 2013] array (
'file' =>
array (
'name' => 'e376030.jpeg',
'type' => 'image/jpeg',
'tmp_name' => '/private/var/folders/wv/jh30ppzd3d9d15d5vw0b4gd40000gn/T/phpsRKOV7',
'error' => 0,
'size' => 13484,
),
)
[Wed Oct 2 12:19:36 2013]
@ricardoalcocer
Copy link

In case someone lands on this Gist, to solve the issue simply leave out line 39 on app.js. Titanium takes care of the HTTP Headers automatically.

@chrisribe
Copy link

Thank you ricardoalcocer !!!
Damit ! Just wasted 2-3 days trying to figure out why this was NOT working under android but was under iOS.
Removed any reference to content-type and now it works... uggg!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment