diff --git a/.gitignore b/.gitignore index e8240f8..a8ce7d4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,9 @@ /bin/ /.idea/ +/target/ +.DS_Store +/build/ +/out/ +/.vscode/ +/production/ +/openJDK-25/ \ No newline at end of file diff --git a/README.md b/README.md index b7a7ea0..3ff8e56 100644 --- a/README.md +++ b/README.md @@ -10,3 +10,5 @@ Java socket server. Currently Connection plugin done for handling WebSocket conn WebSocket server is able to receive, read and send handshake also read/send data from/to client. some code snippets refactored from stackoverflow and mozilla.org + +this stuff should work on Java 1.2 and higher diff --git a/config/server.properties b/config/server.properties index e1669ec..65d04e1 100644 --- a/config/server.properties +++ b/config/server.properties @@ -1,3 +1,12 @@ socketPort=6050 websocketPort=6051 -webPort=8080 \ No newline at end of file +webPort=8080 +socketEnabled=true +websocketEnabled=true +webEnabled=true +sslEnabled=false +maxConnections=100 +#not implemented yet, something like front controller pattern +serverAsDispatcher=false +#use this for dynamic port allocation +dynamicPortRange=6000-7000 \ No newline at end of file diff --git a/src/example/ExampleHttpListener.java b/src/example/ExampleHttpListener.java new file mode 100644 index 0000000..cdd42d0 --- /dev/null +++ b/src/example/ExampleHttpListener.java @@ -0,0 +1,62 @@ +package example; + +import server.core.listener.HttpMethodListener; +import server.module.WebModule; + +public class ExampleHttpListener extends WebModule { + public ExampleHttpListener() { + super(); + setRoot("/example"); + registerListener("GET /test", new HttpMethodListener() { + @Override + public void onBeforeReceive(Object message) { + System.out.println("Received request for /example: " + message); + } + + @Override + public void onAfterReceive(Object request) { + //do something with request here + ExampleHttpListener thisModule = (ExampleHttpListener) getContext(); + thisModule.test((String) request); //call it from context + //or call it directly + test("test request from /example"); + System.out.println("Finished processing request for /example: " + request); + } + + @Override + public void onBeforeBroadcast(Object message) { + //add your response here + setResponse("test response from /example"); + System.out.println("About to broadcast response for /example: " + message); + } + @Override + public void onAfterBroadcast(Object message) { + System.out.println("Finished broadcasting response for /example: " + message); + } + }); + } + + public void test(String test) { + //do something with data + setResponse("test response from /example"); + } + // public static void main(String[] args) { +// HttpMethodListener listener = new HttpMethodListener() { +// @Override +// public void onBeforeReceive(Object message) { +// System.out.println("Before receiving: " + message); +// } +// +// @Override +// public void onAfterReceive(Object message) { +// System.out.println("After receiving: " + message); +// } +// }; +// +// // Simulate receiving a message +// String simulatedMessage = "GET /index.html HTTP/1.1"; +// listener.onBeforeReceive(simulatedMessage); +// // Here would be the logic to process the message +// listener.onAfterReceive(simulatedMessage); +// } +} diff --git a/src/server/config/ConfigConstant.java b/src/server/config/ConfigConstant.java new file mode 100644 index 0000000..79c85bd --- /dev/null +++ b/src/server/config/ConfigConstant.java @@ -0,0 +1,14 @@ +package server.config; + +public class ConfigConstant { + public static final String ENABLED_PROTOCOLS = "enabledProtocols"; + public static final String WEB_ENABLED = "webEnabled"; + public static final String WEBSOCKET_ENABLED = "websocketEnabled"; + public static final String SOCKET_ENABLED = "socketEnabled"; + public static final String WEB_PORT = "webPort"; + public static final String WEBSOCKET_PORT = "websocketPort"; + public static final String SOCKET_PORT = "socketPort"; + public static final String VALUE_WEB = "web"; + public static final String VALUE_WEBSOCKET = "websocket"; + public static final String VALUE_SOCKET = "socket"; +} diff --git a/src/server/config/ServerProperties.java b/src/server/config/ServerProperties.java new file mode 100644 index 0000000..b2fd1ce --- /dev/null +++ b/src/server/config/ServerProperties.java @@ -0,0 +1,27 @@ +package server.config; +import java.util.*; + +public class ServerProperties extends Properties { + private static final long serialVersionUID = 1L; + +// private static ResourceBundle bundle = ResourceBundle.getBundle("config"); +// +// public static ResourceBundle getBundle(String baseName) { +// return bundle; +// } + + @Override + public synchronized Object put(Object key, Object value) { + return super.put(key, value); + } + + @Override + public synchronized Object get(Object key) { + return super.get(key); + } + + public static int getConfigValueAsInt(String val) { + return ServerPropertiesValue.getConfigValueAsInt(val); + } + +} \ No newline at end of file diff --git a/src/server/config/ServerPropertiesValue.java b/src/server/config/ServerPropertiesValue.java new file mode 100644 index 0000000..78aa6ae --- /dev/null +++ b/src/server/config/ServerPropertiesValue.java @@ -0,0 +1,31 @@ +package server.config; + +import server.core.Server; + +public final class ServerPropertiesValue { + private static String getConfigValue(String val) { + ServerProperties cfg = Server.getServerProperties(); + if (cfg == null) return ""; + String v = (String)cfg.get(val); + return v == null ? "" : v; + } + + @SuppressWarnings("unused") + public static int getConfigValueAsInt(String val) { + String v = getConfigValue(val); + try { + return Integer.parseInt(v); + } catch (NumberFormatException e) { +// Server.LOGGER.warning("NumberFormatException: " + e.getMessage()); + return -1; + } + } + public static String getConfigValueAsString(String val) { + return getConfigValue(val); + } + + public static boolean getConfigValueAsBoolean(String val) { + String v = getConfigValue(val); + return v.equalsIgnoreCase("true") || v.equalsIgnoreCase("yes") || v.equals("1"); + } +} diff --git a/src/server/config/ServerXmlConfigValue.java b/src/server/config/ServerXmlConfigValue.java new file mode 100644 index 0000000..3da61ce --- /dev/null +++ b/src/server/config/ServerXmlConfigValue.java @@ -0,0 +1,24 @@ +package server.config; + +import server.core.Server; + +@Deprecated +public class ServerXmlConfigValue { +// private static String getConfigValue(String val) { +// XmlServerConfig cfg = Server.getXmlConfig(); +// if (cfg == null) return ""; +// String v = cfg.get(val); +// return v == null ? "" : v; +// } +// +// @SuppressWarnings("unused") +// public static int getConfigValueAsInt(String val) { +// String v = getConfigValue(val); +// try { +// return Integer.parseInt(v); +// } catch (NumberFormatException e) { +//// Server.LOGGER.warning("NumberFormatException: " + e.getMessage()); +// return -1; +// } +// } +} diff --git a/src/server/config/ServerConfig.java b/src/server/config/XmlServerConfig.java similarity index 96% rename from src/server/config/ServerConfig.java rename to src/server/config/XmlServerConfig.java index b9e74ed..db9af6d 100644 --- a/src/server/config/ServerConfig.java +++ b/src/server/config/XmlServerConfig.java @@ -21,11 +21,11 @@ import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; - -public class ServerConfig { +@Deprecated +public class XmlServerConfig { private final Map parameters = new HashMap(); - public ServerConfig(String file) { + public XmlServerConfig(String file) { read(file); } diff --git a/src/server/core/Connection.java b/src/server/core/Connection.java index 360a32d..6a890de 100644 --- a/src/server/core/Connection.java +++ b/src/server/core/Connection.java @@ -7,8 +7,9 @@ */ public interface Connection { void start(); - void stop(); + void stop() throws IOException; void receive() throws IOException; void broadcast() throws IOException; void broadcast(String data) throws IOException; + int getPort(); } diff --git a/src/server/core/ConnectionAbstract.java b/src/server/core/ConnectionAbstract.java index 9f621e5..0fbb957 100644 --- a/src/server/core/ConnectionAbstract.java +++ b/src/server/core/ConnectionAbstract.java @@ -10,9 +10,9 @@ * @todo add factory */ public abstract class ConnectionAbstract implements Runnable, Connection { - protected static int counter = 0; + protected static int counter; protected final Thread thread; -// protected ObjectOutputStream out; + // protected ObjectOutputStream out; // protected ObjectInputStream in; protected OutputStream outputStream; protected InputStream inputStream; @@ -24,16 +24,32 @@ public abstract class ConnectionAbstract implements Runnable, Connection { protected boolean close = false; protected int instanceNo; protected boolean stop = false; - protected java.net.Socket client; + protected Socket client; protected ServerSocket serverSocket; + protected int port; protected ConnectionAbstract(ServerSocket serverSocket) { + setCounter(0); this.serverSocket = serverSocket; - this.instanceNo = counter++; + this.port = serverSocket.getLocalPort(); + this.instanceNo = getCounter(); + incrementCounter(); + this.thread = new Thread(this, MODULE_NAME + instanceNo); + + } + + //for child classes + protected ConnectionAbstract() { + this.instanceNo = getCounter(); + incrementCounter(); this.thread = new Thread(this, MODULE_NAME + instanceNo); } + public int getPort() { + return port; + } + public static int getCounter() { return counter; } @@ -69,15 +85,16 @@ public void start() { } @Override - public void stop() { + public void stop() throws IOException { // getThread().stop(); getThread().interrupt(); stop = true; decrementCounter(); instanceNo = -1; + client.close(); } - public void processStream(Socket client) { + public void processStreamBinary(Socket client) { try { setClient(client); outputStream = new ObjectOutputStream(getClient().getOutputStream()); @@ -101,4 +118,51 @@ public void processStream() { // throw new RuntimeException(e); } } + + public void processStream(Socket client) { + try { + setClient(client); + outputStream = getClient().getOutputStream(); + inputStream = getClient().getInputStream(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void processStreamBinary() { + try { + processStreamBinary(serverSocket.accept()); + } catch (IOException e) { + e.printStackTrace(); +// throw new RuntimeException(e); + } + } + + protected void flushOutputStream() throws IOException { + outputStream.flush(); + } + + protected void closeInputStream() throws IOException { + inputStream.close(); + } + + protected void closeOutputStream() throws IOException { + outputStream.close(); + } + + public String getResponseAsString() { + return response; + } + + public void setResponse(String response) { + this.response = response; + } + + public String getRequestAsString() { + return request; + } + + public void setRequest(String request) { + this.request = request; + } } diff --git a/src/server/core/HttpMethod.java b/src/server/core/HttpMethod.java new file mode 100644 index 0000000..8253085 --- /dev/null +++ b/src/server/core/HttpMethod.java @@ -0,0 +1,55 @@ +package server.core; + +public enum HttpMethod { + GET, + POST, + PUT, + DELETE, + HEAD, + OPTIONS, + PATCH; + + public static HttpMethod fromString(String method) { + for (HttpMethod httpMethod : HttpMethod.values()) { + if (httpMethod.name().equalsIgnoreCase(method)) { + return httpMethod; + } + } + throw new IllegalArgumentException("Unknown HTTP method: " + method); + } + + public static boolean isGet(String request) { + int indexOf = request.indexOf(GET.toString()); + return indexOf != -1; + } + + public static boolean isPost(String request) { + int indexOf = request.indexOf(POST.toString()); + return indexOf != -1; + } + + public static boolean isPut(String request) { + int indexOf = request.indexOf(PUT.toString()); + return indexOf != -1; + } + + public static boolean isDelete(String request) { + int indexOf = request.indexOf(DELETE.toString()); + return indexOf != -1; + } + + public static boolean isHead(String request) { + int indexOf = request.indexOf(HEAD.toString()); + return indexOf != -1; + } + + public static boolean isOptions(String request) { + int indexOf = request.indexOf(OPTIONS.toString()); + return indexOf != -1; + } + + public static boolean isPatch(String request) { + int indexOf = request.indexOf(PATCH.toString()); + return indexOf != -1; + } +} diff --git a/src/server/core/Listener.java b/src/server/core/Listener.java new file mode 100644 index 0000000..3f5f9d9 --- /dev/null +++ b/src/server/core/Listener.java @@ -0,0 +1,8 @@ +package server.core; + +public interface Listener { + void onBeforeReceive(Object message); + void onBeforeBroadcast(Object message); + void onAfterReceive(Object message); + void onAfterBroadcast(Object message); +} diff --git a/src/server/core/Responsive.java b/src/server/core/Responsive.java deleted file mode 100644 index 0135bc8..0000000 --- a/src/server/core/Responsive.java +++ /dev/null @@ -1,6 +0,0 @@ -package server.core; - -public interface Responsive { - String onBroadcast(); - String onReceive(); -} diff --git a/src/server/core/Server.java b/src/server/core/Server.java index 3a6af0b..ef2f80d 100644 --- a/src/server/core/Server.java +++ b/src/server/core/Server.java @@ -1,6 +1,7 @@ package server.core; -import server.config.ServerConfig; +import server.config.*; +import server.module.*; import server.utils.FileUtils; import java.io.IOException; @@ -9,133 +10,178 @@ import java.net.UnknownHostException; import java.util.List; import java.util.ArrayList; -import java.util.Collections; -import java.util.Properties; - -import server.module.*; /** * @author andrzej.salamon@gmail.com */ public final class Server extends Thread { - private static final String FILE_CONFIG_SERVER_XML = "server.xml"; - private static final String FILE_CONFIG_SERVER_PROPS = "server.properties"; - private static final String FILE_CONFIG_SERVER_YML = "server.yml"; - public static final String DIR_CONFIG = "config"; + private static final String ERR_PORT_PREFIX = "failed listening on port: "; + private static final String FILE_CONFIG_SERVER_PROPS = "server.properties"; + private static final String DIR_CONFIG = "config"; + private static final String FORMAT_PARAM_LOGGER = "{0} -> {1}"; + public static final String IP = getIp(); - private Properties properties; + public enum MODULES { + SOCKET, + WEBSOCKET, + WEB + } + private static ServerProperties serverProperties; private ServerSocket serverSocket = null; private ServerSocket serverWebSocket = null; private ServerSocket serverWeb = null; - public static final String IP = getIp(); - - private static ServerConfig config; // thread-safe list wrapper - private final List connections = Collections.synchronizedList(new ArrayList()); + private final List connections = new ArrayList(); // flags used across threads - private volatile boolean running; - private volatile boolean stop = false; - private volatile boolean exitOnFail = true; - private volatile boolean startFailed = false; + private static boolean running = false; + private static boolean stop = false; + private static boolean exitOnFail = true; + private static boolean startFailed = false; public Server() { - - Server.setConfig(new ServerConfig(DIR_CONFIG + FileUtils.FILE_SEPARATOR + FILE_CONFIG_SERVER_XML)); - + // we are not using nio because we want to have it synchronous try { - setServerSocket(new ServerSocket(getSocketPort())); + setServerProperties( + FileUtils.loadServerProperties(DIR_CONFIG + FileUtils.FILE_SEPARATOR + FILE_CONFIG_SERVER_PROPS)); } catch (IOException e) { - System.err.println("failed listening on port: " + getSocketPort() + " -> " + e.getMessage()); + e.printStackTrace(); +// LOGGER.log(Level.SEVERE, "No configuration found: {0}", e.getMessage()); + System.err.println("No configuration found"); startFailed = true; + System.exit(-1); } - try { - setServerWebSocket(new ServerSocket(getWebsocketPort())); - } catch (IOException e) { - System.err.println("failed listening on port: " + getWebsocketPort() + " -> " + e.getMessage()); + if (getSocketPort() < 1 || getWebsocketPort() < 1 || getWebPort() < 1) { +// LOGGER.severe("Invalid port configuration. Ports must be greater than 0."); + System.err.println("Invalid port number"); startFailed = true; + System.exit(-1); } - try { - setServerWeb(new ServerSocket(getWebPort())); - } catch (IOException e) { - System.err.println("failed listening on port: " + getWebPort() + " -> " + e.getMessage()); - startFailed = true; - } + configureModule(ConfigConstant.SOCKET_ENABLED, getSocketPort(), MODULES.SOCKET); + configureModule(ConfigConstant.WEBSOCKET_ENABLED, getWebsocketPort(), MODULES.WEBSOCKET); + configureModule(ConfigConstant.WEB_ENABLED, getWebPort(), MODULES.WEB); if (startFailed && exitOnFail) { - System.exit(1); + System.exit(-1); } - addDefaultModules(); - this.start(); } - private static int getWebPort() { - return getConfigValueAsInt("webPort"); + public boolean isExitOnFail() { + return exitOnFail; } - private static int getWebsocketPort() { - return getConfigValueAsInt("websocketPort"); + public void setExitOnFail(boolean exitOnFail) { + Server.exitOnFail = exitOnFail; } - private static int getSocketPort() { - return getConfigValueAsInt("socketPort"); + private void configureModule(String socketEnabled, int socketPort, MODULES module) { + if (isEnabled(socketEnabled)) + try { + setupModule(module, socketPort); + } catch (IOException e) { +// LOGGER.log(Level.SEVERE, ERR_PORT_PREFIX + FORMAT_PARAM_LOGGER, +// new Object[] { socketPort, e.getMessage() }); + System.err.println("Failed to configure module: " + e.getMessage()); + startFailed = true; + } } - private static String getConfigValue(String val) { - ServerConfig cfg = getConfig(); - if (cfg == null) return ""; - String v = cfg.get(val); - return v == null ? "" : v; + private static boolean isEnabled(String socketEnabled) { + return ServerPropertiesValue.getConfigValueAsBoolean(socketEnabled); } - private static int getConfigValueAsInt(String val) { - String v = getConfigValue(val); - try { - return Integer.parseInt(v); - } catch (NumberFormatException e) { - return 0; + private void setupModule(MODULES module, int socketPort) throws IOException { + switch (module) { + case SOCKET: + setServerSocket(createSocket(socketPort)); + addSocketModule(); + break; + case WEBSOCKET: + setServerWebSocket(createSocket(socketPort)); + addWebsocketModule(); + break; + case WEB: + setServerWeb(createSocket(socketPort)); + addWebModule(); + break; + default: + System.err.println("Unknown module: " + module); + break; } } + private void addModule() { + } + + private static ServerSocket createSocket(int port) throws IOException { + return new ServerSocket(port); + } + + private static int getWebPort() { + return ServerPropertiesValue.getConfigValueAsInt(ConfigConstant.WEB_PORT); + } + + private static int getWebsocketPort() { + return ServerPropertiesValue.getConfigValueAsInt(ConfigConstant.WEBSOCKET_PORT); + } + + private static int getSocketPort() { + return ServerPropertiesValue.getConfigValueAsInt(ConfigConstant.SOCKET_PORT); + } + private void setServerWeb(ServerSocket serverWebPort) { this.serverWeb = serverWebPort; } - private void addDefaultModules() { - // add modules; connections list is thread-safe - addModule(new WebSocketModule(getServerWebSocket())); // sharing sockets intentionally - addModule(new SocketModule(getServerSocket())); + private void addWebModule() { addModule(new WebModule(getServerWeb())); } + private void addSocketModule() { + addModule(new SocketModule(getServerSocket())); + } + + private void addWebsocketModule() { + addModule(new WebSocketModule(getServerWebSocket())); + } + private ServerSocket getServerWeb() { return serverWeb; } public void addModule(SocketConnection socketConnection) { - if (socketConnection == null) return; - if (!connections.contains(socketConnection)) { - connections.add(socketConnection); + if (socketConnection == null) { + return; + } + synchronized (connections) { + if (!connections.contains(socketConnection)) { + connections.add(socketConnection); + } } } private void startModules() { synchronized (connections) { - if (connections.isEmpty()) return; + if (connections.isEmpty()) { + return; + } for (SocketConnection conn : connections) { try { + System.out.println("Starting module: " + conn.getClass().getSimpleName() + " at port " + conn.getPort()); conn.start(); } catch (IllegalThreadStateException e) { // already started or cannot start; log and continue +// LOGGER.log(Level.SEVERE, "module start failed: {0}", e.getMessage()); System.err.println("module start failed: " + e.getMessage()); } catch (Exception e) { +// LOGGER.log(Level.SEVERE, "unexpected module start error: {0}", e.getMessage()); System.err.println("unexpected module start error: " + e.getMessage()); } } @@ -144,28 +190,33 @@ private void startModules() { private void stopModules() { synchronized (connections) { - if (connections.isEmpty()) return; + if (connections.isEmpty()) { + return; + } for (SocketConnection conn : connections) { try { conn.stop(); } catch (Exception e) { - System.err.println("module stop failed: " + e.getMessage()); +// LOGGER.log(Level.SEVERE, "module stop failed: {0}", e.getMessage()); + System.err.println("unexpected module stop error: " + e.getMessage()); } } } } - public static ServerConfig getConfig() { - return config; + public static ServerProperties getServerProperties() { + return serverProperties; } - public static void setConfig(ServerConfig config) { - Server.config = config; + public static void setServerProperties(ServerProperties config) { + Server.serverProperties = config; } @Override + @SuppressWarnings("unused") public void run() { System.out.println("Andrew (Web)Socket(s) Server v. 1.1"); + System.out.println("Started at IP: " + IP); startModules(); running = true; @@ -173,6 +224,7 @@ public void run() { try { Thread.sleep(1000); // sleep server for a while } catch (InterruptedException e) { + System.err.println("Server sleep interrupted: " + e.getMessage()); // restore interrupted status and exit loop Thread.currentThread().interrupt(); running = false; @@ -183,9 +235,9 @@ public void run() { return; } } - // ensure cleanup in case loop exits - stopModules(); - stopServer(); +// // ensure cleanup in case loop exits +// stopModules(); +// stopServer(); } private static String getIp() { @@ -193,6 +245,7 @@ private static String getIp() { InetAddress addr = InetAddress.getLocalHost(); return addr.getHostAddress(); } catch (UnknownHostException e) { + System.err.println("Unable to get IP address UnknownHostException: " + e.getMessage()); return ""; } } @@ -219,16 +272,17 @@ public ServerSocket getServerWebSocket() { public void stopServer() { running = false; // close server sockets quietly - closeQuietly(serverSocket); - closeQuietly(serverWebSocket); - closeQuietly(serverWeb); + closeServerSocket(serverSocket); + closeServerSocket(serverWebSocket); + closeServerSocket(serverWeb); } - private void closeQuietly(ServerSocket s) { + private void closeServerSocket(ServerSocket s) { if (s == null) return; try { s.close(); } catch (IOException e) { + System.err.println("Unable to close server socket: " + e.getMessage()); // swallow - best effort close } } @@ -240,10 +294,6 @@ public void requestStop() { public static void main(String[] args) { Server server = new Server(); - // optionally handle start failure - if (server.startFailed && server.exitOnFail) { - System.exit(1); - } } } diff --git a/src/server/core/SocketConnection.java b/src/server/core/SocketConnection.java index 03b9bfe..ef4f5e9 100644 --- a/src/server/core/SocketConnection.java +++ b/src/server/core/SocketConnection.java @@ -9,5 +9,6 @@ public interface SocketConnection extends Connection { String getId(); void processStream(); + void processStreamBinary(); void processStream(Socket client); } diff --git a/src/server/core/connection/WebConnectionAbstract.java b/src/server/core/connection/WebConnectionAbstract.java index 0d1279a..ef9a292 100644 --- a/src/server/core/connection/WebConnectionAbstract.java +++ b/src/server/core/connection/WebConnectionAbstract.java @@ -7,8 +7,11 @@ public abstract class WebConnectionAbstract extends ConnectionAbstract implements SocketConnection { - public WebConnectionAbstract(ServerSocket serverSocket) { + protected WebConnectionAbstract(ServerSocket serverSocket) { super(serverSocket); } + public WebConnectionAbstract() { + super(); + } } diff --git a/src/server/core/connection/WebSocketConnectionAbstract.java b/src/server/core/connection/WebSocketConnectionAbstract.java index 23cda66..02442ac 100644 --- a/src/server/core/connection/WebSocketConnectionAbstract.java +++ b/src/server/core/connection/WebSocketConnectionAbstract.java @@ -2,6 +2,7 @@ //import server.core; import server.core.ConnectionAbstract; +import server.core.HttpMethod; import server.core.WebSocketConnection; import javax.xml.bind.DatatypeConverter; @@ -16,7 +17,9 @@ public abstract class WebSocketConnectionAbstract extends ConnectionAbstract implements WebSocketConnection { - protected String secWebSocketKey; + protected static final String UUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + protected String secWebSocketKey; //@todo cache it with timeout + private byte[] responseBuffer; public WebSocketConnectionAbstract(ServerSocket serverSocket) { super(serverSocket); @@ -37,7 +40,7 @@ public void sendHandshake() throws NoSuchAlgorithmException, IOException { .printBase64Binary( MessageDigest .getInstance("SHA-1") - .digest((secWebSocketKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11") + .digest((secWebSocketKey + UUID) .getBytes(StandardCharsets.UTF_8))) + "\r\n\r\n") .getBytes(StandardCharsets.UTF_8); @@ -56,23 +59,22 @@ private Matcher setSecWebSocketKey() { } public boolean isGet() { - Matcher get = Pattern.compile("^GET").matcher(request); - return get.find(); + return HttpMethod.isGet(request); } public void receive() throws IOException { - byte[] buffer = new byte[server.core.WebSocketConnection.MAX_BUFFER]; + responseBuffer = new byte[WebSocketConnection.MAX_BUFFER]; int messageLength, mask, dataStart; - messageLength = inputStream.read(buffer); + messageLength = inputStream.read(responseBuffer); if (messageLength == -1) { return; } - requestByte = new byte[messageLength]; + responseByte = new byte[messageLength]; //b[0] is always text in my case so no need to check; - byte data = buffer[1]; //does it cause a problem ? + byte data = responseBuffer[1]; //does it cause a problem ? byte op = (byte) 127; byte length; @@ -86,17 +88,17 @@ public void receive() throws IOException { int j = 0, i=mask; for (; i < (mask + 4); i++) { //start at mask, stop at last + 4 - masks[j] = buffer[i]; //problem here + masks[j] = responseBuffer[i]; //problem here j++; } dataStart = mask + 4; for (i = dataStart, j = 0; i < messageLength; i++, j++) { - requestByte[j] = (byte) (buffer[i] ^ masks[j % 4]); + responseByte[j] = (byte) (responseBuffer[i] ^ masks[j % 4]); } - response = new String(requestByte); //why now string copy of byte ? + response = new String(responseByte); //why now string copy of byte ? } public void broadcast(String data) throws IOException { @@ -132,18 +134,18 @@ public void broadcast(String data) throws IOException { int responseLength = frameCount + rawData.length; int responseLimit = 0; - responseByte = new byte[responseLength]; + requestByte = new byte[responseLength]; for (; responseLimit < frameCount; responseLimit++) { - responseByte[responseLimit] = frame[responseLimit]; + requestByte[responseLimit] = frame[responseLimit]; } for (byte dataByte : rawData) { - responseByte[responseLimit++] = dataByte; + requestByte[responseLimit++] = dataByte; } - outputStream.write(responseByte); - outputStream.flush(); + outputStream.write(requestByte); + flushOutputStream(); } diff --git a/src/server/core/listener/HttpMethodListener.java b/src/server/core/listener/HttpMethodListener.java new file mode 100644 index 0000000..e470264 --- /dev/null +++ b/src/server/core/listener/HttpMethodListener.java @@ -0,0 +1,37 @@ +package server.core.listener; + +import server.core.Listener; + +public class HttpMethodListener implements Listener { + private Object context; + public HttpMethodListener() { + } + + @Override + public void onBeforeReceive(Object message) { + + } + + @Override + public void onBeforeBroadcast(Object message) { + + } + + @Override + public void onAfterReceive(Object message) { + + } + + @Override + public void onAfterBroadcast(Object message) { + + } + + public Object getContext() { + return context; + } + + public void setContext(Object context) { + this.context = context; + } +} diff --git a/src/server/module/SocketModule.java b/src/server/module/SocketModule.java index c6fe039..a69e76c 100644 --- a/src/server/module/SocketModule.java +++ b/src/server/module/SocketModule.java @@ -4,13 +4,15 @@ import java.net.ServerSocket; +import server.core.Server; import server.core.connection.SocketConnectionAbstract; /** * @author andrzej.salamon@gmail.com */ -public final class SocketModule extends SocketConnectionAbstract { - public final static String MODULE_NAME = "socket"; +public class SocketModule extends SocketConnectionAbstract { + public static final String MODULE_NAME = "socketModule"; + public static final Server.MODULES MODULE_TYPE = Server.MODULES.SOCKET; public SocketModule(ServerSocket serverSocket) { super(serverSocket); @@ -27,35 +29,35 @@ public void run() { processStream(); receive(); broadcast(); - outputStream.flush(); -// out.close(); -// in.close(); -// getClient().close(); -// try { -//// out.close(); -//// in.close(); -// client.close(); -// } catch (IOException e) { -// // e.printStackTrace(); -// } + flushOutputStream(); + // out.close(); + // in.close(); + // getClient().close(); + // try { + //// out.close(); + //// in.close(); + // client.close(); + // } catch (IOException e) { + // // e.printStackTrace(); + // } } catch (Exception e) { } } } -// @Override -// public void start() { -// -// } -// -// @Override -// public void stop() { -// -// } + // @Override + // public void start() { + // + // } + // + // @Override + // public void stop() { + // + // } @Override public void receive() throws IOException { -// request = (SerializedSocketObject)in.readObject(); + // request = (SerializedSocketObject)in.readObject(); } @Override @@ -64,7 +66,7 @@ public void broadcast() throws IOException { @Override public void broadcast(String data) throws IOException { -// response = process(request); -// out.writeObject(response); + // response = process(request); + // out.writeObject(response); } } diff --git a/src/server/module/WebModule.java b/src/server/module/WebModule.java index ebc6f0c..89790c7 100644 --- a/src/server/module/WebModule.java +++ b/src/server/module/WebModule.java @@ -1,24 +1,41 @@ package server.module; -import server.core.connection.SocketConnectionAbstract; +import server.core.HttpMethod; +import server.core.Server; import server.core.connection.WebConnectionAbstract; +import server.core.listener.HttpMethodListener; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; +import java.util.HashMap; +import java.util.Map; /** * @author andrzej.salamon@gmail.com */ -public final class WebModule extends WebConnectionAbstract { +public class WebModule extends WebConnectionAbstract { public static final String MODULE_NAME = "webModuleSocket"; + public static final Server.MODULES MODULE_TYPE = Server.MODULES.WEB; private PrintWriter printWriter; + private Map listeners = new HashMap(); + private String signature; + private String root; + private String method; + private String uri; + private String uriWithMethod; public WebModule(ServerSocket serverSocket) { super(serverSocket); } + public WebModule() { + super(); + } + @Override public String getId() { return MODULE_NAME; @@ -29,36 +46,128 @@ public void run() { try { processStream(); receive(); + setRequest("

Default Response

"); broadcast(); - outputStream.flush(); -// out.close(); -// in.close(); -// getClient().close(); -// try { -//// out.close(); -//// in.close(); -// client.close(); -// } catch (IOException e) { -// // e.printStackTrace(); -// } + flushOutputStream(); + closeOutpInputStreams(); + getClient().close(); } catch (Exception e) { e.printStackTrace(); } } } + private void closeOutpInputStreams() throws IOException { + outputStream.close(); + inputStream.close(); + } + @Override public void receive() throws IOException { + callBeforeReceive(uriWithMethod); + StringBuilder requestBuilder = new StringBuilder(); + BufferedReader in = new BufferedReader(new InputStreamReader(inputStream)); + String line = in.readLine(); + signature = line; + requestBuilder.append(line); + while ((line = in.readLine()) != null) { + requestBuilder.append(line).append("\r\n"); + } + parseSignature(); + request = requestBuilder.toString(); + callAfterReceive(uriWithMethod); + System.out.println("Received request:\n" + request); + } + + private void callBeforeReceive(String uriWithMethod) { + checkListener(uriWithMethod); + HttpMethodListener callback = listeners.get(uri); + callback.setContext(this); + callback.onBeforeReceive(request); + callback.setContext(null); //lets clear context after use + } + + private void checkListener(String uriWithMethod) { + if (!listeners.containsKey(uriWithMethod)) { + throw new RuntimeException("No listener for uriWithMethod: " + uriWithMethod); + } + } + private void callAfterReceive(String uriWithMethod) { + checkListener(uriWithMethod); + HttpMethodListener callback = listeners.get(uri); + callback.setContext(this); //current module that extends WebModule + callback.onAfterReceive(request); + callback.setContext(null); //lets clear context after use } @Override - public void broadcast() throws IOException { + public void broadcast() { + printWriter = new PrintWriter(outputStream, true); + printWriter.println("HTTP/1.1 200 OK"); + printWriter.println("Content-Type: text/html"); + printWriter.println(); + printWriter.println(getResponseAsString()); + flushPrintWriter(); + } + private void flushPrintWriter() { + printWriter.flush(); } @Override public void broadcast(String data) throws IOException { + callBeforeBrodcast(uriWithMethod, data); + printWriter = new PrintWriter(outputStream, true); + printWriter.println("HTTP/1.1 200 OK"); + printWriter.println("Content-Type: text/html"); + printWriter.println(); + printWriter.println(data); + flushPrintWriter(); + callAfterBrodcast(uriWithMethod, data); + } + + private void callAfterBrodcast(String uriWithMethod, String data) { + checkListener(uriWithMethod); + HttpMethodListener callback = listeners.get(uri); + callback.setContext(this); //current module that extends WebModule + callback.onAfterBroadcast(data); + callback.setContext(null); //lets clear context after use + } + + private void callBeforeBrodcast(String uriWithMethod, String data) { + checkListener(uriWithMethod); + HttpMethodListener callback = listeners.get(uri); + callback.setContext(this); //current module that extends WebModule + callback.onBeforeBroadcast(data); + callback.setContext(null); //lets clear context after use + } + + public void parseSignature() { + String[] parts = signature.split(" "); + if (parts.length >= 2) { + this.method = parts[0]; + this.uri = parts[1]; + this.uriWithMethod = method + " " + getRoot() + uri; + } + } + + public void registerListener(String uri, HttpMethodListener callback) { + if (listeners.containsKey(uri)) { + throw new IllegalArgumentException("Listener for URI already registered: " + uri); + } + listeners.put(uri, callback); + } + + public void registerListener(HttpMethod method, String uri, HttpMethodListener callback) { + registerListener(method.toString() + " " + uri, callback); + } + + public String getRoot() { + return root; + } + public void setRoot(String root) { + this.root = root; } } diff --git a/src/server/module/WebSocketModule.java b/src/server/module/WebSocketModule.java index 91196bf..65fa19e 100644 --- a/src/server/module/WebSocketModule.java +++ b/src/server/module/WebSocketModule.java @@ -1,5 +1,6 @@ package server.module; +import server.core.Server; import server.core.connection.WebSocketConnectionAbstract; import java.io.IOException; @@ -9,8 +10,9 @@ /** * @author andrzej.salamon@gmail.com */ -public final class WebSocketModule extends WebSocketConnectionAbstract { //double inheritance, not ellegant, ref,mv - public static final String MODULE_NAME = "websocket"; +public class WebSocketModule extends WebSocketConnectionAbstract { //double inheritance, not ellegant, ref,mv + public static final String MODULE_NAME = "websocketModule"; + public static final Server.MODULES MODULE_TYPE = Server.MODULES.WEBSOCKET; public WebSocketModule(ServerSocket serverSocket) { @@ -24,7 +26,7 @@ public String getId() { public void run() { while (!stop) { - processStream(); + processStreamBinary(); request = getRequestAsString(); @@ -53,19 +55,19 @@ public void run() { e.printStackTrace(); } try { - outputStream.flush(); + flushOutputStream(); } catch (IOException e) { System.err.println("cant flush"); e.printStackTrace(); } try { - outputStream.close(); + closeOutputStream(); } catch (IOException e) { System.err.println("cant close output stream"); e.printStackTrace(); } try { - inputStream.close(); + closeInputStream(); } catch (IOException e) { System.err.println("cant inputSTream close"); e.printStackTrace(); diff --git a/src/server/utils/FileUtils.java b/src/server/utils/FileUtils.java index b045741..de828e2 100644 --- a/src/server/utils/FileUtils.java +++ b/src/server/utils/FileUtils.java @@ -1,11 +1,9 @@ package server.utils; -import java.io.File; +import java.io.IOException; +import java.io.InputStream; -import java.text.ParseException; -import java.text.SimpleDateFormat; - -import java.util.Date; +import server.config.ServerProperties; /** * @author andrzej.salamon@gmail.com @@ -13,9 +11,46 @@ */ public class FileUtils { + /** + * Singleton instance of {@link FileUtils}. The instance is created eagerly and is + * therefore thread‑safe without requiring additional synchronization. + */ + private static final FileUtils INSTANCE = new FileUtils(); + + /** + * Private constructor to prevent external instantiation. The class provides a + * singleton instance via {@link #getInstance()}. + */ + private FileUtils() { + // Prevent instantiation + } + + /** + * Returns the singleton instance of {@link FileUtils}. + * + * @return the singleton {@code FileUtils} instance + */ + public static FileUtils getInstance() { + return INSTANCE; + } + public static final String FILE_SEPARATOR = getFileseparator(); public static String getFileseparator() { return System.getProperty("file.separator"); } + + public static ServerProperties loadServerProperties(String fileName) throws IOException { + // First try to load from the file system using the supplied path. + ServerProperties properties = new ServerProperties(); + ClassLoader loader = FileUtils.class.getClassLoader(); + + InputStream stream = loader.getResourceAsStream(fileName); + if (stream == null) { + // If the resource is not found, mimic the previous behavior by throwing NoSuchFileException + throw new IOException(fileName); + } + properties.load(stream); + return properties; + } }