patterns, boolean fallbackIfEmpty) {
+ return (patterns == null || patterns.isEmpty()) ? fallbackIfEmpty
+ : patterns.stream().anyMatch(pattern -> pattern.matcher(url).matches());
+ }
+}
diff --git a/gateway/src/main/java/org/georchestra/gateway/filter/global/RequestIdGlobalFilter.java b/gateway/src/main/java/org/georchestra/gateway/filter/global/RequestIdGlobalFilter.java
new file mode 100644
index 00000000..0ce186b8
--- /dev/null
+++ b/gateway/src/main/java/org/georchestra/gateway/filter/global/RequestIdGlobalFilter.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 by the geOrchestra PSC
+ *
+ * This file is part of geOrchestra.
+ *
+ * geOrchestra is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * geOrchestra is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * geOrchestra. If not, see .
+ */
+package org.georchestra.gateway.filter.global;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.core.Ordered;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.web.server.ServerWebExchange;
+
+import reactor.core.publisher.Mono;
+
+/**
+ * Makes sure both the request and response have the same
+ * {@literal X-Request-ID} header.
+ *
+ * A new value is created for the header if not provided by the client.
+ */
+public class RequestIdGlobalFilter implements GlobalFilter, Ordered {
+
+ static final String REQUEST_ID_HEADER = "X-Request-ID";
+
+ /**
+ * @return {@link Ordered#HIGHEST_PRECEDENCE}
+ */
+ public @Override int getOrder() {
+ return Ordered.HIGHEST_PRECEDENCE;
+ }
+
+ /**
+ * Makes sure both the request and response have the same
+ * {@literal X-Request-ID} header.
+ *
+ * A new value is created for the header if not provided by the client.
+ */
+ public @Override Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+
+ final String requestId;
+ final ServerHttpRequest request;
+ String providedRequestId = exchange.getRequest().getHeaders().getFirst(REQUEST_ID_HEADER);
+ if (null == providedRequestId) {
+ requestId = RandomStringUtils.randomNumeric(16);
+ request = exchange.getRequest().mutate().header(REQUEST_ID_HEADER, requestId).build();
+ exchange = exchange.mutate().request(request).build();
+ } else {
+ requestId = providedRequestId;
+ request = exchange.getRequest();
+ }
+
+ ServerHttpResponse response = exchange.getResponse();
+ response.beforeCommit(() -> {
+ response.getHeaders().set(REQUEST_ID_HEADER, requestId);
+ return Mono.empty();
+ });
+
+ return chain.filter(exchange);
+ }
+
+}
\ No newline at end of file
diff --git a/gateway/src/main/resources/application.yml b/gateway/src/main/resources/application.yml
index ead19e59..eeb5251e 100644
--- a/gateway/src/main/resources/application.yml
+++ b/gateway/src/main/resources/application.yml
@@ -148,12 +148,15 @@ management:
logging:
level:
root: warn
+ '[reactor.netty.http ]': warn
+ '[reactor.netty.http.server.logging.AccessLog]': warn
+ '[reactor.netty.http.client]': warn
'[org.springframework]': info
'[org.springframework.cloud.gateway]': info
'[org.springframework.security]': info
'[org.springframework.security.oauth2]': debug
- '[reactor.netty.http ]': debug
'[org.georchestra.gateway]': info
+ '[org.georchestra.gateway.accesslog]': info
'[org.georchestra.gateway.filter.headers]': debug
'[org.georchestra.gateway.config.security]': debug
'[org.georchestra.gateway.config.security.accessrules]': debug
diff --git a/gateway/src/main/resources/log4j2-spring.xml b/gateway/src/main/resources/log4j2-spring.xml
new file mode 100644
index 00000000..7cab4223
--- /dev/null
+++ b/gateway/src/main/resources/log4j2-spring.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/gateway/src/test/java/org/georchestra/gateway/filter/global/AccessLogFilterConfigTest.java b/gateway/src/test/java/org/georchestra/gateway/filter/global/AccessLogFilterConfigTest.java
new file mode 100644
index 00000000..060fc169
--- /dev/null
+++ b/gateway/src/test/java/org/georchestra/gateway/filter/global/AccessLogFilterConfigTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 by the geOrchestra PSC
+ *
+ * This file is part of geOrchestra.
+ *
+ * geOrchestra is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * geOrchestra is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * geOrchestra. If not, see .
+ */
+
+package org.georchestra.gateway.filter.global;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.net.URI;
+import java.util.regex.Pattern;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ */
+class AccessLogFilterConfigTest {
+
+ private AccessLogFilterConfig config;
+
+ @BeforeEach
+ void setUp() {
+ config = new AccessLogFilterConfig();
+ }
+
+ @Test
+ void testDisabled() {
+ config.setEnabled(false);
+ assertThat(config.matches(URI.create("https://my.host"))).isTrue();
+
+ config.getExclude().add(Pattern.compile(".*"));
+ assertThat(config.matches(URI.create("https://my.host"))).isTrue();
+ }
+
+ @Test
+ void testIncludes() {
+ config.getInclude().add(Pattern.compile(".*/ows/.*GetFeature.*"));
+ config.getInclude().add(Pattern.compile(".*/ows/.*GetMap.*"));
+
+ assertThat(config.matches(URI.create("https://my.host/ows/?request=GetFeature&typeName=test"))).isTrue();
+ assertThat(config.matches(URI.create("https://my.host/ows/?request=GetMap&typeName=test"))).isTrue();
+ assertThat(config.matches(URI.create("https://my.host/some/path/img1.svg"))).isFalse();
+ }
+
+ @Test
+ void testExcludes() {
+ config.getExclude().add(Pattern.compile(".*\\.png"));
+ config.getExclude().add(Pattern.compile(".*\\.jpeg"));
+
+ assertThat(config.matches(URI.create("https://my.host/some/path/img1.png"))).isFalse();
+ assertThat(config.matches(URI.create("https://my.host/some/path/img1.svg"))).isTrue();
+
+ assertThat(config.matches(URI.create("https://my.host/some/path/img2.jpeg"))).isFalse();
+ assertThat(config.matches(URI.create("https://my.host/some/path/img2.svg"))).isTrue();
+ }
+
+ @Test
+ void testIncludesAndExcludes() {
+ config.getInclude().add(Pattern.compile(".*/ows/.*"));
+ config.getExclude().add(Pattern.compile(".*/ows/.*GetMap.*"));
+
+ assertThat(config.matches(URI.create("https://my.host/ows/?request=GetFeature&typeName=test"))).isTrue();
+ assertThat(config.matches(URI.create("https://my.host/ows/?request=GetMap&typeName=test"))).isFalse();
+ }
+}