Project: titanium
Issue TIMOB-18679: iOS: HttpClient timeout on POST request to a HTTPS server
I'm working on a Titanium Classic Mobile project that needs to get data from SOAP services that are hosted in a https server. I'm using [this suds2 library |https://github.com/benbahrenburg/Suds2] as a helper to construct the xml request. The project was working fine with sdk 3.2.3 but I need to update it because of [this Apple restriction|https://developer.apple.com/news/?id=12172014b] so I basically need to compile with sdk 3.5.0. After sdk 3.3.0 the httpClient responds with a timeout error, so it looks like the request never leaves the client. This problem was pointed in [this issue|https://jira.appcelerator.org/browse/TIMOB-17865] before, I added a comment there too. I'm aware of Vishal Duggal comment in that post, mentioning all the stuff that changed in the httpClient, and I think that was the reason that ticket was closed, but I'm not sending a GET request so that comment does not apply in this case. In order to reproduce this problem I will recommend to use sdk 3.2.3 to show that the code was working fine and then switch to 3.5.0 to show the issue. Follow these steps to reproduce the problem: 1. Create a new Titanium Classic project targeting iOS with 3.2.3 sdk. 2. Create a new file in Resources directory and name it suds2.js and paste the following code taken from the source mentioned above: {code:title=/Resources/suds2.js|borderStyle=solid} /*jslint maxerr:1000 */ /** * * * Sud2 is forked from Kevin Whinnery's suds.js project * Updates to Suds2 can be found at https://github.com/benbahrenburg/Suds2 * * Suds: A Lightweight JavaScript SOAP Client * Copyright: 2009 Kevin Whinnery (http://www.kevinwhinnery.com) * License: http://www.apache.org/licenses/LICENSE-2.0.html * Source: http://github.com/kwhinnery/Suds */ var SudsClient = function(_options) { //A generic extend function - thanks MooTools function extend(original, extended) { for (var key in (extended || {})) { if (original.hasOwnProperty(key)) { original[key] = extended[key]; } } return original; } function endWith(str,suffix){	str = str +'';	var lastIndex = str.lastIndexOf(suffix);	return (lastIndex != -1) && (lastIndex + suffix.length == str.length); }; //Check if an object is an array function isArray(obj) { return Object.prototype.toString.call(obj) == '[object Array]'; } //Grab an XMLHTTPRequest Object function getXHR() { return Titanium.Network.createHTTPClient({ validatesSecureCertificate : false, timeout: 5000 }); } //Parse a string and create an XML DOM object function xmlDomFromString(_xml) { var xmlDoc = Titanium.XML.parseString(_xml); return xmlDoc; }; // Convert a JavaScript object to an XML string - takes either an function convertToXml(_obj, namespacePrefix) { var xml = ''; if (isArray(_obj)) { for (var i = 0; i < _obj.length; i++) { xml += convertToXml(_obj[i], namespacePrefix); } } else { //For now assuming we either have an array or an object graph for (var key in _obj) { if (namespacePrefix && namespacePrefix.length) { xml += '<' + namespacePrefix + ':' + key + '>'; } else { xml += '<'+key+'>'; } if (isArray(_obj[key]) || (typeof _obj[key] == 'object' && _obj[key] != null)) { xml += convertToXml(_obj[key]); } else { xml += _obj[key]; } if (namespacePrefix && namespacePrefix.length) { xml += ''; } else { xml += ''; } } } return xml; } // Client Configuration var config = extend({ endpoint:'http://localhost', targetNamespace: 'http://localhost', envelopeBegin: '', envelopeEnd: '', headerBegin: '', headerNode:'head', headerEnd: '', timeout : 5000, includeNS : true, addTargetSchema : false, ns:'ns0' },_options); function wrapNS(nsOnly){	if(config.includeNS){	return ((nsOnly) ? config.ns : (config.ns + ':'));	}else{	return '';	} }; function addTargetSchema(){	if(config.addTargetSchema){	var temp = config.targetNamespace;	if(endWith(config.targetNamespace,'/')){	temp = temp.substr(0,(temp.length -1));	}	return ' xmlns="' + temp + '"';	}else{	return '';	} }; // Invoke a web service this.invoke = function(_soapAction,_body,_callback,_header,_callbackError) { //Build request body var body = _body; var header = _header; //Allow straight string input for XML body - if not, build from object if (typeof body !== 'string') { body = '<' + wrapNS(false) +_soapAction + addTargetSchema() + '>'; body += convertToXml(_body, wrapNS(true)); body += ''; } var ebegin = config.envelopeBegin; config.envelopeBegin = ebegin.replace('PLACEHOLDER', config.targetNamespace); //Build Soapaction header - if no trailing slash in namespace, need to splice one in for soap action var soapAction = ''; if (config.targetNamespace.lastIndexOf('/') != config.targetNamespace.length - 1) { soapAction = config.targetNamespace+'/'+_soapAction; } else { soapAction = config.targetNamespace+_soapAction; } //POST XML document to service endpoint var xhr = getXHR(); xhr.onload = function() { _callback.call(this, this.responseText); }; xhr.onerror = function(e) { //_callbackError.call(this, e); Ti.API.info("error" + JSON.stringify(e)); }; //xhr.setTimeout(config.timeout); var sendXML = ''; if(!header) { sendXML = config.envelopeBegin+config.bodyBegin+body+config.envelopeEnd; } else { //Allow straight string input for XML body - if not, build from object if (typeof header !== 'string') { header = '<'+_soapAction+' xmlns="'+config.targetNamespace+'">'; header += convertToXml(_header); header += ''; } sendXML = config.envelopeBegin+config.headerBegin+header+config.headerEnd+config.bodyBegin+body+config.envelopeEnd; } xhr.open('POST', config.endpoint);	xhr.setRequestHeader('Content-Type', 'text/xml');	xhr.setRequestHeader('SOAPAction', soapAction);	if (config.authorization !== undefined) {	xhr.setRequestHeader('Authorization', 'Basic ' + config.authorization);	}	Ti.API.info("endpoit " + config.endpoint);	xhr.send(config.envelopeBegin+body+config.envelopeEnd); }; }; module.exports = SudsClient; {code} 3. Add this code to the app.js file: {code:title=/Resources/app.js|borderStyle=solid} var win = Ti.UI.createWindow({	backgroundColor: 'white' }); var label = Ti.UI.createLabel({ }); var config = {	endpoint:"https://cajanet.com:9443/CajaAhorro/services/WebServiceImpl?wsdl", targetNamespace: 'http://services.data.cajaAhorro.isi.com.mx',	includeNS : false, addTargetSchema : true }; var sudsModule = require('suds2'); var sudsClient = new sudsModule(config); sudsClient.invoke('logOff', {strClaveUsu: 19844},	function(xmlDoc) {	Ti.API.info("Response " + xmlDoc);	label.text = xmlDoc;	return;	} ); win.add(label); win.open(); {code} 5. Run the project. You should be able to see the response XML in the label added to the window showing that the code is working. 6. To reproduce the timeout error just change the sdk to a newer one, let's say 3.5.0 and run the project again. You will see this error on the console: {code:javascript} {"type":"error","source":{"url":"https://cajanet.com:9443/CajaAhorro/services/WebServiceImpl?wsdl","method":"POST","timeout":5000,"validatesSecureCertificate":false},"code":-1001,"error":"The request timed out.","success":false} {code} I created a [question about this problem|https://developer.appcelerator.com/question/181272/httpclient-not-working-for-ios-after-sdk-330-for-consuming-a-soap-service-over-https] in the forum too because I finished all my resources looking for an answer. I attached a project with all the resources described above. Please consider to take a look at this, I know that SOAP and XML sucks but it's not my fault that my client's infrastructure is built on this. Thank you for your time.
Story points: 8