mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-05-25 03:36:33 +00:00
+171
-147
@@ -3,36 +3,29 @@ title: Double Buffer
|
|||||||
category: Behavioral
|
category: Behavioral
|
||||||
language: en
|
language: en
|
||||||
tag:
|
tag:
|
||||||
- Performance
|
- Buffering
|
||||||
- Game programming
|
- Game programming
|
||||||
|
- Optimization
|
||||||
|
- Performance
|
||||||
---
|
---
|
||||||
|
|
||||||
## Intent
|
## Intent
|
||||||
Double buffering is a term used to describe a device that has two buffers. The usage of multiple
|
|
||||||
buffers increases the overall throughput of a device and helps prevents bottlenecks. This example
|
The Double Buffer pattern aims to reduce the time necessary for rendering and displaying graphical or computational data by utilizing two buffers. One buffer is used for rendering the next frame or computing the next set of data, while the other is used to display the current frame or data set to the user.
|
||||||
shows using double buffer pattern on graphics. It is used to show one image or frame while a separate
|
|
||||||
frame is being buffered to be shown next. This method makes animations and games look more realistic
|
|
||||||
than the same done in a single buffer mode.
|
|
||||||
|
|
||||||
## Explanation
|
## Explanation
|
||||||
|
|
||||||
Real world example
|
Real world example
|
||||||
> A typical example, and one that every game engine must address, is rendering. When the game draws
|
|
||||||
> the world the users see, it does so one piece at a time -- the mountains in the distance,
|
> A typical example, and one that every game engine must address, is rendering. When the game draws the world the users see, it does so one piece at a time -- the mountains in the distance, the rolling hills, the trees, each in its turn. If the user watched the view draw incrementally like that, the illusion of a coherent world would be shattered. The scene must update smoothly and quickly, displaying a series of complete frames, each appearing instantly. Double buffering solves the problem.
|
||||||
> the rolling hills, the trees, each in its turn. If the user watched the view draw incrementally
|
|
||||||
> like that, the illusion of a coherent world would be shattered. The scene must update smoothly
|
|
||||||
> and quickly, displaying a series of complete frames, each appearing instantly. Double buffering solves
|
|
||||||
> the problem.
|
|
||||||
|
|
||||||
In plain words
|
In plain words
|
||||||
> It ensures a state that is being rendered correctly while that state is modifying incrementally. It is
|
|
||||||
> widely used in computer graphics.
|
> It ensures a state that is being rendered correctly while that state is modifying incrementally. It is widely used in computer graphics.
|
||||||
|
|
||||||
Wikipedia says
|
Wikipedia says
|
||||||
> In computer science, multiple buffering is the use of more than one buffer to hold a block of data,
|
|
||||||
> so that a "reader" will see a complete (though perhaps old) version of the data, rather than a
|
> In computer science, multiple buffering is the use of more than one buffer to hold a block of data, so that a "reader" will see a complete (though perhaps old) version of the data, rather than a partially updated version of the data being created by a "writer". It is very commonly used for computer display images.
|
||||||
> partially updated version of the data being created by a "writer". It is very commonly used for
|
|
||||||
> computer display images.
|
|
||||||
|
|
||||||
**Programmatic Example**
|
**Programmatic Example**
|
||||||
|
|
||||||
@@ -44,76 +37,77 @@ Buffer interface that assures basic functionalities of a buffer.
|
|||||||
*/
|
*/
|
||||||
public interface Buffer {
|
public interface Buffer {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear the pixel in (x, y).
|
* Clear the pixel in (x, y).
|
||||||
*
|
*
|
||||||
* @param x X coordinate
|
* @param x X coordinate
|
||||||
* @param y Y coordinate
|
* @param y Y coordinate
|
||||||
*/
|
*/
|
||||||
void clear(int x, int y);
|
void clear(int x, int y);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draw the pixel in (x, y).
|
* Draw the pixel in (x, y).
|
||||||
*
|
*
|
||||||
* @param x X coordinate
|
* @param x X coordinate
|
||||||
* @param y Y coordinate
|
* @param y Y coordinate
|
||||||
*/
|
*/
|
||||||
void draw(int x, int y);
|
void draw(int x, int y);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear all the pixels.
|
* Clear all the pixels.
|
||||||
*/
|
*/
|
||||||
void clearAll();
|
void clearAll();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all the pixels.
|
* Get all the pixels.
|
||||||
*
|
*
|
||||||
* @return pixel list
|
* @return pixel list
|
||||||
*/
|
*/
|
||||||
Pixel[] getPixels();
|
Pixel[] getPixels();
|
||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
One of the implementation of Buffer interface.
|
One of the implementation of Buffer interface.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
/**
|
/**
|
||||||
* FrameBuffer implementation class.
|
* FrameBuffer implementation class.
|
||||||
*/
|
*/
|
||||||
public class FrameBuffer implements Buffer {
|
public class FrameBuffer implements Buffer {
|
||||||
|
|
||||||
public static final int WIDTH = 10;
|
public static final int WIDTH = 10;
|
||||||
public static final int HEIGHT = 8;
|
public static final int HEIGHT = 8;
|
||||||
|
|
||||||
private final Pixel[] pixels = new Pixel[WIDTH * HEIGHT];
|
private final Pixel[] pixels = new Pixel[WIDTH * HEIGHT];
|
||||||
|
|
||||||
public FrameBuffer() {
|
public FrameBuffer() {
|
||||||
clearAll();
|
clearAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clear(int x, int y) {
|
public void clear(int x, int y) {
|
||||||
pixels[getIndex(x, y)] = Pixel.WHITE;
|
pixels[getIndex(x, y)] = Pixel.WHITE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw(int x, int y) {
|
public void draw(int x, int y) {
|
||||||
pixels[getIndex(x, y)] = Pixel.BLACK;
|
pixels[getIndex(x, y)] = Pixel.BLACK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clearAll() {
|
public void clearAll() {
|
||||||
Arrays.fill(pixels, Pixel.WHITE);
|
Arrays.fill(pixels, Pixel.WHITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Pixel[] getPixels() {
|
public Pixel[] getPixels() {
|
||||||
return pixels;
|
return pixels;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getIndex(int x, int y) {
|
private int getIndex(int x, int y) {
|
||||||
return x + WIDTH * y;
|
return x + WIDTH * y;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -123,11 +117,13 @@ public class FrameBuffer implements Buffer {
|
|||||||
*/
|
*/
|
||||||
public enum Pixel {
|
public enum Pixel {
|
||||||
|
|
||||||
WHITE,
|
WHITE,
|
||||||
BLACK;
|
BLACK;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Scene represents the game scene where current buffer has already been rendered.
|
Scene represents the game scene where current buffer has already been rendered.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
/**
|
/**
|
||||||
* Scene class. Render the output frame.
|
* Scene class. Render the output frame.
|
||||||
@@ -135,93 +131,94 @@ Scene represents the game scene where current buffer has already been rendered.
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class Scene {
|
public class Scene {
|
||||||
|
|
||||||
private final Buffer[] frameBuffers;
|
private final Buffer[] frameBuffers;
|
||||||
|
|
||||||
private int current;
|
private int current;
|
||||||
|
|
||||||
private int next;
|
private int next;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor of Scene.
|
* Constructor of Scene.
|
||||||
*/
|
*/
|
||||||
public Scene() {
|
public Scene() {
|
||||||
frameBuffers = new FrameBuffer[2];
|
frameBuffers = new FrameBuffer[2];
|
||||||
frameBuffers[0] = new FrameBuffer();
|
frameBuffers[0] = new FrameBuffer();
|
||||||
frameBuffers[1] = new FrameBuffer();
|
frameBuffers[1] = new FrameBuffer();
|
||||||
current = 0;
|
current = 0;
|
||||||
next = 1;
|
next = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draw the next frame.
|
* Draw the next frame.
|
||||||
*
|
*
|
||||||
* @param coordinateList list of pixels of which the color should be black
|
* @param coordinateList list of pixels of which the color should be black
|
||||||
*/
|
*/
|
||||||
public void draw(List<? extends Pair<Integer, Integer>> coordinateList) {
|
public void draw(List<? extends Pair<Integer, Integer>> coordinateList) {
|
||||||
LOGGER.info("Start drawing next frame");
|
LOGGER.info("Start drawing next frame");
|
||||||
LOGGER.info("Current buffer: " + current + " Next buffer: " + next);
|
LOGGER.info("Current buffer: " + current + " Next buffer: " + next);
|
||||||
frameBuffers[next].clearAll();
|
frameBuffers[next].clearAll();
|
||||||
coordinateList.forEach(coordinate -> {
|
coordinateList.forEach(coordinate -> {
|
||||||
var x = coordinate.getKey();
|
var x = coordinate.getKey();
|
||||||
var y = coordinate.getValue();
|
var y = coordinate.getValue();
|
||||||
frameBuffers[next].draw(x, y);
|
frameBuffers[next].draw(x, y);
|
||||||
});
|
});
|
||||||
LOGGER.info("Swap current and next buffer");
|
LOGGER.info("Swap current and next buffer");
|
||||||
swap();
|
swap();
|
||||||
LOGGER.info("Finish swapping");
|
LOGGER.info("Finish swapping");
|
||||||
LOGGER.info("Current buffer: " + current + " Next buffer: " + next);
|
LOGGER.info("Current buffer: " + current + " Next buffer: " + next);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Buffer getBuffer() {
|
public Buffer getBuffer() {
|
||||||
LOGGER.info("Get current buffer: " + current);
|
LOGGER.info("Get current buffer: " + current);
|
||||||
return frameBuffers[current];
|
return frameBuffers[current];
|
||||||
}
|
}
|
||||||
|
|
||||||
private void swap() {
|
private void swap() {
|
||||||
current = current ^ next;
|
current = current ^ next;
|
||||||
next = current ^ next;
|
next = current ^ next;
|
||||||
current = current ^ next;
|
current = current ^ next;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public static void main(String[] args) {
|
public static void main(String[]args){
|
||||||
final var scene = new Scene();
|
final var scene=new Scene();
|
||||||
var drawPixels1 = List.of(
|
var drawPixels1=List.of(
|
||||||
new MutablePair<>(1, 1),
|
new MutablePair<>(1,1),
|
||||||
new MutablePair<>(5, 6),
|
new MutablePair<>(5,6),
|
||||||
new MutablePair<>(3, 2)
|
new MutablePair<>(3,2)
|
||||||
);
|
);
|
||||||
scene.draw(drawPixels1);
|
scene.draw(drawPixels1);
|
||||||
var buffer1 = scene.getBuffer();
|
var buffer1=scene.getBuffer();
|
||||||
printBlackPixelCoordinate(buffer1);
|
printBlackPixelCoordinate(buffer1);
|
||||||
|
|
||||||
var drawPixels2 = List.of(
|
var drawPixels2=List.of(
|
||||||
new MutablePair<>(3, 7),
|
new MutablePair<>(3,7),
|
||||||
new MutablePair<>(6, 1)
|
new MutablePair<>(6,1)
|
||||||
);
|
);
|
||||||
scene.draw(drawPixels2);
|
scene.draw(drawPixels2);
|
||||||
var buffer2 = scene.getBuffer();
|
var buffer2=scene.getBuffer();
|
||||||
printBlackPixelCoordinate(buffer2);
|
printBlackPixelCoordinate(buffer2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void printBlackPixelCoordinate(Buffer buffer) {
|
private static void printBlackPixelCoordinate(Buffer buffer){
|
||||||
StringBuilder log = new StringBuilder("Black Pixels: ");
|
StringBuilder log=new StringBuilder("Black Pixels: ");
|
||||||
var pixels = buffer.getPixels();
|
var pixels=buffer.getPixels();
|
||||||
for (var i = 0; i < pixels.length; ++i) {
|
for(var i=0;i<pixels.length;++i){
|
||||||
if (pixels[i] == Pixel.BLACK) {
|
if(pixels[i]==Pixel.BLACK){
|
||||||
var y = i / FrameBuffer.WIDTH;
|
var y=i/FrameBuffer.WIDTH;
|
||||||
var x = i % FrameBuffer.WIDTH;
|
var x=i%FrameBuffer.WIDTH;
|
||||||
log.append(" (").append(x).append(", ").append(y).append(")");
|
log.append(" (").append(x).append(", ").append(y).append(")");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LOGGER.info(log.toString());
|
LOGGER.info(log.toString());
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The console output
|
The console output
|
||||||
|
|
||||||
```text
|
```text
|
||||||
[main] INFO com.iluwatar.doublebuffer.Scene - Start drawing next frame
|
[main] INFO com.iluwatar.doublebuffer.Scene - Start drawing next frame
|
||||||
[main] INFO com.iluwatar.doublebuffer.Scene - Current buffer: 0 Next buffer: 1
|
[main] INFO com.iluwatar.doublebuffer.Scene - Current buffer: 0 Next buffer: 1
|
||||||
@@ -240,16 +237,43 @@ The console output
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Class diagram
|
## Class diagram
|
||||||

|
|
||||||
|

|
||||||
|
|
||||||
## Applicability
|
## Applicability
|
||||||
This pattern is one of those ones where you’ll know when you need it. If you have a system that lacks double buffering, it will probably look visibly wrong (tearing, etc.) or will behave incorrectly. But saying, “you’ll know when you need it” doesn’t give you much to go on. More specifically, this pattern is appropriate when all of these are true:
|
|
||||||
|
|
||||||
- We have some state that is being modified incrementally.
|
* Real-time applications where the display needs to be updated frequently and smoothly, such as video games, simulations, and graphical user interfaces.
|
||||||
- That same state may be accessed in the middle of modification.
|
* Applications requiring high computational resources to prepare data, where the preparation can be done in parallel with data consumption.
|
||||||
- We want to prevent the code that’s accessing the state from seeing the work in progress.
|
* Scenarios where the goal is to minimize the perception of lag or stutter in the display of data or graphics.
|
||||||
- We want to be able to read the state and we don’t want to have to wait while it’s being written.
|
|
||||||
|
## Known Uses
|
||||||
|
|
||||||
|
* Graphics Rendering Engines: Used extensively in 2D and 3D rendering engines to ensure smooth animations and transitions.
|
||||||
|
* User Interface Frameworks: Employed in GUI frameworks to enhance the responsiveness and smoothness of interfaces.
|
||||||
|
* Simulation and Modeling: Utilized in simulations to display real-time updates without interrupting the simulation process.
|
||||||
|
* Video Playback Software: Applied in video players to provide seamless playback by preloading the next frame while the current one is displayed.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
Benefits:
|
||||||
|
|
||||||
|
* Smooth User Experience: Provides a seamless display experience by pre-rendering frames, leading to smoother animations and transitions.
|
||||||
|
* Performance Optimization: Allows intensive rendering or data preparation tasks to be performed in the background, optimizing overall performance.
|
||||||
|
* Minimizes Flickering: Reduces or eliminates flickering and visual artifacts in graphical applications.
|
||||||
|
|
||||||
|
Trade-offs:
|
||||||
|
|
||||||
|
* Memory Overhead: Requires additional memory for the secondary buffer, potentially doubling the memory usage for the buffered data.
|
||||||
|
* Implementation Complexity: Adds complexity to the system architecture, requiring careful management of the two buffers.
|
||||||
|
* Latency: Can introduce a slight delay, as the data must be fully rendered or prepared in the back buffer before being displayed.
|
||||||
|
|
||||||
|
## Related Patterns
|
||||||
|
|
||||||
|
* Triple Buffering: An extension of the Double Buffer pattern, where three buffers are used to further optimize rendering and reduce latency.
|
||||||
|
* [Producer-Consumer](https://java-design-patterns.com/patterns/producer-consumer/): The Double Buffer pattern can be seen as a variant of the Producer-Consumer pattern, with one buffer being "produced" while the other is "consumed".
|
||||||
|
* [Strategy](https://java-design-patterns.com/patterns/strategy/): Often used in conjunction with the Strategy pattern to dynamically choose the buffering strategy based on runtime conditions.
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
* [Game Programming Patterns - Double Buffer](http://gameprogrammingpatterns.com/double-buffer.html)
|
* [Game Programming Patterns - Double Buffer](https://amzn.to/4ayDNkS)
|
||||||
|
* [Real-Time Design Patterns: Robust Scalable Architecture for Real-Time Systems](https://amzn.to/3xFfNxA)
|
||||||
|
|||||||
@@ -30,5 +30,5 @@ package com.iluwatar.doublebuffer;
|
|||||||
public enum Pixel {
|
public enum Pixel {
|
||||||
|
|
||||||
WHITE,
|
WHITE,
|
||||||
BLACK;
|
BLACK
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -219,6 +219,7 @@
|
|||||||
<module>dynamic-proxy</module>
|
<module>dynamic-proxy</module>
|
||||||
<module>gateway</module>
|
<module>gateway</module>
|
||||||
<module>slob</module>
|
<module>slob</module>
|
||||||
|
<module>server-session</module>
|
||||||
</modules>
|
</modules>
|
||||||
<repositories>
|
<repositories>
|
||||||
<repository>
|
<repository>
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ title: Server Session
|
|||||||
category: Behavioral
|
category: Behavioral
|
||||||
language: en
|
language: en
|
||||||
tag:
|
tag:
|
||||||
- Session Management
|
|
||||||
- Session Tracking
|
|
||||||
- Cookies
|
- Cookies
|
||||||
|
- Session management
|
||||||
|
- State tracking
|
||||||
---
|
---
|
||||||
|
|
||||||
## Also known as
|
## Also known as
|
||||||
@@ -20,7 +20,7 @@ Within the context of a client-server relationship, the server is responsible fo
|
|||||||
|
|
||||||
Real-world example
|
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
|
In plain words
|
||||||
|
|
||||||
@@ -179,13 +179,13 @@ Sessions are often given a maximum time in which they will be maintained. The se
|
|||||||
|
|
||||||
## Class diagram
|
## Class diagram
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Applicability
|
## Applicability
|
||||||
|
|
||||||
Use the Adapter pattern when
|
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.
|
* 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 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.
|
* When you want to keep track of user activity and behavior on a website for the sake of analytics purposes.
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -9,19 +9,17 @@
|
|||||||
<artifactId>java-design-patterns</artifactId>
|
<artifactId>java-design-patterns</artifactId>
|
||||||
<version>1.26.0-SNAPSHOT</version>
|
<version>1.26.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>serversession</artifactId>
|
<artifactId>server-session</artifactId>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.junit.jupiter</groupId>
|
<groupId>org.junit.jupiter</groupId>
|
||||||
<artifactId>junit-jupiter-api</artifactId>
|
<artifactId>junit-jupiter-api</artifactId>
|
||||||
<version>5.10.2</version>
|
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.mockito</groupId>
|
<groupId>org.mockito</groupId>
|
||||||
<artifactId>mockito-core</artifactId>
|
<artifactId>mockito-core</artifactId>
|
||||||
<version>5.11.0</version>
|
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package com.iluwatar.sessionserver;
|
package com.iluwatar.sessionserver;
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.HttpServer;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.*;
|
import java.util.HashMap;
|
||||||
import com.sun.net.httpserver.HttpServer;
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,48 +34,54 @@ public class App {
|
|||||||
private static Map<String, Instant> sessionCreationTimes = new HashMap<>();
|
private static Map<String, Instant> sessionCreationTimes = new HashMap<>();
|
||||||
private static final long SESSION_EXPIRATION_TIME = 10000;
|
private static final long SESSION_EXPIRATION_TIME = 10000;
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
/**
|
||||||
// Create HTTP server listening on port 8000
|
* Main entry point.
|
||||||
HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
|
* @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
|
// Set up session management endpoints
|
||||||
server.createContext("/login", new LoginHandler(sessions, sessionCreationTimes));
|
server.createContext("/login", new LoginHandler(sessions, sessionCreationTimes));
|
||||||
server.createContext("/logout", new LogoutHandler(sessions, sessionCreationTimes));
|
server.createContext("/logout", new LogoutHandler(sessions, sessionCreationTimes));
|
||||||
|
|
||||||
// Start the server
|
// Start the server
|
||||||
server.start();
|
server.start();
|
||||||
|
|
||||||
// Start background task to check for expired sessions
|
// Start background task to check for expired sessions
|
||||||
sessionExpirationTask();
|
sessionExpirationTask();
|
||||||
|
|
||||||
LOGGER.info("Server started. Listening on port 8080...");
|
LOGGER.info("Server started. Listening on port 8080...");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void sessionExpirationTask() {
|
private static void sessionExpirationTask() {
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
LOGGER.info("Session expiration checker started...");
|
LOGGER.info("Session expiration checker started...");
|
||||||
Thread.sleep(SESSION_EXPIRATION_TIME); // Sleep for expiration time
|
Thread.sleep(SESSION_EXPIRATION_TIME); // Sleep for expiration time
|
||||||
Instant currentTime = Instant.now();
|
Instant currentTime = Instant.now();
|
||||||
synchronized (sessions) {
|
synchronized (sessions) {
|
||||||
synchronized (sessionCreationTimes) {
|
synchronized (sessionCreationTimes) {
|
||||||
Iterator<Map.Entry<String, Instant>> iterator = sessionCreationTimes.entrySet().iterator();
|
Iterator<Map.Entry<String, Instant>> iterator =
|
||||||
while (iterator.hasNext()) {
|
sessionCreationTimes.entrySet().iterator();
|
||||||
Map.Entry<String, Instant> entry = iterator.next();
|
while (iterator.hasNext()) {
|
||||||
if (entry.getValue().plusMillis(SESSION_EXPIRATION_TIME).isBefore(currentTime)) {
|
Map.Entry<String, Instant> entry = iterator.next();
|
||||||
sessions.remove(entry.getKey());
|
if (entry.getValue().plusMillis(SESSION_EXPIRATION_TIME).isBefore(currentTime)) {
|
||||||
iterator.remove();
|
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();
|
}
|
||||||
}
|
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.HttpExchange;
|
||||||
import com.sun.net.httpserver.HttpHandler;
|
import com.sun.net.httpserver.HttpHandler;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LoginHandler.
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class LoginHandler implements HttpHandler {
|
public class LoginHandler implements HttpHandler {
|
||||||
|
|
||||||
private Map<String, Integer> sessions;
|
private Map<String, Integer> sessions;
|
||||||
private Map<String, Instant> sessionCreationTimes;
|
private Map<String, Instant> sessionCreationTimes;
|
||||||
|
|
||||||
public LoginHandler(Map<String, Integer> sessions, Map<String, Instant> sessionCreationTimes) {
|
public LoginHandler(Map<String, Integer> sessions, Map<String, Instant> sessionCreationTimes) {
|
||||||
this.sessions = sessions;
|
this.sessions = sessions;
|
||||||
this.sessionCreationTimes = sessionCreationTimes;
|
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()) {
|
||||||
@Override
|
os.write(response.getBytes());
|
||||||
public void handle(HttpExchange exchange) {
|
} catch (IOException e) {
|
||||||
// Generate session ID
|
LOGGER.error("An error occurred: ", e);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,58 +2,60 @@ package com.iluwatar.sessionserver;
|
|||||||
|
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
import com.sun.net.httpserver.HttpHandler;
|
import com.sun.net.httpserver.HttpHandler;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LogoutHandler.
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class LogoutHandler implements HttpHandler {
|
public class LogoutHandler implements HttpHandler {
|
||||||
|
|
||||||
private Map<String, Integer> sessions;
|
private Map<String, Integer> sessions;
|
||||||
private Map<String, Instant> sessionCreationTimes;
|
private Map<String, Instant> sessionCreationTimes;
|
||||||
|
|
||||||
public LogoutHandler(Map<String, Integer> sessions, Map<String, Instant> sessionCreationTimes) {
|
public LogoutHandler(Map<String, Integer> sessions, Map<String, Instant> sessionCreationTimes) {
|
||||||
this.sessions = sessions;
|
this.sessions = sessions;
|
||||||
this.sessionCreationTimes = sessionCreationTimes;
|
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
|
//Remove session
|
||||||
public void handle(HttpExchange exchange) {
|
if (currentSessionId != null) {
|
||||||
// Get session ID from cookie
|
LOGGER.info("User " + sessions.get(currentSessionId) + " deleted!");
|
||||||
String sessionID = exchange.getRequestHeaders().getFirst("Cookie").replace("sessionID=", "");
|
} else {
|
||||||
String currentSessionID = sessions.get(sessionID) == null ? null : sessionID;
|
LOGGER.info("User already deleted!");
|
||||||
|
|
||||||
// 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.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.Headers;
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
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.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
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 {
|
public class LoginHandlerTest {
|
||||||
|
|
||||||
private LoginHandler loginHandler;
|
private LoginHandler loginHandler;
|
||||||
//private Headers headers;
|
//private Headers headers;
|
||||||
private Map<String, Integer> sessions;
|
private Map<String, Integer> sessions;
|
||||||
private Map<String, Instant> sessionCreationTimes;
|
private Map<String, Instant> sessionCreationTimes;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private HttpExchange exchange;
|
private HttpExchange exchange;
|
||||||
|
|
||||||
@BeforeEach
|
/**
|
||||||
public void setUp() {
|
* Setup tests.
|
||||||
MockitoAnnotations.initMocks(this);
|
*/
|
||||||
sessions = new HashMap<>();
|
@BeforeEach
|
||||||
sessionCreationTimes = new HashMap<>();
|
public void setUp() {
|
||||||
loginHandler = new LoginHandler(sessions, sessionCreationTimes);
|
MockitoAnnotations.initMocks(this);
|
||||||
}
|
sessions = new HashMap<>();
|
||||||
|
sessionCreationTimes = new HashMap<>();
|
||||||
|
loginHandler = new LoginHandler(sessions, sessionCreationTimes);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testHandle() throws IOException {
|
public void testHandle() throws IOException {
|
||||||
|
|
||||||
//assemble
|
//assemble
|
||||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); //Exchange object is mocked so OutputStream must be manually created
|
ByteArrayOutputStream outputStream =
|
||||||
when(exchange.getResponseHeaders()).thenReturn(new Headers()); //Exchange object is mocked so Header object must be manually created
|
new ByteArrayOutputStream(); //Exchange object is mocked so OutputStream must be manually created
|
||||||
when(exchange.getResponseBody()).thenReturn(outputStream);
|
when(exchange.getResponseHeaders()).thenReturn(
|
||||||
|
new Headers()); //Exchange object is mocked so Header object must be manually created
|
||||||
|
when(exchange.getResponseBody()).thenReturn(outputStream);
|
||||||
|
|
||||||
//act
|
//act
|
||||||
loginHandler.handle(exchange);
|
loginHandler.handle(exchange);
|
||||||
|
|
||||||
//assert
|
//assert
|
||||||
String[] response = outputStream.toString().split("Session ID: ");
|
String[] response = outputStream.toString().split("Session ID: ");
|
||||||
assertEquals(sessions.entrySet().toArray()[0].toString().split("=1")[0], response[1]);
|
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.Headers;
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
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.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
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 {
|
public class LogoutHandlerTest {
|
||||||
|
|
||||||
private LogoutHandler logoutHandler;
|
private LogoutHandler logoutHandler;
|
||||||
private Headers headers;
|
private Headers headers;
|
||||||
private Map<String, Integer> sessions;
|
private Map<String, Integer> sessions;
|
||||||
private Map<String, Instant> sessionCreationTimes;
|
private Map<String, Instant> sessionCreationTimes;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private HttpExchange exchange;
|
private HttpExchange exchange;
|
||||||
|
|
||||||
@BeforeEach
|
/**
|
||||||
public void setUp() {
|
* Setup tests.
|
||||||
MockitoAnnotations.initMocks(this);
|
*/
|
||||||
sessions = new HashMap<>();
|
@BeforeEach
|
||||||
sessionCreationTimes = new HashMap<>();
|
public void setUp() {
|
||||||
logoutHandler = new LogoutHandler(sessions, sessionCreationTimes);
|
MockitoAnnotations.initMocks(this);
|
||||||
headers = new Headers();
|
sessions = new HashMap<>();
|
||||||
headers.add("Cookie", "sessionID=1234"); //Exchange object methods return Header Object but Exchange is mocked so Headers must be manually created
|
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
|
@Test
|
||||||
public void testHandler_SessionNotExpired() throws IOException {
|
public void testHandler_SessionNotExpired() throws IOException {
|
||||||
|
|
||||||
//assemble
|
//assemble
|
||||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
sessions.put("1234", 1); //Fake login details since LoginHandler isn't called
|
||||||
sessions.put("1234", 1); //Fake login details since LoginHandler isn't called
|
sessionCreationTimes.put("1234",
|
||||||
sessionCreationTimes.put("1234", Instant.now()); //Fake login details since LoginHandler isn't called
|
Instant.now()); //Fake login details since LoginHandler isn't called
|
||||||
when(exchange.getRequestHeaders()).thenReturn(headers);
|
when(exchange.getRequestHeaders()).thenReturn(headers);
|
||||||
when(exchange.getResponseBody()).thenReturn(outputStream);
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
when(exchange.getResponseBody()).thenReturn(outputStream);
|
||||||
|
|
||||||
//act
|
//act
|
||||||
logoutHandler.handle(exchange);
|
logoutHandler.handle(exchange);
|
||||||
|
|
||||||
//assert
|
//assert
|
||||||
String[] response = outputStream.toString().split("Session ID: ");
|
String[] response = outputStream.toString().split("Session ID: ");
|
||||||
Assertions.assertEquals("1234", response[1]);
|
Assertions.assertEquals("1234", response[1]);
|
||||||
Assertions.assertFalse(sessions.containsKey(response));
|
Assertions.assertFalse(sessions.containsKey(response));
|
||||||
Assertions.assertFalse(sessionCreationTimes.containsKey(response));
|
Assertions.assertFalse(sessionCreationTimes.containsKey(response));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testHandler_SessionExpired() throws IOException {
|
public void testHandler_SessionExpired() throws IOException {
|
||||||
|
|
||||||
//assemble
|
//assemble
|
||||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
when(exchange.getRequestHeaders()).thenReturn(headers);
|
when(exchange.getRequestHeaders()).thenReturn(headers);
|
||||||
when(exchange.getResponseBody()).thenReturn(outputStream);
|
when(exchange.getResponseBody()).thenReturn(outputStream);
|
||||||
|
|
||||||
//act
|
//act
|
||||||
logoutHandler.handle(exchange);
|
logoutHandler.handle(exchange);
|
||||||
|
|
||||||
//assert
|
//assert
|
||||||
String[] response = outputStream.toString().split("Session ID: ");
|
String[] response = outputStream.toString().split("Session ID: ");
|
||||||
Assertions.assertEquals("Session has already expired!", response[0]);
|
Assertions.assertEquals("Session has already expired!", response[0]);
|
||||||
Assertions.assertFalse(sessions.containsKey(response));
|
Assertions.assertFalse(sessions.containsKey(response));
|
||||||
Assertions.assertFalse(sessionCreationTimes.containsKey(response));
|
Assertions.assertFalse(sessionCreationTimes.containsKey(response));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user