diff --git a/server-session/README.md b/server-session/README.md index ae38fb948a9b..38ff1c298285 100644 --- a/server-session/README.md +++ b/server-session/README.md @@ -1,7 +1,11 @@ --- title: Server Session -category: Architectural +category: Behavioral language: en +tag: + - Session Management + - Session Tracking + - Cookies --- ## Also known as @@ -52,7 +56,7 @@ public class LoginHandler implements HttpHandler { } @Override - public void handle(HttpExchange exchange) throws IOException { + public void handle(HttpExchange exchange) { // Generate session ID String sessionID = UUID.randomUUID().toString(); @@ -60,6 +64,7 @@ public class LoginHandler implements HttpHandler { int newUser = sessions.size() + 1; sessions.put(sessionID, newUser); sessionCreationTimes.put(sessionID, Instant.now()); + LOGGER.info("User " + newUser + " created at time " + sessionCreationTimes.get(sessionID)); // Set session ID as cookie exchange.getResponseHeaders().add("Set-Cookie", "sessionID=" + sessionID); @@ -67,10 +72,16 @@ public class LoginHandler implements HttpHandler { // Send response String response = "Login successful!\n" + "Session ID: " + sessionID; - exchange.sendResponseHeaders(200, response.length()); - OutputStream os = exchange.getResponseBody(); - os.write(response.getBytes()); - os.close(); + try { + exchange.sendResponseHeaders(200, response.length()); + } catch (IOException e) { + LOGGER.error("An error occurred: ", e); + } + try(OutputStream os = exchange.getResponseBody()) { + os.write(response.getBytes()); + } catch(IOException e) { + LOGGER.error("An error occurred: ", e); + } } } ``` @@ -96,7 +107,7 @@ public class LogoutHandler implements HttpHandler { } @Override - public void handle(HttpExchange exchange) throws IOException { + public void handle(HttpExchange exchange) { // Get session ID from cookie String sessionID = exchange.getRequestHeaders().getFirst("Cookie").replace("sessionID=", ""); String currentSessionID = sessions.get(sessionID) == null ? null : sessionID; @@ -112,12 +123,24 @@ public class LogoutHandler implements HttpHandler { } //Remove session + if(currentSessionID != null) + LOGGER.info("User " + sessions.get(currentSessionID) + " deleted!"); + else + LOGGER.info("User already deleted!"); sessions.remove(sessionID); sessionCreationTimes.remove(sessionID); - exchange.sendResponseHeaders(200, response.length()); - OutputStream os = exchange.getResponseBody(); - os.write(response.getBytes()); - os.close(); + + try { + exchange.sendResponseHeaders(200, response.length()); + } catch(IOException e) { + LOGGER.error("An error has occurred: ", e); + } + + try(OutputStream os = exchange.getResponseBody()) { + os.write(response.getBytes()); + } catch(IOException e) { + LOGGER.error("An error has occurred: ", e); + } } } ``` @@ -125,11 +148,11 @@ public class LogoutHandler implements HttpHandler { Sessions are often given a maximum time in which they will be maintained. The sessionExpirationTask() creates a thread which runs every 1 minute to check for sessions that have exceeded the maximum amount of time, in this case 1 minute and removes the session data from the server's storage. ```java - private static void startSessionExpirationTask() { + private static void sessionExpirationTask() { new Thread(() -> { while (true) { try { - System.out.println("Session expiration checker started..."); + LOGGER.info("Session expiration checker started..."); Thread.sleep(SESSION_EXPIRATION_TIME); // Sleep for expiration time Instant currentTime = Instant.now(); synchronized (sessions) { @@ -144,9 +167,10 @@ Sessions are often given a maximum time in which they will be maintained. The se } } } - System.out.println("Session expiration checker finished!"); + LOGGER.info("Session expiration checker finished!"); } catch (InterruptedException e) { - e.printStackTrace(); + LOGGER.error("An error occurred: ", e); + Thread.currentThread().interrupt(); } } }).start(); diff --git a/server-session/src/main/java/com/iluwatar/sessionserver/App.java b/server-session/src/main/java/com/iluwatar/sessionserver/App.java index dcb3faaafdc8..ce54c5f38008 100644 --- a/server-session/src/main/java/com/iluwatar/sessionserver/App.java +++ b/server-session/src/main/java/com/iluwatar/sessionserver/App.java @@ -4,17 +4,33 @@ import java.net.InetSocketAddress; import java.time.Instant; import java.util.*; -import java.util.logging.Logger; - import com.sun.net.httpserver.HttpServer; +import lombok.extern.slf4j.Slf4j; + +/** + * The server session pattern is a behavioral design pattern concerned with assigning the responsibility + * of storing session data on the server side. Within the context of stateless protocols like HTTP all + * requests are isolated events independent of previous requests. In order to create sessions during + * user-access for a particular web application various methods can be used, such as cookies. Cookies + * are a small piece of data that can be sent between client and server on every request and response + * so that the server can "remember" the previous requests. In general cookies can either store the session + * data or the cookie can store a session identifier and be used to access appropriate data from a persistent + * storage. In the latter case the session data is stored on the server-side and appropriate data is + * identified by the cookie sent from a client's request. + * This project demonstrates the latter case. + * In the following example the ({@link App}) class starts a server and assigns ({@link LoginHandler}) + * class to handle login request. When a user logs in a session identifier is created and stored for future + * requests in a list. When a user logs out the session identifier is deleted from the list along with + * the appropriate user session data, which is handle by the ({@link LogoutHandler}) class. + */ +@Slf4j public class App { // Map to store session data (simulated using a HashMap) private static Map sessions = new HashMap<>(); private static Map sessionCreationTimes = new HashMap<>(); private static final long SESSION_EXPIRATION_TIME = 10000; - private static Logger logger = Logger.getLogger(App.class.getName()); public static void main(String[] args) throws IOException { // Create HTTP server listening on port 8000 @@ -30,14 +46,14 @@ public static void main(String[] args) throws IOException { // Start background task to check for expired sessions sessionExpirationTask(); - logger.info("Server started. Listening on port 8080..."); + LOGGER.info("Server started. Listening on port 8080..."); } private static void sessionExpirationTask() { new Thread(() -> { while (true) { try { - logger.info("Session expiration checker started..."); + LOGGER.info("Session expiration checker started..."); Thread.sleep(SESSION_EXPIRATION_TIME); // Sleep for expiration time Instant currentTime = Instant.now(); synchronized (sessions) { @@ -52,9 +68,9 @@ private static void sessionExpirationTask() { } } } - logger.info("Session expiration checker finished!"); + LOGGER.info("Session expiration checker finished!"); } catch (InterruptedException e) { - e.printStackTrace(); + LOGGER.error("An error occurred: ", e); Thread.currentThread().interrupt(); } } diff --git a/server-session/src/main/java/com/iluwatar/sessionserver/LoginHandler.java b/server-session/src/main/java/com/iluwatar/sessionserver/LoginHandler.java index aee66e7ceb90..b8f9b298b688 100644 --- a/server-session/src/main/java/com/iluwatar/sessionserver/LoginHandler.java +++ b/server-session/src/main/java/com/iluwatar/sessionserver/LoginHandler.java @@ -2,6 +2,8 @@ import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; import java.io.IOException; import java.io.OutputStream; @@ -10,6 +12,7 @@ import java.util.Map; import java.util.UUID; +@Slf4j public class LoginHandler implements HttpHandler { private Map sessions; @@ -21,7 +24,7 @@ public LoginHandler(Map sessions, Map sessionC } @Override - public void handle(HttpExchange exchange) throws IOException { + public void handle(HttpExchange exchange) { // Generate session ID String sessionID = UUID.randomUUID().toString(); @@ -29,6 +32,7 @@ public void handle(HttpExchange exchange) throws IOException { int newUser = sessions.size() + 1; sessions.put(sessionID, newUser); sessionCreationTimes.put(sessionID, Instant.now()); + LOGGER.info("User " + newUser + " created at time " + sessionCreationTimes.get(sessionID)); // Set session ID as cookie exchange.getResponseHeaders().add("Set-Cookie", "sessionID=" + sessionID); @@ -36,9 +40,15 @@ public void handle(HttpExchange exchange) throws IOException { // Send response String response = "Login successful!\n" + "Session ID: " + sessionID; - exchange.sendResponseHeaders(200, response.length()); - OutputStream os = exchange.getResponseBody(); - os.write(response.getBytes()); - os.close(); + try { + exchange.sendResponseHeaders(200, response.length()); + } catch (IOException e) { + LOGGER.error("An error occurred: ", e); + } + try(OutputStream os = exchange.getResponseBody()) { + os.write(response.getBytes()); + } catch(IOException e) { + LOGGER.error("An error occurred: ", e); + } } } diff --git a/server-session/src/main/java/com/iluwatar/sessionserver/LogoutHandler.java b/server-session/src/main/java/com/iluwatar/sessionserver/LogoutHandler.java index 4693b0afb1a4..b7a113b02b33 100644 --- a/server-session/src/main/java/com/iluwatar/sessionserver/LogoutHandler.java +++ b/server-session/src/main/java/com/iluwatar/sessionserver/LogoutHandler.java @@ -2,12 +2,14 @@ import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; +import lombok.extern.slf4j.Slf4j; import java.io.IOException; import java.io.OutputStream; import java.time.Instant; import java.util.Map; +@Slf4j public class LogoutHandler implements HttpHandler { private Map sessions; @@ -19,7 +21,7 @@ public LogoutHandler(Map sessions, Map session } @Override - public void handle(HttpExchange exchange) throws IOException { + public void handle(HttpExchange exchange) { // Get session ID from cookie String sessionID = exchange.getRequestHeaders().getFirst("Cookie").replace("sessionID=", ""); String currentSessionID = sessions.get(sessionID) == null ? null : sessionID; @@ -35,11 +37,23 @@ public void handle(HttpExchange exchange) throws IOException { } //Remove session + if(currentSessionID != null) + LOGGER.info("User " + sessions.get(currentSessionID) + " deleted!"); + else + LOGGER.info("User already deleted!"); sessions.remove(sessionID); sessionCreationTimes.remove(sessionID); - exchange.sendResponseHeaders(200, response.length()); - OutputStream os = exchange.getResponseBody(); - os.write(response.getBytes()); - os.close(); + + try { + exchange.sendResponseHeaders(200, response.length()); + } catch(IOException e) { + LOGGER.error("An error has occurred: ", e); + } + + try(OutputStream os = exchange.getResponseBody()) { + os.write(response.getBytes()); + } catch(IOException e) { + LOGGER.error("An error has occurred: ", e); + } } }