Android Question Can I use okhttp's httpdns function?

dune3000

Member
Licensed User
Longtime User
Hi, guys
I use okhttputils to download some files from server, the server use cdn, so its domain name points to many ip, some ip are fast and others are slow, I want to fix one ip to the server's domain name, after google it, looks like okhttp has httpdns function can do this job.
 

DonManfred

Expert
Licensed User
Longtime User
Honestly i do not understand anything from what you wrote about okhttputils.
okhttputils2 does not have any dns methods. This is part of the underlying System Transport.
 
Upvote 0

dune3000

Member
Licensed User
Longtime User
Sorry for my bad English, I found a snippet about setting up dns on okhttpclient, is it possible to implement it in b4a?
B4X:
public class DnsFactory {
    public static Dns getDns() {
        switch (Environment.getType()) {
            case Environment.TYPE_TEST:
                return new DevDns();
            case Environment.TYPE_PRE_RELEASE:
                return new PreReleaseDns();
            case Environment.TYPE_ONLINE:
            default:
                return Dns.SYSTEM;
        }
    }

    // DevDns
    private static class DevDns implements Dns {
        @Override
        public List<InetAddress> lookup(String hostname) throws UnknownHostException {
            return SYSTEM.lookup(hostname);
        }
    }

    // PreReleaseDns
    private static class PreReleaseDns implements Dns {
        @Override
        public List<InetAddress> lookup(String hostname) throws UnknownHostException {
            // hostname ip white list,set Host to IP manually
            if ("xxxx".equals(hostname)
                    || "xxxx".equals(hostname)
                    || "xxxx".equals(hostname)
                    || "xxxx".equals(hostname)) {
                InetAddress byAddress = InetAddress.getByAddress(hostname, new byte[]{xx,xx,xx,xx});
                return Collections.singletonList(byAddress);
            } else {
                return SYSTEM.lookup(hostname);
            }
        }
    }
}

OkHttpClient.Builder builder = new OkHttpClient.Builder()
                .connectTimeout(20, TimeUnit.SECONDS)
                .readTimeout(30, TimeUnit.SECONDS)
                .writeTimeout(30, TimeUnit.SECONDS)
                builder.dns(DnsFactory.getDns());
 
Upvote 0

drgottjr

Expert
Licensed User
Longtime User
assuming the code runs as is, and assuming it does what you think it does, you could leave it as inline java in your b4a app, write a little helper function (or method, depending) and call your helper from b4a with a java object. you pass the host name, and your helper returns a list of candidates or a single candidate back to b4a and you stuff it in the url of your okhttputils2 job. you'll need the required imports to resolve references.

i believe there are some issues that might make it impossible, frustrating, difficult and/or time consuming to get it to work in b4a (with java object and reflection). but there are some members who might see this as an interesting challenge. if you were looking at java code, it sounds to me like you could handle my first suggestion without too much hassle. if you have all the necessary pieces for the inline java (ie, the imports), it will blend perfectly with a b4a app and a single java object declared in the app. if you don't program in java, then you'll have to wait and see if somebody thinks it's worth the time to do it for you. i certainly intend to take a look at it, although i'm not totally convinced it will actually make a difference. cdn is usually set up to optimize the distribution.
 
Upvote 0

drgottjr

Expert
Licensed User
Longtime User
i could be mistaken, but all that code does is a straight dns lookup (against a supplied host name). the programmer added a "white list" feature. i don't follow the context in which it was developed, and there is no reason why you cannot simply look up an ip address and use it in your download. b4a's okhttputils works with a domain name or an ip address. you can create your own white list if that's what you need. again, i could be mistaken (i do not believe that is the case), but that code snippet doesn't do anything for you.

there is no guarantee that a hand-chosen ip address is going to be faster than cdn. and it is quite possible that, even if you try to access an ip address by hand, that the server will not redirect the request to the cdn (that is what usually happens anyway). servers using cdn use it for a reason.

so to revert to your original question, i believe it is possible to access a list of ip addresses for a give domain through the java dnslookup utility. that is, in fact, what your code snippet does. the programmer had something else in mind beyond that, but it doesn't address your cdn issue. anyway, a dns lookup is probably easy with a java object in b4a. you get your list of ip addresses and see how far you can go with them. if one of them works, you save it for next time.

i hope all is clear.
 
Upvote 0

drgottjr

Expert
Licensed User
Longtime User
run this. it's an example of querying the dns for domains with multiple ip addresses. i implemented it with inline java, but i'm sure it can be done in b4a.
in both cases, a b4a java object is required to access the underlying classes. i'm not going to bother with the b4a approach, since a completely different set of network utilities may be necessary to guarantee receiving a list of addresses from a domain that has more than 1. querying the dns alone does not guarantee you'll get a list back. in fact you may even get a different address back the next time you query a big domain. that's the way dns was designed.

i tried a few different domains (among which google), so the code works. but what is returned does not necessarily address your issue. and i apoligize for not wanting to try some more things, but trying to override a complex system of cdn used by some of the largest tech companies in the world with your android phone is kind of quixotic.

anyway, if you want to find some domain's ip address with your phone, the attached will do that. enjoy.
 

Attachments

  • gdns.zip
    8.5 KB · Views: 110
Upvote 0

drgottjr

Expert
Licensed User
Longtime User
here. this is the same thing but using just b4a. the original code is still there; it's just commented out. in case you were wondering how b4a can access stuff not usually available. you can compare the 2 approaches. note: if you feed this version a domain that does not exist, it will probably crash. the previous version traps for that eventually. this version could do that too, but i just put it together to as a demo.
 

Attachments

  • gdns2.zip
    8.7 KB · Views: 111
Upvote 0

dune3000

Member
Licensed User
Longtime User
Thank @drgottjr very much for your help.
But I think I didn't make my request clearly, for example:
when I let okhttp to download a file from www.amazon.com, okhttp will lookup the ip of www.amazon.com from dns, it maybe returns one or multi ips,
123.123.123.1
123.123.123.2
123.123.123.3
...
okhttp will take the first one or random one (I'm not sure), but I just want okhttp uses 123.123.123.2 to make connetion with www.amazon.com
 
Upvote 0

drgottjr

Expert
Licensed User
Longtime User
enter 123.123.1232 in your http call. as i indicated previously, it works with a domain name or an ip address. if you supply a domain name, it has to do the lookup. if you supply the ip address, it doesn't.

just to clarify, okhttp takes the one it has been given by the dns lookup. it's the dns lookup that can supply a random address.
 
Upvote 0

dune3000

Member
Licensed User
Longtime User
I tried to modify okhttp wrapper, add few lines to it,
B4X:
public OkHttpClient.Builder sharedInit(String EventName) {
        okhttp3.OkHttpClient.Builder builder = new okhttp3.OkHttpClient.Builder();
        this.eventName = EventName.toLowerCase(BA.cul);
        setTimeout(builder, 30000);
        CookieManager cm = new CookieManager();
        cm.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
        builder.cookieJar(new JavaNetCookieJar(cm));
        builder.dns(MyDns);
        return builder;
    }
private static class MyDns implements Dns {
        @Override
        public List<InetAddress> lookup(String hostname) throws UnknownHostException {
            if ("cdn.examplesite.com".equals(hostname)) {
                InetAddress byAddress = InetAddress.getByAddress(hostname, new byte[]{123,123,123,123});
                return Collections.singletonList(byAddress);
            } else {
                return SYSTEM.lookup(hostname);
            }
        }
    }

but got error while compile it using slc, how to fix it?
B4X:
Starting step: Compiling Java code.
javac 1.8.0_40
D:\B4A_Projects\SimpleLibraryCompiler\Libs_OkHttp\src\anywheresoftware\b4h\okhttp\OkHttpClientWrapper.java:138: error: cannot find symbol
        builder.dns(MyDns);
                    ^
  symbol:   variable MyDns
  location: class OkHttpClientWrapper
Note: Some input files use or override a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
1 error
 
Upvote 0

OliverA

Expert
Licensed User
Longtime User
Maybe an alternative (only if I understand correctly what you want):
Class module (in my case named ReplaceHostname)
B4X:
Sub Class_Globals
    Private hosts As Map
    Private sb As StringBuilder
End Sub

'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize(hostsMap As Map)
    hosts = hostsMap
End Sub

public Sub fixURL(url As String) As String
    Log($"url in: ${url}"$)
    Dim startIndex As Int = url.IndexOf("//")
    If startIndex = -1 Then
        startIndex = 0
    Else
        startIndex = startIndex + 2
    End If
    
    Dim stopIndex As Int = url.Length
    
    Dim slashIndex As Int = url.IndexOf2("/", startIndex)
    If slashIndex <> -1 And slashIndex < stopIndex Then stopIndex = slashIndex
    
    Dim colonIndex As Int = url.IndexOf2(":", startIndex)
    If colonIndex <> -1 And colonIndex < stopIndex Then stopIndex = colonIndex
    
    Dim questionIndex As Int = url.IndexOf("?")
    If questionIndex <> -1 And questionIndex < stopIndex Then stopIndex = questionIndex
    
    Dim hostname As String = url.SubString2(startIndex,stopIndex)
    
    Dim newHostname As String = hosts.GetDefault(hostname, "")
    
    If newHostname <> "" Then
        sb.Initialize
        sb.Append(url.SubString2(0, startIndex))
        sb.Append(newHostname)
        sb.Append(url.SubString(stopIndex))
        Return sb.ToString
    Else
        Return url
    End If
End Sub

When used, do something like
B4X:
    Dim hosts As Map
    Dim rh As ReplaceHostname
    hosts = CreateMap("www.youtube.com": "123.123.123.123", "youtube.com": "123.123.123.123")
    rh.Initialize(hosts)

Test code
B4X:
    Log(rh.fixURL("http://www.blog.classroom.me.uk/index.php"))
    Log(rh.fixURL("http://www.youtube.com/watch?v=ClkQA2Lb_iE"))
    Log(rh.fixURL("https://www.youtube.com/watch?v=ClkQA2Lb_iE"))
    Log(rh.fixURL("www.youtube.com/watch?v=ClkQA2Lb_iE"))
    Log(rh.fixURL("ftps://ftp.websitename.com/dir/file.txt"))
    Log(rh.fixURL("websitename.com:1234/dir/file.txt"))
    Log(rh.fixURL("ftps://websitename.com:1234/dir/file.txt"))
    Log(rh.fixURL("example.com?param=value"))
    Log(rh.fixURL("https://facebook.github.io/jest/"))
    Log(rh.fixURL("//youtube.com/watch?v=ClkQA2Lb_iE"))
    Log(rh.fixURL("http://localhost:4200/watch?v=ClkQA2Lb_iE"))
    Log(rh.fixURL("http://localhost"))
Results:
B4X:
url in: http://www.blog.classroom.me.uk/index.php
http://www.blog.classroom.me.uk/index.php
url in: http://www.youtube.com/watch?v=ClkQA2Lb_iE
http://123.123.123.123/watch?v=ClkQA2Lb_iE
url in: https://www.youtube.com/watch?v=ClkQA2Lb_iE
https://123.123.123.123/watch?v=ClkQA2Lb_iE
url in: www.youtube.com/watch?v=ClkQA2Lb_iE
123.123.123.123/watch?v=ClkQA2Lb_iE
url in: ftps://ftp.websitename.com/dir/file.txt
ftps://ftp.websitename.com/dir/file.txt
url in: websitename.com:1234/dir/file.txt
websitename.com:1234/dir/file.txt
url in: ftps://websitename.com:1234/dir/file.txt
ftps://websitename.com:1234/dir/file.txt
url in: example.com?param=value
example.com?param=value
url in: https://facebook.github.io/jest/
https://facebook.github.io/jest/
url in: //youtube.com/watch?v=ClkQA2Lb_iE
//123.123.123.123/watch?v=ClkQA2Lb_iE
url in: http://localhost:4200/watch?v=ClkQA2Lb_iE
http://localhost:4200/watch?v=ClkQA2Lb_iE
url in: http://localhost
http://localhost

Note #1: As long as I understand exactly what you want, this code should work for you and it should be platform agnostic (B4A/B4J/B4i) with no Java magic necessary
Note #2: Inspiration: https://stackoverflow.com/a/23945027
Note #3: You can easily comment out the log statement in the fixURL method. Also, feel free to adapt/change/rename to your content (if it works for you)
Note #4: Why did I not use regex? Good question, thought this would be simpler on my brain (regex can get tricky)
Note #5: Why did I use StringBuilder instead of returning url.Replace(hostname, newHostname)? Because of something like
localhost:8080?localhost
Silly, but if I were to replace localhost with another hostname, it would replace all occurrences of localhost (instead of just the first one).
 
Upvote 0

dune3000

Member
Licensed User
Longtime User
Thanks OliverA, but your solution is not what I want, the web server that I want to access can not be accessing by ip directly, the web server is on cdn (
Content Delivery Network)
 
Upvote 0
Top