1 /* 2 * Hunt - A redis client library for D programming language. 3 * 4 * Copyright (C) 2018-2019 HuntLabs 5 * 6 * Website: https://www.huntlabs.net/ 7 * 8 * Licensed under the Apache-2.0 License. 9 * 10 */ 11 12 module hunt.redis.HostAndPort; 13 14 import hunt.Exceptions; 15 import hunt.logging.ConsoleLogger; 16 17 import hunt.text.Common; 18 import hunt.text.StringUtils; 19 20 import std.algorithm; 21 import std.conv; 22 import std.socket; 23 import std.string; 24 25 /** 26 */ 27 class HostAndPort { // : Serializable 28 29 __gshared string localhost; 30 31 private string host; 32 private int port; 33 34 this(string host, int port) { 35 this.host = host; 36 this.port = port; 37 } 38 39 string getHost() { 40 return host; 41 } 42 43 int getPort() { 44 return port; 45 } 46 47 override bool opEquals(Object obj) { 48 if (obj is null) 49 return false; 50 if (obj is this) 51 return true; 52 53 HostAndPort hp = cast(HostAndPort) obj; 54 if (hp is null) 55 return false; 56 57 string thisHost = convertHost(host); 58 string hpHost = convertHost(hp.host); 59 return port == hp.port && thisHost == hpHost; 60 } 61 62 override size_t toHash() @trusted nothrow { 63 return 31 * convertHost(host).hashOf() + port; 64 } 65 66 override string toString() { 67 return host ~ ":" ~ port.to!string(); 68 } 69 70 /** 71 * Splits string into host and port parts. 72 * string must be in ( host ~ ":" ~ port ) format. 73 * Port is optional 74 * @param from string to parse 75 * @return array of host and port strings 76 */ 77 static string[] extractParts(string from) { 78 int idx = cast(int)from.lastIndexOf(":"); 79 string host = idx != -1 ? from.substring(0, idx) : from; 80 string port = idx != -1 ? from.substring(idx + 1) : ""; 81 return [host, port]; 82 } 83 84 /** 85 * Creates HostAndPort instance from string. 86 * string must be in ( host ~ ":" ~ port ) format. 87 * Port is mandatory. Can convert host part. 88 * @see #convertHost(string) 89 * @param from string to parse 90 * @return HostAndPort instance 91 */ 92 static HostAndPort parseString(string from) { 93 // NOTE: redis answers with 94 // '99aa9999aa9a99aa099aaa990aa99a09aa9a9999 9a09:9a9:a090:9a::99a slave 8c88888888cc08088cc8c8c888c88c8888c88cc8 0 1468251272993 37 connected' 95 // for CLUSTER NODES, ASK and MOVED scenarios. That's why there is no possibility to parse address in 'correct' way. 96 // Redis should switch to 'bracketized' (RFC 3986) IPv6 address. 97 try { 98 string[] parts = extractParts(from); 99 string host = parts[0]; 100 int port = to!int(parts[1]); 101 return new HostAndPort(convertHost(host), port); 102 } catch (Exception ex) { 103 throw new IllegalArgumentException(ex); 104 } 105 } 106 107 static string convertHost(string host) @trusted nothrow { 108 try { 109 /* 110 * Validate the host name as an IPV4/IPV6 address. 111 * If this is an AWS ENDPOINT it will not parse. 112 * In that case accept host as is. 113 * 114 * Costs: If this is an IPV4/6 encoding, e.g. 127.0.0.1 then no DNS lookup 115 * is done. If it is a name then a DNS lookup is done but it is normally cached. 116 * Secondarily, this class is typically used to create a connection once 117 * at the beginning of processing and then not used again. So even if the DNS 118 * lookup needs to be done then the cost is miniscule. 119 */ 120 121 // FIXME: Needing refactor or cleanup -@zxp at 7/16/2019, 6:35:54 PM 122 // 123 Address inetAddress = parseAddress(host); 124 // InetAddress inetAddress = InetAddress.getByName(host); 125 // inetAddress.isLoopbackAddress() || 126 127 // isLoopbackAddress() handles both IPV4 and IPV6 128 if (host == "0.0.0.0" 129 || host.startsWith("169.254")) 130 return getLocalhost(); 131 else 132 return host; 133 } catch (Exception e) { 134 // Not a valid IP address 135 warning("{}.convertHost '" ~ host ~ "' is not a valid IP address. ", 136 HostAndPort.stringof, e); 137 return host; 138 } 139 } 140 141 static void setLocalhost(string localhost) { 142 HostAndPort.localhost = localhost; 143 } 144 145 /** 146 * This method resolves the localhost in a 'lazy manner'. 147 * 148 * @return localhost 149 */ 150 static string getLocalhost() { 151 if (localhost is null) { 152 synchronized { 153 if (localhost is null) { 154 return localhost = getLocalHostQuietly(); 155 } 156 } 157 } 158 return localhost; 159 } 160 161 static string getLocalHostQuietly() { 162 string localAddress; 163 try { 164 // FIXME: Needing refactor or cleanup -@zxp at 7/16/2019, 6:34:32 PM 165 // 166 localAddress = "localhost"; // InetAddress.getLocalHost().getHostAddress(); 167 } catch (Exception ex) { 168 error("%s.getLocalHostQuietly : cant resolve localhost address", 169 HostAndPort.stringof, ex); 170 localAddress = "localhost"; 171 } 172 return localAddress; 173 } 174 }