Skip to content

Fix server session #2913

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
354 changes: 189 additions & 165 deletions double-buffer/README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,5 @@
public enum Pixel {

WHITE,
BLACK;
BLACK
}
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@
<module>dynamic-proxy</module>
<module>gateway</module>
<module>slob</module>
<module>server-session</module>
</modules>
<repositories>
<repository>
Expand Down
10 changes: 5 additions & 5 deletions server-session/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ title: Server Session
category: Behavioral
language: en
tag:
- Session Management
- Session Tracking
- Cookies
- Session management
- State tracking
---

## Also known as
Expand All @@ -20,7 +20,7 @@ Within the context of a client-server relationship, the server is responsible fo

Real-world example

> Consider a gaming website which stores user profile data such as username, password, highscore, hours played, etc. Since this website is accessed over the internet which uses the HTTP protocol, all requests sent to the server are stateless. In order for the page to display user relevent information without re-authenticating the user on every request a session must be created. Once the session is created the user can access the homescreen, statistics page, setting page, etc. and view profile specific data without needing to login in on every page request.
> Consider a gaming website which stores user profile data such as username, password, high-score, hours played, etc. Since this website is accessed over the internet which uses the HTTP protocol, all requests sent to the server are stateless. In order for the page to display user relevant information without re-authenticating the user on every request a session must be created. Once the session is created the user can access the homescreen, statistics page, setting page, etc. and view profile specific data without needing to log in on every page request.

In plain words

Expand Down Expand Up @@ -179,13 +179,13 @@ Sessions are often given a maximum time in which they will be maintained. The se

## Class diagram

![alt text](./etc/adapter.urm.png "Adapter class diagram")
![Server Session class diagram](./etc/server-session.urm.puml "Server Session class diagram")

## Applicability

Use the Adapter pattern when

* When a user logs into a website or web application and you want to keep track of their authentication status.
* When a user logs into a website or web application, and you want to keep track of their authentication status.
* In e-commerce websites when you want to maintain the contents of a user's shopping cart across different pages and visits.
* When you want to store user preferences and settings, such as language preferences, theme choices, or any other customizable options.
* When you want to keep track of user activity and behavior on a website for the sake of analytics purposes.
Expand Down
27 changes: 27 additions & 0 deletions server-session/etc/server-session.urm.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
@startuml
package com.iluwatar.sessionserver {
class App {
- LOGGER : Logger {static}
- SESSION_EXPIRATION_TIME : long {static}
- sessionCreationTimes : Map<String, Instant> {static}
- sessions : Map<String, Integer> {static}
+ App()
+ main(args : String[]) {static}
- sessionExpirationTask() {static}
}
class LoginHandler {
- LOGGER : Logger {static}
- sessionCreationTimes : Map<String, Instant>
- sessions : Map<String, Integer>
+ LoginHandler(sessions : Map<String, Integer>, sessionCreationTimes : Map<String, Instant>)
+ handle(exchange : HttpExchange)
}
class LogoutHandler {
- LOGGER : Logger {static}
- sessionCreationTimes : Map<String, Instant>
- sessions : Map<String, Integer>
+ LogoutHandler(sessions : Map<String, Integer>, sessionCreationTimes : Map<String, Instant>)
+ handle(exchange : HttpExchange)
}
}
@enduml
4 changes: 1 addition & 3 deletions server-session/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,17 @@
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
</parent>
<artifactId>serversession</artifactId>
<artifactId>server-session</artifactId>

<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand Down
86 changes: 47 additions & 39 deletions server-session/src/main/java/com/iluwatar/sessionserver/App.java
Original file line number Diff line number Diff line change
@@ -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;

/**
Expand Down Expand Up @@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,52 @@

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;

public LoginHandler(Map<String, Integer> sessions, Map<String, Instant> sessionCreationTimes) {
this.sessions = sessions;
this.sessionCreationTimes = 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;
}

@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);
}
}
}
Loading
Loading