Polish WebSession support and tests
This commit is contained in:
parent
7bb19fcde8
commit
222702f750
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -107,12 +107,12 @@ public interface ServerWebExchange {
|
|||
}
|
||||
|
||||
/**
|
||||
* Return the web session for the current request. Always guaranteed to
|
||||
* return an instance either matching to the session id requested by the
|
||||
* client, or with a new session id either because the client did not
|
||||
* specify one or because the underlying session had expired. Use of this
|
||||
* method does not automatically create a session. See {@link WebSession}
|
||||
* for more details.
|
||||
* Return the web session for the current request.
|
||||
* <p>Always guaranteed to return either an instance matching the session id
|
||||
* requested by the client, or a new session either because the client did not
|
||||
* specify a session id or because the underlying session expired.
|
||||
* <p>Use of this method does not automatically create a session. See
|
||||
* {@link WebSession} for more details.
|
||||
*/
|
||||
Mono<WebSession> getSession();
|
||||
|
||||
|
|
|
|||
|
|
@ -79,9 +79,9 @@ public class InMemoryWebSessionStore implements WebSessionStore {
|
|||
}
|
||||
|
||||
/**
|
||||
* Configure the {@link Clock} to use to set lastAccessTime on every created
|
||||
* session and to calculate if it is expired.
|
||||
* <p>This may be useful to align to different timezone or to set the clock
|
||||
* Configure the {@link Clock} to use to set the {@code lastAccessTime} on
|
||||
* every created session and to calculate if the session has expired.
|
||||
* <p>This may be useful to align to different time zones or to set the clock
|
||||
* back in a test, for example, {@code Clock.offset(clock, Duration.ofMinutes(-31))}
|
||||
* in order to simulate session expiration.
|
||||
* <p>By default this is {@code Clock.system(ZoneId.of("GMT"))}.
|
||||
|
|
@ -94,16 +94,17 @@ public class InMemoryWebSessionStore implements WebSessionStore {
|
|||
}
|
||||
|
||||
/**
|
||||
* Return the configured clock for session lastAccessTime calculations.
|
||||
* Return the configured clock for session {@code lastAccessTime} calculations.
|
||||
*/
|
||||
public Clock getClock() {
|
||||
return this.clock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the map of sessions with an {@link Collections#unmodifiableMap
|
||||
* unmodifiable} wrapper. This could be used for management purposes, to
|
||||
* list active sessions, invalidate expired ones, etc.
|
||||
* Return an {@linkplain Collections#unmodifiableMap unmodifiable} copy of the
|
||||
* map of sessions.
|
||||
* <p>This could be used for management purposes, to list active sessions,
|
||||
* to invalidate expired sessions, etc.
|
||||
* @since 5.0.8
|
||||
*/
|
||||
public Map<String, WebSession> getSessions() {
|
||||
|
|
@ -157,10 +158,11 @@ public class InMemoryWebSessionStore implements WebSessionStore {
|
|||
}
|
||||
|
||||
/**
|
||||
* Check for expired sessions and remove them. Typically such checks are
|
||||
* kicked off lazily during calls to {@link #createWebSession() create} or
|
||||
* {@link #retrieveSession retrieve}, no less than 60 seconds apart.
|
||||
* This method can be called to force a check at a specific time.
|
||||
* Check for expired sessions and remove them.
|
||||
* <p>Typically such checks are kicked off lazily during calls to
|
||||
* {@link #createWebSession()} or {@link #retrieveSession}, no less than 60
|
||||
* seconds apart.
|
||||
* <p>This method can be called to force a check at a specific time.
|
||||
* @since 5.0.8
|
||||
*/
|
||||
public void removeExpiredSessions() {
|
||||
|
|
|
|||
|
|
@ -32,10 +32,10 @@ import org.springframework.web.server.WebSession;
|
|||
public interface WebSessionManager {
|
||||
|
||||
/**
|
||||
* Return the {@link WebSession} for the given exchange. Always guaranteed
|
||||
* to return an instance either matching to the session id requested by the
|
||||
* client, or a new session either because the client did not specify one
|
||||
* or because the underlying session expired.
|
||||
* Return the {@link WebSession} for the given exchange.
|
||||
* <p>Always guaranteed to return either an instance matching the session id
|
||||
* requested by the client, or a new session either because the client did not
|
||||
* specify a session id or because the underlying session expired.
|
||||
* @param exchange the current exchange
|
||||
* @return promise for the WebSession
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -43,7 +43,7 @@ public interface WebSessionStore {
|
|||
* Return the WebSession for the given id.
|
||||
* <p><strong>Note:</strong> This method should perform an expiration check,
|
||||
* and if it has expired remove the session and return empty. This method
|
||||
* should also update the lastAccessTime of retrieved sessions.
|
||||
* should also update the {@code lastAccessTime} of retrieved sessions.
|
||||
* @param sessionId the session to load
|
||||
* @return the session, or an empty {@code Mono}
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -19,7 +19,6 @@ package org.springframework.web.server.session;
|
|||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
|
@ -35,10 +34,11 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
|||
* Tests for {@link InMemoryWebSessionStore}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
class InMemoryWebSessionStoreTests {
|
||||
|
||||
private InMemoryWebSessionStore store = new InMemoryWebSessionStore();
|
||||
private final InMemoryWebSessionStore store = new InMemoryWebSessionStore();
|
||||
|
||||
|
||||
@Test
|
||||
|
|
@ -59,7 +59,7 @@ class InMemoryWebSessionStoreTests {
|
|||
}
|
||||
|
||||
@Test // gh-24027, gh-26958
|
||||
public void createSessionDoesNotBlock() {
|
||||
void createSessionDoesNotBlock() {
|
||||
this.store.createWebSession()
|
||||
.doOnNext(session -> assertThat(Schedulers.isInNonBlockingThread()).isTrue())
|
||||
.block();
|
||||
|
|
@ -103,7 +103,7 @@ class InMemoryWebSessionStoreTests {
|
|||
}
|
||||
|
||||
@Test // SPR-17051
|
||||
public void sessionInvalidatedBeforeSave() {
|
||||
void sessionInvalidatedBeforeSave() {
|
||||
// Request 1 creates session
|
||||
WebSession session1 = this.store.createWebSession().block();
|
||||
assertThat(session1).isNotNull();
|
||||
|
|
@ -132,33 +132,31 @@ class InMemoryWebSessionStoreTests {
|
|||
|
||||
@Test
|
||||
void expirationCheckPeriod() {
|
||||
|
||||
DirectFieldAccessor accessor = new DirectFieldAccessor(this.store);
|
||||
Map<?,?> sessions = (Map<?, ?>) accessor.getPropertyValue("sessions");
|
||||
assertThat(sessions).isNotNull();
|
||||
|
||||
// Create 100 sessions
|
||||
IntStream.range(0, 100).forEach(i -> insertSession());
|
||||
assertThat(sessions).hasSize(100);
|
||||
IntStream.rangeClosed(1, 100).forEach(i -> insertSession());
|
||||
assertNumSessions(100);
|
||||
|
||||
// Force a new clock (31 min later), don't use setter which would clean expired sessions
|
||||
// Force a new clock (31 min later). Don't use setter which would clean expired sessions.
|
||||
DirectFieldAccessor accessor = new DirectFieldAccessor(this.store);
|
||||
accessor.setPropertyValue("clock", Clock.offset(this.store.getClock(), Duration.ofMinutes(31)));
|
||||
assertThat(sessions).hasSize(100);
|
||||
assertNumSessions(100);
|
||||
|
||||
// Create 1 more which forces a time-based check (clock moved forward)
|
||||
// Create 1 more which forces a time-based check (clock moved forward).
|
||||
insertSession();
|
||||
assertThat(sessions).hasSize(1);
|
||||
assertNumSessions(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void maxSessions() {
|
||||
this.store.setMaxSessions(10);
|
||||
|
||||
IntStream.range(0, 10000).forEach(i -> insertSession());
|
||||
assertThatIllegalStateException().isThrownBy(
|
||||
this::insertSession)
|
||||
.withMessage("Max sessions limit reached: 10000");
|
||||
IntStream.rangeClosed(1, 10).forEach(i -> insertSession());
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(this::insertSession)
|
||||
.withMessage("Max sessions limit reached: 10");
|
||||
}
|
||||
|
||||
|
||||
private WebSession insertSession() {
|
||||
WebSession session = this.store.createWebSession().block();
|
||||
assertThat(session).isNotNull();
|
||||
|
|
@ -167,4 +165,8 @@ class InMemoryWebSessionStoreTests {
|
|||
return session;
|
||||
}
|
||||
|
||||
private void assertNumSessions(int numSessions) {
|
||||
assertThat(store.getSessions()).hasSize(numSessions);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue