Skip to content

Commit 07eb395

Browse files
committed
#2848 Moved project from private project directory to forked repo
1 parent 66538dd commit 07eb395

File tree

7 files changed

+505
-0
lines changed

7 files changed

+505
-0
lines changed

server-session/README.md

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
---
2+
title: Server Session
3+
category: Architectural
4+
language: en
5+
---
6+
7+
## Also known as
8+
9+
Server-side session state pattern
10+
11+
## Intent
12+
13+
Within the context of a client-server relationship, the server is responsible for storing session data in order to maintain state in an otherwise stateless environment.
14+
15+
## Explanation
16+
17+
Real-world example
18+
19+
> 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.
20+
21+
In plain words
22+
23+
> Session data is stored on the server, whether in a database, text file or any other form of persistent storage, rather than the client's browser.
24+
25+
Wikipedia says
26+
27+
> A session token is a unique identifier that is generated and sent from a server to a client to identify the current interaction session. The client usually stores and sends the token as an HTTP cookie and/or sends it as a parameter in GET or POST queries. The reason to use session tokens is that the client only has to handle the identifier—all session data is stored on the server (usually in a database, to which the client does not have direct access) linked to that identifier.
28+
29+
**Programmatic Example**
30+
31+
Consider a website in which a user can log in. Once logged in a session is created to maintain the specific user's data.
32+
33+
First, we have the LoginHandler class, which creates a session identifier and sends it to the client as cookie.
34+
Notice that not all data is sent to the client, the session creation time and user number are stored on the server side and thus cannot be accessed by the client.
35+
36+
The user logs in by visiting localhost:8080/login
37+
38+
output:
39+
40+
Login successful!
41+
Session ID: 26434a9c-e734-4a64-97ce-7802b8de46bb
42+
43+
```java
44+
public class LoginHandler implements HttpHandler {
45+
46+
private Map<String, Integer> sessions;
47+
private Map<String, Instant> sessionCreationTimes;
48+
49+
public LoginHandler(Map<String, Integer> sessions, Map<String, Instant> sessionCreationTimes) {
50+
this.sessions = sessions;
51+
this.sessionCreationTimes = sessionCreationTimes;
52+
}
53+
54+
@Override
55+
public void handle(HttpExchange exchange) throws IOException {
56+
// Generate session ID
57+
String sessionID = UUID.randomUUID().toString();
58+
59+
// Store session data (simulated)
60+
int newUser = sessions.size() + 1;
61+
sessions.put(sessionID, newUser);
62+
sessionCreationTimes.put(sessionID, Instant.now());
63+
64+
// Set session ID as cookie
65+
exchange.getResponseHeaders().add("Set-Cookie", "sessionID=" + sessionID);
66+
67+
// Send response
68+
String response = "Login successful!\n" +
69+
"Session ID: " + sessionID;
70+
exchange.sendResponseHeaders(200, response.length());
71+
OutputStream os = exchange.getResponseBody();
72+
os.write(response.getBytes());
73+
os.close();
74+
}
75+
}
76+
```
77+
78+
When the user logs out the session data is removed from storage using the LogoutHandler class.
79+
80+
The user logs out by visiting localhost:8080/logout
81+
82+
output:
83+
84+
Logout successful!
85+
Session ID: 26434a9c-e734-4a64-97ce-7802b8de46bb
86+
87+
```java
88+
public class LogoutHandler implements HttpHandler {
89+
90+
private Map<String, Integer> sessions;
91+
private Map<String, Instant> sessionCreationTimes;
92+
93+
public LogoutHandler(Map<String, Integer> sessions, Map<String, Instant> sessionCreationTimes) {
94+
this.sessions = sessions;
95+
this.sessionCreationTimes = sessionCreationTimes;
96+
}
97+
98+
@Override
99+
public void handle(HttpExchange exchange) throws IOException {
100+
// Get session ID from cookie
101+
String sessionID = exchange.getRequestHeaders().getFirst("Cookie").replace("sessionID=", "");
102+
String currentSessionID = sessions.get(sessionID) == null ? null : sessionID;
103+
104+
// Send response
105+
106+
String response = "";
107+
if(currentSessionID == null) {
108+
response += "Session has already expired!";
109+
} else {
110+
response = "Logout successful!\n" +
111+
"Session ID: " + currentSessionID;
112+
}
113+
114+
//Remove session
115+
sessions.remove(sessionID);
116+
sessionCreationTimes.remove(sessionID);
117+
exchange.sendResponseHeaders(200, response.length());
118+
OutputStream os = exchange.getResponseBody();
119+
os.write(response.getBytes());
120+
os.close();
121+
}
122+
}
123+
```
124+
125+
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.
126+
127+
```java
128+
private static void startSessionExpirationTask() {
129+
new Thread(() -> {
130+
while (true) {
131+
try {
132+
System.out.println("Session expiration checker started...");
133+
Thread.sleep(SESSION_EXPIRATION_TIME); // Sleep for expiration time
134+
Instant currentTime = Instant.now();
135+
synchronized (sessions) {
136+
synchronized (sessionCreationTimes) {
137+
Iterator<Map.Entry<String, Instant>> iterator = sessionCreationTimes.entrySet().iterator();
138+
while (iterator.hasNext()) {
139+
Map.Entry<String, Instant> entry = iterator.next();
140+
if (entry.getValue().plusMillis(SESSION_EXPIRATION_TIME).isBefore(currentTime)) {
141+
sessions.remove(entry.getKey());
142+
iterator.remove();
143+
}
144+
}
145+
}
146+
}
147+
System.out.println("Session expiration checker finished!");
148+
} catch (InterruptedException e) {
149+
e.printStackTrace();
150+
}
151+
}
152+
}).start();
153+
}
154+
```
155+
156+
## Class diagram
157+
158+
![alt text](./etc/adapter.urm.png "Adapter class diagram")
159+
160+
## Applicability
161+
162+
Use the Adapter pattern when
163+
164+
* When a user logs into a website or web application and you want to keep track of their authentication status.
165+
* In e-commerce websites when you want to maintain the contents of a user's shopping cart across different pages and visits.
166+
* When you want to store user preferences and settings, such as language preferences, theme choices, or any other customizable options.
167+
* When you want to keep track of user activity and behavior on a website for the sake of analytics purposes.
168+
169+
## Tutorials
170+
171+
* [Web Dev Simplified](https://www.youtube.com/watch?v=GihQAC1I39Q&pp=ygUMaHR0cCBzZXNzaW9u)
172+
* [Hackersploit](https://www.youtube.com/watch?v=zHBpJA5XfDk)
173+
174+
## Consequences
175+
176+
Pros
177+
178+
* HTTP sessions are typically not implemented using one thread per session, but by means of a database with information about the state of each session. The advantage with multiple processes or threads is relaxed complexity of the software, since each thread is an instance with its own history and encapsulated variables.
179+
* Server-side session management is generally more secure than client-side alternatives like storing session data in cookies.
180+
* Server-side session management can scale more easily, especially when using distributed caching systems or databases.
181+
182+
Cons
183+
184+
* Large overhead in terms of system resources, and that the session may be interrupted if the system is restarted.
185+
* Can become difficult to handle in conjunction with load-balancing/high-availability systems and are not usable at all in some embedded systems with no storage.
186+
* If the server hosting the session data goes down or experiences issues, it can disrupt the entire application's functionality, potentially leading to session data loss and user inconvenience.
187+
188+
## Real-world examples
189+
190+
* Express.js Session Middleware
191+
* Spring Session in Spring Boot
192+
* Django Session Framework
193+
* Java Servlet Session Management
194+
195+
## Credits
196+
197+
* [Session(Computer Science)](https://en.wikipedia.org/wiki/Session_(computer_science)

server-session/pom.xml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<parent>
8+
<groupId>com.iluwatar</groupId>
9+
<artifactId>java-design-patterns</artifactId>
10+
<version>1.26.0-SNAPSHOT</version>
11+
</parent>
12+
<artifactId>serversession</artifactId>
13+
14+
<dependencies>
15+
<dependency>
16+
<groupId>org.junit.jupiter</groupId>
17+
<artifactId>junit-jupiter-api</artifactId>
18+
<version>5.10.2</version>
19+
<scope>test</scope>
20+
</dependency>
21+
<dependency>
22+
<groupId>org.mockito</groupId>
23+
<artifactId>mockito-core</artifactId>
24+
<version>5.11.0</version>
25+
<scope>test</scope>
26+
</dependency>
27+
</dependencies>
28+
29+
</project>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.iluwatar.sessionserver;
2+
3+
import java.io.IOException;
4+
import java.net.InetSocketAddress;
5+
import java.time.Instant;
6+
import java.util.*;
7+
import java.util.logging.Logger;
8+
9+
import com.sun.net.httpserver.HttpServer;
10+
11+
public class App {
12+
13+
// Map to store session data (simulated using a HashMap)
14+
private static Map<String, Integer> sessions = new HashMap<>();
15+
private static Map<String, Instant> sessionCreationTimes = new HashMap<>();
16+
private static final long SESSION_EXPIRATION_TIME = 10000;
17+
private static Logger logger = Logger.getLogger(App.class.getName());
18+
19+
public static void main(String[] args) throws IOException {
20+
// Create HTTP server listening on port 8000
21+
HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
22+
23+
// Set up session management endpoints
24+
server.createContext("/login", new LoginHandler(sessions, sessionCreationTimes));
25+
server.createContext("/logout", new LogoutHandler(sessions, sessionCreationTimes));
26+
27+
// Start the server
28+
server.start();
29+
30+
// Start background task to check for expired sessions
31+
sessionExpirationTask();
32+
33+
logger.info("Server started. Listening on port 8080...");
34+
}
35+
36+
private static void sessionExpirationTask() {
37+
new Thread(() -> {
38+
while (true) {
39+
try {
40+
logger.info("Session expiration checker started...");
41+
Thread.sleep(SESSION_EXPIRATION_TIME); // Sleep for expiration time
42+
Instant currentTime = Instant.now();
43+
synchronized (sessions) {
44+
synchronized (sessionCreationTimes) {
45+
Iterator<Map.Entry<String, Instant>> iterator = sessionCreationTimes.entrySet().iterator();
46+
while (iterator.hasNext()) {
47+
Map.Entry<String, Instant> entry = iterator.next();
48+
if (entry.getValue().plusMillis(SESSION_EXPIRATION_TIME).isBefore(currentTime)) {
49+
sessions.remove(entry.getKey());
50+
iterator.remove();
51+
}
52+
}
53+
}
54+
}
55+
logger.info("Session expiration checker finished!");
56+
} catch (InterruptedException e) {
57+
e.printStackTrace();
58+
Thread.currentThread().interrupt();
59+
}
60+
}
61+
}).start();
62+
}
63+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.iluwatar.sessionserver;
2+
3+
import com.sun.net.httpserver.HttpExchange;
4+
import com.sun.net.httpserver.HttpHandler;
5+
6+
import java.io.IOException;
7+
import java.io.OutputStream;
8+
import java.time.Instant;
9+
import java.util.HashMap;
10+
import java.util.Map;
11+
import java.util.UUID;
12+
13+
public class LoginHandler implements HttpHandler {
14+
15+
private Map<String, Integer> sessions;
16+
private Map<String, Instant> sessionCreationTimes;
17+
18+
public LoginHandler(Map<String, Integer> sessions, Map<String, Instant> sessionCreationTimes) {
19+
this.sessions = sessions;
20+
this.sessionCreationTimes = sessionCreationTimes;
21+
}
22+
23+
@Override
24+
public void handle(HttpExchange exchange) throws IOException {
25+
// Generate session ID
26+
String sessionID = UUID.randomUUID().toString();
27+
28+
// Store session data (simulated)
29+
int newUser = sessions.size() + 1;
30+
sessions.put(sessionID, newUser);
31+
sessionCreationTimes.put(sessionID, Instant.now());
32+
33+
// Set session ID as cookie
34+
exchange.getResponseHeaders().add("Set-Cookie", "sessionID=" + sessionID);
35+
36+
// Send response
37+
String response = "Login successful!\n" +
38+
"Session ID: " + sessionID;
39+
exchange.sendResponseHeaders(200, response.length());
40+
OutputStream os = exchange.getResponseBody();
41+
os.write(response.getBytes());
42+
os.close();
43+
}
44+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.iluwatar.sessionserver;
2+
3+
import com.sun.net.httpserver.HttpExchange;
4+
import com.sun.net.httpserver.HttpHandler;
5+
6+
import java.io.IOException;
7+
import java.io.OutputStream;
8+
import java.time.Instant;
9+
import java.util.Map;
10+
11+
public class LogoutHandler implements HttpHandler {
12+
13+
private Map<String, Integer> sessions;
14+
private Map<String, Instant> sessionCreationTimes;
15+
16+
public LogoutHandler(Map<String, Integer> sessions, Map<String, Instant> sessionCreationTimes) {
17+
this.sessions = sessions;
18+
this.sessionCreationTimes = sessionCreationTimes;
19+
}
20+
21+
@Override
22+
public void handle(HttpExchange exchange) throws IOException {
23+
// Get session ID from cookie
24+
String sessionID = exchange.getRequestHeaders().getFirst("Cookie").replace("sessionID=", "");
25+
String currentSessionID = sessions.get(sessionID) == null ? null : sessionID;
26+
27+
// Send response
28+
29+
String response = "";
30+
if(currentSessionID == null) {
31+
response += "Session has already expired!";
32+
} else {
33+
response = "Logout successful!\n" +
34+
"Session ID: " + currentSessionID;
35+
}
36+
37+
//Remove session
38+
sessions.remove(sessionID);
39+
sessionCreationTimes.remove(sessionID);
40+
exchange.sendResponseHeaders(200, response.length());
41+
OutputStream os = exchange.getResponseBody();
42+
os.write(response.getBytes());
43+
os.close();
44+
}
45+
}

0 commit comments

Comments
 (0)