Search code examples
firemonkeyindyc++builder-10.2-tokyo

TIdHTTP indy component and IPVersion property (firemonkey)


i am using the TIdHTTP component on a multi-device app (building in Rad Studio Tokyo 10.2.3). All i'm doing is downloading a file to my local app folder (iOS). I want to make sure it works with IPv6 but i don't see the "IPVersion" property for TIdHTTP. I see it on other indy components in rad studio (e.g. IdFTP).

Is there a way to set IPVersion in code for the TIdHTTP component? Below is a snip of the code i'm using to download the file. If it fails on IPv4 it is supposed to try IPv6 next:

UnicodeString LFileName = System::Ioutils::TPath::Combine(System::Ioutils::TPath::GetDocumentsPath(), "myfile.txt");
TFileStream* fs = new TFileStream(LFileName, fmCreate);
Form1->TIdHTTP->ConnectTimeout = 8000;  // give it 8 seconds
Form1->TIdHTTP->ReadTimeout = 8000;

try
{
    UnicodeString URL = "http://myservername.com/myfile.txt";
    Form1->TIdHTTP->Get(URL, fs);
    Form1->TIdHTTP->Disconnect();  
    // ShowMessage("Good download via IPv4");
}
catch(const System::Sysutils::Exception &)
{
    try
    {
     Form1->TIdHTTP->Disconnect(); // clean up from IPv4 try
     UnicodeString URL = "http://[myservername.com]/myfile.txt";
     Form1->TIdHTTP->Get(URL, fs);
     Form1->TIdHTTP->Disconnect();  
     // ShowMessage("Good download via IPv6");

Righ now i just put brackets around the domain name in hopes that this would work for IPv6...i won't know for sure until i can get a truly IPv6 only network setup (working on it).

UPDATE: I just had an app accepted to Apple Store that uses this approach so obviously it passed IPv6 testing. FYI


Solution

  • I found this discussion in which Remy Lebeau states:

    TIdHTTP parses IP version info only from the URL itself, so if you need to send a request to an IPv6 target (IP address or hostname), you have to tell TIdHTTP to use IPv6 by wrapping the hostname portion of the URL in square brackets, eg:

    s := idHTTP.Post('http://[IPv6 address or hostname]/resource', ...);
    

    Otherwise TIdHTTP uses IPv4 instead.

    This bracketed syntax is a requirement when using IPv6 address literals in URLs, per RFCs 2732 and 3986. But when using a hostname instead, there is no such syntax requirement. Connecting to a hostname requires a DNS lookup to discover the host's available IP addresses and their respective IP versions, and then connect to those addresses as needed until one succeeds.

    Indy does not currently implement that kind of connection model. It first allocates a socket based on the client's IPVersion property, THEN performs a DNS request suited for that IPVersion, and then finally connects to the first IP address reported by DNS. This is a known limitation of Indy, which would require a redesign of Indy's internal logic to fix. However, it can be worked around manually in most Indy client components by performing your own DNS lookup ahead of time and then looping through the results, setting Indy's Host and IPVersion properties and calling Connect() on each loop iteration.

    However, TIdHTTP is a special case, because it derives the Host and IPVersion values from the requested URL, overriding anything the calling code might set manually. However, it is also because of this model that would potentially allow TIdHTTP to be updated to perform its own DNS loop internally without having to rewrite all of Indy's client components to match. Except that Indy does not currently implement a function that returns all of the IP addresses of a hostname, only the first IP address of a particular version. Until such a function is added to Indy in a cross-platform manner, TIdHTTP cannot be updated to auto-detect IPv4/IPv6 hosts.

    As such, this confirms my approach should work (trying IPv4 first and then IPv6 if that fails) using the brackets.

    Also, I edited my code to show a "Disconnect" prior to trying the IPv6 in case the IPv4 left a mess.