Nullability refinements and related polishing
See gh-32475
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -24,9 +24,11 @@ import io.micrometer.common.KeyValues;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.http.client.ClientHttpRequest;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.http.client.observation.ClientHttpObservationDocumentation.HighCardinalityKeyNames;
|
||||
import org.springframework.http.client.observation.ClientHttpObservationDocumentation.LowCardinalityKeyNames;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -84,8 +86,10 @@ public class DefaultClientRequestObservationConvention implements ClientRequestO
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getContextualName(ClientRequestObservationContext context) {
|
||||
return "http " + context.getCarrier().getMethod().name().toLowerCase();
|
||||
ClientHttpRequest request = context.getCarrier();
|
||||
return (request != null ? "http " + request.getMethod().name().toLowerCase() : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -126,9 +126,9 @@ class ReactorNetty2ClientHttpResponse implements ClientHttpResponse {
|
||||
.flatMap(Collection::stream)
|
||||
.forEach(cookie -> result.add(cookie.name().toString(),
|
||||
ResponseCookie.fromClientResponse(cookie.name().toString(), cookie.value().toString())
|
||||
.domain(cookie.domain() != null ? cookie.domain().toString() : null)
|
||||
.path(cookie.path() != null ? cookie.path().toString() : null)
|
||||
.maxAge(cookie.maxAge() != null ? cookie.maxAge() : -1L)
|
||||
.domain(toString(cookie.domain()))
|
||||
.path(toString(cookie.path()))
|
||||
.maxAge(toLong(cookie.maxAge()))
|
||||
.secure(cookie.isSecure())
|
||||
.httpOnly(cookie.isHttpOnly())
|
||||
.sameSite(getSameSite(cookie))
|
||||
@@ -136,6 +136,15 @@ class ReactorNetty2ClientHttpResponse implements ClientHttpResponse {
|
||||
return CollectionUtils.unmodifiableMultiValueMap(result);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String toString(@Nullable CharSequence value) {
|
||||
return (value != null ? value.toString() : null);
|
||||
}
|
||||
|
||||
private static long toLong(@Nullable Long value) {
|
||||
return (value != null ? value : -1);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getSameSite(HttpSetCookie cookie) {
|
||||
if (cookie instanceof DefaultHttpSetCookie defaultCookie && defaultCookie.sameSite() != null) {
|
||||
|
||||
@@ -301,9 +301,10 @@ public class ChannelSendOperator<T> extends Mono<Void> implements Scannable {
|
||||
}
|
||||
|
||||
private boolean emitCachedSignals() {
|
||||
if (this.error != null) {
|
||||
Throwable error = this.error;
|
||||
if (error != null) {
|
||||
try {
|
||||
requiredWriteSubscriber().onError(this.error);
|
||||
requiredWriteSubscriber().onError(error);
|
||||
}
|
||||
finally {
|
||||
releaseCachedItem();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -205,32 +205,32 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNotModified(@Nullable String eTag, long lastModifiedTimestamp) {
|
||||
public boolean checkNotModified(@Nullable String etag, long lastModifiedTimestamp) {
|
||||
HttpServletResponse response = getResponse();
|
||||
if (this.notModified || (response != null && HttpStatus.OK.value() != response.getStatus())) {
|
||||
return this.notModified;
|
||||
}
|
||||
// Evaluate conditions in order of precedence.
|
||||
// See https://datatracker.ietf.org/doc/html/rfc9110#section-13.2.2
|
||||
if (validateIfMatch(eTag)) {
|
||||
updateResponseStateChanging(eTag, lastModifiedTimestamp);
|
||||
if (validateIfMatch(etag)) {
|
||||
updateResponseStateChanging(etag, lastModifiedTimestamp);
|
||||
return this.notModified;
|
||||
}
|
||||
// 2) If-Unmodified-Since
|
||||
else if (validateIfUnmodifiedSince(lastModifiedTimestamp)) {
|
||||
updateResponseStateChanging(eTag, lastModifiedTimestamp);
|
||||
updateResponseStateChanging(etag, lastModifiedTimestamp);
|
||||
return this.notModified;
|
||||
}
|
||||
// 3) If-None-Match
|
||||
if (!validateIfNoneMatch(eTag)) {
|
||||
if (!validateIfNoneMatch(etag)) {
|
||||
// 4) If-Modified-Since
|
||||
validateIfModifiedSince(lastModifiedTimestamp);
|
||||
}
|
||||
updateResponseIdempotent(eTag, lastModifiedTimestamp);
|
||||
updateResponseIdempotent(etag, lastModifiedTimestamp);
|
||||
return this.notModified;
|
||||
}
|
||||
|
||||
private boolean validateIfMatch(@Nullable String eTag) {
|
||||
private boolean validateIfMatch(@Nullable String etag) {
|
||||
if (SAFE_METHODS.contains(getRequest().getMethod())) {
|
||||
return false;
|
||||
}
|
||||
@@ -238,37 +238,37 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
|
||||
if (!ifMatchHeaders.hasMoreElements()) {
|
||||
return false;
|
||||
}
|
||||
this.notModified = matchRequestedETags(ifMatchHeaders, eTag, false);
|
||||
this.notModified = matchRequestedETags(ifMatchHeaders, etag, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean validateIfNoneMatch(@Nullable String eTag) {
|
||||
private boolean validateIfNoneMatch(@Nullable String etag) {
|
||||
Enumeration<String> ifNoneMatchHeaders = getRequest().getHeaders(HttpHeaders.IF_NONE_MATCH);
|
||||
if (!ifNoneMatchHeaders.hasMoreElements()) {
|
||||
return false;
|
||||
}
|
||||
this.notModified = !matchRequestedETags(ifNoneMatchHeaders, eTag, true);
|
||||
this.notModified = !matchRequestedETags(ifNoneMatchHeaders, etag, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean matchRequestedETags(Enumeration<String> requestedETags, @Nullable String eTag, boolean weakCompare) {
|
||||
eTag = padEtagIfNecessary(eTag);
|
||||
private boolean matchRequestedETags(Enumeration<String> requestedETags, @Nullable String etag, boolean weakCompare) {
|
||||
etag = padEtagIfNecessary(etag);
|
||||
while (requestedETags.hasMoreElements()) {
|
||||
// Compare weak/strong ETags as per https://datatracker.ietf.org/doc/html/rfc9110#section-8.8.3
|
||||
Matcher eTagMatcher = ETAG_HEADER_VALUE_PATTERN.matcher(requestedETags.nextElement());
|
||||
while (eTagMatcher.find()) {
|
||||
Matcher etagMatcher = ETAG_HEADER_VALUE_PATTERN.matcher(requestedETags.nextElement());
|
||||
while (etagMatcher.find()) {
|
||||
// only consider "lost updates" checks for unsafe HTTP methods
|
||||
if ("*".equals(eTagMatcher.group()) && StringUtils.hasLength(eTag)
|
||||
if ("*".equals(etagMatcher.group()) && StringUtils.hasLength(etag)
|
||||
&& !SAFE_METHODS.contains(getRequest().getMethod())) {
|
||||
return false;
|
||||
}
|
||||
if (weakCompare) {
|
||||
if (eTagWeakMatch(eTag, eTagMatcher.group(1))) {
|
||||
if (etagWeakMatch(etag, etagMatcher.group(1))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (eTagStrongMatch(eTag, eTagMatcher.group(1))) {
|
||||
if (etagStrongMatch(etag, etagMatcher.group(1))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -288,14 +288,14 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
|
||||
return "\"" + etag + "\"";
|
||||
}
|
||||
|
||||
private boolean eTagStrongMatch(@Nullable String first, @Nullable String second) {
|
||||
private boolean etagStrongMatch(@Nullable String first, @Nullable String second) {
|
||||
if (!StringUtils.hasLength(first) || first.startsWith("W/")) {
|
||||
return false;
|
||||
}
|
||||
return first.equals(second);
|
||||
}
|
||||
|
||||
private boolean eTagWeakMatch(@Nullable String first, @Nullable String second) {
|
||||
private boolean etagWeakMatch(@Nullable String first, @Nullable String second) {
|
||||
if (!StringUtils.hasLength(first) || !StringUtils.hasLength(second)) {
|
||||
return false;
|
||||
}
|
||||
@@ -308,12 +308,12 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
|
||||
return first.equals(second);
|
||||
}
|
||||
|
||||
private void updateResponseStateChanging(@Nullable String eTag, long lastModifiedTimestamp) {
|
||||
private void updateResponseStateChanging(@Nullable String etag, long lastModifiedTimestamp) {
|
||||
if (this.notModified && getResponse() != null) {
|
||||
getResponse().setStatus(HttpStatus.PRECONDITION_FAILED.value());
|
||||
}
|
||||
else {
|
||||
addCachingResponseHeaders(eTag, lastModifiedTimestamp);
|
||||
addCachingResponseHeaders(etag, lastModifiedTimestamp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,24 +340,24 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
|
||||
}
|
||||
}
|
||||
|
||||
private void updateResponseIdempotent(@Nullable String eTag, long lastModifiedTimestamp) {
|
||||
private void updateResponseIdempotent(@Nullable String etag, long lastModifiedTimestamp) {
|
||||
if (getResponse() != null) {
|
||||
boolean isHttpGetOrHead = SAFE_METHODS.contains(getRequest().getMethod());
|
||||
if (this.notModified) {
|
||||
getResponse().setStatus(isHttpGetOrHead ?
|
||||
HttpStatus.NOT_MODIFIED.value() : HttpStatus.PRECONDITION_FAILED.value());
|
||||
}
|
||||
addCachingResponseHeaders(eTag, lastModifiedTimestamp);
|
||||
addCachingResponseHeaders(etag, lastModifiedTimestamp);
|
||||
}
|
||||
}
|
||||
|
||||
private void addCachingResponseHeaders(@Nullable String eTag, long lastModifiedTimestamp) {
|
||||
if (SAFE_METHODS.contains(getRequest().getMethod())) {
|
||||
private void addCachingResponseHeaders(@Nullable String etag, long lastModifiedTimestamp) {
|
||||
if (getResponse() != null && SAFE_METHODS.contains(getRequest().getMethod())) {
|
||||
if (lastModifiedTimestamp > 0 && parseDateValue(getResponse().getHeader(HttpHeaders.LAST_MODIFIED)) == -1) {
|
||||
getResponse().setDateHeader(HttpHeaders.LAST_MODIFIED, lastModifiedTimestamp);
|
||||
}
|
||||
if (StringUtils.hasLength(eTag) && getResponse().getHeader(HttpHeaders.ETAG) == null) {
|
||||
getResponse().setHeader(HttpHeaders.ETAG, padEtagIfNecessary(eTag));
|
||||
if (StringUtils.hasLength(etag) && getResponse().getHeader(HttpHeaders.ETAG) == null) {
|
||||
getResponse().setHeader(HttpHeaders.ETAG, padEtagIfNecessary(etag));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -68,6 +68,7 @@ class CallableInterceptorChain {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Object applyPostProcess(NativeWebRequest request, Callable<?> task, @Nullable Object concurrentResult) {
|
||||
Throwable exceptionResult = null;
|
||||
for (int i = this.preProcessIndex; i >= 0; i--) {
|
||||
@@ -86,7 +87,7 @@ class CallableInterceptorChain {
|
||||
}
|
||||
}
|
||||
}
|
||||
return (exceptionResult != null) ? exceptionResult : concurrentResult;
|
||||
return (exceptionResult != null ? exceptionResult : concurrentResult);
|
||||
}
|
||||
|
||||
public Object triggerAfterTimeout(NativeWebRequest request, Callable<?> task) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -96,7 +96,7 @@ public class DeferredResult<T> {
|
||||
* timeout depends on the default of the underlying server.
|
||||
* @param timeoutValue timeout value in milliseconds
|
||||
*/
|
||||
public DeferredResult(Long timeoutValue) {
|
||||
public DeferredResult(@Nullable Long timeoutValue) {
|
||||
this(timeoutValue, () -> RESULT_NONE);
|
||||
}
|
||||
|
||||
@@ -239,11 +239,11 @@ public class DeferredResult<T> {
|
||||
* {@code false} if the result was already set or the async request expired
|
||||
* @see #isSetOrExpired()
|
||||
*/
|
||||
public boolean setResult(T result) {
|
||||
public boolean setResult(@Nullable T result) {
|
||||
return setResultInternal(result);
|
||||
}
|
||||
|
||||
private boolean setResultInternal(Object result) {
|
||||
private boolean setResultInternal(@Nullable Object result) {
|
||||
// Immediate expiration check outside of the result lock
|
||||
if (isSetOrExpired()) {
|
||||
return false;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -37,7 +37,6 @@ import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
|
||||
|
||||
|
||||
/**
|
||||
* {@link org.springframework.web.server.WebFilter} that creates {@link Observation observations}
|
||||
* for HTTP exchanges. This collects information about the execution time and
|
||||
@@ -160,14 +159,16 @@ public class ServerHttpObservationFilter implements WebFilter {
|
||||
|
||||
private void doOnTerminate(ServerRequestObservationContext context) {
|
||||
ServerHttpResponse response = context.getResponse();
|
||||
if (response.isCommitted()) {
|
||||
this.observation.stop();
|
||||
}
|
||||
else {
|
||||
response.beforeCommit(() -> {
|
||||
if (response != null) {
|
||||
if (response.isCommitted()) {
|
||||
this.observation.stop();
|
||||
return Mono.empty();
|
||||
});
|
||||
}
|
||||
else {
|
||||
response.beforeCommit(() -> {
|
||||
this.observation.stop();
|
||||
return Mono.empty();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.web.method.annotation;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -284,7 +285,10 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
|
||||
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
|
||||
Class<?> parameterType = parameter.getParameterType();
|
||||
if (KotlinDetector.isKotlinPresent() && KotlinDetector.isInlineClass(parameterType)) {
|
||||
parameterType = BeanUtils.findPrimaryConstructor(parameterType).getParameterTypes()[0];
|
||||
Constructor<?> ctor = BeanUtils.findPrimaryConstructor(parameterType);
|
||||
if (ctor != null) {
|
||||
parameterType = ctor.getParameterTypes()[0];
|
||||
}
|
||||
}
|
||||
try {
|
||||
arg = binder.convertIfNecessary(arg, parameterType, parameter);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -414,14 +414,16 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa
|
||||
|
||||
private void doOnTerminate(ServerRequestObservationContext context) {
|
||||
ServerHttpResponse response = context.getResponse();
|
||||
if (response.isCommitted()) {
|
||||
this.observation.stop();
|
||||
}
|
||||
else {
|
||||
response.beforeCommit(() -> {
|
||||
if (response != null) {
|
||||
if (response.isCommitted()) {
|
||||
this.observation.stop();
|
||||
return Mono.empty();
|
||||
});
|
||||
}
|
||||
else {
|
||||
response.beforeCommit(() -> {
|
||||
this.observation.stop();
|
||||
return Mono.empty();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -134,7 +134,7 @@ class RegexPathElement extends PathElement {
|
||||
if (matches) {
|
||||
if (isNoMorePattern()) {
|
||||
if (matchingContext.determineRemainingPath &&
|
||||
(this.variableNames.isEmpty() || textToMatch.length() > 0)) {
|
||||
(this.variableNames.isEmpty() || !textToMatch.isEmpty())) {
|
||||
matchingContext.remainingPathIndex = pathIndex + 1;
|
||||
matches = true;
|
||||
}
|
||||
@@ -142,9 +142,9 @@ class RegexPathElement extends PathElement {
|
||||
// No more pattern, is there more data?
|
||||
// If pattern is capturing variables there must be some actual data to bind to them
|
||||
matches = (pathIndex + 1 >= matchingContext.pathLength) &&
|
||||
(this.variableNames.isEmpty() || textToMatch.length() > 0);
|
||||
(this.variableNames.isEmpty() || !textToMatch.isEmpty());
|
||||
if (!matches && matchingContext.isMatchOptionalTrailingSeparator()) {
|
||||
matches = (this.variableNames.isEmpty() || textToMatch.length() > 0) &&
|
||||
matches = (this.variableNames.isEmpty() || !textToMatch.isEmpty()) &&
|
||||
(pathIndex + 2 >= matchingContext.pathLength) &&
|
||||
matchingContext.isSeparator(pathIndex + 1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user