feat: #2848 Server Session pattern (#2909)

* #2848 Moved project from private project directory to forked repo

* #2848 Requested changes implemented
This commit is contained in:
Riley
2024-04-11 11:55:47 -04:00
committed by GitHub
parent 70fc5736f6
commit 943d233120
7 changed files with 569 additions and 0 deletions
@@ -0,0 +1,79 @@
package com.iluwatar.sessionserver;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.time.Instant;
import java.util.*;
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<String, Integer> sessions = new HashMap<>();
private static Map<String, Instant> sessionCreationTimes = new HashMap<>();
private static final long SESSION_EXPIRATION_TIME = 10000;
public static void main(String[] args) throws IOException {
// Create HTTP server listening on port 8000
HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
// Set up session management endpoints
server.createContext("/login", new LoginHandler(sessions, sessionCreationTimes));
server.createContext("/logout", new LogoutHandler(sessions, sessionCreationTimes));
// Start the server
server.start();
// Start background task to check for expired sessions
sessionExpirationTask();
LOGGER.info("Server started. Listening on port 8080...");
}
private static void sessionExpirationTask() {
new Thread(() -> {
while (true) {
try {
LOGGER.info("Session expiration checker started...");
Thread.sleep(SESSION_EXPIRATION_TIME); // Sleep for expiration time
Instant currentTime = Instant.now();
synchronized (sessions) {
synchronized (sessionCreationTimes) {
Iterator<Map.Entry<String, Instant>> iterator = sessionCreationTimes.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Instant> entry = iterator.next();
if (entry.getValue().plusMillis(SESSION_EXPIRATION_TIME).isBefore(currentTime)) {
sessions.remove(entry.getKey());
iterator.remove();
}
}
}
}
LOGGER.info("Session expiration checker finished!");
} catch (InterruptedException e) {
LOGGER.error("An error occurred: ", e);
Thread.currentThread().interrupt();
}
}
}).start();
}
}
@@ -0,0 +1,54 @@
package com.iluwatar.sessionserver;
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;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@Slf4j
public class LoginHandler implements HttpHandler {
private Map<String, Integer> sessions;
private Map<String, Instant> sessionCreationTimes;
public LoginHandler(Map<String, Integer> sessions, Map<String, Instant> sessionCreationTimes) {
this.sessions = sessions;
this.sessionCreationTimes = sessionCreationTimes;
}
@Override
public void handle(HttpExchange exchange) {
// Generate session ID
String sessionID = UUID.randomUUID().toString();
// Store session data (simulated)
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);
// Send response
String response = "Login successful!\n" +
"Session ID: " + sessionID;
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);
}
}
}
@@ -0,0 +1,59 @@
package com.iluwatar.sessionserver;
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<String, Integer> sessions;
private Map<String, Instant> sessionCreationTimes;
public LogoutHandler(Map<String, Integer> sessions, Map<String, Instant> sessionCreationTimes) {
this.sessions = sessions;
this.sessionCreationTimes = sessionCreationTimes;
}
@Override
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;
// Send response
String response = "";
if(currentSessionID == null) {
response += "Session has already expired!";
} else {
response = "Logout successful!\n" +
"Session ID: " + currentSessionID;
}
//Remove session
if(currentSessionID != null)
LOGGER.info("User " + sessions.get(currentSessionID) + " deleted!");
else
LOGGER.info("User already deleted!");
sessions.remove(sessionID);
sessionCreationTimes.remove(sessionID);
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);
}
}
}