fix: Fix server session (#2913)

* fix server session

* fix version
This commit is contained in:
Ilkka Seppälä
2024-04-11 22:11:42 +03:00
committed by GitHub
parent 44071ab497
commit 56dfd8c2d7
11 changed files with 445 additions and 371 deletions
@@ -1,10 +1,12 @@
package com.iluwatar.sessionserver;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.time.Instant;
import java.util.*;
import com.sun.net.httpserver.HttpServer;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
/**
@@ -32,48 +34,54 @@ public class App {
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);
/**
* Main entry point.
* @param args arguments
* @throws IOException ex
*/
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));
// 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 the server
server.start();
// Start background task to check for expired sessions
sessionExpirationTask();
// 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...");
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();
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();
}
}
}
}).start();
}
}
LOGGER.info("Session expiration checker finished!");
} catch (InterruptedException e) {
LOGGER.error("An error occurred: ", e);
Thread.currentThread().interrupt();
}
}
}).start();
}
}
@@ -2,53 +2,52 @@ 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;
import lombok.extern.slf4j.Slf4j;
/**
* LoginHandler.
*/
@Slf4j
public class LoginHandler implements HttpHandler {
private Map<String, Integer> sessions;
private Map<String, Instant> sessionCreationTimes;
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;
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);
}
@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);
}
try (OutputStream os = exchange.getResponseBody()) {
os.write(response.getBytes());
} catch (IOException e) {
LOGGER.error("An error occurred: ", e);
}
}
}
@@ -2,58 +2,60 @@ 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;
import lombok.extern.slf4j.Slf4j;
/**
* LogoutHandler.
*/
@Slf4j
public class LogoutHandler implements HttpHandler {
private Map<String, Integer> sessions;
private Map<String, Instant> sessionCreationTimes;
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;
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;
}
@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);
}
//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);
}
}
}
@@ -1,52 +1,59 @@
package com.iluwatar.sessionserver;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import com.iluwatar.sessionserver.LoginHandler;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.Instant;
import java.util.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* LoginHandlerTest.
*/
public class LoginHandlerTest {
private LoginHandler loginHandler;
//private Headers headers;
private Map<String, Integer> sessions;
private Map<String, Instant> sessionCreationTimes;
private LoginHandler loginHandler;
//private Headers headers;
private Map<String, Integer> sessions;
private Map<String, Instant> sessionCreationTimes;
@Mock
private HttpExchange exchange;
@Mock
private HttpExchange exchange;
@BeforeEach
public void setUp() {
MockitoAnnotations.initMocks(this);
sessions = new HashMap<>();
sessionCreationTimes = new HashMap<>();
loginHandler = new LoginHandler(sessions, sessionCreationTimes);
}
/**
* Setup tests.
*/
@BeforeEach
public void setUp() {
MockitoAnnotations.initMocks(this);
sessions = new HashMap<>();
sessionCreationTimes = new HashMap<>();
loginHandler = new LoginHandler(sessions, sessionCreationTimes);
}
@Test
public void testHandle() throws IOException {
@Test
public void testHandle() throws IOException {
//assemble
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); //Exchange object is mocked so OutputStream must be manually created
when(exchange.getResponseHeaders()).thenReturn(new Headers()); //Exchange object is mocked so Header object must be manually created
when(exchange.getResponseBody()).thenReturn(outputStream);
//assemble
ByteArrayOutputStream outputStream =
new ByteArrayOutputStream(); //Exchange object is mocked so OutputStream must be manually created
when(exchange.getResponseHeaders()).thenReturn(
new Headers()); //Exchange object is mocked so Header object must be manually created
when(exchange.getResponseBody()).thenReturn(outputStream);
//act
loginHandler.handle(exchange);
//act
loginHandler.handle(exchange);
//assert
String[] response = outputStream.toString().split("Session ID: ");
assertEquals(sessions.entrySet().toArray()[0].toString().split("=1")[0], response[1]);
}
//assert
String[] response = outputStream.toString().split("Session ID: ");
assertEquals(sessions.entrySet().toArray()[0].toString().split("=1")[0], response[1]);
}
}
@@ -1,75 +1,83 @@
package com.iluwatar.sessionserver;
import static org.mockito.Mockito.when;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import com.iluwatar.sessionserver.LogoutHandler;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.mockito.Mockito.when;
/**
* LogoutHandlerTest.
*/
public class LogoutHandlerTest {
private LogoutHandler logoutHandler;
private Headers headers;
private Map<String, Integer> sessions;
private Map<String, Instant> sessionCreationTimes;
private LogoutHandler logoutHandler;
private Headers headers;
private Map<String, Integer> sessions;
private Map<String, Instant> sessionCreationTimes;
@Mock
private HttpExchange exchange;
@Mock
private HttpExchange exchange;
@BeforeEach
public void setUp() {
MockitoAnnotations.initMocks(this);
sessions = new HashMap<>();
sessionCreationTimes = new HashMap<>();
logoutHandler = new LogoutHandler(sessions, sessionCreationTimes);
headers = new Headers();
headers.add("Cookie", "sessionID=1234"); //Exchange object methods return Header Object but Exchange is mocked so Headers must be manually created
}
/**
* Setup tests.
*/
@BeforeEach
public void setUp() {
MockitoAnnotations.initMocks(this);
sessions = new HashMap<>();
sessionCreationTimes = new HashMap<>();
logoutHandler = new LogoutHandler(sessions, sessionCreationTimes);
headers = new Headers();
headers.add("Cookie",
"sessionID=1234"); //Exchange object methods return Header Object but Exchange is mocked so Headers must be manually created
}
@Test
public void testHandler_SessionNotExpired() throws IOException {
@Test
public void testHandler_SessionNotExpired() throws IOException {
//assemble
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
sessions.put("1234", 1); //Fake login details since LoginHandler isn't called
sessionCreationTimes.put("1234", Instant.now()); //Fake login details since LoginHandler isn't called
when(exchange.getRequestHeaders()).thenReturn(headers);
when(exchange.getResponseBody()).thenReturn(outputStream);
//assemble
sessions.put("1234", 1); //Fake login details since LoginHandler isn't called
sessionCreationTimes.put("1234",
Instant.now()); //Fake login details since LoginHandler isn't called
when(exchange.getRequestHeaders()).thenReturn(headers);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
when(exchange.getResponseBody()).thenReturn(outputStream);
//act
logoutHandler.handle(exchange);
//act
logoutHandler.handle(exchange);
//assert
String[] response = outputStream.toString().split("Session ID: ");
Assertions.assertEquals("1234", response[1]);
Assertions.assertFalse(sessions.containsKey(response));
Assertions.assertFalse(sessionCreationTimes.containsKey(response));
}
//assert
String[] response = outputStream.toString().split("Session ID: ");
Assertions.assertEquals("1234", response[1]);
Assertions.assertFalse(sessions.containsKey(response));
Assertions.assertFalse(sessionCreationTimes.containsKey(response));
}
@Test
public void testHandler_SessionExpired() throws IOException {
@Test
public void testHandler_SessionExpired() throws IOException {
//assemble
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
when(exchange.getRequestHeaders()).thenReturn(headers);
when(exchange.getResponseBody()).thenReturn(outputStream);
//assemble
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
when(exchange.getRequestHeaders()).thenReturn(headers);
when(exchange.getResponseBody()).thenReturn(outputStream);
//act
logoutHandler.handle(exchange);
//act
logoutHandler.handle(exchange);
//assert
String[] response = outputStream.toString().split("Session ID: ");
Assertions.assertEquals("Session has already expired!", response[0]);
Assertions.assertFalse(sessions.containsKey(response));
Assertions.assertFalse(sessionCreationTimes.containsKey(response));
}
//assert
String[] response = outputStream.toString().split("Session ID: ");
Assertions.assertEquals("Session has already expired!", response[0]);
Assertions.assertFalse(sessions.containsKey(response));
Assertions.assertFalse(sessionCreationTimes.containsKey(response));
}
}