Copilot commented on code in PR #7870:
URL: https://github.com/apache/incubator-seata/pull/7870#discussion_r2630202117
##########
namingserver/src/main/java/org/apache/seata/namingserver/filter/ConsoleRemotingFilter.java:
##########
@@ -113,46 +109,34 @@ public void doFilter(ServletRequest servletRequest,
ServletResponse servletRespo
// Create the HttpEntity with headers and body
HttpEntity<byte[]> httpEntity = new
HttpEntity<>(request.getCachedBody(), headers);
-
- // Forward the request
- AsyncContext asyncContext =
servletRequest.startAsync();
- asyncContext.setTimeout(5000L);
- ListenableFuture<ResponseEntity<byte[]>>
responseEntityFuture = asyncRestTemplate.exchange(
- URI.create(targetUrl),
-
Objects.requireNonNull(HttpMethod.resolve(request.getMethod())),
- httpEntity,
- byte[].class);
- responseEntityFuture.addCallback(new
ListenableFutureCallback<ResponseEntity<byte[]>>() {
- @Override
- public void onFailure(Throwable ex) {
- try {
- logger.error(ex.getMessage(), ex);
-
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
- } finally {
- asyncContext.complete();
+ HttpMethod httpMethod;
+ try {
+ httpMethod =
HttpMethod.valueOf(request.getMethod());
+ } catch (IllegalArgumentException ex) {
+ logger.error("Unsupported HTTP method: {}",
request.getMethod(), ex);
+
response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+ return;
+ }
+ try {
+ ResponseEntity<byte[]> responseEntity =
restTemplate.exchange(
+ URI.create(targetUrl), httpMethod,
httpEntity, byte[].class);
+ responseEntity.getHeaders().forEach((key,
value) -> {
+ value.forEach(v -> response.addHeader(key,
v));
+ });
+ response.setStatus(
+
responseEntity.getStatusCode().value());
+
Optional.ofNullable(responseEntity.getBody()).ifPresent(body -> {
+ try (ServletOutputStream outputStream =
response.getOutputStream()) {
+ outputStream.write(body);
+ outputStream.flush();
+ } catch (IOException e) {
+ logger.error(e.getMessage(), e);
}
- }
-
- @Override
- public void onSuccess(ResponseEntity<byte[]>
responseEntity) {
- // Copy response headers and status code
- responseEntity.getHeaders().forEach((key,
value) -> {
- value.forEach(v ->
response.addHeader(key, v));
- });
-
response.setStatus(responseEntity.getStatusCodeValue());
- // Write response body
-
Optional.ofNullable(responseEntity.getBody())
- .ifPresent(body -> {
- try (ServletOutputStream
outputStream = response.getOutputStream()) {
- outputStream.write(body);
- outputStream.flush();
- } catch (IOException e) {
-
logger.error(e.getMessage(), e);
- }
- });
- asyncContext.complete();
- }
- });
+ });
+ } catch (Exception ex) {
+ logger.error(ex.getMessage(), ex);
+
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ }
Review Comment:
The significant refactoring of ConsoleRemotingFilter from AsyncRestTemplate
to RestTemplate changes the core request forwarding mechanism. This change
lacks test coverage to verify that the synchronous RestTemplate with virtual
threads provides equivalent functionality to the previous asynchronous
implementation. Consider adding tests to verify proper request forwarding,
error handling, and timeout behavior.
##########
console/pom.xml:
##########
@@ -121,7 +108,7 @@
<exclusions>
<exclusion>
<groupId>org.apache.tomcat.embed</groupId>
- <artifactId>tomcat-embed-core</artifactId>
+ <artifactId>*</artifactId>
</exclusion>
<exclusion>
Review Comment:
The wildcard exclusion pattern for tomcat-embed artifacts may be too broad.
While excluding all tomcat-embed artifacts (using *) ensures no conflicts,
verify that this doesn't accidentally exclude required embedded Tomcat
components that might be needed by spring-boot-starter-web. Consider being more
specific about which artifacts to exclude if issues arise.
```suggestion
<artifactId>tomcat-embed-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-websocket</artifactId>
</exclusion>
<exclusion>
```
##########
namingserver/src/main/java/org/apache/seata/namingserver/config/WebConfig.java:
##########
@@ -16,22 +16,19 @@
*/
package org.apache.seata.namingserver.config;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClients;
-import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import jakarta.servlet.Filter;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
Review Comment:
The migration from org.apache.httpcomponents:httpclient (HttpClient 4.x) to
org.apache.httpcomponents.client5:httpclient5 (HttpClient 5.x) involves
significant API changes. The imports have been correctly updated to use
org.apache.hc.client5 packages. However, ensure that the
HttpComponentsClientHttpRequestFactory from Spring is compatible with
HttpClient 5.x at runtime, as Spring's HttpComponentsClientHttpRequestFactory
may have different configuration methods for HttpClient 5.
##########
namingserver/src/main/java/org/apache/seata/namingserver/config/WebConfig.java:
##########
@@ -56,21 +53,10 @@ public RestTemplate restTemplate() {
return new RestTemplate(requestFactory);
}
- @Bean
- public AsyncRestTemplate asyncRestTemplate(RestTemplate restTemplate) {
- HttpComponentsAsyncClientHttpRequestFactory
asyncClientHttpRequestFactory =
- new HttpComponentsAsyncClientHttpRequestFactory();
- asyncClientHttpRequestFactory.setConnectionRequestTimeout(
- DEFAULT_REQUEST_TIMEOUT); // Connection request timeout in
milliseconds
-
asyncClientHttpRequestFactory.setConnectTimeout(DEFAULT_REQUEST_TIMEOUT); //
Connection timeout in milliseconds
- asyncClientHttpRequestFactory.setReadTimeout(DEFAULT_REQUEST_TIMEOUT);
// Read timeout in milliseconds
- return new AsyncRestTemplate(asyncClientHttpRequestFactory,
restTemplate);
- }
-
@Bean
public FilterRegistrationBean<Filter> consoleRemotingFilter(
- NamingManager namingManager, AsyncRestTemplate asyncRestTemplate) {
- ConsoleRemotingFilter consoleRemotingFilter = new
ConsoleRemotingFilter(namingManager, asyncRestTemplate);
+ NamingManager namingManager, RestTemplate restTemplate) {
+ ConsoleRemotingFilter consoleRemotingFilter = new
ConsoleRemotingFilter(namingManager, restTemplate);
FilterRegistrationBean<Filter> registration = new
FilterRegistrationBean<>();
registration.setFilter(consoleRemotingFilter);
registration.addUrlPatterns("/*");
Review Comment:
The removal of AsyncRestTemplate and the corresponding asyncRestTemplate
bean removes the original asynchronous request handling capability. While the
PR description mentions using virtual threads for lightweight blocking, the
synchronous RestTemplate call in ConsoleRemotingFilter will now block the
request thread even with virtual threads enabled. Consider if this blocking
behavior is acceptable for the use case, as it changes from a truly
asynchronous callback model to a blocking model (even if on virtual threads).
```suggestion
registration.addUrlPatterns("/console/*");
```
##########
namingserver/src/main/java/org/apache/seata/namingserver/filter/ConsoleRemotingFilter.java:
##########
@@ -113,46 +109,34 @@ public void doFilter(ServletRequest servletRequest,
ServletResponse servletRespo
// Create the HttpEntity with headers and body
HttpEntity<byte[]> httpEntity = new
HttpEntity<>(request.getCachedBody(), headers);
-
- // Forward the request
- AsyncContext asyncContext =
servletRequest.startAsync();
- asyncContext.setTimeout(5000L);
- ListenableFuture<ResponseEntity<byte[]>>
responseEntityFuture = asyncRestTemplate.exchange(
- URI.create(targetUrl),
-
Objects.requireNonNull(HttpMethod.resolve(request.getMethod())),
- httpEntity,
- byte[].class);
- responseEntityFuture.addCallback(new
ListenableFutureCallback<ResponseEntity<byte[]>>() {
- @Override
- public void onFailure(Throwable ex) {
- try {
- logger.error(ex.getMessage(), ex);
-
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
- } finally {
- asyncContext.complete();
+ HttpMethod httpMethod;
+ try {
+ httpMethod =
HttpMethod.valueOf(request.getMethod());
+ } catch (IllegalArgumentException ex) {
+ logger.error("Unsupported HTTP method: {}",
request.getMethod(), ex);
+
response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+ return;
+ }
+ try {
+ ResponseEntity<byte[]> responseEntity =
restTemplate.exchange(
+ URI.create(targetUrl), httpMethod,
httpEntity, byte[].class);
+ responseEntity.getHeaders().forEach((key,
value) -> {
+ value.forEach(v -> response.addHeader(key,
v));
+ });
+ response.setStatus(
+
responseEntity.getStatusCode().value());
+
Optional.ofNullable(responseEntity.getBody()).ifPresent(body -> {
+ try (ServletOutputStream outputStream =
response.getOutputStream()) {
+ outputStream.write(body);
+ outputStream.flush();
+ } catch (IOException e) {
+ logger.error(e.getMessage(), e);
}
- }
-
- @Override
- public void onSuccess(ResponseEntity<byte[]>
responseEntity) {
- // Copy response headers and status code
- responseEntity.getHeaders().forEach((key,
value) -> {
- value.forEach(v ->
response.addHeader(key, v));
- });
-
response.setStatus(responseEntity.getStatusCodeValue());
- // Write response body
-
Optional.ofNullable(responseEntity.getBody())
- .ifPresent(body -> {
- try (ServletOutputStream
outputStream = response.getOutputStream()) {
- outputStream.write(body);
- outputStream.flush();
- } catch (IOException e) {
-
logger.error(e.getMessage(), e);
- }
- });
- asyncContext.complete();
- }
- });
+ });
+ } catch (Exception ex) {
+ logger.error(ex.getMessage(), ex);
+
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ }
Review Comment:
The migration from AsyncRestTemplate with callback-based asynchronous
processing to synchronous RestTemplate removes the async context management
(startAsync/complete). The original code used asyncContext.setTimeout(5000L)
and properly completed the async context. With the new synchronous approach,
the servlet filter chain will be blocked during the entire HTTP
request/response cycle. While virtual threads help mitigate this, the behavior
change from non-blocking asynchronous to blocking (even on virtual threads) is
significant and should be carefully validated in production scenarios with high
concurrency.
##########
namingserver/src/main/resources/application.yml:
##########
@@ -18,6 +18,12 @@
server:
port: 8081
spring:
+ threads:
+ virtual:
+ enabled: true
+ mvc:
+ pathmatch:
Review Comment:
The PathMatchingStrategy configuration using 'ant_path_matcher' forces
Spring MVC to use the legacy AntPathMatcher instead of the newer
PathPatternParser (default in Spring Boot 3.x). While this maintains backward
compatibility with patterns like /**/*.css, be aware that PathPatternParser is
more efficient. Consider migrating path patterns to be compatible with
PathPatternParser in the future for better performance, or document why the
legacy matcher is required.
```suggestion
pathmatch:
# Use legacy AntPathMatcher for compatibility with existing patterns
like /**/*.css, /**/*.js, etc.
# Switching to PathPatternParser (Spring Boot 3.x default) would
require updating these patterns.
```
##########
console/src/main/java/org/apache/seata/console/config/WebSecurityConfig.java:
##########
@@ -79,60 +84,60 @@ public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {
@Autowired
private Environment env;
- @Bean(name = BeanIds.AUTHENTICATION_MANAGER)
- @Override
- public AuthenticationManager authenticationManagerBean() throws Exception {
- return super.authenticationManagerBean();
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
}
- @Override
- protected void configure(AuthenticationManagerBuilder auth) throws
Exception {
-
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
+ @Bean
+ public AuthenticationManager authenticationManager() {
+ DaoAuthenticationProvider authenticationProvider = new
DaoAuthenticationProvider(userDetailsService);
+ authenticationProvider.setPasswordEncoder(passwordEncoder());
+ return new ProviderManager(authenticationProvider);
}
- @Override
- public void configure(WebSecurity web) {
- String ignoreURLs = env.getProperty("seata.security.ignore.urls",
"/**");
- for (String ignoreURL :
ignoreURLs.trim().split(SECURITY_IGNORE_URLS_SPILT_CHAR)) {
- web.ignoring().antMatchers(ignoreURL.trim());
- }
+ @Bean
+ public WebSecurityCustomizer webSecurityCustomizer() {
+ RequestMatcher[] ignoredMatchers =
buildAntMatchers(env.getProperty("seata.security.ignore.urls", "/**"));
+ return web -> {
+ if (ignoredMatchers.length > 0) {
+ web.ignoring().requestMatchers(ignoredMatchers);
+ }
+ };
}
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- String csrfIgnoreUrls =
env.getProperty("seata.security.csrf-ignore-urls");
- CsrfConfigurer<HttpSecurity> csrf = http.authorizeRequests()
- .anyRequest()
- .authenticated()
- .and()
- // custom token authorize exception handler
- .exceptionHandling()
- .authenticationEntryPoint(unauthorizedHandler)
- .and()
- // since we use jwt, session is not necessary
- .sessionManagement()
- .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
- .disable()
- .csrf();
- if (StringUtils.isNotBlank(csrfIgnoreUrls)) {
-
csrf.ignoringAntMatchers(csrfIgnoreUrls.trim().split(SECURITY_IGNORE_URLS_SPILT_CHAR));
- }
-
csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
- // don't disable csrf, jwt may be implemented based on cookies
- http.addFilterBefore(
- new JwtAuthenticationTokenFilter(tokenProvider),
UsernamePasswordAuthenticationFilter.class);
-
- // disable cache
- http.headers().cacheControl();
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity http,
AuthenticationManager authenticationManager)
+ throws Exception {
+ RequestMatcher[] csrfIgnored =
buildAntMatchers(env.getProperty("seata.security.csrf-ignore-urls"));
+
+ http.authenticationManager(authenticationManager)
+ .authorizeHttpRequests(authz ->
authz.anyRequest().authenticated())
+ .exceptionHandling(exception ->
exception.authenticationEntryPoint(unauthorizedHandler))
+ .sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+ .csrf(csrf -> {
+ if (csrfIgnored.length > 0) {
+ csrf.ignoringRequestMatchers(csrfIgnored);
+ }
+
csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
+ })
+ .addFilterBefore(
+ new JwtAuthenticationTokenFilter(tokenProvider),
UsernamePasswordAuthenticationFilter.class)
+ .headers(headers -> headers.cacheControl(cache -> {}));
+
+ return http.build();
}
- /**
- * Password encoder password encoder.
- *
- * @return the password encoder
- */
- @Bean
- public PasswordEncoder passwordEncoder() {
- return new BCryptPasswordEncoder();
+ private RequestMatcher[] buildAntMatchers(String patterns) {
+ if (StringUtils.isBlank(patterns)) {
+ return new RequestMatcher[0];
+ }
+ return
Arrays.stream(patterns.trim().split(SECURITY_IGNORE_URLS_SPILT_CHAR))
+ .map(String::trim)
+ .filter(StringUtils::isNotBlank)
+ // PathPatternParser using the new version of Security cannot
directly achieve the same matching effect
+ // as the deprecated Ant style mode /**/*.css
Review Comment:
The comment explaining why AntPathRequestMatcher is used is helpful.
However, the explanation could be clearer. The issue is that Spring Security
6.x uses PathPatternParser by default, which has stricter matching rules than
the legacy Ant style matcher. Patterns like /**/*.css work differently between
the two. Consider adding a reference or example to make this clearer for future
maintainers.
```suggestion
// In Spring Security 6.x, requestMatchers(String...) uses
PathPatternParser by default, which has
// stricter matching rules than the legacy Ant-style
matcher. For example, the Ant pattern "/**/*.css"
// (often used for static resources) is not interpreted the
same way by PathPatternParser and may no
// longer match all nested paths as expected. We therefore
use AntPathRequestMatcher explicitly so
// that ignore / CSRF patterns coming from configuration
keep their original Ant-style semantics.
// See: Spring Security Reference - "Request Matchers"
section.
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]