mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Merge branch '7.0.x'
This commit is contained in:
@@ -138,7 +138,6 @@ dependencies {
|
||||
api("org.seleniumhq.selenium:selenium-java:4.41.0")
|
||||
api("org.skyscreamer:jsonassert:1.5.3")
|
||||
api("org.testng:testng:7.12.0")
|
||||
api("org.webjars:underscorejs:1.8.3")
|
||||
api("org.webjars:webjars-locator-lite:1.1.0")
|
||||
api("org.xmlunit:xmlunit-assertj:2.10.4")
|
||||
api("org.xmlunit:xmlunit-matchers:2.10.4")
|
||||
|
||||
@@ -238,7 +238,7 @@ public abstract class MimeTypeUtils {
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (ch == '"') {
|
||||
else if (ch == '"' && mimeType.charAt(nextIndex - 1) != '\\') {
|
||||
quoted = !quoted;
|
||||
}
|
||||
nextIndex++;
|
||||
|
||||
@@ -98,7 +98,7 @@ class MimeTypeTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseQuotedSeparator() {
|
||||
void parseQuotedParameterValue() {
|
||||
String s = "application/xop+xml;charset=utf-8;type=\"application/soap+xml;action=\\\"https://x.y.z\\\"\"";
|
||||
MimeType mimeType = MimeType.valueOf(s);
|
||||
assertThat(mimeType.getType()).as("Invalid type").isEqualTo("application");
|
||||
@@ -107,6 +107,15 @@ class MimeTypeTests {
|
||||
assertThat(mimeType.getParameter("type")).isEqualTo("\"application/soap+xml;action=\\\"https://x.y.z\\\"\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseParameterWithQuotedPair() {
|
||||
String s = "text/plain;twelve=\"1\\\"2\"";
|
||||
MimeType mimeType = MimeType.valueOf(s);
|
||||
assertThat(mimeType.getType()).as("Invalid type").isEqualTo("text");
|
||||
assertThat(mimeType.getSubtype()).as("Invalid subtype").isEqualTo("plain");
|
||||
assertThat(mimeType.getParameter("twelve")).isEqualTo("\"1\\\"2\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void withConversionService() {
|
||||
ConversionService conversionService = new DefaultConversionService();
|
||||
|
||||
@@ -100,20 +100,6 @@ public interface WebSession {
|
||||
*/
|
||||
boolean isStarted();
|
||||
|
||||
/**
|
||||
* Generate a new id for the session and update the underlying session
|
||||
* storage to reflect the new id. After a successful call {@link #getId()}
|
||||
* reflects the new session id.
|
||||
* @return completion notification (success or error)
|
||||
*/
|
||||
Mono<Void> changeSessionId();
|
||||
|
||||
/**
|
||||
* Invalidate the current session and clear session storage.
|
||||
* @return completion notification (success or error)
|
||||
*/
|
||||
Mono<Void> invalidate();
|
||||
|
||||
/**
|
||||
* Save the session through the {@code WebSessionStore} as follows:
|
||||
* <ul>
|
||||
@@ -131,6 +117,20 @@ public interface WebSession {
|
||||
*/
|
||||
Mono<Void> save();
|
||||
|
||||
/**
|
||||
* Generate a new id for the session and update the underlying session
|
||||
* storage to reflect the new id. After a successful call {@link #getId()}
|
||||
* reflects the new session id.
|
||||
* @return completion notification (success or error)
|
||||
*/
|
||||
Mono<Void> changeSessionId();
|
||||
|
||||
/**
|
||||
* Invalidate the current session and clear session storage.
|
||||
* @return completion notification (success or error)
|
||||
*/
|
||||
Mono<Void> invalidate();
|
||||
|
||||
/**
|
||||
* Return {@code true} if the session expired after {@link #getMaxIdleTime()
|
||||
* maxIdleTime} elapsed.
|
||||
|
||||
+41
-26
@@ -207,6 +207,7 @@ public class InMemoryWebSessionStore implements WebSessionStore {
|
||||
|
||||
private final AtomicReference<State> state = new AtomicReference<>(State.NEW);
|
||||
|
||||
private final Lock lock = new ReentrantLock();
|
||||
|
||||
public InMemoryWebSession(Instant creationTime, Duration maxIdleTime) {
|
||||
this.creationTime = creationTime;
|
||||
@@ -256,29 +257,6 @@ public class InMemoryWebSessionStore implements WebSessionStore {
|
||||
return this.state.get().equals(State.STARTED) || !getAttributes().isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> changeSessionId() {
|
||||
return Mono.<Void>defer(() -> {
|
||||
String currentId = this.id.get();
|
||||
InMemoryWebSessionStore.this.sessions.remove(currentId);
|
||||
String newId = String.valueOf(idGenerator.generateId());
|
||||
this.id.set(newId);
|
||||
InMemoryWebSessionStore.this.sessions.put(this.id.get(), this);
|
||||
return Mono.empty();
|
||||
})
|
||||
.subscribeOn(Schedulers.boundedElastic())
|
||||
.publishOn(Schedulers.parallel())
|
||||
.then();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> invalidate() {
|
||||
this.state.set(State.EXPIRED);
|
||||
getAttributes().clear();
|
||||
InMemoryWebSessionStore.this.sessions.remove(this.id.get());
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("NullAway") // Dataflow analysis limitation
|
||||
public Mono<Void> save() {
|
||||
@@ -292,11 +270,19 @@ public class InMemoryWebSessionStore implements WebSessionStore {
|
||||
|
||||
if (isStarted()) {
|
||||
// Save
|
||||
InMemoryWebSessionStore.this.sessions.put(this.id.get(), this);
|
||||
if (InMemoryWebSessionStore.this.sessions.get(getId()) == null) {
|
||||
this.lock.lock();
|
||||
try {
|
||||
InMemoryWebSessionStore.this.sessions.putIfAbsent(getId(), this);
|
||||
}
|
||||
finally {
|
||||
this.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
// Unless it was invalidated
|
||||
if (this.state.get().equals(State.EXPIRED)) {
|
||||
InMemoryWebSessionStore.this.sessions.remove(this.id.get());
|
||||
InMemoryWebSessionStore.this.sessions.remove(getId());
|
||||
return Mono.error(new IllegalStateException("Session was invalidated"));
|
||||
}
|
||||
}
|
||||
@@ -307,12 +293,41 @@ public class InMemoryWebSessionStore implements WebSessionStore {
|
||||
private void checkMaxSessionsLimit() {
|
||||
if (sessions.size() >= maxSessions) {
|
||||
expiredSessionChecker.removeExpiredSessions(clock.instant());
|
||||
if (sessions.size() >= maxSessions && !sessions.containsKey(this.id.get())) {
|
||||
if (sessions.size() >= maxSessions && !sessions.containsKey(getId())) {
|
||||
throw new IllegalStateException("Max sessions limit reached: " + sessions.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> changeSessionId() {
|
||||
return Mono.<Void>defer(() -> {
|
||||
this.lock.lock();
|
||||
try {
|
||||
String oldId = getId();
|
||||
String newId = String.valueOf(idGenerator.generateId());
|
||||
InMemoryWebSessionStore.this.sessions.remove(oldId);
|
||||
InMemoryWebSessionStore.this.sessions.put(newId, this);
|
||||
this.id.set(newId);
|
||||
}
|
||||
finally {
|
||||
this.lock.unlock();
|
||||
}
|
||||
return Mono.empty();
|
||||
})
|
||||
.subscribeOn(Schedulers.boundedElastic())
|
||||
.publishOn(Schedulers.parallel())
|
||||
.then();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> invalidate() {
|
||||
this.state.set(State.EXPIRED);
|
||||
getAttributes().clear();
|
||||
InMemoryWebSessionStore.this.sessions.remove(getId());
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExpired() {
|
||||
return isExpired(clock.instant());
|
||||
|
||||
@@ -61,7 +61,7 @@ dependencies {
|
||||
testRuntimeOnly("org.glassfish:jakarta.el")
|
||||
testRuntimeOnly("org.jruby:jruby")
|
||||
testRuntimeOnly("org.python:jython-standalone")
|
||||
testRuntimeOnly("org.webjars:underscorejs")
|
||||
testRuntimeOnly("org.webjars:momentjs:2.29.4")
|
||||
}
|
||||
|
||||
test {
|
||||
|
||||
+2
-1
@@ -90,7 +90,8 @@ public class LiteWebJarsResourceResolver extends AbstractResourceResolver {
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
String webJarResourcePath = findWebJarResourcePath(resourceUrlPath);
|
||||
if (webJarResourcePath != null) {
|
||||
return chain.resolveUrlPath(webJarResourcePath, locations);
|
||||
Mono<String> fallback = (webJarResourcePath.endsWith("/")) ? Mono.just(webJarResourcePath) : Mono.empty();
|
||||
return chain.resolveUrlPath(webJarResourcePath, locations).switchIfEmpty(fallback);
|
||||
}
|
||||
else {
|
||||
return Mono.empty();
|
||||
|
||||
+22
-6
@@ -29,6 +29,8 @@ import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRe
|
||||
import org.springframework.web.testfixture.server.MockServerWebExchange;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
@@ -80,8 +82,8 @@ class LiteWebJarsResourceResolverTests {
|
||||
|
||||
@Test
|
||||
void resolveUrlWebJarResource() {
|
||||
String file = "underscorejs/underscore.js";
|
||||
String expected = "underscorejs/1.8.3/underscore.js";
|
||||
String file = "momentjs/momentjs.js";
|
||||
String expected = "momentjs/2.29.4/momentjs.js";
|
||||
given(this.chain.resolveUrlPath(file, this.locations)).willReturn(Mono.empty());
|
||||
given(this.chain.resolveUrlPath(expected, this.locations)).willReturn(Mono.just(expected));
|
||||
|
||||
@@ -92,10 +94,24 @@ class LiteWebJarsResourceResolverTests {
|
||||
verify(this.chain, times(1)).resolveUrlPath(expected, this.locations);
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveUrlWebJarDirectory() {
|
||||
String folder = "momentjs/locale/";
|
||||
String expected = "momentjs/2.29.4/locale/";
|
||||
given(this.chain.resolveUrlPath(folder, this.locations)).willReturn(Mono.empty());
|
||||
given(this.chain.resolveUrlPath(expected, this.locations)).willReturn(Mono.empty());
|
||||
|
||||
String actual = this.resolver.resolveUrlPath(folder, this.locations, this.chain).block(TIMEOUT);
|
||||
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
verify(this.chain, times(1)).resolveUrlPath(folder, this.locations);
|
||||
verify(this.chain, times(1)).resolveUrlPath(expected, this.locations);
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveUrlWebJarResourceNotFound() {
|
||||
String file = "something/something.js";
|
||||
given(this.chain.resolveUrlPath(file, this.locations)).willReturn(Mono.empty());
|
||||
String file = "momentjs/locale/unknown.js";
|
||||
given(this.chain.resolveUrlPath(anyString(), eq(this.locations))).willReturn(Mono.empty());
|
||||
|
||||
String actual = this.resolver.resolveUrlPath(file, this.locations, this.chain).block(TIMEOUT);
|
||||
|
||||
@@ -134,11 +150,11 @@ class LiteWebJarsResourceResolverTests {
|
||||
|
||||
@Test
|
||||
void resolveResourceWebJar() {
|
||||
String file = "underscorejs/underscore.js";
|
||||
String file = "momentjs/momentjs.js";
|
||||
given(this.chain.resolveResource(this.exchange, file, this.locations)).willReturn(Mono.empty());
|
||||
|
||||
Resource expected = mock();
|
||||
String expectedPath = "underscorejs/1.8.3/underscore.js";
|
||||
String expectedPath = "momentjs/2.29.4/momentjs.js";
|
||||
given(this.chain.resolveResource(this.exchange, expectedPath, this.locations))
|
||||
.willReturn(Mono.just(expected));
|
||||
|
||||
|
||||
@@ -80,5 +80,5 @@ dependencies {
|
||||
testRuntimeOnly("org.glassfish:jakarta.el")
|
||||
testRuntimeOnly("org.jruby:jruby")
|
||||
testRuntimeOnly("org.python:jython-standalone")
|
||||
testRuntimeOnly("org.webjars:underscorejs")
|
||||
testRuntimeOnly("org.webjars:momentjs:2.29.4")
|
||||
}
|
||||
|
||||
+4
-1
@@ -88,7 +88,10 @@ public class LiteWebJarsResourceResolver extends AbstractResourceResolver {
|
||||
if (path == null) {
|
||||
String webJarResourcePath = findWebJarResourcePath(resourceUrlPath);
|
||||
if (webJarResourcePath != null) {
|
||||
return chain.resolveUrlPath(webJarResourcePath, locations);
|
||||
path = chain.resolveUrlPath(webJarResourcePath, locations);
|
||||
if (path == null && webJarResourcePath.endsWith("/")) {
|
||||
path = webJarResourcePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
return path;
|
||||
|
||||
+22
-6
@@ -26,6 +26,8 @@ import org.springframework.core.io.Resource;
|
||||
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
@@ -74,8 +76,8 @@ class LiteWebJarsResourceResolverTests {
|
||||
|
||||
@Test
|
||||
void resolveUrlWebJarResource() {
|
||||
String file = "underscorejs/underscore.js";
|
||||
String expected = "underscorejs/1.8.3/underscore.js";
|
||||
String file = "momentjs/momentjs.js";
|
||||
String expected = "momentjs/2.29.4/momentjs.js";
|
||||
given(this.chain.resolveUrlPath(file, this.locations)).willReturn(null);
|
||||
given(this.chain.resolveUrlPath(expected, this.locations)).willReturn(expected);
|
||||
|
||||
@@ -86,10 +88,24 @@ class LiteWebJarsResourceResolverTests {
|
||||
verify(this.chain, times(1)).resolveUrlPath(expected, this.locations);
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveUrlWebJarDirectory() {
|
||||
String folder = "momentjs/locale/";
|
||||
String expected = "momentjs/2.29.4/locale/";
|
||||
given(this.chain.resolveUrlPath(folder, this.locations)).willReturn(null);
|
||||
given(this.chain.resolveUrlPath(expected, this.locations)).willReturn(null);
|
||||
|
||||
String actual = this.resolver.resolveUrlPath(folder, this.locations, this.chain);
|
||||
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
verify(this.chain, times(1)).resolveUrlPath(folder, this.locations);
|
||||
verify(this.chain, times(1)).resolveUrlPath(expected, this.locations);
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveUrlWebJarResourceNotFound() {
|
||||
String file = "something/something.js";
|
||||
given(this.chain.resolveUrlPath(file, this.locations)).willReturn(null);
|
||||
String file = "momentjs/locale/unknown.js";
|
||||
given(this.chain.resolveUrlPath(anyString(), eq(this.locations))).willReturn(null);
|
||||
|
||||
String actual = this.resolver.resolveUrlPath(file, this.locations, this.chain);
|
||||
|
||||
@@ -125,8 +141,8 @@ class LiteWebJarsResourceResolverTests {
|
||||
@Test
|
||||
void resolveResourceWebJar() {
|
||||
Resource expected = mock();
|
||||
String file = "underscorejs/underscore.js";
|
||||
String expectedPath = "underscorejs/1.8.3/underscore.js";
|
||||
String file = "momentjs/momentjs.js";
|
||||
String expectedPath = "momentjs/2.29.4/momentjs.js";
|
||||
given(this.chain.resolveResource(this.request, expectedPath, this.locations)).willReturn(expected);
|
||||
|
||||
Resource actual = this.resolver.resolveResource(this.request, file, this.locations, this.chain);
|
||||
|
||||
+2
-2
@@ -24,9 +24,9 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.util.AlternativeJdkIdGenerator;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.IdGenerator;
|
||||
import org.springframework.util.JdkIdGenerator;
|
||||
import org.springframework.web.socket.BinaryMessage;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.PingMessage;
|
||||
@@ -44,7 +44,7 @@ import org.springframework.web.socket.WebSocketSession;
|
||||
*/
|
||||
public abstract class AbstractWebSocketSession<T> implements NativeWebSocketSession {
|
||||
|
||||
protected static final IdGenerator idGenerator = new AlternativeJdkIdGenerator();
|
||||
protected static final IdGenerator idGenerator = new JdkIdGenerator();
|
||||
|
||||
protected static final Log logger = LogFactory.getLog(NativeWebSocketSession.class);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user