diff --git a/src/main/java/org/asamk/signal/http/HttpServerException.java b/src/main/java/org/asamk/signal/http/HttpServerException.java new file mode 100644 index 00000000..0f5238e0 --- /dev/null +++ b/src/main/java/org/asamk/signal/http/HttpServerException.java @@ -0,0 +1,14 @@ +package org.asamk.signal.http; + +public class HttpServerException extends RuntimeException { + + private int httpStatus; + + public HttpServerException(final int aHttpStatus, final String message) { + super(message); + } + + public int getHttpStatus() { + return httpStatus; + } +} diff --git a/src/main/java/org/asamk/signal/http/HttpServerHandler.java b/src/main/java/org/asamk/signal/http/HttpServerHandler.java index d22e34ce..c7804ef6 100644 --- a/src/main/java/org/asamk/signal/http/HttpServerHandler.java +++ b/src/main/java/org/asamk/signal/http/HttpServerHandler.java @@ -1,8 +1,10 @@ package org.asamk.signal.http; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ContainerNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.POJONode; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpServer; @@ -11,10 +13,10 @@ import org.asamk.signal.commands.JsonRpcNamespace; import org.asamk.signal.commands.LocalCommand; import org.asamk.signal.commands.MultiLocalCommand; import org.asamk.signal.commands.RegistrationCommand; +import org.asamk.signal.commands.exceptions.CommandException; import org.asamk.signal.jsonrpc.JsonRpcException; import org.asamk.signal.jsonrpc.JsonRpcRequest; import org.asamk.signal.jsonrpc.JsonRpcResponse; -import org.asamk.signal.jsonrpc.SignalJsonRpcDispatcherHandler; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.MultiAccountManager; import org.asamk.signal.manager.RegistrationManager; @@ -27,13 +29,12 @@ import java.io.IOException; import java.io.StringWriter; import java.net.InetSocketAddress; import java.nio.channels.OverlappingFileLockException; -import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.concurrent.Executors; public class HttpServerHandler { - private final static Logger logger = LoggerFactory.getLogger(SignalJsonRpcDispatcherHandler.class); + private final static Logger logger = LoggerFactory.getLogger(HttpServerHandler.class); private final ObjectMapper objectMapper = Util.createJsonObjectMapper(); @@ -41,54 +42,47 @@ public class HttpServerHandler { try { - System.out.println("Starting server on port " + port); + logger.info("Starting server on port " + port); final var server = HttpServer.create(new InetSocketAddress(port), 0); server.setExecutor(Executors.newFixedThreadPool(10)); server.createContext("/api/v1", httpExchange -> { + + JsonRpcRequest request = null; + try { if (!"POST".equals(httpExchange.getRequestMethod())) { - sendResponse(405, "NOT SUPPORTED", httpExchange); + throw new HttpServerException(405, "Method not supported."); } - final var request = objectMapper.readValue(httpExchange.getRequestBody(), JsonRpcRequest.class); + request = objectMapper.readValue(httpExchange.getRequestBody(), JsonRpcRequest.class); final Map params = objectMapper.treeToValue(request.getParams(), Map.class); - System.out.println("Command called " + request.getMethod()); + logger.debug("Command called " + request.getMethod()); - final var command = Commands.getCommand(request.getMethod()); - final var writer = new StringWriter(); final var ns = new JsonRpcNamespace(params); - if (command instanceof LocalCommand) { - final var manager = getManagerFromParams(request.getParams(), m); - ((LocalCommand) command).handleCommand(ns, manager, new JsonWriterImpl(writer)); - } else if (command instanceof MultiLocalCommand) { - ((MultiLocalCommand) command).handleCommand(ns, m, new JsonWriterImpl(writer)); - } else if (command instanceof RegistrationCommand) { - final var registrationManager = getRegistrationManagerFromParams(request.getParams(), m); - if (registrationManager != null) { - ((RegistrationCommand) command).handleCommand(ns, registrationManager); - } else { - throw new JsonRpcException(new JsonRpcResponse.Error(JsonRpcResponse.Error.INVALID_PARAMS, - "Method requires valid account parameter", - null)); - } - } - else { - sendResponse(404, "COMMAND NOT FOUND", httpExchange); - return; - } + final var responseBody = processRequest(m, ns, request); - // TODO if writer empty return some generic response - sendResponse(200, writer.toString(), httpExchange); + sendResponse(200, responseBody, httpExchange); } + catch (JsonRpcException aEx) { + logger.error("Failed to process request.", aEx); + sendResponse(500, JsonRpcResponse.forError(aEx.getError(), request.getId()), httpExchange); + } + catch (HttpServerException aEx) { + logger.error("Failed to process request.", aEx); + sendResponse(aEx.getHttpStatus(), JsonRpcResponse.forError( + new JsonRpcResponse.Error(JsonRpcResponse.Error.INVALID_REQUEST, + aEx.getMessage(), null), null), httpExchange); + } catch (Throwable aEx) { - aEx.printStackTrace(); - //TODO if this is a JSON RPC Error serialize and return the error - sendResponse(500, "ERROR", httpExchange); + logger.error("Failed to process request.", aEx); + sendResponse(500, JsonRpcResponse.forError( + new JsonRpcResponse.Error(JsonRpcResponse.Error.INTERNAL_ERROR, + "An internal server error has occured.", null), null), httpExchange); } }); @@ -101,7 +95,7 @@ public class HttpServerHandler { } } catch (InterruptedException ex) { } - System.out.println("Server shut down"); + logger.info("Server shut down"); } catch (Throwable ex) { ex.printStackTrace(); @@ -109,8 +103,50 @@ public class HttpServerHandler { } - private void sendResponse(int status, String body, HttpExchange httpExchange) throws IOException { - final var byteResponse = body.getBytes(StandardCharsets.UTF_8); + private JsonRpcResponse processRequest( + final MultiAccountManager m, + final JsonRpcNamespace ns, + final JsonRpcRequest request + ) throws JsonRpcException, CommandException, IOException { + + final var writer = new StringWriter(); + final var command = Commands.getCommand(request.getMethod()); + + if (command instanceof LocalCommand) { + final var manager = getManagerFromParams(request.getParams(), m); + ((LocalCommand) command).handleCommand(ns, manager, new JsonWriterImpl(writer)); + } else if (command instanceof MultiLocalCommand) { + ((MultiLocalCommand) command).handleCommand(ns, m, new JsonWriterImpl(writer)); + } else if (command instanceof RegistrationCommand) { + final var registrationManager = getRegistrationManagerFromParams(request.getParams(), m); + if (registrationManager != null) { + ((RegistrationCommand) command).handleCommand(ns, registrationManager); + } else { + throw new JsonRpcException(new JsonRpcResponse.Error(JsonRpcResponse.Error.INVALID_PARAMS, + "Method requires valid account parameter", + null)); + } + } + else { + throw new JsonRpcException(new JsonRpcResponse.Error(JsonRpcResponse.Error.METHOD_NOT_FOUND, + "The specified method is not supported.", + null)); + } + + final var rawJson = writer.toString(); + final JsonNode dataNode; + + if (rawJson.isEmpty()) { + dataNode = new POJONode(new HttpSimpleResponse("OK")); + } else { + dataNode = objectMapper.readTree(rawJson); + } + + return JsonRpcResponse.forSuccess(dataNode, request.getId()); + } + + private void sendResponse(int status, JsonRpcResponse response, HttpExchange httpExchange) throws IOException { + final var byteResponse = objectMapper.writeValueAsBytes(response); httpExchange.sendResponseHeaders(status, byteResponse.length); httpExchange.getResponseBody().write(byteResponse); httpExchange.getResponseBody().close(); diff --git a/src/main/java/org/asamk/signal/http/HttpSimpleResponse.java b/src/main/java/org/asamk/signal/http/HttpSimpleResponse.java new file mode 100644 index 00000000..d2b22057 --- /dev/null +++ b/src/main/java/org/asamk/signal/http/HttpSimpleResponse.java @@ -0,0 +1,3 @@ +package org.asamk.signal.http; + +public record HttpSimpleResponse(String status) {}