package co.gongzh.procbridge;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author Gong Zhang
*/
public final class ProcBridgeServer {
@FunctionalInterface
public interface Delegate {
/**
* An interface that defines how server handles requests.
*
* @param api the requested API name
* @param body the JSON body of request
* @return a JSON object or {@code null} for good response
* @throws Exception any exception for bad response
*/
@Nullable
JSONObject handleRequest(@NotNull String api, @NotNull JSONObject body) throws Exception;
}
private final int port;
private final Delegate delegate;
private boolean started;
private long timeout;
private ExecutorService executor;
private ServerSocket serverSocket;
public ProcBridgeServer(int port, long timeout, @NotNull Object delegate) {
this(port, timeout, new ReflectiveDelegate(delegate));
}
public ProcBridgeServer(int port, long timeout, Delegate delegate) {
this.started = false;
this.port = port;
this.delegate = delegate;
this.timeout = timeout;
this.executor = null;
this.serverSocket = null;
}
public synchronized boolean isStarted() {
return started;
}
public int getPort() {
return port;
}
public long getTimeout() {
return timeout;
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
public synchronized void start() throws IOException {
if (started) {
throw new IllegalStateException("server already started");
}
final ServerSocket serverSocket = new ServerSocket(this.port); // possible throw exception!
this.serverSocket = serverSocket;
final ExecutorService executor = Executors.newCachedThreadPool();
this.executor = executor;
executor.execute(() -> {
while (true) {
try {
Socket socket = serverSocket.accept();
Connection conn = new Connection(socket, timeout, executor, delegate);
synchronized (ProcBridgeServer.this) {
if (!started) {
return; // finish listener
}
executor.execute(conn);
}
} catch (IOException ignored) {
return; // finish listener
}
}
});
started = true;
}
public synchronized void stop() {
if (!started) {
throw new IllegalStateException("server does not started");
}
executor.shutdown();
executor = null;
try {
serverSocket.close();
} catch (IOException ignored) {
}
serverSocket = null;
this.started = false;
}
private static final class Connection implements Runnable {
private final Socket socket;
private final long timeout;
private final Delegate delegate;
private final ExecutorService executor;
Connection(Socket socket, long timeout, ExecutorService executor, Delegate delegate) {
this.socket = socket;
this.timeout = timeout;
this.delegate = delegate;
this.executor = executor;
}
@Override
public void run() {
try {
TimeGuard guard = new TimeGuard(timeout, () -> {
try (OutputStream os = socket.getOutputStream();
InputStream is = socket.getInputStream()) {
RequestDecoder decoder = Protocol.read(is).asRequest();
if (decoder == null) {
throw ProcBridgeException.malformedInputData();
}
final String api = decoder.api;
final JSONObject body = decoder.body;
Encoder encoder;
try {
JSONObject reply = delegate.handleRequest(api, body);
encoder = new GoodResponseEncoder(reply);
} catch (Exception ex) {
encoder = new BadResponseEncoder(ex.getMessage());
}
Protocol.write(os, encoder);
} catch (ProcBridgeException | IOException e) {
throw new RuntimeException(e);
}
});
guard.execute(executor);
} catch (ProcBridgeException e) {
throw new RuntimeException(e);
}
}
}
}