Skip to content

Commit

Permalink
iluwatar#2848 Requested changes implemented
Browse files Browse the repository at this point in the history
  • Loading branch information
Ehspresso committed Apr 10, 2024
1 parent 247bda8 commit 00a7d13
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 32 deletions.
54 changes: 39 additions & 15 deletions server-session/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
---
title: Server Session
category: Architectural
category: Behavioral
language: en
tag:
- Session Management
- Session Tracking
- Cookies
---

## Also known as
Expand Down Expand Up @@ -52,25 +56,32 @@ 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();

// 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;
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);
}
}
}
```
Expand All @@ -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;
Expand All @@ -112,24 +123,36 @@ 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);
}
}
}
```

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) {
Expand All @@ -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();
Expand Down
30 changes: 23 additions & 7 deletions server-session/src/main/java/com/iluwatar/sessionserver/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Integer> sessions = new HashMap<>();
private static Map<String, Instant> 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
Expand All @@ -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) {
Expand All @@ -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();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -10,6 +12,7 @@
import java.util.Map;
import java.util.UUID;

@Slf4j
public class LoginHandler implements HttpHandler {

private Map<String, Integer> sessions;
Expand All @@ -21,24 +24,31 @@ public LoginHandler(Map<String, Integer> sessions, Map<String, Instant> sessionC
}

@Override
public void handle(HttpExchange exchange) throws IOException {
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;
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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Integer> sessions;
Expand All @@ -19,7 +21,7 @@ public LogoutHandler(Map<String, Integer> sessions, Map<String, Instant> 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;
Expand All @@ -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);
}
}
}

0 comments on commit 00a7d13

Please sign in to comment.