mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-25 05:06:45 +00:00
repackage http client as tern library dependency
This commit is contained in:
parent
d49d5967b2
commit
46034b8f11
26 changed files with 10 additions and 1681 deletions
|
@ -122,7 +122,7 @@ dependencies {
|
|||
exclude group: 'org.slf4j'
|
||||
}
|
||||
implementation('com.sparrowwallet.bokmakierie:bokmakierie:1.0')
|
||||
implementation('org.eclipse.jetty:jetty-client:9.4.54.v20240208')
|
||||
implementation('com.sparrowwallet:tern:1.0.2')
|
||||
implementation('io.reactivex.rxjava2:rxjava:2.2.15')
|
||||
implementation('io.reactivex.rxjava2:rxjavafx:2.2.2')
|
||||
implementation('org.apache.commons:commons-lang3:3.7')
|
||||
|
@ -158,7 +158,7 @@ processResources {
|
|||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
jvmArgs = ["--add-opens=java.base/java.io=ALL-UNNAMED", "--add-opens=java.base/java.io=com.google.gson"]
|
||||
jvmArgs = ["--add-opens=java.base/java.io=ALL-UNNAMED", "--add-opens=java.base/java.io=com.google.gson", "--add-reads=org.flywaydb.core=java.desktop"]
|
||||
}
|
||||
|
||||
application {
|
||||
|
|
|
@ -6,7 +6,7 @@ import com.sparrowwallet.drongo.Utils;
|
|||
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
||||
import com.sparrowwallet.drongo.protocol.Transaction;
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.net.http.client.HttpResponseException;
|
||||
import com.sparrowwallet.tern.http.client.HttpResponseException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ package com.sparrowwallet.sparrow.net;
|
|||
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.event.ExchangeRatesUpdatedEvent;
|
||||
import com.sparrowwallet.sparrow.net.http.client.HttpResponseException;
|
||||
import com.sparrowwallet.tern.http.client.HttpResponseException;
|
||||
import javafx.concurrent.ScheduledService;
|
||||
import javafx.concurrent.Service;
|
||||
import javafx.concurrent.Task;
|
||||
|
|
|
@ -1,50 +1,12 @@
|
|||
package com.sparrowwallet.sparrow.net;
|
||||
|
||||
import com.google.common.net.HostAndPort;
|
||||
import com.sparrowwallet.sparrow.net.http.client.AsyncUtil;
|
||||
import com.sparrowwallet.sparrow.net.http.client.HttpUsage;
|
||||
import com.sparrowwallet.sparrow.net.http.client.IHttpClient;
|
||||
import com.sparrowwallet.sparrow.net.http.client.JettyHttpClientService;
|
||||
import io.reactivex.Observable;
|
||||
import javafx.concurrent.Service;
|
||||
import javafx.concurrent.Task;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class HttpClientService extends JettyHttpClientService {
|
||||
private static final int REQUEST_TIMEOUT = 120000;
|
||||
|
||||
public class HttpClientService extends com.sparrowwallet.tern.http.client.HttpClientService {
|
||||
public HttpClientService(HostAndPort torProxy) {
|
||||
super(REQUEST_TIMEOUT, new HttpProxySupplier(torProxy));
|
||||
}
|
||||
|
||||
public <T> T requestJson(String url, Class<T> responseType, Map<String, String> headers) throws Exception {
|
||||
return getHttpClient(HttpUsage.DEFAULT).getJson(url, responseType, headers);
|
||||
}
|
||||
|
||||
public <T> Observable<Optional<T>> postJson(String url, Class<T> responseType, Map<String, String> headers, Object body) {
|
||||
return getHttpClient(HttpUsage.DEFAULT).postJson(url, responseType, headers, body).toObservable();
|
||||
}
|
||||
|
||||
public String postString(String url, Map<String, String> headers, String contentType, String content) throws Exception {
|
||||
IHttpClient httpClient = getHttpClient(HttpUsage.DEFAULT);
|
||||
return AsyncUtil.getInstance().blockingGet(httpClient.postString(url, headers, contentType, content)).get();
|
||||
}
|
||||
|
||||
public HostAndPort getTorProxy() {
|
||||
return getHttpProxySupplier().getTorProxy();
|
||||
}
|
||||
|
||||
public void setTorProxy(HostAndPort torProxy) {
|
||||
//Ensure all http clients are shutdown first
|
||||
stop();
|
||||
getHttpProxySupplier()._setTorProxy(torProxy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpProxySupplier getHttpProxySupplier() {
|
||||
return (HttpProxySupplier)super.getHttpProxySupplier();
|
||||
super(new HttpProxySupplier(torProxy));
|
||||
}
|
||||
|
||||
public static class ShutdownService extends Service<Boolean> {
|
||||
|
|
|
@ -1,44 +1,10 @@
|
|||
package com.sparrowwallet.sparrow.net;
|
||||
|
||||
import com.google.common.net.HostAndPort;
|
||||
import com.sparrowwallet.sparrow.net.http.client.HttpProxy;
|
||||
import com.sparrowwallet.sparrow.net.http.client.HttpProxyProtocol;
|
||||
import com.sparrowwallet.sparrow.net.http.client.HttpUsage;
|
||||
import com.sparrowwallet.sparrow.net.http.client.IHttpProxySupplier;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class HttpProxySupplier implements IHttpProxySupplier {
|
||||
private HostAndPort torProxy;
|
||||
private HttpProxy httpProxy;
|
||||
|
||||
public class HttpProxySupplier extends com.sparrowwallet.tern.http.client.HttpProxySupplier {
|
||||
public HttpProxySupplier(HostAndPort torProxy) {
|
||||
this.torProxy = torProxy;
|
||||
this.httpProxy = computeHttpProxy(torProxy);
|
||||
}
|
||||
|
||||
private HttpProxy computeHttpProxy(HostAndPort hostAndPort) {
|
||||
if (hostAndPort == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new HttpProxy(HttpProxyProtocol.SOCKS, hostAndPort.getHost(), hostAndPort.getPort());
|
||||
}
|
||||
|
||||
public HostAndPort getTorProxy() {
|
||||
return torProxy;
|
||||
}
|
||||
|
||||
// shouldnt call directly but use httpClientService.setTorProxy()
|
||||
public void _setTorProxy(HostAndPort hostAndPort) {
|
||||
// set proxy
|
||||
this.torProxy = hostAndPort;
|
||||
this.httpProxy = computeHttpProxy(hostAndPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<HttpProxy> getHttpProxy(HttpUsage httpUsage) {
|
||||
return Optional.ofNullable(httpProxy);
|
||||
super(torProxy);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,150 +0,0 @@
|
|||
package com.sparrowwallet.sparrow.net.http.client;
|
||||
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.functions.Action;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import org.slf4j.MDC;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class AsyncUtil {
|
||||
private static final ThreadUtil threadUtil = ThreadUtil.getInstance();
|
||||
private static AsyncUtil instance;
|
||||
|
||||
public static AsyncUtil getInstance() {
|
||||
if(instance == null) {
|
||||
instance = new AsyncUtil();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public <T> T unwrapException(Callable<T> c) throws Exception {
|
||||
try {
|
||||
return c.call();
|
||||
} catch(RuntimeException e) {
|
||||
// blockingXXX wraps errors with RuntimeException, unwrap it
|
||||
throw unwrapException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Exception unwrapException(Exception e) throws Exception {
|
||||
if(e.getCause() != null && e.getCause() instanceof Exception) {
|
||||
throw (Exception) e.getCause();
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
public <T> T blockingGet(Single<T> o) throws Exception {
|
||||
try {
|
||||
return unwrapException(o::blockingGet);
|
||||
} catch(ExecutionException e) {
|
||||
// blockingGet(threadUtil.runWithTimeoutAndRetry()) wraps InterruptedException("exit (done)")
|
||||
// with ExecutionException, unwrap it
|
||||
throw unwrapException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T blockingGet(Single<T> o, long timeoutMs) throws Exception {
|
||||
Callable<T> callable = () -> blockingGet(o);
|
||||
return blockingGet(runAsync(callable, timeoutMs));
|
||||
}
|
||||
|
||||
public <T> T blockingGet(Future<T> o, long timeoutMs) throws Exception {
|
||||
return o.get(timeoutMs, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public <T> T blockingLast(Observable<T> o) throws Exception {
|
||||
return unwrapException(o::blockingLast);
|
||||
}
|
||||
|
||||
public void blockingAwait(Completable o) throws Exception {
|
||||
Callable<Optional> callable = () -> {
|
||||
o.blockingAwait();
|
||||
return Optional.empty();
|
||||
};
|
||||
unwrapException(callable);
|
||||
}
|
||||
|
||||
public void blockingAwait(Completable o, long timeoutMs) throws Exception {
|
||||
Callable<Optional> callable = () -> {
|
||||
o.blockingAwait();
|
||||
return Optional.empty();
|
||||
};
|
||||
blockingGet(runAsync(callable, timeoutMs));
|
||||
}
|
||||
|
||||
public <T> Single<T> timeout(Single<T> o, long timeoutMs) {
|
||||
try {
|
||||
return Single.just(blockingGet(o, timeoutMs));
|
||||
} catch(Exception e) {
|
||||
return Single.error(e);
|
||||
}
|
||||
}/*
|
||||
|
||||
public Completable timeout(Completable o, long timeoutMs) {
|
||||
try {
|
||||
return Completable.fromCallable(() -> {
|
||||
blockingAwait(o, timeoutMs);
|
||||
return Optional.empty();
|
||||
});
|
||||
} catch (Exception e) {
|
||||
return Completable.error(e);
|
||||
}
|
||||
}*/
|
||||
|
||||
public <T> Single<T> runIOAsync(final Callable<T> callable) {
|
||||
return Single.fromCallable(callable).subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
public Completable runIOAsyncCompletable(final Action action) {
|
||||
return Completable.fromAction(action).subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
public <T> T runIO(final Callable<T> callable) throws Exception {
|
||||
return blockingGet(runIOAsync(callable));
|
||||
}
|
||||
|
||||
public void runIO(final Action action) throws Exception {
|
||||
blockingAwait(runIOAsyncCompletable(action));
|
||||
}
|
||||
|
||||
public Completable runAsync(Runnable runnable, long timeoutMs) {
|
||||
Future<?> future = runAsync(() -> {
|
||||
runnable.run();
|
||||
return Optional.empty(); // must return an object for using Completable.fromSingle()
|
||||
});
|
||||
return Completable.fromSingle(Single.fromFuture(future, timeoutMs, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
|
||||
public <T> Future<T> runAsync(Callable<T> callable) {
|
||||
// preserve logging context
|
||||
String mdc = mdcAppend("runAsync=" + System.currentTimeMillis());
|
||||
return threadUtil.runAsync(() -> {
|
||||
MDC.put("mdc", mdc);
|
||||
return callable.call();
|
||||
});
|
||||
}
|
||||
|
||||
public <T> Single<T> runAsync(Callable<T> callable, long timeoutMs) {
|
||||
Future<T> future = runAsync(callable);
|
||||
return Single.fromFuture(future, timeoutMs, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private static String mdcAppend(String info) {
|
||||
String mdc = MDC.get("mdc");
|
||||
if(mdc == null) {
|
||||
mdc = "";
|
||||
} else {
|
||||
mdc += ",";
|
||||
}
|
||||
mdc += info;
|
||||
return mdc;
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package com.sparrowwallet.sparrow.net.http.client;
|
||||
|
||||
public abstract class HttpException extends Exception {
|
||||
|
||||
public HttpException(Exception cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public HttpException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package com.sparrowwallet.sparrow.net.http.client;
|
||||
|
||||
public class HttpNetworkException extends HttpException {
|
||||
public HttpNetworkException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public HttpNetworkException(Exception cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
package com.sparrowwallet.sparrow.net.http.client;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class HttpProxy {
|
||||
private final HttpProxyProtocol protocol;
|
||||
private final String host;
|
||||
private final int port;
|
||||
|
||||
public HttpProxy(HttpProxyProtocol protocol, String host, int port) {
|
||||
this.protocol = protocol;
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public static boolean validate(String proxy) {
|
||||
// check protocol
|
||||
String[] protocols = Arrays.stream(HttpProxyProtocol.values()).map(p -> p.name()).toArray(String[]::new);
|
||||
String regex = "^(" + StringUtils.join(protocols, "|").toLowerCase() + ")://(.+?):([0-9]+)";
|
||||
return proxy.trim().toLowerCase().matches(regex);
|
||||
}
|
||||
|
||||
public HttpProxyProtocol getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return protocol + "://" + host + ":" + port;
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package com.sparrowwallet.sparrow.net.http.client;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public enum HttpProxyProtocol {
|
||||
HTTP,
|
||||
SOCKS,
|
||||
SOCKS5;
|
||||
|
||||
public static Optional<HttpProxyProtocol> find(String value) {
|
||||
try {
|
||||
return Optional.of(valueOf(value));
|
||||
} catch(Exception e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package com.sparrowwallet.sparrow.net.http.client;
|
||||
|
||||
public class HttpResponseException extends HttpException {
|
||||
private final String responseBody;
|
||||
private final int statusCode;
|
||||
|
||||
public HttpResponseException(Exception cause, String responseBody, int statusCode) {
|
||||
super(cause);
|
||||
this.responseBody = responseBody;
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
public HttpResponseException(String message, String responseBody, int statusCode) {
|
||||
super(message);
|
||||
this.responseBody = responseBody;
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
public HttpResponseException(String responseBody, int statusCode) {
|
||||
this("response statusCode=" + statusCode, responseBody, statusCode);
|
||||
}
|
||||
|
||||
public String getResponseBody() {
|
||||
return responseBody;
|
||||
}
|
||||
|
||||
public int getStatusCode() {
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "HttpResponseException{" +
|
||||
"message=" + getMessage() + ", " +
|
||||
"responseBody='" + responseBody + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package com.sparrowwallet.sparrow.net.http.client;
|
||||
|
||||
public class HttpSystemException extends HttpException {
|
||||
public HttpSystemException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public HttpSystemException(Exception cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package com.sparrowwallet.sparrow.net.http.client;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class HttpUsage {
|
||||
public static final HttpUsage DEFAULT = new HttpUsage("Default");
|
||||
|
||||
private final String name;
|
||||
|
||||
public HttpUsage(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if(this == o) {
|
||||
return true;
|
||||
}
|
||||
if(o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
HttpUsage httpUsage = (HttpUsage) o;
|
||||
return Objects.equals(name, httpUsage.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name);
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package com.sparrowwallet.sparrow.net.http.client;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface IBackendClient {
|
||||
<T> T getJson(String url, Class<T> responseType, Map<String, String> headers) throws HttpException;
|
||||
|
||||
<T> T getJson(String url, Class<T> responseType, Map<String, String> headers, boolean async) throws HttpException;
|
||||
|
||||
<T> T postUrlEncoded(String url, Class<T> responseType, Map<String, String> headers, Map<String, String> body) throws Exception;
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package com.sparrowwallet.sparrow.net.http.client;
|
||||
|
||||
import io.reactivex.Single;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface IHttpClient extends IBackendClient {
|
||||
void connect() throws Exception;
|
||||
|
||||
<T> Single<Optional<T>> postJson(String url, Class<T> responseType, Map<String, String> headers, Object body);
|
||||
|
||||
Single<Optional<String>> postString(String urlStr, Map<String, String> headers, String contentType, String content);
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package com.sparrowwallet.sparrow.net.http.client;
|
||||
|
||||
public interface IHttpClientService {
|
||||
IHttpClient getHttpClient(HttpUsage httpUsage);
|
||||
|
||||
void changeIdentity(); // change Tor identity if any
|
||||
|
||||
void stop();
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package com.sparrowwallet.sparrow.net.http.client;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface IHttpProxySupplier {
|
||||
Optional<HttpProxy> getHttpProxy(HttpUsage httpUsage);
|
||||
|
||||
void changeIdentity();
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package com.sparrowwallet.sparrow.net.http.client;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
public class JSONUtils {
|
||||
private static JSONUtils instance;
|
||||
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
public JSONUtils() {
|
||||
objectMapper = new ObjectMapper();
|
||||
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
|
||||
}
|
||||
|
||||
public static final JSONUtils getInstance() {
|
||||
if(instance == null) {
|
||||
instance = new JSONUtils();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public ObjectMapper getObjectMapper() {
|
||||
return objectMapper;
|
||||
}
|
||||
}
|
|
@ -1,168 +0,0 @@
|
|||
package com.sparrowwallet.sparrow.net.http.client;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public abstract class JacksonHttpClient implements IHttpClient {
|
||||
private static final Logger log = LoggerFactory.getLogger(JacksonHttpClient.class);
|
||||
|
||||
private final Consumer<Exception> onNetworkError;
|
||||
|
||||
public JacksonHttpClient(Consumer<Exception> onNetworkError) {
|
||||
this.onNetworkError = onNetworkError;
|
||||
}
|
||||
|
||||
protected abstract String requestJsonGet(String urlStr, Map<String, String> headers, boolean async) throws HttpException;
|
||||
|
||||
protected abstract String requestJsonPost(String urlStr, Map<String, String> headers, String jsonBody) throws HttpException;
|
||||
|
||||
protected abstract String requestStringPost(String urlStr, Map<String, String> headers, String contentType, String content) throws HttpException;
|
||||
|
||||
protected abstract String requestJsonPostUrlEncoded(String urlStr, Map<String, String> headers, Map<String, String> body) throws HttpException;
|
||||
|
||||
@Override
|
||||
public <T> T getJson(String urlStr, Class<T> responseType, Map<String, String> headers) throws HttpException {
|
||||
return getJson(urlStr, responseType, headers, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getJson(String urlStr, Class<T> responseType, Map<String, String> headers, boolean async) throws HttpException {
|
||||
return httpObservableBlockingSingle(() -> { // run on ioThread
|
||||
try {
|
||||
String responseContent = handleNetworkError("getJson " + urlStr, () -> requestJsonGet(urlStr, headers, async));
|
||||
return parseJson(responseContent, responseType, 200);
|
||||
} catch(Exception e) {
|
||||
if(log.isDebugEnabled()) {
|
||||
log.error("getJson failed: " + urlStr + ": " + e.toString());
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Single<Optional<T>> postJson(final String urlStr, final Class<T> responseType, final Map<String, String> headers, final Object bodyObj) {
|
||||
return httpObservable(
|
||||
() -> {
|
||||
try {
|
||||
String jsonBody = getObjectMapper().writeValueAsString(bodyObj);
|
||||
String responseContent = handleNetworkError("postJson " + urlStr, () -> requestJsonPost(urlStr, headers, jsonBody));
|
||||
return parseJson(responseContent, responseType, 200);
|
||||
} catch(HttpException e) {
|
||||
if(log.isDebugEnabled()) {
|
||||
log.error("postJson failed: " + urlStr + ": " + e);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Single<Optional<String>> postString(String urlStr, Map<String, String> headers, String contentType, String content) {
|
||||
return httpObservable(
|
||||
() -> {
|
||||
try {
|
||||
return handleNetworkError("postString " + urlStr, () -> requestStringPost(urlStr, headers, contentType, content));
|
||||
} catch(HttpException e) {
|
||||
if(log.isDebugEnabled()) {
|
||||
log.error("postJson failed: " + urlStr + ": " + e.toString());
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T postUrlEncoded(String urlStr, Class<T> responseType, Map<String, String> headers, Map<String, String> body) throws HttpException {
|
||||
return httpObservableBlockingSingle(() -> { // run on ioThread
|
||||
try {
|
||||
String responseContent = handleNetworkError("postUrlEncoded " + urlStr, () -> requestJsonPostUrlEncoded(urlStr, headers, body));
|
||||
return parseJson(responseContent, responseType, 200);
|
||||
} catch(Exception e) {
|
||||
if(log.isDebugEnabled()) {
|
||||
log.error("postUrlEncoded failed: " + urlStr + ": " + e);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private <T> T parseJson(String responseContent, Class<T> responseType, int statusCode) throws HttpException {
|
||||
T result;
|
||||
if(log.isTraceEnabled()) {
|
||||
String responseStr = (responseContent != null ? responseContent : "null");
|
||||
if(responseStr.length() > 500) {
|
||||
responseStr = responseStr.substring(0, 500) + "...";
|
||||
}
|
||||
log.trace("response[" + (responseType != null ? responseType.getCanonicalName() : "null") + "]: " + responseStr);
|
||||
}
|
||||
if(String.class.equals(responseType)) {
|
||||
result = (T) responseContent;
|
||||
} else {
|
||||
try {
|
||||
result = getObjectMapper().readValue(responseContent, responseType);
|
||||
} catch(Exception e) {
|
||||
throw new HttpResponseException(e, responseContent, statusCode);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected String handleNetworkError(String logInfo, Callable<String> doHttpRequest) throws HttpException {
|
||||
try {
|
||||
try {
|
||||
// first attempt
|
||||
return doHttpRequest.call();
|
||||
} catch(HttpNetworkException e) {
|
||||
if(log.isDebugEnabled()) {
|
||||
log.warn("HTTP_ERROR_NETWORK " + logInfo + ", retrying: " + e.getMessage());
|
||||
}
|
||||
// change tor proxy
|
||||
onNetworkError(e);
|
||||
|
||||
// retry second attempt
|
||||
return doHttpRequest.call();
|
||||
}
|
||||
} catch(HttpException e) { // forward
|
||||
throw e;
|
||||
} catch(Exception e) { // should never happen
|
||||
throw new HttpSystemException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onNetworkError(HttpNetworkException e) {
|
||||
if(onNetworkError != null) {
|
||||
synchronized(JacksonHttpClient.class) { // avoid overlapping Tor restarts between httpClients
|
||||
onNetworkError.accept(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected <T> Single<Optional<T>> httpObservable(final Callable<T> supplier) {
|
||||
return Single.fromCallable(() -> Optional.ofNullable(supplier.call())).subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
protected <T> T httpObservableBlockingSingle(final Callable<T> supplier) throws HttpException {
|
||||
try {
|
||||
Optional<T> opt = AsyncUtil.getInstance().blockingGet(httpObservable(supplier));
|
||||
return opt.orElse(null);
|
||||
} catch(HttpException e) { // forward
|
||||
throw e;
|
||||
} catch(Exception e) { // should never happen
|
||||
throw new HttpNetworkException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected ObjectMapper getObjectMapper() {
|
||||
return JSONUtils.getInstance().getObjectMapper();
|
||||
}
|
||||
}
|
|
@ -1,188 +0,0 @@
|
|||
package com.sparrowwallet.sparrow.net.http.client;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.util.FormContentProvider;
|
||||
import org.eclipse.jetty.client.util.InputStreamResponseListener;
|
||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.util.Fields;
|
||||
import org.eclipse.jetty.util.component.LifeCycle;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class JettyHttpClient extends JacksonHttpClient {
|
||||
protected static Logger log = LoggerFactory.getLogger(JettyHttpClient.class);
|
||||
public static final String CONTENTTYPE_APPLICATION_JSON = "application/json";
|
||||
|
||||
private final HttpClient httpClient;
|
||||
private final long requestTimeout;
|
||||
private final HttpUsage httpUsage;
|
||||
|
||||
public JettyHttpClient(Consumer<Exception> onNetworkError, HttpClient httpClient, long requestTimeout, HttpUsage httpUsage) {
|
||||
super(onNetworkError);
|
||||
this.httpClient = httpClient;
|
||||
this.requestTimeout = requestTimeout;
|
||||
this.httpUsage = httpUsage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect() throws HttpException {
|
||||
try {
|
||||
if(!httpClient.isRunning()) {
|
||||
httpClient.start();
|
||||
}
|
||||
} catch(Exception e) {
|
||||
throw new HttpNetworkException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void restart() {
|
||||
try {
|
||||
if(log.isDebugEnabled()) {
|
||||
log.debug("restart");
|
||||
}
|
||||
if(httpClient.isRunning()) {
|
||||
httpClient.stop();
|
||||
}
|
||||
httpClient.start();
|
||||
} catch(Exception e) {
|
||||
log.error("", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
try {
|
||||
if(httpClient.isRunning()) {
|
||||
httpClient.stop();
|
||||
Executor executor = httpClient.getExecutor();
|
||||
if(executor instanceof LifeCycle) {
|
||||
((LifeCycle) executor).stop();
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
log.error("Error stopping client", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String requestJsonGet(String urlStr, Map<String, String> headers, boolean async) throws HttpException {
|
||||
Request req = computeHttpRequest(urlStr, HttpMethod.GET, headers);
|
||||
return makeRequest(req, async);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String requestJsonPost(String urlStr, Map<String, String> headers, String jsonBody) throws HttpException {
|
||||
Request req = computeHttpRequest(urlStr, HttpMethod.POST, headers);
|
||||
req.content(new StringContentProvider(CONTENTTYPE_APPLICATION_JSON, jsonBody, StandardCharsets.UTF_8));
|
||||
return makeRequest(req, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String requestStringPost(String urlStr, Map<String, String> headers, String contentType, String content) throws HttpException {
|
||||
log.debug("POST " + urlStr);
|
||||
Request req = computeHttpRequest(urlStr, HttpMethod.POST, headers);
|
||||
req.content(new StringContentProvider(content), contentType);
|
||||
return makeRequest(req, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String requestJsonPostUrlEncoded(String urlStr, Map<String, String> headers, Map<String, String> body) throws HttpException {
|
||||
Request req = computeHttpRequest(urlStr, HttpMethod.POST, headers);
|
||||
req.content(new FormContentProvider(computeBodyFields(body)));
|
||||
return makeRequest(req, false);
|
||||
}
|
||||
|
||||
private Fields computeBodyFields(Map<String, String> body) {
|
||||
Fields fields = new Fields();
|
||||
for(Map.Entry<String, String> entry : body.entrySet()) {
|
||||
fields.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
protected String makeRequest(Request req, boolean async) throws HttpException {
|
||||
String responseContent;
|
||||
if(async) {
|
||||
InputStreamResponseListener listener = new InputStreamResponseListener();
|
||||
req.send(listener);
|
||||
|
||||
// Call to the listener's get() blocks until the headers arrived
|
||||
Response response;
|
||||
try {
|
||||
response = listener.get(requestTimeout, TimeUnit.MILLISECONDS);
|
||||
} catch(Exception e) {
|
||||
throw new HttpNetworkException(e);
|
||||
}
|
||||
|
||||
// Read content
|
||||
InputStream is = listener.getInputStream();
|
||||
Scanner s = new Scanner(is).useDelimiter("\\A");
|
||||
responseContent = s.hasNext() ? s.next() : null;
|
||||
|
||||
// check status
|
||||
checkResponseStatus(response.getStatus(), responseContent);
|
||||
} else {
|
||||
ContentResponse response;
|
||||
try {
|
||||
response = req.send();
|
||||
} catch(Exception e) {
|
||||
throw new HttpNetworkException(e);
|
||||
}
|
||||
checkResponseStatus(response.getStatus(), response.getContentAsString());
|
||||
responseContent = response.getContentAsString();
|
||||
}
|
||||
return responseContent;
|
||||
}
|
||||
|
||||
private void checkResponseStatus(int status, String responseBody) throws HttpResponseException {
|
||||
if(!HttpStatus.isSuccess(status)) {
|
||||
log.error("Http query failed: status=" + status + ", responseBody=" + responseBody);
|
||||
throw new HttpResponseException(responseBody, status);
|
||||
}
|
||||
}
|
||||
|
||||
public HttpClient getJettyHttpClient() throws HttpException {
|
||||
connect();
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
private Request computeHttpRequest(String url, HttpMethod method, Map<String, String> headers) throws HttpException {
|
||||
if(url.endsWith("/rpc")) {
|
||||
// log RPC as TRACE
|
||||
if(log.isTraceEnabled()) {
|
||||
String headersStr = headers != null ? " (" + headers.keySet() + ")" : "";
|
||||
log.trace("+" + method + ": " + url + headersStr);
|
||||
}
|
||||
} else {
|
||||
if(log.isDebugEnabled()) {
|
||||
String headersStr = headers != null ? " (" + headers.keySet() + ")" : "";
|
||||
log.debug("+" + method + ": " + url + headersStr);
|
||||
}
|
||||
}
|
||||
Request req = getJettyHttpClient().newRequest(url);
|
||||
req.method(method);
|
||||
if(headers != null) {
|
||||
for(Map.Entry<String, String> entry : headers.entrySet()) {
|
||||
req.header(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
req.timeout(requestTimeout, TimeUnit.MILLISECONDS);
|
||||
return req;
|
||||
}
|
||||
|
||||
public HttpUsage getHttpUsage() {
|
||||
return httpUsage;
|
||||
}
|
||||
}
|
|
@ -1,167 +0,0 @@
|
|||
package com.sparrowwallet.sparrow.net.http.client;
|
||||
|
||||
import com.google.common.util.concurrent.RateLimiter;
|
||||
import com.sparrowwallet.sparrow.net.http.client.socks5.Socks5Proxy;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.ProxyConfiguration;
|
||||
import org.eclipse.jetty.client.Socks4Proxy;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class JettyHttpClientService implements IHttpClientService {
|
||||
private static final Logger log = LoggerFactory.getLogger(JettyHttpClientService.class);
|
||||
private static final String NAME = "HttpClient";
|
||||
public static final long DEFAULT_TIMEOUT = 30000;
|
||||
|
||||
// limit changing Tor identity on network error every 4 minutes
|
||||
private static final double RATE_CHANGE_IDENTITY_ON_NETWORK_ERROR = 1.0 / 240;
|
||||
|
||||
protected Map<HttpUsage, JettyHttpClient> httpClients; // used by Sparrow
|
||||
private final IHttpProxySupplier httpProxySupplier;
|
||||
private final long requestTimeout;
|
||||
|
||||
public JettyHttpClientService(long requestTimeout, IHttpProxySupplier httpProxySupplier) {
|
||||
this.httpProxySupplier = httpProxySupplier != null ? httpProxySupplier : computeHttpProxySupplierDefault();
|
||||
this.requestTimeout = requestTimeout;
|
||||
this.httpClients = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
public JettyHttpClientService(long requestTimeout) {
|
||||
this(requestTimeout, null);
|
||||
}
|
||||
|
||||
public JettyHttpClientService() {
|
||||
this(DEFAULT_TIMEOUT);
|
||||
}
|
||||
|
||||
protected static IHttpProxySupplier computeHttpProxySupplierDefault() {
|
||||
return new IHttpProxySupplier() {
|
||||
@Override
|
||||
public Optional<HttpProxy> getHttpProxy(HttpUsage httpUsage) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeIdentity() {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public JettyHttpClient getHttpClient(HttpUsage httpUsage) {
|
||||
JettyHttpClient httpClient = httpClients.get(httpUsage);
|
||||
if(httpClient == null) {
|
||||
if(log.isDebugEnabled()) {
|
||||
log.debug("+httpClient[" + httpUsage + "]");
|
||||
}
|
||||
httpClient = computeHttpClient(httpUsage);
|
||||
httpClients.put(httpUsage, httpClient);
|
||||
}
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
protected JettyHttpClient computeHttpClient(HttpUsage httpUsage) {
|
||||
Consumer<Exception> onNetworkError = computeOnNetworkError();
|
||||
HttpClient httpClient = computeJettyClient(httpUsage);
|
||||
return new JettyHttpClient(onNetworkError, httpClient, requestTimeout, httpUsage);
|
||||
}
|
||||
|
||||
protected HttpClient computeJettyClient(HttpUsage httpUsage) {
|
||||
// we use jetty for proxy SOCKS support
|
||||
HttpClient jettyHttpClient = new HttpClient(new SslContextFactory());
|
||||
// jettyHttpClient.setSocketAddressResolver(new MySocketAddressResolver());
|
||||
|
||||
// prevent user-agent tracking
|
||||
jettyHttpClient.setUserAgentField(null);
|
||||
|
||||
// configure
|
||||
configureProxy(jettyHttpClient, httpUsage);
|
||||
configureThread(jettyHttpClient, httpUsage);
|
||||
|
||||
return jettyHttpClient;
|
||||
}
|
||||
|
||||
protected Consumer<Exception> computeOnNetworkError() {
|
||||
RateLimiter rateLimiter = RateLimiter.create(RATE_CHANGE_IDENTITY_ON_NETWORK_ERROR);
|
||||
return e -> {
|
||||
if(!rateLimiter.tryAcquire()) {
|
||||
if(log.isDebugEnabled()) {
|
||||
log.debug("onNetworkError: not changing Tor identity (too many recent attempts)");
|
||||
}
|
||||
return;
|
||||
}
|
||||
// change Tor identity on network error
|
||||
httpProxySupplier.changeIdentity();
|
||||
};
|
||||
}
|
||||
|
||||
protected void configureProxy(HttpClient jettyHttpClient, HttpUsage httpUsage) {
|
||||
Optional<HttpProxy> httpProxyOptional = httpProxySupplier.getHttpProxy(httpUsage);
|
||||
if(httpProxyOptional != null && httpProxyOptional.isPresent()) {
|
||||
HttpProxy httpProxy = httpProxyOptional.get();
|
||||
if(log.isDebugEnabled()) {
|
||||
log.debug("+httpClient: proxy=" + httpProxy);
|
||||
}
|
||||
ProxyConfiguration.Proxy jettyProxy = computeJettyProxy(httpProxy);
|
||||
jettyHttpClient.getProxyConfiguration().getProxies().add(jettyProxy);
|
||||
} else {
|
||||
if(log.isDebugEnabled()) {
|
||||
log.debug("+httpClient: no proxy");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void configureThread(HttpClient jettyHttpClient, HttpUsage httpUsage) {
|
||||
String name = NAME + "-" + httpUsage.toString();
|
||||
|
||||
QueuedThreadPool threadPool = new QueuedThreadPool();
|
||||
threadPool.setName(name);
|
||||
threadPool.setDaemon(true);
|
||||
jettyHttpClient.setExecutor(threadPool);
|
||||
jettyHttpClient.setScheduler(new ScheduledExecutorScheduler(name + "-scheduler", true));
|
||||
}
|
||||
|
||||
protected ProxyConfiguration.Proxy computeJettyProxy(HttpProxy httpProxy) {
|
||||
ProxyConfiguration.Proxy jettyProxy = null;
|
||||
switch(httpProxy.getProtocol()) {
|
||||
case SOCKS:
|
||||
jettyProxy = new Socks4Proxy(httpProxy.getHost(), httpProxy.getPort());
|
||||
break;
|
||||
case SOCKS5:
|
||||
jettyProxy = new Socks5Proxy(httpProxy.getHost(), httpProxy.getPort());
|
||||
break;
|
||||
case HTTP:
|
||||
jettyProxy = new org.eclipse.jetty.client.HttpProxy(httpProxy.getHost(), httpProxy.getPort());
|
||||
break;
|
||||
}
|
||||
return jettyProxy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void stop() {
|
||||
for(JettyHttpClient httpClient : httpClients.values()) {
|
||||
httpClient.stop();
|
||||
}
|
||||
httpClients.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeIdentity() {
|
||||
stop();
|
||||
httpProxySupplier.changeIdentity();
|
||||
}
|
||||
|
||||
public IHttpProxySupplier getHttpProxySupplier() {
|
||||
return httpProxySupplier;
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
package com.sparrowwallet.sparrow.net.http.client;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class ThreadUtil {
|
||||
private static final Logger log = LoggerFactory.getLogger(ThreadUtil.class);
|
||||
|
||||
private static ThreadUtil instance;
|
||||
|
||||
private ExecutorService executorService;
|
||||
|
||||
protected ThreadUtil() {
|
||||
this.executorService = computeExecutorService();
|
||||
}
|
||||
|
||||
public static ThreadUtil getInstance() {
|
||||
if(instance == null) {
|
||||
instance = new ThreadUtil();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
protected ExecutorService computeExecutorService() {
|
||||
return Executors.newFixedThreadPool(5,
|
||||
r -> {
|
||||
Thread t = Executors.defaultThreadFactory().newThread(r);
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
}
|
||||
|
||||
public void setExecutorService(ScheduledExecutorService executorService) {
|
||||
this.executorService = executorService;
|
||||
}
|
||||
|
||||
public <T> Future<T> runAsync(Callable<T> callable) {
|
||||
return executorService.submit(callable);
|
||||
}
|
||||
}
|
|
@ -1,222 +0,0 @@
|
|||
/**
|
||||
* Socks5 backported from Jetty12 - we still use Jetty9 for JDK8 compatibility.
|
||||
*/
|
||||
|
||||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package com.sparrowwallet.sparrow.net.http.client.socks5;
|
||||
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Helper class for SOCKS5 proxying.
|
||||
*
|
||||
* @see Socks5Proxy
|
||||
*/
|
||||
public class Socks5 {
|
||||
/** The SOCKS protocol version: {@value}. */
|
||||
public static final byte VERSION = 0x05;
|
||||
|
||||
/** The SOCKS5 {@code CONNECT} command used in SOCKS5 connect requests. */
|
||||
public static final byte COMMAND_CONNECT = 0x01;
|
||||
|
||||
/** The reserved byte value: {@value}. */
|
||||
public static final byte RESERVED = 0x00;
|
||||
|
||||
/** The address type for IPv4 used in SOCKS5 connect requests and responses. */
|
||||
public static final byte ADDRESS_TYPE_IPV4 = 0x01;
|
||||
|
||||
/** The address type for domain names used in SOCKS5 connect requests and responses. */
|
||||
public static final byte ADDRESS_TYPE_DOMAIN = 0x03;
|
||||
|
||||
/** The address type for IPv6 used in SOCKS5 connect requests and responses. */
|
||||
public static final byte ADDRESS_TYPE_IPV6 = 0x04;
|
||||
|
||||
private Socks5() {
|
||||
}
|
||||
|
||||
/**
|
||||
* A SOCKS5 authentication method.
|
||||
*
|
||||
* <p>Implementations should send and receive the bytes that are specific to the particular
|
||||
* authentication method.
|
||||
*/
|
||||
public interface Authentication {
|
||||
/**
|
||||
* Performs the authentication send and receive bytes exchanges specific for this {@link
|
||||
* Authentication}.
|
||||
*
|
||||
* @param endPoint the {@link EndPoint} to send to and receive from the SOCKS5 server
|
||||
* @param callback the callback to complete when the authentication is complete
|
||||
*/
|
||||
void authenticate(EndPoint endPoint, Callback callback);
|
||||
|
||||
/** A factory for {@link Authentication}s. */
|
||||
interface Factory {
|
||||
/**
|
||||
* @return the authentication method defined by RFC 1928
|
||||
*/
|
||||
byte getMethod();
|
||||
|
||||
/**
|
||||
* @return a new {@link Authentication}
|
||||
*/
|
||||
Authentication newAuthentication();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The implementation of the {@code NO AUTH} authentication method defined in <a
|
||||
* href="https://datatracker.ietf.org/doc/html/rfc1928">RFC 1928</a>.
|
||||
*/
|
||||
public static class NoAuthenticationFactory implements Authentication.Factory {
|
||||
public static final byte METHOD = 0x00;
|
||||
|
||||
@Override
|
||||
public byte getMethod() {
|
||||
return METHOD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication newAuthentication() {
|
||||
return (endPoint, callback) -> callback.succeeded();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The implementation of the {@code USERNAME/PASSWORD} authentication method defined in <a
|
||||
* href="https://datatracker.ietf.org/doc/html/rfc1929">RFC 1929</a>.
|
||||
*/
|
||||
public static class UsernamePasswordAuthenticationFactory implements Authentication.Factory {
|
||||
public static final byte METHOD = 0x02;
|
||||
public static final byte VERSION = 0x01;
|
||||
private static final Logger LOG =
|
||||
LoggerFactory.getLogger(UsernamePasswordAuthenticationFactory.class);
|
||||
|
||||
private final String userName;
|
||||
private final String password;
|
||||
private final Charset charset;
|
||||
|
||||
public UsernamePasswordAuthenticationFactory(String userName, String password) {
|
||||
this(userName, password, StandardCharsets.US_ASCII);
|
||||
}
|
||||
|
||||
public UsernamePasswordAuthenticationFactory(
|
||||
String userName, String password, Charset charset) {
|
||||
this.userName = Objects.requireNonNull(userName);
|
||||
this.password = Objects.requireNonNull(password);
|
||||
this.charset = Objects.requireNonNull(charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getMethod() {
|
||||
return METHOD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication newAuthentication() {
|
||||
return new UsernamePasswordAuthentication(this);
|
||||
}
|
||||
|
||||
private static class UsernamePasswordAuthentication implements Authentication, Callback {
|
||||
private final ByteBuffer byteBuffer = BufferUtil.allocate(2);
|
||||
private final UsernamePasswordAuthenticationFactory factory;
|
||||
private EndPoint endPoint;
|
||||
private Callback callback;
|
||||
|
||||
private UsernamePasswordAuthentication(UsernamePasswordAuthenticationFactory factory) {
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticate(EndPoint endPoint, Callback callback) {
|
||||
this.endPoint = endPoint;
|
||||
this.callback = callback;
|
||||
|
||||
byte[] userNameBytes = factory.userName.getBytes(factory.charset);
|
||||
byte[] passwordBytes = factory.password.getBytes(factory.charset);
|
||||
ByteBuffer byteBuffer =
|
||||
(ByteBuffer)
|
||||
ByteBuffer.allocate(3 + userNameBytes.length + passwordBytes.length)
|
||||
.put(VERSION)
|
||||
.put((byte) userNameBytes.length)
|
||||
.put(userNameBytes)
|
||||
.put((byte) passwordBytes.length)
|
||||
.put(passwordBytes)
|
||||
.flip();
|
||||
endPoint.write(Callback.from(this::authenticationSent, this::failed), byteBuffer);
|
||||
}
|
||||
|
||||
private void authenticationSent() {
|
||||
if(LOG.isDebugEnabled()) {
|
||||
LOG.debug("Written SOCKS5 username/password authentication request");
|
||||
}
|
||||
endPoint.fillInterested(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void succeeded() {
|
||||
try {
|
||||
int filled = endPoint.fill(byteBuffer);
|
||||
if(filled < 0) {
|
||||
throw new ClosedChannelException();
|
||||
}
|
||||
if(byteBuffer.remaining() < 2) {
|
||||
endPoint.fillInterested(this);
|
||||
return;
|
||||
}
|
||||
if(LOG.isDebugEnabled()) {
|
||||
LOG.debug("Received SOCKS5 username/password authentication response");
|
||||
}
|
||||
byte version = byteBuffer.get();
|
||||
if(version != VERSION) {
|
||||
throw new IOException(
|
||||
"Unsupported username/password authentication version: " + version);
|
||||
}
|
||||
byte status = byteBuffer.get();
|
||||
if(status != 0) {
|
||||
throw new IOException("SOCK5 username/password authentication failure");
|
||||
}
|
||||
if(LOG.isDebugEnabled()) {
|
||||
LOG.debug("SOCKS5 username/password authentication succeeded");
|
||||
}
|
||||
callback.succeeded();
|
||||
} catch(Throwable x) {
|
||||
failed(x);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x) {
|
||||
callback.failed(x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InvocationType getInvocationType() {
|
||||
return InvocationType.NON_BLOCKING;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,419 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package com.sparrowwallet.sparrow.net.http.client.socks5;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpClientTransport;
|
||||
import org.eclipse.jetty.client.HttpDestination;
|
||||
import org.eclipse.jetty.client.Origin;
|
||||
import org.eclipse.jetty.client.ProxyConfiguration.Proxy;
|
||||
import org.eclipse.jetty.io.AbstractConnection;
|
||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Client-side proxy configuration for SOCKS5, defined by <a
|
||||
* href="https://datatracker.ietf.org/doc/html/rfc1928">RFC 1928</a>.
|
||||
*
|
||||
* <p>Multiple authentication methods are supported via {@link
|
||||
* #putAuthenticationFactory(Socks5.Authentication.Factory)}. By default only the {@link
|
||||
* Socks5.NoAuthenticationFactory NO AUTH} authentication method is configured. The {@link
|
||||
* Socks5.UsernamePasswordAuthenticationFactory USERNAME/PASSWORD} is available to applications but
|
||||
* must be explicitly configured and added.
|
||||
*/
|
||||
public class Socks5Proxy extends Proxy {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Socks5Proxy.class);
|
||||
|
||||
private final Map<Byte, Socks5.Authentication.Factory> authentications = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* Creates a new instance with the given SOCKS5 proxy host and port.
|
||||
*
|
||||
* @param host the SOCKS5 proxy host name
|
||||
* @param port the SOCKS5 proxy port
|
||||
*/
|
||||
public Socks5Proxy(String host, int port) {
|
||||
this(new Origin.Address(host, port), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the given SOCKS5 proxy address.
|
||||
*
|
||||
* <p>When {@code secure=true} the communication between the client and the proxy will be
|
||||
* encrypted (using this proxy {@link #getSslContextFactory()} which typically defaults to that of
|
||||
* {@link HttpClient}.
|
||||
*
|
||||
* @param address the SOCKS5 proxy address (host and port)
|
||||
* @param secure whether the communication between the client and the SOCKS5 proxy should be
|
||||
* secure
|
||||
*/
|
||||
public Socks5Proxy(Origin.Address address, boolean secure) {
|
||||
super(address, secure);
|
||||
putAuthenticationFactory(new Socks5.NoAuthenticationFactory());
|
||||
}
|
||||
|
||||
protected static ClientConnectionFactory newSslClientConnectionFactory(HttpClient httpClient, SslContextFactory sslContextFactory, ClientConnectionFactory connectionFactory) {
|
||||
if(sslContextFactory == null) {
|
||||
sslContextFactory = httpClient.getSslContextFactory();
|
||||
}
|
||||
return new SslClientConnectionFactory(sslContextFactory, httpClient.getByteBufferPool(), httpClient.getExecutor(), connectionFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides this class with the given SOCKS5 authentication method.
|
||||
*
|
||||
* @param authenticationFactory the SOCKS5 authentication factory
|
||||
* @return the previous authentication method of the same type, or {@code null} if there was none
|
||||
* of that type already present
|
||||
*/
|
||||
public Socks5.Authentication.Factory putAuthenticationFactory(Socks5.Authentication.Factory authenticationFactory) {
|
||||
return authentications.put(authenticationFactory.getMethod(), authenticationFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the authentication of the given {@code method}.
|
||||
*
|
||||
* @param method the authentication method to remove
|
||||
*/
|
||||
public Socks5.Authentication.Factory removeAuthenticationFactory(byte method) {
|
||||
return authentications.remove(method);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientConnectionFactory newClientConnectionFactory(ClientConnectionFactory connectionFactory) {
|
||||
return new Socks5ProxyClientConnectionFactory(connectionFactory);
|
||||
}
|
||||
|
||||
private static class Socks5ProxyConnection extends AbstractConnection implements Connection.UpgradeFrom {
|
||||
private static final Pattern IPv4_PATTERN = Pattern.compile("(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})");
|
||||
|
||||
// SOCKS5 response max length is 262 bytes.
|
||||
private final ByteBuffer byteBuffer = BufferUtil.allocate(512);
|
||||
private final ClientConnectionFactory connectionFactory;
|
||||
private final Map<String, Object> context;
|
||||
private final Map<Byte, Socks5.Authentication.Factory> authentications;
|
||||
private State state = State.HANDSHAKE;
|
||||
|
||||
private Socks5ProxyConnection(EndPoint endPoint, Executor executor, ClientConnectionFactory connectionFactory, Map<String, Object> context, Map<Byte, Socks5.Authentication.Factory> authentications) {
|
||||
super(endPoint, executor);
|
||||
this.connectionFactory = connectionFactory;
|
||||
this.context = context;
|
||||
this.authentications = new LinkedHashMap<>(authentications);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer onUpgradeFrom() {
|
||||
return BufferUtil.copy(byteBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen() {
|
||||
super.onOpen();
|
||||
sendHandshake();
|
||||
}
|
||||
|
||||
private void sendHandshake() {
|
||||
try {
|
||||
// +-------------+--------------------+------------------+
|
||||
// | version (1) | num of methods (1) | methods (1..255) |
|
||||
// +-------------+--------------------+------------------+
|
||||
int size = authentications.size();
|
||||
ByteBuffer byteBuffer =
|
||||
ByteBuffer.allocate(1 + 1 + size).put(Socks5.VERSION).put((byte) size);
|
||||
authentications.keySet().forEach(byteBuffer::put);
|
||||
byteBuffer.flip();
|
||||
getEndPoint().write(Callback.from(this::handshakeSent, this::fail), byteBuffer);
|
||||
} catch(Throwable x) {
|
||||
fail(x);
|
||||
}
|
||||
}
|
||||
|
||||
private void handshakeSent() {
|
||||
if(LOG.isDebugEnabled()) {
|
||||
LOG.debug("Written SOCKS5 handshake request");
|
||||
}
|
||||
state = State.HANDSHAKE;
|
||||
fillInterested();
|
||||
}
|
||||
|
||||
private void fail(Throwable x) {
|
||||
if(LOG.isDebugEnabled()) {
|
||||
LOG.debug("SOCKS5 failure", x);
|
||||
}
|
||||
getEndPoint().close();
|
||||
@SuppressWarnings("unchecked")
|
||||
Promise<Connection> promise =
|
||||
(Promise<Connection>)
|
||||
this.context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
||||
promise.failed(x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFillable() {
|
||||
try {
|
||||
switch(state) {
|
||||
case HANDSHAKE:
|
||||
receiveHandshake();
|
||||
break;
|
||||
case CONNECT:
|
||||
receiveConnect();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
} catch(Throwable x) {
|
||||
fail(x);
|
||||
}
|
||||
}
|
||||
|
||||
private void receiveHandshake() throws IOException {
|
||||
// +-------------+------------+
|
||||
// | version (1) | method (1) |
|
||||
// +-------------+------------+
|
||||
int filled = getEndPoint().fill(byteBuffer);
|
||||
if(filled < 0) {
|
||||
throw new ClosedChannelException();
|
||||
}
|
||||
if(byteBuffer.remaining() < 2) {
|
||||
fillInterested();
|
||||
return;
|
||||
}
|
||||
|
||||
if(LOG.isDebugEnabled()) {
|
||||
LOG.debug("Received SOCKS5 handshake response {}", BufferUtil.toDetailString(byteBuffer));
|
||||
}
|
||||
|
||||
byte version = byteBuffer.get();
|
||||
if(version != Socks5.VERSION) {
|
||||
throw new IOException("Unsupported SOCKS5 version: " + version);
|
||||
}
|
||||
|
||||
byte method = byteBuffer.get();
|
||||
if(method == -1) {
|
||||
throw new IOException("Unacceptable SOCKS5 authentication methods");
|
||||
}
|
||||
|
||||
Socks5.Authentication.Factory factory = authentications.get(method);
|
||||
if(factory == null) {
|
||||
throw new IOException("Unknown SOCKS5 authentication method: " + method);
|
||||
}
|
||||
|
||||
factory
|
||||
.newAuthentication()
|
||||
.authenticate(getEndPoint(), Callback.from(this::sendConnect, this::fail));
|
||||
}
|
||||
|
||||
private void sendConnect() {
|
||||
try {
|
||||
// +-------------+-------------+--------------+------------------+------------------------+----------+
|
||||
// | version (1) | command (1) | reserved (1) | address type (1) | address bytes (4..255) |
|
||||
// port (2) |
|
||||
// +-------------+-------------+--------------+------------------+------------------------+----------+
|
||||
HttpDestination destination = (HttpDestination) context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||
Origin.Address address = destination.getOrigin().getAddress();
|
||||
String host = address.getHost();
|
||||
short port = (short) address.getPort();
|
||||
|
||||
ByteBuffer byteBuffer;
|
||||
Matcher matcher = IPv4_PATTERN.matcher(host);
|
||||
if(matcher.matches()) {
|
||||
byteBuffer =
|
||||
ByteBuffer.allocate(10)
|
||||
.put(Socks5.VERSION)
|
||||
.put(Socks5.COMMAND_CONNECT)
|
||||
.put(Socks5.RESERVED)
|
||||
.put(Socks5.ADDRESS_TYPE_IPV4);
|
||||
for(int i = 1; i <= 4; ++i) {
|
||||
byteBuffer.put(Byte.parseByte(matcher.group(i)));
|
||||
}
|
||||
byteBuffer.putShort(port).flip();
|
||||
} else if(true /*URIUtil.isValidHostRegisteredName(host)*/) {
|
||||
byte[] bytes = host.getBytes(StandardCharsets.US_ASCII);
|
||||
if(bytes.length > 255) {
|
||||
throw new IOException("Invalid host name: " + host);
|
||||
}
|
||||
byteBuffer =
|
||||
(ByteBuffer)
|
||||
ByteBuffer.allocate(7 + bytes.length)
|
||||
.put(Socks5.VERSION)
|
||||
.put(Socks5.COMMAND_CONNECT)
|
||||
.put(Socks5.RESERVED)
|
||||
.put(Socks5.ADDRESS_TYPE_DOMAIN)
|
||||
.put((byte) bytes.length)
|
||||
.put(bytes)
|
||||
.putShort(port)
|
||||
.flip();
|
||||
} else {
|
||||
// Assume IPv6.
|
||||
byte[] bytes = InetAddress.getByName(host).getAddress();
|
||||
byteBuffer =
|
||||
(ByteBuffer)
|
||||
ByteBuffer.allocate(22)
|
||||
.put(Socks5.VERSION)
|
||||
.put(Socks5.COMMAND_CONNECT)
|
||||
.put(Socks5.RESERVED)
|
||||
.put(Socks5.ADDRESS_TYPE_IPV6)
|
||||
.put(bytes)
|
||||
.putShort(port)
|
||||
.flip();
|
||||
}
|
||||
|
||||
getEndPoint().write(Callback.from(this::connectSent, this::fail), byteBuffer);
|
||||
} catch(Throwable x) {
|
||||
fail(x);
|
||||
}
|
||||
}
|
||||
|
||||
private void connectSent() {
|
||||
if(LOG.isDebugEnabled()) {
|
||||
LOG.debug("Written SOCKS5 connect request");
|
||||
}
|
||||
state = State.CONNECT;
|
||||
fillInterested();
|
||||
}
|
||||
|
||||
private void receiveConnect() throws IOException {
|
||||
// +-------------+-----------+--------------+------------------+------------------------+----------+
|
||||
// | version (1) | reply (1) | reserved (1) | address type (1) | address bytes (4..255) | port
|
||||
// (2) |
|
||||
// +-------------+-----------+--------------+------------------+------------------------+----------+
|
||||
int filled = getEndPoint().fill(byteBuffer);
|
||||
if(filled < 0) {
|
||||
throw new ClosedChannelException();
|
||||
}
|
||||
if(byteBuffer.remaining() < 5) {
|
||||
fillInterested();
|
||||
return;
|
||||
}
|
||||
byte addressType = byteBuffer.get(3);
|
||||
int length = 6;
|
||||
if(addressType == Socks5.ADDRESS_TYPE_IPV4) {
|
||||
length += 4;
|
||||
} else if(addressType == Socks5.ADDRESS_TYPE_DOMAIN) {
|
||||
length += 1 + (byteBuffer.get(4) & 0xFF);
|
||||
} else if(addressType == Socks5.ADDRESS_TYPE_IPV6) {
|
||||
length += 16;
|
||||
} else {
|
||||
throw new IOException("Invalid SOCKS5 address type: " + addressType);
|
||||
}
|
||||
if(byteBuffer.remaining() < length) {
|
||||
fillInterested();
|
||||
return;
|
||||
}
|
||||
|
||||
if(LOG.isDebugEnabled()) {
|
||||
LOG.debug("Received SOCKS5 connect response {}", BufferUtil.toDetailString(byteBuffer));
|
||||
}
|
||||
|
||||
// We have all the SOCKS5 bytes.
|
||||
byte version = byteBuffer.get();
|
||||
if(version != Socks5.VERSION) {
|
||||
throw new IOException("Unsupported SOCKS5 version: " + version);
|
||||
}
|
||||
|
||||
byte status = byteBuffer.get();
|
||||
switch(status) {
|
||||
case 0: {
|
||||
// Consume the buffer before upgrading to the tunnel.
|
||||
byteBuffer.position(length);
|
||||
tunnel();
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
throw new IOException("SOCKS5 general failure");
|
||||
case 2:
|
||||
throw new IOException("SOCKS5 connection not allowed");
|
||||
case 3:
|
||||
throw new IOException("SOCKS5 network unreachable");
|
||||
case 4:
|
||||
throw new IOException("SOCKS5 host unreachable");
|
||||
case 5:
|
||||
throw new IOException("SOCKS5 connection refused");
|
||||
case 6:
|
||||
throw new IOException("SOCKS5 timeout expired");
|
||||
case 7:
|
||||
throw new IOException("SOCKS5 unsupported command");
|
||||
case 8:
|
||||
throw new IOException("SOCKS5 unsupported address");
|
||||
default:
|
||||
throw new IOException("SOCKS5 unknown status: " + status);
|
||||
}
|
||||
}
|
||||
|
||||
private void tunnel() {
|
||||
try {
|
||||
HttpDestination destination =
|
||||
(HttpDestination) context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||
this.context.put("ssl.peer.host", destination.getHost());
|
||||
this.context.put("ssl.peer.port", destination.getPort());
|
||||
// Origin.Address address = destination.getOrigin().getAddress();
|
||||
// Don't want to do DNS resolution here.
|
||||
// InetSocketAddress inet = InetSocketAddress.createUnresolved(address.getHost(),
|
||||
// address.getPort());
|
||||
// context.put(ClientConnector.REMOTE_SOCKET_ADDRESS_CONTEXT_KEY, inet);
|
||||
ClientConnectionFactory connectionFactory = this.connectionFactory;
|
||||
if(destination.isSecure()) {
|
||||
connectionFactory =
|
||||
newSslClientConnectionFactory(destination.getHttpClient(), null, connectionFactory);
|
||||
}
|
||||
Connection newConnection = connectionFactory.newConnection(getEndPoint(), context);
|
||||
getEndPoint().upgrade(newConnection);
|
||||
if(LOG.isDebugEnabled()) {
|
||||
LOG.debug("SOCKS5 tunnel established: {} over {}", this, newConnection);
|
||||
}
|
||||
} catch(Throwable x) {
|
||||
fail(x);
|
||||
}
|
||||
}
|
||||
|
||||
private enum State {
|
||||
HANDSHAKE,
|
||||
CONNECT
|
||||
}
|
||||
}
|
||||
|
||||
private class Socks5ProxyClientConnectionFactory implements ClientConnectionFactory {
|
||||
private final ClientConnectionFactory connectionFactory;
|
||||
|
||||
private Socks5ProxyClientConnectionFactory(ClientConnectionFactory connectionFactory) {
|
||||
this.connectionFactory = connectionFactory;
|
||||
}
|
||||
|
||||
public Connection newConnection(EndPoint endPoint, Map<String, Object> context) {
|
||||
HttpDestination destination = (HttpDestination) context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||
Executor executor = destination.getHttpClient().getExecutor();
|
||||
Socks5ProxyConnection connection = new Socks5ProxyConnection(endPoint, executor, connectionFactory, context, authentications);
|
||||
return customize(connection, context);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@ import com.sparrowwallet.drongo.wallet.WalletNode;
|
|||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.net.HttpClientService;
|
||||
import com.sparrowwallet.sparrow.net.Protocol;
|
||||
import com.sparrowwallet.sparrow.net.http.client.HttpResponseException;
|
||||
import com.sparrowwallet.tern.http.client.HttpResponseException;
|
||||
import javafx.concurrent.Service;
|
||||
import javafx.concurrent.Task;
|
||||
import org.slf4j.Logger;
|
||||
|
|
|
@ -64,8 +64,5 @@ open module com.sparrowwallet.sparrow {
|
|||
requires com.sparrowwallet.bokmakierie;
|
||||
requires java.smartcardio;
|
||||
requires com.jcraft.jzlib;
|
||||
requires org.eclipse.jetty.client;
|
||||
requires org.eclipse.jetty.http;
|
||||
requires org.eclipse.jetty.util;
|
||||
requires org.eclipse.jetty.io;
|
||||
requires com.sparrowwallet.tern;
|
||||
}
|
Loading…
Reference in a new issue