This is an automated email from the ASF dual-hosted git repository.

Aias00 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git


The following commit(s) were added to refs/heads/master by this push:
     new e22a6d9be4 perf(web): reuse compiled CORS origin patterns (#7263)
e22a6d9be4 is described below

commit e22a6d9be4b324c1f79f7ef1c2136d715c65be1f
Author: Liming Deng <[email protected]>
AuthorDate: Sat Oct 17 20:48:32 2026 +0800

    perf(web): reuse compiled CORS origin patterns (#7263)
    
    * perf(web): reuse compiled CORS origin patterns
    
    * docs(cors): document and test invalid origin patterns at startup
---
 RELEASE-NOTES.md                                   |  4 +++
 .../org/apache/shenyu/web/filter/CrossFilter.java  | 16 +++++++++-
 .../apache/shenyu/web/filter/CrossFilterTest.java  | 36 ++++++++++++++++++++++
 3 files changed, 55 insertions(+), 1 deletion(-)

diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index 4ad0c71297..d79cb9ef3b 100644
--- a/RELEASE-NOTES.md
+++ b/RELEASE-NOTES.md
@@ -28,6 +28,10 @@
 
 ### Behavior Changes
 
+- CORS origin patterns are compiled when the gateway starts. An invalid
+  `shenyu.cross.allowed-origin.origin-regex` now fails startup with
+  `PatternSyntaxException`, rather than failing the first matching request.
+  Validate the expression before deploying a configuration change.
 - HTTP client registration now treats non-success Admin responses as failures,
   using the same bounded failback policy as transport errors (18 retry attempts
   per pending task). The text response contract does not reliably distinguish
diff --git 
a/shenyu-web/src/main/java/org/apache/shenyu/web/filter/CrossFilter.java 
b/shenyu-web/src/main/java/org/apache/shenyu/web/filter/CrossFilter.java
index 0aa4c6b074..1edec14fb3 100644
--- a/shenyu-web/src/main/java/org/apache/shenyu/web/filter/CrossFilter.java
+++ b/shenyu-web/src/main/java/org/apache/shenyu/web/filter/CrossFilter.java
@@ -50,8 +50,13 @@ public class CrossFilter implements WebFilter {
 
     private final CrossFilterConfig filterConfig;
 
+    private volatile Pattern originPattern;
+
     public CrossFilter(final CrossFilterConfig filterConfig) {
         this.filterConfig = filterConfig;
+        if (Objects.nonNull(filterConfig.getAllowedOrigin()) && 
StringUtils.isNotBlank(filterConfig.getAllowedOrigin().getOriginRegex())) {
+            originPattern = 
Pattern.compile(filterConfig.getAllowedOrigin().getOriginRegex().trim());
+        }
     }
 
     @Override
@@ -91,7 +96,7 @@ public class CrossFilter implements WebFilter {
                 // if the origin is not allow check match origin again
                 String originRegex;
                 if (!allowCors && StringUtils.isNotBlank(originRegex = 
this.filterConfig.getAllowedOrigin().getOriginRegex())) {
-                    allowCors = Pattern.matches(originRegex.trim(), origin);
+                    allowCors = 
getOriginPattern(originRegex.trim()).matcher(origin).matches();
                 }
             }
             if (allowCors) {
@@ -121,6 +126,15 @@ public class CrossFilter implements WebFilter {
         return chain.filter(exchange);
     }
 
+    private Pattern getOriginPattern(final String regex) {
+        Pattern pattern = originPattern;
+        if (Objects.isNull(pattern) || !pattern.pattern().equals(regex)) {
+            pattern = Pattern.compile(regex);
+            originPattern = pattern;
+        }
+        return pattern;
+    }
+
     /**
      * Filter the same headers.
      *
diff --git 
a/shenyu-web/src/test/java/org/apache/shenyu/web/filter/CrossFilterTest.java 
b/shenyu-web/src/test/java/org/apache/shenyu/web/filter/CrossFilterTest.java
index 68172c0b1d..79a74a15ca 100644
--- a/shenyu-web/src/test/java/org/apache/shenyu/web/filter/CrossFilterTest.java
+++ b/shenyu-web/src/test/java/org/apache/shenyu/web/filter/CrossFilterTest.java
@@ -25,6 +25,7 @@ import org.springframework.http.HttpStatus;
 import org.springframework.http.server.reactive.ServerHttpResponse;
 import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
 import org.springframework.mock.web.server.MockServerWebExchange;
+import org.springframework.test.util.ReflectionTestUtils;
 import org.springframework.web.server.ServerWebExchange;
 import org.springframework.web.server.WebFilterChain;
 import reactor.core.publisher.Mono;
@@ -200,4 +201,39 @@ public final class CrossFilterTest {
         Assertions.assertTrue(Pattern.matches(regex, "http://abc.com";));
         Assertions.assertFalse(Pattern.matches(regex, "http://aabc.com";));
     }
+
+    @Test
+    public void testInvalidInitialOriginPatternFailsDuringConstruction() {
+        CrossFilterConfig config = new CrossFilterConfig();
+        config.getAllowedOrigin().setOriginRegex("[");
+        Assertions.assertThrows(java.util.regex.PatternSyntaxException.class, 
() -> new CrossFilter(config));
+    }
+
+    @Test
+    void reusesCompiledPatternAndObservesConfigurationChanges() {
+        CrossFilterConfig config = new CrossFilterConfig();
+        config.getAllowedOrigin().setOriginRegex("  https://allowed[.]example  
");
+        CrossFilter filter = new CrossFilter(config);
+        Object pattern = ReflectionTestUtils.getField(filter, "originPattern");
+        Assertions.assertNotNull(pattern);
+        for (int i = 0; i < 3; i++) {
+            assertOrigin(filter, "https://allowed.example";, true);
+            assertOrigin(filter, "https://other.example";, false);
+            Assertions.assertSame(pattern, 
ReflectionTestUtils.getField(filter, "originPattern"));
+        }
+        config.getAllowedOrigin().setOriginRegex("https://other[.]example";);
+        assertOrigin(filter, "https://other.example";, true);
+        assertOrigin(filter, "https://allowed.example";, false);
+        Assertions.assertNotSame(pattern, ReflectionTestUtils.getField(filter, 
"originPattern"));
+        config.getAllowedOrigin().setOriginRegex(" ");
+        assertOrigin(filter, "https://other.example";, false);
+        config.setAllowedOrigin(null);
+        assertOrigin(filter, "https://other.example";, false);
+    }
+
+    private void assertOrigin(final CrossFilter filter, final String origin, 
final boolean allowed) {
+        ServerWebExchange exchange = 
MockServerWebExchange.from(MockServerHttpRequest.get("http://localhost";).header(HttpHeaders.ORIGIN,
 origin));
+        StepVerifier.create(filter.filter(exchange, ignored -> 
Mono.empty())).verifyComplete();
+        Assertions.assertEquals(allowed ? origin : null, 
exchange.getResponse().getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
+    }
 }

Reply via email to