Fix NPE in ExternalBrowser
This commit is contained in:
parent
ef2e354d65
commit
8a71668508
|
@ -1,47 +1,37 @@
|
|||
package ctbrec.ui;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.*;
|
||||
import ctbrec.Config;
|
||||
import ctbrec.OS;
|
||||
import ctbrec.io.ProcessOutputLogger;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.*;
|
||||
import java.net.Socket;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.OS;
|
||||
import ctbrec.io.ProcessOutputLogger;
|
||||
|
||||
public class ExternalBrowser implements AutoCloseable {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ExternalBrowser.class);
|
||||
@Slf4j
|
||||
public class ExternalBrowser implements AutoCloseable { // NOSONAR singleton is wanted
|
||||
private static final ExternalBrowser INSTANCE = new ExternalBrowser();
|
||||
private Lock lock = new ReentrantLock();
|
||||
private static final String MSGID = "msgid";
|
||||
private final Lock lock = new ReentrantLock();
|
||||
private Socket socket;
|
||||
|
||||
private Consumer<String> messageListener;
|
||||
private InputStream in;
|
||||
private OutputStream out;
|
||||
private Socket socket; // NOSONAR
|
||||
private Thread reader;
|
||||
private volatile boolean stopped = true;
|
||||
private volatile boolean browserReady = false;
|
||||
private Object browserReadyLock = new Object();
|
||||
private final Object browserReadyLock = new Object();
|
||||
|
||||
private Map<String, CompletableFuture<Object>> responseFutures = new HashMap<>();
|
||||
private final Map<String, CompletableFuture<Object>> responseFutures = new HashMap<>();
|
||||
private Runnable onReadyCallback;
|
||||
|
||||
public static ExternalBrowser getInstance() {
|
||||
|
@ -49,7 +39,7 @@ public class ExternalBrowser implements AutoCloseable {
|
|||
}
|
||||
|
||||
public void run(JSONObject jsonConfig, Consumer<String> messageListener) throws InterruptedException, IOException {
|
||||
LOG.debug("Running browser with config {}", jsonConfig);
|
||||
log.debug("Running browser with config {}", jsonConfig);
|
||||
lock.lock();
|
||||
try {
|
||||
stopped = false;
|
||||
|
@ -62,7 +52,7 @@ public class ExternalBrowser implements AutoCloseable {
|
|||
Process p = new ProcessBuilder(cmdline).start();
|
||||
new Thread(new ProcessOutputLogger(p.getInputStream(), "ExternalBrowser stdout")).start();
|
||||
new Thread(new ProcessOutputLogger(p.getErrorStream(), "ExternalBrowser stderr")).start();
|
||||
LOG.debug("Browser started: {}", Arrays.toString(cmdline));
|
||||
log.debug("Browser started: {}", Arrays.toString(cmdline));
|
||||
|
||||
connectToRemoteControlSocket();
|
||||
while (!browserReady) {
|
||||
|
@ -70,10 +60,10 @@ public class ExternalBrowser implements AutoCloseable {
|
|||
browserReadyLock.wait(100);
|
||||
}
|
||||
}
|
||||
if(LOG.isTraceEnabled()) {
|
||||
LOG.debug("Connected to remote control server. Sending config {}", jsonConfig);
|
||||
if (log.isTraceEnabled()) {
|
||||
log.debug("Connected to remote control server. Sending config {}", jsonConfig);
|
||||
} else {
|
||||
LOG.debug("Connected to remote control server. Sending config");
|
||||
log.debug("Connected to remote control server. Sending config");
|
||||
}
|
||||
out.write(jsonConfig.toString().getBytes(UTF_8));
|
||||
out.write('\n');
|
||||
|
@ -81,14 +71,14 @@ public class ExternalBrowser implements AutoCloseable {
|
|||
|
||||
Optional.ofNullable(onReadyCallback).ifPresent(Runnable::run);
|
||||
|
||||
LOG.debug("Waiting for browser to terminate");
|
||||
log.debug("Waiting for browser to terminate");
|
||||
p.waitFor();
|
||||
int exitValue = p.exitValue();
|
||||
reader = null;
|
||||
in = null;
|
||||
out = null;
|
||||
this.messageListener = null;
|
||||
LOG.debug("Browser Process terminated with {}", exitValue);
|
||||
log.debug("Browser Process terminated with {}", exitValue);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
|
@ -102,11 +92,11 @@ public class ExternalBrowser implements AutoCloseable {
|
|||
out = socket.getOutputStream();
|
||||
reader = new Thread(this::readBrowserOutput);
|
||||
reader.start();
|
||||
LOG.debug("Connected to control socket");
|
||||
log.debug("Connected to control socket");
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
if(i == 19) {
|
||||
LOG.error("Connection to remote control socket failed", e);
|
||||
if (i == 19) {
|
||||
log.error("Connection to remote control socket failed", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
@ -122,9 +112,9 @@ public class ExternalBrowser implements AutoCloseable {
|
|||
|
||||
public CompletableFuture<Object> executeJavaScript(String javaScript) throws IOException {
|
||||
String id = UUID.randomUUID().toString();
|
||||
var future = new CompletableFuture<Object>();
|
||||
var future = new CompletableFuture<>();
|
||||
var script = new JSONObject();
|
||||
script.put("msgid", id);
|
||||
script.put(MSGID, id);
|
||||
script.put("execute", javaScript);
|
||||
if (out != null) {
|
||||
out.write(script.toString().getBytes(UTF_8));
|
||||
|
@ -140,7 +130,7 @@ public class ExternalBrowser implements AutoCloseable {
|
|||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if(stopped) {
|
||||
if (stopped) {
|
||||
return;
|
||||
}
|
||||
stopped = true;
|
||||
|
@ -148,27 +138,29 @@ public class ExternalBrowser implements AutoCloseable {
|
|||
}
|
||||
|
||||
private void readBrowserOutput() {
|
||||
LOG.debug("Browser output reader started");
|
||||
log.debug("Browser output reader started");
|
||||
try (var br = new BufferedReader(new InputStreamReader(in))) {
|
||||
String line;
|
||||
synchronized (browserReadyLock) {
|
||||
browserReady = true;
|
||||
browserReadyLock.notifyAll();
|
||||
}
|
||||
while( !Thread.interrupted() && (line = br.readLine()) != null ) {
|
||||
LOG.debug("Browser output: {}", line);
|
||||
while (!Thread.interrupted() && (line = br.readLine()) != null) {
|
||||
log.debug("Browser output: {}", line);
|
||||
if (line.startsWith("{")) {
|
||||
JSONObject json = new JSONObject(line);
|
||||
if (json.has("msgid")) {
|
||||
if (json.has(MSGID)) {
|
||||
handleExecuteScriptResponse(json);
|
||||
} else {
|
||||
messageListener.accept(line);
|
||||
if (messageListener != null) {
|
||||
messageListener.accept(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if(!stopped) {
|
||||
LOG.error("Couldn't read browser output", e);
|
||||
if (!stopped) {
|
||||
log.error("Couldn't read browser output", e);
|
||||
}
|
||||
} finally {
|
||||
stopped = true;
|
||||
|
@ -180,59 +172,62 @@ public class ExternalBrowser implements AutoCloseable {
|
|||
}
|
||||
|
||||
private void handleExecuteScriptResponse(JSONObject json) {
|
||||
var msgid = json.getString("msgid");
|
||||
LOG.debug("Future {}", msgid);
|
||||
var msgid = json.getString(MSGID);
|
||||
log.debug("Future {}", msgid);
|
||||
CompletableFuture<Object> future = responseFutures.get(msgid);
|
||||
if (future != null) {
|
||||
responseFutures.remove(msgid);
|
||||
if (json.has("result")) {
|
||||
LOG.debug("Future {} done. Result: {}", msgid, json.get("result"));
|
||||
future.complete(json.get("result"));
|
||||
final String RESULT = "result";
|
||||
if (json.has(RESULT)) {
|
||||
log.debug("Future {} done. Result: {}", msgid, json.get(RESULT));
|
||||
future.complete(json.get(RESULT));
|
||||
} else if (json.has("error")) {
|
||||
LOG.debug("Future {} failed", msgid);
|
||||
log.debug("Future {} failed", msgid);
|
||||
future.completeExceptionally(new Exception(json.getJSONObject("error").toString()));
|
||||
}
|
||||
} else {
|
||||
LOG.warn("No future for previous request {}", msgid);
|
||||
log.warn("No future for previous request {}", msgid);
|
||||
}
|
||||
}
|
||||
|
||||
private void addProxyConfig(JSONObject jsonConfig) {
|
||||
var proxyType = Config.getInstance().getSettings().proxyType;
|
||||
var proxy = new JSONObject();
|
||||
final String KEY_ADDRESS = "address";
|
||||
final String KEY_PROXY = "proxy";
|
||||
switch (proxyType) {
|
||||
case HTTP:
|
||||
var proxy = new JSONObject();
|
||||
proxy.put("address",
|
||||
"http=" + Config.getInstance().getSettings().proxyHost + ':' + Config.getInstance().getSettings().proxyPort
|
||||
+ ";https=" + Config.getInstance().getSettings().proxyHost + ':' + Config.getInstance().getSettings().proxyPort);
|
||||
if(Config.getInstance().getSettings().proxyUser != null && !Config.getInstance().getSettings().proxyUser.isEmpty()) {
|
||||
String username = Config.getInstance().getSettings().proxyUser;
|
||||
String password = Config.getInstance().getSettings().proxyPassword;
|
||||
proxy.put("user", username);
|
||||
proxy.put("password", password);
|
||||
}
|
||||
jsonConfig.put("proxy", proxy);
|
||||
break;
|
||||
case SOCKS4:
|
||||
proxy = new JSONObject();
|
||||
proxy.put("address", "socks4://" + Config.getInstance().getSettings().proxyHost + ':' + Config.getInstance().getSettings().proxyPort);
|
||||
jsonConfig.put("proxy", proxy);
|
||||
break;
|
||||
case SOCKS5:
|
||||
proxy = new JSONObject();
|
||||
proxy.put("address", "socks5://" + Config.getInstance().getSettings().proxyHost + ':' + Config.getInstance().getSettings().proxyPort);
|
||||
if(Config.getInstance().getSettings().proxyUser != null && !Config.getInstance().getSettings().proxyUser.isEmpty()) {
|
||||
String username = Config.getInstance().getSettings().proxyUser;
|
||||
String password = Config.getInstance().getSettings().proxyPassword;
|
||||
proxy.put("user", username);
|
||||
proxy.put("password", password);
|
||||
}
|
||||
jsonConfig.put("proxy", proxy);
|
||||
break;
|
||||
case DIRECT:
|
||||
default:
|
||||
// nothing to do here
|
||||
break;
|
||||
case HTTP:
|
||||
proxy.put(KEY_ADDRESS,
|
||||
"http=" + Config.getInstance().getSettings().proxyHost + ':' + Config.getInstance().getSettings().proxyPort
|
||||
+ ";https=" + Config.getInstance().getSettings().proxyHost + ':' + Config.getInstance().getSettings().proxyPort);
|
||||
if (Config.getInstance().getSettings().proxyUser != null && !Config.getInstance().getSettings().proxyUser.isEmpty()) {
|
||||
String username = Config.getInstance().getSettings().proxyUser;
|
||||
String password = Config.getInstance().getSettings().proxyPassword;
|
||||
proxy.put("user", username);
|
||||
proxy.put("password", password);
|
||||
}
|
||||
jsonConfig.put(KEY_PROXY, proxy);
|
||||
break;
|
||||
case SOCKS4:
|
||||
proxy = new JSONObject();
|
||||
proxy.put(KEY_ADDRESS, "socks4://" + Config.getInstance().getSettings().proxyHost + ':' + Config.getInstance().getSettings().proxyPort);
|
||||
jsonConfig.put(KEY_PROXY, proxy);
|
||||
break;
|
||||
case SOCKS5:
|
||||
proxy = new JSONObject();
|
||||
proxy.put(KEY_ADDRESS, "socks5://" + Config.getInstance().getSettings().proxyHost + ':' + Config.getInstance().getSettings().proxyPort);
|
||||
if (Config.getInstance().getSettings().proxyUser != null && !Config.getInstance().getSettings().proxyUser.isEmpty()) {
|
||||
String username = Config.getInstance().getSettings().proxyUser;
|
||||
String password = Config.getInstance().getSettings().proxyPassword;
|
||||
proxy.put("user", username);
|
||||
proxy.put("password", password);
|
||||
}
|
||||
jsonConfig.put(KEY_PROXY, proxy);
|
||||
break;
|
||||
case DIRECT:
|
||||
default:
|
||||
// nothing to do here
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue