package io.bitsquare.http;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
import io.bitsquare.app.Version;
import io.bitsquare.network.Socks5ProxyProvider;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContexts;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import javax.inject.Inject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.URL;
import static com.google.common.base.Preconditions.checkNotNull;
public class HttpClient {
private static final Logger log = LoggerFactory.getLogger(HttpClient.class);
@Nullable
private Socks5ProxyProvider socks5ProxyProvider;
private String baseUrl;
private boolean ignoreSocks5Proxy;
@Inject
public HttpClient(@Nullable Socks5ProxyProvider socks5ProxyProvider) {
this.socks5ProxyProvider = socks5ProxyProvider;
}
public HttpClient(String baseUrl) {
this.baseUrl = baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
public void setIgnoreSocks5Proxy(boolean ignoreSocks5Proxy) {
this.ignoreSocks5Proxy = ignoreSocks5Proxy;
}
public String requestWithGET(String param, @Nullable String headerKey, @Nullable String headerValue) throws IOException, HttpException {
checkNotNull(baseUrl, "baseUrl must be set before calling requestWithGET");
Socks5Proxy socks5Proxy = null;
if (socks5ProxyProvider != null) {
// We use the custom socks5ProxyHttp. If not set we request socks5ProxyProvider.getSocks5ProxyBtc()
// which delivers the btc proxy if set, otherwise the internal proxy.
socks5Proxy = socks5ProxyProvider.getSocks5ProxyHttp();
if (socks5Proxy == null)
socks5Proxy = socks5ProxyProvider.getSocks5Proxy();
}
if (ignoreSocks5Proxy) {
log.debug("Use clear net for HttpClient because ignoreSocks5Proxy is set to true");
return requestWithGETNoProxy(param, headerKey, headerValue);
} else if (socks5Proxy == null) {
log.debug("Use clear net for HttpClient because socks5Proxy is null");
return requestWithGETNoProxy(param, headerKey, headerValue);
} else {
log.debug("Use socks5Proxy for HttpClient: " + socks5Proxy);
return requestWithGETProxy(param, socks5Proxy, headerKey, headerValue);
}
}
/**
* Make an HTTP Get request directly (not routed over socks5 proxy).
*/
public String requestWithGETNoProxy(String param, @Nullable String headerKey, @Nullable String headerValue) throws IOException, HttpException {
HttpURLConnection connection = null;
try {
log.debug("Executing HTTP request " + baseUrl + param + " proxy: none.");
URL url = new URL(baseUrl + param);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(10_000);
connection.setReadTimeout(10_000);
connection.setRequestProperty("User-Agent", "Bitsquare/" + Version.VERSION);
if (headerKey != null && headerValue != null)
connection.setRequestProperty(headerKey, headerValue);
if (connection.getResponseCode() == 200) {
return convertInputStreamToString(connection.getInputStream());
} else {
String error = convertInputStreamToString(connection.getErrorStream());
connection.getErrorStream().close();
throw new HttpException(error);
}
} catch (Throwable t) {
log.debug("Error at requestWithGETNoProxy: " + t.getMessage());
throw new IOException(t);
} finally {
if (connection != null)
connection.getInputStream().close();
}
}
/**
* Make an HTTP Get request routed over socks5 proxy.
*/
private String requestWithGETProxy(String param, Socks5Proxy socks5Proxy, @Nullable String headerKey, @Nullable String headerValue) throws IOException, HttpException {
log.debug("requestWithGETProxy param=" + param);
// This code is adapted from:
// http://stackoverflow.com/a/25203021/5616248
// Register our own SocketFactories to override createSocket() and connectSocket().
// connectSocket does NOT resolve hostname before passing it to proxy.
Registry<ConnectionSocketFactory> reg = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", new SocksConnectionSocketFactory())
.register("https", new SocksSSLConnectionSocketFactory(SSLContexts.createSystemDefault())).build();
// Use FakeDNSResolver if not resolving DNS locally.
// This prevents a local DNS lookup (which would be ignored anyway)
PoolingHttpClientConnectionManager cm = socks5Proxy.resolveAddrLocally() ?
new PoolingHttpClientConnectionManager(reg) :
new PoolingHttpClientConnectionManager(reg, new FakeDnsResolver());
try (CloseableHttpClient httpclient = HttpClients.custom().setConnectionManager(cm).build()) {
InetSocketAddress socksaddr = new InetSocketAddress(socks5Proxy.getInetAddress(), socks5Proxy.getPort());
// remove me: Use this to test with system-wide Tor proxy, or change port for another proxy.
// InetSocketAddress socksaddr = new InetSocketAddress("127.0.0.1", 9050);
HttpClientContext context = HttpClientContext.create();
context.setAttribute("socks.address", socksaddr);
HttpGet request = new HttpGet(baseUrl + param);
if (headerKey != null && headerValue != null)
request.setHeader(headerKey, headerValue);
log.debug("Executing request " + request + " proxy: " + socksaddr);
try (CloseableHttpResponse response = httpclient.execute(request, context)) {
return convertInputStreamToString(response.getEntity().getContent());
}
} catch (Throwable t) {
log.debug("Error at requestWithGETProxy: " + t.getMessage());
throw new IOException(t);
}
}
private String convertInputStreamToString(InputStream inputStream) throws IOException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line);
}
return stringBuilder.toString();
}
@Override
public String toString() {
return "HttpClient{" +
"socks5ProxyProvider=" + socks5ProxyProvider +
", baseUrl='" + baseUrl + '\'' +
", ignoreSocks5Proxy=" + ignoreSocks5Proxy +
'}';
}
}