HTTP client used for any external query (price feed, fee estimation, BSQ explorer, mempool broadcast, XMR proof, …). Hardening invariants enforced here: - URL must parse as a valid http/https URI with a non-empty host (no file://, no userinfo, no schemes outside http/https). - Requests addr
| 91 | * - Bodies are decoded as UTF-8; size accounting is byte-exact. |
| 92 | */ |
| 93 | @Slf4j |
| 94 | public class HttpClientImpl implements HttpClient { |
| 95 | // Connect=80s covers Tor circuit setup + onion-service rendezvous on a cold |
| 96 | // start; read=60s is per-byte inactivity, generous for any healthy server |
| 97 | // once the circuit is up. |
| 98 | static final int CONNECT_TIMEOUT_SEC = 80; |
| 99 | static final int READ_TIMEOUT_SEC = 60; |
| 100 | // 512 KiB is well above any legitimate response from price feeds, fee |
| 101 | // estimators, mempool/explorer JSON, or XMR proof endpoints. A larger cap |
| 102 | // just widens the memory-exhaustion window without enabling any real use. |
| 103 | static final long MAX_RESPONSE_BYTES = 512L * 1024; |
| 104 | |
| 105 | @Nullable |
| 106 | private final Socks5ProxyProvider socks5ProxyProvider; |
| 107 | private final boolean allowLanForHttpRequests; |
| 108 | private final boolean allowClearnetHttpRequests; |
| 109 | @Nullable |
| 110 | private volatile HttpURLConnection connection; |
| 111 | @Nullable |
| 112 | private volatile CloseableHttpClient closeableHttpClient; |
| 113 | |
| 114 | @Getter |
| 115 | private volatile String baseUrl; |
| 116 | private volatile boolean ignoreSocks5Proxy; |
| 117 | @Getter |
| 118 | private final String uid; |
| 119 | private final AtomicBoolean hasPendingRequest = new AtomicBoolean(false); |
| 120 | |
| 121 | @Inject |
| 122 | public HttpClientImpl(@Nullable Socks5ProxyProvider socks5ProxyProvider, |
| 123 | @Named(Config.ALLOW_LAN_FOR_HTTP_REQUESTS) boolean allowLanForHttpRequests, |
| 124 | @Named(Config.ALLOW_CLEARNET_HTTP_REQUESTS) boolean allowClearnetHttpRequests) { |
| 125 | this.socks5ProxyProvider = socks5ProxyProvider; |
| 126 | this.allowLanForHttpRequests = allowLanForHttpRequests; |
| 127 | this.allowClearnetHttpRequests = allowClearnetHttpRequests; |
| 128 | if (allowClearnetHttpRequests) { |
| 129 | log.warn("allowClearnetHttpRequests is enabled: HTTP requests to non-local destinations will " + |
| 130 | "bypass Tor and leak your IP address. Use only for development/testing."); |
| 131 | } |
| 132 | uid = UUID.randomUUID().toString(); |
| 133 | } |
| 134 | |
| 135 | public HttpClientImpl(@Nullable Socks5ProxyProvider socks5ProxyProvider) { |
| 136 | // Test/legacy entry point. Defaults to strict loopback-only. |
| 137 | this(socks5ProxyProvider, false, false); |
| 138 | } |
| 139 | |
| 140 | public HttpClientImpl(String baseUrl) { |
| 141 | this.socks5ProxyProvider = null; |
| 142 | this.allowLanForHttpRequests = false; |
| 143 | this.allowClearnetHttpRequests = false; |
| 144 | setBaseUrl(baseUrl); |
| 145 | uid = UUID.randomUUID().toString(); |
| 146 | } |
| 147 | |
| 148 | @Override |
| 149 | public void setBaseUrl(String baseUrl) { |
| 150 | // Validate up-front so misconfiguration surfaces at injection time, not at |
nothing calls this directly
no outgoing calls
no test coverage detected