22
22
class HTTPAdapterWithRandomDnsResolver (HTTPAdapter ):
23
23
# override to get connection to random host
24
24
def get_connection (self , url , proxies = None ):
25
+ # resolve cname to hostnames
26
+ dns_records = resolve_host_in_url (url )
27
+ random .shuffle (dns_records )
25
28
# parse URL
26
29
parsed = urlparse (url )
27
- host = parsed .hostname
28
- port = parsed .port
29
- if port is None :
30
- if parsed .scheme == "http" :
31
- port = 80
32
- else :
33
- port = 443
34
- # check record
35
- if parsed .hostname in dnsMap :
36
- dns_records = dnsMap [parsed .hostname ]
37
- else :
38
- family = allowed_gai_family ()
39
- dns_records = socket .getaddrinfo (host , port , family , socket .SOCK_STREAM )
40
- dns_records = list (set ([socket .getfqdn (record [4 ][0 ]) for record in dns_records ]))
41
- dnsMap [parsed .hostname ] = dns_records
42
- dns_records = copy .copy (dns_records )
43
- random .shuffle (dns_records )
44
30
# loop over all hosts
45
31
err = None
46
32
for hostname in dns_records :
47
- addr = hostname
48
- if parsed .port is not None :
49
- addr += ":{0}" .format (parsed .port )
50
- tmp_url = parsed ._replace (netloc = addr ).geturl ()
33
+ tmp_url = replace_hostname_in_url (url , hostname )
51
34
try :
52
35
con = HTTPAdapter .get_connection (self , tmp_url , proxies = proxies )
53
36
# return if valid
@@ -60,8 +43,12 @@ def get_connection(self, url, proxies=None):
60
43
return None
61
44
62
45
63
- # utility function to get HTTPAdapterWithRandomDnsResolver
64
- def get_http_adapter_with_random_dns_resolution ():
46
+ # utility function to get a session object with HTTPAdapterWithRandomDnsResolver
47
+ def get_http_adapter_with_random_dns_resolution () -> requests .Session :
48
+ """
49
+ Utility function to get a session object with custom HTTPAdapter which resolves host in URL randomly to distribute access to DNS load balanced HTTP services
50
+ :return: session object
51
+ """
65
52
session = requests .Session ()
66
53
# no randomization if panda is behind real load balancer than DNS LB
67
54
if "PANDA_BEHIND_REAL_LB" in os .environ :
@@ -70,3 +57,61 @@ def get_http_adapter_with_random_dns_resolution():
70
57
session .mount ("http://" , adapter )
71
58
session .mount ("https://" , adapter )
72
59
return session
60
+
61
+
62
+ # resolve a host in a given URL to hostnames
63
+ def resolve_host_in_url (url : str ) -> list [str ]:
64
+ """
65
+ Resolve a host in a given URL to hostnames
66
+ :param url: URL
67
+ :return: list of hostnames
68
+ """
69
+ # parse URL
70
+ parsed = urlparse (url )
71
+ host = parsed .hostname
72
+ port = parsed .port
73
+ if port is None :
74
+ if parsed .scheme == "http" :
75
+ port = 80
76
+ else :
77
+ port = 443
78
+ # check record
79
+ if parsed .hostname in dnsMap :
80
+ dns_records = dnsMap [parsed .hostname ]
81
+ else :
82
+ family = allowed_gai_family ()
83
+ dns_records = socket .getaddrinfo (host , port , family , socket .SOCK_STREAM )
84
+ dns_records = list (set ([socket .getfqdn (record [4 ][0 ]) for record in dns_records ]))
85
+ dnsMap [parsed .hostname ] = dns_records
86
+ return copy .copy (dns_records )
87
+
88
+
89
+ # replace hostname in URL
90
+ def replace_hostname_in_url (url : str , new_host : str ) -> str :
91
+ """
92
+ Replace hostname in URL
93
+ :param url: original URL
94
+ :param new_host: new hostname
95
+ :return: new URL with replaced hostname
96
+ """
97
+ parsed = urlparse (url )
98
+ if parsed .port is not None :
99
+ new_host += f":{ parsed .port } "
100
+ return parsed ._replace (netloc = new_host ).geturl ()
101
+
102
+
103
+ # replace hostname in URL randomly
104
+ def replace_hostname_in_url_randomly (url : str ) -> str :
105
+ """
106
+ Replace hostname in URL randomly
107
+ :param url: original URL
108
+ :return: new URL with new hostname randomly chosen from resolved hostnames
109
+ """
110
+ # no replacement if panda is behind real load balancer than DNS LB
111
+ if "PANDA_BEHIND_REAL_LB" in os .environ :
112
+ return url
113
+ # resolve cname to hostnames
114
+ dns_records = resolve_host_in_url (url )
115
+ # choose one IP randomly
116
+ random .shuffle (dns_records )
117
+ return replace_hostname_in_url (url , dns_records [0 ])
0 commit comments