Titanium JIRA Archive
Titanium SDK/CLI (TIMOB)

[TIMOB-25260] iOS: HTTPclient sends additional post request on receipt of 401 error

GitHub Issuen/a
TypeBug
PriorityCritical
StatusResolved
ResolutionCannot Reproduce
Resolution Date2017-09-12T12:55:45.000+0000
Affected Version/sn/a
Fix Version/sn/a
ComponentsiOS
Labelshttpclient
ReporterIan Taylor
AssigneeHans Knöchel
Created2017-09-10T07:53:15.000+0000
Updated2018-10-04T08:54:02.000+0000

Description

When making a POST request with httpClient and the resource returns a 401 error, a second POST request is being made automatically. Similar issues have previously been reported (and resolved) with Android, but not iOS.
var httpClient = Ti.Network.createHTTPClient({	
  onload: function(e) {
    // This function is not triggered with the 401 response code
  },		
  onerror: function(e) {		
    // This function is triggered with the 401 response code, but a second POST is also made
  },
  onreadystatechange: function(e) {
    Ti.API.warn(this.readyState);
    // 1
    // 3
    // 3
    // 4
    // 4
  },
  timeout: 2000		
});

httpClient.open('POST', 'https://httpstat.us/401');

Comments

  1. Hans Knöchel 2017-09-10

    Hey there! I will move the ticket to TIMOB for further investigation, but we need some more infos from you: - Titanium SDK version? - Example endpoint? Thank you!
  2. Hans Knöchel 2017-09-10

    Ok, so after a quick investigation, here are the results: - The readyState = 4 is DONE - The request is not sent twice, because: - The readyState 3 (LOADING) is skipped in the event, instead DONE is sent - This does not happen when I log the values before they getting sent to the client, which leads to a race-condition while sending the ready-states in situations where the request finished immediately after it returns the "loading" state. - We should still fix this, but the issue would be a different then the one we investigated I would rename the ticket and description to the appropriate issue and schedule it for the next releases, but before I would be interested in your feedback to ensure we do not miss something from the initial ticket description. Thank you! *EDIT 1*: Workaround: Use e.readyState instead of this.readyState! *EDIT 2*: Proposed fix: Use dispatch_barrier_async within the APSHTTPClient library to ensure the readyState property is set before the events are triggered.
  3. Ian Taylor 2017-09-10

    Thanks for the reply. I'm on the road right now so can't give you many more details right now. To elaborate though: I included the onreadystatechange only because I used it for debugging. I don't use it in production. The endpoint I'm using is a login for a web service. When I get a 401 response the onerror callback handles this. However, I can see from the web service logs that an additional request is made. Does that help?
  4. Ian Taylor 2017-09-10

    In the onload and onerror callbacks I use this.responseText. I will try using e.responseText to see if that is a workaround.
  5. Hans Knöchel 2017-09-12

    I have tested this will multiple servers and even have set-up a local node server that received a POST request and logged it to a monitoring-console every time a new POST request was seen. That's why I came to the conclusion that either your app-logic will trigger it twice without noticing (for example with global Ti.App events not being handled correctly or recursive code in special if-statements), or your server is working not correctly and gives multiple responses. Both actually happened to me in my 7 years of Titanium / web-interaction as well, so this might be a direction to check. In any case, I will have to resolve this ticket as Cannot Reproduce unless we receive a full test-case that includes both the client code and and a server-URL that we can trigger.
  6. Ian Taylor 2017-09-12

    Here is the client code. I can send you the endpoint via email. var win = Ti.UI.createWindow({backgroundColor: "#fff"}); var pw = Ti.UI.createTextField({top:100,width:100,value:'adminman'}); var un = Ti.UI.createTextField({top:200,width:100,value:'adminman'}); var btn = Ti.UI.createButton({top:300,width:100,backgroundColor:'#26d045'}); btn.addEventListener('click', function(e) { if (Titanium.Network.online) { var client = Ti.Network.createHTTPClient({ onload:function(event) { Ti.API.info('onload '+this.responseText+ ' code: '+this.status); }, onerror:function(event) { Ti.API.info('onerror '+this.responseText+ ' code: '+this.status); }, timeout:2000 }); client.open('POST','endpoint'); client.setRequestHeader('Content-Type','application/json'); client.send(JSON.stringify({password:pw.value,username:un.value})); } }); win.add(btn); win.add(pw); win.add(un); win.open();
  7. Hans Knöchel 2017-09-12

    Thanks for sharing. I updated it with a test URL and formatted the source:
       var win = Ti.UI.createWindow({
         backgroundColor: "#fff"
       });
       var pw = Ti.UI.createTextField({
         top: 100,
         width: 100,
         value: 'adminman'
       });
       var un = Ti.UI.createTextField({
         top: 200,
         width: 100,
         value: 'adminman'
       });
       var btn = Ti.UI.createButton({
         top: 300,
         width: 100,
         backgroundColor: '#26d045'
       });
       btn.addEventListener('click', function(e) {
         if (Titanium.Network.online) {
           var client = Ti.Network.createHTTPClient({
             onload: function(event) {
               Ti.API.info('onload ' + this.responseText + ' code: ' + this.status);
             },
             onerror: function(event) {
               Ti.API.info('onerror ' + this.responseText + ' code: ' + this.status);
             },
             timeout: 2000
           });
           client.open('POST', 'https://httpstat.us/401');
           client.setRequestHeader('Content-Type', 'application/json');
           client.send(JSON.stringify({
             password: pw.value,
             username: un.value
           }));
         }
       });
       win.add(btn);
       win.add(pw);
       win.add(un);
       win.open();
       
    Which still fires the events properly and the server is only triggered once.
  8. Tommy Cornett 2018-09-06

    I am having this exact same issue in 7.1.1.GA and have narrowed down the scenario. When the server sends a 401 Unauthorized response, AND that response contains a "WWW-Authenticate" header, Appcelerator will send the request again. If the same 401 response comes back the second time, the error "cancelled" is returned. When removing the "WWW-Authenticate" header from the server response in my test app, Appcelerator behaved as expected and did not repeat the request. However, in our production instance, we are unable to remove the header from the response, so this is causing us quite a bit of grief. I have a sample Appc app, and also a small service (written in asp.net/C#) that readily produces this issue. Please reach out to me if you would like me to send this to you. I hope this allows you to reproduce and resolve this issue, and look forward to a fix. Thanks! ::Tommy
  9. Tommy Cornett 2018-09-07

    This also happens for both POST and GET... I haven't tried PUT or DELETE.
  10. David Bankier 2018-10-04

    Also getting the cancelled response.

JSON Source