[Estimated Reading Time: 10 minutes]

Way back in September last year, Mason Wheeler blogged about his first experiences with developing for Android using Oxygene. I said at the time that I would look into reproducing his efforts and respond.

I didn’t get around to that right away and, as I think I have mentioned previously, then got rather busy and honestly, I completely forgot about it. My bad. 🙁

But Mason mentioned this post again the RemObjects Talk forums recently and appears to be under the impression that he has found some fundamentally broken aspect of Android. I think he is mistaken and ironically simply needs to take his own advice.

The entirety of his post can be summed up in one sentence that is actually found within the post itself:

If you don’t know anything about it, it might take a bit of time to research it. But once you do know about it, this only takes a few minutes to write, and it works.

When made in the original post this statement refers to the obscurity of the Indy-based mechanism class dealing with multipart forms. The irony being that almost every criticism he then goes on to level at the “Android” approach to his simple problem can be dismissed with exactly the same response.

Threading

First of all there is the complaint that you are not permitted to perform network calls in the UI thread on Android.

But surely anybody that has done any serious network coding – even in Delphi – knows that this is just good sense. And the same applies with Indy. “Why is my UI locking up when I make a HTTP request ?” is a common refrain among Indy “newbies” (and not so newbies). So I see this being enforced as no bad thing. Start as you mean to go on and all that.

Really, it’s not that difficult, and better to learn the right approach at the start, rather than getting into all sorts of bad habits that you are then forced to deal with only later when you have to unlearn those habits and potentially re-write a whole heap of code that you didn’t realise was going to cause you grief (so gosh-darned easy was it to write).

But I digress.

It is stated that AsyncTask is “how you run something in a background thread on an Android device“.

Which is not quite accurate.

AsyncTask is only one way to achieve this, and not always the most appropriate or straightforward. In fact, I haven’t yet felt the need to use AsyncTask so can’t speak to whether it is truly a “mess” or if perhaps instead simply not enough time was spent researching it for the purposes of his blog post case.

What I can say is that one alternative that I have used, and used on this occasion – using a subclassed Thread – is simplicity itself and that the Android equivalent of “Synchronize()” for then performing UI updates on the UI thread is similarly straightforward, involving a simple, inline, anonymous subclass of a Runnable with an overridden run() method containing your UI code, which you then pass to the appropriate Activity.runOnUiThread().

Having said that, if you don’t know anything about it, it might take a bit of time to research it. But once you do know about it, this only takes a few minutes to write, and it works.

Here is the thread subclass:

  POSTThread = public class(Thread)
  public
    method run; override;
  end;

And here is the code in a button click listener for starting this thread:

    new POSTThread().start;

I didn’t even really need an explicit class. I could have used an anonymous, inline class for this as well, but it helped on this occasion I think to make the Thread more obviously explicit, for illustrative purposes. But I’ll demonstrate anonymous inline classes when I come to show the implementation of the run() method at the end of this post (which will also then show how to easily perform code on the UI thread).

Meanwhile, moving on to Mason’s next problem…

URLEncoded or MultipartForm ?

Make up your Mind

Irritation is then expressed at the hassle involved in setting up a multipart form request.

The mistake that I think was made here was that the research tool used was code completion suggestions and guesswork. Things were identified that looked like they corresponded to the way that Indy worked, and assumptions then made about how they work.

It certainly seems that there was no attempt to initially research how multi-part form content is best formed on Android. For, had this been done, the Apache MultipartEntityBuilder class would quickly have been discovered.

Certainly this is not part of the Android framework. But it is an open source library that is easily added to an Oxygene project. Simply download and unzip the jars and add two references to the solution (httpcore-4.3.2 and httpmime-4.3.5). Yes, these are Java libraries, but Oxygene speaks Java like a native, so this is not a problem. At all.

Curiously, when Mason posted his problem on StackOverflow, he then accepted an answer which did not actually meet his needs !

The answer he accepted yields a x-www-form-urlencoded entity in his request, not a multi-part form. It is also unnecessarily complex.

Here’s the code for actually setting up a multi-part form request, as specified in his original Delphi/Indy code:

  var
    req: HttpPost;
    mpe: MultipartEntityBuilder;
  begin
    req   := new HttpPost('http://posttestserver.com/post.php');

    mpe := MultipartEntityBuilder.create;
    mpe.addTextBody('Username', 'test');
    mpe.addTextBody('Password', 'password');
    mpe.addTextBody('cmd',      'Login');

    req.Entity := mpe.build;
  end;

Once you get there, this is hardly a million miles away from the Indy approach, is it ? Different, yes. Of course. But we’re not talking Chalk vs Cheese. More like Brie vs Camembert. 🙂

Yes, it’s different when compared to Indy and if you don’t know anything about it, it might take a bit of time to research it. But once you do know about it, this only takes a few minutes to write, and it works.

You can see from this code that my test is going to make use of a generously provided public web server specifically for testing POST requests. It’s not exactly complicated. Now, this server won’t enable me to test another of Mason’s complaints (the response to a 302 redirect) but we’ll deal with that when we get to it.

Meanwhile, worth noting is that this in fact is the bulk of the code in my entire working example.

It’s the Same !!

Mason got into such a habit of complaining about Android that he manages to accuse it of behaving in exactly the same way as Indy and turn this into a criticism.

It is observed that the HttpPost class (or instance thereof) doesn’t do anything and that you have to use an HttpClient. Last time I checked, the Indy TIdHttpRequest doesn’t do anything either. You have to use a TIdHttp. No ?

Yes, the mechanism for issuing a request on Android is not the same as Indy, but – just as with the MultipartEntityBuilder – it’s not exactly complicated, and in both cases it has to be done through the HTTP client object.

I think it’s fair to say that if you don’t know anything about it, it might take a bit of time to research it. But once you do know about it, this only takes a few minutes to write, and it works.

Indy is Better Because Of The Bugs

Next on the hit-list is the way that Android handles a 302 redirect response (or rather, the way it doesn’t) to a POST request where Indy takes the redirect in it’s stride and handles it for him.

Depending on the version of Indy that Mason is using, this could be an old bug in Indy. Or, if a more recent version, he has enabled an option that restores that previous buggy behaviour. The HTTP protocol specification stipulates that clients only automatically follow GET and HEAD 302 redirects, “MUST NOT” automatically follow them otherwise, without first confirming with the user, and should NOT change the method (to a GET).

In fact, it seems that Mason is dealing with a buggy server as well since from the description of the redirect response he is expecting it sounds like it should be a 303 redirect, not a 302 at all.

Mason says that his Indy code behaves exactly like a browser would. That may be true, but perhaps only an erroneously implemented browser, dealing with an erroneously implemented server. 😉

But, if you wish to implement an auto-redirect in the same way that Indy does, it should be a simple matter to do so. See: http://stackoverflow.com/questions/5169468/handling-httpclient-redirects

Why Is It So Hard To Get the Response as a String !?!!

Answer: It’s Not

Finally, the issue of obtaining the response content in a string form is bemoaned.

Indy gives me the HTML“.

Well, so does Android. Oooooh, you mean you want the HTML content in a string variable ?

Ok:

    str := EntityUtils.toString(response.Entity);

Now admittedly this is bare-bones. You should ensure that the response is valid and that there is an Entity present to convert, but this is precisely why you would put it in a convenience method if you were doing this a lot, which is all that the Indy implementation of Post() as a function returning a string does.

In fact, the reason that Indy put’s the Delphi equivalent in a helper method is because without that helper method, it’s darned sight more work:

var
  LResponse: TMemoryStream;
begin
  LResponse := TMemoryStream.Create;
  try
    Post(AURL, ASource, LResponse);
    LResponse.Position := 0;
    Result := ReadStringAsCharset(LResponse, ResponseCharset{$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF});
                                                                                  
  finally
    FreeAndNil(LResponse);
  end;
end;

If I had to do that every time in Android I would absolutely want to hide it all away in a helper method.

But wait – that’s exactly what EntityUtils is, except that it is arguably a cleaner, more elegant, and more intuitive approach, providing utilities that can be used on any “Entity”, rather than duplicating basically identical code all over the place inside numerous wrapper methods, as is the case with Indy.

Copy and Paste is undeniably one mechanism for code re-use, but it can be taken too far and has largely been replaced by Object Oriented techniques in the modern era. 😉

Bringing It All Together

So, let’s put everything we have so far together. This is the complete implementation of the POSTThread.run() method:

  method POSTThread.run; 
  var
    http: AndroidHttpClient;
    req: HttpPost;
    mpe: MultipartEntityBuilder;
    response: org.apache.http.HttpResponse;
    s: String;
  begin
    http  := AndroidHttpClient.newInstance('HTTP TEST CLIENT');
    req   := new HttpPost('http://posttestserver.com/post.php');

    mpe := MultipartEntityBuilder.create;
    mpe.addTextBody('Username', 'test');
    mpe.addTextBody('Password', 'password');
    mpe.addTextBody('cmd',      'Login');

    req.Entity := mpe.build;

    response := http.execute(req);
    s        := EntityUtils.toString(response.Entity);
  end;

Actually, not quite complete. I have omitted the final statement which will update my UI with the response, which needs to be performed on the UI thread.

Synchronize()

Just as there are many ways to achieve the objective of running some code on a background thread, there are also many ways to arrange for code to be performed on the UI thread. What I show here is perhaps the quickest and dirtiest, being almost directly equivalent to Synchronize().

As if that wasn’t bad enough, I have shamelessly exploited Oxygene’s ability to fabricate such Delphi nasties as “Global Variables”, even in the Java landscape where such things are usually impossible. In this case, I have declared a reference to my MainActivity in a global variable (Main). This was just to save the bother of passing the reference into the thread, saving a few seconds of typing. But this is a demo, and saving typing is what demo’s are all about, so no harm, no foul. 🙂

So, using the reference to the MainActivity, I call the runOnUiThread() method and pass in an instance of a Runnable class. Runnable, as it’s name suggest, implements a run() method (yes, Thread extends Runnable, but we don’t need another thread here, we just want to have a run() method scheduled on the existing UI Thread).

I have implemented a showResponse(String) method on my MainActivity, and all I need is for my Runnable.run() method to call it. A whole new class just to implement a one-liner override of a scaffolding method is too much like hard work, so I use an anonymous, inline class:

    Main.runOnUiThread(new class Runnable(run := method
                                                 begin
                                                   Main.showResponse(s); 
                                                 end));

And there it is, done.

Just for completeness, here’s the method in it’s entirety:

  method POSTThread.run; 
  var
    http: AndroidHttpClient;
    req: HttpPost;
    mpe: MultipartEntityBuilder;
    response: org.apache.http.HttpResponse;
    s: String;
  begin
    http  := AndroidHttpClient.newInstance('HTTP TEST CLIENT');
    req   := new HttpPost('http://posttestserver.com/post.php');

    mpe := MultipartEntityBuilder.create;
    mpe.addTextBody('Username', 'test');
    mpe.addTextBody('Password', 'password');
    mpe.addTextBody('cmd',      'Login');

    req.Entity := mpe.build;

    response := http.execute(req);
    s        := EntityUtils.toString(response.Entity);

    Main.runOnUiThread(new class Runnable(run := method
                                                 begin
                                                   Main.showResponse(s); 
                                                 end));
  end;

Not that this also includes setting up the AndroidHttpClient (http), which Mason omits from his code. The method above is everything you need to make a HTTP POST request of the required form.

Redirect handling is not addressed in this example code since I didn’t have a test server to exercise that and don’t need one for my purposes (I think I could have played some more with the test POST server to create this test, but as I say, it’s not relevant to my needs. I’ll leave Mason to try that, if he’s so inclined).

And here’s a logged request made using this code on the test server referenced in the code:


Time: Mon, 11 Aug 14 00:55:43 -0700
Source ip: 27.252.213.239

Headers (Some may be inserted by server)
REQUEST_URI = /post.php
QUERY_STRING =
REQUEST_METHOD = POST
GATEWAY_INTERFACE = CGI/1.1
REMOTE_PORT = 61529
REMOTE_ADDR = 27.252.213.239
HTTP_CONNECTION = close
HTTP_USER_AGENT = HTTP TEST CLIENT
HTTP_HOST = posttestserver.com
CONTENT_TYPE = multipart/form-data; boundary=g6uL0L3BNb_xNFHi_tHz4jGazg-AO-tLCvUSc
CONTENT_LENGTH = 574
UNIQUE_ID = U@h2-9Bx6hIAAAzCAfgAAAAG
REQUEST_TIME_FLOAT = 1407743743.6923
REQUEST_TIME = 1407743743

Post Params:
key: 'Username' value: 'test'
key: 'Password' value: 'password'
key: 'cmd' value: 'Login'
Empty post body.

== Multipart File upload. ==
Received 0 file(s)

It’s Really Not Difficult

To be clear here. I had not tackled any aspect of Android networking or HTTP requests until I started looking into this 4 hours ago. I came to this entirely “cold”.

In fact, it took me as long to write this post (2 hours) as it did to reach a working example from a standing, cold start. Quite why it took Mason 2 days and yet he still didn’t get it working is utterly beyond me.

When calling upon Stack Overflow in my research I paid attention to question and answer dates, aware that things can change and that perhaps I was coming across things that had only come to light since Mason went through this exercise. But no, they all pre-dated his post of September 2013 (with the exception of one question dealing with MultipartEntityBuilder, though the class itself predates Sep 2013 by some way and there are other questions on Stack Overflow that deal with it). He should have been able to find exactly the same information that I was working from.

I should also say that it was a fortuitous coincidence that Mason should remind me (inadvertently) of his original post on an evening where investigating the development of a HTTP client was on my to do list, so I thank him for providing such a concrete framework for that investigation ! I’ve learned a lot in 2 of the last 3 hours 🙂

But as a consequence I’m afraid I cannot agree that this is particularly difficult, unless you make it so for yourself by making assumptions and constantly seeking to achieve things in the familiar ways rather than researching the new ways that you need.

I do however agree, whole-heartedly and unreservedly, when it is said about anything that if you don’t know anything about it, it might take a bit of time to research it. But once you do know about it, this only takes a few minutes to write, and it works.

🙂

Bookends and Footnotes

One final note is that Mason’s post is bookended by questioning and then challenging the notion that using Oxygene on Android avoids having to take a “Java Pill”.

In doing so he makes the common mistake of conflating a language and it’s platform/frameworks.

When I said in an earlier post of my own that this absence of Java Pill was one of the reasons I liked Oxygene, I was referring obviously to the LANGUAGE. Of course you cannot avoid dealing with the Java – and Android – frameworks. Working with the native platform frameworks is the very philosophy underpinning Elements !

And with good reason: those frameworks offer a lot, if you are prepared to put the leg-work in to learning them.

Once upon a time I am sure that Mason knew as little about Indy as he now does about Android, and I am sure his knowledge of Indy is the result of far more than the 2 days he spent trying to get his code working on Android.

Sure, with Indy you can get a lot of networking development done very quickly in Delphi, if you know Indy. But Indy is not part of the platform API (imagine doing all this in WinSocks!!). It’s not even – technically – part of Delphi. It just comes “in the box”. And make no mistake, Indy is not free from bugs or issues itself.

And once you know the Android frameworks just as well, you can be just as productive in those. And as a bonus there is a much wider community of developers and resources to call on for assistance and guidance than has ever been – or will ever be – the case with Indy.

14 thoughts on “Delayed POST Response”

  1. Interesting writeup, but you missed a couple of details:

    Mason got into such a habit of complaining about Android that he manages to accuse it of behaving in exactly the same way as Indy and turn this into a criticism.

    It is observed that the HttpPost class (or instance thereof) doesn’t do anything and that you have to use an HttpClient. Last time I checked, the Indy TIdHttpRequest doesn’t do anything either. You have to use a TIdHttp. No ?

    No! With Indy, you don’t use TIdHttpRequest in the first place. You don’t have to configure the request object and poke around for helper methods to deal with the output. It’s all in the client. TIdHttp–the client itself–has an overloaded Post method that can either return a string or fill a stream with returned data. That’s far simpler than the method you described, and it’s intuitive and easily discoverable, the first thing you’d come across when researching it.

    Mason says that his Indy code behaves exactly like a browser would. That may be true, but perhaps only an erroneously implemented browser, dealing with an erroneously implemented server. 😉

    In that case, Firefox, Chrome, IE and the Android built-in web browser are all erroneously implemented. 😉

    And while you went over alternative ways to do the stuff I did manage to come up with solutions for with a little research, notably missing is any solution to the problem that finally made me throw up my hands in frustration and walk away: proper handling of cookies. Which, again, in Indy is all in the client and you don’t have to deal with it. On the Android side, I wasted well over a day just looking for solutions to that problem alone, and all I found was a bunch of other people asking questions about it that never got answered.

    1. Ugh, missed one detail I was going to mention, and no way to edit on here.

      Sure, with Indy you can get a lot of networking development done very quickly in Delphi, if you know Indy.

      Nope. With Indy you can get all this stuff done very quickly from zero. If you know enough basic HTTP to know what a POST is, the process goes like this:

      Q: How do I send a HTTP POST request from Indy?
      A: Well, there’s a class here called TIdHTTP that’s used for making HTTP requests. Let’s look at that. Oh, look, it has a method called POST. Let’s assume, intuitively, that it will make a POST request until demonstrated otherwise.
      Q: So how do I use it?
      A: Pass in a URL and a parameters object, and depending on which overload you pick, either a TStream to receive the response content, or it will just generate a string as a return value.
      Q: What about redirects and cookies?
      A: You don’t actually end up having to ask this question, because TIdHTTP automagically handles it all for you under the hood, just like a browser. Just set up a call to TIdHttp.Post, and you’re done. It literally is just. That. Simple.

      1. Pass in a URL and a parameters object

        You skipped the part about how you go about discovering the multipart parameters object, which by your own admission is “obscure”. And here you betray the fact that you have to know Indy to know that this “just works”. If you aren’t interested in the response to your POST you might intuitively choose the overload of Post() that does not accept a response stream, not realising that the string this overload variant returns is the response content rather than the status text, and that it will create a response stream and (attempt to) convert it to a string even though this is a side effect that you aren’t actually interested in.

        You are also conveniently ignoring the fact that Indy “just works” in this instance by virtue of the fact of violating a part of the HTTP specification that applies in this case. By automatically following a 302 redirect in a response to a POST, Indy flat out gets it wrong, regardless of whether it follows the redirect with a GET or another POST.

        1. Again, this standards-violating behavior is exactly the same thing that every web browser I’ve tested with does. Try it. See if you can find any browser anywhere that doesn’t automatically follow 302s. (And what would it do instead?) Returning a 302 from a POST request to have the browser convert it to a GET, to avoid problematic behavior if the user wants to refresh, for example, is pretty standard practice on the Web.

          When the standards document says one thing, and 100% of implementors do the same different thing, “flat-out gets it wrong” is not as simple a determination to make.

          1. This will make interesting reading for you I think:

            http://blogs.msdn.com/b/ieinternals/archive/2011/08/19/understanding-the-impact-of-redirect-response-status-codes-on-http-methods-like-head-get-post-and-delete.aspx

            In particular the section where the author examines the behaviour of numerous browsers w.r.t 3xx responses to various requests (both “changing the method to GET” and “prompt before redirect” aspects of the protocol). Quite clearly, “100% of implementations” do not all do “the same different thing“.

    2. No! With Indy, you don’t use TIdHttpRequest in the first place. You don’t have to configure the request object

      Actually, sometimes you do, and the fact that when you do is the exception rather than the norm makes figuring out how and why all the more difficult.

      Intuitive and Easily discoverable only applies to Indy up to a point. Perhaps for most cases this is enough, and perhaps it has been for you. But once you find yourself having to go beyond that point, things get very messy with Indy, very quickly, not least because you have to take a different approach to that which you have become used to.

      Which comes back to the point I made about learning to do things the “right” way from the start.

      And when you find a problem in Indy (or where the problem is with a particular server or service for which Indy’s convenient assumptions do not work), the fact that everything is buried deep within convenience methods makes it all the more problematic to fix and change, particularly when trying to limit those changes to only specific use cases.

      As for the correct behaviour in response to a 302 redirect response to a POST request, you don’t have to take my word for it. RFC2616 is quite clear on the required behaviour:

      From section 10.3.3 302 Found:

      If the 302 status code is received in response to a request other
      than GET or HEAD
      , the user agent MUST NOT automatically redirect the
      request
      unless it can be confirmed by the user

      POST is not a GET or a HEAD, but Indy makes no distinction and simply follows the redirect. The only acknowledgment of the protocol specification in Indy is whether or not the subsequent request to the redirect location is made using a GET or the original POST, and there have been different approaches taken at different times in Indy’s history. Currently it is subject to an option to enable non-compliant behaviour (which I presume you either must have enabled without mentioning – in which case I’d be curious to learn how you “intuited” that – or are using an old version of Indy which simply applies the incorrect behaviour regardless).

      And yes, it’s quite possible that those browsers you mention are erroneously implemented, as far as the protocol spec is concerned. The web is notorious for setting it’s own “standards”. But you can’t have it both ways. You complain that the Android HTTP client seems to be implemented for the benefit of people implementing web browsers, and then hold up Indy as a shining example of a framework that just works exactly the way a browser works and complain when the Android client doesn’t !! :O

      As for the cookies, yes there are people asking the same or similar question, and lots of other people saying that a HttpContext cookie store “just works”.

      So let’s see… what is more likely ? Some people have made a mistake in their code ? Or that millions of internet connected Android devices have managed to stumble their way through life despite lacking functioning support for cookies ? Don’t we think somebody might have noticed by now ?

      If it’s a mistake in the code, then a complete working example is needed. Trying to identify where an error might have been made from just a snippet is always problematic.

      But if you stand me up a simple server which demonstrates the behaviour you were testing then I shall extend my test case to cover it. Otherwise, I’ll deal with cookie support if and when I encounter the need.

  2. The second time I read your throwing Mason’s quote back into his face I gave up.
    Why don’t you just post a technical blog without descending into personal attacks.

    1. It’s a blog, not an Android/Oxygene reference site. Clearly, and sadly, some people don’t get my sense of humour. 🙁

  3. “Indy is Better Because Of It’s Bugs” (I guess “it’s” should be “its”?) it’s an intentional or unintentional distortion of the truth. Indy behaves AS DESIGNED. It may not conform with W3C standards (like many other browsers), but that behavior cannot be classified as a bug. From Wikipedia: “A software bug is an error, flaw, failure, or fault in a computer program or system that causes it to produce an incorrect or unexpected result, or to behave in unintended ways.”

    1. Petty typographical errors aside, let’s apply the Wikipedia definition you yourself reference:

      Wikipedia says a bug is something that causes incorrect or unexpected results.

      • Is Indy’s behaviour incorrect ? According to the specification, yes.
      • It Indy’s behaviour unexpected ? If you expect it to conform to the specification, yes.

      So it seems to me that you have made a very good case for classifying Indy’s behaviour in this area as a bug. 😉

      But, and perhaps because of this, you then also seem to be adding a bit of “desirability” into the mix. Even if it’s incorrect and unexpected by some rather unambiguous criteria (the specification) if it nevertheless yields a desirable outcome then this (apparently) trumps the definition of “bug”. I wonder, do you apply this in reverse ? i.e. if a behaviour is both correct and expected is it nevertheless a bug if it is undesirable ? O.o

      Having worked on middleware that had to deal with a wide variety of different web servers, I can testify that the assumptions in Indy, whether designed, expected or correct or not, are certainly not a universal “good fit” and when you need to vary the behaviour it can be a pig to work with in order to make those variations in a safe and adaptable fashion.

      1. W3C RFCs are not software specifications. They specify the HTTP protocol, not the software. I can freely create software, specified by me or someone else, that does not conform with some standard and/or protocol specification. This does not make the software buggy:
        1- The software does not produce incorrect results, once it passes ALL test cases created by the software developer, even though the standard or protocol specification says something different.
        2- The software does not produce unexpected results. Again the results are pretty much predictable based on the software specifications.

        You can say that the software does not conform with HTTP 1.1 spec, and you are correct. But it is not buggy. These are different things!

        In case of Indy, this behavior is user configurable: the software gives the user a choice. Using both options the outcome is correct and predictable, based on the software specification.

        In this specific case: Please note that HTTP 1.0 spec was confusing about the 302 status code and all major browsers at that time (IE and Netscape) didn’t conform with the standard. The de-facto standard came before the written standard.

        1. If you are implementing a protocol client then adherence to a protocol specification is either correct or it is not. Indy is not correct w.r.t the HTTP specification. Intentionally so ? Possibly. But neither that nor the number of other implementations that also get it wrong doesn’t alter the fact that is incorrect. If even N – 1 people claim that 2 + 2 = 5, where N is the total population of Earth, that doesn’t make it so. It’s just a lot of wrong people.

          History may well be written by the victor, but “truth” (if I can put it in those terms) is not subject to a popular vote.

          Whether Indy behaves as expected (since that is the supplementary definition you wish to employ) then this depends on how you form your expectations. Since Indy does not come with a copy of any software specification, your argument rests on an imagined “other” specification that it does comply with.

          What we do have is the Indy documentation (the usual form in which a software specification arrives in the hands of a user). But in fact, if you refer to the documentation that is provided for Indy, you will find this for the TidHttp component:

          [TidHttp] is a client implementation of the Hypertext Transfer Protocol (HTTP) as described in the Internet Standards documents: … RFC 1945 … RFC 2616

          On the face of it, this would tend strongly to suggest that in fact these RFC’s were indeed the specification to which they were working. If you think about it, unless they were to reinvent HTTP and just by a stroke of luck happen to come up with a protocol that happened to be identical to the existing HTTP, they would have to be working to the HTTP specification.

          Certainly it is the substantive documentation to which readers of the Indy documentation are referred. 😉

          (I fully expect you to dismiss this as a “Documentation error”. ;))

          But, given that the Indy documentation refers readers to the protocol specification (and does not make any mention of deviation from this specification in the area where the 302 redirect response is specifically mentioned), it is hard to see how expectations might otherwise be formed. Or are you seriously suggesting that when someone sets about implementing a HTTP client using Indy, they fire up WireShark or Fiddler and consult other browser behaviours as their reference, rather than consulting the Indy or – and thence – the protocol documentation ?

          Some feel that all of this can be “intuited” and “discovered”, though I’m unclear as to how the particulars of a given response behaviour can be intuited. It’s likely to involve what I think most people would call “diagnosis”, or “code review”, which doesn’t sound quite so warm and fuzzy as “discovery”. at least to my ears. 🙂

          The fact remains that Indy does not behave as you would expect from a reading of the protocol specification, and this does appear to be the reference spec that governs “correctness” too.

          As for the ability to configure your way around this, only one part of the bug (replacing a non GET or HEAD with a GET) can be “configured” (and only in relatively recent versions of Indy). The other part of the bug – the automatic redirect – requires code to implement the user confirmation that the specification demands (and for which a greater amount of “supporting behaviour” is found among browsers). At the very least you need an OnRedirect() event handler, complete with a particularly unhelpful set of parameters meaning that you have to jump through hoops and add scaffolding to determine when and whether any redirect intervention should be deployed in any particular case.

          Actually, I’ll give you a free tip: The VMethod parameter to the redirect event is actually initialised with the method of the original request, but other than guesswork and observation you have to inspect the source to be sure of this. The particular response code is not provided at all (the tempting looking “NumRedirect” is a red herring) and neither is there a reference to the response object itself from which it might be obtained. The Sender param is a reference to the originating TIdHttp from which you can get at everything you might need, but bizarrely Indy chooses to passed this as a TObject, so typecasting incantations are needed to get at the properties even of that.

          Seriously, having actually spent a LOT of time working with (and patching) Indy, I won’t deny that it isn’t good, but neither am I under any delusions about, let’s call it: shortcomings, and how big a pain it can be.

          Not least when the problems you encounter are with some server with an incorrect behaviour and you need to modify your client behaviour to accommodate it. Good luck asking MEGACORP.COM to change their server behaviour just to make your life with Indy easier. 🙂

          Ooops, there I go again. “Incorrect server behaviour”. How silly of me. There is of course no such thing as long as the MEGACORP server behaviour complies with MEGACORP.COM’s own specification for their server, then it isn’t incorrect and it isn’t a bug. But then our Indy client isn’t behaving as MEGACORP expect a client to behave, so when this happens (and I have had to deal with precisely this scenario) is it actually Indy that contains the bug ? But I thought we proved that there are no bugs in Indy either … oh golly.

          And not all such cases can be easily dealt with by deploying an event handler or two or even subclassing TIdHttp.

          The monolithic architecture (a “complete” HTTP client with everything neatly tucked inside – request, response and response handling etc – with very few extension points) often makes it a real pain to make changes in a way that don’t require a LOT of scaffolding to keep the change sufficiently isolated – or isolatable – from the “normal” behaviour.

          Which doesn’t alter the fact that Indy is very, very good.

          Or that alternative frameworks have their own quite distinct advantages, even if they work differently.

  4. Oh!! You don’t need to read all Indy doc to point something there to support your – wrong – conclusion. Sorry to waste your time. I won’t waste mine. If you don’t understand that “non compliant” “buggy” than we agree to disagree.

  5. “non compliant” is different from “buggy”. Seems that your blog don’t like less and greater than symbols… I think it is buggy 🙂

Comments are closed.