mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-05-14 10:58:42 +00:00
* #2848 Moved project from private project directory to forked repo * #2848 Requested changes implemented
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
---
|
||||
title: Server Session
|
||||
category: Behavioral
|
||||
language: en
|
||||
tag:
|
||||
- Session Management
|
||||
- Session Tracking
|
||||
- Cookies
|
||||
---
|
||||
|
||||
## Also known as
|
||||
|
||||
Server-side session state pattern
|
||||
|
||||
## Intent
|
||||
|
||||
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.
|
||||
|
||||
## Explanation
|
||||
|
||||
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.
|
||||
|
||||
In plain words
|
||||
|
||||
> 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.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> 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.
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
Consider a website in which a user can log in. Once logged in a session is created to maintain the specific user's data.
|
||||
|
||||
First, we have the LoginHandler class, which creates a session identifier and sends it to the client as cookie.
|
||||
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.
|
||||
|
||||
The user logs in by visiting localhost:8080/login
|
||||
|
||||
output:
|
||||
|
||||
Login successful!
|
||||
Session ID: 26434a9c-e734-4a64-97ce-7802b8de46bb
|
||||
|
||||
```java
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When the user logs out the session data is removed from storage using the LogoutHandler class.
|
||||
|
||||
The user logs out by visiting localhost:8080/logout
|
||||
|
||||
output:
|
||||
|
||||
Logout successful!
|
||||
Session ID: 26434a9c-e734-4a64-97ce-7802b8de46bb
|
||||
|
||||
```java
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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 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();
|
||||
}
|
||||
```
|
||||
|
||||
## 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.
|
||||
* 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.
|
||||
|
||||
## Tutorials
|
||||
|
||||
* [Web Dev Simplified](https://www.youtube.com/watch?v=GihQAC1I39Q&pp=ygUMaHR0cCBzZXNzaW9u)
|
||||
* [Hackersploit](https://www.youtube.com/watch?v=zHBpJA5XfDk)
|
||||
|
||||
## Consequences
|
||||
|
||||
Pros
|
||||
|
||||
* 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.
|
||||
* Server-side session management is generally more secure than client-side alternatives like storing session data in cookies.
|
||||
* Server-side session management can scale more easily, especially when using distributed caching systems or databases.
|
||||
|
||||
Cons
|
||||
|
||||
* Large overhead in terms of system resources, and that the session may be interrupted if the system is restarted.
|
||||
* 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.
|
||||
* 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.
|
||||
|
||||
## Real-world examples
|
||||
|
||||
* Express.js Session Middleware
|
||||
* Spring Session in Spring Boot
|
||||
* Django Session Framework
|
||||
* Java Servlet Session Management
|
||||
|
||||
## Credits
|
||||
|
||||
* [Session(Computer Science)](https://en.wikipedia.org/wiki/Session_(computer_science)
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.iluwatar</groupId>
|
||||
<artifactId>java-design-patterns</artifactId>
|
||||
<version>1.26.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>serversession</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>
|
||||
|
||||
</project>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import com.sun.net.httpserver.Headers;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
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;
|
||||
|
||||
public class LoginHandlerTest {
|
||||
|
||||
private LoginHandler loginHandler;
|
||||
//private Headers headers;
|
||||
private Map<String, Integer> sessions;
|
||||
private Map<String, Instant> sessionCreationTimes;
|
||||
|
||||
@Mock
|
||||
private HttpExchange exchange;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
sessions = new HashMap<>();
|
||||
sessionCreationTimes = new HashMap<>();
|
||||
loginHandler = new LoginHandler(sessions, sessionCreationTimes);
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
//act
|
||||
loginHandler.handle(exchange);
|
||||
|
||||
//assert
|
||||
String[] response = outputStream.toString().split("Session ID: ");
|
||||
assertEquals(sessions.entrySet().toArray()[0].toString().split("=1")[0], response[1]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
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 static org.mockito.Mockito.when;
|
||||
|
||||
public class LogoutHandlerTest {
|
||||
|
||||
private LogoutHandler logoutHandler;
|
||||
private Headers headers;
|
||||
private Map<String, Integer> sessions;
|
||||
private Map<String, Instant> sessionCreationTimes;
|
||||
|
||||
@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
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
//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));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandler_SessionExpired() throws IOException {
|
||||
|
||||
//assemble
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
when(exchange.getRequestHeaders()).thenReturn(headers);
|
||||
when(exchange.getResponseBody()).thenReturn(outputStream);
|
||||
|
||||
//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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user